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