Erebot  latest
A modular IRC bot for PHP 7.0+
SOCKS.php
1 <?php
2 /*
3  This file is part of Erebot, a modular IRC bot written in PHP.
4 
5  Copyright © 2010 François Poirotte
6 
7  Erebot is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  Erebot is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with Erebot. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 namespace Erebot\Proxy;
22 
27 class SOCKS extends \Erebot\Proxy\Base
28 {
29  public function proxify(\Erebot\URIInterface $proxyURI, \Erebot\URIInterface $nextURI)
30  {
31  $port = $nextURI->getPort();
32  $scheme = $nextURI->getScheme();
33 
34  if ($port === null) {
35  $port = getservbyname($scheme, 'tcp');
36  }
37  if (!is_int($port) || $port <= 0 || $port > 65535) {
38  throw new \Erebot\InvalidValueException('Invalid port');
39  }
40 
41  // No authentication or username/password-based authentication.
42  $this->write("\x05\x02\x00\x02");
43  $line = $this->read(2);
44 
45  if ($line[0] != "\x05") {
46  throw new \Erebot\InvalidValueException('Bad SOCKS version');
47  }
48 
49  switch (ord($line[1])) {
50  case 0: // No authentication
51  break;
52  case 2: // Username/password-based authentication
53  $this->userpass($proxyURI);
54  break;
55  default:
56  throw new \Erebot\InvalidValueException('No acceptable method');
57  }
58 
59  // CONNECT.
60  $host = $nextURI->getHost();
61  $this->write(
62  "\x05\x01\x00\x03".
63  pack("Ca*n", strlen($host), $host, $port)
64  );
65 
66  $line = $this->read(4);
67  if ($line[0] != "\x05") {
68  throw new \Erebot\InvalidValueException('Bad SOCKS version');
69  }
70 
71  $error = ord($line[1]);
72  if ($error) {
73  // Taken fromt eh RFC.
74  $errors = array(
75  1 =>
76  'General SOCKS server failure',
77  'Connection not allowed by ruleset',
78  'Network unreachable',
79  'Host unreachable',
80  'Connection refused',
81  'TTL expired',
82  'Command not supported',
83  'Address type not supported',
84  );
85  if (!isset($errors[$error])) {
86  throw new \Erebot\InvalidValueException('Unknown error');
87  }
88  throw new \Erebot\InvalidValueException($errors[$error]);
89  }
90 
91  switch (ord($line[3])) {
92  case 1: // IPv4.
93  $this->read(4);
94  break;
95 
96  case 3: // Domain name.
97  $len = ord($this->read(1));
98  $this->read($len);
99  break;
100 
101  case 4: // IPv6.
102  $this->read(16);
103  break;
104 
105  default:
106  throw new \Erebot\InvalidValueException(
107  'Address type not supported'
108  );
109  }
110 
111  // Consume the port.
112  $this->read(2);
113  }
114 
123  protected function userpass(\Erebot\URIInterface $proxyURI)
124  {
125  $username = $proxyURI->asParsedURL(PHP_URL_USER);
126  $password = $proxyURI->asParsedURL(PHP_URL_PASS);
127 
128  if ($username === null || $password === null) {
129  throw new \Erebot\InvalidValueException(
130  'No username or password supplied'
131  );
132  }
133 
134  $ulen = strlen($username);
135  $plen = strlen($password);
136  if ($ulen > 255) {
137  throw new \Erebot\InvalidValueException(
138  'Username too long (max. 255)'
139  );
140  }
141 
142  if ($plen > 255) {
143  throw new \Erebot\InvalidValueException(
144  'Password too long (max. 255)'
145  );
146  }
147 
148  $this->write(
149  "\x01".pack(
150  "Ca*Ca*",
151  $ulen,
152  $username,
153  $plen,
154  $password
155  )
156  );
157  $line = $this->read(2);
158 
159  if ($line[0] != "\x01") {
160  throw new \Erebot\InvalidValueException(
161  'Bad subnegociation version'
162  );
163  }
164 
165  if ($line[1] != "\x00") {
166  throw new \Erebot\InvalidValueException('Bad username or password');
167  }
168  }
169 
181  protected function write($line)
182  {
183  for ($written = 0, $len = strlen($line); $written < $len; $written += $fwrite) {
184  $fwrite = fwrite($this->socket, substr($line, $written));
185  if ($fwrite === false) {
186  throw new \Erebot\Exception('Connection closed by proxy');
187  }
188  }
189  return $written;
190  }
191 
201  protected function read($len)
202  {
203  $contents = "";
204  $clen = 0;
205  while (!feof($this->socket) && $clen < $len) {
206  $read = fread($this->socket, $len - $clen);
207  if ($read === false) {
208  throw new \Erebot\Exception('Connection closed by proxy');
209  }
210  $contents .= $read;
211  $clen = strlen($contents);
212  }
213  return $contents;
214  }
215 }
userpass(\Erebot\URIInterface $proxyURI)
Definition: SOCKS.php:123
Proxies data through a SOCKS 5 proxy.
Definition: SOCKS.php:27