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 <string.h> 57 #include <syslog.h> 58 #include <unistd.h> 59 60 #include "dma.h" 61 62 char neterr[ERRMSG_SIZE]; 63 64 char * 65 ssl_errstr(void) 66 { 67 long oerr, nerr; 68 69 oerr = 0; 70 while ((nerr = ERR_get_error()) != 0) 71 oerr = nerr; 72 73 return (ERR_error_string(oerr, NULL)); 74 } 75 76 ssize_t 77 send_remote_command(int fd, const char* fmt, ...) 78 { 79 va_list va; 80 char cmd[4096]; 81 size_t len, pos; 82 int s; 83 ssize_t n; 84 85 va_start(va, fmt); 86 s = vsnprintf(cmd, sizeof(cmd) - 2, fmt, va); 87 va_end(va); 88 if (s == sizeof(cmd) - 2 || s < 0) { 89 strcpy(neterr, "Internal error: oversized command string"); 90 return (-1); 91 } 92 93 /* We *know* there are at least two more bytes available */ 94 strcat(cmd, "\r\n"); 95 len = strlen(cmd); 96 97 if (((config.features & SECURETRANS) != 0) && 98 ((config.features & NOSSL) == 0)) { 99 while ((s = SSL_write(config.ssl, (const char*)cmd, len)) <= 0) { 100 s = SSL_get_error(config.ssl, s); 101 if (s != SSL_ERROR_WANT_READ && 102 s != SSL_ERROR_WANT_WRITE) { 103 strncpy(neterr, ssl_errstr(), sizeof(neterr)); 104 return (-1); 105 } 106 } 107 } 108 else { 109 pos = 0; 110 while (pos < len) { 111 n = write(fd, cmd + pos, len - pos); 112 if (n < 0) 113 return (-1); 114 pos += n; 115 } 116 } 117 118 return (len); 119 } 120 121 int 122 read_remote(int fd, int extbufsize, char *extbuf) 123 { 124 ssize_t rlen = 0; 125 size_t pos, len, copysize; 126 char buff[BUF_SIZE]; 127 int done = 0, status = 0, status_running = 0, extbufpos = 0; 128 enum { parse_status, parse_spacedash, parse_rest } parsestate; 129 130 if (do_timeout(CON_TIMEOUT, 1) != 0) { 131 snprintf(neterr, sizeof(neterr), "Timeout reached"); 132 return (-1); 133 } 134 135 /* 136 * Remote reading code from femail.c written by Henning Brauer of 137 * OpenBSD and released under a BSD style license. 138 */ 139 len = 0; 140 pos = 0; 141 parsestate = parse_status; 142 neterr[0] = 0; 143 while (!(done && parsestate == parse_status)) { 144 rlen = 0; 145 if (pos == 0 || 146 (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) { 147 memmove(buff, buff + pos, len - pos); 148 len -= pos; 149 pos = 0; 150 if (((config.features & SECURETRANS) != 0) && 151 (config.features & NOSSL) == 0) { 152 if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) { 153 strncpy(neterr, ssl_errstr(), sizeof(neterr)); 154 goto error; 155 } 156 } else { 157 if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) { 158 strncpy(neterr, strerror(errno), sizeof(neterr)); 159 goto error; 160 } 161 } 162 len += rlen; 163 164 copysize = sizeof(neterr) - strlen(neterr) - 1; 165 if (copysize > len) 166 copysize = len; 167 strncat(neterr, buff, copysize); 168 } 169 /* 170 * If there is an external buffer with a size bigger than zero 171 * and as long as there is space in the external buffer and 172 * there are new characters read from the mailserver 173 * copy them to the external buffer 174 */ 175 if (extbufpos <= (extbufsize - 1) && rlen > 0 && extbufsize > 0 && extbuf != NULL) { 176 /* do not write over the bounds of the buffer */ 177 if(extbufpos + rlen > (extbufsize - 1)) { 178 rlen = extbufsize - extbufpos; 179 } 180 memcpy(extbuf + extbufpos, buff + len - rlen, rlen); 181 extbufpos += rlen; 182 } 183 184 if (pos == len) 185 continue; 186 187 switch (parsestate) { 188 case parse_status: 189 for (; pos < len; pos++) { 190 if (isdigit(buff[pos])) { 191 status_running = status_running * 10 + (buff[pos] - '0'); 192 } else { 193 status = status_running; 194 status_running = 0; 195 parsestate = parse_spacedash; 196 break; 197 } 198 } 199 continue; 200 201 case parse_spacedash: 202 switch (buff[pos]) { 203 case ' ': 204 done = 1; 205 break; 206 207 case '-': 208 /* ignore */ 209 /* XXX read capabilities */ 210 break; 211 212 default: 213 strcpy(neterr, "invalid syntax in reply from server"); 214 goto error; 215 } 216 217 pos++; 218 parsestate = parse_rest; 219 continue; 220 221 case parse_rest: 222 /* skip up to \n */ 223 for (; pos < len; pos++) { 224 if (buff[pos] == '\n') { 225 pos++; 226 parsestate = parse_status; 227 break; 228 } 229 } 230 } 231 232 } 233 234 do_timeout(0, 0); 235 236 /* chop off trailing newlines */ 237 while (neterr[0] != 0 && strchr("\r\n", neterr[strlen(neterr) - 1]) != 0) 238 neterr[strlen(neterr) - 1] = 0; 239 240 return (status/100); 241 242 error: 243 do_timeout(0, 0); 244 return (-1); 245 } 246 247 /* 248 * Handle SMTP authentication 249 */ 250 static int 251 smtp_login(int fd, char *login, char* password) 252 { 253 char *temp; 254 int len, res = 0; 255 256 res = smtp_auth_md5(fd, login, password); 257 if (res == 0) { 258 return (0); 259 } else if (res == -2) { 260 /* 261 * If the return code is -2, then then the login attempt failed, 262 * do not try other login mechanisms 263 */ 264 return (1); 265 } 266 267 if ((config.features & INSECURE) != 0 || 268 (config.features & SECURETRANS) != 0) { 269 /* Send AUTH command according to RFC 2554 */ 270 send_remote_command(fd, "AUTH LOGIN"); 271 if (read_remote(fd, 0, NULL) != 3) { 272 syslog(LOG_NOTICE, "remote delivery deferred:" 273 " AUTH login not available: %s", 274 neterr); 275 return (1); 276 } 277 278 len = base64_encode(login, strlen(login), &temp); 279 if (len < 0) { 280 encerr: 281 syslog(LOG_ERR, "can not encode auth reply: %m"); 282 return (1); 283 } 284 285 send_remote_command(fd, "%s", temp); 286 free(temp); 287 res = read_remote(fd, 0, NULL); 288 if (res != 3) { 289 syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s", 290 res == 5 ? "failed" : "deferred", neterr); 291 return (res == 5 ? -1 : 1); 292 } 293 294 len = base64_encode(password, strlen(password), &temp); 295 if (len < 0) 296 goto encerr; 297 298 send_remote_command(fd, "%s", temp); 299 free(temp); 300 res = read_remote(fd, 0, NULL); 301 if (res != 2) { 302 syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s", 303 res == 5 ? "failed" : "deferred", neterr); 304 return (res == 5 ? -1 : 1); 305 } 306 } else { 307 syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. "); 308 return (1); 309 } 310 311 return (0); 312 } 313 314 static int 315 open_connection(struct mx_hostentry *h) 316 { 317 int fd; 318 319 syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d", 320 h->host, h->addr, h->pref); 321 322 fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol); 323 if (fd < 0) { 324 syslog(LOG_INFO, "socket for %s [%s] failed: %m", 325 h->host, h->addr); 326 return (-1); 327 } 328 329 if (connect(fd, (struct sockaddr *)&h->sa, h->ai.ai_addrlen) < 0) { 330 syslog(LOG_INFO, "connect to %s [%s] failed: %m", 331 h->host, h->addr); 332 close(fd); 333 return (-1); 334 } 335 336 return (fd); 337 } 338 339 static void 340 close_connection(int fd) 341 { 342 if (config.ssl != NULL) { 343 if (((config.features & SECURETRANS) != 0) && 344 ((config.features & NOSSL) == 0)) 345 SSL_shutdown(config.ssl); 346 SSL_free(config.ssl); 347 } 348 349 close(fd); 350 } 351 352 static int 353 deliver_to_host(struct qitem *it, struct mx_hostentry *host) 354 { 355 struct authuser *a; 356 char line[1000]; 357 size_t linelen; 358 int fd, error = 0, do_auth = 0, res = 0; 359 360 if (fseek(it->mailf, 0, SEEK_SET) != 0) { 361 snprintf(errmsg, sizeof(errmsg), "can not seek: %s", strerror(errno)); 362 return (-1); 363 } 364 365 fd = open_connection(host); 366 if (fd < 0) 367 return (1); 368 369 #define READ_REMOTE_CHECK(c, exp) \ 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 error = -1; \ 377 goto out; \ 378 } else if (res != exp) { \ 379 syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ 380 host->host, host->addr, c, neterr); \ 381 error = 1; \ 382 goto out; \ 383 } 384 385 /* Check first reply from remote host */ 386 if ((config.features & SECURETRANS) == 0 || 387 (config.features & STARTTLS) != 0) { 388 config.features |= NOSSL; 389 READ_REMOTE_CHECK("connect", 2); 390 391 config.features &= ~NOSSL; 392 } 393 394 if ((config.features & SECURETRANS) != 0) { 395 error = smtp_init_crypto(fd, config.features); 396 if (error == 0) 397 syslog(LOG_DEBUG, "SSL initialization successful"); 398 else 399 goto out; 400 401 if ((config.features & STARTTLS) == 0) 402 READ_REMOTE_CHECK("connect", 2); 403 } 404 405 /* XXX allow HELO fallback */ 406 /* XXX record ESMTP keywords */ 407 send_remote_command(fd, "EHLO %s", hostname()); 408 READ_REMOTE_CHECK("EHLO", 2); 409 410 /* 411 * Use SMTP authentication if the user defined an entry for the remote 412 * or smarthost 413 */ 414 SLIST_FOREACH(a, &authusers, next) { 415 if (strcmp(a->host, host->host) == 0) { 416 do_auth = 1; 417 break; 418 } 419 } 420 421 if (do_auth == 1) { 422 /* 423 * Check if the user wants plain text login without using 424 * encryption. 425 */ 426 syslog(LOG_INFO, "using SMTP authentication for user %s", a->login); 427 error = smtp_login(fd, a->login, a->password); 428 if (error < 0) { 429 syslog(LOG_ERR, "remote delivery failed:" 430 " SMTP login failed: %m"); 431 snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host); 432 error = -1; 433 goto out; 434 } 435 /* SMTP login is not available, so try without */ 436 else if (error > 0) { 437 syslog(LOG_WARNING, "SMTP login not available. Trying without."); 438 } 439 } 440 441 /* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */ 442 send_remote_command(fd, "MAIL FROM:<%s>", it->sender); 443 READ_REMOTE_CHECK("MAIL FROM", 2); 444 445 /* XXX send ESMTP ORCPT */ 446 send_remote_command(fd, "RCPT TO:<%s>", it->addr); 447 READ_REMOTE_CHECK("RCPT TO", 2); 448 449 send_remote_command(fd, "DATA"); 450 READ_REMOTE_CHECK("DATA", 3); 451 452 error = 0; 453 while (!feof(it->mailf)) { 454 if (fgets(line, sizeof(line), it->mailf) == NULL) 455 break; 456 linelen = strlen(line); 457 if (linelen == 0 || line[linelen - 1] != '\n') { 458 syslog(LOG_CRIT, "remote delivery failed: corrupted queue file"); 459 snprintf(errmsg, sizeof(errmsg), "corrupted queue file"); 460 error = -1; 461 goto out; 462 } 463 464 /* Remove trailing \n's and escape leading dots */ 465 trim_line(line); 466 467 /* 468 * If the first character is a dot, we escape it so the line 469 * length increases 470 */ 471 if (line[0] == '.') 472 linelen++; 473 474 if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) { 475 syslog(LOG_NOTICE, "remote delivery deferred: write error"); 476 error = 1; 477 goto out; 478 } 479 } 480 481 send_remote_command(fd, "."); 482 READ_REMOTE_CHECK("final DATA", 2); 483 484 send_remote_command(fd, "QUIT"); 485 if (read_remote(fd, 0, NULL) != 2) 486 syslog(LOG_INFO, "remote delivery succeeded but QUIT failed: %s", neterr); 487 out: 488 489 close_connection(fd); 490 return (error); 491 } 492 493 int 494 deliver_remote(struct qitem *it) 495 { 496 struct mx_hostentry *hosts, *h; 497 const char *host; 498 int port; 499 int error = 1, smarthost = 0; 500 501 port = SMTP_PORT; 502 503 /* Smarthost support? */ 504 if (config.smarthost != NULL) { 505 host = config.smarthost; 506 port = config.port; 507 syslog(LOG_INFO, "using smarthost (%s:%i)", host, port); 508 smarthost = 1; 509 } else { 510 host = strrchr(it->addr, '@'); 511 /* Should not happen */ 512 if (host == NULL) { 513 snprintf(errmsg, sizeof(errmsg), "Internal error: badly formed address %s", 514 it->addr); 515 return(-1); 516 } else { 517 /* Step over the @ */ 518 host++; 519 } 520 } 521 522 error = dns_get_mx_list(host, port, &hosts, smarthost); 523 if (error) { 524 snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host); 525 syslog(LOG_NOTICE, "remote delivery %s: DNS lookup failure: host %s not found", 526 error < 0 ? "failed" : "deferred", 527 host); 528 return (error); 529 } 530 531 for (h = hosts; *h->host != 0; h++) { 532 switch (deliver_to_host(it, h)) { 533 case 0: 534 /* success */ 535 error = 0; 536 goto out; 537 case 1: 538 /* temp failure */ 539 error = 1; 540 break; 541 default: 542 /* perm failure */ 543 error = -1; 544 goto out; 545 } 546 } 547 out: 548 free(hosts); 549 550 return (error); 551 } 552