popmaildir.c 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * popmaildir: a simple yet powerful POP3 client
  4. * Delivers contents of remote mailboxes to local Maildir
  5. *
  6. * Inspired by original utility by Nikola Vladov
  7. *
  8. * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
  9. *
  10. * Licensed under GPLv2, see file LICENSE in this tarball for details.
  11. */
  12. #include "libbb.h"
  13. #include "mail.h"
  14. static void pop3_checkr(const char *fmt, const char *param, char **ret)
  15. {
  16. const char *msg = command(fmt, param);
  17. char *answer = xmalloc_fgetline(stdin);
  18. if (answer && '+' == *answer) {
  19. if (timeout)
  20. alarm(0);
  21. if (ret)
  22. *ret = answer+4; // skip "+OK "
  23. else if (ENABLE_FEATURE_CLEAN_UP)
  24. free(answer);
  25. return;
  26. }
  27. bb_error_msg_and_die("%s failed: %s", msg, answer);
  28. }
  29. static void pop3_check(const char *fmt, const char *param)
  30. {
  31. pop3_checkr(fmt, param, NULL);
  32. }
  33. int popmaildir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  34. int popmaildir_main(int argc UNUSED_PARAM, char **argv)
  35. {
  36. char *buf;
  37. unsigned nmsg;
  38. char *hostname;
  39. pid_t pid;
  40. const char *retr;
  41. #if ENABLE_FEATURE_POPMAILDIR_DELIVERY
  42. const char *delivery;
  43. #endif
  44. unsigned opt_nlines = 0;
  45. enum {
  46. OPT_b = 1 << 0, // -b binary mode. Ignored
  47. OPT_d = 1 << 1, // -d,-dd,-ddd debug. Ignored
  48. OPT_m = 1 << 2, // -m show used memory. Ignored
  49. OPT_V = 1 << 3, // -V version. Ignored
  50. OPT_c = 1 << 4, // -c use tcpclient. Ignored
  51. OPT_a = 1 << 5, // -a use APOP protocol
  52. OPT_s = 1 << 6, // -s skip authorization
  53. OPT_T = 1 << 7, // -T get messages with TOP instead with RETR
  54. OPT_k = 1 << 8, // -k keep retrieved messages on the server
  55. OPT_t = 1 << 9, // -t90 set timeout to 90 sec
  56. OPT_R = 1 << 10, // -R20000 remove old messages on the server >= 20000 bytes (requires -k). Ignored
  57. OPT_Z = 1 << 11, // -Z11-23 remove messages from 11 to 23 (dangerous). Ignored
  58. OPT_L = 1 << 12, // -L50000 not retrieve new messages >= 50000 bytes. Ignored
  59. OPT_H = 1 << 13, // -H30 type first 30 lines of a message; (-L12000 -H30). Ignored
  60. OPT_M = 1 << 14, // -M\"program arg1 arg2 ...\"; deliver by program. Treated like -F
  61. OPT_F = 1 << 15, // -F\"program arg1 arg2 ...\"; filter by program. Treated like -M
  62. };
  63. // init global variables
  64. INIT_G();
  65. // parse options
  66. opt_complementary = "-1:dd:t+:R+:L+:H+";
  67. opts = getopt32(argv,
  68. "bdmVcasTkt:" "R:Z:L:H:" USE_FEATURE_POPMAILDIR_DELIVERY("M:F:"),
  69. &timeout, NULL, NULL, NULL, &opt_nlines
  70. USE_FEATURE_POPMAILDIR_DELIVERY(, &delivery, &delivery) // we treat -M and -F the same
  71. );
  72. //argc -= optind;
  73. argv += optind;
  74. // get auth info
  75. if (!(opts & OPT_s))
  76. get_cred_or_die(STDIN_FILENO);
  77. // goto maildir
  78. xchdir(*argv++);
  79. // launch connect helper, if any
  80. if (*argv)
  81. launch_helper((const char **)argv);
  82. // get server greeting
  83. pop3_checkr(NULL, NULL, &buf);
  84. // authenticate (if no -s given)
  85. if (!(opts & OPT_s)) {
  86. // server supports APOP and we want it? -> use it
  87. if ('<' == *buf && (opts & OPT_a)) {
  88. md5_ctx_t md5;
  89. // yes! compose <stamp><password>
  90. char *s = strchr(buf, '>');
  91. if (s)
  92. strcpy(s+1, G.pass);
  93. s = buf;
  94. // get md5 sum of <stamp><password>
  95. md5_begin(&md5);
  96. md5_hash(s, strlen(s), &md5);
  97. md5_end(s, &md5);
  98. // NOTE: md5 struct contains enough space
  99. // so we reuse md5 space instead of xzalloc(16*2+1)
  100. #define md5_hex ((uint8_t *)&md5)
  101. // uint8_t *md5_hex = (uint8_t *)&md5;
  102. *bin2hex((char *)md5_hex, s, 16) = '\0';
  103. // APOP
  104. s = xasprintf("%s %s", G.user, md5_hex);
  105. #undef md5_hex
  106. pop3_check("APOP %s", s);
  107. if (ENABLE_FEATURE_CLEAN_UP) {
  108. free(s);
  109. free(buf-4); // buf is "+OK " away from malloc'ed string
  110. }
  111. // server ignores APOP -> use simple text authentication
  112. } else {
  113. // USER
  114. pop3_check("USER %s", G.user);
  115. // PASS
  116. pop3_check("PASS %s", G.pass);
  117. }
  118. }
  119. // get mailbox statistics
  120. pop3_checkr("STAT", NULL, &buf);
  121. // prepare message filename suffix
  122. hostname = safe_gethostname();
  123. pid = getpid();
  124. // get messages counter
  125. // NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
  126. // we only need nmsg and atoi is just exactly what we need
  127. // if atoi fails to convert buf into number it returns 0
  128. // in this case the following loop simply will not be executed
  129. nmsg = atoi(buf);
  130. if (ENABLE_FEATURE_CLEAN_UP)
  131. free(buf-4); // buf is "+OK " away from malloc'ed string
  132. // loop through messages
  133. retr = (opts & OPT_T) ? xasprintf("TOP %%u %u", opt_nlines) : "RETR %u";
  134. for (; nmsg; nmsg--) {
  135. char *filename;
  136. char *target;
  137. char *answer;
  138. FILE *fp;
  139. #if ENABLE_FEATURE_POPMAILDIR_DELIVERY
  140. int rc;
  141. #endif
  142. // generate unique filename
  143. filename = xasprintf("tmp/%llu.%u.%s",
  144. monotonic_us(), (unsigned)pid, hostname);
  145. // retrieve message in ./tmp/ unless filter is specified
  146. pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
  147. #if ENABLE_FEATURE_POPMAILDIR_DELIVERY
  148. // delivery helper ordered? -> setup pipe
  149. if (opts & (OPT_F|OPT_M)) {
  150. // helper will have $FILENAME set to filename
  151. xsetenv("FILENAME", filename);
  152. fp = popen(delivery, "w");
  153. unsetenv("FILENAME");
  154. if (!fp) {
  155. bb_perror_msg("delivery helper");
  156. break;
  157. }
  158. } else
  159. #endif
  160. // create and open file filename
  161. fp = xfopen_for_write(filename);
  162. // copy stdin to fp (either filename or delivery helper)
  163. while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
  164. char *s = answer;
  165. if ('.' == answer[0]) {
  166. if ('.' == answer[1])
  167. s++;
  168. else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
  169. break;
  170. }
  171. //*strchrnul(s, '\r') = '\n';
  172. fputs(s, fp);
  173. free(answer);
  174. }
  175. #if ENABLE_FEATURE_POPMAILDIR_DELIVERY
  176. // analyse delivery status
  177. if (opts & (OPT_F|OPT_M)) {
  178. rc = pclose(fp);
  179. if (99 == rc) // 99 means bail out
  180. break;
  181. // if (rc) // !0 means skip to the next message
  182. goto skip;
  183. // // 0 means continue
  184. } else {
  185. // close filename
  186. fclose(fp);
  187. }
  188. #endif
  189. // delete message from server
  190. if (!(opts & OPT_k))
  191. pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
  192. // atomically move message to ./new/
  193. target = xstrdup(filename);
  194. strncpy(target, "new", 3);
  195. // ... or just stop receiving on failure
  196. if (rename_or_warn(filename, target))
  197. break;
  198. free(target);
  199. #if ENABLE_FEATURE_POPMAILDIR_DELIVERY
  200. skip:
  201. #endif
  202. free(filename);
  203. }
  204. // Bye
  205. pop3_check("QUIT", NULL);
  206. if (ENABLE_FEATURE_CLEAN_UP) {
  207. free(G.user);
  208. free(G.pass);
  209. }
  210. return EXIT_SUCCESS;
  211. }