SOCKS прокси на PHP

Discussion in 'PHP' started by bons, 8 Apr 2009.

  1. bons

    bons Elder - Старейшина

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    это недописанные скрипты для 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;
        }
    ?>
    Code:
    #!/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;
    }
     
    #1 bons, 8 Apr 2009
    Last edited: 8 Apr 2009
    1 person likes this.
  2. winstrool

    winstrool ~~*MasterBlind*~~

    Joined:
    6 Mar 2007
    Messages:
    1,413
    Likes Received:
    910
    Reputations:
    863
    Тоже довненько искал, что не буть подобное, также подписываюсь, автору темы плюс, за актуальность вопроса)
     
    _________________________
    1 person likes this.
  3. needDrivers

    needDrivers New Member

    Joined:
    30 Jan 2009
    Messages:
    16
    Likes Received:
    1
    Reputations:
    0
    Не вглядываясь просмотрел твой скрипт.

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

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

    Pashkela Динозавр

    Joined:
    10 Jan 2008
    Messages:
    2,750
    Likes Received:
    1,044
    Reputations:
    339
    http://www.phpclasses.org/browse/package/1822.html
    http://www.phpclasses.org/browse/package/5049.html

    во второй ссылке клянется, что рабочий, плюс есть поддержка авторизации. В обоих случаях есть пример использования. Сам не проверял.
     
    #4 Pashkela, 14 Apr 2009
    Last edited: 14 Apr 2009
  5. Doom123

    Doom123 Elder - Старейшина

    Joined:
    11 Nov 2006
    Messages:
    749
    Likes Received:
    244
    Reputations:
    22
    я гдето в теме полезных скриптов выкладывал рабочий класс для работы через сокс ...
     
  6. bons

    bons Elder - Старейшина

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    тут немного не тот принцип действия и сокс-класс тут как-бы не поможет. Основная задача в том, как заметил needDrivers, чтобы дослать данные работающему php скрипту.

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

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

    если они отключены то конечно скрипт работать не будет. Но на тех серверах, на которых я это тестил, вроде как с этим было все в порядке
     
    #6 bons, 14 Apr 2009
    Last edited: 14 Apr 2009
  7. needDrivers

    needDrivers New Member

    Joined:
    30 Jan 2009
    Messages:
    16
    Likes Received:
    1
    Reputations:
    0
    Раз у тебя 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 секунд соединения. Да и задача изначально у него другая - вроде как универсальный модификатор исходящих заголовков, хотя цель такая же - изменить заголовок таким образом, чтобы передать его в любом направлении любому клиенту (преимущественно скрипту).
     
  8. needDrivers

    needDrivers New Member

    Joined:
    30 Jan 2009
    Messages:
    16
    Likes Received:
    1
    Reputations:
    0
    Забыл написать

    usock.php?m=r - создаём сокет
    usock.php?m=w - пишем в него
     
  9. paranoya

    paranoya New Member

    Joined:
    25 Aug 2007
    Messages:
    5
    Likes Received:
    4
    Reputations:
    0
    А вы ничего не путаете? мне всегда казалось что max_execution_time - это процесорное время работы скрипта. Т.е. реального времени там вполне может быть несколько часов.
     
  10. needDrivers

    needDrivers New Member

    Joined:
    30 Jan 2009
    Messages:
    16
    Likes Received:
    1
    Reputations:
    0
    Именно время выполнения скрипта!
     
  11. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    вобще вот уже готовое решение http://sourceforge.net/project/showfiles.php?group_id=178782 как собственно упоминалось не раз уже на этом форуме. я пользуюсь - только подправил немного - GET убрал, фигню всякую ненужную чтоб вес уменьшить и упростить, админку и тп, правда шифрование так и несмог заставить работать у себя.
     
  12. PaCo

    PaCo Elder - Старейшина

    Joined:
    6 Feb 2008
    Messages:
    436
    Likes Received:
    138
    Reputations:
    25
    Если не трудно выложи твой упрошеный вариант(под шифрование подразумеваеться https?).
     
  13. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    http://slil.ru/27501988
    но всеже советую качать оригинальный вариант
    http://sourceforge.net/project/showfiles.php?group_id=178782

    шифрование- трафик проходящий между скриптами шифруется.
     
    #13 ShAnKaR, 2 May 2009
    Last edited: 3 May 2009
  14. InFlame

    InFlame Banned

    Joined:
    27 Oct 2008
    Messages:
    207
    Likes Received:
    32
    Reputations:
    0
    При запуске client.pl выдаёт:
    Файл в папке лежит. В чём проблема?
     
  15. PaCo

    PaCo Elder - Старейшина

    Joined:
    6 Feb 2008
    Messages:
    436
    Likes Received:
    138
    Reputations:
    25
    Спс.
     
  16. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    у меня пашет хз что у вас там - попробуйте напрямую прописать конфиг чтоб не искал его. и кстати в офф версии тоже самое ?
     
  17. PaCo

    PaCo Elder - Старейшина

    Joined:
    6 Feb 2008
    Messages:
    436
    Likes Received:
    138
    Reputations:
    25
    из 70 мною перепробаваных хостов(php 5, safe_mode off и сокеты соответсвенно на всех были) он завелся на двух, причем носок поднялся а вот https так и нет, я про офф версию. А насчет путей то он ясно говорит куда скинуть конфиг(C:/Perl/site/lib/).
     
  18. MasterMushi

    MasterMushi Member

    Joined:
    19 Dec 2007
    Messages:
    29
    Likes Received:
    5
    Reputations:
    3
    А если попататься убрать ограничение на время исполенния скрипта функцией set_time_limit(0);
     
  19. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    у меня честно на всех работало где php 5 и папка для темпа, может конечно я мало еще юзал ее, виндовые сервера и фри хостинги в том числе. там у серверной части логи же есть тоже смотрите на всякий случай.
     
  20. ShAnKaR

    ShAnKaR Пачка маргарина

    Joined:
    14 Jul 2005
    Messages:
    904
    Likes Received:
    297
    Reputations:
    553
    да нет вроде больше ничего.