Pseudonym some more without DBG_DATA
[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   /* Set a special property so that we are later able to identify
1197      messages signed or encrypted by us.  */
1198   if (mapi_set_sig_status (message, "@"))
1199     {
1200       log_error ("%s:%s: error setting sigstatus",
1201                  SRCNAME, __func__);
1202       return -1;
1203     }
1204
1205   /* We also need to set the message class into our custom
1206      property. This override is at least required for encrypted
1207      messages.  */
1208   if (is_inline && mapi_set_gpgol_msg_class (message,
1209                                           (encrypt?
1210                                            (protocol == PROTOCOL_SMIME?
1211                                             "IPM.Note.GpgOL.OpaqueEncrypted" :
1212                                             "IPM.Note.GpgOL.PGPMessage") :
1213                                             "IPM.Note.GpgOL.ClearSigned")))
1214     {
1215       log_error ("%s:%s: error setting gpgol msgclass",
1216                  SRCNAME, __func__);
1217       return -1;
1218     }
1219   if (!is_inline && mapi_set_gpgol_msg_class (message,
1220                                 (encrypt?
1221                                  (protocol == PROTOCOL_SMIME?
1222                                   "IPM.Note.GpgOL.OpaqueEncrypted" :
1223                                   "IPM.Note.GpgOL.MultipartEncrypted") :
1224                                  "IPM.Note.GpgOL.MultipartSigned")))
1225     {
1226       log_error ("%s:%s: error setting gpgol msgclass",
1227                  SRCNAME, __func__);
1228       return -1;
1229     }
1230
1231   proparray.cValues = 1;
1232   proparray.aulPropTag[0] = PR_BODY;
1233   hr = message->DeleteProps (&proparray, NULL);
1234   if (hr)
1235     {
1236       log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
1237                      SRCNAME, __func__);
1238     }
1239
1240   proparray.cValues = 1;
1241   proparray.aulPropTag[0] = PR_BODY_HTML;
1242   hr = message->DeleteProps (&proparray, NULL);
1243   if (hr)
1244     {
1245       log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
1246                      SRCNAME, __func__);
1247     }
1248
1249   /* Now delete all parts of the MAPI message except for the one
1250      attachment we just created.  */
1251   if (delete_all_attachments (message, att_table))
1252     {
1253       log_error ("%s:%s: error deleting attachments",
1254                  SRCNAME, __func__);
1255       return -1;
1256     }
1257
1258   /* Remove the draft info so that we don't leak the information on
1259      whether the message has been signed etc.  */
1260   mapi_set_gpgol_draft_info (message, NULL);
1261
1262   if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
1263     {
1264       log_error ("%s:%s: error saving changes.",
1265                  SRCNAME, __func__);
1266       return -1;
1267     }
1268   return 0;
1269 }
1270
1271
1272 /* Helper to create the signing header.  This includes enough space
1273    for later fixup of the micalg parameter.  The MIME version is only
1274    written if FIRST is set.  */
1275 void
1276 create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
1277                            int first, const char *boundary, const char *micalg)
1278 {
1279   snprintf (buffer, buflen,
1280             "%s"
1281             "Content-Type: multipart/signed;\r\n"
1282             "\tprotocol=\"application/%s\";\r\n"
1283             "\tmicalg=%-15.15s;\r\n"
1284             "\tboundary=\"%s\"\r\n"
1285             "\r\n",
1286             first? "MIME-Version: 1.0\r\n":"",
1287             (protocol==PROTOCOL_OPENPGP? "pgp-signature":"pkcs7-signature"),
1288             micalg, boundary);
1289 }
1290
1291 /* Add the body, either as multipart/alternative or just as the
1292   simple body part. Depending on the format set in outlook. To
1293   avoid memory duplication it takes the plain body as parameter.
1294
1295   Boundary is the potential outer boundary of a multipart/mixed
1296   mail. If it is null we assume the multipart/alternative is
1297   the only part.
1298
1299   return is zero on success.
1300 */
1301 static int
1302 add_body (Mail *mail, const char *boundary, sink_t sink,
1303           const char *plain_body)
1304 {
1305   if (!plain_body)
1306     {
1307       return 0;
1308     }
1309   bool is_alternative = false;
1310   if (mail)
1311     {
1312       is_alternative = mail->isHTMLAlternative ();
1313     }
1314
1315   int rc = 0;
1316   if (!is_alternative || !plain_body)
1317     {
1318       if (plain_body)
1319         {
1320           rc = write_part (sink, plain_body, strlen (plain_body),
1321                            boundary, NULL, 1);
1322         }
1323       /* Just the plain body or no body. We are done. */
1324       return rc;
1325     }
1326
1327   /* Add a new multipart / mixed element. */
1328   if (boundary && write_boundary (sink, boundary, 0))
1329     {
1330       TRACEPOINT;
1331       return 1;
1332     }
1333
1334   /* Now for the multipart/alternative part. We never do HTML only. */
1335   char alt_boundary [BOUNDARYSIZE+1];
1336   generate_boundary (alt_boundary);
1337
1338   if ((rc=write_multistring (sink,
1339                             "Content-Type: multipart/alternative;\r\n",
1340                             "\tboundary=\"", alt_boundary, "\"\r\n",
1341                             "\r\n",  /* <-- extra line */
1342                             NULL)))
1343     {
1344       TRACEPOINT;
1345       return rc;
1346     }
1347
1348   /* Now the plain body part */
1349   if ((rc = write_part (sink, plain_body, strlen (plain_body),
1350                        alt_boundary, NULL, 1)))
1351     {
1352       TRACEPOINT;
1353       return rc;
1354     }
1355
1356   /* Now the html body. It is somehow not accessible through PR_HTML,
1357      OutlookSpy also shows MAPI Unsupported (but shows the data) strange.
1358      We just cache it. Memory is cheap :-) */
1359   char *html_body = mail->takeCachedHTMLBody ();
1360   if (!html_body)
1361     {
1362       log_error ("%s:%s: BUG: Body but no html body in alternative mail?",
1363                  SRCNAME, __func__);
1364       return -1;
1365     }
1366
1367   rc = write_part (sink, html_body, strlen (html_body),
1368                    alt_boundary, NULL, 2);
1369   xfree (html_body);
1370   if (rc)
1371     {
1372       TRACEPOINT;
1373       return rc;
1374     }
1375   /* Finish our multipart */
1376   return write_boundary (sink, alt_boundary, 1);
1377 }
1378
1379 /* Add the body and attachments. Does multipart handling. */
1380 int
1381 add_body_and_attachments (sink_t sink, LPMESSAGE message,
1382                           mapi_attach_item_t *att_table, Mail *mail,
1383                           const char *body, int n_att_usable)
1384 {
1385   int related = is_related (mail, att_table);
1386   int rc = 0;
1387   char inner_boundary[BOUNDARYSIZE+1];
1388   char outer_boundary[BOUNDARYSIZE+1];
1389   *outer_boundary = 0;
1390   *inner_boundary = 0;
1391
1392   if (((body && n_att_usable) || n_att_usable > 1) && related == 1)
1393     {
1394       /* A body and at least one attachment or more than one attachment  */
1395       generate_boundary (outer_boundary);
1396       if ((rc=write_multistring (sink,
1397                                  "Content-Type: multipart/related;\r\n",
1398                                  "\tboundary=\"", outer_boundary, "\"\r\n",
1399                                  "\r\n", /* <--- Outlook adds an extra line. */
1400                                  NULL)))
1401         return rc;
1402     }
1403   else if ((body && n_att_usable) || n_att_usable > 1)
1404     {
1405       generate_boundary (outer_boundary);
1406       if ((rc=write_multistring (sink,
1407                                  "Content-Type: multipart/mixed;\r\n",
1408                                  "\tboundary=\"", outer_boundary, "\"\r\n",
1409                                  "\r\n", /* <--- Outlook adds an extra line. */
1410                                  NULL)))
1411         return rc;
1412     }
1413
1414   /* Only one part.  */
1415   if (*outer_boundary && related == 2)
1416     {
1417       /* We have attachments that are related to the body and unrelated
1418          attachments. So we need another part. */
1419       if ((rc=write_boundary (sink, outer_boundary, 0)))
1420         {
1421           return rc;
1422         }
1423       generate_boundary (inner_boundary);
1424       if ((rc=write_multistring (sink,
1425                                  "Content-Type: multipart/related;\r\n",
1426                                  "\tboundary=\"", inner_boundary, "\"\r\n",
1427                                  "\r\n", /* <--- Outlook adds an extra line. */
1428                                  NULL)))
1429         {
1430           return rc;
1431         }
1432     }
1433
1434
1435   if ((rc=add_body (mail, *inner_boundary ? inner_boundary :
1436                           *outer_boundary ? outer_boundary : NULL,
1437                     sink, body)))
1438     {
1439       log_error ("%s:%s: Adding the body failed.",
1440                  SRCNAME, __func__);
1441       return rc;
1442     }
1443   if (!rc && n_att_usable && related)
1444     {
1445       /* Write the related attachments. */
1446       rc = write_attachments (sink, message, att_table,
1447                               *inner_boundary? inner_boundary :
1448                               *outer_boundary? outer_boundary : NULL, 1);
1449       if (rc)
1450         {
1451           return rc;
1452         }
1453       /* Close the related part if neccessary.*/
1454       if (*inner_boundary && (rc=write_boundary (sink, inner_boundary, 1)))
1455         {
1456           return rc;
1457         }
1458     }
1459
1460   /* Now write the other attachments */
1461   if (!rc && n_att_usable)
1462     rc = write_attachments (sink, message, att_table,
1463                             *outer_boundary? outer_boundary : NULL, 0);
1464
1465   /* Finish the possible multipart/mixed. */
1466   if (*outer_boundary && (rc = write_boundary (sink, outer_boundary, 1)))
1467     return rc;
1468
1469   return rc;
1470 }
1471
1472
1473 /* Helper from mime_encrypt.  BOUNDARY is a buffer of at least
1474    BOUNDARYSIZE+1 bytes which will be set on return from that
1475    function.  */
1476 int
1477 create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
1478                               bool is_inline, int exchange_major_version)
1479 {
1480   int rc;
1481
1482   if (is_inline)
1483     {
1484       *boundary = 0;
1485       rc = 0;
1486       /* This would be nice and worked for Google Sync but it failed
1487          for Microsoft Exchange Online *sigh* so we put the body
1488          instead into the oom body property and stick with IPM Note.
1489       rc = write_multistring (sink,
1490                               "MIME-Version: 1.0\r\n"
1491                               "Content-Type: text/plain;\r\n"
1492                               "\tcharset=\"iso-8859-1\"\r\n"
1493                               "Content-Transfer-Encoding: 7BIT\r\n"
1494                               "\r\n",
1495                               NULL);
1496      */
1497     }
1498   else if (protocol == PROTOCOL_SMIME)
1499     {
1500       *boundary = 0;
1501       if (exchange_major_version >= 15)
1502         {
1503           /*
1504              For S/MIME encrypted mails we do not use the S/MIME conversion
1505              code anymore. With Exchange 2016 this no longer works. Instead
1506              we set an override mime tag, the extended headers in OOM in
1507              Mail::update_crypt_oom and let outlook convert the attachment
1508              to base64.
1509
1510              A bit more details can be found in T3853 / T3884
1511              */
1512           rc = 0;
1513         }
1514       else
1515         {
1516           rc = write_multistring (sink,
1517                                   "Content-Type: application/pkcs7-mime; "
1518                                   "smime-type=enveloped-data;\r\n"
1519                                   "\tname=\"smime.p7m\"\r\n"
1520                                   "Content-Disposition: attachment; filename=\"smime.p7m\"\r\n"
1521                                   "Content-Transfer-Encoding: base64\r\n"
1522                                   "MIME-Version: 1.0\r\n"
1523                                   "\r\n",
1524                                   NULL);
1525         }
1526     }
1527   else
1528     {
1529       generate_boundary (boundary);
1530       rc = write_multistring (sink,
1531                               "MIME-Version: 1.0\r\n"
1532                               "Content-Type: multipart/encrypted;\r\n"
1533                               "\tprotocol=\"application/pgp-encrypted\";\r\n",
1534                               "\tboundary=\"", boundary, "\"\r\n",
1535                               NULL);
1536       if (rc)
1537         return rc;
1538
1539       /* Write the PGP/MIME encrypted part.  */
1540       rc = write_boundary (sink, boundary, 0);
1541       if (rc)
1542         return rc;
1543       rc = write_multistring (sink,
1544                               "Content-Type: application/pgp-encrypted\r\n"
1545                               "\r\n"
1546                               "Version: 1\r\n", NULL);
1547       if (rc)
1548         return rc;
1549
1550       /* And start the second part.  */
1551       rc = write_boundary (sink, boundary, 0);
1552       if (rc)
1553         return rc;
1554       rc = write_multistring (sink,
1555                               "Content-Type: application/octet-stream\r\n"
1556                               "\r\n", NULL);
1557      }
1558
1559   return rc;
1560 }
1561
1562
1563 int
1564 restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
1565                        msgtype_t type, char *msgcls)
1566 {
1567   struct sink_s sinkmem;
1568   sink_t sink = &sinkmem;
1569   char *orig = NULL;
1570   int err = -1;
1571   char boundary[BOUNDARYSIZE+1];
1572
1573   (void)msgcls;
1574
1575   LPATTACH new_attach = create_mapi_attachment (message,
1576                                                 sink);
1577   log_debug ("Restore message from moss called.");
1578   if (!new_attach)
1579     {
1580       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1581       goto done;
1582     }
1583   // TODO MORE
1584   if (type == MSGTYPE_SMIME)
1585     {
1586       create_top_encryption_header (sink, PROTOCOL_SMIME, boundary);
1587     }
1588   else if (type == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED)
1589     {
1590       create_top_encryption_header (sink, PROTOCOL_OPENPGP, boundary);
1591     }
1592   else
1593     {
1594       log_error ("%s:%s: Unsupported messagetype: %i",
1595                  SRCNAME, __func__, type);
1596       goto done;
1597     }
1598
1599   orig = get_pa_string (moss_att, PR_ATTACH_DATA_BIN_DASL);
1600
1601   if (!orig)
1602     {
1603       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1604       goto done;
1605     }
1606
1607   if (write_string (sink, orig))
1608     {
1609       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1610       goto done;
1611     }
1612
1613   if (*boundary && write_boundary (sink, boundary, 1))
1614     {
1615       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1616       goto done;
1617     }
1618
1619   if (close_mapi_attachment (&new_attach, sink))
1620     {
1621       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1622       goto done;
1623     }
1624
1625   /* Set a special property so that we are later able to identify
1626      messages signed or encrypted by us.  */
1627   if (mapi_set_sig_status (message, "@"))
1628     {
1629       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1630       goto done;
1631     }
1632
1633   err = 0;
1634 done:
1635   xfree (orig);
1636   return err;
1637 }