chiark / gitweb /
userv: Tidy up a bit. Require file descriptors to be right.
[cryptomail] / bin / cryptomail
index 0c0ed94faaa450a08159c2daf74e60958fc79168..9559acc49a194e1e96f29415f66fc8b17e465023 100755 (executable)
@@ -221,12 +221,22 @@ class CMDB (AttrDB):
                            (SELECT attrset FROM expiry WHERE time < ?)''',
                 [now])
     cur.execute('DELETE FROM expiry WHERE time < ?', [now])
                            (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)
     AttrDB.cleanup(me)
-  def expiredp(me, id):
+  def expiry(me, id):
     for t, in me.select('SELECT time FROM expiry WHERE attrset = ?', [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()
   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'
 keyfile = 'db/keyring'
 tag = 'cryptomail'
 dbfile = 'db/cryptomail.db'
+user = None
 commands = {}
 
 def timecmp(x, y):
 commands = {}
 
 def timecmp(x, y):
@@ -381,10 +392,14 @@ def timecmp(x, y):
   else:
     return cmp(x, y)
 
   else:
     return cmp(x, y)
 
+def token(c, id):
+  return M.base32_encode(c.encrypt(id)).strip('=').lower()
+
 def cmd_generate(argv):
   try:
 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)
   except GetoptError:
     return 1
   kr = C.KeyFile(keyfile, C.KOPEN_WRITE)
@@ -404,8 +419,10 @@ def cmd_generate(argv):
       if c not in constraints:
         die("unknown constraint `%s'", c)
       map.setdefault(c, []).append(v)
       if c not in constraints:
         die("unknown constraint `%s'", c)
       map.setdefault(c, []).append(v)
-    elif o in ('f', '--format'):
+    elif o in ('-f', '--format'):
       format = a
       format = a
+    elif o in ('-i', '--info'):
+      map['info'] = [a]
     else:
       raise 'Barf!'
   if timecmp(expwhen, k.deltime) > 0:
     else:
       raise 'Barf!'
   if timecmp(expwhen, k.deltime) > 0:
@@ -416,10 +433,10 @@ def cmd_generate(argv):
   a = AttrMultiMap(db)
   a.update(map)
   a['addr'] = [addr]
   a = AttrMultiMap(db)
   a.update(map)
   a['addr'] = [addr]
-  c = Crypto(k).encrypt(a.id)
+  if user is not None:
+    a['user'] = [user]
   db.setexpire(a.id, expwhen)
   db.setexpire(a.id, expwhen)
-  print format.replace('%', M.base32_encode(Crypto(k).encrypt(a.id)).
-                       strip('=').lower())
+  print format.replace('%', token(Crypto(k), a.id))
   db.commit()
   kr.save()
 commands['generate'] = \
   db.commit()
   kr.save()
 commands['generate'] = \
@@ -449,18 +466,22 @@ commands['initdb'] = \
   (cmd_initdb, '', """
 Initialize an attribute database.""")
 
   (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)
 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:
   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
     addr = check(db, id, sender)
   except Reject, msg:
     print '-%s' % msg
@@ -476,11 +497,12 @@ def cmd_fwaddr(argv):
     opts, argv = getopt(argv, '', [])
   except GetoptError:
     return 1
     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:
   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)
     if id is None:
       raise Reject, 'decrypt failed'
     addr = check(db, id, sender, stdin)
@@ -490,11 +512,103 @@ def cmd_fwaddr(argv):
   stdin.seek(0)
   print addr
 commands['fwaddr'] = \
   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.""")
 
 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.""")
 
+ignore = {'user': 1, 'addr': 1}
+def show(db, a):
+  keys = a.keys()
+  keys.sort()
+  for k in keys:
+    if k in ignore:
+      continue
+    for v in a[k]:
+      print '\t%s: %s' % (k, v)
+  expwhen = db.expiry(a.id)
+  if expwhen:
+    print '\texpires: %s' % expwhen
+  else:
+    print '\tno-expiry'
+
+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?)')
+    print 'addr: %s' % a['addr'][0]
+    show(db, a)
+  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_list(argv):
+  try:
+    opts, argv = getopt(argv, '', [])
+  except GetoptError:
+    return 1
+  if argv:
+    return 1
+  c = Crypto(C.KeyFile(keyfile, C.KOPEN_READ)[tag])
+  db = CMDB(dbfile)
+  if not user:
+    gen = db.select('SELECT DISTINCT id FROM attrset')
+  else:
+    gen = db.select('''SELECT DISTINCT attrset.id
+                               FROM attrset, attr ON attrset.attr = attr.id
+                               WHERE attr.key = 'user' AND attr.value = ?''',
+                    [user])
+  for id, in gen:
+    a = AttrMultiMap(db, id)    
+    print '%s %s%s' % \
+          (token(c, id),
+           a.get('addr', '<no-address>')[0],
+           (not user and ' [%s]' % a.get('user', ['<no-user>'])[0] or ''))
+    show(db, a)
+commands['list'] = \
+  (cmd_list, '', """
+List the user's tokens and information about them.""")
+
 def cmd_cleanup(argv):
   try:
     opts, argv = getopt(argv, '', [])
 def cmd_cleanup(argv):
   try:
     opts, argv = getopt(argv, '', [])
@@ -544,6 +658,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.
   -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()
 """
     cmds = commands.keys()
     cmds.sort()
@@ -566,12 +681,12 @@ def help():
   cmd_help()  
 
 def main():
   cmd_help()  
 
 def main():
-  global argv
+  global argv, user, keyfile, dbfile, tag
   try:
     opts, argv = getopt(argv[1:],
   try:
     opts, argv = getopt(argv[1:],
-                        'hvud:k:t:',
+                        'hvud:k:t:U:',
                         ['help', 'version', 'usage',
                         ['help', 'version', 'usage',
-                         'database=', 'keyring=', 'tag='])
+                         'database=', 'keyring=', 'tag=', 'user='])
   except GetoptError:
     usage(stderr)
     exit(111)
   except GetoptError:
     usage(stderr)
     exit(111)
@@ -591,6 +706,8 @@ def main():
       keyfile = a
     elif o in ('-t', '--tag'):
       tag = a
       keyfile = a
     elif o in ('-t', '--tag'):
       tag = a
+    elif o in ('-U', '--user'):
+      user = a
     else:
       raise 'Barf!'
   if len(argv) < 1:
     else:
       raise 'Barf!'
   if len(argv) < 1:
@@ -610,7 +727,9 @@ def main():
 
 try:
   main()
 
 try:
   main()
-except Exception:
+except SystemExit:
+  raise
+except:
   ty, exc, tb = exc_info()
   moan('unhandled %s exception' % ty.__name__)
   for file, line, func, text in TB.extract_tb(tb):
   ty, exc, tb = exc_info()
   moan('unhandled %s exception' % ty.__name__)
   for file, line, func, text in TB.extract_tb(tb):