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
23from autotest_lib.client.common_lib import error
24
25SOCKTIMEOUT=5
26RESENDTIMEOUT=300
27
28class Forwarder(Thread):
29    def __init__(self,src,dest):
30        Thread.__init__(self)
31        self.src=src
32        self.dest=dest
33
34
35    def __str__(self):
36        return '<Forwarder from %s to %s>' % (self.src, self.dest)
37
38
39    def run(self):
40        print '%s: starting' % self
41        try:
42            self.forward()
43        except socket.error as e:
44            print '%s: exception %s' % (self, e)
45            self.src.close()
46            self.dest.close()
47        finally:
48            print '%s: exiting' % self
49
50
51    def forward(self):
52        BUFSIZE = 1024
53        data = self.src.recv(BUFSIZE)
54        while data:
55            self.dest.sendall(data)
56            data = self.src.recv(BUFSIZE)
57        self.src.close()
58        self.dest.close()
59        print '%s: client quit normally' % self
60
61
62class ProxyForwarder(Forwarder):
63    def __init__(self, src, dest_addr):
64        Forwarder.__init__(self, src, None)
65        self.dest_addr = dest_addr
66        self.src = src
67        self.dest = None
68
69
70    def __str__(self):
71        return '<ProxyForwarder between %s and %s (%s:%d)' % (
72            self.src, self.dest, self.dest_addr[0], self.dest_addr[1])
73
74
75    def forward(self):
76        self.dest = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
77        self.dest.connect(self.dest_addr)
78        self.src.settimeout(RESENDTIMEOUT)
79        self.dest.settimeout(RESENDTIMEOUT)
80        Forwarder(self.src,self.dest).start()
81        Forwarder(self.dest,self.src).start()
82
83
84def recvbytes(sock, n):
85    bs = sock.recv(n)
86    return [ ord(x) for x in bs ]
87
88
89def recvshort(sock):
90    x = recvbytes(sock, 2)
91    return x[0] * 256 + x[1]
92
93
94def create_server(ip,port):
95    SOCKS5_VER = "\x05"
96    AUTH_NONE = "\x00"
97
98    ATYP_DOMAIN = 0x03
99
100    CMD_CONNECT = 0x01
101
102    ERR_SUCCESS = "\x00"
103    ERR_UNSUPP = "\x07"
104
105    transformer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
106    transformer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
107    transformer.bind((ip, port))
108    transformer.listen(1000)
109
110    network_port = chr(port >> 8) + chr(port & 0xff)
111    # Turn the textual IP address we were supplied with into a
112    # network-byte-order IP address for SOCKS5 wire protocol
113    network_ip = "".join(chr(int(i)) for i in ip.split("."))
114    while True:
115        sock = transformer.accept()[0]
116        sock.settimeout(SOCKTIMEOUT)
117        print "Got one client connection"
118        (_, nmethods) = recvbytes(sock, 2)
119        _ = recvbytes(sock, nmethods)
120        sock.sendall(SOCKS5_VER + AUTH_NONE)
121        (_, cmd, _, atyp) = recvbytes(sock, 4)
122        dst_addr = None
123        dst_port = None
124        if atyp == ATYP_DOMAIN:
125            addr_len = recvbytes(sock, 1)[0]
126            dst_addr = "".join([unichr(x) for x in recvbytes(sock, addr_len)])
127            dst_port = recvshort(sock)
128        else:
129            socket.sendall(SOCKS5_VER + ERR_UNSUPP + network_ip + network_port)
130        print "Proxying to %s:%d" %(dst_addr,dst_port)
131
132        if cmd == CMD_CONNECT:
133            sock.sendall(SOCKS5_VER + ERR_SUCCESS + "\x00" + "\x01" +
134                         network_ip + network_port)
135            print "Starting forwarding thread"
136            ProxyForwarder(sock, (dst_addr, dst_port)).start()
137        else:
138            sock.sendall(SOCKS5_VER + ERR_UNSUPP + network_ip + network_port)
139            sock.close()
140
141
142class ServingThread(Thread):
143    def __init__(self, ip, port):
144        Thread.__init__(self)
145        self.ip = ip
146        self.port = port
147
148
149    def run(self):
150        create_server(self.ip, self.port)
151
152
153class platform_TLSDateActual(test.test):
154    version = 1
155
156
157    def tlsdate(self, host, proxy):
158        args = ['/usr/bin/tlsdate', '-v', '-l', '-H', host]
159        if proxy:
160            args += ['-x', proxy]
161        p = subprocess.Popen(args, stderr=subprocess.PIPE)
162        out = p.communicate()[1]
163        print out
164        return p.returncode
165
166
167    def run_once(self):
168        t = ServingThread("127.0.0.1", 8083)
169        t.start()
170        r = self.tlsdate('clients3.google.com', None)
171        if r != 0:
172            raise error.TestFail('tlsdate with no proxy to good host failed: %d' % r)
173        r = self.tlsdate('clients3.google.com', 'socks5://127.0.0.1:8083')
174        if r != 0:
175            raise error.TestFail('tlsdate with proxy to good host failed: %d' % r)
176        r = self.tlsdate('invalid-host.example.com', None)
177        if r == 0:
178            raise error.TestFail('tlsdate with no proxy to bad host succeeded')
179        r = self.tlsdate('invalid-host.example.com', 'socks5://127.0.0.1:8083')
180        if r == 0:
181            raise error.TestFail('tlsdate with proxy to bad host succeeded')
182