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