Λογισμικό γραφής με τη λειτουργικότητα των βοηθητικών προγραμμάτων πελάτη-διακομιστή των Windows, μέρος 01

Χαιρετισμούς.

Σήμερα θα ήθελα να εξετάσω τη διαδικασία σύνταξης εφαρμογών πελάτη-διακομιστή που εκτελούν τις λειτουργίες τυπικών βοηθητικών προγραμμάτων των Windows, όπως Telnet, TFTP, κ.λπ., κ.λπ. σε καθαρή Java. Είναι σαφές ότι δεν θα φέρω τίποτα νέο - όλα αυτά τα βοηθητικά προγράμματα λειτουργούν με επιτυχία για περισσότερο από ένα χρόνο, αλλά πιστεύω ότι δεν γνωρίζουν όλοι τι συμβαίνει κάτω από την κουκούλα.

Αυτό ακριβώς θα συζητηθεί κάτω από την περικοπή.

Σε αυτό το άρθρο, για να μην το τραβήξω, εκτός από γενικές πληροφορίες, θα γράψω μόνο για τον διακομιστή Telnet, αλλά αυτή τη στιγμή υπάρχει και υλικό για άλλα βοηθητικά προγράμματα - θα είναι σε περαιτέρω μέρη της σειράς.

Πρώτα απ 'όλα, πρέπει να καταλάβετε τι είναι το Telnet, σε τι χρειάζεται και σε τι χρησιμοποιείται. Δεν θα παραθέσω τις πηγές κατά λέξη (εάν είναι απαραίτητο, θα επισυνάψω έναν σύνδεσμο σε υλικά σχετικά με το θέμα στο τέλος του άρθρου), θα πω μόνο ότι το Telnet παρέχει απομακρυσμένη πρόσβαση στη γραμμή εντολών της συσκευής. Σε γενικές γραμμές, εδώ τελειώνει η λειτουργικότητά του (εσκεμμένα σιωπούσα σχετικά με την πρόσβαση στη θύρα διακομιστή, περισσότερα για αυτό αργότερα). Αυτό σημαίνει ότι για να το εφαρμόσουμε, πρέπει να αποδεχθούμε μια γραμμή στον πελάτη, να τη περάσουμε στον διακομιστή, να προσπαθήσουμε να τη περάσουμε στη γραμμή εντολών, να διαβάσουμε την απάντηση της γραμμής εντολών, εάν υπάρχει, να την περάσουμε πίσω στον πελάτη και εμφανίστε το στην οθόνη ή, εάν υπάρχουν σφάλματα, ενημερώστε τον χρήστη ότι κάτι δεν πάει καλά.

Για να υλοποιήσουμε τα παραπάνω, αντίστοιχα, χρειαζόμαστε 2 κλάσεις εργασίας και κάποια δοκιμαστική κλάση από την οποία θα εκκινήσουμε τον διακομιστή και μέσω της οποίας θα εργάζεται ο πελάτης.
Αντίστοιχα, αυτή τη στιγμή η δομή της αίτησης περιλαμβάνει:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Ας δούμε κάθε ένα από αυτά:

TelnetClient

Το μόνο που θα πρέπει να μπορεί να κάνει αυτή η κλάση είναι να στέλνει λαμβανόμενες εντολές και να εμφανίζει τις ληφθείσες απαντήσεις. Επιπλέον, πρέπει να μπορείτε να συνδεθείτε σε μια αυθαίρετη (όπως αναφέρθηκε παραπάνω) θύρα μιας απομακρυσμένης συσκευής και να αποσυνδεθείτε από αυτήν.

Για να επιτευχθεί αυτό, εφαρμόστηκαν οι ακόλουθες λειτουργίες:

Μια συνάρτηση που παίρνει μια διεύθυνση υποδοχής ως όρισμα, ανοίγει μια σύνδεση και ξεκινά ροές εισόδου και εξόδου (οι μεταβλητές ροής δηλώνονται παραπάνω, οι πλήρεις πηγές βρίσκονται στο τέλος του άρθρου).

 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());
        }
    }

Υπερφόρτωση της ίδιας λειτουργίας, σύνδεση στην προεπιλεγμένη θύρα - για telnet αυτό είναι 23


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

Η λειτουργία διαβάζει χαρακτήρες από το πληκτρολόγιο και τους στέλνει στην υποδοχή εξόδου - η οποία είναι τυπική, σε λειτουργία γραμμής, όχι σε λειτουργία χαρακτήρων:


    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());
        }
    }

Η λειτουργία λαμβάνει δεδομένα από την υποδοχή και τα εμφανίζει στην οθόνη


    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());
        }
    }

Η λειτουργία διακόπτει τη λήψη και τη μετάδοση δεδομένων


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

TelnetServer

Αυτή η κλάση πρέπει να έχει τη λειτουργικότητα να λαμβάνει μια εντολή από μια υποδοχή, να την στέλνει για εκτέλεση και να στέλνει μια απάντηση από την εντολή πίσω στην υποδοχή. Το πρόγραμμα σκόπιμα δεν ελέγχει τα δεδομένα εισόδου, γιατί πρώτον, ακόμη και σε "boxed telnet" είναι δυνατή η διαμόρφωση του δίσκου διακομιστή και, δεύτερον, το θέμα της ασφάλειας σε αυτό το άρθρο παραλείπεται κατ' αρχήν και γι 'αυτό δεν υπάρχει λίγα λόγια για την κρυπτογράφηση ή το SSL.

Υπάρχουν μόνο 2 λειτουργίες (μία από αυτές είναι υπερφορτωμένη) και γενικά αυτό δεν είναι πολύ καλή πρακτική, αλλά για τους σκοπούς αυτής της εργασίας, μου φάνηκε σκόπιμο να αφήσω τα πάντα ως έχουν.

 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();

    }

Το πρόγραμμα ανοίγει τη θύρα διακομιστή, διαβάζει δεδομένα από αυτήν μέχρι να συναντήσει έναν χαρακτήρα τέλους εντολής, μεταβιβάζει την εντολή σε μια νέα διεργασία και ανακατευθύνει την έξοδο από τη διεργασία στην υποδοχή. Όλα είναι τόσο απλά όσο ένα τουφέκι επίθεσης Καλάσνικοφ.

Κατά συνέπεια, υπάρχει υπερφόρτωση για αυτήν τη λειτουργία με μια προεπιλεγμένη θύρα:

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

Λοιπόν, κατά συνέπεια, η λειτουργία που σταματά τον διακομιστή είναι επίσης ασήμαντη, διακόπτει τον αιώνιο βρόχο, παραβιάζοντας την κατάστασή του.

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

Δεν θα δώσω μαθήματα δοκιμής εδώ, είναι παρακάτω - το μόνο που κάνουν είναι να ελέγχουν τη λειτουργικότητα των δημόσιων μεθόδων. Τα πάντα είναι στο στάδιο.

Συνοψίζοντας, σε μερικά βράδια μπορείτε να κατανοήσετε τις αρχές λειτουργίας των βασικών βοηθητικών προγραμμάτων κονσόλας. Τώρα, όταν κάνουμε τηλεσύνδεση σε έναν απομακρυσμένο υπολογιστή, καταλαβαίνουμε τι συμβαίνει - η μαγεία έχει εξαφανιστεί)

Λοιπόν, οι σύνδεσμοι:
Όλες οι πηγές ήταν, είναι και θα είναι εδώ
Σχετικά με το Telnet
Περισσότερα για το Telnet

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο