sendmail.c 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * bare bones sendmail
  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. #include "mail.h"
  11. static int smtp_checkp(const char *fmt, const char *param, int code)
  12. {
  13. char *answer;
  14. const char *msg = command(fmt, param);
  15. // read stdin
  16. // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
  17. // parse first bytes to a number
  18. // if code = -1 then just return this number
  19. // if code != -1 then checks whether the number equals the code
  20. // if not equal -> die saying msg
  21. while ((answer = xmalloc_fgetline(stdin)) != NULL)
  22. if (strlen(answer) <= 3 || '-' != answer[3])
  23. break;
  24. if (answer) {
  25. int n = atoi(answer);
  26. if (timeout)
  27. alarm(0);
  28. free(answer);
  29. if (-1 == code || n == code)
  30. return n;
  31. }
  32. bb_error_msg_and_die("%s failed", msg);
  33. }
  34. static int smtp_check(const char *fmt, int code)
  35. {
  36. return smtp_checkp(fmt, NULL, code);
  37. }
  38. // strip argument of bad chars
  39. static char *sane_address(char *str)
  40. {
  41. char *s = str;
  42. char *p = s;
  43. while (*s) {
  44. if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
  45. *p++ = *s;
  46. }
  47. s++;
  48. }
  49. *p = '\0';
  50. return str;
  51. }
  52. static void rcptto(const char *s)
  53. {
  54. smtp_checkp("RCPT TO:<%s>", s, 250);
  55. }
  56. int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  57. int sendmail_main(int argc UNUSED_PARAM, char **argv)
  58. {
  59. char *opt_connect = opt_connect;
  60. char *opt_from;
  61. char *s;
  62. llist_t *list = NULL;
  63. char *domain = sane_address(safe_getdomainname());
  64. int code;
  65. enum {
  66. //--- standard options
  67. OPT_t = 1 << 0, // read message for recipients, append them to those on cmdline
  68. OPT_f = 1 << 1, // sender address
  69. OPT_o = 1 << 2, // various options. -oi IMPLIED! others are IGNORED!
  70. //--- BB specific options
  71. OPT_w = 1 << 3, // network timeout
  72. OPT_H = 1 << 4, // use external connection helper
  73. OPT_S = 1 << 5, // specify connection string
  74. OPT_a = 1 << 6, // authentication tokens
  75. };
  76. // init global variables
  77. INIT_G();
  78. // save initial stdin since body is piped!
  79. xdup2(STDIN_FILENO, 3);
  80. G.fp0 = fdopen(3, "r");
  81. // parse options
  82. // -f is required. -H and -S are mutually exclusive
  83. opt_complementary = "f:w+:H--S:S--H:a::";
  84. // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect
  85. // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility,
  86. // it is still under development.
  87. opts = getopt32(argv, "tf:o:w:H:S:a::", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list);
  88. //argc -= optind;
  89. argv += optind;
  90. // process -a[upm]<token> options
  91. if ((opts & OPT_a) && !list)
  92. bb_show_usage();
  93. while (list) {
  94. char *a = (char *) llist_pop(&list);
  95. if ('u' == a[0])
  96. G.user = xstrdup(a+1);
  97. if ('p' == a[0])
  98. G.pass = xstrdup(a+1);
  99. // N.B. we support only AUTH LOGIN so far
  100. //if ('m' == a[0])
  101. // G.method = xstrdup(a+1);
  102. }
  103. // N.B. list == NULL here
  104. //bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv);
  105. // connect to server
  106. // connection helper ordered? ->
  107. if (opts & OPT_H) {
  108. const char *args[] = { "sh", "-c", opt_connect, NULL };
  109. // plug it in
  110. launch_helper(args);
  111. // vanilla connection
  112. } else {
  113. int fd;
  114. // host[:port] not explicitly specified? -> use $SMTPHOST
  115. // no $SMTPHOST ? -> use localhost
  116. if (!(opts & OPT_S)) {
  117. opt_connect = getenv("SMTPHOST");
  118. if (!opt_connect)
  119. opt_connect = (char *)"127.0.0.1";
  120. }
  121. // do connect
  122. fd = create_and_connect_stream_or_die(opt_connect, 25);
  123. // and make ourselves a simple IO filter
  124. xmove_fd(fd, STDIN_FILENO);
  125. xdup2(STDIN_FILENO, STDOUT_FILENO);
  126. }
  127. // N.B. from now we know nothing about network :)
  128. // wait for initial server OK
  129. // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure
  130. // so we need to kick the server to see whether we are ok
  131. code = smtp_check("NOOP", -1);
  132. // 220 on plain connection, 250 on openssl-helped TLS session
  133. if (220 == code)
  134. smtp_check(NULL, 250); // reread the code to stay in sync
  135. else if (250 != code)
  136. bb_error_msg_and_die("INIT failed");
  137. // we should start with modern EHLO
  138. if (250 != smtp_checkp("EHLO %s", domain, -1)) {
  139. smtp_checkp("HELO %s", domain, 250);
  140. }
  141. if (ENABLE_FEATURE_CLEAN_UP)
  142. free(domain);
  143. // perform authentication
  144. if (opts & OPT_a) {
  145. smtp_check("AUTH LOGIN", 334);
  146. // we must read credentials unless they are given via -a[up] options
  147. if (!G.user || !G.pass)
  148. get_cred_or_die(4);
  149. encode_base64(NULL, G.user, NULL);
  150. smtp_check("", 334);
  151. encode_base64(NULL, G.pass, NULL);
  152. smtp_check("", 235);
  153. }
  154. // set sender
  155. // N.B. we have here a very loosely defined algotythm
  156. // since sendmail historically offers no means to specify secrets on cmdline.
  157. // 1) server can require no authentication ->
  158. // we must just provide a (possibly fake) reply address.
  159. // 2) server can require AUTH ->
  160. // we must provide valid username and password along with a (possibly fake) reply address.
  161. // For the sake of security username and password are to be read either from console or from a secured file.
  162. // Since reading from console may defeat usability, the solution is either to read from a predefined
  163. // file descriptor (e.g. 4), or again from a secured file.
  164. // got no sender address? -> use system username as a resort
  165. // N.B. we marked -f as required option!
  166. //if (!G.user) {
  167. // // N.B. IMHO getenv("USER") can be way easily spoofed!
  168. // G.user = xuid2uname(getuid());
  169. // opt_from = xasprintf("%s@%s", G.user, domain);
  170. //}
  171. //if (ENABLE_FEATURE_CLEAN_UP)
  172. // free(domain);
  173. smtp_checkp("MAIL FROM:<%s>", opt_from, 250);
  174. // process message
  175. // read recipients from message and add them to those given on cmdline.
  176. // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line
  177. // and then use the rest of stdin as message body
  178. code = 0; // set "analyze headers" mode
  179. while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
  180. // put message lines doubling leading dots
  181. if (code) {
  182. // escape leading dots
  183. // N.B. this feature is implied even if no -i (-oi) switch given
  184. // N.B. we need to escape the leading dot regardless of
  185. // whether it is single or not character on the line
  186. if ('.' == s[0] /*&& '\0' == s[1] */)
  187. printf(".");
  188. // dump read line
  189. printf("%s\r\n", s);
  190. free(s);
  191. continue;
  192. }
  193. // analyze headers
  194. // To: or Cc: headers add recipients
  195. if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Bcc: " + 1, s, 4)) {
  196. rcptto(sane_address(s+4));
  197. // goto addh;
  198. llist_add_to_end(&list, s);
  199. // Bcc: header adds blind copy (hidden) recipient
  200. } else if (0 == strncasecmp("Bcc: ", s, 5)) {
  201. rcptto(sane_address(s+5));
  202. free(s);
  203. // N.B. Bcc: vanishes from headers!
  204. // other headers go verbatim
  205. } else if (s[0]) {
  206. // addh:
  207. llist_add_to_end(&list, s);
  208. // the empty line stops analyzing headers
  209. } else {
  210. free(s);
  211. // put recipients specified on cmdline
  212. while (*argv) {
  213. s = sane_address(*argv);
  214. rcptto(s);
  215. llist_add_to_end(&list, xasprintf("To: %s", s));
  216. argv++;
  217. }
  218. // enter "put message" mode
  219. smtp_check("DATA", 354);
  220. // dump the headers
  221. while (list) {
  222. printf("%s\r\n", (char *) llist_pop(&list));
  223. }
  224. printf("%s\r\n" + 2); // quirk for format string to be reused
  225. // stop analyzing headers
  226. code++;
  227. }
  228. }
  229. // finalize the message
  230. smtp_check(".", 250);
  231. // ... and say goodbye
  232. smtp_check("QUIT", 221);
  233. // cleanup
  234. if (ENABLE_FEATURE_CLEAN_UP)
  235. fclose(G.fp0);
  236. return EXIT_SUCCESS;
  237. }