From 92c590397d7df286370ca30a78bfe4c69e5eff4b Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 27 Feb 2011 21:13:25 -0500 Subject: Initial commit for 5th-gen tor privnet script --- .gitignore | 5 + lib/TorNet.py | 272 +++++++++++++++++++++++++++++++++++++++++ lib/templating.py | 205 +++++++++++++++++++++++++++++++ networks/basic | 7 ++ torrc_templates/authority.tmpl | 5 + torrc_templates/client.tmpl | 2 + torrc_templates/common.i | 11 ++ torrc_templates/relay.tmpl | 5 + 8 files changed, 512 insertions(+) create mode 100644 .gitignore create mode 100644 lib/TorNet.py create mode 100644 lib/templating.py create mode 100644 networks/basic create mode 100644 torrc_templates/authority.tmpl create mode 100644 torrc_templates/client.tmpl create mode 100644 torrc_templates/common.i create mode 100644 torrc_templates/relay.tmpl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd00869 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*pyc +*pyd +*~ + +/net diff --git a/lib/TorNet.py b/lib/TorNet.py new file mode 100644 index 0000000..8d993da --- /dev/null +++ b/lib/TorNet.py @@ -0,0 +1,272 @@ +#!/usr/bin/python +# +# Copyright 2011 Nick Mathewson, Michael Stone +# +# You may do anything with this work that copyright law would normally +# restrict, so long as you retain the above notice(s) and this license +# in all redistributed copies and derived works. There is no warranty. + +from __future__ import with_statement +import os +import templating +import subprocess +import sys +import re +import errno + +def mkdir_p(d): + try: + os.makedirs(d) + except OSError, e: + if e.errno == errno.EEXIST: + return + raise + +class Node: + ######## + # Users are expected to call these: + def __init__(self, parent=None, **kwargs): + self._parent = parent + self._fields = self._createEnviron(parent, kwargs) + + def getN(self, N): + return [ Node(self) for i in xrange(N) ] + + def specialize(self, **kwargs): + return Node(parent=self, **kwargs) + + ####### + # Users are NOT expected to call these: + + def _createTorrcFile(self, checkOnly=False): + template = self._getTorrcTemplate() + env = self._fields + fn_out = templating.Template("${torrc_fname}").format(env) + output = template.format(env) + if checkOnly: + return + with open(fn_out, 'w') as f: + f.write(output) + + def _getTorrcTemplate(self): + env = self._fields + template_path = env['torrc_template_path'] + + t = "$${include:$torrc}" + return templating.Template(t, includePath=template_path) + + def _getFreeVars(self): + template = self._getTorrcTemplate() + env = self._fields + return template.freevars(env) + + def _createEnviron(self, parent, argdict): + if parent: + parentfields = parent._fields + else: + parentfields = self._getDefaultFields() + return TorEnviron(parentfields, **argdict) + + def _getDefaultFields(self): + return _BASE_FIELDS + + def _checkConfig(self, net): + self._createTorrcFile(checkOnly=True) + + def _preConfig(self, net): + self._makeDataDir() + if self._fields['authority']: + self._genAuthorityKey() + if self._fields['relay']: + self._genRouterKey() + + def _config(self, net): + self._createTorrcFile() + #self._createScripts() + + def _postConfig(self, net): + #self.net.addNode(self) + pass + + def _setnodenum(self, num): + self._fields['nodenum'] = num + + def _makeDataDir(self): + env = self._fields + datadir = env['dir'] + mkdir_p(os.path.join(datadir, 'keys')) + + def _genAuthorityKey(self): + env = self._fields + datadir = env['dir'] + tor_gencert = env['tor_gencert'] + lifetime = env['auth_cert_lifetime'] + idfile = os.path.join(datadir,'keys',"authority_identity_key") + skfile = os.path.join(datadir,'keys',"authority_signing_key") + certfile = os.path.join(datadir,'keys',"authority_certificate") + addr = "%s:%s" % (env['ip'], env['dirport']) + passphrase = env['auth_passphrase'] + if all(os.path.exists(f) for f in [idfile, skfile, certfile]): + return + cmdline = [ + tor_gencert, + '--create-identity-key', + '--passphrase-fd', '0', + '-i', idfile, + '-s', skfile, + '-c', certfile, + '-m', str(lifetime), + '-a', addr] + print "Creating identity key %s for %s with %s"%(idfile,env['nick']," ".join(cmdline)) + p = subprocess.Popen(cmdline, stdin=subprocess.PIPE) + p.communicate(passphrase+"\n") + assert p.returncode == 0 #XXXX BAD! + + def _genRouterKey(self): + env = self._fields + datadir = env['dir'] + tor = env['tor'] + idfile = os.path.join(datadir,'keys',"identity_key") + cmdline = [ + tor, + "--quiet", + "--list-fingerprint", + "--orport", "1", + "--dirserver", + "xyzzy 127.0.0.1:1 ffffffffffffffffffffffffffffffffffffffff", + "--datadirectory", datadir ] + p = subprocess.Popen(cmdline, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + fingerprint = "".join(stdout.split()[1:]) + assert re.match(r'^[A-F0-9]{40}$', fingerprint) + env['fingerprint'] = fingerprint + + def _getDirServerLine(self): + env = self._fields + if not env['authority']: + return "" + + datadir = env['dir'] + certfile = os.path.join(datadir,'keys',"authority_certificate") + v3id = None + with open(certfile, 'r') as f: + for line in f: + if line.startswith("fingerprint"): + v3id = line.split()[1].strip() + break + + assert v3id is not None + + return "DirServer %s v3ident=%s orport=%s %s %s:%s %s\n" %( + env['nick'], v3id, env['orport'], env['dirserver_flags'], + env['ip'], env['dirport'], env['fingerprint']) + +DEFAULTS = { + 'authority' : False, + 'relay' : False, + 'connlimit' : 60, + 'net_base_dir' : 'net', + 'tor' : 'tor', + 'auth_cert_lifetime' : 12, + 'ip' : '127.0.0.1', + 'dirserver_flags' : 'no-v2', + 'privnet_dir' : '.', + 'torrc_fname' : '${dir}/torrc', + 'orport_base' : 6000, + 'dirport_base' : 7000, + 'controlport_base' : 8000, + 'socksport_base' : 9000, + 'dirservers' : "Dirserver bleargh bad torrc file!" +} + +class TorEnviron(templating.Environ): + def __init__(self,parent=None,**kwargs): + templating.Environ.__init__(self, parent=parent, **kwargs) + + def _get_orport(self, me): + return me['orport_base']+me['nodenum'] + + def _get_controlport(self, me): + return me['controlport_base']+me['nodenum'] + + def _get_socksport(self, me): + return me['socksport_base']+me['nodenum'] + + def _get_dirport(self, me): + return me['dirport_base']+me['nodenum'] + + def _get_dir(self, me): + return os.path.abspath(os.path.join(me['net_base_dir'], + "nodes", + me['nick'])) + + def _get_nick(self, me): + return "%s-%02d"%(me['tag'], me['nodenum']) + + def _get_tor_gencert(self, me): + return me['tor']+"-gencert" + + def _get_auth_passphrase(self, me): + return self['nick'] # OMG TEH SECURE! + + def _get_torrc_template_path(self, me): + return [ os.path.join(me['privnet_dir'], 'torrc_templates') ] + + +class Network: + def __init__(self,defaultEnviron): + self._nodes = [] + self._dfltEnv = defaultEnviron + self._nextnodenum = 0 + + def addNode(self, n): + n._setnodenum(self._nextnodenum) + self._nextnodenum += 1 + self._nodes.append(n) + + def configure(self): + network = self + dirserverlines = [] + + for n in self._nodes: + n._checkConfig(network) + + for n in self._nodes: + n._preConfig(network) + dirserverlines.append(n._getDirServerLine()) + + self._dfltEnv['dirservers'] = "".join(dirserverlines) + + for n in self._nodes: + n._config(network) + + for n in self._nodes: + n._postConfig(network) + + +def ConfigureNodes(nodelist): + network = _THE_NETWORK + + for n in nodelist: + network.addNode(n) + +def runConfigFile(f): + global _BASE_FIELDS + global _THE_NETWORK + _BASE_FIELDS = TorEnviron(templating.Environ(**DEFAULTS)) + _THE_NETWORK = Network(_BASE_FIELDS) + _GLOBALS = dict(_BASE_FIELDS= _BASE_FIELDS, + Node=Node, + ConfigureNodes=ConfigureNodes, + _THE_NETWORK=_THE_NETWORK) + + exec f in _GLOBALS + network = _GLOBALS['_THE_NETWORK'] + + network.configure() + +if __name__ == '__main__': + f = open(sys.argv[1]) + runConfigFile(f) + + diff --git a/lib/templating.py b/lib/templating.py new file mode 100644 index 0000000..1fe3465 --- /dev/null +++ b/lib/templating.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +# +# Copyright 2011 Nick Mathewson, Michael Stone +# +# You may do anything with this work that copyright law would normally +# restrict, so long as you retain the above notice(s) and this license +# in all redistributed copies and derived works. There is no warranty. + +""" +>>> base = Environ(foo=99, bar=600) +>>> derived1 = Environ(parent=base, bar=700, quux=32) +>>> base["foo"] +99 +>>> sorted(base.keys()) +['bar', 'foo'] +>>> derived1["foo"] +99 +>>> base["bar"] +600 +>>> derived1["bar"] +700 +>>> derived1["quux"] +32 +>>> sorted(derived1.keys()) +['bar', 'foo', 'quux'] +>>> class Specialized(Environ): +... def __init__(self, p=None, **kw): +... Environ.__init__(self, p, **kw) +... self._n_calls = 0 +... def _get_expensive_value(self, me): +... self._n_calls += 1 +... return "Let's pretend this is hard to compute" +... +>>> s = Specialized(base, quux="hi") +>>> s["quux"] +'hi' +>>> s['expensive_value'] +"Let's pretend this is hard to compute" +>>> s['expensive_value'] +"Let's pretend this is hard to compute" +>>> s._n_calls +1 +>>> sorted(s.keys()) +['bar', 'expensive_value', 'foo', 'quux'] + +>>> bt = _BetterTemplate("Testing ${hello}, $goodbye$$, $foo , ${a:b:c}") +>>> bt.safe_substitute({'a:b:c': "4"}, hello=1, goodbye=2, foo=3) +'Testing 1, 2$, 3 , 4' + +>>> t = Template("${include:/dev/null} $hi_there") +>>> sorted(t.freevars()) +['hi_there'] +>>> t.format(dict(hi_there=99)) +' 99' +>>> t2 = Template("X$${include:$fname} $bar $baz") +>>> t2.format(dict(fname="/dev/null", bar=33, baz="$foo", foo=1337)) +'X 33 1337' +>>> sorted(t2.freevars({'fname':"/dev/null"})) +['bar', 'baz', 'fname'] + +""" + +from __future__ import with_statement + +import string +import os +import re + +#class _KeyError(KeyError): +# pass + +_KeyError = KeyError + +class _DictWrapper: + def __init__(self, parent=None): + self._parent = parent + + def __getitem__(self, key): + try: + return self._getitem(key) + except KeyError: + pass + if self._parent is None: + raise _KeyError(key) + + try: + return self._parent[key] + except KeyError: + raise _KeyError(key) + +class Environ(_DictWrapper): + def __init__(self, parent=None, **kw): + _DictWrapper.__init__(self, parent) + self._dict = kw + self._cache = {} + + def _getitem(self, key): + try: + return self._dict[key] + except KeyError: + pass + try: + return self._cache[key] + except KeyError: + pass + fn = getattr(self, "_get_%s"%key, None) + if fn is not None: + try: + self._cache[key] = rv = fn(self) + return rv + except _KeyError: + raise KeyError(key) + raise KeyError(key) + + def __setitem__(self, key, val): + self._dict[key] = val + + def keys(self): + s = set() + s.update(self._dict.keys()) + s.update(self._cache.keys()) + if self._parent is not None: + s.update(self._parent.keys()) + s.update(name[5:] for name in dir(self) if name.startswith("_get_")) + return s + +class IncluderDict(_DictWrapper): + def __init__(self, parent, includePath=(".",)): + _DictWrapper.__init__(self, parent) + self._includePath = includePath + + def _getitem(self, key): + if not key.startswith("include:"): + raise KeyError(key) + + filename = key[len("include:"):] + if os.path.isabs(filename): + with open(filename, 'r') as f: + return f.read() + + for elt in self._includePath: + fullname = os.path.join(elt, filename) + if os.path.exists(fullname): + with open(fullname, 'r') as f: + return f.read() + + raise KeyError(key) + +class _BetterTemplate(string.Template): + + idpattern = r'[a-z0-9:_/\.\-]+' + + def __init__(self, template): + string.Template.__init__(self, template) + +class _FindVarsHelper: + def __init__(self, dflts): + self._dflts = dflts + self._vars = set() + def __getitem__(self, var): + self._vars.add(var) + try: + return self._dflts[var] + except KeyError: + return "" + +class Template: + MAX_ITERATIONS = 32 + + def __init__(self, pattern, includePath=(".",)): + self._pat = pattern + self._includePath = includePath + + def freevars(self, defaults=None): + if defaults is None: + defaults = {} + d = _FindVarsHelper(defaults) + self.format(d) + return d._vars + + def format(self, values): + values = IncluderDict(values, self._includePath) + orig_val = self._pat + nIterations = 0 + while True: + v = _BetterTemplate(orig_val).substitute(values) + if v == orig_val: + return v + orig_val = v + nIterations += 1 + if nIterations > self.MAX_ITERATIONS: + raise ValueError("Too many iterations in expanding template!") + +if __name__ == '__main__': + import sys + if len(sys.argv) == 1: + import doctest + doctest.testmod() + print "done" + else: + for fn in sys.argv[1:]: + with open(fn, 'r') as f: + t = Template(f.read()) + print fn, t.freevars() + diff --git a/networks/basic b/networks/basic new file mode 100644 index 0000000..b15a0b7 --- /dev/null +++ b/networks/basic @@ -0,0 +1,7 @@ +Authority = Node(tag="a", authority=1, relay=1, torrc="authority.tmpl") +Relay = Node(tag="r", relay=1, torrc="relay.tmpl") +Client = Node(tag="c", torrc="client.tmpl") + +NODES = Authority.getN(3) + Relay.getN(5) + Client.getN(2) + +ConfigureNodes(NODES) diff --git a/torrc_templates/authority.tmpl b/torrc_templates/authority.tmpl new file mode 100644 index 0000000..6e18d86 --- /dev/null +++ b/torrc_templates/authority.tmpl @@ -0,0 +1,5 @@ +${include:relay.tmpl} +AuthoritativeDirectory 1 +V3AuthoritativeDirectory 1 +ContactInfo auth${nodenum}@test.test +ExitPolicy reject *:* diff --git a/torrc_templates/client.tmpl b/torrc_templates/client.tmpl new file mode 100644 index 0000000..aa86c01 --- /dev/null +++ b/torrc_templates/client.tmpl @@ -0,0 +1,2 @@ +${include:common.i} +SocksPort $socksport diff --git a/torrc_templates/common.i b/torrc_templates/common.i new file mode 100644 index 0000000..3a50f3a --- /dev/null +++ b/torrc_templates/common.i @@ -0,0 +1,11 @@ +TestingTorNetwork 1 +DataDirectory $dir +RunAsDaemon 1 +ConnLimit $connlimit +Nickname $nick +ShutdownWaitLength 0 +PidFile pid +Log notice file ${dir}/notice.log +ControlPort $controlport +${dirservers} + diff --git a/torrc_templates/relay.tmpl b/torrc_templates/relay.tmpl new file mode 100644 index 0000000..09dd1f0 --- /dev/null +++ b/torrc_templates/relay.tmpl @@ -0,0 +1,5 @@ +${include:common.i} +SocksPort 0 +OrPort $orport +Address $ip +DirPort $dirport -- cgit v1.2.3