Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   PHP, PERL, MySQL, JavaScript (https://forum.antichat.xyz/forumdisplay.php?f=37)
-   -   SOCKS прокси на PHP (https://forum.antichat.xyz/showthread.php?t=115025)

bons 08.04.2009 21:48

SOCKS прокси на PHP
 
это недописанные скрипты для SOCKS-версии pproxy.
(HTTP - версия в этой теме http://forum.antichat.ru/thread93318.html). Запускается так же.
Проблема простая - php-скрипт работает только на моем компе (debian AMD64, также работал когда на нем был x86). Версия PHP - та что в stable репозитариях дебиана.
На других машинах(с той же версией линукса и PHP) работать отказывается(если точнее то некорректно работает php-функция stream_select). Из-за чего такая проблема выяснить не удалось.
Так как эта софтина нужна множеству людей, большая просьба знающим найти и исправить ошибки.

PHP код:

<?php
    
//$secret = 'pproxypass';

    
$TIMEOUT 500;
    
$TMPDIR '/tmp';

    
ob_implicit_flush(TRUE);
    
$socketpath $TMPDIR '/pipe' $_POST['key'];
    
$pipename 'unix://' $socketpath;
    

    function 
downscript()
    {
        if(
file_exists($socketpath))unlink($socketpath);
    }
    
    if(isset(
$secret) && ($_POST['secret'] != $secret))exit;
    if(isset(
$_POST['key']) && (isset($_POST['data']) || isset($_POST['kill'])))
    {
        
$pipe = @stream_socket_client($pipename);
        if(!
$pipe)exit;
        if(isset(
$_POST['kill']))fwrite($pipe'kill.');
        elseif(isset(
$_POST['data']))
            
fwrite($pipe'data=' base64_decode(str_replace(" ""+"$_POST['data'])));
        
fclose($pipe);
        exit;
    }

    if(isset(
$_POST['host']) && isset($_POST['key']))
    {
        
set_time_limit(0);
        
header('Content-type: application/octet-stream');
    
        
$sock = @stream_socket_client('tcp://' $_POST['host']);
        if(!
$sock)exit;
        
umask(0);
        
$unixsocket stream_socket_server($pipename);
        if(!
$unixsocket)exit;
        
register_shutdown_function('downscript');
        echo 
'connected';

        
$readers = array($unixsocket$sock);
        
$up true;
        
        while(
$up)
        {
            
$read $readers;
            
$num_changed_streams stream_select($read$write NULL$except NULL$TIMEOUT);
            if(!
$num_changed_streams)break;
            
            if (
$read[0] == $unixsocket)
            {
                
$pipe stream_socket_accept ($unixsocket);
                if(!
$pipe)$up false;
                else
                {
                    
$inbuf '';
                    while (!
feof($pipe)) $inbuf .= fread($pipe300);
                    
fclose($pipe);
                    
$cmd substr($inbuf05);
                    if (
$cmd == 'data=')fwrite($socksubstr($inbuf5));
                    elseif (
$cmd == 'kill.')$up false;
                }
            }
            if (
$read[0] == $sock)
            {
                
$resp fread($sock8096);
                echo 
$resp;
                if(
feof($sock))
                {
                    
$up false;
                    
$readers = array($unixsocket);
                }
            }
        }
        
fclose($sock);
        
fclose($unixsocket);
        if(
file_exists($socketpath))unlink($socketpath);
        exit;
    }
?>

Код:

#!/usr/bin/perl
use MIME::Base64 ();
use Getopt::Long;
use POSIX ":sys_wait_h";
use IO::Socket::INET;
use strict;

our %children;

$|++;

my ($pproxyhost, $pproxyport, $pproxyurl);
my ($tunnelhost, $tunnelport);
my ($pproxy, $bindport, $tunnel, $secret);
my ($destaddr, $destport, $desturl);
my $clientsock;

my $user_agent = 'Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4';



#Вывод справки
Usage() if @ARGV==0;

#Задание опций
GetOptions(
        "px=s"                => \$pproxy,
        "bp=s"        => \$bindport,
        "tpx=s"        => \$tunnel,
        "pwd=s"        => \$secret
    );
die "need pproxysocks.php url" unless $pproxy;

#Получение параметров
$pproxy =~ /http:\/\/([\w\.\-]+)(:\d*)?\/(.+)/;
$pproxyhost = $1; $pproxyport = substr($2, 1); $pproxyurl = '/' . $3;

defined($pproxyport) || ($pproxyport = 80);
defined($bindport) || ($bindport = 8008);

if(defined($tunnel))
{
        $tunnel =~ /http:\/\/([\w\.\-]+):(\d*)?/;
        $tunnelhost = $1; $tunnelport = $2;

        $destaddr = $tunnelhost;
        $destport = $tunnelport;
        $desturl = $pproxy;

        print "# tunnelhost = $tunnelhost\n";
        print "# tunnelport = $tunnelport\n";
}else{
        $destaddr = $pproxyhost;
        $destport = $pproxyport;
        $desturl = $pproxyurl;
}

print "# pproxysocks host = $pproxyhost\n";
print "# pproxysocks port = $pproxyport\n";
print "# pproxysocks url  = $pproxyurl\n";
print "# bindport = $bindport\n\n";

print "# start /p/ local socks script\n";

#Создать сокет и привязать его к порту на локалхосте
my $listener = IO::Socket::INET->new( LocalAddr => 'localhost',
                                      LocalPort => $bindport,
                                      ReuseAddr => 1,
                                      Listen    => 5 )
|| die "cannot create listener: $!\n";

#цикл приема подключений клиента
MainProc($clientsock) while $clientsock = $listener->accept;


#-------------------------------------------------#
# Главная подпрограмма обработки подключений клиента
# Ответвляет себе процесс
# Параметр - сокет соединения с клиентом
#
sub MainProc($)
{
    my $client = shift;
        my ($vers, $size, $methods, $cmd, $reserv, $AType, $ip, $host, $port, $rport, $resp);
       
        #Ответвление процесса
        my $pid = fork();
        unless(defined($pid))
        {
                close $client;
                die "# Erorr couldn't fork\n";
        }

        if($pid)
        {
                close $client;
                $children{$pid}++;
               
                #Зачистка зомби
                foreach(keys %children)
                {
                        my $kid = waitpid($_, &WNOHANG);
                        delete $children{$_} if($kid == -1 || $kid == $_);
                }
                return;
        }
       
        #ОБработка клиента SOCKS5
        sysread($client, $vers, 1);
        exit unless $vers eq "\x05";

        sysread($client, $size, 1);
        exit if $size eq "\x00";

        my $nread = sysread($client,$methods,ord($size));
        syswrite($client, "\x05\x00", 2);
       
        sysread($client, $vers, 1);
        exit unless $vers eq "\x05";

        sysread($client, $cmd, 1);
        exit unless $cmd eq "\x01";
       
        sysread($client, $reserv, 1);
        exit unless $reserv eq "\x00";

        sysread($client, $AType, 1);
       
        #Целевой хост задан IP-адресом
        if($AType eq "\x01")
        {
                sysread($client, $ip, 4);
                my @host = $ip =~ /(.)/g;
                $host = ord($host[0]).".".ord($host[1]).".".ord($host[2]).".".ord($host[3]);
        }
        #Целевой хост задан DNS-именем
        elsif($AType eq "\x03")
        {
                sysread($client, $size, 1);
                sysread($client, $host, ord($size));
        }else{exit;}
       
        #Чтение целевого порта
        sysread($client, $rport, 2);
        my @pps = $rport =~ /(.)/g;
        $port = 256 * ord($pps[0]) | ord($pps[1]);
       
        #Создание уникального идентификатора подключения
        my $key = random_int_in(100000,999999);
       
        CreateTunnel($client, $key, $host . ':' . $port);
        exit;
}

#---------------------------------------
# Возвращает случайное число в указанно диапазоне
# Параметры:
#        1. Минимальное значение
#        2. Максимальное значение
#
sub random_int_in ($$)
{
    my($min, $max) = @_;
    return $min if $min == $max;
    ($min, $max) = ($max, $min) if $min > $max;
    return $min + int rand(1 + $max - $min);
}

#--------------------------------------
# Содержит цикл обработки данных от клиента.
# Ответвляет для себя отдельный процесс.
# Параметры:
#        1. Уникальный идентификатор соединения
#        2. Сокет клиента
sub ReceiveDataFromTunnel($$)
{
        my $key = shift;
        my $client = shift;
       
        my $pid = fork();
        unless(defined($pid))
        {
                close $client;
                die "# Erorr couldn't fork\n";
        }
        if($pid)
        {
                $children{$pid}++;
                return;
        }
        my ($buffer, $result, $proxysock);
       
        while(1)
        {
                $result = sysread($client, $buffer, 4096);
                close $proxysock if defined($proxysock);
                last if !defined($result) || !$result;
                $proxysock = DataToTunnel($key, $buffer);
               
        }

        KillTunnel($key);
        close $client;
        exit;
}

#--------------------------------------
# Отправляет данные по соединению
# Параметры:
#        1. Уникальный идентификатор соединения
#        2. Данные
#
sub DataToTunnel($$)
{
        my $key = shift;
        my $data = shift;
       
        my $post_query = 'key=' . $key . '&data=' . MIME::Base64::encode($data);
        my $proxysock = HttpConnection($post_query);
        unless($proxysock)
        {
                print "could not connect to proxy\n";
                return;
        }
        return $proxysock;
}

#---------------------------------------
# Уничтожает существующее соединение
# Параметр - уникальный идентификатор соединения
#
sub KillTunnel($)
{
        my $key = shift;
       
        my $post_query = 'key=' . $key . '&kill=1';
        my $proxysock = HttpConnection($post_query);
        unless($proxysock)
        {
                print "could not connect to proxy\n";
                return;
        }
        close $proxysock;
}

#---------------------------------------
# Создает соединение к целевому серверу
# Если удалось то
#        1. передает клиенту последний SOCKS-ответ
#        2. создает процесс для дальнейшего приема данных от клиента
#        3. в цикле принимает данные из соединения и передает их клиенту
# Принимает параметры:
#        1. Сокет клиента
#        2. Уникальный идентификатор соединения
#        3. Имя целевого сервера
#
sub CreateTunnel($$$)
{
        my $clientsock = shift;
        my $key = shift;
        my $host = shift;

        my $sig_conn = 'connected';
        my $post_query = 'host=' . $host . '&key=' . $key;
        my $time_start = time;
       
        print TranslateTimeHour($time_start), "    dest host: $host\n";
        my $proxysock = HttpConnection($post_query);
        unless($proxysock)
        {
                print "could not connect to proxy\n";
                return;
        }

        my ($result, $buffer, $contentstart, $response);
        $contentstart = -1;
       
        #Цикл приема данных от pproxysocks.php
        while(1)
        {
                $result = sysread($proxysock, $buffer, 256);
                last if !defined($result) || !$result;

                if($contentstart == -1)
                {
                        $response .= $buffer;
                        last if length($response)>65535;
                       
                        $contentstart = index($response,"\x0D\x0A\x0D\x0A" . $sig_conn);
                        next if $contentstart == -1;

                        #Если HTTP-заголовок с сигнатурой удачно принят то соединение установлено
                        syswrite($clientsock, "\x05\x00\x00\x01\x10\x10\x10\x10\x00\x00", 10);
                        ReceiveDataFromTunnel($key, $clientsock);
                        $buffer = substr($response, $contentstart + 4 + length($sig_conn));
                }
                syswrite($clientsock, $buffer, length($buffer));
        }
       
        my $time_end = time;
        if($contentstart == -1)
        {
                #Сигнатура не обнаружена значит что-то пошло не так
                print TranslateTimeHour($time_end), "    could not connect to $host\n";
                syswrite($clientsock, "\x05\x04\x00", 3);
        }else{
                #Лог
                print TranslateTimeHour($time_end), "    connection closed - ", $host,
                                " (", TranslateTime($time_end - $time_start), ")\n";
        }
        close $proxysock;
        shutdown $clientsock, 0;
        close $clientsock;
}

#---------------------------------------
# Устанавливает соединение с web-сервером или HTTP-прокси
# В качестве параметра принимает данные для POST-запроса
# Использует глобальные переменные:
# $destaddr, $destport, $desturl
# $pproxyhost, $pproxyport, $secret
#
sub HttpConnection($)
{
        my $post = shift;
       
        my $proxysock = IO::Socket::INET->new(Proto=>'tcp', PeerAddr=>$destaddr, PeerPort=>$destport);
        unless($proxysock) { return; }
       
        $post = 'secret=' . $secret . '&' . $post if defined($secret);
        my $postlen = length($post);
       
        my $request = "POST $desturl HTTP/1.0\x0D\x0A".
                        "Host: $pproxyhost:$pproxyport\x0D\x0A".
                        "Accept: */*\x0D\x0A".
                        "Content-Type: application/x-www-form-urlencoded\x0D\x0A".
                        "Content-Length: $postlen\x0D\x0A".
                        "User-Agent: $user_agent\x0D\x0A".
                        "Connection: close\x0D\x0A\x0D\x0A" . $post;

        syswrite($proxysock, $request, length($request));
        return $proxysock;
}

#---------------------------------------
# Переводит время в формат - мин:сек
#
sub TranslateTime
{
        my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(shift);
        return sprintf "%02u:%02u", $min, $sec;
}

#---------------------------------------
# Переводит время в формат - час:мин:сек
#
sub TranslateTimeHour
{
        my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(shift);
        return sprintf "%02u:%02u:%02u", $hour, $min, $sec;
}

#---------------------------------------
# Выводит справку
#
sub Usage
{
        print "Usage: $0 -px proxy_url [-bp bindport] [-tpx tunnel_proxy] [-pwd secret]\n";
        print "Example: $0 -px http://site.com/proxy/proxy.php -bp 8080\n";
        print "        $0 -px http://site.com/proxy/proxy.php -pwd pproxypass\n";
        print "        $0 -px http://site.com/proxy/proxy.php -tpx http://localhost:8118\n";
        print "\nDefault bind port - 8008\n";
        exit;
}


Nightmarе 09.04.2009 00:05

+1 Подписываюсь, гуру ачата помогите разобратся с данной проблеммой, ибо вещь действительно была бы незаменимой!

winstrool 09.04.2009 07:39

Тоже довненько искал, что не буть подобное, также подписываюсь, автору темы плюс, за актуальность вопроса)

needDrivers 10.04.2009 13:43

Не вглядываясь просмотрел твой скрипт.

Насколько я понял ты пытаешься досылать данные работающему скрипту.
Я тоже пытался реализовать подобное поведение, но отказался от этой затеи из-за того, что работа скрипта ограничена max_execution_time и постоянное соединение поддерживать не удастся.

А не влияет ли отключение сокетов на функции stream_socket_*?
Может дело в этом? На большинстве серверов они отключены.

Nightmarе 14.04.2009 02:54

Цитата:

Сообщение от needDrivers
На большинстве серверов они отключены.

Ну а вообще с помощью хоть каких то библотэк php можно ли сокс таким образом организовать?

Pashkela 14.04.2009 03:54

http://www.phpclasses.org/browse/package/1822.html
http://www.phpclasses.org/browse/package/5049.html

во второй ссылке клянется, что рабочий, плюс есть поддержка авторизации. В обоих случаях есть пример использования. Сам не проверял.

Nightmarе 14.04.2009 04:47

Цитата:

Сообщение от Pashkela
http://www.phpclasses.org/browse/package/1822.html
http://www.phpclasses.org/browse/package/5049.html

во второй ссылке клянется, что рабочий, плюс есть поддержка авторизации. В обоих случаях есть пример использования. Сам не проверял.

Мляяя это РАЗНЫЕ вещи.
Мы говорим в этой теме о СКРИПТЕ socks 5 а не о поднятии службы на сервере с открытым портом.
Последних скриптов навалом, только толку от них 0.

Смысл сабжа в том, что мы просто кладём скрипт на сервер и коннектимся к нему клиентом, а не поднимаем процесс на хосте.

Doom123 14.04.2009 07:44

я гдето в теме полезных скриптов выкладывал рабочий класс для работы через сокс ...

bons 14.04.2009 09:42

Цитата:

Сообщение от Doom123
я гдето в теме полезных скриптов выкладывал рабочий класс для работы через сокс ...

тут немного не тот принцип действия и сокс-класс тут как-бы не поможет. Основная задача в том, как заметил needDrivers, чтобы дослать данные работающему php скрипту.

Когда нужно создать туннель, perl-скрипт вызывает по HTTP-протоколу php-скрипт, который в свою очередь соединяется с целью и создает на сервере UNIX-сокет для межпроцессной связи. Когда нужно дослать данные, perl-скрипт опять же шлет данные по HTTP, а php-скрипт просто пишет в этот UNIX-сокет. Таким образом время ограничено только После закрытия соединения сокет удаляется.
Понятно, что php-скрипт не кроссплатформенный, UNIX-сокет в windows создать пока нельзя;)

Цитата:

Сообщение от needDrivers
работа скрипта ограничена max_execution_time и постоянное соединение поддерживать не удастся

если на сервере не запрещена функция set_time_limit то это легко исправляется. Даже если запрещена то соединение можно удерживать целых 30 секунд или сколько там выставят в настройках.

Цитата:

Сообщение от needDrivers
А не влияет ли отключение сокетов на функции stream_socket_*?
Может дело в этом? На большинстве серверов они отключены.

если они отключены то конечно скрипт работать не будет. Но на тех серверах, на которых я это тестил, вроде как с этим было все в порядке

needDrivers 14.04.2009 11:54

Раз у тебя stream_* плохо работают, попробуй тоже самое через socket_* функции реализовать.
Я вот такой тестовый скрипт прогонял:
PHP код:

<?php
    header
("Content-Type: text/plain; charset=windows-1251");

    echo 
"Creating...\r\n";
    
flush();
    
$fp socket_create(AF_UNIXSOCK_STREAM0);
    if(
$fp)
    {
        if(
$m === "r")
        {
            
socket_bind($fp"my.sock");
            
socket_listen($fp);
            
$sk socket_accept($fp);
            echo 
"socket_read: ".socket_read($sk1024PHP_BINARY_READ);
            
socket_close($sk);
        }
        else if(
$m === "w")
        {
            
socket_connect($fp"my.sock");
            echo 
"socket_write: ".socket_write($fp"1024, PHP_BINARY_READ");
        }

        
socket_close($fp);

        if(
$m === "r")
        {
            
unlink("my.sock");
        }
    }
    else
    {
        echo 
"error";
    }
?>

А для чего тебе сокс, который больше 30 секунд соединение держать не будет?

Была у меня мысля свой proxy_rd по такому же принципу реализовать, но столько геморроя с этим из-за каких-то 30 секунд соединения. Да и задача изначально у него другая - вроде как универсальный модификатор исходящих заголовков, хотя цель такая же - изменить заголовок таким образом, чтобы передать его в любом направлении любому клиенту (преимущественно скрипту).


Время: 04:17