2013-06-05 2 views
6

Этот скрипт прослушивает IP-порт и намеревается действовать как прокси-сервер HTTP (S).cURL как прокси, обрабатывать метод HTTPS/CONNECT

Запросы на HTTP-URL работают нормально, но я натыкаюсь на то, как обращаться с HTTPS-запросами и, более конкретно, с подтверждением SSLv3 после того, как клиент отправляет запрос CONNECT в прокси-сервер.

Ближайший я пришел к тому, что выглядит как ответ является:

  • CURLOPT_HTTPPROXYTUNNEL вариант Libcurl для туннельных данных между клиентом и целевым сервером
  • stream_socket_enable_crypto(), чтобы, возможно, «делать вещи» с зашифрованные данные

Я действительно не уверен, поэтому указатель на то, как с этим бороться, будет очень признателен.

Вот пример запроса: http://pastebin.com/xkWhGyjW

<?php 

class proxy { 

    static $server; 
    static $client; 

    static function headers($str) { // Parses HTTP headers into an array 
     $tmp = preg_split("'\r?\n'",$str); 
     $output = array(); 
     $output[] = explode(' ',array_shift($tmp)); 
     $post = ($output[0][0] == 'POST' ? true : false); 

      foreach($tmp as $i => $header) { 
       if($post && !trim($header)) { 
        $output['POST'] = $tmp[$i+1]; 
        break; 
       } 
       else { 
        $l = explode(':',$header,2); 
        $output[$l[0]] = $l[0].': '.ltrim($l[1]); 
       } 
      } 
     return $output; 
    } 

    public function output($curl,$data) { 
     socket_write(proxy::$client,$data); 
     return strlen($data); 
    } 
} 




$ip = "127.0.0.1"; 
$port = 50000; 

proxy::$server = socket_create(AF_INET,SOCK_STREAM, SOL_TCP); 
socket_set_option(proxy::$server,SOL_SOCKET,SO_REUSEADDR,1); 
socket_bind(proxy::$server,$ip,50000); 
socket_getsockname(proxy::$server,$ip,$port); 
socket_listen(proxy::$server); 

while(proxy::$client = socket_accept(proxy::$server)) { 

    $input = socket_read(proxy::$client,4096); 
    preg_match("'^([^\s]+)\s([^\s]+)\s([^\r\n]+)'ims",$input,$request); 
    $headers = proxy::headers($input); 

     echo $input,"\n\n"; 
      if(preg_match("'^CONNECT '",$input)) { // HTTPS 
       // Tell the client we can deal with this 
       socket_write(proxy::$client,"HTTP/1.1 200 Connection Established\r\n\r\n"); 
       // Client sends binary data here (SSLv3, TLS handshake, Client hello?) 
       // socket_read(proxy::$client,4096); 
       // ? 
      } 
      else { // HTTP 

         $input = preg_replace("'^([^\s]+)\s([a-z]+://)?[a-z0-9\.\-]+'","\\1 ",$input); 
         $curl = curl_init($request[2]); 
         curl_setopt($curl,CURLOPT_HEADER,1); 
         curl_setopt($curl,CURLOPT_HTTPHEADER,$headers); 
         curl_setopt($curl,CURLOPT_TIMEOUT,15); 
         curl_setopt($curl,CURLOPT_RETURNTRANSFER,1); 
         curl_setopt($curl,CURLOPT_NOPROGRESS,1); 
         curl_setopt($curl,CURLOPT_VERBOSE,1); 
         curl_setopt($curl,CURLOPT_AUTOREFERER,true); 
         curl_setopt($curl,CURLOPT_FOLLOWLOCATION,1); 
         curl_setopt($curl,CURLOPT_WRITEFUNCTION, array("proxy","output")); 
         curl_exec($curl); 
         curl_close($curl); 
      } 
    socket_close(proxy::$client); 
} 
socket_close(proxy::$server); 


?> 
+0

Не могли бы вы отправить на Pastebin код для запросов HTTP URL? –

+0

Конечно ... Я использую командную строку cURL для тестирования. http://pastebin.com/xkWhGyjW – innvo

+0

Вы пробовали другие сайты, чем google.com (в зависимости от вашего pastebin)? (см. здесь http://superuser.com/questions/404116/cannot-connect-to-website-ssl-handshaking-fails) И вы пытались подключиться без прокси-сервера, то есть 'curl -L -i -v ' https://www.google.com/ "'? Каков результат, если вы добавляете '--trace tracetxt.tmp' к вашему зависанию, при использовании вашего прокси-сервера и при непосредственном подключении? Возможно, может помочь различие в трассировке? – stef77

ответ

4

Если я правильно понимаю, вы пишете HTTP прокси-сервер в PHP. Опция CURLOPT_HTTPPROXYTUNNEL используется, если вы хотите подключиться к прокси-серверу с использованием библиотеки PHP cURL и использовать CONNECT вместо GET. В этом случае это не имеет значения.

Когда прокси-сервер (PROXY) получает CONNECT запрос, он должен подключиться к указанному хосту (ENDPOINT) с использованием socket_create и socket_connect. Как только соединение будет установлено, сообщите клиенту (КЛИЕНТУ), отправив HTTP/1.1 200 Connection Established. После этого вы захотите скопировать все данные, которые ENDPOINT отправляет PROXY в КЛИЕНТ, и все данные, которые КЛИЕНТ отправляет PROXY в ENDPOINT.

Использование cURL, как в вашем примере, приведет к созданию нескольких соединений. Чтобы обрабатывать несколько соединений, я использовал pcntl_fork, который разворачивает новый процесс на каждый запрос CONNECT.

Вот рабочий пример:

<?php 

class proxy { 

    static $server; 
    static $client; 

    static function headers($str) { // Parses HTTP headers into an array 
     $tmp = preg_split("'\r?\n'",$str); 
     $output = array(); 
     $output[] = explode(' ',array_shift($tmp)); 
     $post = ($output[0][0] == 'POST' ? true : false); 

      foreach($tmp as $i => $header) { 
       if($post && !trim($header)) { 
        $output['POST'] = $tmp[$i+1]; 
        break; 
       } 
       else { 
        $l = explode(':',$header,2); 
        $output[$l[0]] = $l[0].': '.ltrim($l[1]); 
       } 
      } 
     return $output; 
    } 

    public function output($curl,$data) { 
     socket_write(proxy::$client,$data); 
     return strlen($data); 
    } 
} 




$ip = "127.0.0.1"; 
$port = 50000; 

proxy::$server = socket_create(AF_INET,SOCK_STREAM, SOL_TCP); 
socket_set_option(proxy::$server,SOL_SOCKET,SO_REUSEADDR,1); 
socket_bind(proxy::$server,$ip,50000); 
socket_getsockname(proxy::$server,$ip,$port); 
socket_listen(proxy::$server); 

while(proxy::$client = socket_accept(proxy::$server)) { 

    $input = socket_read(proxy::$client,4096); 
    preg_match("'^([^\s]+)\s([^\s]+)\s([^\r\n]+)'ims",$input,$request); 
    $headers = proxy::headers($input); 

     echo $input,"\n\n"; 
      if(preg_match("'^CONNECT ([^ ]+):(\d+) '",$input,$match)) { // HTTPS 
       // fork to allow multiple connections 
       if(pcntl_fork()) 
        continue; 

       $connect_host = $match[1]; 
       $connect_port = $match[2]; 

       // connect to endpoint 
       $connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 
       if(!socket_connect($connection, gethostbyname($connect_host), $connect_port)) 
        exit; 

       // let the client know that we're connected 
       socket_write(proxy::$client,"HTTP/1.1 200 Connection Established\r\n\r\n"); 

       // proxy data 
       $all_sockets = array($connection, proxy::$client); 
       $null = null; 
       while(($sockets = $all_sockets) 
         && false !== socket_select($sockets, $null, $null, 10) 
       ) { 
        // can we read from the client without blocking? 
        if(in_array(proxy::$client, $sockets)) { 
         $buf = null; 
         socket_recv(proxy::$client, $buf, 8192, MSG_DONTWAIT); 
         echo "CLIENT => ENDPOINT (" . strlen($buf) . " bytes)\n"; 
         if($buf === null) 
          exit; 
         socket_send($connection, $buf, strlen($buf), 0); 
        } 

        // can we read from the endpoint without blocking? 
        if(in_array($connection, $sockets)) { 
         $buf = null; 
         socket_recv($connection, $buf, 8192, MSG_DONTWAIT); 
         echo "ENDPOINT => CLIENT (" . strlen($buf) . " bytes)\n"; 
         if($buf === null) 
          exit; 
         socket_send(proxy::$client, $buf, strlen($buf), 0); 
        } 
       } 

       exit; 
      } 
      else { // HTTP 

         $input = preg_replace("'^([^\s]+)\s([a-z]+://)?[a-z0-9\.\-]+'","\\1 ",$input); 
         $curl = curl_init($request[2]); 
         curl_setopt($curl,CURLOPT_HEADER,1); 
         curl_setopt($curl,CURLOPT_HTTPHEADER,$headers); 
         curl_setopt($curl,CURLOPT_TIMEOUT,15); 
         curl_setopt($curl,CURLOPT_RETURNTRANSFER,1); 
         curl_setopt($curl,CURLOPT_NOPROGRESS,1); 
         curl_setopt($curl,CURLOPT_VERBOSE,1); 
         curl_setopt($curl,CURLOPT_AUTOREFERER,true); 
         curl_setopt($curl,CURLOPT_FOLLOWLOCATION,1); 
         curl_setopt($curl,CURLOPT_WRITEFUNCTION, array("proxy","output")); 
         curl_exec($curl); 
         curl_close($curl); 
      } 
    socket_close(proxy::$client); 
} 
socket_close(proxy::$server); 
+0

спасибо, я попробую это позже сегодня. Я fork уже, но просто удалил его для публикации здесь, поскольку основное внимание уделяется работе с HTTPS-соединениями, действующими как прокси-сервер/и метод CONNECT. Просто примечание, подобное форматированию оставит процесс зомби, полезно использовать что-то вроде pcntl_signal (SIGCHLD, SIG_IGN) ... и сокет клиент/сервер должен быть закрыт как родителем, так и дочерним. – innvo

+0

выглядит, как будто это делает работу для меня, хорошо сделано, сэр. – innvo

+0

Да, вам нужно улучшить код безопасности. Это был скорее быстрый и грязный пример желаемой функциональности. –

Смежные вопросы