#
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 -----"
# 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"]
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
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
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]
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} {
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..."
}
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
${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)
${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
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
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
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
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)
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
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) == ""} {
}
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
}
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] }