2003-04-24 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / key-cache.c
1 /* key-cache.c - Key cache routines.
2    Copyright (C) 2000 Werner Koch (dd9jn)
3    Copyright (C) 2001, 2002, 2003 g10 Code GmbH
4
5    This file is part of GPGME.
6  
7    GPGME is free software; you can redistribute it and/or modify it
8    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    GPGME is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16  
17    You should have received a copy of the GNU General Public License
18    along with GPGME; if not, write to the Free Software Foundation,
19    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
20
21 #if HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "gpgme.h"
28 #include "util.h"
29 #include "ops.h"
30 #include "sema.h"
31 #include "key.h"
32
33 #if SIZEOF_UNSIGNED_INT < 4
34 #error unsigned int too short to be used as a hash value
35 #endif
36
37 #define KEY_CACHE_SIZE 503
38 #define KEY_CACHE_MAX_CHAIN_LENGTH 10
39
40 struct key_cache_item_s
41 {
42   struct key_cache_item_s *next;
43   GpgmeKey key;
44 };
45
46 /* Protects key_cache and key_cache_unused_items.  */
47 DEFINE_STATIC_LOCK (key_cache_lock);
48 static struct key_cache_item_s *key_cache[KEY_CACHE_SIZE];
49 static struct key_cache_item_s *key_cache_unused_items;
50
51 \f
52 /* We use the first 4 digits to calculate the hash.  */
53 static int
54 hash_key (const char *fpr, unsigned int *rhash)
55 {
56   unsigned int hash;
57   int c;
58
59   if (!fpr)
60     return -1;
61   if ((c = _gpgme_hextobyte (fpr)) == -1)
62     return -1;
63   hash = c;
64   if ((c = _gpgme_hextobyte (fpr+2)) == -1)
65     return -1;
66   hash |= c << 8;
67   if ((c = _gpgme_hextobyte (fpr+4)) == -1)
68     return -1;
69   hash |= c << 16;
70   if ((c = _gpgme_hextobyte (fpr+6)) == -1)
71     return -1;
72   hash |= c << 24;
73
74   *rhash = hash;
75   return 0;
76 }
77
78 \f
79 /* Acquire a reference to KEY and add it to the key cache.  */
80 void
81 _gpgme_key_cache_add (GpgmeKey key)
82 {
83   struct subkey_s *k;
84
85   LOCK (key_cache_lock);
86   /* Put the key under each fingerprint into the cache.  We use the
87      first 4 digits to calculate the hash.  */
88   for (k = &key->keys; k; k = k->next)
89     {
90       size_t n;
91       unsigned int hash;
92       struct key_cache_item_s *item;
93
94       if (hash_key (k->fingerprint, &hash))
95         continue;
96
97       hash %= KEY_CACHE_SIZE;
98       for (item = key_cache[hash], n=0; item; item = item->next, n++)
99         {
100           struct subkey_s *k2;
101           if (item->key == key) 
102             /* Already in cache.  */
103             break;
104           /* Now do a deeper check.  */
105           for (k2 = &item->key->keys; k2; k2 = k2->next)
106             {
107               if (k2->fingerprint && !strcmp (k->fingerprint, k2->fingerprint))
108                 {
109                   /* Okay, replace it with the new copy.  */
110                   gpgme_key_unref (item->key);
111                   item->key = key;
112                   gpgme_key_ref (item->key);
113                   UNLOCK (key_cache_lock);
114                   return;
115                 }
116             }
117         }
118       if (item)
119         continue;
120         
121       if (n > KEY_CACHE_MAX_CHAIN_LENGTH)
122         {
123           /* Remove the last entries.  */
124           struct key_cache_item_s *last = NULL;
125
126           for (item = key_cache[hash];
127                item && n < KEY_CACHE_MAX_CHAIN_LENGTH;
128                last = item, item = item->next, n++)
129             ;
130           
131           if (last)
132             {
133               struct key_cache_item_s *next;
134
135               last->next = NULL;
136               for (; item; item = next)
137                 {
138                   next = item->next;
139                   gpgme_key_unref (item->key);
140                   item->key = NULL;
141                   item->next = key_cache_unused_items;
142                   key_cache_unused_items = item;
143                 }
144             }
145         }
146
147       item = key_cache_unused_items;
148       if (item)
149         {
150           key_cache_unused_items = item->next;
151           item->next = NULL;
152         }
153       else
154         {
155           item = malloc (sizeof *item);
156           if (!item)
157             {
158               UNLOCK (key_cache_lock);
159               return;
160             }
161         }
162
163       item->key = key;
164       gpgme_key_ref (key);
165       item->next = key_cache[hash];
166       key_cache[hash] = item;
167     }
168   UNLOCK (key_cache_lock);
169 }
170
171
172 GpgmeKey 
173 _gpgme_key_cache_get (const char *fpr)
174 {
175   struct key_cache_item_s *item;
176   unsigned int hash;
177
178   LOCK (key_cache_lock);
179   if (hash_key (fpr, &hash))
180     {
181       UNLOCK (key_cache_lock);
182       return NULL;
183     }
184
185   hash %= KEY_CACHE_SIZE;
186   for (item = key_cache[hash]; item; item = item->next)
187     {
188       struct subkey_s *k;
189
190       for (k = &item->key->keys; k; k = k->next)
191         {
192           if (k->fingerprint && !strcmp (k->fingerprint, fpr))
193             {
194               gpgme_key_ref (item->key);
195               UNLOCK (key_cache_lock);
196               return item->key;
197             }
198         }
199     }
200   UNLOCK (key_cache_lock);
201   return NULL;
202 }
203
204 \f
205 /* Get the key with the fingerprint FPR from the key cache or from the
206    crypto backend.  If FORCE_UPDATE is true, force a refresh of the
207    key from the crypto backend and replace the key in the cache, if
208    any.  If SECRET is true, get the secret key.  */
209 GpgmeError
210 gpgme_get_key (GpgmeCtx ctx, const char *fpr, GpgmeKey *r_key,
211                int secret, int force_update)
212 {
213   GpgmeCtx listctx;
214   GpgmeError err;
215
216   if (!ctx || !r_key)
217     return GPGME_Invalid_Value;
218   
219   if (strlen (fpr) < 16)        /* We have at least a key ID.  */
220     return GPGME_Invalid_Key;
221
222   if (!force_update)
223     {
224       *r_key = _gpgme_key_cache_get (fpr);
225       if (*r_key)
226         {
227           /* If the primary UID (if available) has no signatures, and
228              we are in the signature listing keylist mode, then try to
229              update the key below before returning.  */
230           if (!((ctx->keylist_mode & GPGME_KEYLIST_MODE_SIGS)
231                 && (*r_key)->uids && !(*r_key)->uids->certsigs))
232             return 0;
233         }
234     }
235
236   /* We need our own context because we have to avoid the user's I/O
237      callback handlers.  */
238   /* Fixme: This can be optimized by keeping an internal context
239      used for such key listings.  */
240   err = gpgme_new (&listctx);
241   if (err)
242     return err;
243   gpgme_set_protocol (listctx, gpgme_get_protocol (ctx));
244   gpgme_set_keylist_mode (listctx, ctx->keylist_mode);
245   err = gpgme_op_keylist_start (listctx, fpr, secret);
246   if (!err)
247     err = gpgme_op_keylist_next (listctx, r_key);
248   gpgme_release (listctx);
249   return err;
250 }