diff --git a/sys/lib/python/factotum.py b/sys/lib/python/factotum.py new file mode 100644 index 000000000..4971459c0 --- /dev/null +++ b/sys/lib/python/factotum.py @@ -0,0 +1,57 @@ +'''factotum for py''' + +import subprocess + +class FactotumError(Exception): + pass + +class PhaseError(Exception): + pass + +class NeedkeyError(Exception): + pass + +class Factotum: + def start(self, **args): + self.f = open('/mnt/factotum/rpc', 'r+', 0) + msg = 'start' + for k, v in args.iteritems(): + msg += ' ' + k + '=\'' + v + '\'' + self.f.write(msg) + ret = self.f.read(4096) + if ret == "ok": return + if ret[:5] == "error": raise FactotumError(ret[6:]) + raise FactotumError("unexpected " + ret) + def needkey(self, string): + subprocess.call(['/bin/auth/factotum', '-g', string]) + def read(self): + while True: + self.f.write('read') + ret = self.f.read(4096) + if ret[:7] != "needkey": break + self.needkey(ret[8:]) + if ret == "ok": return "" + if ret[:3] == "ok ": return ret[3:] + if ret[:5] == "error": raise FactotumError(ret[6:]) + if ret[:5] == "phase": raise PhaseError(ret[6:]) + raise FactotumError("unexpected " + ret) + def write(self, data): + while True: + self.f.write('write ' + data) + ret = self.f.read(4096) + if ret[:7] != "needkey": break + self.needkey(ret[8:]) + if ret == "ok": return 0 + if ret[:3] == "toosmall ": return int(ret[4:]) + if ret[:5] == "error": raise FactotumError(ret[6:]) + if ret[:5] == "phase": raise PhaseError(ret[6:]) + raise FactotumError("unexpected " + ret) + def close(self): + self.f.close() + def delkey(self, **args): + f = open('/mnt/factotum/ctl', 'w', 0) + msg = 'delkey' + for k, v in args.iteritems(): + msg += ' ' + k + '=\'' + v + '\'' + f.write(msg) + f.close() diff --git a/sys/lib/python/hgext/hgfactotum.py b/sys/lib/python/hgext/hgfactotum.py new file mode 100644 index 000000000..2dbd6ca5d --- /dev/null +++ b/sys/lib/python/hgext/hgfactotum.py @@ -0,0 +1,37 @@ +''' factotum support ''' + +import mercurial.url +import urllib2 +import factotum + +class factotumdigest(urllib2.BaseHandler): + auth_header = 'Authorization' + handler_order = 490 + + def __init__(self, passmgr=None): + self.f = factotum.Factotum() + self.retried = 0 + def http_error_401(self, req, fp, code, msg, headers): + self.retried += 1 + host = urllib2.urlparse.urlparse(req.get_full_url())[1] + authreq = headers.get('www-authenticate', None) + if authreq == None: return None + authreq = authreq.split(' ', 1) + if authreq[0].lower() != 'digest': return None + chal = urllib2.parse_keqv_list(urllib2.parse_http_list(authreq[1])) + realm = chal['realm'] + nonce = chal['nonce'] + if self.retried >= 6: + self.f.delkey(proto="httpdigest", realm=realm) + self.f.start(proto="httpdigest", role="client", realm=realm) + self.f.write(nonce + ' ' + req.get_method() + ' ' + req.get_selector()) + resp = self.f.read() + self.f.close() + val = 'Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s", algorithm=MD5' % ("aiju", realm, nonce, req.get_selector(), resp) + if req.headers.get('Authorization', None) == val: return None + req.add_unredirected_header('Authorization', val) + result = self.parent.open(req) + self.retried = 0 + return result + +mercurial.url.httpdigestauthhandler = factotumdigest