]> git.sommitrealweird.co.uk Git - onak.git/blob - merge.c
3e8e8fcf7eb772ff8ca0b7cd8c1289c4d7ddb112
[onak.git] / merge.c
1 /*
2  * merge.c - Routines to merge OpenPGP public keys.
3  *
4  * Jonathan McDowell <noodles@earth.li>
5  *
6  * Copyright 2002 Project Purple
7  */
8
9 #include <assert.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12
13 #include "decodekey.h"
14 #include "keydb.h"
15 #include "keyid.h"
16 #include "keystructs.h"
17 #include "ll.h"
18 #include "log.h"
19 #include "mem.h"
20 #include "merge.h"
21
22 /**
23  *      compare_packets - Check to see if 2 OpenPGP packets are the same.
24  *      @a: The first packet to compare.
25  *      @b: The second packet to compare.
26  *
27  *      Takes 2 packets and returns true if they are the same and false
28  *      otherwise.
29  */
30 bool compare_packets(struct openpgp_packet *a, struct openpgp_packet *b)
31 {
32         return (a->tag == b->tag && a->length == b->length &&
33                 !memcmp(a->data, b->data, b->length));
34 }
35
36 /**
37  *      compare_signatures - Check to see if 2 OpenPGP signatures are the same.
38  *      @a: The first signature to compare.
39  *      @b: The second signature to compare.
40  *
41  *      Takes 2 signature packets and returns true if they are the same and
42  *      false otherwise.
43  */
44 bool compare_signatures(struct openpgp_packet *a, struct openpgp_packet *b)
45 {
46         return (sig_keyid(a) == sig_keyid(b));
47 }
48
49 /**
50  *      find_packet - Checks to see if an OpenPGP packet exists in a list.
51  *      @packet_list: The list of packets to look in.
52  *      @packet: The packet to look for.
53  *
54  *      Walks through the packet_list checking to see if the packet given is
55  *      present in it. Returns true if it is.
56  */
57 bool find_packet(struct openpgp_packet_list *packet_list,
58                         struct openpgp_packet *packet)
59 {
60         bool found = false;
61
62         while (!found && packet_list != NULL) {
63                 if (compare_packets(packet_list->packet, packet)) {
64                         found = true;
65                 }
66                 packet_list = packet_list -> next;
67         }
68
69         return found;
70 }
71
72 /**
73  *      find_signature - Checks to see if an OpenPGP signature exists in a list.
74  *      @packet_list: The list of packets to look in.
75  *      @packet: The signature to look for.
76  *
77  *      Walks through the packet_list checking to see if the signature given is
78  *      present in it. Returns a pointer to it if it is, NULL otherwise.
79  *
80  */
81 struct openpgp_packet_list *find_signature(
82                         struct openpgp_packet_list *packet_list,
83                         struct openpgp_packet *packet)
84 {
85         struct openpgp_packet_list *found = NULL;
86
87         while (!found && packet_list != NULL) {
88                 if (compare_signatures(packet_list->packet, packet)) {
89                         found = packet_list;
90                 }
91                 packet_list = packet_list -> next;
92         }
93
94         return found;
95 }
96
97 /**
98  *      get_signed_packet - Gets a signed packet from a list.
99  *      @packet_list: The list of packets to look in.
100  *      @packet: The packet to look for.
101  *
102  *      Walks through the signedpacket_list looking for the supplied packet and
103  *      returns it if found. Otherwise returns NULL.
104  */
105 struct openpgp_signedpacket_list *find_signed_packet(
106                 struct openpgp_signedpacket_list *packet_list,
107                 struct openpgp_packet *packet)
108 {
109         struct openpgp_signedpacket_list *found = NULL;
110
111         while (found == NULL && packet_list != NULL) {
112                 if (compare_packets(packet_list->packet, packet)) {
113                         found = packet_list;
114                 }
115                 packet_list = packet_list -> next;
116         }
117
118         return found;
119 }
120
121 /**
122  *      remove_signed_packet - Removes a signed packet from a list.
123  *      @packet_list: The list of packets to look in.
124  *      @packet: The packet to remove.
125  *
126  *      Walks through the signedpacket_list looking for the supplied packet and
127  *      removes it if found. Assumes the packet can only exist a maximum of
128  *      once in the list.
129  */
130 bool remove_signed_packet(struct openpgp_signedpacket_list **packet_list,
131                 struct openpgp_signedpacket_list **list_end,
132                 struct openpgp_packet *packet)
133 {
134         struct openpgp_signedpacket_list *cur = NULL;
135         struct openpgp_signedpacket_list *prev = NULL;
136         bool found = false;
137
138         for (cur = *packet_list; !found && (cur != NULL); cur = cur->next) {
139                 if (compare_packets(cur->packet, packet)) {
140                         found = true;
141                         if (prev == NULL) {
142                                 *packet_list = cur->next;
143                         } else {
144                                 prev->next = cur->next;
145                         }
146                         if (cur->next == NULL) {
147                                 *list_end = prev;
148                         }
149                         // TODO: Free the removed signed packet...
150                 }
151                 prev = cur;
152         }
153
154         return found;
155 }
156
157 /**
158  *      merge_packet_sigs - Takes 2 signed packets and merges their sigs.
159  *      @old: The old signed packet.
160  *      @new: The new signed packet.
161  *
162  *      Takes 2 signed packet list structures and the sigs of the packets on
163  *      the head of these structures. These packets must both be the same and
164  *      the fully merged structure is returned in old and the minimal
165  *      difference to get from old to new in new.
166  */
167 int merge_packet_sigs(struct openpgp_signedpacket_list *old,
168                         struct openpgp_signedpacket_list *new)
169 {
170         struct openpgp_packet_list      *lastpacket = NULL;
171         struct openpgp_packet_list      *curpacket = NULL;
172         struct openpgp_packet_list      *nextpacket = NULL;
173
174         assert(compare_packets(old->packet, new->packet));
175
176         curpacket = new->sigs;
177         while (curpacket != NULL) {
178                 nextpacket = curpacket->next;
179                 /*
180                  * TODO: We should be checking the signature and then
181                  * potentially merging/replacing it depending on the subpackets
182                  * really. For now this stops us adding the same one twice
183                  * however.
184                  */ 
185                 if (find_signature(old->sigs, curpacket->packet)) {
186                         /*
187                          * We already have this sig, remove it from the
188                          * difference list and free the memory allocated for
189                          * it.
190                          */
191                         if (lastpacket != NULL) {
192                                 lastpacket->next = curpacket->next;
193                         } else {
194                                 assert(curpacket == new->sigs);
195                                 new->sigs = curpacket->next;
196                         }
197                         curpacket->next = NULL;
198                         free_packet_list(curpacket);
199                 } else {
200                         lastpacket = curpacket;
201                 }
202                 curpacket = nextpacket;
203         }
204         new->last_sig = lastpacket;
205
206         /*
207          * What's left on new->sigs now are the new signatures, so add them to
208          * old->sigs.
209          */
210         packet_list_add(&old->sigs, &old->last_sig, new->sigs);
211
212         return 0;
213 }
214
215 /**
216  *      merge_signed_packets - Takes 2 lists of signed packets and merges them.
217  *      @old: The old signed packet list.
218  *      @new: The new signed packet list.
219  *
220  *      Takes 2 lists of signed packets and merges them. The complete list of
221  *      signed packets & sigs is returned in old and the minimal set of
222  *      differences required to get from old to new in new.
223  */
224 int merge_signed_packets(struct openpgp_signedpacket_list **old,
225                         struct openpgp_signedpacket_list **old_end,
226                         struct openpgp_signedpacket_list **new,
227                         struct openpgp_signedpacket_list **new_end)
228 {
229         struct openpgp_signedpacket_list *curelem = NULL;
230         struct openpgp_signedpacket_list *newelem = NULL;
231
232         for (curelem = *old; curelem != NULL; curelem = curelem->next) {
233                 newelem = find_signed_packet(*new, curelem->packet);
234                 if (newelem != NULL) {
235                         merge_packet_sigs(curelem, newelem);
236                         
237                         /*
238                          * If there are no sigs left on the new signed packet
239                          * then remove it from the list.
240                          */
241                         if (newelem->sigs == NULL) {
242                                 remove_signed_packet(new,
243                                                 new_end,
244                                                 newelem->packet);
245                         }
246                 }
247         }
248
249         /*
250          * If *new != NULL now then there might be UIDs on the new key that
251          * weren't on the old key. Walk through them, checking if the UID is
252          * on the old key and if not adding them to it.
253          */
254         for (curelem = *new; curelem != NULL;
255                         curelem = curelem->next) {
256
257                 if (find_signed_packet(*old, curelem->packet) == NULL) {
258                         ADD_PACKET_TO_LIST((*old_end),
259                                 packet_dup(curelem->packet));
260                         if (*old == NULL) {
261                                 *old = *old_end;
262                         }
263                         packet_list_add(&(*old_end)->sigs,
264                                 &(*old_end)->last_sig,
265                                 curelem->sigs);
266                 }
267         }
268
269         return 0;
270 }
271
272 /**
273  *      merge_keys - Takes 2 public keys and merges them.
274  *      @a: The old key. The merged key is returned in this structure.
275  *      @b: The new key. The changed from old to new keys are returned in this
276  *              structure.
277  *
278  *      This function takes 2 keys and merges them. It then returns the merged
279  *      key in a and the difference between this new key and the original a
280  *      in b (ie newb contains the minimum amount of detail necessary to
281  *      convert olda to newa). The intention is that olda is provided from
282  *      internal storage and oldb from the remote user. newa is then stored in
283  *      internal storage and newb is sent to all our keysync peers.
284  */
285 int merge_keys(struct openpgp_publickey *a, struct openpgp_publickey *b)
286 {
287         int rc = 0; /* Return code */
288         struct openpgp_packet_list      *curpacket = NULL; 
289         struct openpgp_packet_list      *lastpacket = NULL;
290         struct openpgp_packet_list      *nextpacket = NULL;
291
292         if (a == NULL || b == NULL) {
293                 /*
294                  * Do nothing.
295                  */
296                 rc = 1;
297         } else if (get_keyid(a) != get_keyid(b)) {
298                 /*
299                  * Key IDs are different.
300                  */
301                 rc = -1;
302         } else {
303                 /*
304                  * Key IDs are the same, so I guess we have to merge them.
305                  */
306                 curpacket = b->revocations;
307                 while (curpacket != NULL) {
308                         nextpacket = curpacket->next;
309                         if (find_packet(a->revocations, curpacket->packet)) {
310                                 /*
311                                  * We already have this revocation, remove it
312                                  * from the difference list and free the memory
313                                  * allocated for it.
314                                  */
315
316                                 if (lastpacket != NULL) {
317                                         lastpacket->next = curpacket->next;
318                                 } else {
319                                         assert(curpacket == b->revocations);
320                                         b->revocations = curpacket->next;
321                                 }
322                                 curpacket->next = NULL;
323                                 free_packet_list(curpacket);
324
325                         } else {
326                                 lastpacket = curpacket;
327                         }
328                         curpacket = nextpacket;
329                 }
330                 b->last_revocation = lastpacket;
331
332                 /*
333                  * Anything left on b->revocations doesn't exist on
334                  * a->revocations, so add them to the list.
335                  */
336                 packet_list_add(&a->revocations,
337                                 &a->last_revocation,
338                                 b->revocations);
339
340                 /*
341                  * Merge uids (signed list).
342                  * Merge subkeys (signed list).
343                  */
344                 merge_signed_packets(&a->uids, &a->last_uid, 
345                                 &b->uids, &b->last_uid);
346                 merge_signed_packets(&a->subkeys, &a->last_subkey,
347                                 &b->subkeys, &b->last_subkey);
348
349         }
350
351         return rc;
352 }
353
354 /**
355  *      update_keys - Takes a list of public keys and updates them in the DB.
356  *      @keys: The keys to update in the DB.
357  *
358  *      Takes a list of keys and adds them to the database, merging them with
359  *      the key in the database if it's already present there. The key list is
360  *      update to contain the minimum set of updates required to get from what
361  *      we had before to what we have now (ie the set of data that was added to
362  *      the DB). Returns the number of entirely new keys added.
363  */
364 int update_keys(struct openpgp_publickey **keys)
365 {
366         struct openpgp_publickey *curkey = NULL;
367         struct openpgp_publickey *oldkey = NULL;
368         struct openpgp_publickey *prev = NULL;
369         int newkeys = 0;
370         bool intrans;
371
372         for (curkey = *keys; curkey != NULL; curkey = curkey->next) {
373                 intrans = starttrans();
374                 logthing(LOGTHING_INFO,
375                         "Fetching key 0x%llX, result: %d",
376                         get_keyid(curkey),
377                         fetch_key(get_keyid(curkey), &oldkey, intrans));
378
379                 /*
380                  * If we already have the key stored in the DB then merge it
381                  * with the new one that's been supplied. Otherwise the key
382                  * we've just got is the one that goes in the DB and also the
383                  * one that we send out.
384                  */
385                 if (oldkey != NULL) {
386                         merge_keys(oldkey, curkey);
387                         if (curkey->revocations == NULL &&
388                                         curkey->uids == NULL &&
389                                         curkey->subkeys == NULL) {
390                                 if (prev == NULL) {
391                                         *keys = curkey->next;
392                                 } else {
393                                         prev->next = curkey->next;
394                                         curkey->next = NULL;
395                                         free_publickey(curkey);
396                                         curkey = prev;
397                                 }
398                         } else {
399                                 prev = curkey;
400                                 logthing(LOGTHING_INFO,
401                                         "Merged key; storing updated key.");
402                                 store_key(oldkey, intrans, true);
403                         }
404                         free_publickey(oldkey);
405                         oldkey = NULL;
406                 } else {
407                         logthing(LOGTHING_INFO,
408                                 "Storing completely new key.");
409                         store_key(curkey, intrans, false);
410                         newkeys++;
411                 }
412                 endtrans();
413                 intrans = false;
414         }
415
416         return newkeys;
417 }