├── .gitignore ├── debian ├── compat ├── docs ├── changelog.in ├── rules ├── postrm ├── postinst ├── control.in └── copyright.in ├── MANIFEST.in ├── sockschain ├── __main__.py └── __init__.py ├── scripts ├── rpm-setup.sh └── rpm-install.sh ├── test.py ├── BUGS ├── setup.py ├── Makefile ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | BUGS 2 | README.md 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py *.md LICENSE BUGS 2 | recursive-include scripts * 3 | -------------------------------------------------------------------------------- /sockschain/__main__.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | from . import Main 3 | Main() 4 | -------------------------------------------------------------------------------- /scripts/rpm-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat <setup.cfg 3 | [install] 4 | prefix=/usr 5 | install_lib=$2 6 | single_version_externally_managed=yes 7 | 8 | [bdist_rpm] 9 | release=$1 10 | vendor=PageKite Packaging Team 11 | tac 12 | -------------------------------------------------------------------------------- /scripts/rpm-install.sh: -------------------------------------------------------------------------------- 1 | # This is a replacement for the default disttools RPM build method 2 | # which gets the file lists right, including the byte-compiled files. 3 | python setup.py install --root=$RPM_BUILD_ROOT 4 | find $RPM_BUILD_ROOT -type f \ 5 | |sed -e "s|^$RPM_BUILD_ROOT/*|/|" \ 6 | -e 's|/[^/]*$||' \ 7 | |uniq >INSTALLED_FILES 8 | cat INSTALLED_FILES 9 | -------------------------------------------------------------------------------- /debian/changelog.in: -------------------------------------------------------------------------------- 1 | python-socksipychain-@VERSION@ (@VERSION@-0pagekite) unstable; urgency=low 2 | 3 | * Automatic package build note. 4 | 5 | -- PageKite Packaging Team @DATE@ 6 | 7 | python-socksipychain-2.0.0 (2.0.0-1) unstable; urgency=low 8 | 9 | * Initial release 10 | 11 | -- PageKite Packaging Team Fri, 29 Jul 2011 22:39:51 +0000 12 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with python2,python3 --buildsystem=pybuild 5 | 6 | override_dh_auto_install: 7 | python setup.py install --root=$(CURDIR)/debian/python-socksipychain --install-layout=deb 8 | python3 setup.py install --root=$(CURDIR)/debian/python3-socksipychain --install-layout=deb 9 | rm -rf $(CURDIR)/debian/python3-socksipychain/usr/bin 10 | 11 | # This keeps backwards compatibility with older Debians 12 | override_dh_builddeb: 13 | dh_builddeb -- -Zgzip 14 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for pagekite 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | case "$1" in 9 | *) 10 | rm -f /usr/lib/python2.*/dist-packages/sockschain || true 11 | rm -f /usr/share/pyshared/sockschain || true 12 | ;; 13 | 14 | UNUSED) 15 | echo "WARNING: postinst called with unknown argument \`$1'" >&2 16 | ;; 17 | 18 | esac 19 | 20 | # dh_installdeb will replace this with shell code automatically 21 | # generated by other debhelper scripts. 22 | 23 | #DEBHELPER# 24 | 25 | exit 0 26 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import telnetlib 4 | import urllib.request as urllib_request # Python 3 5 | from urllib.request import urlopen 6 | 7 | # Import SocksiPy 8 | import sockschain as socks 9 | def DEBUG(msg): print(msg) 10 | socks.DEBUG = DEBUG 11 | 12 | # Set the proxy information 13 | socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, 'localhost', 9050) 14 | 15 | # Route an HTTP request through the SOCKS proxy 16 | socks.wrapmodule(urllib_request) 17 | print('\n%s\n' % urlopen('https://wtfismyip.com/text').read()) 18 | 19 | # Route a telnet connection through the SOCKS proxy 20 | socks.wrapmodule(telnetlib) 21 | tn = telnetlib.Telnet('india.colorado.edu', 13) 22 | print(tn.read_all()) 23 | tn.close() 24 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for pagekite 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | case "$1" in 9 | configure|upgrade|install) 10 | S=/usr/share/pyshared/sockschain/ 11 | for D in /usr/lib/python2.*/dist-packages/; do 12 | [ -e "$D/sockschain" ] || [ -e "$D" ] && ln -vs "$S" "$D" || true 13 | done 14 | rm -f "$S/*.pyc" || true 15 | ;; 16 | 17 | abort-*) 18 | ;; 19 | 20 | *) 21 | echo "WARNING: postinst called with unknown argument \`$1'" >&2 22 | ;; 23 | 24 | esac 25 | 26 | # dh_installdeb will replace this with shell code automatically 27 | # generated by other debhelper scripts. 28 | 29 | #DEBHELPER# 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /BUGS: -------------------------------------------------------------------------------- 1 | KNOWN BUGS AND ISSUES 2 | --------------------- 3 | 4 | There are no currently known bugs in this module. 5 | There are some limits though: 6 | 7 | 1) Only outgoing connections are supported - This module currently only 8 | supports outgoing TCP connections, though some servers may support incoming 9 | connections as well. UDP is not supported either. 10 | 11 | 2) GSSAPI Socks5 authenticaion is not supported. 12 | 13 | 3) SSL host name verification using pyOpenSSL does not check the Subject 14 | ALT Name extension, only the common name. This is due to limitations 15 | in pyOpenSSL itself. 16 | 17 | 18 | If you find any new bugs, please file an issue at: 19 | 20 | https://github.com/pagekite/PySocksipyChain/ 21 | 22 | Thank you! 23 | 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | VERSION = "2.1.3" 5 | 6 | setup( 7 | name = "SocksipyChain", 8 | version = VERSION, 9 | description = "A Python SOCKS/HTTP Proxy module", 10 | install_requires=[], 11 | long_description = """\ 12 | This Python module allows you to create TCP connections through a chain 13 | of SOCKS or HTTP proxies without any special effort. It also supports 14 | TLS/SSL encryption if the OpenSSL modules are installed. 15 | """, 16 | url = "http://github.com/PageKite/SocksiPyChain", 17 | author = "Bjarni R. Einarsson", 18 | author_email="bre@pagekite.net", 19 | license = "BSD", 20 | packages=["sockschain"], 21 | entry_points = { 22 | 'console_scripts': [ 23 | 'sockschain = sockschain:Main' 24 | ] 25 | } 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /debian/control.in: -------------------------------------------------------------------------------- 1 | Source: python-socksipychain-@VERSION@ 2 | Section: python 3 | Priority: optional 4 | Maintainer: PageKite Packaging Team 5 | Build-Depends: debhelper (>= 9), 6 | python (>= 2.7), 7 | python3 (>= 3.2) 8 | XS-Python-Version: >= 2.7, >= 3.2 9 | Standards-Version: 3.9.5 10 | Homepage: http://pagekite.net/wiki/Floss/PySocksipyChain/ 11 | 12 | Package: python-socksipychain 13 | Section: python 14 | Architecture: all 15 | Depends: ${misc:Depends}, python (>= 2.7) 16 | Description: Python SOCKS/HTTP/SSL chaining proxy module 17 | This Python module allows you to create TCP connections through a chain 18 | of SOCKS or HTTP proxies without any special effort. It also supports 19 | TLS/SSL encryption if the OpenSSL modules are installed. 20 | 21 | Package: python3-socksipychain 22 | Section: python 23 | Architecture: all 24 | Depends: ${misc:Depends}, python3 (>= 3.2) 25 | Description: Python SOCKS/HTTP/SSL chaining proxy module 26 | This Python module allows you to create TCP connections through a chain 27 | of SOCKS or HTTP proxies without any special effort. It also supports 28 | TLS/SSL encryption if the OpenSSL modules are installed. 29 | 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # These are rules for building various types of distributions of this 2 | # module. This Makefile is known to work on an Ubuntu 11.04 and builds 3 | # the following packages: 4 | # 5 | # - Source .tar.gz distribution 6 | # - Binary (all) .deb 7 | # 8 | dist: .deb .targz 9 | @echo ======= BUILD OK ===================== 10 | @ls -l dist/ 11 | 12 | VERSION=`python setup.py --version` 13 | 14 | .targz: 15 | @echo ======= .TAR.GZ ===================== 16 | @python setup.py sdist 17 | @touch .targz 18 | 19 | .deb: .targz debian/control.in setup.py Makefile 20 | @echo ======= .DEB ===================== 21 | @rm -f setup.cfg 22 | @cp -v dist/SocksipyChain*tar.gz \ 23 | ../python-socksipychain-$(VERSION)_$(VERSION).orig.tar.gz 24 | @sed -e "s/@VERSION@/$(VERSION)/g" \ 25 | < debian/control.in >debian/control 26 | @sed -e "s/@VERSION@/$(VERSION)/g" \ 27 | < debian/copyright.in >debian/copyright 28 | @sed -e "s/@VERSION@/$(VERSION)/g" \ 29 | -e "s/@DATE@/`date -R`/g" \ 30 | < debian/changelog.in >debian/changelog 31 | @debuild -i -us -uc -b 32 | @mv ../python*-socksipychain_*.deb dist/ 33 | @rm ../python*-socksipychain-* 34 | @touch .deb 35 | 36 | clean: 37 | @rm -rf .targz .deb build MANIFEST *.egg-info setup.cfg 38 | @rm -rf debian/files debian/control debian/copyright debian/python-* 39 | @rm -rf debian/changelog 40 | 41 | distclean: clean 42 | @rm -rf dist/* 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Bjarni R. Einarsson. All rights reserved. 2 | Copyright 2006 Dan-Haim. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | 3. Neither the name of Dan Haim nor the names of his contributors may be used 12 | to endorse or promote products derived from this software without specific 13 | prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED 16 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 17 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 18 | EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA 21 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 23 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. 24 | -------------------------------------------------------------------------------- /debian/copyright.in: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: python-socksipychain-@VERSION@ 3 | Source: http://pagekite.net/pk/src/SocksipyChain-@VERSION@.tar.gz 4 | 5 | Files: * 6 | Copyright: 7 | 2011 Bjarni Runar Einarsson 8 | 2006 Dan-Haim. All rights reserved. 9 | License: BSD 10 | Redistribution and use in source and binary forms, with or without modification, 11 | are permitted provided that the following conditions are met: 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 2. Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 3. Neither the name of Dan Haim nor the names of his contributors may be used 18 | to endorse or promote products derived from this software without specific 19 | prior written permission. 20 | . 21 | THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED 22 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 23 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 24 | EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA 27 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 29 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. 30 | 31 | Files: debian/* 32 | Copyright: 2011 PageKite Packaging Team 33 | License: GPL-2+ 34 | This package is free software; you can redistribute it and/or modify 35 | it under the terms of the GNU General Public License as published by 36 | the Free Software Foundation; either version 2 of the License, or 37 | (at your option) any later version. 38 | . 39 | This package is distributed in the hope that it will be useful, 40 | but WITHOUT ANY WARRANTY; without even the implied warranty of 41 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 42 | GNU General Public License for more details. 43 | . 44 | You should have received a copy of the GNU General Public License 45 | along with this program. If not, see 46 | . 47 | On Debian systems, the complete text of the GNU General 48 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PySocksipyChain # 2 | 3 | This is a modified version of socksipy, which supports arbitrary chaining of 4 | proxy servers and various modes of TLS/SSL encryption. 5 | 6 | This can be used to very easily add proxy and TLS/SSL support to almost any 7 | Python TCP-based communications, and was initially done to support the needs 8 | of the [PageKite](http://pagekite.org/) project. 9 | 10 | See [the PySocksipyChain page on 11 | pagekite.net](http://pagekite.net/wiki/Floss/PySocksipyChain/) for further 12 | details. 13 | 14 | 15 | ------------------------------------------------------------------------------- 16 | # Original README # 17 | 18 |
 19 | SocksiPy version 1.00
 20 | A Python SOCKS module.
 21 | (C) 2006 Dan-Haim. All rights reserved.
 22 | See LICENSE file for details.
 23 | 
 24 | 
 25 | WHAT IS A SOCKS PROXY?
 26 | A SOCKS proxy is a proxy server at the TCP level. In other words, it acts as
 27 | a tunnel, relaying all traffic going through it without modifying it.
 28 | SOCKS proxies can be used to relay traffic using any network protocol that
 29 | uses TCP.
 30 | 
 31 | WHAT IS SOCKSIPY?
 32 | This Python module allows you to create TCP connections through a SOCKS
 33 | proxy without any special effort.
 34 | 
 35 | PROXY COMPATIBILITY
 36 | SocksiPy is compatible with three different types of proxies:
 37 | 1. SOCKS Version 4 (Socks4), including the Socks4a extension.
 38 | 2. SOCKS Version 5 (Socks5).
 39 | 3. HTTP Proxies which support tunneling using the CONNECT method.
 40 | 
 41 | SYSTEM REQUIREMENTS
 42 | Being written in Python, SocksiPy can run on any platform that has a Python
 43 | interpreter and TCP/IP support.
 44 | This module has been tested with Python 2.3 and should work with greater versions
 45 | just as well.
 46 | 
 47 | 
 48 | INSTALLATION
 49 | -------------
 50 | 
 51 | Simply copy the file "socks.py" to your Python's lib/site-packages directory,
 52 | and you're ready to go.
 53 | 
 54 | 
 55 | USAGE
 56 | ------
 57 | 
 58 | First load the socks module with the command:
 59 | 
 60 | >>> import socks
 61 | >>>
 62 | 
 63 | The socks module provides a class called "socksocket", which is the base to
 64 | all of the module's functionality.
 65 | The socksocket object has the same initialization parameters as the normal socket
 66 | object to ensure maximal compatibility, however it should be noted that socksocket
 67 | will only function with family being AF_INET and type being SOCK_STREAM.
 68 | Generally, it is best to initialize the socksocket object with no parameters
 69 | 
 70 | >>> s = socks.socksocket()
 71 | >>>
 72 | 
 73 | The socksocket object has an interface which is very similar  to socket's (in fact
 74 | the socksocket class is derived from socket) with a few extra methods.
 75 | To select the proxy server you would like to use, use the setproxy method, whose
 76 | syntax is:
 77 | 
 78 | setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
 79 | 
 80 | Explaination of the parameters:
 81 | 
 82 | proxytype - The type of the proxy server. This can be one of three possible
 83 | choices: PROXY_TYPE_SOCKS4, PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP for Socks4,
 84 | Socks5 and HTTP servers respectively.
 85 | 
 86 | addr - The IP address or DNS name of the proxy server.
 87 | 
 88 | port - The port of the proxy server. Defaults to 1080 for socks and 8080 for http.
 89 | 
 90 | rdns - This is a boolean flag than modifies the behavior regarding DNS resolving.
 91 | If it is set to True, DNS resolving will be preformed remotely, on the server.
 92 | If it is set to False, DNS resolving will be preformed locally. Please note that
 93 | setting this to True with Socks4 servers actually use an extension to the protocol,
 94 | called Socks4a, which may not be supported on all servers (Socks5 and http servers
 95 | always support DNS). The default is True.
 96 | 
 97 | username - For Socks5 servers, this allows simple username / password authentication
 98 | with the server. For Socks4 servers, this parameter will be sent as the userid.
 99 | This parameter is ignored if an HTTP server is being used. If it is not provided,
100 | authentication will not be used (servers may accept unauthentication requests).
101 | 
102 | password - This parameter is valid only for Socks5 servers and specifies the
103 | respective password for the username provided.
104 | 
105 | Example of usage:
106 | 
107 | >>> s.setproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com")
108 | >>>
109 | 
110 | After the setproxy method has been called, simply call the connect method with the
111 | traditional parameters to establish a connection through the proxy:
112 | 
113 | >>> s.connect(("www.sourceforge.net",80))
114 | >>>
115 | 
116 | Connection will take a bit longer to allow negotiation with the proxy server.
117 | Please note that calling connect without calling setproxy earlier will connect
118 | without a proxy (just like a regular socket).
119 | 
120 | Errors: Any errors in the connection process will trigger exceptions. The exception
121 | may either be generated by the underlying socket layer or may be custom module
122 | exceptions, whose details follow:
123 | 
124 | class ProxyError - This is a base exception class. It is not raised directly but
125 | rather all other exception classes raised by this module are derived from it.
126 | This allows an easy way to catch all proxy-related errors.
127 | 
128 | class GeneralProxyError - When thrown, it indicates a problem which does not fall
129 | into another category. The parameter is a tuple containing an error code and a
130 | description of the error, from the following list:
131 | 1 - invalid data - This error means that unexpected data has been received from
132 | the server. The most common reason is that the server specified as the proxy is
133 | not really a Socks4/Socks5/HTTP proxy, or maybe the proxy type specified is wrong.
134 | 4 - bad proxy type - This will be raised if the type of the proxy supplied to the
135 | setproxy function was not PROXY_TYPE_SOCKS4/PROXY_TYPE_SOCKS5/PROXY_TYPE_HTTP.
136 | 5 - bad input - This will be raised if the connect method is called with bad input
137 | parameters.
138 | 
139 | class Socks5AuthError - This indicates that the connection through a Socks5 server
140 | failed due to an authentication problem. The parameter is a tuple containing a
141 | code and a description message according to the following list:
142 | 
143 | 1 - authentication is required - This will happen if you use a Socks5 server which
144 | requires authentication without providing a username / password at all.
145 | 2 - all offered authentication methods were rejected - This will happen if the proxy
146 | requires a special authentication method which is not supported by this module.
147 | 3 - unknown username or invalid password - Self descriptive.
148 | 
149 | class Socks5Error - This will be raised for Socks5 errors which are not related to
150 | authentication. The parameter is a tuple containing a code and a description of the
151 | error, as given by the server. The possible errors, according to the RFC are:
152 | 
153 | 1 - General SOCKS server failure - If for any reason the proxy server is unable to
154 | fulfill your request (internal server error).
155 | 2 - connection not allowed by ruleset - If the address you're trying to connect to
156 | is blacklisted on the server or requires authentication.
157 | 3 - Network unreachable - The target could not be contacted. A router on the network
158 | had replied with a destination net unreachable error.
159 | 4 - Host unreachable - The target could not be contacted. A router on the network
160 | had replied with a destination host unreachable error.
161 | 5 - Connection refused - The target server has actively refused the connection
162 | (the requested port is closed).
163 | 6 - TTL expired - The TTL value of the SYN packet from the proxy to the target server
164 | has expired. This usually means that there are network problems causing the packet
165 | to be caught in a router-to-router "ping-pong".
166 | 7 - Command not supported - The client has issued an invalid command. When using this
167 | module, this error should not occur.
168 | 8 - Address type not supported - The client has provided an invalid address type.
169 | When using this module, this error should not occur.
170 | 
171 | class Socks4Error - This will be raised for Socks4 errors. The parameter is a tuple
172 | containing a code and a description of the error, as given by the server. The
173 | possible error, according to the specification are:
174 | 
175 | 1 - Request rejected or failed - Will be raised in the event of an failure for any
176 | reason other then the two mentioned next.
177 | 2 - request rejected because SOCKS server cannot connect to identd on the client -
178 | The Socks server had tried an ident lookup on your computer and has failed. In this
179 | case you should run an identd server and/or configure your firewall to allow incoming
180 | connections to local port 113 from the remote server.
181 | 3 - request rejected because the client program and identd report different user-ids -
182 | The Socks server had performed an ident lookup on your computer and has received a
183 | different userid than the one you have provided. Change your userid (through the
184 | username parameter of the setproxy method) to match and try again.
185 | 
186 | class HTTPError - This will be raised for HTTP errors. The parameter is a tuple
187 | containing the HTTP status code and the description of the server.
188 | 
189 | 
190 | After establishing the connection, the object behaves like a standard socket.
191 | Call the close method to close the connection.
192 | 
193 | In addition to the socksocket class, an additional function worth mentioning is the
194 | setdefaultproxy function. The parameters are the same as the setproxy method.
195 | This function will set default proxy settings for newly created socksocket objects,
196 | in which the proxy settings haven't been changed via the setproxy method.
197 | This is quite useful if you wish to force 3rd party modules to use a socks proxy,
198 | by overriding the socket object.
199 | For example:
200 | 
201 | >>> socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com")
202 | >>> socket.socket = socks.socksocket
203 | >>> urllib.urlopen("http://www.sourceforge.net/")
204 | 
205 | 
206 | PROBLEMS
207 | ---------
208 | 
209 | If you have any problems using this module, please first refer to the BUGS file
210 | (containing current bugs and issues). If your problem is not mentioned you may
211 | contact the author at the following E-Mail address:
212 | 
213 | negativeiq@users.sourceforge.net
214 | 
215 | Please allow some time for your question to be received and handled.
216 | 
217 | 
218 | Dan-Haim,
219 | Author.
220 | 
221 | -------------------------------------------------------------------------------- /sockschain/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """SocksiPy - Python SOCKS module. 3 | Version 2.1 4 | 5 | Copyright 2011-2019 Bjarni R. Einarsson. All rights reserved. 6 | Copyright 2006 Dan-Haim. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 3. Neither the name of Dan Haim nor the names of his contributors may be used 16 | to endorse or promote products derived from this software without specific 17 | prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED 20 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 22 | EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA 25 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 27 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. 28 | 29 | 30 | This module provides a standard socket-like interface for Python 31 | for tunneling connections through SOCKS proxies. 32 | 33 | """ 34 | 35 | """ 36 | 37 | Refactored to allow proxy chaining and use as a command-line netcat-like 38 | tool by Bjarni R. Einarsson (http://bre.klaki.net/) for use with PageKite 39 | (http://pagekite.net/). 40 | 41 | Minor modifications made by Christopher Gilbert (http://motomastyle.com/) 42 | for use in PyLoris (http://pyloris.sourceforge.net/) 43 | 44 | Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) 45 | mainly to merge bug fixes found in Sourceforge 46 | 47 | """ 48 | 49 | import base64, errno, os, socket, sys, select, struct, threading 50 | 51 | PY2 = ((2, 0) < sys.version_info < (3, 0)) 52 | if PY2: 53 | b = lambda s: s 54 | else: 55 | b = lambda s: s.encode('latin-1') 56 | 57 | DEBUG = False 58 | DEFAULT_TIMEOUT = 30 59 | #def DEBUG(foo): sys.stderr.write(foo + '\n') 60 | 61 | 62 | ##[ SSL compatibility code ]################################################## 63 | 64 | import hashlib 65 | def sha1hex(data): 66 | hl = hashlib.sha1() 67 | hl.update(data) 68 | return hl.hexdigest().lower() 69 | 70 | 71 | def SSL_CheckName(commonName, digest, valid_names): 72 | try: 73 | digest = str(digest, 'iso-8859-1') 74 | except TypeError: 75 | pass 76 | digest = digest.replace(':', '') 77 | pairs = [(commonName, '%s/%s' % (commonName, digest))] 78 | valid = 0 79 | 80 | if commonName.startswith('*.'): 81 | commonName = commonName[1:].lower() 82 | for name in valid_names: 83 | name = name.split('/')[0].lower() 84 | if ('.'+name).endswith(commonName): 85 | pairs.append((name, '%s/%s' % (name, digest))) 86 | 87 | for commonName, cNameDigest in pairs: 88 | if ((commonName in valid_names) or (cNameDigest in valid_names)): 89 | valid += 1 90 | 91 | if DEBUG: DEBUG(('*** Cert score: %s (%s ?= %s)' 92 | ) % (valid, pairs, valid_names)) 93 | return valid 94 | 95 | 96 | HAVE_SSL = False 97 | HAVE_PYOPENSSL = False 98 | TLS_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt" 99 | try: 100 | if sys.version_info >= (3, ): 101 | raise ImportError('pyOpenSSL disabled (Python 3)') 102 | if '--nopyopenssl' in sys.argv or '--nossl' in sys.argv: 103 | raise ImportError('pyOpenSSL disabled') 104 | 105 | from OpenSSL import SSL 106 | HAVE_SSL = HAVE_PYOPENSSL = True 107 | 108 | def SSL_Connect(ctx, sock, 109 | server_side=False, accepted=False, connected=False, 110 | verify_names=None): 111 | if DEBUG: DEBUG('*** TLS is provided by pyOpenSSL') 112 | if verify_names: 113 | def vcb(conn, x509, errno, depth, rc): 114 | if errno != 0: return False 115 | if depth != 0: return True 116 | if DEBUG: DEBUG('Check: %s' % x509.get_subject().commonName.lower()) 117 | return (SSL_CheckName(x509.get_subject().commonName.lower(), 118 | x509.digest('sha1'), 119 | verify_names) > 0) 120 | ctx.set_verify(SSL.VERIFY_PEER | 121 | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, vcb) 122 | else: 123 | def vcb(conn, x509, errno, depth, rc): return (errno == 0) 124 | ctx.set_verify(SSL.VERIFY_NONE, vcb) 125 | 126 | nsock = SSL.Connection(ctx, sock) 127 | if accepted: nsock.set_accept_state() 128 | if connected: nsock.set_connect_state() 129 | if verify_names: nsock.do_handshake() 130 | 131 | return nsock 132 | 133 | except ImportError: 134 | try: 135 | if '--nossl' in sys.argv: 136 | raise ImportError('SSL disabled') 137 | 138 | import ssl 139 | HAVE_SSL = True 140 | 141 | class SSL(object): 142 | TLSv1_METHOD = ssl.PROTOCOL_TLSv1 143 | WantReadError = ssl.SSLError 144 | class Error(Exception): pass 145 | class SysCallError(Exception): pass 146 | class WantWriteError(Exception): pass 147 | class ZeroReturnError(Exception): pass 148 | class Context(object): 149 | def __init__(self, method): 150 | self.method = method 151 | self.privatekey_file = None 152 | self.certchain_file = None 153 | self.ca_certs = None 154 | self.ciphers = None 155 | self.options = 0 156 | def use_privatekey_file(self, fn): 157 | self.privatekey_file = fn 158 | def use_certificate_chain_file(self, fn): 159 | self.certchain_file = fn 160 | def set_cipher_list(self, ciphers): 161 | self.ciphers = ciphers 162 | def load_verify_locations(self, pemfile, capath=None): 163 | self.ca_certs = pemfile 164 | def set_options(self, options): # FIXME: this does nothing 165 | self.options = options 166 | 167 | if hasattr(ssl, 'PROTOCOL_SSLv23'): 168 | SSL.SSLv23_METHOD = ssl.PROTOCOL_SSLv23 169 | if hasattr(ssl, 'OP_NO_SSLv2'): 170 | SSL.OP_NO_SSLv2 = ssl.OP_NO_SSLv2 171 | if hasattr(ssl, 'OP_NO_SSLv3'): 172 | SSL.OP_NO_SSLv3 = ssl.OP_NO_SSLv3 173 | if hasattr(ssl, 'OP_NO_COMPRESSION'): 174 | SSL.OP_NO_COMPRESSION = ssl.OP_NO_COMPRESSION 175 | if hasattr(ssl, 'PROTOCOL_TLS'): 176 | SSL.TLS_METHOD = ssl.PROTOCOL_TLS 177 | 178 | def SSL_CheckPeerName(fd, names): 179 | cert = fd.getpeercert() 180 | certhash = sha1hex(fd.getpeercert(binary_form=True)) 181 | if not cert: return None 182 | valid = 0 183 | for field in cert['subject']: 184 | if field[0][0].lower() == 'commonname': 185 | valid += SSL_CheckName(field[0][1].lower(), certhash, names) 186 | 187 | if 'subjectAltName' in cert: 188 | for field in cert['subjectAltName']: 189 | if field[0].lower() == 'dns': 190 | name = field[1].lower() 191 | valid += SSL_CheckName(name, certhash, names) 192 | 193 | return (valid > 0) 194 | 195 | def SSL_Connect(ctx, sock, 196 | server_side=False, accepted=False, connected=False, 197 | verify_names=None): 198 | if DEBUG: DEBUG('*** TLS is provided by native Python ssl') 199 | reqs = (verify_names and ssl.CERT_REQUIRED or ssl.CERT_NONE) 200 | 201 | context = ssl.SSLContext(protocol=ctx.method) 202 | context.verify_mode = reqs 203 | # context.check_hostname is not set to True as there is a custom 204 | # implementation for checking hostnames below. 205 | if ctx.ca_certs: 206 | context.load_verify_locations(cafile=ctx.ca_certs) 207 | if ctx.certchain_file: 208 | context.load_cert_chain(certfile=ctx.certchain_file, 209 | keyfile=ctx.privatekey_file) 210 | 211 | try: 212 | if ctx.ciphers: 213 | context.set_ciphers(ctx.ciphers) 214 | except ssl.SSLError: 215 | if DEBUG: DEBUG('No cipher could be selected') 216 | 217 | fd = context.wrap_socket(sock, 218 | do_handshake_on_connect=False, 219 | server_side=server_side) 220 | 221 | if verify_names: 222 | fd.do_handshake() 223 | if not SSL_CheckPeerName(fd, verify_names): 224 | raise SSL.Error(('Cert not in %s (%s)' 225 | ) % (verify_names, reqs)) 226 | return fd 227 | 228 | except ImportError: 229 | class SSL(object): 230 | # Mock to let our try/except clauses below not fail. 231 | class Error(Exception): pass 232 | class SysCallError(Exception): pass 233 | class WantReadError(Exception): pass 234 | class WantWriteError(Exception): pass 235 | class ZeroReturnError(Exception): pass 236 | 237 | 238 | def DisableSSLCompression(): 239 | # Why? Disabling compression in OpenSSL may reduce memory usage *lots*. 240 | 241 | # If there is a sslzliboff module available, prefer that. 242 | # See https://github.com/hausen/SSLZlibOff for working code. 243 | try: 244 | import sslzliboff 245 | sslzliboff.disableZlib() 246 | return 247 | except: 248 | pass 249 | 250 | # Otherwise, fall through to the following hack. 251 | # Source: 252 | # http://journal.paul.querna.org/articles/2011/04/05/openssl-memory-use/ 253 | try: 254 | import ctypes 255 | import glob 256 | openssl = ctypes.CDLL(None, ctypes.RTLD_GLOBAL) 257 | try: 258 | f = openssl.SSL_COMP_get_compression_methods 259 | except AttributeError: 260 | ssllib = sorted(glob.glob("/usr/lib/libssl.so.*"))[0] 261 | openssl = ctypes.CDLL(ssllib, ctypes.RTLD_GLOBAL) 262 | 263 | openssl.SSL_COMP_get_compression_methods.restype = ctypes.c_void_p 264 | openssl.sk_zero.argtypes = [ctypes.c_void_p] 265 | openssl.sk_zero(openssl.SSL_COMP_get_compression_methods()) 266 | except Exception: 267 | if DEBUG: DEBUG('disableSSLCompression: Failed') 268 | 269 | 270 | def MakeBestEffortSSLContext(weak=False, legacy=False, anonymous=False, 271 | ciphers=None): 272 | ssl_version, ssl_options = SSL.TLSv1_METHOD, 0 273 | if hasattr(SSL, 'SSLv23_METHOD') and (weak or legacy): 274 | ssl_version = SSL.SSLv23_METHOD 275 | 276 | if hasattr(SSL, 'OP_NO_SSLv2') and not weak: 277 | ssl_version = SSL.SSLv23_METHOD 278 | ssl_options |= SSL.OP_NO_SSLv2 279 | if hasattr(SSL, 'OP_NO_SSLv3') and not (weak or legacy): 280 | ssl_version = SSL.SSLv23_METHOD 281 | ssl_options |= SSL.OP_NO_SSLv3 282 | if hasattr(SSL, 'TLS_METHOD') and not (weak or legacy): 283 | ssl_version = SSL.TLS_METHOD 284 | 285 | if hasattr(SSL, 'OP_NO_COMPRESSION'): 286 | ssl_options |= SSL.OP_NO_COMPRESSION 287 | 288 | if not ciphers: 289 | if anonymous: 290 | # Insecure and use anon ciphers - this is just camoflage 291 | ciphers = 'aNULL' 292 | else: 293 | ciphers = 'HIGH:-aNULL:-eNULL:-PSK:RC4-SHA:RC4-MD5' 294 | 295 | if DEBUG: DEBUG('*** Context: ssl_version=%x, ssl_options=%x, ciphers=%s' 296 | % (ssl_version, ssl_options, ciphers)) 297 | ctx = SSL.Context(ssl_version) 298 | ctx.set_options(ssl_options) 299 | ctx.set_cipher_list(ciphers) 300 | return ctx 301 | 302 | 303 | ##[ SocksiPy itself ]######################################################### 304 | 305 | PROXY_TYPE_DEFAULT = -1 306 | PROXY_TYPE_NONE = 0 307 | PROXY_TYPE_SOCKS4 = 1 308 | PROXY_TYPE_SOCKS5 = 2 309 | PROXY_TYPE_HTTP = 3 310 | PROXY_TYPE_SSL = 4 311 | PROXY_TYPE_SSL_WEAK = 5 312 | PROXY_TYPE_SSL_ANON = 6 313 | PROXY_TYPE_TOR = 7 314 | PROXY_TYPE_HTTPS = 8 315 | PROXY_TYPE_HTTP_CONNECT = 9 316 | PROXY_TYPE_HTTPS_CONNECT = 10 317 | 318 | PROXY_SSL_TYPES = (PROXY_TYPE_SSL, PROXY_TYPE_SSL_WEAK, 319 | PROXY_TYPE_SSL_ANON, PROXY_TYPE_HTTPS, 320 | PROXY_TYPE_HTTPS_CONNECT) 321 | PROXY_HTTP_TYPES = (PROXY_TYPE_HTTP, PROXY_TYPE_HTTPS) 322 | PROXY_HTTPC_TYPES = (PROXY_TYPE_HTTP_CONNECT, PROXY_TYPE_HTTPS_CONNECT) 323 | PROXY_SOCKS5_TYPES = (PROXY_TYPE_SOCKS5, PROXY_TYPE_TOR) 324 | PROXY_DEFAULTS = { 325 | PROXY_TYPE_NONE: 0, 326 | PROXY_TYPE_DEFAULT: 0, 327 | PROXY_TYPE_HTTP: 8080, 328 | PROXY_TYPE_HTTP_CONNECT: 8080, 329 | PROXY_TYPE_SOCKS4: 1080, 330 | PROXY_TYPE_SOCKS5: 1080, 331 | PROXY_TYPE_TOR: 9050, 332 | } 333 | PROXY_TYPES = { 334 | 'none': PROXY_TYPE_NONE, 335 | 'default': PROXY_TYPE_DEFAULT, 336 | 'defaults': PROXY_TYPE_DEFAULT, 337 | 'http': PROXY_TYPE_HTTP, 338 | 'httpc': PROXY_TYPE_HTTP_CONNECT, 339 | 'socks': PROXY_TYPE_SOCKS5, 340 | 'socks4': PROXY_TYPE_SOCKS4, 341 | 'socks4a': PROXY_TYPE_SOCKS4, 342 | 'socks5': PROXY_TYPE_SOCKS5, 343 | 'tor': PROXY_TYPE_TOR, 344 | } 345 | 346 | if HAVE_SSL: 347 | PROXY_DEFAULTS.update({ 348 | PROXY_TYPE_HTTPS: 443, 349 | PROXY_TYPE_HTTPS_CONNECT: 443, 350 | PROXY_TYPE_SSL: 443, 351 | PROXY_TYPE_SSL_WEAK: 443, 352 | PROXY_TYPE_SSL_ANON: 443, 353 | }) 354 | PROXY_TYPES.update({ 355 | 'https': PROXY_TYPE_HTTPS, 356 | 'httpcs': PROXY_TYPE_HTTPS_CONNECT, 357 | 'ssl': PROXY_TYPE_SSL, 358 | 'ssl-anon': PROXY_TYPE_SSL_ANON, 359 | 'ssl-weak': PROXY_TYPE_SSL_WEAK, 360 | }) 361 | 362 | P_TYPE = 0 363 | P_HOST = 1 364 | P_PORT = 2 365 | P_RDNS = 3 366 | P_USER = 4 367 | P_PASS = P_CACERTS = 5 368 | P_CERTS = 6 369 | 370 | DEFAULT_ROUTE = '*' 371 | _proxyroutes = { } 372 | _orgsocket = socket.socket 373 | _orgcreateconn = getattr(socket, 'create_connection', None) 374 | _thread_locals = threading.local() 375 | 376 | 377 | class ProxyError(Exception): pass 378 | class GeneralProxyError(ProxyError): pass 379 | class Socks5AuthError(ProxyError): pass 380 | class Socks5Error(ProxyError): pass 381 | class Socks4Error(ProxyError): pass 382 | class HTTPError(ProxyError): pass 383 | 384 | _generalerrors = ("success", 385 | "invalid data", 386 | "not connected", 387 | "not available", 388 | "bad proxy type", 389 | "bad input") 390 | 391 | _socks5errors = ("succeeded", 392 | "general SOCKS server failure", 393 | "connection not allowed by ruleset", 394 | "Network unreachable", 395 | "Host unreachable", 396 | "Connection refused", 397 | "TTL expired", 398 | "Command not supported", 399 | "Address type not supported", 400 | "Unknown error") 401 | 402 | _socks5autherrors = ("succeeded", 403 | "authentication is required", 404 | "all offered authentication methods were rejected", 405 | "unknown username or invalid password", 406 | "unknown error") 407 | 408 | _socks4errors = ("request granted", 409 | "request rejected or failed", 410 | "request rejected because SOCKS server cannot connect to identd on the client", 411 | "request rejected because the client program and identd report different user-ids", 412 | "unknown error") 413 | 414 | 415 | def parseproxy(arg): 416 | # This silly function will do a quick-and-dirty parse of our argument 417 | # into a proxy specification array. It lets people omit stuff. 418 | if '!' in arg: 419 | # Prefer ! to :, because it works with IPv6 addresses. 420 | args = arg.split('!') 421 | else: 422 | # This is a bit messier to accept common URL syntax 423 | if arg.endswith('/'): 424 | arg = arg[:-1] 425 | args = arg.replace('://', ':').replace('/:', ':').split(':') 426 | args[0] = PROXY_TYPES[args[0] or 'http'] 427 | 428 | if (len(args) in (3, 4, 5)) and ('@' in args[2]): 429 | # Re-order http://user:pass@host:port/ => http:host:port:user:pass 430 | pwd, host = args[2].split('@') 431 | user = args[1] 432 | args[1:3] = [host] 433 | if len(args) == 2: args.append(PROXY_DEFAULTS[args[0]]) 434 | if len(args) == 3: args.append(False) 435 | args.extend([user, pwd]) 436 | elif (len(args) in (2, 3, 4)) and ('@' in args[1]): 437 | user, host = args[1].split('@') 438 | args[1] = host 439 | if len(args) == 2: args.append(PROXY_DEFAULTS[args[0]]) 440 | if len(args) == 3: args.append(False) 441 | args.append(user) 442 | 443 | if len(args) == 2: args.append(PROXY_DEFAULTS[args[0]]) 444 | if len(args) > 2: args[2] = int(args[2]) 445 | 446 | if args[P_TYPE] in PROXY_SSL_TYPES: 447 | names = (args[P_HOST] or '').split(',') 448 | args[P_HOST] = names[0] 449 | while len(args) <= P_CERTS: 450 | args.append((len(args) == P_RDNS) and True or None) 451 | args[P_CERTS] = names 452 | 453 | return args 454 | 455 | def addproxy(dest, proxytype=None, addr=None, port=None, rdns=True, 456 | username=None, password=None, certnames=None): 457 | global _proxyroutes 458 | route = _proxyroutes.get(dest.lower(), None) 459 | proxy = (proxytype, addr, port, rdns, username, password, certnames) 460 | if route is None: 461 | route = _proxyroutes.get(DEFAULT_ROUTE, [])[:] 462 | route.append(proxy) 463 | _proxyroutes[dest.lower()] = route 464 | if DEBUG: DEBUG('Routes are: %s' % (_proxyroutes, )) 465 | 466 | def setproxy(dest, *args, **kwargs): 467 | global _proxyroutes 468 | dest = dest.lower() 469 | if args: 470 | _proxyroutes[dest] = [] 471 | return addproxy(dest, *args, **kwargs) 472 | else: 473 | if dest in _proxyroutes: 474 | del _proxyroutes[dest.lower()] 475 | 476 | def setdefaultcertfile(path): 477 | global TLS_CA_CERTS 478 | TLS_CA_CERTS = path 479 | 480 | def setdefaultproxy(*args, **kwargs): 481 | """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]]) 482 | Sets a default proxy which all further socksocket objects will use, 483 | unless explicitly changed. 484 | """ 485 | if args and args[P_TYPE] == PROXY_TYPE_DEFAULT: 486 | raise ValueError("Circular reference to default proxy.") 487 | return setproxy(DEFAULT_ROUTE, *args, **kwargs) 488 | 489 | def adddefaultproxy(*args, **kwargs): 490 | if args and args[P_TYPE] == PROXY_TYPE_DEFAULT: 491 | raise ValueError("Circular reference to default proxy.") 492 | return addproxy(DEFAULT_ROUTE, *args, **kwargs) 493 | 494 | def usesystemdefaults(): 495 | import os 496 | 497 | no_proxy = ['localhost', 'localhost.localdomain', '127.0.0.1'] 498 | no_proxy.extend(os.environ.get('NO_PROXY', 499 | os.environ.get('NO_PROXY', 500 | '')).split(',')) 501 | for host in no_proxy: 502 | setproxy(host, PROXY_TYPE_NONE) 503 | 504 | for var in ('ALL_PROXY', 'HTTPS_PROXY', 'http_proxy'): 505 | val = os.environ.get(var.lower(), os.environ.get(var, None)) 506 | if val: 507 | setdefaultproxy(*parseproxy(val)) 508 | os.environ[var] = '' 509 | return 510 | 511 | def sockcreateconn(*args, **kwargs): 512 | _thread_locals.create_conn = args[0] 513 | try: 514 | rv = _orgcreateconn(*args, **kwargs) 515 | return rv 516 | finally: 517 | del(_thread_locals.create_conn) 518 | 519 | class socksocket(socket.socket): 520 | """socksocket([family[, type[, proto]]]) -> socket object 521 | Open a SOCKS enabled socket. The parameters are the same as 522 | those of the standard socket init. In order for SOCKS to work, 523 | you must specify family=AF_INET, type=SOCK_STREAM and proto=0. 524 | """ 525 | 526 | def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, 527 | *args, **kwargs): 528 | self.__family = family 529 | self.__type = type 530 | self.__proto = proto 531 | self.__args = args 532 | self.__kwargs = kwargs 533 | self.__sock = _orgsocket(family, self.__type, self.__proto, 534 | *self.__args, **self.__kwargs) 535 | self.__proxy = None 536 | self.__proxysockname = None 537 | self.__proxypeername = None 538 | self.__makefile_refs = 0 539 | self.__buffer = '' 540 | self.__negotiating = False 541 | self.__override = ['addproxy', 'setproxy', 542 | 'getproxysockname', 'getproxypeername', 543 | 'close', 'connect', 'getpeername', 'makefile', 544 | 'recv', 'recv_into'] #, 'send', 'sendall'] 545 | 546 | def __getattribute__(self, name): 547 | if name.startswith('_socksocket__'): 548 | return object.__getattribute__(self, name) 549 | elif name in self.__override: 550 | return object.__getattribute__(self, name) 551 | else: 552 | return getattr(object.__getattribute__(self, "_socksocket__sock"), 553 | name) 554 | 555 | def __setattr__(self, name, value): 556 | if name.startswith('_socksocket__'): 557 | return object.__setattr__(self, name, value) 558 | else: 559 | return setattr(object.__getattribute__(self, "_socksocket__sock"), 560 | name, value) 561 | 562 | def __settimeout(self, timeout): 563 | try: 564 | self.__sock.settimeout(timeout) 565 | except: 566 | # Python 2.2 compatibility hacks. 567 | pass 568 | 569 | def __recvall(self, count): 570 | """__recvall(count) -> data 571 | Receive EXACTLY the number of bytes requested from the socket. 572 | Blocks until the required number of bytes have been received or a 573 | timeout occurs. 574 | """ 575 | self.__sock.setblocking(1) 576 | self.__settimeout(DEFAULT_TIMEOUT) 577 | 578 | data = self.recv(count) 579 | while len(data) < count: 580 | d = self.recv(count-len(data)) 581 | if d == '': 582 | raise GeneralProxyError((0, "connection closed unexpectedly")) 583 | data = data + d 584 | return data 585 | 586 | def close(self): 587 | if self.__makefile_refs < 1: 588 | self.__sock.close() 589 | else: 590 | self.__makefile_refs -= 1 591 | 592 | def makefile(self, mode='r', bufsize=-1): 593 | self.__makefile_refs += 1 594 | if PY2: 595 | return socket._fileobject(self, mode, bufsize, close=True) 596 | else: 597 | return socket.SocketIO(self, mode) 598 | 599 | def addproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None, certnames=None): 600 | """setproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]]) 601 | Sets the proxy to be used. 602 | proxytype - The type of the proxy to be used. Three types 603 | are supported: PROXY_TYPE_SOCKS4 (including socks4a), 604 | PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP 605 | addr - The address of the server (IP or DNS). 606 | port - The port of the server. Defaults to 1080 for SOCKS 607 | servers and 8080 for HTTP proxy servers. 608 | rdns - Should DNS queries be preformed on the remote side 609 | (rather than the local side). The default is True. 610 | Note: This has no effect with SOCKS4 servers. 611 | username - Username to authenticate with to the server. 612 | The default is no authentication. 613 | password - Password to authenticate with to the server. 614 | Only relevant when username is also provided. 615 | """ 616 | proxy = (proxytype, addr, port, rdns, username, password, certnames) 617 | if not self.__proxy: self.__proxy = [] 618 | self.__proxy.append(proxy) 619 | 620 | def setproxy(self, *args, **kwargs): 621 | """setproxy(proxytype, addr[, port[, rdns[, username[, password[, certnames]]]]]) 622 | (see addproxy) 623 | """ 624 | self.__proxy = [] 625 | self.addproxy(*args, **kwargs) 626 | 627 | def __negotiatesocks5(self, destaddr, destport, proxy): 628 | """__negotiatesocks5(self, destaddr, destport, proxy) 629 | Negotiates a connection through a SOCKS5 server. 630 | """ 631 | # First we'll send the authentication packages we support. 632 | if (proxy[P_USER]!=None) and (proxy[P_PASS]!=None): 633 | # The username/password details were supplied to the 634 | # setproxy method so we support the USERNAME/PASSWORD 635 | # authentication (in addition to the standard none). 636 | self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) 637 | else: 638 | # No username/password were entered, therefore we 639 | # only support connections with no authentication. 640 | self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) 641 | # We'll receive the server's response to determine which 642 | # method was selected 643 | chosenauth = self.__recvall(2) 644 | if chosenauth[0:1] != chr(0x05).encode(): 645 | self.close() 646 | raise GeneralProxyError((1, _generalerrors[1])) 647 | # Check the chosen authentication method 648 | if chosenauth[1:2] == chr(0x00).encode(): 649 | # No authentication is required 650 | pass 651 | elif chosenauth[1:2] == chr(0x02).encode(): 652 | # Okay, we need to perform a basic username/password 653 | # authentication. 654 | self.sendall(chr(0x01).encode() + 655 | chr(len(proxy[P_USER])).encode() + proxy[P_USER].encode() + 656 | chr(len(proxy[P_PASS])).encode() + proxy[P_PASS].encode()) 657 | authstat = self.__recvall(2) 658 | if authstat[0:1] != chr(0x01).encode(): 659 | # Bad response 660 | self.close() 661 | raise GeneralProxyError((1, _generalerrors[1])) 662 | if authstat[1:2] != chr(0x00).encode(): 663 | # Authentication failed 664 | self.close() 665 | raise Socks5AuthError((3, _socks5autherrors[3])) 666 | # Authentication succeeded 667 | else: 668 | # Reaching here is always bad 669 | self.close() 670 | if chosenauth[1] == chr(0xFF).encode(): 671 | raise Socks5AuthError((2, _socks5autherrors[2])) 672 | else: 673 | raise GeneralProxyError((1, _generalerrors[1])) 674 | # Now we can request the actual connection 675 | req = struct.pack('BBB', 0x05, 0x01, 0x00) 676 | # If the given destination address is an IP address, we'll 677 | # use the IPv4 address request even if remote resolving was specified. 678 | try: 679 | ipaddr = socket.inet_aton(destaddr) 680 | req = req + chr(0x01).encode() + ipaddr 681 | except socket.error: 682 | # Well it's not an IP number, so it's probably a DNS name. 683 | if proxy[P_RDNS]: 684 | # Resolve remotely 685 | ipaddr = None 686 | req = req + (chr(0x03).encode() + 687 | chr(len(destaddr)).encode() + b(destaddr)) 688 | else: 689 | # Resolve locally 690 | ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) 691 | req = req + chr(0x01).encode() + ipaddr 692 | req = req + struct.pack(">H", destport) 693 | self.sendall(req) 694 | # Get the response 695 | resp = self.__recvall(4) 696 | if resp[0:1] != chr(0x05).encode(): 697 | self.close() 698 | raise GeneralProxyError((1, _generalerrors[1])) 699 | elif resp[1:2] != chr(0x00).encode(): 700 | # Connection failed 701 | self.close() 702 | if ord(resp[1:2])<=8: 703 | raise Socks5Error((ord(resp[1:2]), 704 | _socks5errors[ord(resp[1:2])])) 705 | else: 706 | raise Socks5Error((9, _socks5errors[9])) 707 | # Get the bound address/port 708 | elif resp[3:4] == chr(0x01).encode(): 709 | boundaddr = self.__recvall(4) 710 | elif resp[3:4] == chr(0x03).encode(): 711 | resp = resp + self.recv(1) 712 | boundaddr = self.__recvall(ord(resp[4:5])) 713 | else: 714 | self.close() 715 | raise GeneralProxyError((1,_generalerrors[1])) 716 | boundport = struct.unpack(">H", self.__recvall(2))[0] 717 | self.__proxysockname = (boundaddr, boundport) 718 | if ipaddr != None: 719 | self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) 720 | else: 721 | self.__proxypeername = (destaddr, destport) 722 | 723 | def getproxysockname(self): 724 | """getsockname() -> address info 725 | Returns the bound IP address and port number at the proxy. 726 | """ 727 | return self.__proxysockname 728 | 729 | def getproxypeername(self): 730 | """getproxypeername() -> address info 731 | Returns the IP and port number of the proxy. 732 | """ 733 | return _orgsocket.getpeername(self) 734 | 735 | def getpeername(self): 736 | """getpeername() -> address info 737 | Returns the IP address and port number of the destination 738 | machine (note: getproxypeername returns the proxy) 739 | """ 740 | return self.__proxypeername 741 | 742 | def __negotiatesocks4(self, destaddr, destport, proxy): 743 | """__negotiatesocks4(self, destaddr, destport, proxy) 744 | Negotiates a connection through a SOCKS4 server. 745 | """ 746 | # Check if the destination address provided is an IP address 747 | rmtrslv = False 748 | try: 749 | ipaddr = socket.inet_aton(destaddr) 750 | except socket.error: 751 | # It's a DNS name. Check where it should be resolved. 752 | if proxy[P_RDNS]: 753 | ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) 754 | rmtrslv = True 755 | else: 756 | ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) 757 | # Construct the request packet 758 | req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr 759 | # The username parameter is considered userid for SOCKS4 760 | if proxy[P_USER] != None: 761 | req = req + proxy[P_USER] 762 | req = req + chr(0x00).encode() 763 | # DNS name if remote resolving is required 764 | # NOTE: This is actually an extension to the SOCKS4 protocol 765 | # called SOCKS4A and may not be supported in all cases. 766 | if rmtrslv: 767 | req = req + destaddr + chr(0x00).encode() 768 | self.sendall(req) 769 | # Get the response from the server 770 | resp = self.__recvall(8) 771 | if resp[0:1] != chr(0x00).encode(): 772 | # Bad data 773 | self.close() 774 | raise GeneralProxyError((1,_generalerrors[1])) 775 | if resp[1:2] != chr(0x5A).encode(): 776 | # Server returned an error 777 | self.close() 778 | if ord(resp[1:2]) in (91, 92, 93): 779 | self.close() 780 | raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) 781 | else: 782 | raise Socks4Error((94, _socks4errors[4])) 783 | # Get the bound address/port 784 | self.__proxysockname = (socket.inet_ntoa(resp[4:]), 785 | struct.unpack(">H", resp[2:4])[0]) 786 | if rmtrslv != None: 787 | self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) 788 | else: 789 | self.__proxypeername = (destaddr, destport) 790 | 791 | def __getproxyauthheader(self, proxy): 792 | if proxy[P_USER] and proxy[P_PASS]: 793 | auth = proxy[P_USER] + ":" + proxy[P_PASS] 794 | return "Proxy-Authorization: Basic %s\r\n" % base64.b64encode(auth) 795 | else: 796 | return "" 797 | 798 | def __stop_http_negotiation(self): 799 | buf = self.__buffer 800 | host, port, proxy = self.__negotiating 801 | self.__buffer = self.__negotiating = None 802 | self.__override.remove('send') 803 | self.__override.remove('sendall') 804 | return (buf, host, port, proxy) 805 | 806 | def recv(self, count, flags=0): 807 | if self.__negotiating: 808 | # If the calling code tries to read before negotiating is done, 809 | # assume this is not HTTP, bail and attempt HTTP CONNECT. 810 | if DEBUG: DEBUG("*** Not HTTP, failing back to HTTP CONNECT.") 811 | buf, host, port, proxy = self.__stop_http_negotiation() 812 | self.__negotiatehttpconnect(host, port, proxy) 813 | self.__sock.sendall(buf) 814 | while True: 815 | try: 816 | return self.__sock.recv(count, flags) 817 | except SSL.SysCallError: 818 | return '' 819 | except SSL.WantReadError: 820 | pass 821 | 822 | def recv_into(self, buf, nbytes=0, flags=0): 823 | if self.__negotiating: 824 | # If the calling code tries to read before negotiating is done, 825 | # assume this is not HTTP, bail and attempt HTTP CONNECT. 826 | if DEBUG: DEBUG("*** Not HTTP, failing back to HTTP CONNECT.") 827 | buf, host, port, proxy = self.__stop_http_negotiation() 828 | self.__negotiatehttpconnect(host, port, proxy) 829 | self.__sock.sendall(buf) 830 | while True: 831 | try: 832 | return self.__sock.recv_into(buf, nbytes, flags) 833 | except SSL.SysCallError: 834 | return 0 835 | except SSL.WantReadError: 836 | pass 837 | 838 | def send(self, *args, **kwargs): 839 | if self.__negotiating: 840 | self.__buffer += args[0] 841 | self.__negotiatehttpproxy() 842 | else: 843 | return self.__sock.send(*args, **kwargs) 844 | 845 | def sendall(self, *args, **kwargs): 846 | if self.__negotiating: 847 | self.__buffer += args[0] 848 | self.__negotiatehttpproxy() 849 | else: 850 | return self.__sock.sendall(*args, **kwargs) 851 | 852 | def __negotiatehttp(self, destaddr, destport, proxy): 853 | """__negotiatehttpproxy(self, destaddr, destport, proxy) 854 | Negotiates a connection through an HTTP proxy server. 855 | """ 856 | if destport in (21, 22, 23, 25, 109, 110, 143, 220, 443, 993, 995): 857 | # Go straight to HTTP CONNECT for anything related to e-mail, 858 | # SSH, telnet, FTP, SSL, ... 859 | self.__negotiatehttpconnect(destaddr, destport, proxy) 860 | else: 861 | if DEBUG: DEBUG('*** Transparent HTTP proxy mode...') 862 | self.__negotiating = (destaddr, destport, proxy) 863 | self.__override.extend(['send', 'sendall']) 864 | 865 | def __negotiatehttpproxy(self): 866 | """__negotiatehttp(self, destaddr, destport, proxy) 867 | Negotiates an HTTP request through an HTTP proxy server. 868 | """ 869 | buf = self.__buffer 870 | host, port, proxy = self.__negotiating 871 | 872 | # If our buffer is tiny, wait for data. 873 | if len(buf) <= 3: return 874 | 875 | # If not HTTP, fall back to HTTP CONNECT. 876 | if buf[0:3].lower() not in ('get', 'pos', 'hea', 877 | 'put', 'del', 'opt', 'pro'): 878 | if DEBUG: DEBUG("*** Not HTTP, failing back to HTTP CONNECT.") 879 | self.__stop_http_negotiation() 880 | self.__negotiatehttpconnect(host, port, proxy) 881 | self.__sock.sendall(buf) 882 | return 883 | 884 | # Have we got the end of the headers? 885 | if buf.find('\r\n\r\n'.encode()) != -1: 886 | CRLF = '\r\n' 887 | elif buf.find('\n\n'.encode()) != -1: 888 | CRLF = '\n' 889 | else: 890 | # Nope 891 | return 892 | 893 | # Remove our send/sendall hooks. 894 | self.__stop_http_negotiation() 895 | 896 | # Format the proxy request. 897 | host += ':%d' % port 898 | headers = buf.split(CRLF) 899 | for hdr in headers: 900 | if hdr.lower().startswith('host: '): host = hdr[6:] 901 | req = headers[0].split(' ', 1) 902 | headers[0] = '%s http://%s%s' % (req[0], host, req[1]) 903 | headers[1] = self.__getproxyauthheader(proxy) + headers[1] 904 | 905 | # Send it! 906 | if DEBUG: DEBUG("*** Proxy request:\n%s***" % CRLF.join(headers)) 907 | self.__sock.sendall(CRLF.join(headers).encode()) 908 | 909 | def __negotiatehttpconnect(self, destaddr, destport, proxy): 910 | """__negotiatehttp(self, destaddr, destport, proxy) 911 | Negotiates an HTTP CONNECT through an HTTP proxy server. 912 | """ 913 | # If we need to resolve locally, we do this now 914 | if not proxy[P_RDNS]: 915 | addr = socket.gethostbyname(destaddr) 916 | else: 917 | addr = destaddr 918 | self.__sock.sendall(("CONNECT " 919 | + addr + ":" + str(destport) + " HTTP/1.1\r\n" 920 | + self.__getproxyauthheader(proxy) 921 | + "Host: " + destaddr + "\r\n\r\n" 922 | ).encode()) 923 | # We read the response until we get "\r\n\r\n" or "\n\n" 924 | resp = self.__recvall(1) 925 | while (resp.find("\r\n\r\n".encode()) == -1 and 926 | resp.find("\n\n".encode()) == -1): 927 | resp = resp + self.__recvall(1) 928 | # We just need the first line to check if the connection 929 | # was successful 930 | statusline = resp.splitlines()[0].split(" ".encode(), 2) 931 | if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): 932 | self.close() 933 | raise GeneralProxyError((1, _generalerrors[1])) 934 | try: 935 | statuscode = int(statusline[1]) 936 | except ValueError: 937 | self.close() 938 | raise GeneralProxyError((1, _generalerrors[1])) 939 | if statuscode != 200: 940 | self.close() 941 | raise HTTPError((statuscode, statusline[2])) 942 | self.__proxysockname = ("0.0.0.0", 0) 943 | self.__proxypeername = (addr, destport) 944 | 945 | def __get_ca_certs(self): 946 | return TLS_CA_CERTS 947 | 948 | def __negotiatessl(self, destaddr, destport, proxy, 949 | weak=False, anonymous=False): 950 | """__negotiatessl(self, destaddr, destport, proxy) 951 | Negotiates an SSL session. 952 | """ 953 | want_hosts = ca_certs = self_cert = None 954 | if not weak and not anonymous: 955 | # This is normal, secure mode. 956 | self_cert = proxy[P_USER] or None 957 | ca_certs = proxy[P_CACERTS] or self.__get_ca_certs() or None 958 | want_hosts = proxy[P_CERTS] or [proxy[P_HOST]] 959 | 960 | try: 961 | ctx = MakeBestEffortSSLContext(weak=weak, anonymous=anonymous) 962 | if self_cert: 963 | ctx.use_certificate_chain_file(self_cert) 964 | ctx.use_privatekey_file(self_cert) 965 | if ca_certs and want_hosts: 966 | ctx.load_verify_locations(ca_certs) 967 | 968 | self.__sock.setblocking(1) 969 | self.__sock = SSL_Connect(ctx, self.__sock, 970 | connected=True, verify_names=want_hosts) 971 | except: 972 | if DEBUG: DEBUG('*** SSL problem: %s/%s/%s' % (sys.exc_info(), 973 | self.__sock, 974 | want_hosts)) 975 | raise 976 | 977 | self.__encrypted = True 978 | if DEBUG: DEBUG('*** Wrapped %s:%s in %s' % (destaddr, destport, 979 | self.__sock)) 980 | 981 | def __default_route(self, dest): 982 | route = _proxyroutes.get(str(dest).lower(), [])[:] 983 | if not route or route[0][P_TYPE] == PROXY_TYPE_DEFAULT: 984 | route[0:1] = _proxyroutes.get(DEFAULT_ROUTE, []) 985 | while route and route[0][P_TYPE] == PROXY_TYPE_DEFAULT: 986 | route.pop(0) 987 | return route 988 | 989 | def __do_connect(self, addrspec): 990 | if ':' in addrspec[0]: 991 | self.__sock = _orgsocket(socket.AF_INET6, self.__type, self.__proto, 992 | *self.__args, **self.__kwargs) 993 | self.__settimeout(DEFAULT_TIMEOUT) 994 | return self.__sock.connect(addrspec) 995 | else: 996 | try: 997 | self.__sock = _orgsocket(socket.AF_INET, self.__type, self.__proto, 998 | *self.__args, **self.__kwargs) 999 | self.__settimeout(DEFAULT_TIMEOUT) 1000 | return self.__sock.connect(addrspec) 1001 | except socket.gaierror: 1002 | self.__sock = _orgsocket(socket.AF_INET6, self.__type, self.__proto, 1003 | *self.__args, **self.__kwargs) 1004 | self.__settimeout(DEFAULT_TIMEOUT) 1005 | return self.__sock.connect(addrspec) 1006 | 1007 | def connect(self, destpair): 1008 | """connect(self, despair) 1009 | Connects to the specified destination through a chain of proxies. 1010 | destpar - A tuple of the IP/DNS address and the port number. 1011 | (identical to socket's connect). 1012 | To select the proxy servers use setproxy() and chainproxy(). 1013 | """ 1014 | if DEBUG: DEBUG('*** Connect: %s / %s' % (destpair, self.__proxy)) 1015 | destpair = getattr(_thread_locals, 'create_conn', destpair) 1016 | 1017 | # Do a minimal input check first 1018 | if ((not type(destpair) in (list, tuple)) or 1019 | (len(destpair) < 2) or (type(destpair[0]) != type('')) or 1020 | (type(destpair[1]) != int)): 1021 | raise GeneralProxyError((5, _generalerrors[5])) 1022 | 1023 | if self.__proxy: 1024 | proxy_chain = self.__proxy 1025 | default_dest = destpair[0] 1026 | else: 1027 | proxy_chain = self.__default_route(destpair[0]) 1028 | default_dest = DEFAULT_ROUTE 1029 | 1030 | for proxy in proxy_chain: 1031 | if (proxy[P_TYPE] or PROXY_TYPE_NONE) not in PROXY_DEFAULTS: 1032 | raise GeneralProxyError((4, _generalerrors[4])) 1033 | 1034 | chain = proxy_chain[:] 1035 | chain.append([PROXY_TYPE_NONE, destpair[0], destpair[1]]) 1036 | if DEBUG: DEBUG('*** Chain: %s' % (chain, )) 1037 | 1038 | first = True 1039 | result = None 1040 | while chain: 1041 | proxy = chain.pop(0) 1042 | 1043 | if proxy[P_TYPE] == PROXY_TYPE_DEFAULT: 1044 | chain[0:0] = self.__default_route(default_dest) 1045 | if DEBUG: DEBUG('*** Chain: %s' % chain) 1046 | continue 1047 | 1048 | if proxy[P_PORT] != None: 1049 | portnum = proxy[P_PORT] 1050 | else: 1051 | portnum = PROXY_DEFAULTS[proxy[P_TYPE] or PROXY_TYPE_NONE] 1052 | 1053 | if first and proxy[P_HOST]: 1054 | if DEBUG: DEBUG('*** Connect: %s:%s' % (proxy[P_HOST], portnum)) 1055 | result = self.__do_connect((proxy[P_HOST], portnum)) 1056 | 1057 | if chain: 1058 | nexthop = (chain[0][P_HOST] or '', int(chain[0][P_PORT] or 0)) 1059 | 1060 | if proxy[P_TYPE] in PROXY_SSL_TYPES: 1061 | if DEBUG: DEBUG('*** TLS/SSL Setup: %s' % (nexthop, )) 1062 | self.__negotiatessl(nexthop[0], nexthop[1], proxy, 1063 | weak=(proxy[P_TYPE] == PROXY_TYPE_SSL_WEAK), 1064 | anonymous=(proxy[P_TYPE] == PROXY_TYPE_SSL_ANON)) 1065 | 1066 | if proxy[P_TYPE] in PROXY_HTTPC_TYPES: 1067 | if DEBUG: DEBUG('*** HTTP CONNECT: %s' % (nexthop, )) 1068 | self.__negotiatehttpconnect(nexthop[0], nexthop[1], proxy) 1069 | 1070 | elif proxy[P_TYPE] in PROXY_HTTP_TYPES: 1071 | if len(chain) > 1: 1072 | # Chaining requires HTTP CONNECT. 1073 | if DEBUG: DEBUG('*** HTTP CONNECT: %s' % (nexthop, )) 1074 | self.__negotiatehttpconnect(nexthop[0], nexthop[1], 1075 | proxy) 1076 | else: 1077 | # If we are last in the chain, do transparent magic. 1078 | if DEBUG: DEBUG('*** HTTP PROXY: %s' % (nexthop, )) 1079 | self.__negotiatehttp(nexthop[0], nexthop[1], proxy) 1080 | 1081 | if proxy[P_TYPE] in PROXY_SOCKS5_TYPES: 1082 | if DEBUG: DEBUG('*** SOCKS5: %s' % (nexthop, )) 1083 | self.__negotiatesocks5(nexthop[0], nexthop[1], proxy) 1084 | 1085 | elif proxy[P_TYPE] == PROXY_TYPE_SOCKS4: 1086 | if DEBUG: DEBUG('*** SOCKS4: %s' % (nexthop, )) 1087 | self.__negotiatesocks4(nexthop[0], nexthop[1], proxy) 1088 | 1089 | elif proxy[P_TYPE] == PROXY_TYPE_NONE: 1090 | if first and nexthop[0] and nexthop[1]: 1091 | if DEBUG: DEBUG('*** Connect: %s:%s' % nexthop) 1092 | result = self.__do_connect(nexthop) 1093 | else: 1094 | raise GeneralProxyError((4, _generalerrors[4])) 1095 | 1096 | first = False 1097 | 1098 | if DEBUG: DEBUG('*** Connected! (%s)' % result) 1099 | return result 1100 | 1101 | def wrapmodule(module): 1102 | """wrapmodule(module) 1103 | Attempts to replace a module's socket library with a SOCKS socket. 1104 | This will only work on modules that import socket directly into the 1105 | namespace; most of the Python Standard Library falls into this category. 1106 | """ 1107 | if not hasattr(module, 'socket'): 1108 | module.socket = socket 1109 | module.socket.socket = socksocket 1110 | module.socket.create_connection = sockcreateconn 1111 | if DEBUG: DEBUG('Wrapped: %s' % module.__name__) 1112 | 1113 | 1114 | ## Netcat-like proxy-chaining tools follow ## 1115 | 1116 | def netcat(s, i, o, keep_open=''): 1117 | if hasattr(o, 'buffer'): o = o.buffer 1118 | try: 1119 | in_fileno = i.fileno() 1120 | isel = [s, i] 1121 | obuf, sbuf, oselo, osels = [], [], [], [] 1122 | while isel: 1123 | in_r, out_r, err_r = select.select(isel, oselo+osels, isel, 1000) 1124 | 1125 | # print 'In:%s Out:%s Err:%s' % (in_r, out_r, err_r) 1126 | if s in in_r: 1127 | obuf.append(s.recv(4096)) 1128 | oselo = [o] 1129 | if len(obuf[-1]) == 0: 1130 | if DEBUG: DEBUG('EOF(s, in)') 1131 | isel.remove(s) 1132 | 1133 | if o in out_r: 1134 | o.write(obuf[0]) 1135 | if len(obuf) == 1: 1136 | if len(obuf[0]) == 0: 1137 | if DEBUG: DEBUG('CLOSE(o)') 1138 | o.close() 1139 | if i in isel and 'i' not in keep_open: 1140 | isel.remove(i) 1141 | i.close() 1142 | else: 1143 | o.flush() 1144 | obuf, oselo = [], [] 1145 | else: 1146 | obuf.pop(0) 1147 | 1148 | if i in in_r: 1149 | sbuf.append(os.read(in_fileno, 4096)) 1150 | osels = [s] 1151 | if len(sbuf[-1]) == 0: 1152 | if DEBUG: DEBUG('EOF(i)') 1153 | isel.remove(i) 1154 | 1155 | if s in out_r: 1156 | s.send(sbuf[0]) 1157 | if len(sbuf) == 1: 1158 | if len(sbuf[0]) == 0: 1159 | if s in isel and 's' not in keep_open: 1160 | if DEBUG: DEBUG('CLOSE(s)') 1161 | isel.remove(s) 1162 | s.close() 1163 | else: 1164 | if DEBUG: DEBUG('SHUTDOWN(s, WR)') 1165 | s.shutdown(socket.SHUT_WR) 1166 | sbuf, osels = [], [] 1167 | else: 1168 | sbuf.pop(0) 1169 | 1170 | for data in sbuf: s.sendall(data) 1171 | for data in obuf: o.write(data) 1172 | 1173 | except: 1174 | if DEBUG: DEBUG("Disconnected: %s" % (sys.exc_info(), )) 1175 | 1176 | i.close() 1177 | s.close() 1178 | o.close() 1179 | 1180 | def __proxy_connect_netcat(hostname, port, chain, keep_open): 1181 | try: 1182 | s = socksocket(socket.AF_INET, socket.SOCK_STREAM) 1183 | for proxy in chain: 1184 | s.addproxy(*proxy) 1185 | s.connect((hostname, port)) 1186 | except: 1187 | sys.stderr.write('Error: %s\n' % (sys.exc_info(), )) 1188 | return False 1189 | netcat(s, sys.stdin, sys.stdout, keep_open) 1190 | return True 1191 | 1192 | def __make_proxy_chain(args): 1193 | chain = [] 1194 | for arg in args: 1195 | chain.append(parseproxy(arg)) 1196 | return chain 1197 | 1198 | def DebugPrint(text): 1199 | print(text) 1200 | 1201 | def Main(): 1202 | keep_open = 's' 1203 | try: 1204 | args = sys.argv[1:] 1205 | if '--wait' in args: 1206 | keep_open = 'si' 1207 | args.remove('--wait') 1208 | if '--nowait' in args: 1209 | keep_open = '' 1210 | args.remove('--nowait') 1211 | if '--debug' in args: 1212 | global DEBUG 1213 | DEBUG = DebugPrint 1214 | args.remove('--debug') 1215 | for arg in ('--nopyopenssl', '--nossl'): 1216 | while arg in args: 1217 | args.remove(arg) 1218 | 1219 | usesystemdefaults() 1220 | 1221 | dest_host, dest_port = args.pop().split(':', 1) 1222 | dest_port = int(dest_port) 1223 | chain = __make_proxy_chain(args) 1224 | except: 1225 | DebugPrint('Error: %s' % (sys.exc_info(), )) 1226 | sys.stderr.write(('Usage: python -m sockschain ' 1227 | '[ [ ...]] ' 1228 | '\n')) 1229 | sys.exit(1) 1230 | 1231 | try: 1232 | if not __proxy_connect_netcat(dest_host, dest_port, chain, keep_open): 1233 | sys.exit(2) 1234 | except KeyboardInterrupt: 1235 | sys.exit(0) 1236 | 1237 | if __name__ == "__main__": 1238 | Main() 1239 | 1240 | --------------------------------------------------------------------------------