dma: log mail queueing more thoroughly
[dragonfly.git] / libexec / dma / net.c
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthias Schmidt <matthias@dragonflybsd.org>, University of Marburg,
6  * Germany.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  * 3. Neither the name of The DragonFly Project nor the names of its
19  *    contributors may be used to endorse or promote products derived
20  *    from this software without specific, prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  * $DragonFly: src/libexec/dma/net.c,v 1.9 2008/09/30 17:47:21 swildner Exp $
36  */
37
38 #include <sys/param.h>
39 #include <sys/queue.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45
46 #include <openssl/ssl.h>
47
48 #include <err.h>
49 #include <errno.h>
50 #include <netdb.h>
51 #include <setjmp.h>
52 #include <signal.h>
53 #include <syslog.h>
54 #include <unistd.h>
55
56 #include "dma.h"
57
58 extern struct config *config;
59 extern struct authusers authusers;
60 static jmp_buf timeout_alarm;
61 char neterr[BUF_SIZE];
62
63 static void
64 sig_alarm(int signo __unused)
65 {
66         longjmp(timeout_alarm, 1);
67 }
68
69 ssize_t
70 send_remote_command(int fd, const char* fmt, ...)
71 {
72         va_list va;
73         char cmd[4096];
74         size_t len, pos;
75         int s;
76         ssize_t n;
77
78         va_start(va, fmt);
79         s = vsnprintf(cmd, sizeof(cmd) - 2, fmt, va);
80         va_end(va);
81         if (s == sizeof(cmd) - 2 || s < 0)
82                 errx(1, "Internal error: oversized command string");
83         /* We *know* there are at least two more bytes available */
84         strcat(cmd, "\r\n");
85         len = strlen(cmd);
86
87         if (((config->features & SECURETRANS) != 0) &&
88             ((config->features & NOSSL) == 0)) {
89                 while ((s = SSL_write(config->ssl, (const char*)cmd, len)) <= 0) {
90                         s = SSL_get_error(config->ssl, s);
91                         if (s != SSL_ERROR_WANT_READ &&
92                             s != SSL_ERROR_WANT_WRITE)
93                                 return (-1);
94                 }
95         }
96         else {
97                 pos = 0;
98                 while (pos < len) {
99                         n = write(fd, cmd + pos, len - pos);
100                         if (n < 0)
101                                 return (-1);
102                         pos += n;
103                 }
104         }
105
106         return (len);
107 }
108
109 int
110 read_remote(int fd, int extbufsize, char *extbuf)
111 {
112         ssize_t rlen = 0;
113         size_t pos, len;
114         char buff[BUF_SIZE];
115         int done = 0, status = 0, extbufpos = 0;
116
117         if (signal(SIGALRM, sig_alarm) == SIG_ERR) {
118                 snprintf(neterr, sizeof(neterr), "SIGALRM error: %s",
119                     strerror(errno));
120                 return (1);
121         }
122         if (setjmp(timeout_alarm) != 0) {
123                 snprintf(neterr, sizeof(neterr), "Timeout reached");
124                 return (1);
125         }
126         alarm(CON_TIMEOUT);
127
128         /*
129          * Remote reading code from femail.c written by Henning Brauer of
130          * OpenBSD and released under a BSD style license.
131          */
132         for (len = pos = 0; !done; ) {
133                 rlen = 0;
134                 if (pos == 0 ||
135                     (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) {
136                         memmove(buff, buff + pos, len - pos);
137                         len -= pos;
138                         pos = 0;
139                         if (((config->features & SECURETRANS) != 0) &&
140                             (config->features & NOSSL) == 0) {
141                                 if ((rlen = SSL_read(config->ssl, buff + len,
142                                     sizeof(buff) - len)) == -1)
143                                         err(1, "read");
144                         } else {
145                                 if ((rlen = read(fd, buff + len,
146                                     sizeof(buff) - len)) == -1)
147                                         err(1, "read");
148                         }
149                         len += rlen;
150                 }
151                 /*
152                  * If there is an external buffer with a size bigger than zero
153                  * and as long as there is space in the external buffer and
154                  * there are new characters read from the mailserver
155                  * copy them to the external buffer
156                  */
157                 if (extbufpos <= (extbufsize - 1) && rlen && extbufsize > 0 
158                     && extbuf != NULL) {
159                         /* do not write over the bounds of the buffer */
160                         if(extbufpos + rlen > (extbufsize - 1)) {
161                                 rlen = extbufsize - extbufpos;
162                         }
163                         memcpy(extbuf + extbufpos, buff + len - rlen, rlen);
164                         extbufpos += rlen;
165                 }
166                 for (; pos < len && buff[pos] >= '0' && buff[pos] <= '9'; pos++)
167                         ; /* Do nothing */
168
169                 if (pos == len)
170                         return (0);
171
172                 if (buff[pos] == ' ')
173                         done = 1;
174                 else if (buff[pos] != '-')
175                         syslog(LOG_ERR, "invalid syntax in reply from server");
176
177                 /* skip up to \n */
178                 for (; pos < len && buff[pos - 1] != '\n'; pos++)
179                         ; /* Do nothing */
180
181         }
182         alarm(0);
183
184         buff[len] = '\0';
185         while (len > 0 && (buff[len - 1] == '\r' || buff[len - 1] == '\n'))
186                 buff[--len] = '\0';
187         snprintf(neterr, sizeof(neterr), "%s", buff);
188         status = atoi(buff);
189         return (status/100);
190 }
191
192 /*
193  * Handle SMTP authentication
194  */
195 static int
196 smtp_login(struct qitem *it, int fd, char *login, char* password)
197 {
198         char *temp;
199         int len, res = 0;
200
201         res = smtp_auth_md5(it, fd, login, password);
202         if (res == 0) {
203                 return (0);
204         } else if (res == -2) {
205         /*
206          * If the return code is -2, then then the login attempt failed, 
207          * do not try other login mechanisms
208          */
209                 return (-1);
210         }
211
212         if ((config->features & INSECURE) != 0) {
213                 /* Send AUTH command according to RFC 2554 */
214                 send_remote_command(fd, "AUTH LOGIN");
215                 if (read_remote(fd, 0, NULL) != 3) {
216                         syslog(LOG_NOTICE, "%s: remote delivery deferred:"
217                                         " AUTH login not available: %s",
218                                         it->queueid, neterr);
219                         return (1);
220                 }
221
222                 len = base64_encode(login, strlen(login), &temp);
223                 if (len <= 0)
224                         return (-1);
225
226                 send_remote_command(fd, "%s", temp);
227                 if (read_remote(fd, 0, NULL) != 3) {
228                         syslog(LOG_NOTICE, "%s: remote delivery deferred:"
229                                         " AUTH login failed: %s", it->queueid,
230                                         neterr);
231                         return (-1);
232                 }
233
234                 len = base64_encode(password, strlen(password), &temp);
235                 if (len <= 0)
236                         return (-1);
237
238                 send_remote_command(fd, "%s", temp);
239                 res = read_remote(fd, 0, NULL);
240                 if (res == 5) {
241                         syslog(LOG_NOTICE, "%s: remote delivery failed:"
242                                         " Authentication failed: %s",
243                                         it->queueid, neterr);
244                         return (-1);
245                 } else if (res != 2) {
246                         syslog(LOG_NOTICE, "%s: remote delivery failed:"
247                                         " AUTH password failed: %s",
248                                         it->queueid, neterr);
249                         return (-1);
250                 }
251         } else {
252                 syslog(LOG_WARNING, "%s: non-encrypted SMTP login is disabled in config, so skipping it. ",
253                                 it->queueid);
254                 return (1);
255         }
256
257         return (0);
258 }
259
260 static int
261 open_connection(struct qitem *it, const char *host)
262 {
263         struct addrinfo hints, *res, *res0;
264         char servname[128];
265         const char *errmsg = NULL;
266         int fd, error = 0, port;
267
268         if (config->port != 0)
269                 port = config->port;
270         else
271                 port = SMTP_PORT;
272
273         /* FIXME get MX record of host */
274         /* Shamelessly taken from getaddrinfo(3) */
275         memset(&hints, 0, sizeof(hints));
276         hints.ai_family = PF_UNSPEC;
277         hints.ai_socktype = SOCK_STREAM;
278         hints.ai_protocol = IPPROTO_TCP;
279
280         snprintf(servname, sizeof(servname), "%d", port);
281         error = getaddrinfo(host, servname, &hints, &res0);
282         if (error) {
283                 syslog(LOG_NOTICE, "%s: remote delivery deferred: "
284                        "%s: %m", it->queueid, gai_strerror(error));
285                 return (-1);
286         }
287         fd = -1;
288         for (res = res0; res; res = res->ai_next) {
289                 fd=socket(res->ai_family, res->ai_socktype, res->ai_protocol);
290                 if (fd < 0) {
291                         errmsg = "socket failed";
292                         continue;
293                 }
294                 if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
295                         errmsg = "connect failed";
296                         close(fd);
297                         fd = -1;
298                         continue;
299                 }
300                 break;
301         }
302         if (fd < 0) {
303                 syslog(LOG_NOTICE, "%s: remote delivery deferred: %s (%s:%s)",
304                         it->queueid, errmsg, host, servname);
305                 freeaddrinfo(res0);
306                 return (-1);
307         }
308         freeaddrinfo(res0);
309         return (fd);
310 }
311
312 int
313 deliver_remote(struct qitem *it, const char **errmsg)
314 {
315         struct authuser *a;
316         char *host, line[1000];
317         int fd, error = 0, do_auth = 0, res = 0;
318         size_t linelen;
319         /* asprintf can't take const */
320         void *errmsgc = __DECONST(char **, errmsg);
321
322         host = strrchr(it->addr, '@');
323         /* Should not happen */
324         if (host == NULL) {
325                 asprintf(errmsgc, "Internal error: badly formed address %s",
326                     it->addr);
327                 return(-1);
328         } else {
329                 /* Step over the @ */
330                 host++;
331         }
332
333         /* Smarthost support? */
334         if (config->smarthost != NULL && strlen(config->smarthost) > 0) {
335                 syslog(LOG_INFO, "%s: using smarthost (%s:%i)",
336                        it->queueid, config->smarthost, config->port);
337                 host = config->smarthost;
338         }
339
340         fd = open_connection(it, host);
341         if (fd < 0)
342                 return (1);
343
344         /* Check first reply from remote host */
345         config->features |= NOSSL;
346         res = read_remote(fd, 0, NULL);
347         if (res != 2) {
348                 syslog(LOG_WARNING, "%s: Invalid initial response: %i",
349                         it->queueid, res);
350                 return(1);
351         }
352         config->features &= ~NOSSL;
353
354         if ((config->features & SECURETRANS) != 0) {
355                 error = smtp_init_crypto(it, fd, config->features);
356                 if (error >= 0)
357                         syslog(LOG_INFO, "%s: SSL initialization successful",
358                                 it->queueid);
359                 else
360                         goto out;
361         }
362
363         send_remote_command(fd, "EHLO %s", hostname());
364         if (read_remote(fd, 0, NULL) != 2) {
365                 syslog(LOG_WARNING, "%s: remote delivery deferred: "
366                        " EHLO failed: %s", it->queueid, neterr);
367                 asprintf(errmsgc, "%s did not like our EHLO:\n%s",
368                     host, neterr);
369                 return (-1);
370         }
371
372         /*
373          * Use SMTP authentication if the user defined an entry for the remote
374          * or smarthost
375          */
376         SLIST_FOREACH(a, &authusers, next) {
377                 if (strcmp(a->host, host) == 0) {
378                         do_auth = 1;
379                         break;
380                 }
381         }
382
383         if (do_auth == 1) {
384                 /*
385                  * Check if the user wants plain text login without using
386                  * encryption.
387                  */
388                 syslog(LOG_INFO, "%s: Use SMTP authentication",
389                                 it->queueid);
390                 error = smtp_login(it, fd, a->login, a->password);
391                 if (error < 0) {
392                         syslog(LOG_ERR, "%s: remote delivery failed:"
393                                         " SMTP login failed: %m", it->queueid);
394                         asprintf(errmsgc, "SMTP login to %s failed", host);
395                         return (-1);
396                 }
397                 /* SMTP login is not available, so try without */
398                 else if (error > 0)
399                         syslog(LOG_WARNING, "%s: SMTP login not available."
400                                         " Try without", it->queueid);
401         }
402
403 #define READ_REMOTE_CHECK(c, exp)       \
404         res = read_remote(fd, 0, NULL); \
405         if (res == 5) { \
406                 syslog(LOG_ERR, "%s: remote delivery failed: " \
407                        c " failed: %s", it->queueid, neterr); \
408                 asprintf(errmsgc, "%s did not like our " c ":\n%s", \
409                     host, neterr); \
410                 return (-1); \
411         } else if (res != exp) { \
412                 syslog(LOG_NOTICE, "%s: remote delivery deferred: " \
413                        c " failed: %s", it->queueid, neterr); \
414                 return (1); \
415         }
416
417         send_remote_command(fd, "MAIL FROM:<%s>", it->sender);
418         READ_REMOTE_CHECK("MAIL FROM", 2);
419
420         send_remote_command(fd, "RCPT TO:<%s>", it->addr);
421         READ_REMOTE_CHECK("RCPT TO", 2);
422
423         send_remote_command(fd, "DATA");
424         READ_REMOTE_CHECK("DATA", 3);
425
426         if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) {
427                 syslog(LOG_ERR, "%s: remote delivery deferred: cannot seek: %s",
428                        it->queueid, neterr);
429                 return (1);
430         }
431
432         error = 0;
433         while (!feof(it->queuef)) {
434                 if (fgets(line, sizeof(line), it->queuef) == NULL)
435                         break;
436                 linelen = strlen(line);
437                 if (linelen == 0 || line[linelen - 1] != '\n') {
438                         syslog(LOG_CRIT, "%s: remote delivery failed:"
439                                 "corrupted queue file", it->queueid);
440                         *errmsg = "corrupted queue file";
441                         error = -1;
442                         goto out;
443                 }
444
445                 /* Remove trailing \n's and escape leading dots */
446                 trim_line(line);
447
448                 /*
449                  * If the first character is a dot, we escape it so the line
450                  * length increases
451                 */
452                 if (line[0] == '.')
453                         linelen++;
454
455                 if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) {
456                         syslog(LOG_NOTICE, "%s: remote delivery deferred: "
457                                 "write error", it->queueid);
458                         error = 1;
459                         goto out;
460                 }
461         }
462
463         send_remote_command(fd, ".");
464         READ_REMOTE_CHECK("final DATA", 2);
465
466         send_remote_command(fd, "QUIT");
467         if (read_remote(fd, 0, NULL) != 2)
468                 syslog(LOG_INFO, "%s: remote delivery succeeded but "
469                        "QUIT failed: %s", it->queueid, neterr);
470 out:
471
472         close(fd);
473         return (error);
474 }
475