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