Add minimalistic protected-headers support
[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 = xstrdup (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 ? xstrdup (overrideMimeTag) :
201                          xstrdup ("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, "docx",  "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
586       { 1, "dot",   "application/msword" },
587       { 1, "dotx",  "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
588       { 1, "docm",  "application/application/vnd.ms-word.document.macroEnabled.12" },
589       { 1, "dotm",  "application/vnd.ms-word.template.macroEnabled.12" },
590       { 1, "dv",    "video/dv" },
591       { 1, "dvi",   "application/x-dvi" },
592       { 1, "eml",   "message/rfc822" },
593       { 1, "eps",   "application/postscript" },
594       { 1, "fig",   "application/x-xfig" },
595       { 1, "flac",  "application/x-flac" },
596       { 1, "fli",   "video/fli" },
597       { 1, "gif",   "image/gif" },
598       { 1, "gl",    "video/gl" },
599       { 1, "gnumeric", "application/x-gnumeric" },
600       { 1, "hqx",   "application/mac-binhex40" },
601       { 1, "hta",   "application/hta" },
602       { 0, "htm",   "text/html" },
603       { 0, "html",  "text/html" },
604       { 0, "ics",   "text/calendar" },
605       { 1, "jar",   "application/java-archive" },
606       { 1, "jpeg",  "image/jpeg" },
607       { 1, "jpg",   "image/jpeg" },
608       { 1, "js",    "application/x-javascript" },
609       { 1, "latex", "application/x-latex" },
610       { 1, "lha",   "application/x-lha" },
611       { 1, "lzh",   "application/x-lzh" },
612       { 1, "lzx",   "application/x-lzx" },
613       { 1, "m3u",   "audio/mpegurl" },
614       { 1, "m4a",   "audio/mpeg" },
615       { 1, "mdb",   "application/msaccess" },
616       { 1, "midi",  "audio/midi" },
617       { 1, "mov",   "video/quicktime" },
618       { 1, "mp2",   "audio/mpeg" },
619       { 1, "mp3",   "audio/mpeg" },
620       { 1, "mp4",   "video/mp4" },
621       { 1, "mpeg",  "video/mpeg" },
622       { 1, "mpega", "audio/mpeg" },
623       { 1, "mpg",   "video/mpeg" },
624       { 1, "mpga",  "audio/mpeg" },
625       { 1, "msi",   "application/x-msi" },
626       { 1, "mxu",   "video/vnd.mpegurl" },
627       { 1, "nb",    "application/mathematica" },
628       { 1, "oda",   "application/oda" },
629       { 1, "odb",   "application/vnd.oasis.opendocument.database" },
630       { 1, "odc",   "application/vnd.oasis.opendocument.chart" },
631       { 1, "odf",   "application/vnd.oasis.opendocument.formula" },
632       { 1, "odg",   "application/vnd.oasis.opendocument.graphics" },
633       { 1, "odi",   "application/vnd.oasis.opendocument.image" },
634       { 1, "odm",   "application/vnd.oasis.opendocument.text-master" },
635       { 1, "odp",   "application/vnd.oasis.opendocument.presentation" },
636       { 1, "ods",   "application/vnd.oasis.opendocument.spreadsheet" },
637       { 1, "odt",   "application/vnd.oasis.opendocument.text" },
638       { 1, "ogg",   "application/ogg" },
639       { 1, "otg",   "application/vnd.oasis.opendocument.graphics-template" },
640       { 1, "oth",   "application/vnd.oasis.opendocument.text-web" },
641       { 1, "otp",  "application/vnd.oasis.opendocument.presentation-template"},
642       { 1, "ots",   "application/vnd.oasis.opendocument.spreadsheet-template"},
643       { 1, "ott",   "application/vnd.oasis.opendocument.text-template" },
644       { 1, "pdf",   "application/pdf" },
645       { 1, "png",   "image/png" },
646       { 1, "pps",   "application/vnd.ms-powerpoint" },
647       { 1, "ppt",   "application/vnd.ms-powerpoint" },
648       { 1, "pot",   "application/vnd.ms-powerpoint" },
649       { 1, "ppa",   "application/vnd.ms-powerpoint" },
650       { 1, "pptx",  "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
651       { 1, "potx",  "application/vnd.openxmlformats-officedocument.presentationml.template" },
652       { 1, "ppsx",  "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
653       { 1, "ppam",  "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
654       { 1, "pptm",  "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
655       { 1, "potm",  "application/vnd.ms-powerpoint.template.macroEnabled.12" },
656       { 1, "ppsm",  "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
657       { 1, "prf",   "application/pics-rules" },
658       { 1, "ps",    "application/postscript" },
659       { 1, "qt",    "video/quicktime" },
660       { 1, "rar",   "application/rar" },
661       { 1, "rdf",   "application/rdf+xml" },
662       { 1, "rpm",   "application/x-redhat-package-manager" },
663       { 0, "rss",   "application/rss+xml" },
664       { 1, "ser",   "application/java-serialized-object" },
665       { 0, "sh",    "application/x-sh" },
666       { 0, "shtml", "text/html" },
667       { 1, "sid",   "audio/prs.sid" },
668       { 0, "smil",  "application/smil" },
669       { 1, "snd",   "audio/basic" },
670       { 0, "svg",   "image/svg+xml" },
671       { 1, "tar",   "application/x-tar" },
672       { 0, "texi",  "application/x-texinfo" },
673       { 0, "texinfo", "application/x-texinfo" },
674       { 1, "tif",   "image/tiff" },
675       { 1, "tiff",  "image/tiff" },
676       { 1, "torrent", "application/x-bittorrent" },
677       { 1, "tsp",   "application/dsptype" },
678       { 0, "vrml",  "model/vrml" },
679       { 1, "vsd",   "application/vnd.visio" },
680       { 1, "wp5",   "application/wordperfect5.1" },
681       { 1, "wpd",   "application/wordperfect" },
682       { 0, "xhtml", "application/xhtml+xml" },
683       { 1, "xlb",   "application/vnd.ms-excel" },
684       { 1, "xls",   "application/vnd.ms-excel" },
685       { 1, "xlsx",  "application/vnd.ms-excel" },
686       { 1, "xlt",   "application/vnd.ms-excel" },
687       { 1, "xla",   "application/vnd.ms-excel" },
688       { 1, "xltx",  "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
689       { 1, "xlsm",  "application/vnd.ms-excel.sheet.macroEnabled.12" },
690       { 1, "xltm",  "application/vnd.ms-excel.template.macroEnabled.12" },
691       { 1, "xlam",  "application/vnd.ms-excel.addin.macroEnabled.12" },
692       { 1, "xlsb",  "application/application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
693       { 0, "xml",   "application/xml" },
694       { 0, "xsl",   "application/xml" },
695       { 0, "xul",   "application/vnd.mozilla.xul+xml" },
696       { 1, "zip",   "application/zip" },
697       { 0, NULL, NULL }
698     };
699   int i;
700   std::string suffix;
701
702   *force_b64 = 0;
703   if (filename)
704     {
705       const char *dot = strrchr (filename, '.');
706
707       if (dot)
708         {
709           suffix = dot;
710         }
711     }
712
713   /* Check for at least one char after the dot. */
714   if (suffix.size() > 1)
715     {
716       /* Erase the dot */
717       suffix.erase(0, 1);
718       std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
719       for (i=0; suffix_table[i].suffix; i++)
720         {
721           if (!strcmp (suffix_table[i].suffix, suffix.c_str()))
722             {
723               if (suffix_table[i].b64)
724                 *force_b64 = 1;
725               return suffix_table[i].ct;
726             }
727         }
728     }
729
730   /* Not found via filename, look at the content.  */
731
732   if (is_mapibody == 1)
733     {
734       return "text/plain";
735     }
736   else if (is_mapibody == 2)
737     {
738       return "text/html";
739     }
740   return "application/octet-stream";
741 }
742
743 /* Figure out the best encoding to be used for the part.  Return values are
744      0: Plain ASCII.
745      1: Quoted Printable
746      2: Base64  */
747 static int
748 infer_content_encoding (const void *data, size_t datalen)
749 {
750   const unsigned char *p;
751   int need_qp;
752   size_t len, maxlen, highbin, lowbin, ntotal;
753
754   ntotal = datalen;
755   len = maxlen = lowbin = highbin = 0;
756   need_qp = 0;
757   for (p = (const unsigned char*) data; datalen; p++, datalen--)
758     {
759       len++;
760       if ((*p & 0x80))
761         highbin++;
762       else if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
763         {
764           len--;
765           if (len > maxlen)
766             maxlen = len;
767           len = 0;
768         }
769       else if (*p == '\r')
770         {
771           /* CR not followed by a linefeed. */
772           lowbin++;
773         }
774       else if (*p == '\t' || *p == ' ' || *p == '\f')
775         ;
776       else if (*p < ' ' || *p == 127)
777         lowbin++;
778       else if (len == 1 && datalen > 2
779                && *p == '-' && p[1] == '-' && p[2] == ' '
780                && ( (datalen > 4 && p[3] == '\r' && p[4] == '\n')
781                     || (datalen > 3 && p[3] == '\n')
782                     || datalen == 3))
783         {
784           /* This is a "-- \r\n" line, thus it indicates the usual
785              signature line delimiter.  We need to protect the
786              trailing space.  */
787           need_qp = 1;
788         }
789       else if (len == 1 && datalen > 5 && !memcmp (p, "--=-=", 5))
790         {
791           /* This look pretty much like a our own boundary.
792              We better protect it by forcing QP encoding.  */
793           need_qp = 1;
794         }
795       else if (len == 1 && datalen >= 5 && !memcmp (p, "From ", 5))
796         {
797           /* The usual From hack is required so that MTAs do not
798              prefix it with an '>'.  */
799           need_qp = 1;
800         }
801     }
802   if (len > maxlen)
803     maxlen = len;
804
805   if (maxlen <= 76 && !lowbin && !highbin && !need_qp)
806     return 0; /* Plain ASCII is sufficient.  */
807
808   /* Somewhere in the Outlook documentation 20% is mentioned as
809      discriminating value for Base64.  Though our counting won't be
810      identical we use that value to behave closely to it. */
811   if (ntotal && ((float)(lowbin+highbin))/ntotal < 0.20)
812     return 1; /* Use quoted printable.  */
813
814   return 2;   /* Use base64.  */
815 }
816
817
818 /* Convert an utf8 input string to RFC2047 base64 encoding which
819    is the subset of RFC2047 outlook likes.
820    Return value needs to be freed.
821    */
822 static char *
823 utf8_to_rfc2047b (const char *input)
824 {
825   char *ret,
826        *encoded;
827   int inferred_encoding = 0;
828   if (!input)
829     {
830       return NULL;
831     }
832   inferred_encoding = infer_content_encoding (input, strlen (input));
833   if (!inferred_encoding)
834     {
835       return xstrdup (input);
836     }
837   log_debug ("%s:%s: Encoding attachment filename. With: %s ",
838              SRCNAME, __func__, inferred_encoding == 2 ? "Base64" : "QP");
839
840   if (inferred_encoding == 2)
841     {
842       encoded = b64_encode (input, strlen (input));
843       if (gpgrt_asprintf (&ret, "=?utf-8?B?%s?=", encoded) == -1)
844         {
845           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
846           xfree (encoded);
847           return NULL;
848         }
849     }
850   else
851     {
852       /* There is a Bug here. If you encode 4 Byte UTF-8 outlook can't
853          handle it itself. And sends out a message with ?? inserted in
854          that place. This triggers an invalid signature. */
855       encoded = qp_encode (input, strlen (input), NULL);
856       if (gpgrt_asprintf (&ret, "=?utf-8?Q?%s?=", encoded) == -1)
857         {
858           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
859           xfree (encoded);
860           return NULL;
861         }
862     }
863   xfree (encoded);
864   return ret;
865 }
866
867 /* Write a MIME part to SINK.  First the BOUNDARY is written (unless
868    it is NULL) then the DATA is analyzed and appropriate headers are
869    written.  If FILENAME is given it will be added to the part's
870    header.  IS_MAPIBODY should be passed as true if the data has been
871    retrieved from the body property.  */
872 static int
873 write_part (sink_t sink, const char *data, size_t datalen,
874             const char *boundary, const char *filename, int is_mapibody,
875             const char *content_id = NULL)
876 {
877   int rc;
878   const char *ct;
879   int use_b64, use_qp, is_text;
880   char *encoded_filename;
881
882   if (filename)
883     {
884       /* If there is a filename strip the directory part.  Take care
885          that there might be slashes or backslashes.  */
886       const char *s1 = strrchr (filename, '/');
887       const char *s2 = strrchr (filename, '\\');
888
889       if (!s1)
890         s1 = s2;
891       else if (s1 && s2 && s2 > s1)
892         s1 = s2;
893
894       if (s1)
895         filename = s1;
896       if (*filename && filename[1] == ':')
897         filename += 2;
898       if (!*filename)
899         filename = NULL;
900     }
901
902   log_debug ("Writing part of length %d%s filename=`%s'\n",
903              (int)datalen, is_mapibody? " (body)":"",
904              filename ? anonstr (filename) : "[none]");
905
906   ct = infer_content_type (data, datalen, filename, is_mapibody, &use_b64);
907   use_qp = 0;
908   if (!use_b64)
909     {
910       switch (infer_content_encoding (data, datalen))
911         {
912         case 0: break;
913         case 1: use_qp = 1; break;
914         default: use_b64 = 1; break;
915         }
916     }
917   is_text = !strncmp (ct, "text/", 5);
918
919   if (boundary)
920     if ((rc = write_boundary (sink, boundary, 0)))
921       return rc;
922   if ((rc=write_multistring (sink,
923                              "Content-Type: ", ct,
924                              (is_text || filename? ";\r\n" :"\r\n"),
925                              NULL)))
926     return rc;
927
928   /* OL inserts a charset parameter in many cases, so we do it right
929      away for all text parts.  We can assume us-ascii if no special
930      encoding is required.  */
931   if (is_text)
932     if ((rc=write_multistring (sink,
933                                "\tcharset=\"",
934                                (!use_qp && !use_b64? "us-ascii" : "utf-8"),
935                                filename ? "\";\r\n" : "\"\r\n",
936                                NULL)))
937       return rc;
938
939   encoded_filename = utf8_to_rfc2047b (filename);
940   if (encoded_filename)
941     if ((rc=write_multistring (sink,
942                                "\tname=\"", encoded_filename, "\"\r\n",
943                                NULL)))
944       return rc;
945
946   /* Note that we need to output even 7bit because OL inserts that
947      anyway.  */
948   if ((rc = write_multistring (sink,
949                                "Content-Transfer-Encoding: ",
950                                (use_b64? "base64\r\n":
951                                 use_qp? "quoted-printable\r\n":"7bit\r\n"),
952                                NULL)))
953     return rc;
954
955   if (content_id)
956     {
957       if ((rc=write_multistring (sink,
958                                  "Content-ID: <", content_id, ">\r\n",
959                                  NULL)))
960         return rc;
961     }
962   else if (encoded_filename)
963     if ((rc=write_multistring (sink,
964                                "Content-Disposition: attachment;\r\n"
965                                "\tfilename=\"", encoded_filename, "\"\r\n",
966                                NULL)))
967       return rc;
968
969   xfree(encoded_filename);
970
971   /* Write delimiter.  */
972   if ((rc = write_string (sink, "\r\n")))
973     return rc;
974
975   /* Write the content.  */
976   if (use_b64)
977     rc = write_b64 (sink, data, datalen);
978   else if (use_qp)
979     rc = write_qp (sink, data, datalen);
980   else
981     rc = write_plain (sink, data, datalen);
982
983   return rc;
984 }
985
986
987 /* Return the number of attachments in TABLE to be put into the MIME
988    message.  */
989 int
990 count_usable_attachments (mapi_attach_item_t *table)
991 {
992   int idx, count = 0;
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         count++;
999   return count;
1000 }
1001
1002 /* Write out all attachments from TABLE separated by BOUNDARY to SINK.
1003    This function needs to be syncronized with count_usable_attachments.
1004    If only_related is 1 only include attachments for multipart/related they
1005    are excluded otherwise. */
1006 static int
1007 write_attachments (sink_t sink,
1008                    LPMESSAGE message, mapi_attach_item_t *table,
1009                    const char *boundary, int only_related)
1010 {
1011   int idx, rc;
1012   char *buffer;
1013   size_t buflen;
1014
1015   if (table)
1016     for (idx=0; !table[idx].end_of_table; idx++)
1017       if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
1018           && table[idx].method == ATTACH_BY_VALUE)
1019         {
1020           if (only_related && !table[idx].content_id)
1021             {
1022               continue;
1023             }
1024           else if (!only_related && table[idx].content_id)
1025             {
1026               continue;
1027             }
1028           buffer = mapi_get_attach (message, table+idx, &buflen);
1029           if (!buffer)
1030             log_debug ("Attachment at index %d not found\n", idx);
1031           else
1032             log_debug ("Attachment at index %d: length=%d\n", idx, (int)buflen);
1033           if (!buffer)
1034             return -1;
1035           rc = write_part (sink, buffer, buflen, boundary,
1036                            table[idx].filename, 0, table[idx].content_id);
1037           if (rc)
1038             {
1039               log_error ("Write part returned err: %i", rc);
1040             }
1041           xfree (buffer);
1042         }
1043   return 0;
1044 }
1045
1046 /* Returns 1 if all attachments are related. 2 if there is a
1047    related and a mixed attachment. 0 if there are no other parts*/
1048 static int
1049 is_related (Mail *mail, mapi_attach_item_t *table)
1050 {
1051   if (!mail || !mail->isHTMLAlternative () || !table)
1052     {
1053       return 0;
1054     }
1055
1056   int related = 0;
1057   int mixed = 0;
1058   for (int idx = 0; !table[idx].end_of_table; idx++)
1059     {
1060       if (table[idx].content_id)
1061         {
1062           related = 1;
1063         }
1064       else
1065         {
1066           mixed = 1;
1067         }
1068     }
1069   return mixed + related;
1070 }
1071
1072
1073 /* Delete all attachments from TABLE except for the one we just created */
1074 static int
1075 delete_all_attachments (LPMESSAGE message, mapi_attach_item_t *table)
1076 {
1077   HRESULT hr;
1078   int idx;
1079
1080   if (table)
1081     for (idx=0; !table[idx].end_of_table; idx++)
1082       {
1083         if (table[idx].attach_type == ATTACHTYPE_MOSSTEMPL
1084             && table[idx].filename
1085             && !strcmp (table[idx].filename, MIMEATTACHFILENAME))
1086           continue;
1087         hr = message->DeleteAttach (table[idx].mapipos, 0, NULL, 0);
1088         if (hr)
1089           {
1090             log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
1091                        SRCNAME, __func__, hr);
1092             return -1;
1093           }
1094       }
1095   return 0;
1096 }
1097
1098
1099 /* Commit changes to the attachment ATTACH and release the object.
1100    SINK needs to be passed as well and will also be closed.  Note that
1101    the address of ATTACH is expected so that the fucntion can set it
1102    to NULL. */
1103 int
1104 close_mapi_attachment (LPATTACH *attach, sink_t sink)
1105 {
1106   HRESULT hr;
1107   LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
1108
1109   if (!stream)
1110     {
1111       log_error ("%s:%s: sink not setup", SRCNAME, __func__);
1112       return -1;
1113     }
1114   hr = stream->Commit (0);
1115   if (hr)
1116     {
1117       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
1118                  SRCNAME, __func__, hr);
1119       return -1;
1120     }
1121   gpgol_release (stream);
1122   sink->cb_data = NULL;
1123   hr = (*attach)->SaveChanges (0);
1124   if (hr)
1125     {
1126       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
1127                  SRCNAME, __func__, hr);
1128       return -1;
1129     }
1130   gpgol_release ((*attach));
1131   *attach = NULL;
1132   return 0;
1133 }
1134
1135
1136 /* Cancel changes to the attachment ATTACH and release the object.
1137    SINK needs to be passed as well and will also be closed.  Note that
1138    the address of ATTACH is expected so that the fucntion can set it
1139    to NULL. */
1140 void
1141 cancel_mapi_attachment (LPATTACH *attach, sink_t sink)
1142 {
1143   LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
1144
1145   if (stream)
1146     {
1147       stream->Revert();
1148       gpgol_release (stream);
1149       sink->cb_data = NULL;
1150     }
1151   if (*attach)
1152     {
1153       /* Fixme: Should we try to delete it or is there a Revert method? */
1154       gpgol_release ((*attach));
1155       *attach = NULL;
1156     }
1157 }
1158
1159
1160
1161 /* Do the final processing for a message. */
1162 int
1163 finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
1164                   protocol_t protocol, int encrypt, bool is_inline)
1165 {
1166   HRESULT hr = 0;
1167   SPropValue prop;
1168   SPropTagArray proparray;
1169
1170   /* Set the message class.  */
1171   prop.ulPropTag = PR_MESSAGE_CLASS_A;
1172   if (encrypt)
1173     {
1174       prop.Value.lpszA = xstrdup ("IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned");
1175     }
1176   else
1177     {
1178       prop.Value.lpszA = xstrdup ("IPM.Note.InfoPathForm.GpgOLS.SMIME.MultipartSigned");
1179     }
1180
1181   if (!is_inline)
1182     {
1183       /* For inline we stick with IPM.Note because Exchange Online would
1184          error out if we tried our S/MIME conversion trick with a text
1185          plain message */
1186       hr = message->SetProps(1, &prop, NULL);
1187     }
1188   xfree(prop.Value.lpszA);
1189   if (hr)
1190     {
1191       log_error ("%s:%s: error setting the message class: hr=%#lx\n",
1192                  SRCNAME, __func__, hr);
1193       return -1;
1194     }
1195
1196   /* We also need to set the message class into our custom
1197      property. This override is at least required for encrypted
1198      messages.  */
1199   if (is_inline && mapi_set_gpgol_msg_class (message,
1200                                           (encrypt?
1201                                            (protocol == PROTOCOL_SMIME?
1202                                             "IPM.Note.GpgOL.OpaqueEncrypted" :
1203                                             "IPM.Note.GpgOL.PGPMessage") :
1204                                             "IPM.Note.GpgOL.ClearSigned")))
1205     {
1206       log_error ("%s:%s: error setting gpgol msgclass",
1207                  SRCNAME, __func__);
1208       return -1;
1209     }
1210   if (!is_inline && mapi_set_gpgol_msg_class (message,
1211                                 (encrypt?
1212                                  (protocol == PROTOCOL_SMIME?
1213                                   "IPM.Note.GpgOL.OpaqueEncrypted" :
1214                                   "IPM.Note.GpgOL.MultipartEncrypted") :
1215                                  "IPM.Note.GpgOL.MultipartSigned")))
1216     {
1217       log_error ("%s:%s: error setting gpgol msgclass",
1218                  SRCNAME, __func__);
1219       return -1;
1220     }
1221
1222   proparray.cValues = 1;
1223   proparray.aulPropTag[0] = PR_BODY;
1224   hr = message->DeleteProps (&proparray, NULL);
1225   if (hr)
1226     {
1227       log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
1228                      SRCNAME, __func__);
1229     }
1230
1231   proparray.cValues = 1;
1232   proparray.aulPropTag[0] = PR_BODY_HTML;
1233   hr = message->DeleteProps (&proparray, NULL);
1234   if (hr)
1235     {
1236       log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
1237                      SRCNAME, __func__);
1238     }
1239
1240   /* Now delete all parts of the MAPI message except for the one
1241      attachment we just created.  */
1242   if (delete_all_attachments (message, att_table))
1243     {
1244       log_error ("%s:%s: error deleting attachments",
1245                  SRCNAME, __func__);
1246       return -1;
1247     }
1248
1249   /* Remove the draft info so that we don't leak the information on
1250      whether the message has been signed etc.  */
1251   mapi_set_gpgol_draft_info (message, NULL);
1252
1253   if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
1254     {
1255       log_error ("%s:%s: error saving changes.",
1256                  SRCNAME, __func__);
1257       return -1;
1258     }
1259   return 0;
1260 }
1261
1262
1263 /* Helper to create the signing header.  This includes enough space
1264    for later fixup of the micalg parameter.  The MIME version is only
1265    written if FIRST is set.  */
1266 void
1267 create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
1268                            int first, const char *boundary, const char *micalg)
1269 {
1270   snprintf (buffer, buflen,
1271             "%s"
1272             "Content-Type: multipart/signed;\r\n"
1273             "\tprotocol=\"application/%s\";\r\n"
1274             "\tmicalg=%-15.15s;\r\n"
1275             "\tboundary=\"%s\"\r\n"
1276             "\r\n",
1277             first? "MIME-Version: 1.0\r\n":"",
1278             (protocol==PROTOCOL_OPENPGP? "pgp-signature":"pkcs7-signature"),
1279             micalg, boundary);
1280 }
1281
1282 /* Add the body, either as multipart/alternative or just as the
1283   simple body part. Depending on the format set in outlook. To
1284   avoid memory duplication it takes the plain body as parameter.
1285
1286   Boundary is the potential outer boundary of a multipart/mixed
1287   mail. If it is null we assume the multipart/alternative is
1288   the only part.
1289
1290   return is zero on success.
1291 */
1292 static int
1293 add_body (Mail *mail, const char *boundary, sink_t sink,
1294           const char *plain_body)
1295 {
1296   if (!plain_body)
1297     {
1298       return 0;
1299     }
1300   bool is_alternative = false;
1301   if (mail)
1302     {
1303       is_alternative = mail->isHTMLAlternative ();
1304     }
1305
1306   int rc = 0;
1307   if (!is_alternative || !plain_body)
1308     {
1309       if (plain_body)
1310         {
1311           rc = write_part (sink, plain_body, strlen (plain_body),
1312                            boundary, NULL, 1);
1313         }
1314       /* Just the plain body or no body. We are done. */
1315       return rc;
1316     }
1317
1318   /* Add a new multipart / mixed element. */
1319   if (boundary && write_boundary (sink, boundary, 0))
1320     {
1321       TRACEPOINT;
1322       return 1;
1323     }
1324
1325   /* Now for the multipart/alternative part. We never do HTML only. */
1326   char alt_boundary [BOUNDARYSIZE+1];
1327   generate_boundary (alt_boundary);
1328
1329   if ((rc=write_multistring (sink,
1330                             "Content-Type: multipart/alternative;\r\n",
1331                             "\tboundary=\"", alt_boundary, "\"\r\n",
1332                             "\r\n",  /* <-- extra line */
1333                             NULL)))
1334     {
1335       TRACEPOINT;
1336       return rc;
1337     }
1338
1339   /* Now the plain body part */
1340   if ((rc = write_part (sink, plain_body, strlen (plain_body),
1341                        alt_boundary, NULL, 1)))
1342     {
1343       TRACEPOINT;
1344       return rc;
1345     }
1346
1347   /* Now the html body. It is somehow not accessible through PR_HTML,
1348      OutlookSpy also shows MAPI Unsupported (but shows the data) strange.
1349      We just cache it. Memory is cheap :-) */
1350   char *html_body = mail->takeCachedHTMLBody ();
1351   if (!html_body)
1352     {
1353       log_error ("%s:%s: BUG: Body but no html body in alternative mail?",
1354                  SRCNAME, __func__);
1355       return -1;
1356     }
1357
1358   rc = write_part (sink, html_body, strlen (html_body),
1359                    alt_boundary, NULL, 2);
1360   xfree (html_body);
1361   if (rc)
1362     {
1363       TRACEPOINT;
1364       return rc;
1365     }
1366   /* Finish our multipart */
1367   return write_boundary (sink, alt_boundary, 1);
1368 }
1369
1370 /* Add the body and attachments. Does multipart handling. */
1371 int
1372 add_body_and_attachments (sink_t sink, LPMESSAGE message,
1373                           mapi_attach_item_t *att_table, Mail *mail,
1374                           const char *body, int n_att_usable)
1375 {
1376   int related = is_related (mail, att_table);
1377   int rc = 0;
1378   char inner_boundary[BOUNDARYSIZE+1];
1379   char outer_boundary[BOUNDARYSIZE+1];
1380   *outer_boundary = 0;
1381   *inner_boundary = 0;
1382
1383   if (((body && n_att_usable) || n_att_usable > 1) && related == 1)
1384     {
1385       /* A body and at least one attachment or more than one attachment  */
1386       generate_boundary (outer_boundary);
1387       if ((rc=write_multistring (sink,
1388                                  "Content-Type: multipart/related;\r\n",
1389                                  "\tboundary=\"", outer_boundary, "\"\r\n",
1390                                  "\r\n", /* <--- Outlook adds an extra line. */
1391                                  NULL)))
1392         return rc;
1393     }
1394   else if ((body && n_att_usable) || n_att_usable > 1)
1395     {
1396       generate_boundary (outer_boundary);
1397       if ((rc=write_multistring (sink,
1398                                  "Content-Type: multipart/mixed;\r\n",
1399                                  "\tboundary=\"", outer_boundary, "\"\r\n",
1400                                  "\r\n", /* <--- Outlook adds an extra line. */
1401                                  NULL)))
1402         return rc;
1403     }
1404
1405   /* Only one part.  */
1406   if (*outer_boundary && related == 2)
1407     {
1408       /* We have attachments that are related to the body and unrelated
1409          attachments. So we need another part. */
1410       if ((rc=write_boundary (sink, outer_boundary, 0)))
1411         {
1412           return rc;
1413         }
1414       generate_boundary (inner_boundary);
1415       if ((rc=write_multistring (sink,
1416                                  "Content-Type: multipart/related;\r\n",
1417                                  "\tboundary=\"", inner_boundary, "\"\r\n",
1418                                  "\r\n", /* <--- Outlook adds an extra line. */
1419                                  NULL)))
1420         {
1421           return rc;
1422         }
1423     }
1424
1425
1426   if ((rc=add_body (mail, *inner_boundary ? inner_boundary :
1427                           *outer_boundary ? outer_boundary : NULL,
1428                     sink, body)))
1429     {
1430       log_error ("%s:%s: Adding the body failed.",
1431                  SRCNAME, __func__);
1432       return rc;
1433     }
1434   if (!rc && n_att_usable && related)
1435     {
1436       /* Write the related attachments. */
1437       rc = write_attachments (sink, message, att_table,
1438                               *inner_boundary? inner_boundary :
1439                               *outer_boundary? outer_boundary : NULL, 1);
1440       if (rc)
1441         {
1442           return rc;
1443         }
1444       /* Close the related part if neccessary.*/
1445       if (*inner_boundary && (rc=write_boundary (sink, inner_boundary, 1)))
1446         {
1447           return rc;
1448         }
1449     }
1450
1451   /* Now write the other attachments */
1452   if (!rc && n_att_usable)
1453     rc = write_attachments (sink, message, att_table,
1454                             *outer_boundary? outer_boundary : NULL, 0);
1455
1456   /* Finish the possible multipart/mixed. */
1457   if (*outer_boundary && (rc = write_boundary (sink, outer_boundary, 1)))
1458     return rc;
1459
1460   return rc;
1461 }
1462
1463
1464 /* Helper from mime_encrypt.  BOUNDARY is a buffer of at least
1465    BOUNDARYSIZE+1 bytes which will be set on return from that
1466    function.  */
1467 int
1468 create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
1469                               bool is_inline, int exchange_major_version)
1470 {
1471   int rc;
1472
1473   if (is_inline)
1474     {
1475       *boundary = 0;
1476       rc = 0;
1477       /* This would be nice and worked for Google Sync but it failed
1478          for Microsoft Exchange Online *sigh* so we put the body
1479          instead into the oom body property and stick with IPM Note.
1480       rc = write_multistring (sink,
1481                               "MIME-Version: 1.0\r\n"
1482                               "Content-Type: text/plain;\r\n"
1483                               "\tcharset=\"iso-8859-1\"\r\n"
1484                               "Content-Transfer-Encoding: 7BIT\r\n"
1485                               "\r\n",
1486                               NULL);
1487      */
1488     }
1489   else if (protocol == PROTOCOL_SMIME)
1490     {
1491       *boundary = 0;
1492       if (exchange_major_version >= 15)
1493         {
1494           /*
1495              For S/MIME encrypted mails we do not use the S/MIME conversion
1496              code anymore. With Exchange 2016 this no longer works. Instead
1497              we set an override mime tag, the extended headers in OOM in
1498              Mail::update_crypt_oom and let outlook convert the attachment
1499              to base64.
1500
1501              A bit more details can be found in T3853 / T3884
1502              */
1503           rc = 0;
1504         }
1505       else
1506         {
1507           rc = write_multistring (sink,
1508                                   "Content-Type: application/pkcs7-mime; "
1509                                   "smime-type=enveloped-data;\r\n"
1510                                   "\tname=\"smime.p7m\"\r\n"
1511                                   "Content-Disposition: attachment; filename=\"smime.p7m\"\r\n"
1512                                   "Content-Transfer-Encoding: base64\r\n"
1513                                   "MIME-Version: 1.0\r\n"
1514                                   "\r\n",
1515                                   NULL);
1516         }
1517     }
1518   else
1519     {
1520       generate_boundary (boundary);
1521       rc = write_multistring (sink,
1522                               "MIME-Version: 1.0\r\n"
1523                               "Content-Type: multipart/encrypted;\r\n"
1524                               "\tprotocol=\"application/pgp-encrypted\";\r\n",
1525                               "\tboundary=\"", boundary, "\"\r\n",
1526                               NULL);
1527       if (rc)
1528         return rc;
1529
1530       /* Write the PGP/MIME encrypted part.  */
1531       rc = write_boundary (sink, boundary, 0);
1532       if (rc)
1533         return rc;
1534       rc = write_multistring (sink,
1535                               "Content-Type: application/pgp-encrypted\r\n"
1536                               "\r\n"
1537                               "Version: 1\r\n", NULL);
1538       if (rc)
1539         return rc;
1540
1541       /* And start the second part.  */
1542       rc = write_boundary (sink, boundary, 0);
1543       if (rc)
1544         return rc;
1545       rc = write_multistring (sink,
1546                               "Content-Type: application/octet-stream\r\n"
1547                               "\r\n", NULL);
1548      }
1549
1550   return rc;
1551 }
1552
1553
1554 int
1555 restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
1556                        msgtype_t type, char *msgcls)
1557 {
1558   struct sink_s sinkmem;
1559   sink_t sink = &sinkmem;
1560   char *orig = NULL;
1561   int err = -1;
1562   char boundary[BOUNDARYSIZE+1];
1563
1564   (void)msgcls;
1565
1566   LPATTACH new_attach = create_mapi_attachment (message,
1567                                                 sink);
1568   log_debug ("Restore message from moss called.");
1569   if (!new_attach)
1570     {
1571       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1572       goto done;
1573     }
1574   // TODO MORE
1575   if (type == MSGTYPE_SMIME)
1576     {
1577       create_top_encryption_header (sink, PROTOCOL_SMIME, boundary);
1578     }
1579   else if (type == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED)
1580     {
1581       create_top_encryption_header (sink, PROTOCOL_OPENPGP, boundary);
1582     }
1583   else
1584     {
1585       log_error ("%s:%s: Unsupported messagetype: %i",
1586                  SRCNAME, __func__, type);
1587       goto done;
1588     }
1589
1590   orig = get_pa_string (moss_att, PR_ATTACH_DATA_BIN_DASL);
1591
1592   if (!orig)
1593     {
1594       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1595       goto done;
1596     }
1597
1598   if (write_string (sink, orig))
1599     {
1600       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1601       goto done;
1602     }
1603
1604   if (*boundary && write_boundary (sink, boundary, 1))
1605     {
1606       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1607       goto done;
1608     }
1609
1610   if (close_mapi_attachment (&new_attach, sink))
1611     {
1612       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1613       goto done;
1614     }
1615
1616   err = 0;
1617 done:
1618   xfree (orig);
1619   return err;
1620 }