SOCKS proxying support.
authorPat Thoyts <patthoyts@users.sourceforge.net>
Wed, 2 Dec 2009 23:37:56 +0000 (23:37 +0000)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Wed, 2 Dec 2009 23:37:56 +0000 (23:37 +0000)
This patch enables SockSpy to listen for SOCKS4 and 4a requests. This is
similar to the forwarding mode as SOCKS is protocol independent and can
be used to forward connections to any port.

Signed-off-by: Pat Thoyts <patthoyts@users.sourceforge.net>
bin/sockspy.tcl

index d46150eba3038cbac86c067e0264cb242694464f..e7d03bbcd398f9a0f32e336db3d1d066333c3e2e 100644 (file)
@@ -668,16 +668,17 @@ proc ERROR {emsg} {
 # 
 proc sockReadable {fromSock toSock who} {
     global state
-    set data [read $fromSock]
-    if {[string length $data] == 0} {
+    set died [catch {set data [read $fromSock]} err]
+    if {$died || [eof $fromSock]} {
        close $fromSock
        catch { close $toSock }
+       if {$died} { INFO $err }
        INFO "----- closed connection $fromSock \[eof\] -----"
        INFO "waiting for new connection..." 
        return
     }
-    if {$toSock == ""} {                       ;# Make proxy connection
-       if {[catch {ProxyConnect $fromSock $data} err]} {
+    if {$toSock == ""} { ;# Make socks or http proxy connection
+        if {[catch {ProxyConnect $fromSock $data} err]} {
             INFO $err
             close $fromSock
             INFO "----- closed connection $fromSock -----"
@@ -698,6 +699,10 @@ proc sockReadable {fromSock toSock who} {
 # to forward to.
 # 
 proc ProxyConnect {fromSock data} {
+    global state
+    if {$state(proxy) eq "socks"} {
+        return [SocksConnect $fromSock $data]
+    }
     set skip 0
     set line1 [string range $data 0 [string first "\n" $data]]
     set line1 [string trimright $line1 "\r\n"]
@@ -756,6 +761,35 @@ proc ProxyConnect {fromSock data} {
     return
 }
 
+proc SocksConnect {client data} {
+    set params [parseSOCKS $data]
+    if {[llength $params] == 0} {
+        puts -nonewline $client [binary format ccc2c4 0 0x5b {0 0} {0 0 0 0}]
+        return -code error "failed to parse SOCKS prefix"
+    }
+    foreach {_ user _ host _ port _ xdata} $params break
+    if {[catch {set sock [socket $host $port]} err]} {
+        puts -nonewline $client [binary format ccc2c4 0 0x5b {0 0} {0 0 0 0}]
+        return -code error "cannot connect to $host:$port: $err"
+    }
+    puts "connected to $host:$port on $sock"
+    INFO "forwarding socks connection to $host:$port" meta2
+
+    foreach {ip name port} [fconfigure $sock -peername] break
+    puts -nonewline $client [binary format ccSc4 0 0x5a $port [split $ip .]]
+    flush $client
+
+    fileevent $client readable \
+        [list sockReadable $client $sock client]
+    fconfigure $sock -blocking 0 -buffering none -translation binary
+    fileevent $sock readable \
+        [list sockReadable $sock $client server]
+    if {[string length $xdata] > 0} {
+        puts -nonewline $sock $xdata
+    }
+    return
+}
+
 ##+##########################################################################
 # 
 # ProxySkip
@@ -792,6 +826,35 @@ proc ProxySkip {fromSock toSock} {
     update    
 }
 
+# Parse the SOCKS prefix data. Handles SOCKS4 and SOCKS4a
+proc parseSOCKS {data} {
+    if {[string length $data] < 8} { return }
+    binary scan $data ccSc4 version command port soctets
+    set version [expr {$version & 0xff}]
+    set command [expr {$command & 0xff}]
+    set port [expr {$port & 0xffff}]
+    foreach octet $soctets { lappend octets [expr {$octet & 0xff}] }
+    set addr [join $octets .]
+    set p1 [string first \0 $data 8]
+    if {$p1 == -1} { INFO "no user name data"; return }
+    set user [string range $data 8 [expr {$p1 - 1}]]
+    if {[string match "0.0.0.*" $addr]} {
+        set p2 [string first \0 $data [incr p1]]
+        if {$p2 == -1} { INFO "no hostname data"; return }
+        set host [string range $data $p1 [expr {$p2 - 1}]]
+    } else {
+        set host $addr
+        set p2 $p1
+    }
+    set data [string range $data [incr p2] end]
+
+    INFO [format {%s %s request by '%s' to %s:%d (%d byte excess)} \
+              [expr {[string match "0.0.0*" $addr] ? "socks4a" : "socks4"}]\
+              [expr {$command? "connect" : "listen"}] \
+              $user $host $port [string length $data]]
+    return [list user $user host $host port $port data $data]
+}
+
 ##+##########################################################################
 # 
 # clntConnect -- Called when we get a new client connection
@@ -803,7 +866,9 @@ proc clntConnect {sockClnt ip port} {
     set state(meta) ""
     
     INFO "connect from [fconfigure $sockClnt -sockname] $port" meta2
-    if {$state(proxy) || $SP(servHost) == {} || $SP(servHost) == "none"} {
+    if {$state(proxy) ne "none"
+        || $SP(servHost) == {} 
+        || $SP(servHost) == "none"} {
        set sockServ ""
     } else {
        set n [catch {set sockServ [socket $SP(servHost) $SP(servPort)]} reason]
@@ -848,12 +913,13 @@ proc DoListen {} {
        set n [catch {close $state(listen)} emsg]
        if {$n} { INFO "socket close error: $emsg"}
        set state(listen) ""
-       update                     ;# Need else socket below fails
+       after idle [list DoListen]
+        return
     }
 
     # Listen on clntPort or proxyPort for incoming connections
     set port $SP(clntPort)
-    if {$state(proxy)} {set port $SP(proxyPort)}
+    if {$state(proxy) ne "none"} {set port $SP(proxyPort)}
     set n [catch {set state(listen) [socket -server clntConnect $port]} emsg]
     
     if {$n} {
@@ -861,12 +927,18 @@ proc DoListen {} {
        set state(title) "not connected"
        set rval 0
     } else {
-       if {$state(proxy)} {
-           set state(title) "proxy localhost:$SP(proxyPort)"
-       } else {
-           set state(title) "localhost:$SP(clntPort) <--> "
-           append state(title) "$SP(servHost):$SP(servPort)"
-       }
+       switch -exact -- $state(proxy) {
+            "http" {
+                set state(title) "HTTP proxy localhost:$SP(proxyPort)"
+            }
+            "socks" {
+                set state(title) "SOCKS proxy localhost:$SP(proxyPort)"
+            }
+            default {
+                set state(title) "localhost:$SP(clntPort) <--> "
+                append state(title) "$SP(servHost):$SP(servPort)"
+            }
+        }
        INFO $state(title)
        INFO "waiting for new connection..."
     }
@@ -893,16 +965,23 @@ proc GetSetup {} {
     if {! $state(gui)} {
        catch {close $state(listen)}
 
-       set d "no" ; if {$state(proxy)} { set d yes }
-       set p [Prompt "Proxy mode" $d]
+       set d "no" ; if {$state(proxy) eq "http"} { set d yes }
+       set p [Prompt "HTTP proxy mode" $d]
        if {[regexp -nocase {^y$|^yes$} $p]} {
-           set state(proxy) 1
+           set state(proxy) http
            set SP(proxyPort) [Prompt "proxy port" $SP(proxyPort)]
        } else {
-           set state(proxy) 0
-           set SP(clntPort) [Prompt "Client port" $SP(clntPort)]
-           set SP(servHost) [Prompt "Server host" $SP(servHost)]
-           set SP(servPort) [Prompt "Server port" $SP(servPort)]
+            set d "no" ; if {$state(proxy) eq "socks"} { set d yes }
+            set p [Prompt "SOCKS proxy mode" $d]
+            if {[regexp -nocase {^y$|^yes$} $p]} {
+                set state(proxy) socks
+                set SP(proxyPort) [Prompt "proxy port" $SP(proxyPort)]
+            } else {
+                set state(proxy) none
+                set SP(clntPort) [Prompt "Client port" $SP(clntPort)]
+                set SP(servHost) [Prompt "Server host" $SP(servHost)]
+                set SP(servPort) [Prompt "Server port" $SP(servPort)]
+            }
        }
        DoListen
        return
@@ -922,11 +1001,12 @@ proc GetSetup {} {
 
     ${NS}::frame .dlg.fforward
     ${NS}::frame .dlg.fproxy
+    ${NS}::frame .dlg.fsocks
     ${NS}::frame .dlg.fcmdline
 
     ${NS}::label .dlg.msg -text $msg -justify left
     ${NS}::radiobutton .dlg.forward -text "Use fixed server forwarding" \
-       -variable state(proxy)  -value 0 -command GetSetup2
+       -variable state(proxy)  -value none -command GetSetup2
     if {$NS ne "::ttk"} { .dlg.forward configure -anchor w }
     ${NS}::label .dlg.fl1 -text "Client Port:" -anchor e
     ${NS}::entry .dlg.fe1 -textvariable SP(clntPort)
@@ -937,11 +1017,17 @@ proc GetSetup {} {
     ${NS}::entry .dlg.fe3 -textvariable SP(servPort)
     
     ${NS}::radiobutton .dlg.proxy -text "Use HTTP Proxying" \
-       -variable state(proxy)  -value 1 -command GetSetup2
+       -variable state(proxy)  -value http -command GetSetup2
     if {$NS ne "::ttk"} {.dlg.proxy configure -anchor w}
     ${NS}::label .dlg.pl1 -text "Proxy Port:" -anchor e
     ${NS}::entry .dlg.pe1 -textvariable SP(proxyPort)
 
+    ${NS}::radiobutton .dlg.socks -text "Use SOCKS proying" \
+        -variable state(proxy) -value socks -command GetSetup2
+    if {$NS ne "::ttk"} {.dlg.socks configure -anchor w}
+    ${NS}::label .dlg.sl1 -text "Proxy Port:" -anchor e
+    ${NS}::entry .dlg.se1 -textvariable SP(proxyPort)
+    
     ${NS}::label .dlg.cllabel -text "Command Line Equivalent"
     ${NS}::entry .dlg.clvar -textvariable SP(cmdLine)
     # -state readonly doesn't seem to work, sigh
@@ -959,7 +1045,7 @@ proc GetSetup {} {
     grid rowconfigure .dlg 2 -minsize 8
     
     pack .dlg.msg -in .dlg.top -side top -fill x -padx 2 -pady 1
-    pack .dlg.fforward .dlg.fproxy .dlg.fcmdline -in .dlg.top \
+    pack .dlg.fforward .dlg.fproxy .dlg.fsocks .dlg.fcmdline -in .dlg.top \
        -side top -fill x -padx 2 -pady 2
     
     grid .dlg.cllabel -in .dlg.fcmdline -row 0 -column 0 -sticky w 
@@ -974,6 +1060,13 @@ proc GetSetup {} {
     grid columnconfigure .dlg.fproxy 3 -minsize 10
     grid rowconfigure .dlg.fproxy 2 -minsize 10
 
+    grid .dlg.socks - - -in .dlg.fsocks -sticky w
+    grid x .dlg.sl1 .dlg.se1 -in .dlg.fsocks -sticky ew
+    grid columnconfigure .dlg.fsocks 0 -minsize .2i
+    grid columnconfigure .dlg.fsocks 2 -weight 1
+    grid columnconfigure .dlg.fsocks 3 -minsize 10
+    grid rowconfigure .dlg.socks 2 -minsize 10
+
     grid .dlg.forward - - -in .dlg.fforward -sticky w
     grid x .dlg.fl1 .dlg.fe1 -in .dlg.fforward -sticky ew
     grid x .dlg.fl2 .dlg.fe2 -in .dlg.fforward -sticky ew
@@ -996,8 +1089,8 @@ proc GetSetup {} {
     trace variable SP w cmdlineUpdate
     cmdlineUpdate SP servHost w
 
-    if {$state(proxy)} { focus -force .dlg.pe1 } { focus -force .dlg.fe2 }
-
+    focus -force [expr {$state(proxy) ne "none" ? ".dlg.pe1" : ".dlg.fe2"}]
+    tk::PlaceWindow .dlg widget .
     raise .dlg
     wm deiconify .dlg
     tkwait window .dlg
@@ -1016,7 +1109,7 @@ proc GetSetup {} {
 proc GetSetup2 {} {
     global state
     array set s {1 normal 0 disabled}
-    if {! $state(proxy)} { array set s {0 normal 1 disabled} }
+    if {$state(proxy) eq "none"} { array set s {0 normal 1 disabled} }
     
     .dlg.pl1 config -state $s(1)
     .dlg.pe1 config -state $s(1)
@@ -1032,7 +1125,7 @@ proc GetSetup2 {} {
 proc ValidForm {} {
     global state SP ok
     set ok 0
-    if {$state(proxy)} {
+    if {$state(proxy) eq "http"} {
        if {$SP(proxyPort) != ""} {set ok 1}
     } elseif {$SP(clntPort) !="" && $SP(servHost) !="" && $SP(servPort) !=""} {
        set ok 1
@@ -1051,12 +1144,21 @@ proc cmdlineUpdate {X elt X} {
     global SP
 
     # Check that port values are integers and that server host is not empty.
-    if {$::state(proxy)} {
-       set SP(cmdLine) "sockspy -proxy $SP(proxyPort)"
-       if {! [string is integer -strict $SP(proxyPort)]} {
-           set SP(cmdLine) "none (invalid proxy port above)"
-       }
-       return
+    switch -exact -- $::state(proxy) {
+        "http" {
+            set SP(cmdLine) "sockspy -proxy $SP(proxyPort)"
+            if {! [string is integer -strict $SP(proxyPort)]} {
+                set SP(cmdLine) "none (invalid proxy port above)"
+            }
+            return
+        }
+        "socks" {
+            set SP(cmdLine) "sockspy -socks $SP(proxyPort)"
+            if {! [string is integer -strict $SP(proxyPort)]} {
+                set SP(cmdLine) "none (invalid socks port above)"
+            }
+            return
+        }
     }
     
     if {$SP(servHost) == ""} {
@@ -1332,6 +1434,8 @@ proc stateRestore {} {
     }
     
     set state(stateFile) $stateFile
+    if {$state(proxy) eq "0"} {set state(proxy) "none"}
+    if {$state(proxy) eq "1"} {set state(proxy) "http"}
     
     foreach v $::saveList {
        trace variable $v w stateSave
@@ -1434,13 +1538,21 @@ if {[lindex $argv 0] == "-local"} {
 }
 
 if {[lindex $argv 0] == "-proxy"} {
-    set state(proxy) 1
+    set state(proxy) http
     if {$argc == 2} {
        set SP(proxyPort) [lindex $argv 1]
        DoListen
     } else {
        GetSetup
     }
+} elseif {[lindex $argv 0] eq "-socks"} {
+    set state(proxy) socks
+    if {[scan [lindex $argv 1] %d SP(proxyPort)] == -1} {
+        set SP(proxyPort) 1080 ;# FIX ME - overwrites saved port num
+        GetSetup
+    } else {
+        DoListen
+    }
 } else {
     if {$argc >= 1} { set SP(clntPort) [lindex $argv 0] }
     if {$argc >= 2} { set SP(servHost) [lindex $argv 1] }