Tommy 碎碎念

Tommy Wu's blog

« 上一篇 | 下一篇 »

讓你的 php socket 程式支援 SOCKS5 與 HTTP CONNECT Proxy
post by tommy @ 29 元月, 2013 10:47

最近因為有些程式所在的環境, 對外連線有些麻煩, 所以... 就想加上 proxy 的支援, 而目前最常用的 proxy, 大概就是 SOCKS5 或 HTTP Proxy 了.

一般 php 的程式, 對於 socket 的處理, 通常都是使用 fsockopen() 來處理. 而上頭兩類的 proxy, 都是在一開始連線時, 處理一些 handshake 的動作之後, 就不用再做任何處理了, 後續的動作與你直接連線都一樣. 所以... 我們只要寫一個 function, 用來取代 fsockopen(), 就可以簡單的加上 proxy 的支援了.

首先, 是對於 SOCKS 的部份:

    private function _proxy_socks5_open($host, $port, $errno, $errstr, $timeout)
{
// already define proxy host and port
if ($this->proxy_host === false || $this->proxy_port === false)
return false;
 
// connect to proxy server
$this->_debug_print("SOCKS: Trying to ".$this->proxy_host.":".$this->proxy_port." ...\n");
$sock = @fsockopen($this->proxy_host, $this->proxy_port, $errno, $errstr, $timeout);
if ($sock === false) {
$this->_debug_print("SOCKS: fsockopen() ".$errstr." (".$errno.")\n");
return false;
}
$this->_debug_print("SOCKS: Connected\n");
 
// we have the proxy user and password or not
if ($this->proxy_user !== false && $this->proxy_pass !== false)
$method = 0x02;
else
$method = 0x00;
// sending version/method to proxy server
if (fwrite($sock, pack("C3", 0x05, 0x01, $method)) === false) {
fclose($sock);
$this->_debug_print("SOCKS: fwrite() error for version/method\n");
return false;
}
// get response from proxy server
$buffer = fread($sock, 1024);
$response = unpack("Cversion/Cmethod", $buffer);
if (!(isset($response['version']) && isset($response['method']) && $response['version'] == 0x05 && $response['method'] == $method)) {
fclose($sock);
$this->_debug_print("SOCKS: unknown response (version/method): ".var_export($response, true)."\n");
return false;
}
 
if ($method == 0x02) {
// proxy server request username and password
if (fwrite($sock, pack("CC", 0x01, strlen($this->proxy_user)).$this->proxy_user.
pack("C", strlen($this->proxy_pass)).$this->proxy_pass) === false) {
fclose($sock);
$this->_debug_print("SOCKS: fwrite() error for username/password\n");
return false;
}
$buffer = fread($sock, 1024);
$response = unpack("Cversion/Cstatus", $buffer);
if (!(isset($response['status']) && $response['status'] == 0x00)) {
fclose($sock);
$this->_debug_print("SOCKS: auth failed\n");
return false;
}
// pass the username/password check
}
// DNS perform in proxy or not
if ($this->proxy_dns)
$conn_str = pack("C5", 0x05, 0x01, 0x00, 0x03, strlen($host)).$host.pack("n", $port);
else
$conn_str = pack("C4Nn", 0x05, 0x01, 0x00, 0x01, ip2long(gethostbyname($host)), $port);
// connect it
if (fwrite($sock, $conn_str) === false) {
fclose($sock);
$this->_debug_print("SOCKS: fwrite() error\n");
return false;
}
$buffer = fread($sock, 1024);
$response = unpack("Cversion/Cresult/Creg/Ctype/Lip/Sport", $buffer);
if (isset($response['version']) && isset($response['result']) && $response['version'] == 0x05 && $response['result'] == 0x00) {
// yes, we already connect it via proxy
$this->_debug_print("SOCKS: OK\n");
return $sock;
}
fclose($sock);
$this->_debug_print("SOCKS: error\n");
return false;
}

在這個函式中, 連線到 proxy server, 再依據需求送出登入的帳號密碼, 成功後就把 socket handle 回傳, 若失敗就傳回 false.

接著是 http proxy 的部份:

    private function _proxy_http_open($host, $port, $errno, $errstr, $timeout)
{
// already define proxy host and port
if ($this->proxy_host === false || $this->proxy_port === false)
return false;
 
// connect to proxy server
$this->_debug_print("HTTP: Trying to ".$this->proxy_host.":".$this->proxy_port." ...\n");
$sock = @fsockopen($this->proxy_host, $this->proxy_port, $errno, $errstr, $timeout);
if ($sock === false) {
$this->_debug_print("HTTP: fsockopen() ".$errstr." (".$errno.")\n");
return false;
}
$this->_debug_print("HTTP: Connected\n");
$conn_str = "CONNECT ".$host.":".$port." HTTP/1.1\r\nHost: ".$host.":".$port."\r\nUser-Agent: ftp.cls.php\r\n\r\n";
$this->_debug_print("HTTP: send => '$conn_str'\n");
if (fwrite($sock, $conn_str) === false) {
fclose($sock);
$this->_debug_print("HTTP: fwrite() error\n");
return false;
}
$buffer = fgets($sock, 1024);
$response = rtrim($buffer);
$this->_debug_print("HTTP: data => '".rtrim($buffer)."'\n");
// read following header/body
$auth_array = array();
$auth_header = '';
$content_length = 0;
while (!feof($sock)) {
$buffer = fgets($sock, 1024);
$this->_debug_print("HTTP: header => '".rtrim($buffer)."'\n");
if (substr($buffer, 0, 1) == ' ' && $auth_header !== '') {
$auth_header .= rtrim($buffer);
continue;
}
// end of header
if ($buffer === "\r\n")
break;
if (strncasecmp($buffer, 'Proxy-Authenticate:', 19) == 0) {
// Proxy-Authenticate: Digest realm="TeaTime Squid Proxy Server", nonce="Cjz/UAAAAADQEPuX/H8AAGmnlR0AAAAA", qop="auth", stale=false
if ($auth_header !== '')
$auth_array[] = $auth_header;
$auth_header = rtrim($buffer);
}
if (strncasecmp($buffer, 'Content-Length:', 15) == 0) {
// Content-Length: 3123
$content_length = trim(substr($buffer, 15));
}
}
if ($auth_header !== '')
$auth_array[] = $auth_header;
$body = '';
if ($content_length != 0) {
while ($content_length > 0) {
$data = fread($sock, $content_length);
$body .= $data;
$content_length = $content_length - strlen($data);
}
$this->_debug_print("HTTP: body => '".$body."'\n");
}
fclose($sock);
if (strtolower(substr($response, 0, 7)) != 'http/1.') {
$this->_debug_print("HTTP: error\n");
return false;
}
$code = substr($response, 9, 3);
if ($code[0] == '2') {
// yes, we already connect it via proxy
$this->_debug_print("HTTP: OK\n");
return $sock;
}
if ($code != "407" && $code != "401") {
$this->_debug_print("HTTP: error\n");
return false;
}
// need auth here
if ($this->proxy_user === false || $this->proxy_pass === false) {
$this->_debug_print("HTTP: error\n");
return false;
}
if (count($auth_array) == 0) {
// no Proxy-Authenticate:
$this->_debug_print("HTTP: error\n");
return false;
}
foreach ($auth_array as $auth_header) {
$this->_debug_print("HTTP: auth_header => '$auth_header'\n");
$auth_data = trim(substr($auth_header, 19));
if (strncasecmp($auth_data, 'Digest ', 7) == 0) {
// method Digest
$token = array();
$data = explode(",", substr($auth_data, 7));
foreach ($data as $v) {
$item = explode("=", $v, 2);
$name = trim($item[0]);
$value = trim($item[1]);
if ($value[0] == '"')
$value = substr($value, 1, strlen($value) - 2);
$token[$name] = $value;
}
if (!array_key_exists("nonce", $token)) {
// nonce not found
$this->_debug_print("HTTP: error (nonce not found)\n");
continue;
}
if (!array_key_exists("realm", $token)) {
// realm not found
$this->_debug_print("HTTP: error (realm not found)\n");
continue;
}
// HA1 = md5(username:realm:password)
// if qop = auth or no qop
// HA2 = md5(method:digestURI)
// if qop = auth-int
// HA2 = md5(method:digestURI:md5(body))
// if qop = auth or auth-int
// response = md5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
// if no qop
// response = md5(HA1:nonce:HA2)
$clientNonce = '';
$ha1 = md5($this->proxy_user.":".$token["realm"].":".$this->proxy_pass);
if (!array_key_exists("qop", $token)) {
$ha2 = md5("CONNECT:".$host.":".$port);
$response = md5($ha1.":".$token["nonce"].":".$ha2);
}
else {
$qop_array = array();
foreach (explode(",", $token["qop"]) as $v) {
$qop_array[] = strtolower(trim($v));
}
if (in_array("auth", $qop_array)) {
$qop = "auth";
$ha2 = md5("CONNECT:".$host.":".$port);
}
else if (in_array("auth-int", $qop_array)) {
$qop = "auth-int";
$ha2 = md5("CONNECT:".$host.":".$port.":".$body);
}
else {
// noknown qop
$this->_debug_print("HTTP: error (unknown qop: ".$token["qop"].")\n");
continue;
}
$clientNonce = sprintf("%08x", time());
$response = md5($ha1.":".$token["nonce"].":00000001:".$clientNonce.":".$qop.":".$ha2);
}
$proxy_header = "Digest username=\"".$this->proxy_user."\", realm=\"".$token["realm"]."\", nonce=\"".$token["nonce"]."\", uri=\"".$host.":".$port."\", response=\"".$response."\"";
if ($clientNonce !== '')
$proxy_header .= ", qop=".$qop.", nc=00000001, cnonce=\"".$clientNonce."\"";
// Proxy-Authorization: Digest username="tommy", realm="TeaTime Squid Proxy Server", nonce="h0n/UAAAAABQTgaY/H8AAFj8wDEAAAAA", uri="/phpsysinfo/index.php?disp=dynamic", response="e63732269c77d70b344f8dd650d6f753", qop=auth, nc=00000001, cnonce="557152171b138196"
 
// connect to proxy server
$this->_debug_print("HTTP: Trying to ".$this->proxy_host.":".$this->proxy_port." ...\n");
$sock = @fsockopen($this->proxy_host, $this->proxy_port, $errno, $errstr, $timeout);
if ($sock === false) {
$this->_debug_print("HTTP: fsockopen() ".$errstr." (".$errno.")\n");
continue;
}
$this->_debug_print("HTTP: Connected\n");
$conn_str = "CONNECT ".$host.":".$port." HTTP/1.1\r\nHost: ".$host.":".$port."\r\nProxy-Authorization: ".$proxy_header."\r\nUser-Agent: ftp.cls.php\r\n\r\n";
$this->_debug_print("HTTP: send => '$conn_str'\n");
if (fwrite($sock, $conn_str) === false) {
fclose($sock);
$this->_debug_print("HTTP: fwrite() error\n");
continue;
}
$buffer = fgets($sock, 1024);
$response = rtrim($buffer);
$this->_debug_print("HTTP: data => '".rtrim($buffer)."'\n");
// read following header/body
$content_length = 0;
while (!feof($sock)) {
$buffer = fgets($sock, 1024);
$this->_debug_print("HTTP: header => '".rtrim($buffer)."'\n");
// end of header
if ($buffer === "\r\n")
break;
if (strncasecmp($buffer, 'Content-Length:', 15) == 0) {
// Content-Length: 3123
$content_length = trim(substr($buffer, 15));
}
}
if ($content_length != 0) {
$xbody = '';
while ($content_length > 0) {
$data = fread(
Del.icio.us Furl HEMiDEMi Technorati MyShare
迴響
暱稱:
標題:
個人網頁:
電子郵件:
authimage

迴響

  

Bad Behavior 已經阻擋了 139 個過去 7 天試圖闖關的垃圾迴響與引用。
Power by LifeType. Template design by JamesHuang. Valid XHTML and CSS