add mailutils/*

diff --git a/mailutils/Config.in b/mailutils/Config.in
new file mode 100644
index 0000000..b8d6977
--- /dev/null
+++ b/mailutils/Config.in
@@ -0,0 +1,64 @@
+menu "Mail Utilities"
+
+config MAKEMIME
+	bool "makemime"
+	default n
+	help
+	  Create MIME-formatted messages.
+
+config FEATURE_MIME_CHARSET
+	string "Default charset"
+	default "us-ascii"
+	depends on MAKEMIME || REFORMIME || SENDMAIL
+	help
+	  Default charset of the message.
+
+config POPMAILDIR
+	bool "popmaildir"
+	default n
+	help
+	  Simple yet powerful POP3 mail popper. Delivers content of remote mailboxes to local Maildir.
+
+config FEATURE_POPMAILDIR_DELIVERY
+	bool "Allow message filters and custom delivery program"
+	default n
+	depends on POPMAILDIR
+	help
+	  Allow to use a custom program to filter the content of the message before actual delivery (-F "prog [args...]").
+	  Allow to use a custom program for message actual delivery (-M "prog [args...]").
+
+config REFORMIME
+	bool "reformime"
+	default n
+	help
+	  Parse MIME-formatted messages.
+
+config FEATURE_REFORMIME_COMPAT
+	bool "Accept and ignore options other than -x and -X"
+	default y
+	depends on REFORMIME
+	help
+	  Accept (for compatibility only) and ignore options other than -x and -X.
+
+config SENDMAIL
+	bool "sendmail"
+	default n
+	help
+	  Barebones sendmail.
+
+config FEATURE_SENDMAIL_MAILX
+	bool "Allow to specify subject, attachments, their charset and connection helper"
+	default y
+	depends on SENDMAIL
+	help
+	  Allow to specify subject, attachments and their charset.
+	  Allow to use custom connection helper.
+
+config FEATURE_SENDMAIL_MAILXX
+	bool "Allow to specify Cc: addresses and some additional headers"
+	default n
+	depends on FEATURE_SENDMAIL_MAILX
+	help
+	  Allow to specify Cc: addresses and some additional headers: Errors-To:.
+
+endmenu
diff --git a/mailutils/Kbuild b/mailutils/Kbuild
new file mode 100644
index 0000000..871e879
--- /dev/null
+++ b/mailutils/Kbuild
@@ -0,0 +1,11 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_MAKEMIME)     += mime.o mail.o
+lib-$(CONFIG_POPMAILDIR)   += popmaildir.o mail.o
+lib-$(CONFIG_REFORMIME)    += mime.o mail.o
+lib-$(CONFIG_SENDMAIL)     += sendmail.o mail.o
diff --git a/mailutils/mail.c b/mailutils/mail.c
new file mode 100644
index 0000000..ab1304a
--- /dev/null
+++ b/mailutils/mail.c
@@ -0,0 +1,242 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * helper routines
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+static void kill_helper(void)
+{
+	// TODO!!!: is there more elegant way to terminate child on program failure?
+	if (G.helper_pid > 0)
+		kill(G.helper_pid, SIGTERM);
+}
+
+// generic signal handler
+static void signal_handler(int signo)
+{
+#define err signo
+	if (SIGALRM == signo) {
+		kill_helper();
+		bb_error_msg_and_die("timed out");
+	}
+
+	// SIGCHLD. reap zombies
+	if (safe_waitpid(G.helper_pid, &err, WNOHANG) > 0)
+		if (WIFEXITED(err)) {
+			G.helper_pid = 0;
+			if (WEXITSTATUS(err))
+				bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
+		}
+#undef err
+}
+
+void FAST_FUNC launch_helper(const char **argv)
+{
+	// setup vanilla unidirectional pipes interchange
+	int idx;
+	int pipes[4];
+
+	xpipe(pipes);
+	xpipe(pipes+2);
+	G.helper_pid = vfork();
+	if (G.helper_pid < 0)
+		bb_perror_msg_and_die("vfork");
+	idx = (!G.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 (!G.helper_pid) {
+		// child: try to execute connection helper
+		BB_EXECVP(*argv, (char **)argv);
+		_exit(127);
+	}
+	// parent: check whether child is alive
+	bb_signals(0
+		+ (1 << SIGCHLD)
+		+ (1 << SIGALRM)
+		, signal_handler);
+	signal_handler(SIGCHLD);
+	// child seems OK -> parent goes on
+	atexit(kill_helper);
+}
+
+const FAST_FUNC char *command(const char *fmt, const char *param)
+{
+	const char *msg = fmt;
+	if (timeout)
+		alarm(timeout);
+	if (msg) {
+		msg = xasprintf(fmt, param);
+		printf("%s\r\n", msg);
+	}
+	fflush(stdout);
+	return msg;
+}
+
+// NB: parse_url can modify url[] (despite const), but only if '@' is there
+/*
+static char FAST_FUNC *parse_url(char *url, char **user, char **pass)
+{
+	// parse [user[:pass]@]host
+	// return host
+	char *s = strchr(url, '@');
+	*user = *pass = NULL;
+	if (s) {
+		*s++ = '\0';
+		*user = url;
+		url = s;
+		s = strchr(*user, ':');
+		if (s) {
+			*s++ = '\0';
+			*pass = s;
+		}
+	}
+	return url;
+}
+*/
+
+void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol)
+{
+	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
+	FILE *fp = fp;
+	ssize_t len = len;
+	char dst_buf[DST_BUF_SIZE + 1];
+
+	if (fname) {
+		fp = (NOT_LONE_DASH(fname)) ? xfopen_for_read(fname) : (FILE *)text;
+		src_buf = bb_common_bufsiz1;
+	// N.B. strlen(NULL) segfaults!
+	} else if (text) {
+		// though we do not call uuencode(NULL, NULL) explicitly
+		// still we do not want to break things suddenly
+		len = strlen(text);
+	} else
+		return;
+
+	while (1) {
+		size_t size;
+		if (fname) {
+			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 {
+			size = len;
+			if (len > SRC_BUF_SIZE)
+				size = SRC_BUF_SIZE;
+		}
+		if (!size)
+			break;
+		// encode the buffer we just read in
+		bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
+		if (fname) {
+			printf("%s\n", eol);
+		} else {
+			src_buf += size;
+			len -= size;
+		}
+		fwrite(dst_buf, 1, 4 * ((size + 2) / 3), stdout);
+	}
+	if (fname && NOT_LONE_DASH(fname))
+		fclose(fp);
+#undef src_buf
+}
+
+void FAST_FUNC decode_base64(FILE *src_stream, FILE *dst_stream)
+{
+	int term_count = 1;
+
+	while (1) {
+		char translated[4];
+		int count = 0;
+
+		while (count < 4) {
+			char *table_ptr;
+			int ch;
+
+			/* Get next _valid_ character.
+			 * global vector bb_uuenc_tbl_base64[] contains this string:
+			 * "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n"
+			 */
+			do {
+				ch = fgetc(src_stream);
+				if (ch == EOF) {
+					bb_error_msg_and_die(bb_msg_read_error);
+				}
+				// - means end of MIME section
+				if ('-' == ch) {
+					// push it back
+					ungetc(ch, src_stream);
+					return;
+				}
+				table_ptr = strchr(bb_uuenc_tbl_base64, ch);
+			} while (table_ptr == NULL);
+
+			/* Convert encoded character to decimal */
+			ch = table_ptr - bb_uuenc_tbl_base64;
+
+			if (*table_ptr == '=') {
+				if (term_count == 0) {
+					translated[count] = '\0';
+					break;
+				}
+				term_count++;
+			} else if (*table_ptr == '\n') {
+				/* Check for terminating line */
+				if (term_count == 5) {
+					return;
+				}
+				term_count = 1;
+				continue;
+			} else {
+				translated[count] = ch;
+				count++;
+				term_count = 0;
+			}
+		}
+
+		/* Merge 6 bit chars to 8 bit */
+		if (count > 1) {
+			fputc(translated[0] << 2 | translated[1] >> 4, dst_stream);
+		}
+		if (count > 2) {
+			fputc(translated[1] << 4 | translated[2] >> 2, dst_stream);
+		}
+		if (count > 3) {
+			fputc(translated[2] << 6 | translated[3], dst_stream);
+		}
+	}
+}
+
+
+/*
+ * get username and password from a file descriptor
+ */
+void FAST_FUNC get_cred_or_die(int fd)
+{
+	// either from TTY
+	if (isatty(fd)) {
+		G.user = xstrdup(bb_askpass(0, "User: "));
+		G.pass = xstrdup(bb_askpass(0, "Password: "));
+	// or from STDIN
+	} else {
+		FILE *fp = fdopen(fd, "r");
+		G.user = xmalloc_fgetline(fp);
+		G.pass = xmalloc_fgetline(fp);
+		fclose(fp);
+	}
+	if (!G.user || !*G.user || !G.pass || !*G.pass)
+		bb_error_msg_and_die("no username or password");
+}
diff --git a/mailutils/mail.h b/mailutils/mail.h
new file mode 100644
index 0000000..bb747c4
--- /dev/null
+++ b/mailutils/mail.h
@@ -0,0 +1,35 @@
+
+struct globals {
+	pid_t helper_pid;
+	unsigned timeout;
+	unsigned opts;
+	char *user;
+	char *pass;
+	FILE *fp0; // initial stdin
+	char *opt_charset;
+	char *content_type;
+};
+
+#define G (*ptr_to_globals)
+#define timeout         (G.timeout  )
+#define opts            (G.opts     )
+//#define user            (G.user     )
+//#define pass            (G.pass     )
+//#define fp0             (G.fp0      )
+//#define opt_charset     (G.opt_charset)
+//#define content_type    (G.content_type)
+#define INIT_G() do { \
+	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+	G.opt_charset = (char *)CONFIG_FEATURE_MIME_CHARSET; \
+	G.content_type = (char *)"text/plain"; \
+} while (0)
+
+//char FAST_FUNC *parse_url(char *url, char **user, char **pass);
+
+void FAST_FUNC launch_helper(const char **argv);
+void FAST_FUNC get_cred_or_die(int fd);
+
+const FAST_FUNC char *command(const char *fmt, const char *param);
+
+void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol);
+void FAST_FUNC decode_base64(FILE *src_stream, FILE *dst_stream);
diff --git a/mailutils/mime.c b/mailutils/mime.c
new file mode 100644
index 0000000..b81cfd5
--- /dev/null
+++ b/mailutils/mime.c
@@ -0,0 +1,354 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * makemime: create MIME-encoded message
+ * reformime: parse MIME-encoded message
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+/*
+  makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \
+                   [-a "Header: Contents"] file
+           -m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file
+           -j [-o file] file1 file2
+           @file
+
+   file:  filename    - read or write from filename
+          -           - read or write from stdin or stdout
+          &n          - read or write from file descriptor n
+          \( opts \)  - read from child process, that generates [ opts ]
+
+Options:
+
+  -c type         - create a new MIME section from "file" with this
+                    Content-Type: (default is application/octet-stream).
+  -C charset      - MIME charset of a new text/plain section.
+  -N name         - MIME content name of the new mime section.
+  -m [ type ]     - create a multipart mime section from "file" of this
+                    Content-Type: (default is multipart/mixed).
+  -e encoding     - use the given encoding (7bit, 8bit, quoted-printable,
+                    or base64), instead of guessing.  Omit "-e" and use
+                    -c auto to set Content-Type: to text/plain or
+                    application/octet-stream based on picked encoding.
+  -j file1 file2  - join mime section file2 to multipart section file1.
+  -o file         - write ther result to file, instead of stdout (not
+                    allowed in child processes).
+  -a header       - prepend an additional header to the output.
+
+  @file - read all of the above options from file, one option or
+          value on each line.
+*/
+
+int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int makemime_main(int argc UNUSED_PARAM, char **argv)
+{
+	llist_t *opt_headers = NULL, *l;
+	const char *opt_output;
+#define boundary opt_output
+
+	enum {
+		OPT_c = 1 << 0,         // Content-Type:
+		OPT_e = 1 << 1,         // Content-Transfer-Encoding. Ignored. Assumed base64
+		OPT_o = 1 << 2,         // output to
+		OPT_C = 1 << 3,         // charset
+		OPT_N = 1 << 4,         // COMPAT
+		OPT_a = 1 << 5,         // additional headers
+		OPT_m = 1 << 6,         // COMPAT
+		OPT_j = 1 << 7,         // COMPAT
+	};
+
+	INIT_G();
+
+	// parse options
+	opt_complementary = "a::";
+	opts = getopt32(argv,
+		"c:e:o:C:N:a:m:j:",
+		&G.content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers, NULL, NULL
+	);
+	//argc -= optind;
+	argv += optind;
+
+	// respect -o output
+	if (opts & OPT_o)
+		freopen(opt_output, "w", stdout);
+
+	// no files given on command line? -> use stdin
+	if (!*argv)
+		*--argv = (char *)"-";
+
+	// put additional headers
+	for (l = opt_headers; l; l = l->link)
+		puts(l->data);
+
+	// make a random string -- it will delimit message parts
+	srand(monotonic_us());
+	boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
+
+	// put multipart header
+	printf(
+		"Mime-Version: 1.0\n"
+		"Content-Type: multipart/mixed; boundary=\"%s\"\n"
+		, boundary
+	);
+
+	// put attachments
+	while (*argv) {
+		printf(
+			"\n--%s\n"
+			"Content-Type: %s; charset=%s\n"
+			"Content-Disposition: inline; filename=\"%s\"\n"
+			"Content-Transfer-Encoding: base64\n"
+			, boundary
+			, G.content_type
+			, G.opt_charset
+			, bb_get_last_path_component_strip(*argv)
+		);
+		encode_base64(*argv++, (const char *)stdin, "");
+	}
+
+	// put multipart footer
+	printf("\n--%s--\n" "\n", boundary);
+
+	return EXIT_SUCCESS;
+#undef boundary
+}
+
+static const char *find_token(const char *const string_array[], const char *key, const char *defvalue)
+{
+	const char *r = NULL;
+	for (int i = 0; string_array[i] != 0; i++) {
+		if (strcasecmp(string_array[i], key) == 0) {
+			r = (char *)string_array[i+1];
+			break;
+		}
+	}
+	return (r) ? r : defvalue;
+}
+
+static const char *xfind_token(const char *const string_array[], const char *key)
+{
+	const char *r = find_token(string_array, key, NULL);
+	if (r)
+		return r;
+	bb_error_msg_and_die("header: %s", key);
+}
+
+enum {
+	OPT_x = 1 << 0,
+	OPT_X = 1 << 1,
+#if ENABLE_FEATURE_REFORMIME_COMPAT
+	OPT_d = 1 << 2,
+	OPT_e = 1 << 3,
+	OPT_i = 1 << 4,
+	OPT_s = 1 << 5,
+	OPT_r = 1 << 6,
+	OPT_c = 1 << 7,
+	OPT_m = 1 << 8,
+	OPT_h = 1 << 9,
+	OPT_o = 1 << 10,
+	OPT_O = 1 << 11,
+#endif
+};
+
+static int parse(const char *boundary, char **argv)
+{
+	char *line, *s, *p;
+	const char *type;
+	int boundary_len = strlen(boundary);
+	const char *delims = " ;\"\t\r\n";
+	const char *uniq;
+	int ntokens;
+	const char *tokens[32]; // 32 is enough
+
+	// prepare unique string pattern
+	uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname());
+
+//bb_info_msg("PARSE[%s]", terminator);
+
+	while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) {
+
+		// seek to start of MIME section
+		// N.B. to avoid false positives let us seek to the _last_ occurance
+		p = NULL;
+		s = line;
+		while ((s=strcasestr(s, "Content-Type:")) != NULL)
+			p = s++;
+		if (!p)
+			goto next;
+//bb_info_msg("L[%s]", p);
+
+		// split to tokens
+		// TODO: strip of comments which are of form: (comment-text)
+		ntokens = 0;
+		tokens[ntokens] = NULL;
+		for (s = strtok(p, delims); s; s = strtok(NULL, delims)) {
+			tokens[ntokens] = s;
+			if (ntokens < ARRAY_SIZE(tokens) - 1)
+				ntokens++;
+//bb_info_msg("L[%d][%s]", ntokens, s);
+		}
+		tokens[ntokens] = NULL;
+//bb_info_msg("N[%d]", ntokens);
+
+		// analyse tokens
+		type = find_token(tokens, "Content-Type:", "text/plain");
+//bb_info_msg("T[%s]", type);
+		if (0 == strncasecmp(type, "multipart/", 10)) {
+			if (0 == strcasecmp(type+10, "mixed")) {
+				parse(xfind_token(tokens, "boundary="), argv);
+			} else
+				bb_error_msg_and_die("no support of content type '%s'", type);
+		} else {
+			pid_t pid = pid;
+			int rc;
+			FILE *fp;
+			// fetch charset
+			const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET);
+			// fetch encoding
+			const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
+			// compose target filename
+			char *filename = (char *)find_token(tokens, "filename=", NULL);
+			if (!filename)
+				filename = xasprintf(uniq, monotonic_us());
+			else
+				filename = bb_get_last_path_component_strip(xstrdup(filename));
+
+			// start external helper, if any
+			if (opts & OPT_X) {
+				int fd[2];
+				xpipe(fd);
+				pid = fork();
+				if (0 == pid) {
+					// child reads from fd[0]
+					xdup2(fd[0], STDIN_FILENO);
+					close(fd[0]); close(fd[1]);
+					xsetenv("CONTENT_TYPE", type);
+					xsetenv("CHARSET", charset);
+					xsetenv("ENCODING", encoding);
+					xsetenv("FILENAME", filename);
+					BB_EXECVP(*argv, argv);
+					_exit(EXIT_FAILURE);
+				}
+				// parent dumps to fd[1]
+				close(fd[0]);
+				fp = fdopen(fd[1], "w");
+				signal(SIGPIPE, SIG_IGN); // ignore EPIPE
+			// or create a file for dump
+			} else {
+				char *fname = xasprintf("%s%s", *argv, filename);
+				fp = xfopen_for_write(fname);
+				free(fname);
+			}
+
+			// housekeeping
+			free(filename);
+
+			// dump to fp
+			if (0 == strcasecmp(encoding, "base64")) {
+				decode_base64(stdin, fp);
+			} else if (0 != strcasecmp(encoding, "7bit")
+				&& 0 != strcasecmp(encoding, "8bit")) {
+				// quoted-printable, binary, user-defined are unsupported so far
+				bb_error_msg_and_die("no support of encoding '%s'", encoding);
+			} else {
+				// N.B. we have written redundant \n. so truncate the file
+				// The following weird 2-tacts reading technique is due to
+				// we have to not write extra \n at the end of the file
+				// In case of -x option we could truncate the resulting file as
+				// fseek(fp, -1, SEEK_END);
+				// if (ftruncate(fileno(fp), ftell(fp)))
+				//	bb_perror_msg("ftruncate");
+				// But in case of -X we have to be much more careful. There is
+				// no means to truncate what we already have sent to the helper.
+				p = xmalloc_fgets_str(stdin, "\r\n");
+				while (p) {
+					if ((s = xmalloc_fgets_str(stdin, "\r\n")) == NULL)
+						break;
+					if ('-' == s[0] && '-' == s[1]
+						&& 0 == strncmp(s+2, boundary, boundary_len))
+						break;
+					fputs(p, fp);
+					p = s;
+				}
+
+/*
+				while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) {
+					if ('-' == s[0] && '-' == s[1]
+						&& 0 == strncmp(s+2, boundary, boundary_len))
+						break;
+					fprintf(fp, "%s\n", s);
+				}
+				// N.B. we have written redundant \n. so truncate the file
+				fseek(fp, -1, SEEK_END);
+				if (ftruncate(fileno(fp), ftell(fp)))
+					bb_perror_msg("ftruncate");
+*/
+			}
+			fclose(fp);
+
+			// finalize helper
+			if (opts & OPT_X) {
+				signal(SIGPIPE, SIG_DFL);
+				// exit if helper exited >0
+				rc = wait4pid(pid);
+				if (rc)
+					return rc+20;
+			}
+
+			// check multipart finalized
+			if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) {
+				free(line);
+				break;
+			}
+		}
+ next:
+ 		free(line);
+	}
+
+//bb_info_msg("ENDPARSE[%s]", boundary);
+
+	return EXIT_SUCCESS;
+}
+
+/*
+Usage: reformime [options]
+    -d - parse a delivery status notification.
+    -e - extract contents of MIME section.
+    -x - extract MIME section to a file.
+    -X - pipe MIME section to a program.
+    -i - show MIME info.
+    -s n.n.n.n - specify MIME section.
+    -r - rewrite message, filling in missing MIME headers.
+    -r7 - also convert 8bit/raw encoding to quoted-printable, if possible.
+    -r8 - also convert quoted-printable encoding to 8bit, if possible.
+    -c charset - default charset for rewriting, -o, and -O.
+    -m [file] [file]... - create a MIME message digest.
+    -h "header" - decode RFC 2047-encoded header.
+    -o "header" - encode unstructured header using RFC 2047.
+    -O "header" - encode address list header using RFC 2047.
+*/
+
+int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int reformime_main(int argc UNUSED_PARAM, char **argv)
+{
+	const char *opt_prefix = "";
+
+	INIT_G();
+
+	// parse options
+	// N.B. only -x and -X are supported so far
+	opt_complementary = "x--X:X--x" USE_FEATURE_REFORMIME_COMPAT(":m::");
+	opts = getopt32(argv,
+		"x:X" USE_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"),
+		&opt_prefix
+		USE_FEATURE_REFORMIME_COMPAT(, NULL, NULL, &G.opt_charset, NULL, NULL, NULL, NULL)
+	);
+	//argc -= optind;
+	argv += optind;
+
+	return parse("", (opts & OPT_X) ? argv : (char **)&opt_prefix);
+}
diff --git a/mailutils/popmaildir.c b/mailutils/popmaildir.c
new file mode 100644
index 0000000..d2cc7c0
--- /dev/null
+++ b/mailutils/popmaildir.c
@@ -0,0 +1,237 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * popmaildir: a simple yet powerful POP3 client
+ * Delivers contents of remote mailboxes to local Maildir
+ *
+ * Inspired by original utility by Nikola Vladov
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+static void pop3_checkr(const char *fmt, const char *param, char **ret)
+{
+	const char *msg = command(fmt, param);
+	char *answer = xmalloc_fgetline(stdin);
+	if (answer && '+' == *answer) {
+		if (timeout)
+			alarm(0);
+		if (ret)
+			*ret = answer+4; // skip "+OK "
+		else if (ENABLE_FEATURE_CLEAN_UP)
+			free(answer);
+		return;
+	}
+	bb_error_msg_and_die("%s failed: %s", msg, answer);
+}
+
+static void pop3_check(const char *fmt, const char *param)
+{
+	pop3_checkr(fmt, param, NULL);
+}
+
+int popmaildir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int popmaildir_main(int argc UNUSED_PARAM, char **argv)
+{
+	char *buf;
+	unsigned nmsg;
+	char *hostname;
+	pid_t pid;
+	const char *retr;
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+	const char *delivery;
+#endif
+	unsigned opt_nlines = 0;
+
+	enum {
+		OPT_b = 1 << 0,		// -b binary mode. Ignored
+		OPT_d = 1 << 1,		// -d,-dd,-ddd debug. Ignored
+		OPT_m = 1 << 2,		// -m show used memory. Ignored
+		OPT_V = 1 << 3,		// -V version. Ignored
+		OPT_c = 1 << 4,		// -c use tcpclient. Ignored
+		OPT_a = 1 << 5,		// -a use APOP protocol
+		OPT_s = 1 << 6,		// -s skip authorization
+		OPT_T = 1 << 7,		// -T get messages with TOP instead with RETR
+		OPT_k = 1 << 8,		// -k keep retrieved messages on the server
+		OPT_t = 1 << 9,		// -t90 set timeout to 90 sec
+		OPT_R = 1 << 10,	// -R20000 remove old messages on the server >= 20000 bytes (requires -k). Ignored
+		OPT_Z = 1 << 11,	// -Z11-23 remove messages from 11 to 23 (dangerous). Ignored
+		OPT_L = 1 << 12,	// -L50000 not retrieve new messages >= 50000 bytes. Ignored
+		OPT_H = 1 << 13,	// -H30 type first 30 lines of a message; (-L12000 -H30). Ignored
+		OPT_M = 1 << 14,	// -M\"program arg1 arg2 ...\"; deliver by program. Treated like -F
+		OPT_F = 1 << 15,	// -F\"program arg1 arg2 ...\"; filter by program. Treated like -M
+	};
+
+	// init global variables
+	INIT_G();
+
+	// parse options
+	opt_complementary = "-1:dd:t+:R+:L+:H+";
+	opts = getopt32(argv,
+		"bdmVcasTkt:" "R:Z:L:H:" USE_FEATURE_POPMAILDIR_DELIVERY("M:F:"),
+		&timeout, NULL, NULL, NULL, &opt_nlines
+		USE_FEATURE_POPMAILDIR_DELIVERY(, &delivery, &delivery) // we treat -M and -F the same
+	);
+	//argc -= optind;
+	argv += optind;
+
+	// get auth info
+	if (!(opts & OPT_s))
+		get_cred_or_die(STDIN_FILENO);
+
+	// goto maildir
+	xchdir(*argv++);
+
+	// launch connect helper, if any
+	if (*argv)
+		launch_helper((const char **)argv);
+
+	// get server greeting
+	pop3_checkr(NULL, NULL, &buf);
+
+	// authenticate (if no -s given)
+	if (!(opts & OPT_s)) {
+		// server supports APOP and we want it? -> use it
+		if ('<' == *buf && (opts & OPT_a)) {
+			md5_ctx_t md5;
+			// yes! compose <stamp><password>
+			char *s = strchr(buf, '>');
+			if (s)
+				strcpy(s+1, G.pass);
+			s = buf;
+			// get md5 sum of <stamp><password>
+			md5_begin(&md5);
+			md5_hash(s, strlen(s), &md5);
+			md5_end(s, &md5);
+			// NOTE: md5 struct contains enough space
+			// so we reuse md5 space instead of xzalloc(16*2+1)
+#define md5_hex ((uint8_t *)&md5)
+//			uint8_t *md5_hex = (uint8_t *)&md5;
+			*bin2hex((char *)md5_hex, s, 16) = '\0';
+			// APOP
+			s = xasprintf("%s %s", G.user, md5_hex);
+#undef md5_hex
+			pop3_check("APOP %s", s);
+			if (ENABLE_FEATURE_CLEAN_UP) {
+				free(s);
+				free(buf-4); // buf is "+OK " away from malloc'ed string
+			}
+		// server ignores APOP -> use simple text authentication
+		} else {
+			// USER
+			pop3_check("USER %s", G.user);
+			// PASS
+			pop3_check("PASS %s", G.pass);
+		}
+	}
+
+	// get mailbox statistics
+	pop3_checkr("STAT", NULL, &buf);
+
+	// prepare message filename suffix
+	hostname = safe_gethostname();
+	pid = getpid();
+
+	// get messages counter
+	// NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
+	// we only need nmsg and atoi is just exactly what we need
+	// if atoi fails to convert buf into number it returns 0
+	// in this case the following loop simply will not be executed
+	nmsg = atoi(buf);
+	if (ENABLE_FEATURE_CLEAN_UP)
+		free(buf-4); // buf is "+OK " away from malloc'ed string
+
+	// loop through messages
+	retr = (opts & OPT_T) ? xasprintf("TOP %%u %u", opt_nlines) : "RETR %u";
+	for (; nmsg; nmsg--) {
+
+		char *filename;
+		char *target;
+		char *answer;
+		FILE *fp;
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+		int rc;
+#endif
+		// generate unique filename
+		filename  = xasprintf("tmp/%llu.%u.%s",
+			monotonic_us(), (unsigned)pid, hostname);
+
+		// retrieve message in ./tmp/ unless filter is specified
+		pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
+
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+		// delivery helper ordered? -> setup pipe
+		if (opts & (OPT_F|OPT_M)) {
+			// helper will have $FILENAME set to filename
+			xsetenv("FILENAME", filename);
+			fp = popen(delivery, "w");
+			unsetenv("FILENAME");
+			if (!fp) {
+				bb_perror_msg("delivery helper");
+				break;
+			}
+		} else
+#endif
+		// create and open file filename
+		fp = xfopen_for_write(filename);
+
+		// copy stdin to fp (either filename or delivery helper)
+		while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
+			char *s = answer;
+			if ('.' == answer[0]) {
+				if ('.' == answer[1])
+					s++;
+				else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
+					break;
+			}
+			//*strchrnul(s, '\r') = '\n';
+			fputs(s, fp);
+			free(answer);
+		}
+
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+		// analyse delivery status
+		if (opts & (OPT_F|OPT_M)) {
+			rc = pclose(fp);
+			if (99 == rc) // 99 means bail out
+				break;
+//			if (rc) // !0 means skip to the next message
+				goto skip;
+//			// 0 means continue
+		} else {
+			// close filename
+			fclose(fp);
+		}
+#endif
+
+		// delete message from server
+		if (!(opts & OPT_k))
+			pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
+
+		// atomically move message to ./new/
+		target = xstrdup(filename);
+		strncpy(target, "new", 3);
+		// ... or just stop receiving on failure
+		if (rename_or_warn(filename, target))
+			break;
+		free(target);
+
+#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
+ skip:
+#endif
+		free(filename);
+	}
+
+	// Bye
+	pop3_check("QUIT", NULL);
+
+	if (ENABLE_FEATURE_CLEAN_UP) {
+		free(G.user);
+		free(G.pass);
+	}
+
+	return EXIT_SUCCESS;
+}
diff --git a/mailutils/sendmail.c b/mailutils/sendmail.c
new file mode 100644
index 0000000..55555c3
--- /dev/null
+++ b/mailutils/sendmail.c
@@ -0,0 +1,388 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones sendmail
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+#include "mail.h"
+
+static int smtp_checkp(const char *fmt, const char *param, int code)
+{
+	char *answer;
+	const 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
+	while ((answer = xmalloc_fgetline(stdin)) != NULL)
+		if (strlen(answer) <= 3 || '-' != answer[3])
+			break;
+	if (answer) {
+		int n = atoi(answer);
+		if (timeout)
+			alarm(0);
+		free(answer);
+		if (-1 == code || n == code)
+			return n;
+	}
+	bb_error_msg_and_die("%s failed", msg);
+}
+
+static int smtp_check(const char *fmt, int code)
+{
+	return smtp_checkp(fmt, NULL, code);
+}
+
+// strip argument of bad chars
+static char *sane_address(char *str)
+{
+	char *s = str;
+	char *p = s;
+	while (*s) {
+		if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
+			*p++ = *s;
+		}
+		s++;
+	}
+	*p = '\0';
+	return str;
+}
+
+static void rcptto(const char *s)
+{
+	smtp_checkp("RCPT TO:<%s>", s, 250);
+}
+
+int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sendmail_main(int argc UNUSED_PARAM, char **argv)
+{
+#if ENABLE_FEATURE_SENDMAIL_MAILX
+	llist_t *opt_attachments = NULL;
+	const char *opt_subject;
+#if ENABLE_FEATURE_SENDMAIL_MAILXX
+	llist_t *opt_carboncopies = NULL;
+	char *opt_errors_to;
+#endif
+#endif
+	char *opt_connect = opt_connect;
+	char *opt_from, *opt_fullname;
+	char *boundary;
+	llist_t *l;
+	llist_t *headers = NULL;
+	char *domain = sane_address(safe_getdomainname());
+	int code;
+
+	enum {
+		OPT_w = 1 << 0,         // network timeout
+		OPT_t = 1 << 1,         // read message for recipients
+		OPT_N = 1 << 2,         // request notification
+		OPT_f = 1 << 3,         // sender address
+		OPT_F = 1 << 4,         // sender name, overrides $NAME
+		OPT_s = 1 << 5,         // subject
+		OPT_j = 1 << 6,         // assumed charset
+		OPT_a = 1 << 7,         // attachment(s)
+		OPT_H = 1 << 8,         // use external connection helper
+		OPT_S = 1 << 9,         // specify connection string
+		OPT_c = 1 << 10,        // carbon copy
+		OPT_e = 1 << 11,        // errors-to address
+	};
+
+	// init global variables
+	INIT_G();
+
+	// save initial stdin since body is piped!
+	xdup2(STDIN_FILENO, 3);
+	G.fp0 = fdopen(3, "r");
+
+	// parse options
+	opt_complementary = "w+" USE_FEATURE_SENDMAIL_MAILX(":a::H--S:S--H") USE_FEATURE_SENDMAIL_MAILXX(":c::");
+	opts = getopt32(argv,
+		"w:t" "N:f:F:" USE_FEATURE_SENDMAIL_MAILX("s:j:a:H:S:") USE_FEATURE_SENDMAIL_MAILXX("c:e:")
+		"X:V:vq:R:O:o:nmL:Iih:GC:B:b:A:" // postfix compat only, ignored
+		// r:Q:p:M:Dd are candidates from another man page. TODO?
+		"46E", // ssmtp introduces another quirks. TODO?: -a[upm] (user, pass, method) to be supported
+		&timeout /* -w */, NULL, &opt_from, &opt_fullname,
+		USE_FEATURE_SENDMAIL_MAILX(&opt_subject, &G.opt_charset, &opt_attachments, &opt_connect, &opt_connect,)
+		USE_FEATURE_SENDMAIL_MAILXX(&opt_carboncopies, &opt_errors_to,)
+		NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+	);
+	//argc -= optind;
+	argv += optind;
+
+	// connect to server
+
+#if ENABLE_FEATURE_SENDMAIL_MAILX
+	// N.B. -H and -S are mutually exclusive so they do not spoil opt_connect
+	// connection helper ordered? ->
+	if (opts & OPT_H) {
+		const char *args[] = { "sh", "-c", opt_connect, NULL };
+		// plug it in
+		launch_helper(args);
+	// vanilla connection
+	} else
+#endif
+	{
+		int fd;
+		// host[:port] not explicitly specified ? -> use $SMTPHOST
+		// no $SMTPHOST ? -> use localhost
+		if (!(opts & OPT_S)) {
+			opt_connect = getenv("SMTPHOST");
+			if (!opt_connect)
+				opt_connect = (char *)"127.0.0.1";
+		}
+		// do connect
+		fd = create_and_connect_stream_or_die(opt_connect, 25);
+		// and make ourselves a simple IO filter
+		xmove_fd(fd, STDIN_FILENO);
+		xdup2(STDIN_FILENO, STDOUT_FILENO);
+	}
+	// N.B. from now we know nothing about network :)
+
+	// wait for initial server OK
+	// N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure
+	// so we need to push the server to see whether we are ok
+	code = smtp_check("NOOP", -1);
+	// 220 on plain connection, 250 on openssl-helped TLS session
+	if (220 == code)
+		smtp_check(NULL, 250); // reread the code to stay in sync
+	else if (250 != code)
+		bb_error_msg_and_die("INIT failed");
+
+	// we should start with modern EHLO
+	if (250 != smtp_checkp("EHLO %s", domain, -1)) {
+		smtp_checkp("HELO %s", domain, 250);
+	}
+
+	// set sender
+	// N.B. we have here a very loosely defined algotythm
+	// since sendmail historically offers no means to specify secrets on cmdline.
+	// 1) server can require no authentication ->
+	//	we must just provide a (possibly fake) reply address.
+	// 2) server can require AUTH ->
+	//	we must provide valid username and password along with a (possibly fake) reply address.
+	//	For the sake of security username and password are to be read either from console or from a secured file.
+	//	Since reading from console may defeat usability, the solution is either to read from a predefined
+	//	file descriptor (e.g. 4), or again from a secured file.
+
+	// got no sender address? -> use system username as a resort
+	if (!(opts & OPT_f)) {
+		// N.B. IMHO getenv("USER") can be way easily spoofed!
+		G.user = bb_getpwuid(NULL, -1, getuid());
+		opt_from = xasprintf("%s@%s", G.user, domain);
+	}
+	if (ENABLE_FEATURE_CLEAN_UP)
+		free(domain);
+
+	code = -1; // first try softly without authentication
+	while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) {
+		// MAIL FROM failed -> authentication needed
+		if (334 == smtp_check("AUTH LOGIN", -1)) {
+			// we must read credentials
+			get_cred_or_die(4);
+			encode_base64(NULL, G.user, NULL);
+			smtp_check("", 334);
+			encode_base64(NULL, G.pass, NULL);
+			smtp_check("", 235);
+		}
+		// authenticated OK? -> retry to set sender
+		// but this time die on failure!
+		code = 250;
+	}
+
+	// recipients specified as arguments
+	while (*argv) {
+		char *s = sane_address(*argv);
+		// loose test on email address validity
+//		if (strchr(s, '@')) {
+			rcptto(s);
+			llist_add_to_end(&headers, xasprintf("To: %s", s));
+//		}
+		argv++;
+	}
+
+#if ENABLE_FEATURE_SENDMAIL_MAILXX
+	// carbon copies recipients specified as -c options
+	for (l = opt_carboncopies; l; l = l->link) {
+		char *s = sane_address(l->data);
+		// loose test on email address validity
+//		if (strchr(s, '@')) {
+			rcptto(s);
+			// TODO: do we ever need to mangle the message?
+			//llist_add_to_end(&headers, xasprintf("Cc: %s", s));
+//		}
+	}
+#endif
+
+	// 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 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 & OPT_t || !headers) {
+		// fetch recipients and (optionally) subject
+		char *s;
+		while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
+			if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Cc: ", s, 4)) {
+				rcptto(sane_address(s+4));
+				llist_add_to_end(&headers, s);
+			} else if (0 == strncasecmp("Bcc: ", s, 5)) {
+				rcptto(sane_address(s+5));
+				free(s);
+				// N.B. Bcc vanishes from headers!
+			} else if (0 == strncmp("Subject: ", s, 9)) {
+				// we read subject -> use it verbatim unless it is specified
+				// on command line
+				if (!(opts & OPT_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; // stop on the first empty line
+			}
+		}
+	}
+
+	// enter "put message" mode
+	smtp_check("DATA", 354);
+
+	// put headers we could have preread with -t
+	for (l = headers; l; l = l->link) {
+		printf("%s\r\n", l->data);
+		if (ENABLE_FEATURE_CLEAN_UP)
+			free(l->data);
+	}
+
+	// put (possibly encoded) subject
+#if ENABLE_FEATURE_SENDMAIL_MAILX
+	if (opts & OPT_s) {
+		printf("Subject: ");
+		if (opts & OPT_j) {
+			printf("=?%s?B?", G.opt_charset);
+			encode_base64(NULL, opt_subject, NULL);
+			printf("?=");
+		} else {
+			printf("%s", opt_subject);
+		}
+		printf("\r\n");
+	}
+#endif
+
+	// put sender name, $NAME is the default
+	if (!(opts & OPT_F))
+		opt_fullname = getenv("NAME");
+	if (opt_fullname)
+		printf("From: \"%s\" <%s>\r\n", opt_fullname, opt_from);
+
+	// put notification
+	if (opts & OPT_N)
+		printf("Disposition-Notification-To: %s\r\n", opt_from);
+
+#if ENABLE_FEATURE_SENDMAIL_MAILXX
+	// put errors recipient
+	if (opts & OPT_e)
+		printf("Errors-To: %s\r\n", opt_errors_to);
+#endif
+
+	// make a random string -- it will delimit message parts
+	srand(monotonic_us());
+	boundary = xasprintf("%d=_%d-%d", rand(), rand(), rand());
+
+	// 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(
+			"Mime-Version: 1.0\r\n"
+			"%smultipart/mixed; boundary=\"%s\"\r\n"
+			, "Content-Type: "
+			, boundary
+		);
+
+		// 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"
+			"%stext/plain; charset=%s\r\n"
+			"%s%s\r\n"
+			"%s"
+		;
+		p = G.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"
+			;
+			encode_base64(l->data, (const char *)G.fp0, "\r");
+			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);
+
+	// no attachments? -> just dump message
+	} else
+#endif
+	{
+		char *s;
+		// terminate headers
+		printf("\r\n");
+		// put plain text respecting leading dots
+		while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
+			// escape leading dots
+			// N.B. this feature is implied even if no -i (-oi) switch given
+			// N.B. we need to escape the leading dot regardless of
+			// whether it is single or not character on the line
+			if ('.' == 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(G.fp0);
+
+	return EXIT_SUCCESS;
+}