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