package net.sourceforge.jsocks.test; import java.io.*; import java.net.*; /** Server to used perform tests for SOCKS library. */ public class TestService implements Runnable{ static final String chargenSequence = " !\"#$%&'()*+,-./0123456789:;<=>?@"+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefg"; static final String serviceNames[] = {"echo","discard","chargen","connect"}; static final int servicePorts[] = {5678,5679,5680,5681}; static final int ECHO = 0; static final int DISCARD = 1; static final int CHARGEN = 2; static final int CONNECT = 3; static final int BUF_SIZE = 1024; static final int CHARGEN_WAIT = 1000; //1 second static final int MAX_WAIT = 60000; //1 minute static PrintStream log = null; //Class constants Socket s; int service; /** Creates new TestService object, which will perform particular service on given socket. @param s Socket on which to perform service. @param service Service which to provide. */ public TestService(Socket s, int service){ this.s = s; this.service = service; } /** Default constructor. */ public TestService(){ this.s = null; this.service = -1; } public void run(){ try{ serve(s,service); }catch(IOException ioe){ log("Exception:"+ioe); ioe.printStackTrace(); } try{ s.close();}catch(IOException ioe){} } //Static functions ///////////////// /** Maps service name to the integer id, does it in simple linear search manner. @param serviceName Name of the service whose id one needs. @return Integer identifier for this servuce, or -1, if service can't be found. */ static public int getServiceId(String serviceName){ serviceName = serviceName.toLowerCase(); for(int i = 0;i < serviceNames.length;++i) if(serviceName.equals(serviceNames[i])) return i; //Couldn't find one. return -1; } /** Performs given service on given socket. <p> Simply looks up and calls associated method. @param s Socket on which to perform service. @param service Id of the service to perform. @return true if service have been found, false otherwise. */ static public boolean serve(Socket s, int service) throws IOException{ switch(service){ case ECHO: echo(s); break; case DISCARD: discard(s); break; case CHARGEN: chargen(s,CHARGEN_WAIT,MAX_WAIT); break; case CONNECT: connect(s); break; default: log("Unknown service:"+service); return false; } return true; } /** Echos any input on the socket to the output. Echo is being done line by line. @param s Socket on which to perform service. */ static public void echo(Socket s) throws IOException{ BufferedReader in = new BufferedReader(new InputStreamReader( s.getInputStream())); OutputStream out = s.getOutputStream(); log("Starting \"echo\" on "+s); String line = in.readLine(); while(line != null){ out.write((line+"\n").getBytes()); log(line); line = in.readLine(); } log("Echo done."); } /** Reads input from the socket, and does not write anything back. logs input in line by line fashion. @param s Socket on which to perform service. */ static public void discard(Socket s) throws IOException{ BufferedReader in = new BufferedReader(new InputStreamReader( s.getInputStream())); log("Starting discard on "+s); String line = in.readLine(); while(line != null){ log(line); line = in.readLine(); } log("Discard finished."); } /** Generates characters and sends them to the socket. <p> Unlike usual chargen (port 19), each next line of the generated output is send after increasingly larger time intervals. It starts from wait_time (ms), and each next time wait time is doubled. Eg. 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 ... well you got the idea. <p> It starts if either connection is clsoed or the wait time grows bigger than max_wait. @param s Socket on which to perform service. @param wait_time Time in ms, from which timing sequence should begin. @param max_wait Time in ms, after reaching timeout greater than this value, chargen will stop. */ static public void chargen(Socket s,long wait_time,long max_wait) throws IOException{ byte[] buf = chargenSequence.getBytes(); int pos = 0; OutputStream out = s.getOutputStream(); InputStream in = s.getInputStream(); s.setSoTimeout(100); //0.1 ms log("Starting \"chargen\" on "+s); while(true){ log("Sending message."); out.write(buf,pos,buf.length - pos); out.write(buf,0,pos); out.write("\n".getBytes()); pos++; try{ if(wait_time > max_wait) break; log("Going to sleep for "+wait_time+" ms."); Thread.currentThread().sleep(wait_time); wait_time *= 2; if(in.read() < 0) break; //Connection closed }catch(InterruptedException ie){ }catch(InterruptedIOException ioe){ } } log("Chargen finished."); } /** Models connect back situation. <p> Reads a line from the socket connection, line should be in the form port service_id. Connects back to the remote host to port specified in the line, if successful performs a service speciefied by id on that new connection. If accept was successful closes the control connection, else outputs error message, and then closes the connection. @param s Control connection. */ static public void connect(Socket s)throws IOException{ String line = null; Socket sock; int port; int service_id; BufferedReader in = new BufferedReader(new InputStreamReader( s.getInputStream())); OutputStream out = s.getOutputStream(); log("Starting \"connect\" on "+s); line = in.readLine(); if(line == null) return; //They closed connection java.util.StringTokenizer st = new java.util.StringTokenizer(line); if(st.countTokens() < 2){ //We need at least 'port' and "id" out.write("Expect: port serviceId.\n".getBytes()); log("Invalid arguments."); return; } try{ port = Integer.parseInt(st.nextToken()); service_id = Integer.parseInt(st.nextToken()); }catch(NumberFormatException nfe){ out.write("Expect: port serviceId.\n".getBytes()); log("Invalid arguments."); return; } try{ log("Connecting to "+s.getInetAddress()+":"+port); sock = new Socket(s.getInetAddress(),port); }catch(IOException ioe){ out.write(("Connect to "+s.getInetAddress()+ ":"+port+" failed").getBytes()); log("Connect failed."); return; } s.close(); log("About to serve "+service_id); serve(sock,service_id); } /** Pipes data from the input stream to the output. @param in Input stream. @param out Output stream. */ static public void pipe(InputStream in, OutputStream out) throws IOException{ byte[] buf = new byte[BUF_SIZE]; int bread = 0; while(bread >= 0){ bread = in.read(buf); out.write(buf,0,bread); } } /** Performs logging. */ static synchronized void log(String s){ if(log != null) log.println(s); } }