TorNet.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. #!/usr/bin/python
  2. #
  3. # Copyright 2011 Nick Mathewson, Michael Stone
  4. # Copyright 2013 The Tor Project
  5. #
  6. # You may do anything with this work that copyright law would normally
  7. # restrict, so long as you retain the above notice(s) and this license
  8. # in all redistributed copies and derived works. There is no warranty.
  9. from __future__ import with_statement
  10. # Get verbose tracebacks, so we can diagnose better.
  11. import cgitb
  12. cgitb.enable(format="plain")
  13. import os
  14. import signal
  15. import subprocess
  16. import sys
  17. import re
  18. import errno
  19. import time
  20. import chutney.Templating
  21. import chutney.Traffic
  22. def mkdir_p(d, mode=0777):
  23. """Create directory 'd' and all of its parents as needed. Unlike
  24. os.makedirs, does not give an error if d already exists.
  25. """
  26. try:
  27. os.makedirs(d,mode=mode)
  28. except OSError, e:
  29. if e.errno == errno.EEXIST:
  30. return
  31. raise
  32. class Node(object):
  33. """A Node represents a Tor node or a set of Tor nodes. It's created
  34. in a network configuration file.
  35. This class is responsible for holding the user's selected node
  36. configuration, and figuring out how the node needs to be
  37. configured and launched.
  38. """
  39. ## Fields:
  40. # _parent
  41. # _env
  42. # _builder
  43. # _controller
  44. ########
  45. # Users are expected to call these:
  46. def __init__(self, parent=None, **kwargs):
  47. self._parent = parent
  48. self._env = self._createEnviron(parent, kwargs)
  49. self._builder = None
  50. self._controller = None
  51. def getN(self, N):
  52. return [ Node(self) for _ in xrange(N) ]
  53. def specialize(self, **kwargs):
  54. return Node(parent=self, **kwargs)
  55. ######
  56. # Chutney uses these:
  57. def getBuilder(self):
  58. """Return a NodeBuilder instance to set up this node (that is, to
  59. write all the files that need to be in place so that this
  60. node can be run by a NodeController).
  61. """
  62. if self._builder is None:
  63. self._builder = LocalNodeBuilder(self._env)
  64. return self._builder
  65. def getController(self):
  66. """Return a NodeController instance to control this node (that is,
  67. to start it, stop it, see if it's running, etc.)
  68. """
  69. if self._controller is None:
  70. self._controller = LocalNodeController(self._env)
  71. return self._controller
  72. def setNodenum(self, num):
  73. """Assign a value to the 'nodenum' element of this node. Each node
  74. in a network gets its own nodenum.
  75. """
  76. self._env['nodenum'] = num
  77. #####
  78. # These are internal:
  79. def _createEnviron(self, parent, argdict):
  80. """Return an Environ that delegates to the parent node's Environ (if
  81. there is a parent node), or to the default environment.
  82. """
  83. if parent:
  84. parentenv = parent._env
  85. else:
  86. parentenv = self._getDefaultEnviron()
  87. return TorEnviron(parentenv, **argdict)
  88. def _getDefaultEnviron(self):
  89. """Return the default environment. Any variables that we can't find
  90. set for any particular node, we look for here.
  91. """
  92. return _BASE_ENVIRON
  93. class _NodeCommon(object):
  94. """Internal helper class for functionality shared by some NodeBuilders
  95. and some NodeControllers."""
  96. # XXXX maybe this should turn into a mixin.
  97. def __init__(self, env):
  98. self._env = env
  99. def expand(self, pat, includePath=(".",)):
  100. return chutney.Templating.Template(pat, includePath).format(self._env)
  101. def _getTorrcFname(self):
  102. """Return the name of the file where we'll be writing torrc"""
  103. return self.expand("${torrc_fname}")
  104. class NodeBuilder(_NodeCommon):
  105. """Abstract base class. A NodeBuilder is responsible for doing all the
  106. one-time prep needed to set up a node in a network.
  107. """
  108. def __init__(self, env):
  109. _NodeCommon.__init__(self, env)
  110. def checkConfig(self, net):
  111. """Try to format our torrc; raise an exception if we can't.
  112. """
  113. def preConfig(self, net):
  114. """Called on all nodes before any nodes configure: generates keys as
  115. needed.
  116. """
  117. def config(self, net):
  118. """Called to configure a node: creates a torrc file for it."""
  119. def postConfig(self, net):
  120. """Called on each nodes after all nodes configure."""
  121. class NodeController(_NodeCommon):
  122. """Abstract base class. A NodeController is responsible for running a
  123. node on the network.
  124. """
  125. def __init__(self, env):
  126. _NodeCommon.__init__(self, env)
  127. def check(self, listRunning=True, listNonRunning=False):
  128. """See if this node is running, stopped, or crashed. If it's running
  129. and listRunning is set, print a short statement. If it's
  130. stopped and listNonRunning is set, then print a short statement.
  131. If it's crashed, print a statement. Return True if the
  132. node is running, false otherwise.
  133. """
  134. def start(self):
  135. """Try to start this node; return True if we succeeded or it was
  136. already running, False if we failed."""
  137. def stop(self, sig=signal.SIGINT):
  138. """Try to stop this node by sending it the signal 'sig'."""
  139. class LocalNodeBuilder(NodeBuilder):
  140. ## Environment members used:
  141. # torrc -- which torrc file to use
  142. # torrc_template_path -- path to search for torrc files and include files
  143. # authority -- bool -- are we an authority?
  144. # bridgeauthority -- bool -- are we a bridge authority?
  145. # relay -- bool -- are we a relay?
  146. # bridge -- bool -- are we a bridge?
  147. # nodenum -- int -- set by chutney -- which unique node index is this?
  148. # dir -- path -- set by chutney -- data directory for this tor
  149. # tor_gencert -- path to tor_gencert binary
  150. # tor -- path to tor binary
  151. # auth_cert_lifetime -- lifetime of authority certs, in months.
  152. # ip -- IP to listen on (used only if authority or bridge)
  153. # ipv6_addr -- IPv6 address to listen on (used only if ipv6 bridge)
  154. # orport, dirport -- (used only if authority)
  155. # fingerprint -- used only if authority
  156. # dirserver_flags -- used only if authority
  157. # nick -- nickname of this router
  158. ## Environment members set
  159. # fingerprint -- hex router key fingerprint
  160. # nodenum -- int -- set by chutney -- which unique node index is this?
  161. def __init__(self, env):
  162. NodeBuilder.__init__(self, env)
  163. self._env = env
  164. def _createTorrcFile(self, checkOnly=False):
  165. """Write the torrc file for this node. If checkOnly, just make sure
  166. that the formatting is indeed possible.
  167. """
  168. fn_out = self._getTorrcFname()
  169. torrc_template = self._getTorrcTemplate()
  170. output = torrc_template.format(self._env)
  171. if checkOnly:
  172. # XXXX Is it time-cosuming to format? If so, cache here.
  173. return
  174. with open(fn_out, 'w') as f:
  175. f.write(output)
  176. def _getTorrcTemplate(self):
  177. """Return the template used to write the torrc for this node."""
  178. template_path = self._env['torrc_template_path']
  179. return chutney.Templating.Template("$${include:$torrc}",
  180. includePath=template_path)
  181. def _getFreeVars(self):
  182. """Return a set of the free variables in the torrc template for this
  183. node.
  184. """
  185. template = self._getTorrcTemplate()
  186. return template.freevars(self._env)
  187. def checkConfig(self, net):
  188. """Try to format our torrc; raise an exception if we can't.
  189. """
  190. self._createTorrcFile(checkOnly=True)
  191. def preConfig(self, net):
  192. """Called on all nodes before any nodes configure: generates keys as
  193. needed.
  194. """
  195. self._makeDataDir()
  196. if self._env['authority']:
  197. self._genAuthorityKey()
  198. if self._env['relay']:
  199. self._genRouterKey()
  200. def config(self, net):
  201. """Called to configure a node: creates a torrc file for it."""
  202. self._createTorrcFile()
  203. #self._createScripts()
  204. def postConfig(self, net):
  205. """Called on each nodes after all nodes configure."""
  206. #self.net.addNode(self)
  207. pass
  208. def _makeDataDir(self):
  209. """Create the data directory (with keys subdirectory) for this node.
  210. """
  211. datadir = self._env['dir']
  212. mkdir_p(os.path.join(datadir, 'keys'))
  213. def _genAuthorityKey(self):
  214. """Generate an authority identity and signing key for this authority,
  215. if they do not already exist."""
  216. datadir = self._env['dir']
  217. tor_gencert = self._env['tor_gencert']
  218. lifetime = self._env['auth_cert_lifetime']
  219. idfile = os.path.join(datadir,'keys',"authority_identity_key")
  220. skfile = os.path.join(datadir,'keys',"authority_signing_key")
  221. certfile = os.path.join(datadir,'keys',"authority_certificate")
  222. addr = self.expand("${ip}:${dirport}")
  223. passphrase = self._env['auth_passphrase']
  224. if all(os.path.exists(f) for f in [idfile, skfile, certfile]):
  225. return
  226. cmdline = [
  227. tor_gencert,
  228. '--create-identity-key',
  229. '--passphrase-fd', '0',
  230. '-i', idfile,
  231. '-s', skfile,
  232. '-c', certfile,
  233. '-m', str(lifetime),
  234. '-a', addr]
  235. print "Creating identity key %s for %s with %s"%(
  236. idfile,self._env['nick']," ".join(cmdline))
  237. p = subprocess.Popen(cmdline, stdin=subprocess.PIPE)
  238. p.communicate(passphrase+"\n")
  239. assert p.returncode == 0 #XXXX BAD!
  240. def _genRouterKey(self):
  241. """Generate an identity key for this router, unless we already have,
  242. and set up the 'fingerprint' entry in the Environ.
  243. """
  244. datadir = self._env['dir']
  245. tor = self._env['tor']
  246. idfile = os.path.join(datadir,'keys',"identity_key")
  247. cmdline = [
  248. tor,
  249. "--quiet",
  250. "--list-fingerprint",
  251. "--orport", "1",
  252. "--dirserver",
  253. "xyzzy 127.0.0.1:1 ffffffffffffffffffffffffffffffffffffffff",
  254. "--datadirectory", datadir ]
  255. p = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
  256. stdout, stderr = p.communicate()
  257. fingerprint = "".join(stdout.split()[1:])
  258. assert re.match(r'^[A-F0-9]{40}$', fingerprint)
  259. self._env['fingerprint'] = fingerprint
  260. def _getAltAuthLines(self, hasbridgeauth=False):
  261. """Return a combination of AlternateDirAuthority,
  262. AlternateHSAuthority and AlternateBridgeAuthority lines for
  263. this Node, appropriately. Non-authorities return ""."""
  264. if not self._env['authority']:
  265. return ""
  266. datadir = self._env['dir']
  267. certfile = os.path.join(datadir,'keys',"authority_certificate")
  268. v3id = None
  269. with open(certfile, 'r') as f:
  270. for line in f:
  271. if line.startswith("fingerprint"):
  272. v3id = line.split()[1].strip()
  273. break
  274. assert v3id is not None
  275. if self._env['bridgeauthority']:
  276. # Bridge authorities return AlternateBridgeAuthority with
  277. # the 'bridge' flag set.
  278. options = ("AlternateBridgeAuthority",)
  279. self._env['dirserver_flags'] += " bridge"
  280. else:
  281. # Directory authorities return AlternateDirAuthority with
  282. # the 'hs' and 'v3ident' flags set.
  283. # XXXX This next line is needed for 'bridges' but breaks
  284. # 'basic'
  285. if hasbridgeauth:
  286. options = ("AlternateDirAuthority",)
  287. else:
  288. options = ("DirAuthority",)
  289. self._env['dirserver_flags'] += " hs v3ident=%s" % v3id
  290. authlines = ""
  291. for authopt in options:
  292. authlines += "%s %s orport=%s %s %s:%s %s\n" %(
  293. authopt, self._env['nick'], self._env['orport'],
  294. self._env['dirserver_flags'], self._env['ip'],
  295. self._env['dirport'], self._env['fingerprint'])
  296. return authlines
  297. def _getBridgeLines(self):
  298. """Return potential Bridge line for this Node. Non-bridge
  299. relays return "".
  300. """
  301. if not self._env['bridge']:
  302. return ""
  303. bridgelines = "Bridge %s:%s\n" % (self._env['ip'],
  304. self._env['orport'])
  305. if self._env['ipv6_addr'] is not None:
  306. bridgelines += "Bridge %s:%s\n" % (self._env['ipv6_addr'],
  307. self._env['orport'])
  308. return bridgelines
  309. class LocalNodeController(NodeController):
  310. def __init__(self, env):
  311. NodeController.__init__(self, env)
  312. self._env = env
  313. def getPid(self):
  314. """Assuming that this node has its pidfile in ${dir}/pid, return
  315. the pid of the running process, or None if there is no pid in the
  316. file.
  317. """
  318. pidfile = os.path.join(self._env['dir'], 'pid')
  319. if not os.path.exists(pidfile):
  320. return None
  321. with open(pidfile, 'r') as f:
  322. return int(f.read())
  323. def isRunning(self, pid=None):
  324. """Return true iff this node is running. (If 'pid' is provided, we
  325. assume that the pid provided is the one of this node. Otherwise
  326. we call getPid().
  327. """
  328. if pid is None:
  329. pid = self.getPid()
  330. if pid is None:
  331. return False
  332. try:
  333. os.kill(pid, 0) # "kill 0" == "are you there?"
  334. except OSError, e:
  335. if e.errno == errno.ESRCH:
  336. return False
  337. raise
  338. # okay, so the process exists. Say "True" for now.
  339. # XXXX check if this is really tor!
  340. return True
  341. def check(self, listRunning=True, listNonRunning=False):
  342. """See if this node is running, stopped, or crashed. If it's running
  343. and listRunning is set, print a short statement. If it's
  344. stopped and listNonRunning is set, then print a short statement.
  345. If it's crashed, print a statement. Return True if the
  346. node is running, false otherwise.
  347. """
  348. # XXX Split this into "check" and "print" parts.
  349. pid = self.getPid()
  350. running = self.isRunning(pid)
  351. nick = self._env['nick']
  352. dir = self._env['dir']
  353. if running:
  354. if listRunning:
  355. print "%s is running with PID %s"%(nick,pid)
  356. return True
  357. elif os.path.exists(os.path.join(dir, "core.%s"%pid)):
  358. if listNonRunning:
  359. print "%s seems to have crashed, and left core file core.%s"%(
  360. nick,pid)
  361. return False
  362. else:
  363. if listNonRunning:
  364. print "%s is stopped"%nick
  365. return False
  366. def hup(self):
  367. """Send a SIGHUP to this node, if it's running."""
  368. pid = self.getPid()
  369. running = self.isRunning()
  370. nick = self._env['nick']
  371. if self.isRunning():
  372. print "Sending sighup to %s"%nick
  373. os.kill(pid, signal.SIGHUP)
  374. return True
  375. else:
  376. print "%s is not running"%nick
  377. return False
  378. def start(self):
  379. """Try to start this node; return True if we succeeded or it was
  380. already running, False if we failed."""
  381. if self.isRunning():
  382. print "%s is already running"%self._env['nick']
  383. return True
  384. torrc = self._getTorrcFname()
  385. cmdline = [
  386. self._env['tor'],
  387. "--quiet",
  388. "-f", torrc,
  389. ]
  390. p = subprocess.Popen(cmdline)
  391. # XXXX this requires that RunAsDaemon is set.
  392. p.wait()
  393. if p.returncode != 0:
  394. print "Couldn't launch %s (%s): %s"%(self._env['nick'],
  395. " ".join(cmdline),
  396. p.returncode)
  397. return False
  398. return True
  399. def stop(self, sig=signal.SIGINT):
  400. """Try to stop this node by sending it the signal 'sig'."""
  401. pid = self.getPid()
  402. if not self.isRunning(pid):
  403. print "%s is not running"%self._env['nick']
  404. return
  405. os.kill(pid, sig)
  406. DEFAULTS = {
  407. 'authority' : False,
  408. 'bridgeauthority' : False,
  409. 'hasbridgeauth' : False,
  410. 'relay' : False,
  411. 'bridge' : False,
  412. 'connlimit' : 60,
  413. 'net_base_dir' : 'net',
  414. 'tor' : 'tor',
  415. 'auth_cert_lifetime' : 12,
  416. 'ip' : '127.0.0.1',
  417. 'ipv6_addr' : None,
  418. 'dirserver_flags' : 'no-v2',
  419. 'chutney_dir' : '.',
  420. 'torrc_fname' : '${dir}/torrc',
  421. 'orport_base' : 5000,
  422. 'dirport_base' : 7000,
  423. 'controlport_base' : 8000,
  424. 'socksport_base' : 9000,
  425. 'authorities' : "AlternateDirAuthority bleargh bad torrc file!",
  426. 'bridges' : "Bridge bleargh bad torrc file!",
  427. 'core' : True,
  428. }
  429. class TorEnviron(chutney.Templating.Environ):
  430. """Subclass of chutney.Templating.Environ to implement commonly-used
  431. substitutions.
  432. Environment fields provided:
  433. orport, controlport, socksport, dirport:
  434. dir:
  435. nick:
  436. tor_gencert:
  437. auth_passphrase:
  438. torrc_template_path:
  439. Environment fields used:
  440. nodenum
  441. tag
  442. orport_base, controlport_base, socksport_base, dirport_base
  443. chutney_dir
  444. tor
  445. XXXX document the above. Or document all fields in one place?
  446. """
  447. def __init__(self,parent=None,**kwargs):
  448. chutney.Templating.Environ.__init__(self, parent=parent, **kwargs)
  449. def _get_orport(self, my):
  450. return my['orport_base']+my['nodenum']
  451. def _get_controlport(self, my):
  452. return my['controlport_base']+my['nodenum']
  453. def _get_socksport(self, my):
  454. return my['socksport_base']+my['nodenum']
  455. def _get_dirport(self, my):
  456. return my['dirport_base']+my['nodenum']
  457. def _get_dir(self, my):
  458. return os.path.abspath(os.path.join(my['net_base_dir'],
  459. "nodes",
  460. "%03d%s"%(my['nodenum'], my['tag'])))
  461. def _get_nick(self, my):
  462. return "test%03d%s"%(my['nodenum'], my['tag'])
  463. def _get_tor_gencert(self, my):
  464. return my['tor']+"-gencert"
  465. def _get_auth_passphrase(self, my):
  466. return self['nick'] # OMG TEH SECURE!
  467. def _get_torrc_template_path(self, my):
  468. return [ os.path.join(my['chutney_dir'], 'torrc_templates') ]
  469. class Network(object):
  470. """A network of Tor nodes, plus functions to manipulate them
  471. """
  472. def __init__(self,defaultEnviron):
  473. self._nodes = []
  474. self._dfltEnv = defaultEnviron
  475. self._nextnodenum = 0
  476. def _addNode(self, n):
  477. n.setNodenum(self._nextnodenum)
  478. self._nextnodenum += 1
  479. self._nodes.append(n)
  480. def _checkConfig(self):
  481. for n in self._nodes:
  482. n.getBuilder().checkConfig(self)
  483. def configure(self):
  484. network = self
  485. altauthlines = []
  486. bridgelines = []
  487. builders = [ n.getBuilder() for n in self._nodes ]
  488. self._checkConfig()
  489. # XXX don't change node names or types or count if anything is
  490. # XXX running!
  491. for b in builders:
  492. b.preConfig(network)
  493. altauthlines.append(b._getAltAuthLines(
  494. self._dfltEnv['hasbridgeauth']))
  495. bridgelines.append(b._getBridgeLines())
  496. self._dfltEnv['authorities'] = "".join(altauthlines)
  497. self._dfltEnv['bridges'] = "".join(bridgelines)
  498. for b in builders:
  499. b.config(network)
  500. for b in builders:
  501. b.postConfig(network)
  502. def status(self):
  503. statuses = [ n.getController().check() for n in self._nodes]
  504. n_ok = len([x for x in statuses if x])
  505. print "%d/%d nodes are running"%(n_ok,len(self._nodes))
  506. if n_ok != len(self._nodes):
  507. return False
  508. return True
  509. def restart(self):
  510. self.stop()
  511. self.start()
  512. def start(self):
  513. print "Starting nodes"
  514. return all([n.getController().start() for n in self._nodes])
  515. def hup(self):
  516. print "Sending SIGHUP to nodes"
  517. return all([n.getController().hup() for n in self._nodes])
  518. def stop(self):
  519. controllers = [ n.getController() for n in self._nodes ]
  520. for sig, desc in [(signal.SIGINT, "SIGINT"),
  521. (signal.SIGINT, "another SIGINT"),
  522. (signal.SIGKILL, "SIGKILL")]:
  523. print "Sending %s to nodes"%desc
  524. for c in controllers:
  525. if c.isRunning():
  526. c.stop(sig=sig)
  527. print "Waiting for nodes to finish."
  528. for n in xrange(15):
  529. time.sleep(1)
  530. if all(not c.isRunning() for c in controllers):
  531. return
  532. sys.stdout.write(".")
  533. sys.stdout.flush()
  534. for c in controllers:
  535. c.check(listNonRunning=False)
  536. def verify(self):
  537. sys.stdout.write("Verifying data transmission: ")
  538. sys.stdout.flush()
  539. status = self._verify_traffic()
  540. if status:
  541. print("Success")
  542. else:
  543. print("Failure")
  544. return status
  545. def _verify_traffic(self):
  546. """Verify (parts of) the network by sending traffic through it
  547. and verify what is received."""
  548. LISTEN_PORT = 4747 # FIXME: Do better! Note the default exit policy.
  549. DATALEN = 10*1024 # Octets.
  550. TIMEOUT = 3 # Seconds.
  551. with open('/dev/urandom', 'r') as randfp:
  552. tmpdata = randfp.read(DATALEN)
  553. bind_to = ('127.0.0.1', LISTEN_PORT)
  554. tt = chutney.Traffic.TrafficTester(bind_to, tmpdata, TIMEOUT)
  555. for op in filter(lambda n: n._env['tag'] == 'c', self._nodes):
  556. tt.add(chutney.Traffic.Source(tt, bind_to, tmpdata,
  557. ('localhost', int(op._env['socksport']))))
  558. return tt.run()
  559. def ConfigureNodes(nodelist):
  560. network = _THE_NETWORK
  561. for n in nodelist:
  562. network._addNode(n)
  563. if n._env['bridgeauthority']:
  564. network._dfltEnv['hasbridgeauth'] = True
  565. def usage(network):
  566. return "\n".join(["Usage: chutney {command} {networkfile}",
  567. "Known commands are: %s" % (
  568. " ".join(x for x in dir(network) if not x.startswith("_")))])
  569. def runConfigFile(verb, f):
  570. _GLOBALS = dict(_BASE_ENVIRON= _BASE_ENVIRON,
  571. Node=Node,
  572. ConfigureNodes=ConfigureNodes,
  573. _THE_NETWORK=_THE_NETWORK)
  574. exec f in _GLOBALS
  575. network = _GLOBALS['_THE_NETWORK']
  576. if not hasattr(network, verb):
  577. print usage(network)
  578. print "Error: I don't know how to %s." % verb
  579. return
  580. return getattr(network,verb)()
  581. def main():
  582. global _BASE_ENVIRON
  583. global _THE_NETWORK
  584. _BASE_ENVIRON = TorEnviron(chutney.Templating.Environ(**DEFAULTS))
  585. _THE_NETWORK = Network(_BASE_ENVIRON)
  586. if len(sys.argv) < 3:
  587. print usage(_THE_NETWORK)
  588. print "Error: Not enough arguments given."
  589. sys.exit(1)
  590. f = open(sys.argv[2])
  591. result = runConfigFile(sys.argv[1], f)
  592. if result is False:
  593. return -1
  594. return 0
  595. if __name__ == '__main__':
  596. sys.exit(main())