sendmail.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * bare bones sendmail/fetchmail
  4. *
  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. #define INITIAL_STDIN_FILENO 3
  11. static void uuencode(char *fname, const char *text)
  12. {
  13. enum {
  14. SRC_BUF_SIZE = 45, /* This *MUST* be a multiple of 3 */
  15. DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
  16. };
  17. #define src_buf text
  18. int fd;
  19. #define len fd
  20. char dst_buf[DST_BUF_SIZE + 1];
  21. if (fname) {
  22. fd = INITIAL_STDIN_FILENO;
  23. if (NOT_LONE_DASH(fname))
  24. fd = xopen(fname, O_RDONLY);
  25. src_buf = bb_common_bufsiz1;
  26. // N.B. strlen(NULL) segfaults!
  27. } else if (text) {
  28. // though we do not call uuencode(NULL, NULL) explicitly
  29. // still we do not want to break things suddenly
  30. len = strlen(text);
  31. } else
  32. return;
  33. fflush(stdout); // sync stdio and unistd output
  34. while (1) {
  35. size_t size;
  36. if (fname) {
  37. size = full_read(fd, (char *)src_buf, SRC_BUF_SIZE);
  38. if ((ssize_t)size < 0)
  39. bb_perror_msg_and_die(bb_msg_read_error);
  40. } else {
  41. size = len;
  42. if (len > SRC_BUF_SIZE)
  43. size = SRC_BUF_SIZE;
  44. }
  45. if (!size)
  46. break;
  47. // encode the buffer we just read in
  48. bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
  49. if (fname) {
  50. xwrite(STDOUT_FILENO, "\r\n", 2);
  51. } else {
  52. src_buf += size;
  53. len -= size;
  54. }
  55. xwrite(STDOUT_FILENO, dst_buf, 4 * ((size + 2) / 3));
  56. }
  57. if (fname)
  58. close(fd);
  59. }
  60. struct globals {
  61. pid_t helper_pid;
  62. unsigned timeout;
  63. // arguments for SSL connection helper
  64. const char *xargs[9];
  65. // arguments for postprocess helper
  66. const char *fargs[3];
  67. };
  68. #define G (*ptr_to_globals)
  69. #define helper_pid (G.helper_pid)
  70. #define timeout (G.timeout )
  71. #define xargs (G.xargs )
  72. #define fargs (G.fargs )
  73. #define INIT_G() do { \
  74. SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
  75. xargs[0] = "openssl"; \
  76. xargs[1] = "s_client"; \
  77. xargs[2] = "-quiet"; \
  78. xargs[3] = "-connect"; \
  79. /*xargs[4] = "server[:port]";*/ \
  80. xargs[5] = "-tls1"; \
  81. xargs[6] = "-starttls"; \
  82. xargs[7] = "smtp"; \
  83. fargs[0] = "utf-8"; \
  84. } while (0)
  85. #define opt_connect (xargs[4])
  86. #define opt_after_connect (xargs[5])
  87. #define opt_charset (fargs[0])
  88. #define opt_subject (fargs[1])
  89. static void kill_helper(void)
  90. {
  91. // TODO!!!: is there more elegant way to terminate child on program failure?
  92. if (helper_pid > 0)
  93. kill(helper_pid, SIGTERM);
  94. }
  95. // generic signal handler
  96. static void signal_handler(int signo)
  97. {
  98. #define err signo
  99. if (SIGALRM == signo) {
  100. kill_helper();
  101. bb_error_msg_and_die("timed out");
  102. }
  103. // SIGCHLD. reap zombies
  104. if (wait_any_nohang(&err) > 0)
  105. if (WIFEXITED(err) && WEXITSTATUS(err))
  106. bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
  107. }
  108. static void launch_helper(const char **argv)
  109. {
  110. // setup vanilla unidirectional pipes interchange
  111. int idx;
  112. int pipes[4];
  113. xpipe(pipes);
  114. xpipe(pipes+2);
  115. helper_pid = vfork();
  116. if (helper_pid < 0)
  117. bb_perror_msg_and_die("vfork");
  118. idx = (!helper_pid)*2;
  119. xdup2(pipes[idx], STDIN_FILENO);
  120. xdup2(pipes[3-idx], STDOUT_FILENO);
  121. if (ENABLE_FEATURE_CLEAN_UP)
  122. for (int i = 4; --i >= 0; )
  123. if (pipes[i] > STDOUT_FILENO)
  124. close(pipes[i]);
  125. if (!helper_pid) {
  126. // child: try to execute connection helper
  127. BB_EXECVP(*argv, (char **)argv);
  128. _exit(127);
  129. }
  130. // parent: check whether child is alive
  131. bb_signals(0
  132. + (1 << SIGCHLD)
  133. + (1 << SIGALRM)
  134. , signal_handler);
  135. signal_handler(SIGCHLD);
  136. // child seems OK -> parent goes on
  137. }
  138. static const char *command(const char *fmt, const char *param)
  139. {
  140. const char *msg = fmt;
  141. alarm(timeout);
  142. if (msg) {
  143. msg = xasprintf(fmt, param);
  144. printf("%s\r\n", msg);
  145. }
  146. fflush(stdout);
  147. return msg;
  148. }
  149. static int smtp_checkp(const char *fmt, const char *param, int code)
  150. {
  151. char *answer;
  152. const char *msg = command(fmt, param);
  153. // read stdin
  154. // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
  155. // parse first bytes to a number
  156. // if code = -1 then just return this number
  157. // if code != -1 then checks whether the number equals the code
  158. // if not equal -> die saying msg
  159. while ((answer = xmalloc_fgetline(stdin)) != NULL)
  160. if (strlen(answer) <= 3 || '-' != answer[3])
  161. break;
  162. if (answer) {
  163. int n = atoi(answer);
  164. alarm(0);
  165. if (ENABLE_FEATURE_CLEAN_UP) {
  166. free(answer);
  167. }
  168. if (-1 == code || n == code) {
  169. return n;
  170. }
  171. }
  172. kill_helper();
  173. bb_error_msg_and_die("%s failed", msg);
  174. }
  175. static inline int smtp_check(const char *fmt, int code)
  176. {
  177. return smtp_checkp(fmt, NULL, code);
  178. }
  179. // strip argument of bad chars
  180. static char *sane(char *str)
  181. {
  182. char *s = str;
  183. char *p = s;
  184. while (*s) {
  185. if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
  186. *p++ = *s;
  187. }
  188. s++;
  189. }
  190. *p = '\0';
  191. return str;
  192. }
  193. #if ENABLE_FETCHMAIL
  194. static void pop3_checkr(const char *fmt, const char *param, char **ret)
  195. {
  196. const char *msg = command(fmt, param);
  197. char *answer = xmalloc_fgetline(stdin);
  198. if (answer && '+' == *answer) {
  199. alarm(0);
  200. if (ret)
  201. *ret = answer+4; // skip "+OK "
  202. else if (ENABLE_FEATURE_CLEAN_UP)
  203. free(answer);
  204. return;
  205. }
  206. kill_helper();
  207. bb_error_msg_and_die("%s failed", msg);
  208. }
  209. static inline void pop3_check(const char *fmt, const char *param)
  210. {
  211. pop3_checkr(fmt, param, NULL);
  212. }
  213. static void pop3_message(const char *filename)
  214. {
  215. int fd;
  216. char *answer;
  217. // create and open file filename
  218. // read stdin, copy to created file
  219. fd = xopen(filename, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL);
  220. while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
  221. char *s = answer;
  222. if ('.' == *answer) {
  223. if ('.' == answer[1])
  224. s++;
  225. else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
  226. break;
  227. }
  228. xwrite(fd, s, strlen(s));
  229. free(answer);
  230. }
  231. close(fd);
  232. }
  233. #endif
  234. int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  235. int sendgetmail_main(int argc ATTRIBUTE_UNUSED, char **argv)
  236. {
  237. llist_t *opt_recipients = NULL;
  238. const char *opt_user;
  239. const char *opt_pass;
  240. enum {
  241. OPT_w = 1 << 0, // network timeout
  242. OPT_U = 1 << 1, // user
  243. OPT_P = 1 << 2, // password
  244. OPT_X = 1 << 3, // connect using openssl s_client helper
  245. OPTS_n = 1 << 4, // sendmail: request notification
  246. OPTF_t = 1 << 4, // fetchmail: use "TOP" not "RETR"
  247. OPTS_s = 1 << 5, // sendmail: subject
  248. OPTF_z = 1 << 5, // fetchmail: delete from server
  249. OPTS_c = 1 << 6, // sendmail: assumed charset
  250. OPTS_t = 1 << 7, // sendmail: recipient(s)
  251. OPTS_i = 1 << 8, // sendmail: ignore lone dots in message body (implied)
  252. };
  253. const char *options;
  254. unsigned opts;
  255. // init global variables
  256. INIT_G();
  257. // parse options, different option sets for sendmail and fetchmail
  258. // N.B. opt_after_connect hereafter is NULL if we are called as fetchmail
  259. // and is NOT NULL if we are called as sendmail
  260. if (!ENABLE_FETCHMAIL || 's' == applet_name[0]) {
  261. // SENDMAIL
  262. // save initial stdin (body or attachements can be piped!)
  263. xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
  264. opt_complementary = "-2:w+:t::";
  265. options = "w:U:P:X" "ns:c:t:i";
  266. } else {
  267. // FETCHMAIL
  268. opt_after_connect = NULL;
  269. opt_complementary = "-2:w+:P";
  270. options = "w:U:P:X" "tz";
  271. }
  272. opts = getopt32(argv, options,
  273. &timeout, &opt_user, &opt_pass,
  274. &opt_subject, &opt_charset, &opt_recipients
  275. );
  276. //argc -= optind;
  277. argv += optind;
  278. // first argument is remote server[:port]
  279. opt_connect = *argv++;
  280. // connect to server
  281. // SSL ordered? ->
  282. if (opts & OPT_X) {
  283. // ... use openssl helper
  284. launch_helper(xargs);
  285. // no SSL ordered? ->
  286. } else {
  287. // ... make plain connect
  288. int fd = create_and_connect_stream_or_die(opt_connect, 25);
  289. // make ourselves a simple IO filter
  290. // from now we know nothing about network :)
  291. xmove_fd(fd, STDIN_FILENO);
  292. xdup2(STDIN_FILENO, STDOUT_FILENO);
  293. }
  294. #if ENABLE_FETCHMAIL
  295. // we are sendmail?
  296. if (opt_after_connect)
  297. #endif
  298. {
  299. /***************************************************
  300. * SENDMAIL
  301. ***************************************************/
  302. char *opt_from;
  303. int code;
  304. char *boundary;
  305. const char *fmt;
  306. const char *p;
  307. char *q;
  308. // we didn't use SSL helper? ->
  309. if (!(opts & OPT_X)) {
  310. // ... wait for initial server OK
  311. smtp_check(NULL, 220);
  312. }
  313. // get the sender
  314. opt_from = sane(*argv++);
  315. // if no recipients _and_ no body files specified -> enter all-included mode
  316. // i.e. scan stdin for To: and Subject: lines ...
  317. // ... and then use the rest of stdin as message body
  318. if (!opt_recipients && !*argv) {
  319. // fetch recipients and (optionally) subject
  320. char *s;
  321. while ((s = xmalloc_reads(INITIAL_STDIN_FILENO, NULL, NULL)) != NULL) {
  322. if (0 == strncmp("To: ", s, 4)) {
  323. llist_add_to_end(&opt_recipients, s+4);
  324. } else if (0 == strncmp("Subject: ", s, 9)) {
  325. opt_subject = s+9;
  326. opts |= OPTS_s;
  327. } else {
  328. char first = s[0];
  329. free(s);
  330. if (!first)
  331. break; // empty line
  332. }
  333. }
  334. // order to read body from stdin
  335. *--argv = (char *)"-";
  336. }
  337. // introduce to server
  338. // we should start with modern EHLO
  339. if (250 != smtp_checkp("EHLO %s", opt_from, -1)) {
  340. smtp_checkp("HELO %s", opt_from, 250);
  341. }
  342. // set sender
  343. // NOTE: if password has not been specified
  344. // then no authentication is possible
  345. code = (opts & OPT_P) ? -1 : 250;
  346. // first try softly without authentication
  347. while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) {
  348. // MAIL FROM failed -> authentication needed
  349. // have we got username?
  350. if (!(opts & OPT_U)) {
  351. // no! fetch it from "from" option
  352. //opts |= OPT_U;
  353. opt_user = xstrdup(opt_from);
  354. *strchrnul(opt_user, '@') = '\0';
  355. }
  356. // now we've got username
  357. // so try to authenticate
  358. if (334 == smtp_check("AUTH LOGIN", -1)) {
  359. uuencode(NULL, opt_user);
  360. smtp_check("", 334);
  361. uuencode(NULL, opt_pass);
  362. smtp_check("", 235);
  363. }
  364. // authenticated OK? -> retry to set sender
  365. // but this time die on failure!
  366. code = 250;
  367. }
  368. // set recipients
  369. for (llist_t *to = opt_recipients; to; to = to->link) {
  370. smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
  371. }
  372. // enter "put message" mode
  373. smtp_check("DATA", 354);
  374. // put address headers
  375. printf("From: %s\r\n", opt_from);
  376. for (llist_t *to = opt_recipients; to; to = to->link) {
  377. printf("To: %s\r\n", to->data);
  378. }
  379. // put encoded subject
  380. if (opts & OPTS_c)
  381. sane((char *)opt_charset);
  382. if (opts & OPTS_s) {
  383. printf("Subject: =?%s?B?", opt_charset);
  384. uuencode(NULL, opt_subject);
  385. printf("?=\r\n");
  386. }
  387. // put notification
  388. if (opts & OPTS_n)
  389. printf("Disposition-Notification-To: %s\r\n", opt_from);
  390. // make a random string -- it will delimit message parts
  391. srand(monotonic_us());
  392. boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
  393. // put common headers and body start
  394. printf(
  395. "Message-ID: <%s>\r\n"
  396. "Mime-Version: 1.0\r\n"
  397. "%smultipart/mixed; boundary=\"%s\"\r\n"
  398. , boundary
  399. , "Content-Type: "
  400. , boundary
  401. );
  402. // put body + attachment(s)
  403. // N.B. all these weird things just to be tiny
  404. // by reusing string patterns!
  405. fmt =
  406. "\r\n--%s\r\n"
  407. "%stext/plain; charset=%s\r\n"
  408. "%s%s\r\n"
  409. "%s"
  410. ;
  411. p = opt_charset;
  412. q = (char *)"";
  413. while (*argv) {
  414. printf(
  415. fmt
  416. , boundary
  417. , "Content-Type: "
  418. , p
  419. , "Content-Disposition: inline"
  420. , q
  421. , "Content-Transfer-Encoding: base64\r\n"
  422. );
  423. p = "";
  424. fmt =
  425. "\r\n--%s\r\n"
  426. "%sapplication/octet-stream%s\r\n"
  427. "%s; filename=\"%s\"\r\n"
  428. "%s"
  429. ;
  430. uuencode(*argv, NULL);
  431. if (*(++argv))
  432. q = bb_get_last_path_component_strip(*argv);
  433. }
  434. // put message terminator
  435. printf("\r\n--%s--\r\n" "\r\n", boundary);
  436. // leave "put message" mode
  437. smtp_check(".", 250);
  438. // ... and say goodbye
  439. smtp_check("QUIT", 221);
  440. #if ENABLE_FETCHMAIL
  441. } else {
  442. /***************************************************
  443. * FETCHMAIL
  444. ***************************************************/
  445. char *buf;
  446. unsigned nmsg;
  447. char *hostname;
  448. pid_t pid;
  449. // cache fetch command:
  450. // TOP will return only the headers
  451. // RETR will dump the whole message
  452. const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
  453. // goto maildir
  454. xchdir(*argv++);
  455. // cache postprocess program
  456. *fargs = *argv;
  457. // authenticate
  458. if (!(opts & OPT_U)) {
  459. //opts |= OPT_U;
  460. // N.B. IMHO getenv("USER") can be way easily spoofed!
  461. opt_user = bb_getpwuid(NULL, -1, getuid());
  462. }
  463. // get server greeting
  464. pop3_checkr(NULL, NULL, &buf);
  465. // server supports APOP?
  466. if ('<' == *buf) {
  467. md5_ctx_t md5;
  468. // yes! compose <stamp><password>
  469. char *s = strchr(buf, '>');
  470. if (s)
  471. strcpy(s+1, opt_pass);
  472. s = buf;
  473. // get md5 sum of <stamp><password>
  474. md5_begin(&md5);
  475. md5_hash(s, strlen(s), &md5);
  476. md5_end(s, &md5);
  477. // NOTE: md5 struct contains enough space
  478. // so we reuse md5 space instead of xzalloc(16*2+1)
  479. #define md5_hex ((uint8_t *)&md5)
  480. // uint8_t *md5_hex = (uint8_t *)&md5;
  481. *bin2hex((char *)md5_hex, s, 16) = '\0';
  482. // APOP
  483. s = xasprintf("%s %s", opt_user, md5_hex);
  484. #undef md5_hex
  485. pop3_check("APOP %s", s);
  486. if (ENABLE_FEATURE_CLEAN_UP) {
  487. free(s);
  488. free(buf-4); // buf is "+OK " away from malloc'ed string
  489. }
  490. // server ignores APOP -> use simple text authentication
  491. } else {
  492. // USER
  493. pop3_check("USER %s", opt_user);
  494. // PASS
  495. pop3_check("PASS %s", opt_pass);
  496. }
  497. // get mailbox statistics
  498. pop3_checkr("STAT", NULL, &buf);
  499. // prepare message filename suffix
  500. hostname = safe_gethostname();
  501. pid = getpid();
  502. // get messages counter
  503. // NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
  504. // we only need nmsg and atoi is just exactly what we need
  505. // if atoi fails to convert buf into number it returns 0
  506. // in this case the following loop simply will not be executed
  507. nmsg = atoi(buf);
  508. if (ENABLE_FEATURE_CLEAN_UP)
  509. free(buf-4); // buf is "+OK " away from malloc'ed string
  510. // loop through messages
  511. for (; nmsg; nmsg--) {
  512. // generate unique filename
  513. char *filename = xasprintf("tmp/%llu.%u.%s", monotonic_us(), pid, hostname);
  514. char *target;
  515. int rc;
  516. // retrieve message in ./tmp/
  517. pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
  518. pop3_message(filename);
  519. // delete message from server
  520. if (opts & OPTF_z)
  521. pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
  522. // run postprocessing program
  523. if (*fargs) {
  524. fargs[1] = filename;
  525. rc = wait4pid(spawn((char **)fargs));
  526. if (99 == rc)
  527. break;
  528. if (1 == rc)
  529. goto skip;
  530. }
  531. // atomically move message to ./new/
  532. target = xstrdup(filename);
  533. strncpy(target, "new", 3);
  534. // ... or just stop receiving on error
  535. if (rename_or_warn(filename, target))
  536. break;
  537. free(target);
  538. skip:
  539. free(filename);
  540. }
  541. // Bye
  542. pop3_check("QUIT", NULL);
  543. #endif // ENABLE_FETCHMAIL
  544. }
  545. return 0;
  546. }