autoproxy.tcl

Go to the documentation of this file.
00001 /*  autoproxy.tcl - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sf.net>*/
00002 /* */
00003 /*  On Unix the standard for identifying the local HTTP proxy server*/
00004 /*  seems to be to use the environment variable http_proxy or ftp_proxy and*/
00005 /*  no_proxy to list those domains to be excluded from proxying.*/
00006 /* */
00007 /*  On Windows we can retrieve the Internet Settings values from the registry*/
00008 /*  to obtain pretty much the same information.*/
00009 /* */
00010 /*  With this information we can setup a suitable filter procedure for the */
00011 /*  Tcl http package and arrange for automatic use of the proxy.*/
00012 /* */
00013 /*  Example:*/
00014 /*    package require autoproxy*/
00015 /*    autoproxy::init*/
00016 /*    set tok [http::geturl http://wiki.tcl.tk/]*/
00017 /*    http::data $tok*/
00018 /* */
00019 /*  To support https add:*/
00020 /*    package require tls*/
00021 /*    http::register https 443 ::autoproxy::tls_socket*/
00022 /* */
00023 /*  @(#)$Id: autoproxy.tcl,v 1.9 2007/03/12 22:53:25 patthoyts Exp $*/
00024 
00025 package require http;                   /*  tcl*/
00026 package require uri;                    /*  tcllib*/
00027 package require base64;                 /*  tcllib*/
00028 
00029 namespace ::autoproxy {
00030     variable rcsid {$Id: autoproxy.tcl,v 1.9 2007/03/12 22:53:25 patthoyts Exp $}
00031     variable version 1.4
00032     variable options
00033 
00034     if {! [info exists options]} {
00035         array  options =  {
00036             proxy_host ""
00037             proxy_port 80
00038             no_proxy   {}
00039             basic      {} 
00040             authProc   {}
00041         }
00042     }
00043 
00044     variable winregkey
00045      winregkey =  [join {
00046         HKEY_CURRENT_USER
00047         Software Microsoft Windows
00048         CurrentVersion "Internet Settings"
00049     } \\]
00050 }
00051 
00052 /*  -------------------------------------------------------------------------*/
00053 /*  Description:*/
00054 /*    Obtain configuration options for the server.*/
00055 /* */
00056 ret  ::autoproxy::cget (type option) {
00057     variable options
00058     switch -glob -- $option] {
00059         -host -
00060         -proxy_h* { set options(proxy_host) }
00061         -port -
00062         -proxy_p* { set options(proxy_port) }
00063         -no*      { set options(no_proxy) }
00064         -basic    { set options(basic) }
00065         -authProc { set options(authProc) }
00066         default {
00067             set err [join [lsort [array names options]] ", -"]
00068             return -code error "bad option \"$option\":\
00069                        must be one of -$err"
00070         }
00071     }
00072 }
00073 
00074 /*  -------------------------------------------------------------------------*/
00075 /*  Description:*/
00076 /*   Configure the autoproxy package settings.*/
00077 /*   You may only configure one type of authorisation at a time as once we hit*/
00078 /*   -basic, -digest or -ntlm - all further args are passed to the protocol*/
00079 /*   specific script.*/
00080 /* */
00081 /*   Of course, most of the point of this package is to fill as many of these*/
00082 /*   fields as possible automatically. You should call autoproxy::init to*/
00083 /*   do automatic configuration and then call this method to refine the details.*/
00084 /* */
00085 ret  ::autoproxy::configure (type args) {
00086     variable options
00087 
00088     if {[llength $args] == 0} {
00089         foreach {opt value} [array get options] {
00090             lappend r -$opt $value
00091         }
00092         return $r
00093     }
00094 
00095     while {[string match "-*" [set option [lindex $args 0]]]} {
00096         switch -glob -- $option {
00097             -host - 
00098             -proxy_h* { set options(proxy_host) [Pop args 1]}
00099             -port - 
00100             -proxy_p* { set options(proxy_port) [Pop args 1]}
00101             -no*      { set options(no_proxy) [Pop args 1] }
00102             -basic    { Pop args; configure:basic $args ; break }
00103             -authProc { set options(authProc) [Pop args] }
00104             --        { Pop args; break }
00105             default {
00106                 set opts [join [lsort [array names options]] ", -"]
00107                 return -code error "bad option \"$option\":\
00108                        must be one of -$opts"
00109             }
00110         }
00111         Pop args
00112     }
00113 }
00114 
00115 /*  -------------------------------------------------------------------------*/
00116 /*  Description:*/
00117 /*   Initialise the http proxy information from the environment or the*/
00118 /*   registry (Win32)*/
00119 /* */
00120 /*   This procedure will load the http package and re-writes the*/
00121 /*   http::geturl method to add in the authorisation header.*/
00122 /* */
00123 /*   A better solution will be to arrange for the http package to request the*/
00124 /*   authorisation key on receiving an authorisation reqest.*/
00125 /* */
00126 ret  ::autoproxy::init (optional httpproxy ={) {no_proxy {}}} {
00127     global tcl_platform
00128     global env
00129     variable winregkey
00130     variable options
00131 
00132     /*  Look for standard environment variables.*/
00133     if {[string length $httpproxy] > 0} {
00134         
00135         /*  nothing to do*/
00136 
00137     } elseif {[info exists env(http_proxy)]} {
00138          httpproxy =  $env(http_proxy)
00139         if {[info exists env(no_proxy)]} {
00140              no = _proxy $env(no_proxy)
00141         }
00142     } else {
00143         if {$tcl_platform(platform) == "windows"} {
00144             package require registry 1.0
00145             array  reg =  {ProxyEnable 0 ProxyServer "" ProxyOverride {}}
00146             catch {
00147                 /*  IE5 changed ProxyEnable from a binary to a dword value.*/
00148                 switch -exact -- [registry type $winregkey "ProxyEnable"] {
00149                     dword {
00150                          reg = (ProxyEnable) [registry get $winregkey "ProxyEnable"]
00151                     }
00152                     binary {
00153                          v =  [registry get $winregkey "ProxyEnable"]
00154                         binary scan $v i reg(ProxyEnable)
00155                     }
00156                     default { 
00157                         return -code error "unexpected type found for\
00158                                ProxyEnable registry item"
00159                     }
00160                 }
00161                  reg = (ProxyServer) [GetWin32Proxy http]
00162                  reg = (ProxyOverride) [registry get $winregkey "ProxyOverride"]
00163             }
00164             if {![string is bool $reg(ProxyEnable)]} {
00165                  reg = (ProxyEnable) 0
00166             }
00167             if {$reg(ProxyEnable)} {
00168                  httpproxy =  $reg(ProxyServer)
00169                  no = _proxy  $reg(ProxyOverride)
00170             }
00171         }
00172     }
00173     
00174     /*  If we found something ...*/
00175     if {[string length $httpproxy] > 0} {
00176         /*  The http_proxy is supposed to be a URL - lets make sure.*/
00177         if {![regexp {\w://.*} $httpproxy]} {
00178              httpproxy =  "http://$httpproxy"
00179         }
00180         
00181         /*  decompose the string.*/
00182         array  proxy =  [uri::split $httpproxy]
00183 
00184         /*  turn the no_proxy value into a tcl list*/
00185          no = _proxy [string map {; " " , " "} $no_proxy]
00186 
00187         /*  configure ourselves*/
00188         configure -proxy_host $proxy(host) \
00189             -proxy_port $proxy(port) \
00190             -no_proxy $no_proxy
00191 
00192         /*  Lift the authentication details from the environment if present.*/
00193         if {[string length $proxy(user)] < 1 \
00194                 && [info exists env(http_proxy_user)] \
00195                 && [info exists env(http_proxy_pass)]} {
00196              proxy = (user) $env(http_proxy_user)
00197              proxy = (pwd)  $env(http_proxy_pass)
00198         }
00199 
00200         /*  Maybe the proxy url has authentication parameters?*/
00201         /*  At this time, only Basic is supported.*/
00202         if {[string length $proxy(user)] > 0} {
00203             configure -basic -username $proxy(user) -password $proxy(pwd)
00204         }
00205 
00206         /*  setup and configure the http package to use our proxy info.*/
00207         http::config -proxyfilter [namespace origin filter]
00208     }
00209     return $httpproxy
00210 }
00211 
00212 /*  autoproxy::GetWin32Proxy -- */
00213 /* */
00214 /*  Parse the Windows Internet Settings registry key and return the*/
00215 /*  protocol proxy requested. If the same proxy is in use for all */
00216 /*  protocols, then that will be returned. Otherwise the string is*/
00217 /*  parsed. Example:*/
00218 /*   ftp=proxy:80;http=proxy:80;https=proxy:80*/
00219 /* */
00220 ret  ::autoproxy::GetWin32Proxy (type protocol) {
00221     variable winregkey
00222     set proxies [split [registry get $winregkey "ProxyServer"] ";"]
00223     foreach proxy $proxies {
00224         if {[string first = $proxy] == -1} {
00225             return $proxy
00226         } else {
00227             foreach {prot host} [split $proxy =] break
00228             if {[string compare $protocol $prot] == 0} {
00229                 return $host
00230             }
00231         }
00232     }
00233     return -code error "failed to identify an '$protocol' proxy"
00234 }
00235 
00236 /*  -------------------------------------------------------------------------*/
00237 /*  Description:*/
00238 /*   Pop the nth element off a list. Used in options processing.*/
00239 ret  ::autoproxy::Pop (type varname , optional nth =0) {
00240     upvar $varname args
00241     set r [lindex $args $nth]
00242     set args [lreplace $args $nth $nth]
00243     return $r
00244 }
00245 
00246 /*  -------------------------------------------------------------------------*/
00247 /*  Description*/
00248 /*    An example user authentication procedure.*/
00249 /*  Returns:*/
00250 /*    A two element list consisting of the users authentication id and */
00251 /*    password. */
00252 ret  ::autoproxy::defAuthProc (optional user ={) {passwd {}} {realm {}}} {
00253     if {[string length $realm] > 0} {
00254          title =  "Realm: $realm"
00255     } else {
00256          title =  {}
00257     }
00258 
00259     /*  If you are using BWidgets then the following will do:*/
00260     /* */
00261     /*     package require BWidget*/
00262     /*     return [PasswdDlg .defAuthDlg -parent {} -transient 0 \*/
00263     /*          -title $title -logintext $user -passwdtext $passwd]*/
00264     /* */
00265     /*  if you just have Tk and no BWidgets --*/
00266     
00267      dlg =  [toplevel .autoproxy_defAuthProc -class Dialog]
00268     wm title $dlg "$realm"
00269     label $dlg.ll -text Login -underline 0 -anchor w
00270     entry $dlg.le -textvariable [namespace current]::${dlg}:l
00271     label $dlg.pl -text Password -underline 0 -anchor w
00272     entry $dlg.pe -show * -textvariable [namespace current]::${dlg}:p
00273     button $dlg.ok -text OK -default active -width -11 \
00274         -command [list  [namespace =  current]::${dlg}:ok 1]
00275     grid $dlg.ll $dlg.le -sticky news
00276     grid $dlg.pl $dlg.pe -sticky news
00277     grid $dlg.ok - -sticky e
00278     grid columnconfigure $dlg 1 -weight 1
00279     bind $dlg <Return> [list $dlg.ok invoke]
00280     bind $dlg <Alt-l> [list focus $dlg.le]
00281     bind $dlg <Alt-p> [list focus $dlg.pe]
00282     variable ${dlg}:l $user; variable ${dlg}:p $passwd
00283     variable ${dlg}:ok 0
00284     wm deiconify $dlg; focus $dlg.pe; update idletasks
00285      old =  [::grab current]; grab $dlg
00286     tkwait variable [namespace current]::${dlg}:ok
00287     grab release $dlg ; if {[llength $old] > 0} {::grab $old}
00288      r =  [list [ ${dlg = }:l] [ ${dlg = }:p]]
00289     un ${dlg = }:l; un ${dlg = }:p; un ${dlg = }:ok
00290     destroy $dlg
00291     return $r
00292 }
00293 
00294 /*  -------------------------------------------------------------------------*/
00295 
00296 /*  Description:*/
00297 /*   Implement support for the Basic authentication scheme (RFC 1945,2617).*/
00298 /*  Options:*/
00299 /*   -user userid  - pass in the user ID (May require Windows NT domain*/
00300 /*                   as DOMAIN\\username)*/
00301 /*   -password pwd - pass in the user's password.*/
00302 /*   -realm realm  - pass in the http realm.*/
00303 /* */
00304 ret  ::autoproxy::configure:basic (type arglist) {
00305     variable options
00306     array set opts {user {} passwd {} realm {}}
00307     foreach {opt value} $arglist {
00308         switch -glob -- $opt {
00309             -u* { set opts(user) $value}
00310             -p* { set opts(passwd) $value}
00311             -r* { set opts(realm) $value}
00312             default {
00313                 return -code error "invalid option \"$opt\": must be one of\
00314                      -username or -password or -realm"
00315             }
00316         }
00317     }
00318 
00319     # If nothing was provided, try calling the authProc
00320     if {$options(authProc) != {} \
00321             && ($opts(user) == {} || $opts(passwd) == {})} {
00322         set r [$options(authProc) $opts(user) $opts(passwd) $opts(realm)]
00323         set opts(user) [lindex $r 0]
00324         set opts(passwd) [lindex $r 1]
00325     }
00326 
00327     # Store the encoded string to avoid re-encoding all the time.
00328     set options(basic) [list "Proxy-Authorization" \
00329                             [concat "Basic" \
00330                                  [base64::encode $opts(user):$opts(passwd)]]]
00331     return
00332 }
00333 
00334 /*  -------------------------------------------------------------------------*/
00335 /*  Description:*/
00336 /*   An http package proxy filter. This attempts to work out if a request*/
00337 /*   should go via the configured proxy using a glob comparison against the*/
00338 /*   no_proxy list items. A typical no_proxy list might be*/
00339 /*    [list localhost *.my.domain.com 127.0.0.1]*/
00340 /* */
00341 /*   If we are going to use the proxy - then insert the proxy authorization*/
00342 /*   header.*/
00343 /* */
00344 ret  ::autoproxy::filter (type host) {
00345     variable options
00346 
00347     if {$options(proxy_host) == {}} {
00348         return {}
00349     }
00350     
00351     foreach domain $options(no_proxy) {
00352         if {[string match $domain $host]} {
00353             return {}
00354         }
00355     }
00356     
00357     # Add authorisation header to the request (by Anders Ramdahl)
00358     catch {
00359         upvar state State
00360         if {$options(basic) != {}} {
00361             set State(-headers) [concat $options(basic) $State(-headers)]
00362         }
00363     }
00364     return [list $options(proxy_host) $options(proxy_port)]
00365 }
00366 
00367 /*  autoproxy::tls_connect --*/
00368 /* */
00369 /*  Create a connection to a remote machine through a proxy*/
00370 /*  if necessary. This is used by the tls_socket comment for */
00371 /*  use with the http package but can also be used more generally*/
00372 /*  provided your proxy will permit CONNECT attempts to ports*/
00373 /*  other than port 443 (many will not).*/
00374 /* */
00375 ret  ::autoproxy::tls_connect (type host , type port , optional useragent ={)} {
00376     variable options
00377     if {[string length $options(proxy_host)] > 0} {
00378          s =  [::socket $options(proxy_host) $options(proxy_port)]
00379         fconfigure $s -blocking 1 -buffering line -translation crlf
00380         puts $s "CONNECT $host:$port HTTP/1.1"
00381         puts $s "Host: $host"
00382         if {[string length $useragent] > 0} {
00383             puts $s "User-Agent: $useragent"
00384         }
00385         puts $s "Proxy-Connection: keep-alive"
00386         puts $s "Connection: keep-alive"
00387         if {[string length $options(basic)] > 0} {
00388             puts $s [join $options(basic) ": "]
00389         }
00390         puts $s ""
00391 
00392          block =  ""
00393         while {[gets $s r] > 0} {
00394             lappend block $r
00395         }
00396          result =  [lindex $block 0]
00397          code =  [lindex [split $result { }] 1]
00398 
00399         if {$code >= 200 && $code < 300} {
00400             fconfigure $s -blocking 1 -buffering none -translation binary
00401             tls::import $s
00402         } else {
00403             close $s
00404             return -code error $result
00405         }
00406     } else {
00407          s =  [eval [linsert $args 0 ::tls::socket]]
00408     }
00409     return $s
00410 }
00411 
00412 /*  autoproxy::tls_socket --*/
00413 /* */
00414 /*  This can be used to handle TLS connections indenpendantly of*/
00415 /*  proxy presence. It can only be used with the Tcl http package*/
00416 /*  and to use it you must do:*/
00417 /*     http::register https 443 ::autoproxy::tls_socket*/
00418 /*  After that you can use the http::geturl command to access*/
00419 /*  secure web pages and any proxy details will be handled for you.*/
00420 /* */
00421 ret  ::autoproxy::tls_socket (type args) {
00422     variable options
00423 
00424     # Look into the http package for the actual target. The function
00425     # has unfortunately not passed these as parameters.
00426     upvar host host port port
00427     set s [tls_connect $host $port [http::config -useragent]]
00428 
00429     # record the tls connection status in the http state array.
00430     upvar state state
00431     tls::handshake $s
00432     set state(tls_status) [tls::status $s]
00433 
00434     return $s
00435 }
00436 
00437 /*  -------------------------------------------------------------------------*/
00438 
00439 package provide autoproxy $::autoproxy::version
00440 
00441 /*  -------------------------------------------------------------------------*/
00442 /* */
00443 /*  Local variables:*/
00444 /*    mode: tcl*/
00445 /*    indent-tabs-mode: nil*/
00446 /*  End:*/
00447 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1