Joe Gregorio | 845a545 | 2010-09-08 13:50:34 -0400 | [diff] [blame^] | 1 | """SocksiPy - Python SOCKS module. |
| 2 | Version 1.00 |
| 3 | |
| 4 | Copyright 2006 Dan-Haim. All rights reserved. |
| 5 | |
| 6 | Redistribution and use in source and binary forms, with or without |
| 7 | modification, are permitted provided that the following conditions are met: |
| 8 | 1. Redistributions of source code must retain the above copyright notice, this |
| 9 | list of conditions and the following disclaimer. |
| 10 | 2. Redistributions in binary form must reproduce the above copyright notice, |
| 11 | this list of conditions and the following disclaimer in the documentation |
| 12 | and/or other materials provided with the distribution. |
| 13 | 3. Neither the name of Dan Haim nor the names of his contributors may be used |
| 14 | to endorse or promote products derived from this software without specific |
| 15 | prior written permission. |
| 16 | |
| 17 | THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED |
| 18 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 19 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| 20 | EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA |
| 23 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| 24 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| 25 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | |
| 27 | |
| 28 | This module provides a standard socket-like interface for Python |
| 29 | for tunneling connections through SOCKS proxies. |
| 30 | |
| 31 | """ |
| 32 | |
| 33 | """ |
| 34 | |
| 35 | Minor modifications made by Christopher Gilbert (http://motomastyle.com/) |
| 36 | for use in PyLoris (http://pyloris.sourceforge.net/) |
| 37 | |
| 38 | Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) |
| 39 | mainly to merge bug fixes found in Sourceforge |
| 40 | |
| 41 | """ |
| 42 | |
| 43 | import socket |
| 44 | import struct |
| 45 | import sys |
| 46 | |
| 47 | if not hasattr(socket, 'socket'): |
| 48 | raise ImportError("Running on App Engine?") |
| 49 | |
| 50 | PROXY_TYPE_SOCKS4 = 1 |
| 51 | PROXY_TYPE_SOCKS5 = 2 |
| 52 | PROXY_TYPE_HTTP = 3 |
| 53 | |
| 54 | _defaultproxy = None |
| 55 | |
| 56 | # Small hack for Python 2.x |
| 57 | if sys.version_info[0] <= 2: |
| 58 | def bytes(obj, enc=None): |
| 59 | return obj |
| 60 | |
| 61 | class ProxyError(Exception): |
| 62 | def __init__(self, value): |
| 63 | self.value = value |
| 64 | def __str__(self): |
| 65 | return repr(self.value) |
| 66 | |
| 67 | class GeneralProxyError(ProxyError): |
| 68 | def __init__(self, value): |
| 69 | self.value = value |
| 70 | def __str__(self): |
| 71 | return repr(self.value) |
| 72 | |
| 73 | class Socks5AuthError(ProxyError): |
| 74 | def __init__(self, value): |
| 75 | self.value = value |
| 76 | def __str__(self): |
| 77 | return repr(self.value) |
| 78 | |
| 79 | class Socks5Error(ProxyError): |
| 80 | def __init__(self, value): |
| 81 | self.value = value |
| 82 | def __str__(self): |
| 83 | return repr(self.value) |
| 84 | |
| 85 | class Socks4Error(ProxyError): |
| 86 | def __init__(self, value): |
| 87 | self.value = value |
| 88 | def __str__(self): |
| 89 | return repr(self.value) |
| 90 | |
| 91 | class HTTPError(ProxyError): |
| 92 | def __init__(self, value): |
| 93 | self.value = value |
| 94 | def __str__(self): |
| 95 | return repr(self.value) |
| 96 | |
| 97 | _generalerrors = ("success", |
| 98 | "invalid data", |
| 99 | "not connected", |
| 100 | "not available", |
| 101 | "bad proxy type", |
| 102 | "bad input") |
| 103 | |
| 104 | _socks5errors = ("succeeded", |
| 105 | "general SOCKS server failure", |
| 106 | "connection not allowed by ruleset", |
| 107 | "Network unreachable", |
| 108 | "Host unreachable", |
| 109 | "Connection refused", |
| 110 | "TTL expired", |
| 111 | "Command not supported", |
| 112 | "Address type not supported", |
| 113 | "Unknown error") |
| 114 | |
| 115 | _socks5autherrors = ("succeeded", |
| 116 | "authentication is required", |
| 117 | "all offered authentication methods were rejected", |
| 118 | "unknown username or invalid password", |
| 119 | "unknown error") |
| 120 | |
| 121 | _socks4errors = ("request granted", |
| 122 | "request rejected or failed", |
| 123 | ("request rejected because SOCKS server cannot connect to " |
| 124 | "identd on the client"), |
| 125 | ("request rejected because the client program and identd" |
| 126 | " report different user-ids"), |
| 127 | "unknown error") |
| 128 | |
| 129 | |
| 130 | def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, |
| 131 | username=None, password=None): |
| 132 | """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) |
| 133 | Sets a default proxy which all further socksocket objects will use, |
| 134 | unless explicitly changed. |
| 135 | """ |
| 136 | global _defaultproxy |
| 137 | _defaultproxy = (proxytype, addr, port, rdns, username, password) |
| 138 | |
| 139 | |
| 140 | class socksocket(socket.socket): |
| 141 | """socksocket([family[, type[, proto]]]) -> socket object |
| 142 | |
| 143 | Open a SOCKS enabled socket. The parameters are the same as |
| 144 | those of the standard socket init. In order for SOCKS to work, |
| 145 | you must specify family=AF_INET, type=SOCK_STREAM and proto=0. |
| 146 | """ |
| 147 | |
| 148 | def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, |
| 149 | proto=0, _sock=None): |
| 150 | socket.socket.__init__(self, family, type, proto, _sock) |
| 151 | if _defaultproxy != None: |
| 152 | self.__proxy = _defaultproxy |
| 153 | else: |
| 154 | self.__proxy = (None, None, None, None, None, None) |
| 155 | self.__proxysockname = None |
| 156 | self.__proxypeername = None |
| 157 | |
| 158 | def __decode(self, bytes): |
| 159 | if getattr(bytes, 'decode', False): |
| 160 | try: |
| 161 | bytes = bytes.decode() |
| 162 | except Exception: |
| 163 | pass |
| 164 | return bytes |
| 165 | |
| 166 | def __encode(self, bytes): |
| 167 | if getattr(bytes, 'encode', False): |
| 168 | try: |
| 169 | bytes = bytes.encode() |
| 170 | except Exception: |
| 171 | pass |
| 172 | return bytes |
| 173 | |
| 174 | def __recvall(self, count): |
| 175 | """__recvall(count) -> data |
| 176 | Receive EXACTLY the number of bytes requested from the socket. |
| 177 | Blocks until the required number of bytes have been received. |
| 178 | """ |
| 179 | data = bytes("") |
| 180 | while len(data) < count: |
| 181 | d = self.recv(count - len(data)) |
| 182 | if not d: |
| 183 | raise GeneralProxyError( |
| 184 | (0, "connection closed unexpectedly")) |
| 185 | data = data + self.__decode(d) |
| 186 | return data |
| 187 | |
| 188 | def sendall(self, bytes): |
| 189 | socket.socket.sendall(self, self.__encode(bytes)) |
| 190 | |
| 191 | def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, |
| 192 | username=None, password=None): |
| 193 | """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) |
| 194 | Sets the proxy to be used. |
| 195 | proxytype - The type of the proxy to be used. Three types |
| 196 | are supported: PROXY_TYPE_SOCKS4 (including socks4a), |
| 197 | PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP |
| 198 | addr - The address of the server (IP or DNS). |
| 199 | port - The port of the server. Defaults to 1080 for SOCKS |
| 200 | servers and 8080 for HTTP proxy servers. |
| 201 | rdns - Should DNS queries be preformed on the remote side |
| 202 | (rather than the local side). The default is True. |
| 203 | Note: This has no effect with SOCKS4 servers. |
| 204 | username - Username to authenticate with to the server. |
| 205 | The default is no authentication. |
| 206 | password - Password to authenticate with to the server. |
| 207 | Only relevant when username is also provided. |
| 208 | """ |
| 209 | self.__proxy = (proxytype, addr, port, rdns, username, password) |
| 210 | |
| 211 | def __negotiatesocks5(self, destaddr, destport): |
| 212 | """__negotiatesocks5(self,destaddr,destport) |
| 213 | Negotiates a connection through a SOCKS5 server. |
| 214 | """ |
| 215 | # First we'll send the authentication packages we support. |
| 216 | if (self.__proxy[4] != None) and (self.__proxy[5] != None): |
| 217 | # The username/password details were supplied to the |
| 218 | # setproxy method so we support the USERNAME/PASSWORD |
| 219 | # authentication (in addition to the standard none). |
| 220 | self.sendall("\x05\x02\x00\x02") |
| 221 | else: |
| 222 | # No username/password were entered, therefore we |
| 223 | # only support connections with no authentication. |
| 224 | self.sendall("\x05\x01\x00") |
| 225 | # We'll receive the server's response to determine which |
| 226 | # method was selected |
| 227 | chosenauth = self.__recvall(2) |
| 228 | if chosenauth[0] != "\x05": |
| 229 | self.close() |
| 230 | raise GeneralProxyError((1, _generalerrors[1])) |
| 231 | # Check the chosen authentication method |
| 232 | if chosenauth[1] == "\x00": |
| 233 | # No authentication is required |
| 234 | pass |
| 235 | elif chosenauth[1] == "\x02": |
| 236 | # Okay, we need to perform a basic username/password |
| 237 | # authentication. |
| 238 | self.sendall("\x01" + chr(len(self.__proxy[4])) + self.__proxy[4] + |
| 239 | chr(len(self.__proxy[5])) + self.__proxy[5]) |
| 240 | authstat = self.__recvall(2) |
| 241 | if authstat[0] != "\x01": |
| 242 | # Bad response |
| 243 | self.close() |
| 244 | raise GeneralProxyError((1, _generalerrors[1])) |
| 245 | if authstat[1] != "\x00": |
| 246 | # Authentication failed |
| 247 | self.close() |
| 248 | raise Socks5AuthError((3, _socks5autherrors[3])) |
| 249 | # Authentication succeeded |
| 250 | else: |
| 251 | # Reaching here is always bad |
| 252 | self.close() |
| 253 | if chosenauth[1] == "\xFF": |
| 254 | raise Socks5AuthError((2, _socks5autherrors[2])) |
| 255 | else: |
| 256 | raise GeneralProxyError((1, _generalerrors[1])) |
| 257 | # Now we can request the actual connection |
| 258 | req = "\x05\x01\x00" |
| 259 | # If the given destination address is an IP address, we'll |
| 260 | # use the IPv4 address request even if remote resolving was specified. |
| 261 | try: |
| 262 | ipaddr = socket.inet_aton(destaddr) |
| 263 | req = req + "\x01" + ipaddr |
| 264 | except socket.error: |
| 265 | # Well it's not an IP number, so it's probably a DNS name. |
| 266 | if self.__proxy[3] == True: |
| 267 | # Resolve remotely |
| 268 | ipaddr = None |
| 269 | req = req + "\x03" + chr(len(destaddr)) + destaddr |
| 270 | else: |
| 271 | # Resolve locally |
| 272 | ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) |
| 273 | req = req + "\x01" + ipaddr |
| 274 | req = req + self.__decode(struct.pack(">H", destport)) |
| 275 | self.sendall(req) |
| 276 | # Get the response |
| 277 | resp = self.__recvall(4) |
| 278 | if resp[0] != "\x05": |
| 279 | self.close() |
| 280 | raise GeneralProxyError((1, _generalerrors[1])) |
| 281 | elif resp[1] != "\x00": |
| 282 | # Connection failed |
| 283 | self.close() |
| 284 | if ord(resp[1]) <= 8: |
| 285 | raise Socks5Error((ord(resp[1]), _socks5errors[ord(resp[1])])) |
| 286 | else: |
| 287 | raise Socks5Error((9, _socks5errors[9])) |
| 288 | # Get the bound address/port |
| 289 | elif resp[3] == "\x01": |
| 290 | boundaddr = self.__recvall(4) |
| 291 | elif resp[3] == "\x03": |
| 292 | resp = resp + self.recv(1) |
| 293 | boundaddr = self.__recvall(ord(resp[4])) |
| 294 | else: |
| 295 | self.close() |
| 296 | raise GeneralProxyError((1, _generalerrors[1])) |
| 297 | boundport = struct.unpack(">H", bytes(self.__recvall(2), 'utf8'))[0] |
| 298 | self.__proxysockname = boundaddr, boundport |
| 299 | if ipaddr != None: |
| 300 | self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) |
| 301 | else: |
| 302 | self.__proxypeername = (destaddr, destport) |
| 303 | |
| 304 | def getproxysockname(self): |
| 305 | """getsockname() -> address info |
| 306 | Returns the bound IP address and port number at the proxy. |
| 307 | """ |
| 308 | return self.__proxysockname |
| 309 | |
| 310 | def getproxypeername(self): |
| 311 | """getproxypeername() -> address info |
| 312 | Returns the IP and port number of the proxy. |
| 313 | """ |
| 314 | return socket.socket.getpeername(self) |
| 315 | |
| 316 | def getpeername(self): |
| 317 | """getpeername() -> address info |
| 318 | Returns the IP address and port number of the destination |
| 319 | machine (note: getproxypeername returns the proxy) |
| 320 | """ |
| 321 | return self.__proxypeername |
| 322 | |
| 323 | def __negotiatesocks4(self, destaddr, destport): |
| 324 | """__negotiatesocks4(self,destaddr,destport) |
| 325 | Negotiates a connection through a SOCKS4 server. |
| 326 | """ |
| 327 | # Check if the destination address provided is an IP address |
| 328 | rmtrslv = False |
| 329 | try: |
| 330 | ipaddr = socket.inet_aton(destaddr) |
| 331 | except socket.error: |
| 332 | # It's a DNS name. Check where it should be resolved. |
| 333 | if self.__proxy[3] == True: |
| 334 | ipaddr = "\x00\x00\x00\x01" |
| 335 | rmtrslv = True |
| 336 | else: |
| 337 | ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) |
| 338 | # Construct the request packet |
| 339 | req = "\x04\x01" + self.__decode(struct.pack(">H", destport)) + ipaddr |
| 340 | # The username parameter is considered userid for SOCKS4 |
| 341 | if self.__proxy[4] != None: |
| 342 | req = req + self.__proxy[4] |
| 343 | req = req + "\x00" |
| 344 | # DNS name if remote resolving is required |
| 345 | # NOTE: This is actually an extension to the SOCKS4 protocol |
| 346 | # called SOCKS4A and may not be supported in all cases. |
| 347 | if rmtrslv==True: |
| 348 | req = req + destaddr + "\x00" |
| 349 | self.sendall(req) |
| 350 | # Get the response from the server |
| 351 | resp = self.__recvall(8) |
| 352 | if resp[0] != "\x00": |
| 353 | # Bad data |
| 354 | self.close() |
| 355 | raise GeneralProxyError((1, _generalerrors[1])) |
| 356 | if resp[1] != "\x5A": |
| 357 | # Server returned an error |
| 358 | self.close() |
| 359 | if ord(resp[1]) in (91,92,93): |
| 360 | self.close() |
| 361 | raise Socks4Error((ord(resp[1]), _socks4errors[ord(resp[1])-90])) |
| 362 | else: |
| 363 | raise Socks4Error((94,_socks4errors[4])) |
| 364 | # Get the bound address/port |
| 365 | self.__proxysockname = (socket.inet_ntoa(resp[4:]),struct.unpack(">H",bytes(resp[2:4],'utf8'))[0]) |
| 366 | if rmtrslv != None: |
| 367 | self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) |
| 368 | else: |
| 369 | self.__proxypeername = (destaddr, destport) |
| 370 | |
| 371 | def __negotiatehttp(self, destaddr, destport): |
| 372 | """__negotiatehttp(self,destaddr,destport) |
| 373 | Negotiates a connection through an HTTP server. |
| 374 | """ |
| 375 | # If we need to resolve locally, we do this now |
| 376 | if self.__proxy[3] == False: |
| 377 | addr = socket.gethostbyname(destaddr) |
| 378 | else: |
| 379 | addr = destaddr |
| 380 | self.sendall(("CONNECT %s:%s HTTP/1.1\r\n" |
| 381 | "Host: %s\r\n\r\n") % (addr, destport, destaddr)) |
| 382 | # We read the response until we get the string "\r\n\r\n" |
| 383 | resp = self.recv(1) |
| 384 | while resp.find("\r\n\r\n") == -1: |
| 385 | resp = resp + self.recv(1) |
| 386 | # We just need the first line to check if the connection |
| 387 | # was successful |
| 388 | statusline = resp.splitlines()[0].split(" ", 2) |
| 389 | if statusline[0] not in ("HTTP/1.0", "HTTP/1.1"): |
| 390 | self.close() |
| 391 | raise GeneralProxyError((1, _generalerrors[1])) |
| 392 | try: |
| 393 | statuscode = int(statusline[1]) |
| 394 | except ValueError: |
| 395 | self.close() |
| 396 | raise GeneralProxyError((1, _generalerrors[1])) |
| 397 | if statuscode != 200: |
| 398 | self.close() |
| 399 | raise HTTPError((statuscode, statusline[2])) |
| 400 | self.__proxysockname = ("0.0.0.0", 0) |
| 401 | self.__proxypeername = (addr, destport) |
| 402 | |
| 403 | def connect(self, destpair): |
| 404 | """connect(self,despair) |
| 405 | Connects to the specified destination through a proxy. |
| 406 | destpar - A tuple of the IP/DNS address and the port number. |
| 407 | (identical to socket's connect). |
| 408 | To select the proxy server use setproxy(). |
| 409 | """ |
| 410 | # Do a minimal input check first |
| 411 | # TODO(durin42): seriously? type checking? do we care? |
| 412 | if ((not isinstance(destpair, (list, tuple))) or len(destpair) < 2 |
| 413 | or not isinstance(destpair[0], str) or not isinstance(destpair[1], int)): |
| 414 | raise GeneralProxyError((5, _generalerrors[5])) |
| 415 | if self.__proxy[0] == PROXY_TYPE_SOCKS5: |
| 416 | if self.__proxy[2] != None: |
| 417 | portnum = self.__proxy[2] |
| 418 | else: |
| 419 | portnum = 1080 |
| 420 | socket.socket.connect(self,(self.__proxy[1], portnum)) |
| 421 | self.__negotiatesocks5(destpair[0], destpair[1]) |
| 422 | elif self.__proxy[0] == PROXY_TYPE_SOCKS4: |
| 423 | if self.__proxy[2] != None: |
| 424 | portnum = self.__proxy[2] |
| 425 | else: |
| 426 | portnum = 1080 |
| 427 | socket.socket.connect(self, (self.__proxy[1], portnum)) |
| 428 | self.__negotiatesocks4(destpair[0], destpair[1]) |
| 429 | elif self.__proxy[0] == PROXY_TYPE_HTTP: |
| 430 | if self.__proxy[2] != None: |
| 431 | portnum = self.__proxy[2] |
| 432 | else: |
| 433 | portnum = 8080 |
| 434 | socket.socket.connect(self, (self.__proxy[1], portnum)) |
| 435 | self.__negotiatehttp(destpair[0], destpair[1]) |
| 436 | elif self.__proxy[0] == None: |
| 437 | socket.socket.connect(self, (destpair[0], destpair[1])) |
| 438 | else: |
| 439 | raise GeneralProxyError((4, _generalerrors[4])) |