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