sendmail: really svn add it
[oweals/busybox.git] / networking / 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 tarball for details.
8  */
9 #include "libbb.h"
10
11 /*
12    Extracted from BB uuencode.c
13  */
14 enum {
15         SRC_BUF_SIZE = 45,  /* This *MUST* be a multiple of 3 */
16         DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3),
17 };
18
19 static void uuencode(const char *fname)
20 {
21         int fd;
22         char src_buf[SRC_BUF_SIZE];
23         char dst_buf[1 + DST_BUF_SIZE + 1];
24
25         fd = xopen(fname, O_RDONLY);
26         fflush(stdout);
27         dst_buf[0] = '\n';
28         while (1) {
29                 size_t size = full_read(fd, src_buf, SRC_BUF_SIZE);
30                 if (!size)
31                         break;
32                 if ((ssize_t)size < 0)
33                         bb_perror_msg_and_die(bb_msg_read_error);
34                 /* Encode the buffer we just read in */
35                 bb_uuencode(dst_buf + 1, src_buf, size, bb_uuenc_tbl_base64);
36                 xwrite(STDOUT_FILENO, dst_buf, 1 + 4 * ((size + 2) / 3));
37         }
38         close(fd);
39 }
40
41 // "inline" version
42 // encodes content of given buffer instead of fd
43 // used to encode subject and authentication terms
44 static void uuencode_inline(const char *src_buf)
45 {
46         size_t len;
47         char dst_buf[DST_BUF_SIZE + 1];
48
49         len = strlen(src_buf);
50         fflush(stdout);
51         while (len > 0) {
52                 size_t chunk = (len <= SRC_BUF_SIZE) ? len : SRC_BUF_SIZE;
53                 bb_uuencode(dst_buf, src_buf, chunk, bb_uuenc_tbl_base64);
54                 xwrite(STDOUT_FILENO, dst_buf, 4 * ((chunk + 2) / 3));
55                 src_buf += chunk;
56                 len -= chunk;
57         }
58 }
59
60 #if ENABLE_FEATURE_SENDMAIL_NETWORK
61 // generic signal handler
62 static void signal_handler(int signo)
63 {
64         int err;
65
66         if (SIGALRM == signo)
67                 bb_error_msg_and_die("timed out");
68
69         // SIGCHLD. reap zombies
70         if (wait_any_nohang(&err) > 0)
71                 if (WIFEXITED(err) && WEXITSTATUS(err))
72                         bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err));
73 }
74
75 static pid_t helper_pid = -1;
76
77 // read stdin, parses first bytes to a number, i.e. server response
78 // if code = -1 then just return this number
79 // if code != -1 then checks whether the number equals the code
80 // if not equal -> die saying errmsg
81 static int check(int code, const char *errmsg)
82 {
83         char *answer;
84
85         // read a string and match it against the set of available answers
86         fflush(stdout);
87         answer = xmalloc_getline(stdin);
88         if (answer) {
89                 int n = atoi(answer);
90                 if (-1 == code || n == code) {
91                         free(answer);
92                         return n;
93                 }
94         }
95         // TODO!!!: is there more elegant way to terminate child on program failure?
96         if (helper_pid > 0)
97                 kill(helper_pid, SIGTERM);
98         if (!answer)
99                 answer = (char*)"EOF";
100         else
101                 *strchrnul(answer, '\r') = '\0';
102         bb_error_msg_and_die("error at %s: got '%s' instead", errmsg, answer);
103 }
104
105 static int puts_and_check(const char *msg, int code, const char *errmsg)
106 {
107         puts(msg);
108         return check(code, errmsg);
109 }
110 #endif
111
112 int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
113 int sendmail_main(int argc, char **argv)
114 {
115         llist_t *recipients = NULL;
116         llist_t *bodies = NULL;
117         llist_t *attachments = NULL;
118         const char *from;
119         const char *notify;
120         const char *subject;
121         const char *charset = "utf-8";
122 #if ENABLE_FEATURE_SENDMAIL_NETWORK
123         const char *wsecs = "10";
124         const char *server = "127.0.0.1";
125         const char *port = NULL;
126         const char *opt_user;
127         const char *opt_pass;
128         unsigned timeout;
129 #endif
130         char *boundary;
131         unsigned opts;
132         enum {
133                 OPT_f = 1 << 0,         // sender
134                 OPT_n = 1 << 2,         // notification
135                 OPT_s = 1 << 3,         // subject given
136                 OPT_d = 1 << 7,         // dry run - no networking
137                 OPT_w = 1 << 8,         // network timeout
138                 OPT_h = 1 << 9,         // server
139                 OPT_p = 1 << 10,        // port
140                 OPT_U = 1 << 11,        // user specified
141                 OPT_P = 1 << 12,        // password specified
142         };
143
144         // -f must be specified
145         // -t, -b, -a may be multiple
146         opt_complementary = "f:t::b::a::";
147         opts = getopt32(argv,
148                 "f:t:n::s:b:a:c:" USE_FEATURE_SENDMAIL_NETWORK("dw:h:p:U:P:"),
149                 &from, &recipients, &notify, &subject, &bodies, &attachments, &charset
150                 USE_FEATURE_SENDMAIL_NETWORK(, &wsecs, &server, &port, &opt_user, &opt_pass)
151         );
152         //argc -= optind;
153         argv += optind;
154
155 //printf("OPTS[%4x]\n", opts);
156
157         // TODO!!!: strip recipients and sender from <>
158
159         // establish connection
160 #if ENABLE_FEATURE_SENDMAIL_NETWORK
161         timeout = xatou(wsecs);
162         if (!(opts & OPT_d)) {
163                 // ask password if we need to and while we're still have terminal
164                 // TODO: get password directly from /dev/tty? or from a secret file?
165                 if ((opts & (OPT_U+OPT_P)) == OPT_U) {
166                         if (!isatty(STDIN_FILENO) || !(opt_pass = bb_askpass(0, "Password: "))) {
167                                 bb_error_msg_and_die("no password");
168                         }
169                 }
170 //printf("OPTS[%4x][%s][%s]\n", opts, opt_user, opt_pass);
171 //exit(0);
172                 // set chat timeout
173                 alarm(timeout);
174                 // connect to server
175                 if (argv[0]) {
176                         // if connection helper given
177                         // setup vanilla unidirectional pipes interchange
178                         int pipes[4];
179                         xpipe(pipes);
180                         xpipe(pipes+2);
181                         helper_pid = vfork();
182                         if (helper_pid < 0)
183                                 bb_perror_msg_and_die("vfork");
184                         xdup2(pipes[(helper_pid)?0:2], STDIN_FILENO);
185                         xdup2(pipes[(helper_pid)?3:1], STDOUT_FILENO);
186                         if (ENABLE_FEATURE_CLEAN_UP)
187                                 for (int i = 4; --i >= 0; )
188                                         if (pipes[i] > STDOUT_FILENO)
189                                                 close(pipes[i]);
190                         // replace child with connection helper
191                         if (!helper_pid) {
192                                 // child - try to execute connection helper
193                                 BB_EXECVP(argv[0], argv);
194                                 _exit(127);
195                         }
196                         // parent - check whether child is alive
197                         sig_catch(SIGCHLD, signal_handler);
198                         sig_catch(SIGALRM, signal_handler);
199                         signal_handler(SIGCHLD);
200                         // child seems OK -> parent goes on SMTP chat
201                 } else {
202                         // no connection helper provided -> make plain connect
203                         int fd = create_and_connect_stream_or_die(
204                                 server,
205                                 bb_lookup_port(port, "tcp", 25)
206                         );
207                         xmove_fd(fd, STDIN_FILENO);
208                         xdup2(STDIN_FILENO, STDOUT_FILENO);
209                         // wait for OK
210                         check(220, "INIT");
211                 }
212                 // mail user specified? try modern AUTHentication
213                 if (opt_user && (334 == puts_and_check("auth login", -1, "auth login"))) {
214                         uuencode_inline(opt_user);
215                         puts_and_check("", 334, "AUTH");
216                         uuencode_inline(opt_pass);
217                         puts_and_check("", 235, "AUTH");
218                 // no mail user specified or modern AUTHentication is not supported?
219                 } else {
220                         // fallback to simple HELO authentication
221                         // fetch domain name (defaults to local)
222                         const char *domain = strchr(from, '@');
223                         if (domain)
224                                 domain++;
225                         else
226                                 domain = "local";
227                         printf("helo %s\n", domain);
228                         check(250, "HELO");
229                 }
230
231                 // set addresses
232                 printf("mail from:<%s>\n", from);
233                 check(250, "MAIL FROM");
234                 for (llist_t *to = recipients; to; to = to->link) {
235                         printf("rcpt to:<%s>\n", to->data);
236                         check(250, "RCPT TO");
237                 }
238                 puts_and_check("data", 354, "DATA");
239                 // no timeout while sending message
240                 alarm(0);
241         }
242 #endif
243
244         // now put message
245         // put address headers
246         printf("From: %s\n", from);
247         for (llist_t *to = recipients; to; to = to->link) {
248                 printf("To: %s\n", to->data);
249         }
250         // put encoded subject
251         if (opts & OPT_s) {
252                 printf("Subject: =?%s?B?", charset);
253                 uuencode_inline(subject);
254                 puts("?=");
255         }
256         // put notification
257         if (opts & OPT_n) {
258                 const char *s = notify;
259                 if (!s[0])
260                         s = from; // notify sender by default
261                 printf("Disposition-Notification-To: %s\n", s);
262         }
263         // put common headers and body start
264         //srand(?);
265         boundary = xasprintf("%d-%d-%d", rand(), rand(), rand());
266         printf(
267                 "X-Mailer: busybox " BB_VER " sendmail\n"
268                 "X-Priority: 3\n"
269                 "Message-ID: <%s>\n"
270                 "Mime-Version: 1.0\n"
271                 "Content-Type: multipart/mixed; boundary=\"%s\"\n"
272                 "\n"
273                 "--%s\n"
274                 "Content-Type: text/plain; charset=%s\n"
275                 "%s\n%s"
276                 , boundary, boundary, boundary, charset
277                 , "Content-Disposition: inline"
278                 , "Content-Transfer-Encoding: base64\n"
279         );
280         // put body(ies)
281         for (llist_t *f = bodies; f; f = f->link) {
282                 uuencode(f->data);
283         }
284         // put attachment(s)
285         for (llist_t *f = attachments; f; f = f->link) {
286                 printf(
287                         "\n--%s\n"
288                         "Content-Type: application/octet-stream\n"
289                         "%s; filename=\"%s\"\n"
290                         "%s"
291                         , boundary
292                         , "Content-Disposition: inline"
293                         , bb_get_last_path_component_strip(f->data)
294                         , "Content-Transfer-Encoding: base64\n"
295                 );
296                 uuencode(f->data);
297         }
298         // put terminator
299         printf("\n--%s--\n\n", boundary);
300         if (ENABLE_FEATURE_CLEAN_UP)
301                 free(boundary);
302
303 #if ENABLE_FEATURE_SENDMAIL_NETWORK
304         // end message and say goodbye
305         if (!(opts & OPT_d)) {
306                 alarm(timeout);
307                 puts_and_check(".", 250, "BODY");
308                 puts_and_check("quit", 221, "QUIT");
309         }
310 #endif
311
312         return 0;
313 }