| [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 | } | 
|---|
|  | 83 | if {[string equal $line "quit"] || [string equal $line "exit"]} { | 
|---|
|  | 84 | disconnect $client | 
|---|
|  | 85 | return | 
|---|
|  | 86 | } | 
|---|
|  | 87 | if {$auth($client)} { | 
|---|
|  | 88 | eval $cmd($client) [list $client $line 0] | 
|---|
|  | 89 | eval $cmd($client) [list $client $line 1] | 
|---|
|  | 90 | return | 
|---|
|  | 91 | } | 
|---|
|  | 92 | foreach {id pass} [split $line] {break} | 
|---|
|  | 93 | if {![info exist pass]} { | 
|---|
|  | 94 | catch {puts -nonewline $client "Login: "} | 
|---|
|  | 95 | return | 
|---|
|  | 96 | } | 
|---|
|  | 97 | if { | 
|---|
|  | 98 | [info exist passwords($serverport,$id)] && | 
|---|
|  | 99 | [string equal $passwords($serverport,$id) $pass] | 
|---|
|  | 100 | } then { | 
|---|
|  | 101 | set auth($client) 1 | 
|---|
|  | 102 | puts stdout "$id logged in on $client" | 
|---|
|  | 103 | catch {puts $client "Welcome, $id!"} | 
|---|
|  | 104 | eval $cmd($client) [list $client $line 1] | 
|---|
|  | 105 | return | 
|---|
|  | 106 | } | 
|---|
|  | 107 | puts stdout "AUTH FAILURE ON $client" | 
|---|
|  | 108 | catch {puts $client "Unknown name or password"} | 
|---|
|  | 109 | disconnect $client | 
|---|
|  | 110 | } | 
|---|
|  | 111 |  | 
|---|
|  | 112 | ## Standard handler for logged-in conversations and prompt-generation. | 
|---|
|  | 113 | proc execCommand {client line prompt} { | 
|---|
|  | 114 | global tcl_platform | 
|---|
|  | 115 | if {$prompt} { | 
|---|
|  | 116 | catch {puts -nonewline $client "\$ "} | 
|---|
|  | 117 | return | 
|---|
|  | 118 | } | 
|---|
|  | 119 | switch $tcl_platform(platform) { | 
|---|
|  | 120 | unix { | 
|---|
|  | 121 | catch {exec sh -c $line <@$client >@$client 2>@$client} | 
|---|
|  | 122 | } | 
|---|
|  | 123 | default { | 
|---|
|  | 124 | catch {exec $line} data | 
|---|
|  | 125 | puts $client $data | 
|---|
|  | 126 | } | 
|---|
|  | 127 | } | 
|---|
|  | 128 | } | 
|---|
|  | 129 |  | 
|---|
|  | 130 | ## Administration service handler.  Chains to the normal handler for | 
|---|
|  | 131 | ## everything it doesn't recognise itself. | 
|---|
|  | 132 | proc admin {client line prompt} { | 
|---|
|  | 133 | if {$prompt} { | 
|---|
|  | 134 | catch {puts -nonewline $client "# "} | 
|---|
|  | 135 | return | 
|---|
|  | 136 | } | 
|---|
|  | 137 | set cmd [split $line] | 
|---|
|  | 138 | global denyHosts connections services | 
|---|
|  | 139 | if {[string equal $line "shutdown"]} { | 
|---|
|  | 140 | set ::termination 1 | 
|---|
|  | 141 | puts stdout "Shutdown requested on $client" | 
|---|
|  | 142 | catch {puts $client "System will shut down as soon as possible"} | 
|---|
|  | 143 | return -code return "SHUTTING DOWN" | 
|---|
|  | 144 | } elseif {[string equal [lindex $cmd 0] "deny"]} { | 
|---|
|  | 145 | set denyHosts([lindex $cmd 1]) 1 | 
|---|
|  | 146 | } elseif {[string equal [lindex $cmd 0] "allow"]} { | 
|---|
|  | 147 | catch {unset denyHosts([lindex $cmd 1])} | 
|---|
|  | 148 | } elseif {[string equal $line "denied"]} { | 
|---|
|  | 149 | foreach host [array names denyHosts] { | 
|---|
|  | 150 | catch {puts $client $host} | 
|---|
|  | 151 | } | 
|---|
|  | 152 | } elseif {[string equal $line "connections"]} { | 
|---|
|  | 153 | set len 0 | 
|---|
|  | 154 | foreach conn [array names connections] { | 
|---|
|  | 155 | if {$len < [string length $conn]} { | 
|---|
|  | 156 | set len [string length $conn] | 
|---|
|  | 157 | } | 
|---|
|  | 158 | } | 
|---|
|  | 159 | foreach {conn details} [array get connections] { | 
|---|
|  | 160 | catch {puts $client [format "%-*s = %s" $len $conn $details]} | 
|---|
|  | 161 | } | 
|---|
|  | 162 | } elseif {[string equal [lindex $cmd 0] "close"]} { | 
|---|
|  | 163 | set sock [lindex $cmd 1] | 
|---|
|  | 164 | if {[info exist connections($sock)]} { | 
|---|
|  | 165 | disconnect $sock | 
|---|
|  | 166 | } | 
|---|
|  | 167 | } elseif {[string equal $line "services"]} { | 
|---|
|  | 168 | set len 0 | 
|---|
|  | 169 | foreach serv [array names services] { | 
|---|
|  | 170 | if {$len < [string length $serv]} { | 
|---|
|  | 171 | set len [string length $serv] | 
|---|
|  | 172 | } | 
|---|
|  | 173 | } | 
|---|
|  | 174 | foreach {serv handler} [array get services] { | 
|---|
|  | 175 | set port [lindex [fconfigure $serv -sockname] 2] | 
|---|
|  | 176 | catch {puts $client [format "%-*s (port %d) = handler %s" $len $serv $port $handler]} | 
|---|
|  | 177 | } | 
|---|
|  | 178 | } elseif {[string equal [lindex $cmd 0] "addService"]} { | 
|---|
|  | 179 | set service [eval telnetServer [lrange $cmd 1 end]] | 
|---|
|  | 180 | catch {puts $client "Created service as $service"} | 
|---|
|  | 181 | } elseif {[string equal [lindex $cmd 0] "removeService"]} { | 
|---|
|  | 182 | set service [lindex $cmd 1] | 
|---|
|  | 183 | if {[info exist services($service)]} { | 
|---|
|  | 184 | closedownServer $service | 
|---|
|  | 185 | } | 
|---|
|  | 186 | } else { | 
|---|
|  | 187 | # CHAIN TO DEFAULT | 
|---|
|  | 188 | execCommand $client $line 0 | 
|---|
|  | 189 | } | 
|---|
|  | 190 | } | 
|---|
|  | 191 |  | 
|---|
|  | 192 | ## Executes a given command | 
|---|
|  | 193 | proc remoteCommand {client line prompt} { | 
|---|
|  | 194 | global tcl_platform | 
|---|
|  | 195 | if {$prompt} { | 
|---|
|  | 196 | catch {puts -nonewline $client "\$ "} | 
|---|
|  | 197 | return | 
|---|
|  | 198 | } | 
|---|
|  | 199 | catch {eval $line} data | 
|---|
|  | 200 | puts $client $data | 
|---|
|  | 201 | } | 
|---|
|  | 202 |  | 
|---|
|  | 203 | telnetServer 2560 {orxonox rocks} remoteCommand | 
|---|
|  | 204 | telnetServer 2561 {orxadmin *****} admin | 
|---|
|  | 205 |  | 
|---|
|  | 206 | puts stdout "Ready for service" | 
|---|
|  | 207 |  | 
|---|
|  | 208 | vwait termination | 
|---|
|  | 209 | execute exit | 
|---|