Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
m0rtem
GitHub Repository: m0rtem/CloudFail
Path: blob/master/socks.py
138 views
1
"""
2
SocksiPy - Python SOCKS module.
3
Version 1.5.7
4
5
Copyright 2006 Dan-Haim. All rights reserved.
6
7
Redistribution and use in source and binary forms, with or without modification,
8
are permitted provided that the following conditions are met:
9
1. Redistributions of source code must retain the above copyright notice, this
10
list of conditions and the following disclaimer.
11
2. 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.
14
3. 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
18
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
19
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
24
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
27
28
29
This module provides a standard socket-like interface for Python
30
for tunneling connections through SOCKS proxies.
31
32
===============================================================================
33
34
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
35
for use in PyLoris (http://pyloris.sourceforge.net/)
36
37
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
38
mainly to merge bug fixes found in Sourceforge
39
40
Modifications made by Anorov (https://github.com/Anorov)
41
-Forked and renamed to PySocks
42
-Fixed issue with HTTP proxy failure checking (same bug that was in the old ___recvall() method)
43
-Included SocksiPyHandler (sockshandler.py), to be used as a urllib2 handler,
44
courtesy of e000 (https://github.com/e000): https://gist.github.com/869791#file_socksipyhandler.py
45
-Re-styled code to make it readable
46
-Aliased PROXY_TYPE_SOCKS5 -> SOCKS5 etc.
47
-Improved exception handling and output
48
-Removed irritating use of sequence indexes, replaced with tuple unpacked variables
49
-Fixed up Python 3 bytestring handling - chr(0x03).encode() -> b"\x03"
50
-Other general fixes
51
-Added clarification that the HTTP proxy connection method only supports CONNECT-style tunneling HTTP proxies
52
-Various small bug fixes
53
"""
54
55
__version__ = "1.5.7"
56
57
import socket
58
import struct
59
from errno import EOPNOTSUPP, EINVAL, EAGAIN
60
from io import BytesIO
61
from os import SEEK_CUR
62
from base64 import b64encode
63
try:
64
from collections.abc import Callable
65
except ImportError:
66
from collections import Callable
67
68
PROXY_TYPE_SOCKS4 = SOCKS4 = 1
69
PROXY_TYPE_SOCKS5 = SOCKS5 = 2
70
PROXY_TYPE_HTTP = HTTP = 3
71
72
PROXY_TYPES = {"SOCKS4": SOCKS4, "SOCKS5": SOCKS5, "HTTP": HTTP}
73
PRINTABLE_PROXY_TYPES = dict(zip(PROXY_TYPES.values(), PROXY_TYPES.keys()))
74
75
_orgsocket = _orig_socket = socket.socket
76
77
class ProxyError(IOError):
78
"""
79
socket_err contains original socket.error exception.
80
"""
81
def __init__(self, msg, socket_err=None):
82
self.msg = msg
83
self.socket_err = socket_err
84
85
if socket_err:
86
self.msg += ": {0}".format(socket_err)
87
88
def __str__(self):
89
return self.msg
90
91
class GeneralProxyError(ProxyError): pass
92
class ProxyConnectionError(ProxyError): pass
93
class SOCKS5AuthError(ProxyError): pass
94
class SOCKS5Error(ProxyError): pass
95
class SOCKS4Error(ProxyError): pass
96
class HTTPError(ProxyError): pass
97
98
SOCKS4_ERRORS = { 0x5B: "Request rejected or failed",
99
0x5C: "Request rejected because SOCKS server cannot connect to identd on the client",
100
0x5D: "Request rejected because the client program and identd report different user-ids"
101
}
102
103
SOCKS5_ERRORS = { 0x01: "General SOCKS server failure",
104
0x02: "Connection not allowed by ruleset",
105
0x03: "Network unreachable",
106
0x04: "Host unreachable",
107
0x05: "Connection refused",
108
0x06: "TTL expired",
109
0x07: "Command not supported, or protocol error",
110
0x08: "Address type not supported"
111
}
112
113
DEFAULT_PORTS = { SOCKS4: 1080,
114
SOCKS5: 1080,
115
HTTP: 8080
116
}
117
118
def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
119
"""
120
set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]])
121
122
Sets a default proxy which all further socksocket objects will use,
123
unless explicitly changed. All parameters are as for socket.set_proxy().
124
"""
125
socksocket.default_proxy = (proxy_type, addr, port, rdns,
126
username.encode() if username else None,
127
password.encode() if password else None)
128
129
setdefaultproxy = set_default_proxy
130
131
def get_default_proxy():
132
"""
133
Returns the default proxy, set by set_default_proxy.
134
"""
135
return socksocket.default_proxy
136
137
getdefaultproxy = get_default_proxy
138
139
def wrap_module(module):
140
"""
141
Attempts to replace a module's socket library with a SOCKS socket. Must set
142
a default proxy using set_default_proxy(...) first.
143
This will only work on modules that import socket directly into the namespace;
144
most of the Python Standard Library falls into this category.
145
"""
146
if socksocket.default_proxy:
147
module.socket.socket = socksocket
148
else:
149
raise GeneralProxyError("No default proxy specified")
150
151
wrapmodule = wrap_module
152
153
def create_connection(dest_pair, proxy_type=None, proxy_addr=None,
154
proxy_port=None, proxy_rdns=True,
155
proxy_username=None, proxy_password=None,
156
timeout=None, source_address=None,
157
socket_options=None):
158
"""create_connection(dest_pair, *[, timeout], **proxy_args) -> socket object
159
160
Like socket.create_connection(), but connects to proxy
161
before returning the socket object.
162
163
dest_pair - 2-tuple of (IP/hostname, port).
164
**proxy_args - Same args passed to socksocket.set_proxy() if present.
165
timeout - Optional socket timeout value, in seconds.
166
source_address - tuple (host, port) for the socket to bind to as its source
167
address before connecting (only for compatibility)
168
"""
169
# Remove IPv6 brackets on the remote address and proxy address.
170
remote_host, remote_port = dest_pair
171
if remote_host.startswith('['):
172
remote_host = remote_host.strip('[]')
173
if proxy_addr and proxy_addr.startswith('['):
174
proxy_addr = proxy_addr.strip('[]')
175
176
err = None
177
178
# Allow the SOCKS proxy to be on IPv4 or IPv6 addresses.
179
for r in socket.getaddrinfo(proxy_addr, proxy_port, 0, socket.SOCK_STREAM):
180
family, socket_type, proto, canonname, sa = r
181
sock = None
182
try:
183
sock = socksocket(family, socket_type, proto)
184
185
if socket_options is not None:
186
for opt in socket_options:
187
sock.setsockopt(*opt)
188
189
if isinstance(timeout, (int, float)):
190
sock.settimeout(timeout)
191
192
if proxy_type is not None:
193
sock.set_proxy(proxy_type, proxy_addr, proxy_port, proxy_rdns,
194
proxy_username, proxy_password)
195
if source_address is not None:
196
sock.bind(source_address)
197
198
sock.connect((remote_host, remote_port))
199
return sock
200
201
except socket.error as e:
202
err = e
203
if sock is not None:
204
sock.close()
205
sock = None
206
207
if err is not None:
208
raise err
209
210
raise socket.error("gai returned empty list.")
211
212
class _BaseSocket(socket.socket):
213
"""Allows Python 2's "delegated" methods such as send() to be overridden
214
"""
215
def __init__(self, *pos, **kw):
216
_orig_socket.__init__(self, *pos, **kw)
217
218
self._savedmethods = dict()
219
for name in self._savenames:
220
self._savedmethods[name] = getattr(self, name)
221
delattr(self, name) # Allows normal overriding mechanism to work
222
223
_savenames = list()
224
225
def _makemethod(name):
226
return lambda self, *pos, **kw: self._savedmethods[name](*pos, **kw)
227
for name in ("sendto", "send", "recvfrom", "recv"):
228
method = getattr(_BaseSocket, name, None)
229
230
# Determine if the method is not defined the usual way
231
# as a function in the class.
232
# Python 2 uses __slots__, so there are descriptors for each method,
233
# but they are not functions.
234
if not isinstance(method, Callable):
235
_BaseSocket._savenames.append(name)
236
setattr(_BaseSocket, name, _makemethod(name))
237
238
class socksocket(_BaseSocket):
239
"""socksocket([family[, type[, proto]]]) -> socket object
240
241
Open a SOCKS enabled socket. The parameters are the same as
242
those of the standard socket init. In order for SOCKS to work,
243
you must specify family=AF_INET and proto=0.
244
The "type" argument must be either SOCK_STREAM or SOCK_DGRAM.
245
"""
246
247
default_proxy = None
248
249
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, *args, **kwargs):
250
if type not in (socket.SOCK_STREAM, socket.SOCK_DGRAM):
251
msg = "Socket type must be stream or datagram, not {!r}"
252
raise ValueError(msg.format(type))
253
254
_BaseSocket.__init__(self, family, type, proto, *args, **kwargs)
255
self._proxyconn = None # TCP connection to keep UDP relay alive
256
257
if self.default_proxy:
258
self.proxy = self.default_proxy
259
else:
260
self.proxy = (None, None, None, None, None, None)
261
self.proxy_sockname = None
262
self.proxy_peername = None
263
264
def _readall(self, file, count):
265
"""
266
Receive EXACTLY the number of bytes requested from the file object.
267
Blocks until the required number of bytes have been received.
268
"""
269
data = b""
270
while len(data) < count:
271
d = file.read(count - len(data))
272
if not d:
273
raise GeneralProxyError("Connection closed unexpectedly")
274
data += d
275
return data
276
277
def set_proxy(self, proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None):
278
"""set_proxy(proxy_type, addr[, port[, rdns[, username[, password]]]])
279
Sets the proxy to be used.
280
281
proxy_type - The type of the proxy to be used. Three types
282
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
283
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
284
addr - The address of the server (IP or DNS).
285
port - The port of the server. Defaults to 1080 for SOCKS
286
servers and 8080 for HTTP proxy servers.
287
rdns - Should DNS queries be performed on the remote side
288
(rather than the local side). The default is True.
289
Note: This has no effect with SOCKS4 servers.
290
username - Username to authenticate with to the server.
291
The default is no authentication.
292
password - Password to authenticate with to the server.
293
Only relevant when username is also provided.
294
"""
295
self.proxy = (proxy_type, addr, port, rdns,
296
username.encode() if username else None,
297
password.encode() if password else None)
298
299
setproxy = set_proxy
300
301
def bind(self, *pos, **kw):
302
"""
303
Implements proxy connection for UDP sockets,
304
which happens during the bind() phase.
305
"""
306
proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
307
if not proxy_type or self.type != socket.SOCK_DGRAM:
308
return _orig_socket.bind(self, *pos, **kw)
309
310
if self._proxyconn:
311
raise socket.error(EINVAL, "Socket already bound to an address")
312
if proxy_type != SOCKS5:
313
msg = "UDP only supported by SOCKS5 proxy type"
314
raise socket.error(EOPNOTSUPP, msg)
315
_BaseSocket.bind(self, *pos, **kw)
316
317
# Need to specify actual local port because
318
# some relays drop packets if a port of zero is specified.
319
# Avoid specifying host address in case of NAT though.
320
_, port = self.getsockname()
321
dst = ("0", port)
322
323
self._proxyconn = _orig_socket()
324
proxy = self._proxy_addr()
325
self._proxyconn.connect(proxy)
326
327
UDP_ASSOCIATE = b"\x03"
328
_, relay = self._SOCKS5_request(self._proxyconn, UDP_ASSOCIATE, dst)
329
330
# The relay is most likely on the same host as the SOCKS proxy,
331
# but some proxies return a private IP address (10.x.y.z)
332
host, _ = proxy
333
_, port = relay
334
_BaseSocket.connect(self, (host, port))
335
self.proxy_sockname = ("0.0.0.0", 0) # Unknown
336
337
def sendto(self, bytes, *args, **kwargs):
338
if self.type != socket.SOCK_DGRAM:
339
return _BaseSocket.sendto(self, bytes, *args, **kwargs)
340
if not self._proxyconn:
341
self.bind(("", 0))
342
343
address = args[-1]
344
flags = args[:-1]
345
346
header = BytesIO()
347
RSV = b"\x00\x00"
348
header.write(RSV)
349
STANDALONE = b"\x00"
350
header.write(STANDALONE)
351
self._write_SOCKS5_address(address, header)
352
353
sent = _BaseSocket.send(self, header.getvalue() + bytes, *flags, **kwargs)
354
return sent - header.tell()
355
356
def send(self, bytes, flags=0, **kwargs):
357
if self.type == socket.SOCK_DGRAM:
358
return self.sendto(bytes, flags, self.proxy_peername, **kwargs)
359
else:
360
return _BaseSocket.send(self, bytes, flags, **kwargs)
361
362
def recvfrom(self, bufsize, flags=0):
363
if self.type != socket.SOCK_DGRAM:
364
return _BaseSocket.recvfrom(self, bufsize, flags)
365
if not self._proxyconn:
366
self.bind(("", 0))
367
368
buf = BytesIO(_BaseSocket.recv(self, bufsize, flags))
369
buf.seek(+2, SEEK_CUR)
370
frag = buf.read(1)
371
if ord(frag):
372
raise NotImplementedError("Received UDP packet fragment")
373
fromhost, fromport = self._read_SOCKS5_address(buf)
374
375
if self.proxy_peername:
376
peerhost, peerport = self.proxy_peername
377
if fromhost != peerhost or peerport not in (0, fromport):
378
raise socket.error(EAGAIN, "Packet filtered")
379
380
return (buf.read(), (fromhost, fromport))
381
382
def recv(self, *pos, **kw):
383
bytes, _ = self.recvfrom(*pos, **kw)
384
return bytes
385
386
def close(self):
387
if self._proxyconn:
388
self._proxyconn.close()
389
return _BaseSocket.close(self)
390
391
def get_proxy_sockname(self):
392
"""
393
Returns the bound IP address and port number at the proxy.
394
"""
395
return self.proxy_sockname
396
397
getproxysockname = get_proxy_sockname
398
399
def get_proxy_peername(self):
400
"""
401
Returns the IP and port number of the proxy.
402
"""
403
return _BaseSocket.getpeername(self)
404
405
getproxypeername = get_proxy_peername
406
407
def get_peername(self):
408
"""
409
Returns the IP address and port number of the destination
410
machine (note: get_proxy_peername returns the proxy)
411
"""
412
return self.proxy_peername
413
414
getpeername = get_peername
415
416
def _negotiate_SOCKS5(self, *dest_addr):
417
"""
418
Negotiates a stream connection through a SOCKS5 server.
419
"""
420
CONNECT = b"\x01"
421
self.proxy_peername, self.proxy_sockname = self._SOCKS5_request(self,
422
CONNECT, dest_addr)
423
424
def _SOCKS5_request(self, conn, cmd, dst):
425
"""
426
Send SOCKS5 request with given command (CMD field) and
427
address (DST field). Returns resolved DST address that was used.
428
"""
429
proxy_type, addr, port, rdns, username, password = self.proxy
430
431
writer = conn.makefile("wb")
432
reader = conn.makefile("rb", 0) # buffering=0 renamed in Python 3
433
try:
434
# First we'll send the authentication packages we support.
435
if username and password:
436
# The username/password details were supplied to the
437
# set_proxy method so we support the USERNAME/PASSWORD
438
# authentication (in addition to the standard none).
439
writer.write(b"\x05\x02\x00\x02")
440
else:
441
# No username/password were entered, therefore we
442
# only support connections with no authentication.
443
writer.write(b"\x05\x01\x00")
444
445
# We'll receive the server's response to determine which
446
# method was selected
447
writer.flush()
448
chosen_auth = self._readall(reader, 2)
449
450
if chosen_auth[0:1] != b"\x05":
451
# Note: string[i:i+1] is used because indexing of a bytestring
452
# via bytestring[i] yields an integer in Python 3
453
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
454
455
# Check the chosen authentication method
456
457
if chosen_auth[1:2] == b"\x02":
458
# Okay, we need to perform a basic username/password
459
# authentication.
460
writer.write(b"\x01" + chr(len(username)).encode()
461
+ username
462
+ chr(len(password)).encode()
463
+ password)
464
writer.flush()
465
auth_status = self._readall(reader, 2)
466
if auth_status[0:1] != b"\x01":
467
# Bad response
468
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
469
if auth_status[1:2] != b"\x00":
470
# Authentication failed
471
raise SOCKS5AuthError("SOCKS5 authentication failed")
472
473
# Otherwise, authentication succeeded
474
475
# No authentication is required if 0x00
476
elif chosen_auth[1:2] != b"\x00":
477
# Reaching here is always bad
478
if chosen_auth[1:2] == b"\xFF":
479
raise SOCKS5AuthError("All offered SOCKS5 authentication methods were rejected")
480
else:
481
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
482
483
# Now we can request the actual connection
484
writer.write(b"\x05" + cmd + b"\x00")
485
resolved = self._write_SOCKS5_address(dst, writer)
486
writer.flush()
487
488
# Get the response
489
resp = self._readall(reader, 3)
490
if resp[0:1] != b"\x05":
491
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
492
493
status = ord(resp[1:2])
494
if status != 0x00:
495
# Connection failed: server returned an error
496
error = SOCKS5_ERRORS.get(status, "Unknown error")
497
raise SOCKS5Error("{0:#04x}: {1}".format(status, error))
498
499
# Get the bound address/port
500
bnd = self._read_SOCKS5_address(reader)
501
return (resolved, bnd)
502
finally:
503
reader.close()
504
writer.close()
505
506
def _write_SOCKS5_address(self, addr, file):
507
"""
508
Return the host and port packed for the SOCKS5 protocol,
509
and the resolved address as a tuple object.
510
"""
511
host, port = addr
512
proxy_type, _, _, rdns, username, password = self.proxy
513
family_to_byte = {socket.AF_INET: b"\x01", socket.AF_INET6: b"\x04"}
514
515
# If the given destination address is an IP address, we'll
516
# use the IP address request even if remote resolving was specified.
517
# Detect whether the address is IPv4/6 directly.
518
for family in (socket.AF_INET, socket.AF_INET6):
519
try:
520
addr_bytes = socket.inet_pton(family, host)
521
file.write(family_to_byte[family] + addr_bytes)
522
host = socket.inet_ntop(family, addr_bytes)
523
file.write(struct.pack(">H", port))
524
return host, port
525
except socket.error:
526
continue
527
528
# Well it's not an IP number, so it's probably a DNS name.
529
if rdns:
530
# Resolve remotely
531
host_bytes = host.encode('idna')
532
file.write(b"\x03" + chr(len(host_bytes)).encode() + host_bytes)
533
else:
534
# Resolve locally
535
addresses = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG)
536
# We can't really work out what IP is reachable, so just pick the
537
# first.
538
target_addr = addresses[0]
539
family = target_addr[0]
540
host = target_addr[4][0]
541
542
addr_bytes = socket.inet_pton(family, host)
543
file.write(family_to_byte[family] + addr_bytes)
544
host = socket.inet_ntop(family, addr_bytes)
545
file.write(struct.pack(">H", port))
546
return host, port
547
548
def _read_SOCKS5_address(self, file):
549
atyp = self._readall(file, 1)
550
if atyp == b"\x01":
551
addr = socket.inet_ntoa(self._readall(file, 4))
552
elif atyp == b"\x03":
553
length = self._readall(file, 1)
554
addr = self._readall(file, ord(length))
555
elif atyp == b"\x04":
556
addr = socket.inet_ntop(socket.AF_INET6, self._readall(file, 16))
557
else:
558
raise GeneralProxyError("SOCKS5 proxy server sent invalid data")
559
560
port = struct.unpack(">H", self._readall(file, 2))[0]
561
return addr, port
562
563
def _negotiate_SOCKS4(self, dest_addr, dest_port):
564
"""
565
Negotiates a connection through a SOCKS4 server.
566
"""
567
proxy_type, addr, port, rdns, username, password = self.proxy
568
569
writer = self.makefile("wb")
570
reader = self.makefile("rb", 0) # buffering=0 renamed in Python 3
571
try:
572
# Check if the destination address provided is an IP address
573
remote_resolve = False
574
try:
575
addr_bytes = socket.inet_aton(dest_addr)
576
except socket.error:
577
# It's a DNS name. Check where it should be resolved.
578
if rdns:
579
addr_bytes = b"\x00\x00\x00\x01"
580
remote_resolve = True
581
else:
582
addr_bytes = socket.inet_aton(socket.gethostbyname(dest_addr))
583
584
# Construct the request packet
585
writer.write(struct.pack(">BBH", 0x04, 0x01, dest_port))
586
writer.write(addr_bytes)
587
588
# The username parameter is considered userid for SOCKS4
589
if username:
590
writer.write(username)
591
writer.write(b"\x00")
592
593
# DNS name if remote resolving is required
594
# NOTE: This is actually an extension to the SOCKS4 protocol
595
# called SOCKS4A and may not be supported in all cases.
596
if remote_resolve:
597
writer.write(dest_addr.encode('idna') + b"\x00")
598
writer.flush()
599
600
# Get the response from the server
601
resp = self._readall(reader, 8)
602
if resp[0:1] != b"\x00":
603
# Bad data
604
raise GeneralProxyError("SOCKS4 proxy server sent invalid data")
605
606
status = ord(resp[1:2])
607
if status != 0x5A:
608
# Connection failed: server returned an error
609
error = SOCKS4_ERRORS.get(status, "Unknown error")
610
raise SOCKS4Error("{0:#04x}: {1}".format(status, error))
611
612
# Get the bound address/port
613
self.proxy_sockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
614
if remote_resolve:
615
self.proxy_peername = socket.inet_ntoa(addr_bytes), dest_port
616
else:
617
self.proxy_peername = dest_addr, dest_port
618
finally:
619
reader.close()
620
writer.close()
621
622
def _negotiate_HTTP(self, dest_addr, dest_port):
623
"""
624
Negotiates a connection through an HTTP server.
625
NOTE: This currently only supports HTTP CONNECT-style proxies.
626
"""
627
proxy_type, addr, port, rdns, username, password = self.proxy
628
629
# If we need to resolve locally, we do this now
630
addr = dest_addr if rdns else socket.gethostbyname(dest_addr)
631
632
http_headers = [
633
b"CONNECT " + addr.encode('idna') + b":" + str(dest_port).encode() + b" HTTP/1.1",
634
b"Host: " + dest_addr.encode('idna')
635
]
636
637
if username and password:
638
http_headers.append(b"Proxy-Authorization: basic " + b64encode(username + b":" + password))
639
640
http_headers.append(b"\r\n")
641
642
self.sendall(b"\r\n".join(http_headers))
643
644
# We just need the first line to check if the connection was successful
645
fobj = self.makefile()
646
status_line = fobj.readline()
647
fobj.close()
648
649
if not status_line:
650
raise GeneralProxyError("Connection closed unexpectedly")
651
652
try:
653
proto, status_code, status_msg = status_line.split(" ", 2)
654
except ValueError:
655
raise GeneralProxyError("HTTP proxy server sent invalid response")
656
657
if not proto.startswith("HTTP/"):
658
raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy")
659
660
try:
661
status_code = int(status_code)
662
except ValueError:
663
raise HTTPError("HTTP proxy server did not return a valid HTTP status")
664
665
if status_code != 200:
666
error = "{0}: {1}".format(status_code, status_msg)
667
if status_code in (400, 403, 405):
668
# It's likely that the HTTP proxy server does not support the CONNECT tunneling method
669
error += ("\n[*] Note: The HTTP proxy server may not be supported by PySocks"
670
" (must be a CONNECT tunnel proxy)")
671
raise HTTPError(error)
672
673
self.proxy_sockname = (b"0.0.0.0", 0)
674
self.proxy_peername = addr, dest_port
675
676
_proxy_negotiators = {
677
SOCKS4: _negotiate_SOCKS4,
678
SOCKS5: _negotiate_SOCKS5,
679
HTTP: _negotiate_HTTP
680
}
681
682
683
def connect(self, dest_pair):
684
"""
685
Connects to the specified destination through a proxy.
686
Uses the same API as socket's connect().
687
To select the proxy server, use set_proxy().
688
689
dest_pair - 2-tuple of (IP/hostname, port).
690
"""
691
if len(dest_pair) != 2 or dest_pair[0].startswith("["):
692
# Probably IPv6, not supported -- raise an error, and hope
693
# Happy Eyeballs (RFC6555) makes sure at least the IPv4
694
# connection works...
695
raise socket.error("PySocks doesn't support IPv6")
696
697
dest_addr, dest_port = dest_pair
698
699
if self.type == socket.SOCK_DGRAM:
700
if not self._proxyconn:
701
self.bind(("", 0))
702
dest_addr = socket.gethostbyname(dest_addr)
703
704
# If the host address is INADDR_ANY or similar, reset the peer
705
# address so that packets are received from any peer
706
if dest_addr == "0.0.0.0" and not dest_port:
707
self.proxy_peername = None
708
else:
709
self.proxy_peername = (dest_addr, dest_port)
710
return
711
712
proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
713
714
# Do a minimal input check first
715
if (not isinstance(dest_pair, (list, tuple))
716
or len(dest_pair) != 2
717
or not dest_addr
718
or not isinstance(dest_port, int)):
719
raise GeneralProxyError("Invalid destination-connection (host, port) pair")
720
721
722
if proxy_type is None:
723
# Treat like regular socket object
724
self.proxy_peername = dest_pair
725
_BaseSocket.connect(self, (dest_addr, dest_port))
726
return
727
728
proxy_addr = self._proxy_addr()
729
730
try:
731
# Initial connection to proxy server
732
_BaseSocket.connect(self, proxy_addr)
733
734
except socket.error as error:
735
# Error while connecting to proxy
736
self.close()
737
proxy_addr, proxy_port = proxy_addr
738
proxy_server = "{0}:{1}".format(proxy_addr, proxy_port)
739
printable_type = PRINTABLE_PROXY_TYPES[proxy_type]
740
741
msg = "Error connecting to {0} proxy {1}".format(printable_type,
742
proxy_server)
743
raise ProxyConnectionError(msg, error)
744
745
else:
746
# Connected to proxy server, now negotiate
747
try:
748
# Calls negotiate_{SOCKS4, SOCKS5, HTTP}
749
negotiate = self._proxy_negotiators[proxy_type]
750
negotiate(self, dest_addr, dest_port)
751
except socket.error as error:
752
# Wrap socket errors
753
self.close()
754
raise GeneralProxyError("Socket error", error)
755
except ProxyError:
756
# Protocol error while negotiating with proxy
757
self.close()
758
raise
759
760
def _proxy_addr(self):
761
"""
762
Return proxy address to connect to as tuple object
763
"""
764
proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy
765
proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type)
766
if not proxy_port:
767
raise GeneralProxyError("Invalid proxy type")
768
return proxy_addr, proxy_port
769
770