sendmail: compat update by Vladimir
authorDenis Vlasenko <vda.linux@googlemail.com>
Sat, 27 Sep 2008 14:01:22 +0000 (14:01 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sat, 27 Sep 2008 14:01:22 +0000 (14:01 -0000)
function                                             old     new   delta
sendgetmail_main                                    1889    2091    +202
putchar_unlocked                                       -      43     +43
putchar                                                -      43     +43
packed_usage                                       24868   24910     +42
smtp_checkp                                          102     111      +9
uuencode                                             235     227      -8
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 3/1 up/down: 339/-8)            Total: 331 bytes

include/usage.h
networking/Config.in
networking/sendmail.c

index d60ffef1b37a0e2c3652d9f96041d12d2a4401e7..a5234e0536dc823554ce44bc1aa5685da2375a3b 100644 (file)
 
 #define sendmail_trivial_usage \
        "[-w timeout] [-H [user:pass@]server[:port]] [-S]\n" \
-       "[-c charset] [-N type] [-i] [-s subject] [-a attach]... [-t] [-f sender] [rcpt]..."
+       "[-N type] [-f sender] [-F fullname] " \
+       USE_FEATURE_SENDMAIL_MAILX("[-s subject] [-c charset] [-a attach]... ") "[-t] [rcpt]..."
 #define sendmail_full_usage "\n\n" \
        "Send an email\n" \
      "\nOptions:" \
      "\n       -w timeout      Network timeout" \
      "\n       -H [user:pass@]server[:port] Server" \
      "\n       -S              Use openssl connection helper for secure servers" \
-     "\n       -c charset      Assume charset for body and subject (utf-8)" \
      "\n       -N type         Request delivery notification. Type is ignored" \
-     "\n       -i              Ignore single dots in mail body. Implied" \
+     "\n       -f sender       Sender" \
+     "\n       -F fullname     Sender full name. Overrides $NAME" \
+       USE_FEATURE_SENDMAIL_MAILX( \
      "\n       -s subject      Subject" \
+     "\n       -c charset      Assume charset for body and subject (" CONFIG_FEATURE_SENDMAIL_CHARSET ")" \
      "\n       -a file         File to attach. May be multiple" \
+       )
      "\n       -t              Read recipients and subject from body" \
-     "\n       -f sender       Sender" \
+     "\n" \
+     "\nOther options are silently ignored; -oi is implied" \
 
 #define seq_trivial_usage \
        "[first [increment]] last"
index f23ca47c0a15659c8707b448819e606a15cfb9af..1984297a6316a5508f4ea20e0f642350cf642720 100644 (file)
@@ -687,6 +687,27 @@ config SENDMAIL
        help
          Barebones sendmail.
 
+config FEATURE_SENDMAIL_MAILX
+       bool "Allow to specify subject, attachments and their charset"
+       default y
+       depends on SENDMAIL
+       help
+         Allow to specify subject, attachments and their charset.
+
+config FEATURE_SENDMAIL_SSL
+       bool "Allow to communicate via SSL/TLS"
+       default y
+       depends on SENDMAIL
+       help
+         Allow to use secure connections provided by openssl. E.g. @gmail.com.
+
+config FEATURE_SENDMAIL_CHARSET
+       string "Default charset"
+       default "utf-8"
+       depends on SENDMAIL
+       help
+         Default charset of the message.
+
 config FETCHMAIL
        bool "fetchmail"
        default n
index c605d7c1b28d770e99e296e6d4900b98725c674a..6d00026e27121edd62b506891cdb73ab65ce73ad 100644 (file)
@@ -8,7 +8,38 @@
  */
 #include "libbb.h"
 
-#define INITIAL_STDIN_FILENO 3
+struct globals {
+       pid_t helper_pid;
+       unsigned timeout;
+       FILE *fp0; // initial stdin
+       // arguments for SSL connection helper
+       const char *xargs[9];
+       // arguments for postprocess helper
+       const char *fargs[3];
+};
+#define G (*ptr_to_globals)
+#define helper_pid      (G.helper_pid)
+#define timeout         (G.timeout   )
+#define fp0             (G.fp0       )
+#define xargs           (G.xargs     )
+#define fargs           (G.fargs     )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       xargs[0] = "openssl"; \
+       xargs[1] = "s_client"; \
+       xargs[2] = "-quiet"; \
+       xargs[3] = "-connect"; \
+       /*xargs[4] = "localhost";*/ \
+       xargs[5] = "-tls1"; \
+       xargs[6] = "-starttls"; \
+       xargs[7] = "smtp"; \
+       fargs[0] = CONFIG_FEATURE_SENDMAIL_CHARSET; \
+} while (0)
+
+#define opt_connect       (xargs[4])
+#define opt_after_connect (xargs[5])
+#define opt_charset       (fargs[0])
+#define opt_subject       (fargs[1])
 
 static void uuencode(char *fname, const char *text)
 {
@@ -18,14 +49,12 @@ static void uuencode(char *fname, const char *text)
        };
 
 #define src_buf text
-       int fd;
-#define len fd
+       FILE *fp = fp;
+       ssize_t len = len;
        char dst_buf[DST_BUF_SIZE + 1];
 
        if (fname) {
-               fd = INITIAL_STDIN_FILENO;
-               if (NOT_LONE_DASH(fname))
-                       fd = xopen(fname, O_RDONLY);
+               fp = (NOT_LONE_DASH(fname)) ? xfopen_for_read(fname) : fp0;
                src_buf = bb_common_bufsiz1;
        // N.B. strlen(NULL) segfaults!
        } else if (text) {
@@ -35,11 +64,10 @@ static void uuencode(char *fname, const char *text)
        } else
                return;
 
-       fflush(stdout); // sync stdio and unistd output
        while (1) {
                size_t size;
                if (fname) {
-                       size = full_read(fd, (char *)src_buf, SRC_BUF_SIZE);
+                       size = fread((char *)src_buf, 1, SRC_BUF_SIZE, fp);
                        if ((ssize_t)size < 0)
                                bb_perror_msg_and_die(bb_msg_read_error);
                } else {
@@ -52,50 +80,20 @@ static void uuencode(char *fname, const char *text)
                // encode the buffer we just read in
                bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
                if (fname) {
-                       xwrite(STDOUT_FILENO, "\r\n", 2);
+                       printf("\r\n");
                } else {
                        src_buf += size;
                        len -= size;
                }
-               xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
+               fwrite(dst_buf, 1, 4 * ((size + 2) / 3), stdout);
        }
        if (fname)
-               close(fd);
+               fclose(fp);
 #undef src_buf
-#undef len
 }
 
-struct globals {
-       pid_t helper_pid;
-       unsigned timeout;
-       // arguments for SSL connection helper
-       const char *xargs[9];
-       // arguments for postprocess helper
-       const char *fargs[3];
-};
-#define G (*ptr_to_globals)
-#define helper_pid      (G.helper_pid)
-#define timeout         (G.timeout   )
-#define xargs           (G.xargs     )
-#define fargs           (G.fargs     )
-#define INIT_G() do { \
-       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
-       xargs[0] = "openssl"; \
-       xargs[1] = "s_client"; \
-       xargs[2] = "-quiet"; \
-       xargs[3] = "-connect"; \
-       /*xargs[4] = "localhost";*/ \
-       xargs[5] = "-tls1"; \
-       xargs[6] = "-starttls"; \
-       xargs[7] = "smtp"; \
-       fargs[0] = "utf-8"; \
-} while (0)
-
-#define opt_connect      (xargs[4])
-#define opt_after_connect (xargs[5])
-#define opt_charset      (fargs[0])
-#define opt_subject      (fargs[1])
 
+#if ENABLE_FEATURE_SENDMAIL_SSL
 static void kill_helper(void)
 {
        // TODO!!!: is there more elegant way to terminate child on program failure?
@@ -107,7 +105,6 @@ static void kill_helper(void)
 static void signal_handler(int signo)
 {
 #define err signo
-
        if (SIGALRM == signo) {
                kill_helper();
                bb_error_msg_and_die("timed out");
@@ -151,6 +148,10 @@ static void launch_helper(const char **argv)
        signal_handler(SIGCHLD);
        // child seems OK -> parent goes on
 }
+#else
+#define kill_helper() ((void)0)
+#define launch_helper(x) bb_error_msg_and_die("no SSL support")
+#endif
 
 static const char *command(const char *fmt, const char *param)
 {
@@ -177,15 +178,14 @@ static int smtp_checkp(const char *fmt, const char *param, int code)
        while ((answer = xmalloc_fgetline(stdin)) != NULL)
                if (strlen(answer) <= 3 || '-' != answer[3])
                        break;
+//bb_error_msg("FMT[%s]ANS[%s]", fmt, answer);
        if (answer) {
                int n = atoi(answer);
+//bb_error_msg("FMT[%s]COD[%d][%d]", fmt, n, code);
                alarm(0);
-               if (ENABLE_FEATURE_CLEAN_UP) {
-                       free(answer);
-               }
-               if (-1 == code || n == code) {
+               free(answer);
+               if (-1 == code || n == code)
                        return n;
-               }
        }
        kill_helper();
        bb_error_msg_and_die("%s failed", msg);
@@ -283,30 +283,34 @@ static void rcptto(const char *s)
 int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
 {
+#if ENABLE_FEATURE_SENDMAIL_MAILX
        llist_t *opt_attachments = NULL;
-       char *opt_from;
+#endif
+       char *opt_from, *opt_fullname;
        const char *opt_user;
        const char *opt_pass;
+
        enum {
                OPT_w = 1 << 0,         // network timeout
+
                OPT_H = 1 << 1,         // [user:password@]server[:port]
                OPT_S = 1 << 2,         // connect using openssl s_client helper
 
-               OPTS_t = 1 << 3,        // sendmail: read addresses from body
+               OPTS_t = 1 << 3,        // sendmail: read message for recipients
                OPTF_t = 1 << 3,        // fetchmail: use "TOP" not "RETR"
 
-               OPTS_s = 1 << 4,        // sendmail: subject
+               OPTS_N = 1 << 4,        // sendmail: request notification
                OPTF_z = 1 << 4,        // fetchmail: delete from server
 
-               OPTS_c = 1 << 5,        // sendmail: assumed charset
-               OPTS_a = 1 << 6,        // sendmail: attachment(s)
-               OPTS_i = 1 << 7,        // sendmail: ignore lone dots in message body (implied)
+               OPTS_f = 1 << 5,        // sendmail: sender address
+               OPTS_F = 1 << 6,        // sendmail: sender name, overrides $NAME
 
-               OPTS_N = 1 << 8,        // sendmail: request notification
-               OPTS_f = 1 << 9,        // sendmail: sender address
+               OPTS_s = 1 << 7,        // sendmail: subject
+               OPTS_c = 1 << 8,        // sendmail: assumed charset
+               OPTS_a = 1 << 9,        // sendmail: attachment(s)
        };
        const char *options;
-       int opts;
+       unsigned opts;
 
        // init global variables
        INIT_G();
@@ -317,11 +321,11 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
        if (!ENABLE_FETCHMAIL || 's' == applet_name[0]) {
                // SENDMAIL
                // save initial stdin since body is piped!
-               xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
+               xdup2(STDIN_FILENO, 3);
+               fp0 = fdopen(3, "r");
                opt_complementary = "w+:a::";
-               options = "w:H:St" "s:c:a:iN:f:";
-               // body is pseudo attachment read from stdin
-               llist_add_to_end(&opt_attachments, (char *)"-");
+               options = "w:H:St" "N:f:F:" USE_FEATURE_SENDMAIL_MAILX("s:c:a:")
+               "X:V:vq:R:O:o:nmL:Iih:GC:B:b:A:"; // postfix compat only, ignored
        } else {
                // FETCHMAIL
                opt_after_connect = NULL;
@@ -330,15 +334,19 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
        }
        opts = getopt32(argv, options,
                &timeout /* -w */, &opt_connect /* -H */,
-               &opt_subject, &opt_charset, &opt_attachments, NULL, &opt_from
+               NULL, &opt_from, &opt_fullname,
+#if ENABLE_FEATURE_SENDMAIL_MAILX
+               &opt_subject, &opt_charset, &opt_attachments,
+#endif
+               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
        );
        //argc -= optind;
        argv += optind;
 
        // connect to server
-       // host[:port] not specified ? -> use $HOSTNAME. no $HOSTNAME ? -> use localhost
+       // host[:port] not specified ? -> use $SMTPHOST. no $SMTPHOST ? -> use localhost
        if (!(opts & OPT_H)) {
-               opt_connect = getenv("HOSTNAME");
+               opt_connect = getenv("SMTPHOST");
                if (!opt_connect)
                        opt_connect = "127.0.0.1";
        }
@@ -376,18 +384,13 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
        {
                int code;
                char *boundary;
-               const char *fmt;
-               const char *p;
-               char *q;
                llist_t *l;
                llist_t *headers = NULL;
+               char *domain = sane(safe_getdomainname());
 
                // got no sender address? -> use username as a resort
                if (!(opts & OPTS_f)) {
-                       char *domain = safe_getdomainname();
                        opt_from = xasprintf("%s@%s", opt_user, domain);
-                       if (ENABLE_FEATURE_CLEAN_UP)
-                               free(domain);
                }
 
                // introduce to server
@@ -399,9 +402,11 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
                }
 
                // we should start with modern EHLO
-               if (250 != smtp_checkp("EHLO %s", sane(opt_from), -1)) {
-                       smtp_checkp("HELO %s", opt_from, 250);
+               if (250 != smtp_checkp("EHLO %s", domain, -1)) {
+                       smtp_checkp("HELO %s", domain, 250);
                }
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(domain);
 
                // set sender
                // NOTE: if password has not been specified
@@ -431,37 +436,36 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
                        argv++;
                }
 
-               // if -t specified or no recipients specified -> enter all-included mode
-               // i.e. scan stdin for To:, Cc:, Bcc:, and Subject: lines ...
+               // if -t specified or no recipients specified -> read recipients from message
+               // i.e. scan stdin for To:, Cc:, Bcc: lines ...
                // ... and then use the rest of stdin as message body
-               // N.B. subject read from body has priority
-               // over that specified on command line.
-               // recipients are merged
+               // N.B. subject read from body can be further overrided with one specified on command line.
+               // recipients are merged. Bcc: lines are deleted
                // N.B. other headers are collected and will be dumped verbatim
                if (opts & OPTS_t || !headers) {
                        // fetch recipients and (optionally) subject
                        char *s;
-                       while ((s = xmalloc_reads(INITIAL_STDIN_FILENO, NULL, NULL)) != NULL) {
+                       while ((s = xmalloc_fgetline(fp0)) != NULL) {
                                if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Cc: ", s, 4)) {
                                        rcptto(sane(s+4));
                                        llist_add_to_end(&headers, s);
                                } else if (0 == strncasecmp("Bcc: ", s, 5)) {
                                        rcptto(sane(s+5));
-                                       if (ENABLE_FEATURE_CLEAN_UP)
-                                               free(s);
+                                       free(s);
                                        // N.B. Bcc vanishes from headers!
-/*                             } else if (0 == strncmp("From: ", s, 6)) {
-                                       opt_from = s+6;
-                                       opts |= OPTS_f;
-*/                             } else if (0 == strncmp("Subject: ", s, 9)) {
-                                       opt_subject = s+9;
-                                       opts |= OPTS_s;
+                               } else if (0 == strncmp("Subject: ", s, 9)) {
+                                       // we read subject -> use it verbatim unless it is specified
+                                       // on command line
+                                       if (!(opts & OPTS_s))
+                                               llist_add_to_end(&headers, s);
+                                       else
+                                               free(s);
                                } else if (s[0]) {
                                        // misc header
                                        llist_add_to_end(&headers, s);
                                } else {
                                        free(s);
-                                       break; // empty line
+                                       break; // stop on the first empty line
                                }
                        }
                }
@@ -476,18 +480,27 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
                                free(l->data);
                }
 
-               // put address header
-               printf("From: %s\r\n", opt_from);
-
-               // put encoded subject
+               // put (possibly encoded) subject
                if (opts & OPTS_c)
                        sane((char *)opt_charset);
                if (opts & OPTS_s) {
-                       printf("Subject: =?%s?B?", opt_charset);
-                       uuencode(NULL, opt_subject);
-                       printf("?=\r\n");
+                       printf("Subject: ");
+                       if (opts & OPTS_c) {
+                               printf("=?%s?B?", opt_charset);
+                               uuencode(NULL, opt_subject);
+                               printf("?=");
+                       } else {
+                               printf("%s", opt_subject);
+                       }
+                       printf("\r\n");
                }
 
+               // put sender name, $NAME is the default
+               if (!(opts & OPTS_F))
+                       opt_fullname = getenv("NAME");
+               if (opt_fullname)
+                       printf("From: \"%s\" <%s>\r\n", opt_fullname, opt_from);
+
                // put notification
                if (opts & OPTS_N)
                        printf("Disposition-Notification-To: %s\r\n", opt_from);
@@ -496,58 +509,92 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
                srand(monotonic_us());
                boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
 
-               // put common headers and body start
-               printf(
-                       "Message-ID: <%s>\r\n"
-                       "Mime-Version: 1.0\r\n"
-                       "%smultipart/mixed; boundary=\"%s\"\r\n"
-                       , boundary
-                       , "Content-Type: "
-                       , boundary
-               );
-
-               // put body + attachment(s)
-               // N.B. all these weird things just to be tiny
-               // by reusing string patterns!
-               fmt =
-                       "\r\n--%s\r\n"
-                       "%stext/plain; charset=%s\r\n"
-                       "%s%s\r\n"
-                       "%s"
-               ;
-               p = opt_charset;
-               q = (char *)"";
-               l = opt_attachments;
-               while (l) {
+               // put common headers
+               // TODO: do we really need this?
+//             printf("Message-ID: <%s>\r\n", boundary);
+
+#if ENABLE_FEATURE_SENDMAIL_MAILX
+               // have attachments? -> compose multipart MIME
+               if (opt_attachments) {
+                       const char *fmt;
+                       const char *p;
+                       char *q;
+
                        printf(
-                               fmt
-                               , boundary
+                               "Mime-Version: 1.0\r\n"
+                               "%smultipart/mixed; boundary=\"%s\"\r\n"
                                , "Content-Type: "
-                               , p
-                               , "Content-Disposition: inline"
-                               , q
-                               , "Content-Transfer-Encoding: base64\r\n"
+                               , boundary
                        );
-                       p = "";
+
+                       // body is pseudo attachment read from stdin in first turn
+                       llist_add_to(&opt_attachments, (char *)"-");
+
+                       // put body + attachment(s)
+                       // N.B. all these weird things just to be tiny
+                       // by reusing string patterns!
                        fmt =
                                "\r\n--%s\r\n"
-                               "%sapplication/octet-stream%s\r\n"
-                               "%s; filename=\"%s\"\r\n"
+                               "%stext/plain; charset=%s\r\n"
+                               "%s%s\r\n"
                                "%s"
                        ;
-                       uuencode(l->data, NULL);
-                       l = l->link;
-                       if (l)
-                               q = bb_get_last_path_component_strip(l->data);
-               }
+                       p = opt_charset;
+                       q = (char *)"";
+                       l = opt_attachments;
+                       while (l) {
+                               printf(
+                                       fmt
+                                       , boundary
+                                       , "Content-Type: "
+                                       , p
+                                       , "Content-Disposition: inline"
+                                       , q
+                                       , "Content-Transfer-Encoding: base64\r\n"
+                               );
+                               p = "";
+                               fmt =
+                                       "\r\n--%s\r\n"
+                                       "%sapplication/octet-stream%s\r\n"
+                                       "%s; filename=\"%s\"\r\n"
+                                       "%s"
+                               ;
+                               uuencode(l->data, NULL);
+                               l = l->link;
+                               if (l)
+                                       q = bb_get_last_path_component_strip(l->data);
+                       }
+
+                       // put message terminator
+                       printf("\r\n--%s--\r\n" "\r\n", boundary);
 
-               // put message terminator
-               printf("\r\n--%s--\r\n" "\r\n", boundary);
+               // no attachments? -> just dump message
+               } else
+#endif
+               {
+                       char *s;
+                       // terminate headers
+                       printf("\r\n");
+                       // put plain text respecting leading dots
+                       while ((s = xmalloc_fgetline(fp0)) != NULL) {
+                               // escape leading dots
+                               // N.B. this feature is implied even if no -i switch given
+                               // N.B. we need to escape the leading dot regardless of
+                               // whether it is single or not character on the line
+                               if (/*(opts & OPTS_i) && */ '.' == s[0] /*&& '\0' == s[1] */)
+                                       printf(".");
+                               // dump read line
+                               printf("%s\r\n", s);
+                       }
+               }
 
                // leave "put message" mode
                smtp_check(".", 250);
                // ... and say goodbye
                smtp_check("QUIT", 221);
+               // cleanup
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       fclose(fp0);
        }
 #if ENABLE_FETCHMAIL
 /***************************************************
@@ -671,5 +718,5 @@ int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
        }
 #endif // ENABLE_FETCHMAIL
 
-       return 0;
+       return EXIT_SUCCESS;
 }