Ходим в php черех socks4/5

Тема в разделе "Статьи", создана пользователем hodik, 20 авг 2007.

  1. hodik
    hodik Guest
    Класс сокета с поддержкой socks4/5-прокси
    фичи:
    - полная поддержка сокс4/5-прокси
    - автоопределение типа прокси


    вспомогательный класс (socket.class.php)
    от этого класса идет наследование класса с поддержкой соксов
    PHP:
    <?
    /**
     * Socket Class

     * @author  hodik <icq://664679>
     * @package Classes
     * @subpackage network
     * @version 1.0
     * @filesource
     */

    /**
     * CSocket

     * @package Classes
     * @subpackage network
     */ 

    class CSocket{
        
    /**
          * @desc
          * @access private
          */
        
    var $_persistent = false;
        
        
    /**
          * @desc
          * @access private
          */
        
    var $_connected = false;
        
        
    /**
          * @desc
          * @access private
          */
        
    var $_conn = null;
        
        
    /**
          * Конструктор класса
          * @param boolean постоянное соединение или нет
          * @return void
          */
        
    function CSocket($persistent = false)
        {
            
    $this->_persistent = $persistent;
        }
        
        
    /**
          * Проверяет, установлено ли соединение
          * @return boolean
          */
        
    function _valid()
        {
            return 
    is_resource($this->_conn) && ($this->_connected == true);
        }
        
        
    /**
          * Устанавливает соединение
          * @param string
          * @param int
          * @return bool
          */
        
    function connect($url, $timeout = 10)
        {
            
    $host     = parse_url($url, PHP_URL_HOST);
            if(!
    $port = parse_url($url, PHP_URL_PORT)) $port = 80;
            
            
    $sockFunc    = ($this->_persistent) ? "pfsockopen" : "fsockopen";
            
    $this->_conn = $sockFunc($host, $port, $errno, $errstr, $timeout);
            
            if(!
    $this->_conn)
            {
                
    trigger_error("Couldn't connect to $host:$port", E_USER_WARNING);
                return 
    false;
            }
            
            
    $this->_connected = true;
            return 
    true;
        }
        
        
    /**
          * Посылает данные (binary-safe)
          * @param какие-то данные
          * @return boolean
          */
        
    function send($data)
        {
            if(!
    $this->_valid())
            {
                
    trigger_error("Error on sending data. Reason: Not connected", E_USER_WARNING);
                return 
    false;
            }
            
            if(
    fputs($this->_conn, $data) === FALSE)
            {
                
    trigger_error("Error on sending data to the server", E_USER_WARNING);
                return 
    false;
            }
            return 
    true;
        }
        
        
    /**
          * Читает из входного потока определенное количество символов
          * @param int
          * @return string
          */
        
    function read($len)
        {
            if(!
    $this->_valid())
            {
                
    trigger_error("Error on reading data. Reason: Not connected", E_USER_WARNING);
                return 
    false;
            }

            if((
    $buf = fgets($this->_conn, $len)) === FALSE)
            {
                
    trigger_error("Error on reading data. Reason: Connection error", E_USER_WARNING);
                return 
    false;
            }
            return 
    $buf;
        }
        
        
    /**
          * Читает строку из входного потока
          * @return string
          */
        
    function readString()
        {
            if(!
    $this->_valid())
            {
                
    trigger_error("Error on reading string. Reason: Not connected", E_USER_WARNING);
                return 
    false;
            }
            
            
    $str = null; 
            while(!
    feof($this->_conn))
            {
                if((
    $buf = fgets($this->_conn, 128)) === FALSE)
                {
                    
    trigger_error("Error on reading string. Reason: Connection error", E_USER_WARNING);
                    return 
    false;
                }
                
                
    $str .= $buf;
                if(
    strlen($str) >= 1 && (substr($str, -2) == "\r\n" || substr($str, -1) == "\n"))
                {
                    return 
    rtrim($str);
                }
            }
            
            if(
    $str) return $str; else return false;
        }
        
        
    /**
          * Читает все данные из входного потока
          * @return string
          */
        
    function readAll()
        {
            if(!
    $this->_valid())
            {
                
    trigger_error("Error on reading data. Reason: Not connected", E_USER_WARNING);
                return 
    false;
            }

            
    $str = "";
            while(!
    feof($this->_conn))
            {
                if((
    $buf = fgets($this->_conn, 128)) === FALSE)
                {
                    
    trigger_error("Error on reading data. Reason: Connection error", E_USER_WARNING);
                    return 
    false;            
                }
                
    $str .= $buf;
            }
            return 
    $str;
        }
        
        
    /**
          * Проверяет, активно ли соединение
          * @return boolean
          */
        
    function isActive()
        {
            return 
    $this->_valid();
        }
        
        
    /**
          * Разрывает соединение
          * @return boolean
          */
        
    function disconnect()
        {
            if(!
    $this->_valid())
            {
                
    trigger_error("Error on disconnecting. Reason: Not connected", E_USER_WARNING);
                return 
    false;
            }
            
            
    $this->_connected = false;
            
    fclose($this->_conn);
            return 
    true;
        }
    }
    ?>
    собсно сам сокет-класс для соксов
    (socks.class.php)
    PHP:
    <?
    /**
     * Socket Class Through SOCKS4/5 proxy

     * @author hodik <icq://664679>
     * @package Classes
     * @subpackage network
     * @version 1.0
     * @filesource
     */

    /**
     * CSocksSocket

     * @package Classes
     * @subpackage network
     */ 

    require_once("socket.class.php");
    define("SOCKS4_TYPE", 1);
    define("SOCKS5_TYPE", 2);
    define("AUTOMATIC_TYPE", 3);

    class 
    CSocksSocket extends CSocket{
        
    /**
          * @desc
          * @access private
          */
        
    var $_proxyHost;
        
        
    /**
          * @desc
          * @access private
          */    
        
    var $_proxyPort;
        
        
    /**
          * @desc
          * @access private
          */
        
    var $_proxyType;

        
    /**
          * @desc
          * @access private
          */
        
    var $_proxyUser;

        
    /**
          * @desc
          * @access private
          */
        
    var $_proxyPwd;
        
        
    /**
          * @desc
          * @access private
          */
        
    var $_host;
        
        
    /**
          * @desc
          * @access private
          */
        
    var $_port;

        
    /**
          * @desc
          * @access private
          */
        
    var $_timeout;
        
        
    /**
          * Устанавливает прокси и его тип
          * @param string
          * @return void
          */
        
    function setProxy($proxyURL)
        {
            
    $this->_proxyHost = parse_url($proxyURL, PHP_URL_HOST);
            
    $this->_proxyPort = parse_url($proxyURL, PHP_URL_PORT);
            
    $this->_proxyUser = parse_url($proxyURL, PHP_URL_USER);
            
    $this->_proxyPwd  = parse_url($proxyURL, PHP_URL_PASS);
            
    $this->_proxyType = strtolower(parse_url($proxyURL, PHP_URL_SCHEME));
        }
        
        
    /**
          * Преобразует имя хоста или его адрес в число (4 байта)
          * @param string
          * @return int
          * @access private
          */
        
    function _host2int($host)
        {
            
    $ip = gethostbyname($host);
            if(
    preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/", $ip, $matches))
            {
                
    $retVal = pack("C4", $matches[1], $matches[2], $matches[3], $matches[4]);
            }
            return 
    $retVal;
        }
        
        
    /** 
          * Соединяется через socks4
          * @access private
          * @return boolean
          */
        
    function _connectThroughSocks4()
        {
            
    $query  = pack("C2", 0x04, 0x01); //VN, CD
            
    $query .= pack("n", $this->_port); //DSTPORT
            
    $query .= $this->_host2int($this->_host); //DSTIP
            
    $query .= ($this->_proxyUser != "")? $this->_proxyUser : "0"; //IDENTD
            
    $query .= pack("C", 0); //NULL
            
            
    if(!$this->send($query)) return false;
            if((
    $response = $this->read(9)) === false) return false;
            
            
    $answer   = unpack("Cvn/Ccd", substr($response, 0, 2));
            
            
    //по протоколу vn всегда должен быть 0
            
    if($answer["vn"] != 0)
            {
                
    trigger_error("The VN field of proxy server response is not zero. It should be 0", E_USER_WARNING);
                return 
    false;
            }
            
            switch(
    $answer["cd"])
            {
                case 
    90:
                    return 
    true;
                    break;
                case 
    91:
                case 
    92:
                case 
    93:
                    
    trigger_error("Request to proxy have been rejected or failed", E_USER_WARNING);
                    return 
    false;
                    break;
                default:
                    
    trigger_error("Unknow response from proxy", E_USER_WARNING);
                    return 
    false;
                    break;
            }
            return 
    false;
        }
        
        
    /**
          * Выполняет аутентификацию логин/пароль по протоколу SOCKS5
          * @access private
          * @return boolean
          */
        
    function _doSOCKS5Auth()
        {
            
    $request  = pack("C", 1); //VN
            
    $request .= pack("C", strlen($this->_proxyUser));
            
    $request .= $this->_proxyUser;
            
    $request .= pack("C", strlen($this->_proxyPwd));
            
    $request .= $this->_proxyPwd;
            
            if(!
    $this->send($request)) return false;
            if((
    $response = $this->read(3)) === false) return false;
            
            
    $answer = unpack("Cvn/Cresult", $response);
            if(
    $answer["vn"] != 1 && $answer["result"] != 0)
            {
                
    trigger_error("Proxy have rejected the connection. Reason: Invalid username or/and password", E_USER_WARNING);
                return 
    false;
            }
            return 
    true;
        }
        
        
    /**
          * Соединяется через socks5
          * @access private
          * @return boolean
          */
        
    function _connectThroughSocks5()
        {
            
    //выбор тип аутентификации
            
    if($this->_proxyUser)
            {
                
    $request = pack("C4", 5, //версия сокс-протокола
                                      
    2, //длина
                                      
    0, //без аутентификации
                                      
    2);//логин и пароль
            
    }
            else
            {
                
    $request = pack("C3", 5, 1, 0); //без аутентификации
            
    }
            
            if(!
    $this->send($request)) return false;
            if((
    $response = $this->read(3)) === false) return false;

            
    $answer   = unpack("Cver/Cmethod", $response);

            
    //аутентификация: логин и пароль
            
    if($answer["method"] == 2 && !$this->_doSOCKS5Auth()) return false;
            
    //без аутентификации
            
    elseif($answer["method"] == 0) { /* ничего не делаем*/ }
            else 
            {
                
    trigger_error("Proxy have rejected the connection. Reason: All the auth methods not supported", E_USER_WARNING);
                return 
    false;
            }
            
            
    //запрос CONNECT
            
    $request  = pack("C4", 0x05, 0x01, 0x00, 0x01); //VN=5,Method=1,Reserved=0,ATYP=ipv4
            
    $request .= $this->_host2int($this->_host); //DSTIP
            
    $request .= pack("n", $this->_port); //DSTPORT
            
            
    if(!$this->send($request)) return false;
            if((
    $response = $this->read(11)) === false) return false; //читаем ответ сервера

            
    $answer   = unpack("Cver/CREP", substr($response, 0, 2));
            switch(
    $answer["REP"])
            {
                case 
    0:
                    return 
    true;
                default:
                    
    trigger_error("Proxy have rejected the connection. Reason: code error={$answer['REP']}", E_USER_WARNING);
                    return 
    false;
            }
        }
        
        
    /**
          * Определяет тип прокси и соединяется
          * @return boolean
          */
        
    function _connectAutoType()
        {
            
    //попробуем как через сокс4, а вдруг получится
            
    if($this->_connectThroughSocks4())
            {
                
    $this->_proxyType = "socks4";
                return 
    true;
            }
            
    //остался последний вариант: сокс5 (!!!)
            
    else
            {
                
    //на всякий случай разорвать соединение, а то сервер подумает, что над ним издеваются =)
                
    $this->disconnect();
                if(!
    parent::connect("{$this->_proxyHost}:{$this->_proxyPort}", $this->_timeout)) return false;
                if(
    $this->_connectThroughSocks5())
                {
                    
    $this->_proxyType = "socks5";
                    return 
    true;
                }
            }
            
            
    //наверно это оказался SOCKS6, поддержку потом добавлю =)
            
    return false;
        }
        
        
    /**
          * Устанавливает соединение
          * @param string
          * @param int
          * @return boolean
          */
        
    function connect($url, $timeout = 10)
        {
            
    $this->_host     = parse_url($url, PHP_URL_HOST);
            if(!
    $this->_port = parse_url($url, PHP_URL_PORT)) $this->_port = 80;
            
    $this->_timeout  = $timeout;

            if(!
    parent::connect("{$this->_proxyHost}:{$this->_proxyPort}", $this->_timeout)) return false;
            
            switch(
    $this->_proxyType)
            {
                case 
    "socks4";
                    return 
    $this->_connectThroughSocks4();
                    break;
                case 
    "socks5":
                    return 
    $this->_connectThroughSocks5();
                    break;
                case 
    "socks":
                    return 
    $this->_connectAutoType();
                    break;
                default:
                    return 
    $this->_connectAutoType();
                    break;
            }
        }
    }
    ?>
    Пример использования:
    PHP:
    include("socks.class.php");

    $sock = new CSocksSocket();
    $sock->setProxy("socks://127.0.0.1:1080");//автоопределение
    $sock->setProxy("socks4://127.0.0.1:1080"); //явно задан вид - сокс4
    $sock->setProxy("socks5://127.0.0.1:1080"); //явно задан вид - сокс5
    $sock->connect("yandex.ru:80", 10); //10-это таймаут
    $sock->send("GET / HTTP/1.0\r\nConnection: close\r\n\r\n");
    print 
    $sock->readAll();
     
    20 авг 2007
    2 пользователям это понравилось.
Загрузка...