sendmail.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  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_getline(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 int inline 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_getline(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 void inline 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. };
  252. const char *options;
  253. unsigned opts;
  254. // init global variables
  255. INIT_G();
  256. // parse options, different option sets for sendmail and fetchmail
  257. // N.B. opt_after_connect hereafter is NULL if we are called as fetchmail
  258. // and is NOT NULL if we are called as sendmail
  259. if (!ENABLE_FETCHMAIL || 's' == applet_name[0]) {
  260. // SENDMAIL
  261. // save initial stdin (body or attachements can be piped!)
  262. xdup2(STDIN_FILENO, INITIAL_STDIN_FILENO);
  263. opt_complementary = "-2:w+:t:t::"; // count(-t) > 0
  264. options = "w:U:P:X" "ns:c:t:";
  265. } else {
  266. // FETCHMAIL
  267. opt_after_connect = NULL;
  268. opt_complementary = "-2:w+:P";
  269. options = "w:U:P:X" "tz";
  270. }
  271. opts = getopt32(argv, options,
  272. &timeout, &opt_user, &opt_pass,
  273. &opt_subject, &opt_charset, &opt_recipients
  274. );
  275. //argc -= optind;
  276. argv += optind;
  277. // first argument is remote server[:port]
  278. opt_connect = *argv++;
  279. // connect to server
  280. // SSL ordered? ->
  281. if (opts & OPT_X) {
  282. // ... use openssl helper
  283. launch_helper(xargs);
  284. // no SSL ordered? ->
  285. } else {
  286. // ... make plain connect
  287. int fd = create_and_connect_stream_or_die(opt_connect, 25);
  288. // make ourselves a simple IO filter
  289. // from now we know nothing about network :)
  290. xmove_fd(fd, STDIN_FILENO);
  291. xdup2(STDIN_FILENO, STDOUT_FILENO);
  292. }
  293. #if ENABLE_FETCHMAIL
  294. // we are sendmail?
  295. if (opt_after_connect)
  296. #endif
  297. {
  298. /***************************************************
  299. * SENDMAIL
  300. ***************************************************/
  301. char *opt_from;
  302. int code;
  303. char *boundary;
  304. const char *fmt;
  305. const char *p;
  306. char *q;
  307. // we didn't use SSL helper? ->
  308. if (!(opts & OPT_X)) {
  309. // ... wait for initial server OK
  310. smtp_check(NULL, 220);
  311. }
  312. // get the sender
  313. opt_from = sane(*argv++);
  314. // introduce to server
  315. // we should start with modern EHLO
  316. if (250 != smtp_checkp("EHLO %s", opt_from, -1)) {
  317. smtp_checkp("HELO %s", opt_from, 250);
  318. }
  319. // set sender
  320. // NOTE: if password has not been specified
  321. // then no authentication is possible
  322. code = (opts & OPT_P) ? -1 : 250;
  323. // first try softly without authentication
  324. while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) {
  325. // MAIL FROM failed -> authentication needed
  326. // have we got username?
  327. if (!(opts & OPT_U)) {
  328. // no! fetch it from "from" option
  329. //opts |= OPT_U;
  330. opt_user = xstrdup(opt_from);
  331. *strchrnul(opt_user, '@') = '\0';
  332. }
  333. // now we've got username
  334. // so try to authenticate
  335. if (334 == smtp_check("AUTH LOGIN", -1)) {
  336. uuencode(NULL, opt_user);
  337. smtp_check("", 334);
  338. uuencode(NULL, opt_pass);
  339. smtp_check("", 235);
  340. }
  341. // authenticated OK? -> retry to set sender
  342. // but this time die on failure!
  343. code = 250;
  344. }
  345. // set recipients
  346. for (llist_t *to = opt_recipients; to; to = to->link) {
  347. smtp_checkp("RCPT TO:<%s>", sane(to->data), 250);
  348. }
  349. // enter "put message" mode
  350. smtp_check("DATA", 354);
  351. // put address headers
  352. printf("From: %s\r\n", opt_from);
  353. for (llist_t *to = opt_recipients; to; to = to->link) {
  354. printf("To: %s\r\n", to->data);
  355. }
  356. // put encoded subject
  357. if (opts & OPTS_c)
  358. sane((char *)opt_charset);
  359. if (opts & OPTS_s) {
  360. printf("Subject: =?%s?B?", opt_charset);
  361. uuencode(NULL, opt_subject);
  362. printf("?=\r\n");
  363. }
  364. // put notification
  365. if (opts & OPTS_n)
  366. printf("Disposition-Notification-To: %s\r\n", opt_from);
  367. // make a random string -- it will delimit message parts
  368. srand(monotonic_us());
  369. boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
  370. // put common headers and body start
  371. printf(
  372. "Message-ID: <%s>\r\n"
  373. "Mime-Version: 1.0\r\n"
  374. "%smultipart/mixed; boundary=\"%s\"\r\n"
  375. , boundary
  376. , "Content-Type: "
  377. , boundary
  378. );
  379. // put body + attachment(s)
  380. // N.B. all these weird things just to be tiny
  381. // by reusing string patterns!
  382. fmt =
  383. "\r\n--%s\r\n"
  384. "%stext/plain; charset=%s\r\n"
  385. "%s%s\r\n"
  386. "%s"
  387. ;
  388. p = opt_charset;
  389. q = (char *)"";
  390. while (*argv) {
  391. printf(
  392. fmt
  393. , boundary
  394. , "Content-Type: "
  395. , p
  396. , "Content-Disposition: inline"
  397. , q
  398. , "Content-Transfer-Encoding: base64\r\n"
  399. );
  400. p = "";
  401. fmt =
  402. "\r\n--%s\r\n"
  403. "%sapplication/octet-stream%s\r\n"
  404. "%s; filename=\"%s\"\r\n"
  405. "%s"
  406. ;
  407. uuencode(*argv, NULL);
  408. if (*(++argv))
  409. q = bb_get_last_path_component_strip(*argv);
  410. }
  411. // put message terminator
  412. printf("\r\n--%s--\r\n" "\r\n", boundary);
  413. // leave "put message" mode
  414. smtp_check(".", 250);
  415. // ... and say goodbye
  416. smtp_check("QUIT", 221);
  417. #if ENABLE_FETCHMAIL
  418. } else {
  419. /***************************************************
  420. * FETCHMAIL
  421. ***************************************************/
  422. char *buf;
  423. unsigned nmsg;
  424. char *hostname;
  425. pid_t pid;
  426. // cache fetch command:
  427. // TOP will return only the headers
  428. // RETR will dump the whole message
  429. const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
  430. // goto maildir
  431. xchdir(*argv++);
  432. // cache postprocess program
  433. *fargs = *argv;
  434. // authenticate
  435. if (!(opts & OPT_U)) {
  436. //opts |= OPT_U;
  437. // N.B. IMHO getenv("USER") can be way easily spoofed!
  438. opt_user = bb_getpwuid(NULL, -1, getuid());
  439. }
  440. // get server greeting
  441. pop3_checkr(NULL, NULL, &buf);
  442. // server supports APOP?
  443. if ('<' == *buf) {
  444. md5_ctx_t md5;
  445. // yes! compose <stamp><password>
  446. char *s = strchr(buf, '>');
  447. if (s)
  448. strcpy(s+1, opt_pass);
  449. s = buf;
  450. // get md5 sum of <stamp><password>
  451. md5_begin(&md5);
  452. md5_hash(s, strlen(s), &md5);
  453. md5_end(s, &md5);
  454. // NOTE: md5 struct contains enough space
  455. // so we reuse md5 space instead of xzalloc(16*2+1)
  456. #define md5_hex ((uint8_t *)&md5)
  457. // uint8_t *md5_hex = (uint8_t *)&md5;
  458. *bin2hex(md5_hex, s, 16) = '\0';
  459. // APOP
  460. s = xasprintf("%s %s", opt_user, md5_hex);
  461. #undef md5_hex
  462. pop3_check("APOP %s", s);
  463. if (ENABLE_FEATURE_CLEAN_UP) {
  464. free(s);
  465. free(buf-4); // buf is "+OK " away from malloc'ed string
  466. }
  467. // server ignores APOP -> use simple text authentication
  468. } else {
  469. // USER
  470. pop3_check("USER %s", opt_user);
  471. // PASS
  472. pop3_check("PASS %s", opt_pass);
  473. }
  474. // get mailbox statistics
  475. pop3_checkr("STAT", NULL, &buf);
  476. // prepare message filename suffix
  477. hostname = safe_gethostname();
  478. pid = getpid();
  479. // get messages counter
  480. // NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
  481. // we only need nmsg and atoi is just exactly what we need
  482. // if atoi fails to convert buf into number it returns 0
  483. // in this case the following loop simply will not be executed
  484. nmsg = atoi(buf);
  485. if (ENABLE_FEATURE_CLEAN_UP)
  486. free(buf-4); // buf is "+OK " away from malloc'ed string
  487. // loop through messages
  488. for (; nmsg; nmsg--) {
  489. // generate unique filename
  490. char *filename = xasprintf("tmp/%llu.%u.%s", monotonic_us(), pid, hostname);
  491. char *target;
  492. int rc;
  493. // retrieve message in ./tmp/
  494. pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
  495. pop3_message(filename);
  496. // delete message from server
  497. if (opts & OPTF_z)
  498. pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
  499. // run postprocessing program
  500. if (*fargs) {
  501. fargs[1] = filename;
  502. rc = wait4pid(spawn((char **)fargs));
  503. if (99 == rc)
  504. break;
  505. if (1 == rc)
  506. goto skip;
  507. }
  508. // atomically move message to ./new/
  509. target = xstrdup(filename);
  510. strncpy(target, "new", 3);
  511. // ... or just stop receiving on error
  512. if (rename_or_warn(filename, target))
  513. break;
  514. free(target);
  515. skip:
  516. free(filename);
  517. }
  518. // Bye
  519. pop3_check("QUIT", NULL);
  520. #endif // ENABLE_FETCHMAIL
  521. }
  522. return 0;
  523. }