2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
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.
10 #include <lib-lib/lib-lib.h>
15 #ifdef HAVE_GNUTLS_OPENSSL_H
16 #define SSL_SESSION_ASN1_VERSION
17 #include <gnutls/openssl.h>
19 #include <auth-client.h>
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>
34 #include "recvattach.h"
38 #include "mutt_idna.h"
41 #include <nntp/nntp.h>
44 #ifdef HAVE_SYSEXITS_H
46 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
51 static char authpass[STRING] = "";
58 #define MSGFAIL(msg) \
60 mutt_error("%s", msg); \
63 #define LIBCFAIL(msg) \
65 mutt_error("%s: %s", msg, strerror(errno)); \
68 #define SMTPFAIL(msg) \
70 _send_smtp_perror(msg); \
73 #define extna(msg) { mutt_error (_("SMTP Extension '%s' not supported by MTA."), \
77 * _send_smtp_ensure_init
78 * Make sure the libESMTP support in mutt is initialized at some time.
80 static void _send_smtp_ensure_init ()
82 static int libesmtp_init = 0;
93 * Prints 'msg', a colon, and then a string representation of the
94 * libesmtp errno as a mutt error.
96 static void _send_smtp_perror (const char *msg)
100 mutt_error ("%s: %s", msg,
101 smtp_strerror (smtp_errno (), buf, sizeof (buf)));
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).
111 * Very similar to sendlib.c::add_args
114 _send_smtp_add_recipients (smtp_message_t message, address_t * addr)
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");
131 _send_smtp_auth_interact (auth_client_request_t request,
132 char **result, int fields,
133 void *arg __attribute__ ((unused)))
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) {
142 result[i] = SmtpAuthPass;
144 if (authpass[0] == '\0') {
147 snprintf(prompt, sizeof(prompt), "%s%s: ", request[i].prompt,
148 request[i].flags & AUTH_CLEARTEXT ? " (not encrypted)" :
150 mutt_get_field_unbuffered(prompt, authpass, sizeof(authpass),
153 result[i] = authpass;
164 _send_smtp_messagefp_cb(void **buf, int *len, void *arg)
169 *buf = xmalloc(BUFLEN);
172 rewind ((FILE *) arg);
176 if (fgets (*buf, BUFLEN - 2, (FILE *) arg) == NULL) {
180 char *p = strchr (*buf, '\0');
182 if (p[-1] == '\n' && p[-2] != '\r') {
183 m_strcpy(p - 1, (char *) *buf + BUFLEN - p + 1, "\r\n");
186 octets = p - (char *) *buf;
193 static void event_cb (smtp_session_t session __attribute__ ((unused)),
194 int event_no, void *arg,...)
199 va_start(alist, arg);
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: {
209 bits = va_arg(alist, long); ok = va_arg(alist, int*);
210 mutt_message (_("SMTP_EV_WEAK_CIPHER, bits=%d - accepted."), bits);
214 case SMTP_EV_STARTTLS_OK:
215 mutt_message (_("Using TLS"));
218 case SMTP_EV_INVALID_PEER_CERTIFICATE: {
220 vfy_result = va_arg(alist, long); ok = va_arg(alist, int*);
221 mutt_error (_("Error verifying certificate. Error Code: %lu"), vfy_result);
226 case SMTP_EV_NO_PEER_CERTIFICATE: {
227 ok = va_arg(alist, int*);
228 mutt_message (_("SMTP_EV_NO_PEER_CERTIFICATE - accepted."));
232 case SMTP_EV_WRONG_PEER_CERTIFICATE: {
233 ok = va_arg(alist, int*);
234 mutt_message (_("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted."));
238 case SMTP_EV_NO_CLIENT_CERTIFICATE: {
239 ok = va_arg(alist, int*);
240 mutt_message (_("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted."));
244 case SMTP_EV_EXTNA_DSN:
247 case SMTP_EV_EXTNA_STARTTLS:
250 case SMTP_EV_EXTNA_8BITMIME:
254 mutt_message(_("Got unhandled event ID = %d - ignored."), event_no);
260 static void do_dsn_notify (smtp_message_t message, const char* from) {
261 int flags = Notify_NOTSET;
262 smtp_recipient_t self = NULL;
264 if (m_strisempty(MTransport.dsn_notify) || !message || m_strisempty(from) ||
265 strstr (MTransport.dsn_notify, "never") != NULL)
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;
275 if (flags != Notify_NOTSET) {
276 if (!(self = smtp_add_recipient (message, from)))
278 smtp_dsn_set_notify (self, flags);
282 static void do_dsn_ret (smtp_message_t message) {
283 if (m_strisempty(MTransport.dsn_return) || !message)
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);
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))
296 if (m_strncmp(val, "enabled", 7) != 0 &&
297 m_strncmp(val, "required", 8) != 0) {
299 snprintf (errbuf, errlen, _("'%s' is invalid for %s"), val, option);
307 * Sends a mail message to the provided recipients using libesmtp.
308 * Returns 0 upon success, -1 upon failure (and prints an error
312 send_smtp_invoke(address_t *from, address_t *to, address_t *cc,
313 address_t *bcc, const char *msg, int eightbit)
316 smtp_session_t session;
317 smtp_message_t message;
318 char *hostportstr = NULL;
321 auth_context_t authctx = NULL;
322 const smtp_status_t *status;
323 char* envfrom = from->mailbox;
325 _send_smtp_ensure_init ();
327 if ((session = smtp_create_session ()) == NULL)
328 SMTPFAIL ("smtp_create_session");
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);
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");
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);
352 if (!smtp_auth_set_context (session, authctx))
353 SMTPFAIL ("smtp_auth_set_context");
356 #ifdef HAVE_GNUTLS_OPENSSL_H
357 smtp_starttls_set_ctx (session, NULL);
359 smtp_set_eventcb (session, event_cb, NULL);
361 if ((message = smtp_add_message (session)) == NULL)
362 SMTPFAIL ("smtp_add_message");
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");
370 /* set up DSN for message */
371 do_dsn_notify (message, envfrom);
372 do_dsn_ret (message);
374 /* set up 8bitmime flag */
375 if (eightbit && MTransport.use_8bitmime)
376 smtp_8bitmime_set_body (message, E8bitmime_8BITMIME);
378 if ((fp = fopen (msg, "r")) == NULL)
380 if (!smtp_set_messagecb (message, _send_smtp_messagefp_cb, fp))
381 SMTPFAIL ("smtp_set_messagecb");
382 if (_send_smtp_add_recipients (message, to))
384 if (_send_smtp_add_recipients (message, cc))
386 if (_send_smtp_add_recipients (message, bcc))
388 if (!smtp_start_session (session))
389 SMTPFAIL ("smtp_start_session");
391 status = smtp_message_transfer_status (message);
392 if (status->code < 200 || status->code > 299) {
395 snprintf (buf, sizeof (buf), "SMTP error while sending: %d %s",
396 status->code, status->text);
402 if (hostportstr != NULL)
403 p_delete(&hostportstr);
405 smtp_destroy_session (session);
407 auth_destroy_context (authctx);
409 /* Forget user-entered SMTP AUTH password if send fails */
417 static void transform_to_7bit (BODY * a, FILE * fpin);
419 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
422 char line[77], savechar;
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
431 if (line[linelen - 3] == '=') {
432 line[linelen - 3] = 0;
437 line[1] = line[linelen - 2];
438 line[2] = line[linelen - 1];
442 savechar = line[linelen - 1];
443 line[linelen - 1] = '=';
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");
457 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
458 m_strcpy(line, sizeof(line), "=66rom");
461 else if (linelen == 1 && line[0] == '.') {
462 m_strcpy(line, sizeof(line), "=2E");
467 if (c == '\n' && istext) {
468 /* Check to make sure there is no trailing space on this line. */
470 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
472 sprintf (line + linelen - 1, "=%2.2X",
473 (unsigned char) line[linelen - 1]);
477 savechar = line[linelen - 1];
479 line[linelen - 1] = '=';
482 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
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.
497 line[linelen++] = '=';
503 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
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.
514 /* Take care of anything left in the buffer */
516 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
517 /* take care of trailing whitespace */
519 sprintf (line + linelen - 1, "=%2.2X",
520 (unsigned char) line[linelen - 1]);
522 savechar = line[linelen - 1];
523 line[linelen - 1] = '=';
527 sprintf (line, "=%2.2X", (unsigned char) savechar);
536 static char b64_buffer[3];
537 static short b64_num;
538 static short b64_linelen;
540 static void b64_flush (FILE * fout)
547 if (b64_linelen >= 72) {
552 for (i = b64_num; i < 3; i++)
553 b64_buffer[i] = '\0';
555 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
558 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
563 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
567 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
572 while (b64_linelen % 4) {
581 static void b64_putc (char c, FILE * fout)
586 b64_buffer[b64_num++] = c;
590 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
594 b64_num = b64_linelen = 0;
596 while ((ch = fgetconv (fc)) != EOF) {
597 if (istext && ch == '\n' && ch1 != '\r')
598 b64_putc ('\r', fout);
606 static void encode_8bit (fgetconv_t * fc, FILE * fout,
607 int istext __attribute__ ((unused)))
611 while ((ch = fgetconv (fc)) != EOF)
616 int mutt_write_mime_header (BODY * a, FILE * f)
625 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
630 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
632 for (p = a->parameter; p; p = p->next) {
641 tmp = m_strdup(p->value);
642 encode = rfc2231_encode_string (&tmp);
643 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
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.
650 if (!ascii_strcasecmp (p->attribute, "boundary")
651 && !strcmp (buffer, tmp))
652 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
656 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
658 if (len + tmplen + 2 > 76) {
667 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
675 fprintf (f, "Content-Description: %s\n", a->description);
677 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
678 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
681 if (!(fn = a->d_filename))
687 /* Strip off the leading path... */
688 if ((t = strrchr (fn, '/')))
695 encode = rfc2231_encode_string (&tmp);
696 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
698 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
704 if (a->encoding != ENC7BIT)
705 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
707 /* Do NOT add the terminator here!!! */
708 return (ferror (f) ? -1 : 0);
711 int mutt_write_mime_body (BODY * a, FILE * f)
714 char boundary[STRING];
715 char send_charset[STRING];
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]");
727 m_strcpy(boundary, sizeof(boundary), p);
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)
734 if (mutt_write_mime_body (t, f) == -1)
737 fprintf (f, "\n--%s--\n", boundary);
738 return (ferror (f) ? -1 : 0);
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);
747 if ((fpin = fopen (a->filename, "r")) == NULL) {
748 mutt_error (_("%s no longer exists!"), a->filename);
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);
757 fc = fgetconv_open (fpin, 0, 0, 0);
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));
767 mutt_copy_stream (fpin, f);
768 #undef write_as_text_part
770 fgetconv_close (&fc);
773 return (ferror (f) ? -1 : 0);
785 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
789 int whitespace = s->whitespace;
791 int linelen = s->linelen;
792 int was_cr = s->was_cr;
794 if (!d) { /* This signals EOF */
797 if (linelen > info->linemax)
798 info->linemax = linelen;
803 for (; dlen; d++, dlen--) {
816 if (linelen > info->linemax)
817 info->linemax = linelen;
832 if (linelen > info->linemax)
833 info->linemax = linelen;
838 else if (ch == '\r') {
846 else if (ch == '\t' || ch == '\f') {
850 else if (ch < 32 || ch == 127)
854 if ((ch == 'F') || (ch == 'f'))
864 if (linelen == 2 && ch != 'r')
866 else if (linelen == 3 && ch != 'o')
868 else if (linelen == 4) {
881 if (ch != ' ' && ch != '\t')
886 s->whitespace = whitespace;
888 s->linelen = linelen;
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.
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.
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
909 static ssize_t convert_file_to (FILE * file, const char *fromcode,
910 int ncodes, const char **tocodes,
911 int *tocode, CONTENT * info)
914 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
917 ssize_t ibl, obl, ubl, ubl1, n, ret;
920 CONTENT_STATE *states;
923 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
924 if (cd1 == MUTT_ICONV_ERROR)
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);
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);
936 /* Special case for conversion to UTF-8 */
937 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
943 /* Try to fill input buffer */
944 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
947 /* Convert to UTF-8 */
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)) {
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);
968 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
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);
976 /* Save unused input */
977 memmove (bufi, ib, ibl);
978 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
985 /* Find best score */
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 */
994 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
996 else if (ret == -1 || score[i] < ret) {
1004 memcpy (info, &infos[*tocode], sizeof (CONTENT));
1005 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
1009 for (i = 0; i < ncodes; i++)
1010 if (cd[i] != MUTT_ICONV_ERROR)
1011 iconv_close (cd[i]);
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.
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.
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)
1044 /* Count the tocodes */
1046 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
1047 if ((c1 = strchr (c, ':')) == c)
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)
1057 tcode[i] = m_substrdup(c, c1);
1062 /* Try each fromcode in turn */
1063 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
1064 if ((c1 = strchr (c, ':')) == c)
1066 fcode = m_substrdup(c, c1);
1068 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
1072 *tocode = tcode[cn];
1080 /* There is only one fromcode */
1081 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
1084 *tocode = tcode[cn];
1090 for (i = 0; i < ncodes; i++)
1091 p_delete(&tcode[i]);
1099 * Analyze the contents of a file to determine which MIME encoding to use.
1100 * Also set the body charset, sometimes, or not.
1102 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
1105 CONTENT_STATE state;
1107 char *fromcode = NULL;
1108 char *tocode = NULL;
1110 char chsbuf[STRING];
1116 fname = b->filename;
1118 if (stat (fname, &sb) == -1) {
1119 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
1123 if (!S_ISREG (sb.st_mode)) {
1124 mutt_error (_("%s isn't a regular file."), fname);
1128 if ((fp = fopen (fname, "r")) == NULL) {
1132 info = p_new(CONTENT, 1);
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) {
1143 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
1144 parameter_setval(&b->parameter, "charset", chsbuf);
1146 b->file_charset = fromcode;
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);
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"));
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
1176 int mutt_lookup_mime_type (BODY * att, const char *path)
1180 char buf[LONG_STRING];
1181 char subtype[STRING], xtype[STRING];
1183 int szf, sze, cur_sze;
1191 szf = m_strlen(path);
1193 for (count = 0; count < 4; count++) {
1195 * can't use strtok() because we use it in an inner loop below, so use
1196 * a switch statement here instead.
1200 snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(mod_core.homedir));
1203 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
1206 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
1209 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
1212 goto bye; /* shouldn't happen */
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, '#')))
1221 /* remove any leading space. */
1222 ct = vskipspaces(buf);
1224 /* position on the next field in this line */
1225 if ((p = strpbrk (ct, " \t")) == NULL)
1230 /* cycle through the file extensions */
1231 while ((p = strtok (p, " \t\n"))) {
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] == '.'))
1238 /* get the content-type */
1240 if ((p = strchr (ct, '/')) == NULL) {
1241 /* malformed line, just skip it. */
1246 for (q = p; *q && !ISSPACE (*q); q++);
1248 m_strncpy(subtype, sizeof(subtype), p, q - p);
1250 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1251 m_strcpy(xtype, sizeof(xtype), ct);
1264 if (type != TYPEOTHER || *xtype != '\0') {
1266 m_strreplace(&att->subtype, subtype);
1267 m_strreplace(&att->xtype, xtype);
1273 void mutt_message_to_7bit (BODY * a, FILE * fp)
1275 char temp[_POSIX_PATH_MAX];
1281 if (!a->filename && fp)
1283 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1284 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1289 if (stat (a->filename, &sb) == -1) {
1290 mutt_perror ("stat");
1293 a->length = sb.st_size;
1296 fpout = m_tempfile(temp, sizeof(temp), NONULL(mod_core.tmpdir), NULL);
1298 mutt_error(_("Could not create temporary file"));
1302 fseeko (fpin, a->offset, 0);
1303 a->parts = mutt_parse_messageRFC822 (fpin, a);
1305 transform_to_7bit (a->parts, fpin);
1307 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1308 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
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);
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);
1331 if (stat (a->filename, &sb) == -1) {
1332 mutt_perror ("stat");
1335 a->length = sb.st_size;
1336 body_list_wipe(&a->parts);
1337 a->hdr->content = NULL;
1340 static void transform_to_7bit (BODY * a, FILE * fpin)
1342 char buff[_POSIX_PATH_MAX];
1347 for (; a; a = a->next) {
1348 if (a->type == TYPEMULTIPART) {
1349 if (a->encoding != ENC7BIT)
1350 a->encoding = ENC7BIT;
1352 transform_to_7bit (a->parts, fpin);
1354 else if (mutt_is_message_type(a)) {
1355 mutt_message_to_7bit (a, fpin);
1359 a->force_charset = 1;
1361 s.fpout = m_tempfile(buff, sizeof(buff), NONULL(mod_core.tmpdir), NULL);
1363 mutt_error(_("Could not create temporary file"));
1367 mutt_decode_attachment (a, &s);
1369 a->d_filename = a->filename;
1370 a->filename = m_strdup(buff);
1372 if (stat (a->filename, &sb) == -1) {
1373 mutt_perror ("stat");
1376 a->length = sb.st_size;
1378 mutt_update_encoding (a);
1379 if (a->encoding == ENC8BIT)
1380 a->encoding = ENCQUOTEDPRINTABLE;
1381 else if (a->encoding == ENCBINARY)
1382 a->encoding = ENCBASE64;
1387 /* determine which Content-Transfer-Encoding to use */
1388 static void mutt_set_encoding (BODY * b, CONTENT * info)
1390 char send_charset[STRING];
1392 if (b->type == TYPETEXT) {
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;
1401 b->encoding = ENC7BIT;
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;
1408 mutt_message_to_7bit (b, NULL);
1411 b->encoding = ENC7BIT;
1413 else if (b->type == TYPEAPPLICATION
1414 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1415 b->encoding = ENC7BIT;
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;
1423 b->encoding = ENCQUOTEDPRINTABLE;
1427 void mutt_stamp_attachment (BODY * a)
1429 a->stamp = time (NULL);
1432 /* Get a body's character set */
1434 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1438 if (b && b->type != TYPETEXT)
1441 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1442 charset_canonicalize(d, dlen, p);
1447 /* Assumes called from send mode where BODY->filename points to actual file */
1448 void mutt_update_encoding (BODY * a)
1451 char chsbuff[STRING];
1453 /* override noconv when it's us-ascii */
1454 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1457 if (!a->force_charset && !a->noconv)
1458 parameter_delval(&a->parameter, "charset");
1460 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1463 mutt_set_encoding (a, info);
1464 mutt_stamp_attachment (a);
1466 p_delete(&a->content);
1471 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1473 char buffer[LONG_STRING];
1476 int cmflags, chflags;
1477 int pgp = hdr->security;
1479 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1480 (hdr->security & ENCRYPT)) {
1483 fp = m_tempfile(buffer, sizeof(buffer), NONULL(mod_core.tmpdir), NULL);
1488 body->type = TYPEMESSAGE;
1489 body->subtype = m_strdup("rfc822");
1490 body->filename = m_strdup(buffer);
1493 body->disposition = DISPINLINE;
1496 mutt_parse_mime_message (ctx, hdr);
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);
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;
1513 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1514 chflags |= CH_MIME | CH_TXTPLAIN;
1515 cmflags = M_CM_DECODE | M_CM_CHARCONV;
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;
1525 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
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;
1543 BODY *mutt_make_file_attach (const char *path)
1549 att->filename = m_strdup(path);
1551 /* Attempt to determine the appropriate content-type based on the filename
1554 mutt_lookup_mime_type (att, path);
1556 if ((info = mutt_get_content_info (path, att)) == NULL) {
1557 body_list_wipe(&att);
1561 if (!att->subtype) {
1562 if (info->lobin == 0
1563 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1565 * Statistically speaking, there should be more than 10% "lobin"
1566 * chars if this is really a binary file...
1568 att->type = TYPETEXT;
1569 att->subtype = m_strdup("plain");
1571 att->type = TYPEAPPLICATION;
1572 att->subtype = m_strdup("octet-stream");
1576 mutt_update_encoding (att);
1580 static int get_toplevel_encoding (BODY * a)
1584 for (; a; a = a->next) {
1585 if (a->encoding == ENCBINARY)
1588 if (a->encoding == ENC8BIT)
1595 BODY *mutt_make_multipart (BODY * b)
1600 new->type = TYPEMULTIPART;
1601 new->subtype = m_strdup("mixed");
1602 new->encoding = get_toplevel_encoding (b);
1603 parameter_set_boundary(&new->parameter);
1605 new->disposition = DISPINLINE;
1611 /* remove the multipart body if it exists */
1612 BODY *mutt_remove_multipart (BODY * b)
1625 char *mutt_make_date (char *s, ssize_t len)
1627 time_t t = time (NULL);
1628 struct tm *l = localtime (&t);
1629 time_t tz = mutt_local_tz (t);
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);
1640 /* wrapper around mutt_write_address() so we can handle very large
1641 recipient lists without needing a huge temporary buffer in memory */
1643 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1648 char buf[LONG_STRING];
1649 int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1652 if (linelen + len > 74) {
1654 linelen = 8; /* tab is usually about 8 spaces... */
1656 if (addr->mailbox) {
1666 if (!addr->group && addr->next && addr->next->mailbox) {
1676 /* need to write the list in reverse because they are stored in reverse order
1677 * when parsed to speed up threading
1679 void mutt_write_references(string_list_t *r, FILE *f)
1681 string_list_t *refs[10];
1684 p_clear(refs, countof(refs));
1685 for (i = 0; i < countof(refs) && r; r = r->next) {
1690 fprintf(f, " %s", refs[i]->data);
1694 static int edit_header(int mode, const char *s)
1697 int slen = m_strlen(s);
1699 if (mode != 1 || option(OPTXMAILTO))
1702 p = skipspaces(EditorHeaders);
1704 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1706 p = skipspaces(p + slen);
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.
1716 * Likewise, all IDN processing should happen outside of this routine.
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)
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.
1727 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1728 int mode, int privacy)
1730 char buffer[LONG_STRING];
1732 string_list_t *tmp = env->userhdrs;
1733 int has_agent = 0; /* user defined user-agent header field exists */
1736 if (!option (OPTNEWSSEND))
1738 if (mode == 0 && !privacy)
1739 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
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
1744 if (env->from && !privacy) {
1746 rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1747 fprintf (fp, "From: %s\n", buffer);
1752 mutt_write_address_list (env->to, fp, 4, 0);
1756 if (!option (OPTNEWSSEND))
1758 if (edit_header(mode, "To:"))
1759 fputs ("To:\n", fp);
1763 mutt_write_address_list (env->cc, fp, 4, 0);
1767 if (!option (OPTNEWSSEND))
1769 if (edit_header(mode, "Cc:"))
1770 fputs ("Cc:\n", fp);
1773 if (mode != 0 || option (OPTWRITEBCC)) {
1774 fputs ("Bcc: ", fp);
1775 mutt_write_address_list (env->bcc, fp, 5, 0);
1780 if (!option (OPTNEWSSEND))
1782 if (edit_header(mode, "Bcc:"))
1783 fputs ("Bcc:\n", fp);
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);
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);
1798 fprintf (fp, "Subject: %s\n", env->subject);
1799 else if (mode == 1 && edit_header(mode, "Subject:"))
1800 fputs ("Subject:\n", fp);
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);
1806 if (env->reply_to) {
1807 fputs ("Reply-To: ", fp);
1808 mutt_write_address_list (env->reply_to, fp, 10, 0);
1810 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1811 fputs ("Reply-To:\n", fp);
1813 if (env->mail_followup_to)
1815 if (!option (OPTNEWSSEND))
1818 fputs ("Mail-Followup-To: ", fp);
1819 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1823 if (env->references) {
1824 fputs ("References:", fp);
1825 mutt_write_references (env->references, fp);
1829 /* Add the MIME headers */
1830 fputs ("MIME-Version: 1.0\n", fp);
1831 mutt_write_mime_header (attach, fp);
1834 if (env->in_reply_to) {
1835 fputs ("In-Reply-To:", fp);
1836 mutt_write_references (env->in_reply_to, fp);
1840 /* Add any user defined headers */
1841 for (; tmp; tmp = tmp->next) {
1842 if ((p = strchr (tmp->data, ':'))) {
1843 p = vskipspaces(p + 1);
1845 continue; /* don't emit empty fields. */
1847 /* check to see if the user has overridden the user-agent field */
1848 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1854 fputs (tmp->data, fp);
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);
1864 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1868 return (ferror (fp) == 0 ? 0 : -1);
1871 static void encode_headers (string_list_t * h)
1877 for (; h; h = h->next) {
1878 if (!(p = strchr (h->data, ':')))
1882 p = vskipspaces(p + 1);
1888 rfc2047_encode_string (&tmp);
1889 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1891 sprintf (h->data + i, ": %s", NONULL (tmp));
1897 const char *mutt_fqdn(short may_hide_host)
1901 if (mod_core.hostname && mod_core.hostname[0] != '@') {
1902 p = mod_core.hostname;
1904 if (may_hide_host && option (OPTHIDDENHOST)) {
1905 if ((p = strchr(mod_core.hostname, '.')))
1908 /* sanity check: don't hide the host if
1909 the fqdn is something like detebe.org. */
1911 if (!p || !(q = strchr(p, '.')))
1912 p = mod_core.hostname;
1919 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1921 #define APPEND_FMT(fmt, arg) \
1923 int snlen = snprintf(buf, len, fmt, arg); \
1928 #define APPEND_BYTE(c) \
1942 static char MsgIdPfx = 'A';
1946 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1947 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1955 APPEND_FMT("%02d", tm->tm_mday);
1958 APPEND_FMT("%02d", tm->tm_hour);
1961 APPEND_FMT("%02d", tm->tm_mon + 1);
1964 APPEND_FMT("%02d", tm->tm_min);
1967 APPEND_FMT("%lo", (unsigned long)now);
1970 APPEND_FMT("%u", (unsigned int)getpid());
1973 APPEND_FMT("%c", MsgIdPfx);
1974 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1977 APPEND_FMT("%u", (unsigned int)rand());
1980 APPEND_FMT("%x", (unsigned int)rand());
1983 APPEND_FMT("%02d", tm->tm_sec);
1986 APPEND_FMT("%u", (unsigned int) now);
1989 APPEND_FMT("%x", (unsigned int) now);
1991 case 'Y': /* this will break in the year 10000 ;-) */
1992 APPEND_FMT("%04d", tm->tm_year + 1900);
1997 default: /* invalid formats are replaced by '.' */
2008 static char *mutt_gen_msgid (void)
2011 char localpart[STRING];
2014 if (!(fqdn = mutt_fqdn(0)))
2015 fqdn = NONULL(mod_core.shorthost);
2017 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
2018 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
2019 return m_strdup(buf);
2022 static void alarm_handler (int sig __attribute__ ((unused)))
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
2035 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
2041 mutt_block_signals_system ();
2044 /* we also don't want to be stopped right now */
2045 sigaddset (&set, SIGTSTP);
2046 sigprocmask (SIG_BLOCK, &set, NULL);
2048 if (MTransport.sendmail_wait >= 0) {
2049 char tmp[_POSIX_PATH_MAX];
2052 *tempfile = m_strdup(tmp);
2055 if ((pid = fork ()) == 0) {
2056 struct sigaction act, oldalrm;
2058 /* save parent's ID before setsid() */
2061 /* we want the delivery to continue even after the main process dies,
2062 * so we put ourselves into another session right away
2066 /* next we close all open files */
2067 for (fd = 0; fd < getdtablesize(); fd++)
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) {
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) <
2084 /* redirect stderr to *tempfile too */
2088 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
2090 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
2094 execv (path, (char**)args);
2097 else if (pid == -1) {
2103 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
2104 * sendmail_wait = 0: wait forever
2105 * sendmail_wait < 0: don't wait
2107 if (MTransport.sendmail_wait > 0) {
2109 act.sa_handler = alarm_handler;
2111 /* need to make sure waitpid() is interrupted on SIGALRM */
2112 act.sa_flags = SA_INTERRUPT;
2116 sigemptyset (&act.sa_mask);
2117 sigaction (SIGALRM, &act, &oldalrm);
2118 alarm (MTransport.sendmail_wait);
2120 else if (MTransport.sendmail_wait < 0)
2121 _exit (0xff & EX_OK);
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 */
2130 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
2131 if (MTransport.sendmail_wait > 0) {
2137 /* reset alarm; not really needed, but... */
2139 sigaction (SIGALRM, &oldalrm, NULL);
2141 if (kill (ppid, 0) == -1 && errno == ESRCH) {
2142 /* the parent is already dead */
2150 sigprocmask (SIG_UNBLOCK, &set, NULL);
2152 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2153 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2155 st = S_ERR; /* error */
2157 mutt_unblock_signals_system (1);
2162 static const char **
2163 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
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;
2176 static const char **
2177 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
2179 if (*argslen == *argsmax) {
2180 p_realloc(&args, *argsmax += 5);
2182 args[(*argslen)++] = s;
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 */
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;
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);
2208 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
2213 while ((ps = strtok(ps, " "))) {
2214 if (argslen == argsmax)
2215 p_realloc(&args, argsmax += 5);
2218 args[argslen++] = ps;
2220 path = m_strdup(ps);
2221 ps = strrchr (ps, '/');
2226 args[argslen++] = ps;
2233 if (!option (OPTNEWSSEND)) {
2235 if (eightbit && MTransport.use_8bitmime)
2236 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2238 if (MTransport.use_envelope_from) {
2239 address_t *f = MTransport.envelope_from_address;
2240 if (!f && from && !from->next)
2243 args = add_option (args, &argslen, &argsmax, "-f");
2244 args = add_args (args, &argslen, &argsmax, f);
2247 if (MTransport.dsn_notify) {
2248 args = add_option (args, &argslen, &argsmax, "-N");
2249 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
2251 if (MTransport.dsn_return) {
2252 args = add_option (args, &argslen, &argsmax, "-R");
2253 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
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);
2263 if (argslen >= argsmax)
2264 p_realloc(&args, ++argsmax);
2266 args[argslen++] = NULL;
2268 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2270 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2275 if (!stat(childout, &st) && st.st_size > 0)
2276 mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
2283 p_delete(&childout);
2287 if (i == (EX_OK & 0xff))
2289 else if (i == S_BKG)
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 */
2300 { /* message contains 8bit chars */
2303 if (!option (OPTNEWSSEND))
2306 return send_smtp_invoke (from, to, cc, bcc, msg, eightbit);
2309 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2312 /* For postponing (!final) do the necessary encodings only */
2313 void mutt_prepare_envelope (ENVELOPE * env, int 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.
2321 env->to = address_new();
2323 env->to->next = address_new();
2324 env->to->mailbox = m_strdup("undisclosed-recipients");
2327 mutt_set_followup_to(env);
2329 if (!env->message_id && !m_strisempty(MsgIdFormat))
2330 env->message_id = mutt_gen_msgid();
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");
2342 rfc2047_encode_string (&env->subject);
2343 encode_headers (env->userhdrs);
2346 void mutt_unprepare_envelope (ENVELOPE * env)
2348 string_list_t *item;
2350 for (item = env->userhdrs; item; item = item->next)
2351 rfc2047_decode(&item->data);
2353 address_list_wipe(&env->mail_followup_to);
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);
2364 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2365 const char *resent_from, address_t * env_from)
2369 char date[STRING], tempfile[_POSIX_PATH_MAX];
2370 MESSAGE *msg = NULL;
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)
2377 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2382 /* If we failed to open a message, return with error */
2383 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2389 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2391 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2393 if (!option (OPTBOUNCEDELIVERED))
2394 ch_flags |= CH_WEED_DELIVERED;
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);
2405 mutt_copy_bytes (fp, f, h->content->length);
2408 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2409 h->content->encoding == ENC8BIT);
2413 mx_close_message (&msg);
2418 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2421 char resent_from[STRING];
2425 resent_from[0] = '\0';
2426 from = mutt_default_from ();
2428 rfc822_qualify(from, mutt_fqdn(1));
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);
2435 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2438 unset_option (OPTNEWSSEND);
2441 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2443 address_list_wipe(&from);
2448 static void set_noconv_flags (BODY * b, short flag)
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);
2459 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2460 int post, char *fcc)
2464 char tempfile[_POSIX_PATH_MAX];
2465 FILE *tempfp = NULL;
2469 set_noconv_flags (hdr->content, 1);
2471 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2475 /* We need to add a Content-Length field to avoid problems where a line in
2476 * the message body begins with "From "
2478 if (f.magic == M_MBOX) {
2479 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2481 mutt_error(_("Could not create temporary file"));
2482 mx_close_mailbox (&f, NULL);
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);
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()
2496 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
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.
2505 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2507 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2508 * it can be picked up when the message is recalled
2511 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2512 fprintf (msg->fp, "Status: RO\n");
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);
2524 if (hdr->security & INLINE)
2525 fputc ('I', msg->fp);
2526 fputc ('\n', msg->fp);
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);
2537 if (hdr->security & SIGN) {
2538 fputc ('S', msg->fp);
2539 if (SmimeDefaultKey && *SmimeDefaultKey)
2540 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2542 if (hdr->security & INLINE)
2543 fputc ('I', msg->fp);
2544 fputc ('\n', msg->fp);
2547 /* (postponement) if the mail is to be sent through a mixmaster
2548 * chain, save that information
2550 if (post && hdr->chain && hdr->chain) {
2553 fputs ("X-Mutt-Mix:", msg->fp);
2554 for (p = hdr->chain; p; p = p->next)
2555 fprintf (msg->fp, " %s", (char *) p->data);
2557 fputc ('\n', msg->fp);
2561 char sasha[LONG_STRING];
2564 mutt_write_mime_body (hdr->content, tempfp);
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
2570 fseeko (tempfp, -1, 2);
2571 if (fgetc (tempfp) != '\n') {
2572 fseeko (tempfp, 0, 2);
2573 fputc ('\n', tempfp);
2577 if (ferror (tempfp)) {
2580 mx_commit_message (msg, &f); /* XXX - really? */
2581 mx_close_message (&msg);
2582 mx_close_mailbox (&f, NULL);
2586 /* count the number of lines */
2588 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2590 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2591 fprintf (msg->fp, "Lines: %d\n\n", lines);
2593 /* copy the body and clean up */
2595 r = mutt_copy_stream (tempfp, msg->fp);
2596 if (m_fclose(&tempfp) != 0)
2598 /* if there was an error, leave the temp version */
2602 fputc ('\n', msg->fp); /* finish off the header */
2603 r = mutt_write_mime_body (hdr->content, msg->fp);
2606 if (mx_commit_message (msg, &f) != 0)
2608 mx_close_message (&msg);
2609 mx_close_mailbox (&f, NULL);
2612 set_noconv_flags (hdr->content, 0);