by
Reinhard Max, Jul 21 2003.
#!/usr/bin/tclsh
#
# Authors:
# 2003, Reinhard Max
# SSL support added by Pat Thoyts <[email protected]>
#
# This file may be used and distributed under the same conditions as Tcl/Tk
#
# Which port do we listen on. The second element can be an alternative socket
# command.
set ports {{4242 {}} {443 {}}}
# If you want to use SSL on port 443 then you need to provide a pair of OpenSSL
# files for the keys. We setup the tls package here and below we can specify
# what command to use to create the socket for each port.
if {![catch {package require tls 1.4}] } {
if {[file exists server-public.pem]} {
::tls::init \
-certfile server-public.pem \
-keyfile server-private.pem \
-ssl2 1 \
-ssl3 1 \
-tls1 0 \
-require 0 \
-request 0
# fix this if you change the ports variable above.
lset ports 1 1 ::tls::socket
}
}
# Which commands shall be understood by our protocol
set commands {
echo
I
help
listme
bye
reload
showstate
auth
}
array unset help
array set help {
help {Lists the available commands.}
{help <command>} {Prints a short help on the given command.}
{echo <arg>} {Return the given arguments.}
{I am <name>} {Tell the server your name and it will greet you.}
listme {Returns the Tcl script that implements this server.}
bye {Close the connection.}
showstate {Show the state array of the current connection.}
{auth <user> <password>} {Authenticate yourself.}
}
proc auth {user pass} {
upvar 1 state state
# Put your code for username/password lookup here.
set state(user) $user
set state(pass) $pass
set state(auth) 1
return OK
}
proc showstate {} {
upvar 1 state state
farray state
}
proc reload {args} {
after idle [list source [info script]]
return "Matrix reloaded! ;)"
}
proc echo {args} {
upvar 1 state state
return $args
}
proc I {args} {
set args [lrange $args 1 end]
return "Hello $args!"
}
proc listme {} {
set fd [open [info script]]
set script [read $fd]
close $fd
return $script
}
proc bye {} {
upvar 1 state state
after idle [list slaveServer::closeSocket $state(socket)]
return "Good bye!"
}
proc strip {string} {
regsub -all -line {^\s+} $string {}
}
proc max {a b} {expr {$a > $b ? $a : $b}}
proc farray {array {separator =} {pattern *}} {
upvar $array a
set names [lsort [array names a $pattern]]
set max 0
foreach name $names {
set max [max $max [string length $name]]
}
set result [list]
foreach name $names {
lappend result [format " %-*s %s %s" $max $name $separator $a($name)]
}
return [join $result "\n"]
}
proc help {{{<command>} {}}} {
global help
set helps [farray help - ${<command>}*]
if {$helps == ""} {
set helps "No help available for ${<command>}!"
}
return "\n$helps\n"
}
namespace eval slaveServer {
# procs that start with a lowercase letter are public
namespace export {[a-z]*}
variable serversocket
}
proc slaveServer::closeSocket {socket} {
variable $socket
upvar 0 $socket state
puts stderr "Closing $socket [clock format [clock seconds]]"
catch {close $socket}
unset state
}
# This gets called whenever a client connects
proc slaveServer::Server {socket host port} {
variable $socket
upvar 0 $socket state
# just to be sure ...
array unset state
set state(socket) $socket
set state(host) $host
set state(port) $port
puts stderr "New Connection: $socket $host $port [clock format [clock seconds]]"
fconfigure $socket -buffering line -blocking 0
fileevent $socket readable [namespace code [list Handler $socket]]
puts $socket "Welcome to this little demo server!"
puts $socket "Type \"help\" to see what you can do here."
}
# This gets called whenever a client sends a new line
# of data or disconnects
proc slaveServer::Handler {socket} {
variable $socket
upvar 0 $socket state
# Do we have a disconnect?
if {[eof $socket]} {
closeSocket $socket
return
}
# Does reading the socket give us an error?
if {[catch {gets $socket line} ret] == -1} {
puts stderr "Closing $socket"
closeSocket $socket
return
}
# Did we really get a whole line?
if {$ret == -1} return
# ... and is it not empty? ...
set line [string trim $line]
if {$line == ""} return
## ... and not an SSL request? ...
#if {[string index $line 0] == "\200"} {
# puts stderr "SSL request - closing connection"
# closeSocket $socket
# return
#}
# OK, so log it ...
puts stderr "$socket > $line"
# ... evaluate it, ...
if {[catch {slave eval $line} ret]} {
set ret "ERROR: $ret"
}
# ... log the result ...
puts stderr [regsub -all -line ^ $ret "$socket < "]
# ... and send it back to the client.
if {[catch {puts $socket $ret}]} {
closeSocket $socket
}
}
proc slaveServer::init {ports commands} {
variable serversockets
# (re-)create a safe slave interpreter
catch {interp delete slave}
interp create -safe slave
# remove all predefined commands from the slave
foreach command [slave eval info commands] {
slave hide $command
}
# link the commands for the protocol into the slave
puts -nonewline stderr "Initializing commands:"
foreach command $commands {
puts -nonewline stderr " $command"
interp alias slave $command {} $command
}
puts stderr ""
#(re-)create the server socket
if {[info exists serversockets]} {
foreach sock $serversockets {
catch {close $sock}
}
unset serversockets
}
puts -nonewline stderr "Opening sockets:"
foreach {port} $ports {
foreach {port socketCmd} $port {}
if {$socketCmd == {}} { set socketCmd ::socket }
puts -nonewline stderr " $port ($socketCmd)"
lappend serversockets \
[$socketCmd -server [namespace code Server] $port]
}
puts stderr ""
}
slaveServer::init $ports $commands
if {![info exists forever]} {
set forever 1
vwait forever
}