d461c40e9534ac845206b980a031d22c42b142a3
[gpgol.git] / src / passcache.c
1 /* passcache.c - passphrase cache for OutlGPG
2  *      Copyright (C) 2005 g10 Code GmbH
3  *
4  * This file is part of OutlGPG.
5  * 
6  * OutlGPG is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  * 
11  * OutlGPG 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 Lesser General Public License for more details.
15  * 
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21
22 /* We use a global passphrase cache.  The cache time is set at the
23    time the passphrase gets stored.  */
24
25 #include <config.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <assert.h>
29 #include <time.h>
30 #include <windows.h> /* Argg: Only required for the locking. */
31
32 #include "util.h"
33 #include "passcache.h"
34
35
36 /* An item to hold a cached passphrase. */
37 struct cache_item
38 {
39   /* We love linked lists; there are only a few passwords and access
40      to them is not in any way time critical. */
41   struct cache_item *next;  
42
43   /* The Time to Live for this entry. */
44   int ttl;
45
46   /* The timestamp is updated with each access to the item and used
47      along with TTL to exire this item. */
48   time_t timestamp;
49
50   /* The value of this item.  Malloced C String.  If this one is NULL
51      this item may be deleted. */
52   char *value;         
53
54   /* The key for this item. C String. */
55   char key[1];
56 };
57 typedef struct cache_item *cache_item_t;
58
59
60 /* The actual cache is a simple list anchord at this global
61    variable. */
62 static cache_item_t the_cache;
63
64 /* Mutex used to serialize access to the cache. */
65 static HANDLE cache_mutex;
66
67
68 /* Initialize this mode.  Called at a very early stage. Returns 0 on
69    success. */
70 int
71 initialize_passcache (void)
72 {
73   SECURITY_ATTRIBUTES sa;
74   
75   memset (&sa, 0, sizeof sa);
76   sa.bInheritHandle = TRUE;
77   sa.lpSecurityDescriptor = NULL;
78   sa.nLength = sizeof sa;
79   cache_mutex = CreateMutex (&sa, FALSE, NULL);
80   return cache_mutex? 0 : -1;
81 }
82
83 /* Acquire the mutex.  Returns 0 on success. */
84 static int 
85 lock_cache (void)
86 {
87   int code = WaitForSingleObject (cache_mutex, INFINITE);
88   if (code != WAIT_OBJECT_0)
89     log_error ("%s:%s: waiting on mutex failed: code=%#x\n",
90                __FILE__, __func__, code);
91   return code != WAIT_OBJECT_0;
92 }
93
94 /* Release the mutex.  No error is returned because this is a fatal
95    error anyway and there is no way to clean up. */
96 static void
97 unlock_cache (void)
98 {
99   if (!ReleaseMutex (cache_mutex))
100     log_error_w32 (-1, "%s:%s: ReleaseMutex failed", __FILE__, __func__);
101 }
102
103
104 /* This is routine is used to remove all deleted entries from the
105    linked list.  Deleted entries are marked by a value of NULL.  Note,
106    that this routibne must be called in a locked state. */
107 static void
108 remove_deleted_items (void)
109 {
110   cache_item_t item, prev;
111
112  again:
113   for (item = the_cache; item; item = item->next)
114     if (!item->value)
115       {
116         if (item == the_cache)
117           {
118             the_cache = item->next;
119             xfree (item);
120           }
121         else
122           {
123             for (prev=the_cache; prev->next; prev = prev->next)
124               if (prev->next == item)
125                 {
126                   prev->next = item->next;
127                   xfree (item);
128                   item = NULL;
129                   break;
130                 }
131             assert (!item);
132           }
133         goto again; /* Yes, we use this pretty dumb algorithm ;-) */
134       }
135 }
136
137
138
139 /* Flush all entries from the cache. */
140 void
141 passcache_flushall (void)
142 {
143   cache_item_t item;
144
145   if (lock_cache ())
146     return; /* FIXME: Should we pop up a message box? */ 
147
148   for (item = the_cache; item; item = item->next)
149     if (item->value)
150       {
151         wipestring (item->value);
152         xfree (item->value);
153         item->value = NULL;
154       }
155   remove_deleted_items ();
156
157   unlock_cache ();
158 }
159
160
161 /* Store the passphrase in VALUE under KEY in out cache. Assign TTL
162    seconds as maximum caching time.  If it already exists, merely
163    updates the TTL. If the TTL is 0 or VALUE is NULL or empty, flush a
164    possible entry. */
165 void
166 passcache_put (const char *key, const char *value, int ttl)
167 {
168   cache_item_t item;
169
170   if (!key || !*key)
171     {
172       log_error ("%s:%s: no key given", __FILE__, __func__);
173       return;
174     }
175
176   if (lock_cache ())
177     return; /* FIXME: Should we pop up a message box if a flush was
178                requested? */
179   
180   for (item = the_cache; item; item = item->next)
181     if (item->value && !strcmp (item->key, key))
182       break;
183   if (item && (!ttl || !value || !*value))
184     {
185       /* Delete this entry. */
186       wipestring (item->value);
187       xfree (item->value);
188       item->value = NULL;
189       /* Actual delete will happen before we allocate a new entry. */
190     }
191   else if (item)
192     {
193       /* Update this entry. */
194       if (item->value)
195         {
196           wipestring (item->value);
197           xfree (item->value);
198         }
199       item->value = xstrdup (value);
200       item->ttl = ttl;
201       item->timestamp = time (NULL);
202     }
203   else if (!ttl || !value || !*value)
204     {
205       log_debug ("%s:%s: ignoring attempt to add empty entry `%s'",
206                  __FILE__, __func__, key);
207     }
208   else 
209     {
210       /* Create new cache entry. */
211       remove_deleted_items ();
212       item = xcalloc (1, sizeof *item + strlen (key));
213       strcpy (item->key, key);
214       item->ttl = ttl;
215       item->value = xstrdup (value);
216       item->timestamp = time (NULL);
217
218       item->next = the_cache;
219       the_cache = item;
220     }
221
222   unlock_cache ();
223 }
224
225
226 /* Return the passphrase stored under KEY as a newly malloced string.
227    Caller must release that string using xfree.  Using this function
228    won't update the TTL.  If no passphrase is available under this
229    key, the function returns NULL.  Calling thsi function with KEY set
230    to NULL will only expire old entries. */
231 char *
232 passcache_get (const char *key)
233 {
234   cache_item_t item;
235   char *result = NULL;
236   time_t now = time (NULL);
237
238   if (lock_cache ())
239     return NULL;
240   
241   /* Expire entries. */
242   for (item = the_cache; item; item = item->next)
243     if (item->value && item->timestamp + item->ttl < now)
244       {
245         wipestring (item->value);
246         xfree (item->value);
247         item->value = NULL;
248       }
249
250   /* Look for the entry. */
251   if (key && *key)
252     {
253       for (item = the_cache; item; item = item->next)
254         if (item->value && !strcmp (item->key, key))
255           {
256             result = xstrdup (item->value);
257             item->timestamp = time (NULL);
258             break;
259           }
260     }
261
262   unlock_cache ();
263
264   return result;
265 }