KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > socks > ProxyServer


1 package socks;
2 import socks.server.ServerAuthenticator;
3 import java.net.*;
4 import java.io.*;
5
6 /**
7     SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously.
8     Implements all SOCKS commands, including UDP relaying.
9     <p>
10     In order to use it you will need to implement ServerAuthenticator
11     interface. There is an implementation of this interface which does
12     no authentication ServerAuthenticatorNone, but it is very dangerous
13     to use, as it will give access to your local network to anybody
14     in the world. One should never use this authentication scheme unless
15     one have pretty good reason to do so.
16     There is a couple of other authentication schemes in socks.server package.
17     @see socks.server.ServerAuthenticator
18 */

19 public class ProxyServer implements Runnable JavaDoc{
20
21    ServerAuthenticator auth;
22    ProxyMessage msg = null;
23
24    Socket sock=null,remote_sock=null;
25    ServerSocket ss=null;
26    UDPRelayServer relayServer = null;
27    InputStream in,remote_in;
28    OutputStream out,remote_out;
29
30    int mode;
31    static final int START_MODE = 0;
32    static final int ACCEPT_MODE = 1;
33    static final int PIPE_MODE = 2;
34    static final int ABORT_MODE = 3;
35
36    static final int BUF_SIZE = 8192;
37
38    Thread JavaDoc pipe_thread1,pipe_thread2;
39    long lastReadTime;
40
41    static int iddleTimeout = 180000; //3 minutes
42
static int acceptTimeout = 180000; //3 minutes
43

44    static PrintStream log = null;
45    static Proxy proxy;
46
47
48 //Public Constructors
49
/////////////////////
50

51
52    /**
53     Creates a proxy server with given Authentication scheme.
54     @param auth Authentication scheme to be used.
55     */

56    public ProxyServer(ServerAuthenticator auth){
57      this.auth = auth;
58    }
59
60 //Other constructors
61
////////////////////
62

63    ProxyServer(ServerAuthenticator auth,Socket s){
64       this.auth = auth;
65       this.sock = s;
66       mode = START_MODE;
67    }
68
69 //Public methods
70
/////////////////
71

72    /**
73      Set the logging stream. Specifying null disables logging.
74    */

75    public static void setLog(OutputStream out){
76       if(out == null){
77         log = null;
78       }else{
79         log = new PrintStream(out,true);
80       }
81
82       UDPRelayServer.log = log;
83    }
84    
85    /**
86     Set proxy.
87     <p>
88     Allows Proxy chaining so that one Proxy server is connected to another
89     and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests
90     can be handled, UDP would not work, however CONNECT and BIND will be
91     translated.
92
93     @param p Proxy which should be used to handle user requests.
94    */

95    public static void setProxy(Proxy p){
96       proxy =p;
97       UDPRelayServer.proxy = proxy;
98    }
99
100    /**
101     Get proxy.
102     @return Proxy wich is used to handle user requests.
103    */

104    public static Proxy getProxy(){
105       return proxy;
106    }
107
108    /**
109     Sets the timeout for connections, how long shoud server wait
110     for data to arrive before dropping the connection.<br>
111     Zero timeout implies infinity.<br>
112     Default timeout is 3 minutes.
113    */

114    public static void setIddleTimeout(int timeout){
115       iddleTimeout = timeout;
116    }
117    /**
118     Sets the timeout for BIND command, how long the server should
119     wait for the incoming connection.<br>
120     Zero timeout implies infinity.<br>
121     Default timeout is 3 minutes.
122    */

123    public static void setAcceptTimeout(int timeout){
124       acceptTimeout = timeout;
125    }
126
127    /**
128     Sets the timeout for UDPRelay server.<br>
129     Zero timeout implies infinity.<br>
130     Default timeout is 3 minutes.
131    */

132    public static void setUDPTimeout(int timeout){
133       UDPRelayServer.setTimeout(timeout);
134    }
135
136    /**
137      Sets the size of the datagrams used in the UDPRelayServer.<br>
138      Default size is 64K, a bit more than maximum possible size of the
139      datagram.
140     */

141    public static void setDatagramSize(int size){
142       UDPRelayServer.setDatagramSize(size);
143    }
144
145
146    /**
147      Start the Proxy server at given port.<br>
148      This methods blocks.
149     */

150    public void start(int port){
151       start(port,5,null);
152    }
153
154    /**
155      Create a server with the specified port, listen backlog, and local
156      IP address to bind to. The localIP argument can be used on a multi-homed
157      host for a ServerSocket that will only accept connect requests to one of
158      its addresses. If localIP is null, it will default accepting connections
159      on any/all local addresses. The port must be between 0 and 65535,
160      inclusive. <br>
161      This methods blocks.
162     */

163    public void start(int port,int backlog,InetAddress localIP){
164       try{
165         ss = new ServerSocket(port,backlog,localIP);
166         log("Starting SOCKS Proxy on:"+ss.getInetAddress().getHostAddress()+":"
167                                       +ss.getLocalPort());
168         while(true){
169           Socket s = ss.accept();
170           log("Accepted from:"+s.getInetAddress().getHostName()+":"
171                               +s.getPort());
172           ProxyServer ps = new ProxyServer(auth,s);
173           (new Thread JavaDoc(ps)).start();
174         }
175       }catch(IOException ioe){
176         ioe.printStackTrace();
177       }finally{
178       }
179    }
180
181    /**
182      Stop server operation.It would be wise to interrupt thread running the
183      server afterwards.
184     */

185    public void stop(){
186      try{
187        if(ss != null) ss.close();
188      }catch(IOException ioe){
189      }
190    }
191
192 //Runnable interface
193
////////////////////
194
public void run(){
195       switch(mode){
196         case START_MODE:
197          try{
198            startSession();
199          }catch(IOException ioe){
200            handleException(ioe);
201            //ioe.printStackTrace();
202
}finally{
203            abort();
204            if(auth!=null) auth.endSession();
205            log("Main thread(client->remote)stopped.");
206          }
207         break;
208         case ACCEPT_MODE:
209           try{
210             doAccept();
211             mode = PIPE_MODE;
212             pipe_thread1.interrupt(); //Tell other thread that connection have
213
//been accepted.
214
pipe(remote_in,out);
215           }catch(IOException ioe){
216             //log("Accept exception:"+ioe);
217
handleException(ioe);
218           }finally{
219             abort();
220             log("Accept thread(remote->client) stopped");
221           }
222         break;
223         case PIPE_MODE:
224          try{
225             pipe(remote_in,out);
226          }catch(IOException ioe){
227          }finally{
228             abort();
229             log("Support thread(remote->client) stopped");
230          }
231         break;
232         case ABORT_MODE:
233         break;
234         default:
235          log("Unexpected MODE "+mode);
236       }
237    }
238
239 //Private methods
240
/////////////////
241
private void startSession() throws IOException{
242      sock.setSoTimeout(iddleTimeout);
243
244      try{
245         auth = auth.startSession(sock);
246      }catch(IOException ioe){
247        log("Auth throwed exception:"+ioe);
248        auth = null;
249        return;
250      }
251
252      if(auth == null){ //Authentication failed
253
log("Authentication failed");
254         return;
255      }
256
257      in = auth.getInputStream();
258      out = auth.getOutputStream();
259
260      msg = readMsg(in);
261      handleRequest(msg);
262    }
263    
264    private void handleRequest(ProxyMessage msg)
265                 throws IOException{
266       if(!auth.checkRequest(msg)) throw new
267                                   SocksException(Proxy.SOCKS_FAILURE);
268
269       if(msg.ip == null){
270         if(msg instanceof Socks5Message){
271           msg.ip = InetAddress.getByName(msg.host);
272         }else
273           throw new SocksException(Proxy.SOCKS_FAILURE);
274       }
275       log(msg);
276
277       switch(msg.command){
278         case Proxy.SOCKS_CMD_CONNECT:
279           onConnect(msg);
280         break;
281         case Proxy.SOCKS_CMD_BIND:
282           onBind(msg);
283         break;
284         case Proxy.SOCKS_CMD_UDP_ASSOCIATE:
285           onUDP(msg);
286         break;
287         default:
288           throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED);
289       }
290    }
291
292    private void handleException(IOException ioe){
293       //If we couldn't read the request, return;
294
if(msg == null) return;
295       //If have been aborted by other thread
296
if(mode == ABORT_MODE) return;
297       //If the request was successfully completed, but exception happened later
298
if(mode == PIPE_MODE) return;
299
300       int error_code = Proxy.SOCKS_FAILURE;
301
302       if(ioe instanceof SocksException)
303           error_code = ((SocksException)ioe).errCode;
304       else if(ioe instanceof NoRouteToHostException)
305           error_code = Proxy.SOCKS_HOST_UNREACHABLE;
306       else if(ioe instanceof ConnectException)
307           error_code = Proxy.SOCKS_CONNECTION_REFUSED;
308       else if(ioe instanceof InterruptedIOException)
309           error_code = Proxy.SOCKS_TTL_EXPIRE;
310
311       if(error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0){
312           error_code = Proxy.SOCKS_FAILURE;
313       }
314
315       sendErrorMessage(error_code);
316    }
317
318    private void onConnect(ProxyMessage msg) throws IOException{
319       Socket s;
320       ProxyMessage response = null;
321
322       if(proxy == null)
323          s = new Socket(msg.ip,msg.port);
324       else
325          s = new SocksSocket(proxy,msg.ip,msg.port);
326
327       log("Connected to "+s.getInetAddress()+":"+s.getPort());
328
329       if(msg instanceof Socks5Message){
330         response = new Socks5Message(Proxy.SOCKS_SUCCESS,
331                                          s.getLocalAddress(),
332                                          s.getLocalPort());
333       }else{
334         response = new Socks4Message(Socks4Message.REPLY_OK,
335                                      s.getLocalAddress(),s.getLocalPort());
336
337       }
338       response.write(out);
339       startPipe(s);
340    }
341
342    private void onBind(ProxyMessage msg) throws IOException{
343       ProxyMessage response = null;
344
345       if(proxy == null)
346         ss = new ServerSocket(0);
347       else
348         ss = new SocksServerSocket(proxy, msg.ip, msg.port);
349
350       ss.setSoTimeout(acceptTimeout);
351
352       log("Trying accept on "+ss.getInetAddress()+":"+ss.getLocalPort());
353
354       if(msg.version == 5)
355          response = new Socks5Message(Proxy.SOCKS_SUCCESS,ss.getInetAddress(),
356                                                           ss.getLocalPort());
357       else
358          response = new Socks4Message(Socks4Message.REPLY_OK,
359                                       ss.getInetAddress(),
360                                       ss.getLocalPort());
361       response.write(out);
362
363       mode = ACCEPT_MODE;
364
365       pipe_thread1 = Thread.currentThread();
366       pipe_thread2 = new Thread JavaDoc(this);
367       pipe_thread2.start();
368
369       //Make timeout infinit.
370
sock.setSoTimeout(0);
371       int eof=0;
372
373       try{
374         while((eof=in.read())>=0){
375           if(mode != ACCEPT_MODE){
376             if(mode != PIPE_MODE) return;//Accept failed
377

378             remote_out.write(eof);
379             break;
380           }
381         }
382       }catch(EOFException eofe){
383         //System.out.println("EOF exception");
384
return;//Connection closed while we were trying to accept.
385
}catch(InterruptedIOException iioe){
386         //Accept thread interrupted us.
387
//System.out.println("Interrupted");
388
if(mode != PIPE_MODE)
389           return;//If accept thread was not successfull return.
390
}finally{
391         //System.out.println("Finnaly!");
392
}
393
394       if(eof < 0)//Connection closed while we were trying to accept;
395
return;
396       
397       //Do not restore timeout, instead timeout is set on the
398
//remote socket. It does not make any difference.
399

400       pipe(in,remote_out);
401    }
402
403    private void onUDP(ProxyMessage msg) throws IOException{
404       if(msg.ip.getHostAddress().equals("0.0.0.0"))
405          msg.ip = sock.getInetAddress();
406       log("Creating UDP relay server for "+msg.ip+":"+msg.port);
407       relayServer = new UDPRelayServer(msg.ip,msg.port,
408                         Thread.currentThread(),sock,auth);
409
410       ProxyMessage response;
411
412       response = new Socks5Message(Proxy.SOCKS_SUCCESS,
413                                    relayServer.relayIP,relayServer.relayPort);
414
415       response.write(out);
416
417       relayServer.start();
418
419       //Make timeout infinit.
420
sock.setSoTimeout(0);
421       try{
422         while(in.read()>=0) /*do nothing*/;
423       }catch(EOFException eofe){
424       }
425    }
426
427 //Private methods
428
//////////////////
429

430    private void doAccept() throws IOException{
431       Socket s;
432       long startTime = System.currentTimeMillis();
433
434       while(true){
435          s = ss.accept();
436          if(s.getInetAddress().equals(msg.ip)){
437             //got the connection from the right host
438
//Close listenning socket.
439
ss.close();
440             break;
441          }else if(ss instanceof SocksServerSocket){
442             //We can't accept more then one connection
443
s.close();
444             ss.close();
445             throw new SocksException(Proxy.SOCKS_FAILURE);
446          }else{
447             if(acceptTimeout!=0){ //If timeout is not infinit
448
int newTimeout = acceptTimeout-(int)(System.currentTimeMillis()-
449                                                            startTime);
450                if(newTimeout <= 0) throw new InterruptedIOException(
451                                 "In doAccept()");
452                ss.setSoTimeout(newTimeout);
453             }
454             s.close(); //Drop all connections from other hosts
455
}
456       }
457
458       //Accepted connection
459
remote_sock = s;
460       remote_in = s.getInputStream();
461       remote_out = s.getOutputStream();
462
463       //Set timeout
464
remote_sock.setSoTimeout(iddleTimeout);
465
466       log("Accepted from "+s.getInetAddress()+":"+s.getPort());
467
468       ProxyMessage response;
469
470       if(msg.version == 5)
471          response = new Socks5Message(Proxy.SOCKS_SUCCESS, s.getInetAddress(),
472                                                            s.getPort());
473       else
474          response = new Socks4Message(Socks4Message.REPLY_OK,
475                                       s.getInetAddress(), s.getPort());
476       response.write(out);
477    }
478
479    private ProxyMessage readMsg(InputStream in) throws IOException{
480       PushbackInputStream push_in;
481       if(in instanceof PushbackInputStream)
482          push_in = (PushbackInputStream) in;
483       else
484          push_in = new PushbackInputStream(in);
485
486       int version = push_in.read();
487       push_in.unread(version);
488
489
490       ProxyMessage msg;
491
492       if(version == 5){
493         msg = new Socks5Message(push_in,false);
494       }else if(version == 4){
495         msg = new Socks4Message(push_in,false);
496       }else{
497         throw new SocksException(Proxy.SOCKS_FAILURE);
498       }
499       return msg;
500    }
501
502    private void startPipe(Socket s){
503       mode = PIPE_MODE;
504       remote_sock = s;
505       try{
506          remote_in = s.getInputStream();
507          remote_out = s.getOutputStream();
508          pipe_thread1 = Thread.currentThread();
509          pipe_thread2 = new Thread JavaDoc(this);
510          pipe_thread2.start();
511          pipe(in,remote_out);
512       }catch(IOException ioe){
513       }
514    }
515
516    private void sendErrorMessage(int error_code){
517       ProxyMessage err_msg;
518       if(msg instanceof Socks4Message)
519          err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
520       else
521          err_msg = new Socks5Message(error_code);
522       try{
523          err_msg.write(out);
524       }catch(IOException ioe){}
525    }
526
527    private synchronized void abort(){
528       if(mode == ABORT_MODE) return;
529       mode = ABORT_MODE;
530       try{
531          log("Aborting operation");
532          if(remote_sock != null) remote_sock.close();
533          if(sock != null) sock.close();
534          if(relayServer!=null) relayServer.stop();
535          if(ss!=null) ss.close();
536          if(pipe_thread1 != null) pipe_thread1.interrupt();
537          if(pipe_thread2 != null) pipe_thread2.interrupt();
538       }catch(IOException ioe){}
539    }
540
541    static final void log(String JavaDoc s){
542      if(log != null){
543        log.println(s);
544        log.flush();
545      }
546    }
547
548    static final void log(ProxyMessage msg){
549       log("Request version:"+msg.version+
550           "\tCommand: "+command2String(msg.command));
551       log("IP:"+msg.ip +"\tPort:"+msg.port+
552          (msg.version==4?"\tUser:"+msg.user:""));
553    }
554
555    private void pipe(InputStream in,OutputStream out) throws IOException{
556       lastReadTime = System.currentTimeMillis();
557       byte[] buf = new byte[BUF_SIZE];
558       int len = 0;
559       while(len >= 0){
560          try{
561            if(len!=0){
562              out.write(buf,0,len);
563              out.flush();
564            }
565            len= in.read(buf);
566            lastReadTime = System.currentTimeMillis();
567          }catch(InterruptedIOException iioe){
568            if(iddleTimeout == 0) return;//Other thread interrupted us.
569
long timeSinceRead = System.currentTimeMillis() - lastReadTime;
570            if(timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment.
571
return;
572            len = 0;
573
574          }
575       }
576    }
577    static final String JavaDoc command_names[] = {"CONNECT","BIND","UDP_ASSOCIATE"};
578
579    static final String JavaDoc command2String(int cmd){
580       if(cmd > 0 && cmd < 4) return command_names[cmd-1];
581       else return "Unknown Command "+cmd;
582    }
583 }
584
Popular Tags