1# Copyright (c) 2013 The Chromium OS Authors and the python-socks5 authors.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License version 3,
5# as published by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15import subprocess
16import test
17
18# Taken and hacked from https://code.google.com/p/python-socks5/
19
20import socket
21from threading import Thread
22
23SOCKTIMEOUT=5
24RESENDTIMEOUT=300
25
26class Forwarder(Thread):
27    def __init__(self,src,dest):
28        Thread.__init__(self)
29        self.src=src
30        self.dest=dest
31
32    def __str__(self):
33        return '<Forwarder from %s to %s>' % (self.src, self.dest)
34
35    def run(self):
36        print '%s: starting' % self
37        try:
38            self.forward()
39        except socket.error as e:
40            print '%s: exception %s' % (self, e)
41            self.src.close()
42            self.dest.close()
43        finally:
44            print '%s: exiting' % self
45
46    def forward(self):
47        BUFSIZE = 1024
48        data = self.src.recv(BUFSIZE)
49        while data:
50            self.dest.sendall(data)
51            data = self.src.recv(BUFSIZE)
52        self.src.close()
53        self.dest.close()
54        print '%s: client quit normally' % self
55
56class ProxyForwarder(Forwarder):
57    def __init__(self, src, dest_addr):
58        Forwarder.__init__(self, src, None)
59        self.dest_addr = dest_addr
60        self.src = src
61        self.dest = None
62
63    def __str__(self):
64        return '<ProxyForwarder between %s and %s (%s:%d)' % (
65            self.src, self.dest, self.dest_addr[0], self.dest_addr[1])
66
67    def forward(self):
68        self.dest = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
69        self.dest.connect(self.dest_addr)
70        self.src.settimeout(RESENDTIMEOUT)
71        self.dest.settimeout(RESENDTIMEOUT)
72        Forwarder(self.src,self.dest).start()
73        Forwarder(self.dest,self.src).start()
74
75def recvbytes(sock, n):
76    bs = sock.recv(n)
77    return [ ord(x) for x in bs ]
78
79def recvshort(sock):
80    x = recvbytes(sock, 2)
81    return x[0] * 256 + x[1]
82
83def create_server(ip,port):
84    SOCKS5_VER = "\x05"
85    AUTH_NONE = "\x00"
86
87    ATYP_DOMAIN = 0x03
88
89    CMD_CONNECT = 0x01
90
91    ERR_SUCCESS = "\x00"
92    ERR_UNSUPP = "\x07"
93
94    transformer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
95    transformer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
96    transformer.bind((ip, port))
97    transformer.listen(1000)
98
99    network_port = chr(port >> 8) + chr(port & 0xff)
100    # Turn the textual IP address we were supplied with into a
101    # network-byte-order IP address for SOCKS5 wire protocol
102    network_ip = "".join(chr(int(i)) for i in ip.split("."))
103    while True:
104        sock = transformer.accept()[0]
105        sock.settimeout(SOCKTIMEOUT)
106        print "Got one client connection"
107        (_, nmethods) = recvbytes(sock, 2)
108        _ = recvbytes(sock, nmethods)
109        sock.sendall(SOCKS5_VER + AUTH_NONE)
110        (_, cmd, _, atyp) = recvbytes(sock, 4)
111        dst_addr = None
112        dst_port = None
113        if atyp == ATYP_DOMAIN:
114            addr_len = recvbytes(sock, 1)[0]
115            dst_addr = "".join([unichr(x) for x in recvbytes(sock, addr_len)])
116            dst_port = recvshort(sock)
117        else:
118            socket.sendall(SOCKS5_VER + ERR_UNSUPP + network_ip + network_port)
119        print "Proxying to %s:%d" %(dst_addr,dst_port)
120
121        if cmd == CMD_CONNECT:
122            sock.sendall(SOCKS5_VER + ERR_SUCCESS + "\x00" + "\x01" +
123                         network_ip + network_port)
124            print "Starting forwarding thread"
125            ProxyForwarder(sock, (dst_addr, dst_port)).start()
126        else:
127            sock.sendall(SOCKS5_VER + ERR_UNSUPP + network_ip + network_port)
128            sock.close()
129
130class ServingThread(Thread):
131	def __init__(self, ip, port):
132		Thread.__init__(self)
133		self.ip = ip
134		self.port = port
135
136	def run(self):
137		create_server(self.ip, self.port)
138
139class platform_TLSDateActual(test.test):
140    version = 1
141
142    def tlsdate(self, host, proxy):
143        args = ['/usr/bin/tlsdate', '-v', '-l', '-H', host]
144        if proxy:
145            args += ['-x', proxy]
146        p = subprocess.Popen(args, stderr=subprocess.PIPE)
147        out = p.communicate()[1]
148        print out
149        return p.returncode
150
151    def run_once(self):
152        t = ServingThread("127.0.0.1", 8083)
153        t.start()
154        r = self.tlsdate('clients3.google.com', None)
155        if r != 0:
156            raise error.TestFail('tlsdate with no proxy to good host failed: %d' % r)
157        r = self.tlsdate('clients3.google.com', 'socks5://127.0.0.1:8083')
158        if r != 0:
159            raise error.TestFail('tlsdate with proxy to good host failed: %d' % r)
160        r = self.tlsdate('invalid-host.example.com', None)
161        if r == 0:
162            raise error.TestFail('tlsdate with no proxy to bad host succeeded')
163        r = self.tlsdate('invalid-host.example.com', 'socks5://127.0.0.1:8083')
164        if r == 0:
165            raise error.TestFail('tlsdate with proxy to bad host succeeded')
166