sendmail: update from maintainer
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 8 Feb 2008 18:24:54 +0000 (18:24 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 8 Feb 2008 18:24:54 +0000 (18:24 -0000)
include/applets.h
include/usage.h
networking/Config.in
networking/Kbuild
networking/sendmail.c

index a78ce1c77b1b9fe24e78c340017890e0f177d971..40fa39069df8a3aa0e3209b1184fec084f59a27b 100644 (file)
@@ -151,6 +151,7 @@ USE_FBSET(APPLET(fbset, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
 USE_FDFLUSH(APPLET_ODDNAME(fdflush, freeramdisk, _BB_DIR_BIN, _BB_SUID_NEVER, fdflush))
 USE_FDFORMAT(APPLET(fdformat, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_FDISK(APPLET(fdisk, _BB_DIR_SBIN, _BB_SUID_NEVER))
+USE_FETCHMAIL(APPLET_ODDNAME(fetchmail, sendgetmail, _BB_DIR_USR_BIN, _BB_SUID_NEVER, fetchmail))
 USE_FEATURE_GREP_FGREP_ALIAS(APPLET_NOUSAGE(fgrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER))
 USE_FIND(APPLET_NOEXEC(find, find, _BB_DIR_USR_BIN, _BB_SUID_NEVER, find))
 //USE_FINDFS(APPLET_NOUSAGE(findfs, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER))
@@ -299,7 +300,7 @@ USE_RUNSV(APPLET(runsv, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_RUNSVDIR(APPLET(runsvdir, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_RX(APPLET(rx, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_SED(APPLET(sed, _BB_DIR_BIN, _BB_SUID_NEVER))
-USE_SENDMAIL(APPLET(sendmail, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_SENDMAIL(APPLET_ODDNAME(sendmail, sendgetmail, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sendmail))
 USE_SELINUXENABLED(APPLET(selinuxenabled, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
 USE_SEQ(APPLET_NOFORK(seq, seq, _BB_DIR_USR_BIN, _BB_SUID_NEVER, seq))
 USE_SESTATUS(APPLET(sestatus, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
index dfa751597e41a3b016d4edb4ef45569b8bb9a48c..77a86074bf1e19a542331628e6a8b81591269522 100644 (file)
@@ -1017,6 +1017,19 @@ USE_FEATURE_BRCTL_FANCY("\n" \
        "       -S SECTORS      Set the number of sectors\n" \
        "       -v              Give fdisk version"
 
+#define fetchmail_trivial_usage \
+       "[-C dir] [-w timeout] [-U user] -P password [-X] [-t] [-z] server[:port]"
+#define fetchmail_full_usage \
+       "Fetch content of remote mailbox to local Maildir." \
+       "\n\nOptions:\n" \
+       "       -C dir          Set Maildir location\n" \
+       "       -w timeout      Set timeout on network operations\n" \
+       "       -U username     Authenticate with specified username\n" \
+       "       -P password     Authenticate with specified password\n" \
+       "       -X              Use openssl connection helper for secured servers\n" \
+       "       -t              Get only headers\n" \
+       "       -z              Delete messages on server"
+
 #define find_trivial_usage \
        "[PATH...] [EXPRESSION]"
 #define find_full_usage \
@@ -3138,29 +3151,21 @@ USE_FEATURE_RUN_PARTS_FANCY("\n -l      Prints names of all matching files even when
 #define selinuxenabled_full_usage
 
 #define sendmail_trivial_usage \
-       "{-t to}+ {-f from} [-n[notify]] [-s subject] [-b file]*\n" \
-       "[-a attachment]* [-c charset]" \
-       USE_FEATURE_SENDMAIL_NETWORK("\n" \
-       " [-d] [-w timeout] [-h server] [-p port] [-U user] [-P password]" \
-       )
+       "[-C dir] [-w timeout] [-U user] [-P password] [-X]\n" \
+       "-t to [-t to] -f from [-n] [-s subject] [-c charset] server[:port] [body] [attachment ...]"
 #define sendmail_full_usage \
-       "Send an email <from> <to> with <subject> and optional attachments." \
-       "\n\nArguments:\n" \
+       "Send an email with optional attachments." \
+       "\n\nOptions:\n" \
+       "       -C dir          Change current directory to dir\n" \
+       "       -w timeout      Set timeout on network operations\n" \
+       "       -U username     Authenticate with specified username\n" \
+       "       -P password     Authenticate with specified password\n" \
+       "       -X              Use openssl connection helper for secured servers\n" \
        "       -t to           Recipient email. May be multiple\n" \
        "       -f from         Sender address\n" \
-       "       -n[notify]      Optional notification address. If just -n given then notifies the sender\n" \
-       "       -s subject      Optional subject\n" \
-       "       -b filename     Optional body content file. May be multiple\n" \
-       "       -a filename     Optional file attachment. May be multiple\n" \
-       "       -c charset      Assumed charset for body and subject [koi8-r]" \
-       USE_FEATURE_SENDMAIL_NETWORK("\n" \
-       "       -d              Just dump composed message\n" \
-       "       -w timeout      Set timeout on network operations\n" \
-       "       -h server       Optional mail server name or IP [127.0.0.1]\n" \
-       "       -p port         Optional mail server port [25]\n" \
-       "       -U username     Authenticate using AUTH LOGIN with specified username\n" \
-       "       -P password     Authenticate using AUTH LOGIN with specified password" \
-       )
+       "       -n              Request delivery notification to sender\n" \
+       "       -s subject      Subject\n" \
+       "       -c charset      Assumed charset for body and subject [utf-8]"
 
 #define seq_trivial_usage \
        "[first [increment]] last"
index e8820e10de6c96eb7065eebd38ab97d1bf11a0be..ed87a178d80bf02147a091303313c33f47db0a5c 100644 (file)
@@ -671,12 +671,39 @@ config SENDMAIL
        help
          Barebones sendmail.
 
-config FEATURE_SENDMAIL_NETWORK
-       bool "Support network connectivity"
-       default y
+config FEATURE_SENDMAIL_EHLO
+       bool "Support EHLO command"
+       default n
+       depends on SENDMAIL
+       help
+         Support ESMTP EHLO command.
+
+config FEATURE_SENDMAIL_BLOATY
+       bool "Be more verbose"
+       default n
        depends on SENDMAIL
        help
-         Add ability to send, not only compose messages.
+         Should be turned off.
+
+config FETCHMAIL
+       bool "fetchmail"
+       default n
+       help
+         Barebones fetchmail.
+
+config FEATURE_FETCHMAIL_APOP
+       bool "Support APOP authentication"
+       default y
+       depends on FETCHMAIL
+       help
+         Support secure APOP authentication.
+
+config FEATURE_FETCHMAIL_FILTER
+       bool "Pipe thru external filter"
+       default n
+       depends on FETCHMAIL
+       help
+         Support piping incoming messages thru external filter.
 
 config SLATTACH
        bool "slattach"
index 2819e850775f3188ec5a8b870a448bf4d967bf85..8f309cdb2f0634c68ea61368fc6172052b6c3ded 100644 (file)
@@ -11,6 +11,7 @@ lib-$(CONFIG_BRCTL)        += brctl.o
 lib-$(CONFIG_DNSD)         += dnsd.o
 lib-$(CONFIG_ETHER_WAKE)   += ether-wake.o
 lib-$(CONFIG_FAKEIDENTD)   += isrv_identd.o isrv.o
+lib-$(CONFIG_FETCHMAIL)    += sendmail.o
 lib-$(CONFIG_FTPGET)       += ftpgetput.o
 lib-$(CONFIG_FTPPUT)       += ftpgetput.o
 lib-$(CONFIG_HOSTNAME)     += hostname.o
index fa1abc7ff794797a1ed770db457bd2538cd55d15..5dababc4b89a926ca52f08a5db72ff9344c9ae6e 100644 (file)
@@ -1,6 +1,6 @@
 /* vi: set sw=4 ts=4: */
 /*
- * bare bones sendmail
+ * bare bones sendmail/fetchmail
  *
  * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
  *
@@ -8,23 +8,24 @@
  */
 #include "libbb.h"
 
-/*
-   Extracted from BB uuencode.c
- */
-enum {
-       SRC_BUF_SIZE = 45,  /* This *MUST* be a multiple of 3 */
-       DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
-};
+#define INITIAL_STDIN_FILENO 3
 
 static void uuencode(char *fname, const char *text)
 {
+       enum {
+               SRC_BUF_SIZE = 45,  /* This *MUST* be a multiple of 3 */
+               DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
+       };
+
 #define src_buf text
        int fd;
 #define len fd
        char dst_buf[DST_BUF_SIZE + 1];
 
        if (fname) {
-               fd = xopen(fname, O_RDONLY);
+               fd = INITIAL_STDIN_FILENO;
+               if (NOT_LONE_DASH(fname))
+                       fd = xopen(fname, O_RDONLY);
                src_buf = bb_common_bufsiz1;
        } else {
                len = strlen(text);
@@ -44,69 +45,147 @@ static void uuencode(char *fname, const char *text)
                }
                if (!size)
                        break;
-               // Encode the buffer we just read in
+               // encode the buffer we just read in
                bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
                if (fname) {
-                       xwrite(STDOUT_FILENO, "\n", 1);
+                       xwrite(STDOUT_FILENO, "\r\n", 2);
                } else {
                        src_buf += size;
                        len -= size;
                }
                xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
        }
-       if (ENABLE_FEATURE_CLEAN_UP && fname)
+       if (fname)
                close(fd);
 }
 
-#if ENABLE_FEATURE_SENDMAIL_NETWORK
+static pid_t helper_pid;
+
+static void kill_helper(void)
+{
+       // TODO!!!: is there more elegant way to terminate child on program failure?
+       if (helper_pid > 0)
+               kill(helper_pid, SIGTERM);
+}
+
 // generic signal handler
 static void signal_handler(int signo)
 {
        int err;
 
-       if (SIGALRM == signo)
+       if (SIGALRM == signo) {
+               kill_helper();
                bb_error_msg_and_die("timed out");
+       }
 
        // SIGCHLD. reap zombies
        if (wait_any_nohang(&err) > 0)
                if (WIFEXITED(err) && WEXITSTATUS(err))
+#if ENABLE_FEATURE_SENDMAIL_BLOATY
                        bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
+#else
+                       bb_error_msg_and_die("child failed");
+#endif
 }
 
-static pid_t helper_pid;
-
-// read stdin, parses first bytes to a number, i.e. server response
-// if code = -1 then just return this number
-// if code != -1 then checks whether the number equals the code
-// if not equal -> die saying errmsg
-static int check(int code, const char *errmsg)
+static void launch_helper(const char **argv)
 {
-       char *answer;
+       // setup vanilla unidirectional pipes interchange
+       int idx;
+       int pipes[4];
+       xpipe(pipes);
+       xpipe(pipes+2);
+       helper_pid = vfork();
+       if (helper_pid < 0)
+               bb_perror_msg_and_die("vfork");
+       idx = (!helper_pid)*2;
+       xdup2(pipes[idx], STDIN_FILENO);
+       xdup2(pipes[3-idx], STDOUT_FILENO);
+       if (ENABLE_FEATURE_CLEAN_UP)
+               for (int i = 4; --i >= 0; )
+                       if (pipes[i] > STDOUT_FILENO)
+                               close(pipes[i]);
+       if (!helper_pid) {
+               // child - try to execute connection helper
+               BB_EXECVP(argv[0], (char **)argv);
+               _exit(127);
+       }
+       // parent - check whether child is alive
+       sig_catch(SIGCHLD, signal_handler);
+       sig_catch(SIGALRM, signal_handler);
+       signal_handler(SIGCHLD);
+       // child seems OK -> parent goes on
+}
+
+static unsigned timeout;
 
-       // read a string and match it against the set of available answers
+static char *command(const char *fmt, const char *param)
+{
+       char *msg = (char *)fmt;
+       alarm(timeout);
+       if (msg) {
+//             if (param)
+                       msg = xasprintf(fmt, param);
+               printf("%s\r\n", msg);
+       }
        fflush(stdout);
+       return msg;
+}
+
+static int smtp_checkp(const char *fmt, const char *param, int code)
+{
+       char *answer;
+       char *msg = command(fmt, param);
+       // read stdin
+       // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
+       // parse first bytes to a number
+       // if code = -1 then just return this number
+       // if code != -1 then checks whether the number equals the code
+       // if not equal -> die saying msg
+#if ENABLE_FEATURE_SENDMAIL_EHLO
+       while ((answer = xmalloc_getline(stdin)) && '-' == answer[3]) ;
+#else
        answer = xmalloc_getline(stdin);
+#endif
        if (answer) {
                int n = atoi(answer);
-               if (-1 == code || n == code) {
+               alarm(0);
+               if (ENABLE_FEATURE_CLEAN_UP) {
+                       free(msg);
                        free(answer);
+               }
+               if (-1 == code || n == code) {
                        return n;
                }
        }
-       // TODO: is there more elegant way to terminate child on program failure?
-       if (helper_pid > 0)
-               kill(helper_pid, SIGTERM);
-       if (!answer)
-               answer = (char*)"EOF";
-       else
-               *strchrnul(answer, '\r') = '\0';
-       bb_error_msg_and_die("error at %s: got '%s' instead", errmsg, answer);
+       kill_helper();
+       bb_error_msg_and_die("%s failed", msg);
 }
 
-static int puts_and_check(const char *msg, int code, const char *errmsg)
+static int smtp_check(const char *fmt, int code)
 {
-       printf("%s\r\n", msg);
-       return check(code, errmsg);
+       return smtp_checkp(fmt, NULL, code);
+}
+
+static void pop3_checkr(const char *fmt, const char *param, char **ret)
+{
+       char *msg = command(fmt, param);
+       char *answer = xmalloc_getline(stdin);
+       if (answer && '+' == answer[0]) {
+               alarm(0);
+               if (ret)
+                       *ret = answer;
+               else
+                       free(answer);
+               return;
+       }
+       kill_helper();
+       bb_error_msg_and_die("%s failed", msg);
+}
+
+static void pop3_check(const char *fmt, const char *param)
+{
+       pop3_checkr(fmt, param, NULL);
 }
 
 // strip argument of bad chars
@@ -123,215 +202,332 @@ static char *sane(char *str)
        *p = '\0';
        return str;
 }
-#endif
 
-int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int sendmail_main(int argc, char **argv)
+static void pop3_message(int fd)
+{
+       char *answer;
+       // read stdin, copy to file fd
+       while ((answer = xmalloc_fgets_str(stdin, "\r\n"))) {
+               char *s = answer;
+               if ('.' == answer[0]) {
+                       if ('.' == answer[1])
+                               s++;
+                       else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
+                               break;
+               }
+               xwrite(fd, s, strlen(s));
+               free(answer);
+       }
+       close(fd);
+}
+
+static const char *args[] = {
+       "openssl", "s_client", "-quiet", "-connect", NULL, "-tls1", "-starttls", "smtp", NULL
+};
+#define opt_connect    args[4]
+#define opt_after_connect args[5]
+
+int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sendgetmail_main(int argc, char **argv)
 {
        llist_t *recipients = NULL;
-       llist_t *bodies = NULL;
-       llist_t *attachments = NULL;
        char *from;
-       char *notify = NULL;
        const char *subject;
-       char *charset = (char*)"utf-8";
-#if ENABLE_FEATURE_SENDMAIL_NETWORK
-       const char *wsecs = "10";
-       const char *server = "127.0.0.1";
-       const char *port = NULL;
+       char *charset = (char *)"utf-8";
+
        const char *opt_user;
        const char *opt_pass;
-       unsigned timeout;
-#endif
-       char *boundary;
-       unsigned opts;
+       const char *opt_timeout;
+       const char *opt_chdir;
+
        enum {
-               OPT_f = 1 << 0,         // sender
-               OPT_n = 1 << 2,         // notification
-               OPT_s = 1 << 3,         // subject given
-               OPT_c = 1 << 6,         // charset
-               OPT_d = 1 << 7,         // dry run - no networking
-               OPT_w = 1 << 8,         // network timeout
-               OPT_h = 1 << 9,         // server
-               OPT_p = 1 << 10,        // port
-               OPT_U = 1 << 11,        // user specified
-               OPT_P = 1 << 12,        // password specified
+               OPT_C = 1 << 0,         // chdir
+               OPT_w = 1 << 1,         // network timeout
+               OPT_U = 1 << 2,         // user
+               OPT_P = 1 << 3,         // password
+               OPT_X = 1 << 4,         // use openssl connection helper
+
+               OPTS_t = 1 << 5,        // sendmail "to"
+               OPTF_t = 1 << 5,        // fetchmail "TOP"
+
+               OPTS_f = 1 << 6,        // sendmail "from"
+               OPTF_z = 1 << 6,        // fetchmail "delete"
+
+               OPTS_n = 1 << 7,        // notification
+               OPTS_s = 1 << 8,        // subject given
+               OPTS_c = 1 << 9,        // charset for subject and body
        };
 
-       // -f must be specified
-       // -t, -b, -a may be multiple
-       opt_complementary = "f:t::b::a::";
-       opts = getopt32(argv,
-               "f:t:n::s:b:a:c:" USE_FEATURE_SENDMAIL_NETWORK("dw:h:p:U:P:"),
-               &from, &recipients, &notify, &subject, &bodies, &attachments, &charset
-               USE_FEATURE_SENDMAIL_NETWORK(, &wsecs, &server, &port, &opt_user, &opt_pass)
+       const char *options;
+       unsigned opts;
+
+       // SENDMAIL
+       if ('s' == applet_name[0]) {
+               // save initial stdin
+               xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
+               // -f must be specified
+               // -t may be multiple
+               opt_complementary = "-1:f:t::";
+               options = "C:w:U:P:X" "t:f:ns:c:";
+       // FETCHMAIL
+       } else {
+               opt_after_connect = NULL;
+               opt_complementary = "=1:P";
+               options = "C:w:U:P:X" "tz";
+       }
+       opts = getopt32(argv, options,
+               &opt_chdir, &opt_timeout, &opt_user, &opt_pass,
+               &recipients, &from, &subject, &charset
        );
+
        //argc -= optind;
        argv += optind;
 
-       // sanitize user input
-       sane(from);
-       if (opts & OPT_c)
-               sane(charset);
-
-       // establish connection
-#if ENABLE_FEATURE_SENDMAIL_NETWORK
-       timeout = xatou(wsecs);
-       if (!(opts & OPT_d)) {
-               // ask password if we need to and while we're still have terminal
-               // TODO: get password directly from /dev/tty? or from a secret file?
-               if ((opts & (OPT_U+OPT_P)) == OPT_U) {
-                       if (!isatty(STDIN_FILENO) || !(opt_pass = bb_askpass(0, "Password: "))) {
-                               bb_error_msg_and_die("no password");
-                       }
+       // first argument is remote server[:port]
+       opt_connect = *argv++;
+
+       if (opts & OPT_w)
+               timeout = xatou(opt_timeout);
+
+       // chdir
+       if (opts & OPT_C)
+               xchdir(opt_chdir);
+
+       // connect to server
+       if (opts & OPT_X) {
+               launch_helper(args);
+       } else {
+               // no connection helper provided -> make plain connect
+               int fd = create_and_connect_stream_or_die(opt_connect, 0);
+               xmove_fd(fd, STDIN_FILENO);
+               xdup2(STDIN_FILENO, STDOUT_FILENO);
+       }
+
+       // randomize
+       srand(time(NULL));
+
+       // SENDMAIL
+       if (recipients) {
+               int code;
+               char *boundary;
+
+               // wait for initial OK on plain connect
+               if (!(opts & OPT_X))
+                       smtp_check(NULL, 220);
+
+               sane(from);
+               // introduce to server
+               // should we respect modern (but useless here) EHLO?
+               // or should they respect we wanna be tiny?!
+               if (!ENABLE_FEATURE_SENDMAIL_EHLO || 250 != smtp_checkp("EHLO %s", from, -1)) {
+                       smtp_checkp("HELO %s", from, 250);
                }
-               // set chat timeout
-               alarm(timeout);
-               // connect to server
-               if (argv[0]) {
-                       // if connection helper given
-                       // setup vanilla unidirectional pipes interchange
-                       int idx;
-                       int pipes[4];
-                       xpipe(pipes);
-                       xpipe(pipes+2);
-                       helper_pid = vfork();
-                       if (helper_pid < 0)
-                               bb_perror_msg_and_die("vfork");
-                       idx = (!helper_pid)*2;
-                       xdup2(pipes[idx], STDIN_FILENO);
-                       xdup2(pipes[3-idx], STDOUT_FILENO);
-                       if (ENABLE_FEATURE_CLEAN_UP)
-                               for (int i = 4; --i >= 0;)
-                                       if (pipes[i] > STDOUT_FILENO)
-                                               close(pipes[i]);
-                       // replace child with connection helper
-                       if (!helper_pid) {
-                               // child - try to execute connection helper
-                               BB_EXECVP(argv[0], argv);
-                               _exit(127);
+
+               // set sender
+               // NOTE: if password has not been specified ->
+               // no authentication is possible
+               code = (opts & OPT_P) ? -1 : 250;
+               // first try softly without authentication
+               while (250 != smtp_checkp("MAIL FROM:<%s>", from, code)) {
+                       // MAIL FROM failed -> authentication needed
+                       // do we have username?
+                       if (!(opts & OPT_U)) {
+                               // no! fetch it from "from" option
+                               //opts |= OPT_U;
+                               opt_user = xstrdup(from);
+                               *strchrnul(opt_user, '@') = '\0';
                        }
-                       // parent - check whether child is alive
-                       sig_catch(SIGCHLD, signal_handler);
-                       sig_catch(SIGALRM, signal_handler);
-                       signal_handler(SIGCHLD);
-                       // child seems OK -> parent goes on SMTP chat
-               } else {
-                       // no connection helper provided -> make plain connect
-                       int fd = create_and_connect_stream_or_die(
-                               server,
-                               bb_lookup_port(port, "tcp", 25)
-                       );
-                       xmove_fd(fd, STDIN_FILENO);
-                       xdup2(STDIN_FILENO, STDOUT_FILENO);
-                       // wait for OK
-                       check(220, "INIT");
+                       // now it seems we have username
+                       // try to authenticate
+                       if (334 == smtp_check("AUTH LOGIN", -1)) {
+                               uuencode(NULL, opt_user);
+                               smtp_check("", 334);
+                               uuencode(NULL, opt_pass);
+                               smtp_check("", 235);
+                       }
+                       // authenticated -> retry set sender
+                       // but now die on failure
+                       code = 250;
                }
-               // mail user specified? try modern AUTHentication
-               if ((opts & OPT_U)
-                && (334 == puts_and_check("auth login", -1, "auth login"))
-               ) {
-                       uuencode(NULL, opt_user);
-                       puts_and_check("", 334, "AUTH");
-                       uuencode(NULL, opt_pass);
-                       puts_and_check("", 235, "AUTH");
-               // no mail user specified or modern AUTHentication is not supported?
-               } else {
-                       // fallback to simple HELO authentication
-                       // fetch domain name (defaults to local)
-                       const char *domain = strchr(from, '@');
-                       if (domain)
-                               domain++;
-                       else
-                               domain = "local";
-                       printf("helo %s\r\n", domain);
-                       check(250, "HELO");
+               // set recipients
+               for (llist_t *to = recipients; to; to = to->link) {
+                       smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
                }
 
-               // set addresses
-               printf("mail from:<%s>\r\n", from);
-               check(250, "MAIL FROM");
+               // now put message
+               smtp_check("DATA", 354);
+               // put address headers
+               printf("From: %s\r\n", from);
                for (llist_t *to = recipients; to; to = to->link) {
-                       printf("rcpt to:<%s>\r\n", sane(to->data));
-                       check(250, "RCPT TO");
+                       printf("To: %s\r\n", to->data);
                }
-               puts_and_check("data", 354, "DATA");
-               // no timeout while sending message
-               alarm(0);
-       }
-#endif
-
-       // now put message
-       // put address headers
-       printf("From: %s\r\n", from);
-       for (llist_t *to = recipients; to; to = to->link) {
-               printf("To: %s\r\n", sane(to->data));
-       }
-       // put encoded subject
-       if (opts & OPT_s) {
-               printf("Subject: =?%s?B?", charset);
-               uuencode(NULL, subject);
-               puts("?=\r");
-       }
-       // put notification
-       if (opts & OPT_n) {
-               // -n without parameter?
-               if (!notify)
-                       notify = from; // notify sender by default
-               printf("Disposition-Notification-To: %s\r\n", sane(notify));
-       }
-       // put common headers and body start
-       //srand(?);
-       boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
-       printf(
-               "X-Mailer: busybox " BB_VER " sendmail\r\n"
-               "Message-ID: <%s>\r\n"
-               "Mime-Version: 1.0\r\n"
-               "%smultipart/mixed; boundary=\"%s\"\r\n"
-               "\r\n"
-               "--%s\r\n"
-               "%stext/plain; charset=%s\r\n"
-               "%s\r\n%s"
-               , boundary
-               , "Content-Type: "
-               , boundary, boundary
-               , "Content-Type: "
-               , charset
-               , "Content-Disposition: inline"
-               , "Content-Transfer-Encoding: base64\r\n"
-       );
-       // put body(ies)
-       for (llist_t *f = bodies; f; f = f->link) {
-               uuencode(f->data, NULL);
-       }
-       // put attachment(s)
-       for (llist_t *f = attachments; f; f = f->link) {
+               // put encoded subject
+               if (opts & OPTS_c)
+                       sane(charset);
+               if (opts & OPTS_s) {
+                       printf("Subject: =?%s?B?", charset);
+                       uuencode(NULL, subject);
+                       printf("?=\r\n");
+               }
+               // put notification
+               if (opts & OPTS_n)
+                       printf("Disposition-Notification-To: %s\r\n", from);
+               // put common headers and body start
+               boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
                printf(
-                       "\r\n--%s\r\n"
-                       "%sapplication/octet-stream\r\n"
-                       "%s; filename=\"%s\"\r\n"
-                       "%s"
+                       USE_FEATURE_SENDMAIL_BLOATY("X-Mailer: busybox " BB_VER " sendmail\r\n")
+                       "Message-ID: <%s>\r\n"
+                       "Mime-Version: 1.0\r\n"
+                       "%smultipart/mixed; boundary=\"%s\"\r\n"
                        , boundary
                        , "Content-Type: "
-                       , "Content-Disposition: inline"
-                       , bb_get_last_path_component_strip(f->data)
-                       , "Content-Transfer-Encoding: base64\r\n"
+                       , boundary
                );
-               uuencode(f->data, NULL);
-       }
-       // put terminator
-       printf("\r\n--%s--\r\n\r\n", boundary);
-       if (ENABLE_FEATURE_CLEAN_UP)
-               free(boundary);
-
-#if ENABLE_FEATURE_SENDMAIL_NETWORK
-       // end message and say goodbye
-       if (!(opts & OPT_d)) {
-               alarm(timeout);
-               puts_and_check(".", 250, "BODY");
-               puts_and_check("quit", 221, "QUIT");
+               // put body + attachment(s)
+       {
+               const char *fmt =
+                       "\r\n--%s\r\n"
+                       "%stext/plain; charset=%s\r\n"
+                       "%s%s\r\n"
+                       "%s"
+               ;
+               const char *p = charset;
+               char *q = (char *)"";
+               while (argv[0]) {
+                       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(*argv, NULL);
+                       if (*(++argv))
+                               q = bb_get_last_path_component_strip(argv[0]);
+               }
        }
+               // put terminator
+               printf("\r\n--%s--\r\n" "\r\n", boundary);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(boundary);
+
+               // end message and say goodbye
+               smtp_check(".", 250);
+               smtp_check("QUIT", 221);
+
+       // FETCHMAIL
+       } else {
+               // authenticate
+               char *buf;
+               unsigned nmsg;
+               if (!(opts & OPT_U)) {
+                       //opts |= OPT_U;
+                       opt_user = getenv("USER");
+               }
+#if ENABLE_FEATURE_FETCHMAIL_APOP
+               pop3_checkr(NULL, NULL, &buf);
+               // server supports APOP?
+               if ('<' == buf[4]) {
+                       md5_ctx_t md5;
+                       uint8_t hex[16*2 + 1];
+                       // yes. compose <stamp><password>
+                       char *s = strchr(buf, '>');
+                       if (s)
+                               strcpy(s+1, opt_pass);
+                       s = buf+4;
+                       // get md5 sum of <stamp><password>
+                       md5_begin(&md5);
+                       md5_hash(s, strlen(s), &md5);
+                       md5_end(s, &md5);
+                       bin2hex(hex, s, 16);
+                       // APOP
+                       s = xasprintf("%s %s", opt_user, hex);
+                       pop3_check("APOP %s", s);
+                       if (ENABLE_FEATURE_CLEAN_UP) {
+                               free(s);
+                               free(buf);
+                       }
+               } else {
+#else
+               {
+                       pop3_check(NULL, NULL);
 #endif
+                       // USER
+                       pop3_check("USER %s", opt_user);
+                       // PASS
+                       pop3_check("PASS %s", opt_pass);
+               }
+
+               // get statistics
+               pop3_checkr("STAT", NULL, &buf);
+
+               // get number of messages
+               nmsg = atoi(buf+4);
+               if (ENABLE_FEATURE_CLEAN_UP)
+                       free(buf);
+
+               // lock maildir
+               ////USE_FEATURE_CLEAN_UP(close)(xopen(".lock", O_CREAT | O_WRONLY | O_TRUNC | O_EXCL));
+               
+               // make tempnam(dir, salt) respect dir argument
+               unsetenv("TMPDIR");
+
+               // TODO: piping thru external filter argv... if *argv
+
+               // cache fetch command
+       {
+               const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
+               // loop thru messages
+               for (; nmsg; nmsg--) {
+                       int fd;
+                       char tmp_name[sizeof("tmp/XXXXXX")];
+                       char new_name[sizeof("new/XXXXXX")];
+
+                       // retrieve message in ./tmp
+                       strcpy(tmp_name, "tmp/XXXXXX");
+                       fd = mkstemp(tmp_name);
+                       if (fd < 0)
+                               bb_perror_msg_and_die("cannot create unique file");
+                       pop3_check(retr, (const char *)nmsg);
+                       pop3_message(fd); // NB: closes fd
+
+                       // move file to ./new atomically
+                       strncpy(new_name, "new", 3);
+                       strcpy(new_name + 3, tmp_name + 3);
+                       if (rename(tmp_name, new_name) < 0) {
+                               // rats! such file exists! try to make unique name
+                               strcpy(new_name + 3, "tmp/XXXXXX" + 3);
+                               fd = mkstemp(new_name);
+                               if (fd < 0)
+                                       bb_perror_msg_and_die("cannot create unique file");
+                               close(fd);
+                               if (rename(tmp_name, new_name) < 0) {
+                                       // something is very wrong
+                                       bb_perror_msg_and_die("cannot move %s to %s", tmp_name, new_name);
+                               }
+                       }
+
+                       // delete message from server
+                       if (opts & OPTF_z)
+                               pop3_check("DELE %u", (const char*)nmsg);
+               }
+       }
+
+               // Bye
+               pop3_check("QUIT", NULL);
+
+               // unlock maildir
+               ////unlink(".lock");
+       }
 
        return 0;
 }