various fixes and simplifications wrt openssl.h
[apps/madmutt.git] / sendlib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-lib/lib-lib.h>
11
12 #include <signal.h>
13
14 #ifdef USE_LIBESMTP
15 #ifdef HAVE_GNUTLS_OPENSSL_H
16 #define SSL_SESSION_ASN1_VERSION
17 #include <gnutls/openssl.h>
18 #endif
19 #include <auth-client.h>
20 #include <libesmtp.h>
21 #endif
22
23 #include <lib-lua/lib-lua.h>
24 #include <lib-sys/exit.h>
25 #include <lib-sys/mutt_signal.h>
26 #include <lib-mime/mime.h>
27 #include <lib-ui/enter.h>
28 #include <lib-ui/curses.h>
29 #include <lib-mx/mx.h>
30
31 #include "mutt.h"
32 #include "handler.h"
33 #include "crypt.h"
34 #include "recvattach.h"
35 #include "copy.h"
36 #include "pager.h"
37 #include "charset.h"
38 #include "mutt_idna.h"
39
40 #ifdef USE_NNTP
41 #include <nntp/nntp.h>
42 #endif
43
44 #ifdef HAVE_SYSEXITS_H
45 #include <sysexits.h>
46 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
47 #define EX_OK 0
48 #endif
49
50 #ifdef USE_LIBESMTP
51 static char authpass[STRING] = "";
52
53 #define FAIL() \
54   do { \
55     ret = -1; \
56     goto Done; \
57   } while (0)
58 #define MSGFAIL(msg) \
59   do { \
60     mutt_error("%s", msg); \
61     FAIL(); \
62   } while (0)
63 #define LIBCFAIL(msg) \
64   do { \
65     mutt_error("%s: %s", msg, strerror(errno)); \
66     FAIL(); \
67   } while (0)
68 #define SMTPFAIL(msg) \
69   do { \
70     _send_smtp_perror(msg); \
71     FAIL(); \
72   } while (0)
73 #define extna(msg) { mutt_error (_("SMTP Extension '%s' not supported by MTA."), \
74                                  msg); sleep (1); }
75
76 /*
77  * _send_smtp_ensure_init
78  *   Make sure the libESMTP support in mutt is initialized at some time.
79  */
80 static void _send_smtp_ensure_init ()
81 {
82   static int libesmtp_init = 0;
83
84   if (!libesmtp_init) {
85     if (SmtpAuthUser)
86       auth_client_init ();
87     libesmtp_init = 1;
88   }
89 }
90
91 /*
92  * _send_smtp_perror
93  *   Prints 'msg', a colon, and then a string representation of the
94  *   libesmtp errno as a mutt error.
95  */
96 static void _send_smtp_perror (const char *msg)
97 {
98   char buf[512];
99
100   mutt_error ("%s: %s", msg,
101               smtp_strerror (smtp_errno (), buf, sizeof (buf)));
102 }
103
104 /*
105  * _send_smtp_add_recipients
106  *   Adds every address in 'addr' as a recipient to the smtp message
107  *   'message'.  Note that this does not mean that they will necessarily
108  *   show up in the mail headers (e.g., when bcc'ing).  Returns 0 upon
109  *   success, -1 upon failure (and prints an error message).
110  *
111  *   Very similar to sendlib.c::add_args
112  */
113 static int
114 _send_smtp_add_recipients (smtp_message_t message, address_t * addr)
115 {
116   int ret = 0;
117
118   for (; addr; addr = addr->next) {
119     /* weed out group mailboxes, since those are for display only */
120     if (addr->mailbox && !addr->group) {
121       if (!smtp_add_recipient (message, addr->mailbox))
122         SMTPFAIL ("smtp_add_recipient");
123     }
124   }
125
126 Done:
127   return ret;
128 }
129
130 static int
131 _send_smtp_auth_interact (auth_client_request_t request,
132                               char **result, int fields,
133                               void *arg __attribute__ ((unused)))
134 {
135   int i;
136
137   for (i = 0; i < fields; i++) {
138     if (request[i].flags & AUTH_USER) {
139       result[i] = SmtpAuthUser;
140     } else if (request[i].flags & AUTH_PASS) {
141       if (SmtpAuthPass) {
142         result[i] = SmtpAuthPass;
143       } else {
144         if (authpass[0] == '\0') {
145           char prompt[STRING];
146
147           snprintf(prompt, sizeof(prompt), "%s%s: ", request[i].prompt,
148                    request[i].flags & AUTH_CLEARTEXT ? " (not encrypted)" :
149                    "");
150           mutt_get_field_unbuffered(prompt, authpass, sizeof(authpass),
151                                     M_PASS);
152         }
153         result[i] = authpass;
154       }
155     }
156   }
157
158   return 1;
159 }
160
161 #define BUFLEN 8192
162
163 static const char *
164 _send_smtp_messagefp_cb(void **buf, int *len, void *arg)
165 {
166   int octets;
167
168   if (*buf == NULL)
169     *buf = xmalloc(BUFLEN);
170
171   if (len == NULL) {
172     rewind ((FILE *) arg);
173     return NULL;
174   }
175
176   if (fgets (*buf, BUFLEN - 2, (FILE *) arg) == NULL) {
177     octets = 0;
178   }
179   else {
180     char *p = strchr (*buf, '\0');
181
182     if (p[-1] == '\n' && p[-2] != '\r') {
183       m_strcpy(p - 1, (char *) *buf + BUFLEN - p + 1, "\r\n");
184       p++;
185     }
186     octets = p - (char *) *buf;
187   }
188
189   *len = octets;
190   return *buf;
191 }
192
193 static void event_cb (smtp_session_t session __attribute__ ((unused)),
194                       int event_no, void *arg,...)
195
196   va_list alist;
197   int *ok;
198
199   va_start(alist, arg);
200   switch(event_no) {
201   case SMTP_EV_CONNECT:
202   case SMTP_EV_MAILSTATUS:
203   case SMTP_EV_RCPTSTATUS:
204   case SMTP_EV_MESSAGEDATA:
205   case SMTP_EV_MESSAGESENT:
206   case SMTP_EV_DISCONNECT: break;
207   case SMTP_EV_WEAK_CIPHER: {
208     int bits;
209     bits = va_arg(alist, long); ok = va_arg(alist, int*);
210     mutt_message (_("SMTP_EV_WEAK_CIPHER, bits=%d - accepted."), bits);
211     sleep(1);
212     *ok = 1; break;
213   } 
214   case SMTP_EV_STARTTLS_OK:
215     mutt_message (_("Using TLS"));
216     sleep(1);
217     break;
218   case SMTP_EV_INVALID_PEER_CERTIFICATE: {
219     long vfy_result;
220     vfy_result = va_arg(alist, long); ok = va_arg(alist, int*);
221     mutt_error (_("Error verifying certificate. Error Code: %lu"), vfy_result);
222     sleep(2);
223     *ok = 1;
224     break;
225   } 
226   case SMTP_EV_NO_PEER_CERTIFICATE: {
227     ok = va_arg(alist, int*); 
228     mutt_message (_("SMTP_EV_NO_PEER_CERTIFICATE - accepted."));
229     sleep(1);
230     *ok = 1; break;
231   }
232   case SMTP_EV_WRONG_PEER_CERTIFICATE: {
233     ok = va_arg(alist, int*);
234     mutt_message (_("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted."));
235     sleep(1);
236     *ok = 1; break;
237   }
238   case SMTP_EV_NO_CLIENT_CERTIFICATE: {
239     ok = va_arg(alist, int*);
240     mutt_message (_("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted."));
241     sleep(1);
242     *ok = 1; break;
243   }
244   case SMTP_EV_EXTNA_DSN:
245     extna ("DSN");
246     break;
247   case SMTP_EV_EXTNA_STARTTLS:
248     extna ("StartTLS");
249     break;
250   case SMTP_EV_EXTNA_8BITMIME:
251     extna ("8BITMIME");
252     break;
253   default:
254     mutt_message(_("Got unhandled event ID = %d - ignored."), event_no);
255     sleep(1);
256   }
257   va_end(alist);
258 }
259
260 static void do_dsn_notify (smtp_message_t message, const char* from) {
261   int flags = Notify_NOTSET;
262   smtp_recipient_t self = NULL;
263
264   if (m_strisempty(MTransport.dsn_notify) || !message || m_strisempty(from) || 
265       strstr (MTransport.dsn_notify, "never") != NULL)
266     return;
267
268   if (strstr (MTransport.dsn_notify, "failure") != NULL)
269     flags |= Notify_FAILURE;
270   if (strstr (MTransport.dsn_notify, "delay") != NULL)
271     flags |= Notify_DELAY;
272   if (strstr (MTransport.dsn_notify, "success") != NULL)
273     flags |= Notify_SUCCESS;
274
275   if (flags != Notify_NOTSET) {
276     if (!(self = smtp_add_recipient (message, from)))
277       return;
278     smtp_dsn_set_notify (self, flags);
279   }
280 }
281
282 static void do_dsn_ret (smtp_message_t message) {
283   if (m_strisempty(MTransport.dsn_return) || !message)
284     return;
285   if (ascii_strncasecmp (MTransport.dsn_return, "hdrs", 4) == 0)
286     smtp_dsn_set_ret (message, Ret_HDRS);
287   else if (ascii_strncasecmp (MTransport.dsn_return, "full", 4) == 0)
288     smtp_dsn_set_ret (message, Ret_FULL);
289 }
290
291 int send_smtp_check_usetls (const char* option, unsigned long p,
292                                 char* errbuf, ssize_t errlen) {
293   char* val = (char*) p;
294   if (m_strisempty(val))
295     return (1);
296   if (m_strncmp(val, "enabled", 7) != 0 &&
297       m_strncmp(val, "required", 8) != 0) {
298     if (errbuf)
299       snprintf (errbuf, errlen, _("'%s' is invalid for %s"), val, option);
300     return (0);
301   }
302   return (1);
303 }
304
305 /*
306  * send_smtp_invoke
307  *   Sends a mail message to the provided recipients using libesmtp.
308  *   Returns 0 upon success, -1 upon failure (and prints an error
309  *   message).
310  */
311 static int
312 send_smtp_invoke(address_t *from, address_t *to, address_t *cc,
313                  address_t *bcc, const char *msg, int eightbit)
314 {
315   int ret = 0;
316   smtp_session_t session;
317   smtp_message_t message;
318   char *hostportstr = NULL;
319   size_t hostportlen;
320   FILE *fp = NULL;
321   auth_context_t authctx = NULL;
322   const smtp_status_t *status;
323   char* envfrom = from->mailbox;
324
325   _send_smtp_ensure_init ();
326
327   if ((session = smtp_create_session ()) == NULL)
328     SMTPFAIL ("smtp_create_session");
329
330 #ifdef HAVE_GNUTLS_OPENSSL_H
331   if (!ascii_strncasecmp("enabled", SmtpUseTLS, 7)) {
332     smtp_starttls_enable(session, Starttls_ENABLED);
333   } else if (!ascii_strncasecmp("required", SmtpUseTLS, 8)) {
334     smtp_starttls_enable(session, Starttls_REQUIRED);
335   }
336 #endif
337
338   /* Create hostname:port string and tell libesmtp */
339   /* len = SmtpHost len + colon + max port (65536 => 5 chars) + terminator */
340   hostportlen = m_strlen(SmtpHost) + 7;
341   hostportstr = p_new(char, hostportlen);
342   snprintf (hostportstr, hostportlen, "%s:%d", SmtpHost, SmtpPort);
343   if (!smtp_set_server (session, hostportstr))
344     SMTPFAIL ("smtp_set_server");
345
346   if (SmtpAuthUser) {
347     if ((authctx = auth_create_context ()) == NULL)
348       MSGFAIL ("auth_create_context failed");
349     auth_set_mechanism_flags (authctx, AUTH_PLUGIN_PLAIN, 0);
350     auth_set_interact_cb (authctx, _send_smtp_auth_interact, NULL);
351
352     if (!smtp_auth_set_context (session, authctx))
353       SMTPFAIL ("smtp_auth_set_context");
354   }
355
356 #ifdef HAVE_GNUTLS_OPENSSL_H
357   smtp_starttls_set_ctx (session, NULL);
358 #endif
359   smtp_set_eventcb (session, event_cb, NULL);
360
361   if ((message = smtp_add_message (session)) == NULL)
362     SMTPFAIL ("smtp_add_message");
363
364   /*  Initialize envelope sender */
365   if (MTransport.use_envelope_from && MTransport.envelope_from_address)
366     envfrom = MTransport.envelope_from_address->mailbox;
367   if (!smtp_set_reverse_path (message, envfrom))
368     SMTPFAIL ("smtp_set_reverse_path");
369
370   /* set up DSN for message */
371   do_dsn_notify (message, envfrom);
372   do_dsn_ret (message);
373
374   /* set up 8bitmime flag */
375   if (eightbit && MTransport.use_8bitmime)
376     smtp_8bitmime_set_body (message, E8bitmime_8BITMIME);
377
378   if ((fp = fopen (msg, "r")) == NULL)
379     LIBCFAIL ("fopen");
380   if (!smtp_set_messagecb (message, _send_smtp_messagefp_cb, fp))
381     SMTPFAIL ("smtp_set_messagecb");
382   if (_send_smtp_add_recipients (message, to))
383     FAIL ();
384   if (_send_smtp_add_recipients (message, cc))
385     FAIL ();
386   if (_send_smtp_add_recipients (message, bcc))
387     FAIL ();
388   if (!smtp_start_session (session))
389     SMTPFAIL ("smtp_start_session");
390
391   status = smtp_message_transfer_status (message);
392   if (status->code < 200 || status->code > 299) {
393     char buf[256];
394
395     snprintf (buf, sizeof (buf), "SMTP error while sending: %d %s",
396               status->code, status->text);
397     MSGFAIL (buf);
398   }
399
400 Done:
401   m_fclose(&fp);
402   if (hostportstr != NULL)
403     p_delete(&hostportstr);
404   if (session != NULL)
405     smtp_destroy_session (session);
406   if (authctx != NULL)
407     auth_destroy_context (authctx);
408
409   /* Forget user-entered SMTP AUTH password if send fails */
410   if (ret != 0)
411     authpass[0] = '\0';
412
413   return ret;
414 }
415 #endif
416
417 static void transform_to_7bit (BODY * a, FILE * fpin);
418
419 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
420 {
421   int c, linelen = 0;
422   char line[77], savechar;
423
424   while ((c = fgetconv (fc)) != EOF) {
425     /* Wrap the line if needed. */
426     if (linelen == 76 && ((istext && c != '\n') || !istext)) {
427       /* If the last character is "quoted", then be sure to move all three
428        * characters to the next line.  Otherwise, just move the last
429        * character...
430        */
431       if (line[linelen - 3] == '=') {
432         line[linelen - 3] = 0;
433         fputs (line, fout);
434         fputs ("=\n", fout);
435         line[linelen] = 0;
436         line[0] = '=';
437         line[1] = line[linelen - 2];
438         line[2] = line[linelen - 1];
439         linelen = 3;
440       }
441       else {
442         savechar = line[linelen - 1];
443         line[linelen - 1] = '=';
444         line[linelen] = 0;
445         fputs (line, fout);
446         fputc ('\n', fout);
447         line[0] = savechar;
448         linelen = 1;
449       }
450     }
451
452     /* Escape lines that begin with/only contain "the message separator". */
453     if (linelen == 4 && !m_strncmp("From", line, 4)) {
454       m_strcpy(line, sizeof(line), "=46rom");
455       linelen = 6;
456     }
457     else if (linelen == 4 && !m_strncmp("from", line, 4)) {
458       m_strcpy(line, sizeof(line), "=66rom");
459       linelen = 6;
460     }
461     else if (linelen == 1 && line[0] == '.') {
462       m_strcpy(line, sizeof(line), "=2E");
463       linelen = 3;
464     }
465
466
467     if (c == '\n' && istext) {
468       /* Check to make sure there is no trailing space on this line. */
469       if (linelen > 0
470           && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
471         if (linelen < 74) {
472           sprintf (line + linelen - 1, "=%2.2X",
473                    (unsigned char) line[linelen - 1]);
474           fputs (line, fout);
475         }
476         else {
477           savechar = line[linelen - 1];
478
479           line[linelen - 1] = '=';
480           line[linelen] = 0;
481           fputs (line, fout);
482           fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
483         }
484       }
485       else {
486         line[linelen] = 0;
487         fputs (line, fout);
488       }
489       fputc ('\n', fout);
490       linelen = 0;
491     }
492     else if (c != 9 && (c < 32 || c > 126 || c == '=')) {
493       /* Check to make sure there is enough room for the quoted character.
494        * If not, wrap to the next line.
495        */
496       if (linelen > 73) {
497         line[linelen++] = '=';
498         line[linelen] = 0;
499         fputs (line, fout);
500         fputc ('\n', fout);
501         linelen = 0;
502       }
503       sprintf (line + linelen, "=%2.2X", (unsigned char) c);
504       linelen += 3;
505     }
506     else {
507       /* Don't worry about wrapping the line here.  That will happen during
508        * the next iteration when I'll also know what the next character is.
509        */
510       line[linelen++] = c;
511     }
512   }
513
514   /* Take care of anything left in the buffer */
515   if (linelen > 0) {
516     if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
517       /* take care of trailing whitespace */
518       if (linelen < 74)
519         sprintf (line + linelen - 1, "=%2.2X",
520                  (unsigned char) line[linelen - 1]);
521       else {
522         savechar = line[linelen - 1];
523         line[linelen - 1] = '=';
524         line[linelen] = 0;
525         fputs (line, fout);
526         fputc ('\n', fout);
527         sprintf (line, "=%2.2X", (unsigned char) savechar);
528       }
529     }
530     else
531       line[linelen] = 0;
532     fputs (line, fout);
533   }
534 }
535
536 static char b64_buffer[3];
537 static short b64_num;
538 static short b64_linelen;
539
540 static void b64_flush (FILE * fout)
541 {
542   short i;
543
544   if (!b64_num)
545     return;
546
547   if (b64_linelen >= 72) {
548     fputc ('\n', fout);
549     b64_linelen = 0;
550   }
551
552   for (i = b64_num; i < 3; i++)
553     b64_buffer[i] = '\0';
554
555   fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
556   b64_linelen++;
557   fputc(__m_b64chars
558          [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
559   b64_linelen++;
560
561   if (b64_num > 1) {
562     fputc (__m_b64chars
563            [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
564            fout);
565     b64_linelen++;
566     if (b64_num > 2) {
567       fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
568       b64_linelen++;
569     }
570   }
571
572   while (b64_linelen % 4) {
573     fputc ('=', fout);
574     b64_linelen++;
575   }
576
577   b64_num = 0;
578 }
579
580
581 static void b64_putc (char c, FILE * fout)
582 {
583   if (b64_num == 3)
584     b64_flush (fout);
585
586   b64_buffer[b64_num++] = c;
587 }
588
589
590 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
591 {
592   int ch, ch1 = EOF;
593
594   b64_num = b64_linelen = 0;
595
596   while ((ch = fgetconv (fc)) != EOF) {
597     if (istext && ch == '\n' && ch1 != '\r')
598       b64_putc ('\r', fout);
599     b64_putc (ch, fout);
600     ch1 = ch;
601   }
602   b64_flush (fout);
603   fputc ('\n', fout);
604 }
605
606 static void encode_8bit (fgetconv_t * fc, FILE * fout,
607                          int istext __attribute__ ((unused)))
608 {
609   int ch;
610
611   while ((ch = fgetconv (fc)) != EOF)
612     fputc (ch, fout);
613 }
614
615
616 int mutt_write_mime_header (BODY * a, FILE * f)
617 {
618   char buffer[STRING];
619   char *t;
620   char *fn;
621   int len;
622   int tmplen;
623   int encode;
624
625   fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
626
627   if (a->parameter) {
628     parameter_t *p;
629
630     len = 25 + m_strlen(a->subtype);        /* approximate len. of content-type */
631
632     for (p = a->parameter; p; p = p->next) {
633       char *tmp;
634
635       if (!p->value)
636         continue;
637
638       fputc (';', f);
639
640       buffer[0] = 0;
641       tmp = m_strdup(p->value);
642       encode = rfc2231_encode_string (&tmp);
643       rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
644
645       /* Dirty hack to make messages readable by Outlook Express 
646        * for the Mac: force quotes around the boundary parameter
647        * even when they aren't needed.
648        */
649
650       if (!ascii_strcasecmp (p->attribute, "boundary")
651           && !strcmp (buffer, tmp))
652         snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
653
654       p_delete(&tmp);
655
656       tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
657
658       if (len + tmplen + 2 > 76) {
659         fputs ("\n\t", f);
660         len = tmplen + 8;
661       }
662       else {
663         fputc (' ', f);
664         len += tmplen + 1;
665       }
666
667       fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
668
669     }
670   }
671
672   fputc ('\n', f);
673
674   if (a->description)
675     fprintf (f, "Content-Description: %s\n", a->description);
676
677 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
678   fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
679
680   if (a->use_disp) {
681     if (!(fn = a->d_filename))
682       fn = a->filename;
683
684     if (fn) {
685       char *tmp;
686
687       /* Strip off the leading path... */
688       if ((t = strrchr (fn, '/')))
689         t++;
690       else
691         t = fn;
692
693       buffer[0] = 0;
694       tmp = m_strdup(t);
695       encode = rfc2231_encode_string (&tmp);
696       rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
697       p_delete(&tmp);
698       fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
699     }
700   }
701
702   fputc ('\n', f);
703
704   if (a->encoding != ENC7BIT)
705     fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
706
707   /* Do NOT add the terminator here!!! */
708   return (ferror (f) ? -1 : 0);
709 }
710
711 int mutt_write_mime_body (BODY * a, FILE * f)
712 {
713   const char *p;
714   char boundary[STRING];
715   char send_charset[STRING];
716   FILE *fpin;
717   BODY *t;
718   fgetconv_t *fc;
719
720   if (a->type == TYPEMULTIPART) {
721     /* First, find the boundary to use */
722     if (!(p = parameter_getval(a->parameter, "boundary"))) {
723       mutt_error _("No boundary parameter found! [report this error]");
724
725       return (-1);
726     }
727     m_strcpy(boundary, sizeof(boundary), p);
728
729     for (t = a->parts; t; t = t->next) {
730       fprintf (f, "\n--%s\n", boundary);
731       if (mutt_write_mime_header (t, f) == -1)
732         return -1;
733       fputc ('\n', f);
734       if (mutt_write_mime_body (t, f) == -1)
735         return -1;
736     }
737     fprintf (f, "\n--%s--\n", boundary);
738     return (ferror (f) ? -1 : 0);
739   }
740
741   /* This is pretty gross, but it's the best solution for now... */
742   if (a->type == TYPEAPPLICATION && !m_strcmp(a->subtype, "pgp-encrypted")) {
743     fputs ("Version: 1\n", f);
744     return 0;
745   }
746
747   if ((fpin = fopen (a->filename, "r")) == NULL) {
748     mutt_error (_("%s no longer exists!"), a->filename);
749     return -1;
750   }
751
752   if (a->type == TYPETEXT && (!a->noconv))
753     fc = fgetconv_open (fpin, a->file_charset,
754                         mutt_get_body_charset (send_charset,
755                                                sizeof (send_charset), a), 0);
756   else
757     fc = fgetconv_open (fpin, 0, 0, 0);
758
759 #define write_as_text_part(a)  (mutt_is_text_part(a) || mutt_is_application_pgp(a))
760   if (a->encoding == ENCQUOTEDPRINTABLE)
761     encode_quoted (fc, f, write_as_text_part (a));
762   else if (a->encoding == ENCBASE64)
763     encode_base64 (fc, f, write_as_text_part (a));
764   else if (a->type == TYPETEXT && (!a->noconv))
765     encode_8bit (fc, f, write_as_text_part (a));
766   else
767     mutt_copy_stream (fpin, f);
768 #undef write_as_text_part
769
770   fgetconv_close (&fc);
771   m_fclose(&fpin);
772
773   return (ferror (f) ? -1 : 0);
774 }
775
776 typedef struct {
777   int from;
778   int whitespace;
779   int dot;
780   int linelen;
781   int was_cr;
782 } CONTENT_STATE;
783
784
785 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
786                                  ssize_t dlen)
787 {
788   int from = s->from;
789   int whitespace = s->whitespace;
790   int dot = s->dot;
791   int linelen = s->linelen;
792   int was_cr = s->was_cr;
793
794   if (!d) {                     /* This signals EOF */
795     if (was_cr)
796       info->binary = 1;
797     if (linelen > info->linemax)
798       info->linemax = linelen;
799
800     return;
801   }
802
803   for (; dlen; d++, dlen--) {
804     char ch = *d;
805
806     if (was_cr) {
807       was_cr = 0;
808       if (ch != '\n') {
809         info->binary = 1;
810       }
811       else {
812         if (whitespace)
813           info->space = 1;
814         if (dot)
815           info->dot = 1;
816         if (linelen > info->linemax)
817           info->linemax = linelen;
818         whitespace = 0;
819         dot = 0;
820         linelen = 0;
821         continue;
822       }
823     }
824
825     linelen++;
826     if (ch == '\n') {
827       info->crlf++;
828       if (whitespace)
829         info->space = 1;
830       if (dot)
831         info->dot = 1;
832       if (linelen > info->linemax)
833         info->linemax = linelen;
834       whitespace = 0;
835       linelen = 0;
836       dot = 0;
837     }
838     else if (ch == '\r') {
839       info->crlf++;
840       info->cr = 1;
841       was_cr = 1;
842       continue;
843     }
844     else if (ch & 0x80)
845       info->hibin++;
846     else if (ch == '\t' || ch == '\f') {
847       info->ascii++;
848       whitespace++;
849     }
850     else if (ch < 32 || ch == 127)
851       info->lobin++;
852     else {
853       if (linelen == 1) {
854         if ((ch == 'F') || (ch == 'f'))
855           from = 1;
856         else
857           from = 0;
858         if (ch == '.')
859           dot = 1;
860         else
861           dot = 0;
862       }
863       else if (from) {
864         if (linelen == 2 && ch != 'r')
865           from = 0;
866         else if (linelen == 3 && ch != 'o')
867           from = 0;
868         else if (linelen == 4) {
869           if (ch == 'm')
870             info->from = 1;
871           from = 0;
872         }
873       }
874       if (ch == ' ')
875         whitespace++;
876       info->ascii++;
877     }
878
879     if (linelen > 1)
880       dot = 0;
881     if (ch != ' ' && ch != '\t')
882       whitespace = 0;
883   }
884
885   s->from = from;
886   s->whitespace = whitespace;
887   s->dot = dot;
888   s->linelen = linelen;
889   s->was_cr = was_cr;
890
891 }
892
893 /*
894  * Find the best charset conversion of the file from fromcode into one
895  * of the tocodes. If successful, set *tocode and CONTENT *info and
896  * return the number of characters converted inexactly. If no
897  * conversion was possible, return -1.
898  *
899  * We convert via UTF-8 in order to avoid the condition -1(EINVAL),
900  * which would otherwise prevent us from knowing the number of inexact
901  * conversions. Where the candidate target charset is UTF-8 we avoid
902  * doing the second conversion because iconv_open("UTF-8", "UTF-8")
903  * fails with some libraries.
904  *
905  * We assume that the output from iconv is never more than 4 times as
906  * long as the input for any pair of charsets we might be interested
907  * in.
908  */
909 static ssize_t convert_file_to (FILE * file, const char *fromcode,
910                                int ncodes, const char **tocodes,
911                                int *tocode, CONTENT * info)
912 {
913   iconv_t cd1, *cd;
914   char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
915   const char *ib, *ub;
916   char *ob;
917   ssize_t ibl, obl, ubl, ubl1, n, ret;
918   int i;
919   CONTENT *infos;
920   CONTENT_STATE *states;
921   ssize_t *score;
922
923   cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
924   if (cd1 == MUTT_ICONV_ERROR)
925     return -1;
926
927   cd = p_new(iconv_t, ncodes);
928   score = p_new(ssize_t, ncodes);
929   states = p_new(CONTENT_STATE, ncodes);
930   infos = p_new(CONTENT, ncodes);
931
932   for (i = 0; i < ncodes; i++)
933     if (ascii_strcasecmp (tocodes[i], "UTF-8"))
934       cd[i] = mutt_iconv_open (tocodes[i], "UTF-8", 0);
935     else
936       /* Special case for conversion to UTF-8 */
937       cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
938
939   rewind (file);
940   ibl = 0;
941   for (;;) {
942
943     /* Try to fill input buffer */
944     n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
945     ibl += n;
946
947     /* Convert to UTF-8 */
948     ib = bufi;
949     ob = bufu, obl = sizeof (bufu);
950     n = my_iconv(cd1, ibl ? &ib : 0, &ibl, &ob, &obl);
951     if (n == -1 && ((errno != EINVAL && errno != E2BIG) || ib == bufi)) {
952       ret = -1;
953       break;
954     }
955     ubl1 = ob - bufu;
956
957     /* Convert from UTF-8 */
958     for (i = 0; i < ncodes; i++)
959       if (cd[i] != MUTT_ICONV_ERROR && score[i] != -1) {
960         ub = bufu, ubl = ubl1;
961         ob = bufo, obl = sizeof (bufo);
962         n = my_iconv(cd[i], (ibl || ubl) ? &ub : 0, &ubl, &ob, &obl);
963         if (n == -1) {
964           score[i] = -1;
965         }
966         else {
967           score[i] += n;
968           update_content_info (&infos[i], &states[i], bufo, ob - bufo);
969         }
970       }
971       else if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1)
972         /* Special case for conversion to UTF-8 */
973         update_content_info (&infos[i], &states[i], bufu, ubl1);
974
975     if (ibl)
976       /* Save unused input */
977       memmove (bufi, ib, ibl);
978     else if (!ubl1 && ib < bufi + sizeof (bufi)) {
979       ret = 0;
980       break;
981     }
982   }
983
984   if (!ret) {
985     /* Find best score */
986     ret = -1;
987     for (i = 0; i < ncodes; i++) {
988       if (cd[i] == MUTT_ICONV_ERROR && score[i] == -1) {
989         /* Special case for conversion to UTF-8 */
990         *tocode = i;
991         ret = 0;
992         break;
993       }
994       else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
995         continue;
996       else if (ret == -1 || score[i] < ret) {
997         *tocode = i;
998         ret = score[i];
999         if (!ret)
1000           break;
1001       }
1002     }
1003     if (ret != -1) {
1004       memcpy (info, &infos[*tocode], sizeof (CONTENT));
1005       update_content_info (info, &states[*tocode], 0, 0);       /* EOF */
1006     }
1007   }
1008
1009   for (i = 0; i < ncodes; i++)
1010     if (cd[i] != MUTT_ICONV_ERROR)
1011       iconv_close (cd[i]);
1012
1013   iconv_close (cd1);
1014   p_delete(&cd);
1015   p_delete(&infos);
1016   p_delete(&score);
1017   p_delete(&states);
1018
1019   return ret;
1020 }
1021
1022 /*
1023  * Find the first of the fromcodes that gives a valid conversion and
1024  * the best charset conversion of the file into one of the tocodes. If
1025  * successful, set *fromcode and *tocode to dynamically allocated
1026  * strings, set CONTENT *info, and return the number of characters
1027  * converted inexactly. If no conversion was possible, return -1.
1028  *
1029  * Both fromcodes and tocodes may be colon-separated lists of charsets.
1030  * However, if fromcode is zero then fromcodes is assumed to be the
1031  * name of a single charset even if it contains a colon.
1032  */
1033 static ssize_t convert_file_from_to (FILE * file,
1034                                     const char *fromcodes,
1035                                     const char *tocodes, char **fromcode,
1036                                     char **tocode, CONTENT * info)
1037 {
1038   char *fcode;
1039   char **tcode;
1040   const char *c, *c1;
1041   ssize_t ret;
1042   int ncodes, i, cn;
1043
1044   /* Count the tocodes */
1045   ncodes = 0;
1046   for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
1047     if ((c1 = strchr (c, ':')) == c)
1048       continue;
1049     ++ncodes;
1050   }
1051
1052   /* Copy them */
1053   tcode = p_new(char *, ncodes);
1054   for (c = tocodes, i = 0; c; c = c1 ? c1 + 1 : 0, i++) {
1055     if ((c1 = strchr (c, ':')) == c)
1056       continue;
1057     tcode[i] = m_substrdup(c, c1);
1058   }
1059
1060   ret = -1;
1061   if (fromcode) {
1062     /* Try each fromcode in turn */
1063     for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
1064       if ((c1 = strchr (c, ':')) == c)
1065         continue;
1066       fcode = m_substrdup(c, c1);
1067
1068       ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
1069                              &cn, info);
1070       if (ret != -1) {
1071         *fromcode = fcode;
1072         *tocode = tcode[cn];
1073         tcode[cn] = 0;
1074         break;
1075       }
1076       p_delete(&fcode);
1077     }
1078   }
1079   else {
1080     /* There is only one fromcode */
1081     ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
1082                            &cn, info);
1083     if (ret != -1) {
1084       *tocode = tcode[cn];
1085       tcode[cn] = 0;
1086     }
1087   }
1088
1089   /* Free memory */
1090   for (i = 0; i < ncodes; i++)
1091     p_delete(&tcode[i]);
1092
1093   p_delete(tcode);
1094
1095   return ret;
1096 }
1097
1098 /* 
1099  * Analyze the contents of a file to determine which MIME encoding to use.
1100  * Also set the body charset, sometimes, or not.
1101  */
1102 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
1103 {
1104   CONTENT *info;
1105   CONTENT_STATE state;
1106   FILE *fp = NULL;
1107   char *fromcode = NULL;
1108   char *tocode = NULL;
1109   char buffer[100];
1110   char chsbuf[STRING];
1111   ssize_t r;
1112
1113   struct stat sb;
1114
1115   if (b && !fname)
1116     fname = b->filename;
1117
1118   if (stat (fname, &sb) == -1) {
1119     mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
1120     return NULL;
1121   }
1122
1123   if (!S_ISREG (sb.st_mode)) {
1124     mutt_error (_("%s isn't a regular file."), fname);
1125     return NULL;
1126   }
1127
1128   if ((fp = fopen (fname, "r")) == NULL) {
1129     return (NULL);
1130   }
1131
1132   info = p_new(CONTENT, 1);
1133   p_clear(&state, 1);
1134
1135   if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset)) {
1136     const char *chs = parameter_getval(b->parameter, "charset");
1137     char *fchs = b->use_disp && !m_strisempty(mod_cset.file_charset)
1138         ? FileCharset : mod_cset.charset;
1139     if (mod_cset.charset && (chs || mod_cset.send_charset) &&
1140         convert_file_from_to (fp, fchs, chs ? chs : mod_cset.send_charset,
1141                               &fromcode, &tocode, info) != -1) {
1142       if (!chs) {
1143         charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
1144         parameter_setval(&b->parameter, "charset", chsbuf);
1145       }
1146       b->file_charset = fromcode;
1147       p_delete(&tocode);
1148       m_fclose(&fp);
1149       return info;
1150     }
1151   }
1152
1153   rewind (fp);
1154   while ((r = fread (buffer, 1, sizeof (buffer), fp)))
1155     update_content_info (info, &state, buffer, r);
1156   update_content_info (info, &state, 0, 0);
1157
1158   m_fclose(&fp);
1159
1160   if (b != NULL && b->type == TYPETEXT && (!b->noconv && !b->force_charset))
1161     parameter_setval(&b->parameter, "charset",
1162                      (!info->hibin ? "us-ascii"
1163                       : mod_cset.charset && !charset_is_us_ascii(mod_cset.charset)
1164                                          ? mod_cset.charset : "unknown-8bit"));
1165
1166   return info;
1167 }
1168
1169 /* Given a file with path ``s'', see if there is a registered MIME type.
1170  * returns the major MIME type, and copies the subtype to ``d''.  First look
1171  * for ~/.mime.types, then look in a system mime.types if we can find one.
1172  * The longest match is used so that we can match `ps.gz' when `gz' also
1173  * exists.
1174  */
1175
1176 int mutt_lookup_mime_type (BODY * att, const char *path)
1177 {
1178   FILE *f;
1179   char *p, *q, *ct;
1180   char buf[LONG_STRING];
1181   char subtype[STRING], xtype[STRING];
1182   int count;
1183   int szf, sze, cur_sze;
1184   int type;
1185
1186   *subtype = '\0';
1187   *xtype = '\0';
1188   type = TYPEOTHER;
1189   cur_sze = 0;
1190
1191   szf = m_strlen(path);
1192
1193   for (count = 0; count < 4; count++) {
1194     /*
1195      * can't use strtok() because we use it in an inner loop below, so use
1196      * a switch statement here instead.
1197      */
1198     switch (count) {
1199     case 0:
1200       snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(mod_core.homedir));
1201       break;
1202     case 1:
1203       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
1204       break;
1205     case 2:
1206       m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
1207       break;
1208     case 3:
1209       m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
1210       break;
1211     default:
1212       goto bye;                 /* shouldn't happen */
1213     }
1214
1215     if ((f = fopen (buf, "r")) != NULL) {
1216       while (fgets (buf, sizeof (buf) - 1, f) != NULL) {
1217         /* weed out any comments */
1218         if ((p = strchr (buf, '#')))
1219           *p = 0;
1220
1221         /* remove any leading space. */
1222         ct = vskipspaces(buf);
1223
1224         /* position on the next field in this line */
1225         if ((p = strpbrk (ct, " \t")) == NULL)
1226           continue;
1227         *p++ = 0;
1228         p = vskipspaces(p);
1229
1230         /* cycle through the file extensions */
1231         while ((p = strtok (p, " \t\n"))) {
1232           sze = m_strlen(p);
1233           if ((sze > cur_sze) && (szf >= sze) &&
1234               (m_strcasecmp(path + szf - sze, p) == 0
1235                || ascii_strcasecmp (path + szf - sze, p) == 0)
1236               && (szf == sze || path[szf - sze - 1] == '.'))
1237           {
1238             /* get the content-type */
1239
1240             if ((p = strchr (ct, '/')) == NULL) {
1241               /* malformed line, just skip it. */
1242               break;
1243             }
1244             *p++ = 0;
1245
1246             for (q = p; *q && !ISSPACE (*q); q++);
1247
1248             m_strncpy(subtype, sizeof(subtype), p, q - p);
1249
1250             if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1251               m_strcpy(xtype, sizeof(xtype), ct);
1252
1253             cur_sze = sze;
1254           }
1255           p = NULL;
1256         }
1257       }
1258       m_fclose(&f);
1259     }
1260   }
1261
1262 bye:
1263
1264   if (type != TYPEOTHER || *xtype != '\0') {
1265     att->type = type;
1266     m_strreplace(&att->subtype, subtype);
1267     m_strreplace(&att->xtype, xtype);
1268   }
1269
1270   return (type);
1271 }
1272
1273 void mutt_message_to_7bit (BODY * a, FILE * fp)
1274 {
1275   char temp[_POSIX_PATH_MAX];
1276   char *line = NULL;
1277   FILE *fpin = NULL;
1278   FILE *fpout = NULL;
1279   struct stat sb;
1280
1281   if (!a->filename && fp)
1282     fpin = fp;
1283   else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1284     mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1285     return;
1286   }
1287   else {
1288     a->offset = 0;
1289     if (stat (a->filename, &sb) == -1) {
1290       mutt_perror ("stat");
1291       m_fclose(&fpin);
1292     }
1293     a->length = sb.st_size;
1294   }
1295
1296   fpout = m_tempfile(temp, sizeof(temp), NONULL(mod_core.tmpdir), NULL);
1297   if (!fpout) {
1298     mutt_error(_("Could not create temporary file"));
1299     goto cleanup;
1300   }
1301
1302   fseeko (fpin, a->offset, 0);
1303   a->parts = mutt_parse_messageRFC822 (fpin, a);
1304
1305   transform_to_7bit (a->parts, fpin);
1306
1307   mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1308                  CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
1309
1310   fputs ("MIME-Version: 1.0\n", fpout);
1311   mutt_write_mime_header (a->parts, fpout);
1312   fputc ('\n', fpout);
1313   mutt_write_mime_body (a->parts, fpout);
1314
1315 cleanup:
1316   p_delete(&line);
1317
1318   if (fpin && !fp)
1319     m_fclose(&fpin);
1320   if (fpout)
1321     m_fclose(&fpout);
1322   else
1323     return;
1324
1325   a->encoding = ENC7BIT;
1326   a->d_filename = a->filename;
1327   if (a->filename && a->unlink)
1328     unlink (a->filename);
1329   a->filename = m_strdup(temp);
1330   a->unlink = 1;
1331   if (stat (a->filename, &sb) == -1) {
1332     mutt_perror ("stat");
1333     return;
1334   }
1335   a->length = sb.st_size;
1336   body_list_wipe(&a->parts);
1337   a->hdr->content = NULL;
1338 }
1339
1340 static void transform_to_7bit (BODY * a, FILE * fpin)
1341 {
1342   char buff[_POSIX_PATH_MAX];
1343   STATE s;
1344   struct stat sb;
1345
1346   p_clear(&s, 1);
1347   for (; a; a = a->next) {
1348     if (a->type == TYPEMULTIPART) {
1349       if (a->encoding != ENC7BIT)
1350         a->encoding = ENC7BIT;
1351
1352       transform_to_7bit (a->parts, fpin);
1353     }
1354     else if (mutt_is_message_type(a)) {
1355       mutt_message_to_7bit (a, fpin);
1356     }
1357     else {
1358       a->noconv = 1;
1359       a->force_charset = 1;
1360
1361       s.fpout = m_tempfile(buff, sizeof(buff), NONULL(mod_core.tmpdir), NULL);
1362       if (!s.fpout) {
1363         mutt_error(_("Could not create temporary file"));
1364         return;
1365       }
1366       s.fpin = fpin;
1367       mutt_decode_attachment (a, &s);
1368       m_fclose(&s.fpout);
1369       a->d_filename = a->filename;
1370       a->filename = m_strdup(buff);
1371       a->unlink = 1;
1372       if (stat (a->filename, &sb) == -1) {
1373         mutt_perror ("stat");
1374         return;
1375       }
1376       a->length = sb.st_size;
1377
1378       mutt_update_encoding (a);
1379       if (a->encoding == ENC8BIT)
1380         a->encoding = ENCQUOTEDPRINTABLE;
1381       else if (a->encoding == ENCBINARY)
1382         a->encoding = ENCBASE64;
1383     }
1384   }
1385 }
1386
1387 /* determine which Content-Transfer-Encoding to use */
1388 static void mutt_set_encoding (BODY * b, CONTENT * info)
1389 {
1390   char send_charset[STRING];
1391
1392   if (b->type == TYPETEXT) {
1393     char *chsname =
1394       mutt_get_body_charset (send_charset, sizeof (send_charset), b);
1395     if ((info->lobin && ascii_strncasecmp (chsname, "iso-2022", 8))
1396         || info->linemax > 990 || (info->from && option (OPTENCODEFROM)))
1397       b->encoding = ENCQUOTEDPRINTABLE;
1398     else if (info->hibin)
1399       b->encoding = option (OPTALLOW8BIT) ? ENC8BIT : ENCQUOTEDPRINTABLE;
1400     else
1401       b->encoding = ENC7BIT;
1402   }
1403   else if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART) {
1404     if (info->lobin || info->hibin) {
1405       if (option (OPTALLOW8BIT) && !info->lobin)
1406         b->encoding = ENC8BIT;
1407       else
1408         mutt_message_to_7bit (b, NULL);
1409     }
1410     else
1411       b->encoding = ENC7BIT;
1412   }
1413   else if (b->type == TYPEAPPLICATION
1414            && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1415     b->encoding = ENC7BIT;
1416   else
1417   {
1418     /* Determine which encoding is smaller  */
1419     if (1.33 * (float) (info->lobin + info->hibin + info->ascii) <
1420         3.0 * (float) (info->lobin + info->hibin) + (float) info->ascii)
1421       b->encoding = ENCBASE64;
1422     else
1423       b->encoding = ENCQUOTEDPRINTABLE;
1424   }
1425 }
1426
1427 void mutt_stamp_attachment (BODY * a)
1428 {
1429   a->stamp = time (NULL);
1430 }
1431
1432 /* Get a body's character set */
1433
1434 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1435 {
1436     const char *p;
1437
1438     if (b && b->type != TYPETEXT)
1439         return NULL;
1440
1441     p = b ? parameter_getval(b->parameter, "charset") : NULL;
1442     charset_canonicalize(d, dlen, p);
1443     return d;
1444 }
1445
1446
1447 /* Assumes called from send mode where BODY->filename points to actual file */
1448 void mutt_update_encoding (BODY * a)
1449 {
1450   CONTENT *info;
1451   char chsbuff[STRING];
1452
1453   /* override noconv when it's us-ascii */
1454   if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1455     a->noconv = 0;
1456
1457   if (!a->force_charset && !a->noconv)
1458     parameter_delval(&a->parameter, "charset");
1459
1460   if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1461     return;
1462
1463   mutt_set_encoding (a, info);
1464   mutt_stamp_attachment (a);
1465
1466   p_delete(&a->content);
1467   a->content = info;
1468
1469 }
1470
1471 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1472 {
1473   char buffer[LONG_STRING];
1474   BODY *body;
1475   FILE *fp;
1476   int cmflags, chflags;
1477   int pgp = hdr->security;
1478
1479   if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1480       (hdr->security & ENCRYPT)) {
1481   }
1482
1483   fp = m_tempfile(buffer, sizeof(buffer), NONULL(mod_core.tmpdir), NULL);
1484   if (!fp)
1485     return NULL;
1486
1487   body = body_new();
1488   body->type = TYPEMESSAGE;
1489   body->subtype = m_strdup("rfc822");
1490   body->filename = m_strdup(buffer);
1491   body->unlink = 1;
1492   body->use_disp = 0;
1493   body->disposition = DISPINLINE;
1494   body->noconv = 1;
1495
1496   mutt_parse_mime_message (ctx, hdr);
1497
1498   chflags = CH_XMIT;
1499   cmflags = 0;
1500
1501   /* If we are attaching a message, ignore OPTMIMEFORWDECODE */
1502   if (!attach_msg && option (OPTMIMEFORWDECODE)) {
1503     chflags |= CH_MIME | CH_TXTPLAIN;
1504     cmflags = M_CM_DECODE | M_CM_CHARCONV;
1505     pgp &= ~(PGPENCRYPT|SMIMEENCRYPT);
1506   }
1507   else if (option (OPTFORWDECRYPT) && (hdr->security & ENCRYPT)) {
1508     if (mutt_is_multipart_encrypted (hdr->content)) {
1509       chflags |= CH_MIME | CH_NONEWLINE;
1510       cmflags = M_CM_DECODE_PGP;
1511       pgp &= ~PGPENCRYPT;
1512     }
1513     else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1514       chflags |= CH_MIME | CH_TXTPLAIN;
1515       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1516       pgp &= ~PGPENCRYPT;
1517     }
1518     else if (mutt_is_application_smime (hdr->content) & SMIMEENCRYPT) {
1519       chflags |= CH_MIME | CH_TXTPLAIN;
1520       cmflags = M_CM_DECODE | M_CM_CHARCONV;
1521       pgp &= ~SMIMEENCRYPT;
1522     }
1523   }
1524
1525   mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
1526
1527   fflush (fp);
1528   rewind (fp);
1529
1530   body->hdr = header_new();
1531   body->hdr->offset = 0;
1532   /* we don't need the user headers here */
1533   body->hdr->env = mutt_read_rfc822_header (fp, body->hdr, 0, 0);
1534   body->hdr->security = pgp;
1535   mutt_update_encoding (body);
1536   body->parts = body->hdr->content;
1537
1538   m_fclose(&fp);
1539
1540   return (body);
1541 }
1542
1543 BODY *mutt_make_file_attach (const char *path)
1544 {
1545   BODY *att;
1546   CONTENT *info;
1547
1548   att = body_new();
1549   att->filename = m_strdup(path);
1550
1551   /* Attempt to determine the appropriate content-type based on the filename
1552    * suffix.
1553    */
1554   mutt_lookup_mime_type (att, path);
1555
1556   if ((info = mutt_get_content_info (path, att)) == NULL) {
1557     body_list_wipe(&att);
1558     return NULL;
1559   }
1560
1561   if (!att->subtype) {
1562     if (info->lobin == 0
1563         || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1564       /*
1565        * Statistically speaking, there should be more than 10% "lobin" 
1566        * chars if this is really a binary file...
1567        */
1568       att->type = TYPETEXT;
1569       att->subtype = m_strdup("plain");
1570     } else {
1571       att->type = TYPEAPPLICATION;
1572       att->subtype = m_strdup("octet-stream");
1573     }
1574   }
1575
1576   mutt_update_encoding (att);
1577   return att;
1578 }
1579
1580 static int get_toplevel_encoding (BODY * a)
1581 {
1582     int e = ENC7BIT;
1583
1584     for (; a; a = a->next) {
1585         if (a->encoding == ENCBINARY)
1586             return ENCBINARY;
1587
1588         if (a->encoding == ENC8BIT)
1589             e = ENC8BIT;
1590     }
1591
1592     return e;
1593 }
1594
1595 BODY *mutt_make_multipart (BODY * b)
1596 {
1597   BODY *new;
1598
1599   new = body_new();
1600   new->type = TYPEMULTIPART;
1601   new->subtype = m_strdup("mixed");
1602   new->encoding = get_toplevel_encoding (b);
1603   parameter_set_boundary(&new->parameter);
1604   new->use_disp = 0;
1605   new->disposition = DISPINLINE;
1606   new->parts = b;
1607
1608   return new;
1609 }
1610
1611 /* remove the multipart body if it exists */
1612 BODY *mutt_remove_multipart (BODY * b)
1613 {
1614   BODY *t;
1615
1616   if (b->parts) {
1617     t = b;
1618     b = b->parts;
1619     t->parts = NULL;
1620     body_list_wipe(&t);
1621   }
1622   return b;
1623 }
1624
1625 char *mutt_make_date (char *s, ssize_t len)
1626 {
1627   time_t t = time (NULL);
1628   struct tm *l = localtime (&t);
1629   time_t tz = mutt_local_tz (t);
1630
1631   tz /= 60;
1632
1633   snprintf (s, len, "Date: %s, %d %s %d %02d:%02d:%02d %+03d%02d\n",
1634             Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon],
1635             l->tm_year + 1900, l->tm_hour, l->tm_min, l->tm_sec,
1636             (int) tz / 60, (int) abs (tz) % 60);
1637   return (s);
1638 }
1639
1640 /* wrapper around mutt_write_address() so we can handle very large
1641    recipient lists without needing a huge temporary buffer in memory */
1642 void
1643 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1644 {
1645     int first = 1;
1646
1647     while (addr) {
1648         char buf[LONG_STRING];
1649         int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1650
1651         if (!first) {
1652             if (linelen + len > 74) {
1653                 fputs("\n\t", fp);
1654                 linelen = 8;        /* tab is usually about 8 spaces... */
1655             } else
1656             if (addr->mailbox) {
1657                 fputc(' ', fp);
1658                 linelen++;
1659             }
1660         }
1661         first = 0;
1662
1663         linelen += len + 1;
1664         fputs(buf, fp);
1665
1666         if (!addr->group && addr->next && addr->next->mailbox) {
1667             fputc(',', fp);
1668             linelen++;
1669         }
1670
1671         addr = addr->next;
1672     }
1673     fputc ('\n', fp);
1674 }
1675
1676 /* need to write the list in reverse because they are stored in reverse order
1677  * when parsed to speed up threading
1678  */
1679 void mutt_write_references(string_list_t *r, FILE *f)
1680 {
1681     string_list_t *refs[10];
1682     int i;
1683
1684     p_clear(refs, countof(refs));
1685     for (i = 0; i < countof(refs) && r; r = r->next) {
1686         refs[i++] = r;
1687     }
1688
1689     while (i-- > 0) {
1690         fprintf(f, " %s", refs[i]->data);
1691     }
1692 }
1693
1694 static int edit_header(int mode, const char *s)
1695 {
1696     const char *p;
1697     int slen = m_strlen(s);
1698
1699     if (mode != 1 || option(OPTXMAILTO))
1700         return 1;
1701
1702     p = skipspaces(EditorHeaders);
1703     while (*p) {
1704         if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1705             return 1;
1706         p = skipspaces(p + slen);
1707     }
1708
1709     return 0;
1710 }
1711
1712 /* Note: all RFC2047 encoding should be done outside of this routine, except
1713  * for the "real name."  This will allow this routine to be used more than
1714  * once, if necessary.
1715  * 
1716  * Likewise, all IDN processing should happen outside of this routine.
1717  *
1718  * mode == 1  => "lite" mode (used for edit_hdrs)
1719  * mode == 0  => normal mode.  write full header + MIME headers
1720  * mode == -1 => write just the envelope info (used for postponing messages)
1721  * 
1722  * privacy != 0 => will omit any headers which may identify the user.
1723  *               Output generated is suitable for being sent through
1724  *               anonymous remailer chains.
1725  *
1726  */
1727 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1728                               int mode, int privacy)
1729 {
1730   char buffer[LONG_STRING];
1731   char *p;
1732   string_list_t *tmp = env->userhdrs;
1733   int has_agent = 0;            /* user defined user-agent header field exists */
1734
1735 #ifdef USE_NNTP
1736   if (!option (OPTNEWSSEND))
1737 #endif
1738     if (mode == 0 && !privacy)
1739       fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
1740
1741   /* OPTUSEFROM is not consulted here so that we can still write a From:
1742    * field if the user sets it with the `my_hdr' command
1743    */
1744   if (env->from && !privacy) {
1745     buffer[0] = 0;
1746     rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1747     fprintf (fp, "From: %s\n", buffer);
1748   }
1749
1750   if (env->to) {
1751     fputs ("To: ", fp);
1752     mutt_write_address_list (env->to, fp, 4, 0);
1753   }
1754   else if (mode > 0)
1755 #ifdef USE_NNTP
1756     if (!option (OPTNEWSSEND))
1757 #endif
1758       if (edit_header(mode, "To:"))
1759         fputs ("To:\n", fp);
1760
1761   if (env->cc) {
1762     fputs ("Cc: ", fp);
1763     mutt_write_address_list (env->cc, fp, 4, 0);
1764   }
1765   else if (mode > 0)
1766 #ifdef USE_NNTP
1767     if (!option (OPTNEWSSEND))
1768 #endif
1769       if (edit_header(mode, "Cc:"))
1770         fputs ("Cc:\n", fp);
1771
1772   if (env->bcc) {
1773     if (mode != 0 || option (OPTWRITEBCC)) {
1774       fputs ("Bcc: ", fp);
1775       mutt_write_address_list (env->bcc, fp, 5, 0);
1776     }
1777   }
1778   else if (mode > 0)
1779 #ifdef USE_NNTP
1780     if (!option (OPTNEWSSEND))
1781 #endif
1782       if (edit_header(mode, "Bcc:"))
1783         fputs ("Bcc:\n", fp);
1784
1785 #ifdef USE_NNTP
1786   if (env->newsgroups)
1787     fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
1788   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Newsgroups:"))
1789     fputs ("Newsgroups:\n", fp);
1790
1791   if (env->followup_to)
1792     fprintf (fp, "Followup-To: %s\n", env->followup_to);
1793   else if (mode == 1 && option (OPTNEWSSEND) && edit_header(mode, "Followup-To:"))
1794     fputs ("Followup-To:\n", fp);
1795 #endif
1796
1797   if (env->subject)
1798     fprintf (fp, "Subject: %s\n", env->subject);
1799   else if (mode == 1 && edit_header(mode, "Subject:"))
1800     fputs ("Subject:\n", fp);
1801
1802   /* save message id if the user has set it */
1803   if (env->message_id && !privacy)
1804     fprintf (fp, "Message-ID: %s\n", env->message_id);
1805
1806   if (env->reply_to) {
1807     fputs ("Reply-To: ", fp);
1808     mutt_write_address_list (env->reply_to, fp, 10, 0);
1809   }
1810   else if (mode > 0 && edit_header(mode, "Reply-To:"))
1811     fputs ("Reply-To:\n", fp);
1812
1813   if (env->mail_followup_to)
1814 #ifdef USE_NNTP
1815     if (!option (OPTNEWSSEND))
1816 #endif
1817     {
1818       fputs ("Mail-Followup-To: ", fp);
1819       mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1820     }
1821
1822   if (mode <= 0) {
1823     if (env->references) {
1824       fputs ("References:", fp);
1825       mutt_write_references (env->references, fp);
1826       fputc ('\n', fp);
1827     }
1828
1829     /* Add the MIME headers */
1830     fputs ("MIME-Version: 1.0\n", fp);
1831     mutt_write_mime_header (attach, fp);
1832   }
1833
1834   if (env->in_reply_to) {
1835     fputs ("In-Reply-To:", fp);
1836     mutt_write_references (env->in_reply_to, fp);
1837     fputc ('\n', fp);
1838   }
1839
1840   /* Add any user defined headers */
1841   for (; tmp; tmp = tmp->next) {
1842     if ((p = strchr (tmp->data, ':'))) {
1843       p = vskipspaces(p + 1);
1844       if (!*p)
1845         continue;               /* don't emit empty fields. */
1846
1847       /* check to see if the user has overridden the user-agent field */
1848       if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1849         has_agent = 1;
1850         if (privacy)
1851           continue;
1852       }
1853
1854       fputs (tmp->data, fp);
1855       fputc ('\n', fp);
1856     }
1857   }
1858
1859   if (mode == 0 && !privacy && option (OPTXMAILER) && !has_agent) {
1860     if (mod_core.operating_system) {
1861       fprintf(fp, "User-Agent: %s (%s)\n", mutt_make_version(),
1862               mod_core.operating_system);
1863     } else {
1864       fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1865     }
1866   }
1867
1868   return (ferror (fp) == 0 ? 0 : -1);
1869 }
1870
1871 static void encode_headers (string_list_t * h)
1872 {
1873   char *tmp;
1874   char *p;
1875   int i;
1876
1877   for (; h; h = h->next) {
1878     if (!(p = strchr (h->data, ':')))
1879       continue;
1880
1881     i = p - h->data;
1882     p = vskipspaces(p + 1);
1883     tmp = m_strdup(p);
1884
1885     if (!tmp)
1886       continue;
1887
1888     rfc2047_encode_string (&tmp);
1889     p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1890
1891     sprintf (h->data + i, ": %s", NONULL (tmp));
1892
1893     p_delete(&tmp);
1894   }
1895 }
1896
1897 const char *mutt_fqdn(short may_hide_host)
1898 {
1899   char *p = NULL, *q;
1900
1901   if (mod_core.hostname && mod_core.hostname[0] != '@') {
1902     p = mod_core.hostname;
1903
1904     if (may_hide_host && option (OPTHIDDENHOST)) {
1905       if ((p = strchr(mod_core.hostname, '.')))
1906         p++;
1907
1908       /* sanity check: don't hide the host if
1909          the fqdn is something like detebe.org.  */
1910
1911       if (!p || !(q = strchr(p, '.')))
1912         p = mod_core.hostname;
1913     }
1914   }
1915
1916   return p;
1917 }
1918
1919 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1920 {
1921 #define APPEND_FMT(fmt, arg) \
1922         if (len > 1) {                                  \
1923             int snlen = snprintf(buf, len, fmt, arg);   \
1924             buf += snlen;                               \
1925             len -= snlen;                               \
1926         }
1927
1928 #define APPEND_BYTE(c) \
1929         if (len > 1) {                                  \
1930             *buf++ = c;                                 \
1931             *buf   = '\0';                              \
1932             len--;                                      \
1933         }
1934
1935     time_t now;
1936     struct tm *tm;
1937
1938     now = time (NULL);
1939     tm = gmtime (&now);
1940
1941     while (*fmt) {
1942         static char MsgIdPfx = 'A';
1943         int c = *fmt++;
1944
1945         if (c != '%') {
1946             /* normalized character (we're stricter than RFC2822, 3.6.4) */
1947             APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1948             continue;
1949         }
1950
1951         switch (*fmt++) {
1952           case 0:
1953             return;
1954           case 'd':
1955             APPEND_FMT("%02d", tm->tm_mday);
1956             break;
1957           case 'h':
1958             APPEND_FMT("%02d", tm->tm_hour);
1959             break;
1960           case 'm':
1961             APPEND_FMT("%02d", tm->tm_mon + 1);
1962             break;
1963           case 'M':
1964             APPEND_FMT("%02d", tm->tm_min);
1965             break;
1966           case 'O':
1967             APPEND_FMT("%lo", (unsigned long)now);
1968             break;
1969           case 'p':
1970             APPEND_FMT("%u", (unsigned int)getpid());
1971             break;
1972           case 'P':
1973             APPEND_FMT("%c", MsgIdPfx);
1974             MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1975             break;
1976           case 'r':
1977             APPEND_FMT("%u", (unsigned int)rand());
1978             break;
1979           case 'R':
1980             APPEND_FMT("%x", (unsigned int)rand());
1981             break;
1982           case 's':
1983             APPEND_FMT("%02d", tm->tm_sec);
1984             break;
1985           case 'T':
1986             APPEND_FMT("%u", (unsigned int) now);
1987             break;
1988           case 'X':
1989             APPEND_FMT("%x", (unsigned int) now);
1990             break;
1991           case 'Y':       /* this will break in the year 10000 ;-) */
1992             APPEND_FMT("%04d", tm->tm_year + 1900);
1993             break;
1994           case '%':
1995             APPEND_BYTE('%');
1996             break;
1997           default:       /* invalid formats are replaced by '.' */
1998             APPEND_BYTE('.');
1999         }
2000     }
2001
2002     *buf = '\0';
2003
2004 #undef APPEND_BYTE
2005 #undef APPEND_FMT
2006 }
2007
2008 static char *mutt_gen_msgid (void)
2009 {
2010     char buf[STRING];
2011     char localpart[STRING];
2012     const char *fqdn;
2013
2014     if (!(fqdn = mutt_fqdn(0)))
2015         fqdn = NONULL(mod_core.shorthost);
2016
2017     mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
2018     snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
2019     return m_strdup(buf);
2020 }
2021
2022 static void alarm_handler (int sig __attribute__ ((unused)))
2023 {
2024   SigAlrm = 1;
2025 }
2026
2027 /* invoke sendmail in a subshell
2028    path (in)            path to program to execute
2029    args (in)            arguments to pass to program
2030    msg (in)             temp file containing message to send
2031    tempfile (out)       if sendmail is put in the background, this points
2032                         to the temporary file containing the stdout of the
2033                         child process */
2034 static int
2035 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
2036 {
2037   sigset_t set;
2038   int fd, st;
2039   pid_t pid, ppid;
2040
2041   mutt_block_signals_system ();
2042
2043   sigemptyset (&set);
2044   /* we also don't want to be stopped right now */
2045   sigaddset (&set, SIGTSTP);
2046   sigprocmask (SIG_BLOCK, &set, NULL);
2047
2048   if (MTransport.sendmail_wait >= 0) {
2049     char tmp[_POSIX_PATH_MAX];
2050
2051     mutt_mktemp (tmp);
2052     *tempfile = m_strdup(tmp);
2053   }
2054
2055   if ((pid = fork ()) == 0) {
2056     struct sigaction act, oldalrm;
2057
2058     /* save parent's ID before setsid() */
2059     ppid = getppid ();
2060
2061     /* we want the delivery to continue even after the main process dies,
2062      * so we put ourselves into another session right away
2063      */
2064     setsid ();
2065
2066     /* next we close all open files */
2067     for (fd = 0; fd < getdtablesize(); fd++)
2068       close (fd);
2069
2070     /* now the second fork() */
2071     if ((pid = fork ()) == 0) {
2072       /* "msg" will be opened as stdin */
2073       if (open (msg, O_RDONLY, 0) < 0) {
2074         unlink (msg);
2075         _exit (S_ERR);
2076       }
2077       unlink (msg);
2078
2079       if (MTransport.sendmail_wait >= 0) {
2080         /* *tempfile will be opened as stdout */
2081         if (open (*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) <
2082             0)
2083           _exit (S_ERR);
2084         /* redirect stderr to *tempfile too */
2085         if (dup (1) < 0)
2086           _exit (S_ERR);
2087       } else {
2088         if (open ("/dev/null", O_WRONLY | O_APPEND) < 0)        /* stdout */
2089           _exit (S_ERR);
2090         if (open ("/dev/null", O_RDWR | O_APPEND) < 0)  /* stderr */
2091           _exit (S_ERR);
2092       }
2093
2094       execv (path, (char**)args);
2095       _exit (S_ERR);
2096     }
2097     else if (pid == -1) {
2098       unlink (msg);
2099       p_delete(tempfile);
2100       _exit (S_ERR);
2101     }
2102
2103     /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
2104      * sendmail_wait = 0: wait forever
2105      * sendmail_wait < 0: don't wait
2106      */
2107     if (MTransport.sendmail_wait > 0) {
2108       SigAlrm = 0;
2109       act.sa_handler = alarm_handler;
2110 #ifdef SA_INTERRUPT
2111       /* need to make sure waitpid() is interrupted on SIGALRM */
2112       act.sa_flags = SA_INTERRUPT;
2113 #else
2114       act.sa_flags = 0;
2115 #endif
2116       sigemptyset (&act.sa_mask);
2117       sigaction (SIGALRM, &act, &oldalrm);
2118       alarm (MTransport.sendmail_wait);
2119     }
2120     else if (MTransport.sendmail_wait < 0)
2121       _exit (0xff & EX_OK);
2122
2123     if (waitpid (pid, &st, 0) > 0) {
2124       st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;
2125       if (MTransport.sendmail_wait && st == (0xff & EX_OK)) {
2126         unlink (*tempfile);     /* no longer needed */
2127         p_delete(tempfile);
2128       }
2129     } else {
2130       st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
2131       if (MTransport.sendmail_wait > 0) {
2132         unlink (*tempfile);
2133         p_delete(tempfile);
2134       }
2135     }
2136
2137     /* reset alarm; not really needed, but... */
2138     alarm (0);
2139     sigaction (SIGALRM, &oldalrm, NULL);
2140
2141     if (kill (ppid, 0) == -1 && errno == ESRCH) {
2142       /* the parent is already dead */
2143       unlink (*tempfile);
2144       p_delete(tempfile);
2145     }
2146
2147     _exit (st);
2148   }
2149
2150   sigprocmask (SIG_UNBLOCK, &set, NULL);
2151
2152   if (pid != -1 && waitpid (pid, &st, 0) > 0)
2153     st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR;     /* return child status */
2154   else
2155     st = S_ERR;                 /* error */
2156
2157   mutt_unblock_signals_system (1);
2158
2159   return (st);
2160 }
2161
2162 static const char **
2163 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
2164 {
2165   for (; addr; addr = addr->next) {
2166     /* weed out group mailboxes, since those are for display only */
2167     if (addr->mailbox && !addr->group) {
2168       if (*argslen == *argsmax)
2169         p_realloc(&args, *argsmax += 5);
2170       args[(*argslen)++] = addr->mailbox;
2171     }
2172   }
2173   return (args);
2174 }
2175
2176 static const char **
2177 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
2178 {
2179     if (*argslen == *argsmax) {
2180         p_realloc(&args, *argsmax += 5);
2181     }
2182     args[(*argslen)++] = s;
2183     return (args);
2184 }
2185
2186 static int mutt_invoke_sendmail (address_t * from,        /* the sender */
2187                                  address_t * to, address_t * cc, address_t * bcc,     /* recips */
2188                                  const char *msg,       /* file containing message */
2189                                  int eightbit)
2190 {                               /* message contains 8bit chars */
2191   char cmd[LONG_STRING];
2192   char *ps = NULL, *path = NULL, *childout = NULL;
2193   const char **args = NULL;
2194   ssize_t argslen = 0, argsmax = 0;
2195   int i;
2196
2197 #ifdef USE_NNTP
2198   if (option (OPTNEWSSEND)) {
2199     m_strformat(cmd, sizeof(cmd), 0, Inews, nntp_format_str, 0, 0);
2200     if (m_strisempty(cmd)) {
2201       i = nntp_post (msg);
2202       unlink (msg);
2203       return i;
2204     }
2205   } else
2206 #endif
2207   {
2208     m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
2209   }
2210
2211   ps = cmd;
2212   i = 0;
2213   while ((ps = strtok(ps, " "))) {
2214     if (argslen == argsmax)
2215       p_realloc(&args, argsmax += 5);
2216
2217     if (i)
2218       args[argslen++] = ps;
2219     else {
2220       path = m_strdup(ps);
2221       ps = strrchr (ps, '/');
2222       if (ps)
2223         ps++;
2224       else
2225         ps = path;
2226       args[argslen++] = ps;
2227     }
2228     ps = NULL;
2229     i++;
2230   }
2231
2232 #ifdef USE_NNTP
2233   if (!option (OPTNEWSSEND)) {
2234 #endif
2235     if (eightbit && MTransport.use_8bitmime)
2236       args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2237
2238     if (MTransport.use_envelope_from) {
2239       address_t *f = MTransport.envelope_from_address;
2240       if (!f && from && !from->next)
2241         f = from;
2242       if (f) {
2243         args = add_option (args, &argslen, &argsmax, "-f");
2244         args = add_args (args, &argslen, &argsmax, f);
2245       }
2246     }
2247     if (MTransport.dsn_notify) {
2248       args = add_option (args, &argslen, &argsmax, "-N");
2249       args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
2250     }
2251     if (MTransport.dsn_return) {
2252       args = add_option (args, &argslen, &argsmax, "-R");
2253       args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
2254     }
2255     args = add_option (args, &argslen, &argsmax, "--");
2256     args = add_args (args, &argslen, &argsmax, to);
2257     args = add_args (args, &argslen, &argsmax, cc);
2258     args = add_args (args, &argslen, &argsmax, bcc);
2259 #ifdef USE_NNTP
2260   }
2261 #endif
2262
2263   if (argslen >= argsmax)
2264     p_realloc(&args, ++argsmax);
2265
2266   args[argslen++] = NULL;
2267
2268   if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2269     if (i != S_BKG) {
2270       mutt_error (_("Error sending message, child exited %d (%s)."), i,
2271                   m_strsysexit(i));
2272       if (childout) {
2273         struct stat st;
2274
2275         if (!stat(childout, &st) && st.st_size > 0)
2276           mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
2277       }
2278     }
2279   } else {
2280     unlink (childout);
2281   }
2282
2283   p_delete(&childout);
2284   p_delete(&path);
2285   p_delete(&args);
2286
2287   if (i == (EX_OK & 0xff))
2288     i = 0;
2289   else if (i == S_BKG)
2290     i = 1;
2291   else
2292     i = -1;
2293   return (i);
2294 }
2295
2296 int mutt_invoke_mta (address_t * from,    /* the sender */
2297                      address_t * to, address_t * cc, address_t * bcc, /* recips */
2298                      const char *msg,   /* file containing message */
2299                      int eightbit)
2300 {                               /* message contains 8bit chars */
2301 #ifdef USE_LIBESMTP
2302 #ifdef USE_NNTP
2303   if (!option (OPTNEWSSEND))
2304 #endif
2305     if (SmtpHost)
2306       return send_smtp_invoke (from, to, cc, bcc, msg, eightbit);
2307 #endif
2308
2309   return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2310 }
2311
2312 /* For postponing (!final) do the necessary encodings only */
2313 void mutt_prepare_envelope (ENVELOPE * env, int final)
2314 {
2315   if (final) {
2316     if (env->bcc && !(env->to || env->cc)) {
2317       /* some MTA's will put an Apparently-To: header field showing the Bcc:
2318        * recipients if there is no To: or Cc: field, so attempt to suppress
2319        * it by using an empty To: field.
2320        */
2321       env->to = address_new();
2322       env->to->group = 1;
2323       env->to->next  = address_new();
2324       env->to->mailbox = m_strdup("undisclosed-recipients");
2325     }
2326
2327     mutt_set_followup_to(env);
2328
2329     if (!env->message_id && !m_strisempty(MsgIdFormat))
2330       env->message_id = mutt_gen_msgid();
2331   }
2332
2333   /* Take care of 8-bit => 7-bit conversion. */
2334   rfc2047_encode_adrlist(env->to, "To");
2335   rfc2047_encode_adrlist(env->cc, "Cc");
2336   rfc2047_encode_adrlist(env->bcc, "Bcc");
2337   rfc2047_encode_adrlist(env->from, "From");
2338   rfc2047_encode_adrlist(env->mail_followup_to, "Mail-Followup-To");
2339   rfc2047_encode_adrlist(env->reply_to, "Reply-To");
2340
2341   if (env->subject)
2342     rfc2047_encode_string (&env->subject);
2343   encode_headers (env->userhdrs);
2344 }
2345
2346 void mutt_unprepare_envelope (ENVELOPE * env)
2347 {
2348     string_list_t *item;
2349
2350     for (item = env->userhdrs; item; item = item->next)
2351         rfc2047_decode(&item->data);
2352
2353     address_list_wipe(&env->mail_followup_to);
2354
2355     /* back conversions */
2356     rfc2047_decode_adrlist(env->to);
2357     rfc2047_decode_adrlist(env->cc);
2358     rfc2047_decode_adrlist(env->bcc);
2359     rfc2047_decode_adrlist(env->from);
2360     rfc2047_decode_adrlist(env->reply_to);
2361     rfc2047_decode(&env->subject);
2362 }
2363
2364 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2365                                  const char *resent_from, address_t * env_from)
2366 {
2367   int i, ret = 0;
2368   FILE *f;
2369   char date[STRING], tempfile[_POSIX_PATH_MAX];
2370   MESSAGE *msg = NULL;
2371
2372   if (!h) {
2373     /* Try to bounce each message out, aborting if we get any failures. */
2374     for (i = 0; i < Context->msgcount; i++)
2375       if (Context->hdrs[i]->tagged)
2376         ret |=
2377           _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2378                                 env_from);
2379     return ret;
2380   }
2381
2382   /* If we failed to open a message, return with error */
2383   if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2384     return -1;
2385
2386   if (!fp)
2387     fp = msg->fp;
2388
2389   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2390   if (f) {
2391     int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2392
2393     if (!option (OPTBOUNCEDELIVERED))
2394       ch_flags |= CH_WEED_DELIVERED;
2395
2396     fseeko (fp, h->offset, 0);
2397     fprintf (f, "Resent-From: %s", resent_from);
2398     fprintf (f, "\nResent-%s", mutt_make_date (date, sizeof (date)));
2399     if (!m_strisempty(MsgIdFormat))
2400       fprintf (f, "Resent-Message-ID: %s\n", mutt_gen_msgid());
2401     fputs ("Resent-To: ", f);
2402     mutt_write_address_list (to, f, 11, 0);
2403     mutt_copy_header (fp, h, f, ch_flags, NULL);
2404     fputc ('\n', f);
2405     mutt_copy_bytes (fp, f, h->content->length);
2406     m_fclose(&f);
2407
2408     ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2409                           h->content->encoding == ENC8BIT);
2410   }
2411
2412   if (msg)
2413     mx_close_message (&msg);
2414
2415   return ret;
2416 }
2417
2418 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2419 {
2420   address_t *from;
2421   char resent_from[STRING];
2422   int ret;
2423   char *err;
2424
2425   resent_from[0] = '\0';
2426   from = mutt_default_from ();
2427
2428   rfc822_qualify(from, mutt_fqdn(1));
2429
2430   rfc2047_encode_adrlist(from, "Resent-From");
2431   if (mutt_addrlist_to_idna (from, &err)) {
2432     mutt_error (_("Bad IDN %s while preparing resent-from."), err);
2433     return -1;
2434   }
2435   rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2436
2437 #ifdef USE_NNTP
2438   unset_option (OPTNEWSSEND);
2439 #endif
2440
2441   ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2442
2443   address_list_wipe(&from);
2444
2445   return ret;
2446 }
2447
2448 static void set_noconv_flags (BODY * b, short flag)
2449 {
2450   for (; b; b = b->next) {
2451     if (b->type == TYPEMESSAGE || b->type == TYPEMULTIPART)
2452       set_noconv_flags (b->parts, flag);
2453     else if (b->type == TYPETEXT && b->noconv) {
2454       parameter_setval(&b->parameter, "x-mutt-noconv", flag ? "yes" : NULL);
2455     }
2456   }
2457 }
2458
2459 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2460                     int post, char *fcc)
2461 {
2462   CONTEXT f;
2463   MESSAGE *msg;
2464   char tempfile[_POSIX_PATH_MAX];
2465   FILE *tempfp = NULL;
2466   int r;
2467
2468   if (post)
2469     set_noconv_flags (hdr->content, 1);
2470
2471   if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2472     return (-1);
2473   }
2474
2475   /* We need to add a Content-Length field to avoid problems where a line in
2476    * the message body begins with "From "   
2477    */
2478   if (f.magic == M_MBOX) {
2479     tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2480     if (!tempfp) {
2481       mutt_error(_("Could not create temporary file"));
2482       mx_close_mailbox (&f, NULL);
2483       return -1;
2484     }
2485   }
2486
2487   hdr->read = !post;            /* make sure to put it in the `cur' directory (maildir) */
2488   if ((msg = mx_open_new_message (&f, hdr, M_ADD_FROM)) == NULL) {
2489     mx_close_mailbox (&f, NULL);
2490     return (-1);
2491   }
2492
2493   /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
2494    * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header() 
2495    * */
2496   mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
2497
2498   /* (postponment) if this was a reply of some sort, <msgid> contians the
2499    * Message-ID: of message replied to.  Save it using a special X-Mutt-
2500    * header so it can be picked up if the message is recalled at a later
2501    * point in time.  This will allow the message to be marked as replied if
2502    * the same mailbox is still open.
2503    */
2504   if (post && msgid)
2505     fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2506
2507   /* (postponment) save the Fcc: using a special X-Mutt- header so that
2508    * it can be picked up when the message is recalled 
2509    */
2510   if (post && fcc)
2511     fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2512   fprintf (msg->fp, "Status: RO\n");
2513
2514   /* (postponment) if the mail is to be signed or encrypted, save this info */
2515   if (post && (hdr->security & APPLICATION_PGP)) {
2516     fputs ("X-Mutt-PGP: ", msg->fp);
2517     if (hdr->security & ENCRYPT)
2518       fputc ('E', msg->fp);
2519     if (hdr->security & SIGN) {
2520       fputc ('S', msg->fp);
2521       if (PgpSignAs && *PgpSignAs)
2522         fprintf (msg->fp, "<%s>", PgpSignAs);
2523     }
2524     if (hdr->security & INLINE)
2525       fputc ('I', msg->fp);
2526     fputc ('\n', msg->fp);
2527   }
2528
2529   /* (postponment) if the mail is to be signed or encrypted, save this info */
2530   if (post && (hdr->security & APPLICATION_SMIME)) {
2531     fputs ("X-Mutt-SMIME: ", msg->fp);
2532     if (hdr->security & ENCRYPT) {
2533       fputc ('E', msg->fp);
2534       if (SmimeCryptAlg && *SmimeCryptAlg)
2535         fprintf (msg->fp, "C<%s>", SmimeCryptAlg);
2536     }
2537     if (hdr->security & SIGN) {
2538       fputc ('S', msg->fp);
2539       if (SmimeDefaultKey && *SmimeDefaultKey)
2540         fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2541     }
2542     if (hdr->security & INLINE)
2543       fputc ('I', msg->fp);
2544     fputc ('\n', msg->fp);
2545   }
2546
2547   /* (postponement) if the mail is to be sent through a mixmaster 
2548    * chain, save that information
2549    */
2550   if (post && hdr->chain && hdr->chain) {
2551     string_list_t *p;
2552
2553     fputs ("X-Mutt-Mix:", msg->fp);
2554     for (p = hdr->chain; p; p = p->next)
2555       fprintf (msg->fp, " %s", (char *) p->data);
2556
2557     fputc ('\n', msg->fp);
2558   }
2559
2560   if (tempfp) {
2561     char sasha[LONG_STRING];
2562     int lines = 0;
2563
2564     mutt_write_mime_body (hdr->content, tempfp);
2565
2566     /* make sure the last line ends with a newline.  Emacs doesn't ensure
2567      * this will happen, and it can cause problems parsing the mailbox   
2568      * later.
2569      */
2570     fseeko (tempfp, -1, 2);
2571     if (fgetc (tempfp) != '\n') {
2572       fseeko (tempfp, 0, 2);
2573       fputc ('\n', tempfp);
2574     }
2575
2576     fflush (tempfp);
2577     if (ferror (tempfp)) {
2578       m_fclose(&tempfp);
2579       unlink (tempfile);
2580       mx_commit_message (msg, &f);      /* XXX - really? */
2581       mx_close_message (&msg);
2582       mx_close_mailbox (&f, NULL);
2583       return -1;
2584     }
2585
2586     /* count the number of lines */
2587     rewind (tempfp);
2588     while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2589       lines++;
2590     fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2591     fprintf (msg->fp, "Lines: %d\n\n", lines);
2592
2593     /* copy the body and clean up */
2594     rewind (tempfp);
2595     r = mutt_copy_stream (tempfp, msg->fp);
2596     if (m_fclose(&tempfp) != 0)
2597       r = -1;
2598     /* if there was an error, leave the temp version */
2599     if (!r)
2600       unlink (tempfile);
2601   } else {
2602     fputc ('\n', msg->fp);      /* finish off the header */
2603     r = mutt_write_mime_body (hdr->content, msg->fp);
2604   }
2605
2606   if (mx_commit_message (msg, &f) != 0)
2607     r = -1;
2608   mx_close_message (&msg);
2609   mx_close_mailbox (&f, NULL);
2610
2611   if (post)
2612     set_noconv_flags (hdr->content, 0);
2613
2614   return r;
2615 }