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