| OLD | NEW |
| (Empty) |
| 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 modification, | |
| 7 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 DAMANGE. | |
| 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 Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/) | |
| 42 | |
| 43 """ | |
| 44 | |
| 45 import socket | |
| 46 import struct | |
| 47 import sys | |
| 48 | |
| 49 PROXY_TYPE_SOCKS4 = 1 | |
| 50 PROXY_TYPE_SOCKS5 = 2 | |
| 51 PROXY_TYPE_HTTP = 3 | |
| 52 | |
| 53 _defaultproxy = None | |
| 54 _orgsocket = socket.socket | |
| 55 | |
| 56 class ProxyError(Exception): pass | |
| 57 class GeneralProxyError(ProxyError): pass | |
| 58 class Socks5AuthError(ProxyError): pass | |
| 59 class Socks5Error(ProxyError): pass | |
| 60 class Socks4Error(ProxyError): pass | |
| 61 class HTTPError(ProxyError): pass | |
| 62 | |
| 63 _generalerrors = ("success", | |
| 64 "invalid data", | |
| 65 "not connected", | |
| 66 "not available", | |
| 67 "bad proxy type", | |
| 68 "bad input") | |
| 69 | |
| 70 _socks5errors = ("succeeded", | |
| 71 "general SOCKS server failure", | |
| 72 "connection not allowed by ruleset", | |
| 73 "Network unreachable", | |
| 74 "Host unreachable", | |
| 75 "Connection refused", | |
| 76 "TTL expired", | |
| 77 "Command not supported", | |
| 78 "Address type not supported", | |
| 79 "Unknown error") | |
| 80 | |
| 81 _socks5autherrors = ("succeeded", | |
| 82 "authentication is required", | |
| 83 "all offered authentication methods were rejected", | |
| 84 "unknown username or invalid password", | |
| 85 "unknown error") | |
| 86 | |
| 87 _socks4errors = ("request granted", | |
| 88 "request rejected or failed", | |
| 89 "request rejected because SOCKS server cannot connect to identd on the clien
t", | |
| 90 "request rejected because the client program and identd report different use
r-ids", | |
| 91 "unknown error") | |
| 92 | |
| 93 def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=No
ne, password=None): | |
| 94 """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) | |
| 95 Sets a default proxy which all further socksocket objects will use, | |
| 96 unless explicitly changed. | |
| 97 """ | |
| 98 global _defaultproxy | |
| 99 _defaultproxy = (proxytype, addr, port, rdns, username, password) | |
| 100 | |
| 101 def wrapmodule(module): | |
| 102 """wrapmodule(module) | |
| 103 Attempts to replace a module's socket library with a SOCKS socket. Must set | |
| 104 a default proxy using setdefaultproxy(...) first. | |
| 105 This will only work on modules that import socket directly into the namespac
e; | |
| 106 most of the Python Standard Library falls into this category. | |
| 107 """ | |
| 108 if _defaultproxy != None: | |
| 109 module.socket.socket = socksocket | |
| 110 else: | |
| 111 raise GeneralProxyError((4, "no proxy specified")) | |
| 112 | |
| 113 class socksocket(socket.socket): | |
| 114 """socksocket([family[, type[, proto]]]) -> socket object | |
| 115 Open a SOCKS enabled socket. The parameters are the same as | |
| 116 those of the standard socket init. In order for SOCKS to work, | |
| 117 you must specify family=AF_INET, type=SOCK_STREAM and proto=0. | |
| 118 """ | |
| 119 | |
| 120 def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0,
_sock=None): | |
| 121 _orgsocket.__init__(self, family, type, proto, _sock) | |
| 122 if _defaultproxy != None: | |
| 123 self.__proxy = _defaultproxy | |
| 124 else: | |
| 125 self.__proxy = (None, None, None, None, None, None) | |
| 126 self.__proxysockname = None | |
| 127 self.__proxypeername = None | |
| 128 | |
| 129 def __recvall(self, count): | |
| 130 """__recvall(count) -> data | |
| 131 Receive EXACTLY the number of bytes requested from the socket. | |
| 132 Blocks until the required number of bytes have been received. | |
| 133 """ | |
| 134 data = self.recv(count) | |
| 135 while len(data) < count: | |
| 136 d = self.recv(count-len(data)) | |
| 137 if not d: raise GeneralProxyError((0, "connection closed unexpectedl
y")) | |
| 138 data = data + d | |
| 139 return data | |
| 140 | |
| 141 def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username
=None, password=None): | |
| 142 """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) | |
| 143 Sets the proxy to be used. | |
| 144 proxytype - The type of the proxy to be used. Three types | |
| 145 are supported: PROXY_TYPE_SOCKS4 (including socks4a), | |
| 146 PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP | |
| 147 addr - The address of the server (IP or DNS). | |
| 148 port - The port of the server. Defaults to 1080 for SOCKS | |
| 149 servers and 8080 for HTTP proxy servers. | |
| 150 rdns - Should DNS queries be preformed on the remote side | |
| 151 (rather than the local side). The default is True. | |
| 152 Note: This has no effect with SOCKS4 servers. | |
| 153 username - Username to authenticate with to the server. | |
| 154 The default is no authentication. | |
| 155 password - Password to authenticate with to the server. | |
| 156 Only relevant when username is also provided. | |
| 157 """ | |
| 158 self.__proxy = (proxytype, addr, port, rdns, username, password) | |
| 159 | |
| 160 def __negotiatesocks5(self, destaddr, destport): | |
| 161 """__negotiatesocks5(self,destaddr,destport) | |
| 162 Negotiates a connection through a SOCKS5 server. | |
| 163 """ | |
| 164 # First we'll send the authentication packages we support. | |
| 165 if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): | |
| 166 # The username/password details were supplied to the | |
| 167 # setproxy method so we support the USERNAME/PASSWORD | |
| 168 # authentication (in addition to the standard none). | |
| 169 self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) | |
| 170 else: | |
| 171 # No username/password were entered, therefore we | |
| 172 # only support connections with no authentication. | |
| 173 self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) | |
| 174 # We'll receive the server's response to determine which | |
| 175 # method was selected | |
| 176 chosenauth = self.__recvall(2) | |
| 177 if chosenauth[0:1] != chr(0x05).encode(): | |
| 178 self.close() | |
| 179 raise GeneralProxyError((1, _generalerrors[1])) | |
| 180 # Check the chosen authentication method | |
| 181 if chosenauth[1:2] == chr(0x00).encode(): | |
| 182 # No authentication is required | |
| 183 pass | |
| 184 elif chosenauth[1:2] == chr(0x02).encode(): | |
| 185 # Okay, we need to perform a basic username/password | |
| 186 # authentication. | |
| 187 self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self._
_proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) | |
| 188 authstat = self.__recvall(2) | |
| 189 if authstat[0:1] != chr(0x01).encode(): | |
| 190 # Bad response | |
| 191 self.close() | |
| 192 raise GeneralProxyError((1, _generalerrors[1])) | |
| 193 if authstat[1:2] != chr(0x00).encode(): | |
| 194 # Authentication failed | |
| 195 self.close() | |
| 196 raise Socks5AuthError((3, _socks5autherrors[3])) | |
| 197 # Authentication succeeded | |
| 198 else: | |
| 199 # Reaching here is always bad | |
| 200 self.close() | |
| 201 if chosenauth[1] == chr(0xFF).encode(): | |
| 202 raise Socks5AuthError((2, _socks5autherrors[2])) | |
| 203 else: | |
| 204 raise GeneralProxyError((1, _generalerrors[1])) | |
| 205 # Now we can request the actual connection | |
| 206 req = struct.pack('BBB', 0x05, 0x01, 0x00) | |
| 207 # If the given destination address is an IP address, we'll | |
| 208 # use the IPv4 address request even if remote resolving was specified. | |
| 209 try: | |
| 210 ipaddr = socket.inet_aton(destaddr) | |
| 211 req = req + chr(0x01).encode() + ipaddr | |
| 212 except socket.error: | |
| 213 # Well it's not an IP number, so it's probably a DNS name. | |
| 214 if self.__proxy[3]: | |
| 215 # Resolve remotely | |
| 216 ipaddr = None | |
| 217 req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + d
estaddr | |
| 218 else: | |
| 219 # Resolve locally | |
| 220 ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) | |
| 221 req = req + chr(0x01).encode() + ipaddr | |
| 222 req = req + struct.pack(">H", destport) | |
| 223 self.sendall(req) | |
| 224 # Get the response | |
| 225 resp = self.__recvall(4) | |
| 226 if resp[0:1] != chr(0x05).encode(): | |
| 227 self.close() | |
| 228 raise GeneralProxyError((1, _generalerrors[1])) | |
| 229 elif resp[1:2] != chr(0x00).encode(): | |
| 230 # Connection failed | |
| 231 self.close() | |
| 232 if ord(resp[1:2])<=8: | |
| 233 raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]
)) | |
| 234 else: | |
| 235 raise Socks5Error((9, _socks5errors[9])) | |
| 236 # Get the bound address/port | |
| 237 elif resp[3:4] == chr(0x01).encode(): | |
| 238 boundaddr = self.__recvall(4) | |
| 239 elif resp[3:4] == chr(0x03).encode(): | |
| 240 resp = resp + self.recv(1) | |
| 241 boundaddr = self.__recvall(ord(resp[4:5])) | |
| 242 else: | |
| 243 self.close() | |
| 244 raise GeneralProxyError((1,_generalerrors[1])) | |
| 245 boundport = struct.unpack(">H", self.__recvall(2))[0] | |
| 246 self.__proxysockname = (boundaddr, boundport) | |
| 247 if ipaddr != None: | |
| 248 self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) | |
| 249 else: | |
| 250 self.__proxypeername = (destaddr, destport) | |
| 251 | |
| 252 def getproxysockname(self): | |
| 253 """getsockname() -> address info | |
| 254 Returns the bound IP address and port number at the proxy. | |
| 255 """ | |
| 256 return self.__proxysockname | |
| 257 | |
| 258 def getproxypeername(self): | |
| 259 """getproxypeername() -> address info | |
| 260 Returns the IP and port number of the proxy. | |
| 261 """ | |
| 262 return _orgsocket.getpeername(self) | |
| 263 | |
| 264 def getpeername(self): | |
| 265 """getpeername() -> address info | |
| 266 Returns the IP address and port number of the destination | |
| 267 machine (note: getproxypeername returns the proxy) | |
| 268 """ | |
| 269 return self.__proxypeername | |
| 270 | |
| 271 def __negotiatesocks4(self,destaddr,destport): | |
| 272 """__negotiatesocks4(self,destaddr,destport) | |
| 273 Negotiates a connection through a SOCKS4 server. | |
| 274 """ | |
| 275 # Check if the destination address provided is an IP address | |
| 276 rmtrslv = False | |
| 277 try: | |
| 278 ipaddr = socket.inet_aton(destaddr) | |
| 279 except socket.error: | |
| 280 # It's a DNS name. Check where it should be resolved. | |
| 281 if self.__proxy[3]: | |
| 282 ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) | |
| 283 rmtrslv = True | |
| 284 else: | |
| 285 ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) | |
| 286 # Construct the request packet | |
| 287 req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr | |
| 288 # The username parameter is considered userid for SOCKS4 | |
| 289 if self.__proxy[4] != None: | |
| 290 req = req + self.__proxy[4] | |
| 291 req = req + chr(0x00).encode() | |
| 292 # DNS name if remote resolving is required | |
| 293 # NOTE: This is actually an extension to the SOCKS4 protocol | |
| 294 # called SOCKS4A and may not be supported in all cases. | |
| 295 if rmtrslv: | |
| 296 req = req + destaddr + chr(0x00).encode() | |
| 297 self.sendall(req) | |
| 298 # Get the response from the server | |
| 299 resp = self.__recvall(8) | |
| 300 if resp[0:1] != chr(0x00).encode(): | |
| 301 # Bad data | |
| 302 self.close() | |
| 303 raise GeneralProxyError((1,_generalerrors[1])) | |
| 304 if resp[1:2] != chr(0x5A).encode(): | |
| 305 # Server returned an error | |
| 306 self.close() | |
| 307 if ord(resp[1:2]) in (91, 92, 93): | |
| 308 self.close() | |
| 309 raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2])
- 90])) | |
| 310 else: | |
| 311 raise Socks4Error((94, _socks4errors[4])) | |
| 312 # Get the bound address/port | |
| 313 self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H",
resp[2:4])[0]) | |
| 314 if rmtrslv != None: | |
| 315 self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) | |
| 316 else: | |
| 317 self.__proxypeername = (destaddr, destport) | |
| 318 | |
| 319 def __negotiatehttp(self, destaddr, destport): | |
| 320 """__negotiatehttp(self,destaddr,destport) | |
| 321 Negotiates a connection through an HTTP server. | |
| 322 """ | |
| 323 # If we need to resolve locally, we do this now | |
| 324 if not self.__proxy[3]: | |
| 325 addr = socket.gethostbyname(destaddr) | |
| 326 else: | |
| 327 addr = destaddr | |
| 328 self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n"
+ "Host: " + destaddr + "\r\n\r\n").encode()) | |
| 329 # We read the response until we get the string "\r\n\r\n" | |
| 330 resp = self.recv(1) | |
| 331 while resp.find("\r\n\r\n".encode()) == -1: | |
| 332 recv = self.recv(1) | |
| 333 if not recv: | |
| 334 raise GeneralProxyError((1, _generalerrors[1])) | |
| 335 resp = resp + recv | |
| 336 # We just need the first line to check if the connection | |
| 337 # was successful | |
| 338 statusline = resp.splitlines()[0].split(" ".encode(), 2) | |
| 339 if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): | |
| 340 self.close() | |
| 341 raise GeneralProxyError((1, _generalerrors[1])) | |
| 342 try: | |
| 343 statuscode = int(statusline[1]) | |
| 344 except ValueError: | |
| 345 self.close() | |
| 346 raise GeneralProxyError((1, _generalerrors[1])) | |
| 347 if statuscode != 200: | |
| 348 self.close() | |
| 349 raise HTTPError((statuscode, statusline[2])) | |
| 350 self.__proxysockname = ("0.0.0.0", 0) | |
| 351 self.__proxypeername = (addr, destport) | |
| 352 | |
| 353 def connect(self, destpair): | |
| 354 """connect(self, despair) | |
| 355 Connects to the specified destination through a proxy. | |
| 356 destpar - A tuple of the IP/DNS address and the port number. | |
| 357 (identical to socket's connect). | |
| 358 To select the proxy server use setproxy(). | |
| 359 """ | |
| 360 # Do a minimal input check first | |
| 361 if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(
destpair[0]) != type('')) or (type(destpair[1]) != int): | |
| 362 raise GeneralProxyError((5, _generalerrors[5])) | |
| 363 if self.__proxy[0] == PROXY_TYPE_SOCKS5: | |
| 364 if self.__proxy[2] != None: | |
| 365 portnum = self.__proxy[2] | |
| 366 else: | |
| 367 portnum = 1080 | |
| 368 _orgsocket.connect(self, (self.__proxy[1], portnum)) | |
| 369 self.__negotiatesocks5(destpair[0], destpair[1]) | |
| 370 elif self.__proxy[0] == PROXY_TYPE_SOCKS4: | |
| 371 if self.__proxy[2] != None: | |
| 372 portnum = self.__proxy[2] | |
| 373 else: | |
| 374 portnum = 1080 | |
| 375 _orgsocket.connect(self,(self.__proxy[1], portnum)) | |
| 376 self.__negotiatesocks4(destpair[0], destpair[1]) | |
| 377 elif self.__proxy[0] == PROXY_TYPE_HTTP: | |
| 378 if self.__proxy[2] != None: | |
| 379 portnum = self.__proxy[2] | |
| 380 else: | |
| 381 portnum = 8080 | |
| 382 _orgsocket.connect(self,(self.__proxy[1], portnum)) | |
| 383 self.__negotiatehttp(destpair[0], destpair[1]) | |
| 384 elif self.__proxy[0] == None: | |
| 385 _orgsocket.connect(self, (destpair[0], destpair[1])) | |
| 386 else: | |
| 387 raise GeneralProxyError((4, _generalerrors[4])) | |
| OLD | NEW |