]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/dma/net.c
zfs: merge openzfs/zfs@ec64fdb93 (master) into main
[FreeBSD/FreeBSD.git] / contrib / 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 <strings.h>
57 #include <string.h>
58 #include <syslog.h>
59 #include <unistd.h>
60
61 #include "dma.h"
62
63 char neterr[ERRMSG_SIZE];
64
65 char *
66 ssl_errstr(void)
67 {
68         long oerr, nerr;
69
70         oerr = 0;
71         while ((nerr = ERR_get_error()) != 0)
72                 oerr = nerr;
73
74         return (ERR_error_string(oerr, NULL));
75 }
76
77 ssize_t
78 send_remote_command(int fd, const char* fmt, ...)
79 {
80         va_list va;
81         char cmd[4096];
82         size_t len, pos;
83         int s;
84         ssize_t n;
85
86         va_start(va, fmt);
87         s = vsnprintf(cmd, sizeof(cmd) - 2, fmt, va);
88         va_end(va);
89         if (s == sizeof(cmd) - 2 || s < 0) {
90                 strcpy(neterr, "Internal error: oversized command string");
91                 return (-1);
92         }
93
94         /* We *know* there are at least two more bytes available */
95         strcat(cmd, "\r\n");
96         len = strlen(cmd);
97
98         if (((config.features & SECURETRANSFER) != 0) &&
99             ((config.features & NOSSL) == 0)) {
100                 while ((s = SSL_write(config.ssl, (const char*)cmd, len)) <= 0) {
101                         s = SSL_get_error(config.ssl, s);
102                         if (s != SSL_ERROR_WANT_READ &&
103                             s != SSL_ERROR_WANT_WRITE) {
104                                 strlcpy(neterr, ssl_errstr(), sizeof(neterr));
105                                 return (-1);
106                         }
107                 }
108         }
109         else {
110                 pos = 0;
111                 while (pos < len) {
112                         n = write(fd, cmd + pos, len - pos);
113                         if (n < 0)
114                                 return (-1);
115                         pos += n;
116                 }
117         }
118
119         return (len);
120 }
121
122 int
123 read_remote(int fd, int extbufsize, char *extbuf)
124 {
125         ssize_t rlen = 0;
126         size_t pos, len, copysize;
127         char buff[BUF_SIZE];
128         int done = 0, status = 0, status_running = 0, extbufpos = 0;
129         enum { parse_status, parse_spacedash, parse_rest } parsestate;
130
131         if (do_timeout(CON_TIMEOUT, 1) != 0) {
132                 snprintf(neterr, sizeof(neterr), "Timeout reached");
133                 return (-1);
134         }
135
136         /*
137          * Remote reading code from femail.c written by Henning Brauer of
138          * OpenBSD and released under a BSD style license.
139          */
140         len = 0;
141         pos = 0;
142         parsestate = parse_status;
143         neterr[0] = 0;
144         while (!(done && parsestate == parse_status)) {
145                 rlen = 0;
146                 if (pos == 0 ||
147                     (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) {
148                         memmove(buff, buff + pos, len - pos);
149                         len -= pos;
150                         pos = 0;
151                         if (((config.features & SECURETRANSFER) != 0) &&
152                             (config.features & NOSSL) == 0) {
153                                 if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) {
154                                         strlcpy(neterr, ssl_errstr(), sizeof(neterr));
155                                         goto error;
156                                 }
157                         } else {
158                                 if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) {
159                                         strlcpy(neterr, strerror(errno), sizeof(neterr));
160                                         goto error;
161                                 }
162                         }
163                         len += rlen;
164
165                         copysize = sizeof(neterr) - strlen(neterr) - 1;
166                         if (copysize > len)
167                                 copysize = len;
168                         strncat(neterr, buff, copysize);
169                 }
170                 /*
171                  * If there is an external buffer with a size bigger than zero
172                  * and as long as there is space in the external buffer and
173                  * there are new characters read from the mailserver
174                  * copy them to the external buffer
175                  */
176                 if (extbufpos <= (extbufsize - 1) && rlen > 0 && extbufsize > 0 && extbuf != NULL) {
177                         /* do not write over the bounds of the buffer */
178                         if(extbufpos + rlen > (extbufsize - 1)) {
179                                 rlen = extbufsize - extbufpos;
180                         }
181                         memcpy(extbuf + extbufpos, buff + len - rlen, rlen);
182                         extbufpos += rlen;
183                 }
184
185                 if (pos == len)
186                         continue;
187
188                 switch (parsestate) {
189                 case parse_status:
190                         for (; pos < len; pos++) {
191                                 if (isdigit(buff[pos])) {
192                                         status_running = status_running * 10 + (buff[pos] - '0');
193                                 } else {
194                                         status = status_running;
195                                         status_running = 0;
196                                         parsestate = parse_spacedash;
197                                         break;
198                                 }
199                         }
200                         continue;
201
202                 case parse_spacedash:
203                         switch (buff[pos]) {
204                         case ' ':
205                                 done = 1;
206                                 break;
207
208                         case '-':
209                                 /* ignore */
210                                 /* XXX read capabilities */
211                                 break;
212
213                         default:
214                                 strcpy(neterr, "invalid syntax in reply from server");
215                                 goto error;
216                         }
217
218                         pos++;
219                         parsestate = parse_rest;
220                         continue;
221
222                 case parse_rest:
223                         /* skip up to \n */
224                         for (; pos < len; pos++) {
225                                 if (buff[pos] == '\n') {
226                                         pos++;
227                                         parsestate = parse_status;
228                                         break;
229                                 }
230                         }
231                 }
232
233         }
234
235         do_timeout(0, 0);
236
237         /* chop off trailing newlines */
238         while (neterr[0] != 0 && strchr("\r\n", neterr[strlen(neterr) - 1]) != 0)
239                 neterr[strlen(neterr) - 1] = 0;
240
241         return (status/100);
242
243 error:
244         do_timeout(0, 0);
245         return (-1);
246 }
247
248 /*
249  * Handle SMTP authentication
250  */
251 static int
252 smtp_login(int fd, char *login, char* password, const struct smtp_features* features)
253 {
254         char *temp;
255         int len, res = 0;
256
257         // CRAM-MD5
258         if (features->auth.cram_md5) {
259                 res = smtp_auth_md5(fd, login, password);
260                 if (res == 0) {
261                         return (0);
262                 } else if (res == -2) {
263                 /*
264                  * If the return code is -2, then then the login attempt failed,
265                  * do not try other login mechanisms
266                  */
267                         return (1);
268                 }
269         }
270
271         // LOGIN
272         if (features->auth.login) {
273                 if ((config.features & INSECURE) != 0 ||
274                     (config.features & SECURETRANSFER) != 0) {
275                         /* Send AUTH command according to RFC 2554 */
276                         send_remote_command(fd, "AUTH LOGIN");
277                         if (read_remote(fd, 0, NULL) != 3) {
278                                 syslog(LOG_NOTICE, "remote delivery deferred:"
279                                                 " AUTH login not available: %s",
280                                                 neterr);
281                                 return (1);
282                         }
283
284                         len = base64_encode(login, strlen(login), &temp);
285                         if (len < 0) {
286 encerr:
287                                 syslog(LOG_ERR, "can not encode auth reply: %m");
288                                 return (1);
289                         }
290
291                         send_remote_command(fd, "%s", temp);
292                         free(temp);
293                         res = read_remote(fd, 0, NULL);
294                         if (res != 3) {
295                                 syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s",
296                                        res == 5 ? "failed" : "deferred", neterr);
297                                 return (res == 5 ? -1 : 1);
298                         }
299
300                         len = base64_encode(password, strlen(password), &temp);
301                         if (len < 0)
302                                 goto encerr;
303
304                         send_remote_command(fd, "%s", temp);
305                         free(temp);
306                         res = read_remote(fd, 0, NULL);
307                         if (res != 2) {
308                                 syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s",
309                                                 res == 5 ? "failed" : "deferred", neterr);
310                                 return (res == 5 ? -1 : 1);
311                         }
312                 } else {
313                         syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. ");
314                         return (1);
315                 }
316         }
317
318         return (0);
319 }
320
321 static int
322 open_connection(struct mx_hostentry *h)
323 {
324         int fd;
325
326         syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d",
327                h->host, h->addr, h->pref);
328
329         fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol);
330         if (fd < 0) {
331                 syslog(LOG_INFO, "socket for %s [%s] failed: %m",
332                        h->host, h->addr);
333                 return (-1);
334         }
335
336         if (connect(fd, (struct sockaddr *)&h->sa, h->ai.ai_addrlen) < 0) {
337                 syslog(LOG_INFO, "connect to %s [%s] failed: %m",
338                        h->host, h->addr);
339                 close(fd);
340                 return (-1);
341         }
342
343         return (fd);
344 }
345
346 static void
347 close_connection(int fd)
348 {
349         if (config.ssl != NULL) {
350                 if (((config.features & SECURETRANSFER) != 0) &&
351                     ((config.features & NOSSL) == 0))
352                         SSL_shutdown(config.ssl);
353                 SSL_free(config.ssl);
354         }
355
356         close(fd);
357 }
358
359 static void parse_auth_line(char* line, struct smtp_auth_mechanisms* auth) {
360         // Skip the auth prefix
361         line += strlen("AUTH ");
362
363         char* method = strtok(line, " ");
364         while (method) {
365                 if (strcmp(method, "CRAM-MD5") == 0)
366                         auth->cram_md5 = 1;
367
368                 else if (strcmp(method, "LOGIN") == 0)
369                         auth->login = 1;
370
371                 method = strtok(NULL, " ");
372         }
373 }
374
375 int perform_server_greeting(int fd, struct smtp_features* features) {
376         /*
377                 Send EHLO
378                 XXX allow HELO fallback
379         */
380         send_remote_command(fd, "EHLO %s", hostname());
381
382         char buffer[EHLO_RESPONSE_SIZE];
383         memset(buffer, 0, sizeof(buffer));
384
385         int res = read_remote(fd, sizeof(buffer) - 1, buffer);
386
387         // Got an unexpected response
388         if (res != 2)
389                 return -1;
390
391         // Reset all features
392         memset(features, 0, sizeof(*features));
393
394         // Run through the buffer line by line
395         char linebuffer[EHLO_RESPONSE_SIZE];
396         char* p = buffer;
397
398         while (*p) {
399                 char* line = linebuffer;
400                 while (*p && *p != '\n') {
401                         *line++ = *p++;
402                 }
403
404                 // p should never point to NULL after the loop
405                 // above unless we reached the end of the buffer.
406                 // In that case we will raise an error.
407                 if (!*p) {
408                         return -1;
409                 }
410
411                 // Otherwise p points to the newline character which
412                 // we will skip.
413                 p++;
414
415                 // Terminte the string (and remove the carriage-return character)
416                 *--line = '\0';
417                 line = linebuffer;
418
419                 // End main loop for empty lines
420                 if (*line == '\0')
421                         break;
422
423                 // Process the line
424                 // - Must start with 250, followed by dash or space
425                 // - We won't check for the correct usage of space and dash because
426                 //    that is already done in read_remote().
427                 if ((strncmp(line, "250-", 4) != 0) && (strncmp(line, "250 ", 4) != 0)) {
428                         syslog(LOG_ERR, "Invalid line: %s\n", line);
429                         return -1;
430                 }
431
432                 // Skip the prefix
433                 line += 4;
434
435                 // Check for STARTTLS
436                 if (strcmp(line, "STARTTLS") == 0)
437                         features->starttls = 1;
438
439                 // Parse authentication mechanisms
440                 else if (strncmp(line, "AUTH ", 5) == 0)
441                         parse_auth_line(line, &features->auth);
442         }
443
444         syslog(LOG_DEBUG, "Server greeting successfully completed");
445
446         // STARTTLS
447         if (features->starttls)
448                 syslog(LOG_DEBUG, "  Server supports STARTTLS");
449         else
450                 syslog(LOG_DEBUG, "  Server does not support STARTTLS");
451
452         // Authentication
453         if (features->auth.cram_md5) {
454                 syslog(LOG_DEBUG, "  Server supports CRAM-MD5 authentication");
455         }
456         if (features->auth.login) {
457                 syslog(LOG_DEBUG, "  Server supports LOGIN authentication");
458         }
459
460         return 0;
461 }
462
463 static int
464 deliver_to_host(struct qitem *it, struct mx_hostentry *host)
465 {
466         struct authuser *a;
467         struct smtp_features features;
468         char line[1000], *addrtmp = NULL, *to_addr;
469         size_t linelen;
470         int fd, error = 0, do_auth = 0, res = 0;
471
472         if (fseek(it->mailf, 0, SEEK_SET) != 0) {
473                 snprintf(errmsg, sizeof(errmsg), "can not seek: %s", strerror(errno));
474                 return (-1);
475         }
476
477         fd = open_connection(host);
478         if (fd < 0)
479                 return (1);
480
481 #define READ_REMOTE_CHECK(c, exp)                                       \
482         do {                                                            \
483                 res = read_remote(fd, 0, NULL);                         \
484                 if (res == 5) {                                         \
485                         syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \
486                                host->host, host->addr, c, neterr);      \
487                         snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \
488                                  host->host, host->addr, c, neterr);    \
489                         error = -1;                                     \
490                         goto out;                                       \
491                 } else if (res != exp) {                                \
492                         syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \
493                                host->host, host->addr, c, neterr);      \
494                         error = 1;                                      \
495                         goto out;                                       \
496                 }                                                       \
497         } while (0)
498
499         /* Check first reply from remote host */
500         if ((config.features & SECURETRANSFER) == 0 ||
501             (config.features & STARTTLS) != 0) {
502                 config.features |= NOSSL;
503                 READ_REMOTE_CHECK("connect", 2);
504
505                 config.features &= ~NOSSL;
506         }
507
508         if ((config.features & SECURETRANSFER) != 0) {
509                 error = smtp_init_crypto(fd, config.features, &features);
510                 if (error == 0)
511                         syslog(LOG_DEBUG, "SSL initialization successful");
512                 else
513                         goto out;
514
515                 if ((config.features & STARTTLS) == 0)
516                         READ_REMOTE_CHECK("connect", 2);
517         }
518
519         // Say EHLO
520         if (perform_server_greeting(fd, &features) != 0) {
521                 syslog(LOG_ERR, "Could not perform server greeting at %s [%s]: %s",
522                         host->host, host->addr, neterr);
523                 return -1;
524         }
525
526         /*
527          * Use SMTP authentication if the user defined an entry for the remote
528          * or smarthost
529          */
530         SLIST_FOREACH(a, &authusers, next) {
531                 if (strcmp(a->host, host->host) == 0) {
532                         do_auth = 1;
533                         break;
534                 }
535         }
536
537         if (do_auth == 1) {
538                 /*
539                  * Check if the user wants plain text login without using
540                  * encryption.
541                  */
542                 syslog(LOG_INFO, "using SMTP authentication for user %s", a->login);
543                 error = smtp_login(fd, a->login, a->password, &features);
544                 if (error < 0) {
545                         syslog(LOG_ERR, "remote delivery failed:"
546                                         " SMTP login failed: %m");
547                         snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host);
548                         error = -1;
549                         goto out;
550                 }
551                 /* SMTP login is not available, so try without */
552                 else if (error > 0) {
553                         syslog(LOG_WARNING, "SMTP login not available. Trying without.");
554                 }
555         }
556
557         /* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */
558         send_remote_command(fd, "MAIL FROM:<%s>", it->sender);
559         READ_REMOTE_CHECK("MAIL FROM", 2);
560
561         /* XXX send ESMTP ORCPT */
562         if ((addrtmp = strdup(it->addr)) == NULL) {
563                 syslog(LOG_CRIT, "remote delivery deferred: unable to allocate memory");
564                 error = 1;
565                 goto out;
566         }
567         to_addr = strtok(addrtmp, ",");
568         while (to_addr != NULL) {
569                 send_remote_command(fd, "RCPT TO:<%s>", to_addr);
570                 READ_REMOTE_CHECK("RCPT TO", 2);
571                 to_addr = strtok(NULL, ",");
572         }
573
574         send_remote_command(fd, "DATA");
575         READ_REMOTE_CHECK("DATA", 3);
576
577         error = 0;
578         while (!feof(it->mailf)) {
579                 if (fgets(line, sizeof(line), it->mailf) == NULL)
580                         break;
581                 linelen = strlen(line);
582                 if (linelen == 0 || line[linelen - 1] != '\n') {
583                         syslog(LOG_CRIT, "remote delivery failed: corrupted queue file");
584                         snprintf(errmsg, sizeof(errmsg), "corrupted queue file");
585                         error = -1;
586                         goto out;
587                 }
588
589                 /* Remove trailing \n's and escape leading dots */
590                 trim_line(line);
591
592                 /*
593                  * If the first character is a dot, we escape it so the line
594                  * length increases
595                 */
596                 if (line[0] == '.')
597                         linelen++;
598
599                 if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) {
600                         syslog(LOG_NOTICE, "remote delivery deferred: write error");
601                         error = 1;
602                         goto out;
603                 }
604         }
605
606         send_remote_command(fd, ".");
607         READ_REMOTE_CHECK("final DATA", 2);
608
609         send_remote_command(fd, "QUIT");
610         if (read_remote(fd, 0, NULL) != 2)
611                 syslog(LOG_INFO, "remote delivery succeeded but QUIT failed: %s", neterr);
612 out:
613
614         free(addrtmp);
615         close_connection(fd);
616         return (error);
617 }
618
619 int
620 deliver_remote(struct qitem *it)
621 {
622         struct mx_hostentry *hosts, *h;
623         const char *host;
624         int port;
625         int error = 1, smarthost = 0;
626
627         port = SMTP_PORT;
628
629         /* Smarthost support? */
630         if (config.smarthost != NULL) {
631                 host = config.smarthost;
632                 port = config.port;
633                 syslog(LOG_INFO, "using smarthost (%s:%i)", host, port);
634                 smarthost = 1;
635         } else {
636                 host = strrchr(it->addr, '@');
637                 /* Should not happen */
638                 if (host == NULL) {
639                         snprintf(errmsg, sizeof(errmsg), "Internal error: badly formed address %s",
640                                  it->addr);
641                         return(-1);
642                 } else {
643                         /* Step over the @ */
644                         host++;
645                 }
646         }
647
648         error = dns_get_mx_list(host, port, &hosts, smarthost);
649         if (error) {
650                 snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host);
651                 syslog(LOG_NOTICE, "remote delivery %s: DNS lookup failure: host %s not found",
652                        error < 0 ? "failed" : "deferred",
653                        host);
654                 return (error);
655         }
656
657         for (h = hosts; *h->host != 0; h++) {
658                 switch (deliver_to_host(it, h)) {
659                 case 0:
660                         /* success */
661                         error = 0;
662                         goto out;
663                 case 1:
664                         /* temp failure */
665                         error = 1;
666                         break;
667                 default:
668                         /* perm failure */
669                         error = -1;
670                         goto out;
671                 }
672         }
673 out:
674         free(hosts);
675
676         return (error);
677 }