Convert mimemaker to cpp
[gpgol.git] / src / mimemaker.cpp
1 /* mimemaker.c - Construct MIME message out of a MAPI
2  *    Copyright (C) 2007, 2008 g10 Code GmbH
3  *    Copyright (C) 2015 Intevation GmbH
4  *
5  * This file is part of GpgOL.
6  *
7  * GpgOL is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * GpgOL is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <assert.h>
29 #include <string.h>
30 #include <ctype.h>
31
32 #define COBJMACROS
33 #include <windows.h>
34 #include <objidl.h>
35
36 #include "mymapi.h"
37 #include "mymapitags.h"
38
39 #include "common.h"
40 #include "engine.h"
41 #include "mapihelp.h"
42 #include "mimemaker.h"
43 #include "oomhelp.h"
44 #include "gpgolstr.h"
45
46 static const unsigned char oid_mimetag[] =
47     {0x2A, 0x86, 0x48, 0x86, 0xf7, 0x14, 0x03, 0x0a, 0x04};
48
49 /* The base-64 list used for base64 encoding. */
50 static unsigned char bintoasc[64+1] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
51                                        "abcdefghijklmnopqrstuvwxyz"
52                                        "0123456789+/");
53
54
55 /* Object used to collect data in a memory buffer.  */
56 struct databuf_s
57 {
58   size_t len;      /* Used length.  */
59   size_t size;     /* Allocated length of BUF.  */
60   char *buf;       /* Malloced buffer.  */
61 };
62
63
64 /*** local prototypes  ***/
65 static int write_multistring (sink_t sink, const char *text1,
66                               ...) GPGOL_GCC_A_SENTINEL(0);
67
68
69
70
71 \f
72 /* Standard write method used with a sink_t object.  */
73 int
74 sink_std_write (sink_t sink, const void *data, size_t datalen)
75 {
76   HRESULT hr;
77   LPSTREAM stream = static_cast<LPSTREAM>(sink->cb_data);
78
79   if (!stream)
80     {
81       log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
82       return -1;
83     }
84   if (!data)
85     return 0;  /* Flush - nothing to do here.  */
86
87   hr = stream->Write(data, datalen, NULL);
88   if (hr)
89     {
90       log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
91       return -1;
92     }
93   return 0;
94 }
95
96 /* Write method used with a sink_t that contains a file object.  */
97 int
98 sink_file_write (sink_t sink, const void *data, size_t datalen)
99 {
100   HANDLE hFile = sink->cb_data;
101   DWORD written = 0;
102
103   if (!hFile || hFile == INVALID_HANDLE_VALUE)
104     {
105       log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
106       return -1;
107     }
108   if (!data)
109     return 0;  /* Flush - nothing to do here.  */
110
111   if (!WriteFile (hFile, data, datalen, &written, NULL))
112     {
113       log_error ("%s:%s: Write failed: ", SRCNAME, __func__);
114       return -1;
115     }
116   return 0;
117 }
118
119
120 /* Make sure that PROTOCOL is usable or return a suitable protocol.
121    On error PROTOCOL_UNKNOWN is returned.  */
122 static protocol_t
123 check_protocol (protocol_t protocol)
124 {
125   switch (protocol)
126     {
127     case PROTOCOL_UNKNOWN:
128       return PROTOCOL_UNKNOWN;
129     case PROTOCOL_OPENPGP:
130     case PROTOCOL_SMIME:
131       return protocol;
132     }
133
134   log_error ("%s:%s: BUG", SRCNAME, __func__);
135   return PROTOCOL_UNKNOWN;
136 }
137
138
139
140 /* Create a new MAPI attchment for MESSAGE which will be used to
141    prepare the MIME message.  On sucess the stream to write the data
142    to is stored at STREAM and the attachment object itself is
143    returned.  The caller needs to call SaveChanges.  Returns NULL on
144    failure in which case STREAM will be set to NULL.  */
145 static LPATTACH
146 create_mapi_attachment (LPMESSAGE message, sink_t sink)
147 {
148   HRESULT hr;
149   ULONG pos;
150   SPropValue prop;
151   LPATTACH att = NULL;
152   LPUNKNOWN punk;
153
154   sink->cb_data = NULL;
155   sink->writefnc = NULL;
156   hr = message->CreateAttach(NULL, 0, &pos, &att);
157   if (hr)
158     {
159       log_error ("%s:%s: can't create attachment: hr=%#lx\n",
160                  SRCNAME, __func__, hr);
161       return NULL;
162     }
163
164   prop.ulPropTag = PR_ATTACH_METHOD;
165   prop.Value.ul = ATTACH_BY_VALUE;
166   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
167   if (hr)
168     {
169       log_error ("%s:%s: can't set attach method: hr=%#lx\n",
170                  SRCNAME, __func__, hr);
171       goto failure;
172     }
173
174   /* Mark that attachment so that we know why it has been created.  */
175   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
176     goto failure;
177   prop.Value.l = ATTACHTYPE_MOSSTEMPL;
178   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
179   if (hr)
180     {
181       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
182                  SRCNAME, __func__, "GpgOL Attach Type", hr);
183       goto failure;
184     }
185
186
187   /* We better insert a short filename. */
188   prop.ulPropTag = PR_ATTACH_FILENAME_A;
189   prop.Value.lpszA = GpgOLStr (MIMEATTACHFILENAME);
190   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
191   if (hr)
192     {
193       log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
194                  SRCNAME, __func__, hr);
195       goto failure;
196     }
197
198
199   /* Even for encrypted messages we need to set the MAPI property to
200      multipart/signed.  This seems to be a part of the trigger which
201      leads OL to process such a message in a special way.  */
202   prop.ulPropTag = PR_ATTACH_TAG;
203   prop.Value.bin.cb  = sizeof oid_mimetag;
204   prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
205   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
206   if (!hr)
207     {
208       prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
209       prop.Value.lpszA = GpgOLStr("multipart/signed");
210       hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
211     }
212   if (hr)
213     {
214       log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
215                  SRCNAME, __func__, hr);
216       goto failure;
217     }
218
219   punk = NULL;
220   hr = att->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0,
221                          (MAPI_CREATE|MAPI_MODIFY), &punk);
222   if (FAILED (hr))
223     {
224       log_error ("%s:%s: can't create output stream: hr=%#lx\n",
225                  SRCNAME, __func__, hr);
226       goto failure;
227     }
228   sink->cb_data = (LPSTREAM)punk;
229   sink->writefnc = sink_std_write;
230   return att;
231
232  failure:
233   gpgol_release (att);
234   return NULL;
235 }
236
237
238 /* Write data to a sink_t.  */
239 int
240 write_buffer (sink_t sink, const void *data, size_t datalen)
241 {
242   if (!sink || !sink->writefnc)
243     {
244       log_error ("%s:%s: sink not properly setup", SRCNAME, __func__);
245       return -1;
246     }
247   return sink->writefnc (sink, data, datalen);
248 }
249
250 /* Same as above but used for passing as callback function.  This
251    fucntion does not return an error code but the number of bytes
252    written.  */
253 int
254 write_buffer_for_cb (void *opaque, const void *data, size_t datalen)
255 {
256   sink_t sink = (sink_t) opaque;
257   sink->enc_counter += datalen;
258   return write_buffer (sink, data, datalen) ? -1 : datalen;
259 }
260
261
262 /* Write the string TEXT to the IStream STREAM.  Returns 0 on sucsess,
263    prints an error message and returns -1 on error.  */
264 static int
265 write_string (sink_t sink, const char *text)
266 {
267   return write_buffer (sink, text, strlen (text));
268 }
269
270
271 /* Write the string TEXT1 and all folloing arguments of type (const
272    char*) to the SINK.  The list of argumens needs to be terminated
273    with a NULL.  Returns 0 on sucsess, prints an error message and
274    returns -1 on error.  */
275 static int
276 write_multistring (sink_t sink, const char *text1, ...)
277 {
278   va_list arg_ptr;
279   int rc;
280   const char *s;
281
282   va_start (arg_ptr, text1);
283   s = text1;
284   do
285     rc = write_string (sink, s);
286   while (!rc && (s=va_arg (arg_ptr, const char *)));
287   va_end (arg_ptr);
288   return rc;
289 }
290
291
292
293 /* Helper to write a boundary to the output sink.  The leading LF
294    will be written as well.  */
295 static int
296 write_boundary (sink_t sink, const char *boundary, int lastone)
297 {
298   int rc = write_string (sink, "\r\n--");
299   if (!rc)
300     rc = write_string (sink, boundary);
301   if (!rc)
302     rc = write_string (sink, lastone? "--\r\n":"\r\n");
303   return rc;
304 }
305
306
307 /* Write DATALEN bytes of DATA to SINK in base64 encoding.  This
308    creates a complete Base64 chunk including the trailing fillers.  */
309 static int
310 write_b64 (sink_t sink, const void *data, size_t datalen)
311 {
312   int rc;
313   const unsigned char *p;
314   unsigned char inbuf[4];
315   int idx, quads;
316   char outbuf[2048];
317   size_t outlen;
318
319   log_debug ("  writing base64 of length %d\n", (int)datalen);
320   idx = quads = 0;
321   outlen = 0;
322   for (p = (const unsigned char*)data; datalen; p++, datalen--)
323     {
324       inbuf[idx++] = *p;
325       if (idx > 2)
326         {
327           /* We need space for a quad and a possible CR,LF.  */
328           if (outlen+4+2 >= sizeof outbuf)
329             {
330               if ((rc = write_buffer (sink, outbuf, outlen)))
331                 return rc;
332               outlen = 0;
333             }
334           outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
335           outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
336                                        |((inbuf[1] >> 4)&017))&077];
337           outbuf[outlen++] = bintoasc[(((inbuf[1]<<2)&074)
338                                        |((inbuf[2]>>6)&03))&077];
339           outbuf[outlen++] = bintoasc[inbuf[2]&077];
340           idx = 0;
341           if (++quads >= (64/4))
342             {
343               quads = 0;
344               outbuf[outlen++] = '\r';
345               outbuf[outlen++] = '\n';
346             }
347         }
348     }
349
350   /* We need space for a quad and a final CR,LF.  */
351   if (outlen+4+2 >= sizeof outbuf)
352     {
353       if ((rc = write_buffer (sink, outbuf, outlen)))
354         return rc;
355       outlen = 0;
356     }
357   if (idx)
358     {
359       outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
360       if (idx == 1)
361         {
362           outbuf[outlen++] = bintoasc[((*inbuf<<4)&060)&077];
363           outbuf[outlen++] = '=';
364           outbuf[outlen++] = '=';
365         }
366       else
367         {
368           outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
369                                     |((inbuf[1]>>4)&017))&077];
370           outbuf[outlen++] = bintoasc[((inbuf[1]<<2)&074)&077];
371           outbuf[outlen++] = '=';
372         }
373       ++quads;
374     }
375
376   if (quads)
377     {
378       outbuf[outlen++] = '\r';
379       outbuf[outlen++] = '\n';
380     }
381
382   if (outlen)
383     {
384       if ((rc = write_buffer (sink, outbuf, outlen)))
385         return rc;
386     }
387
388   return 0;
389 }
390
391 /* Write DATALEN bytes of DATA to SINK in quoted-prinable encoding. */
392 static int
393 write_qp (sink_t sink, const void *data, size_t datalen)
394 {
395   int rc;
396   const unsigned char *p;
397   char outbuf[80];  /* We only need 76 octect + 2 for the lineend. */
398   int outidx;
399
400   /* Check whether the current character is followed by a line ending.
401      Note that the end of the etxt also counts as a lineending */
402 #define nextlf_p() ((datalen > 2 && p[1] == '\r' && p[2] == '\n') \
403                     || (datalen > 1 && p[1] == '\n')              \
404                     || datalen == 1 )
405
406   /* Macro to insert a soft line break if needed.  */
407 # define do_softlf(n) \
408           do {                                                        \
409             if (outidx + (n) > 76                                     \
410                 || (outidx + (n) == 76 && !nextlf_p()))               \
411               {                                                       \
412                 outbuf[outidx++] = '=';                               \
413                 outbuf[outidx++] = '\r';                              \
414                 outbuf[outidx++] = '\n';                              \
415                 if ((rc = write_buffer (sink, outbuf, outidx)))       \
416                   return rc;                                          \
417                 outidx = 0;                                           \
418               }                                                       \
419           } while (0)
420
421   log_debug ("  writing qp of length %d\n", (int)datalen);
422   outidx = 0;
423   for (p = (const unsigned char*) data; datalen; p++, datalen--)
424     {
425       if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
426         {
427           /* Line break.  */
428           outbuf[outidx++] = '\r';
429           outbuf[outidx++] = '\n';
430           if ((rc = write_buffer (sink, outbuf, outidx)))
431             return rc;
432           outidx = 0;
433           if (*p == '\r')
434             {
435               p++;
436               datalen--;
437             }
438         }
439       else if (*p == '\t' || *p == ' ')
440         {
441           /* Check whether tab or space is followed by a line break
442              which forbids verbatim encoding.  If we are already at
443              the end of the buffer we take that as a line end too. */
444           if (nextlf_p())
445             {
446               do_softlf (3);
447               outbuf[outidx++] = '=';
448               outbuf[outidx++] = tohex ((*p>>4)&15);
449               outbuf[outidx++] = tohex (*p&15);
450             }
451           else
452             {
453               do_softlf (1);
454               outbuf[outidx++] = *p;
455             }
456
457         }
458       else if (!outidx && *p == '.' && nextlf_p () )
459         {
460           /* We better protect a line with just a single dot.  */
461           outbuf[outidx++] = '=';
462           outbuf[outidx++] = tohex ((*p>>4)&15);
463           outbuf[outidx++] = tohex (*p&15);
464         }
465       else if (!outidx && datalen >= 5 && !memcmp (p, "From ", 5))
466         {
467           /* Protect the 'F' so that MTAs won't prefix the "From "
468              with an '>' */
469           outbuf[outidx++] = '=';
470           outbuf[outidx++] = tohex ((*p>>4)&15);
471           outbuf[outidx++] = tohex (*p&15);
472         }
473       else if (*p >= '!' && *p <= '~' && *p != '=')
474         {
475           do_softlf (1);
476           outbuf[outidx++] = *p;
477         }
478       else
479         {
480           do_softlf (3);
481           outbuf[outidx++] = '=';
482           outbuf[outidx++] = tohex ((*p>>4)&15);
483           outbuf[outidx++] = tohex (*p&15);
484         }
485     }
486   if (outidx)
487     {
488       outbuf[outidx++] = '\r';
489       outbuf[outidx++] = '\n';
490       if ((rc = write_buffer (sink, outbuf, outidx)))
491         return rc;
492     }
493
494 # undef do_softlf
495 # undef nextlf_p
496   return 0;
497 }
498
499
500 /* Write DATALEN bytes of DATA to SINK in plain ascii encoding. */
501 static int
502 write_plain (sink_t sink, const void *data, size_t datalen)
503 {
504   int rc;
505   const unsigned char *p;
506   char outbuf[100];
507   int outidx;
508
509   log_debug ("  writing ascii of length %d\n", (int)datalen);
510   outidx = 0;
511   for (p = (const unsigned char*) data; datalen; p++, datalen--)
512     {
513       if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
514         {
515           outbuf[outidx++] = '\r';
516           outbuf[outidx++] = '\n';
517           if ((rc = write_buffer (sink, outbuf, outidx)))
518             return rc;
519           outidx = 0;
520           if (*p == '\r')
521             {
522               p++;
523               datalen--;
524             }
525         }
526       else if (!outidx && *p == '.'
527                && ( (datalen > 2 && p[1] == '\r' && p[2] == '\n')
528                     || (datalen > 1 && p[1] == '\n')
529                     || datalen == 1))
530         {
531           /* Better protect a line with just a single dot.  We do
532              this by adding a space.  */
533           outbuf[outidx++] = *p;
534           outbuf[outidx++] = ' ';
535         }
536       else if (outidx > 80)
537         {
538           /* We should never be called for too long lines - QP should
539              have been used.  */
540           log_error ("%s:%s: BUG: line longer than exepcted",
541                      SRCNAME, __func__);
542           return -1;
543         }
544       else
545         outbuf[outidx++] = *p;
546     }
547
548   if (outidx)
549     {
550       outbuf[outidx++] = '\r';
551       outbuf[outidx++] = '\n';
552       if ((rc = write_buffer (sink, outbuf, outidx)))
553         return rc;
554     }
555
556   return 0;
557 }
558
559
560 /* Infer the conent type from DATA and FILENAME.  The return value is
561    a static string there won't be an error return.  In case Bae 64
562    encoding is required for the type true will be stored at FORCE_B64;
563    however, this is only a shortcut and if that is not set, the caller
564    should infer the encoding by otehr means. */
565 static const char *
566 infer_content_type (const char *data, size_t datalen, const char *filename,
567                     int is_mapibody, int *force_b64)
568 {
569   static struct {
570     char b64;
571     const char *suffix;
572     const char *ct;
573   } suffix_table[] =
574     {
575       { 1, "3gp",   "video/3gpp" },
576       { 1, "abw",   "application/x-abiword" },
577       { 1, "ai",    "application/postscript" },
578       { 1, "au",    "audio/basic" },
579       { 1, "bin",   "application/octet-stream" },
580       { 1, "class", "application/java-vm" },
581       { 1, "cpt",   "application/mac-compactpro" },
582       { 0, "css",   "text/css" },
583       { 0, "csv",   "text/comma-separated-values" },
584       { 1, "deb",   "application/x-debian-package" },
585       { 1, "dl",    "video/dl" },
586       { 1, "doc",   "application/msword" },
587       { 1, "dv",    "video/dv" },
588       { 1, "dvi",   "application/x-dvi" },
589       { 1, "eml",   "message/rfc822" },
590       { 1, "eps",   "application/postscript" },
591       { 1, "fig",   "application/x-xfig" },
592       { 1, "flac",  "application/x-flac" },
593       { 1, "fli",   "video/fli" },
594       { 1, "gif",   "image/gif" },
595       { 1, "gl",    "video/gl" },
596       { 1, "gnumeric", "application/x-gnumeric" },
597       { 1, "hqx",   "application/mac-binhex40" },
598       { 1, "hta",   "application/hta" },
599       { 0, "htm",   "text/html" },
600       { 0, "html",  "text/html" },
601       { 0, "ics",   "text/calendar" },
602       { 1, "jar",   "application/java-archive" },
603       { 1, "jpeg",  "image/jpeg" },
604       { 1, "jpg",   "image/jpeg" },
605       { 1, "js",    "application/x-javascript" },
606       { 1, "latex", "application/x-latex" },
607       { 1, "lha",   "application/x-lha" },
608       { 1, "lzh",   "application/x-lzh" },
609       { 1, "lzx",   "application/x-lzx" },
610       { 1, "m3u",   "audio/mpegurl" },
611       { 1, "m4a",   "audio/mpeg" },
612       { 1, "mdb",   "application/msaccess" },
613       { 1, "midi",  "audio/midi" },
614       { 1, "mov",   "video/quicktime" },
615       { 1, "mp2",   "audio/mpeg" },
616       { 1, "mp3",   "audio/mpeg" },
617       { 1, "mp4",   "video/mp4" },
618       { 1, "mpeg",  "video/mpeg" },
619       { 1, "mpega", "audio/mpeg" },
620       { 1, "mpg",   "video/mpeg" },
621       { 1, "mpga",  "audio/mpeg" },
622       { 1, "msi",   "application/x-msi" },
623       { 1, "mxu",   "video/vnd.mpegurl" },
624       { 1, "nb",    "application/mathematica" },
625       { 1, "oda",   "application/oda" },
626       { 1, "odb",   "application/vnd.oasis.opendocument.database" },
627       { 1, "odc",   "application/vnd.oasis.opendocument.chart" },
628       { 1, "odf",   "application/vnd.oasis.opendocument.formula" },
629       { 1, "odg",   "application/vnd.oasis.opendocument.graphics" },
630       { 1, "odi",   "application/vnd.oasis.opendocument.image" },
631       { 1, "odm",   "application/vnd.oasis.opendocument.text-master" },
632       { 1, "odp",   "application/vnd.oasis.opendocument.presentation" },
633       { 1, "ods",   "application/vnd.oasis.opendocument.spreadsheet" },
634       { 1, "odt",   "application/vnd.oasis.opendocument.text" },
635       { 1, "ogg",   "application/ogg" },
636       { 1, "otg",   "application/vnd.oasis.opendocument.graphics-template" },
637       { 1, "oth",   "application/vnd.oasis.opendocument.text-web" },
638       { 1, "otp",  "application/vnd.oasis.opendocument.presentation-template"},
639       { 1, "ots",   "application/vnd.oasis.opendocument.spreadsheet-template"},
640       { 1, "ott",   "application/vnd.oasis.opendocument.text-template" },
641       { 1, "pdf",   "application/pdf" },
642       { 1, "png",   "image/png" },
643       { 1, "pps",   "application/vnd.ms-powerpoint" },
644       { 1, "ppt",   "application/vnd.ms-powerpoint" },
645       { 1, "prf",   "application/pics-rules" },
646       { 1, "ps",    "application/postscript" },
647       { 1, "qt",    "video/quicktime" },
648       { 1, "rar",   "application/rar" },
649       { 1, "rdf",   "application/rdf+xml" },
650       { 1, "rpm",   "application/x-redhat-package-manager" },
651       { 0, "rss",   "application/rss+xml" },
652       { 1, "ser",   "application/java-serialized-object" },
653       { 0, "sh",    "application/x-sh" },
654       { 0, "shtml", "text/html" },
655       { 1, "sid",   "audio/prs.sid" },
656       { 0, "smil",  "application/smil" },
657       { 1, "snd",   "audio/basic" },
658       { 0, "svg",   "image/svg+xml" },
659       { 1, "tar",   "application/x-tar" },
660       { 0, "texi",  "application/x-texinfo" },
661       { 0, "texinfo", "application/x-texinfo" },
662       { 1, "tif",   "image/tiff" },
663       { 1, "tiff",  "image/tiff" },
664       { 1, "torrent", "application/x-bittorrent" },
665       { 1, "tsp",   "application/dsptype" },
666       { 0, "vrml",  "model/vrml" },
667       { 1, "vsd",   "application/vnd.visio" },
668       { 1, "wp5",   "application/wordperfect5.1" },
669       { 1, "wpd",   "application/wordperfect" },
670       { 0, "xhtml", "application/xhtml+xml" },
671       { 1, "xlb",   "application/vnd.ms-excel" },
672       { 1, "xls",   "application/vnd.ms-excel" },
673       { 1, "xlt",   "application/vnd.ms-excel" },
674       { 0, "xml",   "application/xml" },
675       { 0, "xsl",   "application/xml" },
676       { 0, "xul",   "application/vnd.mozilla.xul+xml" },
677       { 1, "zip",   "application/zip" },
678       { 0, NULL, NULL }
679     };
680   int i;
681   char suffix_buffer[12+1];
682   const char *suffix;
683
684   *force_b64 = 0;
685   suffix = filename? strrchr (filename, '.') : NULL;
686   if (suffix && strlen (suffix) < sizeof suffix_buffer -1 )
687     {
688       suffix++;
689       for (i=0; i < sizeof suffix_buffer - 1; i++)
690         suffix_buffer[i] = tolower (*(const unsigned char*)suffix);
691       suffix_buffer[i] = 0;
692       for (i=0; suffix_table[i].suffix; i++)
693         if (!strcmp (suffix_table[i].suffix, suffix_buffer))
694           {
695             if (suffix_table[i].b64)
696               *force_b64 = 1;
697             return suffix_table[i].ct;
698           }
699     }
700
701   /* Not found via filename, look at the content.  */
702
703   if (is_mapibody)
704     {
705       /* Fixme:  This is too simple. */
706       if (datalen > 6  && (!memcmp (data, "<html>", 6)
707                            ||!memcmp (data, "<HTML>", 6)))
708         return "text/html";
709       return "text/plain";
710     }
711
712   return "application/octet-stream";
713 }
714
715 /* Figure out the best encoding to be used for the part.  Return values are
716      0: Plain ASCII.
717      1: Quoted Printable
718      2: Base64  */
719 static int
720 infer_content_encoding (const void *data, size_t datalen)
721 {
722   const unsigned char *p;
723   int need_qp;
724   size_t len, maxlen, highbin, lowbin, ntotal;
725
726   ntotal = datalen;
727   len = maxlen = lowbin = highbin = 0;
728   need_qp = 0;
729   for (p = (const unsigned char*) data; datalen; p++, datalen--)
730     {
731       len++;
732       if ((*p & 0x80))
733         highbin++;
734       else if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
735         {
736           len--;
737           if (len > maxlen)
738             maxlen = len;
739           len = 0;
740         }
741       else if (*p == '\r')
742         {
743           /* CR not followed by a linefeed. */
744           lowbin++;
745         }
746       else if (*p == '\t' || *p == ' ' || *p == '\f')
747         ;
748       else if (*p < ' ' || *p == 127)
749         lowbin++;
750       else if (len == 1 && datalen > 2
751                && *p == '-' && p[1] == '-' && p[2] == ' '
752                && ( (datalen > 4 && p[3] == '\r' && p[4] == '\n')
753                     || (datalen > 3 && p[3] == '\n')
754                     || datalen == 3))
755         {
756           /* This is a "-- \r\n" line, thus it indicates the usual
757              signature line delimiter.  We need to protect the
758              trailing space.  */
759           need_qp = 1;
760         }
761       else if (len == 1 && datalen > 5 && !memcmp (p, "--=-=", 5))
762         {
763           /* This look pretty much like a our own boundary.
764              We better protect it by forcing QP encoding.  */
765           need_qp = 1;
766         }
767       else if (len == 1 && datalen >= 5 && !memcmp (p, "From ", 5))
768         {
769           /* The usual From hack is required so that MTAs do not
770              prefix it with an '>'.  */
771           need_qp = 1;
772         }
773     }
774   if (len > maxlen)
775     maxlen = len;
776
777   if (maxlen <= 76 && !lowbin && !highbin && !need_qp)
778     return 0; /* Plain ASCII is sufficient.  */
779
780   /* Somewhere in the Outlook documentation 20% is mentioned as
781      discriminating value for Base64.  Though our counting won't be
782      identical we use that value to behave closely to it. */
783   if (ntotal && ((float)(lowbin+highbin))/ntotal < 0.20)
784     return 1; /* Use quoted printable.  */
785
786   return 2;   /* Use base64.  */
787 }
788
789
790 /* Convert an utf8 input string to RFC2047 base64 encoding which
791    is the subset of RFC2047 outlook likes.
792    Return value needs to be freed.
793    */
794 static char *
795 utf8_to_rfc2047b (const char *input)
796 {
797   char *ret,
798        *encoded;
799   int inferred_encoding = 0;
800   if (!input)
801     {
802       return NULL;
803     }
804   inferred_encoding = infer_content_encoding (input, strlen (input));
805   if (!inferred_encoding)
806     {
807       return xstrdup (input);
808     }
809   log_debug ("%s:%s: Encoding attachment filename. With: %s ",
810              SRCNAME, __func__, inferred_encoding == 2 ? "Base64" : "QP");
811
812   if (inferred_encoding == 2)
813     {
814       encoded = b64_encode (input, strlen (input));
815       if (gpgrt_asprintf (&ret, "=?utf-8?B?%s?=", encoded) == -1)
816         {
817           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
818           xfree (encoded);
819           return NULL;
820         }
821     }
822   else
823     {
824       /* There is a Bug here. If you encode 4 Byte UTF-8 outlook can't
825          handle it itself. And sends out a message with ?? inserted in
826          that place. This triggers an invalid signature. */
827       encoded = qp_encode (input, strlen (input), NULL);
828       if (gpgrt_asprintf (&ret, "=?utf-8?Q?%s?=", encoded) == -1)
829         {
830           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
831           xfree (encoded);
832           return NULL;
833         }
834     }
835   xfree (encoded);
836   return ret;
837 }
838
839 /* Write a MIME part to SINK.  First the BOUNDARY is written (unless
840    it is NULL) then the DATA is analyzed and appropriate headers are
841    written.  If FILENAME is given it will be added to the part's
842    header.  IS_MAPIBODY should be passed as true if the data has been
843    retrieved from the body property.  */
844 static int
845 write_part (sink_t sink, const char *data, size_t datalen,
846             const char *boundary, const char *filename, int is_mapibody)
847 {
848   int rc;
849   const char *ct;
850   int use_b64, use_qp, is_text;
851   char *encoded_filename;
852
853   if (filename)
854     {
855       /* If there is a filename strip the directory part.  Take care
856          that there might be slashes or backslashes.  */
857       const char *s1 = strrchr (filename, '/');
858       const char *s2 = strrchr (filename, '\\');
859
860       if (!s1)
861         s1 = s2;
862       else if (s1 && s2 && s2 > s1)
863         s1 = s2;
864
865       if (s1)
866         filename = s1;
867       if (*filename && filename[1] == ':')
868         filename += 2;
869       if (!*filename)
870         filename = NULL;
871     }
872
873   log_debug ("Writing part of length %d%s filename=`%s'\n",
874              (int)datalen, is_mapibody? " (body)":"",
875              filename?filename:"[none]");
876
877   ct = infer_content_type (data, datalen, filename, is_mapibody, &use_b64);
878   use_qp = 0;
879   if (!use_b64)
880     {
881       switch (infer_content_encoding (data, datalen))
882         {
883         case 0: break;
884         case 1: use_qp = 1; break;
885         default: use_b64 = 1; break;
886         }
887     }
888   is_text = !strncmp (ct, "text/", 5);
889
890   if (boundary)
891     if ((rc = write_boundary (sink, boundary, 0)))
892       return rc;
893   if ((rc=write_multistring (sink,
894                              "Content-Type: ", ct,
895                              (is_text || filename? ";\r\n" :"\r\n"),
896                              NULL)))
897     return rc;
898
899   /* OL inserts a charset parameter in many cases, so we do it right
900      away for all text parts.  We can assume us-ascii if no special
901      encoding is required.  */
902   if (is_text)
903     if ((rc=write_multistring (sink,
904                                "\tcharset=\"",
905                                (!use_qp && !use_b64? "us-ascii" : "utf-8"),
906                                filename ? "\";\r\n" : "\"\r\n",
907                                NULL)))
908       return rc;
909
910   encoded_filename = utf8_to_rfc2047b (filename);
911   if (encoded_filename)
912     if ((rc=write_multistring (sink,
913                                "\tname=\"", encoded_filename, "\"\r\n",
914                                NULL)))
915       return rc;
916
917   /* Note that we need to output even 7bit because OL inserts that
918      anyway.  */
919   if ((rc = write_multistring (sink,
920                                "Content-Transfer-Encoding: ",
921                                (use_b64? "base64\r\n":
922                                 use_qp? "quoted-printable\r\n":"7bit\r\n"),
923                                NULL)))
924     return rc;
925
926   if (encoded_filename)
927     if ((rc=write_multistring (sink,
928                                "Content-Disposition: attachment;\r\n"
929                                "\tfilename=\"", encoded_filename, "\"\r\n",
930                                NULL)))
931       return rc;
932
933   xfree(encoded_filename);
934
935   /* Write delimiter.  */
936   if ((rc = write_string (sink, "\r\n")))
937     return rc;
938
939   /* Write the content.  */
940   if (use_b64)
941     rc = write_b64 (sink, data, datalen);
942   else if (use_qp)
943     rc = write_qp (sink, data, datalen);
944   else
945     rc = write_plain (sink, data, datalen);
946
947   return rc;
948 }
949
950
951 /* Return the number of attachments in TABLE to be put into the MIME
952    message.  */
953 static int
954 count_usable_attachments (mapi_attach_item_t *table)
955 {
956   int idx, count = 0;
957
958   if (table)
959     for (idx=0; !table[idx].end_of_table; idx++)
960       if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
961           && table[idx].method == ATTACH_BY_VALUE)
962         count++;
963   return count;
964 }
965
966 /* Write out all attachments from TABLE separated by BOUNDARY to SINK.
967    This function needs to be syncronized with count_usable_attachments.  */
968 static int
969 write_attachments (sink_t sink,
970                    LPMESSAGE message, mapi_attach_item_t *table,
971                    const char *boundary)
972 {
973   int idx, rc;
974   char *buffer;
975   size_t buflen;
976
977   if (table)
978     for (idx=0; !table[idx].end_of_table; idx++)
979       if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
980           && table[idx].method == ATTACH_BY_VALUE)
981         {
982           buffer = mapi_get_attach (message, 0, table+idx, &buflen);
983           if (!buffer)
984             log_debug ("Attachment at index %d not found\n", idx);
985           else
986             log_debug ("Attachment at index %d: length=%d\n", idx, (int)buflen);
987           if (!buffer)
988             return -1;
989           rc = write_part (sink, buffer, buflen, boundary,
990                            table[idx].filename, 0);
991           if (rc)
992             {
993               log_error ("Write part returned err: %i", rc);
994             }
995           xfree (buffer);
996         }
997   return 0;
998 }
999
1000
1001
1002 /* Delete all attachments from TABLE except for the one we just created */
1003 static int
1004 delete_all_attachments (LPMESSAGE message, mapi_attach_item_t *table)
1005 {
1006   HRESULT hr;
1007   int idx;
1008
1009   if (table)
1010     for (idx=0; !table[idx].end_of_table; idx++)
1011       {
1012         if (table[idx].attach_type == ATTACHTYPE_MOSSTEMPL
1013             && table[idx].filename
1014             && !strcmp (table[idx].filename, MIMEATTACHFILENAME))
1015           continue;
1016         hr = message->DeleteAttach (table[idx].mapipos, 0, NULL, 0);
1017         if (hr)
1018           {
1019             log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
1020                        SRCNAME, __func__, hr);
1021             return -1;
1022           }
1023       }
1024   return 0;
1025 }
1026
1027
1028
1029 /* Commit changes to the attachment ATTACH and release the object.
1030    SINK needs to be passed as well and will also be closed.  Note that
1031    the address of ATTACH is expected so that the fucntion can set it
1032    to NULL. */
1033 static int
1034 close_mapi_attachment (LPATTACH *attach, sink_t sink)
1035 {
1036   HRESULT hr;
1037   LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
1038
1039   if (!stream)
1040     {
1041       log_error ("%s:%s: sink not setup", SRCNAME, __func__);
1042       return -1;
1043     }
1044   hr = stream->Commit (0);
1045   if (hr)
1046     {
1047       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
1048                  SRCNAME, __func__, hr);
1049       return -1;
1050     }
1051   gpgol_release (stream);
1052   sink->cb_data = NULL;
1053   hr = (*attach)->SaveChanges (0);
1054   if (hr)
1055     {
1056       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
1057                  SRCNAME, __func__, hr);
1058       return -1;
1059     }
1060   gpgol_release ((*attach));
1061   *attach = NULL;
1062   return 0;
1063 }
1064
1065
1066 /* Cancel changes to the attachment ATTACH and release the object.
1067    SINK needs to be passed as well and will also be closed.  Note that
1068    the address of ATTACH is expected so that the fucntion can set it
1069    to NULL. */
1070 static void
1071 cancel_mapi_attachment (LPATTACH *attach, sink_t sink)
1072 {
1073   LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
1074
1075   if (stream)
1076     {
1077       stream->Revert();
1078       gpgol_release (stream);
1079       sink->cb_data = NULL;
1080     }
1081   if (*attach)
1082     {
1083       /* Fixme: Should we try to delete it or is there a Revert method? */
1084       gpgol_release ((*attach));
1085       *attach = NULL;
1086     }
1087 }
1088
1089
1090
1091 /* Do the final processing for a message. */
1092 static int
1093 finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
1094                   protocol_t protocol, int encrypt)
1095 {
1096   HRESULT hr;
1097   SPropValue prop;
1098
1099   /* Set the message class.  */
1100   prop.ulPropTag = PR_MESSAGE_CLASS_A;
1101   prop.Value.lpszA = GpgOLStr ("IPM.Note.SMIME.MultipartSigned");
1102   hr = message->SetProps(1, &prop, NULL);
1103   if (hr)
1104     {
1105       log_error ("%s:%s: error setting the message class: hr=%#lx\n",
1106                  SRCNAME, __func__, hr);
1107       return -1;
1108     }
1109
1110   /* Set a special property so that we are later able to identify
1111      messages signed or encrypted by us.  */
1112   if (mapi_set_sig_status (message, "@"))
1113     return -1;
1114
1115   /* We also need to set the message class into our custom
1116      property. This override is at least required for encrypted
1117      messages.  */
1118   if (mapi_set_gpgol_msg_class (message,
1119                                 (encrypt?
1120                                  (protocol == PROTOCOL_SMIME?
1121                                   "IPM.Note.GpgOL.OpaqueEncrypted" :
1122                                   "IPM.Note.GpgOL.MultipartEncrypted") :
1123                                  "IPM.Note.GpgOL.MultipartSigned")))
1124     return -1;
1125
1126   /* Now delete all parts of the MAPI message except for the one
1127      attachment we just created.  */
1128   if (delete_all_attachments (message, att_table))
1129     return -1;
1130
1131   /* Remove the draft info so that we don't leak the information on
1132      whether the message has been signed etc.  */
1133   mapi_set_gpgol_draft_info (message, NULL);
1134
1135   return mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE);
1136 }
1137
1138
1139 \f
1140 /* Sink write method used by mime_sign.  We write the data to the
1141    filter and also to the EXTRASINK but we don't pass a flush request
1142    to EXTRASINK. */
1143 static int
1144 sink_hashing_write (sink_t hashsink, const void *data, size_t datalen)
1145 {
1146   int rc;
1147   engine_filter_t filter = (engine_filter_t) hashsink->cb_data;
1148
1149   if (!filter || !hashsink->extrasink)
1150     {
1151       log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
1152       return -1;
1153     }
1154
1155   rc = engine_filter (filter, data, datalen);
1156   if (!rc && data && datalen)
1157     write_buffer (hashsink->extrasink, data, datalen);
1158   return rc;
1159 }
1160
1161
1162 /* This function is called by the filter to collect the output which
1163    is a detached signature.  */
1164 static int
1165 collect_signature (void *opaque, const void *data, size_t datalen)
1166 {
1167   struct databuf_s *db = (databuf_s *)opaque;
1168
1169   if (db->len + datalen >= db->size)
1170     {
1171       db->size += datalen + 1024;
1172       db->buf = (char*) xrealloc (db->buf, db->size);
1173     }
1174   memcpy (db->buf + db->len, data, datalen);
1175   db->len += datalen;
1176
1177   return datalen;
1178 }
1179
1180
1181 /* Helper to create the signing header.  This includes enough space
1182    for later fixup of the micalg parameter.  The MIME version is only
1183    written if FIRST is set.  */
1184 static void
1185 create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
1186                            int first, const char *boundary, const char *micalg)
1187 {
1188   snprintf (buffer, buflen,
1189             "%s"
1190             "Content-Type: multipart/signed;\r\n"
1191             "\tprotocol=\"application/%s\";\r\n"
1192             "\tmicalg=%-15.15s;\r\n"
1193             "\tboundary=\"%s\"\r\n"
1194             "\r\n",
1195             first? "MIME-Version: 1.0\r\n":"",
1196             (protocol==PROTOCOL_OPENPGP? "pgp-signature":"pkcs7-signature"),
1197             micalg, boundary);
1198 }
1199
1200
1201 /* Main body of mime_sign without the the code to delete the original
1202    attachments.  On success the function returns the current
1203    attachment table at R_ATT_TABLE or sets this to NULL on error.  If
1204    TMPSINK is set no attachment will be created but the output
1205    written to that sink.  */
1206 static int
1207 do_mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
1208               mapi_attach_item_t **r_att_table, sink_t tmpsink,
1209               unsigned int session_number, const char *sender)
1210 {
1211   int result = -1;
1212   int rc;
1213   LPATTACH attach = NULL;
1214   struct sink_s sinkmem;
1215   sink_t sink = &sinkmem;
1216   struct sink_s hashsinkmem;
1217   sink_t hashsink = &hashsinkmem;
1218   char boundary[BOUNDARYSIZE+1];
1219   char inner_boundary[BOUNDARYSIZE+1];
1220   mapi_attach_item_t *att_table = NULL;
1221   char *body = NULL;
1222   int n_att_usable;
1223   char top_header[BOUNDARYSIZE+200];
1224   engine_filter_t filter = NULL;
1225   struct databuf_s sigbuffer;
1226   char *my_sender = NULL;
1227
1228   *r_att_table = NULL;
1229
1230   memset (sink, 0, sizeof *sink);
1231   memset (hashsink, 0, sizeof *hashsink);
1232   memset (&sigbuffer, 0, sizeof sigbuffer);
1233
1234   if (tmpsink)
1235     {
1236       attach = NULL;
1237       sink = tmpsink;
1238     }
1239   else
1240     {
1241       attach = create_mapi_attachment (message, sink);
1242       if (!attach)
1243         return -1;
1244     }
1245
1246   /* Get the attachment info and the body.  */
1247   body = mapi_get_body (message, NULL);
1248   if (body && !*body)
1249     {
1250       xfree (body);
1251       body = NULL;
1252     }
1253   att_table = mapi_create_attach_table (message, 0);
1254   n_att_usable = count_usable_attachments (att_table);
1255   if (!n_att_usable && !body)
1256     {
1257       log_debug ("%s:%s: can't sign an empty message\n", SRCNAME, __func__);
1258       result = gpg_error (GPG_ERR_NO_DATA);
1259       goto failure;
1260     }
1261
1262   /* Prepare the signing. */
1263   if (engine_create_filter (&filter, collect_signature, &sigbuffer))
1264     goto failure;
1265
1266   if (session_number)
1267     {
1268       engine_set_session_number (filter, session_number);
1269       {
1270         char *tmp = mapi_get_subject (message);
1271         engine_set_session_title (filter, tmp);
1272         xfree (tmp);
1273       }
1274     }
1275
1276   if (sender)
1277     my_sender = xstrdup (sender);
1278   else
1279     my_sender = mapi_get_sender (message);
1280   if (engine_sign_start (filter, hwnd, protocol, my_sender, &protocol))
1281     goto failure;
1282
1283   protocol = check_protocol (protocol);
1284   if (protocol == PROTOCOL_UNKNOWN)
1285     {
1286       log_error ("%s:%s: no protocol selected", SRCNAME, __func__);
1287       goto failure;
1288     }
1289
1290
1291   /* Write the top header.  */
1292   generate_boundary (boundary);
1293   create_top_signing_header (top_header, sizeof top_header,
1294                              protocol, 1, boundary, "xxx");
1295   if ((rc = write_string (sink, top_header)))
1296     goto failure;
1297
1298   /* Create the inner boundary if we have a body and at least one
1299      attachment or more than one attachment.  */
1300   if ((body && n_att_usable) || n_att_usable > 1)
1301     generate_boundary (inner_boundary);
1302   else
1303     *inner_boundary = 0;
1304
1305   /* Write the boundary so that it is not included in the hashing.  */
1306   if ((rc = write_boundary (sink, boundary, 0)))
1307     goto failure;
1308
1309   /* Create a new sink for hashing and write/hash our content.  */
1310   hashsink->cb_data = filter;
1311   hashsink->extrasink = sink;
1312   hashsink->writefnc = sink_hashing_write;
1313
1314   /* Note that OL2003 will add an extra line after the multipart
1315      header, thus we do the same to avoid running all through an
1316      IConverterSession first. */
1317   if (*inner_boundary
1318       && (rc=write_multistring (hashsink,
1319                                 "Content-Type: multipart/mixed;\r\n",
1320                                 "\tboundary=\"", inner_boundary, "\"\r\n",
1321                                 "\r\n",  /* <-- extra line */
1322                                 NULL)))
1323         goto failure;
1324
1325
1326   if (body)
1327     rc = write_part (hashsink, body, strlen (body),
1328                      *inner_boundary? inner_boundary : NULL, NULL, 1);
1329   if (!rc && n_att_usable)
1330     rc = write_attachments (hashsink, message, att_table,
1331                             *inner_boundary? inner_boundary : NULL);
1332   if (rc)
1333     goto failure;
1334
1335   xfree (body);
1336   body = NULL;
1337
1338   /* Finish the possible multipart/mixed. */
1339   if (*inner_boundary && (rc = write_boundary (hashsink, inner_boundary, 1)))
1340     goto failure;
1341
1342   /* Here we are ready with the hashing.  Flush the filter and wait
1343      for the signing process to finish.  */
1344   if ((rc = write_buffer (hashsink, NULL, 0)))
1345     goto failure;
1346   if ((rc = engine_wait (filter)))
1347     goto failure;
1348   filter = NULL; /* Not valid anymore.  */
1349   hashsink->cb_data = NULL; /* Not needed anymore.  */
1350
1351
1352   /* Write signature attachment.  */
1353   if ((rc = write_boundary (sink, boundary, 0)))
1354     goto failure;
1355
1356   if (protocol == PROTOCOL_OPENPGP)
1357     {
1358       rc = write_string (sink,
1359                          "Content-Type: application/pgp-signature\r\n");
1360     }
1361   else
1362     {
1363       rc = write_string (sink,
1364                          "Content-Transfer-Encoding: base64\r\n"
1365                          "Content-Type: application/pkcs7-signature\r\n");
1366       /* rc = write_string (sink, */
1367       /*                    "Content-Type: application/x-pkcs7-signature\r\n" */
1368       /*                    "\tname=\"smime.p7s\"\r\n" */
1369       /*                    "Content-Transfer-Encoding: base64\r\n" */
1370       /*                    "Content-Disposition: attachment;\r\n" */
1371       /*                    "\tfilename=\"smime.p7s\"\r\n"); */
1372
1373     }
1374   /* About the above code:
1375      If we would add "Content-Transfer-Encoding: 7bit\r\n" to this
1376      attachment, Outlooks does not proceed with sending and even does
1377      not return any error.  A wild guess is that while OL adds this
1378      header itself, it detects that it already exists and somehow gets
1379      into a problem.  It is not a problem with the other parts,
1380      though.  Hmmm, triggered by the top levels CT protocol parameter?
1381      Anyway, it is not required that we add it as we won't hash it.
1382      Note, that this only holds for OpenPGP; for S/MIME we need to set
1383      set CTE.  We even write it before the CT because that is the same
1384      as Outlook would do it for a missing CTE. */
1385   if (rc)
1386     goto failure;
1387
1388   if ((rc = write_string (sink, "\r\n")))
1389     goto failure;
1390
1391   /* Write the signature.  We add an extra CR,LF which should not harm
1392      and a terminating 0. */
1393   collect_signature (&sigbuffer, "\r\n", 3);
1394   if ((rc = write_string (sink, sigbuffer.buf)))
1395     goto failure;
1396
1397
1398   /* Write the final boundary and finish the attachment.  */
1399   if ((rc = write_boundary (sink, boundary, 1)))
1400     goto failure;
1401
1402   /* Fixup the micalg parameter.  */
1403   {
1404     HRESULT hr;
1405     LARGE_INTEGER off;
1406     LPSTREAM stream = (LPSTREAM) sink->cb_data;
1407
1408     off.QuadPart = 0;
1409     hr = stream->Seek (off, STREAM_SEEK_SET, NULL);
1410     if (hr)
1411       {
1412         log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
1413                    SRCNAME, __func__, hr);
1414         goto failure;
1415       }
1416
1417     create_top_signing_header (top_header, sizeof top_header,
1418                                protocol, 1, boundary,
1419                                protocol == PROTOCOL_SMIME? "sha1":"pgp-sha1");
1420
1421     hr = stream->Write (top_header, strlen (top_header), NULL);
1422     if (hr)
1423       {
1424         log_error ("%s:%s: writing fixed micalg failed: hr=%#lx",
1425                    SRCNAME, __func__, hr);
1426         goto failure;
1427       }
1428
1429     /* Better seek again to the end. */
1430     off.QuadPart = 0;
1431     hr = stream->Seek (off, STREAM_SEEK_END, NULL);
1432     if (hr)
1433       {
1434         log_error ("%s:%s: seeking back to the end failed: hr=%#lx",
1435                    SRCNAME, __func__, hr);
1436         goto failure;
1437       }
1438   }
1439
1440
1441   if (attach)
1442     {
1443       if (close_mapi_attachment (&attach, sink))
1444         goto failure;
1445     }
1446
1447   result = 0;  /* Everything is fine, fall through the cleanup now.  */
1448
1449  failure:
1450   engine_cancel (filter);
1451   if (attach)
1452     cancel_mapi_attachment (&attach, sink);
1453   xfree (body);
1454   if (result)
1455     mapi_release_attach_table (att_table);
1456   else
1457     *r_att_table = att_table;
1458   xfree (sigbuffer.buf);
1459   xfree (my_sender);
1460   return result;
1461 }
1462
1463
1464 /* Sign the MESSAGE using PROTOCOL.  If PROTOCOL is PROTOCOL_UNKNOWN
1465    the engine decides what protocol to use.  On return MESSAGE is
1466    modified so that sending it will result in a properly MOSS (that is
1467    PGP or S/MIME) signed message.  On failure the function tries to
1468    keep the original message intact but there is no 100% guarantee for
1469    it. */
1470 int
1471 mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
1472            const char *sender)
1473 {
1474   int result = -1;
1475   mapi_attach_item_t *att_table;
1476
1477   result = do_mime_sign (message, hwnd, protocol, &att_table, 0,
1478                          engine_new_session_number (), sender);
1479   if (!result)
1480     {
1481       if (!finalize_message (message, att_table, protocol, 0))
1482         result = 0;
1483     }
1484
1485   mapi_release_attach_table (att_table);
1486   return result;
1487 }
1488
1489
1490 \f
1491 /* Sink write method used by mime_encrypt.  */
1492 int
1493 sink_encryption_write (sink_t encsink, const void *data, size_t datalen)
1494 {
1495   engine_filter_t filter = (engine_filter_t) encsink->cb_data;
1496
1497   if (!filter)
1498     {
1499       log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
1500       return -1;
1501     }
1502
1503   return engine_filter (filter, data, datalen);
1504 }
1505
1506
1507 #if 0 /* Not used.  */
1508 /* Sink write method used by mime_encrypt for writing Base64.  */
1509 static int
1510 sink_encryption_write_b64 (sink_t encsink, const void *data, size_t datalen)
1511 {
1512   engine_filter_t filter = encsink->cb_data;
1513   int rc;
1514   const unsigned char *p;
1515   unsigned char inbuf[4];
1516   int idx, quads;
1517   char outbuf[6];
1518   size_t outbuflen;
1519
1520   if (!filter)
1521     {
1522       log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
1523       return -1;
1524     }
1525
1526   idx = encsink->b64.idx;
1527   assert (idx < 4);
1528   memcpy (inbuf, encsink->b64.inbuf, idx);
1529   quads = encsink->b64.quads;
1530
1531   if (!data)  /* Flush. */
1532     {
1533       outbuflen = 0;
1534       if (idx)
1535         {
1536           outbuf[0] = bintoasc[(*inbuf>>2)&077];
1537           if (idx == 1)
1538             {
1539               outbuf[1] = bintoasc[((*inbuf<<4)&060)&077];
1540               outbuf[2] = '=';
1541               outbuf[3] = '=';
1542             }
1543           else
1544             {
1545               outbuf[1] = bintoasc[(((*inbuf<<4)&060)|
1546                                     ((inbuf[1]>>4)&017))&077];
1547               outbuf[2] = bintoasc[((inbuf[1]<<2)&074)&077];
1548               outbuf[3] = '=';
1549             }
1550           outbuflen = 4;
1551           quads++;
1552         }
1553
1554       if (quads)
1555         {
1556           outbuf[outbuflen++] = '\r';
1557           outbuf[outbuflen++] = '\n';
1558         }
1559
1560       if (outbuflen && (rc = engine_filter (filter, outbuf, outbuflen)))
1561         return rc;
1562       /* Send the flush command to the filter.  */
1563       if ((rc = engine_filter (filter, data, datalen)))
1564         return rc;
1565     }
1566   else
1567     {
1568       for (p = data; datalen; p++, datalen--)
1569         {
1570           inbuf[idx++] = *p;
1571           if (idx > 2)
1572             {
1573               idx = 0;
1574               outbuf[0] = bintoasc[(*inbuf>>2)&077];
1575               outbuf[1] = bintoasc[(((*inbuf<<4)&060)
1576                                     |((inbuf[1] >> 4)&017))&077];
1577               outbuf[2] = bintoasc[(((inbuf[1]<<2)&074)
1578                                     |((inbuf[2]>>6)&03))&077];
1579               outbuf[3] = bintoasc[inbuf[2]&077];
1580               outbuflen = 4;
1581               if (++quads >= (64/4))
1582                 {
1583                   quads = 0;
1584                   outbuf[4] = '\r';
1585                   outbuf[5] = '\n';
1586                   outbuflen += 2;
1587                 }
1588               if ((rc = engine_filter (filter, outbuf, outbuflen)))
1589                 return rc;
1590             }
1591         }
1592     }
1593
1594   encsink->b64.idx = idx;
1595   memcpy (encsink->b64.inbuf, inbuf, idx);
1596   encsink->b64.quads = quads;
1597
1598   return 0;
1599 }
1600 #endif /*Not used.*/
1601
1602
1603 /* Helper from mime_encrypt.  BOUNDARY is a buffer of at least
1604    BOUNDARYSIZE+1 bytes which will be set on return from that
1605    function.  */
1606 static int
1607 create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary)
1608 {
1609   int rc;
1610
1611   if (protocol == PROTOCOL_SMIME)
1612     {
1613       *boundary = 0;
1614       rc = write_multistring (sink,
1615                               "MIME-Version: 1.0\r\n"
1616                               "Content-Type: application/pkcs7-mime;\r\n"
1617                               "\tsmime-type=enveloped-data;\r\n"
1618                               "\tname=\"smime.p7m\"\r\n"
1619                               "Content-Transfer-Encoding: base64\r\n"
1620                               "\r\n",
1621                               NULL);
1622     }
1623   else
1624     {
1625       generate_boundary (boundary);
1626       rc = write_multistring (sink,
1627                               "MIME-Version: 1.0\r\n"
1628                               "Content-Type: multipart/encrypted;\r\n"
1629                               "\tprotocol=\"application/pgp-encrypted\";\r\n",
1630                               "\tboundary=\"", boundary, "\"\r\n",
1631                               NULL);
1632       if (rc)
1633         return rc;
1634
1635       /* Write the PGP/MIME encrypted part.  */
1636       rc = write_boundary (sink, boundary, 0);
1637       if (rc)
1638         return rc;
1639       rc = write_multistring (sink,
1640                               "Content-Type: application/pgp-encrypted\r\n"
1641                               "\r\n"
1642                               "Version: 1\r\n", NULL);
1643       if (rc)
1644         return rc;
1645
1646       /* And start the second part.  */
1647       rc = write_boundary (sink, boundary, 0);
1648       if (rc)
1649         return rc;
1650       rc = write_multistring (sink,
1651                               "Content-Type: application/octet-stream\r\n"
1652                               "\r\n", NULL);
1653      }
1654
1655   return rc;
1656 }
1657
1658
1659 /* Encrypt the MESSAGE.  */
1660 int
1661 mime_encrypt (LPMESSAGE message, HWND hwnd,
1662               protocol_t protocol, char **recipients,
1663               const char *sender)
1664 {
1665   int result = -1;
1666   int rc;
1667   LPATTACH attach;
1668   struct sink_s sinkmem;
1669   sink_t sink = &sinkmem;
1670   struct sink_s encsinkmem;
1671   sink_t encsink = &encsinkmem;
1672   char boundary[BOUNDARYSIZE+1];
1673   char inner_boundary[BOUNDARYSIZE+1];
1674   mapi_attach_item_t *att_table = NULL;
1675   char *body = NULL;
1676   int n_att_usable;
1677   engine_filter_t filter = NULL;
1678   char *my_sender = NULL;
1679
1680   memset (sink, 0, sizeof *sink);
1681   memset (encsink, 0, sizeof *encsink);
1682
1683   attach = create_mapi_attachment (message, sink);
1684   if (!attach)
1685     return -1;
1686
1687   /* Get the attachment info and the body.  We need to do this before
1688      creating the engine's filter sue problem sending the cancel to
1689      the engine with nothing for the engine to process.  This is
1690      actually a bug in our engine code but we better avoid triggering
1691      this bug because the engine sometimes hangs.  Fixme: Needs a
1692      proper fix. */
1693   body = mapi_get_body (message, NULL);
1694   if (body && !*body)
1695     {
1696       xfree (body);
1697       body = NULL;
1698     }
1699   att_table = mapi_create_attach_table (message, 0);
1700   n_att_usable = count_usable_attachments (att_table);
1701   if (!n_att_usable && !body)
1702     {
1703       log_debug ("%s:%s: can't encrypt an empty message\n", SRCNAME, __func__);
1704       result = gpg_error (GPG_ERR_NO_DATA);
1705       goto failure;
1706     }
1707
1708   /* Prepare the encryption.  We do this early as it is quite common
1709      that some recipient keys are not available and thus the
1710      encryption will fail early. */
1711   if (engine_create_filter (&filter, write_buffer_for_cb, sink))
1712     goto failure;
1713
1714   engine_set_session_number (filter, engine_new_session_number ());
1715   {
1716     char *tmp = mapi_get_subject (message);
1717     engine_set_session_title (filter, tmp);
1718     xfree (tmp);
1719   }
1720
1721   if (sender)
1722     my_sender = xstrdup (sender);
1723   else
1724     my_sender = mapi_get_sender (message);
1725   if (engine_encrypt_prepare (filter, hwnd, protocol, 0,
1726                               my_sender, recipients, &protocol))
1727     goto failure;
1728   if (engine_encrypt_start (filter, 0))
1729     goto failure;
1730
1731   protocol = check_protocol (protocol);
1732   if (protocol == PROTOCOL_UNKNOWN)
1733     goto failure;
1734
1735   /* Write the top header.  */
1736   rc = create_top_encryption_header (sink, protocol, boundary);
1737   if (rc)
1738     goto failure;
1739
1740   /* Create a new sink for encrypting the following stuff.  */
1741   encsink->cb_data = filter;
1742   encsink->writefnc = sink_encryption_write;
1743
1744   if ((body && n_att_usable) || n_att_usable > 1)
1745     {
1746       /* A body and at least one attachment or more than one attachment  */
1747       generate_boundary (inner_boundary);
1748       if ((rc=write_multistring (encsink,
1749                                  "Content-Type: multipart/mixed;\r\n",
1750                                  "\tboundary=\"", inner_boundary, "\"\r\n",
1751                                  NULL)))
1752         goto failure;
1753     }
1754   else /* Only one part.  */
1755     *inner_boundary = 0;
1756
1757   if (body)
1758     rc = write_part (encsink, body, strlen (body),
1759                      *inner_boundary? inner_boundary : NULL, NULL, 1);
1760   if (!rc && n_att_usable)
1761     rc = write_attachments (encsink, message, att_table,
1762                             *inner_boundary? inner_boundary : NULL);
1763   if (rc)
1764     goto failure;
1765
1766   xfree (body);
1767   body = NULL;
1768
1769   /* Finish the possible multipart/mixed. */
1770   if (*inner_boundary && (rc = write_boundary (encsink, inner_boundary, 1)))
1771     goto failure;
1772
1773   /* Flush the encryption sink and wait for the encryption to get
1774      ready.  */
1775   if ((rc = write_buffer (encsink, NULL, 0)))
1776     goto failure;
1777   if ((rc = engine_wait (filter)))
1778     goto failure;
1779   filter = NULL; /* Not valid anymore.  */
1780   encsink->cb_data = NULL; /* Not needed anymore.  */
1781
1782     if (!sink->enc_counter)
1783     {
1784       log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
1785       goto failure;
1786     }
1787
1788   /* Write the final boundary (for OpenPGP) and finish the attachment.  */
1789   if (*boundary && (rc = write_boundary (sink, boundary, 1)))
1790     goto failure;
1791
1792   if (close_mapi_attachment (&attach, sink))
1793     goto failure;
1794
1795   if (finalize_message (message, att_table, protocol, 1))
1796     goto failure;
1797
1798   result = 0;  /* Everything is fine, fall through the cleanup now.  */
1799
1800  failure:
1801   engine_cancel (filter);
1802   cancel_mapi_attachment (&attach, sink);
1803   xfree (body);
1804   mapi_release_attach_table (att_table);
1805   xfree (my_sender);
1806   return result;
1807 }
1808
1809
1810
1811 \f
1812 /* Sign and Encrypt the MESSAGE.  */
1813 int
1814 mime_sign_encrypt (LPMESSAGE message, HWND hwnd,
1815                    protocol_t protocol, char **recipients,
1816                    const char *sender)
1817 {
1818   int result = -1;
1819   int rc = 0;
1820   HRESULT hr;
1821   LPATTACH attach;
1822   LPSTREAM tmpstream = NULL;
1823   struct sink_s sinkmem;
1824   sink_t sink = &sinkmem;
1825   struct sink_s encsinkmem;
1826   sink_t encsink = &encsinkmem;
1827   struct sink_s tmpsinkmem;
1828   sink_t tmpsink = &tmpsinkmem;
1829   char boundary[BOUNDARYSIZE+1];
1830   mapi_attach_item_t *att_table = NULL;
1831   engine_filter_t filter = NULL;
1832   unsigned int session_number;
1833   char *my_sender = NULL;
1834
1835   memset (sink, 0, sizeof *sink);
1836   memset (encsink, 0, sizeof *encsink);
1837   memset (tmpsink, 0, sizeof *tmpsink);
1838
1839   attach = create_mapi_attachment (message, sink);
1840   if (!attach)
1841     return -1;
1842
1843   /* First check that we are not trying to process an empty message
1844      which might lock up our engine.  Unfortunately we need to
1845      duplicate the code we use in do_mime_sign here.  FIXME: The
1846      engine should be fixed instead of using such a workaround.  */
1847   {
1848     char *body;
1849
1850     body = mapi_get_body (message, NULL);
1851     if (body && !*body)
1852     {
1853       xfree (body);
1854       body = NULL;
1855     }
1856     att_table = mapi_create_attach_table (message, 0);
1857     if (!count_usable_attachments (att_table) && !body)
1858       result = gpg_error (GPG_ERR_NO_DATA);
1859     xfree (body);
1860     if (att_table)
1861       {
1862         mapi_release_attach_table (att_table);
1863         att_table = NULL;
1864       }
1865     if (gpg_err_code (result) == GPG_ERR_NO_DATA)
1866       {
1867         log_debug ("%s:%s: can't sign+encrypt an empty message\n",
1868                    SRCNAME, __func__);
1869         goto failure;
1870       }
1871   }
1872
1873
1874   /* Create a temporary sink to construct the signed data.  */
1875   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1876                          (SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
1877                           | STGM_CREATE | STGM_READWRITE),
1878                          NULL, GpgOLStr("GPG"), &tmpstream);
1879   if (FAILED (hr))
1880     {
1881       log_error ("%s:%s: can't create temp file: hr=%#lx\n",
1882                  SRCNAME, __func__, hr);
1883       rc = -1;
1884       goto failure;
1885     }
1886   tmpsink->cb_data = tmpstream;
1887   tmpsink->writefnc = sink_std_write;
1888
1889
1890   /* Prepare the encryption.  We do this early as it is quite common
1891      that some recipients are not available and thus the encryption
1892      will fail early.  This is also required to allow the UIserver to
1893      figure out the protocol to use if we have not forced one.  */
1894   if (engine_create_filter (&filter, write_buffer_for_cb, sink))
1895     goto failure;
1896
1897   session_number = engine_new_session_number ();
1898   engine_set_session_number (filter, session_number);
1899   {
1900     char *tmp = mapi_get_subject (message);
1901     engine_set_session_title (filter, tmp);
1902     xfree (tmp);
1903   }
1904
1905   if (sender)
1906     my_sender = xstrdup (sender);
1907   else
1908     my_sender = mapi_get_sender (message);
1909   if ((rc=engine_encrypt_prepare (filter, hwnd,
1910                                   protocol, ENGINE_FLAG_SIGN_FOLLOWS,
1911                                   my_sender, recipients, &protocol)))
1912     goto failure;
1913
1914   protocol = check_protocol (protocol);
1915   if (protocol == PROTOCOL_UNKNOWN)
1916     goto failure;
1917
1918   /* Now sign the message.  This creates another attachment with the
1919      complete MIME object of the signed message.  We can't do the
1920      encryption in streaming mode while running the encryption because
1921      we need to fix up that ugly micalg parameter after having created
1922      the signature.  Note that the protocol to use is taken from the
1923      encryption operation. */
1924   if (do_mime_sign (message, hwnd, protocol, &att_table, tmpsink,
1925                     session_number, sender))
1926     goto failure;
1927
1928   /* Now send the actual ENCRYPT command.  This split up between
1929      prepare and start is necessary to help with the implementarion of
1930      the UI-server.  If we would send the ENCRYPT command immediately
1931      the UI-server might block while reading from the input stream
1932      because we are first going to do a sign operation which in trun
1933      needs the attention of the UI server.  A more robust but
1934      complicated approach to the UI-server would be to delay the
1935      reading (and thus the start of the underlying encrypt operation)
1936      until the first byte has been received. */
1937   if ((rc=engine_encrypt_start (filter, 0)))
1938     goto failure;
1939
1940   /* Write the top header.  */
1941   rc = create_top_encryption_header (sink, protocol, boundary);
1942   if (rc)
1943     goto failure;
1944
1945   /* Create a new sink for encrypting the temporary attachment with
1946      the signed message.  */
1947   encsink->cb_data = filter;
1948   encsink->writefnc = sink_encryption_write;
1949
1950   /* Copy the temporary stream to the encryption sink.  */
1951   {
1952     LARGE_INTEGER off;
1953     ULONG nread;
1954     char buffer[4096];
1955
1956     off.QuadPart = 0;
1957     hr = tmpstream->Seek (off, STREAM_SEEK_SET, NULL);
1958     if (hr)
1959       {
1960         log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
1961                    SRCNAME, __func__, hr);
1962         rc = gpg_error (GPG_ERR_EIO);
1963         goto failure;
1964       }
1965
1966     for (;;)
1967       {
1968         hr = tmpstream->Read (buffer, sizeof buffer, &nread);
1969         if (hr)
1970           {
1971             log_error ("%s:%s: IStream::Read failed: hr=%#lx",
1972                        SRCNAME, __func__, hr);
1973             rc = gpg_error (GPG_ERR_EIO);
1974             goto failure;
1975           }
1976         if (!nread)
1977           break;  /* EOF */
1978         rc = write_buffer (encsink, buffer, nread);
1979         if (rc)
1980           {
1981             log_error ("%s:%s: writing tmpstream to encsink failed: %s",
1982                        SRCNAME, __func__, gpg_strerror (rc));
1983             goto failure;
1984           }
1985       }
1986   }
1987
1988
1989   /* Flush the encryption sink and wait for the encryption to get
1990      ready.  */
1991   if ((rc = write_buffer (encsink, NULL, 0)))
1992     goto failure;
1993   if ((rc = engine_wait (filter)))
1994     goto failure;
1995   filter = NULL; /* Not valid anymore.  */
1996   encsink->cb_data = NULL; /* Not needed anymore.  */
1997
1998   if (!sink->enc_counter)
1999     {
2000       log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
2001       goto failure;
2002     }
2003
2004   /* Write the final boundary (for OpenPGP) and finish the attachment.  */
2005   if (*boundary && (rc = write_boundary (sink, boundary, 1)))
2006     goto failure;
2007
2008   if (close_mapi_attachment (&attach, sink))
2009     goto failure;
2010
2011   if (finalize_message (message, att_table, protocol, 1))
2012     goto failure;
2013
2014   result = 0;  /* Everything is fine, fall through the cleanup now.  */
2015
2016  failure:
2017   if (result)
2018     log_debug ("%s:%s: failed rc=%d (%s) <%s>", SRCNAME, __func__, rc,
2019                gpg_strerror (rc), gpg_strsource (rc));
2020   engine_cancel (filter);
2021   gpgol_release (tmpstream);
2022   mapi_release_attach_table (att_table);
2023   xfree (my_sender);
2024   return result;
2025 }
2026
2027 int
2028 restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
2029                        msgtype_t type, char *msgcls)
2030 {
2031   struct sink_s sinkmem;
2032   sink_t sink = &sinkmem;
2033   char *orig = NULL;
2034   int err = -1;
2035   char boundary[BOUNDARYSIZE+1];
2036
2037   (void)msgcls;
2038
2039   LPATTACH new_attach = create_mapi_attachment (message,
2040                                                 sink);
2041   log_debug ("Restore message from moss called.");
2042   if (!new_attach)
2043     {
2044       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
2045       goto done;
2046     }
2047   // TODO MORE
2048   if (type == MSGTYPE_SMIME)
2049     {
2050       create_top_encryption_header (sink, PROTOCOL_SMIME, boundary);
2051     }
2052   else if (type == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED)
2053     {
2054       create_top_encryption_header (sink, PROTOCOL_OPENPGP, boundary);
2055     }
2056   else
2057     {
2058       log_error ("%s:%s: Unsupported messagetype: %i",
2059                  SRCNAME, __func__, type);
2060       goto done;
2061     }
2062
2063   orig = get_pa_string (moss_att, PR_ATTACH_DATA_BIN_DASL);
2064
2065   if (!orig)
2066     {
2067       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
2068       goto done;
2069     }
2070
2071   if (write_string (sink, orig))
2072     {
2073       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
2074       goto done;
2075     }
2076
2077   if (*boundary && write_boundary (sink, boundary, 1))
2078     {
2079       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
2080       goto done;
2081     }
2082
2083   if (close_mapi_attachment (&new_attach, sink))
2084     {
2085       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
2086       goto done;
2087     }
2088
2089   /* Set a special property so that we are later able to identify
2090      messages signed or encrypted by us.  */
2091   if (mapi_set_sig_status (message, "@"))
2092     {
2093       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
2094       goto done;
2095     }
2096
2097   err = 0;
2098 done:
2099   xfree (orig);
2100   return err;
2101 }