blob: 63305d1492f73d3484397413f96fa61df68cf5a4 [file] [log] [blame]
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +00001/* vi: set sw=4 ts=4: */
2/*
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +00003 * bare bones sendmail/fetchmail
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +00004 *
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
6 *
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 */
9#include "libbb.h"
10
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000011#define INITIAL_STDIN_FILENO 3
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000012
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000013static void uuencode(char *fname, const char *text)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000014{
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000015 enum {
16 SRC_BUF_SIZE = 45, /* This *MUST* be a multiple of 3 */
17 DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
18 };
19
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000020#define src_buf text
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000021 int fd;
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000022#define len fd
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000023 char dst_buf[DST_BUF_SIZE + 1];
24
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000025 if (fname) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000026 fd = INITIAL_STDIN_FILENO;
27 if (NOT_LONE_DASH(fname))
28 fd = xopen(fname, O_RDONLY);
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000029 src_buf = bb_common_bufsiz1;
30 } else {
31 len = strlen(text);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000032 }
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000033
34 fflush(stdout); // sync stdio and unistd output
35 while (1) {
36 size_t size;
37 if (fname) {
38 size = full_read(fd, (char *)src_buf, SRC_BUF_SIZE);
39 if ((ssize_t)size < 0)
40 bb_perror_msg_and_die(bb_msg_read_error);
41 } else {
42 size = len;
43 if (len > SRC_BUF_SIZE)
44 size = SRC_BUF_SIZE;
45 }
46 if (!size)
47 break;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000048 // encode the buffer we just read in
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000049 bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
50 if (fname) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000051 xwrite(STDOUT_FILENO, "\r\n", 2);
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000052 } else {
53 src_buf += size;
54 len -= size;
55 }
56 xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
57 }
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000058 if (fname)
Denis Vlasenkoa2980c62008-02-02 17:54:35 +000059 close(fd);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000060}
61
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000062static pid_t helper_pid;
63
64static void kill_helper(void)
65{
66 // TODO!!!: is there more elegant way to terminate child on program failure?
67 if (helper_pid > 0)
68 kill(helper_pid, SIGTERM);
69}
70
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000071// generic signal handler
72static void signal_handler(int signo)
73{
74 int err;
75
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000076 if (SIGALRM == signo) {
77 kill_helper();
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000078 bb_error_msg_and_die("timed out");
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000079 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000080
81 // SIGCHLD. reap zombies
82 if (wait_any_nohang(&err) > 0)
83 if (WIFEXITED(err) && WEXITSTATUS(err))
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000084#if ENABLE_FEATURE_SENDMAIL_BLOATY
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000085 bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000086#else
87 bb_error_msg_and_die("child failed");
88#endif
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +000089}
90
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000091static void launch_helper(const char **argv)
92{
93 // setup vanilla unidirectional pipes interchange
94 int idx;
95 int pipes[4];
96 xpipe(pipes);
97 xpipe(pipes+2);
98 helper_pid = vfork();
99 if (helper_pid < 0)
100 bb_perror_msg_and_die("vfork");
101 idx = (!helper_pid)*2;
102 xdup2(pipes[idx], STDIN_FILENO);
103 xdup2(pipes[3-idx], STDOUT_FILENO);
104 if (ENABLE_FEATURE_CLEAN_UP)
105 for (int i = 4; --i >= 0; )
106 if (pipes[i] > STDOUT_FILENO)
107 close(pipes[i]);
108 if (!helper_pid) {
109 // child - try to execute connection helper
110 BB_EXECVP(argv[0], (char **)argv);
111 _exit(127);
112 }
113 // parent - check whether child is alive
114 sig_catch(SIGCHLD, signal_handler);
115 sig_catch(SIGALRM, signal_handler);
116 signal_handler(SIGCHLD);
117 // child seems OK -> parent goes on
118}
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000119
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000120static unsigned timeout;
121
122static char *command(const char *fmt, const char *param)
123{
124 char *msg = (char *)fmt;
125 alarm(timeout);
126 if (msg) {
127// if (param)
128 msg = xasprintf(fmt, param);
129 printf("%s\r\n", msg);
130 }
131 fflush(stdout);
132 return msg;
133}
134
135static int smtp_checkp(const char *fmt, const char *param, int code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000136{
137 char *answer;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000138 char *msg = command(fmt, param);
139 // read stdin
140 // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
141 // parse first bytes to a number
142 // if code = -1 then just return this number
143 // if code != -1 then checks whether the number equals the code
144 // if not equal -> die saying msg
145#if ENABLE_FEATURE_SENDMAIL_EHLO
146 while ((answer = xmalloc_getline(stdin)) && '-' == answer[3]) ;
147#else
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000148 answer = xmalloc_getline(stdin);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000149#endif
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000150 if (answer) {
151 int n = atoi(answer);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000152 alarm(0);
153 if (ENABLE_FEATURE_CLEAN_UP) {
154 free(msg);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000155 free(answer);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000156 }
157 if (-1 == code || n == code) {
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000158 return n;
159 }
160 }
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000161 kill_helper();
162 bb_error_msg_and_die("%s failed", msg);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000163}
164
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000165static int smtp_check(const char *fmt, int code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000166{
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000167 return smtp_checkp(fmt, NULL, code);
168}
169
170static void pop3_checkr(const char *fmt, const char *param, char **ret)
171{
172 char *msg = command(fmt, param);
173 char *answer = xmalloc_getline(stdin);
174 if (answer && '+' == answer[0]) {
175 alarm(0);
176 if (ret)
177 *ret = answer;
178 else
179 free(answer);
180 return;
181 }
182 kill_helper();
183 bb_error_msg_and_die("%s failed", msg);
184}
185
186static void pop3_check(const char *fmt, const char *param)
187{
188 pop3_checkr(fmt, param, NULL);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000189}
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000190
191// strip argument of bad chars
192static char *sane(char *str)
193{
194 char *s = str;
195 char *p = s;
196 while (*s) {
197 if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
198 *p++ = *s;
199 }
200 s++;
201 }
202 *p = '\0';
203 return str;
204}
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000205
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000206static void pop3_message(int fd)
207{
208 char *answer;
209 // read stdin, copy to file fd
210 while ((answer = xmalloc_fgets_str(stdin, "\r\n"))) {
211 char *s = answer;
212 if ('.' == answer[0]) {
213 if ('.' == answer[1])
214 s++;
215 else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
216 break;
217 }
218 xwrite(fd, s, strlen(s));
219 free(answer);
220 }
221 close(fd);
222}
223
224static const char *args[] = {
225 "openssl", "s_client", "-quiet", "-connect", NULL, "-tls1", "-starttls", "smtp", NULL
226};
227#define opt_connect args[4]
228#define opt_after_connect args[5]
229
230int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
231int sendgetmail_main(int argc, char **argv)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000232{
233 llist_t *recipients = NULL;
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000234 char *from;
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000235 const char *subject;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000236 char *charset = (char *)"utf-8";
237
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000238 const char *opt_user;
239 const char *opt_pass;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000240 const char *opt_timeout;
241 const char *opt_chdir;
242
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000243 enum {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000244 OPT_C = 1 << 0, // chdir
245 OPT_w = 1 << 1, // network timeout
246 OPT_U = 1 << 2, // user
247 OPT_P = 1 << 3, // password
248 OPT_X = 1 << 4, // use openssl connection helper
249
250 OPTS_t = 1 << 5, // sendmail "to"
251 OPTF_t = 1 << 5, // fetchmail "TOP"
252
253 OPTS_f = 1 << 6, // sendmail "from"
254 OPTF_z = 1 << 6, // fetchmail "delete"
255
256 OPTS_n = 1 << 7, // notification
257 OPTS_s = 1 << 8, // subject given
258 OPTS_c = 1 << 9, // charset for subject and body
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000259 };
260
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000261 const char *options;
262 unsigned opts;
263
264 // SENDMAIL
265 if ('s' == applet_name[0]) {
266 // save initial stdin
267 xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
268 // -f must be specified
269 // -t may be multiple
270 opt_complementary = "-1:f:t::";
271 options = "C:w:U:P:X" "t:f:ns:c:";
272 // FETCHMAIL
273 } else {
274 opt_after_connect = NULL;
275 opt_complementary = "=1:P";
276 options = "C:w:U:P:X" "tz";
277 }
278 opts = getopt32(argv, options,
279 &opt_chdir, &opt_timeout, &opt_user, &opt_pass,
280 &recipients, &from, &subject, &charset
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000281 );
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000282
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000283 //argc -= optind;
284 argv += optind;
285
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000286 // first argument is remote server[:port]
287 opt_connect = *argv++;
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000288
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000289 if (opts & OPT_w)
290 timeout = xatou(opt_timeout);
291
292 // chdir
293 if (opts & OPT_C)
294 xchdir(opt_chdir);
295
296 // connect to server
297 if (opts & OPT_X) {
298 launch_helper(args);
299 } else {
300 // no connection helper provided -> make plain connect
301 int fd = create_and_connect_stream_or_die(opt_connect, 0);
302 xmove_fd(fd, STDIN_FILENO);
303 xdup2(STDIN_FILENO, STDOUT_FILENO);
304 }
305
306 // randomize
307 srand(time(NULL));
308
309 // SENDMAIL
310 if (recipients) {
311 int code;
312 char *boundary;
313
314 // wait for initial OK on plain connect
315 if (!(opts & OPT_X))
316 smtp_check(NULL, 220);
317
318 sane(from);
319 // introduce to server
320 // should we respect modern (but useless here) EHLO?
321 // or should they respect we wanna be tiny?!
322 if (!ENABLE_FEATURE_SENDMAIL_EHLO || 250 != smtp_checkp("EHLO %s", from, -1)) {
323 smtp_checkp("HELO %s", from, 250);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000324 }
325
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000326 // set sender
327 // NOTE: if password has not been specified ->
328 // no authentication is possible
329 code = (opts & OPT_P) ? -1 : 250;
330 // first try softly without authentication
331 while (250 != smtp_checkp("MAIL FROM:<%s>", from, code)) {
332 // MAIL FROM failed -> authentication needed
333 // do we have username?
334 if (!(opts & OPT_U)) {
335 // no! fetch it from "from" option
336 //opts |= OPT_U;
337 opt_user = xstrdup(from);
338 *strchrnul(opt_user, '@') = '\0';
339 }
340 // now it seems we have username
341 // try to authenticate
342 if (334 == smtp_check("AUTH LOGIN", -1)) {
343 uuencode(NULL, opt_user);
344 smtp_check("", 334);
345 uuencode(NULL, opt_pass);
346 smtp_check("", 235);
347 }
348 // authenticated -> retry set sender
349 // but now die on failure
350 code = 250;
351 }
352 // set recipients
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000353 for (llist_t *to = recipients; to; to = to->link) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000354 smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000355 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000356
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000357 // now put message
358 smtp_check("DATA", 354);
359 // put address headers
360 printf("From: %s\r\n", from);
361 for (llist_t *to = recipients; to; to = to->link) {
362 printf("To: %s\r\n", to->data);
363 }
364 // put encoded subject
365 if (opts & OPTS_c)
366 sane(charset);
367 if (opts & OPTS_s) {
368 printf("Subject: =?%s?B?", charset);
369 uuencode(NULL, subject);
370 printf("?=\r\n");
371 }
372 // put notification
373 if (opts & OPTS_n)
374 printf("Disposition-Notification-To: %s\r\n", from);
375 // put common headers and body start
376 boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000377 printf(
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000378 USE_FEATURE_SENDMAIL_BLOATY("X-Mailer: busybox " BB_VER " sendmail\r\n")
379 "Message-ID: <%s>\r\n"
380 "Mime-Version: 1.0\r\n"
381 "%smultipart/mixed; boundary=\"%s\"\r\n"
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000382 , boundary
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000383 , "Content-Type: "
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000384 , boundary
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000385 );
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000386 // put body + attachment(s)
387 {
388 const char *fmt =
389 "\r\n--%s\r\n"
390 "%stext/plain; charset=%s\r\n"
391 "%s%s\r\n"
392 "%s"
393 ;
394 const char *p = charset;
395 char *q = (char *)"";
396 while (argv[0]) {
397 printf(
398 fmt
399 , boundary
400 , "Content-Type: "
401 , p
402 , "Content-Disposition: inline"
403 , q
404 , "Content-Transfer-Encoding: base64\r\n"
405 );
406 p = "";
407 fmt =
408 "\r\n--%s\r\n"
409 "%sapplication/octet-stream%s\r\n"
410 "%s; filename=\"%s\"\r\n"
411 "%s"
412 ;
413 uuencode(*argv, NULL);
414 if (*(++argv))
415 q = bb_get_last_path_component_strip(argv[0]);
416 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000417 }
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000418 // put terminator
419 printf("\r\n--%s--\r\n" "\r\n", boundary);
420 if (ENABLE_FEATURE_CLEAN_UP)
421 free(boundary);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000422
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000423 // end message and say goodbye
424 smtp_check(".", 250);
425 smtp_check("QUIT", 221);
426
427 // FETCHMAIL
428 } else {
429 // authenticate
430 char *buf;
431 unsigned nmsg;
432 if (!(opts & OPT_U)) {
433 //opts |= OPT_U;
434 opt_user = getenv("USER");
435 }
436#if ENABLE_FEATURE_FETCHMAIL_APOP
437 pop3_checkr(NULL, NULL, &buf);
438 // server supports APOP?
439 if ('<' == buf[4]) {
440 md5_ctx_t md5;
441 uint8_t hex[16*2 + 1];
442 // yes. compose <stamp><password>
443 char *s = strchr(buf, '>');
444 if (s)
445 strcpy(s+1, opt_pass);
446 s = buf+4;
447 // get md5 sum of <stamp><password>
448 md5_begin(&md5);
449 md5_hash(s, strlen(s), &md5);
450 md5_end(s, &md5);
451 bin2hex(hex, s, 16);
452 // APOP
453 s = xasprintf("%s %s", opt_user, hex);
454 pop3_check("APOP %s", s);
455 if (ENABLE_FEATURE_CLEAN_UP) {
456 free(s);
457 free(buf);
458 }
459 } else {
460#else
461 {
462 pop3_check(NULL, NULL);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000463#endif
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000464 // USER
465 pop3_check("USER %s", opt_user);
466 // PASS
467 pop3_check("PASS %s", opt_pass);
468 }
469
470 // get statistics
471 pop3_checkr("STAT", NULL, &buf);
472
473 // get number of messages
474 nmsg = atoi(buf+4);
475 if (ENABLE_FEATURE_CLEAN_UP)
476 free(buf);
477
478 // lock maildir
479 ////USE_FEATURE_CLEAN_UP(close)(xopen(".lock", O_CREAT | O_WRONLY | O_TRUNC | O_EXCL));
480
481 // make tempnam(dir, salt) respect dir argument
482 unsetenv("TMPDIR");
483
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +0000484 // TODO: piping through external filter argv... if *argv
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000485
486 // cache fetch command
487 {
488 const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +0000489 // loop through messages
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000490 for (; nmsg; nmsg--) {
491 int fd;
492 char tmp_name[sizeof("tmp/XXXXXX")];
493 char new_name[sizeof("new/XXXXXX")];
494
495 // retrieve message in ./tmp
496 strcpy(tmp_name, "tmp/XXXXXX");
497 fd = mkstemp(tmp_name);
498 if (fd < 0)
499 bb_perror_msg_and_die("cannot create unique file");
500 pop3_check(retr, (const char *)nmsg);
501 pop3_message(fd); // NB: closes fd
502
503 // move file to ./new atomically
504 strncpy(new_name, "new", 3);
505 strcpy(new_name + 3, tmp_name + 3);
506 if (rename(tmp_name, new_name) < 0) {
507 // rats! such file exists! try to make unique name
508 strcpy(new_name + 3, "tmp/XXXXXX" + 3);
509 fd = mkstemp(new_name);
510 if (fd < 0)
511 bb_perror_msg_and_die("cannot create unique file");
512 close(fd);
513 if (rename(tmp_name, new_name) < 0) {
514 // something is very wrong
515 bb_perror_msg_and_die("cannot move %s to %s", tmp_name, new_name);
516 }
517 }
518
519 // delete message from server
520 if (opts & OPTF_z)
521 pop3_check("DELE %u", (const char*)nmsg);
522 }
523 }
524
525 // Bye
526 pop3_check("QUIT", NULL);
527
528 // unlock maildir
529 ////unlink(".lock");
530 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000531
532 return 0;
533}