chiark / gitweb /
cryptomail: Implement info and revoke commands.
[cryptomail] / bin / cryptomail
index 27beea296f557688db5dc7921d69bd18ab944bb6..3a24d042cf639c3b1506aecaffb9225d0229fdbc 100755 (executable)
@@ -221,12 +221,22 @@ class CMDB (AttrDB):
                            (SELECT attrset FROM expiry WHERE time < ?)''',
                 [now])
     cur.execute('DELETE FROM expiry WHERE time < ?', [now])
+    cur.execute('''DELETE FROM expiry WHERE attrset IN
+                           (SELECT attrset
+                            FROM expiry LEFT JOIN attrset
+                            ON expiry.attrset = attrset.id
+                            WHERE attrset.id ISNULL)''')
     AttrDB.cleanup(me)
-  def expiredp(me, id):
+  def expiry(me, id):
     for t, in me.select('SELECT time FROM expiry WHERE attrset = ?', [id]):
-      if t < time_format():
-        return True
-    return False
+      return t
+    return None
+  def expiredp(me, id):
+    t = me.expiry(id)
+    if t is not None and t < time_format():
+      return True
+    else:
+      return False
   def setexpire(me, id, when):
     if when != C.KEXP_FOREVER:
       cur = me.db.cursor()
@@ -369,6 +379,7 @@ def check(db, id, sender = None, msgfile = None):
 keyfile = 'db/keyring'
 tag = 'cryptomail'
 dbfile = 'db/cryptomail.db'
+user = None
 commands = {}
 
 def timecmp(x, y):
@@ -383,8 +394,9 @@ def timecmp(x, y):
 
 def cmd_generate(argv):
   try:
-    opts, argv = getopt(argv, 't:c:f:',
-                        ['expire=', 'timeout=', 'constraint=', 'format='])
+    opts, argv = getopt(argv, 't:c:f:i:',
+                        ['expire=', 'timeout=', 'constraint=',
+                         'info=', 'format='])
   except GetoptError:
     return 1
   kr = C.KeyFile(keyfile, C.KOPEN_WRITE)
@@ -406,6 +418,8 @@ def cmd_generate(argv):
       map.setdefault(c, []).append(v)
     elif o in ('-f', '--format'):
       format = a
+    elif o in ('-i', '--info'):
+      map['info'] = [a]
     else:
       raise 'Barf!'
   if timecmp(expwhen, k.deltime) > 0:
@@ -416,6 +430,8 @@ def cmd_generate(argv):
   a = AttrMultiMap(db)
   a.update(map)
   a['addr'] = [addr]
+  if user is not None:
+    a['user'] = [user]
   c = Crypto(k).encrypt(a.id)
   db.setexpire(a.id, expwhen)
   print format.replace('%', M.base32_encode(Crypto(k).encrypt(a.id)).
@@ -449,18 +465,22 @@ commands['initdb'] = \
   (cmd_initdb, '', """
 Initialize an attribute database.""")
 
+def getid(local):
+  k = C.KeyFile(keyfile, C.KOPEN_READ)[tag]
+  id = Crypto(k).decrypt(M.base32_decode(local))
+  if id is None:
+    raise Reject, 'decrypt failed'
+  return id
+
 def cmd_addrcheck(argv):
   try:
     opts, argv = getopt(argv, '', [])
   except GetoptError:
     return 1
   local, sender = (lambda addr, sender = None, *hunoz: (addr, sender))(*argv)
-  k = C.KeyFile(keyfile, C.KOPEN_READ)[tag]
   db = CMDB(dbfile)
   try:
-    id = Crypto(k).decrypt(M.base32_decode(local))
-    if id is None:
-      raise Reject, 'decrypt failed'
+    id = getid(local)
     addr = check(db, id, sender)
   except Reject, msg:
     print '-%s' % msg
@@ -476,11 +496,12 @@ def cmd_fwaddr(argv):
     opts, argv = getopt(argv, '', [])
   except GetoptError:
     return 1
-  local, sender = (lambda addr, sender = None, *hunoz: (addr, sender))(*argv)
-  k = C.KeyFile(keyfile, C.KOPEN_READ)[tag]
+  if len(argv) not in (1, 2):
+    return 1
+  local, sender = (lambda addr, sender = None: (addr, sender))(*argv)
   db = CMDB(dbfile)
   try:
-    id = Crypto(k).decrypt(M.base32_decode(local))
+    id = getid(local)
     if id is None:
       raise Reject, 'decrypt failed'
     addr = check(db, id, sender, stdin)
@@ -490,11 +511,69 @@ def cmd_fwaddr(argv):
   stdin.seek(0)
   print addr
 commands['fwaddr'] = \
-  (cmd_fwaddr, 'LOCAL [SENDER [IGNORED ...]]', """
+  (cmd_fwaddr, 'LOCAL [SENDER]', """
 Check address token LOCAL.  On failure, report reason to stderr and exit
 111.  On success, write forwarding address to stdout and exit 0.  Expects
 the message on standard input, as a seekable file.""")
 
+def cmd_info(argv):
+  try:
+    opts, argv = getopt(argv, '', [])
+  except GetoptError:
+    return 1
+  if len(argv) != 1:
+    return 1
+  local = argv[0]
+  db = CMDB(dbfile)
+  try:
+    id = getid(local)
+    a = AttrMultiMap(db, id)
+    if user is not None and user != a.get('user', [None])[0]:
+      raise Reject, 'not your token'
+    if 'addr' not in a:
+      die('unknown token (expired?)')
+    keys = a.keys()
+    keys.sort()
+    for k in keys:
+      for v in a[k]:
+        print '%s: %s' % (k, v)
+    expwhen = db.expiry(id)
+    if expwhen:
+      print 'expires: %s'
+    else:
+      print 'no-expiry'
+  except Reject, msg:
+    die('invalid token')
+commands['info'] = \
+  (cmd_info, 'LOCAL', """
+Exaimne the address token LOCAL, and print information about it to standard
+output.""")
+
+def cmd_revoke(argv):
+  try:
+    opts, argv = getopt(argv, '', [])
+  except GetoptError:
+    return 1
+  if len(argv) != 1:
+    return 1
+  local = argv[0]
+  db = CMDB(dbfile)
+  try:
+    id = getid(local)
+    a = AttrMultiMap(db, id)
+    if user is not None and user != a.get('user', [None])[0]:
+      raise Reject, 'not your token'
+    if 'addr' not in a:
+      die('unknown token (expired?)')
+    a.clear()
+    db.cleanup()
+    db.commit()
+  except Reject, msg:
+    die('invalid token')
+commands['revoke'] = \
+  (cmd_revoke, 'LOCAL', """
+Revoke the token LOCAL.""")
+
 def cmd_cleanup(argv):
   try:
     opts, argv = getopt(argv, '', [])
@@ -544,6 +623,7 @@ Global options:
   -d, --database=FILE          Use FILE as the attribute database.
   -k, --keyring=KEYRING                Use KEYRING as the keyring.
   -t, --tag=TAG                        Use TAG as the key tag.
+  -U, --user=USER              Claim to be USER.
 """
     cmds = commands.keys()
     cmds.sort()
@@ -566,12 +646,12 @@ def help():
   cmd_help()  
 
 def main():
-  global argv
+  global argv, user, keyfile, dbfile, tag
   try:
     opts, argv = getopt(argv[1:],
-                        'hvud:k:t:',
+                        'hvud:k:t:U:',
                         ['help', 'version', 'usage',
-                         'database=', 'keyring=', 'tag='])
+                         'database=', 'keyring=', 'tag=', 'user='])
   except GetoptError:
     usage(stderr)
     exit(111)
@@ -591,6 +671,8 @@ def main():
       keyfile = a
     elif o in ('-t', '--tag'):
       tag = a
+    elif o in ('-U', '--user'):
+      user = a
     else:
       raise 'Barf!'
   if len(argv) < 1: