<?
/**
* Socksed
* This class can be used to connect to a SOCKS server.
* It supports SOCKS version 4, 4A and 5 with SOCKS authentication.
It also support IPv6 connection.
*
* @package socksed
* @access public
* @author WindyLea (hide@address.com, 2011)
* @version 0.2.1
*/
class socksed {
var $hostname;
var $port;
var $version = 5;
var $timeout;
var $username;
var $password;
var $context;
var $error;
var $_socket;
var $_socks4_error;
var $_socks5_error;
/**
* Some initializing before connecting, this function exists just to make the class compatible with PHP 4
*
* @access public
* @return bool *false* on error
*/
function initialize()
{
if(!is_array($this->_socks4_error))
{
$this->_socks4_error = array(
91 => "request rejected or failed",
92 => "request failed because client is not running identd (or not reachable from the server)",
93 => "request failed because client's identd could not confirm the user ID string in the request"
);
}
if(!is_array($this->_socks5_error))
{
$this->_socks5_error = array(
1 => "general failure",
2 => "connection not allowed by ruleset",
3 => "network unreachable",
4 => "host unreachable",
5 => "connection refused by destination host",
6 => "TTL expired",
7 => "command not supported / protocol error",
8 => "address type not supported"
);
}
if($this->port < 0 || $this->port > 65535)
return $this->error("Invalid SOCKS port specified");
if($this->version != 5 && $this->version != 4 && $this->version != 4.5)
return $this->error("Invalid SOCKS version specified");
if($this->timeout < 0 || !ctype_digit($this->timeout))
$this->timeout = 30;
if(!$this->is_ipv6($this->hostname) && $this->version != 5)
return $this->error("SOCKS4 does not support IPv6 connection");
if(isset($this->context))
{
if (version_compare(PHP_VERSION, "5.0.0", "<") || !function_exists("stream_socket_client"))
{
$this->error("Using socket context requires PHP 5 or higher");
unset($this->context);
} else
{
if(!is_resource($this->context))
$this->context = stream_context_create($this->context);
}
}
return true;
}
/**
* Check whether an input string is a IPv4 or not
*
* @access public
* @param string $string The string to be checked
* @return bool *false* is not a IPv4
*/
function is_ipv4($string)
{
$parts = explode(".", $string);
if(count($parts) != 4)
return false;
foreach($parts as $part)
{
if(is_numeric($part) && ($part >= 0) && ($part < 256))
$i++;
}
if($parts[0] = 0)
return false;
if($i != 4)
return false;
return true;
}
/**
* Check whether an input string is a IPv6 or not
This function is derived from crisp. For reference, visit this link :
http://crisp.tweakblogs.net/blog/3049/ipv6-validation-more-caveats.html
*
* @access public
* @param string $string The string to be checked
* @return bool *false* is not a IPv6
*/
function is_ipv6($string)
{
if (strlen($string) < 3)
return $string == "::";
if (strpos($string, "."))
{
$lastcolon = strrpos($string, ":");
if (!($lastcolon && socksed::is_ipv4(substr($string, $lastcolon + 1))))
return false;
$string = substr($string, 0, $lastcolon) . ":0:0";
}
if (strpos($string, "::") === false)
return preg_match("/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i", $string);
$colonCount = substr_count($string, ":");
if ($colonCount < 8)
return preg_match("/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i", $string);
if ($colonCount == 8)
return preg_match("/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i", $string);
return false;
}
/**
* Converts a string containing an (IPv6) Internet Protocol dotted address into a proper address
This function is derived from johniskew[at]yahoo[dot]com. For reference, visit this URL :
http://www.php.net/manual/en/function.ip2long.php#82013
*
* @access public
* @param string $ip The IP to be converted
* @return bool *false* on error
*/
function ipv62long($ip) {
if(!socksed::is_ipv4($ip) && !socksed::is_ipv6($ip))
return false;
$parts = explode(':', $ip);
# If this is mixed IPv6/IPv4, convert end to IPv6 value
if(socksed::is_ipv4($parts[count($parts) - 1]))
{
$partsV4 = explode(".", $parts[count($parts) - 1]);
for($i = 0; $i < 4; $i++)
{
$partsV4[$i] = str_pad(dechex($partsV4[$i]), 2, "0", STR_PAD_LEFT);
}
$parts[count($parts) - 1] = $partsV4[0].$partsV4[1];
$parts[] = $partsV4[2].$partsV4[3];
}
$numMissing = 8 - count($parts);
$expandedParts = array();
$expansionDone = false;
foreach($parts as $part)
{
if(!$expansionDone && $part == "")
{
for($i = 0; $i <= $numMissing; $i++)
{
$expandedParts[] = "0000";
}
$expansionDone = true;
}
else
{
$expandedParts[] = $part;
}
}
foreach($expandedParts as &$part)
{
$part = str_pad($part, 4, "0", STR_PAD_LEFT);
}
$ip = implode(":", $expandedParts);
$hex = implode("", $expandedParts);
# Validate the final IP
if(!socksed::is_ipv4($ip) && !socksed::is_ipv6($ip))
return false;
return strtolower(str_pad($hex, 32, "0", STR_PAD_LEFT));
}
/**
* Print an error message
*
* @access public
* @param string $error Error message
* @return bool
*/
function error($error)
{
$this->disconnect();
$this->error = $error;
return false;
}
/**
* Connect to an Internet address through the SOCKS server and send a request
*
* @access public
* @param string $hostname The destination address
* @param string $port The port ( default is 80 )
* @param string $request The client request for the destination address $hostname, default is null
* @return string|bool On succress return the SOCKS server response if $request is not empty or "false" if error occurred
*/
function connect($hostname, $port = 80, $request = null)
{
$this->disconnect();
if(!$this->initialize())
return false;
if($this->is_ipv6($this->hostname))
{
if(isset($this->context))
$this->_socket = @stream_socket_client("tcp://[".$this->hostname."]:".$this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $this->context);
else
$this->_socket = @fsockopen("tcp://[".$this->hostname."]", $this->port, $errno, $errstr, $this->timeout);
} else {
if(isset($this->context))
$this->_socket = @stream_socket_client("tcp://".$this->hostname.":".$this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $this->context);
else
$this->_socket = @fsockopen("tcp://".$this->hostname, $this->port, $errno, $errstr, $this->timeout);
}
if (!$this->_socket)
return $this->error($errstr);
if($this->version == 4 || $this->version == 4.5)
{
if($this->version == 4)
{
if(!$this->is_ipv4($hostname)) $hostname = gethostbyname($hostname);
$packet = "\x04\x01".pack("n", $port).pack("H*", dechex(ip2long($hostname)))."\x43\x6C\x69\x65\x6E\x74\x00";
} else
{
$packet = "\x04\x01".pack("n", $port)."\x00\x00\x00\x7B\x43\x6C\x69\x65\x6E\x74\x00".$hostname."\x00";
}
fwrite($this->_socket, $packet, strlen($packet));
$response = fread($this->_socket, 9);
if(strlen($response) == 8 && (ord($response[0]) == 0 || ord($response[0]) == 4))
{
$status = ord($response[1]);
if($status != 90)
return $this->error("Error from SOCKS4 server : ".$this->_socks4_error[$status]);
} else
{
return $this->error("The SOCKS server returned an invalid response");
}
} elseif($this->version == 5)
{
$authentication = null;
if(strlen($this->username) <= 255 && strlen($this->username) > 0 && strlen($this->password) <= 255 && strlen($this->password) > 0)
$authentication = true;
$packet = "\x05\x01\x00";
if($authentication)
$packet = "\x05\x02\x00\x02";
fwrite($this->_socket, $packet, strlen($packet));
$response = fread($this->_socket, 3);
if(strlen($response) == 2 && ord($response[0]) == 5)
{
$method = ord($response[1]);
switch($method)
{
case 0:
break;
case 2:
if(!$authentication)
return $this->error("The SOCKS server requires authentication");
$packet = "\x01".chr(strlen($this->username)).$this->username.chr(strlen($this->password)).$this->password;
fwrite($this->_socket, $packet, strlen($packet));
$response = fread($this->_socket, 3);
if(strlen($response) == 2 && ord($response[0]) == 1 && ord($response[1]) == 0)
break;
else
return $this->error("Authentication with the SOCKS server failed (wrong username/password?)");
default:
return $this->error("No negotiation method was accepted by the SOCKS server");
}
$packet = "\x05\x01\x00";
if($this->is_ipv4($hostname))
{
$packet.= "\x01".pack("H*", dechex(ip2long($hostname)));
} elseif($this->is_ipv6($hostname))
{
$hostname = socksed::ipv62long($hostname);
if($hostname === false)
return $this->error("Unable to convert the specified IPv6 address");
$packet.= "\x04".pack("H*", dechex(ip2long($hostname)));
} else
{
$packet.= "\x03".chr(strlen($hostname)).$hostname;
}
$packet.= pack("n", $port);
fwrite($this->_socket, $packet, strlen($packet));
$response = fread($this->_socket, 1024);
if(ord($response[0]) == 5)
{
$status = ord($response[1]);
if($status != 0)
return $this->error("Error from SOCKS5 server : ".$this->_socks5_error[$status]);
} else
{
return $this->error("The SOCKS server returned an invalid response");
}
} else
{
return $this->error("The SOCKS server actively refused the connection");
}
}
if(empty($request))
return true;
return($this->send($request));
}
/**
* Disconnect from the SOCKS server
*
* @access public
* @return bool
*/
function disconnect()
{
if($this->_socket)
{
fclose($this->_socket);
unset($this->_socket);
}
return true;
}
/**
* Send a packet to the SOCKS server
*
* @access public
* @param string $packet The packet
* @return bool|string On success return the response message from the SOCKS server or "false" if not connected
*/
function send($packet)
{
$response = null;
if($this->_socket)
{
fwrite($this->_socket, $packet, strlen($packet));
while (!feof($this->_socket))
{
$response.= fread($this->_socket, 1024);
}
return $response;
}
return false;
}
}
?>