Doc fixes, moved some fizmes to TODO, fixed minor bugs.
[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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <assert.h>
28
29 #include "agent.h"
30
31 struct secret_data_s {
32   int  totallen; /* this includes the padding */
33   int  datalen;  /* actual data length */
34   char data[1];
35 };
36
37 typedef struct cache_item_s *ITEM;
38 struct cache_item_s {
39   ITEM next;
40   time_t created;
41   time_t accessed;
42   int  ttl;  /* max. lifetime given in seonds */
43   struct secret_data_s *pw;
44   char key[1];
45 };
46
47
48 static ITEM thecache;
49
50
51 static void
52 release_data (struct secret_data_s *data)
53 {
54    xfree (data);
55 }
56
57 static struct secret_data_s *
58 new_data (const void *data, size_t length)
59 {
60   struct secret_data_s *d;
61   int total;
62
63   /* we pad the data to 32 bytes so that it get more complicated
64      finding something out by watching allocation patterns.  This is
65      usally not possible but we better assume nothing about our
66      secure storage provider*/
67   total = length + 32 - (length % 32);
68
69   d = gcry_malloc_secure (sizeof d + total - 1);
70   if (d)
71     {
72       d->totallen = total;
73       d->datalen  = length;
74       memcpy (d->data, data, length);
75     }
76   return d;
77 }
78
79
80 /* check whether there are items to expire */
81 static void
82 housekeeping (void)
83 {
84   ITEM r, rprev;
85   time_t current = time (NULL);
86
87   /* first expire the actual data */
88   for (r=thecache; r; r = r->next)
89     {
90       if (r->pw && r->accessed + r->ttl < current)
91         {
92           if (DBG_CACHE)
93             log_debug ("  expired `%s' (%ds after last access)\n",
94                        r->key, r->ttl);
95           release_data (r->pw);
96           r->pw = NULL;
97           r->accessed = current;
98         }
99     }
100
101   /* second, make sure that we also remove them based on the created stamp so
102      that the used has to enter it from time to time.  We do this every hour */
103   for (r=thecache; r; r = r->next)
104     {
105       if (r->pw && r->created + 60*60 < current)
106         {
107           if (DBG_CACHE)
108             log_debug ("  expired `%s' (1h after creation)\n", r->key);
109           release_data (r->pw);
110           r->pw = NULL;
111           r->accessed = current;
112         }
113     }
114
115   /* third, make sure that we don't have too many items in the list.
116      Expire old and unused entries after 30 minutes */
117   for (rprev=NULL, r=thecache; r; )
118     {
119       if (!r->pw && r->accessed + 60*30 < current)
120         {
121           ITEM r2 = r->next;
122           if (DBG_CACHE)
123             log_debug ("  removed `%s' (slot not used for 30m)\n", r->key);
124           xfree (r);
125           if (!rprev)
126             thecache = r2;
127           else
128             rprev->next = r2;
129           r = r2;
130         }
131       else
132         {
133           rprev = r;
134           r = r->next;
135         }
136     }
137 }
138
139
140
141 /* Store DATA of length DATALEN in the cache under KEY and mark it
142    with a maximum lifetime of TTL seconds.  If tehre is already data
143    under this key, it will be replaced.  Using a DATA of NULL deletes
144    the entry */
145 int
146 agent_put_cache (const char *key, const char *data, int ttl)
147 {
148   ITEM r;
149
150   if (DBG_CACHE)
151     log_debug ("agent_put_cache `%s'\n", key);
152   housekeeping ();
153
154   if (ttl < 1)
155     ttl = opt.def_cache_ttl;
156   if (!ttl)
157     return 0;
158
159   for (r=thecache; r; r = r->next)
160     {
161       if ( !strcmp (r->key, key))
162         break;
163     }
164   if (r)
165     { /* replace */
166       if (r->pw)
167         {
168           release_data (r->pw);
169           r->pw = NULL;
170         }
171       if (data)
172         {
173           r->pw = new_data (data, strlen (data)+1);
174           if (!r->pw)
175             log_error ("out of core while allocating new cache item\n");
176         }
177     }
178   else if (data)
179     { /* simply insert */
180       r = xtrycalloc (1, sizeof *r + strlen (key));
181       if (!r)
182         log_error ("out of core while allocating new cache control\n");
183       else
184         {
185           strcpy (r->key, key);
186           r->created = r->accessed = time (NULL); 
187           r->ttl = ttl;
188           r->pw = new_data (data, strlen (data)+1);
189           if (!r->pw)
190             {
191               log_error ("out of core while allocating new cache item\n");
192               xfree (r);
193             }
194           else
195             {
196               r->next = thecache;
197               thecache = r;
198             }
199         }
200     }
201   return 0;
202 }
203
204
205 /* Try to find an item in the cache */
206 const char *
207 agent_get_cache (const char *key)
208 {
209   ITEM r;
210   int count = 0;
211
212   if (DBG_CACHE)
213     log_debug ("agent_get_cache `%s'...\n", key);
214   housekeeping ();
215
216   /* FIXME: Returning pointers is not thread safe - add a reference
217      counter */
218   for (r=thecache; r; r = r->next, count++)
219     {
220       if (r->pw && !strcmp (r->key, key))
221         {
222           /* put_cache does only put strings into the cache, so we
223              don't need the lengths */
224           r->accessed = time (NULL);
225           if (DBG_CACHE)
226             log_debug ("... hit\n");
227           return r->pw->data;
228         }
229     }
230   if (DBG_CACHE)
231     log_debug ("... miss\n");
232
233   return NULL;
234 }
235
236
237