diff --git a/shellbox.tcl b/shellbox.tcl new file mode 100755 index 0000000..241301c --- /dev/null +++ b/shellbox.tcl @@ -0,0 +1,164 @@ +#!/usr/bin/tclsh + +# shellbox.tcl +# +# This program serves as a box that runs a shell in it. +# stdout and stdin of the shell are connected to the box. +# If the shell uses stderr, you should wrap it in a script +# that redirects stderr to stdout. +# +# Whenever the shell dies, it will be restarted. +# +# Clients can connect to the box via TCP (e.g. with telnet). +# Any number of clients can connect at the same time. Input +# of all clients is merged. Output of the shell is broadcasted +# to all clients and to stdout of the box. +# +# When started with the -paranoid option, the box only accepts +# connections from localhost. +# +# The box intercepts the following commands: +# exit or quit disconnect from the box +# clients dives a list of all connected clients +# kill terminates the shell +# + +set version {$Id: shellbox.tcl,v 1.1 2002/10/23 08:50:50 zimoch Exp $} + +proc createServer {port {paranoid 0}} { + if $paranoid { + socket -server connectionHandler -myaddr localhost $port + } else { + socket -server connectionHandler $port + } + puts "server started on port $port" +} + +proc bgerror {args} { + global errorInfo + puts stderr $errorInfo +} + +set channels {} + +proc connectionHandler {channel addr port} { + global channels command clientinfo + set peer [fconfigure $channel -peername] + set clientinfo($channel) [lindex $peer 1]:[lindex $peer 2] + fconfigure $channel -blocking no -buffering line + fileevent $channel readable "inputHandler $channel" + forwardOutput "**** new client $clientinfo($channel) ****" + puts $channel "**** '$command' running ****" + lappend channels $channel + puts $channel "clients:\n[getClientlist]" + forwardInput "\n" +} + +proc closeChannel {channel} { + global channels clientinfo + puts "closing connection to $clientinfo($channel)" + catch {puts $channel "**** see you later ****"} + set index [lsearch $channels $channel] + set channels [lreplace $channels $index $index] + forwardOutput "**** client $clientinfo($channel) logged out ****\n" + close $channel + unset clientinfo($channel) +} + +proc inputHandler {channel} { + set data [read $channel] + if [eof $channel] { + closeChannel $channel + return + } + binary scan $data H* hex + switch $data { + "clients\n" {forwardOutput "clients:\n[getClientlist]"} + "exit\n" {closeChannel $channel} + "quit\n" {closeChannel $channel} + "kill\n" {killProgram} + default { + switch $hex { + fff4fffd06 {closeChannel $channel} + 04 {closeChannel $channel} + ffedfffd06 {closeChannel $channel} + default {forwardInput $data} + } + } + } +} + +proc forwardInput {data} { + global pipe + puts -nonewline $pipe $data +} + +proc outputHandler {channel} { + set data [read $channel] + if [eof $channel] { + global command + forwardOutput "**** '$command' died ****\n" + startProgram + return + } + forwardOutput $data +} + +proc forwardOutput {data} { + global channels + catch {puts -nonewline $data} + foreach channel $channels { + catch {puts -nonewline $channel $data} + } +} + +proc getClientlist {} { + global channels clientinfo + set result "" + foreach channel $channels { + append result "$clientinfo($channel)\n" + } + return $result +} + +proc startProgram {} { + global command pipe + catch {close $pipe} + set pipe [open "|$command" RDWR] + fconfigure $pipe -blocking no -buffering line + gets $pipe + if [eof $pipe] exit + fileevent $pipe readable "outputHandler $pipe" + forwardOutput "**** '$command' started ****\n" +} + +proc killProgram {} { + global pipe command + close $pipe + forwardOutput "**** '$command' killed ****\n" + startProgram +} + +if {[lindex $argv 0] == "-v"} { + puts $version + exit 0 +} + +if {[lindex $argv 0] == "-paranoid"} { + set paranoid 1 + set argv [lrange $argv 1 end] +} else { + set paranoid 0 +} + +set port [lindex $argv 0] +set command [lrange $argv 1 end] +if {![string is integer $port] || [llength command] == 0} { + puts stderr "usage: [file tail $argv0] \[-paranoid\] \[args\]" + exit 1 +} + +createServer $port $paranoid +startProgram +vwait forever +