Bash-Script mit (x)inetd oder socat auf Port 23 Telnet- und Netcat-Kompatibel machen

Fusl

Blog Benutzer
Angenommen ich habe folgendes, simple Script, welches mit /opt/script.sh aufgerufen wird, super toll funktioniert und alle meine eingegebenen Texte/Zeilen wiedergibt:

Code:
#!/usr/bin/env bash

while read -p "> " variable; do
        echo "You typed: ${variable}"
        echo -n "${variable}" | hexdump -C
done
Packe ich folgendes in meine inetd.conf

Code:
telnet stream tcp nowait nobody /usr/sbin/tcpd /usr/sbin/telnetd -h -E /opt/script.sh
mache ich es über Telnet Port 23 erreichbar, was auch super klappt. Selbiges auch mit xinetd und folgender socat Zeile:

Code:
socat tcp-l:23,reuseaddr,fork,bind=0.0.0.0 exec:'/usr/sbin/telnetd -h -E /opt/script.sh'
Wenn ich mich nun abwechselnd per Telnet und Netcat verbinde, passiert folgendes:

telnet auf telnetd:
Code:
› telnet 5.9.181.167 23
Trying 5.9.181.167...
Connected to 5.9.181.167.
Escape character is '^]'.
> test mit telnet auf telnetd
You typed: test mit telnet auf telnetd
00000000  74 65 73 74 20 6d 69 74  20 74 65 6c 6e 65 74 20  |test mit telnet |
00000010  61 75 66 20 74 65 6c 6e  65 74 64                 |auf telnetd|
0000001b
> ^DConnection closed by foreign host.
›
Funktioniert.

netcat auf telnetd:
Code:
› nc -v 5.9.181.167 23
Connection to 5.9.181.167 23 port [tcp/telnet] succeeded!
%& #'$test mit netcat auf telnetd
zweiter test mit netcat auf telnetd
^C
›
Streikt und ignoriert alle meine Benutzereingaben.

Wenn ich meine inetd/xinetd/socat Zeile so abändere:

Code:
socat tcp-l:23,reuseaddr,fork,bind=0.0.0.0 exec:'/opt/script.sh'
passiert folgendes:

telnet auf bash ohne telnetd:
Code:
› telnet 5.9.181.167 23
Trying 5.9.181.167...
Connected to 5.9.181.167.
Escape character is '^]'.
test mit telnet auf bash ohne telnetd
You typed: test mit telnet auf bash ohne telnetd
00000000  ff fd 03 ff fb 18 ff fb  1f ff fb 20 ff fb 21 ff  |........... ..!.|
00000010  fb 22 ff fb 27 ff fd 05  ff fb 23 74 65 73 74 20  |."..'.....#test |
00000020  6d 69 74 20 74 65 6c 6e  65 74 20 61 75 66 20 62  |mit telnet auf b|
00000030  61 73 68 20 6f 68 6e 65  20 74 65 6c 6e 65 74 64  |ash ohne telnetd|
00000040  0d                                                |.|
00000041
zweiter test mit telnet auf bash ohne telnetd^M^M^M^M^M^C^Z^[^]
telnet> quit
Connection closed.
›
Schmeißt mir in dem Fall irgendwelche Random Telnet negotiation control characters rein, macht mir ein carriage return nach der ersten Zeile und Streikt danach komplett.

netcat auf bash ohne telnetd:
Code:
› nc -v 5.9.181.167 23
Connection to 5.9.181.167 23 port [tcp/telnet] succeeded!
test mit netcat auf bash ohne telnetd
You typed: test mit netcat auf bash ohne telnetd
00000000  74 65 73 74 20 6d 69 74  20 6e 65 74 63 61 74 20  |test mit netcat |
00000010  61 75 66 20 62 61 73 68  20 6f 68 6e 65 20 74 65  |auf bash ohne te|
00000020  6c 6e 65 74 64                                    |lnetd|
00000025
zweiter test mit netcat auf bash ohne telnetd
You typed: zweiter test mit netcat auf bash ohne telnetd
00000000  7a 77 65 69 74 65 72 20  74 65 73 74 20 6d 69 74  |zweiter test mit|
00000010  20 6e 65 74 63 61 74 20  61 75 66 20 62 61 73 68  | netcat auf bash|
00000020  20 6f 68 6e 65 20 74 65  6c 6e 65 74 64           | ohne telnetd|
0000002d
^D›
Funktioniert alles super.


Ändere ich den Port nun auf etwas anderes ab, sagen wir 'mal Port 8080, und fahre ich die gleiche socat Zeile ohne telnetd, probiert mein telnet client kein Telnet negotiation.


Wenn ich mir das nun auf anderen Geräten so ansehe, nehmen wir als Beispiel mal Public Cisco oder Juniper Route Server, sehe ich im Netcat output folgende Zeichenfolge, welche vermutlich auch zu der Gruppe an Telnet negotiation control characters gehört:

Code:
› nc route-server.opentransit.net 23 | hexdump -C
00000000  ff fb 01 ff fb 03 ff fd  18 ff fd 1f 43 0d 0a 0d  |............C...|
[...]
(die letzten 3 Character in dem Fall sind 1½ LFCR, also einfach ignorieren)

Nichtsdestotrotz komme ich mit netcat hier weiter als bei meinem Bash Script, kann Username/Password eingeben und habe keine komischen Control Character Artefakte.


Wie also ist es mir möglich, mein Bash Script so ausführen zu lassen (alle Lösungswege sind erwünscht) sodass es beiderseits mit Netcat und mit Telnet kompatibel ist ohne dafür nun 2 Ports "verschwenden" zu müssen?
 

DeaD_EyE

Blog Benutzer
Interessantes Problem.

Für mich sieht das wie ein EchoServer aus.

Wird wohl an CR/LF liegen. Das Shell-Script sollte die Zeilenenden egal welche, einfach am ende des Strings entfernen.

Wenn du was modernes haben willst: https://docs.python.org/3/library/asyncio-protocol.html#tcp-echo-server-protocol (Python => 3.4)

Es hat den Vorteil, dass es Asynchron ist. 10k gleichzeitige Verbindungen sind dann auch kein Problem mehr. Keine Ahnung ob das socat + intetd + bash auch funktionieren kann.

Die Umwandlung der Strings nach hex könnte man weiterhin mit hexdump erledigen oder gleich in Python programmieren.
 

Fusl

Blog Benutzer
Ich hab das leider nicht mit der von dir Erwähnten Python-"Lösung" hinbekommen, DeaD_EyE.

Nach einigem hin und her habe ich es aber jetzt trotzdem einigermaßen zum Laufen gebracht:

Der Telnet-Client macht Standardmäßig auf Port 23 einen Telnet Handshake, bzw. versucht dies und schickt daher Handshake Daten am Anfang des Strings.

telnetd macht dies auch Standardmäßig, egal auf welchem Port, aber eben auf der Serverseite.

telnet <-> telnetd funktioniert somit wenn ich beides auf Port 23 laufen lasse aber wie auch erwähnt dann eben nicht mit netcat.

Die "Lösung" bzw. der Workaround ist jedoch viel einfacher als angenommen:

Mit socat auf Port 23 binden:

Code:
socat tcp-l:23,reuseaddr,fork,bind=0.0.0.0 exec:/opt/script.sh
Das Script beinhaltet in etwa folgenden Code:
Code:
rnecho() {
    # echo wrapper to print \r\n instead of \n as line feed
    echo "${*}" | n2rn
}
n2rn() {
    # convert \n to \r\n
    unix2dos -f -q
}
while read -p "> " line; do
    line=$(echo -n "${line}" | tr -d '\r' | sed -r 's/\xff(\xfb|\xfd).//g')
    rnecho "Parsed line: ${line}"
done
Das tr in der dritt-letzten Zeile entfernt mir alle carriage-returns (\r) von den CRLF die telnet (und ich denke auch manche netcat Versionen) macht.
Das sed löscht mir alle Hex-Zeichenfolgen FFFBxx (IAC-WILL-*) oder FFFDxx (IAC-DO-*) aus dem String, damit werde ich die Telnet Negotiation Handshake Daten am Anfang des Streams los.
Warum ich das tr und sed nicht vor dem while read mache hat einen ziemlich einfachen Grund: Macht ich nach tr und/oder sed eine Pipe wird stdout von beiden Befehlen gebuffered. sed hat zwar hierfür eine Option
Code:
-u, --unbuffered
load minimal amounts of data from the input files and flush the output buffers more often
welche aber nicht immer Funktioniert und tr hat so eine Option überhaupt nicht.

Das ganze ist nicht gerade eine perfekte Lösung, überhaupt weil dafür Bash verwendet wird daher werde ich das in Zukunft in Node.js portieren.

Wer es austesten möchte:
Code:
telnet route-viewer.fuslvz.ws 23
 
Last edited by a moderator:

DeaD_EyE

Blog Benutzer
Oh, das ist ja schick. Ich denke mal, dass die Implementierung in der Shell etwas tricky ist.

Telnet unter Linux scheint keine Zeichenfolgen am Anfang des Strings bei mir zu senden. Ich hab aber beobachtet, das wenn man z.B. den String 'foo\r' sendet, sendet telnet folgendes: 'foo\r\0\r\n'. Hab ich jetzt nen Bug gefunden oder ist das Verhalten normal? In C nutzt man das Nullbyte um Strings zu terminieren.

Sende ich einen String ohne Zeilenende, wird es auch via telnet ohne \r\n übergeben. Hängt nun am Ende ein newlinecaracter am string, was Zwangsweise die Folge ist, wenn man Enter betätigt, wird \r\n gesendet (Windows-Welt halt. Arbeiten noch mit Schreibmaschinen).
 

DeaD_EyE

Blog Benutzer
Stimmt, ist viel älter und hat etwas mit Schreibmaschinen zu tun. Lies erstmal den kompletten Text und wenn du etwas sinnvolles beizutragen hast, kannst du dich ja nochmal melden. Am besten mit Fakten. Wir veranstalten hier kein Glücksrad. Von dir hätte ich jetzt die Erklärung erwartet was z.B. das Steuerzeichen 0x0d mechanisch ausgelöst hat und wieso das in Windows, aber nicht in Unix übernommen worden ist.

@Topic
Für jede Verbindung wird ja ein Prozess in der Shell gestartet. Hast du das begrenzt?
 

Fusl

Blog Benutzer
Telnet unter Linux scheint keine Zeichenfolgen am Anfang des Strings bei mir zu senden.
Hast du das getestet? Auf welchem Port? Wie oben erwähnt passiert das nur wenn man sich per Port 23 verbindet, andere Ports werden als raw TCP-Stream behandelt.
 
Läuft der in eine Endlosschleife, wenn man route show primary (ohne weitere Argumente) eingibt? Bei Abbruch mit Ctrl+C hört es auf, danach passiert aber gar nichts mehr.


MfG Christian
 

DeaD_EyE

Blog Benutzer
MOD: Teile des Beitrags entfernt.
Ich Sprach von \r (Wagenrücklauf) und es ist ein Relikt aus der Vergangenheit. Ob du das jetzt für richtig hältst oder nicht geht mir am ARSCH vorbei!

@On-Topic
Getestet habe ich bei mir mit einem Socket in Python:

Code:
import socket
sock = socket.socket() #default ist AF_INET, SOCK_STREAM
sock.bind(('0.0.0.0', 23))
sock.listen(5)
client, addr = sock.accept()
client.read(1024)
b'Hello World\r\n'
Auf der anderen Seite einfach mit telnet localhost 23 verbunden und Hello World gesendet. Könnte an socat liegen oder ich hab eine andere telnetversion. Vielleicht hätte ich noch erwähnen sollen, dass ich kein Windows nutze. Ich weiß ja nicht in wie weit sich die Versionen unterscheiden.

Letztendlich überträgst du ja normale Befehle, die mit a-zA-Z0-9 anfangen. Also kannst du einfach alle Steuerzeichen am Anfang und am Ende entfernen.
 
Last edited by a moderator:

Joe User

Zentrum der Macht
MOD: Teile des Beitrags entfernt.

Ich Sprach von \r (Wagenrücklauf) und es ist ein Relikt aus der Vergangenheit.
Lies bitte die RFC 15 und 854, sowie diverse weitere ältere RFC, dann begreifst Du irgendwann, dass <CR> nichts mit Windows oder Schreibmaschinen zu tun hat, sondern mit Printern.

BTW: MacOS/Darwin (<LF><CR>) = BSD = UNIX
Soviel zu "<CR> = Windows-Welt"...

MOD: Teile des Beitrags entfernt.
 
Last edited by a moderator:

Fusl

Blog Benutzer
Bei Abbruch mit Ctrl+C hört es auf, danach passiert aber gar nichts mehr.
Das Problem ist bekannt, habe noch nicht genau herausgefunden was das problem ist. Ctrl+C per telnet macht das ganze Ding generell Kaputt, man sieht zumindest nichts mehr, Befehle werden aber weiterhin abgeschickt:
Code:
########################################################################
> ^C

help
quit
Connection closed by foreign host.
›
Passiert allerdings auch nur wenn man sich per Telnet verbindet, nicht aber mit socat/netcat/... Ich vermute dass Telnet da irgendwas nicht mag oder als falsches Steuerzeichen interpretiert.

Getestet habe ich bei mir mit einem Socket in Python:
Möchtest du das mal per socat probieren?

Code:
› cat hexdump.sh
#!/usr/bin/env bash

echo SOCAT_PEERADDR=${SOCAT_PEERADDR} 1>&2
hexdump -C 1>&2
› socat tcp-l:23,reuseaddr,fork,bind=0.0.0.0 exec:./hexdump.sh
DEBUG: nosudowrap.c:235:intercept_bind(): bind(5, {sa_family=AF_INET, sin_port=htons(23), sin_addr=inet_addr("0.0.0.0")}, 16) = -1 EACCES (Permission denied)
[sudo] password for root:
SOCAT_PEERADDR=127.0.0.1
00000000  ff fd 03 ff fb 18 ff fb  1f ff fb 20 ff fb 21 ff  |........... ..!.|
00000010  fb 22 ff fb 27 ff fd 05  ff fb 23 41 20 76 65 72  |."..'.....#A ver|
00000020  79 20 6c 6f 6e 67 20 74  65 78 74 2e 0d 0a 0d 0a  |y long text.....|
00000030  0d 0a 0d 0a 0d 0a 54 65  73 74 0d 0a 4d 65 6f 77  |......Test..Meow|
Edit:

Ich muss Joe User btw Recht geben dass das CR im CRLF nichts bzw. nur bedingt was mit Schreibmaschinen zu tun hat.

Früher haben solche Text-Parser, o.Ä. und auch Programme die den aus einem File eingelesenen Text ausgeben nicht automatisch einen CR gemacht, daher hatte man das CR zum LF hinzufügen müssen, sonst
Code:
hat
   der
      Text
          dann
              halt
                  eben
                      so
                        ausgesehen.
Das kann man auch heutzutage noch produzieren wenn man z.B. unter Linux unabsichtlich irgendwelche Binary files cat-ted und sich dann wundert warum der weitere Text danach eben wie oben dargestellt aussieht und kein CR aber ein LF gemacht wird.
 
Last edited by a moderator:

nexus

Active Member
[OFFTOPIC]

Stimmt, ist viel älter und hat etwas mit Schreibmaschinen zu tun
dass <CR> nichts mit Windows oder Schreibmaschinen zu tun hat, sondern mit Printern.
Ich muß euch leider enttäuschen, aber ihr habt beide nur zum Teil Recht ;)

Das Grundprinzip stammt zwar von den Schreibmaschinen, aber die systematische Trennung von Carriage Return (<CR>) und Line Feed (<LF>) wurde erst aufgrund technischer Notwendigkeiten in der Fernschreiber-Technik eingeführt und vollständig umgesetzt und wurde dann später bei Printern von den Fernschreibern übernommen.

Hier wird das relativ schön erläutert --> https://de.wikipedia.org/wiki/Wagenrücklauf#Fernschreiber

Ich hoffe, daß dieser kurze Exkurs in die Vergangenheit die Gemüter ein klein wenig beruhigen konnte... :D:cool:

[/OFFTOPIC]
 

Fusl

Blog Benutzer
ihr habt beide nur zum Teil Recht
Ach, ist das so? Dann hat mich Wikipedia also anscheinend nicht eines Besseren belehrt:

https://en.wikipedia.org/wiki/Carriage_return said:
In computing, the carriage return is one of the control characters in ASCII code, Unicode, EBCDIC, and many other codes. It commands a printer, or other output system such as the display of a system console, to move the position of the cursor to the first position on the same line. It was mostly used along with line feed (LF), a move to the next line, so that together they start a new line. Together, this sequence can be referred to as CRLF.
Wir sprechen hier immerhin vom CRLF bei Computern - Niemanden hier interessiert ob wir das printen wollen, sondern warum der CRLF bei Computern überhaupt (noch) existiert.
 

nexus

Active Member
Ach, ist das so?
Ja, denn beide bezogen sich in ihren Aussagen auf die Herkunft bzw. Ursprünge der Steuerzeichen/Steuerbefehle CR und LF...und die stammen nunmal aus der Fernschreibertechnik.

So, nun aber wirklich genug OffTopic zu dem Thema :)
 

Fusl

Blog Benutzer
Joe User, mir ist es sehr wichtig dass das Bash-Script kompatibel mit Telnet-Clients aber gleichzeitig auch abwärtskompatibel mit Netcat/Socat oder anderen RAW TCP Clients ist, wie auch viele der anderen hier gelisteten Public Route Server, da ich nicht beeinflussen kann ob sich User per Telnet oder per Netcat/Socat/RAW-TCP dort hin verirren. Der Fakt dass die gelisteten Public Route Server eben mit beidem kompatibel sind, aber von keiner der verwendeten Client-Software wirklich abhängig ist hat mich eben stutzig gemacht und ich wollte daher auch nicht einfach akzeptieren dass meine Serverseitige Anwendung entweder * oder * unterstützt aber nicht beides. Das ist in etwa gleichgestellt mit einem HTTP-Server der entweder nur Firefox oder nur Chrome unterstützt aber nicht beide gleichzeitig. Der Grund warum Telnet mit meinem Workaround funktioniert ist der, dass sich die Entwickler von Telnet gedacht haben, dass ein Fallback auf ein RAW TCP Protokoll sinnvoll wäre, eben daher dass dieser nicht unbrauchbar wird wenn sich ein Telnet Client auf einen Server verirrt der auf Port 23 lauscht aber kein Telnet Protokoll macht.

Mit der von mir beschriebenen und demonstrierten Variante in Bash ist es möglich, den String von Telnet Negotiation Zeichen zu bereinigen und somit die Software im Fallback Modus zu betreiben. Natürlich ist es besser wenn der Server eine Telnet negotiation macht, denn dann ist dieser auch von Clients verwendbar die keinen Fallback-Mechanismus implementiert haben (Android ConnectBot um mal ein Beispiel genannt zu haben), dafür müsste ich dann allerdings wieder auf der Server-Seite einen Fallback für nicht-Telnet Clients implementieren und das bei jeder String-Ausgabe gegenprüfen.

Geplant ist genau das in Node.js zu implementieren, Telnet Neg. Steuerzeichen vom Server zum Client am Anfang des TCP-Streams senden und dann einfach normal weiterarbeiten aber trotzdem Telnet Neg. Steuerzeichen vom Client nachträglich akzeptieren, Parsen und dementsprechend die Ausgabe und das Format an das vom Client gewünsche Format und Escaping anpassen (u.A. auch Window-Resizing). Damit wäre es dann auch möglich für beide Arten von Clients, Telnet und RAW-TCP, Serverseitig eine Readline Library oder rlwrap zu implementieren mit welcher dann eben eine vollwertige Shell emuliert werden kann. Möglich ist das ganze bestimmt, ist aber eine Frage des Aufwandes und der hierfür benötigten Zeit.

Edit: Ich hoffe meine Beiträge machen mehr oder weniger Sinn - Ich habe - soweit mir bekannt ist - noch nie so einen langen Beitrag oder nur sehr wenige annähernd gleich lange oder längere Beiträge hier im SSF hinterlassen wie in diesem Thread :eek:
 
Last edited by a moderator:

Top