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