add mailutils/*
No binary (build failed)
—
(log yok)
| @@ -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 |
| @@ -0,0 +1,11 @@ | |
| + | # Makefile for busybox |
| + | # |
| + | # Copyright (C) 1999-2005 by Erik Andersen <[email protected]> |
| + | # |
| + | # 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 |
| @@ -0,0 +1,242 @@ | |
| + | /* vi: set sw=4 ts=4: */ |
| + | /* |
| + | * helper routines |
| + | * |
| + | * Copyright (C) 2008 by Vladimir Dronnikov <[email protected]> |
| + | * |
| + | * 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"); |
| + | } |
| @@ -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); |
| @@ -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 <[email protected]> |
| + | * |
| + | * 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); |
| + | } |
| @@ -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 <[email protected]> |
| + | * |
| + | * 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; |
| + | } |
| @@ -0,0 +1,388 @@ | |
| + | /* vi: set sw=4 ts=4: */ |
| + | /* |
| + | * bare bones sendmail |
| + | * |
| + | * Copyright (C) 2008 by Vladimir Dronnikov <[email protected]> |
| + | * |
| + | * 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; |
| + | } |