/*********** PipeProxy.java Piping proxy that can be used to relay socket-based TCP network traffic to another computer or to debug a socket connection Written by Gabor Paller (paller@evt.bme.hu) Usage: java PipeProxy [] The proxy will accept network connections on if specified or if is not specified and when a connection is opened, will open a socket to : and will relay everything to and from this location. The connection is broken either if the client breaks it or if the server on the original host breaks it. The proxy never breaks the connection Copyright: PipeProxy is freeware, use it for anything you like. If you make serious enhancement, consider sending it back to the author so that others can enjoy the enhancements too. Version log: 1.0.0 20000502 Gabor Paller Original functionality 1.0.1 20000509 Laszlo Szenttornyai local port support and dump modes added 1.0.2 20000712 Gabor Paller MIX dump mode made more readable 1.0.3 20180123 Gabor Paller Python dump mode added *****************/ import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Enumeration; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.Vector; // This interface is implemented by classes that has separate controlling // thread so that they can be called back by the monitor thread interface CallBack { void callback( int funccode, long lparam, Object parm ); } class Dumper { StringBuffer ascdump = null; public static final int NUM = 1; public static final int ASC = 2; public static final int MIX = 3; public static final int PYTHON = 4; static final int numWidth = 12; static final int ascWidth = 36; static final int mixWidth = 7; static final int pythonWidth = 5; static final String numPad; static final String ascPad; static final String mixPad; static final String pythonPad; static { StringBuffer fill = new StringBuffer(); int i,n; n = numWidth*3; for( i=0; i 0 || CtrStoC > 0 ) { if( ( mode == MIX ) && ( ascdump != null ) ) { System.out.print( " : "+ascdump ); ascdump = null; } System.out.println(); } CtrCtoS = 0; CtrStoC = 0; if( s != null ) System.out.println( s ); } public synchronized void printMode() { if ( mode == MIX ) println( "mixed" ); else if ( mode == NUM ) println( "numeric" ); else if( mode == ASC ) println( "ascii" ); else println( "python" ); } public synchronized void changeMode() { if ( mode == MIX ) setMode( NUM ); else if ( mode == NUM ) setMode( ASC ); else if( mode == ASC ) setMode( PYTHON ); else setMode( MIX ); } public synchronized void setMode( int textMode ) { mode = textMode; switch( mode ) { case NUM: width = numWidth; pad = numPad; break; case ASC: width = ascWidth; pad = ascPad; break; case MIX: width = mixWidth; pad = mixPad; break; case PYTHON: width = pythonWidth; pad = pythonPad; } } public synchronized void dumpByte( int ConnID, boolean CtoS, int c ) { if( CtoS != LastCtoS || ConnID != LastConnID ) { LastCtoS = CtoS; LastConnID = ConnID; if( mode == PYTHON ) { System.out.println(); if( !CtoS ) System.out.print( pythonPad ); } else newline( null ); } StringBuffer byt = new StringBuffer(); if( mode == PYTHON ) { byt.append( "\\x"+fixLengthNumber( Integer.toString( c,16 ), 2 ) ); System.out.print( byt ); } else { if ( mode == ASC ) { if ( c >= 0x20 && c < 0x80 ) byt.append( (char)c ); else byt.append( '.' ); } else { byt.append( fixLengthNumber( Integer.toString( c,16 ), 2 ) ); if ( mode == MIX ) if( ascdump == null ) ascdump = new StringBuffer(); if( c >= 0x20 && c < 0x80 ) ascdump.append( (char)c ); else ascdump.append( '.' ); } if( ( !CtoS && CtrStoC == 0 ) || ( CtoS && CtrCtoS == 0 ) ) System.out.print( fixLengthNumber( Integer.toString( ConnID ),5 )+" " ); if( !CtoS && ( CtrStoC == 0 ) ) System.out.print( pad ); System.out.print( byt ); if ( mode != ASC ) System.out.print( ' ' ); if( CtoS ) ++CtrCtoS; else ++CtrStoC; if( CtrCtoS >= width || CtrStoC >= width ) newline( null ); } } public synchronized void println( String s ) { newline( s ); } } class Piper extends Thread { CallBack parent; InputStream IS; OutputStream OS; boolean CtoS; int ConnID; Dumper d; public Piper( Dumper d, boolean CtoS, int ConnID, CallBack parent, InputStream IS, OutputStream OS ) { this.d = d; this.parent = parent; this.IS = IS; this.OS = OS; this.CtoS = CtoS; this.ConnID = ConnID; } public void run() { try { while( true ) { int ch = IS.read(); if( ch < 0 ) break; OS.write( ch ); d.dumpByte( ConnID, CtoS,ch ); } } catch( IOException ex ) { d.println( "IOException in Piper: "+ex.getMessage() ); } parent.callback( ServerConnection.PIPE_BROKEN, 0L, null ); } } class ServerConnection extends Thread implements CallBack { public static final int PIPE_BROKEN = 0; public Socket ClientSocket, TargetSocket; public long ConnNumber; InputStream CIS,SIS; OutputStream COS,SOS; CallBack Parent; String TargetHost; int TargetPort; Piper CtoS,StoC; int PortID; Dumper d; public ServerConnection( Dumper d,Socket s, long cn, CallBack parent, String TargetHost, int TargetPort ) { setDaemon( true ); // PipeProxyBody is the only // not daemon thread in this system this.d = d; ClientSocket = s; ConnNumber = cn; Parent = parent; this.TargetHost = TargetHost; this.TargetPort = TargetPort; } public synchronized void run() { try { PortID = ClientSocket.getPort(); d.println( "New connection on port " + PortID ); CIS = ClientSocket.getInputStream(); COS = ClientSocket.getOutputStream(); TargetSocket = new Socket( TargetHost,TargetPort ); SIS = TargetSocket.getInputStream(); SOS = TargetSocket.getOutputStream(); } catch( Exception ex ) { d.println( "Exception while setting up pipe: "+ex.getMessage() ); } CtoS = new Piper( d,true, PortID, this, CIS, SOS ); StoC = new Piper( d,false,PortID, this, SIS, COS ); CtoS.start(); StoC.start(); try { wait(); // Wait for Piper threads to die } catch( InterruptedException ex ) {} CtoS.stop(); StoC.stop(); try { ClientSocket.close(); TargetSocket.close(); } catch( IOException ex ) {} Parent.callback( PipeProxyBody.REMOVE_CONNECTION, 0L, (Object)this ); d.println( "Connection died on "+PortID ); } public synchronized void callback( int funccode, long lparam, Object parm ) { switch( funccode ) { case PIPE_BROKEN: notify(); // Pipe broken, release the main thread to close the connection break; } } } // A thread for handling user input class TerminalHandler extends Thread { CallBack Target; Dumper d; public TerminalHandler( Dumper d, CallBack target ) { setDaemon( true ); // So that PipeProxyBody can die peacefully ... Target = target; this.d = d; } public void run() { while( true ) { try { while( System.in.available() > 0 ) { switch( System.in.read() ) { case 'm': case 'M': d.changeMode(); d.printMode(); break; case 'h': // help case 'H': d.println( "c - active connection list" ); d.println( "m - change dump format" ); d.println( "q - terminate server" ); d.println( "h - this page" ); break; case 'c': case 'C': // Connection list Target.callback( PipeProxyBody.LIST_CONNECTIONS,0L,null ); break; case 'q': // quit case 'Q': d.println( "PipeProxy server terminated" ); Target.callback( PipeProxyBody.EXIT_PROGRAM,0L,null ); // Terminate return; } // switch } // // Let's wait a bit to avoid CPU load try { sleep( 1000L ); } catch( InterruptedException ex2 ) {}; } catch( IOException ex1 ) {}; } // while( true ) } // run } // GrafittiServerBody class contains all the high-level server code class PipeProxyBody extends Thread implements CallBack { public static final int EXIT_PROGRAM = 0; public static final int LIST_CONNECTIONS = 1; public static final int REMOVE_CONNECTION = 2; ServerSocket SS; String Host; int targetPort; int sourcePort; Vector ActiveConnections = new Vector( 8,8 ); long ConnCounter = 0L; Dumper d; // Scan command line arguments. Returns true if error occured public boolean ProcessArgs( String args[] ) { if ( args.length > 0 ) { Host = args[0]; targetPort = 80; if ( args.length > 1 ) { try { targetPort = Integer.parseInt( args[1] ); } catch( NumberFormatException ex ) { } } sourcePort = targetPort; if ( args.length > 2 ) { try { sourcePort = Integer.parseInt( args[2] ); } catch( NumberFormatException ex ) { // stay with default, same source as target port } } return true; } System.out.println( "Usage: java PipeProxy [localport]" ); return false; } // public void ProcessArgs() public void run() { Socket NewSocket; System.out.println( "PipeProxy V1.0.2 (20000712)" ); System.out.println( "Piping to "+Host+":"+targetPort ); if ( sourcePort != targetPort ) System.out.println( "from port:"+sourcePort ); // Opening server socket try { SS = new ServerSocket( sourcePort ); } catch( IOException ex1 ) { System.out.println( "Create ServerSocket: " + ex1.getMessage() ); return; } System.out.println( "Type h for available commands" ); System.out.println( "Waiting for connection requests ..." ); d = new Dumper(); new TerminalHandler( d,this ).start(); while( true ) { try { NewSocket = SS.accept(); } catch( IOException ex2 ) { // This exception can occur normally as well. When the application // is closed, GrafittiServerBody is stopped. That will mark SS for // garbage collection which in turn throws I/O Exception to waiting // accept. That's how we can get here ... try { SS.close(); } catch( IOException ex20 ) { System.out.println( "close: " + ex20.getMessage() ); } return; } // catch( IOException ex2 ) ServerConnection SC = new ServerConnection( d,NewSocket, ConnCounter++, this, Host, targetPort ); ActiveConnections.addElement( SC ); SC.start(); // Start connection as new thread } // while( true ) } // public void run() public synchronized void callback( int funccode, long lparam, Object parm ) { ServerConnection SC; String s; switch( funccode ) { case EXIT_PROGRAM: System.exit( 0 ); break; // We can never get here ... case LIST_CONNECTIONS: // Called by TerminalHandler, connection list if( ActiveConnections.size() == 0 ) d.println( "No active connections" ); else for( Enumeration e = ActiveConnections.elements() ; e.hasMoreElements() ; ) { SC = (ServerConnection)e.nextElement(); d.println( "Connection " + SC.ConnNumber + " : port " + SC.ClientSocket.getPort() + " : " + SC.ClientSocket.getInetAddress().getHostName() ); } break; case REMOVE_CONNECTION: // Called by ServerConnection, connection death ActiveConnections.removeElement( parm ); break; } // switch } // public void callback() } // GrafittiServer starter class. The relevant code is in GrafittiServerBody. // It was moved there because the static property of run() caused endless // problems public class PipeProxy { public static void main( String args[] ) { PipeProxyBody GS = new PipeProxyBody(); if( GS.ProcessArgs( args ) ) GS.start(); } // public void main( ... }