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 } 678