* Licensed under GPLv2, see file LICENSE in this source tree.
*/
+//kbuild:lib-$(CONFIG_SENDMAIL) += sendmail.o mail.o
+
//usage:#define sendmail_trivial_usage
//usage: "[OPTIONS] [RECIPIENT_EMAIL]..."
//usage:#define sendmail_full_usage "\n\n"
//usage: "\n Examples:"
//usage: "\n -H 'exec openssl s_client -quiet -tls1 -starttls smtp"
//usage: "\n -connect smtp.gmail.com:25' <email.txt"
-//usage: "\n [4<username_and_passwd.txt | -au<username> -ap<password>]"
+//usage: "\n [4<username_and_passwd.txt | -auUSER -apPASS]"
//usage: "\n -H 'exec openssl s_client -quiet -tls1"
//usage: "\n -connect smtp.gmail.com:465' <email.txt"
-//usage: "\n [4<username_and_passwd.txt | -au<username> -ap<password>]"
+//usage: "\n [4<username_and_passwd.txt | -auUSER -apPASS]"
//usage: "\n -S HOST[:PORT] Server"
-//usage: "\n -au<username> Username for AUTH LOGIN"
-//usage: "\n -ap<password> Password for AUTH LOGIN"
-//usage: "\n -am<method> Authentication method. Ignored. LOGIN is implied"
+//usage: "\n -auUSER Username for AUTH LOGIN"
+//usage: "\n -apPASS Password for AUTH LOGIN"
+////usage: "\n -amMETHOD Authentication method. Ignored. LOGIN is implied"
//usage: "\n"
//usage: "\nOther options are silently ignored; -oi -t is implied"
//usage: IF_MAKEMIME(
-//usage: "\nUse makemime applet to create message with attachments"
+//usage: "\nUse makemime to create emails with attachments"
//usage: )
#include "libbb.h"
// if code != -1 then checks whether the number equals the code
// if not equal -> die saying msg
while ((answer = xmalloc_fgetline(stdin)) != NULL) {
-// if (verbose)
- bb_error_msg("recv:'%.*s' %d", (int)(strchrnul(answer, '\r') - answer), answer, verbose);
+ if (verbose)
+ bb_error_msg("recv:'%.*s'", (int)(strchrnul(answer, '\r') - answer), answer);
if (strlen(answer) <= 3 || '-' != answer[3])
break;
free(answer);
int n = atoi(answer);
if (timeout)
alarm(0);
- free(msg);
free(answer);
- if (-1 == code || n == code)
+ if (-1 == code || n == code) {
+ free(msg);
return n;
+ }
}
bb_error_msg_and_die("%s failed", msg);
}
// strip argument of bad chars
static char *sane_address(char *str)
{
- char *s = str;
- char *p = s;
+ char *s;
+
+ trim(str);
+ s = str;
while (*s) {
- if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
- *p++ = *s;
+ if (!isalnum(*s) && !strchr("_-.@", *s)) {
+ bb_error_msg("bad address '%s'", str);
+ /* returning "": */
+ str[0] = '\0';
+ return str;
}
s++;
}
- *p = '\0';
return str;
}
+// check for an address inside angle brackets, if not found fall back to normal
+static char *angle_address(char *str)
+{
+ char *s, *e;
+
+ trim(str);
+ e = last_char_is(str, '>');
+ if (e) {
+ s = strrchr(str, '<');
+ if (s) {
+ *e = '\0';
+ str = s + 1;
+ }
+ }
+ return sane_address(str);
+}
+
static void rcptto(const char *s)
{
+ if (!*s)
+ return;
// N.B. we don't die if recipient is rejected, for the other recipients may be accepted
if (250 != smtp_checkp("RCPT TO:<%s>", s, -1))
bb_error_msg("Bad recipient: <%s>", s);
}
+// send to a list of comma separated addresses
+static void rcptto_list(const char *list)
+{
+ char *str = xstrdup(list);
+ char *s = str;
+ char prev = 0;
+ int in_quote = 0;
+
+ while (*s) {
+ char ch = *s++;
+
+ if (ch == '"' && prev != '\\') {
+ in_quote = !in_quote;
+ } else if (!in_quote && ch == ',') {
+ s[-1] = '\0';
+ rcptto(angle_address(str));
+ str = s;
+ }
+ prev = ch;
+ }
+ if (prev != ',')
+ rcptto(angle_address(str));
+ free(str);
+}
+
int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int sendmail_main(int argc UNUSED_PARAM, char **argv)
{
char *opt_from;
char *s;
llist_t *list = NULL;
- char *domain = sane_address(safe_getdomainname());
+ char *host = sane_address(safe_gethostname());
unsigned nheaders = 0;
int code;
+ enum {
+ HDR_OTHER = 0,
+ HDR_TOCC,
+ HDR_BCC,
+ } last_hdr = 0;
+ int check_hdr;
+ int has_to = 0;
enum {
//--- standard options
const char *args[] = { "sh", "-c", opt_connect, NULL };
// plug it in
launch_helper(args);
- // vanilla connection
+ // Now:
+ // our stdout will go to helper's stdin,
+ // helper's stdout will be available on our stdin.
+
+ // Wait for initial server message.
+ // If helper (such as openssl) invokes STARTTLS, the initial 220
+ // is swallowed by helper (and not repeated after TLS is initiated).
+ // We will send NOOP cmd to server and check the response.
+ // We should get 220+250 on plain connection, 250 on STARTTLSed session.
+ //
+ // The problem here is some servers delay initial 220 message,
+ // and consider client to be a spammer if it starts sending cmds
+ // before 220 reached it. The code below is unsafe in this regard:
+ // in non-STARTTLSed case, we potentially send NOOP before 220
+ // is sent by server.
+ // Ideas? (--delay SECS opt? --assume-starttls-helper opt?)
+ code = smtp_check("NOOP", -1);
+ if (code == 220)
+ // we got 220 - this is not STARTTLSed connection,
+ // eat 250 response to our NOOP
+ smtp_check(NULL, 250);
+ else
+ if (code != 250)
+ bb_error_msg_and_die("SMTP init failed");
} else {
+ // vanilla connection
int fd;
// host[:port] not explicitly specified? -> use $SMTPHOST
- // no $SMTPHOST ? -> use localhost
+ // no $SMTPHOST? -> use localhost
if (!(opts & OPT_S)) {
opt_connect = getenv("SMTPHOST");
if (!opt_connect)
// and make ourselves a simple IO filter
xmove_fd(fd, STDIN_FILENO);
xdup2(STDIN_FILENO, STDOUT_FILENO);
+
+ // Wait for initial server 220 message
+ smtp_check(NULL, 220);
}
- // 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 kick 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);
- }
- if (ENABLE_FEATURE_CLEAN_UP)
- free(domain);
+ if (250 != smtp_checkp("EHLO %s", host, -1))
+ smtp_checkp("HELO %s", host, 250);
+ free(host);
// perform authentication
if (opts & OPT_a) {
}
// set sender
- // N.B. we have here a very loosely defined algotythm
+ // N.B. we have here a very loosely defined algorythm
// 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.
// G.user = xuid2uname(getuid());
// opt_from = xasprintf("%s@%s", G.user, domain);
//}
- //if (ENABLE_FEATURE_CLEAN_UP)
- // free(domain);
smtp_checkp("MAIL FROM:<%s>", opt_from, 250);
// process message
// analyze headers
// To: or Cc: headers add recipients
- if (0 == strncasecmp("To:", s, 3) || 0 == strncasecmp("Bcc:" + 1, s, 3)) {
- rcptto(sane_address(s+3));
- goto addheader;
- // Bcc: header adds blind copy (hidden) recipient
- } else if (0 == strncasecmp("Bcc:", s, 4)) {
- rcptto(sane_address(s+4));
- free(s);
- // N.B. Bcc: vanishes from headers!
-
- // other headers go verbatim
-
- // N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines.
- // Continuation is denoted by prefixing additional lines with whitespace(s).
- // Thanks (stefan.seyfried at googlemail.com) for pointing this out.
- } else if (strchr(s, ':') || (list && skip_whitespace(s) != s)) {
+ check_hdr = (0 == strncasecmp("To:", s, 3));
+ has_to |= check_hdr;
+ if (opts & OPT_t) {
+ if (check_hdr || 0 == strncasecmp("Bcc:" + 1, s, 3)) {
+ rcptto_list(s+3);
+ last_hdr = HDR_TOCC;
+ goto addheader;
+ }
+ // Bcc: header adds blind copy (hidden) recipient
+ if (0 == strncasecmp("Bcc:", s, 4)) {
+ rcptto_list(s+4);
+ free(s);
+ last_hdr = HDR_BCC;
+ continue; // N.B. Bcc: vanishes from headers!
+ }
+ }
+ check_hdr = (list && isspace(s[0]));
+ if (strchr(s, ':') || check_hdr) {
+ // other headers go verbatim
+ // N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines.
+ // Continuation is denoted by prefixing additional lines with whitespace(s).
+ // Thanks (stefan.seyfried at googlemail.com) for pointing this out.
+ if (check_hdr && last_hdr != HDR_OTHER) {
+ rcptto_list(s+1);
+ if (last_hdr == HDR_BCC)
+ continue;
+ // N.B. Bcc: vanishes from headers!
+ } else {
+ last_hdr = HDR_OTHER;
+ }
addheader:
// N.B. we allow MAX_HEADERS generic headers at most to prevent attacks
if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
goto bail;
llist_add_to_end(&list, s);
- // a line without ":" (an empty line too, by definition) doesn't look like a valid header
- // so stop "analyze headers" mode
} else {
+ // a line without ":" (an empty line too, by definition) doesn't look like a valid header
+ // so stop "analyze headers" mode
reenter:
// put recipients specified on cmdline
+ check_hdr = 1;
while (*argv) {
char *t = sane_address(*argv);
rcptto(t);
//if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
// goto bail;
- llist_add_to_end(&list, xasprintf("To: %s", t));
+ if (!has_to) {
+ const char *hdr;
+
+ if (check_hdr && argv[1])
+ hdr = "To: %s,";
+ else if (check_hdr)
+ hdr = "To: %s";
+ else if (argv[1])
+ hdr = "To: %s," + 3;
+ else
+ hdr = "To: %s" + 3;
+ llist_add_to_end(&list,
+ xasprintf(hdr, t));
+ check_hdr = 0;
+ }
argv++;
}
// enter "put message" mode