From c9cda780ca72e273c52de56f89cbb5b2a4e5ce04 Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Wed, 2 Dec 2009 23:37:56 +0000 Subject: [PATCH] SOCKS proxying support. 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 --- bin/sockspy.tcl | 180 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 34 deletions(-) diff --git a/bin/sockspy.tcl b/bin/sockspy.tcl index d46150e..e7d03bb 100644 --- a/bin/sockspy.tcl +++ b/bin/sockspy.tcl @@ -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] } -- 2.23.0