blob: b2fbc5a7af1bea863c4b4da221288b77144285f7 [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
Denis Vlasenko25591c32008-02-16 22:58:56 +0000114 bb_signals_recursive(0
115 + (1 << SIGCHLD)
116 + (1 << SIGALRM)
117 , signal_handler);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000118 signal_handler(SIGCHLD);
119 // child seems OK -> parent goes on
120}
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000121
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000122static unsigned timeout;
123
124static char *command(const char *fmt, const char *param)
125{
126 char *msg = (char *)fmt;
127 alarm(timeout);
128 if (msg) {
129// if (param)
130 msg = xasprintf(fmt, param);
131 printf("%s\r\n", msg);
132 }
133 fflush(stdout);
134 return msg;
135}
136
137static int smtp_checkp(const char *fmt, const char *param, int code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000138{
139 char *answer;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000140 char *msg = command(fmt, param);
141 // read stdin
142 // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
143 // parse first bytes to a number
144 // if code = -1 then just return this number
145 // if code != -1 then checks whether the number equals the code
146 // if not equal -> die saying msg
147#if ENABLE_FEATURE_SENDMAIL_EHLO
148 while ((answer = xmalloc_getline(stdin)) && '-' == answer[3]) ;
149#else
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000150 answer = xmalloc_getline(stdin);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000151#endif
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000152 if (answer) {
153 int n = atoi(answer);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000154 alarm(0);
155 if (ENABLE_FEATURE_CLEAN_UP) {
156 free(msg);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000157 free(answer);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000158 }
159 if (-1 == code || n == code) {
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000160 return n;
161 }
162 }
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000163 kill_helper();
164 bb_error_msg_and_die("%s failed", msg);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000165}
166
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000167static int smtp_check(const char *fmt, int code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000168{
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000169 return smtp_checkp(fmt, NULL, code);
170}
171
172static void pop3_checkr(const char *fmt, const char *param, char **ret)
173{
174 char *msg = command(fmt, param);
175 char *answer = xmalloc_getline(stdin);
176 if (answer && '+' == answer[0]) {
177 alarm(0);
178 if (ret)
179 *ret = answer;
180 else
181 free(answer);
182 return;
183 }
184 kill_helper();
185 bb_error_msg_and_die("%s failed", msg);
186}
187
188static void pop3_check(const char *fmt, const char *param)
189{
190 pop3_checkr(fmt, param, NULL);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000191}
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000192
193// strip argument of bad chars
194static char *sane(char *str)
195{
196 char *s = str;
197 char *p = s;
198 while (*s) {
199 if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
200 *p++ = *s;
201 }
202 s++;
203 }
204 *p = '\0';
205 return str;
206}
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000207
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000208static void pop3_message(int fd)
209{
210 char *answer;
211 // read stdin, copy to file fd
212 while ((answer = xmalloc_fgets_str(stdin, "\r\n"))) {
213 char *s = answer;
214 if ('.' == answer[0]) {
215 if ('.' == answer[1])
216 s++;
217 else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
218 break;
219 }
220 xwrite(fd, s, strlen(s));
221 free(answer);
222 }
223 close(fd);
224}
225
226static const char *args[] = {
227 "openssl", "s_client", "-quiet", "-connect", NULL, "-tls1", "-starttls", "smtp", NULL
228};
229#define opt_connect args[4]
230#define opt_after_connect args[5]
231
232int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
233int sendgetmail_main(int argc, char **argv)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000234{
235 llist_t *recipients = NULL;
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000236 char *from;
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000237 const char *subject;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000238 char *charset = (char *)"utf-8";
239
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000240 const char *opt_user;
241 const char *opt_pass;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000242 const char *opt_timeout;
243 const char *opt_chdir;
244
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000245 enum {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000246 OPT_C = 1 << 0, // chdir
247 OPT_w = 1 << 1, // network timeout
248 OPT_U = 1 << 2, // user
249 OPT_P = 1 << 3, // password
250 OPT_X = 1 << 4, // use openssl connection helper
251
252 OPTS_t = 1 << 5, // sendmail "to"
253 OPTF_t = 1 << 5, // fetchmail "TOP"
254
255 OPTS_f = 1 << 6, // sendmail "from"
256 OPTF_z = 1 << 6, // fetchmail "delete"
257
258 OPTS_n = 1 << 7, // notification
259 OPTS_s = 1 << 8, // subject given
260 OPTS_c = 1 << 9, // charset for subject and body
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000261 };
262
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000263 const char *options;
264 unsigned opts;
265
266 // SENDMAIL
267 if ('s' == applet_name[0]) {
268 // save initial stdin
269 xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
270 // -f must be specified
271 // -t may be multiple
272 opt_complementary = "-1:f:t::";
273 options = "C:w:U:P:X" "t:f:ns:c:";
274 // FETCHMAIL
275 } else {
276 opt_after_connect = NULL;
277 opt_complementary = "=1:P";
278 options = "C:w:U:P:X" "tz";
279 }
280 opts = getopt32(argv, options,
281 &opt_chdir, &opt_timeout, &opt_user, &opt_pass,
282 &recipients, &from, &subject, &charset
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000283 );
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000284
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000285 //argc -= optind;
286 argv += optind;
287
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000288 // first argument is remote server[:port]
289 opt_connect = *argv++;
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000290
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000291 if (opts & OPT_w)
292 timeout = xatou(opt_timeout);
293
294 // chdir
295 if (opts & OPT_C)
296 xchdir(opt_chdir);
297
298 // connect to server
299 if (opts & OPT_X) {
300 launch_helper(args);
301 } else {
302 // no connection helper provided -> make plain connect
303 int fd = create_and_connect_stream_or_die(opt_connect, 0);
304 xmove_fd(fd, STDIN_FILENO);
305 xdup2(STDIN_FILENO, STDOUT_FILENO);
306 }
307
308 // randomize
309 srand(time(NULL));
310
311 // SENDMAIL
312 if (recipients) {
313 int code;
314 char *boundary;
315
316 // wait for initial OK on plain connect
317 if (!(opts & OPT_X))
318 smtp_check(NULL, 220);
319
320 sane(from);
321 // introduce to server
322 // should we respect modern (but useless here) EHLO?
323 // or should they respect we wanna be tiny?!
324 if (!ENABLE_FEATURE_SENDMAIL_EHLO || 250 != smtp_checkp("EHLO %s", from, -1)) {
325 smtp_checkp("HELO %s", from, 250);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000326 }
327
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000328 // set sender
329 // NOTE: if password has not been specified ->
330 // no authentication is possible
331 code = (opts & OPT_P) ? -1 : 250;
332 // first try softly without authentication
333 while (250 != smtp_checkp("MAIL FROM:<%s>", from, code)) {
334 // MAIL FROM failed -> authentication needed
335 // do we have username?
336 if (!(opts & OPT_U)) {
337 // no! fetch it from "from" option
338 //opts |= OPT_U;
339 opt_user = xstrdup(from);
340 *strchrnul(opt_user, '@') = '\0';
341 }
342 // now it seems we have username
343 // try to authenticate
344 if (334 == smtp_check("AUTH LOGIN", -1)) {
345 uuencode(NULL, opt_user);
346 smtp_check("", 334);
347 uuencode(NULL, opt_pass);
348 smtp_check("", 235);
349 }
350 // authenticated -> retry set sender
351 // but now die on failure
352 code = 250;
353 }
354 // set recipients
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000355 for (llist_t *to = recipients; to; to = to->link) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000356 smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000357 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000358
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000359 // now put message
360 smtp_check("DATA", 354);
361 // put address headers
362 printf("From: %s\r\n", from);
363 for (llist_t *to = recipients; to; to = to->link) {
364 printf("To: %s\r\n", to->data);
365 }
366 // put encoded subject
367 if (opts & OPTS_c)
368 sane(charset);
369 if (opts & OPTS_s) {
370 printf("Subject: =?%s?B?", charset);
371 uuencode(NULL, subject);
372 printf("?=\r\n");
373 }
374 // put notification
375 if (opts & OPTS_n)
376 printf("Disposition-Notification-To: %s\r\n", from);
377 // put common headers and body start
378 boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000379 printf(
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000380 USE_FEATURE_SENDMAIL_BLOATY("X-Mailer: busybox " BB_VER " sendmail\r\n")
381 "Message-ID: <%s>\r\n"
382 "Mime-Version: 1.0\r\n"
383 "%smultipart/mixed; boundary=\"%s\"\r\n"
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000384 , boundary
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000385 , "Content-Type: "
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000386 , boundary
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000387 );
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000388 // put body + attachment(s)
389 {
390 const char *fmt =
391 "\r\n--%s\r\n"
392 "%stext/plain; charset=%s\r\n"
393 "%s%s\r\n"
394 "%s"
395 ;
396 const char *p = charset;
397 char *q = (char *)"";
398 while (argv[0]) {
399 printf(
400 fmt
401 , boundary
402 , "Content-Type: "
403 , p
404 , "Content-Disposition: inline"
405 , q
406 , "Content-Transfer-Encoding: base64\r\n"
407 );
408 p = "";
409 fmt =
410 "\r\n--%s\r\n"
411 "%sapplication/octet-stream%s\r\n"
412 "%s; filename=\"%s\"\r\n"
413 "%s"
414 ;
415 uuencode(*argv, NULL);
416 if (*(++argv))
417 q = bb_get_last_path_component_strip(argv[0]);
418 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000419 }
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000420 // put terminator
421 printf("\r\n--%s--\r\n" "\r\n", boundary);
422 if (ENABLE_FEATURE_CLEAN_UP)
423 free(boundary);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000424
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000425 // end message and say goodbye
426 smtp_check(".", 250);
427 smtp_check("QUIT", 221);
428
429 // FETCHMAIL
430 } else {
431 // authenticate
432 char *buf;
433 unsigned nmsg;
434 if (!(opts & OPT_U)) {
435 //opts |= OPT_U;
436 opt_user = getenv("USER");
437 }
438#if ENABLE_FEATURE_FETCHMAIL_APOP
439 pop3_checkr(NULL, NULL, &buf);
440 // server supports APOP?
441 if ('<' == buf[4]) {
442 md5_ctx_t md5;
443 uint8_t hex[16*2 + 1];
444 // yes. compose <stamp><password>
445 char *s = strchr(buf, '>');
446 if (s)
447 strcpy(s+1, opt_pass);
448 s = buf+4;
449 // get md5 sum of <stamp><password>
450 md5_begin(&md5);
451 md5_hash(s, strlen(s), &md5);
452 md5_end(s, &md5);
453 bin2hex(hex, s, 16);
454 // APOP
455 s = xasprintf("%s %s", opt_user, hex);
456 pop3_check("APOP %s", s);
457 if (ENABLE_FEATURE_CLEAN_UP) {
458 free(s);
459 free(buf);
460 }
461 } else {
462#else
463 {
464 pop3_check(NULL, NULL);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000465#endif
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000466 // USER
467 pop3_check("USER %s", opt_user);
468 // PASS
469 pop3_check("PASS %s", opt_pass);
470 }
471
472 // get statistics
473 pop3_checkr("STAT", NULL, &buf);
474
475 // get number of messages
476 nmsg = atoi(buf+4);
477 if (ENABLE_FEATURE_CLEAN_UP)
478 free(buf);
479
480 // lock maildir
481 ////USE_FEATURE_CLEAN_UP(close)(xopen(".lock", O_CREAT | O_WRONLY | O_TRUNC | O_EXCL));
482
483 // make tempnam(dir, salt) respect dir argument
484 unsetenv("TMPDIR");
485
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +0000486 // TODO: piping through external filter argv... if *argv
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000487
488 // cache fetch command
489 {
490 const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +0000491 // loop through messages
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000492 for (; nmsg; nmsg--) {
493 int fd;
494 char tmp_name[sizeof("tmp/XXXXXX")];
495 char new_name[sizeof("new/XXXXXX")];
496
497 // retrieve message in ./tmp
498 strcpy(tmp_name, "tmp/XXXXXX");
499 fd = mkstemp(tmp_name);
500 if (fd < 0)
501 bb_perror_msg_and_die("cannot create unique file");
502 pop3_check(retr, (const char *)nmsg);
503 pop3_message(fd); // NB: closes fd
504
505 // move file to ./new atomically
506 strncpy(new_name, "new", 3);
507 strcpy(new_name + 3, tmp_name + 3);
508 if (rename(tmp_name, new_name) < 0) {
509 // rats! such file exists! try to make unique name
510 strcpy(new_name + 3, "tmp/XXXXXX" + 3);
511 fd = mkstemp(new_name);
512 if (fd < 0)
513 bb_perror_msg_and_die("cannot create unique file");
514 close(fd);
Denis Vlasenkocb448fe2008-02-17 14:28:53 +0000515 xrename(tmp_name, new_name);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000516 }
517
518 // delete message from server
519 if (opts & OPTF_z)
520 pop3_check("DELE %u", (const char*)nmsg);
521 }
522 }
523
524 // Bye
525 pop3_check("QUIT", NULL);
526
527 // unlock maildir
528 ////unlink(".lock");
529 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000530
531 return 0;
532}