Add missing memdbg_addRefs
[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 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
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <assert.h>
30 #include <string.h>
31 #include <ctype.h>
32
33 #define COBJMACROS
34 #include <windows.h>
35 #include <objidl.h>
36
37 #include "mymapi.h"
38 #include "mymapitags.h"
39
40 #include "common.h"
41 #include "mapihelp.h"
42 #include "mimemaker.h"
43 #include "oomhelp.h"
44 #include "mail.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 int
97 sink_string_write (sink_t sink, const void *data, size_t datalen)
98 {
99   Mail *mail = static_cast<Mail *>(sink->cb_data);
100   mail->appendToInlineBody (std::string((char*)data, datalen));
101   return 0;
102 }
103
104 /* Write method used with a sink_t that contains a file object.  */
105 int
106 sink_file_write (sink_t sink, const void *data, size_t datalen)
107 {
108   HANDLE hFile = sink->cb_data;
109   DWORD written = 0;
110
111   if (!hFile || hFile == INVALID_HANDLE_VALUE)
112     {
113       log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
114       return -1;
115     }
116   if (!data)
117     return 0;  /* Flush - nothing to do here.  */
118
119   if (!WriteFile (hFile, data, datalen, &written, NULL))
120     {
121       log_error ("%s:%s: Write failed: ", SRCNAME, __func__);
122       return -1;
123     }
124   return 0;
125 }
126
127
128 /* Create a new MAPI attchment for MESSAGE which will be used to
129    prepare the MIME message.  On sucess the stream to write the data
130    to is stored at STREAM and the attachment object itself is
131    returned.  The caller needs to call SaveChanges.  Returns NULL on
132    failure in which case STREAM will be set to NULL.  */
133 LPATTACH
134 create_mapi_attachment (LPMESSAGE message, sink_t sink,
135                         const char *overrideMimeTag)
136 {
137   HRESULT hr;
138   ULONG pos;
139   SPropValue prop;
140   LPATTACH att = NULL;
141   LPUNKNOWN punk;
142
143   sink->cb_data = NULL;
144   sink->writefnc = NULL;
145   hr = message->CreateAttach(NULL, 0, &pos, &att);
146   memdbg_addRef (att);
147   if (hr)
148     {
149       log_error ("%s:%s: can't create attachment: hr=%#lx\n",
150                  SRCNAME, __func__, hr);
151       return NULL;
152     }
153
154   prop.ulPropTag = PR_ATTACH_METHOD;
155   prop.Value.ul = ATTACH_BY_VALUE;
156   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
157   if (hr)
158     {
159       log_error ("%s:%s: can't set attach method: hr=%#lx\n",
160                  SRCNAME, __func__, hr);
161       goto failure;
162     }
163
164   /* Mark that attachment so that we know why it has been created.  */
165   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
166     goto failure;
167   prop.Value.l = ATTACHTYPE_MOSSTEMPL;
168   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
169   if (hr)
170     {
171       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
172                  SRCNAME, __func__, "GpgOL Attach Type", hr);
173       goto failure;
174     }
175
176
177   /* We better insert a short filename. */
178   prop.ulPropTag = PR_ATTACH_FILENAME_A;
179   prop.Value.lpszA = strdup (MIMEATTACHFILENAME);
180   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
181   xfree (prop.Value.lpszA);
182   if (hr)
183     {
184       log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
185                  SRCNAME, __func__, hr);
186       goto failure;
187     }
188
189
190   /* Even for encrypted messages we need to set the MAPI property to
191      multipart/signed.  This seems to be a part of the trigger which
192      leads OL to process such a message in a special way.  */
193   prop.ulPropTag = PR_ATTACH_TAG;
194   prop.Value.bin.cb  = sizeof oid_mimetag;
195   prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
196   hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
197   if (!hr)
198     {
199       prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
200       prop.Value.lpszA = overrideMimeTag ? strdup (overrideMimeTag) :
201                          strdup ("multipart/signed");
202       if (overrideMimeTag)
203         {
204           log_debug ("%s:%s: using override mimetag: %s\n",
205                      SRCNAME, __func__, overrideMimeTag);
206         }
207       hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
208       xfree (prop.Value.lpszA);
209     }
210   if (hr)
211     {
212       log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
213                  SRCNAME, __func__, hr);
214       goto failure;
215     }
216
217   punk = NULL;
218   hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream, 0,
219                          (MAPI_CREATE|MAPI_MODIFY), &punk);
220   if (FAILED (hr))
221     {
222       log_error ("%s:%s: can't create output stream: hr=%#lx\n",
223                  SRCNAME, __func__, hr);
224       goto failure;
225     }
226   sink->cb_data = (LPSTREAM)punk;
227   sink->writefnc = sink_std_write;
228   return att;
229
230  failure:
231   gpgol_release (att);
232   return NULL;
233 }
234
235
236 /* Write data to a sink_t.  */
237 int
238 write_buffer (sink_t sink, const void *data, size_t datalen)
239 {
240   if (!sink || !sink->writefnc)
241     {
242       log_error ("%s:%s: sink not properly setup", SRCNAME, __func__);
243       return -1;
244     }
245   return sink->writefnc (sink, data, datalen);
246 }
247
248 /* Same as above but used for passing as callback function.  This
249    fucntion does not return an error code but the number of bytes
250    written.  */
251 int
252 write_buffer_for_cb (void *opaque, const void *data, size_t datalen)
253 {
254   sink_t sink = (sink_t) opaque;
255   sink->enc_counter += datalen;
256   return write_buffer (sink, data, datalen) ? -1 : datalen;
257 }
258
259
260 /* Write the string TEXT to the IStream STREAM.  Returns 0 on sucsess,
261    prints an error message and returns -1 on error.  */
262 int
263 write_string (sink_t sink, const char *text)
264 {
265   return write_buffer (sink, text, strlen (text));
266 }
267
268
269 /* Write the string TEXT1 and all folloing arguments of type (const
270    char*) to the SINK.  The list of argumens needs to be terminated
271    with a NULL.  Returns 0 on sucsess, prints an error message and
272    returns -1 on error.  */
273 static int
274 write_multistring (sink_t sink, const char *text1, ...)
275 {
276   va_list arg_ptr;
277   int rc;
278   const char *s;
279
280   va_start (arg_ptr, text1);
281   s = text1;
282   do
283     rc = write_string (sink, s);
284   while (!rc && (s=va_arg (arg_ptr, const char *)));
285   va_end (arg_ptr);
286   return rc;
287 }
288
289
290
291 /* Helper to write a boundary to the output sink.  The leading LF
292    will be written as well.  */
293 int
294 write_boundary (sink_t sink, const char *boundary, int lastone)
295 {
296   int rc = write_string (sink, "\r\n--");
297   if (!rc)
298     rc = write_string (sink, boundary);
299   if (!rc)
300     rc = write_string (sink, lastone? "--\r\n":"\r\n");
301   return rc;
302 }
303
304
305 /* Write DATALEN bytes of DATA to SINK in base64 encoding.  This
306    creates a complete Base64 chunk including the trailing fillers.  */
307 int
308 write_b64 (sink_t sink, const void *data, size_t datalen)
309 {
310   int rc;
311   const unsigned char *p;
312   unsigned char inbuf[4];
313   int idx, quads;
314   char outbuf[2048];
315   size_t outlen;
316
317   log_debug ("  writing base64 of length %d\n", (int)datalen);
318   idx = quads = 0;
319   outlen = 0;
320   for (p = (const unsigned char*)data; datalen; p++, datalen--)
321     {
322       inbuf[idx++] = *p;
323       if (idx > 2)
324         {
325           /* We need space for a quad and a possible CR,LF.  */
326           if (outlen+4+2 >= sizeof outbuf)
327             {
328               if ((rc = write_buffer (sink, outbuf, outlen)))
329                 return rc;
330               outlen = 0;
331             }
332           outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
333           outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
334                                        |((inbuf[1] >> 4)&017))&077];
335           outbuf[outlen++] = bintoasc[(((inbuf[1]<<2)&074)
336                                        |((inbuf[2]>>6)&03))&077];
337           outbuf[outlen++] = bintoasc[inbuf[2]&077];
338           idx = 0;
339           if (++quads >= (64/4))
340             {
341               quads = 0;
342               outbuf[outlen++] = '\r';
343               outbuf[outlen++] = '\n';
344             }
345         }
346     }
347
348   /* We need space for a quad and a final CR,LF.  */
349   if (outlen+4+2 >= sizeof outbuf)
350     {
351       if ((rc = write_buffer (sink, outbuf, outlen)))
352         return rc;
353       outlen = 0;
354     }
355   if (idx)
356     {
357       outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
358       if (idx == 1)
359         {
360           outbuf[outlen++] = bintoasc[((*inbuf<<4)&060)&077];
361           outbuf[outlen++] = '=';
362           outbuf[outlen++] = '=';
363         }
364       else
365         {
366           outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
367                                     |((inbuf[1]>>4)&017))&077];
368           outbuf[outlen++] = bintoasc[((inbuf[1]<<2)&074)&077];
369           outbuf[outlen++] = '=';
370         }
371       ++quads;
372     }
373
374   if (quads)
375     {
376       outbuf[outlen++] = '\r';
377       outbuf[outlen++] = '\n';
378     }
379
380   if (outlen)
381     {
382       if ((rc = write_buffer (sink, outbuf, outlen)))
383         return rc;
384     }
385
386   return 0;
387 }
388
389 /* Write DATALEN bytes of DATA to SINK in quoted-prinable encoding. */
390 static int
391 write_qp (sink_t sink, const void *data, size_t datalen)
392 {
393   int rc;
394   const unsigned char *p;
395   char outbuf[80];  /* We only need 76 octect + 2 for the lineend. */
396   int outidx;
397
398   /* Check whether the current character is followed by a line ending.
399      Note that the end of the etxt also counts as a lineending */
400 #define nextlf_p() ((datalen > 2 && p[1] == '\r' && p[2] == '\n') \
401                     || (datalen > 1 && p[1] == '\n')              \
402                     || datalen == 1 )
403
404   /* Macro to insert a soft line break if needed.  */
405 # define do_softlf(n) \
406           do {                                                        \
407             if (outidx + (n) > 76                                     \
408                 || (outidx + (n) == 76 && !nextlf_p()))               \
409               {                                                       \
410                 outbuf[outidx++] = '=';                               \
411                 outbuf[outidx++] = '\r';                              \
412                 outbuf[outidx++] = '\n';                              \
413                 if ((rc = write_buffer (sink, outbuf, outidx)))       \
414                   return rc;                                          \
415                 outidx = 0;                                           \
416               }                                                       \
417           } while (0)
418
419   log_debug ("  writing qp of length %d\n", (int)datalen);
420   outidx = 0;
421   for (p = (const unsigned char*) data; datalen; p++, datalen--)
422     {
423       if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
424         {
425           /* Line break.  */
426           outbuf[outidx++] = '\r';
427           outbuf[outidx++] = '\n';
428           if ((rc = write_buffer (sink, outbuf, outidx)))
429             return rc;
430           outidx = 0;
431           if (*p == '\r')
432             {
433               p++;
434               datalen--;
435             }
436         }
437       else if (*p == '\t' || *p == ' ')
438         {
439           /* Check whether tab or space is followed by a line break
440              which forbids verbatim encoding.  If we are already at
441              the end of the buffer we take that as a line end too. */
442           if (nextlf_p())
443             {
444               do_softlf (3);
445               outbuf[outidx++] = '=';
446               outbuf[outidx++] = tohex ((*p>>4)&15);
447               outbuf[outidx++] = tohex (*p&15);
448             }
449           else
450             {
451               do_softlf (1);
452               outbuf[outidx++] = *p;
453             }
454
455         }
456       else if (!outidx && *p == '.' && nextlf_p () )
457         {
458           /* We better protect a line with just a single dot.  */
459           outbuf[outidx++] = '=';
460           outbuf[outidx++] = tohex ((*p>>4)&15);
461           outbuf[outidx++] = tohex (*p&15);
462         }
463       else if (!outidx && datalen >= 5 && !memcmp (p, "From ", 5))
464         {
465           /* Protect the 'F' so that MTAs won't prefix the "From "
466              with an '>' */
467           outbuf[outidx++] = '=';
468           outbuf[outidx++] = tohex ((*p>>4)&15);
469           outbuf[outidx++] = tohex (*p&15);
470         }
471       else if (*p >= '!' && *p <= '~' && *p != '=')
472         {
473           do_softlf (1);
474           outbuf[outidx++] = *p;
475         }
476       else
477         {
478           do_softlf (3);
479           outbuf[outidx++] = '=';
480           outbuf[outidx++] = tohex ((*p>>4)&15);
481           outbuf[outidx++] = tohex (*p&15);
482         }
483     }
484   if (outidx)
485     {
486       outbuf[outidx++] = '\r';
487       outbuf[outidx++] = '\n';
488       if ((rc = write_buffer (sink, outbuf, outidx)))
489         return rc;
490     }
491
492 # undef do_softlf
493 # undef nextlf_p
494   return 0;
495 }
496
497
498 /* Write DATALEN bytes of DATA to SINK in plain ascii encoding. */
499 static int
500 write_plain (sink_t sink, const void *data, size_t datalen)
501 {
502   int rc;
503   const unsigned char *p;
504   char outbuf[100];
505   int outidx;
506
507   log_debug ("  writing ascii of length %d\n", (int)datalen);
508   outidx = 0;
509   for (p = (const unsigned char*) data; datalen; p++, datalen--)
510     {
511       if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
512         {
513           outbuf[outidx++] = '\r';
514           outbuf[outidx++] = '\n';
515           if ((rc = write_buffer (sink, outbuf, outidx)))
516             return rc;
517           outidx = 0;
518           if (*p == '\r')
519             {
520               p++;
521               datalen--;
522             }
523         }
524       else if (!outidx && *p == '.'
525                && ( (datalen > 2 && p[1] == '\r' && p[2] == '\n')
526                     || (datalen > 1 && p[1] == '\n')
527                     || datalen == 1))
528         {
529           /* Better protect a line with just a single dot.  We do
530              this by adding a space.  */
531           outbuf[outidx++] = *p;
532           outbuf[outidx++] = ' ';
533         }
534       else if (outidx > 80)
535         {
536           /* We should never be called for too long lines - QP should
537              have been used.  */
538           log_error ("%s:%s: BUG: line longer than exepcted",
539                      SRCNAME, __func__);
540           return -1;
541         }
542       else
543         outbuf[outidx++] = *p;
544     }
545
546   if (outidx)
547     {
548       outbuf[outidx++] = '\r';
549       outbuf[outidx++] = '\n';
550       if ((rc = write_buffer (sink, outbuf, outidx)))
551         return rc;
552     }
553
554   return 0;
555 }
556
557
558 /* Infer the conent type from the FILENAME.  The return value is
559    a static string there won't be an error return.  In case Bae 64
560    encoding is required for the type true will be stored at FORCE_B64;
561    however, this is only a shortcut and if that is not set, the caller
562    should infer the encoding by other means. */
563 static const char *
564 infer_content_type (const char * /*data*/, size_t /*datalen*/,
565                     const char *filename, int is_mapibody, int *force_b64)
566 {
567   static struct {
568     char b64;
569     const char *suffix;
570     const char *ct;
571   } suffix_table[] =
572     {
573       { 1, "3gp",   "video/3gpp" },
574       { 1, "abw",   "application/x-abiword" },
575       { 1, "ai",    "application/postscript" },
576       { 1, "au",    "audio/basic" },
577       { 1, "bin",   "application/octet-stream" },
578       { 1, "class", "application/java-vm" },
579       { 1, "cpt",   "application/mac-compactpro" },
580       { 0, "css",   "text/css" },
581       { 0, "csv",   "text/comma-separated-values" },
582       { 1, "deb",   "application/x-debian-package" },
583       { 1, "dl",    "video/dl" },
584       { 1, "doc",   "application/msword" },
585       { 1, "dv",    "video/dv" },
586       { 1, "dvi",   "application/x-dvi" },
587       { 1, "eml",   "message/rfc822" },
588       { 1, "eps",   "application/postscript" },
589       { 1, "fig",   "application/x-xfig" },
590       { 1, "flac",  "application/x-flac" },
591       { 1, "fli",   "video/fli" },
592       { 1, "gif",   "image/gif" },
593       { 1, "gl",    "video/gl" },
594       { 1, "gnumeric", "application/x-gnumeric" },
595       { 1, "hqx",   "application/mac-binhex40" },
596       { 1, "hta",   "application/hta" },
597       { 0, "htm",   "text/html" },
598       { 0, "html",  "text/html" },
599       { 0, "ics",   "text/calendar" },
600       { 1, "jar",   "application/java-archive" },
601       { 1, "jpeg",  "image/jpeg" },
602       { 1, "jpg",   "image/jpeg" },
603       { 1, "js",    "application/x-javascript" },
604       { 1, "latex", "application/x-latex" },
605       { 1, "lha",   "application/x-lha" },
606       { 1, "lzh",   "application/x-lzh" },
607       { 1, "lzx",   "application/x-lzx" },
608       { 1, "m3u",   "audio/mpegurl" },
609       { 1, "m4a",   "audio/mpeg" },
610       { 1, "mdb",   "application/msaccess" },
611       { 1, "midi",  "audio/midi" },
612       { 1, "mov",   "video/quicktime" },
613       { 1, "mp2",   "audio/mpeg" },
614       { 1, "mp3",   "audio/mpeg" },
615       { 1, "mp4",   "video/mp4" },
616       { 1, "mpeg",  "video/mpeg" },
617       { 1, "mpega", "audio/mpeg" },
618       { 1, "mpg",   "video/mpeg" },
619       { 1, "mpga",  "audio/mpeg" },
620       { 1, "msi",   "application/x-msi" },
621       { 1, "mxu",   "video/vnd.mpegurl" },
622       { 1, "nb",    "application/mathematica" },
623       { 1, "oda",   "application/oda" },
624       { 1, "odb",   "application/vnd.oasis.opendocument.database" },
625       { 1, "odc",   "application/vnd.oasis.opendocument.chart" },
626       { 1, "odf",   "application/vnd.oasis.opendocument.formula" },
627       { 1, "odg",   "application/vnd.oasis.opendocument.graphics" },
628       { 1, "odi",   "application/vnd.oasis.opendocument.image" },
629       { 1, "odm",   "application/vnd.oasis.opendocument.text-master" },
630       { 1, "odp",   "application/vnd.oasis.opendocument.presentation" },
631       { 1, "ods",   "application/vnd.oasis.opendocument.spreadsheet" },
632       { 1, "odt",   "application/vnd.oasis.opendocument.text" },
633       { 1, "ogg",   "application/ogg" },
634       { 1, "otg",   "application/vnd.oasis.opendocument.graphics-template" },
635       { 1, "oth",   "application/vnd.oasis.opendocument.text-web" },
636       { 1, "otp",  "application/vnd.oasis.opendocument.presentation-template"},
637       { 1, "ots",   "application/vnd.oasis.opendocument.spreadsheet-template"},
638       { 1, "ott",   "application/vnd.oasis.opendocument.text-template" },
639       { 1, "pdf",   "application/pdf" },
640       { 1, "png",   "image/png" },
641       { 1, "pps",   "application/vnd.ms-powerpoint" },
642       { 1, "ppt",   "application/vnd.ms-powerpoint" },
643       { 1, "prf",   "application/pics-rules" },
644       { 1, "ps",    "application/postscript" },
645       { 1, "qt",    "video/quicktime" },
646       { 1, "rar",   "application/rar" },
647       { 1, "rdf",   "application/rdf+xml" },
648       { 1, "rpm",   "application/x-redhat-package-manager" },
649       { 0, "rss",   "application/rss+xml" },
650       { 1, "ser",   "application/java-serialized-object" },
651       { 0, "sh",    "application/x-sh" },
652       { 0, "shtml", "text/html" },
653       { 1, "sid",   "audio/prs.sid" },
654       { 0, "smil",  "application/smil" },
655       { 1, "snd",   "audio/basic" },
656       { 0, "svg",   "image/svg+xml" },
657       { 1, "tar",   "application/x-tar" },
658       { 0, "texi",  "application/x-texinfo" },
659       { 0, "texinfo", "application/x-texinfo" },
660       { 1, "tif",   "image/tiff" },
661       { 1, "tiff",  "image/tiff" },
662       { 1, "torrent", "application/x-bittorrent" },
663       { 1, "tsp",   "application/dsptype" },
664       { 0, "vrml",  "model/vrml" },
665       { 1, "vsd",   "application/vnd.visio" },
666       { 1, "wp5",   "application/wordperfect5.1" },
667       { 1, "wpd",   "application/wordperfect" },
668       { 0, "xhtml", "application/xhtml+xml" },
669       { 1, "xlb",   "application/vnd.ms-excel" },
670       { 1, "xls",   "application/vnd.ms-excel" },
671       { 1, "xlt",   "application/vnd.ms-excel" },
672       { 0, "xml",   "application/xml" },
673       { 0, "xsl",   "application/xml" },
674       { 0, "xul",   "application/vnd.mozilla.xul+xml" },
675       { 1, "zip",   "application/zip" },
676       { 0, NULL, NULL }
677     };
678   int i;
679   std::string suffix;
680
681   *force_b64 = 0;
682   if (filename)
683     {
684       const char *dot = strrchr (filename, '.');
685
686       if (dot)
687         {
688           suffix = dot;
689         }
690     }
691
692   /* Check for at least one char after the dot. */
693   if (suffix.size() > 1)
694     {
695       /* Erase the dot */
696       suffix.erase(0, 1);
697       std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
698       for (i=0; suffix_table[i].suffix; i++)
699         {
700           if (!strcmp (suffix_table[i].suffix, suffix.c_str()))
701             {
702               if (suffix_table[i].b64)
703                 *force_b64 = 1;
704               return suffix_table[i].ct;
705             }
706         }
707     }
708
709   /* Not found via filename, look at the content.  */
710
711   if (is_mapibody == 1)
712     {
713       return "text/plain";
714     }
715   else if (is_mapibody == 2)
716     {
717       return "text/html";
718     }
719   return "application/octet-stream";
720 }
721
722 /* Figure out the best encoding to be used for the part.  Return values are
723      0: Plain ASCII.
724      1: Quoted Printable
725      2: Base64  */
726 static int
727 infer_content_encoding (const void *data, size_t datalen)
728 {
729   const unsigned char *p;
730   int need_qp;
731   size_t len, maxlen, highbin, lowbin, ntotal;
732
733   ntotal = datalen;
734   len = maxlen = lowbin = highbin = 0;
735   need_qp = 0;
736   for (p = (const unsigned char*) data; datalen; p++, datalen--)
737     {
738       len++;
739       if ((*p & 0x80))
740         highbin++;
741       else if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
742         {
743           len--;
744           if (len > maxlen)
745             maxlen = len;
746           len = 0;
747         }
748       else if (*p == '\r')
749         {
750           /* CR not followed by a linefeed. */
751           lowbin++;
752         }
753       else if (*p == '\t' || *p == ' ' || *p == '\f')
754         ;
755       else if (*p < ' ' || *p == 127)
756         lowbin++;
757       else if (len == 1 && datalen > 2
758                && *p == '-' && p[1] == '-' && p[2] == ' '
759                && ( (datalen > 4 && p[3] == '\r' && p[4] == '\n')
760                     || (datalen > 3 && p[3] == '\n')
761                     || datalen == 3))
762         {
763           /* This is a "-- \r\n" line, thus it indicates the usual
764              signature line delimiter.  We need to protect the
765              trailing space.  */
766           need_qp = 1;
767         }
768       else if (len == 1 && datalen > 5 && !memcmp (p, "--=-=", 5))
769         {
770           /* This look pretty much like a our own boundary.
771              We better protect it by forcing QP encoding.  */
772           need_qp = 1;
773         }
774       else if (len == 1 && datalen >= 5 && !memcmp (p, "From ", 5))
775         {
776           /* The usual From hack is required so that MTAs do not
777              prefix it with an '>'.  */
778           need_qp = 1;
779         }
780     }
781   if (len > maxlen)
782     maxlen = len;
783
784   if (maxlen <= 76 && !lowbin && !highbin && !need_qp)
785     return 0; /* Plain ASCII is sufficient.  */
786
787   /* Somewhere in the Outlook documentation 20% is mentioned as
788      discriminating value for Base64.  Though our counting won't be
789      identical we use that value to behave closely to it. */
790   if (ntotal && ((float)(lowbin+highbin))/ntotal < 0.20)
791     return 1; /* Use quoted printable.  */
792
793   return 2;   /* Use base64.  */
794 }
795
796
797 /* Convert an utf8 input string to RFC2047 base64 encoding which
798    is the subset of RFC2047 outlook likes.
799    Return value needs to be freed.
800    */
801 static char *
802 utf8_to_rfc2047b (const char *input)
803 {
804   char *ret,
805        *encoded;
806   int inferred_encoding = 0;
807   if (!input)
808     {
809       return NULL;
810     }
811   inferred_encoding = infer_content_encoding (input, strlen (input));
812   if (!inferred_encoding)
813     {
814       return xstrdup (input);
815     }
816   log_debug ("%s:%s: Encoding attachment filename. With: %s ",
817              SRCNAME, __func__, inferred_encoding == 2 ? "Base64" : "QP");
818
819   if (inferred_encoding == 2)
820     {
821       encoded = b64_encode (input, strlen (input));
822       if (gpgrt_asprintf (&ret, "=?utf-8?B?%s?=", encoded) == -1)
823         {
824           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
825           xfree (encoded);
826           return NULL;
827         }
828     }
829   else
830     {
831       /* There is a Bug here. If you encode 4 Byte UTF-8 outlook can't
832          handle it itself. And sends out a message with ?? inserted in
833          that place. This triggers an invalid signature. */
834       encoded = qp_encode (input, strlen (input), NULL);
835       if (gpgrt_asprintf (&ret, "=?utf-8?Q?%s?=", encoded) == -1)
836         {
837           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
838           xfree (encoded);
839           return NULL;
840         }
841     }
842   xfree (encoded);
843   return ret;
844 }
845
846 /* Write a MIME part to SINK.  First the BOUNDARY is written (unless
847    it is NULL) then the DATA is analyzed and appropriate headers are
848    written.  If FILENAME is given it will be added to the part's
849    header.  IS_MAPIBODY should be passed as true if the data has been
850    retrieved from the body property.  */
851 static int
852 write_part (sink_t sink, const char *data, size_t datalen,
853             const char *boundary, const char *filename, int is_mapibody,
854             const char *content_id = NULL)
855 {
856   int rc;
857   const char *ct;
858   int use_b64, use_qp, is_text;
859   char *encoded_filename;
860
861   if (filename)
862     {
863       /* If there is a filename strip the directory part.  Take care
864          that there might be slashes or backslashes.  */
865       const char *s1 = strrchr (filename, '/');
866       const char *s2 = strrchr (filename, '\\');
867
868       if (!s1)
869         s1 = s2;
870       else if (s1 && s2 && s2 > s1)
871         s1 = s2;
872
873       if (s1)
874         filename = s1;
875       if (*filename && filename[1] == ':')
876         filename += 2;
877       if (!*filename)
878         filename = NULL;
879     }
880
881   log_debug ("Writing part of length %d%s filename=`%s'\n",
882              (int)datalen, is_mapibody? " (body)":"",
883              filename?filename:"[none]");
884
885   ct = infer_content_type (data, datalen, filename, is_mapibody, &use_b64);
886   use_qp = 0;
887   if (!use_b64)
888     {
889       switch (infer_content_encoding (data, datalen))
890         {
891         case 0: break;
892         case 1: use_qp = 1; break;
893         default: use_b64 = 1; break;
894         }
895     }
896   is_text = !strncmp (ct, "text/", 5);
897
898   if (boundary)
899     if ((rc = write_boundary (sink, boundary, 0)))
900       return rc;
901   if ((rc=write_multistring (sink,
902                              "Content-Type: ", ct,
903                              (is_text || filename? ";\r\n" :"\r\n"),
904                              NULL)))
905     return rc;
906
907   /* OL inserts a charset parameter in many cases, so we do it right
908      away for all text parts.  We can assume us-ascii if no special
909      encoding is required.  */
910   if (is_text)
911     if ((rc=write_multistring (sink,
912                                "\tcharset=\"",
913                                (!use_qp && !use_b64? "us-ascii" : "utf-8"),
914                                filename ? "\";\r\n" : "\"\r\n",
915                                NULL)))
916       return rc;
917
918   encoded_filename = utf8_to_rfc2047b (filename);
919   if (encoded_filename)
920     if ((rc=write_multistring (sink,
921                                "\tname=\"", encoded_filename, "\"\r\n",
922                                NULL)))
923       return rc;
924
925   /* Note that we need to output even 7bit because OL inserts that
926      anyway.  */
927   if ((rc = write_multistring (sink,
928                                "Content-Transfer-Encoding: ",
929                                (use_b64? "base64\r\n":
930                                 use_qp? "quoted-printable\r\n":"7bit\r\n"),
931                                NULL)))
932     return rc;
933
934   if (content_id)
935     {
936       if ((rc=write_multistring (sink,
937                                  "Content-ID: <", content_id, ">\r\n",
938                                  NULL)))
939         return rc;
940     }
941   else if (encoded_filename)
942     if ((rc=write_multistring (sink,
943                                "Content-Disposition: attachment;\r\n"
944                                "\tfilename=\"", encoded_filename, "\"\r\n",
945                                NULL)))
946       return rc;
947
948   xfree(encoded_filename);
949
950   /* Write delimiter.  */
951   if ((rc = write_string (sink, "\r\n")))
952     return rc;
953
954   /* Write the content.  */
955   if (use_b64)
956     rc = write_b64 (sink, data, datalen);
957   else if (use_qp)
958     rc = write_qp (sink, data, datalen);
959   else
960     rc = write_plain (sink, data, datalen);
961
962   return rc;
963 }
964
965
966 /* Return the number of attachments in TABLE to be put into the MIME
967    message.  */
968 int
969 count_usable_attachments (mapi_attach_item_t *table)
970 {
971   int idx, count = 0;
972
973   if (table)
974     for (idx=0; !table[idx].end_of_table; idx++)
975       if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
976           && table[idx].method == ATTACH_BY_VALUE)
977         count++;
978   return count;
979 }
980
981 /* Write out all attachments from TABLE separated by BOUNDARY to SINK.
982    This function needs to be syncronized with count_usable_attachments.
983    If only_related is 1 only include attachments for multipart/related they
984    are excluded otherwise. */
985 static int
986 write_attachments (sink_t sink,
987                    LPMESSAGE message, mapi_attach_item_t *table,
988                    const char *boundary, int only_related)
989 {
990   int idx, rc;
991   char *buffer;
992   size_t buflen;
993
994   if (table)
995     for (idx=0; !table[idx].end_of_table; idx++)
996       if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
997           && table[idx].method == ATTACH_BY_VALUE)
998         {
999           if (only_related && !table[idx].content_id)
1000             {
1001               continue;
1002             }
1003           else if (!only_related && table[idx].content_id)
1004             {
1005               continue;
1006             }
1007           buffer = mapi_get_attach (message, table+idx, &buflen);
1008           if (!buffer)
1009             log_debug ("Attachment at index %d not found\n", idx);
1010           else
1011             log_debug ("Attachment at index %d: length=%d\n", idx, (int)buflen);
1012           if (!buffer)
1013             return -1;
1014           rc = write_part (sink, buffer, buflen, boundary,
1015                            table[idx].filename, 0, table[idx].content_id);
1016           if (rc)
1017             {
1018               log_error ("Write part returned err: %i", rc);
1019             }
1020           xfree (buffer);
1021         }
1022   return 0;
1023 }
1024
1025 /* Returns 1 if all attachments are related. 2 if there is a
1026    related and a mixed attachment. 0 if there are no other parts*/
1027 static int
1028 is_related (Mail *mail, mapi_attach_item_t *table)
1029 {
1030   if (!mail || !mail->isHTMLAlternative () || !table)
1031     {
1032       return 0;
1033     }
1034
1035   int related = 0;
1036   int mixed = 0;
1037   for (int idx = 0; !table[idx].end_of_table; idx++)
1038     {
1039       if (table[idx].content_id)
1040         {
1041           related = 1;
1042         }
1043       else
1044         {
1045           mixed = 1;
1046         }
1047     }
1048   return mixed + related;
1049 }
1050
1051
1052 /* Delete all attachments from TABLE except for the one we just created */
1053 static int
1054 delete_all_attachments (LPMESSAGE message, mapi_attach_item_t *table)
1055 {
1056   HRESULT hr;
1057   int idx;
1058
1059   if (table)
1060     for (idx=0; !table[idx].end_of_table; idx++)
1061       {
1062         if (table[idx].attach_type == ATTACHTYPE_MOSSTEMPL
1063             && table[idx].filename
1064             && !strcmp (table[idx].filename, MIMEATTACHFILENAME))
1065           continue;
1066         hr = message->DeleteAttach (table[idx].mapipos, 0, NULL, 0);
1067         if (hr)
1068           {
1069             log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
1070                        SRCNAME, __func__, hr);
1071             return -1;
1072           }
1073       }
1074   return 0;
1075 }
1076
1077
1078 /* Commit changes to the attachment ATTACH and release the object.
1079    SINK needs to be passed as well and will also be closed.  Note that
1080    the address of ATTACH is expected so that the fucntion can set it
1081    to NULL. */
1082 int
1083 close_mapi_attachment (LPATTACH *attach, sink_t sink)
1084 {
1085   HRESULT hr;
1086   LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
1087
1088   if (!stream)
1089     {
1090       log_error ("%s:%s: sink not setup", SRCNAME, __func__);
1091       return -1;
1092     }
1093   hr = stream->Commit (0);
1094   if (hr)
1095     {
1096       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
1097                  SRCNAME, __func__, hr);
1098       return -1;
1099     }
1100   gpgol_release (stream);
1101   sink->cb_data = NULL;
1102   hr = (*attach)->SaveChanges (0);
1103   if (hr)
1104     {
1105       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
1106                  SRCNAME, __func__, hr);
1107       return -1;
1108     }
1109   gpgol_release ((*attach));
1110   *attach = NULL;
1111   return 0;
1112 }
1113
1114
1115 /* Cancel changes to the attachment ATTACH and release the object.
1116    SINK needs to be passed as well and will also be closed.  Note that
1117    the address of ATTACH is expected so that the fucntion can set it
1118    to NULL. */
1119 void
1120 cancel_mapi_attachment (LPATTACH *attach, sink_t sink)
1121 {
1122   LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
1123
1124   if (stream)
1125     {
1126       stream->Revert();
1127       gpgol_release (stream);
1128       sink->cb_data = NULL;
1129     }
1130   if (*attach)
1131     {
1132       /* Fixme: Should we try to delete it or is there a Revert method? */
1133       gpgol_release ((*attach));
1134       *attach = NULL;
1135     }
1136 }
1137
1138
1139
1140 /* Do the final processing for a message. */
1141 int
1142 finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
1143                   protocol_t protocol, int encrypt, bool is_inline)
1144 {
1145   HRESULT hr = 0;
1146   SPropValue prop;
1147   SPropTagArray proparray;
1148
1149   /* Set the message class.  */
1150   prop.ulPropTag = PR_MESSAGE_CLASS_A;
1151   if (encrypt)
1152     {
1153       prop.Value.lpszA = strdup ("IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned");
1154     }
1155   else
1156     {
1157       prop.Value.lpszA = strdup ("IPM.Note.InfoPathForm.GpgOLS.SMIME.MultipartSigned");
1158     }
1159
1160   if (!is_inline)
1161     {
1162       /* For inline we stick with IPM.Note because Exchange Online would
1163          error out if we tried our S/MIME conversion trick with a text
1164          plain message */
1165       hr = message->SetProps(1, &prop, NULL);
1166     }
1167   xfree(prop.Value.lpszA);
1168   if (hr)
1169     {
1170       log_error ("%s:%s: error setting the message class: hr=%#lx\n",
1171                  SRCNAME, __func__, hr);
1172       return -1;
1173     }
1174
1175   /* Set a special property so that we are later able to identify
1176      messages signed or encrypted by us.  */
1177   if (mapi_set_sig_status (message, "@"))
1178     {
1179       log_error ("%s:%s: error setting sigstatus",
1180                  SRCNAME, __func__);
1181       return -1;
1182     }
1183
1184   /* We also need to set the message class into our custom
1185      property. This override is at least required for encrypted
1186      messages.  */
1187   if (is_inline && mapi_set_gpgol_msg_class (message,
1188                                           (encrypt?
1189                                            (protocol == PROTOCOL_SMIME?
1190                                             "IPM.Note.GpgOL.OpaqueEncrypted" :
1191                                             "IPM.Note.GpgOL.PGPMessage") :
1192                                             "IPM.Note.GpgOL.ClearSigned")))
1193     {
1194       log_error ("%s:%s: error setting gpgol msgclass",
1195                  SRCNAME, __func__);
1196       return -1;
1197     }
1198   if (!is_inline && mapi_set_gpgol_msg_class (message,
1199                                 (encrypt?
1200                                  (protocol == PROTOCOL_SMIME?
1201                                   "IPM.Note.GpgOL.OpaqueEncrypted" :
1202                                   "IPM.Note.GpgOL.MultipartEncrypted") :
1203                                  "IPM.Note.GpgOL.MultipartSigned")))
1204     {
1205       log_error ("%s:%s: error setting gpgol msgclass",
1206                  SRCNAME, __func__);
1207       return -1;
1208     }
1209
1210   proparray.cValues = 1;
1211   proparray.aulPropTag[0] = PR_BODY;
1212   hr = message->DeleteProps (&proparray, NULL);
1213   if (hr)
1214     {
1215       log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
1216                      SRCNAME, __func__);
1217     }
1218
1219   proparray.cValues = 1;
1220   proparray.aulPropTag[0] = PR_BODY_HTML;
1221   hr = message->DeleteProps (&proparray, NULL);
1222   if (hr)
1223     {
1224       log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
1225                      SRCNAME, __func__);
1226     }
1227
1228   /* Now delete all parts of the MAPI message except for the one
1229      attachment we just created.  */
1230   if (delete_all_attachments (message, att_table))
1231     {
1232       log_error ("%s:%s: error deleting attachments",
1233                  SRCNAME, __func__);
1234       return -1;
1235     }
1236
1237   /* Remove the draft info so that we don't leak the information on
1238      whether the message has been signed etc.  */
1239   mapi_set_gpgol_draft_info (message, NULL);
1240
1241   if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
1242     {
1243       log_error ("%s:%s: error saving changes.",
1244                  SRCNAME, __func__);
1245       return -1;
1246     }
1247   return 0;
1248 }
1249
1250
1251 /* Helper to create the signing header.  This includes enough space
1252    for later fixup of the micalg parameter.  The MIME version is only
1253    written if FIRST is set.  */
1254 void
1255 create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
1256                            int first, const char *boundary, const char *micalg)
1257 {
1258   snprintf (buffer, buflen,
1259             "%s"
1260             "Content-Type: multipart/signed;\r\n"
1261             "\tprotocol=\"application/%s\";\r\n"
1262             "\tmicalg=%-15.15s;\r\n"
1263             "\tboundary=\"%s\"\r\n"
1264             "\r\n",
1265             first? "MIME-Version: 1.0\r\n":"",
1266             (protocol==PROTOCOL_OPENPGP? "pgp-signature":"pkcs7-signature"),
1267             micalg, boundary);
1268 }
1269
1270 /* Add the body, either as multipart/alternative or just as the
1271   simple body part. Depending on the format set in outlook. To
1272   avoid memory duplication it takes the plain body as parameter.
1273
1274   Boundary is the potential outer boundary of a multipart/mixed
1275   mail. If it is null we assume the multipart/alternative is
1276   the only part.
1277
1278   return is zero on success.
1279 */
1280 static int
1281 add_body (Mail *mail, const char *boundary, sink_t sink,
1282           const char *plain_body)
1283 {
1284   if (!plain_body)
1285     {
1286       return 0;
1287     }
1288   bool is_alternative = false;
1289   if (mail)
1290     {
1291       is_alternative = mail->isHTMLAlternative ();
1292     }
1293
1294   int rc = 0;
1295   if (!is_alternative || !plain_body)
1296     {
1297       if (plain_body)
1298         {
1299           rc = write_part (sink, plain_body, strlen (plain_body),
1300                            boundary, NULL, 1);
1301         }
1302       /* Just the plain body or no body. We are done. */
1303       return rc;
1304     }
1305
1306   /* Add a new multipart / mixed element. */
1307   if (boundary && write_boundary (sink, boundary, 0))
1308     {
1309       TRACEPOINT;
1310       return 1;
1311     }
1312
1313   /* Now for the multipart/alternative part. We never do HTML only. */
1314   char alt_boundary [BOUNDARYSIZE+1];
1315   generate_boundary (alt_boundary);
1316
1317   if ((rc=write_multistring (sink,
1318                             "Content-Type: multipart/alternative;\r\n",
1319                             "\tboundary=\"", alt_boundary, "\"\r\n",
1320                             "\r\n",  /* <-- extra line */
1321                             NULL)))
1322     {
1323       TRACEPOINT;
1324       return rc;
1325     }
1326
1327   /* Now the plain body part */
1328   if ((rc = write_part (sink, plain_body, strlen (plain_body),
1329                        alt_boundary, NULL, 1)))
1330     {
1331       TRACEPOINT;
1332       return rc;
1333     }
1334
1335   /* Now the html body. It is somehow not accessible through PR_HTML,
1336      OutlookSpy also shows MAPI Unsupported (but shows the data) strange.
1337      We just cache it. Memory is cheap :-) */
1338   char *html_body = mail->takeCachedHTMLBody ();
1339   if (!html_body)
1340     {
1341       log_error ("%s:%s: BUG: Body but no html body in alternative mail?",
1342                  SRCNAME, __func__);
1343       return -1;
1344     }
1345
1346   rc = write_part (sink, html_body, strlen (html_body),
1347                    alt_boundary, NULL, 2);
1348   xfree (html_body);
1349   if (rc)
1350     {
1351       TRACEPOINT;
1352       return rc;
1353     }
1354   /* Finish our multipart */
1355   return write_boundary (sink, alt_boundary, 1);
1356 }
1357
1358 /* Add the body and attachments. Does multipart handling. */
1359 int
1360 add_body_and_attachments (sink_t sink, LPMESSAGE message,
1361                           mapi_attach_item_t *att_table, Mail *mail,
1362                           const char *body, int n_att_usable)
1363 {
1364   int related = is_related (mail, att_table);
1365   int rc = 0;
1366   char inner_boundary[BOUNDARYSIZE+1];
1367   char outer_boundary[BOUNDARYSIZE+1];
1368   *outer_boundary = 0;
1369   *inner_boundary = 0;
1370
1371   if (((body && n_att_usable) || n_att_usable > 1) && related == 1)
1372     {
1373       /* A body and at least one attachment or more than one attachment  */
1374       generate_boundary (outer_boundary);
1375       if ((rc=write_multistring (sink,
1376                                  "Content-Type: multipart/related;\r\n",
1377                                  "\tboundary=\"", outer_boundary, "\"\r\n",
1378                                  "\r\n", /* <--- Outlook adds an extra line. */
1379                                  NULL)))
1380         return rc;
1381     }
1382   else if ((body && n_att_usable) || n_att_usable > 1)
1383     {
1384       generate_boundary (outer_boundary);
1385       if ((rc=write_multistring (sink,
1386                                  "Content-Type: multipart/mixed;\r\n",
1387                                  "\tboundary=\"", outer_boundary, "\"\r\n",
1388                                  "\r\n", /* <--- Outlook adds an extra line. */
1389                                  NULL)))
1390         return rc;
1391     }
1392
1393   /* Only one part.  */
1394   if (*outer_boundary && related == 2)
1395     {
1396       /* We have attachments that are related to the body and unrelated
1397          attachments. So we need another part. */
1398       if ((rc=write_boundary (sink, outer_boundary, 0)))
1399         {
1400           return rc;
1401         }
1402       generate_boundary (inner_boundary);
1403       if ((rc=write_multistring (sink,
1404                                  "Content-Type: multipart/related;\r\n",
1405                                  "\tboundary=\"", inner_boundary, "\"\r\n",
1406                                  "\r\n", /* <--- Outlook adds an extra line. */
1407                                  NULL)))
1408         {
1409           return rc;
1410         }
1411     }
1412
1413
1414   if ((rc=add_body (mail, *inner_boundary ? inner_boundary :
1415                           *outer_boundary ? outer_boundary : NULL,
1416                     sink, body)))
1417     {
1418       log_error ("%s:%s: Adding the body failed.",
1419                  SRCNAME, __func__);
1420       return rc;
1421     }
1422   if (!rc && n_att_usable && related)
1423     {
1424       /* Write the related attachments. */
1425       rc = write_attachments (sink, message, att_table,
1426                               *inner_boundary? inner_boundary :
1427                               *outer_boundary? outer_boundary : NULL, 1);
1428       if (rc)
1429         {
1430           return rc;
1431         }
1432       /* Close the related part if neccessary.*/
1433       if (*inner_boundary && (rc=write_boundary (sink, inner_boundary, 1)))
1434         {
1435           return rc;
1436         }
1437     }
1438
1439   /* Now write the other attachments */
1440   if (!rc && n_att_usable)
1441     rc = write_attachments (sink, message, att_table,
1442                             *outer_boundary? outer_boundary : NULL, 0);
1443
1444   /* Finish the possible multipart/mixed. */
1445   if (*outer_boundary && (rc = write_boundary (sink, outer_boundary, 1)))
1446     return rc;
1447
1448   return rc;
1449 }
1450
1451
1452 /* Helper from mime_encrypt.  BOUNDARY is a buffer of at least
1453    BOUNDARYSIZE+1 bytes which will be set on return from that
1454    function.  */
1455 int
1456 create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
1457                               bool is_inline, int exchange_major_version)
1458 {
1459   int rc;
1460
1461   if (is_inline)
1462     {
1463       *boundary = 0;
1464       rc = 0;
1465       /* This would be nice and worked for Google Sync but it failed
1466          for Microsoft Exchange Online *sigh* so we put the body
1467          instead into the oom body property and stick with IPM Note.
1468       rc = write_multistring (sink,
1469                               "MIME-Version: 1.0\r\n"
1470                               "Content-Type: text/plain;\r\n"
1471                               "\tcharset=\"iso-8859-1\"\r\n"
1472                               "Content-Transfer-Encoding: 7BIT\r\n"
1473                               "\r\n",
1474                               NULL);
1475      */
1476     }
1477   else if (protocol == PROTOCOL_SMIME)
1478     {
1479       *boundary = 0;
1480       if (exchange_major_version >= 15)
1481         {
1482           /*
1483              For S/MIME encrypted mails we do not use the S/MIME conversion
1484              code anymore. With Exchange 2016 this no longer works. Instead
1485              we set an override mime tag, the extended headers in OOM in
1486              Mail::update_crypt_oom and let outlook convert the attachment
1487              to base64.
1488
1489              A bit more details can be found in T3853 / T3884
1490              */
1491           rc = 0;
1492         }
1493       else
1494         {
1495           rc = write_multistring (sink,
1496                                   "Content-Type: application/pkcs7-mime; "
1497                                   "smime-type=enveloped-data;\r\n"
1498                                   "\tname=\"smime.p7m\"\r\n"
1499                                   "Content-Disposition: attachment; filename=\"smime.p7m\"\r\n"
1500                                   "Content-Transfer-Encoding: base64\r\n"
1501                                   "MIME-Version: 1.0\r\n"
1502                                   "\r\n",
1503                                   NULL);
1504         }
1505     }
1506   else
1507     {
1508       generate_boundary (boundary);
1509       rc = write_multistring (sink,
1510                               "MIME-Version: 1.0\r\n"
1511                               "Content-Type: multipart/encrypted;\r\n"
1512                               "\tprotocol=\"application/pgp-encrypted\";\r\n",
1513                               "\tboundary=\"", boundary, "\"\r\n",
1514                               NULL);
1515       if (rc)
1516         return rc;
1517
1518       /* Write the PGP/MIME encrypted part.  */
1519       rc = write_boundary (sink, boundary, 0);
1520       if (rc)
1521         return rc;
1522       rc = write_multistring (sink,
1523                               "Content-Type: application/pgp-encrypted\r\n"
1524                               "\r\n"
1525                               "Version: 1\r\n", NULL);
1526       if (rc)
1527         return rc;
1528
1529       /* And start the second part.  */
1530       rc = write_boundary (sink, boundary, 0);
1531       if (rc)
1532         return rc;
1533       rc = write_multistring (sink,
1534                               "Content-Type: application/octet-stream\r\n"
1535                               "\r\n", NULL);
1536      }
1537
1538   return rc;
1539 }
1540
1541
1542 int
1543 restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
1544                        msgtype_t type, char *msgcls)
1545 {
1546   struct sink_s sinkmem;
1547   sink_t sink = &sinkmem;
1548   char *orig = NULL;
1549   int err = -1;
1550   char boundary[BOUNDARYSIZE+1];
1551
1552   (void)msgcls;
1553
1554   LPATTACH new_attach = create_mapi_attachment (message,
1555                                                 sink);
1556   log_debug ("Restore message from moss called.");
1557   if (!new_attach)
1558     {
1559       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1560       goto done;
1561     }
1562   // TODO MORE
1563   if (type == MSGTYPE_SMIME)
1564     {
1565       create_top_encryption_header (sink, PROTOCOL_SMIME, boundary);
1566     }
1567   else if (type == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED)
1568     {
1569       create_top_encryption_header (sink, PROTOCOL_OPENPGP, boundary);
1570     }
1571   else
1572     {
1573       log_error ("%s:%s: Unsupported messagetype: %i",
1574                  SRCNAME, __func__, type);
1575       goto done;
1576     }
1577
1578   orig = get_pa_string (moss_att, PR_ATTACH_DATA_BIN_DASL);
1579
1580   if (!orig)
1581     {
1582       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1583       goto done;
1584     }
1585
1586   if (write_string (sink, orig))
1587     {
1588       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1589       goto done;
1590     }
1591
1592   if (*boundary && write_boundary (sink, boundary, 1))
1593     {
1594       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1595       goto done;
1596     }
1597
1598   if (close_mapi_attachment (&new_attach, sink))
1599     {
1600       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1601       goto done;
1602     }
1603
1604   /* Set a special property so that we are later able to identify
1605      messages signed or encrypted by us.  */
1606   if (mapi_set_sig_status (message, "@"))
1607     {
1608       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1609       goto done;
1610     }
1611
1612   err = 0;
1613 done:
1614   xfree (orig);
1615   return err;
1616 }