#!/usr/bin/perl

use strict;
use warnings;
use Socket;

use Getopt::Std;
our ($opt_S, $opt_d);
getopts('S:d');
defined $opt_S or die "SOCKS proxy address not defined\n";



@ARGV == 2 or die <<EOU;
Usage:
  $0 -S <unix_socket> <host> <port>

EOU

my ($host, $port) = @ARGV;

socket(my $sock, PF_UNIX, SOCK_STREAM, 0)
    or die "unable to create socket: $!\n";
connect($sock, sockaddr_un($opt_S))
    or die "unable to connect to socket $opt_S: $!\n";

my $req1 = "\x05\x01\x00";
my $req2 = pack('C4C/A*n', 5, 1, 0, 3, $host, $port);

my ($res1, $res2, $res2_1);

( syswrite($sock, $req1) == length($req1) and
  sysread($sock, $res1, 2) == 2 and
  $res1 eq "\x05\x00" and
  syswrite($sock, $req2) == length($req2) and
  sysread($sock, $res2, 2) == 2 )
    or die "connection broken: $!\n";

$res1 eq "\x05\x00"
    or die sprintf("request failed: 0x%02x\n", ord(substr($res2, 1)));

( sysread($sock, $res2_1, 8) == 8 and
  $res2_1 =~ /^\x00\x01/ )
    or die "bad response\n";

$opt_d and warn "SOCKS connection open";

my $sfn = fileno $sock;
my $ifn = 0;
my $ofn = 1;

my $sto = '';
my $sfrom = '';

my $sto_end;
my $sfrom_end;

while (length $sfrom or length $sto or !$sto_end or !$sfrom_end) {
    my $rb = '';
    vec($rb, $sfn, 1) = 1 if (!$sfrom_end and length $sfrom < 16384);
    vec($rb, $ifn, 1) = 1 if (!$sto_end and length $sto < 16384);
    my $wb = '';
    vec($wb, $sfn, 1) = 1 if length $sto;
    vec($wb, $ofn, 1) = 1 if length $sfrom;
    if (select($rb, $wb, undef, 0) > 0) {
	if (vec($rb, $sfn, 1)) {
	    unless(sysread($sock, $sfrom, 1024, length $sfrom)) {
		vec($rb, $sfn, 1) = 0;
		$sfrom_end = 1;
		shutdown $sock, 0;
		$opt_d and warn "sock read shutdown";
		unless (length $sfrom) {
		    close STDOUT;
		    $opt_d and warn "STDOUT closed";
		}
		$sto_end = 1;
		close STDIN;
		shutdown $sock, 1 unless length $sto;
		$opt_d and warn "STDIN closed on socket close";

	    }
	}
	if (vec($rb, $ifn, 1)) {
	    my $bytes = sysread(STDIN, $sto, 1024, length $sto);
	    $opt_d and warn "sysread(STDIN) => $bytes"; 
	    unless ($bytes) {
		vec($rb, $ifn, 1) = 0;
		$sto_end = 1;
		close STDIN;
		$opt_d and warn "STDIN closed";
		unless (length $sto) {
		    shutdown $sock, 1;
		    $opt_d and warn "sock write shutdown";
		}
	    }
	}
	if (vec($wb, $sfn, 1)) {
	    my $bytes = syswrite($sock, $sto, 1024);
	    $opt_d and warn "syswrite(sock) => $bytes";
	    if ($bytes) {
		substr($sto, 0, $bytes, "");
		if ($sto eq '' and $sto_end) {
		    shutdown $sock, 1;
		    $opt_d and warn "sock write shutdown";
		}
	    }
	    else {
		$sto = "";
		$sto_end = 1;
		shutdown $sock, 1;
		close STDIN unless $sto_end;
		$opt_d and warn "sock write shutdown";
	    }
	}
	if (vec($wb, $ofn, 1)) {
	    my $bytes = syswrite(STDOUT, $sfrom, 1024);
	    $opt_d and warn "syswrite(STDOUT) => $bytes";
	    if ($bytes) {
		substr($sfrom, 0, $bytes, "");
		if ($sfrom eq '' and $sfrom_end) {
		    close STDOUT;
		    $opt_d and warn "STDOUT closed";
		}
	    }
	    else {
		$sfrom = "";
		$sfrom_end = 1;
		close STDOUT;
		shutdown $sock, 0 unless $sfrom_end;
		$opt_d and warn "STDOUT closed";
	    }
	}
    }
}
