# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import logging import os import re import sys import tempfile import random try: import _winreg except ImportError: _winreg = None import thandy.formats import thandy.keys import thandy.master_keys _jsonModule = None def importJSON(): """Load and return the json module. When everybody has Python 2.6 or later, we can just replace this with 'import json; return json' """ global _jsonModule if _jsonModule is not None: return _jsonModule for name in [ "json", "simplejson" ]: try: mod = __import__(name) except ImportError: continue if not hasattr(mod, "dumps"): # Some versions of Ubuntu have a module called 'json' that is # not a recognizable simplejson module. Naughty. if name == 'json': logging.warn("Your operating system has a nonfunctional json " "module. That's going to break any programs that " "use the real json module in Python 2.6. Trying " "simplejson instead.") continue # Some old versions of simplejson escape / as \/ in a misguided and # inadequate attempt to fix XSS attacks. Make them not do that. This # code is not guaranteed to work on all broken versions of simplejson: # it replaces an entry in the internal character-replacement # dictionary so that "/" is translated to itself rather than to \/. # We also need to make sure that ensure_ascii is False, so that we # do not call the C-optimized string encoder in these broken versions, # which we can't fix easily. Both parts are a kludge. try: escape_dct = mod.encoder.ESCAPE_DCT except NameError: pass else: if escape_dct.has_key("/"): escape_dct["/"] = "/" save_dumps = mod.dumps save_dump = mod.dump def dumps(*k, **v): v['ensure_ascii']=False return save_dumps(*k,**v) def dump(*k,**v): v['ensure_ascii']=False return save_dump(*k,**v) mod.dump = dump mod.dumps = dumps logging.warn("Your operating system has an old broken " "simplejson module. I tried to fix it for you.") _jsonModule = mod return mod raise ImportError("Couldn't import a working json module") json = importJSON() def moveFile(fromLocation, toLocation): """Move the file from fromLocation to toLocation, removing any file in toLocation. """ if sys.platform in ('cygwin', 'win32'): # Win32 doesn't let rename replace an existing file. try: os.unlink(toLocation) except OSError: pass os.rename(fromLocation, toLocation) def replaceFile(fname, contents, textMode=False): """overwrite the file in 'fname' atomically with the content of 'contents' """ dir, prefix = os.path.split(fname) fd, fname_tmp = tempfile.mkstemp(prefix=prefix, dir=dir, text=textMode) try: os.write(fd, contents) finally: os.close(fd) moveFile(fname_tmp, fname) def userFilename(name): """Return a path relative to $THANDY_HOME or ~/.thandy whose final path component is 'name', creating parent directories as needed.""" try: base = os.environ["THANDY_HOME"] except KeyError: base = "~/.thandy" base = os.path.expanduser(base) result = os.path.normpath(os.path.join(base, name)) ensureParentDir(result) return result def ensureParentDir(name): """If the parent directory of 'name' does not exist, create it.""" directory = os.path.split(name)[0] if not os.path.exists(directory): os.makedirs(directory, 0700) def getKeylist(keys_fname, checkKeys=True): """Return a Keylist() containing all the keys in master_keys, plus all the keys in $THANDY_HOME.preload_keys, plus all the keys stored in keys_fname. If check_keys, exclude from keys_fname any keys not signed by enough master keys. Do not allow master keys to occur in keys_fname.""" keydb = thandy.formats.Keylist() for key in thandy.master_keys.MASTER_KEYS: keydb.addKey(thandy.keys.RSAKey.fromJSon(key)) user_keys = userFilename("preload_keys") if os.path.exists(user_keys): #XXXX somewhat roundabout. keylist = thandy.formats.makeKeylistObj(user_keys) keydb.addFromKeylist(keylist, allowMasterKeys=True) if keys_fname and os.path.exists(keys_fname): f = open(keys_fname, 'r') try: obj = json.load(f) finally: f.close() ss, role, path = thandy.formats.checkSignedObj(obj, keydb) if role != 'master': raise thandy.FormatException("%s wasn't a keylist."%keys_fname) if checkKeys and not ss.isValid(): raise thandy.FormatException("%s not signed by enough master keys"% keys_fname) keydb.addFromKeylist(obj['signed'], allowMasterKeys=False) return keydb def randChooseWeighted(lst): """Given a list of (weight,item) tuples, pick an item with probability proportional to its weight. """ totalweight = sum(w for w,i in lst) position = random.uniform(0, totalweight) soFar = 0 # We could use bisect here, but this is not going to be in the # critical path. If it is, oops. for w,i in lst: soFar += w if position < soFar: return i return lst[-1][1] class NoRegistry(thandy.Exception): """Exception raised when we try to access the registry on a non-win32 machine.""" pass def getRegistryValue(keyname): """Read the contents of a Windows registry key from a given base.""" if _winreg is None: raise NoRegistry() hkey, rest = keyname.split("\\", 1) key, value = rest.rsplit("\\", 1) if not hkey.startswith("HKEY_"): return None base = getattr(_winreg, hkey) settings = None try: try: settings = _winreg.OpenKey(base, key) return _winreg.QueryValueEx(settings, value)[0] except (OSError, ValueError, TypeError): return None finally: if settings is not None: settings.Close() _controlLog = logging.getLogger("thandy-ctrl") def formatLogString(s): """DOCDOC""" s = '"%s"' % re.sub(r'(["\\])', r'\\\1', s) s = s.replace("\n", "\\n") return s def logCtrl(key, **args): """DOCDOC""" parts = [ key ] parts.extend( "%s=%s"%(k, formatLogString(v)) for k,v in sorted(args.iteritems())) _controlLog.log(logging.INFO, " ".join(parts)) def deltree(top): for dirpath, dirnames, filenames in os.walk(top, topdown=False): for f in filenames: os.unlink(os.path.join(dirpath, f)) for d in dirnames: os.rmdir(os.path.join(dirpath, d)) os.rmdir(top)