]> git.decadent.org.uk Git - dak.git/blob - dak/import_keyring.py
New functionality for dak: import-keyring
[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 import daklib.database, daklib.logging
23 import apt_pkg, pg
24 import sys, os, email.Utils, re
25
26 # Globals
27 Cnf = None
28 Options = None
29 projectB = None
30 Logger = None
31
32 ################################################################################
33
34 # These could possibly be daklib.db functions, and reused by
35 # import-ldap-fingerprints
36
37 def get_uid_info():
38     byname = {}
39     byid = {}
40     q = projectB.query("SELECT id, uid, name FROM uid")
41     for (id, uid, name) in q.getresult():
42         byname[uid] = (id, name)
43         byid[id] = (uid, name)
44     return (byname, byid)
45
46 def get_fingerprint_info():
47     fins = {}
48     q = projectB.query("SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f")
49     for (fingerprint, fingerprint_id, uid, keyring) in q.getresult():
50         fins[fingerprint] = (uid, fingerprint_id, keyring)
51     return fins
52
53 ################################################################################
54
55 class Keyring:
56         gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
57                          " --with-colons --fingerprint --fingerprint"
58         keys = {}
59
60         def de_escape_str(self, str):
61                 esclist = re.split(r'(\\x..)', str)
62                 for x in range(1,len(esclist),2):
63                         esclist[x] = "%c" % (int(esclist[x][2:],16))
64                 return "".join(esclist)
65
66         def __init__(self, keyring):
67                 k = os.popen(self.gpg_invocation % keyring, "r")
68                 keys = self.keys
69                 key = None
70                 signingkey = False
71                 for line in k.xreadlines():
72                         field = line.split(":")
73                         if field[0] == "pub":
74                                 key = field[4]
75                                 (name, addr) = email.Utils.parseaddr(field[9])
76                                 name = re.sub(r"\s*[(].*[)]", "", name)
77                                 if name == "" or addr == "" or "@" not in addr:
78                                         name = field[9]
79                                         addr = "invalid-uid"
80                                 name = self.de_escape_str(name)
81                                 keys[key] = {"email": addr}
82                                 if name != "": keys[key]["name"] = name
83                                 keys[key]["aliases"] = [name]
84                                 keys[key]["fingerprints"] = []
85                                 signingkey = True
86                         elif key and field[0] == "sub" and len(field) >= 12:
87                                 signingkey = ("s" in field[11])
88                         elif key and field[0] == "uid":
89                                 (name, addr) = email.Utils.parseaddr(field[9])
90                                 if name and name not in keys[key]["aliases"]:
91                                         keys[key]["aliases"].append(name)
92                         elif signingkey and field[0] == "fpr":
93                                 keys[key]["fingerprints"].append(field[9])
94
95         def desired_users(self, format="%s"):
96                 if not Options["Generate-Users"]:
97                         return ({}, {})
98
99                 byuid = {}
100                 byname = {}
101                 keys = self.keys
102                 any_invalid = False
103                 for x in keys.keys():
104                         if keys[x]["email"] == "invalid-uid":
105                                 any_invalid = True
106                         else:
107                                 uid = format % keys[x]["email"]
108                                 id = daklib.database.get_or_set_uid_id(uid)
109                                 byuid[id] = (uid, keys[x]["name"])
110                                 byname[uid] = (id, keys[x]["name"])
111                 if any_invalid:
112                         uid = format % "invalid-uid"
113                         id = daklib.database.get_or_set_uid_id(uid)
114                         byuid[id] = (uid, "ungeneratable user id")
115                         byname[uid] = (id, "ungeneratable user id")
116                 return (byname, byuid)
117
118 ################################################################################
119
120 def usage (exit_code=0):
121     print """Usage: dak import-keyring [OPTION]... [KEYRING]
122   -h, --help                  show this help and exit.
123   -U, --generate-users FMT    generate uid entries from keyring as FMT"""
124     sys.exit(exit_code)
125
126
127 ################################################################################
128
129 def main():
130     global Cnf, projectB, Options
131
132     Cnf = daklib.utils.get_conf()
133     Arguments = [('h',"help","Import-Keyring::Options::Help"),
134                  ('U',"generate-users","Import-Keyring::Options::Generate-Users", "HasArg"),
135                 ]
136
137     for i in [ "help", "report-changes", "generate-users" ]:
138         if not Cnf.has_key("Import-Keyring::Options::%s" % (i)):
139             Cnf["Import-Keyring::Options::%s" % (i)] = ""
140
141     keyring_names = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
142
143     Options = Cnf.SubTree("Import-Keyring::Options")
144     if Options["Help"]:
145         usage()
146
147     uid_format = "%s"
148     if Options["Generate-Users"]:
149         uid_format = Options["Generate-Users"]
150
151     changes = []   # (uid, changes strings)
152
153     projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
154     daklib.database.init(Cnf, projectB)
155
156     projectB.query("BEGIN WORK")
157
158     if len(keyring_names) != 1:
159         usage(1)
160
161     # Parse the keyring
162     keyringname = keyring_names[0]
163     keyring = Keyring(keyringname)
164
165     keyring_id = daklib.database.get_or_set_keyring_id(
166                         keyringname.split("/")[-1])
167
168     # If we're generating uids, make sure we have entries in the uid
169     # table for every uid
170     (desuid_byname, desuid_byid) = keyring.desired_users(uid_format)
171
172     # Cache all the existing fingerprint and uid entries
173     db_fin_info = get_fingerprint_info()
174     (db_uid_byname, db_uid_byid) = get_uid_info()
175
176     # Update full names of uids
177
178     for id in desuid_byid.keys():
179         uid = (id, desuid_byid[id][0])
180         name = desuid_byid[id][1]
181         oname = db_uid_byid[id][1]
182         if name and oname != name:
183             changes.append((uid[1], "Full name: %s\n" % (name)))
184             projectB.query("UPDATE uid SET name = '%s' WHERE id = %s" %
185                 (pg.escape_string(name), id))
186
187     # Work out what the fingerprint table should look like for the keys
188     # in this keyring
189     fpr = {}
190     for z in keyring.keys.keys():
191         id = db_uid_byname.get(uid_format % keyring.keys[z]["email"], [None])[0]
192         if id == None:
193             id = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0]
194         for y in keyring.keys[z]["fingerprints"]:
195             fpr[y] = (id,keyring_id)
196
197     # For any keys that used to be in this keyring, disassociate them.
198     # We don't change the uid, leaving that to for historical info; if
199     # the id should change, it'll be set when importing another keyring
200     # or importing ldap fingerprints.
201
202     for f,(u,fid,kr) in db_fin_info.iteritems():
203         if kr != keyring_id: continue
204         if f in fpr: continue
205         changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s\n" % (f)))
206         projectB.query("UPDATE fingerprint SET keyring = NULL WHERE id = %d" % (fid))
207         
208     # For the keys in this keyring, add/update any fingerprints that've
209     # changed.
210
211     for f in fpr:
212         newuid = fpr[f][0]
213         newuiduid = db_uid_byid.get(newuid, [None])[0] 
214         (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1,-1,-1])
215         if olduid == None: olduid = -1
216         if oldkid == None: oldkid = -1
217         if oldfid == -1:
218             changes.append((newuiduid, "Added key: %s\n" % (f)))
219             if newuid:
220                 projectB.query("INSERT INTO fingerprint (fingerprint, uid, keyring) VALUES ('%s', %d, %d)" % (f, newuid, keyring_id))
221             else:
222                 projectB.query("INSERT INTO fingerprint (fingerprint, keyring) VALUES ('%s', %d)" % (f, keyring_id))
223         else:
224             if newuid and olduid != newuid:
225                 if olduid != -1:
226                     changes.append((newuiduid, "Linked key: %s (formerly belonging to %s)" % (f, db_uid_byid[olduid][0])))
227                 else:
228                     changes.append((newuiduid, "Linked key: %s (formerly unowned)\n" % (f)))
229                 projectB.query("UPDATE fingerprint SET uid = %d WHERE id = %d" % (newuid, oldfid))
230
231             if oldkid != keyring_id:
232                 projectB.query("UPDATE fingerprint SET keyring = %d WHERE id = %d" % (keyring_id, oldfid))
233
234     # All done!
235
236     projectB.query("COMMIT WORK")
237
238     changesd = {}
239     for (k, v) in changes:
240         if k not in changesd: changesd[k] = ""
241         changesd[k] += "    " + v
242
243     keys = changesd.keys()
244     keys.sort()
245     for k in keys:
246         print "%s\n%s" % (k, changesd[k])
247
248 ################################################################################
249
250 if __name__ == '__main__':
251     main()