diff --git a/conf.c b/conf.c index 6e4eb25..f3c014e 100644 --- a/conf.c +++ b/conf.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -74,12 +75,34 @@ trim_line(char *line) static void chomp(char *str) { + char *p; + size_t i; size_t len = strlen(str); + /* remove trailing spaces */ + for (i = 0; i < len; i++) { + if (!isspace(str[i])) + break; + } + + memmove(str, str + i, len + 1 - i); + len -= i; if (len == 0) return; - if (str[len - 1] == '\n') - str[len - 1] = 0; + + /* remove ending spaces (also handles ending '\n', if any) */ + while (len-- > 0) { + if (!isspace(str[len])) + break; + } + + str[len + 1] = 0; + + /* remove comments */ + p = strchr(str, '#'); + if (p) { + *p = 0; + } } /* @@ -88,7 +111,7 @@ chomp(char *str) * file format is: * user|host:password * - * A line starting with # is treated as comment and ignored. + * Anything following a # is treated as comment and ignored. */ void parse_authfile(const char *path) @@ -97,6 +120,7 @@ parse_authfile(const char *path) struct authuser *au; FILE *a; char *data; + int error; int lineno = 0; a = fopen(path, "r"); @@ -105,16 +129,11 @@ parse_authfile(const char *path) /* NOTREACHED */ } - while (!feof(a)) { - if (fgets(line, sizeof(line), a) == NULL) - break; + while (fgets(line, sizeof(line), a)) { lineno++; chomp(line); - /* We hit a comment */ - if (*line == '#') - continue; /* Ignore empty lines */ if (*line == 0) continue; @@ -138,8 +157,14 @@ parse_authfile(const char *path) SLIST_INSERT_HEAD(&authusers, au, next); } - + + error = ferror(a); fclose(a); + + if (error) { + errlog(1, "I/O error while reading file `%s'", path); + /* NOTREACHED */ + } } /* @@ -153,6 +178,7 @@ parse_conf(const char *config_path) char *data; FILE *conf; char line[2048]; + int error; int lineno = 0; conf = fopen(config_path, "r"); @@ -164,17 +190,11 @@ parse_conf(const char *config_path) /* NOTREACHED */ } - while (!feof(conf)) { - if (fgets(line, sizeof(line), conf) == NULL) - break; + while (fgets(line, sizeof(line), conf)) { lineno++; chomp(line); - /* We hit a comment */ - if (strchr(line, '#')) - *strchr(line, '#') = 0; - data = line; word = strsep(&data, EQS); @@ -186,12 +206,20 @@ parse_conf(const char *config_path) data = strdup(data); else data = NULL; - + if (strcmp(word, "SMARTHOST") == 0 && data != NULL) config.smarthost = data; - else if (strcmp(word, "PORT") == 0 && data != NULL) - config.port = atoi(data); - else if (strcmp(word, "ALIASES") == 0 && data != NULL) + else if (strcmp(word, "PORT") == 0 && data != NULL) { + char*check; + long port = strtol(data, &check, 10); + + if (*check != '\0' || port < 0 || port > 0xffff) { + errlogx(1, "invalid value for PORT in %s:%d", config_path, lineno); + /* NOTREACHED */ + } + + config.port = (unsigned int)port; + } else if (strcmp(word, "ALIASES") == 0 && data != NULL) config.aliases = data; else if (strcmp(word, "SPOOLDIR") == 0 && data != NULL) config.spooldir = data; @@ -217,8 +245,12 @@ parse_conf(const char *config_path) user = NULL; config.masquerade_host = host; config.masquerade_user = user; - } else if (strcmp(word, "STARTTLS") == 0 && data == NULL) + } else if (strcmp(word, "VERBOSE") == 0 && data == NULL) + config.features |= VERBOSE; + else if (strcmp(word, "STARTTLS") == 0 && data == NULL) config.features |= STARTTLS; + else if (strcmp(word, "NOHELO") == 0 && data == NULL) + config.features |= NOHELO; else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) config.features |= TLS_OPP; else if (strcmp(word, "SECURETRANSFER") == 0 && data == NULL) @@ -235,5 +267,19 @@ parse_conf(const char *config_path) } } + error = ferror(conf); fclose(conf); + + if (error) { + errlog(1, "I/O error while reading file `%s'", config_path); + /* NOTREACHED */ + } + + /* ensure a meaningful configuration */ + if ((config.features & STARTTLS) != 0) { + if ((config.features & SECURETRANS) == 0) { + syslog(LOG_WARNING, "STARTTLS enabled in `%s', implicitly assuming SECURETRANSFER is enabled", config_path); + config.features |= SECURETRANS; + } + } } diff --git a/crypto.c b/crypto.c index 7e8865c..6552262 100644 --- a/crypto.c +++ b/crypto.c @@ -40,6 +40,7 @@ #include #include +#include #include #include "dma.h" @@ -77,7 +78,7 @@ init_cert_file(SSL_CTX *ctx, const char *path) } int -smtp_init_crypto(int fd, int feature) +smtp_init_crypto(struct connection *c) { SSL_CTX *ctx = NULL; const SSL_METHOD *meth = NULL; @@ -107,42 +108,46 @@ smtp_init_crypto(int fd, int feature) } /* - * If the user wants STARTTLS, we have to send EHLO here + * If STARTTLS is required, issue it here */ - if (((feature & SECURETRANS) != 0) && - (feature & STARTTLS) != 0) { - /* TLS init phase, disable SSL_write */ - config.features |= NOSSL; - - send_remote_command(fd, "EHLO %s", hostname()); - if (read_remote(fd, 0, NULL) == 2) { - send_remote_command(fd, "STARTTLS"); - if (read_remote(fd, 0, NULL) != 2) { - if ((feature & TLS_OPP) == 0) { - syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available: %s", neterr); - return (1); - } else { - syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available: %s", neterr); - return (0); - } + if ((config.features & STARTTLS) != 0) { + /* TLS init phase */ + if ((c->flags & HASSTARTTLS) != 0) { + send_remote_command(c, "STARTTLS"); + if (read_remote(c, NULL, NULL) != 220) { + /* even in opportunistic TLS, if server marked it as available, an error + * is unexpected + */ + syslog(LOG_ERR, "remote delivery deferred: STARTTLS failed: %s", neterr); + return (1); } + + /* End of TLS init phase */ + c->flags |= USESTARTTLS; + } else { + if ((config.features & TLS_OPP) == 0) { + /* remote has no STARTTLS but user required it */ + syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available"); + return (1); + } + + /* disable STARTTLS, opportunistic mode */ + syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available"); } - /* End of TLS init phase, enable SSL_write/read */ - config.features &= ~NOSSL; } - config.ssl = SSL_new(ctx); - if (config.ssl == NULL) { + c->ssl = SSL_new(ctx); + if (c->ssl == NULL) { syslog(LOG_NOTICE, "remote delivery deferred: SSL struct creation failed: %s", ssl_errstr()); return (1); } /* Set ssl to work in client mode */ - SSL_set_connect_state(config.ssl); + SSL_set_connect_state(c->ssl); /* Set fd for SSL in/output */ - error = SSL_set_fd(config.ssl, fd); + error = SSL_set_fd(c->ssl, c->fd); if (error == 0) { syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s", ssl_errstr()); @@ -150,7 +155,7 @@ smtp_init_crypto(int fd, int feature) } /* Open SSL connection */ - error = SSL_connect(config.ssl); + error = SSL_connect(c->ssl); if (error < 0) { syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s", ssl_errstr()); @@ -158,13 +163,15 @@ smtp_init_crypto(int fd, int feature) } /* Get peer certificate */ - cert = SSL_get_peer_certificate(config.ssl); + cert = SSL_get_peer_certificate(c->ssl); if (cert == NULL) { syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s", ssl_errstr()); } X509_free(cert); - + + /* At this point we can safely use SSL write/read*/ + c->flags |= USESSL; return (0); } @@ -217,10 +224,10 @@ hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len, */ /* start out by storing key in pads */ - bzero( k_ipad, sizeof k_ipad); - bzero( k_opad, sizeof k_opad); - bcopy( key, k_ipad, key_len); - bcopy( key, k_opad, key_len); + memset(k_ipad, 0, sizeof(k_ipad)); + memset(k_opad, 0, sizeof(k_opad)); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); /* XOR key with ipad and opad values */ for (i=0; i<64; i++) { @@ -250,27 +257,40 @@ hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len, * CRAM-MD5 authentication */ int -smtp_auth_md5(int fd, char *login, char *password) +smtp_auth_md5(struct connection *c, char *login, char *password) { unsigned char digest[BUF_SIZE]; char buffer[BUF_SIZE], ascii_digest[33]; char *temp; int len, i; + size_t buffsize = sizeof(buffer); static char hextab[] = "0123456789abcdef"; - - temp = calloc(BUF_SIZE, 1); + memset(buffer, 0, sizeof(buffer)); memset(digest, 0, sizeof(digest)); memset(ascii_digest, 0, sizeof(ascii_digest)); /* Send AUTH command according to RFC 2554 */ - send_remote_command(fd, "AUTH CRAM-MD5"); - if (read_remote(fd, sizeof(buffer), buffer) != 3) { - syslog(LOG_DEBUG, "smarthost authentication:" - " AUTH cram-md5 not available: %s", neterr); + send_remote_command(c, "AUTH CRAM-MD5"); + if (read_remote(c, &buffsize, buffer) != 334) { /* if cram-md5 is not available */ - free(temp); - return (-1); + syslog(LOG_DEBUG, "smarthost authentication:" + " AUTH CRAM-MD5 failed: %s", neterr); + return (1); + } + + if (buffsize > sizeof(buffer)) { + syslog(LOG_DEBUG, "smarthost authentication:" + " oversized response to AUTH CRAM-MD5"); + return (1); + } + + /* allocate decoding buffer */ + temp = calloc(BUF_SIZE, 1); + if (!temp) { + syslog(LOG_WARNING, "remote delivery deferred:" + " memory allocation failed"); + return (1); } /* skip 3 char status + 1 char space */ @@ -291,17 +311,17 @@ smtp_auth_md5(int fd, char *login, char *password) /* encode answer */ len = base64_encode(buffer, strlen(buffer), &temp); if (len < 0) { - syslog(LOG_ERR, "can not encode auth reply: %m"); - return (-1); + syslog(LOG_ERR, "cannot encode auth reply: %m"); + return (1); } /* send answer */ - send_remote_command(fd, "%s", temp); + send_remote_command(c, "%s", temp); free(temp); - if (read_remote(fd, 0, NULL) != 2) { + if (read_remote(c, NULL, NULL) != 220) { syslog(LOG_WARNING, "remote delivery deferred:" - " AUTH cram-md5 failed: %s", neterr); - return (-2); + " AUTH CRAM-MD5 failed: %s", neterr); + return (1); } return (0); diff --git a/dma.8 b/dma.8 index b121cd0..5f3ef01 100644 --- a/dma.8 +++ b/dma.8 @@ -29,7 +29,7 @@ .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 22, 2010 +.Dd October 03, 2012 .Dt DMA 8 .Os .Sh NAME @@ -212,7 +212,17 @@ Just stick with the default. Path to the .Sq auth.conf file. -.It Ic SECURETRANS Xo +.It Ic VERBOSE Xo +(boolean, default=commented) +.Xc +Uncomment to turn on verbose development debug output, disabled by default +for security reasons. Use this option with extreme caution, always leave this +option disabled unless you are aware of what you are doing. +When in debug mode +.Nm +is going to log any network communication, including possibly sensitive +data and authentication credentials, to the system logger. +.It Ic SECURETRANSFER Xo (boolean, default=commented) .Xc Uncomment if you want TLS/SSL secured transfer. @@ -221,7 +231,15 @@ Uncomment if you want TLS/SSL secured transfer. .Xc Uncomment if you want to use STARTTLS. Only useful together with -.Sq SECURETRANS . +.Sq SECURETRANSFER . +.It Ic NOHELO Xo +(boolean, default=commented) +.Xc +Uncomment this option to disable HELO fallback when extended +SMTP features are not supported by the remote host. By default +.Nm +falls back to HELO if possible, unless authentication or +secure connection are required. .It Ic OPPORTUNISTIC_TLS Xo (boolean, default=commented) .Xc @@ -233,7 +251,7 @@ the outside mail exchangers; in opportunistic TLS mode, the connection will be encrypted if the remote server supports STARTTLS, but an unencrypted delivery will still be made if the negotiation fails. Only useful together with -.Sq SECURETRANS +.Sq SECURETRANSFER and .Sq STARTTLS . .It Ic CERTFILE Xo diff --git a/dma.c b/dma.c index bb5fa19..dda398f 100644 --- a/dma.c +++ b/dma.c @@ -240,7 +240,7 @@ go_background(struct queue *queue) } daemonize = 0; - bzero(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); @@ -309,8 +309,12 @@ deliver(struct qitem *it) snprintf(errmsg, sizeof(errmsg), "unknown bounce reason"); retry: + /* clear error and EOF indicator just in case + * last delivery was aborted due to I/O error. + */ + clearerr(it->mailf); syslog(LOG_INFO, "trying delivery"); - + if (it->remote) error = deliver_remote(it); else @@ -355,6 +359,7 @@ deliver(struct qitem *it) } bounce: + clearerr(it->mailf); bounce(it, errmsg); /* NOTREACHED */ } @@ -437,7 +442,7 @@ main(int argc, char **argv) atexit(deltmp); init_random(); - bzero(&queue, sizeof(queue)); + memset(&queue, 0, sizeof(queue)); LIST_INIT(&queue.queue); if (strcmp(argv[0], "mailq") == 0) { diff --git a/dma.conf b/dma.conf index 56fa104..cd00a69 100644 --- a/dma.conf +++ b/dma.conf @@ -18,6 +18,12 @@ # SMTP authentication #AUTHPATH /etc/dma/auth.conf +# Verbose output, uncomment if you want development debug output +# to LOG_DEBUG level on syslog. Use this option with extreme caution, +# it might log sensible data to the system logger, always keep it +# disabled unless your know what you are doing +#VERBOSE + # Uncomment if yout want TLS/SSL support #SECURETRANSFER @@ -25,6 +31,11 @@ # SECURETRANSFER) #STARTTLS +# Uncomment if you want to abort when the smarthost doesn't support EHLO, +# by default if EHLO is not supported dma falls back to HELO +# unless secure communication is required. +#NOHELO + # Uncomment if you have specified STARTTLS above and it should be allowed # to fail ("opportunistic TLS", use an encrypted connection when available # but allow an unencrypted one to servers that do not support it) diff --git a/dma.h b/dma.h index 440a7a3..a8ab52d 100644 --- a/dma.h +++ b/dma.h @@ -43,10 +43,12 @@ #include #include #include +#include /*size_t*/ #define VERSION "DragonFly Mail Agent " DMA_VERSION #define BUF_SIZE 2048 +#define ESMTPBUF_SIZE 8192 #define ERRMSG_SIZE 200 #define USERNAME_SIZE 50 #define MIN_RETRY 300 /* 5 minutes */ @@ -59,13 +61,14 @@ #define SMTP_PORT 25 /* Default SMTP port */ #define CON_TIMEOUT (5*60) /* Connection timeout per RFC5321 */ -#define STARTTLS 0x002 /* StartTLS support */ -#define SECURETRANS 0x004 /* SSL/TLS in general */ -#define NOSSL 0x008 /* Do not use SSL */ -#define DEFER 0x010 /* Defer mails */ -#define INSECURE 0x020 /* Allow plain login w/o encryption */ -#define FULLBOUNCE 0x040 /* Bounce the full message */ -#define TLS_OPP 0x080 /* Opportunistic STARTTLS */ +#define VERBOSE 0x0001 /* Enable debug logging output to LOG_DEBUG */ +#define STARTTLS 0x0002 /* StartTLS support required by the user*/ +#define NOHELO 0x0004 /* Don't fallback to HELO if EHLO isn't supported*/ +#define SECURETRANS 0x0008 /* SSL/TLS in general */ +#define DEFER 0x0020 /* Defer mails */ +#define INSECURE 0x0040 /* Allow plain login w/o encryption */ +#define FULLBOUNCE 0x0080 /* Bounce the full message */ +#define TLS_OPP 0x0100 /* Opportunistic STARTTLS */ #ifndef CONF_PATH #error Please define CONF_PATH @@ -121,7 +124,7 @@ struct queue { struct config { const char *smarthost; - int port; + unsigned int port; /* should be unsigned for maximum compatibility (16 bit ints) */ const char *aliases; const char *spooldir; const char *authpath; @@ -130,9 +133,20 @@ struct config { const char *mailname; const char *masquerade_host; const char *masquerade_user; +}; + +#define USESSL 0x0001 /* Use SSL for communication */ +#define USESTARTTLS 0x0002 /* Has performed STARTTLS */ +#define HASSTARTTLS 0x0004 /* STARTTLS advertised by the remote host */ +#define AUTHPLAIN 0x0008 /* PLAIN authentication method support */ +#define AUTHLOGIN 0x0010 /* LOGIN authentication method support */ +#define AUTHCRAMMD5 0x0020 /* CRAM MD5 authentication method support */ +#define ESMTPMASK (HASSTARTTLS | AUTHPLAIN | AUTHLOGIN | AUTHCRAMMD5) - /* XXX does not belong into config */ +struct connection { + int fd; SSL *ssl; + int flags; }; @@ -177,16 +191,16 @@ void parse_authfile(const char *); /* crypto.c */ void hmac_md5(unsigned char *, int, unsigned char *, int, unsigned char *); -int smtp_auth_md5(int, char *, char *); -int smtp_init_crypto(int, int); +int smtp_auth_md5(struct connection *, char *, char *) __attribute__((__nonnull__(1, 2, 3))); +int smtp_init_crypto(struct connection *) __attribute__((__nonnull__(1))); /* dns.c */ -int dns_get_mx_list(const char *, int, struct mx_hostentry **, int); +int dns_get_mx_list(const char *, unsigned int, struct mx_hostentry **, int); /* net.c */ char *ssl_errstr(void); -int read_remote(int, int, char *); -ssize_t send_remote_command(int, const char*, ...) __attribute__((__nonnull__(2), __format__ (__printf__, 2, 3))); +int read_remote(struct connection *, size_t *, char *) __attribute__((__nonnull__(1))); +ssize_t send_remote_command(struct connection *, const char*, ...) __attribute__((__nonnull__(1, 2), __format__ (__printf__, 2, 3))); int deliver_remote(struct qitem *); /* base64.c */ diff --git a/dns.c b/dns.c index fc5213f..720359f 100644 --- a/dns.c +++ b/dns.c @@ -61,7 +61,7 @@ sort_pref(const void *a, const void *b) } static int -add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps) +add_host(int pref, const char *host, unsigned int port, struct mx_hostentry **he, size_t *ps) { struct addrinfo hints, *res, *res0 = NULL; char servname[10]; @@ -74,7 +74,7 @@ add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; - snprintf(servname, sizeof(servname), "%d", port); + snprintf(servname, sizeof(servname), "%u", port); err = getaddrinfo(host, servname, &hints, &res0); if (err) return (err == EAI_AGAIN ? 1 : -1); @@ -92,7 +92,7 @@ add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t p->pref = pref; p->ai = *res; p->ai.ai_addr = NULL; - bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen); + memcpy(&p->sa, res->ai_addr, p->ai.ai_addrlen); getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen, p->addr, sizeof(p->addr), @@ -111,7 +111,7 @@ add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t } int -dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx) +dns_get_mx_list(const char *host, unsigned int port, struct mx_hostentry **he, int no_mx) { char outname[MAXDNAME]; ns_msg msg; diff --git a/local.c b/local.c index 6a6407e..5e53a78 100644 --- a/local.c +++ b/local.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +63,7 @@ create_mbox(const char *name) /* * We need to enable SIGCHLD temporarily so that waitpid works. */ - bzero(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(SIGCHLD, &sa, &osa); @@ -74,7 +75,7 @@ create_mbox(const char *name) /* child */ maxfd = sysconf(_SC_OPEN_MAX); if (maxfd == -1) - maxfd = 1024; /* what can we do... */ + maxfd = FOPEN_MAX; /* what can we do... */ for (i = 3; i <= maxfd; ++i) close(i); @@ -203,9 +204,7 @@ deliver_local(struct qitem *it) if (write(mbox, line, error) != error) goto wrerror; - while (!feof(it->mailf)) { - if (fgets(line, sizeof(line), it->mailf) == NULL) - break; + while (fgets(line, sizeof(line), it->mailf)) { linelen = strlen(line); if (linelen == 0 || line[linelen - 1] != '\n') { syslog(LOG_CRIT, "local delivery failed: corrupted queue file"); @@ -238,6 +237,13 @@ deliver_local(struct qitem *it) if ((size_t)write(mbox, line, linelen) != linelen) goto wrerror; } + + if (ferror(it->mailf)) { + syslog(LOG_ERR, "local delivery failed: I/O error while reading: %m"); + error = 1; + goto chop; + } + close(mbox); return (0); diff --git a/mail.c b/mail.c index 9cbde41..faa883f 100644 --- a/mail.c +++ b/mail.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -54,7 +55,7 @@ bounce(struct qitem *it, const char *reason) exit(1); } - bzero(&bounceq, sizeof(bounceq)); + memset(&bounceq, 0, sizeof(bounceq)); LIST_INIT(&bounceq.queue); bounceq.sender = ""; if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0) @@ -110,9 +111,7 @@ bounce(struct qitem *it, const char *reason) goto fail; } } else { - while (!feof(it->mailf)) { - if (fgets(line, sizeof(line), it->mailf) == NULL) - break; + while (fgets(line, sizeof(line), it->mailf)) { if (line[0] == '\n') break; if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1) @@ -122,8 +121,12 @@ bounce(struct qitem *it, const char *reason) if (linkspool(&bounceq) != 0) goto fail; + + /* warn on partial bounce, but still try delivery */ + if (ferror(it->mailf)) + syslog(LOG_ERR, "error I/O error while crating bounce: %m"); + /* bounce is safe */ - delqueue(it); run_queue(&bounceq); @@ -170,7 +173,7 @@ parse_addrs(struct parse_state *ps, char *s, struct queue *queue) case START: /* init our data */ - bzero(ps, sizeof(*ps)); + memset(ps, 0, sizeof(*ps)); /* skip over header name */ while (*s != ':') diff --git a/net.c b/net.c index fa4a3a4..8033fcc 100644 --- a/net.c +++ b/net.c @@ -72,7 +72,7 @@ ssl_errstr(void) } ssize_t -send_remote_command(int fd, const char* fmt, ...) +send_remote_command(struct connection *c, const char* fmt, ...) { va_list va; char cmd[4096]; @@ -88,14 +88,16 @@ send_remote_command(int fd, const char* fmt, ...) return (-1); } + if (config.features & VERBOSE) + syslog(LOG_DEBUG, ">>> %s", cmd); + /* We *know* there are at least two more bytes available */ strcat(cmd, "\r\n"); len = strlen(cmd); - - if (((config.features & SECURETRANS) != 0) && - ((config.features & NOSSL) == 0)) { - while ((s = SSL_write(config.ssl, (const char*)cmd, len)) <= 0) { - s = SSL_get_error(config.ssl, s); + + if ((c->flags & USESSL) != 0) { + while ((s = SSL_write(c->ssl, (const char*)cmd, len)) <= 0) { + s = SSL_get_error(c->ssl, s); if (s != SSL_ERROR_WANT_READ && s != SSL_ERROR_WANT_WRITE) { strncpy(neterr, ssl_errstr(), sizeof(neterr)); @@ -106,9 +108,14 @@ send_remote_command(int fd, const char* fmt, ...) else { pos = 0; while (pos < len) { - n = write(fd, cmd + pos, len - pos); - if (n < 0) + n = write(c->fd, cmd + pos, len - pos); + if (n < 0) { + if (errno == EINTR) + continue; + return (-1); + } + pos += n; } } @@ -117,292 +124,531 @@ send_remote_command(int fd, const char* fmt, ...) } int -read_remote(int fd, int extbufsize, char *extbuf) +read_remote(struct connection *c, size_t *extbufsize, char *extbuf) { - ssize_t rlen = 0; - size_t pos, len, copysize; + ssize_t pos = 0, len = 0, copysize = 0; + size_t ebufpos = 0, ebufmax = 0; char buff[BUF_SIZE]; - int done = 0, status = 0, status_running = 0, extbufpos = 0; - enum { parse_status, parse_spacedash, parse_rest } parsestate; - + int statnum = 0, currstatnum = 0, done = 0; + enum { PARSE_STATNUM, PARSE_DASH, PARSE_REST } parse = PARSE_STATNUM; + if (do_timeout(CON_TIMEOUT, 1) != 0) { - snprintf(neterr, sizeof(neterr), "Timeout reached"); - return (-1); + strncpy(neterr, "timeout reached", sizeof(neterr)); + statnum = -1; + goto timeout; } - + + if (extbufsize) { + /*if no extbuf is provided interpret the call as a "peek" to the size*/ + if (extbuf) { + ebufmax = *extbufsize; + } + /*always leave room for ending null byte*/ + if (ebufmax) { + ebufmax--; + } + } + /* * Remote reading code from femail.c written by Henning Brauer of * OpenBSD and released under a BSD style license. */ - len = 0; - pos = 0; - parsestate = parse_status; neterr[0] = 0; - while (!(done && parsestate == parse_status)) { - rlen = 0; - if (pos == 0 || - (pos > 0 && memchr(buff + pos, '\n', len - pos) == NULL)) { - memmove(buff, buff + pos, len - pos); - len -= pos; + do { + if (pos == len) { + /* no more data in buffer, read the next chunk */ pos = 0; - if (((config.features & SECURETRANS) != 0) && - (config.features & NOSSL) == 0) { - if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) { - strncpy(neterr, ssl_errstr(), sizeof(neterr)); - goto error; + + /* Read the next bytes */ + if ((c->flags & USESSL) != 0) { + if ((len = SSL_read(c->ssl, buff, sizeof(buff))) == -1) { + strncpy(neterr, ssl_errstr(), sizeof(neterr)); + goto error; } } else { - if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) { + if ((len = read(c->fd, buff, sizeof(buff))) == -1) { strncpy(neterr, strerror(errno), sizeof(neterr)); goto error; } } - len += rlen; - + + if (len == 0) { + strncpy(neterr, "unexpected connection end", sizeof(neterr)); + statnum = -1; + goto error; + } + + /* Copy (at least partially) contents to an error buffer*/ copysize = sizeof(neterr) - strlen(neterr) - 1; if (copysize > len) copysize = len; + strncat(neterr, buff, copysize); - } - /* - * If there is an external buffer with a size bigger than zero - * and as long as there is space in the external buffer and - * there are new characters read from the mailserver - * copy them to the external buffer - */ - if (extbufpos <= (extbufsize - 1) && rlen > 0 && extbufsize > 0 && extbuf != NULL) { - /* do not write over the bounds of the buffer */ - if(extbufpos + rlen > (extbufsize - 1)) { - rlen = extbufsize - extbufpos; + + /* If an external buffer is available, copy data over there */ + if (ebufmax > 0) { + /* Do not write over the buffer bounds */ + copysize = len; + + if (ebufpos + copysize > ebufmax) { + copysize = ebufmax - ebufpos; + } + + if (copysize > 0) { + memcpy(extbuf + ebufpos, buff, copysize); + extbuf[ebufpos + copysize] = 0; + } } - memcpy(extbuf + extbufpos, buff + len - rlen, rlen); - extbufpos += rlen; + + /* Add len to ebufpos, so the caller + * can know if the provided buffer was too small. + */ + ebufpos += len; } - - if (pos == len) - continue; - - switch (parsestate) { - case parse_status: + + /* since rlen can't be zero, we're sure that there is at least one character*/ + switch (parse) { + default: + /* shouldn't happen */ + syslog(LOG_CRIT, "reached dead code"); + statnum = -1; + goto error; + case PARSE_STATNUM: + /* parse status number*/ for (; pos < len; pos++) { if (isdigit(buff[pos])) { - status_running = status_running * 10 + (buff[pos] - '0'); + currstatnum = currstatnum * 10 + (buff[pos] - '0'); } else { - status = status_running; - status_running = 0; - parsestate = parse_spacedash; + /* verify and store status */ + if (currstatnum < 100 || currstatnum > 999) { + strncpy(neterr, "error reading status, out of range value", sizeof(neterr)); + statnum = -1; + goto error; + } + + statnum = currstatnum; + currstatnum = 0; + parse = PARSE_DASH; break; } } - continue; - - case parse_spacedash: - switch (buff[pos]) { - case ' ': - done = 1; - break; - - case '-': - /* ignore */ - /* XXX read capabilities */ - break; - - default: - strcpy(neterr, "invalid syntax in reply from server"); + + break; + case PARSE_DASH: + /* parse dash, if space then we're done*/ + if (buff[pos] == ' ') { + done = 1; + } else if (buff[pos] == '-') { + /* ignore */ + /* XXX read capabilities */ + } else { + strncpy(neterr, "invalid syntax in reply from server", sizeof(neterr)); + statnum = -1; goto error; } - + pos++; - parsestate = parse_rest; - continue; - - case parse_rest: - /* skip up to \n */ + parse = PARSE_REST; + break; + case PARSE_REST: + /* parse to newline */ for (; pos < len; pos++) { if (buff[pos] == '\n') { + /* Skip the newline and expect a status number */ pos++; - parsestate = parse_status; + parse = PARSE_STATNUM; break; } } + + break; } + + } while (!done); + + if (config.features & VERBOSE) + syslog(LOG_DEBUG, "<<< %d", statnum); - } - +error: + /* Disable timeout */ do_timeout(0, 0); - /* chop off trailing newlines */ +timeout: + /* Ensure neterr null-termination*/ + neterr[sizeof(neterr) - 1] = 0; + /* Chop off trailing newlines */ while (neterr[0] != 0 && strchr("\r\n", neterr[strlen(neterr) - 1]) != 0) neterr[strlen(neterr) - 1] = 0; - return (status/100); - -error: - do_timeout(0, 0); - return (-1); + if (extbufsize) + *extbufsize = ebufpos; + + return (statnum); } /* * Handle SMTP authentication */ static int -smtp_login(int fd, char *login, char* password) +smtp_login(struct connection *c, char *login, char* password) { char *temp; int len, res = 0; - res = smtp_auth_md5(fd, login, password); - if (res == 0) { - return (0); - } else if (res == -2) { - /* - * If the return code is -2, then then the login attempt failed, - * do not try other login mechanisms - */ + if ((c->flags & AUTHCRAMMD5) != 0) { + /* Use CRAM-MD5 authentication if available*/ + return smtp_auth_md5(c, login, password); + } + + /* Try non-encrypted logins */ + if ((c->flags & USESSL) == 0 && (config.features & INSECURE) == 0) { + syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it"); return (1); } - - if ((config.features & INSECURE) != 0 || - (config.features & SECURETRANS) != 0) { + + if ((c->flags & AUTHLOGIN) != 0) { /* Send AUTH command according to RFC 2554 */ - send_remote_command(fd, "AUTH LOGIN"); - if (read_remote(fd, 0, NULL) != 3) { + send_remote_command(c, "AUTH LOGIN"); + if (read_remote(c, NULL, NULL) != 334) { syslog(LOG_NOTICE, "remote delivery deferred:" - " AUTH login not available: %s", + " AUTH LOGIN was refused: %s", neterr); return (1); } len = base64_encode(login, strlen(login), &temp); if (len < 0) { -encerr: syslog(LOG_ERR, "can not encode auth reply: %m"); return (1); } - send_remote_command(fd, "%s", temp); + send_remote_command(c, "%s", temp); free(temp); - res = read_remote(fd, 0, NULL); - if (res != 3) { - syslog(LOG_NOTICE, "remote delivery %s: AUTH login failed: %s", - res == 5 ? "failed" : "deferred", neterr); - return (res == 5 ? -1 : 1); + res = read_remote(c, NULL, NULL); + if (res != 334) { + syslog(LOG_NOTICE, "remote delivery %s: AUTH LOGIN failed: %s", + res == 503 ? "failed" : "deferred", neterr); + return (res == 503 ? -1 : 1); } len = base64_encode(password, strlen(password), &temp); - if (len < 0) - goto encerr; + if (len < 0) { + syslog(LOG_ERR, "can not encode auth reply: %m"); + return (1); + } - send_remote_command(fd, "%s", temp); + send_remote_command(c, "%s", temp); + free(temp); + res = read_remote(c, NULL, NULL); + if (res != 235) { + syslog(LOG_NOTICE, "remote delivery %s: authentication failed: %s", + res == 503 ? "failed" : "deferred", neterr); + return (res == 503 ? -1 : 1); + } + + return (0); + } else if ((c->flags & AUTHPLAIN) != 0) { + /* PLAIN login (single string with authority, authetication and password, + * if no authority is provided the SMTP server will derive it from authentication. + */ + char *buff; + + send_remote_command(c, "AUTH PLAIN"); + if (read_remote(c, NULL, NULL) != 334) { + syslog(LOG_NOTICE, "remote delivery deferred:" + " AUTH PLAIN was refused: %s", + neterr); + return (1); + } + + len = strlen(login) + strlen(password) + 2; + buff = calloc(len + 1, 1); + if (!buff) { + syslog(LOG_NOTICE, "remote delivery deferred: memory allocation failure"); + return (1); + } + + /* we want this: '\0'username'\0'password*/ + strcpy(buff + 1, login); + strcpy(buff + 1 + strlen(login) + 1, password); + + len = base64_encode(buff, len, &temp); + free(buff); + + if (len < 0) { + syslog(LOG_ERR, "can not encode auth reply: %m"); + return (1); + } + + send_remote_command(c, "%s", temp); free(temp); - res = read_remote(fd, 0, NULL); - if (res != 2) { - syslog(LOG_NOTICE, "remote delivery %s: Authentication failed: %s", - res == 5 ? "failed" : "deferred", neterr); - return (res == 5 ? -1 : 1); + res = read_remote(c, NULL, NULL); + if (res != 235) { + syslog(LOG_NOTICE, "remote delivery deferred: authentication failed: %s", + neterr); + return (1); } + + return (0); } else { - syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. "); + /* No supported authentication method */ + syslog(LOG_ERR, "no supported authentication method for remote host"); return (1); } - - return (0); } static int -open_connection(struct mx_hostentry *h) +esmtp_nextline(char **buff, int skip) { - int fd; + char *line = *buff; + long status; + + if (skip) { + /* Allow skipping to the next line, + * possible scenarios are: + * - the parser is already on a newline + * when it deleted '\r' and is currently over + * a '\n' character. + * - the parser is in a token inside a line, + * happens when it deleted ' ' and is over + * the next token of a line. + */ + while (*line != '\0' && *line != '\n') + line++; + + if (*line == '\n') { + line++; + } + } + + /* now we expect the status number*/ + status = strtol(line, &line, 10); + if (status != 250) { + /*invalid status*/ + return -1; + } + + if (*line == ' ') { + /* signal end of parse with a positive number */ + *buff = line; + return 1; + } + + if (*line != '-') { + /*invalid syntax*/ + return -1; + } + + /* success */ + line++; + *buff = line; + return 0; +} - syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d", - h->host, h->addr, h->pref); +static char * +esmtp_nexttoken(char **buff) +{ + char *line = *buff; + char *tok = line; + + if (*line == '\n' || *line == '\0') { + /* No more tokens available, + * we are on the next line of the ESMTP response. + */ + return NULL; + } + + /* make the line uppercase to honour RFC + * (tokens are parsed regardless their case) + */ + while (*line != '\0' && *line != '\r' && *line != ' ') { + *line = toupper(*line); + line++; + } + + /* null terminate and update the parser */ + if (*line == '\r' || *line == ' ') { + *line++ = 0; + } + + *buff = line; + return tok; +} - fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol); - if (fd < 0) { - syslog(LOG_INFO, "socket for %s [%s] failed: %m", - h->host, h->addr); - return (-1); +static int +esmtp_response(struct connection *c) +{ + char buff[ESMTPBUF_SIZE]; + size_t buffsize = sizeof(buff); + char **parse; + char *esmtp; + char *tok; + int error; + int res; + + res = read_remote(c, &buffsize, buff); + if (res != 250) { + return res; + } + + if (buffsize > sizeof(buff)) { + /*oversized or invalid buffer*/ + return -1; + } + + /* initialize ESMTP parsing */ + esmtp = buff; + parse = &esmtp; + error = esmtp_nextline(parse, 0); + while (error == 0) { + tok = esmtp_nexttoken(parse); + if (!tok) { + /* shouldn't happen, return parse error */ + return -1; + } + + if ((config.features & VERBOSE) != 0) + syslog(LOG_DEBUG, "ESMTP got %s", tok); + + if (strcmp(tok, "STARTTLS") == 0) { + /* STARTTLS is supported */ + c->flags |= HASSTARTTLS; + } else if (strcmp(tok, "AUTH") == 0) { + /* retrieve supported authentication methods */ + while ((tok = esmtp_nexttoken(parse)) != NULL) { + if (strcmp(tok, "CRAM-MD5") == 0) + c->flags |= AUTHCRAMMD5; + else if (strcmp(tok, "LOGIN") == 0) + c->flags |= AUTHLOGIN; + else if (strcmp(tok, "PLAIN") == 0) + c->flags |= AUTHPLAIN; + } + + } + + /*position over next line*/ + error = esmtp_nextline(parse, 1); } + + /*return a negative number on parsing error*/ + if (error < 0) + res = -1; + + return res; +} - if (connect(fd, (struct sockaddr *)&h->sa, h->ai.ai_addrlen) < 0) { - syslog(LOG_INFO, "connect to %s [%s] failed: %m", - h->host, h->addr); - close(fd); +static int +expect_response(struct connection *c, const char *when, int exp) +{ + int res = read_remote(c, NULL, NULL); + + if (res == 500 || res == 502) { + syslog(LOG_NOTICE, "remote delivery deferred: failed after %s: %s", when, neterr); + return (1); + } + + if (res != exp) { + syslog(LOG_ERR, "remote delivery failed after %s: %s", when, neterr); + snprintf(errmsg, sizeof(errmsg), "remote host did not like our %s:\n%s", when, neterr); return (-1); } + + return 0; +} + +static int +open_connection(struct connection*c, struct mx_hostentry *h, int ehlo) +{ + int res; + + syslog(LOG_INFO, "connecting to remote host %s [%s] pref %d using %s", + h->host, h->addr, h->pref, ehlo? "EHLO" : "HELO"); + + memset(c, 0, sizeof(*c)); + c->fd = socket(h->ai.ai_family, h->ai.ai_socktype, h->ai.ai_protocol); + if (c->fd < 0) { + syslog(LOG_INFO, "socket creation failed: %m"); + return (1); + } + + if (connect(c->fd, (struct sockaddr *)&h->sa, h->ai.ai_addrlen) < 0) { + syslog(LOG_INFO, "connection failed: %m"); + close(c->fd); + return (1); + } + + /* Check first reply from remote host */ + res = read_remote(c, NULL, NULL); + if (res != 220) { + switch(res) { + case 421: + syslog(LOG_INFO, "connection rejected temporarily by remote host"); + break; + case 554: + syslog(LOG_INFO, "connection failed, remote host requires QUIT"); + send_remote_command(c, "QUIT"); + break; + default: + syslog(LOG_INFO, "connection failed, remote host greeted us with %d", res); + break; + } + + close(c->fd); + return (1); + } + + if (ehlo) { + /* Try EHLO */ + send_remote_command(c, "EHLO %s", hostname()); + res = esmtp_response(c); + } else { + send_remote_command(c, "HELO %s", hostname()); + res = read_remote(c, NULL, NULL); + } - return (fd); + if (res != 250) { + if (res < 0) { + syslog(LOG_INFO, "connection failed, malformed response by remote host"); + } else { + syslog(LOG_INFO, "connection failed, remote host refused our greeting"); + } + + close(c->fd); + return -1; + } + + syslog(LOG_DEBUG, "connection accepted"); + return 0; } static void -close_connection(int fd) +close_connection(struct connection *c) { - if (config.ssl != NULL) { - if (((config.features & SECURETRANS) != 0) && - ((config.features & NOSSL) == 0)) - SSL_shutdown(config.ssl); - SSL_free(config.ssl); + if (c->ssl != NULL) { + if ((c->flags & USESSL) != 0) + SSL_shutdown(c->ssl); + + SSL_free(c->ssl); } - close(fd); + close(c->fd); } static int deliver_to_host(struct qitem *it, struct mx_hostentry *host) { + struct connection c; struct authuser *a; char line[1000]; size_t linelen; - int fd, error = 0, do_auth = 0, res = 0; + int error = 0, do_auth = 0, res = 0; + /*initialize read structure*/ if (fseek(it->mailf, 0, SEEK_SET) != 0) { snprintf(errmsg, sizeof(errmsg), "can not seek: %s", strerror(errno)); return (-1); } - - fd = open_connection(host); - if (fd < 0) - return (1); - -#define READ_REMOTE_CHECK(c, exp) \ - res = read_remote(fd, 0, NULL); \ - if (res == 5) { \ - syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \ - host->host, host->addr, c, neterr); \ - snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \ - host->host, host->addr, c, neterr); \ - return (-1); \ - } else if (res != exp) { \ - syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ - host->host, host->addr, c, neterr); \ - return (1); \ - } - - /* Check first reply from remote host */ - if ((config.features & SECURETRANS) == 0 || - (config.features & STARTTLS) != 0) { - config.features |= NOSSL; - READ_REMOTE_CHECK("connect", 2); - - config.features &= ~NOSSL; - } - - if ((config.features & SECURETRANS) != 0) { - error = smtp_init_crypto(fd, config.features); - if (error == 0) - syslog(LOG_DEBUG, "SSL initialization successful"); - else - goto out; - - if ((config.features & STARTTLS) == 0) - READ_REMOTE_CHECK("connect", 2); - } - - /* XXX allow HELO fallback */ - /* XXX record ESMTP keywords */ - send_remote_command(fd, "EHLO %s", hostname()); - READ_REMOTE_CHECK("EHLO", 2); - + /* * Use SMTP authentication if the user defined an entry for the remote * or smarthost @@ -414,40 +660,102 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host) } } - if (do_auth == 1) { + error = open_connection(&c, host, 1); + if (error < 0) { + /* fallback to HELO if possible */ + if ((config.features & NOHELO) != 0) { + /* HELO disabled in config file */ + syslog(LOG_NOTICE, "remote delivery deferred:" + " EHLO unsupported by remote host and HELO fallback is disabled"); + return (1); + } + + if (do_auth) { + /* cannot fallback to HELO, authentication is required */ + syslog(LOG_NOTICE, "remote delivery deferred:" + " EHLO unsupported by remote host and authentication is required"); + return (1); + } + + if ((config.features & SECURETRANS) != 0 && (config.features & INSECURE) == 0) { + /* cannot fallback to HELO if secure connection is required */ + syslog(LOG_NOTICE, "remote delivery deferred:" + " ESMTP unsupported by remote host and secure connection is required"); + return (1); + } + + if ((config.features & STARTTLS) != 0 && (config.features & TLS_OPP) == 0) { + /* cannot fallback to HELO if STARTTLS is mandatory */ + syslog(LOG_NOTICE, "remote delivery deferred:" + " ESMTP unsupported by remote host and STARTTLS is required"); + return (1); + + } + + error = open_connection(&c, host, 0); + } + + if (error) { + /*connection failed*/ + return (1); + } + + if ((config.features & SECURETRANS) != 0) { + /* initialize secure transaction */ + error = smtp_init_crypto(&c); + if (error != 0) { + goto out; + } + + syslog(LOG_DEBUG, "SSL initialization successful"); + /* refresh supported ESMTP features if STARTTLS was used*/ + if ((c.flags & USESTARTTLS) != 0) { + c.flags &= ~ESMTPMASK; + send_remote_command(&c, "EHLO %s", hostname()); + res = esmtp_response(&c); + if (res != 250) { + /* shouldn't happen */ + syslog(LOG_NOTICE, "remote delivery deferred: EHLO after STARTTLS failed: %s", neterr); + error = 1; + goto out; + } + } + } + + if (do_auth) { /* * Check if the user wants plain text login without using * encryption. */ syslog(LOG_INFO, "using SMTP authentication for user %s", a->login); - error = smtp_login(fd, a->login, a->password); - if (error < 0) { + error = smtp_login(&c, a->login, a->password); + if (error) { syslog(LOG_ERR, "remote delivery failed:" - " SMTP login failed: %m"); + " SMTP login failed"); snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host); - return (-1); - } - /* SMTP login is not available, so try without */ - else if (error > 0) { - syslog(LOG_WARNING, "SMTP login not available. Trying without."); + goto out; } } /* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */ - send_remote_command(fd, "MAIL FROM:<%s>", it->sender); - READ_REMOTE_CHECK("MAIL FROM", 2); + send_remote_command(&c, "MAIL FROM:<%s>", it->sender); + error = expect_response(&c, "MAIL FROM", 250); + if (error) + goto out; /* XXX send ESMTP ORCPT */ - send_remote_command(fd, "RCPT TO:<%s>", it->addr); - READ_REMOTE_CHECK("RCPT TO", 2); + send_remote_command(&c, "RCPT TO:<%s>", it->addr); + error = expect_response(&c, "RCPT TO", 250); + if (error) + goto out; - send_remote_command(fd, "DATA"); - READ_REMOTE_CHECK("DATA", 3); + send_remote_command(&c, "DATA"); + error = expect_response(&c, "DATA", 354); + if (error) + goto out; error = 0; - while (!feof(it->mailf)) { - if (fgets(line, sizeof(line), it->mailf) == NULL) - break; + while (fgets(line, sizeof(line), it->mailf)) { linelen = strlen(line); if (linelen == 0 || line[linelen - 1] != '\n') { syslog(LOG_CRIT, "remote delivery failed: corrupted queue file"); @@ -466,22 +774,30 @@ deliver_to_host(struct qitem *it, struct mx_hostentry *host) if (line[0] == '.') linelen++; - if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) { + if (send_remote_command(&c, "%s", line) != (ssize_t)linelen+1) { syslog(LOG_NOTICE, "remote delivery deferred: write error"); error = 1; goto out; } } + + if (ferror(it->mailf)) { + syslog(LOG_NOTICE, "remote delivery deferred: I/O read error, %m"); + error = 1; + goto out; + } - send_remote_command(fd, "."); - READ_REMOTE_CHECK("final DATA", 2); + send_remote_command(&c, "."); + error = expect_response(&c, "final DATA", 250); + if (error) + goto out; - send_remote_command(fd, "QUIT"); - if (read_remote(fd, 0, NULL) != 2) + send_remote_command(&c, "QUIT"); + if (read_remote(&c, NULL, NULL) != 221) syslog(LOG_INFO, "remote delivery succeeded but QUIT failed: %s", neterr); out: - close_connection(fd); + close_connection(&c); return (error); } @@ -490,7 +806,7 @@ deliver_remote(struct qitem *it) { struct mx_hostentry *hosts, *h; const char *host; - int port; + unsigned int port; int error = 1, smarthost = 0; host = strrchr(it->addr, '@'); diff --git a/spool.c b/spool.c index fa42654..10866f6 100644 --- a/spool.c +++ b/spool.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include "dma.h" @@ -159,7 +160,7 @@ readqueuef(struct queue *queue, char *queuefn) char *queueid = NULL, *sender = NULL, *addr = NULL; struct qitem *it = NULL; - bzero(&itmqueue, sizeof(itmqueue)); + memset(&itmqueue, 0, sizeof(itmqueue)); LIST_INIT(&itmqueue.queue); queuef = fopen(queuefn, "r"); @@ -284,7 +285,7 @@ load_queue(struct queue *queue) char *queuefn; char *mailfn; - bzero(queue, sizeof(*queue)); + memset(queue, 0, sizeof(*queue)); LIST_INIT(&queue->queue); spooldir = opendir(config.spooldir); diff --git a/util.c b/util.c index a139b20..09dfcfe 100644 --- a/util.c +++ b/util.c @@ -277,6 +277,9 @@ do_timeout(int timeout, int dojmp) int open_locked(const char *fname, int flags, ...) { +#ifndef O_EXLOCK + int fd, save_errno; +#endif int mode = 0; if (flags & O_CREAT) { @@ -287,8 +290,6 @@ open_locked(const char *fname, int flags, ...) } #ifndef O_EXLOCK - int fd, save_errno; - fd = open(fname, flags, mode); if (fd < 0) return(fd);