#!/bin/sh #and tcl/expect # # clientauth [OPTIONS] [ITS-USERCODE] # # Authenticate the client host to the university's firewall for unlimited # outbound access. Details are in # http://www.it.canterbury.ac.nz/internet/internet.htm # http://www.it.canterbury.ac.nz/internet/clientauthentication.htm # # WARNING Using Client Authentication means all traffic from this computer is # charged against the authenticated user code. DO NOT use from a machine that # allows multiple users to log in simultaneously unless you want others to use # the Internet at YOUR expense. # # Tested with tcl 8.0.5, 8.3.1, 8.3.3 # and expect 5.30, 5.31, 5.32 # on SuSE Linux. # Should work on any linux and any unix. # Run clientauth --help for usage. # # To Install: # 1) Save file as clientauth into a directory searched for executable files # ($PATH variable) # 2) Set the file's permissions to 700 (or 755 if you know what you're # doing) # 3) Adjust the variables in the variable section below, if necessary # # Latest version: # http://cantua.canterbury.ac.nz/~vku10/soft/clientauth # # Copyright (C) by Volker Kuhlmann # Released under the terms of the GNU General Public License (GPL) Version 2. # See http://www.gnu.org/ for details. # # Volker Kuhlmann # 10, 13, 21 Jul 2000 # 25, 26 Jul; 1, 20, 21, 22, 24, 25 Aug; 17, 21 Sep; 23, 29 Oct; 6 Nov 2000 # 23 May; 22 Jul 2001 # 11 Jan 2002 # # This is a TCL comment \ # Check whether expect is installed \ type expect >/dev/null 2>&1 || { echo \ "Oops - Can't find expect. Install it first!"; exit 20; } # This is a TCL comment \ # Deal with --authcmd before running expect (avoid quoting problems with tcl) \ if [ "$1" = "--authcmd" ]; then \ shift; echo "NYI"; exec "$@"; \ fi # This is a TCL comment \ # Start expect \ exec expect -- "$0" "$@" #exec tclsh "$0" "$@" ########## Modify these variables for your own use ########## # default proxy user ID: set its(dfltuser) $env(USER) # Your proxy password, if you insist on putting it here (if it's empty, the # script will search for it elsewhere, and then prompt if not found) set its_password "" # The university's proxy and port (not advisable to change it :-) ) set its(proxy) "ienabler.canterbury.ac.nz" set its(proxyport) "259" # Retries for proxy connection # Delay in seconds, for first, second, etc. retry set its(retry,delays) [list 1 60 300] # Number of retries. If this is higher than the number of values in # its(retry,delays), the last value in its(retry,delays) will be repeated as # often as necessary. set its(retry,max) 4 # Firewall prompt configuration # (If this string is empty, no attempt to load the config will be made) #set its(config_url) "http://www.it.canterbury.ac.nz/internet/client.txt" set its(config_url) "http://www.it.canterbury.ac.nz/internet/internet-enabler.cfg" # Firewall prompt configuration default (will be used if configuration can't be # loaded or its(config_url) is empty) set its(userprompt) "User:" set its(passprompt) "assword:" set its(userfail) "Access denied" # the following its(..) are not part of the official config! set its(userquery) "hoice:" set its(userpass) "User authorized" set its(select_on) "1" set its(select_off) "2" # Directory for clientauth files # (if it doesn't exist it will be created) set cmdarg(clientauth) "$env(HOME)/.clientauth" # # Log file. Empty string turns logging off. #set cmdarg(logfile) "$env(HOME)/clientauth.log" set cmdarg(logfile) "$cmdarg(clientauth)/clientauth.log" # # Lock & PID file. #set PIDfile "$env(HOME)/run/clientauth.pid" set PIDfile "$cmdarg(clientauth)/clientauth.pid" # # Resource file set Resourcefile "$cmdarg(clientauth)/clientauthrc" # Test whether we are already authenticated. Can we reach any of these hosts' # web servers: set cmdarg(authtesthosts) [list www.ccc.govt.nz www.lincoln.ac.nz] # Name of the telnet binary set telnet "telnet" # Name of the xterm binary (this executable must also take a few typical # arguments like -title) set xterm "xterm" # Name of the wget binary, and its arguments to retrieve a URL from stdin to # stdout (wget runs within the un-authenticated environment!) set wget "wget --input-file - --output-document - \ --proxy off --proxy-passwd=none --quiet \ --execute timestamping=off --execute recursive=off" # end of user-definable variable section ################################################################################ set VERSIONNUM "3.0.2" set VERSIONDATE "11 Jan 2002" set VERSION "VK $VERSIONNUM $VERSIONDATE " proc usage { } { global VERSION its PIDfile Resourcefile puts stdout " =============================================================================== WARNING Using Client Authentication means all traffic from this computer is charged against the authenticated user code. DO NOT use from a machine that allows multiple users to log in simultaneously unless you want others to use the Internet at YOUR expense. =============================================================================== clientauth \[-h|--help] \[OPTIONS] \[\[-u|--its-user] ITSUSER] Version $VERSION Authenticate the client host to the university's firewall for unlimited outbound access. Details are in http://www.it.canterbury.ac.nz/internet/internet.htm If \$DISPLAY is set, an xterm will be opened and the current shell freed. Options: -u|--its-user ITSUSER Use the given ITS user code instead of '$its(dfltuser)' -x|--no-X Never open an xterm window (this is the \"GUI\") -l|--no-log Don't write to the log file -c|--no-config Don't load the firewall prompt configuration from IT -p|--no-search Don't search \$HOME/.wgetrc for your password -d|--display DISP Set the \$DISPLAY environment variable to DISP -L|--lock Use locking when running clientauth. (default) --no-lock Don't use locking. Not recommended. -S|--stop Stop a previously started clientauth program. Previous clientauth must have been started with --lock!! Currently this unauthenticates, regardless! --authtest Test whether host is already authenticated. Sets exit status true if yes. --authcmd CMD \[..] Run CMD, and authenticate for duration of its running if necessary. (Not totally multi-tasking safe!) --dbg Generate debugging output (mainly of signals) --version Display version number and exit Notes: Requires the external programs expect/tcl, and kill; some options also require xterm, ping. When running without xterm, backgrounding should work. To terminate, foreground the job and press ^C, or kill expect (not telnet!). When running inside xterm do not send SIGSTOP to the xterm or expect processes! If the telnet process ID is 0, socket facilities of tcl are used instead of telnet. There should be no user-visible difference. TODO: - anything else which might be useful :-) E.g. - if one could get rid of telnet (done) and kill (probably possible) no external programs would be needed and the script would be indeed platform-independent - implement reading a resource file - get rid of xterm and use Tk " exit 2 } proc scan_cmd_args { } { global argv env global its cmdarg VERSION set its(user) $its(dfltuser) set cmdarg(nopasswdsearch) 0 set cmdarg(noloadconf) 0 set cmdarg(useX) 0 if { [catch {string length $env(DISPLAY)}] } { # variable is unset; set to empty string to avoid error set env(DISPLAY) "" } if {[string length $env(DISPLAY)] > 0} { set cmdarg(useX) 1 } set cmdarg(debug) 0 set cmdarg(lock) 1 set cmdarg(auth) "" set cmdarg(subargs) "--lock" set cmdarg(disconnected) 0 ;# if we can put things into the background... set n [llength $argv] for { set i 0 } { $i < $n } { incr i } { set arg [lindex $argv $i] set arg2 [lindex $argv [expr $i+1]] switch -glob -- $arg { -h - -help - --help { usage } --version { user "clientauth Version $VERSION"; exit } -u - --user - --ituser - --it-user - --its-user { set its(user) $arg2; incr i } -d - --display { set env(DISPLAY) $arg2; incr i } -X - --X { set cmdarg(useX) 1 } -x - --noX - --no-X { set cmdarg(useX) 0 } -p - --nosearch - --no-search { set cmdarg(nopasswdsearch) 1 lappend cmdarg(subargs) --no-search } -c - --noconfig - --no-config { set cmdarg(noloadconf) 1 lappend cmdarg(subargs) --no-config } -l - --nolog - --no-log { set cmdarg(logfile) "" lappend cmdarg(subargs) --no-log } -L - --lock { set cmdarg(lock) 1 lappend cmdarg(subargs) --lock } --nolock - --no-lock { set cmdarg(lock) 0 lappend cmdarg(subargs) --no-lock } -S - --stop { set cmdarg(lock) 1; set cmdarg(auth) "stop" lappend cmdarg(subargs) --stop } -s - --start { # is default action } --authtest { test_authenticated } --dbg - --debug { set cmdarg(debug) 1 lappend cmdarg(subargs) --debug } default { set its(user) $arg } } } } ;#scan_cmd_args # ensure $env(HOSTNAME) is set to something proc fix_hostname { } { global env if { [catch {string length $env(HOSTNAME)}] } { # we don't have a host name, so need to fill in something set env(HOSTNAME) "HOST" } if {[set i [string first "." $env(HOSTNAME)]] >= 0} { set env(HOSTNAME) [string range $env(HOSTNAME) 0 [expr $i-1]] } #puts stdout "==> $env(HOSTNAME)" } ;# fix_hostname # Produce current date in a useful format proc Date { } { # exec date "+%a %d %b %Y %T %Z" #clock format [clock seconds] -format "%a %d %b %Y %T %Z" clock format [clock seconds] -format "%a %d-%b-%y %T" } # Send text to user (stdout) proc user { output } { global cmdarg if {$cmdarg(disconnected)} { ;# no output to stdout if disconnected... logdbg "$output" ;# this copious stuff is hardly of use } else { puts stdout "$output" } } # Send text to user (stderr) proc user_err { output } { global cmdarg if {$cmdarg(disconnected)} { ;# no output to stderr if disconnected... logdbg "$output" } else { puts stderr "$output" } } # Display error message proc showerr {errmsg infomsg} { user "Error: $errmsg" if { [string length $infomsg] > 0} { user " $infomsg" } } # Send text to user and log file proc userlog { output } { user "$output" log_line "$output" } # Debugging output to user proc showdbg { line } { global cmdarg if {$cmdarg(debug)} {user_err "$line"} } # Debugging output to log file proc logdbg { line } { global cmdarg if {$cmdarg(debug)} {log_line "$line"} } # Debugging output to user and log file proc showlogdbg { line } { showdbg "$line" logdbg "$line" } # Append a line to the log file proc log_string { string } { global cmdarg # Don't log if no logfile given if {[string length $cmdarg(logfile)] == 0} { return } # open file with mode 600 if it doesn't exist; mode unchanged if it does if [catch {set fileid [ \ open $cmdarg(logfile) {CREAT WRONLY APPEND} 0600]} errmsg] { if {! $cmdarg(disconnected)} { ;# prevent loop... showerr $errmsg "While writing to log file." } return } puts $fileid $string close $fileid } # Log a dated line to the log file proc log_line { line } { log_string "[Date] \[[pid]\] $line" } # Start log with a blank line set _log_first 0 proc log_start { } { global _log_first VERSIONNUM VERSIONDATE if {!$_log_first} { set _log_first 1 log_string "" log_line "clientauth VK $VERSIONNUM $VERSIONDATE" } } # Append an "authorised" line to the log file proc log_auth { } { global env pid_telnet log_start set pids "[pid]/$pid_telnet" ;# PID of expect/telnet log_line "auth $env(HOSTNAME) by $env(USER), PIDs $pids" } # Append an "no-auth" line to the log file proc log_unauth { } { global PIDfile log_line "stopped PID [pid]" catch {file delete "$PIDfile"} } proc create_clientauth_dir { } { global cmdarg if {! [file isdirectory "$cmdarg(clientauth)"]} { user "Directory for clientauth does not exist." user "Creating dir: $cmdarg(clientauth)" if [catch {file mkdir "$cmdarg(clientauth)"} errmsg] { showerr $errmsg "Could not create directory." autherr } } } # Start xterm if needed # Test if called recursively proc xtermflag { {setflag ""} } { global env argv set flagval "nbx73264bb34yf" if {![string compare $setflag set]} { set env(FLAG) "$flagval" } else { # [array names env FLAG] # FLAG exists in env? Returns "FLAG" or "" set val "" catch {set val $env(FLAG)} set flag [expr ![string compare "$val" "$flagval"]] if {![string compare $setflag print]} { if {$flag} { user "FLAG DETECTED" user "args=$argv" foreach n $argv { user " = $n" } #user "sleeping" #sleep 10 #exit 8 } else { user "NO FLAG" user "args=$argv" } } return $flag } } ;# xtermflag proc start_xterm { } { global env cmdarg its xterm argv0 #xtermflag print if {$cmdarg(useX) && ![xtermflag]} { user "Starting xterm on '$env(DISPLAY)' ..." user "(Press Ctrl-C in xterm window to quit.)" xtermflag set lappend cmdarg(subargs) --its-user $its(user) #works eval exec $xterm [list \ -title "clientauth" \ -e /bin/sh -c "$argv0 [split $cmdarg(subargs)]" ] & # ; sleep 10" ] & #works eval exec $xterm [list \ -title "clientauth" \ -e /bin/sh -c "$argv0 $cmdarg(subargs)" ] & eval exec $xterm -title [list "clientauth $env(HOSTNAME)"] \ -e $argv0 $cmdarg(subargs) & exit 0 } } ;# start_xterm # Read clientauth configuration from URL proc get_config { } { global its cmdarg wget # Don't load config if no URL if {[string length $its(config_url)] == 0} { return } # Don't load if turned off if {$cmdarg(noloadconf)} { return } user "Loading configuration from IT..." # how do I get the words in $wget into the command pipe?? # [exec echo $config_url | [ ... $gwet] ] # works: #puts [exec sh -c "echo $config_url | $wget"] #if [catch { exec wget --version >/dev/null } errmsg] { # puts "Error: $errmsg" # puts " Using default firewall configuration, fingers crossed." # return -error #} ## uses wget #set cmd "| echo $its(config_url) | $wget" ##user $wget; user $cmd #if [catch { set input [open "$cmd" r] } errmsg] { # foreach line [split [gets $input] \n] { # } } ## uses tcl socket regexp "^http://(\[^/]*)(/.*)" "$its(config_url)" url host path #set host "localhost" ;# debug #puts "config: $url, $host, $path" ;# debug if [catch { set input [socket "$host" 80] } errmsg] { showerr $errmsg "Using default firewall configuration, fingers crossed." } else { # send HTTP headers puts $input "GET $path HTTP/1.0" puts $input "" flush $input set header 2 set sawline 0 set l 0 while 1 { set line "[gets $input]" if [eof $input] break set sawline 1 # discard received HTTP headers if {$header > 1} { #puts stdout "line 1: $line" set header 1 if {! [string match "HTTP/* 200 OK" "$line"]} { set header -1 break } continue } elseif $header { #puts stdout "$line" if {[string length $line] == 0} { set header 0 } continue } #if [string match \#* $line ] { continue } switch -regexp -- $line { {^[A-Za-z0-9_ ]*=} { #user $line foreach {key val} [split $line =] { #user "key=$key, val=$val" switch -exact $key { userprompt { incr l; set its(userprompt) $val } passprompt { incr l; set its(passprompt) $val } userfail { incr l; set its(userfail) $val } faillines { incr l; set its(faillines) $val } default { user_err "New key: $key" } } } } } } if {$l == 4} { user " ... success" } else { user " ... got $l values instead of 4!!" } if {$header < 0} { showerr "$line" "Unable to retrieve configuration\ - using defaults. Fingers crossed." } #user $sawline; #close $input # If no PPP is running, the open succeeds, no data is read, and the # close fails with "child terminated abnormally". (with wget anyway) if [catch {close $input} errmsg] { showerr $errmsg \ "Probably using default configuration, fingers crossed." } } } ;#get_config # Quick and dirty search for the user's ITS-password proc search_for_password { } { global env cmdarg its_password # Don't search if turned off if {$cmdarg(nopasswdsearch)} { return } # Look in $HOME/.wgetrc, unless there's none set file $env(HOME)/.wgetrc if {! [file readable $file]} { return } # Forget it if file not owned by current user if {! [file owned $file]} { user "Ignoring '$file'. You are not the owner!" return } # This "file open" may have security problems if it was in a publicly # writable directory if [catch { set input [open $file r] } errmsg] { user "Error: $errmsg" return } foreach line [split [read $input] \n] { switch -regexp -- $line { {^[A-Za-z0-9_ ]*=} { #puts $line foreach {key val} [split $line =] { #puts "key='$key', val='$val'" switch -exact [string trim $key] { proxy_passwd { set its_password [string trim $val] user "Obtained ITS password from $file." #user "its_password=$its_password" } } } } } } } ;#search_for_password # Program termination; if running under xterm generate delay for the user # to read the bottom of the screen # Note: when calling this from inside an ISR, ^C does nothing... proc autherr { {delay 7} } { global cmdarg if [expr $delay > 0 && [xtermflag]] { # make sure it get's deleted if we exit prematurely # There should be a better solution: hook into exithandler trap {delete_lockfile; exit 1} {SIGINT SIGTERM SIGQUIT} user "Exiting in a few seconds... (or press ^C)" sleep $delay if {$cmdarg(debug)} { user "debug - waiting for 30 more seconds" sleep 30 } } delete_lockfile exit 1 } # Signal handler proc trapexit {key sig} { global exitdelay user "received $key! exiting" log_line "trapped: _$sig\_, exiting" log_unauth if {[xtermflag]} {sleep $exitdelay} delete_lockfile exit } set exitdelay 1 # signal handler - special case for SIGSTOP proc sigstop { } { if [xtermflag] { user "Inside xterm - avoiding lockup by ignoring SIGTSTP." user "Do NOT send SIGSTOP to the xterm or expect processes!" logdbg "ignored _SIGTSTP_" } else { exec kill -STOP [pid] } } # signal debugging proc traplog {sig cmd} { showlogdbg "trapped: _$sig\_" #$cmd #if {[string length $cmd] > 0} {$cmd} eval $cmd } # user wants to quit program proc finish { key } { global finish user "pressed $key" log_line "received $key" set finish 1 } # quit; connection always terminates since 1 July 2001 proc finish_Jul2001 { } { global finish user "done - connection closed" log_line "done - connection closed" set finish 1 if {[xtermflag]} { sleep 3 } } # Handle the telnet login proc telnet_login { {choice} } { global its its_password cmdarg env telnet pid_telnet sid_telnet spawn_id global tty_spawn_id user_spawn_id error_spawn_id any_spawn_id spawn_id # check cmd arg is valid switch -- "$choice" { start { set its_select "$its(select_on)" } stop { set its_select "$its(select_off)" } default { showerr "telnet_login() called with illegal arg" \ "Must be none, \"start\", or \"stop\"." } } set stars "***************************************************************" user "Login as user '$its(user)' to '$its(proxy)' port '$its(proxyport)' ..." log_start #debug 1 #set its(proxy) "localhost"; set its(proxyport) "21" set env(TERM) vt100 set timeout 5 log_user 0 if {! $cmdarg(disconnected)} {log_user 1} if 0 {spawn $telnet "$its(proxy)" "$its(proxyport)"} \ else {spawn -open [socket "$its(proxy)" "$its(proxyport)"]} set pid_telnet [exp_pid] set sid_telnet $spawn_id user "*** spawned; awaiting response" user "$stars\n" logdbg "spawned PID $pid_telnet, sid $sid_telnet" # a) why does this "piece" need a LF after the "expect openbrace" ?? # (and why can't I put a real openbrace into this comment without the # thing crapping out? It's gotta be the first language where even a # comment must be syntactically correct!!) # b) -nocase and -re are incompatible (but docs don't say so) expect_after { timeout { error "Login timed out. Can't authenticate." } } #eof { # error "Opening connection to proxy failed" } # Can't have this in expect_after - or telnet will report eof # immediately at the first expect if I/O is redirected to /dev/null # when clientauth is started without xterm if {$pid_telnet != 0} { # this resets the timeout to when DNS result arrives expect { "Trying" { } eof { error "Opening connection to proxy failed" } } } expect { "Connection refused" { user " ******************************************************** *** Impossible to authenticate. *** *** The client host is not allowed to use the client *** *** authentication scheme. *** ********************************************************" autherr } -re "$its(userprompt) *$" { user "\n*** Sending user code:" send "$its(user)\r" } eof { error "Opening connection to proxy failed" } } expect { -re "$its(passprompt) *$" { if {[llength "$its_password"] > 0} { user "\n*** Sending password:" send "$its_password\r" } else { set timeout -1 stty -echo expect_tty -re "(.*)\n" set passwd $expect_out(1,string) user "\n*** Sending password:" send "$passwd\r" #send_user "Your passwd was $passwd\n" stty echo } } } expect { "$its(userquery)" { send "$its_select\r" } } expect { "$its(userfail)" { user "\n$stars\n*** authentication failed;" user "*** check user name and password and re-run program" autherr } "$its(userpass)" { user "$stars\n*** authenticated; press Ctrl-C to terminate" } } # expect ".*" ;# read and discard remaining characters #expect_after if {![string compare $choice start]} { user "*** (expect: PID = [pid], spawned telnet PID = $pid_telnet)" log_auth } } ;# telnet_login # Wait for established connection proc telnet_wait { } { global cmdarg env pid_telnet sid_telnet exitdelay finish trap_usr1 set trap_usr1 0 if 0 { set spawn_id $sid_telnet set file [exp_open] showlogdbg "start eof loop file, $sid_telnet, $file" while {! [eof $file]} { #gets $file ;# blocks all signals #read $file ;# likewise ; ;# works, but sucks 100% CPU } showlogdbg "stop eof loop file, [catch {close $file}]" } elseif [xtermflag] { # shell (tcsh) job control works (^C triggers trap) # but it also reads the terminal after some terminal output, causing # [1] + Suspended (tty input) clientauth -x --dbg USER # also see comments for the expect below # The exp_continue global tty_spawn_id user_spawn_id error_spawn_id spawn_id any_spawn_id logdbg "$tty_spawn_id, $user_spawn_id, $error_spawn_id, $any_spawn_id,\ $sid_telnet; $pid_telnet" logdbg "start eof loop xterm" #set timeout -1 #stty raw YES this reacts immediately after pressing q, but it # leaves the tty in raw mode also for any signal handlers; # could be tidied up but hey expect { -i $sid_telnet timeout { if {! $trap_usr1} exp_continue } eof { showlogdbg "EOF on telnet" } -i $tty_spawn_id q { finish "q (tty)" } Q { finish "Q (tty)" } eof { finish "^D (tty)" } -i $user_spawn_id q { finish "q (user)" } Q { finish "Q (user)" } eof { finish "^D (user)" } -i $any_spawn_id "." { showlogdbg "CHAR '$expect_out(buffer)'"; exp_continue } } logdbg "stop eof loop xterm" finish_Jul2001 ;# FIXME } elseif 1 { # Try without reading tty - job control works # But we can't act on q or Q - too damn bad. # MAKE SURE this interacts with the spawned process, not user or tty!! # ($spawn_id is not global...) logdbg "start expect wait loop, $pid_telnet, $sid_telnet" #set timeout -1 # An infinite timeout disables the SIGUSR1 exit (default 10s) expect { -i $sid_telnet timeout { if {! $trap_usr1} exp_continue } eof { showlogdbg "EOF on telnet" } } logdbg "end expect wait loop" } else { # When trying to background (either with ^Z and bg or running # with &) throws a # [1] + Suspended (tty output) clientauth --dbg -x USER # When started with I/O redirected to /dev/null, with cause error # interact: invalid spawn id (0) # It seems this version can't be backgrounded properly - no good # for terminal use global tty_spawn_id interact { -reset \003 { set exitdelay 0; finish "^C"; return } -reset q { finish "q"; return } -reset Q { finish "Q"; return } -reset \032 { showdbg "interact - ^Z"; sigstop } } } } ;# telnet_wait # Establish connection and wait proc do_connect { {choice ""} } { global cmdarg env pid_telnet sid_telnet exitdelay finish telnet_login "$choice" # cause expect to exit: trap {showdbg "_SIGINT_"; set exitdelay 0 trapexit "ctrl-C" "SIGINT"} {SIGINT} trap {showdbg "_SIGTERM_"; trapexit "SIGTERM" "SIGTERM"} {SIGTERM} # SIGTERM: default is to exit with "exit" # let's see what's going on: # Killing telnet sends SIGCHLD. trap {traplog "SIGCHLD" { } } SIGCHLD trap {traplog "SIGCONT" { } } SIGCONT trap {traplog "SIGTSTP" sigstop} SIGTSTP #trap {puts stderr "SIGSTOP"} SIGSTOP # expect does not allow traping SIGSTOP (error when trying to set trap) # default for SIGHUP and SIGQUIT: die without calling exit # That might not be a bad idea in case the signal handler has an error... trap {trapexit "SIGHUP" "SIGHUP"} SIGHUP trap {trapexit "SIGQUIT" "SIGQUIT"} SIGQUIT # Only useful for debugging trap {traplog "SIGUSR1" {global trap_usr1; set trap_usr1 1} } SIGUSR1 # All of this isn't any good. #if {[fork] != 0} {logdbg "forked - exit"; sleep 1; exit} #user "*** forked" #user "*** (expect: PID = [pid], spawned telnet PID = $pid_telnet)" #disconnect #logdbg "forked - disconnected" #set cmdarg(disconnected) 1 # We need to a) read the password before disconnecting, b) make sure we # don't generate I/O. # Never get a SIGCHLD, telnet is no longer a child of expect after fork # If anything happens the whole thing just exits without writing logs # We probably would want to fork before spawning telnet # Wait until keypress: ^C, q, Q flush stdout; flush stderr set finish 0 if {![string compare "$choice" start]} { telnet_wait } else { finish_Jul2001 } if $finish { #wait ;# this will hang -> SIGKILL expect #wait -nowait ;# probably not needed set line "[wait -nowait]"; logdbg "wn: $line" user "Ok, terminating" log_unauth sleep $exitdelay delete_lockfile exit } else { # YES!!! MADE IT! # This makes the telnet process exit, rather than becoming defunct. #wait set line "[wait]"; logdbg "w: $line" } } ;# do_connect # Return the delay before the next connect retry proc get_reconnect_delay {{reset {}}} { global its recon_attempt if {"$reset" == "reset"} { set recon_attempt 0 #showdbg "reset" } else { incr recon_attempt #showdbg "recon_attempt = $recon_attempt" if {$recon_attempt > $its(retry,max)} { return -1 } else { set d [lindex $its(retry,delays) [expr $recon_attempt - 1]] if {"_$d" == "_"} { set d [lindex $its(retry,delays) end] } return $d } } } ;# get_reconnect_delay # Keep the connection open # Main loop; does not return. proc authenticate { } { global errorCode errorInfo # errorCode only gives e.g. "POSIX ECONNREFUSED {connection refused}" # errorInfo gives the same plus stack trace # debug #global its; set its(proxy) "localhost"; set its(proxyport) "789" # keep authenticated forever get_reconnect_delay reset while { 1 } { if [catch {do_connect start} errmsg] { if {[string length "$errmsg"] == 0} { # When the firewall gets restarted in the morning, the # connection breaks but tcl does not report an error set errmsg "(connection broke - no error message)" } showerr "$errmsg" "While trying to connect to proxy." log_line "$errmsg" # possibilities here: # initial connect failed, e.g. exec telnet / wrong password / .. # connection broke at random # timed reconnect failed # we treat the first 2 the same (i.e. repeat connect...) set delay "[get_reconnect_delay]" #showdbg "delay = '$delay'" if {$delay < 0} { userlog "Too many repeated failures. Giving up." autherr } else { user "Attempting to reconnect in $delay seconds." logdbg "attempting to reconnect in $delay\s" sleep $delay } } else { finish_Jul2001 exit ;# FIXME get_reconnect_delay reset user "Warning: authentication connection terminated.\ Attempting to reconnect." logdbg "attempting to reconnect" } # debug #global its; set its(proxy) "localhost"; set its(proxyport) "789" } } ;# authenticate # Lock period start proc lock_start { } { global PIDfile exitdelay # check if a lock file already exists if [file readable "$PIDfile"] { if [catch {check_lockfile}] { user "Clientauth was already running. If that is not true, delete the" user "lock file $PIDfile." if [xtermflag] {sleep 5} exit 0 } else { user "Ignoring stale lock file" } } create_lockfile } ;# lock_start # Check whether the lock file is stale. # This currently only works under linux, otherwise it does nothing. proc check_lockfile { } { global PIDfile # do we have a process directory under /proc ? if {! [file isdirectory "/proc/[pid]"]} { error "lock" } set fileid [open "$PIDfile"] set process [gets $fileid] # Does the process in the lock file exist? if {[file isdirectory "/proc/$process"]} { error "lock" } } # Lock period end # (Calling "kill" is probably not portable) proc lock_stop { } { global PIDfile # Jul 2001: login to unauthenticate start_xterm get_config search_for_password do_connect stop if [catch {set fileid [open "$PIDfile"]} errmsg] { showerr $errmsg \ "There was no clientauth running (could not open lock file)." log_line "couldn't open lock file" exit 0 } else { catch {set process [gets $fileid]} catch {exec kill "$process"} errmsg close $fileid if {[string length "$errmsg"] > 0} { showerr $errmsg "Removing probably stale lockfile." log_line "no process $process, deleting lock file" delete_lockfile exit 1 } exit 0 } } ;# lock_stop proc create_lockfile { } { global PIDfile cmdarg # do nothing if locking was not requested if {! $cmdarg(lock)} return # # create lock file directory if necessary # regexp ".*/" "$PIDfile" piddir # if {! [file isdirectory "$piddir"]} { # user "Directory for lock file does not exist." # user "Creating dir: $piddir" # if [catch {file mkdir "$piddir"} errmsg] { # showerr $errmsg "Could not create directory." # autherr # } # } # create lock file if [catch {set fileid [open "$PIDfile" w] } errmsg] { showerr $errmsg "Could not create lock file." autherr } else { puts $fileid [pid] close $fileid } } ;# create_lockfile proc delete_lockfile { } { global PIDfile cmdarg # do nothing if locking was not requested if {! $cmdarg(lock)} return catch {file delete "$PIDfile"} } # Open http channel; channel ID returned, != "" if success proc http_connect {host path usererrmsg} { set httpchannel "" if [catch { set httpchannel [socket "$host" 80] } errmsg] { showerr $errmsg $usererrmsg } else { # send HTTP headers puts $httpchannel "GET $path HTTP/1.0" puts $httpchannel "" flush $httpchannel } return $httpchannel } # A quick test whether we are already authenticated: proc test_authenticated { } { global cmdarg user "Testing whether we are authenticated:" set auth 1 trap "exit 1" {HUP INT QUIT TERM TSTP} foreach host $cmdarg(authtesthosts) { user "Trying http://$host:80/" set input [http_connect $host "/" "not reachable"] if {"$input" != ""} { if [catch { set line [gets $input] } errmsg] { user " not reachable" } else { if { [regexp -line "^HTTP.*Unauthorized\$" "$line"] } { user " not authorized" } else { set auth 0 break } } if [catch {close $input} errmsg] { } } } exit $auth } ;# test_authenticated fix_hostname scan_cmd_args #user "its_user='$its(user)', useX='$cmdarg(useX)', \ nopasswdsearch='$cmdarg(nopasswdsearch)' \ noloadconf='$cmdarg(noloadconf)' \ DISPLAY='$env(DISPLAY)'" create_clientauth_dir # check for a stop request before starting xterm if {$cmdarg(lock) && ("$cmdarg(auth)" == "stop")} lock_stop start_xterm if {$cmdarg(lock)} lock_start # this really must be after the xterm, in case clientauth restarts itself # inside an xterm #set passprompt "sometestprompt" get_config #user "user: '$userprompt', pass: '$passprompt', fail: '$userfail'" search_for_password authenticate ;# does not return delete_lockfile