summaryrefslogtreecommitdiffstats
path: root/config/chroot_local-includes/usr/local/lib/tor-controlport-filter
blob: 6dc3a60859660d5734641d312b017eadc9224202 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/python

# Tor control port filter proxy, only white-listing SIGNAL NEWNYM.

# This filter proxy should allow Torbutton to request a
# new Tor circuit, without exposing dangerous control requests
# like "GETINFO address" to applications running as a local user.

# If something goes wrong, an error code is returned, and
# Torbutton will display a warning dialog that New Identity failed.

# Only one application can talk through this filter proxy
# simultaneously. A malicious application that is running as a
# local user could use this to prevent other applications from
# doing NEWNYM. But it could just as well rewrite the
# TOR_CONTROL_PORT environment variable to itself or do something else.

import socket
import binascii
import re

# Limit the length of a line, to prevent DoS attacks trying to
# crash this filter proxy by sending infinitely long lines.
MAX_LINESIZE = 128

class UnexpectedAnswer(Exception):
	def __init__(self, msg):
		self.msg = msg
	def __str__(self):
		return "[UnexpectedAnswer] " + self.msg

def do_newnym_real():
	# Read authentication cookie
	with open("/var/run/tor/control.authcookie", "rb") as f:
		rawcookie = f.read(32)

	hexcookie = binascii.hexlify(rawcookie)

	# Connect to the real control port
	sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
	sock.settimeout(10.0)
	sock.connect("/var/run/tor/control")
	readh = sock.makefile("r")
	writeh = sock.makefile("w")

	# Authenticate
	writeh.write("AUTHENTICATE " + hexcookie + "\n")
	writeh.flush()

	answer = readh.readline(MAX_LINESIZE)
	if not answer.startswith("250"):
		raise UnexpectedAnswer("AUTHENTICATE failed")

	# Send the newnym signal
	writeh.write("SIGNAL NEWNYM\n")
	writeh.flush()

	answer = readh.readline(MAX_LINESIZE)
	if not answer.startswith("250"):
		raise UnexpectedAnswer("SIGNAL NEWNYM failed")

	# Close the connection
	writeh.write("QUIT\n")
	writeh.flush()

	answer = readh.readline(MAX_LINESIZE)
	if not answer.startswith("250"):
		raise UnexpectedAnswer("QUIT failed")

	sock.close()

def do_newnym():
	# Catch innocent exceptions, will report error instead
	try:
		do_newnym_real()
		print "Newnym went fine"
		return True
	except (IOError, UnexpectedAnswer) as e:
		print "Warning: Couldn't perform newnym!"
		print e
		return False

def handle_connection(sock):
	# Create file handles for the socket
	readh = sock.makefile("r")
	writeh = sock.makefile("w")

	# Keep accepting commands
	while True:
		# Read in a newline terminated line
		line = readh.readline(MAX_LINESIZE)
		if not line: break

                def line_matches_command(cmd):
                        # The control port language does not care about case
                        # for commands.
                        return re.match(r"^%s\b" % cmd, line, re.IGNORECASE)

		# Check what it is
		if line_matches_command("AUTHENTICATE"):
			# Don't check authentication, since only
			# safe commands are allowed
			writeh.write("250 OK\n")
		elif line_matches_command("SIGNAL NEWNYM"):
			# Perform a real SIGNAL NEWNYM (new Tor circuit)
			if do_newnym():
				writeh.write("250 OK\n")
			else:
				writeh.write("510 Newnym signal failed\n")
		elif line_matches_command("QUIT"):
			# Quit session
			writeh.write("250 Closing connection\n")
			break
		else:
			# Everything else we ignore/block
			writeh.write("510 Command filtered\n")

		# Ensure the answer was written
		writeh.flush()

	# Ensure all data was written
	writeh.flush()

def main():
	# Listen on port 9052 (we cannot use 9051 as Tor uses that one)
	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	server.bind(("127.0.0.1", 9052))
	server.listen(4)

	print "Tor control port filter started, listening on 9052"

	# Accept and handle connections one after one,
	# sessions are short enough that the added complexity of
	# simultaneous connections are unnecessary (in absence of attacks)
	while True:
		clisock, cliaddr = server.accept()

		try:
			print "Accepted a connection"
			handle_connection(clisock)
			print "Connection closed"
		except IOError:
			print "Connection closed (IOError)"

		clisock.close()

if __name__ == "__main__":
	main()