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