import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSniffer {

  public static final int NOBODY_LAST = 0, SERVER_LAST_1 = 1, CLIENT_LAST = 2;
  public static int whoLast;
  public static boolean markSender;
  public static String clientPrefix, serverPrefix;

  public static void main(String args[]) throws Exception {
    whoLast = NOBODY_LAST;
    markSender = true; // make false to suppress client/server markings "client $ " and "server $ "

    if (markSender) {
      clientPrefix = "client $ ";
      serverPrefix = "server $";
    } else {
      clientPrefix = "";
      serverPrefix = "";
    }

    String hostname = "";
    int portToHost = 0;
    int portForClients = 0;
    try {
      hostname = args[0];
      portToHost = Integer.parseInt(args[1]);
      portForClients = Integer.parseInt(args[2]);
    } catch (Exception e) {
      System.err.println(
        "usage: java ServerSniffer hostname port-to-host port-for-clients"
      );
      System.exit(-1);
    }

    int numberOfConnections = 0;
    ServerSocket serverSocket = new ServerSocket(portForClients);
    while (true) {
      Socket incomingSocketFromClient = serverSocket.accept();
      Socket outgoingSocketToHost = new Socket(hostname, portToHost);
      numberOfConnections++;
      ClientToHostForwarder c2h = new ClientToHostForwarder(
        numberOfConnections,
        outgoingSocketToHost,
        incomingSocketFromClient
      );
      HostToClientForwarder h2c = new HostToClientForwarder(
        numberOfConnections,
        outgoingSocketToHost,
        incomingSocketFromClient
      );
      c2h.twin = h2c;
      h2c.twin = c2h;
      c2h.start();
      h2c.start();
    }
  }
}

class ClientToHostForwarder extends Thread {

  Socket socketWithHost;
  Socket socketWithClient;
  HostToClientForwarder twin;
  boolean running;
  int id;

  public ClientToHostForwarder(
    int id,
    Socket socketWithHost,
    Socket socketWithClient
  ) {
    this.id = id;
    this.socketWithHost = socketWithHost;
    this.socketWithClient = socketWithClient;
  }

  public void run() {
    try {
      // System.err.println("client-to-host thread " + id + " started");
      int b;
      running = true;
      while (running && (b = socketWithClient.getInputStream().read()) != -1) {
        //System.out.print("client to host:");
        System.out.write(b);
        //System.out.println("");
        System.out.flush();
        socketWithHost.getOutputStream().write(b);
        socketWithHost.getOutputStream().flush();
      }
      System.err.println(
        "\nconnection " + id + " from client to Host has been closed"
      );
      socketWithHost.close();
      twin.running = false;
    } catch (IOException e) {
      System.err.println("\nconnection " + id + " has error");
      System.err.println(e);
      System.err.println("This ClientToHostForwarder will now end");
    }
  }
}

class HostToClientForwarder extends Thread {

  Socket socketWithHost;
  Socket socketWithClient;
  ClientToHostForwarder twin;
  int id;
  boolean running;

  public HostToClientForwarder(
    int id,
    Socket socketWithHost,
    Socket socketWithClient
  ) {
    this.id = id;
    this.socketWithHost = socketWithHost;
    this.socketWithClient = socketWithClient;
  }

  public void run() {
    try {
      // System.err.println("host-to-client thread " + id + " started");
      int b;
      running = true;
      while (running && (b = socketWithHost.getInputStream().read()) != -1) {
        //System.out.print("host to client:");
        System.out.write(b);
        //System.out.println();
        System.out.flush();
        socketWithClient.getOutputStream().write(b);
        socketWithClient.getOutputStream().flush();
      }
      System.err.println(
        "\nconnection " + id + " from Host to client has been closed"
      );
      socketWithClient.close();
      twin.running = false;
    } catch (IOException e) {
      System.err.println("\nconnection " + id + " has error");
      System.err.println(e + "\n");
    }
  }
}
