diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 4a2ac5cd4..39d3c3d6b 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -11,7 +11,7 @@ import threading import OpenSSL.crypto -from netlib import tcp, http, http2, certutils, websockets +from netlib import tcp, http, http2, certutils, websockets, socks import language.http import language.websockets @@ -254,6 +254,39 @@ class Pathoc(tcp.TCPClient): ) http.read_headers(self.rfile) + def socks_connect(self, connect_to): + try: + client_greet = socks.ClientGreeting(socks.VERSION.SOCKS5, [socks.METHOD.NO_AUTHENTICATION_REQUIRED]) + client_greet.to_file(self.wfile) + self.wfile.flush() + + server_greet = socks.ServerGreeting.from_file(self.rfile) + server_greet.assert_socks5() + if server_greet.method != socks.METHOD.NO_AUTHENTICATION_REQUIRED: + raise socks.SocksError( + socks.METHOD.NO_ACCEPTABLE_METHODS, + "pathoc only supports SOCKS without authentication" + ) + + connect_request = socks.Message( + socks.VERSION.SOCKS5, + socks.CMD.CONNECT, + socks.ATYP.DOMAINNAME, + tcp.Address.wrap(connect_to) + ) + connect_request.to_file(self.wfile) + self.wfile.flush() + + connect_reply = socks.Message.from_file(self.rfile) + connect_reply.assert_socks5() + if connect_reply.msg != socks.REP.SUCCEEDED: + raise socks.SocksError( + connect_reply.msg, + "SOCKS server error" + ) + except (socks.SocksError, tcp.NetLibDisconnect) as e: + raise PathocError(str(e)) + def connect(self, connect_to=None, showssl=False, fp=sys.stdout): """ connect_to: A (host, port) tuple, which will be connected to with diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 6b037bcc2..fb8d348ac 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -4,7 +4,7 @@ import re import OpenSSL from mock import Mock -from netlib import tcp, http, http2 +from netlib import tcp, http, http2, socks from libpathod import pathoc, test, version, pathod, language import tutils @@ -230,6 +230,29 @@ class TestDaemon(_TestDaemon): ) c.http_connect(to) + def test_socks_connect(self): + to = ("foobar", 80) + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.rfile, c.wfile = tutils.treader(""), cStringIO.StringIO() + tutils.raises(pathoc.PathocError, c.socks_connect, to) + + c.rfile = tutils.treader( + "\x05\xEE" + ) + tutils.raises("SOCKS without authentication", c.socks_connect, ("example.com", 0xDEAD)) + + c.rfile = tutils.treader( + "\x05\x00" + + "\x05\xEE\x00\x03\x0bexample.com\xDE\xAD" + ) + tutils.raises("SOCKS server error", c.socks_connect, ("example.com", 0xDEAD)) + + c.rfile = tutils.treader( + "\x05\x00" + + "\x05\x00\x00\x03\x0bexample.com\xDE\xAD" + ) + c.socks_connect(("example.com", 0xDEAD)) + class TestDaemonHTTP2(_TestDaemon): ssl = True diff --git a/test/tutils.py b/test/tutils.py index 9d9b687d2..f7fcc3121 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -5,8 +5,16 @@ import shutil import cStringIO from contextlib import contextmanager from libpathod import utils, test, pathoc, pathod, language +from netlib import tcp import requests +def treader(bytes): + """ + Construct a tcp.Read object from bytes. + """ + fp = cStringIO.StringIO(bytes) + return tcp.Reader(fp) + class DaemonTests(object): noweb = False