Fix possible system freeze on Mac OS X.
[gnupg.git] / agent / cache.c
1 /* cache.c - keep a cache of passphrases
2  *      Copyright (C) 2002 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG 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 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG 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, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <assert.h>
27
28 #include "agent.h"
29
30 struct secret_data_s {
31   int  totallen; /* this includes the padding */
32   int  datalen;  /* actual data length */
33   char data[1];
34 };
35
36 typedef struct cache_item_s *ITEM;
37 struct cache_item_s {
38   ITEM next;
39   time_t created;
40   time_t accessed;
41   int ttl;  /* max. lifetime given in seconds, -1 one means infinite */
42   int lockcount;
43   struct secret_data_s *pw;
44   cache_mode_t cache_mode;
45   char key[1];
46 };
47
48
49 static ITEM thecache;
50
51
52 static void
53 release_data (struct secret_data_s *data)
54 {
55    xfree (data);
56 }
57
58 static struct secret_data_s *
59 new_data (const void *data, size_t length)
60 {
61   struct secret_data_s *d;
62   int total;
63
64   /* we pad the data to 32 bytes so that it get more complicated
65      finding something out by watching allocation patterns.  This is
66      usally not possible but we better assume nothing about our
67      secure storage provider*/
68   total = length + 32 - (length % 32);
69
70   d = gcry_malloc_secure (sizeof *d + total - 1);
71   if (d)
72     {
73       d->totallen = total;
74       d->datalen  = length;
75       memcpy (d->data, data, length);
76     }
77   return d;
78 }
79
80
81
82 /* check whether there are items to expire */
83 static void
84 housekeeping (void)
85 {
86   ITEM r, rprev;
87   time_t current = gnupg_get_time ();
88
89   /* First expire the actual data */
90   for (r=thecache; r; r = r->next)
91     {
92       if (!r->lockcount && r->pw
93           && r->ttl >= 0 && r->accessed + r->ttl < current)
94         {
95           if (DBG_CACHE)
96             log_debug ("  expired `%s' (%ds after last access)\n",
97                        r->key, r->ttl);
98           release_data (r->pw);
99           r->pw = NULL;
100           r->accessed = current;
101         }
102     }
103
104   /* Second, make sure that we also remove them based on the created stamp so
105      that the user has to enter it from time to time. */
106   for (r=thecache; r; r = r->next)
107     {
108       unsigned long maxttl;
109       
110       switch (r->cache_mode)
111         {
112         case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break;
113         default: maxttl = opt.max_cache_ttl; break;
114         }
115       if (!r->lockcount && r->pw && r->created + maxttl < current)
116         {
117           if (DBG_CACHE)
118             log_debug ("  expired `%s' (%lus after creation)\n",
119                        r->key, opt.max_cache_ttl);
120           release_data (r->pw);
121           r->pw = NULL;
122           r->accessed = current;
123         }
124     }
125
126   /* Third, make sure that we don't have too many items in the list.
127      Expire old and unused entries after 30 minutes */
128   for (rprev=NULL, r=thecache; r; )
129     {
130       if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current)
131         {
132           if (r->lockcount)
133             {
134               log_error ("can't remove unused cache entry `%s' due to"
135                          " lockcount=%d\n",
136                          r->key, r->lockcount);
137               r->accessed += 60*10; /* next error message in 10 minutes */
138               rprev = r;
139               r = r->next;
140             }
141           else
142             {
143               ITEM r2 = r->next;
144               if (DBG_CACHE)
145                 log_debug ("  removed `%s' (slot not used for 30m)\n", r->key);
146               xfree (r);
147               if (!rprev)
148                 thecache = r2;
149               else
150                 rprev->next = r2;
151               r = r2;
152             }
153         }
154       else
155         {
156           rprev = r;
157           r = r->next;
158         }
159     }
160 }
161
162
163 void
164 agent_flush_cache (void)
165 {
166   ITEM r;
167
168   if (DBG_CACHE)
169     log_debug ("agent_flush_cache\n");
170
171   for (r=thecache; r; r = r->next)
172     {
173       if (!r->lockcount && r->pw)
174         {
175           if (DBG_CACHE)
176             log_debug ("  flushing `%s'\n", r->key);
177           release_data (r->pw);
178           r->pw = NULL;
179           r->accessed = 0;
180         }
181       else if (r->lockcount && r->pw)
182         {
183           if (DBG_CACHE)
184             log_debug ("    marked `%s' for flushing\n", r->key);
185           r->accessed = 0;
186           r->ttl = 0;
187         }
188     }
189 }
190
191
192
193 /* Store DATA of length DATALEN in the cache under KEY and mark it
194    with a maximum lifetime of TTL seconds.  If there is already data
195    under this key, it will be replaced.  Using a DATA of NULL deletes
196    the entry.  A TTL of 0 is replaced by the default TTL and a TTL of
197    -1 set infinite timeout.  CACHE_MODE is stored with the cache entry
198    and used to select different timeouts.  */
199 int
200 agent_put_cache (const char *key, cache_mode_t cache_mode,
201                  const char *data, int ttl)
202 {
203   ITEM r;
204
205   if (DBG_CACHE)
206     log_debug ("agent_put_cache `%s' requested ttl=%d mode=%d\n",
207                key, ttl, cache_mode);
208   housekeeping ();
209
210   if (!ttl)
211     {
212       switch(cache_mode)
213         {
214         case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break;
215         default: ttl = opt.def_cache_ttl; break;
216         }
217     }
218   if (!ttl || cache_mode == CACHE_MODE_IGNORE)
219     return 0;
220
221   for (r=thecache; r; r = r->next)
222     {
223       if (!r->lockcount && !strcmp (r->key, key))
224         break;
225     }
226   if (r)
227     { /* replace */
228       if (r->pw)
229         {
230           release_data (r->pw);
231           r->pw = NULL;
232         }
233       if (data)
234         {
235           r->created = r->accessed = gnupg_get_time (); 
236           r->ttl = ttl;
237           r->cache_mode = cache_mode;
238           r->pw = new_data (data, strlen (data)+1);
239           if (!r->pw)
240             log_error ("out of core while allocating new cache item\n");
241         }
242     }
243   else if (data)
244     { /* simply insert */
245       r = xtrycalloc (1, sizeof *r + strlen (key));
246       if (!r)
247         log_error ("out of core while allocating new cache control\n");
248       else
249         {
250           strcpy (r->key, key);
251           r->created = r->accessed = gnupg_get_time (); 
252           r->ttl = ttl;
253           r->cache_mode = cache_mode;
254           r->pw = new_data (data, strlen (data)+1);
255           if (!r->pw)
256             {
257               log_error ("out of core while allocating new cache item\n");
258               xfree (r);
259             }
260           else
261             {
262               r->next = thecache;
263               thecache = r;
264             }
265         }
266     }
267   return 0;
268 }
269
270
271 /* Try to find an item in the cache.  Note that we currently don't
272    make use of CACHE_MODE.  */
273 const char *
274 agent_get_cache (const char *key, cache_mode_t cache_mode, void **cache_id)
275 {
276   ITEM r;
277
278   if (cache_mode == CACHE_MODE_IGNORE)
279     return NULL;
280
281   if (DBG_CACHE)
282     log_debug ("agent_get_cache `%s'...\n", key);
283   housekeeping ();
284
285   /* first try to find one with no locks - this is an updated cache
286      entry: We might have entries with a lockcount and without a
287      lockcount. */
288   for (r=thecache; r; r = r->next)
289     {
290       if (!r->lockcount && r->pw && !strcmp (r->key, key))
291         {
292           /* put_cache does only put strings into the cache, so we
293              don't need the lengths */
294           r->accessed = gnupg_get_time ();
295           if (DBG_CACHE)
296             log_debug ("... hit\n");
297           r->lockcount++;
298           *cache_id = r;
299           return r->pw->data;
300         }
301     }
302   /* again, but this time get even one with a lockcount set */
303   for (r=thecache; r; r = r->next)
304     {
305       if (r->pw && !strcmp (r->key, key))
306         {
307           r->accessed = gnupg_get_time ();
308           if (DBG_CACHE)
309             log_debug ("... hit (locked)\n");
310           r->lockcount++;
311           *cache_id = r;
312           return r->pw->data;
313         }
314     }
315   if (DBG_CACHE)
316     log_debug ("... miss\n");
317
318   *cache_id = NULL;
319   return NULL;
320 }
321
322
323 void
324 agent_unlock_cache_entry (void **cache_id)
325 {
326   ITEM r;
327
328   for (r=thecache; r; r = r->next)
329     {
330       if (r == *cache_id)
331         {
332           if (!r->lockcount)
333             log_error ("trying to unlock non-locked cache entry `%s'\n",
334                        r->key);
335           else
336             r->lockcount--;
337           return;
338         }
339     }
340 }