blob: 6d00026e27121edd62b506891cdb73ab65ce73ad [file] [log] [blame]
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +00001/* vi: set sw=4 ts=4: */
2/*
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +00003 * bare bones sendmail/fetchmail
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +00004 *
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
Denis Vlasenko17db1a92008-02-26 21:13:17 +000011struct globals {
12 pid_t helper_pid;
13 unsigned timeout;
Denis Vlasenkobed22a02008-09-27 14:01:22 +000014 FILE *fp0; // initial stdin
Denis Vlasenko17db1a92008-02-26 21:13:17 +000015 // arguments for SSL connection helper
16 const char *xargs[9];
Denis Vlasenko17db1a92008-02-26 21:13:17 +000017 // arguments for postprocess helper
18 const char *fargs[3];
Denis Vlasenko17db1a92008-02-26 21:13:17 +000019};
20#define G (*ptr_to_globals)
21#define helper_pid (G.helper_pid)
22#define timeout (G.timeout )
Denis Vlasenkobed22a02008-09-27 14:01:22 +000023#define fp0 (G.fp0 )
Denis Vlasenko17db1a92008-02-26 21:13:17 +000024#define xargs (G.xargs )
25#define fargs (G.fargs )
26#define INIT_G() do { \
Denis Vlasenko574f2f42008-02-27 18:41:59 +000027 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
Denis Vlasenko8195d202008-02-27 09:39:04 +000028 xargs[0] = "openssl"; \
29 xargs[1] = "s_client"; \
30 xargs[2] = "-quiet"; \
31 xargs[3] = "-connect"; \
Denis Vlasenko3dee8e22008-06-27 21:24:08 +000032 /*xargs[4] = "localhost";*/ \
Denis Vlasenko8195d202008-02-27 09:39:04 +000033 xargs[5] = "-tls1"; \
34 xargs[6] = "-starttls"; \
35 xargs[7] = "smtp"; \
Denis Vlasenkobed22a02008-09-27 14:01:22 +000036 fargs[0] = CONFIG_FEATURE_SENDMAIL_CHARSET; \
Denis Vlasenko17db1a92008-02-26 21:13:17 +000037} while (0)
38
Denis Vlasenkobed22a02008-09-27 14:01:22 +000039#define opt_connect (xargs[4])
Denis Vlasenko17db1a92008-02-26 21:13:17 +000040#define opt_after_connect (xargs[5])
Denis Vlasenkobed22a02008-09-27 14:01:22 +000041#define opt_charset (fargs[0])
42#define opt_subject (fargs[1])
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000043
Denis Vlasenkobed22a02008-09-27 14:01:22 +000044static void uuencode(char *fname, const char *text)
45{
46 enum {
47 SRC_BUF_SIZE = 45, /* This *MUST* be a multiple of 3 */
48 DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
49 };
50
51#define src_buf text
52 FILE *fp = fp;
53 ssize_t len = len;
54 char dst_buf[DST_BUF_SIZE + 1];
55
56 if (fname) {
57 fp = (NOT_LONE_DASH(fname)) ? xfopen_for_read(fname) : fp0;
58 src_buf = bb_common_bufsiz1;
59 // N.B. strlen(NULL) segfaults!
60 } else if (text) {
61 // though we do not call uuencode(NULL, NULL) explicitly
62 // still we do not want to break things suddenly
63 len = strlen(text);
64 } else
65 return;
66
67 while (1) {
68 size_t size;
69 if (fname) {
70 size = fread((char *)src_buf, 1, SRC_BUF_SIZE, fp);
71 if ((ssize_t)size < 0)
72 bb_perror_msg_and_die(bb_msg_read_error);
73 } else {
74 size = len;
75 if (len > SRC_BUF_SIZE)
76 size = SRC_BUF_SIZE;
77 }
78 if (!size)
79 break;
80 // encode the buffer we just read in
81 bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64);
82 if (fname) {
83 printf("\r\n");
84 } else {
85 src_buf += size;
86 len -= size;
87 }
88 fwrite(dst_buf, 1, 4 * ((size + 2) / 3), stdout);
89 }
90 if (fname)
91 fclose(fp);
92#undef src_buf
93}
94
95
96#if ENABLE_FEATURE_SENDMAIL_SSL
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +000097static void kill_helper(void)
98{
99 // TODO!!!: is there more elegant way to terminate child on program failure?
100 if (helper_pid > 0)
101 kill(helper_pid, SIGTERM);
102}
103
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000104// generic signal handler
105static void signal_handler(int signo)
106{
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000107#define err signo
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000108 if (SIGALRM == signo) {
109 kill_helper();
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000110 bb_error_msg_and_die("timed out");
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000111 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000112
113 // SIGCHLD. reap zombies
114 if (wait_any_nohang(&err) > 0)
115 if (WIFEXITED(err) && WEXITSTATUS(err))
116 bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000117#undef err
118}
119
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000120static void launch_helper(const char **argv)
121{
122 // setup vanilla unidirectional pipes interchange
123 int idx;
124 int pipes[4];
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000125
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000126 xpipe(pipes);
127 xpipe(pipes+2);
Denis Vlasenkofa0b56d2008-07-01 16:09:07 +0000128 helper_pid = vfork();
129 if (helper_pid < 0)
130 bb_perror_msg_and_die("vfork");
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000131 idx = (!helper_pid) * 2;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000132 xdup2(pipes[idx], STDIN_FILENO);
133 xdup2(pipes[3-idx], STDOUT_FILENO);
134 if (ENABLE_FEATURE_CLEAN_UP)
135 for (int i = 4; --i >= 0; )
136 if (pipes[i] > STDOUT_FILENO)
137 close(pipes[i]);
138 if (!helper_pid) {
Denis Vlasenko8195d202008-02-27 09:39:04 +0000139 // child: try to execute connection helper
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000140 BB_EXECVP(*argv, (char **)argv);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000141 _exit(127);
142 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000143 // parent: check whether child is alive
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000144 bb_signals(0
145 + (1 << SIGCHLD)
146 + (1 << SIGALRM)
147 , signal_handler);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000148 signal_handler(SIGCHLD);
149 // child seems OK -> parent goes on
150}
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000151#else
152#define kill_helper() ((void)0)
153#define launch_helper(x) bb_error_msg_and_die("no SSL support")
154#endif
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000155
Denis Vlasenko8195d202008-02-27 09:39:04 +0000156static const char *command(const char *fmt, const char *param)
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000157{
Denis Vlasenko8195d202008-02-27 09:39:04 +0000158 const char *msg = fmt;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000159 alarm(timeout);
160 if (msg) {
Denis Vlasenko8195d202008-02-27 09:39:04 +0000161 msg = xasprintf(fmt, param);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000162 printf("%s\r\n", msg);
163 }
164 fflush(stdout);
165 return msg;
166}
167
168static int smtp_checkp(const char *fmt, const char *param, int code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000169{
170 char *answer;
Denis Vlasenko8195d202008-02-27 09:39:04 +0000171 const char *msg = command(fmt, param);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000172 // read stdin
173 // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
174 // parse first bytes to a number
175 // if code = -1 then just return this number
176 // if code != -1 then checks whether the number equals the code
177 // if not equal -> die saying msg
Denis Vlasenko8ee649a2008-03-26 20:04:27 +0000178 while ((answer = xmalloc_fgetline(stdin)) != NULL)
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000179 if (strlen(answer) <= 3 || '-' != answer[3])
180 break;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000181//bb_error_msg("FMT[%s]ANS[%s]", fmt, answer);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000182 if (answer) {
183 int n = atoi(answer);
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000184//bb_error_msg("FMT[%s]COD[%d][%d]", fmt, n, code);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000185 alarm(0);
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000186 free(answer);
187 if (-1 == code || n == code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000188 return n;
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000189 }
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000190 kill_helper();
191 bb_error_msg_and_die("%s failed", msg);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000192}
193
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000194static inline int smtp_check(const char *fmt, int code)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000195{
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000196 return smtp_checkp(fmt, NULL, code);
197}
198
Denis Vlasenkoa2980c62008-02-02 17:54:35 +0000199// strip argument of bad chars
200static char *sane(char *str)
201{
202 char *s = str;
203 char *p = s;
204 while (*s) {
205 if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
206 *p++ = *s;
207 }
208 s++;
209 }
210 *p = '\0';
211 return str;
212}
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000213
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000214#if ENABLE_FETCHMAIL
215static void pop3_checkr(const char *fmt, const char *param, char **ret)
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000216{
Denis Vlasenko8195d202008-02-27 09:39:04 +0000217 const char *msg = command(fmt, param);
Denis Vlasenko8ee649a2008-03-26 20:04:27 +0000218 char *answer = xmalloc_fgetline(stdin);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000219 if (answer && '+' == *answer) {
220 alarm(0);
221 if (ret)
222 *ret = answer+4; // skip "+OK "
223 else if (ENABLE_FEATURE_CLEAN_UP)
224 free(answer);
225 return;
226 }
227 kill_helper();
228 bb_error_msg_and_die("%s failed", msg);
229}
230
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000231static inline void pop3_check(const char *fmt, const char *param)
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000232{
233 pop3_checkr(fmt, param, NULL);
234}
235
236static void pop3_message(const char *filename)
237{
238 int fd;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000239 char *answer;
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000240 // create and open file filename
241 // read stdin, copy to created file
242 fd = xopen(filename, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL);
Denis Vlasenko8195d202008-02-27 09:39:04 +0000243 while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000244 char *s = answer;
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000245 if ('.' == *answer) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000246 if ('.' == answer[1])
247 s++;
248 else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
249 break;
250 }
251 xwrite(fd, s, strlen(s));
252 free(answer);
253 }
254 close(fd);
255}
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000256#endif
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000257
Denis Vlasenko8d075602008-08-10 20:46:39 +0000258// NB: parse_url can modify url[] (despite const), but only if '@' is there
259static const char *parse_url(const char *url, const char **user, const char **pass)
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000260{
261 // parse [user[:pass]@]host
262 // return host
263 char *s = strchr(url, '@');
264 *user = *pass = NULL;
265 if (s) {
266 *s++ = '\0';
267 *user = url;
268 url = s;
269 s = strchr(*user, ':');
270 if (s) {
271 *s++ = '\0';
272 *pass = s;
273 }
274 }
275 return url;
276}
277
Denis Vlasenko73542442008-07-17 19:37:09 +0000278static void rcptto(const char *s)
279{
280 smtp_checkp("RCPT TO:<%s>", s, 250);
281}
282
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000283int sendgetmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000284int sendgetmail_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000285{
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000286#if ENABLE_FEATURE_SENDMAIL_MAILX
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000287 llist_t *opt_attachments = NULL;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000288#endif
289 char *opt_from, *opt_fullname;
Denis Vlasenko8d075602008-08-10 20:46:39 +0000290 const char *opt_user;
291 const char *opt_pass;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000292
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000293 enum {
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000294 OPT_w = 1 << 0, // network timeout
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000295
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000296 OPT_H = 1 << 1, // [user:password@]server[:port]
297 OPT_S = 1 << 2, // connect using openssl s_client helper
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000298
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000299 OPTS_t = 1 << 3, // sendmail: read message for recipients
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000300 OPTF_t = 1 << 3, // fetchmail: use "TOP" not "RETR"
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000301
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000302 OPTS_N = 1 << 4, // sendmail: request notification
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000303 OPTF_z = 1 << 4, // fetchmail: delete from server
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000304
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000305 OPTS_f = 1 << 5, // sendmail: sender address
306 OPTS_F = 1 << 6, // sendmail: sender name, overrides $NAME
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000307
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000308 OPTS_s = 1 << 7, // sendmail: subject
309 OPTS_c = 1 << 8, // sendmail: assumed charset
310 OPTS_a = 1 << 9, // sendmail: attachment(s)
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000311 };
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000312 const char *options;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000313 unsigned opts;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000314
Denis Vlasenko8195d202008-02-27 09:39:04 +0000315 // init global variables
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000316 INIT_G();
317
Denis Vlasenko8195d202008-02-27 09:39:04 +0000318 // parse options, different option sets for sendmail and fetchmail
319 // N.B. opt_after_connect hereafter is NULL if we are called as fetchmail
320 // and is NOT NULL if we are called as sendmail
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000321 if (!ENABLE_FETCHMAIL || 's' == applet_name[0]) {
322 // SENDMAIL
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000323 // save initial stdin since body is piped!
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000324 xdup2(STDIN_FILENO, 3);
325 fp0 = fdopen(3, "r");
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000326 opt_complementary = "w+:a::";
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000327 options = "w:H:St" "N:f:F:" USE_FEATURE_SENDMAIL_MAILX("s:c:a:")
328 "X:V:vq:R:O:o:nmL:Iih:GC:B:b:A:"; // postfix compat only, ignored
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000329 } else {
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000330 // FETCHMAIL
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000331 opt_after_connect = NULL;
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000332 opt_complementary = "-1:w+";
333 options = "w:H:St" "z";
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000334 }
335 opts = getopt32(argv, options,
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000336 &timeout /* -w */, &opt_connect /* -H */,
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000337 NULL, &opt_from, &opt_fullname,
338#if ENABLE_FEATURE_SENDMAIL_MAILX
339 &opt_subject, &opt_charset, &opt_attachments,
340#endif
341 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000342 );
343 //argc -= optind;
344 argv += optind;
345
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000346 // connect to server
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000347 // host[:port] not specified ? -> use $SMTPHOST. no $SMTPHOST ? -> use localhost
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000348 if (!(opts & OPT_H)) {
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000349 opt_connect = getenv("SMTPHOST");
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000350 if (!opt_connect)
351 opt_connect = "127.0.0.1";
352 }
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000353 // fetch username and password, if any
354 // NB: parse_url modifies opt_connect[] ONLY if '@' is there.
355 // Thus "127.0.0.1" won't be modified, an is ok that it is RO.
Denis Vlasenko8d075602008-08-10 20:46:39 +0000356 opt_connect = parse_url(opt_connect, &opt_user, &opt_pass);
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000357// bb_error_msg("H[%s] U[%s] P[%s]", opt_connect, opt_user, opt_pass);
358
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000359 // username must be defined!
360 if (!opt_user) {
361 // N.B. IMHO getenv("USER") can be way easily spoofed!
362 opt_user = bb_getpwuid(NULL, -1, getuid());
363 }
364
Denis Vlasenko8195d202008-02-27 09:39:04 +0000365 // SSL ordered? ->
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000366 if (opts & OPT_S) {
Denis Vlasenko8195d202008-02-27 09:39:04 +0000367 // ... use openssl helper
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000368 launch_helper(xargs);
Denis Vlasenko8195d202008-02-27 09:39:04 +0000369 // no SSL ordered? ->
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000370 } else {
Denis Vlasenko8195d202008-02-27 09:39:04 +0000371 // ... make plain connect
372 int fd = create_and_connect_stream_or_die(opt_connect, 25);
373 // make ourselves a simple IO filter
374 // from now we know nothing about network :)
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000375 xmove_fd(fd, STDIN_FILENO);
376 xdup2(STDIN_FILENO, STDOUT_FILENO);
377 }
378
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000379 // are we sendmail?
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000380 if (!ENABLE_FETCHMAIL || opt_after_connect)
Denis Vlasenko8195d202008-02-27 09:39:04 +0000381/***************************************************
382 * SENDMAIL
383 ***************************************************/
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000384 {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000385 int code;
386 char *boundary;
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000387 llist_t *l;
Denis Vlasenko38e54f12008-07-16 21:55:03 +0000388 llist_t *headers = NULL;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000389 char *domain = sane(safe_getdomainname());
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000390
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000391 // got no sender address? -> use username as a resort
392 if (!(opts & OPTS_f)) {
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000393 opt_from = xasprintf("%s@%s", opt_user, domain);
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000394 }
395
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000396 // introduce to server
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000397
398 // we didn't use SSL helper? ->
399 if (!(opts & OPT_S)) {
400 // ... wait for initial server OK
401 smtp_check(NULL, 220);
402 }
403
Denis Vlasenko8195d202008-02-27 09:39:04 +0000404 // we should start with modern EHLO
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000405 if (250 != smtp_checkp("EHLO %s", domain, -1)) {
406 smtp_checkp("HELO %s", domain, 250);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000407 }
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000408 if (ENABLE_FEATURE_CLEAN_UP)
409 free(domain);
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000410
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000411 // set sender
Denis Vlasenko8195d202008-02-27 09:39:04 +0000412 // NOTE: if password has not been specified
413 // then no authentication is possible
Denis Vlasenko8d075602008-08-10 20:46:39 +0000414 code = (opt_pass ? -1 : 250);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000415 // first try softly without authentication
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000416 while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000417 // MAIL FROM failed -> authentication needed
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000418 if (334 == smtp_check("AUTH LOGIN", -1)) {
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000419 uuencode(NULL, opt_user); // opt_user != NULL
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000420 smtp_check("", 334);
421 uuencode(NULL, opt_pass);
422 smtp_check("", 235);
423 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000424 // authenticated OK? -> retry to set sender
425 // but this time die on failure!
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000426 code = 250;
427 }
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000428
Denis Vlasenko73542442008-07-17 19:37:09 +0000429 // recipients specified as arguments
430 while (*argv) {
431 // loose test on email address validity
432 if (strchr(sane(*argv), '@')) {
433 rcptto(sane(*argv));
434 llist_add_to_end(&headers, xasprintf("To: %s", *argv));
435 }
436 argv++;
437 }
438
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000439 // if -t specified or no recipients specified -> read recipients from message
440 // i.e. scan stdin for To:, Cc:, Bcc: lines ...
Denis Vlasenko73542442008-07-17 19:37:09 +0000441 // ... and then use the rest of stdin as message body
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000442 // N.B. subject read from body can be further overrided with one specified on command line.
443 // recipients are merged. Bcc: lines are deleted
Denis Vlasenko73542442008-07-17 19:37:09 +0000444 // N.B. other headers are collected and will be dumped verbatim
445 if (opts & OPTS_t || !headers) {
446 // fetch recipients and (optionally) subject
447 char *s;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000448 while ((s = xmalloc_fgetline(fp0)) != NULL) {
Denis Vlasenko73542442008-07-17 19:37:09 +0000449 if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Cc: ", s, 4)) {
450 rcptto(sane(s+4));
451 llist_add_to_end(&headers, s);
452 } else if (0 == strncasecmp("Bcc: ", s, 5)) {
453 rcptto(sane(s+5));
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000454 free(s);
Denis Vlasenko73542442008-07-17 19:37:09 +0000455 // N.B. Bcc vanishes from headers!
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000456 } else if (0 == strncmp("Subject: ", s, 9)) {
457 // we read subject -> use it verbatim unless it is specified
458 // on command line
459 if (!(opts & OPTS_s))
460 llist_add_to_end(&headers, s);
461 else
462 free(s);
Denis Vlasenko73542442008-07-17 19:37:09 +0000463 } else if (s[0]) {
464 // misc header
465 llist_add_to_end(&headers, s);
466 } else {
467 free(s);
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000468 break; // stop on the first empty line
Denis Vlasenko73542442008-07-17 19:37:09 +0000469 }
470 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000471 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000472
Denis Vlasenko8195d202008-02-27 09:39:04 +0000473 // enter "put message" mode
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000474 smtp_check("DATA", 354);
Denis Vlasenko8195d202008-02-27 09:39:04 +0000475
Denis Vlasenko73542442008-07-17 19:37:09 +0000476 // put headers we could have preread with -t
477 for (l = headers; l; l = l->link) {
478 printf("%s\r\n", l->data);
479 if (ENABLE_FEATURE_CLEAN_UP)
480 free(l->data);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000481 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000482
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000483 // put (possibly encoded) subject
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000484 if (opts & OPTS_c)
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000485 sane((char *)opt_charset);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000486 if (opts & OPTS_s) {
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000487 printf("Subject: ");
488 if (opts & OPTS_c) {
489 printf("=?%s?B?", opt_charset);
490 uuencode(NULL, opt_subject);
491 printf("?=");
492 } else {
493 printf("%s", opt_subject);
494 }
495 printf("\r\n");
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000496 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000497
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000498 // put sender name, $NAME is the default
499 if (!(opts & OPTS_F))
500 opt_fullname = getenv("NAME");
501 if (opt_fullname)
502 printf("From: \"%s\" <%s>\r\n", opt_fullname, opt_from);
503
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000504 // put notification
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000505 if (opts & OPTS_N)
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000506 printf("Disposition-Notification-To: %s\r\n", opt_from);
Denis Vlasenko8195d202008-02-27 09:39:04 +0000507
508 // make a random string -- it will delimit message parts
509 srand(monotonic_us());
Denis Vlasenkoc6938402008-03-24 02:18:03 +0000510 boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
Denis Vlasenko8195d202008-02-27 09:39:04 +0000511
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000512 // put common headers
513 // TODO: do we really need this?
514// printf("Message-ID: <%s>\r\n", boundary);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000515
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000516#if ENABLE_FEATURE_SENDMAIL_MAILX
517 // have attachments? -> compose multipart MIME
518 if (opt_attachments) {
519 const char *fmt;
520 const char *p;
521 char *q;
522
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000523 printf(
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000524 "Mime-Version: 1.0\r\n"
525 "%smultipart/mixed; boundary=\"%s\"\r\n"
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000526 , "Content-Type: "
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000527 , boundary
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000528 );
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000529
530 // body is pseudo attachment read from stdin in first turn
531 llist_add_to(&opt_attachments, (char *)"-");
532
533 // put body + attachment(s)
534 // N.B. all these weird things just to be tiny
535 // by reusing string patterns!
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000536 fmt =
537 "\r\n--%s\r\n"
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000538 "%stext/plain; charset=%s\r\n"
539 "%s%s\r\n"
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000540 "%s"
541 ;
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000542 p = opt_charset;
543 q = (char *)"";
544 l = opt_attachments;
545 while (l) {
546 printf(
547 fmt
548 , boundary
549 , "Content-Type: "
550 , p
551 , "Content-Disposition: inline"
552 , q
553 , "Content-Transfer-Encoding: base64\r\n"
554 );
555 p = "";
556 fmt =
557 "\r\n--%s\r\n"
558 "%sapplication/octet-stream%s\r\n"
559 "%s; filename=\"%s\"\r\n"
560 "%s"
561 ;
562 uuencode(l->data, NULL);
563 l = l->link;
564 if (l)
565 q = bb_get_last_path_component_strip(l->data);
566 }
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000567
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000568 // put message terminator
569 printf("\r\n--%s--\r\n" "\r\n", boundary);
570
571 // no attachments? -> just dump message
572 } else
573#endif
574 {
575 char *s;
576 // terminate headers
577 printf("\r\n");
578 // put plain text respecting leading dots
579 while ((s = xmalloc_fgetline(fp0)) != NULL) {
580 // escape leading dots
581 // N.B. this feature is implied even if no -i switch given
582 // N.B. we need to escape the leading dot regardless of
583 // whether it is single or not character on the line
584 if (/*(opts & OPTS_i) && */ '.' == s[0] /*&& '\0' == s[1] */)
585 printf(".");
586 // dump read line
587 printf("%s\r\n", s);
588 }
589 }
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000590
Denis Vlasenko8195d202008-02-27 09:39:04 +0000591 // leave "put message" mode
Denis Vlasenkoc6938402008-03-24 02:18:03 +0000592 smtp_check(".", 250);
Denis Vlasenko8195d202008-02-27 09:39:04 +0000593 // ... and say goodbye
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000594 smtp_check("QUIT", 221);
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000595 // cleanup
596 if (ENABLE_FEATURE_CLEAN_UP)
597 fclose(fp0);
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000598 }
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000599#if ENABLE_FETCHMAIL
Denis Vlasenko8195d202008-02-27 09:39:04 +0000600/***************************************************
601 * FETCHMAIL
602 ***************************************************/
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000603 else {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000604 char *buf;
605 unsigned nmsg;
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000606 char *hostname;
607 pid_t pid;
Denis Vlasenko8195d202008-02-27 09:39:04 +0000608
609 // cache fetch command:
610 // TOP will return only the headers
611 // RETR will dump the whole message
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000612 const char *retr = (opts & OPTF_t) ? "TOP %u 0" : "RETR %u";
613
614 // goto maildir
615 xchdir(*argv++);
616
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000617 // cache postprocess program
618 *fargs = *argv;
Denis Vlasenkoc6938402008-03-24 02:18:03 +0000619
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000620 // authenticate
Denis Vlasenkoc94d3562008-06-30 13:30:21 +0000621
Denis Vlasenko6ea75e22008-06-28 21:46:41 +0000622 // password is mandatory
623 if (!opt_pass) {
624 bb_error_msg_and_die("no password");
625 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000626
627 // get server greeting
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000628 pop3_checkr(NULL, NULL, &buf);
Denis Vlasenko8195d202008-02-27 09:39:04 +0000629
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000630 // server supports APOP?
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000631 if ('<' == *buf) {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000632 md5_ctx_t md5;
Denis Vlasenko8195d202008-02-27 09:39:04 +0000633 // yes! compose <stamp><password>
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000634 char *s = strchr(buf, '>');
635 if (s)
636 strcpy(s+1, opt_pass);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000637 s = buf;
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000638 // get md5 sum of <stamp><password>
639 md5_begin(&md5);
640 md5_hash(s, strlen(s), &md5);
641 md5_end(s, &md5);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000642 // NOTE: md5 struct contains enough space
643 // so we reuse md5 space instead of xzalloc(16*2+1)
644#define md5_hex ((uint8_t *)&md5)
645// uint8_t *md5_hex = (uint8_t *)&md5;
Denis Vlasenko023dc672008-05-09 18:07:15 +0000646 *bin2hex((char *)md5_hex, s, 16) = '\0';
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000647 // APOP
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000648 s = xasprintf("%s %s", opt_user, md5_hex);
649#undef md5_hex
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000650 pop3_check("APOP %s", s);
651 if (ENABLE_FEATURE_CLEAN_UP) {
652 free(s);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000653 free(buf-4); // buf is "+OK " away from malloc'ed string
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000654 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000655 // server ignores APOP -> use simple text authentication
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000656 } else {
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000657 // USER
658 pop3_check("USER %s", opt_user);
659 // PASS
660 pop3_check("PASS %s", opt_pass);
661 }
662
Denis Vlasenko8195d202008-02-27 09:39:04 +0000663 // get mailbox statistics
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000664 pop3_checkr("STAT", NULL, &buf);
665
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000666 // prepare message filename suffix
Denis Vlasenko8195d202008-02-27 09:39:04 +0000667 hostname = safe_gethostname();
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000668 pid = getpid();
669
Denis Vlasenko8195d202008-02-27 09:39:04 +0000670 // get messages counter
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000671 // NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
672 // we only need nmsg and atoi is just exactly what we need
673 // if atoi fails to convert buf into number it returns 0
Denis Vlasenko42cc3042008-03-24 02:05:58 +0000674 // in this case the following loop simply will not be executed
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000675 nmsg = atoi(buf);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000676 if (ENABLE_FEATURE_CLEAN_UP)
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000677 free(buf-4); // buf is "+OK " away from malloc'ed string
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000678
Bernhard Reutner-Fischera985d302008-02-11 11:44:38 +0000679 // loop through messages
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000680 for (; nmsg; nmsg--) {
Denis Vlasenko8195d202008-02-27 09:39:04 +0000681
682 // generate unique filename
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000683 char *filename = xasprintf("tmp/%llu.%u.%s",
684 monotonic_us(), (unsigned)pid, hostname);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000685 char *target;
Denis Vlasenko8195d202008-02-27 09:39:04 +0000686 int rc;
687
688 // retrieve message in ./tmp/
Denis Vlasenko85c24712008-03-17 09:04:04 +0000689 pop3_check(retr, (const char *)(ptrdiff_t)nmsg);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000690 pop3_message(filename);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000691 // delete message from server
692 if (opts & OPTF_z)
Denis Vlasenko85c24712008-03-17 09:04:04 +0000693 pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000694
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000695 // run postprocessing program
696 if (*fargs) {
697 fargs[1] = filename;
698 rc = wait4pid(spawn((char **)fargs));
699 if (99 == rc)
700 break;
701 if (1 == rc)
702 goto skip;
703 }
Denis Vlasenko8195d202008-02-27 09:39:04 +0000704
705 // atomically move message to ./new/
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000706 target = xstrdup(filename);
707 strncpy(target, "new", 3);
708 // ... or just stop receiving on error
709 if (rename_or_warn(filename, target))
710 break;
711 free(target);
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000712 skip:
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000713 free(filename);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000714 }
Denis Vlasenko17db1a92008-02-26 21:13:17 +0000715
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000716 // Bye
717 pop3_check("QUIT", NULL);
Denis Vlasenko6d52c1e2008-02-08 18:24:54 +0000718 }
Denis Vlasenko3dee8e22008-06-27 21:24:08 +0000719#endif // ENABLE_FETCHMAIL
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000720
Denis Vlasenkobed22a02008-09-27 14:01:22 +0000721 return EXIT_SUCCESS;
Denis Vlasenkoddd42cb2008-01-29 00:59:15 +0000722}