| [1897] | 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 | 
|---|