1# Future imports for Python 2.7, mandatory in 3.0
2from __future__ import division
3from __future__ import print_function
4from __future__ import unicode_literals
5
6import errno
7import logging
8import os
9import random
10import socket
11import subprocess
12import sys
13import time
14
15LOG_TIMEOUT = 60.0
16LOG_WAIT = 0.1
17
18def fail(msg):
19    logging.error('FAIL')
20    sys.exit(msg)
21
22def skip(msg):
23    logging.warning('SKIP: {}'.format(msg))
24    sys.exit(77)
25
26def try_connecting_to_socksport():
27    socks_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
28    e = socks_socket.connect_ex(('127.0.0.1', socks_port))
29    if e:
30        tor_process.terminate()
31        fail('Cannot connect to SOCKSPort: error ' + os.strerror(e))
32    socks_socket.close()
33
34def wait_for_log(s):
35    cutoff = time.time() + LOG_TIMEOUT
36    while time.time() < cutoff:
37        l = tor_process.stdout.readline()
38        l = l.decode('utf8', 'backslashreplace')
39        if s in l:
40            logging.info('Tor logged: "{}"'.format(l.strip()))
41            return
42        # readline() returns a blank string when there is no output
43        # avoid busy-waiting
44        if len(l) == 0:
45            logging.debug('Tor has not logged anything, waiting for "{}"'.format(s))
46            time.sleep(LOG_WAIT)
47        else:
48            logging.info('Tor logged: "{}", waiting for "{}"'.format(l.strip(), s))
49    fail('Could not find "{}" in logs after {} seconds'.format(s, LOG_TIMEOUT))
50
51def pick_random_port():
52    port = 0
53    random.seed()
54
55    for i in range(8):
56        port = random.randint(10000, 60000)
57        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
58        if s.connect_ex(('127.0.0.1', port)) == 0:
59            s.close()
60        else:
61            break
62
63    if port == 0:
64        fail('Could not find a random free port between 10000 and 60000')
65
66    return port
67
68logging.basicConfig(level=logging.DEBUG,
69                    format='%(asctime)s.%(msecs)03d %(message)s',
70                    datefmt='%Y-%m-%d %H:%M:%S')
71
72if sys.hexversion < 0x02070000:
73    fail("ERROR: unsupported Python version (should be >= 2.7)")
74
75if sys.hexversion > 0x03000000 and sys.hexversion < 0x03010000:
76    fail("ERROR: unsupported Python3 version (should be >= 3.1)")
77
78if 'TOR_SKIP_TEST_REBIND' in os.environ:
79    skip('$TOR_SKIP_TEST_REBIND is set')
80
81control_port = pick_random_port()
82socks_port = pick_random_port()
83
84assert control_port != 0
85assert socks_port != 0
86
87if len(sys.argv) < 3:
88     fail('Usage: %s <path-to-tor> <data-dir>' % sys.argv[0])
89
90if not os.path.exists(sys.argv[1]):
91    fail('ERROR: cannot find tor at %s' % sys.argv[1])
92if not os.path.exists(sys.argv[2]):
93    fail('ERROR: cannot find datadir at %s' % sys.argv[2])
94
95tor_path = sys.argv[1]
96data_dir = sys.argv[2]
97
98empty_torrc_path = os.path.join(data_dir, 'empty_torrc')
99open(empty_torrc_path, 'w').close()
100empty_defaults_torrc_path = os.path.join(data_dir, 'empty_defaults_torrc')
101open(empty_defaults_torrc_path, 'w').close()
102
103tor_process = subprocess.Popen([tor_path,
104                               '-DataDirectory', data_dir,
105                               '-ControlPort', '127.0.0.1:{}'.format(control_port),
106                               '-SOCKSPort', '127.0.0.1:{}'.format(socks_port),
107                               '-Log', 'debug stdout',
108                               '-LogTimeGranularity', '1',
109                               '-FetchServerDescriptors', '0',
110                               '-f', empty_torrc_path,
111                               '--defaults-torrc', empty_defaults_torrc_path,
112                               ],
113                               stdout=subprocess.PIPE,
114                               stderr=subprocess.PIPE)
115
116if tor_process == None:
117    fail('ERROR: running tor failed')
118
119wait_for_log('Opened Control listener')
120
121try_connecting_to_socksport()
122
123control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
124if control_socket.connect_ex(('127.0.0.1', control_port)):
125    tor_process.terminate()
126    fail('Cannot connect to ControlPort')
127
128control_socket.sendall('AUTHENTICATE \r\n'.encode('ascii'))
129control_socket.sendall('SETCONF SOCKSPort=0.0.0.0:{}\r\n'.format(socks_port).encode('ascii'))
130wait_for_log('Opened Socks listener')
131
132try_connecting_to_socksport()
133
134control_socket.sendall('SETCONF SOCKSPort=127.0.0.1:{}\r\n'.format(socks_port).encode('ascii'))
135wait_for_log('Opened Socks listener')
136
137try_connecting_to_socksport()
138
139control_socket.sendall('SIGNAL HALT\r\n'.encode('ascii'))
140
141wait_for_log('exiting cleanly')
142logging.info('OK')
143
144try:
145    tor_process.terminate()
146except OSError as e:
147    if e.errno == errno.ESRCH: # errno 3: No such process
148        # assume tor has already exited due to SIGNAL HALT
149        logging.warn("Tor has already exited")
150    else:
151        raise
152