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 #include <auth-client.h>
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>
30 #include "recvattach.h"
34 #include "mutt_idna.h"
37 #include <nntp/nntp.h>
40 #ifdef HAVE_SYSEXITS_H
42 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
47 static char authpass[STRING] = "";
54 #define MSGFAIL(msg) \
56 mutt_error("%s", msg); \
59 #define LIBCFAIL(msg) \
61 mutt_error("%s: %s", msg, strerror(errno)); \
64 #define SMTPFAIL(msg) \
66 _send_smtp_perror(msg); \
69 #define extna(msg) { mutt_error (_("SMTP Extension '%s' not supported by MTA."), \
73 * _send_smtp_ensure_init
74 * Make sure the libESMTP support in mutt is initialized at some time.
76 static void _send_smtp_ensure_init ()
78 static int libesmtp_init = 0;
89 * Prints 'msg', a colon, and then a string representation of the
90 * libesmtp errno as a mutt error.
92 static void _send_smtp_perror (const char *msg)
96 mutt_error ("%s: %s", msg,
97 smtp_strerror (smtp_errno (), buf, sizeof (buf)));
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).
107 * Very similar to sendlib.c::add_args
110 _send_smtp_add_recipients (smtp_message_t message, address_t * addr)
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");
127 _send_smtp_auth_interact (auth_client_request_t request,
128 char **result, int fields,
129 void *arg __attribute__ ((unused)))
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) {
138 result[i] = SmtpAuthPass;
140 if (authpass[0] == '\0') {
143 snprintf(prompt, sizeof(prompt), "%s%s: ", request[i].prompt,
144 request[i].flags & AUTH_CLEARTEXT ? " (not encrypted)" :
146 mutt_get_field_unbuffered(prompt, authpass, sizeof(authpass),
149 result[i] = authpass;
160 _send_smtp_messagefp_cb(void **buf, int *len, void *arg)
165 *buf = xmalloc(BUFLEN);
168 rewind ((FILE *) arg);
172 if (fgets (*buf, BUFLEN - 2, (FILE *) arg) == NULL) {
176 char *p = strchr (*buf, '\0');
178 if (p[-1] == '\n' && p[-2] != '\r') {
179 m_strcpy(p - 1, (char *) *buf + BUFLEN - p + 1, "\r\n");
182 octets = p - (char *) *buf;
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)));
194 mutt_error (_("Error verifying certificate. Error Code: %lu"), vfy_result);
197 return 1; /* Accept the problem */
200 static void event_cb (smtp_session_t session __attribute__ ((unused)),
201 int event_no, void *arg,...)
206 va_start(alist, arg);
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: {
216 bits = va_arg(alist, long); ok = va_arg(alist, int*);
217 mutt_message (_("SMTP_EV_WEAK_CIPHER, bits=%d - accepted."), bits);
221 case SMTP_EV_STARTTLS_OK:
222 mutt_message (_("Using TLS"));
225 case SMTP_EV_INVALID_PEER_CERTIFICATE: {
227 vfy_result = va_arg(alist, long); ok = va_arg(alist, int*);
228 *ok = handle_invalid_peer_certificate(vfy_result);
232 case SMTP_EV_NO_PEER_CERTIFICATE: {
233 ok = va_arg(alist, int*);
234 mutt_message (_("SMTP_EV_NO_PEER_CERTIFICATE - accepted."));
238 case SMTP_EV_WRONG_PEER_CERTIFICATE: {
239 ok = va_arg(alist, int*);
240 mutt_message (_("SMTP_EV_WRONG_PEER_CERTIFICATE - accepted."));
244 case SMTP_EV_NO_CLIENT_CERTIFICATE: {
245 ok = va_arg(alist, int*);
246 mutt_message (_("SMTP_EV_NO_CLIENT_CERTIFICATE - accepted."));
250 case SMTP_EV_EXTNA_DSN:
253 case SMTP_EV_EXTNA_STARTTLS:
256 case SMTP_EV_EXTNA_8BITMIME:
260 mutt_message(_("Got unhandled event ID = %d - ignored."), event_no);
266 static void do_dsn_notify (smtp_message_t message, const char* from) {
267 int flags = Notify_NOTSET;
268 smtp_recipient_t self = NULL;
270 if (m_strisempty(MTransport.dsn_notify) || !message || m_strisempty(from) ||
271 strstr (MTransport.dsn_notify, "never") != NULL)
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;
281 if (flags != Notify_NOTSET) {
282 if (!(self = smtp_add_recipient (message, from)))
284 smtp_dsn_set_notify (self, flags);
288 static void do_dsn_ret (smtp_message_t message) {
289 if (m_strisempty(MTransport.dsn_return) || !message)
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);
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))
302 if (m_strncmp(val, "enabled", 7) != 0 &&
303 m_strncmp(val, "required", 8) != 0) {
305 snprintf (errbuf, errlen, _("'%s' is invalid for %s"), val, option);
313 * Sends a mail message to the provided recipients using libesmtp.
314 * Returns 0 upon success, -1 upon failure (and prints an error
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;
327 auth_context_t authctx = NULL;
328 const smtp_status_t *status;
329 char* envfrom = from->mailbox;
331 _send_smtp_ensure_init ();
333 if ((session = smtp_create_session ()) == NULL)
334 SMTPFAIL ("smtp_create_session");
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);
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");
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);
358 if (!smtp_auth_set_context (session, authctx))
359 SMTPFAIL ("smtp_auth_set_context");
362 #ifdef HAVE_GNUTLS_OPENSSL_H
363 smtp_starttls_set_ctx (session, NULL);
365 smtp_set_eventcb (session, event_cb, NULL);
367 if ((message = smtp_add_message (session)) == NULL)
368 SMTPFAIL ("smtp_add_message");
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");
376 /* set up DSN for message */
377 do_dsn_notify (message, envfrom);
378 do_dsn_ret (message);
380 /* set up 8bitmime flag */
381 if (eightbit && MTransport.use_8bitmime)
382 smtp_8bitmime_set_body (message, E8bitmime_8BITMIME);
384 if ((fp = fopen (msg, "r")) == NULL)
386 if (!smtp_set_messagecb (message, _send_smtp_messagefp_cb, fp))
387 SMTPFAIL ("smtp_set_messagecb");
388 if (_send_smtp_add_recipients (message, to))
390 if (_send_smtp_add_recipients (message, cc))
392 if (_send_smtp_add_recipients (message, bcc))
394 if (!smtp_start_session (session))
395 SMTPFAIL ("smtp_start_session");
397 status = smtp_message_transfer_status (message);
398 if (status->code < 200 || status->code > 299) {
401 snprintf (buf, sizeof (buf), "SMTP error while sending: %d %s",
402 status->code, status->text);
408 if (hostportstr != NULL)
409 p_delete(&hostportstr);
411 smtp_destroy_session (session);
413 auth_destroy_context (authctx);
415 /* Forget user-entered SMTP AUTH password if send fails */
423 static void transform_to_7bit (BODY * a, FILE * fpin);
425 static void encode_quoted (fgetconv_t * fc, FILE * fout, int istext)
428 char line[77], savechar;
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
437 if (line[linelen - 3] == '=') {
438 line[linelen - 3] = 0;
443 line[1] = line[linelen - 2];
444 line[2] = line[linelen - 1];
448 savechar = line[linelen - 1];
449 line[linelen - 1] = '=';
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");
463 else if (linelen == 4 && !m_strncmp("from", line, 4)) {
464 m_strcpy(line, sizeof(line), "=66rom");
467 else if (linelen == 1 && line[0] == '.') {
468 m_strcpy(line, sizeof(line), "=2E");
473 if (c == '\n' && istext) {
474 /* Check to make sure there is no trailing space on this line. */
476 && (line[linelen - 1] == ' ' || line[linelen - 1] == '\t')) {
478 sprintf (line + linelen - 1, "=%2.2X",
479 (unsigned char) line[linelen - 1]);
483 savechar = line[linelen - 1];
485 line[linelen - 1] = '=';
488 fprintf (fout, "\n=%2.2X", (unsigned char) savechar);
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.
503 line[linelen++] = '=';
509 sprintf (line + linelen, "=%2.2X", (unsigned char) c);
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.
520 /* Take care of anything left in the buffer */
522 if (line[linelen - 1] == ' ' || line[linelen - 1] == '\t') {
523 /* take care of trailing whitespace */
525 sprintf (line + linelen - 1, "=%2.2X",
526 (unsigned char) line[linelen - 1]);
528 savechar = line[linelen - 1];
529 line[linelen - 1] = '=';
533 sprintf (line, "=%2.2X", (unsigned char) savechar);
542 static char b64_buffer[3];
543 static short b64_num;
544 static short b64_linelen;
546 static void b64_flush (FILE * fout)
553 if (b64_linelen >= 72) {
558 for (i = b64_num; i < 3; i++)
559 b64_buffer[i] = '\0';
561 fputc(__m_b64chars[(b64_buffer[0] >> 2) & 0x3f], fout);
564 [((b64_buffer[0] & 0x3) << 4) | ((b64_buffer[1] >> 4) & 0xf)], fout);
569 [((b64_buffer[1] & 0xf) << 2) | ((b64_buffer[2] >> 6) & 0x3)],
573 fputc (__m_b64chars[b64_buffer[2] & 0x3f], fout);
578 while (b64_linelen % 4) {
587 static void b64_putc (char c, FILE * fout)
592 b64_buffer[b64_num++] = c;
596 static void encode_base64 (fgetconv_t * fc, FILE * fout, int istext)
600 b64_num = b64_linelen = 0;
602 while ((ch = fgetconv (fc)) != EOF) {
603 if (istext && ch == '\n' && ch1 != '\r')
604 b64_putc ('\r', fout);
612 static void encode_8bit (fgetconv_t * fc, FILE * fout,
613 int istext __attribute__ ((unused)))
617 while ((ch = fgetconv (fc)) != EOF)
622 int mutt_write_mime_header (BODY * a, FILE * f)
631 fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
636 len = 25 + m_strlen(a->subtype); /* approximate len. of content-type */
638 for (p = a->parameter; p; p = p->next) {
647 tmp = m_strdup(p->value);
648 encode = rfc2231_encode_string (&tmp);
649 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
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.
656 if (!ascii_strcasecmp (p->attribute, "boundary")
657 && !strcmp (buffer, tmp))
658 snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
662 tmplen = m_strlen(buffer) + m_strlen(p->attribute) + 1;
664 if (len + tmplen + 2 > 76) {
673 fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
681 fprintf (f, "Content-Description: %s\n", a->description);
683 #define DISPOSITION(X) X==DISPATTACH?"attachment":"inline"
684 fprintf (f, "Content-Disposition: %s", DISPOSITION (a->disposition));
687 if (!(fn = a->d_filename))
693 /* Strip off the leading path... */
694 if ((t = strrchr (fn, '/')))
701 encode = rfc2231_encode_string (&tmp);
702 rfc822_strcpy(buffer, sizeof(buffer), tmp, MimeSpecials);
704 fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
710 if (a->encoding != ENC7BIT)
711 fprintf (f, "Content-Transfer-Encoding: %s\n", ENCODING (a->encoding));
713 /* Do NOT add the terminator here!!! */
714 return (ferror (f) ? -1 : 0);
717 int mutt_write_mime_body (BODY * a, FILE * f)
720 char boundary[STRING];
721 char send_charset[STRING];
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]");
733 m_strcpy(boundary, sizeof(boundary), p);
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)
740 if (mutt_write_mime_body (t, f) == -1)
743 fprintf (f, "\n--%s--\n", boundary);
744 return (ferror (f) ? -1 : 0);
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);
753 if ((fpin = fopen (a->filename, "r")) == NULL) {
754 mutt_error (_("%s no longer exists!"), a->filename);
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);
763 fc = fgetconv_open (fpin, 0, 0, 0);
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));
773 mutt_copy_stream (fpin, f);
774 #undef write_as_text_part
776 fgetconv_close (&fc);
779 return (ferror (f) ? -1 : 0);
791 static void update_content_info (CONTENT * info, CONTENT_STATE * s, char *d,
795 int whitespace = s->whitespace;
797 int linelen = s->linelen;
798 int was_cr = s->was_cr;
800 if (!d) { /* This signals EOF */
803 if (linelen > info->linemax)
804 info->linemax = linelen;
809 for (; dlen; d++, dlen--) {
822 if (linelen > info->linemax)
823 info->linemax = linelen;
838 if (linelen > info->linemax)
839 info->linemax = linelen;
844 else if (ch == '\r') {
852 else if (ch == '\t' || ch == '\f') {
856 else if (ch < 32 || ch == 127)
860 if ((ch == 'F') || (ch == 'f'))
870 if (linelen == 2 && ch != 'r')
872 else if (linelen == 3 && ch != 'o')
874 else if (linelen == 4) {
887 if (ch != ' ' && ch != '\t')
892 s->whitespace = whitespace;
894 s->linelen = linelen;
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.
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.
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
915 static ssize_t convert_file_to (FILE * file, const char *fromcode,
916 int ncodes, const char **tocodes,
917 int *tocode, CONTENT * info)
920 char bufi[256], bufu[512], bufo[4 * sizeof (bufi)];
923 ssize_t ibl, obl, ubl, ubl1, n, ret;
926 CONTENT_STATE *states;
929 cd1 = mutt_iconv_open ("UTF-8", fromcode, 0);
930 if (cd1 == MUTT_ICONV_ERROR)
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);
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);
942 /* Special case for conversion to UTF-8 */
943 cd[i] = MUTT_ICONV_ERROR, score[i] = -1;
949 /* Try to fill input buffer */
950 n = fread (bufi + ibl, 1, sizeof (bufi) - ibl, file);
953 /* Convert to UTF-8 */
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)) {
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);
974 update_content_info (&infos[i], &states[i], bufo, ob - bufo);
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);
982 /* Save unused input */
983 memmove (bufi, ib, ibl);
984 else if (!ubl1 && ib < bufi + sizeof (bufi)) {
991 /* Find best score */
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 */
1000 else if (cd[i] == MUTT_ICONV_ERROR || score[i] == -1)
1002 else if (ret == -1 || score[i] < ret) {
1010 memcpy (info, &infos[*tocode], sizeof (CONTENT));
1011 update_content_info (info, &states[*tocode], 0, 0); /* EOF */
1015 for (i = 0; i < ncodes; i++)
1016 if (cd[i] != MUTT_ICONV_ERROR)
1017 iconv_close (cd[i]);
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.
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.
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)
1050 /* Count the tocodes */
1052 for (c = tocodes; c; c = c1 ? c1 + 1 : 0) {
1053 if ((c1 = strchr (c, ':')) == c)
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)
1063 tcode[i] = m_substrdup(c, c1);
1068 /* Try each fromcode in turn */
1069 for (c = fromcodes; c; c = c1 ? c1 + 1 : 0) {
1070 if ((c1 = strchr (c, ':')) == c)
1072 fcode = m_substrdup(c, c1);
1074 ret = convert_file_to (file, fcode, ncodes, (const char **) tcode,
1078 *tocode = tcode[cn];
1086 /* There is only one fromcode */
1087 ret = convert_file_to (file, fromcodes, ncodes, (const char **) tcode,
1090 *tocode = tcode[cn];
1096 for (i = 0; i < ncodes; i++)
1097 p_delete(&tcode[i]);
1105 * Analyze the contents of a file to determine which MIME encoding to use.
1106 * Also set the body charset, sometimes, or not.
1108 CONTENT *mutt_get_content_info (const char *fname, BODY * b)
1111 CONTENT_STATE state;
1113 char *fromcode = NULL;
1114 char *tocode = NULL;
1116 char chsbuf[STRING];
1122 fname = b->filename;
1124 if (stat (fname, &sb) == -1) {
1125 mutt_error (_("Can't stat %s: %s"), fname, strerror (errno));
1129 if (!S_ISREG (sb.st_mode)) {
1130 mutt_error (_("%s isn't a regular file."), fname);
1134 if ((fp = fopen (fname, "r")) == NULL) {
1138 info = p_new(CONTENT, 1);
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) {
1149 charset_canonicalize (chsbuf, sizeof (chsbuf), tocode);
1150 parameter_setval(&b->parameter, "charset", chsbuf);
1152 b->file_charset = fromcode;
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);
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"));
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
1182 int mutt_lookup_mime_type (BODY * att, const char *path)
1186 char buf[LONG_STRING];
1187 char subtype[STRING], xtype[STRING];
1189 int szf, sze, cur_sze;
1197 szf = m_strlen(path);
1199 for (count = 0; count < 4; count++) {
1201 * can't use strtok() because we use it in an inner loop below, so use
1202 * a switch statement here instead.
1206 snprintf(buf, sizeof (buf), "%s/.mime.types", NONULL(mod_core.homedir));
1209 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/madmutt-mime.types");
1212 m_strcpy(buf, sizeof(buf), PKGDATADIR "/mime.types");
1215 m_strcpy(buf, sizeof(buf), SYSCONFDIR "/mime.types");
1218 goto bye; /* shouldn't happen */
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, '#')))
1227 /* remove any leading space. */
1228 ct = vskipspaces(buf);
1230 /* position on the next field in this line */
1231 if ((p = strpbrk (ct, " \t")) == NULL)
1236 /* cycle through the file extensions */
1237 while ((p = strtok (p, " \t\n"))) {
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] == '.'))
1244 /* get the content-type */
1246 if ((p = strchr (ct, '/')) == NULL) {
1247 /* malformed line, just skip it. */
1252 for (q = p; *q && !ISSPACE (*q); q++);
1254 m_strncpy(subtype, sizeof(subtype), p, q - p);
1256 if ((type = mutt_check_mime_type (ct)) == TYPEOTHER)
1257 m_strcpy(xtype, sizeof(xtype), ct);
1270 if (type != TYPEOTHER || *xtype != '\0') {
1272 m_strreplace(&att->subtype, subtype);
1273 m_strreplace(&att->xtype, xtype);
1279 void mutt_message_to_7bit (BODY * a, FILE * fp)
1281 char temp[_POSIX_PATH_MAX];
1287 if (!a->filename && fp)
1289 else if (!a->filename || !(fpin = fopen (a->filename, "r"))) {
1290 mutt_error (_("Could not open %s"), a->filename ? a->filename : "(null)");
1295 if (stat (a->filename, &sb) == -1) {
1296 mutt_perror ("stat");
1299 a->length = sb.st_size;
1302 fpout = m_tempfile(temp, sizeof(temp), NONULL(mod_core.tmpdir), NULL);
1304 mutt_error(_("Could not create temporary file"));
1308 fseeko (fpin, a->offset, 0);
1309 a->parts = mutt_parse_messageRFC822 (fpin, a);
1311 transform_to_7bit (a->parts, fpin);
1313 mutt_copy_hdr (fpin, fpout, a->offset, a->offset + a->length,
1314 CH_MIME | CH_NONEWLINE | CH_XMIT, NULL);
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);
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);
1337 if (stat (a->filename, &sb) == -1) {
1338 mutt_perror ("stat");
1341 a->length = sb.st_size;
1342 body_list_wipe(&a->parts);
1343 a->hdr->content = NULL;
1346 static void transform_to_7bit (BODY * a, FILE * fpin)
1348 char buff[_POSIX_PATH_MAX];
1353 for (; a; a = a->next) {
1354 if (a->type == TYPEMULTIPART) {
1355 if (a->encoding != ENC7BIT)
1356 a->encoding = ENC7BIT;
1358 transform_to_7bit (a->parts, fpin);
1360 else if (mutt_is_message_type(a)) {
1361 mutt_message_to_7bit (a, fpin);
1365 a->force_charset = 1;
1367 s.fpout = m_tempfile(buff, sizeof(buff), NONULL(mod_core.tmpdir), NULL);
1369 mutt_error(_("Could not create temporary file"));
1373 mutt_decode_attachment (a, &s);
1375 a->d_filename = a->filename;
1376 a->filename = m_strdup(buff);
1378 if (stat (a->filename, &sb) == -1) {
1379 mutt_perror ("stat");
1382 a->length = sb.st_size;
1384 mutt_update_encoding (a);
1385 if (a->encoding == ENC8BIT)
1386 a->encoding = ENCQUOTEDPRINTABLE;
1387 else if (a->encoding == ENCBINARY)
1388 a->encoding = ENCBASE64;
1393 /* determine which Content-Transfer-Encoding to use */
1394 static void mutt_set_encoding (BODY * b, CONTENT * info)
1396 char send_charset[STRING];
1398 if (b->type == TYPETEXT) {
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;
1407 b->encoding = ENC7BIT;
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;
1414 mutt_message_to_7bit (b, NULL);
1417 b->encoding = ENC7BIT;
1419 else if (b->type == TYPEAPPLICATION
1420 && ascii_strcasecmp (b->subtype, "pgp-keys") == 0)
1421 b->encoding = ENC7BIT;
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;
1429 b->encoding = ENCQUOTEDPRINTABLE;
1433 void mutt_stamp_attachment (BODY * a)
1435 a->stamp = time (NULL);
1438 /* Get a body's character set */
1440 char *mutt_get_body_charset(char *d, ssize_t dlen, BODY * b)
1444 if (b && b->type != TYPETEXT)
1447 p = b ? parameter_getval(b->parameter, "charset") : NULL;
1448 charset_canonicalize(d, dlen, p);
1453 /* Assumes called from send mode where BODY->filename points to actual file */
1454 void mutt_update_encoding (BODY * a)
1457 char chsbuff[STRING];
1459 /* override noconv when it's us-ascii */
1460 if (charset_is_us_ascii (mutt_get_body_charset (chsbuff, sizeof (chsbuff), a)))
1463 if (!a->force_charset && !a->noconv)
1464 parameter_delval(&a->parameter, "charset");
1466 if ((info = mutt_get_content_info (a->filename, a)) == NULL)
1469 mutt_set_encoding (a, info);
1470 mutt_stamp_attachment (a);
1472 p_delete(&a->content);
1477 BODY *mutt_make_message_attach (CONTEXT * ctx, HEADER * hdr, int attach_msg)
1479 char buffer[LONG_STRING];
1482 int cmflags, chflags;
1483 int pgp = hdr->security;
1485 if ((option (OPTMIMEFORWDECODE) || option (OPTFORWDECRYPT)) &&
1486 (hdr->security & ENCRYPT)) {
1489 fp = m_tempfile(buffer, sizeof(buffer), NONULL(mod_core.tmpdir), NULL);
1494 body->type = TYPEMESSAGE;
1495 body->subtype = m_strdup("rfc822");
1496 body->filename = m_strdup(buffer);
1499 body->disposition = DISPINLINE;
1502 mutt_parse_mime_message (ctx, hdr);
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);
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;
1519 else if (mutt_is_application_pgp (hdr->content) & PGPENCRYPT) {
1520 chflags |= CH_MIME | CH_TXTPLAIN;
1521 cmflags = M_CM_DECODE | M_CM_CHARCONV;
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;
1531 mutt_copy_message (fp, ctx, hdr, cmflags, chflags);
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;
1549 BODY *mutt_make_file_attach (const char *path)
1555 att->filename = m_strdup(path);
1557 /* Attempt to determine the appropriate content-type based on the filename
1560 mutt_lookup_mime_type (att, path);
1562 if ((info = mutt_get_content_info (path, att)) == NULL) {
1563 body_list_wipe(&att);
1567 if (!att->subtype) {
1568 if (info->lobin == 0
1569 || (info->lobin + info->hibin + info->ascii) / info->lobin >= 10) {
1571 * Statistically speaking, there should be more than 10% "lobin"
1572 * chars if this is really a binary file...
1574 att->type = TYPETEXT;
1575 att->subtype = m_strdup("plain");
1577 att->type = TYPEAPPLICATION;
1578 att->subtype = m_strdup("octet-stream");
1582 mutt_update_encoding (att);
1586 static int get_toplevel_encoding (BODY * a)
1590 for (; a; a = a->next) {
1591 if (a->encoding == ENCBINARY)
1594 if (a->encoding == ENC8BIT)
1601 BODY *mutt_make_multipart (BODY * b)
1606 new->type = TYPEMULTIPART;
1607 new->subtype = m_strdup("mixed");
1608 new->encoding = get_toplevel_encoding (b);
1609 parameter_set_boundary(&new->parameter);
1611 new->disposition = DISPINLINE;
1617 /* remove the multipart body if it exists */
1618 BODY *mutt_remove_multipart (BODY * b)
1631 char *mutt_make_date (char *s, ssize_t len)
1633 time_t t = time (NULL);
1634 struct tm *l = localtime (&t);
1635 time_t tz = mutt_local_tz (t);
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);
1646 /* wrapper around mutt_write_address() so we can handle very large
1647 recipient lists without needing a huge temporary buffer in memory */
1649 mutt_write_address_list(address_t *addr, FILE *fp, int linelen, int display)
1654 char buf[LONG_STRING];
1655 int len = rfc822_addrcpy(buf, ssizeof(buf), addr, display);
1658 if (linelen + len > 74) {
1660 linelen = 8; /* tab is usually about 8 spaces... */
1662 if (addr->mailbox) {
1672 if (!addr->group && addr->next && addr->next->mailbox) {
1682 /* need to write the list in reverse because they are stored in reverse order
1683 * when parsed to speed up threading
1685 void mutt_write_references(string_list_t *r, FILE *f)
1687 string_list_t *refs[10];
1690 p_clear(refs, countof(refs));
1691 for (i = 0; i < countof(refs) && r; r = r->next) {
1696 fprintf(f, " %s", refs[i]->data);
1700 static int edit_header(int mode, const char *s)
1703 int slen = m_strlen(s);
1705 if (mode != 1 || option(OPTXMAILTO))
1708 p = skipspaces(EditorHeaders);
1710 if (!ascii_strncasecmp(p, s, slen) && p[slen - 1] == ':')
1712 p = skipspaces(p + slen);
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.
1722 * Likewise, all IDN processing should happen outside of this routine.
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)
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.
1733 int mutt_write_rfc822_header (FILE * fp, ENVELOPE * env, BODY * attach,
1734 int mode, int privacy)
1736 char buffer[LONG_STRING];
1738 string_list_t *tmp = env->userhdrs;
1739 int has_agent = 0; /* user defined user-agent header field exists */
1742 if (!option (OPTNEWSSEND))
1744 if (mode == 0 && !privacy)
1745 fputs (mutt_make_date (buffer, sizeof (buffer)), fp);
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
1750 if (env->from && !privacy) {
1752 rfc822_addrcat(buffer, sizeof(buffer), env->from, 0);
1753 fprintf (fp, "From: %s\n", buffer);
1758 mutt_write_address_list (env->to, fp, 4, 0);
1762 if (!option (OPTNEWSSEND))
1764 if (edit_header(mode, "To:"))
1765 fputs ("To:\n", fp);
1769 mutt_write_address_list (env->cc, fp, 4, 0);
1773 if (!option (OPTNEWSSEND))
1775 if (edit_header(mode, "Cc:"))
1776 fputs ("Cc:\n", fp);
1779 if (mode != 0 || option (OPTWRITEBCC)) {
1780 fputs ("Bcc: ", fp);
1781 mutt_write_address_list (env->bcc, fp, 5, 0);
1786 if (!option (OPTNEWSSEND))
1788 if (edit_header(mode, "Bcc:"))
1789 fputs ("Bcc:\n", fp);
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);
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);
1804 fprintf (fp, "Subject: %s\n", env->subject);
1805 else if (mode == 1 && edit_header(mode, "Subject:"))
1806 fputs ("Subject:\n", fp);
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);
1812 if (env->reply_to) {
1813 fputs ("Reply-To: ", fp);
1814 mutt_write_address_list (env->reply_to, fp, 10, 0);
1816 else if (mode > 0 && edit_header(mode, "Reply-To:"))
1817 fputs ("Reply-To:\n", fp);
1819 if (env->mail_followup_to)
1821 if (!option (OPTNEWSSEND))
1824 fputs ("Mail-Followup-To: ", fp);
1825 mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
1829 if (env->references) {
1830 fputs ("References:", fp);
1831 mutt_write_references (env->references, fp);
1835 /* Add the MIME headers */
1836 fputs ("MIME-Version: 1.0\n", fp);
1837 mutt_write_mime_header (attach, fp);
1840 if (env->in_reply_to) {
1841 fputs ("In-Reply-To:", fp);
1842 mutt_write_references (env->in_reply_to, fp);
1846 /* Add any user defined headers */
1847 for (; tmp; tmp = tmp->next) {
1848 if ((p = strchr (tmp->data, ':'))) {
1849 p = vskipspaces(p + 1);
1851 continue; /* don't emit empty fields. */
1853 /* check to see if the user has overridden the user-agent field */
1854 if (!ascii_strncasecmp ("user-agent", tmp->data, 10)) {
1860 fputs (tmp->data, fp);
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);
1870 fprintf(fp, "User-Agent: %s\n", mutt_make_version());
1874 return (ferror (fp) == 0 ? 0 : -1);
1877 static void encode_headers (string_list_t * h)
1883 for (; h; h = h->next) {
1884 if (!(p = strchr (h->data, ':')))
1888 p = vskipspaces(p + 1);
1894 rfc2047_encode_string (&tmp);
1895 p_realloc(&h->data, m_strlen(h->data) + 2 + m_strlen(tmp) + 1);
1897 sprintf (h->data + i, ": %s", NONULL (tmp));
1903 const char *mutt_fqdn(short may_hide_host)
1907 if (mod_core.hostname && mod_core.hostname[0] != '@') {
1908 p = mod_core.hostname;
1910 if (may_hide_host && option (OPTHIDDENHOST)) {
1911 if ((p = strchr(mod_core.hostname, '.')))
1914 /* sanity check: don't hide the host if
1915 the fqdn is something like detebe.org. */
1917 if (!p || !(q = strchr(p, '.')))
1918 p = mod_core.hostname;
1925 static void mutt_gen_localpart(char *buf, unsigned int len, const char *fmt)
1927 #define APPEND_FMT(fmt, arg) \
1929 int snlen = snprintf(buf, len, fmt, arg); \
1934 #define APPEND_BYTE(c) \
1948 static char MsgIdPfx = 'A';
1952 /* normalized character (we're stricter than RFC2822, 3.6.4) */
1953 APPEND_BYTE((isalnum(c) || strchr(".!#$%&'*+-/=?^_`{|}~", c)) ? c : '.');
1961 APPEND_FMT("%02d", tm->tm_mday);
1964 APPEND_FMT("%02d", tm->tm_hour);
1967 APPEND_FMT("%02d", tm->tm_mon + 1);
1970 APPEND_FMT("%02d", tm->tm_min);
1973 APPEND_FMT("%lo", (unsigned long)now);
1976 APPEND_FMT("%u", (unsigned int)getpid());
1979 APPEND_FMT("%c", MsgIdPfx);
1980 MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
1983 APPEND_FMT("%u", (unsigned int)rand());
1986 APPEND_FMT("%x", (unsigned int)rand());
1989 APPEND_FMT("%02d", tm->tm_sec);
1992 APPEND_FMT("%u", (unsigned int) now);
1995 APPEND_FMT("%x", (unsigned int) now);
1997 case 'Y': /* this will break in the year 10000 ;-) */
1998 APPEND_FMT("%04d", tm->tm_year + 1900);
2003 default: /* invalid formats are replaced by '.' */
2014 static char *mutt_gen_msgid (void)
2017 char localpart[STRING];
2020 if (!(fqdn = mutt_fqdn(0)))
2021 fqdn = NONULL(mod_core.shorthost);
2023 mutt_gen_localpart(localpart, sizeof(localpart), MsgIdFormat);
2024 snprintf(buf, sizeof(buf), "<%s@%s>", localpart, fqdn);
2025 return m_strdup(buf);
2028 static void alarm_handler (int sig __attribute__ ((unused)))
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
2041 send_msg(const char *path, const char **args, const char *msg, char **tempfile)
2047 mutt_block_signals_system ();
2050 /* we also don't want to be stopped right now */
2051 sigaddset (&set, SIGTSTP);
2052 sigprocmask (SIG_BLOCK, &set, NULL);
2054 if (MTransport.sendmail_wait >= 0) {
2055 char tmp[_POSIX_PATH_MAX];
2058 *tempfile = m_strdup(tmp);
2061 if ((pid = fork ()) == 0) {
2062 struct sigaction act, oldalrm;
2064 /* save parent's ID before setsid() */
2067 /* we want the delivery to continue even after the main process dies,
2068 * so we put ourselves into another session right away
2072 /* next we close all open files */
2073 for (fd = 0; fd < getdtablesize(); fd++)
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) {
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) <
2090 /* redirect stderr to *tempfile too */
2094 if (open ("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
2096 if (open ("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
2100 execv (path, (char**)args);
2103 else if (pid == -1) {
2109 /* sendmail_wait > 0: interrupt waitpid() after sendmail_wait seconds
2110 * sendmail_wait = 0: wait forever
2111 * sendmail_wait < 0: don't wait
2113 if (MTransport.sendmail_wait > 0) {
2115 act.sa_handler = alarm_handler;
2117 /* need to make sure waitpid() is interrupted on SIGALRM */
2118 act.sa_flags = SA_INTERRUPT;
2122 sigemptyset (&act.sa_mask);
2123 sigaction (SIGALRM, &act, &oldalrm);
2124 alarm (MTransport.sendmail_wait);
2126 else if (MTransport.sendmail_wait < 0)
2127 _exit (0xff & EX_OK);
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 */
2136 st = (MTransport.sendmail_wait > 0 && errno == EINTR && SigAlrm) ? S_BKG : S_ERR;
2137 if (MTransport.sendmail_wait > 0) {
2143 /* reset alarm; not really needed, but... */
2145 sigaction (SIGALRM, &oldalrm, NULL);
2147 if (kill (ppid, 0) == -1 && errno == ESRCH) {
2148 /* the parent is already dead */
2156 sigprocmask (SIG_UNBLOCK, &set, NULL);
2158 if (pid != -1 && waitpid (pid, &st, 0) > 0)
2159 st = WIFEXITED (st) ? WEXITSTATUS (st) : S_ERR; /* return child status */
2161 st = S_ERR; /* error */
2163 mutt_unblock_signals_system (1);
2168 static const char **
2169 add_args(const char **args, ssize_t *argslen, ssize_t *argsmax, address_t * addr)
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;
2182 static const char **
2183 add_option(const char **args, ssize_t *argslen, ssize_t *argsmax, const char *s)
2185 if (*argslen == *argsmax) {
2186 p_realloc(&args, *argsmax += 5);
2188 args[(*argslen)++] = s;
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 */
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;
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);
2214 m_strcpy(cmd, sizeof(cmd), MTransport.sendmail);
2219 while ((ps = strtok(ps, " "))) {
2220 if (argslen == argsmax)
2221 p_realloc(&args, argsmax += 5);
2224 args[argslen++] = ps;
2226 path = m_strdup(ps);
2227 ps = strrchr (ps, '/');
2232 args[argslen++] = ps;
2239 if (!option (OPTNEWSSEND)) {
2241 if (eightbit && MTransport.use_8bitmime)
2242 args = add_option(args, &argslen, &argsmax, "-B8BITMIME");
2244 if (MTransport.use_envelope_from) {
2245 address_t *f = MTransport.envelope_from_address;
2246 if (!f && from && !from->next)
2249 args = add_option (args, &argslen, &argsmax, "-f");
2250 args = add_args (args, &argslen, &argsmax, f);
2253 if (MTransport.dsn_notify) {
2254 args = add_option (args, &argslen, &argsmax, "-N");
2255 args = add_option (args, &argslen, &argsmax, MTransport.dsn_notify);
2257 if (MTransport.dsn_return) {
2258 args = add_option (args, &argslen, &argsmax, "-R");
2259 args = add_option (args, &argslen, &argsmax, MTransport.dsn_return);
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);
2269 if (argslen >= argsmax)
2270 p_realloc(&args, ++argsmax);
2272 args[argslen++] = NULL;
2274 if ((i = send_msg (path, args, msg, &childout)) != (EX_OK & 0xff)) {
2276 mutt_error (_("Error sending message, child exited %d (%s)."), i,
2281 if (!stat(childout, &st) && st.st_size > 0)
2282 mutt_pager(_("Output of the delivery process"), childout, 0, NULL);
2289 p_delete(&childout);
2293 if (i == (EX_OK & 0xff))
2295 else if (i == S_BKG)
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 */
2306 { /* message contains 8bit chars */
2309 if (!option (OPTNEWSSEND))
2312 return send_smtp_invoke (from, to, cc, bcc, msg, eightbit);
2315 return mutt_invoke_sendmail (from, to, cc, bcc, msg, eightbit);
2318 /* For postponing (!final) do the necessary encodings only */
2319 void mutt_prepare_envelope (ENVELOPE * env, int 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.
2327 env->to = address_new();
2329 env->to->next = address_new();
2330 env->to->mailbox = m_strdup("undisclosed-recipients");
2333 mutt_set_followup_to(env);
2335 if (!env->message_id && !m_strisempty(MsgIdFormat))
2336 env->message_id = mutt_gen_msgid();
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");
2348 rfc2047_encode_string (&env->subject);
2349 encode_headers (env->userhdrs);
2352 void mutt_unprepare_envelope (ENVELOPE * env)
2354 string_list_t *item;
2356 for (item = env->userhdrs; item; item = item->next)
2357 rfc2047_decode(&item->data);
2359 address_list_wipe(&env->mail_followup_to);
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);
2370 static int _mutt_bounce_message (FILE * fp, HEADER * h, address_t * to,
2371 const char *resent_from, address_t * env_from)
2375 char date[STRING], tempfile[_POSIX_PATH_MAX];
2376 MESSAGE *msg = NULL;
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)
2383 _mutt_bounce_message (fp, Context->hdrs[i], to, resent_from,
2388 /* If we failed to open a message, return with error */
2389 if (!fp && (msg = mx_open_message (Context, h->msgno)) == NULL)
2395 f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2397 int ch_flags = CH_XMIT | CH_NONEWLINE | CH_NOQFROM;
2399 if (!option (OPTBOUNCEDELIVERED))
2400 ch_flags |= CH_WEED_DELIVERED;
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);
2411 mutt_copy_bytes (fp, f, h->content->length);
2414 ret = mutt_invoke_mta(env_from, to, NULL, NULL, tempfile,
2415 h->content->encoding == ENC8BIT);
2419 mx_close_message (&msg);
2424 int mutt_bounce_message (FILE * fp, HEADER * h, address_t * to)
2427 char resent_from[STRING];
2431 resent_from[0] = '\0';
2432 from = mutt_default_from ();
2434 rfc822_qualify(from, mutt_fqdn(1));
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);
2441 rfc822_addrcat(resent_from, sizeof(resent_from), from, 0);
2444 unset_option (OPTNEWSSEND);
2447 ret = _mutt_bounce_message (fp, h, to, resent_from, from);
2449 address_list_wipe(&from);
2454 static void set_noconv_flags (BODY * b, short flag)
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);
2465 int mutt_write_fcc (const char *path, HEADER * hdr, const char *msgid,
2466 int post, char *fcc)
2470 char tempfile[_POSIX_PATH_MAX];
2471 FILE *tempfp = NULL;
2475 set_noconv_flags (hdr->content, 1);
2477 if (mx_open_mailbox (path, M_APPEND | M_QUIET, &f) == NULL) {
2481 /* We need to add a Content-Length field to avoid problems where a line in
2482 * the message body begins with "From "
2484 if (f.magic == M_MBOX) {
2485 tempfp = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
2487 mutt_error(_("Could not create temporary file"));
2488 mx_close_mailbox (&f, NULL);
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);
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()
2502 mutt_write_rfc822_header(msg->fp, hdr->env, hdr->content, -post, 0);
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.
2511 fprintf (msg->fp, "X-Mutt-References: %s\n", msgid);
2513 /* (postponment) save the Fcc: using a special X-Mutt- header so that
2514 * it can be picked up when the message is recalled
2517 fprintf (msg->fp, "X-Mutt-Fcc: %s\n", fcc);
2518 fprintf (msg->fp, "Status: RO\n");
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);
2530 if (hdr->security & INLINE)
2531 fputc ('I', msg->fp);
2532 fputc ('\n', msg->fp);
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);
2543 if (hdr->security & SIGN) {
2544 fputc ('S', msg->fp);
2545 if (SmimeDefaultKey && *SmimeDefaultKey)
2546 fprintf (msg->fp, "<%s>", SmimeDefaultKey);
2548 if (hdr->security & INLINE)
2549 fputc ('I', msg->fp);
2550 fputc ('\n', msg->fp);
2553 /* (postponement) if the mail is to be sent through a mixmaster
2554 * chain, save that information
2556 if (post && hdr->chain && hdr->chain) {
2559 fputs ("X-Mutt-Mix:", msg->fp);
2560 for (p = hdr->chain; p; p = p->next)
2561 fprintf (msg->fp, " %s", (char *) p->data);
2563 fputc ('\n', msg->fp);
2567 char sasha[LONG_STRING];
2570 mutt_write_mime_body (hdr->content, tempfp);
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
2576 fseeko (tempfp, -1, 2);
2577 if (fgetc (tempfp) != '\n') {
2578 fseeko (tempfp, 0, 2);
2579 fputc ('\n', tempfp);
2583 if (ferror (tempfp)) {
2586 mx_commit_message (msg, &f); /* XXX - really? */
2587 mx_close_message (&msg);
2588 mx_close_mailbox (&f, NULL);
2592 /* count the number of lines */
2594 while (fgets (sasha, sizeof (sasha), tempfp) != NULL)
2596 fprintf (msg->fp, "Content-Length: %zd\n", ftello (tempfp));
2597 fprintf (msg->fp, "Lines: %d\n\n", lines);
2599 /* copy the body and clean up */
2601 r = mutt_copy_stream (tempfp, msg->fp);
2602 if (m_fclose(&tempfp) != 0)
2604 /* if there was an error, leave the temp version */
2608 fputc ('\n', msg->fp); /* finish off the header */
2609 r = mutt_write_mime_body (hdr->content, msg->fp);
2612 if (mx_commit_message (msg, &f) != 0)
2614 mx_close_message (&msg);
2615 mx_close_mailbox (&f, NULL);
2618 set_noconv_flags (hdr->content, 0);