mirror of https://github.com/mirror/busybox.git
add mailutils/*
parent
b9d572a273
commit
239d06bd47
|
@ -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 <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
|
|
@ -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");
|
||||||
|
}
|
|
@ -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 <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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue