Add minimalistic protected-headers support
[gpgol.git] / src / revert.cpp
1 /* revert.cpp - Convert messages back to the orignal format
2  * Copyright (C) 2008 g10 Code GmbH
3  * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
4  * Software engineering by Intevation GmbH
5  * 
6  * This file is part of GpgOL.
7  * 
8  * GpgOL is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  * 
13  * GpgOL is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  * 
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <windows.h>
26
27 #include "mymapi.h"
28 #include "mymapitags.h"
29 #include "common.h"
30 #include "oomhelp.h"
31 #include "mapihelp.h"
32 #include "mimemaker.h"
33 #include "mail.h"
34
35 /* Helper method for mailitem_revert to add changes on the mapi side
36    and save them. */
37 static int finalize_mapi (LPMESSAGE message)
38 {
39   HRESULT hr;
40   SPropTagArray proparray;
41   ULONG tag_id;
42
43   if (get_gpgollastdecrypted_tag (message, &tag_id))
44     {
45       log_error ("%s:%s: can't getlastdecrypted tag",
46                  SRCNAME, __func__);
47       return -1;
48     }
49   proparray.cValues = 1;
50   proparray.aulPropTag[0] = tag_id;
51   hr = message->DeleteProps (&proparray, NULL);
52   if (hr)
53     {
54       log_error ("%s:%s: failed to delete lastdecrypted tag",
55                  SRCNAME, __func__);
56       return -1;
57     }
58
59   return mapi_save_changes (message, FORCE_SAVE);
60 }
61
62 /* Similar to gpgol_message_revert but works on OOM and is
63    used by the Ol > 2010 implementation.
64    Doing this through OOM was necessary as the MAPI structures
65    in the write event are not in sync with the OOM side.
66    Trying to revert in the AfterWrite where MAPI is synced
67    led to an additional save_changes after the wipe and
68    so an additional sync.
69    Updating the BODY through MAPI did not appear to work
70    at all. Not sure why this is the case.
71    Using the property accessor methods instead of
72    MAPI properties might also not be necessary.
73
74    Returns 0 on success, -1 on error. On error this
75    function might leave plaintext in the mail.    */
76 EXTERN_C LONG __stdcall
77 gpgol_mailitem_revert (LPDISPATCH mailitem)
78 {
79   LPDISPATCH attachments = NULL;
80   LPMESSAGE message = NULL;
81   char *item_str;
82   char *msgcls = NULL;
83   int i;
84   int count = 0;
85   LONG result = -1;
86   msgtype_t msgtype;
87   int body_restored = 0;
88   LPDISPATCH *to_delete = NULL;
89   int del_cnt = 0;
90   LPDISPATCH to_restore = NULL;
91   int mosstmpl_found = 0;
92   int is_smime = 0;
93   Mail *mail = NULL;
94
95   /* Check whether we need to care about this message.  */
96   msgcls = get_pa_string (mailitem, PR_MESSAGE_CLASS_W_DASL);
97   log_debug ("%s:%s: message class is `%s'\n",
98              SRCNAME, __func__, msgcls? msgcls:"[none]");
99   if (!msgcls)
100     {
101       return -1;
102     }
103   if ( !( !strncmp (msgcls, "IPM.Note.GpgOL", 14)
104           && (!msgcls[14] || msgcls[14] == '.') ) )
105     {
106       xfree (msgcls);
107       log_error ("%s:%s: Message processed but not our class. Bug.",
108                  SRCNAME, __func__);
109       return -1;
110     }
111
112   mail = Mail::getMailForItem (mailitem);
113   if (!mail)
114     {
115       xfree (msgcls);
116       log_error ("%s:%s: No mail object for mailitem. Bug.",
117                  SRCNAME, __func__);
118       return -1;
119     }
120   is_smime = mail->isSMIME_m ();
121
122   message = get_oom_base_message (mailitem);
123   attachments = get_oom_object (mailitem, "Attachments");
124
125   if (!message)
126     {
127       log_error ("%s:%s: No message object.",
128                  SRCNAME, __func__);
129       goto done;
130     }
131
132   if (!attachments)
133     {
134       log_error ("%s:%s: No attachments object.",
135                  SRCNAME, __func__);
136       goto done;
137     }
138   msgtype = mapi_get_message_type (message);
139
140   if (msgtype != MSGTYPE_GPGOL_PGP_MESSAGE &&
141       msgtype != MSGTYPE_GPGOL_MULTIPART_ENCRYPTED &&
142       msgtype != MSGTYPE_GPGOL_MULTIPART_SIGNED &&
143       msgtype != MSGTYPE_GPGOL_OPAQUE_ENCRYPTED &&
144       msgtype != MSGTYPE_GPGOL_OPAQUE_SIGNED)
145     {
146       log_error ("%s:%s: Revert not supported for msgtype: %i",
147                  SRCNAME, __func__, msgtype);
148       goto done;
149     }
150
151
152   count = get_oom_int (attachments, "Count");
153   to_delete = (LPDISPATCH*) xmalloc (count * sizeof (LPDISPATCH));
154
155   /* Yes the items start at 1! */
156   for (i = 1; i <= count; i++)
157     {
158       LPDISPATCH attachment;
159       attachtype_t att_type;
160
161       if (gpgrt_asprintf (&item_str, "Item(%i)", i) == -1)
162         {
163           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
164           goto done;
165         }
166
167       memdbg_alloc (item_str);
168       attachment = get_oom_object (attachments, item_str);
169       xfree (item_str);
170       if (!attachment)
171         {
172           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
173           goto done;
174         }
175
176       if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type))
177         {
178           att_type = ATTACHTYPE_FROMMOSS;
179         }
180
181       switch (att_type)
182         {
183           case ATTACHTYPE_PGPBODY:
184             {
185               /* Restore Body */
186               char *body = get_pa_string (attachment, PR_ATTACH_DATA_BIN_DASL);
187               if (!body)
188                 {
189                   log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
190                   gpgol_release (attachment);
191                   goto done;
192                 }
193               log_debug ("%s:%s: Restoring pgp-body.",
194                          SRCNAME, __func__);
195               if (put_oom_string (mailitem, "Body", body))
196                 {
197                   log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
198                   xfree (body);
199                   gpgol_release (attachment);
200                   goto done;
201                 }
202               body_restored = 1;
203               xfree (body);
204               to_delete[del_cnt++] = attachment;
205               break;
206             } /* No break we also want to delete that. */
207           case ATTACHTYPE_MOSS:
208             {
209               char *mime_tag = get_pa_string (attachment,
210                                               PR_ATTACH_MIME_TAG_DASL);
211               if (!mime_tag)
212                 {
213                   log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
214                 }
215               else if (msgtype == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED &&
216                        !strcmp (mime_tag, "application/octet-stream"))
217                 {
218                   /* This is the body attachment of a multipart encrypted
219                      message. Rebuild the message. */
220                   to_restore = attachment;
221                   to_delete[del_cnt++] = attachment;
222                 }
223               else if (msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED &&
224                        mime_tag && !strcmp (mime_tag, "multipart/signed"))
225                 {
226                   /* This is the MIME formatted MOSS attachment of a multipart
227                      signed message. Rebuild the MIME structure from that.
228                      This means treating it as a MOSSTMPL */
229                   mosstmpl_found = 1;
230                 }
231               else if (is_smime)
232                 {
233                   /* Same here. No restoration but just rebuilding from the
234                      attachment. */
235                   mosstmpl_found = 1;
236                 }
237               else
238                 {
239                   log_oom ("%s:%s: Skipping attachment with tag: %s", SRCNAME,
240                            __func__, mime_tag);
241                   to_delete[del_cnt++] = attachment;
242                 }
243               xfree (mime_tag);
244               break;
245             }
246           case ATTACHTYPE_FROMMOSS:
247           case ATTACHTYPE_FROMMOSS_DEC:
248             {
249               to_delete[del_cnt++] = attachment;
250               break;
251             }
252           case ATTACHTYPE_MOSSTEMPL:
253             /* This is a newly created attachment containing a MIME structure
254                other clients could handle */
255             {
256               if (mosstmpl_found)
257                 {
258                   log_error ("More then one mosstempl.");
259                   goto done;
260                 }
261               mosstmpl_found = 1;
262               break;
263             }
264           default:
265             to_delete[del_cnt++] = attachment;
266         }
267     }
268
269   if (to_restore && !mosstmpl_found)
270     {
271       log_debug ("%s:%s: Restoring from MOSS.", SRCNAME, __func__);
272       if (restore_msg_from_moss (message, to_restore, msgtype,
273                                  msgcls))
274         {
275           log_error ("%s:%s: Error: %i", SRCNAME, __func__,
276                      __LINE__);
277         }
278       else
279         {
280           to_restore = NULL;
281         }
282     }
283   if (to_restore || mosstmpl_found)
284     {
285       HRESULT hr;
286       SPropValue prop;
287       /* Message was either restored or the only attachment is the
288          mosstmplate in which case we need to activate the
289          MultipartSigned magic.*/
290       prop.ulPropTag = PR_MESSAGE_CLASS_A;
291       if (is_smime)
292         {
293 #if 0
294           /* FIXME this does not appear to work somehow. */
295           if (opt.enable_smime)
296             {
297               prop.Value.lpszA =
298                 (char*) "IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned";
299               hr = HrSetOneProp (message, &prop);
300             }
301           else
302 #endif
303             {
304               ULONG tag;
305               if (msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED)
306                 prop.Value.lpszA = (char*) "IPM.Note.SMIME.MultipartSigned";
307               else
308                 prop.Value.lpszA = (char*) "IPM.Note.SMIME";
309               hr = HrSetOneProp (message, &prop);
310
311               if (!get_gpgolmsgclass_tag (message, &tag))
312                 {
313                   SPropTagArray proparray;
314                   proparray.cValues = 1;
315                   proparray.aulPropTag[0] = tag;
316                   hr = message->DeleteProps (&proparray, NULL);
317                   if (hr)
318                     {
319                       log_error ("%s:%s: deleteprops smime failed: hr=%#lx\n",
320                                  SRCNAME, __func__, hr);
321
322                     }
323                 }
324             }
325         }
326       else if (msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED)
327         {
328           prop.Value.lpszA =
329             (char*) "IPM.Note.InfoPathForm.GpgOLS.SMIME.MultipartSigned";
330           hr = HrSetOneProp (message, &prop);
331         }
332       else
333         {
334           prop.Value.lpszA =
335             (char*) "IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned";
336           hr = HrSetOneProp (message, &prop);
337         }
338       if (hr)
339         {
340           log_error ("%s:%s: error setting the message class: hr=%#lx\n",
341                      SRCNAME, __func__, hr);
342           goto done;
343         }
344
345       /* Backup the real message class */
346       if (!is_smime || opt.enable_smime)
347         {
348           if (mapi_set_gpgol_msg_class (message, msgcls))
349             {
350               log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
351               goto done;
352             }
353         }
354       else if (is_smime && !opt.enable_smime)
355         {
356           /* SMIME is disabled remove our categories. */
357           mail->removeCategories_o ();
358         }
359     }
360
361   result = 0;
362 done:
363
364   /* Do the deletion body wipe even on error. */
365
366   for (i = 0; i < del_cnt; i++)
367     {
368       LPDISPATCH attachment = to_delete[i];
369
370       if (attachment == to_restore)
371         {
372           /* If restoring failed to restore is still set. In that case
373              do not delete the MOSS attachment to avoid data loss. */
374           continue;
375         }
376       /* Delete the attachments that are marked to delete */
377       if (invoke_oom_method (attachment, "Delete", NULL))
378         {
379           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
380           result = -1;
381         }
382     }
383   if (!body_restored && put_oom_string (mailitem, "Body", ""))
384     {
385       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
386       result = -1;
387     }
388
389   for (i = 0; i < del_cnt; i++)
390     {
391       gpgol_release (to_delete[i]);
392     }
393
394   xfree (to_delete);
395   gpgol_release (attachments);
396   xfree (msgcls);
397
398   if (!result && finalize_mapi (message))
399     {
400       log_error ("%s:%s: Finalize failed.",
401                  SRCNAME, __func__);
402       result = -1;
403     }
404
405   gpgol_release (message);
406
407   return result;
408 }