1 /* vi: set sw=4 ts=4: */
3 * bare bones sendmail/fetchmail
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
11 #define INITIAL_STDIN_FILENO 3
13 static void uuencode(char *fname, const char *text)
16 SRC_BUF_SIZE = 45, /* This *MUST* be a multiple of 3 */
17 DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
23 char dst_buf[DST_BUF_SIZE + 1];
26 fd = INITIAL_STDIN_FILENO;
27 if (NOT_LONE_DASH(fname))
28 fd = xopen(fname, O_RDONLY);
29 src_buf = bb_common_bufsiz1;
34 fflush(stdout); // sync stdio and unistd output
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);
43 if (len > SRC_BUF_SIZE)
48 // encode the buffer we just read in
49 bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
51 xwrite(STDOUT_FILENO, "\r\n", 2);
56 xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
62 static pid_t helper_pid;
64 static void kill_helper(void)
66 // TODO!!!: is there more elegant way to terminate child on program failure?
68 kill(helper_pid, SIGTERM);
71 // generic signal handler
72 static void signal_handler(int signo)
76 if (SIGALRM == signo) {
78 bb_error_msg_and_die("timed out");
81 // SIGCHLD. reap zombies
82 if (wait_any_nohang(&err) > 0)
83 if (WIFEXITED(err) && WEXITSTATUS(err))
84 #if ENABLE_FEATURE_SENDMAIL_BLOATY
85 bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
87 bb_error_msg_and_die("child failed");
91 static void launch_helper(const char **argv)
93 // setup vanilla unidirectional pipes interchange
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)
109 // child - try to execute connection helper
110 BB_EXECVP(argv[0], (char **)argv);
113 // parent - check whether child is alive
114 bb_signals_recursive(0
118 signal_handler(SIGCHLD);
119 // child seems OK -> parent goes on
122 static unsigned timeout;
124 static char *command(const char *fmt, const char *param)
126 char *msg = (char *)fmt;
130 msg = xasprintf(fmt, param);
131 printf("%s\r\n", msg);
137 static int smtp_checkp(const char *fmt, const char *param, int code)
140 char *msg = command(fmt, param);
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]) ;
150 answer = xmalloc_getline(stdin);
153 int n = atoi(answer);
155 if (ENABLE_FEATURE_CLEAN_UP) {
159 if (-1 == code || n == code) {
164 bb_error_msg_and_die("%s failed", msg);
167 static int smtp_check(const char *fmt, int code)
169 return smtp_checkp(fmt, NULL, code);
172 static void pop3_checkr(const char *fmt, const char *param, char **ret)
174 char *msg = command(fmt, param);
175 char *answer = xmalloc_getline(stdin);
176 if (answer && '+' == answer[0]) {
185 bb_error_msg_and_die("%s failed", msg);
188 static void pop3_check(const char *fmt, const char *param)
190 pop3_checkr(fmt, param, NULL);
193 // strip argument of bad chars
194 static char *sane(char *str)
199 if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
208 static void pop3_message(int fd)
211 // read stdin, copy to file fd
212 while ((answer = xmalloc_fgets_str(stdin, "\r\n"))) {
214 if ('.' == answer[0]) {
215 if ('.' == answer[1])
217 else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
220 xwrite(fd, s, strlen(s));
226 static const char *args[] = {
227 "openssl", "s_client", "-quiet", "-connect", NULL, "-tls1", "-starttls", "smtp", NULL
229 #define opt_connect args[4]
230 #define opt_after_connect args[5]
232 int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
233 int sendgetmail_main(int argc, char **argv)
235 llist_t *recipients = NULL;
238 char *charset = (char *)"utf-8";
240 const char *opt_user;
241 const char *opt_pass;
242 const char *opt_timeout;
243 const char *opt_chdir;
246 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
252 OPTS_t = 1 << 5, // sendmail "to"
253 OPTF_t = 1 << 5, // fetchmail "TOP"
255 OPTS_f = 1 << 6, // sendmail "from"
256 OPTF_z = 1 << 6, // fetchmail "delete"
258 OPTS_n = 1 << 7, // notification
259 OPTS_s = 1 << 8, // subject given
260 OPTS_c = 1 << 9, // charset for subject and body
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:";
276 opt_after_connect = NULL;
277 opt_complementary = "=1:P";
278 options = "C:w:U:P:X" "tz";
280 opts = getopt32(argv, options,
281 &opt_chdir, &opt_timeout, &opt_user, &opt_pass,
282 &recipients, &from, &subject, &charset
288 // first argument is remote server[:port]
289 opt_connect = *argv++;
292 timeout = xatou(opt_timeout);
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);
316 // wait for initial OK on plain connect
318 smtp_check(NULL, 220);
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);
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
339 opt_user = xstrdup(from);
340 *strchrnul(opt_user, '@') = '\0';
342 // now it seems we have username
343 // try to authenticate
344 if (334 == smtp_check("AUTH LOGIN", -1)) {
345 uuencode(NULL, opt_user);
347 uuencode(NULL, opt_pass);
350 // authenticated -> retry set sender
351 // but now die on failure
355 for (llist_t *to = recipients; to; to = to->link) {
356 smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
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);
366 // put encoded subject
370 printf("Subject: =?%s?B?", charset);
371 uuencode(NULL, subject);
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());
380 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"
388 // put body + attachment(s)
392 "%stext/plain; charset=%s\r\n"
396 const char *p = charset;
397 char *q = (char *)"";
404 , "Content-Disposition: inline"
406 , "Content-Transfer-Encoding: base64\r\n"
411 "%sapplication/octet-stream%s\r\n"
412 "%s; filename=\"%s\"\r\n"
415 uuencode(*argv, NULL);
417 q = bb_get_last_path_component_strip(argv[0]);
421 printf("\r\n--%s--\r\n" "\r\n", boundary);
422 if (ENABLE_FEATURE_CLEAN_UP)
425 // end message and say goodbye
426 smtp_check(".", 250);
427 smtp_check("QUIT", 221);
434 if (!(opts & OPT_U)) {
436 opt_user = getenv("USER");
438 #if ENABLE_FEATURE_FETCHMAIL_APOP
439 pop3_checkr(NULL, NULL, &buf);
440 // server supports APOP?
443 uint8_t hex[16*2 + 1];
444 // yes. compose <stamp><password>
445 char *s = strchr(buf, '>');
447 strcpy(s+1, opt_pass);
449 // get md5 sum of <stamp><password>
451 md5_hash(s, strlen(s), &md5);
455 s = xasprintf("%s %s", opt_user, hex);
456 pop3_check("APOP %s", s);
457 if (ENABLE_FEATURE_CLEAN_UP) {
464 pop3_check(NULL, NULL);
467 pop3_check("USER %s", opt_user);
469 pop3_check("PASS %s", opt_pass);
473 pop3_checkr("STAT", NULL, &buf);
475 // get number of messages
477 if (ENABLE_FEATURE_CLEAN_UP)
481 ////USE_FEATURE_CLEAN_UP(close)(xopen(".lock", O_CREAT | O_WRONLY | O_TRUNC | O_EXCL));
483 // make tempnam(dir, salt) respect dir argument
486 // TODO: piping through external filter argv... if *argv
488 // cache fetch command
490 const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
491 // loop through messages
492 for (; nmsg; nmsg--) {
494 char tmp_name[sizeof("tmp/XXXXXX")];
495 char new_name[sizeof("new/XXXXXX")];
497 // retrieve message in ./tmp
498 strcpy(tmp_name, "tmp/XXXXXX");
499 fd = mkstemp(tmp_name);
501 bb_perror_msg_and_die("cannot create unique file");
502 pop3_check(retr, (const char *)nmsg);
503 pop3_message(fd); // NB: closes fd
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);
513 bb_perror_msg_and_die("cannot create unique file");
515 xrename(tmp_name, new_name);
518 // delete message from server
520 pop3_check("DELE %u", (const char*)nmsg);
525 pop3_check("QUIT", NULL);