getopt32: add new syntax of 'o:+' and 'o:*' for -o NUM and -o LIST
[oweals/busybox.git] / mailutils / sendmail.c
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 source tree.
8  */
9
10 //kbuild:lib-$(CONFIG_SENDMAIL) += sendmail.o mail.o
11
12 //usage:#define sendmail_trivial_usage
13 //usage:       "[OPTIONS] [RECIPIENT_EMAIL]..."
14 //usage:#define sendmail_full_usage "\n\n"
15 //usage:       "Read email from stdin and send it\n"
16 //usage:     "\nStandard options:"
17 //usage:     "\n        -t              Read additional recipients from message body"
18 //usage:     "\n        -f SENDER       For use in MAIL FROM:<sender>. Can be empty string"
19 //usage:     "\n                        Default: -auUSER, or username of current UID"
20 //usage:     "\n        -o OPTIONS      Various options. -oi implied, others are ignored"
21 //usage:     "\n        -i              -oi synonym. implied and ignored"
22 //usage:     "\n"
23 //usage:     "\nBusybox specific options:"
24 //usage:     "\n        -v              Verbose"
25 //usage:     "\n        -w SECS         Network timeout"
26 //usage:     "\n        -H 'PROG ARGS'  Run connection helper"
27 //usage:     "\n                        Examples:"
28 //usage:     "\n                        -H 'exec openssl s_client -quiet -tls1 -starttls smtp"
29 //usage:     "\n                                -connect smtp.gmail.com:25' <email.txt"
30 //usage:     "\n                                [4<username_and_passwd.txt | -auUSER -apPASS]"
31 //usage:     "\n                        -H 'exec openssl s_client -quiet -tls1"
32 //usage:     "\n                                -connect smtp.gmail.com:465' <email.txt"
33 //usage:     "\n                                [4<username_and_passwd.txt | -auUSER -apPASS]"
34 //usage:     "\n        -S HOST[:PORT]  Server"
35 //usage:     "\n        -auUSER         Username for AUTH LOGIN"
36 //usage:     "\n        -apPASS         Password for AUTH LOGIN"
37 ////usage:     "\n      -amMETHOD       Authentication method. Ignored. LOGIN is implied"
38 //usage:     "\n"
39 //usage:     "\nOther options are silently ignored; -oi -t is implied"
40 //usage:        IF_MAKEMIME(
41 //usage:     "\nUse makemime to create emails with attachments"
42 //usage:        )
43
44 /* Currently we don't sanitize or escape user-supplied SENDER and RECIPIENT_EMAILs.
45  * We may need to do so. For one, '.' in usernames seems to require escaping!
46  *
47  * From http://cr.yp.to/smtp/address.html:
48  *
49  * SMTP offers three ways to encode a character inside an address:
50  *
51  * "safe": the character, if it is not <>()[].,;:@, backslash,
52  *  double-quote, space, or an ASCII control character;
53  * "quoted": the character, if it is not \012, \015, backslash,
54  *   or double-quote; or
55  * "slashed": backslash followed by the character.
56  *
57  * An encoded box part is either (1) a sequence of one or more slashed
58  * or safe characters or (2) a double quote, a sequence of zero or more
59  * slashed or quoted characters, and a double quote. It represents
60  * the concatenation of the characters encoded inside it.
61  *
62  * For example, the encoded box parts
63  *      angels
64  *      \a\n\g\e\l\s
65  *      "\a\n\g\e\l\s"
66  *      "angels"
67  *      "ang\els"
68  * all represent the 6-byte string "angels", and the encoded box parts
69  *      a\,comma
70  *      \a\,\c\o\m\m\a
71  *      "a,comma"
72  * all represent the 7-byte string "a,comma".
73  *
74  * An encoded address contains
75  *      the byte <;
76  *      optionally, a route followed by a colon;
77  *      an encoded box part, the byte @, and a domain; and
78  *      the byte >.
79  *
80  * It represents an Internet mail address, given by concatenating
81  * the string represented by the encoded box part, the byte @,
82  * and the domain. For example, the encoded addresses
83  *     <God@heaven.af.mil>
84  *     <\God@heaven.af.mil>
85  *     <"God"@heaven.af.mil>
86  *     <@gateway.af.mil,@uucp.local:"\G\o\d"@heaven.af.mil>
87  * all represent the Internet mail address "God@heaven.af.mil".
88  */
89
90 #include "libbb.h"
91 #include "mail.h"
92
93 // limit maximum allowed number of headers to prevent overflows.
94 // set to 0 to not limit
95 #define MAX_HEADERS 256
96
97 static void send_r_n(const char *s)
98 {
99         if (verbose)
100                 bb_error_msg("send:'%s'", s);
101         printf("%s\r\n", s);
102 }
103
104 static int smtp_checkp(const char *fmt, const char *param, int code)
105 {
106         char *answer;
107         char *msg = send_mail_command(fmt, param);
108         // read stdin
109         // if the string has a form NNN- -- read next string. E.g. EHLO response
110         // parse first bytes to a number
111         // if code = -1 then just return this number
112         // if code != -1 then checks whether the number equals the code
113         // if not equal -> die saying msg
114         while ((answer = xmalloc_fgetline(stdin)) != NULL) {
115                 if (verbose)
116                         bb_error_msg("recv:'%.*s'", (int)(strchrnul(answer, '\r') - answer), answer);
117                 if (strlen(answer) <= 3 || '-' != answer[3])
118                         break;
119                 free(answer);
120         }
121         if (answer) {
122                 int n = atoi(answer);
123                 if (timeout)
124                         alarm(0);
125                 free(answer);
126                 if (-1 == code || n == code) {
127                         free(msg);
128                         return n;
129                 }
130         }
131         bb_error_msg_and_die("%s failed", msg);
132 }
133
134 static int smtp_check(const char *fmt, int code)
135 {
136         return smtp_checkp(fmt, NULL, code);
137 }
138
139 // strip argument of bad chars
140 static char *sane_address(char *str)
141 {
142         char *s;
143
144         trim(str);
145         s = str;
146         while (*s) {
147                 if (!isalnum(*s) && !strchr("_-.@", *s)) {
148                         bb_error_msg("bad address '%s'", str);
149                         /* returning "": */
150                         str[0] = '\0';
151                         return str;
152                 }
153                 s++;
154         }
155         return str;
156 }
157
158 // check for an address inside angle brackets, if not found fall back to normal
159 static char *angle_address(char *str)
160 {
161         char *s, *e;
162
163         trim(str);
164         e = last_char_is(str, '>');
165         if (e) {
166                 s = strrchr(str, '<');
167                 if (s) {
168                         *e = '\0';
169                         str = s + 1;
170                 }
171         }
172         return sane_address(str);
173 }
174
175 static void rcptto(const char *s)
176 {
177         if (!*s)
178                 return;
179         // N.B. we don't die if recipient is rejected, for the other recipients may be accepted
180         if (250 != smtp_checkp("RCPT TO:<%s>", s, -1))
181                 bb_error_msg("Bad recipient: <%s>", s);
182 }
183
184 // send to a list of comma separated addresses
185 static void rcptto_list(const char *list)
186 {
187         char *str = xstrdup(list);
188         char *s = str;
189         char prev = 0;
190         int in_quote = 0;
191
192         while (*s) {
193                 char ch = *s++;
194
195                 if (ch == '"' && prev != '\\') {
196                         in_quote = !in_quote;
197                 } else if (!in_quote && ch == ',') {
198                         s[-1] = '\0';
199                         rcptto(angle_address(str));
200                         str = s;
201                 }
202                 prev = ch;
203         }
204         if (prev != ',')
205                 rcptto(angle_address(str));
206         free(str);
207 }
208
209 int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
210 int sendmail_main(int argc UNUSED_PARAM, char **argv)
211 {
212         char *opt_connect = opt_connect;
213         char *opt_from = NULL;
214         char *s;
215         llist_t *list = NULL;
216         char *host = sane_address(safe_gethostname());
217         unsigned nheaders = 0;
218         int code;
219         enum {
220                 HDR_OTHER = 0,
221                 HDR_TOCC,
222                 HDR_BCC,
223         } last_hdr = 0;
224         int check_hdr;
225         int has_to = 0;
226
227         enum {
228         //--- standard options
229                 OPT_t = 1 << 0,         // read message for recipients, append them to those on cmdline
230                 OPT_f = 1 << 1,         // sender address
231                 OPT_o = 1 << 2,         // various options. -oi IMPLIED! others are IGNORED!
232                 OPT_i = 1 << 3,         // IMPLIED!
233         //--- BB specific options
234                 OPT_w = 1 << 4,         // network timeout
235                 OPT_H = 1 << 5,         // use external connection helper
236                 OPT_S = 1 << 6,         // specify connection string
237                 OPT_a = 1 << 7,         // authentication tokens
238                 OPT_v = 1 << 8,         // verbosity
239         };
240
241         // init global variables
242         INIT_G();
243
244         // save initial stdin since body is piped!
245         xdup2(STDIN_FILENO, 3);
246         G.fp0 = xfdopen_for_read(3);
247
248         // parse options
249         // -v is a counter, -H and -S are mutually exclusive, -a is a list
250         opt_complementary = "vv:H--S:S--H";
251         // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect
252         // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility,
253         // it is still under development.
254         opts = getopt32(argv, "tf:o:iw:+H:S:a:*:v", &opt_from, NULL,
255                         &timeout, &opt_connect, &opt_connect, &list, &verbose);
256         //argc -= optind;
257         argv += optind;
258
259         // process -a[upm]<token> options
260         if ((opts & OPT_a) && !list)
261                 bb_show_usage();
262         while (list) {
263                 char *a = (char *) llist_pop(&list);
264                 if ('u' == a[0])
265                         G.user = xstrdup(a+1);
266                 if ('p' == a[0])
267                         G.pass = xstrdup(a+1);
268                 // N.B. we support only AUTH LOGIN so far
269                 //if ('m' == a[0])
270                 //      G.method = xstrdup(a+1);
271         }
272         // N.B. list == NULL here
273         //bb_error_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv);
274
275         // connect to server
276
277         // connection helper ordered? ->
278         if (opts & OPT_H) {
279                 const char *args[] = { "sh", "-c", opt_connect, NULL };
280                 // plug it in
281                 launch_helper(args);
282                 // Now:
283                 // our stdout will go to helper's stdin,
284                 // helper's stdout will be available on our stdin.
285
286                 // Wait for initial server message.
287                 // If helper (such as openssl) invokes STARTTLS, the initial 220
288                 // is swallowed by helper (and not repeated after TLS is initiated).
289                 // We will send NOOP cmd to server and check the response.
290                 // We should get 220+250 on plain connection, 250 on STARTTLSed session.
291                 //
292                 // The problem here is some servers delay initial 220 message,
293                 // and consider client to be a spammer if it starts sending cmds
294                 // before 220 reached it. The code below is unsafe in this regard:
295                 // in non-STARTTLSed case, we potentially send NOOP before 220
296                 // is sent by server.
297                 // Ideas? (--delay SECS opt? --assume-starttls-helper opt?)
298                 code = smtp_check("NOOP", -1);
299                 if (code == 220)
300                         // we got 220 - this is not STARTTLSed connection,
301                         // eat 250 response to our NOOP
302                         smtp_check(NULL, 250);
303                 else
304                 if (code != 250)
305                         bb_error_msg_and_die("SMTP init failed");
306         } else {
307                 // vanilla connection
308                 int fd;
309                 // host[:port] not explicitly specified? -> use $SMTPHOST
310                 // no $SMTPHOST? -> use localhost
311                 if (!(opts & OPT_S)) {
312                         opt_connect = getenv("SMTPHOST");
313                         if (!opt_connect)
314                                 opt_connect = (char *)"127.0.0.1";
315                 }
316                 // do connect
317                 fd = create_and_connect_stream_or_die(opt_connect, 25);
318                 // and make ourselves a simple IO filter
319                 xmove_fd(fd, STDIN_FILENO);
320                 xdup2(STDIN_FILENO, STDOUT_FILENO);
321
322                 // Wait for initial server 220 message
323                 smtp_check(NULL, 220);
324         }
325
326         // we should start with modern EHLO
327         if (250 != smtp_checkp("EHLO %s", host, -1))
328                 smtp_checkp("HELO %s", host, 250);
329
330         // perform authentication
331         if (opts & OPT_a) {
332                 smtp_check("AUTH LOGIN", 334);
333                 // we must read credentials unless they are given via -a[up] options
334                 if (!G.user || !G.pass)
335                         get_cred_or_die(4);
336                 encode_base64(NULL, G.user, NULL);
337                 smtp_check("", 334);
338                 encode_base64(NULL, G.pass, NULL);
339                 smtp_check("", 235);
340         }
341
342         // set sender
343         // N.B. we have here a very loosely defined algorythm
344         // since sendmail historically offers no means to specify secrets on cmdline.
345         // 1) server can require no authentication ->
346         //      we must just provide a (possibly fake) reply address.
347         // 2) server can require AUTH ->
348         //      we must provide valid username and password along with a (possibly fake) reply address.
349         //      For the sake of security username and password are to be read either from console or from a secured file.
350         //      Since reading from console may defeat usability, the solution is either to read from a predefined
351         //      file descriptor (e.g. 4), or again from a secured file.
352
353         // got no sender address? use auth name, then UID username as a last resort
354         if (!opt_from) {
355                 opt_from = xasprintf("%s@%s",
356                                      G.user ? G.user : xuid2uname(getuid()),
357                                      xgethostbyname(host)->h_name);
358         }
359         free(host);
360
361         smtp_checkp("MAIL FROM:<%s>", opt_from, 250);
362
363         // process message
364
365         // read recipients from message and add them to those given on cmdline.
366         // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line
367         // and then use the rest of stdin as message body
368         code = 0; // set "analyze headers" mode
369         while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
370  dump:
371                 // put message lines doubling leading dots
372                 if (code) {
373                         // escape leading dots
374                         // N.B. this feature is implied even if no -i (-oi) switch given
375                         // N.B. we need to escape the leading dot regardless of
376                         // whether it is single or not character on the line
377                         if ('.' == s[0] /*&& '\0' == s[1] */)
378                                 bb_putchar('.');
379                         // dump read line
380                         send_r_n(s);
381                         free(s);
382                         continue;
383                 }
384
385                 // analyze headers
386                 // To: or Cc: headers add recipients
387                 check_hdr = (0 == strncasecmp("To:", s, 3));
388                 has_to |= check_hdr;
389                 if (opts & OPT_t) {
390                         if (check_hdr || 0 == strncasecmp("Bcc:" + 1, s, 3)) {
391                                 rcptto_list(s+3);
392                                 last_hdr = HDR_TOCC;
393                                 goto addheader;
394                         }
395                         // Bcc: header adds blind copy (hidden) recipient
396                         if (0 == strncasecmp("Bcc:", s, 4)) {
397                                 rcptto_list(s+4);
398                                 free(s);
399                                 last_hdr = HDR_BCC;
400                                 continue; // N.B. Bcc: vanishes from headers!
401                         }
402                 }
403                 check_hdr = (list && isspace(s[0]));
404                 if (strchr(s, ':') || check_hdr) {
405                         // other headers go verbatim
406                         // N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines.
407                         // Continuation is denoted by prefixing additional lines with whitespace(s).
408                         // Thanks (stefan.seyfried at googlemail.com) for pointing this out.
409                         if (check_hdr && last_hdr != HDR_OTHER) {
410                                 rcptto_list(s+1);
411                                 if (last_hdr == HDR_BCC)
412                                         continue;
413                                         // N.B. Bcc: vanishes from headers!
414                         } else {
415                                 last_hdr = HDR_OTHER;
416                         }
417  addheader:
418                         // N.B. we allow MAX_HEADERS generic headers at most to prevent attacks
419                         if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
420                                 goto bail;
421                         llist_add_to_end(&list, s);
422                 } else {
423                         // a line without ":" (an empty line too, by definition) doesn't look like a valid header
424                         // so stop "analyze headers" mode
425  reenter:
426                         // put recipients specified on cmdline
427                         check_hdr = 1;
428                         while (*argv) {
429                                 char *t = sane_address(*argv);
430                                 rcptto(t);
431                                 //if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
432                                 //      goto bail;
433                                 if (!has_to) {
434                                         const char *hdr;
435
436                                         if (check_hdr && argv[1])
437                                                 hdr = "To: %s,";
438                                         else if (check_hdr)
439                                                 hdr = "To: %s";
440                                         else if (argv[1])
441                                                 hdr = "To: %s," + 3;
442                                         else
443                                                 hdr = "To: %s" + 3;
444                                         llist_add_to_end(&list,
445                                                         xasprintf(hdr, t));
446                                         check_hdr = 0;
447                                 }
448                                 argv++;
449                         }
450                         // enter "put message" mode
451                         // N.B. DATA fails iff no recipients were accepted (or even provided)
452                         // in this case just bail out gracefully
453                         if (354 != smtp_check("DATA", -1))
454                                 goto bail;
455                         // dump the headers
456                         while (list) {
457                                 send_r_n((char *) llist_pop(&list));
458                         }
459                         // stop analyzing headers
460                         code++;
461                         // N.B. !s means: we read nothing, and nothing to be read in the future.
462                         // just dump empty line and break the loop
463                         if (!s) {
464                                 send_r_n("");
465                                 break;
466                         }
467                         // go dump message body
468                         // N.B. "s" already contains the first non-header line, so pretend we read it from input
469                         goto dump;
470                 }
471         }
472         // odd case: we didn't stop "analyze headers" mode -> message body is empty. Reenter the loop
473         // N.B. after reenter code will be > 0
474         if (!code)
475                 goto reenter;
476
477         // finalize the message
478         smtp_check(".", 250);
479  bail:
480         // ... and say goodbye
481         smtp_check("QUIT", 221);
482         // cleanup
483         if (ENABLE_FEATURE_CLEAN_UP)
484                 fclose(G.fp0);
485
486         return EXIT_SUCCESS;
487 }