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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
|
import functools
from twisted.internet import defer
from txtorcon.interface import ITorControlProtocol
class MagicContainer(object):
"""
This merely contains 1 or more methods or further MagicContainer
instances; see _do_setup in TorInfo.
Once _setup_complete() is called, this behaves differently so that
one can get nicer access to GETINFO things from TorInfo --
specifically dir() and so forth pretend that there are only
methods/attributes that pertain to actual Tor GETINFO keys.
See TorInfo.
"""
def __init__(self, n):
self._txtorcon_name = n
self.attrs = {}
self._setup = False
def _setup_complete(self):
self._setup = True
def _add_attribute(self, n, v):
self.attrs[n] = v
def __repr__(self):
return object.__getattribute__(self, '_txtorcon_name')
def __getitem__(self, idx):
return object.__getattribute__(self, 'attrs').items()[idx][1]
def __len__(self):
return len(object.__getattribute__(self, 'attrs'))
def __getattribute__(self, name):
sup = super(MagicContainer, self)
if sup.__getattribute__('_setup') is False:
return sup.__getattribute__(name)
attrs = sup.__getattribute__('attrs')
if name == '__members__':
return attrs.keys()
else:
try:
return attrs[name]
except KeyError:
if name in ['dump']:
return object.__getattribute__(self, name)
raise AttributeError(name)
def dump(self, prefix):
prefix = prefix + '.' + object.__getattribute__(self, '_txtorcon_name')
for x in object.__getattribute__(self, 'attrs').values():
x.dump(prefix)
class ConfigMethod(object):
def __init__(self, info_key, protocol, takes_arg=False):
self.info_key = info_key
self.proto = protocol
self.takes_arg = takes_arg
def dump(self, prefix):
n = self.info_key.replace('/', '.')
n = n.replace('-', '_')
s = '%s(%s)' % (n, 'arg' if self.takes_arg else '')
#print s
return s
def __call__(self, *args):
if self.takes_arg:
if len(args) != 1:
raise TypeError('"%s" takes exactly one argument' % self.info_key)
req = '%s/%s' % (self.info_key, str(args[0]))
else:
if len(args) != 0:
raise TypeError('"%s" takes no arguments' % self.info_key)
req = self.info_key
def stripper(key, arg):
## strip "keyname="
## sometimes keyname= is followed by a newline, so the final .strip()
return arg.strip()[len(key) + 1:].strip()
return self.proto.get_info_raw(req).addCallback(functools.partial(stripper, req))
def __str__(self):
arg = ''
if self.takes_arg:
arg = 'arg'
return '%s(%s)' % (self.info_key.replace('-', '_'), arg)
class TorInfo(object):
"""
Implements some attribute magic over top of TorControlProtocol so
that all the available GETINFO values are gettable in a little
easier fashion. Dashes are replaced by underscores (since dashes
aren't valid in method/attribute names for Python). Some of the
magic methods will take a single string argument if the
corresponding Tor GETINFO would take one (in 'GETINFO info/names'
it will end with '/*', and the same in torspec). In either case,
the method returns a Deferred which will callback with the
requested value, always a string.
For example (see also examples/tor_info.py):
proto = TorControlProtocol()
#...
def cb(arg):
print arg
info = TorInfo(proto)
info.traffic.written().addCallback(cb)
info.ip_to_country('8.8.8.8').addCallback(cb)
For interactive use -- or even checking things progammatically -- TorInfo
pretends it only has attributes that coorespond to valid GETINFO calls.
So for example, dir(info) will only return all the currently valid top-level
things. In the above example this might be ['traffic', 'ip_to_country'] (of
course in practice this is a much longer list). And "dir(info.traffic)" might
return ['read', 'written']
For something similar to this for configuration (GETCONF, SETCONF) see
TorConfig which is quite a lot more complicated (internally) since you can set
config items.
NOTE that 'GETINFO config/*' is not supported as it's the only case that's not a
leaf, but theoretically a method.
"""
def __init__(self, control, errback=None):
self._setup = False
self.attrs = {}
'''After _setup is True, these are all we show as attributes.'''
self.protocol = ITorControlProtocol(control)
self.errback = errback
self.post_bootstrap = defer.Deferred()
if self.protocol.post_bootstrap:
self.protocol.post_bootstrap.addCallback(self.bootstrap)
else:
self.bootstrap()
def _add_attribute(self, n, v):
self.attrs[n] = v
## iterator protocol
def __getitem__(self, idx):
return object.__getattribute__(self, 'attrs').items()[idx][1]
def __len__(self):
return len(object.__getattribute__(self, 'attrs'))
## change our attribute behavior based on the value of _setup
def __getattribute__(self, name):
sup = super(TorInfo, self)
if sup.__getattribute__('_setup') is False:
return sup.__getattribute__(name)
attrs = sup.__getattribute__('attrs')
if name == '__members__':
return attrs.keys()
else:
try:
return attrs[name]
except KeyError:
if name == 'dump':
return object.__getattribute__(self, name)
raise AttributeError(name)
def bootstrap(self, *args):
d = self.protocol.get_info_raw("info/names").addCallback(self._do_setup)
if self.errback:
d.addErrback(self.errback)
d.addCallback(self._setup_complete)
return d
def dump(self):
for x in object.__getattribute__(self, 'attrs').values():
x.dump('')
def _do_setup(self, data):
# FIXME figure out why network-status doesn't work (get nothing back from
# Tor it seems, although stem does get an answer). this is a space-separated
# list of ~2500 OR id's; could it be that LineReceiver can't handle it?
added_magic = []
for line in data.split('\n'):
if line == "info/names=" or line.strip() == '':
continue
#print "LINE:",line
(name, documentation) = line.split(' ', 1)
## FIXME think about this -- this is the only case where
## there's something that's a directory
## (i.e. MagicContainer) AND needs to be a ConfigMethod as
## well...but doesn't really seem very useful. Somewhat
## simpler to not support this case for now...
if name == 'config/*':
continue
if name.endswith('/*'):
## this takes an arg, so make a method
bits = name[:-2].split('/')
takes_arg = True
else:
bits = name.split('/')
takes_arg = False
mine = self
for bit in bits[:-1]:
bit = bit.replace('-', '_')
if bit in mine.attrs:
mine = mine.attrs[bit]
if not isinstance(mine, MagicContainer):
raise RuntimeError("Already had something: %s for %s" % (bit, name))
else:
c = MagicContainer(bit)
added_magic.append(c)
mine._add_attribute(bit, c)
mine = c
n = bits[-1].replace('-', '_')
if n in mine.attrs:
raise RuntimeError("Already had something: %s for %s" % (n, name))
mine._add_attribute(n, ConfigMethod('/'.join(bits), self.protocol, takes_arg))
for c in added_magic:
c._setup_complete()
return None
def _setup_complete(self, *args):
pb = self.post_bootstrap
self._setup = True
pb.callback(self)
|