9 |
#include "libbb.h" |
#include "libbb.h" |
10 |
#include "mail.h" |
#include "mail.h" |
11 |
|
|
12 |
|
// limit maximum allowed number of headers to prevent overflows. |
13 |
|
// set to 0 to not limit |
14 |
|
#define MAX_HEADERS 256 |
15 |
|
|
16 |
static int smtp_checkp(const char *fmt, const char *param, int code) |
static int smtp_checkp(const char *fmt, const char *param, int code) |
17 |
{ |
{ |
18 |
char *answer; |
char *answer; |
59 |
|
|
60 |
static void rcptto(const char *s) |
static void rcptto(const char *s) |
61 |
{ |
{ |
62 |
smtp_checkp("RCPT TO:<%s>", s, 250); |
// N.B. we don't die if recipient is rejected, for the other recipients may be accepted |
63 |
|
if (250 != smtp_checkp("RCPT TO:<%s>", s, -1)) |
64 |
|
bb_error_msg("Bad recipient: <%s>", s); |
65 |
} |
} |
66 |
|
|
67 |
int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
68 |
int sendmail_main(int argc UNUSED_PARAM, char **argv) |
int sendmail_main(int argc UNUSED_PARAM, char **argv) |
69 |
{ |
{ |
|
#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 |
|
70 |
char *opt_connect = opt_connect; |
char *opt_connect = opt_connect; |
71 |
char *opt_from, *opt_fullname; |
char *opt_from; |
72 |
char *boundary; |
char *s; |
73 |
llist_t *l; |
llist_t *list = NULL; |
|
llist_t *headers = NULL; |
|
74 |
char *domain = sane_address(safe_getdomainname()); |
char *domain = sane_address(safe_getdomainname()); |
75 |
|
unsigned nheaders = 0; |
76 |
int code; |
int code; |
77 |
|
|
78 |
enum { |
enum { |
79 |
OPT_w = 1 << 0, // network timeout |
//--- standard options |
80 |
OPT_t = 1 << 1, // read message for recipients |
OPT_t = 1 << 0, // read message for recipients, append them to those on cmdline |
81 |
OPT_N = 1 << 2, // request notification |
OPT_f = 1 << 1, // sender address |
82 |
OPT_f = 1 << 3, // sender address |
OPT_o = 1 << 2, // various options. -oi IMPLIED! others are IGNORED! |
83 |
OPT_F = 1 << 4, // sender name, overrides $NAME |
OPT_i = 1 << 3, // IMPLIED! |
84 |
OPT_s = 1 << 5, // subject |
//--- BB specific options |
85 |
OPT_j = 1 << 6, // assumed charset |
OPT_w = 1 << 4, // network timeout |
86 |
OPT_a = 1 << 7, // attachment(s) |
OPT_H = 1 << 5, // use external connection helper |
87 |
OPT_H = 1 << 8, // use external connection helper |
OPT_S = 1 << 6, // specify connection string |
88 |
OPT_S = 1 << 9, // specify connection string |
OPT_a = 1 << 7, // authentication tokens |
|
OPT_c = 1 << 10, // carbon copy |
|
|
OPT_e = 1 << 11, // errors-to address |
|
89 |
}; |
}; |
90 |
|
|
91 |
// init global variables |
// init global variables |
93 |
|
|
94 |
// save initial stdin since body is piped! |
// save initial stdin since body is piped! |
95 |
xdup2(STDIN_FILENO, 3); |
xdup2(STDIN_FILENO, 3); |
96 |
G.fp0 = fdopen(3, "r"); |
G.fp0 = xfdopen_for_read(3); |
97 |
|
|
98 |
// parse options |
// parse options |
99 |
opt_complementary = "w+" USE_FEATURE_SENDMAIL_MAILX(":a::H--S:S--H") USE_FEATURE_SENDMAIL_MAILXX(":c::"); |
// -f is required. -H and -S are mutually exclusive |
100 |
opts = getopt32(argv, |
opt_complementary = "f:w+:H--S:S--H:a::"; |
101 |
"w:t" "N:f:F:" USE_FEATURE_SENDMAIL_MAILX("s:j:a:H:S:") USE_FEATURE_SENDMAIL_MAILXX("c:e:") |
// N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect |
102 |
"X:V:vq:R:O:o:nmL:Iih:GC:B:b:A:" // postfix compat only, ignored |
// -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility, |
103 |
// r:Q:p:M:Dd are candidates from another man page. TODO? |
// it is still under development. |
104 |
"46E", // ssmtp introduces another quirks. TODO?: -a[upm] (user, pass, method) to be supported |
opts = getopt32(argv, "tf:o:iw:H:S:a::", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list); |
|
&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 |
|
|
); |
|
105 |
//argc -= optind; |
//argc -= optind; |
106 |
argv += optind; |
argv += optind; |
107 |
|
|
108 |
|
// process -a[upm]<token> options |
109 |
|
if ((opts & OPT_a) && !list) |
110 |
|
bb_show_usage(); |
111 |
|
while (list) { |
112 |
|
char *a = (char *) llist_pop(&list); |
113 |
|
if ('u' == a[0]) |
114 |
|
G.user = xstrdup(a+1); |
115 |
|
if ('p' == a[0]) |
116 |
|
G.pass = xstrdup(a+1); |
117 |
|
// N.B. we support only AUTH LOGIN so far |
118 |
|
//if ('m' == a[0]) |
119 |
|
// G.method = xstrdup(a+1); |
120 |
|
} |
121 |
|
// N.B. list == NULL here |
122 |
|
//bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv); |
123 |
|
|
124 |
// connect to server |
// connect to server |
125 |
|
|
|
#if ENABLE_FEATURE_SENDMAIL_MAILX |
|
|
// N.B. -H and -S are mutually exclusive so they do not spoil opt_connect |
|
126 |
// connection helper ordered? -> |
// connection helper ordered? -> |
127 |
if (opts & OPT_H) { |
if (opts & OPT_H) { |
128 |
const char *args[] = { "sh", "-c", opt_connect, NULL }; |
const char *args[] = { "sh", "-c", opt_connect, NULL }; |
129 |
// plug it in |
// plug it in |
130 |
launch_helper(args); |
launch_helper(args); |
131 |
// vanilla connection |
// vanilla connection |
132 |
} else |
} else { |
|
#endif |
|
|
{ |
|
133 |
int fd; |
int fd; |
134 |
// host[:port] not explicitly specified ? -> use $SMTPHOST |
// host[:port] not explicitly specified? -> use $SMTPHOST |
135 |
// no $SMTPHOST ? -> use localhost |
// no $SMTPHOST ? -> use localhost |
136 |
if (!(opts & OPT_S)) { |
if (!(opts & OPT_S)) { |
137 |
opt_connect = getenv("SMTPHOST"); |
opt_connect = getenv("SMTPHOST"); |
148 |
|
|
149 |
// wait for initial server OK |
// wait for initial server OK |
150 |
// N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure |
// N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure |
151 |
// so we need to push the server to see whether we are ok |
// so we need to kick the server to see whether we are ok |
152 |
code = smtp_check("NOOP", -1); |
code = smtp_check("NOOP", -1); |
153 |
// 220 on plain connection, 250 on openssl-helped TLS session |
// 220 on plain connection, 250 on openssl-helped TLS session |
154 |
if (220 == code) |
if (220 == code) |
160 |
if (250 != smtp_checkp("EHLO %s", domain, -1)) { |
if (250 != smtp_checkp("EHLO %s", domain, -1)) { |
161 |
smtp_checkp("HELO %s", domain, 250); |
smtp_checkp("HELO %s", domain, 250); |
162 |
} |
} |
163 |
|
if (ENABLE_FEATURE_CLEAN_UP) |
164 |
|
free(domain); |
165 |
|
|
166 |
|
// perform authentication |
167 |
|
if (opts & OPT_a) { |
168 |
|
smtp_check("AUTH LOGIN", 334); |
169 |
|
// we must read credentials unless they are given via -a[up] options |
170 |
|
if (!G.user || !G.pass) |
171 |
|
get_cred_or_die(4); |
172 |
|
encode_base64(NULL, G.user, NULL); |
173 |
|
smtp_check("", 334); |
174 |
|
encode_base64(NULL, G.pass, NULL); |
175 |
|
smtp_check("", 235); |
176 |
|
} |
177 |
|
|
178 |
// set sender |
// set sender |
179 |
// N.B. we have here a very loosely defined algotythm |
// N.B. we have here a very loosely defined algotythm |
187 |
// file descriptor (e.g. 4), or again from a secured file. |
// file descriptor (e.g. 4), or again from a secured file. |
188 |
|
|
189 |
// got no sender address? -> use system username as a resort |
// got no sender address? -> use system username as a resort |
190 |
if (!(opts & OPT_f)) { |
// N.B. we marked -f as required option! |
191 |
// N.B. IMHO getenv("USER") can be way easily spoofed! |
//if (!G.user) { |
192 |
G.user = bb_getpwuid(NULL, -1, getuid()); |
// // N.B. IMHO getenv("USER") can be way easily spoofed! |
193 |
opt_from = xasprintf("%s@%s", G.user, domain); |
// G.user = xuid2uname(getuid()); |
194 |
} |
// opt_from = xasprintf("%s@%s", G.user, domain); |
195 |
if (ENABLE_FEATURE_CLEAN_UP) |
//} |
196 |
free(domain); |
//if (ENABLE_FEATURE_CLEAN_UP) |
197 |
|
// free(domain); |
198 |
code = -1; // first try softly without authentication |
smtp_checkp("MAIL FROM:<%s>", opt_from, 250); |
199 |
while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) { |
|
200 |
// MAIL FROM failed -> authentication needed |
// process message |
201 |
if (334 == smtp_check("AUTH LOGIN", -1)) { |
|
202 |
// we must read credentials |
// read recipients from message and add them to those given on cmdline. |
203 |
get_cred_or_die(4); |
// this means we scan stdin for To:, Cc:, Bcc: lines until an empty line |
204 |
encode_base64(NULL, G.user, NULL); |
// and then use the rest of stdin as message body |
205 |
smtp_check("", 334); |
code = 0; // set "analyze headers" mode |
206 |
encode_base64(NULL, G.pass, NULL); |
while ((s = xmalloc_fgetline(G.fp0)) != NULL) { |
207 |
smtp_check("", 235); |
dump: |
208 |
} |
// put message lines doubling leading dots |
209 |
// authenticated OK? -> retry to set sender |
if (code) { |
|
// 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) { |
|
210 |
// escape leading dots |
// escape leading dots |
211 |
// N.B. this feature is implied even if no -i (-oi) switch given |
// N.B. this feature is implied even if no -i (-oi) switch given |
212 |
// N.B. we need to escape the leading dot regardless of |
// N.B. we need to escape the leading dot regardless of |
215 |
printf("."); |
printf("."); |
216 |
// dump read line |
// dump read line |
217 |
printf("%s\r\n", s); |
printf("%s\r\n", s); |
218 |
|
free(s); |
219 |
|
continue; |
220 |
|
} |
221 |
|
|
222 |
|
// analyze headers |
223 |
|
// To: or Cc: headers add recipients |
224 |
|
if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Bcc: " + 1, s, 4)) { |
225 |
|
rcptto(sane_address(s+4)); |
226 |
|
goto addheader; |
227 |
|
// Bcc: header adds blind copy (hidden) recipient |
228 |
|
} else if (0 == strncasecmp("Bcc: ", s, 5)) { |
229 |
|
rcptto(sane_address(s+5)); |
230 |
|
free(s); |
231 |
|
// N.B. Bcc: vanishes from headers! |
232 |
|
|
233 |
|
// other headers go verbatim |
234 |
|
|
235 |
|
// N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines. |
236 |
|
// Continuation is denoted by prefixing additional lines with whitespace(s). |
237 |
|
// Thanks (stefan.seyfried at googlemail.com) for pointing this out. |
238 |
|
} else if (strchr(s, ':') || (list && skip_whitespace(s) != s)) { |
239 |
|
addheader: |
240 |
|
// N.B. we allow MAX_HEADERS generic headers at most to prevent attacks |
241 |
|
if (MAX_HEADERS && ++nheaders >= MAX_HEADERS) |
242 |
|
goto bail; |
243 |
|
llist_add_to_end(&list, s); |
244 |
|
// a line without ":" (an empty line too, by definition) doesn't look like a valid header |
245 |
|
// so stop "analyze headers" mode |
246 |
|
} else { |
247 |
|
reenter: |
248 |
|
// put recipients specified on cmdline |
249 |
|
while (*argv) { |
250 |
|
char *t = sane_address(*argv); |
251 |
|
rcptto(t); |
252 |
|
//if (MAX_HEADERS && ++nheaders >= MAX_HEADERS) |
253 |
|
// goto bail; |
254 |
|
llist_add_to_end(&list, xasprintf("To: %s", t)); |
255 |
|
argv++; |
256 |
|
} |
257 |
|
// enter "put message" mode |
258 |
|
// N.B. DATA fails iff no recipients were accepted (or even provided) |
259 |
|
// in this case just bail out gracefully |
260 |
|
if (354 != smtp_check("DATA", -1)) |
261 |
|
goto bail; |
262 |
|
// dump the headers |
263 |
|
while (list) { |
264 |
|
printf("%s\r\n", (char *) llist_pop(&list)); |
265 |
|
} |
266 |
|
// stop analyzing headers |
267 |
|
code++; |
268 |
|
// N.B. !s means: we read nothing, and nothing to be read in the future. |
269 |
|
// just dump empty line and break the loop |
270 |
|
if (!s) { |
271 |
|
puts("\r"); |
272 |
|
break; |
273 |
|
} |
274 |
|
// go dump message body |
275 |
|
// N.B. "s" already contains the first non-header line, so pretend we read it from input |
276 |
|
goto dump; |
277 |
} |
} |
278 |
} |
} |
279 |
|
// odd case: we didn't stop "analyze headers" mode -> message body is empty. Reenter the loop |
280 |
|
// N.B. after reenter code will be > 0 |
281 |
|
if (!code) |
282 |
|
goto reenter; |
283 |
|
|
284 |
// leave "put message" mode |
// finalize the message |
285 |
smtp_check(".", 250); |
smtp_check(".", 250); |
286 |
|
bail: |
287 |
// ... and say goodbye |
// ... and say goodbye |
288 |
smtp_check("QUIT", 221); |
smtp_check("QUIT", 221); |
289 |
// cleanup |
// cleanup |