#!/usr/bin/wish

proc check_callback {} {
	global debug
	if {$debug} {
		puts stderr "."
	}
	check_closed
	after 1000 check_callback 
}

proc getout {} {
	global client_fh server_fh
	
	set delay 50
	catch {flush $client_fh}
	after $delay
	catch {close $client_fh}
	after $delay
	catch {flush $server_fh}
	after $delay
	catch {close $server_fh}
	after $delay

	global bmesg_cnt
	if [info exists bmesg_cnt] {
		catch {tkwait window .bmesg$bmesg_cnt}
	}
	destroy .
	exit
}

proc check_closed {} {
	global got_connection debug
	global client_fh server_fh

	if {! $got_connection} {
		return
	}
	if {$client_fh != "" && [eof $client_fh]} {
		if {$debug} {
			puts stderr "client_fh EOF"
		}
		getout
	}
	if {$server_fh != "" && [eof $server_fh]} {
		if {$debug} {
			puts stderr "server_fh EOF"
		}
		getout
	}
}

proc xfer_in_to_out {} {
	global client_fh server_fh debug
	if {$client_fh != "" && ![eof $client_fh]} {
		set str ""
		catch {set str [read $client_fh 4096]}
		if {$debug} {
			puts stderr "xfer_in_to_out: $str"
		}
		if {$server_fh != "" && $str != ""} {
			puts -nonewline $server_fh $str
			flush $server_fh
		}
	}
	check_closed
}

proc xfer_out_to_in {} {
	global client_fh server_fh debug
	if {$server_fh != "" && ![eof $server_fh]} {
		set str ""
		catch {set str [read $server_fh 4096]}
		if {$debug} {
			puts stderr "xfer_out_to_in: $str"
		}
		if {$client_fh != "" && $str != ""} {
			puts -nonewline $client_fh $str
			flush $client_fh
		}
	}
	check_closed
}

proc bmesg {msg} {
	return

	global bmesg_cnt
	if {! [info exists bmesg_cnt]} {
		set bmesg_cnt 0
	}
	incr bmesg_cnt
	set w .bmesg$bmesg_cnt
	catch {destroy $w}
	toplevel $w
	label $w.l -width 70 -text "$msg"
	pack $w.l
	update
}

proc do_connect_http {sock hostport which} {
	global debug cur_proxy
	set con ""
	append con "CONNECT $hostport HTTP/1.1\r\n"
	append con "Host: $hostport\r\n"
	append con "Connection: close\r\n\r\n"

	puts stderr "pxy=$which CONNECT $hostport HTTP/1.1 via $cur_proxy"
	bmesg "H: $which CONNECT $hostport HTTP/1.1 $cur_proxy";

	puts -nonewline $sock $con
	flush $sock

	set r ""
	set cnt 0
	while {1} {
		incr cnt
		set c [read $sock 1]
		if {$c == ""} {
			check_closed
			after 20
		}
		append r $c
		if {[regexp "\r\n\r\n" $r] || [regexp "a--no--\n\n" $r]} {
			break
		}
		if {$cnt > 30000} {
			break
		}
	}
	if {! [regexp {HTTP/.* 200} $r]} {
		puts stderr "did not find HTTP 200 #1"
		destroy .
		exit 1
	}
}

proc do_connect_socks4 {sock hostport which} {
	global debug cur_proxy

	set s [split $hostport ":"]
	set host [lindex $s 0]
	set port [lindex $s 1]

	set i1 ""
	set i2 ""
	set i3 ""
	set i4 ""

	set socks4a 0

	if {$host == "localhost" || $host == "127.0.0.1"} {
		set i1 127
		set i2 0
		set i3 0
		set i4 1
		
	} elseif [regexp {^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$} $host] {
		set n [split $host "."]
		set i1 [lindex $n 0]
		set i2 [lindex $n 1]
		set i3 [lindex $n 2]
		set i4 [lindex $n 3]
	} else {
		set i1 0
		set i2 0
		set i3 0
		set i4 3
		
		set socks4a 1
	}

	if {$socks4a} {
		puts stderr "pxy=$which socks4a connection to $host:$port via $cur_proxy"
	} else {
		puts stderr "pxy=$which socks4  connection to $host:$port via $cur_proxy"
	}

	set p1 [binary format ccScccc 4 1 $port $i1 $i2 $i3 $i4]
	set p2 "nobody"
	set p3 [binary format c 0]

	puts -nonewline $sock $p1
	puts -nonewline $sock $p2
	puts -nonewline $sock $p3
	if {$socks4a} {
		puts -nonewline $sock $host
		puts -nonewline $sock $p3
	}
	flush $sock

	set r ""; set s ""; set i 0; set cnt 0
	set ok 1
	while {$cnt < 30000 && $i < 8} {
		incr cnt
		set c [read $sock 1]
		if {$c == ""} {
			check_closed
			after 20
			continue
		}
		
		binary scan $c c s
		if {$i == 0 && $s != 0} {
			puts stderr "socks4: $i - $s"
			set ok 0
		}
		if {$i == 1 && $s != 90} {
			puts stderr "socks4: $i - $s"
			set ok 0
		}
		set r "$r,$s"
		incr i
	}
	if {! $ok} {
		puts stderr "socks4 failure: $r"
		destroy .
		exit 1
	}
}

proc do_connect_socks5 {sock hostport which} {
	global debug cur_proxy

	set s [split $hostport ":"]
	set host [lindex $s 0]
	set port [lindex $s 1]

	set p1 [binary format ccc 5 1 0]
	puts -nonewline $sock $p1
	flush $sock

	set r ""; set s ""; set i 0; set cnt 0
	set ok 1
	while {$cnt < 30000 && $i < 2} {
		incr cnt
		set c [read $sock 1]
		if {$c == ""} {
			check_closed
			after 20
			continue
		}
		
		binary scan $c c s
		if {$i == 0 && $s != 5} {
			puts stderr "$i - $s"
			set ok 0
		}
		if {$i == 1 && $s != 0} {
			puts stderr "$i - $s"
			set ok 0
		}
		set r "$r,$s"
		incr i
	}
	if {! $ok} {
		puts stderr "socks5 failure: $r"
		destroy .
		exit 1
	}

	set len [string length $host]
	set p1 [binary format ccccc 5 1 0 3 $len]
	set p2 $host

	set n1 [expr int($port/256)]
	set n2 [expr "$port - $n1 * 256"]
	set p3 [binary format cc $n1 $n2]

	puts stderr "pxy=$which socks5  connection to $host:$port via $cur_proxy"

	puts -nonewline $sock $p1
	puts -nonewline $sock $p2
	puts -nonewline $sock $p3
	flush $sock

	set i1 ""; set i2 ""; set i3 ""; set i4 ""
	set r ""; set s ""; set i 0; set cnt 0
	set ok 1
	while {$cnt < 30000 && $i < 4} {
		incr cnt
		set c [read $sock 1]
		if {$c == ""} {
			check_closed
			after 20
			continue
		}
		
		binary scan $c c s
		if {$i == 0} {
			set i1 $s
		} elseif {$i == 1} {
			set i2 $s
		} elseif {$i == 2} {
			set i3 $s
		} elseif {$i == 3} {
			set i4 $s
		}
		incr i
	}
	set r "i1=$i1,i2=$i2,i3=$i3,i4=$i4"

	if {$i4 == 1} {
		set n 6
	} elseif {$i4 == 3} {
		set c ""
		for {set i 0} {$i < 1000} {incr i} {
			set c [read $sock 1]
			if {$c == ""} {
				check_closed
				after 20
				continue
			}
			break;
		}
		if {$c == ""} {
			puts stderr "socks5 failure c: $r"
			destroy .
			exit 1
		}
		binary scan $c c s
		set n [expr $s + 2] 
	} elseif {$i4 == 4} {
		set n 18 
	} else {
		puts stderr "socks5 failure x: $r"
		destroy .
		exit 1
	}
	#puts "n=$n --- $r"

	set i 0; set cnt 0
	while {$cnt < 30000 && $i < $n} {
		incr cnt
		set c [read $sock 1]
		if {$c == ""} {
			check_closed
			after 20
			continue
		}
		incr i
	}
	if {$i1 != 5 || $i2 != 0 || $i3 != 0} {
		puts stderr "socks failure $r"
		destroy .
		exit 1
	}
}

proc do_connect_repeater {sock hostport which repeater} {
	global debug cur_proxy

	# 250 is UltraVNC buffer size.
	set con [binary format a250 $repeater]

	puts stderr "pxy=$which REPEATER $repeater via $cur_proxy"
	bmesg "R: $which CONNECT $hostport | $repeater $cur_proxy";

	puts -nonewline $sock $con
	flush $sock

	set r ""
	set cnt 0
	while {1} {
		incr cnt
		set c [read $sock 1]
		if {$c == ""} {
			check_closed
			after 20
		}
		append r $c
		if {[string length $r] >= 12} {
			puts stderr "do_connect_repeater: $r"
			break
		}
		if {$cnt > 30000} {
			break
		}
	}
}

proc do_connect {sock type hostport which} {
	if {$type == "http"} 	{
		do_connect_http $sock $hostport $which
	} elseif {$type == "socks"} {
		do_connect_socks4 $sock $hostport $which
	} elseif {$type == "socks5"} {
		do_connect_socks5 $sock $hostport $which
	} elseif [regexp -nocase {^repeater:} $type] {
		regsub -nocase {^repeater:} $type "" repeater
		do_connect_repeater $sock $hostport $which $repeater
	}
}

proc handle_connection {fh host port} {
	global proxy1_host proxy1_port proxy1_type
	global proxy2_host proxy2_port proxy2_type
	global proxy3_host proxy3_port proxy3_type
	global proxy1 proxy2 proxy3 dest
	global debug cur_proxy
	global got_connection

	if {$got_connection} {
		catch {close $fh}
		return
	}
	set got_connection 1

	if {$debug} {
		puts stderr "connection from: $host $port"	
		puts stderr "socket $proxy1_host $proxy1_port"
	}

	set rc [catch {set sock [socket $proxy1_host $proxy1_port]}]
	if {$rc != 0} {
		puts stderr "error connecting"	
		catch {close $sock}
		destroy .
		exit
	}

	if {$debug} {
		puts stderr "got sock: $sock"	
	}
	
	global client_fh server_fh
	set client_fh $fh
	set server_fh $sock

	fconfigure $fh   -translation binary -blocking 0
	fconfigure $sock -translation binary -blocking 0

	fileevent $fh   readable xfer_in_to_out
	fileevent $sock readable xfer_out_to_in

	set cur_proxy $proxy1
	if {$proxy2 != ""} {
		do_connect $sock $proxy1_type "$proxy2_host:$proxy2_port" 1

		set cur_proxy $proxy2
		if {$proxy3 != ""} {
			do_connect $sock $proxy2_type "$proxy3_host:$proxy3_port" 2

			set cur_proxy $proxy3
			do_connect $sock $proxy3_type $dest 3

		} else {
			do_connect $sock $proxy2_type $dest 2
		}
	} else {
		do_connect $sock $proxy1_type $dest 1
	}
}

proc proxy_type {proxy} {
	if [regexp -nocase {^socks://} $proxy] {
		return "socks"
	} elseif [regexp -nocase {^socks4://} $proxy] {
		return "socks"
	} elseif [regexp -nocase {^socks4a://} $proxy] {
		return "socks"
	} elseif [regexp -nocase {^socks5://} $proxy] {
		return "socks5"
	} elseif [regexp -nocase {^http://} $proxy] {
		return "http"
	} elseif [regexp -nocase {^https://} $proxy] {
		return "http"
	} elseif [regexp -nocase {^repeater://.*\+(.*)$} $proxy mat idstr] {
		return "repeater:$idstr"
	} else {
		return "http"
	}
}

proc proxy_hostport {proxy} {
	regsub -nocase {^[a-z][a-z]*://} $proxy "" hp
	regsub {\+.*$} $hp "" hp
	if {! [regexp {:[0-9]} $hp] && [regexp {^repeater:} $proxy]} {
		set hp "$hp:5900"
	}
	return $hp
}

global env

set proxy1 ""
set proxy2 ""
set proxy3 ""
set client_fh ""
set server_fh ""

set debug 0
if {$debug} {
	if {! [info exists env(SSVNC_DEST)]} {
		set env(SSVNC_DEST) "haystack:2037"
	}
	if {! [info exists env(SSVNC_PROXY)]} {
		set env(SSVNC_PROXY) "haystack:2037"
	}
	if {! [info exists env(SSVNC_LISTEN)]} {
		set env(SSVNC_LISTEN) "6789"
	}
} else {
	if {! [info exists env(SSVNC_DEST)]} {
		destroy .; exit;
	}
	if {! [info exists env(SSVNC_PROXY)]} {
		destroy .; exit;
	}
	if {! [info exists env(SSVNC_LISTEN)] && ! [info exists env(SSVNC_REVERSE)]} {
		destroy .; exit;
	}
}

set dest $env(SSVNC_DEST)

if [regexp {,} $env(SSVNC_PROXY)] {
	set s [split $env(SSVNC_PROXY) ","]
	set proxy1 [lindex $s 0]
	set proxy2 [lindex $s 1]
	set proxy3 [lindex $s 2]
} else {
	set proxy1 $env(SSVNC_PROXY)
}

set proxy1_type [proxy_type     $proxy1]
set proxy1_hp   [proxy_hostport $proxy1]

set s [split $proxy1_hp ":"]
set proxy1_host [lindex $s 0]
set proxy1_port [lindex $s 1]

set proxy2_type ""
set proxy2_host ""
set proxy2_port ""

if {$proxy2 != ""} {
	set proxy2_type [proxy_type     $proxy2]
	set proxy2_hp   [proxy_hostport $proxy2]
	set s [split $proxy2_hp ":"]
	set proxy2_host [lindex $s 0]
	set proxy2_port [lindex $s 1]
}

set proxy3_type ""
set proxy3_host ""
set proxy3_port ""

if {$proxy3 != ""} {
	set proxy3_type [proxy_type     $proxy3]
	set proxy3_hp   [proxy_hostport $proxy3]
	set s [split $proxy3_hp ":"]
	set proxy3_host [lindex $s 0]
	set proxy3_port [lindex $s 1]
}

bmesg "1: '$proxy1_host' '$proxy1_port' '$proxy1_type'";
bmesg "2: '$proxy2_host' '$proxy2_port' '$proxy2_type'";
bmesg "3: '$proxy3_host' '$proxy3_port' '$proxy3_type'";

set got_connection 0

proc setb {} {
	wm withdraw .
	button .b -text "CONNECT_BR" -command {destroy .}
	pack .b
	after 1000 check_callback 
}

if [info exists env(SSVNC_REVERSE)] {
	set s [split $env(SSVNC_REVERSE) ":"]
	set rhost [lindex $s 0]
	set rport [lindex $s 1]
	set rc [catch {set lsock [socket $rhost $rport]}]
	if {$rc != 0} {
		puts stderr "error reversing"	
		destroy .; exit 1
	}
	puts stderr "SSVNC_REVERSE to $rhost $rport OK";
	setb
	handle_connection $lsock $rhost $rport
} else {
	set lport $env(SSVNC_LISTEN)
	set rc [catch {set lsock [socket -myaddr 127.0.0.1 -server handle_connection $lport]}]
	if {$rc != 0} {
		puts stderr "error listening"	
		destroy .; exit 1
	}
	puts stderr "SSVNC_LISTEN on $lport OK";
	setb
}
