Writing software with the functionality of Windows client-server utilities, part 01

Welcome.

Today I would like to analyze the process of writing client-server applications that perform the functions of standard Windows utilities, such as Telnet, TFTP, et cetera, et cetera in pure Java. It is clear that I will not bring anything new - all these utilities have been successfully working for more than one year, but, I believe, not everyone knows what is happening under the hood.

This is what will be discussed under the cut.

In this article, in order not to drag it out, in addition to general information, I will write only about the Telnet server, but at the moment there is still material on other utilities - it will be in further parts of the cycle.

First of all, you should figure out what Telnet is, what it is for and what it is eaten with. I will not quote sources verbatim (if necessary, I will attach a link to materials on the topic at the end of the article), I will only say that Telnet provides remote access to the device's command line. By and large, this is where its functionality ends (I consciously kept silent about accessing the server port, more on that later). So, to implement it, we need to receive a string on the client, pass it to the server, try to pass it to the command line, read the command line response, if any, send it back to the client and display it on the screen, or, in case of errors, let the user know that something is wrong.

To implement the above, respectively, you need 2 working classes, and some test class, from which we will start the server and through which the client will work.
Accordingly, at the moment the structure of the application includes:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Let's run through each of them:

TelnetClient

All this class should be able to do is send the received commands and display the received responses. In addition, you need to be able to connect to an arbitrary (as mentioned above) port of a remote device and disconnect from it.

For this, the following functions have been implemented:

A function that takes a socket address as an argument, opens a connection and starts input and output streams (stream variables are declared above, full sources are at the end of the article).

 public void run(String ip, int port)
    {
        try {
            Socket socket = new Socket(ip, port);
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();
            Scanner keyboard = new Scanner(System.in);
            reader = new Thread(()->read(keyboard, sout));
            writer = new Thread(()->write(sin));
            reader.start();
            writer.start();
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

An overload of the same function that connects to the default port - for telnet it is 23


    public void run(String ip)
    {
        run(ip, 23);
    }

The function reads characters from the keyboard and sends them to the output socket - which is typical, in line, not character mode:


    private void read(Scanner keyboard, OutputStream sout)
    {
        try {
            String input = new String();
            while (true) {
                input = keyboard.nextLine();
                for (char i : (input + " n").toCharArray())
                    sout.write(i);
            }
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

The function receives data from the socket and displays it on the screen.


    private void write(InputStream sin)
    {
        try {
            int tmp;
            while (true){
                tmp = sin.read();
                System.out.print((char)tmp);
            }
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

The function stops receiving and transmitting data


    public void stop()
    {
        reader.stop();
        writer.stop();
    }
}

TelnetServer

This class must have the functionality of accepting a command from the socket, sending it for execution, and sending the response from the command back to the socket. The program deliberately does not check the input data, because, firstly, in the β€œboxed telnet” it is possible to format the server disk, and secondly, the security issue is omitted in this article in principle, and that is why there is not a word about encryption or SSL.

There are only 2 functions here (one of them is overloaded), and in general this is not a very good practice, however, within the framework of this task, it seemed appropriate for me to leave everything as it is.

 boolean isRunning = true;
    public void run(int port)    {

        (new Thread(()->{ try {
            ServerSocket ss = new ServerSocket(port); // создаСм сокСт сСрвСра ΠΈ привязываСм Π΅Π³ΠΎ ΠΊ Π²Ρ‹ΡˆΠ΅ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠΌΡƒ ΠΏΠΎΡ€Ρ‚Ρƒ
            System.out.println("Port "+port+" is waiting for connections");

            Socket socket = ss.accept();
            System.out.println("Connected");
            System.out.println();

            // Π‘Π΅Ρ€Π΅ΠΌ Π²Ρ…ΠΎΠ΄Π½ΠΎΠΉ ΠΈ Π²Ρ‹Ρ…ΠΎΠ΄Π½ΠΎΠΉ ΠΏΠΎΡ‚ΠΎΠΊΠΈ сокСта, Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ ΠΈ ΠΎΡ‚ΡΡ‹Π»Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Ρƒ.
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();

            Map<String, String> env = System.getenv();
            String wayToTemp = env.get("TEMP") + "tmp.txt";
            for (int i :("Connectednnr".toCharArray()))
                sout.write(i);
            sout.flush();

            String buffer = new String();
            while (isRunning) {

                int intReader = 0;
                while ((char) intReader != 'n') {
                    intReader = sin.read();
                    buffer += (char) intReader;
                }


                final String inputToSubThread = "cmd /c " + buffer.substring(0, buffer.length()-2) + " 2>&1";


                new Thread(()-> {
                    try {

                        Process p = Runtime.getRuntime().exec(inputToSubThread);
                        InputStream out = p.getInputStream();
                        Scanner fromProcess = new Scanner(out);
                        try {

                            while (fromProcess.hasNextLine()) {
                                String temp = fromProcess.nextLine();
                                System.out.println(temp);
                                for (char i : temp.toCharArray())
                                    sout.write(i);
                                sout.write('n');
                                sout.write('r');
                            }
                        }
                        catch (Exception e) {
                            String output = "Something gets wrong... Err code: "+ e.getStackTrace();
                            System.out.println(output);
                            for (char i : output.toCharArray())
                                sout.write(i);
                            sout.write('n');
                            sout.write('r');
                        }

                        p.getErrorStream().close();
                        p.getOutputStream().close();
                        p.getInputStream().close();
                        sout.flush();

                    }
                    catch (Exception e) {
                        System.out.println("Error: " + e.getMessage());
                    }
                }).start();
                System.out.println(buffer);
                buffer = "";

            }
        }
        catch(Exception x) {
            System.out.println(x.getMessage());
        }})).start();

    }

The program opens the server port, reads data from it until it encounters a command termination character, passes the command to the new process, and the output from the process is redirected to the socket. Everything is as simple as a Kalashnikov assault rifle.

Accordingly, there is an overload for this function with a default port:

 public void run()
    {
        run(23);
    }

Well, accordingly, the function that stops the server is also trivial, it interrupts the eternal loop, violating its condition.

    public void stop()
    {
        System.out.println("Server was stopped");
        this.isRunning = false;
    }

I will not give test classes here, they are below - all they do is check the performance of public methods. Everything is on the guitar.

Summarizing, in a couple of evenings you can understand the principles of operation of the main console utilities. Now, when we telnet to a remote computer, we understand what is happening - the magic is gone)

So the links:
All sources were, are and will be here
About Telnet
More about Telnet

Source: habr.com

Add a comment