📜 ⬆️ ⬇️

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

Greetings.

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

This is what will be discussed under the cut.

In this article, in order not to delay it, in addition to general information, I will write only about the Telnet server, but at the moment there is more material on other utilities - it will be in further parts of the cycle.
')
First of all, you need to figure out what Telnet is, what it is for, and what it is eaten with. I will not quote literally the sources (if necessary - at the end of the article I will attach a link to the materials on the topic), I will just say that Telnet provides remote access to the command line of the device. By and large, this is where its functionality ends (deliberately kept silent about the access to the server port, more on that later). So, to implement it, we need to accept the line on the client, transfer it to the server, try to transfer it to the command line, read the command line response, if any, transfer it back to the client and display it, 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 run the server and through which the client will work.
Accordingly, at the moment the structure of the application includes:


Let's run through each of them:

Telnetclient

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

To this end, the following functions were implemented:

A function that takes the address of a socket as an argument, opens a connection and starts input and output streams (the flow variables are declared above, the 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()); } } 

Overloading the same function, connecting 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 lowercase, 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 accepts data from the socket and displays it.
 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 should have the functionality of accepting a command from a socket, sending it to execution and sending a response from the command back to the socket. The program intentionally does not check the input data, because firstly, in the “box telnet” there is an opportunity to format the server disk, and secondly, the security issue in this article is omitted in principle, and that is why there is not a word about encryption or SSL

There are only 2 functions (one of them is overloaded), and in general it is not very good practice, however, within the framework of this task, it seemed appropriate 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 :("Connected\n\n\r".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 the command end character, sends the command to the new process, and the output from the process is redirected to the socket. Everything is just like a Kalashnikov rifle.

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

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

Well, respectively, the function that stops the server is also all trivial, it interrupts the eternal cycle, breaking 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 that they do is check the efficiency of public methods. Everything is on the gita.

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

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

Source: https://habr.com/ru/post/460569/


All Articles