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