[1505] | 1 | #!/usr/bin/env tclsh |
---|
| 2 | # Pseudo-telnet server. Includes basic auth, but no separate identities |
---|
| 3 | # or proper multi-threaded operation, so whoever runs this had better |
---|
| 4 | # trust those he gives identities/passwords to and they had better trust |
---|
| 5 | # each other too. Note this script does not support command-line arguments. |
---|
| 6 | |
---|
| 7 | ## The names of this array are IP addresses of hosts that are not permitted |
---|
| 8 | ## to connect to any of our services. Admin account(s) can change this |
---|
| 9 | ## at run-time, though this info is not maintained across whole-server shutdowns. |
---|
| 10 | array set denyHosts {} |
---|
| 11 | |
---|
| 12 | ## Keep the Tcl-thread busy |
---|
| 13 | proc every {ms body} { eval $body; after $ms [list every $ms $body] } |
---|
| 14 | every 200 {} |
---|
| 15 | |
---|
| 16 | ## Create a server on the given port with the given name/password map |
---|
| 17 | ## and the given core interaction handler. |
---|
| 18 | proc telnetServer {port {passmap} {handlerCmd remoteCommand}} { |
---|
| 19 | if {$port == 0} { |
---|
| 20 | return -code error "Only non-zero port numbers are supported" |
---|
| 21 | } |
---|
| 22 | set server [socket -server [list connect $port $handlerCmd] $port] |
---|
| 23 | global passwords services |
---|
| 24 | foreach {id pass} $passmap {set passwords($port,$id) $pass} |
---|
| 25 | set services($server) $handlerCmd |
---|
| 26 | return $server |
---|
| 27 | } |
---|
| 28 | |
---|
| 29 | ## Removes the server on the given port, cleaning up the extra state too. |
---|
| 30 | proc closedownServer {server} { |
---|
| 31 | global services passwords connections auth |
---|
| 32 | set port [lindex [fconfigure $server -sockname] 2] |
---|
| 33 | catch {close $server} |
---|
| 34 | unset services($server) |
---|
| 35 | foreach passmap [array names passwords $port,*] { |
---|
| 36 | unset passwords($passmap) |
---|
| 37 | } |
---|
| 38 | # Hmph! Have to remove unauthorized connections too, though any |
---|
| 39 | # connection which has been authorized can continue safely. |
---|
| 40 | foreach {client data} [array get connections] { |
---|
| 41 | if {$port == [lindex $data 0] && !$auth($client)} { |
---|
| 42 | disconnect $client |
---|
| 43 | } |
---|
| 44 | } |
---|
| 45 | } |
---|
| 46 | |
---|
| 47 | ## Handle an incoming connection to the given server |
---|
| 48 | proc connect {serverport handlerCmd client clienthost clientport} { |
---|
| 49 | global auth cmd denyHosts connections |
---|
| 50 | if {[info exist denyHosts($clienthost)]} { |
---|
| 51 | puts stdout "${clienthost}:${clientport} attempted connection" |
---|
| 52 | catch {puts $client "Connection denied"} |
---|
| 53 | catch {close $client} |
---|
| 54 | return |
---|
| 55 | } |
---|
| 56 | puts stdout "${clienthost}:${clientport} connected on $client" |
---|
| 57 | fileevent $client readable "handle $serverport $client" |
---|
| 58 | set auth($client) 0 |
---|
| 59 | set cmd($client) $handlerCmd |
---|
| 60 | set connections($client) [list $serverport $clienthost $clientport] |
---|
| 61 | fconfigure $client -buffering none |
---|
| 62 | catch {puts -nonewline $client "Login: "} |
---|
| 63 | } |
---|
| 64 | |
---|
| 65 | ## Disconnect the given client, cleaning up any connection-specific data |
---|
| 66 | proc disconnect {client} { |
---|
| 67 | catch {close $client} |
---|
| 68 | global auth cmd connections |
---|
| 69 | unset auth($client) |
---|
| 70 | unset cmd($client) |
---|
| 71 | unset connections($client) |
---|
| 72 | puts stdout "$client disconnected" |
---|
| 73 | } |
---|
| 74 | |
---|
| 75 | ## Handle data sent from the client. Log-in is handled directly by this |
---|
| 76 | ## procedure, and requires the name and password on the same line |
---|
| 77 | proc handle {serverport client} { |
---|
| 78 | global passwords auth cmd |
---|
| 79 | if {[gets $client line] < 0} { |
---|
| 80 | disconnect $client |
---|
| 81 | return |
---|
| 82 | } |
---|
[2174] | 83 | if {[string equal $line "logout"] || [string equal $line "quit"]} { |
---|
[1505] | 84 | disconnect $client |
---|
| 85 | return |
---|
| 86 | } |
---|
[2174] | 87 | if {[string equal $line "exit"]} { |
---|
| 88 | set ::termination 1 |
---|
| 89 | return |
---|
| 90 | } |
---|
[1505] | 91 | if {$auth($client)} { |
---|
| 92 | eval $cmd($client) [list $client $line 0] |
---|
| 93 | eval $cmd($client) [list $client $line 1] |
---|
| 94 | return |
---|
| 95 | } |
---|
| 96 | foreach {id pass} [split $line] {break} |
---|
| 97 | if {![info exist pass]} { |
---|
| 98 | catch {puts -nonewline $client "Login: "} |
---|
| 99 | return |
---|
| 100 | } |
---|
| 101 | if { |
---|
| 102 | [info exist passwords($serverport,$id)] && |
---|
| 103 | [string equal $passwords($serverport,$id) $pass] |
---|
| 104 | } then { |
---|
| 105 | set auth($client) 1 |
---|
| 106 | puts stdout "$id logged in on $client" |
---|
| 107 | catch {puts $client "Welcome, $id!"} |
---|
| 108 | eval $cmd($client) [list $client $line 1] |
---|
| 109 | return |
---|
| 110 | } |
---|
| 111 | puts stdout "AUTH FAILURE ON $client" |
---|
| 112 | catch {puts $client "Unknown name or password"} |
---|
| 113 | disconnect $client |
---|
| 114 | } |
---|
| 115 | |
---|
| 116 | ## Standard handler for logged-in conversations and prompt-generation. |
---|
| 117 | proc execCommand {client line prompt} { |
---|
| 118 | global tcl_platform |
---|
| 119 | if {$prompt} { |
---|
| 120 | catch {puts -nonewline $client "\$ "} |
---|
| 121 | return |
---|
| 122 | } |
---|
| 123 | switch $tcl_platform(platform) { |
---|
| 124 | unix { |
---|
| 125 | catch {exec sh -c $line <@$client >@$client 2>@$client} |
---|
| 126 | } |
---|
| 127 | default { |
---|
| 128 | catch {exec $line} data |
---|
| 129 | puts $client $data |
---|
| 130 | } |
---|
| 131 | } |
---|
| 132 | } |
---|
| 133 | |
---|
| 134 | ## Administration service handler. Chains to the normal handler for |
---|
| 135 | ## everything it doesn't recognise itself. |
---|
| 136 | proc admin {client line prompt} { |
---|
| 137 | if {$prompt} { |
---|
| 138 | catch {puts -nonewline $client "# "} |
---|
| 139 | return |
---|
| 140 | } |
---|
| 141 | set cmd [split $line] |
---|
| 142 | global denyHosts connections services |
---|
| 143 | if {[string equal $line "shutdown"]} { |
---|
| 144 | set ::termination 1 |
---|
| 145 | puts stdout "Shutdown requested on $client" |
---|
| 146 | catch {puts $client "System will shut down as soon as possible"} |
---|
| 147 | return -code return "SHUTTING DOWN" |
---|
| 148 | } elseif {[string equal [lindex $cmd 0] "deny"]} { |
---|
| 149 | set denyHosts([lindex $cmd 1]) 1 |
---|
| 150 | } elseif {[string equal [lindex $cmd 0] "allow"]} { |
---|
| 151 | catch {unset denyHosts([lindex $cmd 1])} |
---|
| 152 | } elseif {[string equal $line "denied"]} { |
---|
| 153 | foreach host [array names denyHosts] { |
---|
| 154 | catch {puts $client $host} |
---|
| 155 | } |
---|
| 156 | } elseif {[string equal $line "connections"]} { |
---|
| 157 | set len 0 |
---|
| 158 | foreach conn [array names connections] { |
---|
| 159 | if {$len < [string length $conn]} { |
---|
| 160 | set len [string length $conn] |
---|
| 161 | } |
---|
| 162 | } |
---|
| 163 | foreach {conn details} [array get connections] { |
---|
| 164 | catch {puts $client [format "%-*s = %s" $len $conn $details]} |
---|
| 165 | } |
---|
| 166 | } elseif {[string equal [lindex $cmd 0] "close"]} { |
---|
| 167 | set sock [lindex $cmd 1] |
---|
| 168 | if {[info exist connections($sock)]} { |
---|
| 169 | disconnect $sock |
---|
| 170 | } |
---|
| 171 | } elseif {[string equal $line "services"]} { |
---|
| 172 | set len 0 |
---|
| 173 | foreach serv [array names services] { |
---|
| 174 | if {$len < [string length $serv]} { |
---|
| 175 | set len [string length $serv] |
---|
| 176 | } |
---|
| 177 | } |
---|
| 178 | foreach {serv handler} [array get services] { |
---|
| 179 | set port [lindex [fconfigure $serv -sockname] 2] |
---|
| 180 | catch {puts $client [format "%-*s (port %d) = handler %s" $len $serv $port $handler]} |
---|
| 181 | } |
---|
| 182 | } elseif {[string equal [lindex $cmd 0] "addService"]} { |
---|
| 183 | set service [eval telnetServer [lrange $cmd 1 end]] |
---|
| 184 | catch {puts $client "Created service as $service"} |
---|
| 185 | } elseif {[string equal [lindex $cmd 0] "removeService"]} { |
---|
| 186 | set service [lindex $cmd 1] |
---|
| 187 | if {[info exist services($service)]} { |
---|
| 188 | closedownServer $service |
---|
| 189 | } |
---|
| 190 | } else { |
---|
| 191 | # CHAIN TO DEFAULT |
---|
| 192 | execCommand $client $line 0 |
---|
| 193 | } |
---|
| 194 | } |
---|
| 195 | |
---|
| 196 | ## Executes a given command |
---|
| 197 | proc remoteCommand {client line prompt} { |
---|
| 198 | global tcl_platform |
---|
| 199 | if {$prompt} { |
---|
| 200 | catch {puts -nonewline $client "\$ "} |
---|
| 201 | return |
---|
| 202 | } |
---|
| 203 | catch {eval $line} data |
---|
| 204 | puts $client $data |
---|
| 205 | } |
---|
| 206 | |
---|
| 207 | telnetServer 2560 {orxonox rocks} remoteCommand |
---|
| 208 | telnetServer 2561 {orxadmin *****} admin |
---|
| 209 | |
---|
| 210 | puts stdout "Ready for service" |
---|
| 211 | |
---|
| 212 | vwait termination |
---|
| 213 | execute exit |
---|