saslclient.tcl

Go to the documentation of this file.
00001 /*  saslclient.tcl - Copyright (C) 2005 Pat Thoyts <patthoyts@users.sf.net>*/
00002 /* */
00003 /*  This is a SMTP SASL test client. It connects to a SMTP server and uses */
00004 /*  the STARTTLS feature if available to switch to a secure link before */
00005 /*  negotiating authentication using SASL.*/
00006 /* */
00007 /*  $Id: saslclient.tcl,v 1.4 2005/10/06 14:48:02 patthoyts Exp $*/
00008 
00009 package require SASL
00010 package require base64
00011 catch {package require SASL::NTLM}
00012 
00013 variable user
00014 array  user =  {username "" password ""}
00015 if {[info exists env(http_proxy_user)]} {
00016      user = (username) $env(http_proxy_user)
00017 } else {
00018     if {[info exists env(USERNAME)]} {
00019          user = (username) $env(USERNAME)
00020     }
00021 }
00022 if {[info exists env(http_proxy_pass)]} {
00023      user = (password) $env(http_proxy_pass)
00024 }
00025 
00026 
00027 /*  SASLCallback --*/
00028 /* */
00029 /*  This procedure is called from the SASL library when it needs to get*/
00030 /*  information from the client application. The callback can be specified*/
00031 /*  with additional data elements and when called the SASL library will*/
00032 /*  append the SASL context, the command and possibly additional arguments.*/
00033 /*  The command specified the type of information needed.*/
00034 /*  So far we have:*/
00035 /*    login     users authorization identity (can be same as username).*/
00036 /*    username  users authentication identity*/
00037 /*    password  users authentication token*/
00038 /*    realm     the authentication realm (domain for NTLM)*/
00039 /*    hostname  the client's idea of its hostname (for NTLM)*/
00040 /* */
00041 ret  SASLCallback (type clientblob , type chan , type context , type command , type args) {
00042     global env
00043     variable user
00044     upvar #0 $context ctx
00045     switch -exact -- $command {
00046         login { 
00047             return "";# means use the authentication id
00048         }
00049         username {
00050             return $user(username)
00051         }
00052         password { 
00053             return $user(password)
00054         }
00055         realm {
00056             if {$ctx(mech) eq "NTLM"} {
00057                 return "$env(USERDOMAIN)"
00058             } else {
00059                 return [lindex [fconfigure $chan -peername] 1]
00060             }
00061         }
00062         hostname {
00063             return [info host]
00064         }
00065         default {
00066             return -code error "oops: client needs to write $command"
00067         }
00068     }
00069 }
00070 
00071 /*  SMTPClient --*/
00072 /* */
00073 /*  This implements a minimal SMTP client state engine. It will*/
00074 /*  do enough of the SMTP protocol to initiate a SSL/TLS link and*/
00075 /*  negotiate SASL parameters. Then it terminates.*/
00076 /* */
00077 ret  Callback (type chan , type eof , type line) {
00078     variable mechs
00079     variable tls
00080     variable ctx
00081     if {![info exists mechs]} {set mechs {}}
00082     if {$eof} { set ::forever 1; return }
00083     puts "> $line"
00084     switch -glob -- $line {
00085         "220 *" { 
00086             if {$tls} {
00087                 set tls 0
00088                 puts "| switching to SSL"
00089                 fileevent $chan readable {}
00090                 tls::import $chan
00091                 catch {tls::handshake $chan} msg
00092                 set mechs {}
00093                 fileevent $chan readable [list Read $chan ::Callback]
00094             }
00095             Write $chan "EHLO [info host]" 
00096         }
00097         "250 *" {
00098             if {$tls} {
00099                 Write $chan STARTTLS
00100             } else {
00101                 set supported [SASL::mechanisms]
00102                 puts "SASL mechanisms: $mechs\ncan do $supported"
00103                 foreach mech $mechs {
00104                     if {[lsearch -exact $supported $mech] != -1} {
00105                         
00106                         set ctx [SASL::new \
00107                                      -mechanism $mech \
00108                                      -callback [list [namespace origin SASLCallback] "client blob" $chan]]
00109                         Write $chan "AUTH $mech"
00110                         return
00111                     }
00112                 }
00113                 puts "! No matching SASL mechanism found"
00114             }
00115         }
00116         "250-AUTH*" {
00117             set line [string trim [string range $line 9 end]]
00118             set mechs [concat $mechs [split $line]]
00119         }
00120         "250-STARTTLS*" {
00121             if {![catch {package require tls}]} {
00122                 set tls 1
00123             }
00124         }
00125         "235 *" {
00126             SASL::cleanup $ctx
00127             Write $chan "QUIT" 
00128         }
00129         "334 *" {
00130             set challenge [string range $line 4 end]
00131             set e [string range $challenge end-5 end]
00132             puts "? '$e' [binary scan $e H* r; set r]"
00133             if {![catch {set dec [base64::decode $challenge]}]} {
00134                 set challenge $dec
00135             }
00136 
00137             set mech [set [subst $ctx](mech)]
00138             #puts "> $challenge"
00139             if {$mech eq "NTLM"} {puts ">CHA [SASL::NTLM::Debug $challenge]"}
00140             set code [catch {SASL::step $ctx $challenge} err]
00141             if {! $code} {
00142                 set rsp [SASL::response $ctx]
00143                 # puts "< $rsp"
00144                 if {$mech eq "NTLM"} {puts "<RSP [SASL::NTLM::Debug $rsp]"}
00145                 Write $chan [join [base64::encode $rsp] {}]
00146             } else {
00147                 puts stderr "sasl error: $err"
00148                 Write $chan "QUIT"
00149             }
00150         }
00151         "535*" {
00152             Write $chan QUIT
00153         }
00154         default {
00155         }
00156     }
00157 }
00158 
00159 /*  Write --*/
00160 /* */
00161 /*  Write data to the socket channel with logging.*/
00162 /* */
00163 ret  Write (type chan , type what) {
00164     puts "< $what"
00165     puts $chan $what
00166     return
00167 }
00168 
00169 /*  Read --*/
00170 /* */
00171 /*  fileevent handler reads data when available from the network socket*/
00172 /*  and calls the specified callback when it has recieved a complete line.*/
00173 /* */
00174 ret  Read (type chan , type callback) {
00175     if {[eof $chan]} {
00176         fileevent $chan readable {}
00177         puts stderr "eof"
00178         eval $callback [list $chan 1 {}]
00179         return
00180     }
00181     if {[gets $chan line] != -1} {
00182         eval $callback [list $chan 0 $line]
00183     }
00184     return
00185 }
00186 
00187 /*  connect -- */
00188 /* */
00189 /*  Open an SMTP session to test out the SASL implementation.*/
00190 /* */
00191 ret  connect ( type server , type port , optional username ={) {passwd {}}} {
00192     variable mechs ;  mechs =  {}
00193     variable tls  ;  tls =  0
00194 
00195     variable user
00196     if {$username ne {}} { user = (username) $username}
00197     if {$passwd ne {}} { user = (password) $passwd}
00198 
00199     puts "Connect to $server:$port"
00200      sock =  [socket $server $port]
00201     fconfigure $sock -buffering line -blocking 1 -translation {auto crlf}
00202     fileevent $sock readable [list Read $sock ::Callback]
00203     after 6000 {puts timeout ;  ::forever =  1}
00204     vwait ::forever
00205     catch {close $sock}
00206     return
00207 }
00208 
00209 if {!$tcl_interactive} {
00210     catch {eval ::connect $argv} res
00211     puts $res
00212 }
00213 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1