This is telnet as remote-execution protocol, and not as specified in the RFC 854 [L1 ]. These two scripts have been tested against each other, and work nicely with Solaris. No idea if they will function correctly on other Unix machines (though they ought to) and limitations in Windows reduce the power of the server on that platform...
Have fun! -DKF
Q & A
On your next holiday, be sure to visit: [L4 ]
DKF
DL
If you came here looking for a real telnet
Download plink from http://www.chiark.greenend.org.uk/~sgtatham/putty/
set f [open "|plink -telnet 10.0.7.16" w+] fconfigure $f -blocking 0 -buffering none read $f puts $f "help\n" read $f
LEM
SERVER
I give a demonstration of this with an admin service which offers the standard operations, but also permits a bunch of special operations:
#! /bin/sh # Pseudo-telnet server. Includes basic auth, but no separate identities # or proper multi-threaded operation, so whoever runs this had better # trust those he gives identities/passwords to and they had better trust # each other too. # THIS LINE MUST BE HERE \ exec tclsh "$0" # Note that this script takes no command-line arguments... ## The names of this array are IP addresses of hosts that are not permitted ## to connect to any of our services. Admin account(s) can change this ## at run-time, though this info is not maintained across whole-server shutdowns. array set denyHosts {} ## Create a server on the given port with the given name/password map ## and the given core interaction handler. proc telnetServer {port {passmap {foo bar spong wibble}} {handlerCmd execCommand}} { if {$port == 0} { return -code error "Only non-zero port numbers are supported" } set server [socket -server [list connect $port $handlerCmd] $port] global passwords services foreach {id pass} $passmap {set passwords($port,$id) $pass} set services($server) $handlerCmd return $server } ## Removes the server on the given port, cleaning up the extra state too. proc closedownServer {server} { global services passwords connections auth set port [lindex [fconfigure $server -sockname] 2] catch {close $server} unset services($server) foreach passmap [array names passwords $port,*] { unset passwords($passmap) } # Hmph! Have to remove unauthorized connections too, though any # connection which has been authorized can continue safely. foreach {client data} [array get connections] { if {$port == [lindex $data 0] && !$auth($client)} { disconnect $client } } } ## Handle an incoming connection to the given server proc connect {serverport handlerCmd client clienthost clientport} { global auth cmd denyHosts connections if {[info exist denyHosts($clienthost)]} { puts stdout "${clienthost}:${clientport} attempted connection" catch {puts $client "Connection denied"} catch {close $client} return } puts stdout "${clienthost}:${clientport} connected on $client" fileevent $client readable "handle $serverport $client" set auth($client) 0 set cmd($client) $handlerCmd set connections($client) [list $serverport $clienthost $clientport] fconfigure $client -buffering none catch {puts -nonewline $client "Login: "} } ## Disconnect the given client, cleaning up any connection-specific data proc disconnect {client} { catch {close $client} global auth cmd connections unset auth($client) unset cmd($client) unset connections($client) puts stdout "$client disconnected" } ## Handle data sent from the client. Log-in is handled directly by this ## procedure, and requires the name and password on the same line proc handle {serverport client} { global passwords auth cmd if {[gets $client line] < 0} { disconnect $client return } if {[string equal $line "quit"] || [string equal $line "exit"]} { disconnect $client return } if {$auth($client)} { eval $cmd($client) [list $client $line 0] eval $cmd($client) [list $client $line 1] return } foreach {id pass} [split $line] {break} if {![info exist pass]} { catch {puts -nonewline $client "Login: "} return } if { [info exist passwords($serverport,$id)] && [string equal $passwords($serverport,$id) $pass] } then { set auth($client) 1 puts stdout "$id logged in on $client" catch {puts $client "Welcome, $id!"} eval $cmd($client) [list $client $line 1] return } puts stdout "AUTH FAILURE ON $client" catch {puts $client "Unknown name or password"} disconnect $client } ## Standard handler for logged-in conversations and prompt-generation. proc execCommand {client line prompt} { global tcl_platform if {$prompt} { catch {puts -nonewline $client "\$ "} return } switch $tcl_platform(platform) { unix { catch {exec sh -c $line <@$client >@$client 2>@$client} } default { catch {exec $line} data puts $client $data } } } telnetServer 12345 ;# DEFAULT NAMES/PASSWORDS telnetServer 12346 {aleph alpha beth beta} ## Administration service handler. Chains to the normal handler for ## everything it doesn't recognise itself. proc admin {client line prompt} { if {$prompt} { catch {puts -nonewline $client "# "} return } set cmd [split $line] global denyHosts connections services if {[string equal $line "shutdown"]} { set ::termination 1 puts stdout "Shutdown requested on $client" catch {puts $client "System will shut down as soon as possible"} return -code return "SHUTTING DOWN" } elseif {[string equal [lindex $cmd 0] "deny"]} { set denyHosts([lindex $cmd 1]) 1 } elseif {[string equal [lindex $cmd 0] "allow"]} { catch {unset denyHosts([lindex $cmd 1])} } elseif {[string equal $line "denied"]} { foreach host [array names denyHosts] { catch {puts $client $host} } } elseif {[string equal $line "connections"]} { set len 0 foreach conn [array names connections] { if {$len < [string length $conn]} { set len [string length $conn] } } foreach {conn details} [array get connections] { catch {puts $client [format "%-*s = %s" $len $conn $details]} } } elseif {[string equal [lindex $cmd 0] "close"]} { set sock [lindex $cmd 1] if {[info exist connections($sock)]} { disconnect $sock } } elseif {[string equal $line "services"]} { set len 0 foreach serv [array names services] { if {$len < [string length $serv]} { set len [string length $serv] } } foreach {serv handler} [array get services] { set port [lindex [fconfigure $serv -sockname] 2] catch {puts $client [format "%-*s (port %d) = handler %s" $len $serv $port $handler]} } } elseif {[string equal [lindex $cmd 0] "addService"]} { set service [eval telnetServer [lrange $cmd 1 end]] catch {puts $client "Created service as $service"} } elseif {[string equal [lindex $cmd 0] "removeService"]} { set service [lindex $cmd 1] if {[info exist services($service)]} { closedownServer $service } } else { # CHAIN TO DEFAULT execCommand $client $line 0 } } telnetServer 12347 {root OfAllEvil} admin puts stdout "Ready for service" vwait termination exit
CLIENT
This client is great for interactively accessing simple internet protocols (like SMTP, NNTP, POP and even HTTP) though it is not up to being a real TELNET client, and it lacks the additional support needed for being an FTP client.
#! /bin/sh # Pseudo-telnet client. # THIS LINE MUST BE HERE \ exec tclsh "$0" ${1+"$@"} proc telnet {{server localhost} {port telnet}} { set sock [socket $server $port] fconfigure $sock -buffering none -blocking 0 fconfigure stdout -buffering none #fileevent $sock readable [list initEvents $sock] fileevent $sock readable [list fromServer $sock] fileevent stdin readable [list toServer $sock] global closed vwait closed($sock) unset closed($sock) } proc initEvents {sock} { puts -nonewline [read $sock 4096] fileevent $sock readable [list fromServer $sock] fileevent stdin readable [list toServer $sock] } proc toServer {sock} { if {[gets stdin line] >= 0} { puts $sock $line } else { disconnect $sock } } proc fromServer {sock} { set data x while {[string length $data]} { set data [read $sock 4096] if {[eof $sock]} { disconnect $sock return } if {[string length $data]} { puts -nonewline stdout $data } } } proc disconnect {sock} { global closed close $sock set closed($sock) 1 } if {[llength $argv] > 2} { puts stderr "wrong # args: should be \"telnet ?hostname? ?port?\"" puts stderr "\thostname defaults to \"localhost\"" puts stderr "\tport defaults to the telnet port, and may be specified\n\teither by name or by number" } else { eval telnet $argv } exit
[ CL intends to illustrate use of this telnet with interesting examples in summer 2002.] - Apparently your plans have changed? -FW