]> git.decadent.org.uk Git - dak.git/blob - dak/import_keyring.py
Merge branch 'master' into dm_upload_allowed
[dak.git] / dak / import_keyring.py
1 #!/usr/bin/env python
2
3 # Imports a keyring into the database
4 # Copyright (C) 2007  Anthony Towns <aj@erisian.com.au>
5
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.
10
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.
15
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
19
20 ################################################################################
21
22 from daklib import database
23 from daklib import utils
24 import sys, os, re
25 import apt_pkg, pg, ldap, email.Utils
26
27 # Globals
28 Cnf = None
29 Options = None
30 projectB = None
31
32 ################################################################################
33
34 def get_uid_info():
35     byname = {}
36     byid = {}
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)
41     return (byname, byid)
42
43 def get_fingerprint_info():
44     fins = {}
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)
48     return fins
49
50 ################################################################################
51
52 def get_ldap_name(entry):
53     name = []
54     for k in ["cn", "mn", "sn"]:
55         ret = entry.get(k)
56         if ret and ret[0] != "" and ret[0] != "-":
57             name.append(ret[0])
58     return " ".join(name)
59
60 ################################################################################
61
62 class Keyring:
63     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
64                      " --with-colons --fingerprint --fingerprint"
65     keys = {}
66     fpr_lookup = {}
67
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)
73
74     def __init__(self, keyring):
75         k = os.popen(self.gpg_invocation % keyring, "r")
76         keys = self.keys
77         key = None
78         fpr_lookup = self.fpr_lookup
79         signingkey = False
80         for line in k.xreadlines():
81             field = line.split(":")
82             if field[0] == "pub":
83                 key = field[4]
84                 (name, addr) = email.Utils.parseaddr(field[9])
85                 name = re.sub(r"\s*[(].*[)]", "", name)
86                 if name == "" or addr == "" or "@" not in addr:
87                     name = field[9]
88                     addr = "invalid-uid"
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"] = []
94                 signingkey = True
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
104
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()
111         return ({}, {})
112
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"])
121
122         ldap_fin_uid_id = {}
123
124         byuid = {}
125         byname = {}
126         keys = self.keys
127         fpr_lookup = self.fpr_lookup
128
129         for i in Attrs:
130             entry = i[1]
131             uid = entry["uid"][0]
132             name = get_ldap_name(entry)
133             fingerprints = entry["keyFingerPrint"]
134             id = None
135             for f in fingerprints:
136                 key = fpr_lookup.get(f, None)
137                 if key not in keys: continue
138                 keys[key]["uid"] = uid
139
140                 if id != None: continue
141                 id = database.get_or_set_uid_id(uid)
142                 byuid[id] = (uid, name)
143                 byname[uid] = (id, name)
144
145         return (byname, byuid)
146
147     def generate_users_from_keyring(self, format):
148         byuid = {}
149         byname = {}
150         keys = self.keys
151         any_invalid = False
152         for x in keys.keys():
153             if keys[x]["email"] == "invalid-uid":
154                 any_invalid = True
155                 keys[x]["uid"] = format % "invalid-uid"
156             else:
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"])
161                 keys[x]["uid"] = uid
162         if any_invalid:
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)
168
169 ################################################################################
170
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"""
177     sys.exit(exit_code)
178
179
180 ################################################################################
181
182 def main():
183     global Cnf, projectB, Options
184
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"),
190         ]
191
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)] = ""
195
196     keyring_names = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
197
198     ### Parse options
199
200     Options = Cnf.SubTree("Import-Keyring::Options")
201     if Options["Help"]:
202         usage()
203
204     if len(keyring_names) != 1:
205         usage(1)
206
207     ### Keep track of changes made
208
209     changes = []   # (uid, changes strings)
210
211     ### Initialise
212
213     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
214     database.init(Cnf, projectB)
215
216     projectB.query("BEGIN WORK")
217
218     ### Cache all the existing fingerprint entries
219
220     db_fin_info = get_fingerprint_info()
221
222     ### Parse the keyring
223
224     keyringname = keyring_names[0]
225     keyring = Keyring(keyringname)
226
227     keyring_id = database.get_or_set_keyring_id(
228                         keyringname.split("/")[-1])
229
230     ### Generate new uid entries if they're needed (from LDAP or the keyring)
231     (desuid_byname, desuid_byid) = keyring.generate_desired_users()
232
233     ### Cache all the existing uid entries
234     (db_uid_byname, db_uid_byid) = get_uid_info()
235
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))
245
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
251
252     fpr = {}
253     for z in keyring.keys.keys():
254         id = db_uid_byname.get(keyring.keys[z].get("uid", None), [None])[0]
255         if id == None:
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)
259
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.
263
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))
269
270     # For the keys in this keyring, add/update any fingerprints that've
271     # changed.
272
273     # Determine if we need to set the DM flag
274     is_dm = "no"
275     if Cnf("Import-Keyring::Options::Debian-Maintainer"):
276         is_dm = "yes"
277
278     for f in fpr:
279         newuid = fpr[f][0]
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
284         if oldfid == -1:
285             changes.append((newuiduid, "Added key: %s" % (f)))
286             if newuid:
287                 projectB.query("INSERT INTO fingerprint (fingerprint, uid, keyring, debian_maintainer) VALUES ('%s', %d, %d, %s)" % (f, newuid, keyring_id, is_dm))
288             else:
289                 projectB.query("INSERT INTO fingerprint (fingerprint, keyring) VALUES ('%s', %d, %s)" % (f, keyring_id, is_dm))
290         else:
291             if newuid and olduid != newuid:
292                 if olduid != -1:
293                     changes.append((newuiduid, "Linked key: %s" % f))
294                     changes.append((newuiduid, "  (formerly belonging to %s)" % (db_uid_byid[olduid][0])))
295                 else:
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))
299
300             if oldkid != keyring_id:
301                 projectB.query("UPDATE fingerprint SET keyring = %d WHERE id = %d" % (keyring_id, oldfid))
302
303     # All done!
304
305     projectB.query("COMMIT WORK")
306
307     changesd = {}
308     for (k, v) in changes:
309         if k not in changesd: changesd[k] = ""
310         changesd[k] += "    %s\n" % (v)
311
312     keys = changesd.keys()
313     keys.sort()
314     for k in keys:
315         print "%s\n%s\n" % (k, changesd[k])
316
317 ################################################################################
318
319 if __name__ == '__main__':
320     main()