]> git.decadent.org.uk Git - dak.git/commitdiff
New functionality for dak: import-keyring
authorAnthony Towns <aj@azure.humbug.org.au>
Wed, 5 Dec 2007 06:42:36 +0000 (06:42 +0000)
committerAnthony Towns <aj@azure.humbug.org.au>
Wed, 5 Dec 2007 06:42:36 +0000 (06:42 +0000)
dak/import_keyring.py [new file with mode: 0755]

diff --git a/dak/import_keyring.py b/dak/import_keyring.py
new file mode 100755 (executable)
index 0000000..f61cc2c
--- /dev/null
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+
+# Imports a keyring into the database
+# Copyright (C) 2007  Anthony Towns <aj@erisian.com.au>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+################################################################################
+
+import daklib.database, daklib.logging
+import apt_pkg, pg
+import sys, os, email.Utils, re
+
+# Globals
+Cnf = None
+Options = None
+projectB = None
+Logger = None
+
+################################################################################
+
+# These could possibly be daklib.db functions, and reused by
+# import-ldap-fingerprints
+
+def get_uid_info():
+    byname = {}
+    byid = {}
+    q = projectB.query("SELECT id, uid, name FROM uid")
+    for (id, uid, name) in q.getresult():
+        byname[uid] = (id, name)
+       byid[id] = (uid, name)
+    return (byname, byid)
+
+def get_fingerprint_info():
+    fins = {}
+    q = projectB.query("SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f")
+    for (fingerprint, fingerprint_id, uid, keyring) in q.getresult():
+        fins[fingerprint] = (uid, fingerprint_id, keyring)
+    return fins
+
+################################################################################
+
+class Keyring:
+       gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
+                        " --with-colons --fingerprint --fingerprint"
+       keys = {}
+
+       def de_escape_str(self, str):
+               esclist = re.split(r'(\\x..)', str)
+               for x in range(1,len(esclist),2):
+                       esclist[x] = "%c" % (int(esclist[x][2:],16))
+               return "".join(esclist)
+
+       def __init__(self, keyring):
+               k = os.popen(self.gpg_invocation % keyring, "r")
+               keys = self.keys
+               key = None
+               signingkey = False
+               for line in k.xreadlines():
+                       field = line.split(":")
+                       if field[0] == "pub":
+                               key = field[4]
+                               (name, addr) = email.Utils.parseaddr(field[9])
+                               name = re.sub(r"\s*[(].*[)]", "", name)
+                               if name == "" or addr == "" or "@" not in addr:
+                                       name = field[9]
+                                       addr = "invalid-uid"
+                               name = self.de_escape_str(name)
+                               keys[key] = {"email": addr}
+                               if name != "": keys[key]["name"] = name
+                               keys[key]["aliases"] = [name]
+                               keys[key]["fingerprints"] = []
+                               signingkey = True
+                       elif key and field[0] == "sub" and len(field) >= 12:
+                               signingkey = ("s" in field[11])
+                       elif key and field[0] == "uid":
+                               (name, addr) = email.Utils.parseaddr(field[9])
+                               if name and name not in keys[key]["aliases"]:
+                                       keys[key]["aliases"].append(name)
+                       elif signingkey and field[0] == "fpr":
+                               keys[key]["fingerprints"].append(field[9])
+
+       def desired_users(self, format="%s"):
+               if not Options["Generate-Users"]:
+                       return ({}, {})
+
+               byuid = {}
+               byname = {}
+               keys = self.keys
+               any_invalid = False
+               for x in keys.keys():
+                       if keys[x]["email"] == "invalid-uid":
+                               any_invalid = True
+                       else:
+                               uid = format % keys[x]["email"]
+                               id = daklib.database.get_or_set_uid_id(uid)
+                               byuid[id] = (uid, keys[x]["name"])
+                               byname[uid] = (id, keys[x]["name"])
+               if any_invalid:
+                       uid = format % "invalid-uid"
+                       id = daklib.database.get_or_set_uid_id(uid)
+                       byuid[id] = (uid, "ungeneratable user id")
+                       byname[uid] = (id, "ungeneratable user id")
+               return (byname, byuid)
+
+################################################################################
+
+def usage (exit_code=0):
+    print """Usage: dak import-keyring [OPTION]... [KEYRING]
+  -h, --help                  show this help and exit.
+  -U, --generate-users FMT    generate uid entries from keyring as FMT"""
+    sys.exit(exit_code)
+
+
+################################################################################
+
+def main():
+    global Cnf, projectB, Options
+
+    Cnf = daklib.utils.get_conf()
+    Arguments = [('h',"help","Import-Keyring::Options::Help"),
+                ('U',"generate-users","Import-Keyring::Options::Generate-Users", "HasArg"),
+               ]
+
+    for i in [ "help", "report-changes", "generate-users" ]:
+        if not Cnf.has_key("Import-Keyring::Options::%s" % (i)):
+            Cnf["Import-Keyring::Options::%s" % (i)] = ""
+
+    keyring_names = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Import-Keyring::Options")
+    if Options["Help"]:
+        usage()
+
+    uid_format = "%s"
+    if Options["Generate-Users"]:
+        uid_format = Options["Generate-Users"]
+
+    changes = []   # (uid, changes strings)
+
+    projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
+    daklib.database.init(Cnf, projectB)
+
+    projectB.query("BEGIN WORK")
+
+    if len(keyring_names) != 1:
+       usage(1)
+
+    # Parse the keyring
+    keyringname = keyring_names[0]
+    keyring = Keyring(keyringname)
+
+    keyring_id = daklib.database.get_or_set_keyring_id(
+                       keyringname.split("/")[-1])
+
+    # If we're generating uids, make sure we have entries in the uid
+    # table for every uid
+    (desuid_byname, desuid_byid) = keyring.desired_users(uid_format)
+
+    # Cache all the existing fingerprint and uid entries
+    db_fin_info = get_fingerprint_info()
+    (db_uid_byname, db_uid_byid) = get_uid_info()
+
+    # Update full names of uids
+
+    for id in desuid_byid.keys():
+        uid = (id, desuid_byid[id][0])
+        name = desuid_byid[id][1]
+       oname = db_uid_byid[id][1]
+       if name and oname != name:
+           changes.append((uid[1], "Full name: %s\n" % (name)))
+            projectB.query("UPDATE uid SET name = '%s' WHERE id = %s" %
+               (pg.escape_string(name), id))
+
+    # Work out what the fingerprint table should look like for the keys
+    # in this keyring
+    fpr = {}
+    for z in keyring.keys.keys():
+       id = db_uid_byname.get(uid_format % keyring.keys[z]["email"], [None])[0]
+        if id == None:
+           id = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0]
+       for y in keyring.keys[z]["fingerprints"]:
+           fpr[y] = (id,keyring_id)
+
+    # For any keys that used to be in this keyring, disassociate them.
+    # We don't change the uid, leaving that to for historical info; if
+    # the id should change, it'll be set when importing another keyring
+    # or importing ldap fingerprints.
+
+    for f,(u,fid,kr) in db_fin_info.iteritems():
+        if kr != keyring_id: continue
+       if f in fpr: continue
+       changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s\n" % (f)))
+       projectB.query("UPDATE fingerprint SET keyring = NULL WHERE id = %d" % (fid))
+       
+    # For the keys in this keyring, add/update any fingerprints that've
+    # changed.
+
+    for f in fpr:
+        newuid = fpr[f][0]
+       newuiduid = db_uid_byid.get(newuid, [None])[0] 
+       (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1,-1,-1])
+       if olduid == None: olduid = -1
+       if oldkid == None: oldkid = -1
+       if oldfid == -1:
+           changes.append((newuiduid, "Added key: %s\n" % (f)))
+            if newuid:
+               projectB.query("INSERT INTO fingerprint (fingerprint, uid, keyring) VALUES ('%s', %d, %d)" % (f, newuid, keyring_id))
+           else:
+               projectB.query("INSERT INTO fingerprint (fingerprint, keyring) VALUES ('%s', %d)" % (f, keyring_id))
+       else:
+           if newuid and olduid != newuid:
+               if olduid != -1:
+                   changes.append((newuiduid, "Linked key: %s (formerly belonging to %s)" % (f, db_uid_byid[olduid][0])))
+               else:
+                   changes.append((newuiduid, "Linked key: %s (formerly unowned)\n" % (f)))
+               projectB.query("UPDATE fingerprint SET uid = %d WHERE id = %d" % (newuid, oldfid))
+
+           if oldkid != keyring_id:
+               projectB.query("UPDATE fingerprint SET keyring = %d WHERE id = %d" % (keyring_id, oldfid))
+
+    # All done!
+
+    projectB.query("COMMIT WORK")
+
+    changesd = {}
+    for (k, v) in changes:
+        if k not in changesd: changesd[k] = ""
+        changesd[k] += "    " + v
+
+    keys = changesd.keys()
+    keys.sort()
+    for k in keys:
+        print "%s\n%s" % (k, changesd[k])
+
+################################################################################
+
+if __name__ == '__main__':
+    main()