[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [msmtp-users] Generating a From: header if it is missing



Hi everyone!

On Wed, 12 Nov 2014 07:20:50 +0100, Martin Lambers wrote:
> currently msmtp never generates a From: header. However,
> sendmail/postfix/exim/ssmtp and probably every other MTA and MTA
> replacement generate such a header if none is present in the mail.
> 
> There are programs that expect this behavior, and msmtp claims
> compatibility with the sendmail interface, so it should do the same.
> 
> My current idea is to use the envelope-from address that msmtp already
> knows, and add a From: header with this address as the last header
> line if no From: header was seen. That way, msmtp can re-use the
> existing functionality of 'auto_from'/'maildomain' (to generate an
> address) and 'from' (to set an address explicitly).
> 
> This is a change in behavior from previous versions, but I expect no
> problems: in most use cases, mails should already have a From: header,
> so nothing changes there. In the remaining use cases, not having a
> From: header is a bug, since per RFC 2822 every mail *must* contain a
> From: header.
> 
> The attached patch implements this, and also supports the sendmail -F
> option for setting a full name for the From: header.
> 
> Do you see problems with this approach? Are there alternative
> suggestions?

It was pointed out that this version is not as robust against malformed
mails (e.g. without headers) as it could be.

Here is a second version, which also adds a Date: header if none is
present (for the same reasons that apply to the From: header). Both
headers are now prepended to the mail instead of inserted at the end of
what msmtp thinks are the mail headers, for robustness.

Thoughts?

Martin
diff --git a/src/msmtp.c b/src/msmtp.c
index 315e7f9..73ac06f 100644
--- a/src/msmtp.c
+++ b/src/msmtp.c
@@ -981,7 +981,7 @@ error_exit:
 
 
 /*
- * msmtp_read_addresses()
+ * msmtp_read_headers()
  *
  * Copies the headers of the mail from 'mailf' to a temporary file 'tmpfile',
  * including the blank line that separates the header from the body of the mail.
@@ -994,6 +994,9 @@ error_exit:
  * If 'from' is not NULL: extracts the address from the From header and stores
  * it in an allocated string. A pointer to this string is stored in 'from'.
  *
+ * If 'have_date' is not NULL: set this flag to 1 if a Date header is present
+ * , and to 0 otherwise.
+ *
  * See RFC2822, section 3 for the format of these headers.
  *
  * Return codes: EX_OK, EX_IOERR
@@ -1007,30 +1010,37 @@ error_exit:
                                                    recipient header */
 #define STATE_OTHER_HDR                 2       /* a header we don't
                                                    care about */
-#define STATE_FROM1                     3       /* we saw "^F" */
-#define STATE_FROM2                     4       /* we saw "^Fr" */
-#define STATE_FROM3                     5       /* we saw "^Fro" */
-#define STATE_TO                        6       /* we saw "^T" */
-#define STATE_CC                        7       /* we saw "^C" */
-#define STATE_BCC1                      8       /* we saw "^B" */
-#define STATE_BCC2                      9       /* we saw "^Bc" */
-#define STATE_ADDRHDR_ALMOST            10      /* we saw "^To", "^Cc"
+#define STATE_DATE1                     3       /* we saw "^D" */
+#define STATE_DATE2                     4       /* we saw "^Da" */
+#define STATE_DATE3                     5       /* we saw "^Dat" */
+#define STATE_DATE4                     6       /* we saw "^Date" */
+#define STATE_FROM1                     7       /* we saw "^F" */
+#define STATE_FROM2                     8       /* we saw "^Fr" */
+#define STATE_FROM3                     9       /* we saw "^Fro" */
+#define STATE_TO                        10      /* we saw "^T" */
+#define STATE_CC                        11      /* we saw "^C" */
+#define STATE_BCC1                      12      /* we saw "^B" */
+#define STATE_BCC2                      13      /* we saw "^Bc" */
+#define STATE_ADDRHDR_ALMOST            14      /* we saw "^To", "^Cc"
                                                    or "^Bcc" */
-#define STATE_RESENT                    11      /* we saw part of "^Resent-" */
-#define STATE_ADDRHDR_DEFAULT           12      /* in_rcpt_hdr and in_rcpt
+#define STATE_RESENT                    15      /* we saw part of "^Resent-" */
+#define STATE_ADDRHDR_DEFAULT           16      /* in_rcpt_hdr and in_rcpt
                                                    state our position */
-#define STATE_ADDRHDR_DQUOTE            13      /* duoble quotes */
-#define STATE_ADDRHDR_BRACKETS_START    14      /* entering <...> */
-#define STATE_ADDRHDR_IN_BRACKETS       15      /* an address inside <> */
-#define STATE_ADDRHDR_PARENTH_START     16      /* entering (...) */
-#define STATE_ADDRHDR_IN_PARENTH        17      /* a comment inside () */
-#define STATE_ADDRHDR_IN_ADDRESS        18      /* a bare address */
-#define STATE_ADDRHDR_BACKQUOTE         19      /* we saw a '\\' */
-#define STATE_HEADERS_END               20      /* we saw "^$", the blank line
+#define STATE_ADDRHDR_DQUOTE            17      /* duoble quotes */
+#define STATE_ADDRHDR_BRACKETS_START    18      /* entering <...> */
+#define STATE_ADDRHDR_IN_BRACKETS       19      /* an address inside <> */
+#define STATE_ADDRHDR_PARENTH_START     20      /* entering (...) */
+#define STATE_ADDRHDR_IN_PARENTH        21      /* a comment inside () */
+#define STATE_ADDRHDR_IN_ADDRESS        22      /* a bare address */
+#define STATE_ADDRHDR_BACKQUOTE         23      /* we saw a '\\' */
+#define STATE_HEADERS_END               24      /* we saw "^$", the blank line
                                                    between headers and body */
 
-int msmtp_read_addresses(FILE *mailf, FILE *tmpfile,
-        list_t *recipients, char **from, char **errstr)
+int msmtp_read_headers(FILE *mailf, FILE *tmpfile,
+        list_t *recipients,
+        char **from,
+        int *have_date,
+        char **errstr)
 {
     int c;
     int state = STATE_LINESTART_FRESH;
@@ -1068,6 +1078,10 @@ int msmtp_read_addresses(FILE *mailf, FILE *tmpfile,
     {
         *from = NULL;
     }
+    if (have_date)
+    {
+        *have_date = 0;
+    }
     if (recipients)
     {
         normal_recipients_list = list_new();
@@ -1099,7 +1113,9 @@ int msmtp_read_addresses(FILE *mailf, FILE *tmpfile,
                 case STATE_LINESTART_FRESH:
                     parentheses_depth = 0;
                     resent_index = -1;
-                    if (from && from_hdr < 0 && (c == 'f' || c == 'F'))
+                    if (have_date && (c == 'd' || c == 'D'))
+                        state = STATE_DATE1;
+                    else if (from && from_hdr < 0 && (c == 'f' || c == 'F'))
                         state = STATE_FROM1;
                     else if (recipients && (c == 't' || c == 'T'))
                         state = STATE_TO;
@@ -1125,6 +1141,8 @@ int msmtp_read_addresses(FILE *mailf, FILE *tmpfile,
                         finish_current_recipient = 1;
                     if (c == ' ' || c == '\t')
                         state = folded_rcpthdr_savestate;
+                    else if (have_date && (c == 'd' || c == 'D'))
+                        state = STATE_DATE1;
                     else if (from && from_hdr < 0 && (c == 'f' || c == 'F'))
                         state = STATE_FROM1;
                     else if (recipients && (c == 't' || c == 'T'))
@@ -1181,6 +1199,45 @@ int msmtp_read_addresses(FILE *mailf, FILE *tmpfile,
                         state = STATE_OTHER_HDR;
                     break;
 
+                case STATE_DATE1:
+                    if (c == 'a' || c == 'A')
+                        state = STATE_DATE2;
+                    else if (c == '\n')
+                        state = STATE_LINESTART_FRESH;
+                    else
+                        state = STATE_OTHER_HDR;
+                    break;
+
+                case STATE_DATE2:
+                    if (c == 't' || c == 'T')
+                        state = STATE_DATE3;
+                    else if (c == '\n')
+                        state = STATE_LINESTART_FRESH;
+                    else
+                        state = STATE_OTHER_HDR;
+                    break;
+
+                case STATE_DATE3:
+                    if (c == 'e' || c == 'E')
+                        state = STATE_DATE4;
+                    else if (c == '\n')
+                        state = STATE_LINESTART_FRESH;
+                    else
+                        state = STATE_OTHER_HDR;
+                    break;
+
+                case STATE_DATE4:
+                    if (c == ':')
+                    {
+                        *have_date = 1;
+                        state = STATE_OTHER_HDR;
+                    }
+                    else if (c == '\n')
+                        state = STATE_LINESTART_FRESH;
+                    else
+                        state = STATE_OTHER_HDR;
+                    break;
+
                 case STATE_FROM1:
                     if (resent_block == 0 && resent_index != 6)
                         resent_block = 1;
@@ -1594,7 +1651,8 @@ error_exit:
  */
 
 int msmtp_sendmail(account_t *acc, list_t *recipients,
-        FILE *f, FILE *tmpfile, int debug, long *mailsize,
+        FILE *prepend_header_file, FILE *header_file, FILE *f,
+        int debug, long *mailsize,
         list_t **lmtp_errstrs, list_t **lmtp_error_msgs,
         list_t **msg, char **errstr)
 {
@@ -1768,33 +1826,31 @@ int msmtp_sendmail(account_t *acc, list_t *recipients,
     }
     /* send header and body */
     *mailsize = 0;
-    if (tmpfile)
+    if (prepend_header_file)
     {
-        /* first the headers from the temp file */
-        if ((e = smtp_send_mail(&srv, tmpfile, acc->keepbcc, mailsize, errstr))
-                != SMTP_EOK)
-        {
-            msmtp_endsession(&srv, 0);
-            e = exitcode_smtp(e);
-            return e;
-        }
-        /* then the body from the original file */
-        if ((e = smtp_send_mail(&srv, f, 1, mailsize, errstr)) != SMTP_EOK)
+        /* first: prepended headers, if any */
+        if ((e = smtp_send_mail(&srv, prepend_header_file, acc->keepbcc,
+                        mailsize, errstr)) != SMTP_EOK)
         {
             msmtp_endsession(&srv, 0);
             e = exitcode_smtp(e);
             return e;
         }
     }
-    else
+    /* next: original mail headers */
+    if ((e = smtp_send_mail(&srv, header_file, acc->keepbcc,
+                    mailsize, errstr)) != SMTP_EOK)
     {
-        if ((e = smtp_send_mail(&srv, f, acc->keepbcc, mailsize, errstr))
-                != SMTP_EOK)
-        {
-            msmtp_endsession(&srv, 0);
-            e = exitcode_smtp(e);
-            return e;
-        }
+        msmtp_endsession(&srv, 0);
+        e = exitcode_smtp(e);
+        return e;
+    }
+    /* then: the body from the original file */
+    if ((e = smtp_send_mail(&srv, f, 1, mailsize, errstr)) != SMTP_EOK)
+    {
+        msmtp_endsession(&srv, 0);
+        e = exitcode_smtp(e);
+        return e;
     }
     /* end the mail */
     if (acc->protocol == SMTP_PROTO_SMTP)
@@ -2424,6 +2480,8 @@ typedef struct
     account_t *cmdline_account;
     const char *account_id;
     char *user_conffile;
+    /* additional information */
+    char *full_name;
     /* the list of recipients */
     list_t *recipients;
 } msmtp_cmdline_conf_t;
@@ -2554,6 +2612,8 @@ int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
     conf->cmdline_account = account_new(NULL, NULL);
     conf->account_id = NULL;
     conf->user_conffile = NULL;
+    /* additional information */
+    conf->full_name = NULL;
     /* the recipients */
     conf->recipients = NULL;
 
@@ -3147,9 +3207,13 @@ int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
                 }
                 break;
 
+            case 'F':
+                free(conf->full_name);
+                conf->full_name = xstrdup(optarg);
+                break;
+
             case 'A':
             case 'B':
-            case 'F':
             case 'G':
             case 'h':
             case 'i':
@@ -3176,7 +3240,7 @@ int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
     }
 
     /* The list of recipients.
-     * Write these to a temporary mail header so that msmtp_read_addresses() can
+     * Write these to a temporary mail header so that msmtp_read_headers() can
      * parse them. */
     rcptc = argc - optind;
     rcptv = &(argv[optind]);
@@ -3229,8 +3293,8 @@ int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
         goto error_exit;
     }
     conf->recipients = list_new();
-    if ((error_code = msmtp_read_addresses(tmpfile, NULL,
-                    list_last(conf->recipients), NULL, &errstr)) != EX_OK)
+    if ((error_code = msmtp_read_headers(tmpfile, NULL,
+                    list_last(conf->recipients), NULL, NULL, &errstr)) != EX_OK)
     {
         print_error("%s", msmtp_sanitize_string(errstr));
         goto error_exit;
@@ -3586,8 +3650,11 @@ int main(int argc, char *argv[])
 #if HAVE_GETSERVBYNAME
     struct servent *se;
 #endif
-    /* needed to extract addresses from headers */
-    FILE *tmpfile = NULL;
+    /* needed to read the headers and extract addresses */
+    FILE *header_tmpfile = NULL;
+    FILE *prepend_header_tmpfile = NULL;
+    int have_from_header = 0;
+    int have_date_header = 0;
 
 
     /* Avoid the side effects of text mode interpretations on DOS systems. */
@@ -3636,31 +3703,35 @@ int main(int argc, char *argv[])
         goto exit;
     }
     /* Read recipients and/or the envelope from address from the mail. */
-    if (conf.sendmail && (conf.read_recipients || conf.read_envelope_from))
+    if (conf.sendmail)
     {
-        if (!(tmpfile = tempfile(PACKAGE_NAME)))
+        char *envelope_from = NULL;
+        if (!(header_tmpfile = tempfile(PACKAGE_NAME)))
         {
             print_error(_("cannot create temporary file: %s"),
                     msmtp_sanitize_string(strerror(errno)));
             error_code = EX_IOERR;
             goto exit;
         }
-        if ((error_code = msmtp_read_addresses(stdin, tmpfile,
+        if ((error_code = msmtp_read_headers(stdin, header_tmpfile,
                         conf.read_recipients
                             ? list_last(conf.recipients) : NULL,
-                        conf.read_envelope_from
-                            ? &(conf.cmdline_account->from) : NULL,
-                        &errstr)) != EX_OK)
+                        &envelope_from, &have_date_header, &errstr)) != EX_OK)
         {
             print_error("%s", msmtp_sanitize_string(errstr));
             goto exit;
         }
-        if (conf.read_envelope_from && (conf.pretend || conf.debug))
+        have_from_header = (envelope_from ? 1 : 0);
+        if (conf.read_envelope_from)
         {
-            printf(_("envelope from address extracted from mail: %s\n"),
-                    conf.cmdline_account->from);
+            conf.cmdline_account->from = envelope_from;
+            if (conf.pretend || conf.debug)
+            {
+                printf(_("envelope from address extracted from mail: %s\n"),
+                        conf.cmdline_account->from);
+            }
         }
-        if (fseeko(tmpfile, 0, SEEK_SET) != 0)
+        if (fseeko(header_tmpfile, 0, SEEK_SET) != 0)
         {
             print_error(_("cannot rewind temporary file: %s"),
                     msmtp_sanitize_string(strerror(errno)));
@@ -3931,8 +4002,52 @@ int main(int argc, char *argv[])
     /* do the work */
     if (conf.sendmail)
     {
+        if (!have_from_header || !have_date_header)
+        {
+            if (!(prepend_header_tmpfile = tempfile(PACKAGE_NAME)))
+            {
+                print_error(_("cannot create temporary file: %s"),
+                        msmtp_sanitize_string(strerror(errno)));
+                error_code = EX_IOERR;
+                goto exit;
+            }
+        }
+        if (!have_from_header)
+        {
+            if (conf.full_name)
+            {
+                fprintf(prepend_header_tmpfile, "From: %s <%s>\n",
+                        conf.full_name, account->from);
+            }
+            else
+            {
+                fprintf(prepend_header_tmpfile, "From: %s\n", account->from);
+            }
+        }
+        if (!have_date_header)
+        {
+            time_t t;
+            char rfc2822_timestamp[32];
+            if ((t = time(NULL)) < 0)
+            {
+                print_error(_("cannot get system time: %s"), strerror(errno));
+                error_code = EX_OSERR;
+                goto exit;
+            }
+            print_time_rfc2822(t, rfc2822_timestamp);
+            fprintf(prepend_header_tmpfile, "Date: %s\n", rfc2822_timestamp);
+        }
+        if (prepend_header_tmpfile
+                && fseeko(prepend_header_tmpfile, 0, SEEK_SET) != 0)
+        {
+            print_error(_("cannot rewind temporary file: %s"),
+                    msmtp_sanitize_string(strerror(errno)));
+            error_code = EX_IOERR;
+            goto exit;
+        }
         if ((error_code = msmtp_sendmail(account, conf.recipients,
-                        stdin, tmpfile, conf.debug, &mailsize,
+                        prepend_header_tmpfile, header_tmpfile, stdin,
+                        conf.debug, &mailsize,
                         &lmtp_errstrs, &lmtp_error_msgs,
                         &errmsg, &errstr)) != EX_OK)
         {
@@ -4072,9 +4187,13 @@ int main(int argc, char *argv[])
 exit:
 
     /* clean up */
-    if (tmpfile)
+    if (header_tmpfile)
     {
-        fclose(tmpfile);
+        fclose(header_tmpfile);
+    }
+    if (prepend_header_tmpfile)
+    {
+        fclose(prepend_header_tmpfile);
     }
     free(loaded_system_conffile);
     free(loaded_user_conffile);
diff --git a/src/tools.c b/src/tools.c
index b980f8d..8c9efe1 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -3,7 +3,7 @@
  *
  * This file is part of msmtp, an SMTP client.
  *
- * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2011
+ * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2011, 2014
  * Martin Lambers <marlam@...23...>
  *
  *   This program is free software; you can redistribute it and/or modify
@@ -772,3 +772,56 @@ char *string_replace(char *str, const char *s, const char *r)
     }
     return str;
 }
+
+/*
+ * print_time_rfc2822()
+ *
+ * see tools.h
+ */
+
+void print_time_rfc2822(time_t t, char rfc2822_timestamp[32])
+{
+    /* Calculate a RFC 2822 timestamp. strftime() is unreliable for this because
+     * it is locale dependant, and because the timezone offset conversion
+     * specifier %z is not portable. */
+    struct tm gmt, *lt;
+    char tz_offset_sign;
+    int tz_offset_hours;
+    int tz_offset_minutes;
+    const char *weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
+        "Sat" };
+    const char *month[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
+        "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+    /* copy the struct tm, because the subsequent call to localtime() will
+     * overwrite it */
+    gmt = *gmtime(&t);
+    lt = localtime(&t);
+    tz_offset_minutes = (lt->tm_hour - gmt.tm_hour) * 60
+        + lt->tm_min - gmt.tm_min
+        + (lt->tm_year - gmt.tm_year) * 24 * 60
+        + (lt->tm_yday - gmt.tm_yday) * 24 * 60;
+    if (tz_offset_minutes < 0)
+    {
+        tz_offset_sign = '-';
+        tz_offset_minutes = -tz_offset_minutes;
+    }
+    else
+    {
+        tz_offset_sign = '+';
+    }
+    tz_offset_hours = tz_offset_minutes / 60;
+    tz_offset_minutes %= 60;
+    if (tz_offset_hours > 99)
+    {
+        /* Values equal to or larger than 24 are not meaningful, but we just
+         * make sure that the value fits into two digits. If the system time is
+         * broken, we cannot fix it. */
+        tz_offset_hours = 99;
+    }
+    (void)snprintf(rfc2822_timestamp, 32,
+            "%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d",
+            weekday[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
+            lt->tm_year + 1900, lt->tm_hour, lt->tm_min, lt->tm_sec,
+            tz_offset_sign, tz_offset_hours, tz_offset_minutes);
+}
diff --git a/src/tools.h b/src/tools.h
index a541a13..2e1addc 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -3,7 +3,7 @@
  *
  * This file is part of msmtp, an SMTP client.
  *
- * Copyright (C) 2004, 2005, 2006, 2007, 2011
+ * Copyright (C) 2004, 2005, 2006, 2007, 2011, 2014
  * Martin Lambers <marlam@...23...>
  *
  *   This program is free software; you can redistribute it and/or modify
@@ -193,4 +193,11 @@ int lock_file(FILE *f, int lock_type, int timeout);
  */
 char *string_replace(char *str, const char *s, const char *r);
 
+/*
+ * print_time_rfc2822()
+ *
+ * Print the given time stamp in RFC2822 format into the given buffer.
+ */
+void print_time_rfc2822(time_t t, char rfc2822_timestamp[32]);
+
 #endif