pproxy - прокси на PHP

Discussion in 'Избранное' started by bons, 24 Nov 2008.

  1. bons

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

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

    Описание

    Состоит из двух частей. На удаленный веб-сервер заливается первая часть прокси, написанная на PHP - скрипт pproxy.php. На локалхосте запускается вторая часть прокси, реализованная на Perl (скрипт plocal.pl), которая прослушивает порт как HTTP-прокси. На этот локальный HTTP прокси настраивается, например, браузер.

    Скрипты

    pproxy.php - первая, удаленная часть прокси, на PHP:

    PHP:
    <?php

    //$secret = 'pproxypass';

    if(isset($_POST['query']) && isset($_POST['host']))
    {
        if(isset(
    $secret) && ($_POST['secret'] != $secret))exit;
        
    header('Content-type: application/octet-stream');
        @
    set_time_limit(0);
        
    $query base64_decode(str_replace(' ''+'$_POST['query']));
        @list(
    $host$port) = @explode(':'base64_decode($_POST['host']));
        if(!
    $port)$port 80;
        
    $ip gethostbyname($host);
        if(
    $fp = @fsockopen($ip$port$errno$errstr20))
        {
            
    fwrite($fp$query);        
            while(!
    feof($fp))
            {
                
    $answer fread($fp1024);
                echo 
    $answer;
            }
            
    fclose($fp);
        }
        exit;
    }
    ?>
    plocal.pl - вторая, локальная часть прокси, на Perl:

    Code:
    use HTTP::Daemon;
    use MIME::Base64 ();
    use Getopt::Long;
    use POSIX ":sys_wait_h";
    use strict;
    
    my $user_agent = 'Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.0.4) Gecko/2008102920 Firefox/3.0.4';
    
    my $errheader = "HTTP/1.1 200 OK\x0D\x0AContent-Type: text/plain\x0D\x0A\x0D\x0A";
    my $errmsg1 = $errheader . 'Could not connect to server';
    my $errmsg2 = $errheader . 'Could not connect to pproxy';
    
    my ($pproxyhost, $pproxyport, $pproxyurl);
    my ($tunnelhost, $tunnelport);
    my ($pproxy, $bindport, $tunnel, $secret);
    my ($destaddr, $destport, $desturl);
    
    #Вывод справки
    Usage() if @ARGV==0;
    
    #Задание опций
    GetOptions(
             "px=s"		=> \$pproxy,
             "bp=s" 	=> \$bindport,
             "tpx=s"	=> \$tunnel,
             "pwd=s"	=> \$secret
         );
    die "need pproxy.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 "# pproxyhost = $pproxyhost\n";
    print "# pproxyport = $pproxyport\n";
    print "# pproxyurl = $pproxyurl\n";
    print "# bindport = $bindport\n\n";
    
    $|=1;
    my %children;
    my $slave;
    
    #Прием подключений
    my $master = HTTP::Daemon->new(	LocalPort => $bindport,
    				LocalAddr => 'localhost', 
    				Reuse=>1)
    || die "Can't start server ($@)";
    &MainProc($slave) while $slave = $master->accept;
    
    sub MainProc
    {
    		my $conn = shift;
    
    		#Прием HTTP-запроса
    		my $request = $conn->get_request();
    		
    		#Ответвление процесса(потока)
    		my $pid = fork();
    		unless(defined($pid))
    		{
    			print "# Erorr couldn't fork\n";
    			close $conn;
    			return;
    		}
    
    		if($pid)
    		{
    			close $conn;
    			$children{$pid}++;
    			foreach(keys %children )
    			{
    				my $kid = waitpid($_, &WNOHANG);
    				delete $children{$_} if($kid == -1 || $kid == $_);
    			}
    			return;
    		}
    
    		#Преобразование HTTP заголовка
    		$request -> remove_header('Proxy-Connection');
    		$request -> remove_header('Keep-Alive');
    		$request -> header(Connection=>'close');
    		my $host = $request -> header('Host');
    		my $http = $request -> as_string();
    		my $head_end = index ($http, "\x0A\x0A") + 2;
    		my $head = substr ($http, 0, $head_end);
    		my $post = substr ($http, $head_end);
    		$head =~ s/\x0A/\x0D\x0A/g;
    		$http = $head . $post;
    		$http =~ s/http:\/\/$host//;
    		my $time_start = time;
    		
    		#Отправка запроса прокси и прием результата
    		SendToPProxy(	MIME::Base64::encode($host),
    				MIME::Base64::encode($http),
    				$conn
    					);
    
    		my $time_end = time;
    		
    		#Лог
    		print &TranslateTimeHour($time_end), "   ", $host,
    			" (", &TranslateTime($time_end - $time_start),
    			")\n";
    
    		close $conn;
    		exit;
    }
    
    sub SendToPProxy
    {
    	my $dest_host = shift;
    	my $query_content = shift;
    	my $clientsock = shift;
    	
    	my $proxysock = IO::Socket::INET->new(Proto=>'tcp',PeerAddr=>$destaddr,PeerPort=>$destport);
    	unless($proxysock)
    	{
    		syswrite($clientsock, $errmsg2, length($errmsg2));
    		return; 
    	}
    	
    	my $post_query;
    	$post_query = 'secret=' . $secret . '&' if defined($secret);
    	$post_query .= 'host=' . $dest_host . '&query=' . $query_content;
    	my $postlen = length($post_query);
    
    	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_query;
    
    	syswrite($proxysock, $request, length($request));
    	my ($result, $buffer, $response);
    	my $contentstart = -1;
    	my $count = 0;
    	while(1)
    	{
    		$result = sysread($proxysock, $buffer, 1024);
    		last if !defined($result) || !$result;
    
    		if($contentstart == -1)
    		{
    			$response .= $buffer;
    			last if length($response)>65535;
    			$contentstart = index($response,"\x0D\x0A\x0D\x0A");
    			next if $contentstart == -1;
    			$buffer = substr($response, $contentstart+4);
    		}
    		$count += length($buffer);
    		syswrite($clientsock, $buffer, length($buffer));
    	}
    	syswrite($clientsock, $errmsg1, length($errmsg1)) if $count == 0;
    	close $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;
    }

    Использование прокси

    Допустим адрес pproxy.php будет http://site.com/proxy/pproxy.php
    Тогда локальный скрипт запускается так:
    Code:
    perl plocal.pl -px http://site.com/proxy/pproxy.php
    По умолчанию открывается порт 8008


    Построение цепочки прокси

    Для примера случай с двумя элементами цепочки.
    Есть два веб-сервера с двумя скриптами pproxy:
    http://site1.com/proxy/pproxy.php и
    http://site2.com/proxy/pproxy.php

    Локальный скрипт запускается два раза с такими параметрами:
    Code:
    perl plocal.pl -px http://site1.com/proxy/pproxy.php
    perl plocal.pl -px http://site2.com/proxy/pproxy.php -tpx http://localhost:8008 -bp 8009
    
    Браузер следует настроить на 8009 порт и трафик пойдет по такой цепочке:
    localhost -> site1.com -> site2.com -> target

    Думаю, несложно будет настроить и на более длинную цепочку.

    Запуск через Tor

    Если вы используете Tor вместе с Privoxy (по умолчанию на порту 8118)
    тогда прокси запускается так:
    Code:
    perl plocal.pl -px http://site.com/proxy/pproxy.php -tpx http://localhost:8118
    Соответственно http://site.com/proxy/pproxy.php - адрес PHP-прокси
    К сожалению, через Tor некоторые сайты могут загружаться некорректно,
    причины пока неизвестны.

    Установка пароля

    В pproxy.php раскомментировать строчку,
    написать там свой пароль

    PHP:
    $secret 'pproxypass';
    в параметрах запуска plocal.pl указать его же.
    Code:
    perl plocal.pl -px http://site.com/proxy/pproxy.php -pwd pproxypass
    Примечания

    1. Если вы хотите в качестве элемента цепочки узел SOCKS, то Privoxy поможет вам (см пример с Tor)
    2. pproxy.php очень компактен и легко может быть внедрен в код сайта,
    Возможно это немного повысит ваш уровень анонимности.
    3. Не стоит забывать что веб-серверы ведут логи и восстановить истинный источник нетрудно даже по цепочке;)
    4. Альтернативный клиент(Delphi/Pascal) с открытым исходным кодом доступен тут http://dump.ru/file/3320224

    Спасибо за совет: b3, AkyHa_MaTaTa
     
    #1 bons, 24 Nov 2008
    Last edited: 22 Jan 2011
    madik, makag, LASS0 and 31 others like this.
  2. DVD_RW

    DVD_RW Banned

    Joined:
    27 Apr 2008
    Messages:
    0
    Likes Received:
    202
    Reputations:
    -36
    эм...а прокси хттп\с или сокс?
     
  3. nerezus

    nerezus Banned

    Joined:
    12 Aug 2004
    Messages:
    3,191
    Likes Received:
    729
    Reputations:
    266
    http
    Перенес в избранное. кстати.
     
    makag and DVD_RW like this.
  4. KemSucks

    KemSucks Banned

    Joined:
    6 Jan 2008
    Messages:
    10
    Likes Received:
    10
    Reputations:
    -5
    может как нить на https прикрутить можно?
     
  5. bons

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

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    нет, HTTPS тут реализовать почти невозможно. HTTPS прокси - он как и сокс основан на постоянном соединении и непрерывном обмене данными. А здесь, как видишь, обмен данными сводится к отсылке HTTP-запроса браузера и получению результата.
     
    #5 bons, 28 Nov 2008
    Last edited: 28 Nov 2008
  6. jumperby

    jumperby New Member

    Joined:
    26 Jul 2007
    Messages:
    8
    Likes Received:
    1
    Reputations:
    0
    А оградить это дело от чужих глаз можно как-то? логин:пасс какой-нибудь.
    Связка ActivePerl + Opera 9.62 + Proxy, дает в результате тормоза, не в курсе как это можно исправить?
    хттпс идет как я понял не через проксик, а через впн?
     
    #6 jumperby, 28 Nov 2008
    Last edited: 28 Nov 2008
  7. b3

    b3 Banned

    Joined:
    5 Dec 2004
    Messages:
    2,170
    Likes Received:
    1,155
    Reputations:
    202
    допиши в начало кода РНР, строки:
    Время выполнения - неограничено. Скрипт не останавливаеться после дисконекта клиента.
     
  8. bons

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

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    дописал set_time_limit(0);
    добавил авторизацию в самом простом виде.
     
  9. jumperby

    jumperby New Member

    Joined:
    26 Jul 2007
    Messages:
    8
    Likes Received:
    1
    Reputations:
    0
    Подскажите пожалуйста, почему ActivePerl 5.10 так жестко начинает жрать память через минуту юзания проксика, заодно начинает жрать и браузер (опера и файрфокс, ведут себя одинаково). Как это исправить?
    п.с. выкиньте с фиксом версию (сет тайм лимит + авторизация)
     
  10. bons

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

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    да, память и процессорное время оно ест немилосердно, это расплата за небольшой размер скрипта.
    Если слабая машина то могу предложить
    1. Снизь приоритет выполнения процесса, это можно сделать в диспетчере задач. (процесс perl.exe)
    2. Отключи многопоточность, тогда запросы будут выполняться последовательно и не так загружать процессор.

    закомментируй эти строчки
    Code:
    ...
    		#my $pid = fork();
    		#unless(defined($pid))
    		#{
    		#	print "# Erorr couldn't fork\n";
    		#	close $conn;
    		#	return;
    		#}
    
    		#if($pid)
    		#{
    		#	close $conn;
    		#	$children{$pid}++;
    		#	foreach(keys %children )
    		#	{
    		#		my $kid = waitpid($_, &WNOHANG);
    		#		delete $children{$_} if($kid == -1 || $kid == $_);
    		#	}
    		#	return;
    		#}
    ...
    и еще команду exit:

    Code:
    ...
    		close $conn;
    		#exit;
    }
    ...
     
    #10 bons, 30 Nov 2008
    Last edited: 30 Nov 2008
  11. c0m

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

    Joined:
    26 Jan 2007
    Messages:
    37
    Likes Received:
    3
    Reputations:
    0
    так, а если залью скрипт в индекс сайта, по идее тяжело будет отпределить кто через них лазел и куда заходил?
     
  12. bons

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

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    в логах апача будет сохранен факт обращения к скрипту с твоего айпи и скорее всего User_Agent перла (User-Agent впрочем можно сменить)
    То, куда ты обращался через этот скрипт возможно будет сохранено в логах файрволла сервера, это зависит от его настроек.
     
  13. jumperby

    jumperby New Member

    Joined:
    26 Jul 2007
    Messages:
    8
    Likes Received:
    1
    Reputations:
    0
    Нашел себе счастье, спешу поделиться.
    HTTPTunnel​
    На машине должен быть перл, на серваке можно через перл и обычный пхп. Все очень шустро работает, кучи возможностей:
    Portmapping, SOCKS4, SOCKS5, веб-админка, авторизация с мускула или ldap.
    http://sourceforge.net/projects/http-tunnel/
    Все шустро работает и носки есть :) После ппрокси как-будто в летающую тарелку сел...
    п.с. искал в чем трабл этого скрипта (ппрокси), вроде как в том что use много используется
     
  14. i-Worm.Fizzer

    i-Worm.Fizzer New Member

    Joined:
    5 Dec 2008
    Messages:
    15
    Likes Received:
    0
    Reputations:
    0
    Хостинг провайдер может как-то запалить, что я использую проксик, через него ?
     
  15. serfertty

    serfertty Guest

    Reputations:
    0
    Да.Большинство хостингов отключают внешние соеденения,а те которые разрешают все быстро палят.
     
  16. DIAgen

    DIAgen Banned Life!

    Joined:
    2 May 2006
    Messages:
    1,055
    Likes Received:
    376
    Reputations:
    460
    Только есть маленький не достаток, пока perl не загрузит полность весь фаил он его не выдаст, и из-за этого такие тупки, кто знает к это можно исправить? :)
     
  17. preda1or

    preda1or Member

    Joined:
    27 Oct 2008
    Messages:
    167
    Likes Received:
    96
    Reputations:
    6
    DIAgen могу предположить, только, сорри если не прав
    в php есть команда flush...
    в перле нет аналогов?
     
  18. bons

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

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    разве что переписать весь код, написаный с LWP на сокеты. Как время будет, может сделаю...
     
    1 person likes this.
  19. banned

    banned Banned

    Joined:
    20 Nov 2006
    Messages:
    3,324
    Likes Received:
    1,193
    Reputations:
    252
    Почему ssl & socks нельзя?
    Curl можно использовать
     
  20. bons

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

    Joined:
    20 Dec 2007
    Messages:
    286
    Likes Received:
    121
    Reputations:
    21
    SOCKS здесь вряд ли возможен исходя из самого механизма обращения к прокси.
    То есть алгоритм такой:
    1. perl-скрипт принимает HTTP-запрос и передает POST-параметром к php-скрипту
    2. PHP-скрипт принимает POST-параметр и с помощью сокетов посылает целевому серверу, все что вернул ему сервер отправляет назад perl-скрипту

    для реализации обычного HTTP-запроса этого достаточно. Но SOCKS предусматривает создание постоянного канала, то есть многократный прием и передачу данных. А как передавать данные скрипту второй раз? При таком подходе это невозможно!
    HTTPS прокси или HTTP CONNECT прокси по сути тот же SOCKS-прокси, так как для подключения к HTTPS нужен сложный протокол - передача сертификатов и т.д.

    Но если цель не создание полноценного HTTPS-прокси а например просто зайти через прокси на сайт, расположенный на https то все же можно например придумать следующий алгоритм:
    1. perl-скрипт принимает запрос как HTTP-прокси (!) и передает его POST-параметром PHP-скрипту
    2. PHP-скрипт с помощью curl посылает этот запрос целевому серверу и т.д.

    в теории это возможно, но при этом возникает некоторые проблемы:
    - как браузер будет сообщать perl-скрипту что целевой хост именно на https а не на http
    - на сервере с PHP-прокси будет возникать проблемы c памятью - ведь PHP-скрипту необходмо сначала полностью принять результат и только потом он сможет передать результат и освободить занятую память. (подобно проблемам с памятью в текущей версии perl-скрипта)

    Плюс еще не на каждом сервере curl доступен

    P.S. Посмотрел исходник HTTPTunnel. Можно использовать IPC, тогда все проблемы сразу решаются...
     
    #20 bons, 13 Dec 2008
    Last edited: 14 Dec 2008