]> git.decadent.org.uk Git - dak.git/blob - dak/import_keyring.py
Merge remote-tracking branch 'nthykier/auto-decruft'
[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 # Copyright (C) 2009  Mark Hymers <mhy@debian.org>
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ################################################################################
22
23 import sys, os, re
24 import apt_pkg, ldap
25
26 from daklib.config import Config
27 from daklib.dbconn import *
28
29 # Globals
30 Options = None
31
32 ################################################################################
33
34 def get_uid_info(session):
35     byname = {}
36     byid = {}
37     q = session.execute("SELECT id, uid, name FROM uid")
38     for (keyid, uid, name) in q.fetchall():
39         byname[uid] = (keyid, name)
40         byid[keyid] = (uid, name)
41
42     return (byname, byid)
43
44 def get_fingerprint_info(session):
45     fins = {}
46     q = session.execute("SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f")
47     for (fingerprint, fingerprint_id, uid, keyring) in q.fetchall():
48         fins[fingerprint] = (uid, fingerprint_id, keyring)
49     return fins
50
51 def list_uids(session, pattern):
52     sql_pattern = "%%%s%%" % pattern
53     message = "List UIDs matching pattern %s" % sql_pattern
54     message += "\n" + ("=" * len(message))
55     print message
56     uid_query = session.query(Uid).filter(Uid.uid.ilike(sql_pattern))
57     for uid in uid_query.all():
58         print "\nuid %s" % uid.uid
59         for fp in uid.fingerprint:
60             print "    fingerprint %s" % fp.fingerprint
61             keyring = "unknown"
62             if fp.keyring:
63                 keyring = fp.keyring.keyring_name
64             print "        keyring %s" % keyring
65
66 ################################################################################
67
68 def usage (exit_code=0):
69     print """Usage: dak import-keyring [OPTION]... [KEYRING]
70   -h, --help                  show this help and exit.
71   -L, --import-ldap-users     generate uid entries for keyring from LDAP
72   -U, --generate-users FMT    generate uid entries from keyring as FMT
73   -l, --list-uids STRING      list all uids matching *STRING*
74   -n, --no-action             don't change database"""
75     sys.exit(exit_code)
76
77
78 ################################################################################
79
80 def main():
81     global Options
82
83     cnf = Config()
84     Arguments = [('h',"help","Import-Keyring::Options::Help"),
85                  ('L',"import-ldap-users","Import-Keyring::Options::Import-Ldap-Users"),
86                  ('U',"generate-users","Import-Keyring::Options::Generate-Users", "HasArg"),
87                  ('l',"list-uids","Import-Keyring::Options::List-UIDs", "HasArg"),
88                  ('n',"no-action","Import-Keyring::Options::No-Action"),
89                 ]
90
91     for i in [ "help", "report-changes", "generate-users",
92             "import-ldap-users", "list-uids", "no-action" ]:
93         if not cnf.has_key("Import-Keyring::Options::%s" % (i)):
94             cnf["Import-Keyring::Options::%s" % (i)] = ""
95
96     keyring_names = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
97
98     ### Parse options
99
100     Options = cnf.subtree("Import-Keyring::Options")
101
102     if Options["Help"]:
103         usage()
104
105     ### Initialise
106     session = DBConn().session()
107
108     if Options["List-UIDs"]:
109         list_uids(session, Options["List-UIDs"])
110         sys.exit(0)
111
112     if len(keyring_names) != 1:
113         usage(1)
114
115     ### Keep track of changes made
116     changes = []   # (uid, changes strings)
117
118     ### Cache all the existing fingerprint entries
119     db_fin_info = get_fingerprint_info(session)
120
121     ### Parse the keyring
122
123     keyringname = keyring_names[0]
124     keyring = get_keyring(keyringname, session)
125     if not keyring:
126         print "E: Can't load keyring %s from database" % keyringname
127         sys.exit(1)
128
129     keyring.load_keys(keyringname)
130
131     ### Generate new uid entries if they're needed (from LDAP or the keyring)
132     if Options["Generate-Users"]:
133         format = Options["Generate-Users"]
134         (desuid_byname, desuid_byid) = keyring.generate_users_from_keyring(Options["Generate-Users"], session)
135     elif Options["Import-Ldap-Users"]:
136         (desuid_byname, desuid_byid) = keyring.import_users_from_ldap(session)
137     else:
138         (desuid_byname, desuid_byid) = ({}, {})
139
140     ### Cache all the existing uid entries
141     (db_uid_byname, db_uid_byid) = get_uid_info(session)
142
143     ### Update full names of applicable users
144     for keyid in desuid_byid.keys():
145         uid = (keyid, desuid_byid[keyid][0])
146         name = desuid_byid[keyid][1]
147         oname = db_uid_byid[keyid][1]
148         if name and oname != name:
149             changes.append((uid[1], "Full name: %s" % (name)))
150             session.execute("UPDATE uid SET name = :name WHERE id = :keyid",
151                             {'name': name, 'keyid': keyid})
152
153     # The fingerprint table (fpr) points to a uid and a keyring.
154     #   If the uid is being decided here (ldap/generate) we set it to it.
155     #   Otherwise, if the fingerprint table already has a uid (which we've
156     #     cached earlier), we preserve it.
157     #   Otherwise we leave it as None
158
159     fpr = {}
160     for z in keyring.keys.keys():
161         keyid = db_uid_byname.get(keyring.keys[z].get("uid", None), [None])[0]
162         if keyid == None:
163             keyid = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0]
164         for y in keyring.keys[z]["fingerprints"]:
165             fpr[y] = (keyid, keyring.keyring_id)
166
167     # For any keys that used to be in this keyring, disassociate them.
168     # We don't change the uid, leaving that for historical info; if
169     # the id should change, it'll be set when importing another keyring.
170
171     for f,(u,fid,kr) in db_fin_info.iteritems():
172         if kr != keyring.keyring_id:
173             continue
174
175         if f in fpr:
176             continue
177
178         changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s" % (f)))
179         session.execute("""UPDATE fingerprint
180                               SET keyring = NULL
181                             WHERE id = :fprid""", {'fprid': fid})
182
183     # For the keys in this keyring, add/update any fingerprints that've
184     # changed.
185
186     for f in fpr:
187         newuid = fpr[f][0]
188         newuiduid = db_uid_byid.get(newuid, [None])[0]
189
190         (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1,-1,-1])
191
192         if olduid == None:
193             olduid = -1
194
195         if oldkid == None:
196             oldkid = -1
197
198         if oldfid == -1:
199             changes.append((newuiduid, "Added key: %s" % (f)))
200             fp = Fingerprint()
201             fp.fingerprint = f
202             fp.keyring_id = keyring.keyring_id
203             if newuid:
204                 fp.uid_id = newuid
205
206             session.add(fp)
207             session.flush()
208
209         else:
210             if newuid and olduid != newuid and olduid == -1:
211                 changes.append((newuiduid, "Linked key: %s" % f))
212                 changes.append((newuiduid, "  (formerly unowned)"))
213                 session.execute("UPDATE fingerprint SET uid = :uid WHERE id = :fpr",
214                                 {'uid': newuid, 'fpr': oldfid})
215
216             # Don't move a key from a keyring with a higher priority to a lower one
217             if oldkid != keyring.keyring_id:
218                 movekey = False
219                 if oldkid == -1:
220                     movekey = True
221                 else:
222                     try:
223                         oldkeyring = session.query(Keyring).filter_by(keyring_id=oldkid).one()
224                     except NotFoundError:
225                         print "ERROR: Cannot find old keyring with id %s" % oldkid
226                         sys.exit(1)
227
228                     if oldkeyring.priority < keyring.priority:
229                         movekey = True
230
231                 # Only change the keyring if it won't result in a loss of permissions
232                 if movekey:
233                     session.execute("""UPDATE fingerprint
234                                           SET keyring = :keyring
235                                         WHERE id = :fpr""",
236                                     {'keyring': keyring.keyring_id,
237                                      'fpr': oldfid})
238
239                     session.flush()
240
241                 else:
242                     print "Key %s exists in both %s and %s keyrings. Not demoting." % (f,
243                                                                                        oldkeyring.keyring_name,
244                                                                                        keyring.keyring_name)
245
246     # All done!
247     if Options["No-Action"]:
248         session.rollback()
249     else:
250         session.commit()
251
252     # Print a summary
253     changesd = {}
254     for (k, v) in changes:
255         if k not in changesd:
256             changesd[k] = ""
257         changesd[k] += "    %s\n" % (v)
258
259     keys = changesd.keys()
260     keys.sort()
261     for k in keys:
262         print "%s\n%s\n" % (k, changesd[k])
263
264 ################################################################################
265
266 if __name__ == '__main__':
267     main()