1#! /usr/bin/env python3
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>
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.
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.
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
21################################################################################
23import sys
25import apt_pkg
26from sqlalchemy.orm.exc import NoResultFound
28from daklib.config import Config
29from daklib.dbconn import DBConn, Fingerprint, Keyring, Uid, get_keyring
31# Globals
32Options = None
34################################################################################
37def get_uid_info(session):
38 byname = {}
39 byid = {}
40 q = session.execute("SELECT id, uid, name FROM uid")
41 for keyid, uid, name in q.fetchall():
42 byname[uid] = (keyid, name)
43 byid[keyid] = (uid, name)
45 return (byname, byid)
48def get_fingerprint_info(session):
49 fins = {}
50 q = session.execute(
51 "SELECT f.fingerprint, f.id, f.uid, f.keyring FROM fingerprint f"
52 )
53 for fingerprint, fingerprint_id, uid, keyring in q.fetchall():
54 fins[fingerprint] = (uid, fingerprint_id, keyring)
55 return fins
58def list_uids(session, pattern):
59 sql_pattern = f"%{pattern}%"
60 message = "List UIDs matching pattern %s" % sql_pattern
61 message += "\n" + ("=" * len(message))
62 print(message)
63 uid_query = session.query(Uid).filter(Uid.uid.ilike(sql_pattern))
64 for uid in uid_query.all():
65 print("\nuid %s" % uid.uid)
66 for fp in uid.fingerprint:
67 print(" fingerprint %s" % fp.fingerprint)
68 keyring = "unknown"
69 if fp.keyring:
70 keyring = fp.keyring.keyring_name
71 print(" keyring %s" % keyring)
74################################################################################
77def usage(exit_code=0):
78 print(
79 """Usage: dak import-keyring [OPTION]... [KEYRING]
80 -h, --help show this help and exit.
81 -L, --import-ldap-users generate uid entries for keyring from LDAP
82 -U, --generate-users FMT generate uid entries from keyring as FMT
83 -l, --list-uids STRING list all uids matching *STRING*
84 -n, --no-action don't change database"""
85 )
86 sys.exit(exit_code)
89################################################################################
92def main():
93 global Options
95 cnf = Config()
96 Arguments = [
97 ("h", "help", "Import-Keyring::Options::Help"),
98 ("L", "import-ldap-users", "Import-Keyring::Options::Import-Ldap-Users"),
99 ("U", "generate-users", "Import-Keyring::Options::Generate-Users", "HasArg"),
100 ("l", "list-uids", "Import-Keyring::Options::List-UIDs", "HasArg"),
101 ("n", "no-action", "Import-Keyring::Options::No-Action"),
102 ]
104 for i in [
105 "help",
106 "report-changes",
107 "generate-users",
108 "import-ldap-users",
109 "list-uids",
110 "no-action",
111 ]:
112 key = "Import-Keyring::Options::%s" % i
113 if key not in cnf: 113 ↛ 104line 113 didn't jump to line 104
114 cnf[key] = ""
116 keyring_names = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
118 ### Parse options
120 Options = cnf.subtree("Import-Keyring::Options")
122 if Options["Help"]:
123 usage()
125 ### Initialise
126 session = DBConn().session()
128 if Options["List-UIDs"]: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true
129 list_uids(session, Options["List-UIDs"])
130 sys.exit(0)
132 if len(keyring_names) != 1: 132 ↛ 133line 132 didn't jump to line 133, because the condition on line 132 was never true
133 usage(1)
135 ### Keep track of changes made
136 changes = [] # (uid, changes strings)
138 ### Cache all the existing fingerprint entries
139 db_fin_info = get_fingerprint_info(session)
141 ### Parse the keyring
143 keyringname = keyring_names[0]
144 keyring = get_keyring(keyringname, session)
145 if not keyring: 145 ↛ 146line 145 didn't jump to line 146, because the condition on line 145 was never true
146 print("E: Can't load keyring %s from database" % keyringname)
147 sys.exit(1)
149 keyring.load_keys(keyringname)
151 ### Generate new uid entries if they're needed (from LDAP or the keyring)
152 if Options["Generate-Users"]: 152 ↛ 156line 152 didn't jump to line 156, because the condition on line 152 was never false
153 _, desuid_byid = keyring.generate_users_from_keyring(
154 Options["Generate-Users"], session
155 )
156 elif Options["Import-Ldap-Users"]:
157 _, desuid_byid = keyring.import_users_from_ldap(session)
158 else:
159 desuid_byid = {}
161 ### Cache all the existing uid entries
162 (db_uid_byname, db_uid_byid) = get_uid_info(session)
164 ### Update full names of applicable users
165 for keyid in desuid_byid.keys():
166 uid = (keyid, desuid_byid[keyid][0])
167 name = desuid_byid[keyid][1]
168 oname = db_uid_byid[keyid][1]
169 if name and oname != name: 169 ↛ 165line 169 didn't jump to line 165, because the condition on line 169 was never false
170 changes.append((uid[1], "Full name: %s" % (name)))
171 session.execute(
172 "UPDATE uid SET name = :name WHERE id = :keyid",
173 {"name": name, "keyid": keyid},
174 )
176 # The fingerprint table (fpr) points to a uid and a keyring.
177 # If the uid is being decided here (ldap/generate) we set it to it.
178 # Otherwise, if the fingerprint table already has a uid (which we've
179 # cached earlier), we preserve it.
180 # Otherwise we leave it as None
182 fpr = {}
183 for z in keyring.keys.keys():
184 keyid = db_uid_byname.get(keyring.keys[z].get("uid", None), [None])[0]
185 if keyid is None: 185 ↛ 186line 185 didn't jump to line 186, because the condition on line 185 was never true
186 keyid = db_fin_info.get(keyring.keys[z]["fingerprints"][0], [None])[0]
187 for y in keyring.keys[z]["fingerprints"]:
188 fpr[y] = (keyid, keyring.keyring_id)
190 # For any keys that used to be in this keyring, disassociate them.
191 # We don't change the uid, leaving that for historical info; if
192 # the id should change, it'll be set when importing another keyring.
194 for f, (u, fid, kr) in db_fin_info.items():
195 if kr != keyring.keyring_id: 195 ↛ 198line 195 didn't jump to line 198, because the condition on line 195 was never false
196 continue
198 if f in fpr:
199 continue
201 changes.append((db_uid_byid.get(u, [None])[0], "Removed key: %s" % (f)))
202 session.execute(
203 """UPDATE fingerprint
204 SET keyring = NULL
205 WHERE id = :fprid""",
206 {"fprid": fid},
207 )
209 # For the keys in this keyring, add/update any fingerprints that've
210 # changed.
212 for f in fpr:
213 newuid = fpr[f][0]
214 newuiduid = db_uid_byid.get(newuid, [None])[0]
216 (olduid, oldfid, oldkid) = db_fin_info.get(f, [-1, -1, -1])
218 if olduid is None: 218 ↛ 219line 218 didn't jump to line 219, because the condition on line 218 was never true
219 olduid = -1
221 if oldkid is None: 221 ↛ 222line 221 didn't jump to line 222, because the condition on line 221 was never true
222 oldkid = -1
224 if oldfid == -1: 224 ↛ 236line 224 didn't jump to line 236, because the condition on line 224 was never false
225 changes.append((newuiduid, "Added key: %s" % (f)))
226 fp = Fingerprint()
227 fp.fingerprint = f
228 fp.keyring_id = keyring.keyring_id
229 if newuid: 229 ↛ 232line 229 didn't jump to line 232, because the condition on line 229 was never false
230 fp.uid_id = newuid
232 session.add(fp)
233 session.flush()
235 else:
236 if newuid and olduid != newuid and olduid == -1:
237 changes.append((newuiduid, "Linked key: %s" % f))
238 changes.append((newuiduid, " (formerly unowned)"))
239 session.execute(
240 "UPDATE fingerprint SET uid = :uid WHERE id = :fpr",
241 {"uid": newuid, "fpr": oldfid},
242 )
244 # Don't move a key from a keyring with a higher priority to a lower one
245 if oldkid != keyring.keyring_id:
246 movekey = False
247 if oldkid == -1:
248 movekey = True
249 else:
250 try:
251 oldkeyring = (
252 session.query(Keyring).filter_by(keyring_id=oldkid).one()
253 )
254 except NoResultFound:
255 print("ERROR: Cannot find old keyring with id %s" % oldkid)
256 sys.exit(1)
258 if oldkeyring.priority < keyring.priority:
259 movekey = True
261 # Only change the keyring if it won't result in a loss of permissions
262 if movekey:
263 session.execute(
264 """UPDATE fingerprint
265 SET keyring = :keyring
266 WHERE id = :fpr""",
267 {"keyring": keyring.keyring_id, "fpr": oldfid},
268 )
270 session.flush()
272 else:
273 print(
274 "Key %s exists in both %s and %s keyrings. Not demoting."
275 % (f, oldkeyring.keyring_name, keyring.keyring_name)
276 )
278 # All done!
279 if Options["No-Action"]: 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true
280 session.rollback()
281 else:
282 session.commit()
284 # Print a summary
285 changesd = {}
286 for k, v in changes:
287 if k not in changesd:
288 changesd[k] = ""
289 changesd[k] += " %s\n" % (v)
291 for k in sorted(changesd):
292 print("%s\n%s\n" % (k, changesd[k]))
295################################################################################
298if __name__ == "__main__":
299 main()