a2fea6c017a7e8690bcd7140006739cfecfe732a
[gpgol.git] / src / msgcache.c
1 /* msgcache.cpp - Implementation of a message cache.
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 /* Due to some peculiarities of Outlook 2003 and possible also earlier
23    versions we need fixup the text of a reply before editing starts.
24    This is done using the Echache extension mechanism and this module
25    provides the means of caching and locating messages.  To be exact,
26    we don't cache entire messages but just the plaintext after
27    decryption.
28
29    What we do here is to save the plaintext in a list under a key
30    taken from the PR_STORE_ENTRYID property.  It seems that this is a
31    reliable key to match the message again after Reply has been
32    called.  We can't use PR_ENTRYID because this one is different in
33    the reply template message.
34
35    To keep the memory size at bay we but a limit on the maximum cache
36    size; thus depending on the total size of the messages the number
37    of open inspectors with decrypted messages which can be matched
38    against a reply template is limited. We try to make sure that there
39    is at least one message; this makes sure that in the most common
40    case the plaintext is always available.  We use a circular buffer
41    so that the oldest messages are flushed from the cache first.  I
42    don't think that it makes much sense to take the sieze of a message
43    into account here.
44
45    FUBOH: This damned Outlook think is fucked up beyond all hacking.
46    After implementing this cace module here I realized that none of
47    the properties are taken from the message to the reply template:
48    Neither STORE_ENTRYID (which BTW is anyway constant within a
49    folder), nor ENTRYID, nor SEARCHID nor RECORD_KEY.  The only way to
50    solve this is by assuming that the last mail read will be the one
51    which gets replied to.  To implement this, we mark a message as the
52    active one through msgcache_set_active() called from the OnRead
53    hook.  msgcache_get will then simply return the active message.
54 */
55    
56
57 #ifdef HAVE_CONFIG_H
58 #include <config.h>
59 #endif
60
61 #include <windows.h>
62 #include <assert.h>
63 #include <string.h>
64
65 #include "mymapi.h"
66 #include "mymapitags.h"
67
68 #include "msgcache.h"
69 #include "util.h"
70
71
72 /* A Item to hold a cache message, i.e. the plaintext and a key. */
73 struct cache_item
74 {
75   struct cache_item *next;  
76
77   char *plaintext;  /* The malloced plaintext of the message.  This is
78                        assumed to be a valid C String, UTF8
79                        encoded. */
80   size_t length;    /* The length of that plaintext used to compute
81                        the total size of the cache. */
82
83   int ref;          /* Reference counter, indicating how many callers
84                        are currently accessing the plaintext of this
85                        object. */
86
87   int is_active;    /* Flag, see comment at top. */
88
89   /* The length of the key and the key itself.  The cache item is
90      dynamically allocated to fit the size of the key.  Note, that
91      the key is a binary blob. */
92   size_t keylen;
93   char key[1];
94 };
95 typedef struct cache_item *cache_item_t;
96
97
98 /* The actual cache is a simple list anchord at this global
99    variable. */
100 static cache_item_t the_cache;
101
102 /* Mutex used to serialize access to the cache. */
103 static HANDLE cache_mutex;
104
105
106
107 /* Initialize this module.  Called at a very early stage during DLL
108    loading.  Returns 0 on success. */
109 int
110 initialize_msgcache (void)
111 {
112   SECURITY_ATTRIBUTES sa;
113   
114   memset (&sa, 0, sizeof sa);
115   sa.bInheritHandle = TRUE;
116   sa.lpSecurityDescriptor = NULL;
117   sa.nLength = sizeof sa;
118   cache_mutex = CreateMutex (&sa, FALSE, NULL);
119   return cache_mutex? 0 : -1;
120 }
121
122
123 /* Acquire the mutex.  Returns 0 on success. */
124 static int 
125 lock_cache (void)
126 {
127   int code = WaitForSingleObject (cache_mutex, INFINITE);
128   if (code != WAIT_OBJECT_0)
129     log_error ("%s:%s: waiting on mutex failed: code=%#x\n",
130                __FILE__, __func__, code);
131   return code != WAIT_OBJECT_0;
132 }
133
134 /* Release the mutex.  No error is returned because this is a fatal
135    error anyway and there is no way to clean up. */
136 static void
137 unlock_cache (void)
138 {
139   if (!ReleaseMutex (cache_mutex))
140     log_error_w32 (-1, "%s:%s: ReleaseMutex failed", __FILE__, __func__);
141 }
142
143
144
145 /* Put the BODY of a message into the cache.  BODY should be a
146    malloced string, UTF8 encoded.  If TRANSFER is given as true, the
147    ownership of the malloced memory for BODY is transferred to this
148    module.  MESSAGE is the MAPI message object used to retrieve the
149    storarge key for the BODY. */
150 void
151 msgcache_put (char *body, int transfer, LPMESSAGE message)
152 {
153   HRESULT hr;
154   LPSPropValue lpspvFEID = NULL;
155   cache_item_t item;
156   size_t keylen;
157   void *key;
158
159   if (!message)
160     return; /* No message: Nop. */
161
162   hr = HrGetOneProp ((LPMAPIPROP)message, PR_SEARCH_KEY, &lpspvFEID);
163   if (FAILED (hr))
164     {
165       log_error ("%s: HrGetOneProp failed: hr=%#lx\n", __func__, hr);
166       return;
167     }
168     
169   if ( PROP_TYPE (lpspvFEID->ulPropTag) != PT_BINARY )
170     {
171       log_error ("%s: HrGetOneProp returned unexpected property type\n",
172                  __func__);
173       MAPIFreeBuffer (lpspvFEID);
174       return;
175     }
176   keylen = lpspvFEID->Value.bin.cb;
177   key = lpspvFEID->Value.bin.lpb;
178
179   if (!keylen || !key || keylen > 10000)
180     {
181       log_error ("%s: malformed PR_SEARCH_KEY\n", __func__);
182       MAPIFreeBuffer (lpspvFEID);
183       return;
184     }
185
186   item = xmalloc (sizeof *item + keylen - 1);
187   item->next = NULL;
188   item->ref = 0;
189   item->is_active = 0;
190   item->plaintext = transfer? body : xstrdup (body);
191   item->length = strlen (body);
192   item->keylen = keylen;
193   memcpy (item->key, key, keylen);
194
195   MAPIFreeBuffer (lpspvFEID);
196
197   if (!lock_cache ())
198     {
199       /* FIXME: Decide whether to kick out some entries. */
200       item->next = the_cache;
201       the_cache = item;
202       unlock_cache ();
203     }
204   msgcache_set_active (message);
205 }
206
207
208 /* If MESSAGE is in our cse set it's active flag and reset the active
209    flag of all others.  */
210 void
211 msgcache_set_active (LPMESSAGE message)
212 {
213   HRESULT hr;
214   LPSPropValue lpspvFEID = NULL;
215   cache_item_t item;
216   size_t keylen = 0;
217   void *key = NULL;
218   int okay = 0;
219
220   if (!message)
221     return; /* No message: Nop. */
222   if (!the_cache)
223     return; /* No cache: avoid needless work. */
224   
225   hr = HrGetOneProp ((LPMAPIPROP)message, PR_SEARCH_KEY, &lpspvFEID);
226   if (FAILED (hr))
227     {
228       log_error ("%s: HrGetOneProp failed: hr=%#lx\n", __func__, hr);
229       goto leave;
230     }
231     
232   if ( PROP_TYPE (lpspvFEID->ulPropTag) != PT_BINARY )
233     {
234       log_error ("%s: HrGetOneProp returned unexpected property type\n",
235                  __func__);
236       goto leave;
237     }
238   keylen = lpspvFEID->Value.bin.cb;
239   key = lpspvFEID->Value.bin.lpb;
240
241   if (!keylen || !key || keylen > 10000)
242     {
243       log_error ("%s: malformed PR_SEARCH_KEY\n", __func__);
244       goto leave;
245     }
246   okay = 1;
247
248
249  leave:
250   if (!lock_cache ())
251     {
252       for (item = the_cache; item; item = item->next)
253         item->is_active = 0;
254       if (okay)
255         {
256           for (item = the_cache; item; item = item->next)
257             {
258               if (item->keylen == keylen 
259                   && !memcmp (item->key, key, keylen))
260                 {
261                   item->is_active = 1;
262                   break;
263                 }
264             }
265         }
266       unlock_cache ();
267     }
268
269   MAPIFreeBuffer (lpspvFEID);
270 }
271
272
273 /* Locate a plaintext stored under a key derived from the MAPI object
274    MESSAGE and return it.  The user must provide the address of a void
275    pointer variable which he later needs to pass to the
276    msgcache_unref. Returns NULL if no plaintext is available;
277    msgcache_unref is then not required but won't harm either. */
278 const char *
279 msgcache_get (LPMESSAGE message, void **refhandle)
280 {
281   cache_item_t item;
282   const char *result = NULL;
283
284   *refhandle = NULL;
285
286   if (!message)
287     return NULL; /* No message: Nop. */
288   if (!the_cache)
289     return NULL; /* No cache: avoid needless work. */
290   
291 #if 0 /* Old code, see comment at top of file. */
292   HRESULT hr;
293   LPSPropValue lpspvFEID = NULL;
294   size_t keylen;
295   void *key;
296
297   hr = HrGetOneProp ((LPMAPIPROP)message, PR_SEARCH_KEY, &lpspvFEID);
298   if (FAILED (hr))
299     {
300       log_error ("%s: HrGetOneProp failed: hr=%#lx\n", __func__, hr);
301       return NULL;
302     }
303     
304   if ( PROP_TYPE (lpspvFEID->ulPropTag) != PT_BINARY )
305     {
306       log_error ("%s: HrGetOneProp returned unexpected property type\n",
307                  __func__);
308       MAPIFreeBuffer (lpspvFEID);
309       return NULL;
310     }
311   keylen = lpspvFEID->Value.bin.cb;
312   key = lpspvFEID->Value.bin.lpb;
313
314   if (!keylen || !key || keylen > 10000)
315     {
316       log_error ("%s: malformed PR_SEARCH_KEY\n", __func__);
317       MAPIFreeBuffer (lpspvFEID);
318       return NULL;
319     }
320
321
322   if (!lock_cache ())
323     {
324       for (item = the_cache; item; item = item->next)
325         {
326           if (item->keylen == keylen 
327               && !memcmp (item->key, key, keylen))
328             {
329               item->ref++;
330               result = item->plaintext; 
331               *refhandle = item;
332               break;
333             }
334         }
335       unlock_cache ();
336     }
337
338   MAPIFreeBuffer (lpspvFEID);
339 #else /* New code. */
340   if (!lock_cache ())
341     {
342       for (item = the_cache; item; item = item->next)
343         {
344           if (item->is_active)
345             {
346               item->ref++;
347               result = item->plaintext; 
348               *refhandle = item;
349               break;
350             }
351         }
352       unlock_cache ();
353     }
354
355 #endif /* New code. */
356
357   return result;
358 }
359
360
361 /* Release access to a value returned by msgcache_get.  REFHANDLE is
362    the value as stored in the pointer variable by msgcache_get. */
363 void
364 msgcache_unref (void *refhandle)
365 {
366   cache_item_t item;
367
368   if (!refhandle)
369     return;
370
371   if (!lock_cache ())
372     {
373       for (item = the_cache; item; item = item->next)
374         {
375           if (item == refhandle)
376             {
377               if (item->ref < 1)
378                 log_error ("%s: zero reference count for item %p\n",
379                            __func__, item);
380               else
381                 item->ref--;
382               /* Fixme: check whether this one has been scheduled for
383                  removal. */
384               break;
385             }
386         }
387       unlock_cache ();
388       if (!item)
389         log_error ("%s: invalid reference handle %p detected\n",
390                    __func__, refhandle);
391     }
392 }