3 # Imports a keyring into the database
4 # Copyright (C) 2007 Anthony Towns <aj@erisian.com.au>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 ################################################################################
22 from daklib import database
23 from daklib import utils
25 import apt_pkg, pg, ldap, email.Utils
32 ################################################################################
37 q = projectB.query("SELECT id, uid, name FROM uid")
38 for (id, uid, name) in q.getresult():
39 byname[uid] = (id, name)
40 byid[id] = (uid, name)
43 def get_fingerprint_info():
45 q = projectB.query("SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f")
46 for (fingerprint, fingerprint_id, uid, keyring) in q.getresult():
47 fins[fingerprint] = (uid, fingerprint_id, keyring)
50 ################################################################################
52 def get_ldap_name(entry):
54 for k in ["cn", "mn", "sn"]:
56 if ret and ret[0] != "" and ret[0] != "-":
60 ################################################################################
63 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
64 " --with-colons --fingerprint --fingerprint"
68 def de_escape_gpg_str(self, str):
69 esclist = re.split(r'(\\x..)', str)
70 for x in range(1,len(esclist),2):
71 esclist[x] = "%c" % (int(esclist[x][2:],16))
72 return "".join(esclist)
74 def __init__(self, keyring):
75 k = os.popen(self.gpg_invocation % keyring, "r")
78 fpr_lookup = self.fpr_lookup
80 for line in k.xreadlines():
81 field = line.split(":")
84 (name, addr) = email.Utils.parseaddr(field[9])
85 name = re.sub(r"\s*[(].*[)]", "", name)
86 if name == "" or addr == "" or "@" not in addr:
89 name = self.de_escape_gpg_str(name)
90 keys[key] = {"email": addr}
91 if name != "": keys[key]["name"] = name
92 keys[key]["aliases"] = [name]
93 keys[key]["fingerprints"] = []
95 elif key and field[0] == "sub" and len(field) >= 12:
96 signingkey = ("s" in field[11])
97 elif key and field[0] == "uid":
98 (name, addr) = email.Utils.parseaddr(field[9])
99 if name and name not in keys[key]["aliases"]:
100 keys[key]["aliases"].append(name)
101 elif signingkey and field[0] == "fpr":
102 keys[key]["fingerprints"].append(field[9])
103 fpr_lookup[field[9]] = key
105 def generate_desired_users(self):
106 if Options["Generate-Users"]:
107 format = Options["Generate-Users"]
108 return self.generate_users_from_keyring(format)
109 if Options["Import-Ldap-Users"]:
110 return self.import_users_from_ldap()
113 def import_users_from_ldap(self):
114 LDAPDn = Cnf["Import-LDAP-Fingerprints::LDAPDn"]
115 LDAPServer = Cnf["Import-LDAP-Fingerprints::LDAPServer"]
116 l = ldap.open(LDAPServer)
117 l.simple_bind_s("","")
118 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
119 "(&(keyfingerprint=*)(gidnumber=%s))" % (Cnf["Import-Users-From-Passwd::ValidGID"]),
120 ["uid", "keyfingerprint", "cn", "mn", "sn"])
127 fpr_lookup = self.fpr_lookup
131 uid = entry["uid"][0]
132 name = get_ldap_name(entry)
133 fingerprints = entry["keyFingerPrint"]
135 for f in fingerprints:
136 key = fpr_lookup.get(f, None)
137 if key not in keys: continue
138 keys[key]["uid"] = uid
140 if id != None: continue
141 id = database.get_or_set_uid_id(uid)
142 byuid[id] = (uid, name)
143 byname[uid] = (id, name)
145 return (byname, byuid)
147 def generate_users_from_keyring(self, format):
152 for x in keys.keys():
153 if keys[x]["email"] == "invalid-uid":
155 keys[x]["uid"] = format % "invalid-uid"
157 uid = format % keys[x]["email"]
158 id = database.get_or_set_uid_id(uid)
159 byuid[id] = (uid, keys[x]["name"])
160 byname[uid] = (id, keys[x]["name"])
163 uid = format % "invalid-uid"
164 id = database.get_or_set_uid_id(uid)
165 byuid[id] = (uid, "ungeneratable user id")
166 byname[uid] = (id, "ungeneratable user id")
167 return (byname, byuid)
169 ################################################################################
171 def usage (exit_code=0):
172 print """Usage: dak import-keyring [OPTION]... [KEYRING]
173 -h, --help show this help and exit.
174 -L, --import-ldap-users generate uid entries for keyring from LDAP
175 -U, --generate-users FMT generate uid entries from keyring as FMT
176 -D, --debian-maintainer mark generated uids as debian-maintainers"""
180 ################################################################################
183 global Cnf, projectB, Options
185 Cnf = utils.get_conf()
186 Arguments = [('h',"help","Import-Keyring::Options::Help"),
187 ('L',"import-ldap-users","Import-Keyring::Options::Import-Ldap-Users"),
188 ('U',"generate-users","Import-Keyring::Options::Generate-Users", "HasArg"),
189 ('D',"debian-maintainer","Import-Keyring::Options::Debian-Maintainer"),
192 for i in [ "help", "report-changes", "generate-users", "import-ldap-users" ]:
193 if not Cnf.has_key("Import-Keyring::Options::%s" % (i)):
194 Cnf["Import-Keyring::Options::%s" % (i)] = ""
196 keyring_names = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
200 Options = Cnf.SubTree("Import-Keyring::Options")
204 if len(keyring_names) != 1:
207 ### Keep track of changes made
209 changes = [] # (uid, changes strings)
213 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
214 database.init(Cnf, projectB)
216 projectB.query("BEGIN WORK")
218 ### Cache all the existing fingerprint entries
220 db_fin_info = get_fingerprint_info()
222 ### Parse the keyring
224 keyringname = keyring_names[0]
225 keyring = Keyring(keyringname)
227 keyring_id = database.get_or_set_keyring_id(
228 keyringname.split("/")[-1])
230 ### Generate new uid entries if they're needed (from LDAP or the keyring)
231 (desuid_byname, desuid_byid) = keyring.generate_desired_users()
233 ### Cache all the existing uid entries
234 (db_uid_byname, db_uid_byid) = get_uid_info()
236 ### Update full names of applicable users
237 for id in desuid_byid.keys():
238 uid = (id, desuid_byid[id][0])
239 name = desuid_byid[id][1]
240 oname = db_uid_byid[id][1]
241 if name and oname != name:
242 changes.append((uid[1], "Full name: %s" % (name)))
243 projectB.query("UPDATE uid SET name = '%s' WHERE id = %s" %
244 (pg.escape_string(name), id))
246 # The fingerprint table (fpr) points to a uid and a keyring.
247 # If the uid is being decided here (ldap/generate) we set it to it.
248 # Otherwise, if the fingerprint table already has a uid (which we've
249 # cached earlier), we preserve it.
250 # Otherwise we leave it as None
253 for z in keyring.keys.keys():
254 id = db_uid_byname.get(keyring.keys[z].get("uid", None), [None])[0]
256 id = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0]
257 for y in keyring.keys[z]["fingerprints"]:
258 fpr[y] = (id,keyring_id)
260 # For any keys that used to be in this keyring, disassociate them.
261 # We don't change the uid, leaving that for historical info; if
262 # the id should change, it'll be set when importing another keyring.
264 for f,(u,fid,kr) in db_fin_info.iteritems():
265 if kr != keyring_id: continue
266 if f in fpr: continue
267 changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s" % (f)))
268 projectB.query("UPDATE fingerprint SET keyring = NULL WHERE id = %d" % (fid))
270 # For the keys in this keyring, add/update any fingerprints that've
273 # Determine if we need to set the DM flag
275 if Cnf("Import-Keyring::Options::Debian-Maintainer"):
280 newuiduid = db_uid_byid.get(newuid, [None])[0]
281 (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1,-1,-1])
282 if olduid == None: olduid = -1
283 if oldkid == None: oldkid = -1
285 changes.append((newuiduid, "Added key: %s" % (f)))
287 projectB.query("INSERT INTO fingerprint (fingerprint, uid, keyring, debian_maintainer) VALUES ('%s', %d, %d, %s)" % (f, newuid, keyring_id, is_dm))
289 projectB.query("INSERT INTO fingerprint (fingerprint, keyring) VALUES ('%s', %d, %s)" % (f, keyring_id, is_dm))
291 if newuid and olduid != newuid:
293 changes.append((newuiduid, "Linked key: %s" % f))
294 changes.append((newuiduid, " (formerly belonging to %s)" % (db_uid_byid[olduid][0])))
296 changes.append((newuiduid, "Linked key: %s" % f))
297 changes.append((newuiduid, " (formerly unowned)"))
298 projectB.query("UPDATE fingerprint SET uid = %d WHERE id = %d" % (newuid, oldfid))
300 if oldkid != keyring_id:
301 projectB.query("UPDATE fingerprint SET keyring = %d WHERE id = %d" % (keyring_id, oldfid))
305 projectB.query("COMMIT WORK")
308 for (k, v) in changes:
309 if k not in changesd: changesd[k] = ""
310 changesd[k] += " %s\n" % (v)
312 keys = changesd.keys()
315 print "%s\n%s\n" % (k, changesd[k])
317 ################################################################################
319 if __name__ == '__main__':