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