diff --git a/contrib/dma/Makefile b/contrib/dma/Makefile index aed2ef7246cf..8cae5b28f98b 100644 --- a/contrib/dma/Makefile +++ b/contrib/dma/Makefile @@ -1,111 +1,111 @@ # # Depending on your operating system, you might want to influence # the conditional inclusion of some helper functions: # # Define HAVE_* (in caps) if your system already provides: # reallocf # strlcpy # getprogname # SH?= sh version= $(shell ${SH} get-version.sh) debversion= $(shell ${SH} get-version.sh | sed -Ee 's/^v//;s/[.]([[:digit:]]+)[.](g[[:xdigit:]]+)$$/+\1+\2/') CC?= gcc CFLAGS?= -O -pipe LDADD?= -lssl -lcrypto -lresolv -CFLAGS+= -Wall -DDMA_VERSION='"${version}"' -DLIBEXEC_PATH='"${LIBEXEC}"' -DCONF_PATH='"${CONFDIR}"' +CFLAGS+= -Wall -Wno-format-truncation -DDMA_VERSION='"${version}"' -DLIBEXEC_PATH='"${LIBEXEC}"' -DCONF_PATH='"${CONFDIR}"' INSTALL?= install -p CHGRP?= chgrp CHMOD?= chmod PREFIX?= /usr/local SBIN?= ${PREFIX}/sbin LIBEXEC?= ${PREFIX}/lib CONFDIR?= /etc/dma MAN?= ${PREFIX}/share/man VAR?= /var DMASPOOL?= ${VAR}/spool/dma VARMAIL?= ${VAR}/mail SYMLINK?= -s # or empty to create hard link YACC?= yacc LEX?= lex LN?= ln OBJS= aliases_parse.o aliases_scan.o base64.o conf.o crypto.o OBJS+= dma.o dns.o local.o mail.o net.o spool.o util.o OBJS+= dfcompat.o all: dma dma-mbox-create clean: -rm -f .depend dma dma-mbox-create *.[do] -rm -f aliases_parse.[ch] aliases_scan.c install: all ${INSTALL} -d ${DESTDIR}${SBIN} ${INSTALL} -d ${DESTDIR}${MAN}/man8 ${DESTDIR}${LIBEXEC} ${INSTALL} -m 2755 -o root -g mail dma ${DESTDIR}${SBIN} ${INSTALL} -m 4754 -o root -g mail dma-mbox-create ${DESTDIR}${LIBEXEC} ${INSTALL} -m 0644 dma.8 ${DESTDIR}${MAN}/man8/ sendmail-link: cd ${DESTDIR}${SBIN} && ${LN} ${SYMLINK} dma sendmail mailq-link: cd ${DESTDIR}${SBIN} && ${LN} ${SYMLINK} dma mailq install-spool-dirs: ${INSTALL} -d -m 2775 -o root -g mail ${DESTDIR}${DMASPOOL} ${INSTALL} -d -m 2775 -o root -g mail ${DESTDIR}${VARMAIL} permissions: -${CHGRP} mail ${DESTDIR}${VARMAIL}/* -${CHMOD} g+w ${DESTDIR}${VARMAIL}/* -${CHMOD} 660 ${DESTDIR}${DMASPOOL}/flush install-etc: ${INSTALL} -d ${DESTDIR}${CONFDIR} @if [ -e ${DESTDIR}${CONFDIR}/dma.conf ]; then \ echo "Not overwriting ${DESTDIR}${CONFDIR}/dma.conf."; \ else \ echo ${INSTALL} -m 644 -o root -g mail dma.conf ${DESTDIR}${CONFDIR}; \ ${INSTALL} -m 644 -o root -g mail dma.conf ${DESTDIR}${CONFDIR}; \ fi @if [ -e ${DESTDIR}${CONFDIR}/auth.conf ]; then \ echo "Not overwriting ${DESTDIR}${CONFDIR}/auth.conf."; \ else \ echo ${INSTALL} -m 640 -o root -g mail auth.conf ${DESTDIR}${CONFDIR}; \ ${INSTALL} -m 640 -o root -g mail auth.conf ${DESTDIR}${CONFDIR}; \ fi aliases_parse.c: aliases_parse.y ${YACC} -d -o aliases_parse.c aliases_parse.y aliases_scan.c: aliases_scan.l ${LEX} -t aliases_scan.l > aliases_scan.c .SUFFIXES: .c .o .c.o: ${CC} ${CFLAGS} ${CPPFLAGS} -include dfcompat.h -o $@ -c $< dma: ${OBJS} ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} dch: dch --release-heuristic changelog -v ${debversion} ppa: @if [ -z '${DEB_DIST}' ]; then echo "please set DEB_DIST to build"; exit 1; fi dch -v "${debversion}~${DEB_DIST}" -D ${DEB_DIST} "${DEB_DIST} build" -b debuild -S -sa ver=$$(dpkg-parsechangelog -n1 | awk '$$1 == "Version:" { print $$2 }'); \ dput ppa:corecode/dma ../dma_$${ver}_source.changes diff --git a/contrib/dma/VERSION b/contrib/dma/VERSION index 5416288bc5ef..3c58828758cd 100644 --- a/contrib/dma/VERSION +++ b/contrib/dma/VERSION @@ -1 +1 @@ -v0.11 +v0.13 diff --git a/contrib/dma/conf.c b/contrib/dma/conf.c index b8a6a2e2cbd7..13cfac7a6de4 100644 --- a/contrib/dma/conf.c +++ b/contrib/dma/conf.c @@ -1,245 +1,261 @@ /* * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthias Schmidt , University of Marburg, * Germany. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "dma.h" #define DP ": \t" #define EQS " \t" /* * Remove trailing \n's */ void trim_line(char *line) { size_t linelen; char *p; if ((p = strchr(line, '\n'))) *p = (char)0; /* Escape leading dot in every case */ linelen = strlen(line); if (line[0] == '.') { if ((linelen + 2) > 1000) { syslog(LOG_CRIT, "Cannot escape leading dot. Buffer overflow"); exit(EX_DATAERR); } memmove((line + 1), line, (linelen + 1)); line[0] = '.'; } } static void chomp(char *str) { size_t len = strlen(str); if (len == 0) return; if (str[len - 1] == '\n') str[len - 1] = 0; } /* * Read the SMTP authentication config file * * file format is: * user|host:password * * A line starting with # is treated as comment and ignored. */ void parse_authfile(const char *path) { char line[2048]; struct authuser *au; FILE *a; char *data; int lineno = 0; a = fopen(path, "r"); if (a == NULL) { errlog(EX_NOINPUT, "can not open auth file `%s'", path); /* NOTREACHED */ } while (!feof(a)) { if (fgets(line, sizeof(line), a) == NULL) break; lineno++; chomp(line); /* We hit a comment */ if (*line == '#') continue; /* Ignore empty lines */ if (*line == 0) continue; au = calloc(1, sizeof(*au)); if (au == NULL) errlog(EX_OSERR, "calloc()"); data = strdup(line); au->login = strsep(&data, "|"); au->host = strsep(&data, DP); au->password = data; if (au->login == NULL || au->host == NULL || au->password == NULL) { errlogx(EX_CONFIG, "syntax error in authfile %s:%d", path, lineno); /* NOTREACHED */ } SLIST_INSERT_HEAD(&authusers, au, next); } fclose(a); } /* * XXX TODO * Check for bad things[TM] */ void parse_conf(const char *config_path) { char *word; char *data; FILE *conf; char line[2048]; int lineno = 0; conf = fopen(config_path, "r"); if (conf == NULL) { /* Don't treat a non-existing config file as error */ if (errno == ENOENT) return; errlog(EX_NOINPUT, "can not open config `%s'", config_path); /* NOTREACHED */ } while (!feof(conf)) { if (fgets(line, sizeof(line), conf) == NULL) break; lineno++; chomp(line); /* We hit a comment */ if (strchr(line, '#')) *strchr(line, '#') = 0; data = line; word = strsep(&data, EQS); /* Ignore empty lines */ if (word == NULL || *word == 0) continue; if (data != NULL && *data != 0) 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) config.aliases = data; else if (strcmp(word, "SPOOLDIR") == 0 && data != NULL) config.spooldir = data; else if (strcmp(word, "AUTHPATH") == 0 && data != NULL) config.authpath= data; else if (strcmp(word, "CERTFILE") == 0 && data != NULL) config.certfile = data; else if (strcmp(word, "MAILNAME") == 0 && data != NULL) config.mailname = data; else if (strcmp(word, "MASQUERADE") == 0 && data != NULL) { char *user = NULL, *host = NULL; if (strrchr(data, '@')) { host = strrchr(data, '@'); *host = 0; host++; user = data; } else { host = data; } if (host && *host == 0) host = NULL; if (user && *user == 0) user = NULL; config.masquerade_host = host; config.masquerade_user = user; } else if (strcmp(word, "STARTTLS") == 0 && data == NULL) config.features |= STARTTLS; - else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) + else if (strcmp(word, "FINGERPRINT") == 0) { + if (strlen(data) != SHA256_DIGEST_LENGTH * 2) { + errlogx(EX_CONFIG, "invalid sha256 fingerprint length"); + } + unsigned char *fingerprint = malloc(SHA256_DIGEST_LENGTH); + if (fingerprint == NULL) { + errlogx(EX_CONFIG, "fingerprint allocation failed"); + } + unsigned int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { + if(sscanf(data + 2 * i, "%02hhx", &fingerprint[i]) != 1) { + errlogx(EX_CONFIG, "failed to read fingerprint"); + } + } + free(data); + config.fingerprint = fingerprint; + } else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) config.features |= TLS_OPP; else if (strcmp(word, "SECURETRANSFER") == 0 && data == NULL) - config.features |= SECURETRANS; + config.features |= SECURETRANSFER; else if (strcmp(word, "DEFER") == 0 && data == NULL) config.features |= DEFER; else if (strcmp(word, "INSECURE") == 0 && data == NULL) config.features |= INSECURE; else if (strcmp(word, "FULLBOUNCE") == 0 && data == NULL) config.features |= FULLBOUNCE; else if (strcmp(word, "NULLCLIENT") == 0 && data == NULL) config.features |= NULLCLIENT; else { errlogx(EX_CONFIG, "syntax error in %s:%d", config_path, lineno); /* NOTREACHED */ } } if ((config.features & NULLCLIENT) && config.smarthost == NULL) { errlogx(EX_CONFIG, "%s: NULLCLIENT requires SMARTHOST", config_path); /* NOTREACHED */ } fclose(conf); } diff --git a/contrib/dma/crypto.c b/contrib/dma/crypto.c index 37b255c18310..368238b9d632 100644 --- a/contrib/dma/crypto.c +++ b/contrib/dma/crypto.c @@ -1,318 +1,351 @@ /* * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthias Schmidt , University of Marburg, * Germany. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include +#include #include #include #include "dma.h" static int init_cert_file(SSL_CTX *ctx, const char *path) { int error; /* Load certificate into ctx */ error = SSL_CTX_use_certificate_chain_file(ctx, path); if (error < 1) { syslog(LOG_ERR, "SSL: Cannot load certificate `%s': %s", path, ssl_errstr()); return (-1); } /* Add private key to ctx */ error = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM); if (error < 1) { syslog(LOG_ERR, "SSL: Cannot load private key `%s': %s", path, ssl_errstr()); return (-1); } /* * Check the consistency of a private key with the corresponding * certificate */ error = SSL_CTX_check_private_key(ctx); if (error < 1) { syslog(LOG_ERR, "SSL: Cannot check private key: %s", ssl_errstr()); return (-1); } return (0); } +static int +verify_server_fingerprint(const X509 *cert) +{ + unsigned char fingerprint[EVP_MAX_MD_SIZE] = {0}; + unsigned int fingerprint_len = 0; + if(!X509_digest(cert, EVP_sha256(), fingerprint, &fingerprint_len)) { + syslog(LOG_WARNING, "failed to load fingerprint of server certicate: %s", + ssl_errstr()); + return (1); + } + if(fingerprint_len != SHA256_DIGEST_LENGTH) { + syslog(LOG_WARNING, "sha256 fingerprint has unexpected length of %d bytes", + fingerprint_len); + return (1); + } + if(memcmp(fingerprint, config.fingerprint, SHA256_DIGEST_LENGTH) != 0) { + syslog(LOG_WARNING, "fingerprints do not match"); + return (1); + } + syslog(LOG_DEBUG, "successfully verified server certificate fingerprint"); + return (0); +} + int -smtp_init_crypto(int fd, int feature) +smtp_init_crypto(int fd, int feature, struct smtp_features* features) { SSL_CTX *ctx = NULL; #if (OPENSSL_VERSION_NUMBER >= 0x00909000L) const SSL_METHOD *meth = NULL; #else SSL_METHOD *meth = NULL; #endif X509 *cert; int error; /* XXX clean up on error/close */ /* Init SSL library */ SSL_library_init(); SSL_load_error_strings(); // Allow any possible version #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) meth = TLS_client_method(); #else meth = SSLv23_client_method(); #endif ctx = SSL_CTX_new(meth); if (ctx == NULL) { syslog(LOG_WARNING, "remote delivery deferred: SSL init failed: %s", ssl_errstr()); return (1); } /* User supplied a certificate */ if (config.certfile != NULL) { error = init_cert_file(ctx, config.certfile); if (error) { syslog(LOG_WARNING, "remote delivery deferred"); return (1); } } /* * If the user wants STARTTLS, we have to send EHLO here */ - if (((feature & SECURETRANS) != 0) && + if (((feature & SECURETRANSFER) != 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) { + if (perform_server_greeting(fd, features) == 0) { 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); } } + } else { + syslog(LOG_ERR, "remote delivery deferred: could not perform server greeting: %s", + neterr); + return (1); } + /* End of TLS init phase, enable SSL_write/read */ config.features &= ~NOSSL; } config.ssl = SSL_new(ctx); if (config.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); /* Set fd for SSL in/output */ error = SSL_set_fd(config.ssl, fd); if (error == 0) { syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s", ssl_errstr()); return (1); } /* Open SSL connection */ error = SSL_connect(config.ssl); - if (error < 0) { + if (error != 1) { syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s", ssl_errstr()); return (1); } /* Get peer certificate */ cert = SSL_get_peer_certificate(config.ssl); if (cert == NULL) { syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s", ssl_errstr()); + return (1); + } + if(config.fingerprint != NULL && verify_server_fingerprint(cert)) { + X509_free(cert); + return (1); } X509_free(cert); return (0); } /* * hmac_md5() taken out of RFC 2104. This RFC was written by H. Krawczyk, * M. Bellare and R. Canetti. * * text pointer to data stream * text_len length of data stream * key pointer to authentication key * key_len length of authentication key * digest caller digest to be filled int */ void hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len, unsigned char* digest) { MD5_CTX context; unsigned char k_ipad[65]; /* inner padding - * key XORd with ipad */ unsigned char k_opad[65]; /* outer padding - * key XORd with opad */ unsigned char tk[16]; int i; /* if key is longer than 64 bytes reset it to key=MD5(key) */ if (key_len > 64) { MD5_CTX tctx; MD5_Init(&tctx); MD5_Update(&tctx, key, key_len); MD5_Final(tk, &tctx); key = tk; key_len = 16; } /* * the HMAC_MD5 transform looks like: * * MD5(K XOR opad, MD5(K XOR ipad, text)) * * where K is an n byte key * ipad is the byte 0x36 repeated 64 times * * opad is the byte 0x5c repeated 64 times * and text is the data being protected */ /* 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); /* XOR key with ipad and opad values */ for (i=0; i<64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } /* * perform inner MD5 */ MD5_Init(&context); /* init context for 1st * pass */ MD5_Update(&context, k_ipad, 64); /* start with inner pad */ MD5_Update(&context, text, text_len); /* then text of datagram */ MD5_Final(digest, &context); /* finish up 1st pass */ /* * perform outer MD5 */ MD5_Init(&context); /* init context for 2nd * pass */ MD5_Update(&context, k_opad, 64); /* start with outer pad */ MD5_Update(&context, digest, 16); /* then results of 1st * hash */ MD5_Final(digest, &context); /* finish up 2nd pass */ } /* * CRAM-MD5 authentication */ int smtp_auth_md5(int fd, char *login, char *password) { unsigned char digest[BUF_SIZE]; char buffer[BUF_SIZE], ascii_digest[33]; char *temp; int len, i; 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); /* if cram-md5 is not available */ free(temp); return (-1); } /* skip 3 char status + 1 char space */ base64_decode(buffer + 4, temp); hmac_md5((unsigned char *)temp, strlen(temp), (unsigned char *)password, strlen(password), digest); free(temp); ascii_digest[32] = 0; for (i = 0; i < 16; i++) { ascii_digest[2*i] = hextab[digest[i] >> 4]; ascii_digest[2*i+1] = hextab[digest[i] & 15]; } /* prepare answer */ snprintf(buffer, BUF_SIZE, "%s %s", login, ascii_digest); /* encode answer */ len = base64_encode(buffer, strlen(buffer), &temp); if (len < 0) { syslog(LOG_ERR, "can not encode auth reply: %m"); return (-1); } /* send answer */ send_remote_command(fd, "%s", temp); free(temp); if (read_remote(fd, 0, NULL) != 2) { syslog(LOG_WARNING, "remote delivery deferred:" " AUTH cram-md5 failed: %s", neterr); return (-2); } return (0); } diff --git a/contrib/dma/dfcompat.c b/contrib/dma/dfcompat.c index 014fa88b8d49..d4ecc1d74ae9 100644 --- a/contrib/dma/dfcompat.c +++ b/contrib/dma/dfcompat.c @@ -1,122 +1,122 @@ #ifndef HAVE_STRLCPY /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ - * $FreeBSD$ + * $FreeBSD: src/lib/libc/string/strlcpy.c,v 1.10 2008/10/19 10:11:35 delphij Exp $ * $DragonFly: src/lib/libc/string/strlcpy.c,v 1.4 2005/09/18 16:32:34 asmodai Exp $ */ #include "dfcompat.h" #include #include /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } #endif /* !HAVE_STRLCPY */ #ifndef HAVE_REALLOCF /*- * Copyright (c) 1998, M. Warner Losh * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD$ + * $FreeBSD: src/lib/libc/stdlib/reallocf.c,v 1.3 1999/08/28 00:01:37 peter Exp $ * $DragonFly: src/lib/libc/stdlib/reallocf.c,v 1.2 2003/06/17 04:26:46 dillon Exp $ */ #include void * reallocf(void *ptr, size_t size) { void *nptr; nptr = realloc(ptr, size); - if (!nptr && ptr) + if (!nptr && ptr && size != 0) free(ptr); return (nptr); } #endif /* !HAVE_REALLOCF */ #ifndef HAVE_GETPROGNAME #ifdef __GLIBC__ #include const char * getprogname(void) { return (program_invocation_short_name); } #else /* __GLIBC__ */ #error "no getprogname implementation available" #endif #endif /* !HAVE_GETPROGNAME */ diff --git a/contrib/dma/dma.8 b/contrib/dma/dma.8 index cadf899e50fc..e0f5e79ff47d 100644 --- a/contrib/dma/dma.8 +++ b/contrib/dma/dma.8 @@ -1,365 +1,376 @@ .\" .\" Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. .\" Copyright (c) 2008 .\" The DragonFly Project. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in .\" the documentation and/or other materials provided with the .\" distribution. .\" 3. Neither the name of The DragonFly Project nor the names of its .\" contributors may be used to endorse or promote products derived .\" from this software without specific, prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS .\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE .\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, .\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; .\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED .\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, .\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .Dd May 20, 2017 .Dt DMA 8 .Os .Sh NAME .Nm dma .Nd DragonFly Mail Agent .Sh SYNOPSIS .Nm .Op Fl DiOt .Op Fl A Ns Ar mode .Op Fl b Ns Ar mode .Op Fl f Ar sender .Op Fl L Ar tag .Op Fl o Ns Ar option .Op Fl r Ar sender .Op Fl q Ns Op Ar arg .Op Ar recipient ... .Sh DESCRIPTION .Nm is a small Mail Transport Agent (MTA), designed for home and office use. It accepts mails from locally installed Mail User Agents (MUA) and delivers the mails either locally or to a remote destination. Remote delivery includes several features like TLS/SSL support and SMTP authentication. .Pp .Nm is not intended as a replacement for real, big MTAs like .Xr sendmail 8 or .Xr postfix 1 . Consequently, .Nm does not listen on port 25 for incoming connections. .Pp The options are as follows: .Bl -tag -width indent .It Fl A Ns Ar mode .Fl \&Ac acts as a compatibility option for sendmail. .It Fl b Ns Ar mode .Bl -tag -width indent .It Fl bp List all mails currently stored in the mail queue. .It Fl bq Queue the mail, but do not attempt to deliver it. See also the .Sq DEFER config file setting below. .El .Pp All other .Ar mode Ns s are ignored. .It Fl D Do not run in the background. Useful for debugging. .It Fl f Ar sender Set sender address (envelope-from) to .Ar sender . -This overrides the value of the environment variable -.Ev EMAIL . +This overrides the value of the +.Ev EMAIL +environment variable, but is overridden by the +.Sq MASQUERADE +config file setting. .It Fl i Ignore dots alone on lines by themselves in incoming messages. This should be set if you are reading data from a file. .It Fl L Ar tag Set the identifier used in syslog messages to the supplied .Ar tag . This is a compatibility option for sendmail. .It Fl O This is a compatibility option for sendmail. .It Fl o Ns Ar option Specifying .Fl oi is synonymous to .Fl i . All other options are ignored. .It Fl q Ns Op Ar arg Process saved messages in the queue. The argument is optional and ignored. .It Fl r Ar sender Same as .Fl f . .It Fl t Obtain recipient addresses from the message header. .Nm will parse the .Li To: , .Li Cc: , and .Li Bcc: headers. The .Li Bcc: header will be removed independent of whether .Fl t is specified or not. .El .Sh CONFIGURATION .Nm can be configured with two config files: .Pp .Bl -bullet -compact .It auth.conf .It dma.conf .El .Pp These two files are stored per default in .Pa /etc/dma . .Sh FILE FORMAT Every file contains parameters of the form .Sq name value . Lines containing boolean values are set to .Sq NO if the line is commented and to .Sq YES if the line is uncommented. Empty lines or lines beginning with a .Sq # are ignored. Parameter names and their values are case sensitive. .Sh PARAMETERS .Ss auth.conf SMTP authentication can be configured in .Pa auth.conf . Each line has the format .Dq Li user|smarthost:password . .Ss dma.conf Most of the behaviour of .Nm can be configured in .Pa dma.conf . .Bl -tag -width 4n .It Ic SMARTHOST Xo (string, default=empty) .Xc If you want to send outgoing mails via a smarthost, set this variable to your smarthosts address. .It Ic PORT Xo (numeric, default=25) .Xc Use this port to deliver remote emails. Only useful together with the .Sq SMARTHOST option, because .Nm will deliver all mails to this port, regardless of whether a smarthost is set or not. .It Ic ALIASES Xo (string, default=/etc/aliases) .Xc Path to the local aliases file. Just stick with the default. The aliases file is of the format .Dl nam: dest1 dest2 ... In this case, mails to .Li nam will instead be delivered to .Li dest1 and .Li dest2 , which in turn could be entries in .Pa /etc/aliases . The special name .Ql * can be used to create a catch-all alias, which gets used if no other matching alias is found. Use the catch-all alias only if you do not want any local mail to be delivered. .It Ic SPOOLDIR Xo (string, default=/var/spool/dma) .Xc Path to .Nm Ap s spool directory. Just stick with the default. .It Ic AUTHPATH Xo (string, default=not set) .Xc Path to the .Sq auth.conf file. .It Ic SECURETRANSFER Xo (boolean, default=commented) .Xc Uncomment if you want TLS/SSL secured transfer. .It Ic STARTTLS Xo (boolean, default=commented) .Xc Uncomment if you want to use STARTTLS. Only useful together with .Sq SECURETRANSFER . +.It Ic FINGERPRINT Xo +(string, default=empty) +.Xc +Pin the server certificate by specifying its SHA256 fingerprint. +Only makes sense if you use a smarthost. .It Ic OPPORTUNISTIC_TLS Xo (boolean, default=commented) .Xc Uncomment if you want to allow the STARTTLS negotiation to fail. Most useful when .Nm is used without a smarthost, delivering remote messages directly to 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 SECURETRANSFER and .Sq STARTTLS . .It Ic CERTFILE Xo (string, default=empty) .Xc Path to your SSL certificate file. .It Ic SECURE Xo (boolean, default=commented) .Xc Uncomment this entry and change it to .Sq INSECURE to use plain text SMTP login over an insecure connection. You have to rename this variable manually to prevent that you send your password accidentally over an insecure connection. .It Ic DEFER Xo (boolean, default=commented) .Xc Uncomment if you want that .Nm defers your mail. You have to flush your mail queue manually with the .Fl q option. This option is handy if you are behind a dialup line. .It Ic FULLBOUNCE Xo (boolean, default=commented) .Xc Uncomment if you want the bounce message to include the complete original message, not just the headers. .It Ic MAILNAME Xo (string, default=empty) .Xc The internet hostname .Nm uses to identify the host. If not set or empty, the result of .Xr gethostname 3 is used. If .Sq MAILNAME is an absolute path to a file, the first line of this file will be used as the hostname. .It Ic MASQUERADE Xo (string, default=empty) .Xc Masquerade the envelope-from addresses with this address/hostname. Use this setting if mails are not accepted by destination mail servers because your sender domain is invalid. -This setting is overridden by the +This setting overrides the .Fl f flag and the .Ev EMAIL environment variable. .Pp If .Sq MASQUERADE does not contain a .Li @ sign, the string is interpreted as a host name. For example, setting .Sq MASQUERADE to .Ql john@ on host .Ql hamlet will send all mails as .Ql john@hamlet ; setting it to .Ql percolator will send all mails as .Sm off .Ql Va username @percolator . .Sm on .It Ic NULLCLIENT Xo +(boolean, default=commented) .Xc Bypass aliases and local delivery, and instead forward all mails to the defined .Sq SMARTHOST . .Sq NULLCLIENT requires .Sq SMARTHOST to be set. .El .Ss Environment variables The behavior of .Nm can be influenced by some environment variables. .Bl -tag -width 4n .It Ev EMAIL Xo .Xc Used to set the sender address (envelope-from). Use a plain address, in the form of .Li user@example.com . This value will be overridden when the +.Sq MASQUERADE +config file setting or the .Fl f flag is used. .El .Sh SEE ALSO .Xr mailaddr 7 , .Xr mailwrapper 8 , .Xr sendmail 8 .Rs .%A "J. B. Postel" .%T "Simple Mail Transfer Protocol" .%O RFC 821 .Re .Rs .%A "J. Myers" .%T "SMTP Service Extension for Authentication" .%O RFC 2554 .Re .Rs .%A "P. Hoffman" .%T "SMTP Service Extension for Secure SMTP over TLS" .%O RFC 2487 .Re .Sh HISTORY The .Nm utility first appeared in .Dx 1.11 . .Sh AUTHORS .An -nosplit .Nm was written by .An Matthias Schmidt Aq Mt matthias@dragonflybsd.org and .An Simon Schubert Aq Mt 2@0x2c.org . diff --git a/contrib/dma/dma.c b/contrib/dma/dma.c index b553c0fa0eef..72115ae2b55e 100644 --- a/contrib/dma/dma.c +++ b/contrib/dma/dma.c @@ -1,633 +1,635 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "dfcompat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dma.h" static void deliver(struct qitem *); struct aliases aliases = LIST_HEAD_INITIALIZER(aliases); struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs); struct authusers authusers = LIST_HEAD_INITIALIZER(authusers); char username[USERNAME_SIZE]; uid_t useruid; const char *logident_base; char errmsg[ERRMSG_SIZE]; static int daemonize = 1; static int doqueue = 0; struct config config = { .smarthost = NULL, .port = 25, .aliases = "/etc/aliases", .spooldir = "/var/spool/dma", .authpath = NULL, .certfile = NULL, .features = 0, .mailname = NULL, .masquerade_host = NULL, .masquerade_user = NULL, + .fingerprint = NULL, }; static void sighup_handler(int signo) { (void)signo; /* so that gcc doesn't complain */ } static char * set_from(struct queue *queue, const char *osender) { const char *addr; char *sender; - if (osender) { + if (config.masquerade_user) { + addr = config.masquerade_user; + } else if (osender) { addr = osender; } else if (getenv("EMAIL") != NULL) { addr = getenv("EMAIL"); } else { - if (config.masquerade_user) - addr = config.masquerade_user; - else - addr = username; + addr = username; } if (!strchr(addr, '@')) { const char *from_host = hostname(); if (config.masquerade_host) from_host = config.masquerade_host; if (asprintf(&sender, "%s@%s", addr, from_host) <= 0) return (NULL); } else { sender = strdup(addr); if (sender == NULL) return (NULL); } if (strchr(sender, '\n') != NULL) { errno = EINVAL; return (NULL); } queue->sender = sender; return (sender); } static int read_aliases(void) { yyin = fopen(config.aliases, "r"); if (yyin == NULL) { /* * Non-existing aliases file is not a fatal error */ if (errno == ENOENT) return (0); /* Other problems are. */ return (-1); } if (yyparse()) return (-1); /* fatal error, probably malloc() */ fclose(yyin); return (0); } static int do_alias(struct queue *queue, const char *addr) { struct alias *al; struct stritem *sit; int aliased = 0; LIST_FOREACH(al, &aliases, next) { if (strcmp(al->alias, addr) != 0) continue; SLIST_FOREACH(sit, &al->dests, next) { if (add_recp(queue, sit->str, EXPAND_ADDR) != 0) return (-1); } aliased = 1; } return (aliased); } int add_recp(struct queue *queue, const char *str, int expand) { struct qitem *it, *tit; struct passwd *pw; char *host; int aliased = 0; it = calloc(1, sizeof(*it)); if (it == NULL) return (-1); it->addr = strdup(str); if (it->addr == NULL) return (-1); it->sender = queue->sender; host = strrchr(it->addr, '@'); if (host != NULL && (strcmp(host + 1, hostname()) == 0 || strcmp(host + 1, "localhost") == 0)) { *host = 0; } LIST_FOREACH(tit, &queue->queue, next) { /* weed out duplicate dests */ if (strcmp(tit->addr, it->addr) == 0) { free(it->addr); free(it); return (0); } } LIST_INSERT_HEAD(&queue->queue, it, next); /** * Do local delivery if there is no @. * Do not do local delivery when NULLCLIENT is set. */ if (strrchr(it->addr, '@') == NULL && (config.features & NULLCLIENT) == 0) { it->remote = 0; if (expand) { aliased = do_alias(queue, it->addr); if (!aliased && expand == EXPAND_WILDCARD) aliased = do_alias(queue, "*"); if (aliased < 0) return (-1); if (aliased) { LIST_REMOVE(it, next); } else { /* Local destination, check */ pw = getpwnam(it->addr); if (pw == NULL) goto out; /* XXX read .forward */ endpwent(); } } } else { it->remote = 1; } return (0); out: free(it->addr); free(it); return (-1); } static struct qitem * go_background(struct queue *queue) { struct sigaction sa; struct qitem *it; pid_t pid; if (daemonize && daemon(0, 0) != 0) { syslog(LOG_ERR, "can not daemonize: %m"); exit(EX_OSERR); } daemonize = 0; bzero(&sa, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); LIST_FOREACH(it, &queue->queue, next) { /* No need to fork for the last dest */ if (LIST_NEXT(it, next) == NULL) goto retit; pid = fork(); switch (pid) { case -1: syslog(LOG_ERR, "can not fork: %m"); exit(EX_OSERR); break; case 0: /* * Child: * * return and deliver mail */ retit: /* * If necessary, acquire the queue and * mail files. * If this fails, we probably were raced by another * process. It is okay to be raced if we're supposed * to flush the queue. */ setlogident("%s", it->queueid); switch (acquirespool(it)) { case 0: break; case 1: if (doqueue) exit(EX_OK); syslog(LOG_WARNING, "could not lock queue file"); exit(EX_SOFTWARE); default: exit(EX_SOFTWARE); } dropspool(queue, it); return (it); default: /* * Parent: * * fork next child */ break; } } syslog(LOG_CRIT, "reached dead code"); exit(EX_SOFTWARE); } static void deliver(struct qitem *it) { int error; unsigned int backoff = MIN_RETRY, slept; struct timeval now; struct stat st; snprintf(errmsg, sizeof(errmsg), "unknown bounce reason"); retry: syslog(LOG_INFO, "<%s> trying delivery", it->addr); if (it->remote) error = deliver_remote(it); else error = deliver_local(it); switch (error) { case 0: syslog(LOG_INFO, "<%s> delivery successful", it->addr); delqueue(it); exit(EX_OK); case 1: if (stat(it->queuefn, &st) != 0) { syslog(LOG_ERR, "lost queue file `%s'", it->queuefn); exit(EX_SOFTWARE); } if (gettimeofday(&now, NULL) == 0 && (now.tv_sec - st.st_mtim.tv_sec > MAX_TIMEOUT)) { snprintf(errmsg, sizeof(errmsg), "Could not deliver for the last %d seconds. Giving up.", MAX_TIMEOUT); goto bounce; } for (slept = 0; slept < backoff;) { slept += SLEEP_TIMEOUT - sleep(SLEEP_TIMEOUT); if (flushqueue_since(slept)) { backoff = MIN_RETRY; goto retry; } } if (slept >= backoff) { /* pick the next backoff between [1.5, 2.5) times backoff */ backoff = backoff + backoff / 2 + random() % backoff; if (backoff > MAX_RETRY) backoff = MAX_RETRY; } goto retry; case -1: default: break; } bounce: bounce(it, errmsg); /* NOTREACHED */ } void run_queue(struct queue *queue) { struct qitem *it; if (LIST_EMPTY(&queue->queue)) return; it = go_background(queue); deliver(it); /* NOTREACHED */ } static void show_queue(struct queue *queue) { struct qitem *it; int locked = 0; /* XXX */ if (LIST_EMPTY(&queue->queue)) { printf("Mail queue is empty\n"); return; } LIST_FOREACH(it, &queue->queue, next) { printf("ID\t: %s%s\n" "From\t: %s\n" "To\t: %s\n", it->queueid, locked ? "*" : "", it->sender, it->addr); if (LIST_NEXT(it, next) != NULL) printf("--\n"); } } /* * TODO: * * - alias processing * - use group permissions * - proper sysexit codes */ int main(int argc, char **argv) { struct sigaction act; char *sender = NULL; + char *own_name = NULL; struct queue queue; int i, ch; - int nodot = 0, showq = 0, queue_only = 0; + int nodot = 0, showq = 0, queue_only = 0, newaliases = 0; int recp_from_header = 0; set_username(); /* * We never run as root. If called by root, drop permissions * to the mail user. */ if (geteuid() == 0 || getuid() == 0) { struct passwd *pw; errno = 0; pw = getpwnam(DMA_ROOT_USER); if (pw == NULL) { if (errno == 0) errx(EX_CONFIG, "user '%s' not found", DMA_ROOT_USER); else err(EX_OSERR, "cannot drop root privileges"); } if (setuid(pw->pw_uid) != 0) err(EX_OSERR, "cannot drop root privileges"); if (geteuid() == 0 || getuid() == 0) errx(EX_OSERR, "cannot drop root privileges"); } atexit(deltmp); init_random(); bzero(&queue, sizeof(queue)); LIST_INIT(&queue.queue); - if (strcmp(basename(argv[0]), "mailq") == 0) { + own_name = basename(argv[0]); + + if (strcmp(own_name, "mailq") == 0) { argv++; argc--; showq = 1; if (argc != 0) errx(EX_USAGE, "invalid arguments"); goto skipopts; - } else if (strcmp(argv[0], "newaliases") == 0) { - logident_base = "dma"; - setlogident("%s", logident_base); - - if (read_aliases() != 0) - errx(EX_SOFTWARE, "could not parse aliases file `%s'", config.aliases); - exit(EX_OK); + } else if (strcmp(own_name, "newaliases") == 0) { + newaliases = 1; + goto skipopts; } opterr = 0; while ((ch = getopt(argc, argv, ":A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:tUV:vX:")) != -1) { switch (ch) { case 'A': /* -AX is being ignored, except for -A{c,m} */ if (optarg[0] == 'c' || optarg[0] == 'm') { break; } - /* else FALLTRHOUGH */ + /* Else FALLTHROUGH */ case 'b': /* -bX is being ignored, except for -bp */ if (optarg[0] == 'p') { showq = 1; break; } else if (optarg[0] == 'q') { queue_only = 1; break; } - /* else FALLTRHOUGH */ + /* Else FALLTHROUGH */ case 'D': daemonize = 0; break; case 'L': logident_base = optarg; break; case 'f': case 'r': sender = optarg; break; case 't': recp_from_header = 1; break; case 'o': /* -oX is being ignored, except for -oi */ if (optarg[0] != 'i') break; - /* else FALLTRHOUGH */ + /* Else FALLTHROUGH */ case 'O': break; case 'i': nodot = 1; break; case 'q': /* Don't let getopt slup up other arguments */ if (optarg && *optarg == '-') optind--; doqueue = 1; break; /* Ignored options */ case 'B': case 'C': case 'd': case 'F': case 'h': case 'N': case 'n': case 'R': case 'U': case 'V': case 'v': case 'X': break; case ':': if (optopt == 'q') { doqueue = 1; break; } - /* FALLTHROUGH */ + /* Else FALLTHROUGH */ default: fprintf(stderr, "invalid argument: `-%c'\n", optopt); exit(EX_USAGE); } } argc -= optind; argv += optind; opterr = 1; if (argc != 0 && (showq || doqueue)) errx(EX_USAGE, "sending mail and queue operations are mutually exclusive"); if (showq + doqueue > 1) errx(EX_USAGE, "conflicting queue operations"); skipopts: if (logident_base == NULL) logident_base = "dma"; setlogident("%s", logident_base); act.sa_handler = sighup_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); if (sigaction(SIGHUP, &act, NULL) != 0) syslog(LOG_WARNING, "can not set signal handler: %m"); parse_conf(CONF_PATH "/dma.conf"); if (config.authpath != NULL) parse_authfile(config.authpath); if (showq) { if (load_queue(&queue) < 0) errlog(EX_NOINPUT, "can not load queue"); show_queue(&queue); return (0); } if (doqueue) { flushqueue_signal(); if (load_queue(&queue) < 0) errlog(EX_NOINPUT, "can not load queue"); run_queue(&queue); return (0); } if (read_aliases() != 0) errlog(EX_SOFTWARE, "could not parse aliases file `%s'", config.aliases); + if (newaliases) + return(0); + if ((sender = set_from(&queue, sender)) == NULL) errlog(EX_SOFTWARE, "set_from()"); if (newspoolf(&queue) != 0) errlog(EX_CANTCREAT, "can not create temp file in `%s'", config.spooldir); setlogident("%s", queue.id); for (i = 0; i < argc; i++) { if (add_recp(&queue, argv[i], EXPAND_WILDCARD) != 0) errlogx(EX_DATAERR, "invalid recipient `%s'", argv[i]); } if (LIST_EMPTY(&queue.queue) && !recp_from_header) errlogx(EX_NOINPUT, "no recipients"); if (readmail(&queue, nodot, recp_from_header) != 0) errlog(EX_NOINPUT, "can not read mail"); if (LIST_EMPTY(&queue.queue)) errlogx(EX_NOINPUT, "no recipients"); if (linkspool(&queue) != 0) errlog(EX_CANTCREAT, "can not create spools"); /* From here on the mail is safe. */ if (config.features & DEFER || queue_only) return (0); run_queue(&queue); /* NOTREACHED */ return (0); } diff --git a/contrib/dma/dma.conf b/contrib/dma/dma.conf index 1cc2bf5bc843..fa95fc1a0c22 100644 --- a/contrib/dma/dma.conf +++ b/contrib/dma/dma.conf @@ -1,66 +1,70 @@ # $DragonFly: src/etc/dma/dma.conf,v 1.2 2008/02/04 10:11:41 matthias Exp $ # # Your smarthost (also called relayhost). Leave blank if you don't want # smarthost support. # NOTE: on Debian systems this is handled via debconf! # Please use dpkg-reconfigure dma to change this value. #SMARTHOST # Use this SMTP port. Most users will be fine with the default (25) #PORT 25 # Path to your alias file. Just stay with the default. #ALIASES /etc/aliases # Path to your spooldir. Just stay with the default. #SPOOLDIR /var/spool/dma # SMTP authentication #AUTHPATH /etc/dma/auth.conf -# Uncomment if yout want TLS/SSL support +# Uncomment if you want TLS/SSL support #SECURETRANSFER # Uncomment if you want STARTTLS support (only used in combination with # SECURETRANSFER) #STARTTLS +# Pin the server certificate by specifying its SHA256 fingerprint. +# Only makes sense if you use a smarthost. +#FINGERPRINT 1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF + # 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) #OPPORTUNISTIC_TLS # Path to your local SSL certificate #CERTFILE # If you want to use plain text SMTP login without using encryption, change # the SECURE entry below to INSECURE. Otherwise plain login will only work # over a secure connection. Use this option with caution. #SECURE # Uncomment if you want to defer your mails. This is useful if you are # behind a dialup line. You have to submit your mails manually with dma -q #DEFER # Uncomment if you want the bounce message to include the complete original # message, not just the headers. #FULLBOUNCE # The internet hostname dma uses to identify the host. # If not set or empty, the result of gethostname(2) is used. # If MAILNAME is an absolute path to a file, the first line of this file # will be used as the hostname. #MAILNAME mail.example.net # Masquerade envelope from addresses with this address/hostname. # Use this if mails are not accepted by destination mail servers because # your sender domain is invalid. # By default, MASQUERADE is not set. # Format: MASQUERADE [user@][host] # Examples: # MASQUERADE john@ on host "hamlet" will send all mails as john@hamlet # MASQUERADE percolator will send mails as $username@percolator, e.g. fish@percolator # MASQUERADE herb@ert will send all mails as herb@ert # Directly forward the mail to the SMARTHOST bypassing aliases and local delivery #NULLCLIENT diff --git a/contrib/dma/dma.h b/contrib/dma/dma.h index 593417617d3d..9e7f6cd2c431 100644 --- a/contrib/dma/dma.h +++ b/contrib/dma/dma.h @@ -1,241 +1,254 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org> and * Matthias Schmidt . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef DMA_H #define DMA_H #include #include #include #include #include #include #include #include #define VERSION "DragonFly Mail Agent " DMA_VERSION #define BUF_SIZE 2048 #define ERRMSG_SIZE 1024 #define USERNAME_SIZE 50 +#define EHLO_RESPONSE_SIZE BUF_SIZE #define MIN_RETRY 300 /* 5 minutes */ #define MAX_RETRY (3*60*60) /* retry at least every 3 hours */ #define MAX_TIMEOUT (5*24*60*60) /* give up after 5 days */ #define SLEEP_TIMEOUT 30 /* check for queue flush every 30 seconds */ #ifndef PATH_MAX #define PATH_MAX 1024 /* Max path len */ #endif #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 SECURETRANSFER 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 NULLCLIENT 0x100 /* Nullclient support */ #ifndef CONF_PATH #error Please define CONF_PATH #endif #ifndef LIBEXEC_PATH #error Please define LIBEXEC_PATH #endif #define SPOOL_FLUSHFILE "flush" #ifndef DMA_ROOT_USER #define DMA_ROOT_USER "mail" #endif #ifndef DMA_GROUP #define DMA_GROUP "mail" #endif #ifndef MBOX_STRICT #define MBOX_STRICT 0 #endif struct stritem { SLIST_ENTRY(stritem) next; char *str; }; SLIST_HEAD(strlist, stritem); struct alias { LIST_ENTRY(alias) next; char *alias; struct strlist dests; }; LIST_HEAD(aliases, alias); struct qitem { LIST_ENTRY(qitem) next; const char *sender; char *addr; char *queuefn; char *mailfn; char *queueid; FILE *queuef; FILE *mailf; int remote; }; LIST_HEAD(queueh, qitem); struct queue { struct queueh queue; char *id; FILE *mailf; char *tmpf; const char *sender; }; struct config { const char *smarthost; int port; const char *aliases; const char *spooldir; const char *authpath; const char *certfile; int features; const char *mailname; const char *masquerade_host; const char *masquerade_user; + const unsigned char *fingerprint; /* XXX does not belong into config */ SSL *ssl; }; struct authuser { SLIST_ENTRY(authuser) next; char *login; char *password; char *host; }; SLIST_HEAD(authusers, authuser); struct mx_hostentry { char host[MAXDNAME]; char addr[INET6_ADDRSTRLEN]; int pref; struct addrinfo ai; struct sockaddr_storage sa; }; +struct smtp_auth_mechanisms { + int cram_md5; + int login; +}; + +struct smtp_features { + struct smtp_auth_mechanisms auth; + int starttls; +}; /* global variables */ extern struct aliases aliases; extern struct config config; extern struct strlist tmpfs; extern struct authusers authusers; extern char username[USERNAME_SIZE]; extern uid_t useruid; extern const char *logident_base; extern char neterr[ERRMSG_SIZE]; extern char errmsg[ERRMSG_SIZE]; /* aliases_parse.y */ int yyparse(void); int yywrap(void); int yylex(void); extern FILE *yyin; /* conf.c */ void trim_line(char *); void parse_conf(const char *); 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_init_crypto(int, int, struct smtp_features*); /* dns.c */ int dns_get_mx_list(const char *, 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 perform_server_greeting(int, struct smtp_features*); int deliver_remote(struct qitem *); /* base64.c */ int base64_encode(const void *, int, char **); int base64_decode(const char *, void *); /* dma.c */ #define EXPAND_ADDR 1 #define EXPAND_WILDCARD 2 int add_recp(struct queue *, const char *, int); void run_queue(struct queue *); /* spool.c */ int newspoolf(struct queue *); int linkspool(struct queue *); int load_queue(struct queue *); void delqueue(struct qitem *); int acquirespool(struct qitem *); void dropspool(struct queue *, struct qitem *); int flushqueue_since(unsigned int); int flushqueue_signal(void); /* local.c */ int deliver_local(struct qitem *); /* mail.c */ void bounce(struct qitem *, const char *); int readmail(struct queue *, int, int); /* util.c */ const char *hostname(void); +const char *systemhostname(void); void setlogident(const char *, ...) __attribute__((__format__ (__printf__, 1, 2))); void errlog(int, const char *, ...) __attribute__((__format__ (__printf__, 2, 3))); void errlogx(int, const char *, ...) __attribute__((__format__ (__printf__, 2, 3))); void set_username(void); void deltmp(void); int do_timeout(int, int); int open_locked(const char *, int, ...); char *rfc822date(void); int strprefixcmp(const char *, const char *); void init_random(void); #endif diff --git a/contrib/dma/dns.c b/contrib/dma/dns.c index bd28c4db724c..449e6b463caa 100644 --- a/contrib/dma/dns.c +++ b/contrib/dma/dns.c @@ -1,298 +1,293 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "dma.h" static int sort_pref(const void *a, const void *b) { const struct mx_hostentry *ha = a, *hb = b; int v; /* sort increasing by preference primarily */ v = ha->pref - hb->pref; if (v != 0) return (v); /* sort PF_INET6 before PF_INET */ v = - (ha->ai.ai_family - hb->ai.ai_family); return (v); } static int add_host(int pref, const char *host, int port, struct mx_hostentry **he, size_t *ps) { struct addrinfo hints, *res, *res0 = NULL; char servname[10]; struct mx_hostentry *p; const int count_inc = 10; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; snprintf(servname, sizeof(servname), "%d", port); switch (getaddrinfo(host, servname, &hints, &res0)) { case 0: break; case EAI_AGAIN: case EAI_NONAME: /* * EAI_NONAME gets returned for: * SMARTHOST set but DNS server not reachable -> defer * SMARTHOST set but DNS server returns "host does not exist" * -> buggy configuration * -> either defer or bounce would be ok -> defer * MX entry was returned by DNS server but name doesn't resolve * -> hopefully transient situation -> defer * all other DNS problems should have been caught earlier * in dns_get_mx_list(). */ goto out; default: return(-1); } for (res = res0; res != NULL; res = res->ai_next) { if (*ps + 1 >= roundup(*ps, count_inc)) { size_t newsz = roundup(*ps + 2, count_inc); *he = reallocf(*he, newsz * sizeof(**he)); if (*he == NULL) goto out; } p = &(*he)[*ps]; strlcpy(p->host, host, sizeof(p->host)); p->pref = pref; p->ai = *res; p->ai.ai_addr = NULL; bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen); getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen, p->addr, sizeof(p->addr), NULL, 0, NI_NUMERICHOST); (*ps)++; } freeaddrinfo(res0); return (0); out: if (res0 != NULL) freeaddrinfo(res0); return (1); } int dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx) { char outname[MAXDNAME]; ns_msg msg; ns_rr rr; const char *searchhost; const unsigned char *cp; unsigned char *ans; struct mx_hostentry *hosts = NULL; size_t nhosts = 0; size_t anssz; int pref; int cname_recurse; int have_mx = 0; int err; int i; res_init(); searchhost = host; cname_recurse = 0; anssz = 65536; ans = malloc(anssz); if (ans == NULL) return (1); if (no_mx) goto out; repeat: err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz); if (err < 0) { switch (h_errno) { case NO_DATA: /* * Host exists, but no MX (or CNAME) entry. * Not an error, use host name instead. */ goto out; case TRY_AGAIN: /* transient error */ goto transerr; case NO_RECOVERY: case HOST_NOT_FOUND: default: errno = ENOENT; goto err; } } if (!ns_initparse(ans, anssz, &msg)) goto transerr; switch (ns_msg_getflag(msg, ns_f_rcode)) { case ns_r_noerror: break; case ns_r_nxdomain: goto err; default: goto transerr; } for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) { if (ns_parserr(&msg, ns_s_an, i, &rr)) goto transerr; cp = ns_rr_rdata(rr); switch (ns_rr_type(rr)) { case ns_t_mx: have_mx = 1; pref = ns_get16(cp); cp += 2; err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), cp, outname, sizeof(outname)); if (err < 0) goto transerr; err = add_host(pref, outname, port, &hosts, &nhosts); if (err == -1) goto err; break; case ns_t_cname: err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), cp, outname, sizeof(outname)); if (err < 0) goto transerr; /* Prevent a CNAME loop */ if (cname_recurse++ > 10) goto err; searchhost = outname; goto repeat; default: break; } } out: err = 0; if (0) { transerr: if (nhosts == 0) err = 1; } if (0) { err: err = -1; } free(ans); if (err == 0) { if (!have_mx) { /* * If we didn't find any MX, use the hostname instead. */ err = add_host(0, host, port, &hosts, &nhosts); } else if (nhosts == 0) { /* * We did get MX, but couldn't resolve any of them * due to transient errors. */ err = 1; } } if (nhosts > 0) { qsort(hosts, nhosts, sizeof(*hosts), sort_pref); /* terminate list */ *hosts[nhosts].host = 0; } else { if (hosts != NULL) free(hosts); hosts = NULL; } *he = hosts; return (err); - - free(ans); - if (hosts != NULL) - free(hosts); - return (err); } #if defined(TESTING) int main(int argc, char **argv) { struct mx_hostentry *he, *p; int err; err = dns_get_mx_list(argv[1], 53, &he, 0); if (err) return (err); for (p = he; *p->host != 0; p++) { printf("%d\t%s\t%s\n", p->pref, p->host, p->addr); } return (0); } #endif diff --git a/contrib/dma/get-version.sh b/contrib/dma/get-version.sh old mode 100755 new mode 100644 diff --git a/contrib/dma/local.c b/contrib/dma/local.c index b6c4180fc5dc..2c3483ea0380 100644 --- a/contrib/dma/local.c +++ b/contrib/dma/local.c @@ -1,255 +1,256 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include "dma.h" static int create_mbox(const char *name) { struct sigaction sa, osa; pid_t child, waitchild; int status; int i; long maxfd; int e; int r = -1; /* * We need to enable SIGCHLD temporarily so that waitpid works. */ bzero(&sa, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(SIGCHLD, &sa, &osa); do_timeout(100, 0); child = fork(); switch (child) { case 0: /* child */ maxfd = sysconf(_SC_OPEN_MAX); if (maxfd == -1) maxfd = 1024; /* what can we do... */ for (i = 3; i <= maxfd; ++i) close(i); - execl(LIBEXEC_PATH "/dma-mbox-create", "dma-mbox-create", name, NULL); + execl(LIBEXEC_PATH "/dma-mbox-create", "dma-mbox-create", name, (char *)NULL); syslog(LOG_ERR, "cannot execute "LIBEXEC_PATH"/dma-mbox-create: %m"); exit(EX_SOFTWARE); default: /* parent */ waitchild = waitpid(child, &status, 0); e = errno; do_timeout(0, 0); if (waitchild == -1 && e == EINTR) { syslog(LOG_ERR, "hung child while creating mbox `%s': %m", name); break; } if (waitchild == -1) { syslog(LOG_ERR, "child disappeared while creating mbox `%s': %m", name); break; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { syslog(LOG_ERR, "error creating mbox `%s'", name); break; } /* success */ r = 0; break; case -1: /* error */ syslog(LOG_ERR, "error creating mbox"); break; } sigaction(SIGCHLD, &osa, NULL); return (r); } int deliver_local(struct qitem *it) { char fn[PATH_MAX+1]; char line[1000]; const char *sender; const char *newline = "\n"; size_t linelen; int tries = 0; int mbox; int error; int hadnl = 0; off_t mboxlen; time_t now = time(NULL); error = snprintf(fn, sizeof(fn), "%s/%s", _PATH_MAILDIR, it->addr); if (error < 0 || (size_t)error >= sizeof(fn)) { syslog(LOG_NOTICE, "local delivery deferred: %m"); return (1); } retry: /* wait for a maximum of 100s to get the lock to the file */ do_timeout(100, 0); /* don't use O_CREAT here, because we might be running as the wrong user. */ mbox = open_locked(fn, O_WRONLY|O_APPEND); if (mbox < 0) { int e = errno; do_timeout(0, 0); switch (e) { case EACCES: case ENOENT: /* * The file does not exist or we can't access it. * Call dma-mbox-create to create it and fix permissions. */ if (tries > 0 || create_mbox(it->addr) != 0) { syslog(LOG_ERR, "local delivery deferred: can not create `%s'", fn); return (1); } ++tries; goto retry; case EINTR: syslog(LOG_NOTICE, "local delivery deferred: can not lock `%s'", fn); break; default: syslog(LOG_NOTICE, "local delivery deferred: can not open `%s': %m", fn); break; } return (1); } do_timeout(0, 0); mboxlen = lseek(mbox, 0, SEEK_END); /* New mails start with \nFrom ...., unless we're at the beginning of the mbox */ if (mboxlen == 0) newline = ""; /* If we're bouncing a message, claim it comes from MAILER-DAEMON */ sender = it->sender; if (strcmp(sender, "") == 0) sender = "MAILER-DAEMON"; if (fseek(it->mailf, 0, SEEK_SET) != 0) { syslog(LOG_NOTICE, "local delivery deferred: can not seek: %m"); goto out; } error = snprintf(line, sizeof(line), "%sFrom %s %s", newline, sender, ctime(&now)); if (error < 0 || (size_t)error >= sizeof(line)) { syslog(LOG_NOTICE, "local delivery deferred: can not write header: %m"); goto out; } if (write(mbox, line, error) != error) goto wrerror; while (!feof(it->mailf)) { if (fgets(line, sizeof(line), it->mailf) == NULL) break; linelen = strlen(line); if (linelen == 0 || line[linelen - 1] != '\n') { syslog(LOG_CRIT, "local delivery failed: corrupted queue file"); snprintf(errmsg, sizeof(errmsg), "corrupted queue file"); error = -1; goto chop; } /* * mboxro processing: * - escape lines that start with "From " with a > sign. - * - be reversable by escaping lines that contain an arbitrary + * - be reversible by escaping lines that contain an arbitrary * number of > signs, followed by "From ", i.e. />*From / in regexp. * - strict mbox processing only requires escaping after empty lines, * yet most MUAs seem to relax this requirement and will treat any * line starting with "From " as the beginning of a new mail. */ if ((!MBOX_STRICT || hadnl) && strncmp(&line[strspn(line, ">")], "From ", 5) == 0) { const char *gt = ">"; if (write(mbox, gt, 1) != 1) goto wrerror; hadnl = 0; } else if (strcmp(line, "\n") == 0) { hadnl = 1; } else { hadnl = 0; } if ((size_t)write(mbox, line, linelen) != linelen) goto wrerror; } close(mbox); return (0); wrerror: syslog(LOG_ERR, "local delivery failed: write error: %m"); error = 1; chop: if (ftruncate(mbox, mboxlen) != 0) syslog(LOG_WARNING, "error recovering mbox `%s': %m", fn); out: close(mbox); return (error); } diff --git a/contrib/dma/mail.c b/contrib/dma/mail.c index ad928272e1a1..48c8ee6d4dd2 100644 --- a/contrib/dma/mail.c +++ b/contrib/dma/mail.c @@ -1,492 +1,518 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include +#include #include #include #include #include "dma.h" #define MAX_LINE_RFC822 1000 void bounce(struct qitem *it, const char *reason) { struct queue bounceq; char line[1000]; size_t pos; int error; /* Don't bounce bounced mails */ if (it->sender[0] == 0) { syslog(LOG_INFO, "can not bounce a bounce message, discarding"); exit(EX_SOFTWARE); } bzero(&bounceq, sizeof(bounceq)); LIST_INIT(&bounceq.queue); bounceq.sender = ""; if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0) goto fail; if (newspoolf(&bounceq) != 0) goto fail; syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id); setlogident("%s", bounceq.id); error = fprintf(bounceq.mailf, "Received: from MAILER-DAEMON\n" "\tid %s\n" - "\tby %s (%s);\n" + "\tby %s (%s on %s);\n" "\t%s\n" "X-Original-To: <%s>\n" "From: MAILER-DAEMON <>\n" "To: %s\n" "Subject: Mail delivery failed\n" "Message-Id: <%s@%s>\n" "Date: %s\n" "\n" "This is the %s at %s.\n" "\n" "There was an error delivering your mail to <%s>.\n" "\n" "%s\n" "\n" "%s\n" "\n", bounceq.id, - hostname(), VERSION, + hostname(), VERSION, systemhostname(), rfc822date(), it->addr, it->sender, bounceq.id, hostname(), rfc822date(), VERSION, hostname(), it->addr, reason, config.features & FULLBOUNCE ? "Original message follows." : "Message headers follow."); if (error < 0) goto fail; if (fseek(it->mailf, 0, SEEK_SET) != 0) goto fail; if (config.features & FULLBOUNCE) { while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) { if (fwrite(line, 1, pos, bounceq.mailf) != pos) goto fail; } } else { while (!feof(it->mailf)) { if (fgets(line, sizeof(line), it->mailf) == NULL) break; if (line[0] == '\n') break; if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1) goto fail; } } if (linkspool(&bounceq) != 0) goto fail; /* bounce is safe */ delqueue(it); run_queue(&bounceq); /* NOTREACHED */ fail: syslog(LOG_CRIT, "error creating bounce: %m"); delqueue(it); exit(EX_IOERR); } struct parse_state { char addr[1000]; int pos; enum { NONE = 0, START, MAIN, EOL, QUIT } state; int comment; int quote; int brackets; int esc; }; /* * Simplified RFC2822 header/address parsing. * We copy escapes and quoted strings directly, since * we have to pass them like this to the mail server anyways. * XXX local addresses will need treatment */ static int parse_addrs(struct parse_state *ps, char *s, struct queue *queue) { char *addr; again: switch (ps->state) { case NONE: return (-1); case START: /* init our data */ bzero(ps, sizeof(*ps)); /* skip over header name */ while (*s != ':') s++; s++; ps->state = MAIN; break; case MAIN: /* all fine */ break; case EOL: switch (*s) { case ' ': case '\t': - s++; - /* continue */ + ps->state = MAIN; break; default: ps->state = QUIT; if (ps->pos != 0) goto newaddr; return (0); } + break; case QUIT: return (0); } for (; *s != 0; s++) { if (ps->esc) { ps->esc = 0; switch (*s) { case '\r': case '\n': goto err; default: goto copy; } } if (ps->quote) { switch (*s) { case '"': ps->quote = 0; goto copy; case '\\': ps->esc = 1; goto copy; case '\r': case '\n': goto eol; default: goto copy; } } switch (*s) { case '(': ps->comment++; break; case ')': if (ps->comment) ps->comment--; else goto err; goto skip; case '"': ps->quote = 1; goto copy; case '\\': ps->esc = 1; goto copy; case '\r': case '\n': goto eol; } if (ps->comment) goto skip; switch (*s) { case ' ': case '\t': /* ignore whitespace */ goto skip; case '<': /* this is the real address now */ ps->brackets = 1; ps->pos = 0; goto skip; case '>': if (!ps->brackets) goto err; ps->brackets = 0; s++; goto newaddr; case ':': /* group - ignore */ ps->pos = 0; goto skip; case ',': case ';': /* * Next address, copy previous one. * However, we might be directly after * a
, or have two consecutive * commas. * Skip the comma unless there is * really something to copy. */ if (ps->pos == 0) goto skip; s++; goto newaddr; default: goto copy; } copy: if (ps->comment) goto skip; if (ps->pos + 1 == sizeof(ps->addr)) goto err; ps->addr[ps->pos++] = *s; skip: ; } eol: ps->state = EOL; return (0); err: ps->state = QUIT; return (-1); newaddr: ps->addr[ps->pos] = 0; ps->pos = 0; addr = strdup(ps->addr); if (addr == NULL) errlog(EX_SOFTWARE, "strdup"); if (add_recp(queue, addr, EXPAND_WILDCARD) != 0) errlogx(EX_DATAERR, "invalid recipient `%s'", addr); goto again; } static int writeline(struct queue *queue, const char *line, ssize_t linelen) { ssize_t len; while (linelen > 0) { len = linelen; if (linelen > MAX_LINE_RFC822) { len = MAX_LINE_RFC822 - 10; } if (fwrite(line, len, 1, queue->mailf) != 1) return (-1); if (linelen <= MAX_LINE_RFC822) break; if (fwrite("\n", 1, 1, queue->mailf) != 1) return (-1); line += MAX_LINE_RFC822 - 10; linelen = strlen(line); } return (0); } int readmail(struct queue *queue, int nodot, int recp_from_header) { struct parse_state parse_state; char *line = NULL; ssize_t linelen; size_t linecap = 0; char newline[MAX_LINE_RFC822]; size_t error; int had_headers = 0; int had_from = 0; int had_messagid = 0; int had_date = 0; + int had_first_line = 0; + int had_last_line = 0; int nocopy = 0; int ret = -1; parse_state.state = NONE; error = fprintf(queue->mailf, "Received: from %s (uid %d)\n" "\t(envelope-from %s)\n" "\tid %s\n" - "\tby %s (%s);\n" + "\tby %s (%s on %s);\n" "\t%s\n", username, useruid, queue->sender, queue->id, - hostname(), VERSION, + hostname(), VERSION, systemhostname(), rfc822date()); if ((ssize_t)error < 0) return (-1); while (!feof(stdin)) { newline[0] = '\0'; if ((linelen = getline(&line, &linecap, stdin)) <= 0) break; - + if (had_last_line) + errlogx(EX_DATAERR, "bad mail input format:" + " from %s (uid %d) (envelope-from %s)", + username, useruid, queue->sender); + linelen = strlen(line); + if (linelen == 0 || line[linelen - 1] != '\n') { + /* + * This line did not end with a newline character. + * If we fix it, it better be the last line of + * the file. + */ + line[linelen] = '\n'; + line[linelen + 1] = 0; + had_last_line = 1; + } + if (!had_first_line) { + /* + * Ignore a leading RFC-976 From_ or >From_ line mistakenly + * inserted by some programs. + */ + if (strprefixcmp(line, "From ") == 0 || strprefixcmp(line, ">From ") == 0) + continue; + had_first_line = 1; + } if (!had_headers) { if (linelen > MAX_LINE_RFC822) { /* XXX also split headers */ errlogx(EX_DATAERR, "bad mail input format:" " from %s (uid %d) (envelope-from %s)", username, useruid, queue->sender); } /* * Unless this is a continuation, switch of * the Bcc: nocopy flag. */ if (!(line[0] == ' ' || line[0] == '\t')) nocopy = 0; if (strprefixcmp(line, "Date:") == 0) had_date = 1; else if (strprefixcmp(line, "Message-Id:") == 0) had_messagid = 1; else if (strprefixcmp(line, "From:") == 0) had_from = 1; else if (strprefixcmp(line, "Bcc:") == 0) nocopy = 1; if (parse_state.state != NONE) { if (parse_addrs(&parse_state, line, queue) < 0) { errlogx(EX_DATAERR, "invalid address in header\n"); /* NOTREACHED */ } } if (recp_from_header && ( strprefixcmp(line, "To:") == 0 || strprefixcmp(line, "Cc:") == 0 || strprefixcmp(line, "Bcc:") == 0)) { parse_state.state = START; if (parse_addrs(&parse_state, line, queue) < 0) { errlogx(EX_DATAERR, "invalid address in header\n"); /* NOTREACHED */ } } } if (strcmp(line, "\n") == 0 && !had_headers) { had_headers = 1; while (!had_date || !had_messagid || !had_from) { if (!had_date) { had_date = 1; snprintf(newline, sizeof(newline), "Date: %s\n", rfc822date()); } else if (!had_messagid) { /* XXX msgid, assign earlier and log? */ had_messagid = 1; snprintf(newline, sizeof(newline), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n", (uintmax_t)time(NULL), queue->id, (uintmax_t)random(), hostname()); } else if (!had_from) { had_from = 1; snprintf(newline, sizeof(newline), "From: <%s>\n", queue->sender); } if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1) goto fail; } strlcpy(newline, "\n", sizeof(newline)); } if (!nodot && linelen == 2 && line[0] == '.') break; if (!nocopy) { if (newline[0]) { if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1) goto fail; } else { if (writeline(queue, line, linelen) != 0) goto fail; } } } ret = 0; fail: free(line); return (ret); } diff --git a/contrib/dma/net.c b/contrib/dma/net.c index 7953370500d8..e8e2634a9386 100644 --- a/contrib/dma/net.c +++ b/contrib/dma/net.c @@ -1,551 +1,677 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthias Schmidt , University of Marburg, * Germany. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "dfcompat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include "dma.h" char neterr[ERRMSG_SIZE]; char * ssl_errstr(void) { long oerr, nerr; oerr = 0; while ((nerr = ERR_get_error()) != 0) oerr = nerr; return (ERR_error_string(oerr, NULL)); } ssize_t send_remote_command(int fd, const char* fmt, ...) { va_list va; char cmd[4096]; size_t len, pos; int s; ssize_t n; va_start(va, fmt); s = vsnprintf(cmd, sizeof(cmd) - 2, fmt, va); va_end(va); if (s == sizeof(cmd) - 2 || s < 0) { strcpy(neterr, "Internal error: oversized command string"); return (-1); } /* We *know* there are at least two more bytes available */ strcat(cmd, "\r\n"); len = strlen(cmd); - if (((config.features & SECURETRANS) != 0) && + if (((config.features & SECURETRANSFER) != 0) && ((config.features & NOSSL) == 0)) { while ((s = SSL_write(config.ssl, (const char*)cmd, len)) <= 0) { s = SSL_get_error(config.ssl, s); if (s != SSL_ERROR_WANT_READ && s != SSL_ERROR_WANT_WRITE) { - strncpy(neterr, ssl_errstr(), sizeof(neterr)); + strlcpy(neterr, ssl_errstr(), sizeof(neterr)); return (-1); } } } else { pos = 0; while (pos < len) { n = write(fd, cmd + pos, len - pos); if (n < 0) return (-1); pos += n; } } return (len); } int read_remote(int fd, int extbufsize, char *extbuf) { ssize_t rlen = 0; size_t pos, len, copysize; char buff[BUF_SIZE]; int done = 0, status = 0, status_running = 0, extbufpos = 0; enum { parse_status, parse_spacedash, parse_rest } parsestate; if (do_timeout(CON_TIMEOUT, 1) != 0) { snprintf(neterr, sizeof(neterr), "Timeout reached"); return (-1); } /* * 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; pos = 0; - if (((config.features & SECURETRANS) != 0) && + if (((config.features & SECURETRANSFER) != 0) && (config.features & NOSSL) == 0) { if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) { - strncpy(neterr, ssl_errstr(), sizeof(neterr)); + strlcpy(neterr, ssl_errstr(), sizeof(neterr)); goto error; } } else { if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) { - strncpy(neterr, strerror(errno), sizeof(neterr)); + strlcpy(neterr, strerror(errno), sizeof(neterr)); goto error; } } len += rlen; 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; } memcpy(extbuf + extbufpos, buff + len - rlen, rlen); extbufpos += rlen; } if (pos == len) continue; switch (parsestate) { case parse_status: for (; pos < len; pos++) { if (isdigit(buff[pos])) { status_running = status_running * 10 + (buff[pos] - '0'); } else { status = status_running; status_running = 0; parsestate = parse_spacedash; 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"); goto error; } pos++; parsestate = parse_rest; continue; case parse_rest: /* skip up to \n */ for (; pos < len; pos++) { if (buff[pos] == '\n') { pos++; parsestate = parse_status; break; } } } } do_timeout(0, 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); } /* * Handle SMTP authentication */ static int -smtp_login(int fd, char *login, char* password) +smtp_login(int fd, char *login, char* password, const struct smtp_features* features) { 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 - */ - return (1); - } - - if ((config.features & INSECURE) != 0 || - (config.features & SECURETRANS) != 0) { - /* Send AUTH command according to RFC 2554 */ - send_remote_command(fd, "AUTH LOGIN"); - if (read_remote(fd, 0, NULL) != 3) { - syslog(LOG_NOTICE, "remote delivery deferred:" - " AUTH login not available: %s", - neterr); + // CRAM-MD5 + if (features->auth.cram_md5) { + 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 + */ return (1); } + } - len = base64_encode(login, strlen(login), &temp); - if (len < 0) { + // LOGIN + if (features->auth.login) { + if ((config.features & INSECURE) != 0 || + (config.features & SECURETRANSFER) != 0) { + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH LOGIN"); + if (read_remote(fd, 0, NULL) != 3) { + syslog(LOG_NOTICE, "remote delivery deferred:" + " AUTH login not available: %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); - } + syslog(LOG_ERR, "can not encode auth reply: %m"); + return (1); + } - send_remote_command(fd, "%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); - } + send_remote_command(fd, "%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); + } - len = base64_encode(password, strlen(password), &temp); - if (len < 0) - goto encerr; - - send_remote_command(fd, "%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); + len = base64_encode(password, strlen(password), &temp); + if (len < 0) + goto encerr; + + send_remote_command(fd, "%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); + } + } else { + syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. "); + return (1); } - } else { - syslog(LOG_WARNING, "non-encrypted SMTP login is disabled in config, so skipping it. "); - return (1); } return (0); } static int open_connection(struct mx_hostentry *h) { int fd; syslog(LOG_INFO, "trying remote delivery to %s [%s] pref %d", h->host, h->addr, h->pref); 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); } 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); return (-1); } return (fd); } static void close_connection(int fd) { if (config.ssl != NULL) { - if (((config.features & SECURETRANS) != 0) && + if (((config.features & SECURETRANSFER) != 0) && ((config.features & NOSSL) == 0)) SSL_shutdown(config.ssl); SSL_free(config.ssl); } close(fd); } +static void parse_auth_line(char* line, struct smtp_auth_mechanisms* auth) { + // Skip the auth prefix + line += strlen("AUTH "); + + char* method = strtok(line, " "); + while (method) { + if (strcmp(method, "CRAM-MD5") == 0) + auth->cram_md5 = 1; + + else if (strcmp(method, "LOGIN") == 0) + auth->login = 1; + + method = strtok(NULL, " "); + } +} + +int perform_server_greeting(int fd, struct smtp_features* features) { + /* + Send EHLO + XXX allow HELO fallback + */ + send_remote_command(fd, "EHLO %s", hostname()); + + char buffer[EHLO_RESPONSE_SIZE]; + memset(buffer, 0, sizeof(buffer)); + + int res = read_remote(fd, sizeof(buffer) - 1, buffer); + + // Got an unexpected response + if (res != 2) + return -1; + + // Reset all features + memset(features, 0, sizeof(*features)); + + // Run through the buffer line by line + char linebuffer[EHLO_RESPONSE_SIZE]; + char* p = buffer; + + while (*p) { + char* line = linebuffer; + while (*p && *p != '\n') { + *line++ = *p++; + } + + // p should never point to NULL after the loop + // above unless we reached the end of the buffer. + // In that case we will raise an error. + if (!*p) { + return -1; + } + + // Otherwise p points to the newline character which + // we will skip. + p++; + + // Terminte the string (and remove the carriage-return character) + *--line = '\0'; + line = linebuffer; + + // End main loop for empty lines + if (*line == '\0') + break; + + // Process the line + // - Must start with 250, followed by dash or space + // - We won't check for the correct usage of space and dash because + // that is already done in read_remote(). + if ((strncmp(line, "250-", 4) != 0) && (strncmp(line, "250 ", 4) != 0)) { + syslog(LOG_ERR, "Invalid line: %s\n", line); + return -1; + } + + // Skip the prefix + line += 4; + + // Check for STARTTLS + if (strcmp(line, "STARTTLS") == 0) + features->starttls = 1; + + // Parse authentication mechanisms + else if (strncmp(line, "AUTH ", 5) == 0) + parse_auth_line(line, &features->auth); + } + + syslog(LOG_DEBUG, "Server greeting successfully completed"); + + // STARTTLS + if (features->starttls) + syslog(LOG_DEBUG, " Server supports STARTTLS"); + else + syslog(LOG_DEBUG, " Server does not support STARTTLS"); + + // Authentication + if (features->auth.cram_md5) { + syslog(LOG_DEBUG, " Server supports CRAM-MD5 authentication"); + } + if (features->auth.login) { + syslog(LOG_DEBUG, " Server supports LOGIN authentication"); + } + + return 0; +} + static int deliver_to_host(struct qitem *it, struct mx_hostentry *host) { struct authuser *a; - char line[1000]; + struct smtp_features features; + char line[1000], *addrtmp = NULL, *to_addr; size_t linelen; int fd, error = 0, do_auth = 0, res = 0; 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); \ - error = -1; \ - goto out; \ - } else if (res != exp) { \ - syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ - host->host, host->addr, c, neterr); \ - error = 1; \ - goto out; \ - } +#define READ_REMOTE_CHECK(c, exp) \ + do { \ + 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); \ + error = -1; \ + goto out; \ + } else if (res != exp) { \ + syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + error = 1; \ + goto out; \ + } \ + } while (0) /* Check first reply from remote host */ - if ((config.features & SECURETRANS) == 0 || + if ((config.features & SECURETRANSFER) == 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 ((config.features & SECURETRANSFER) != 0) { + error = smtp_init_crypto(fd, config.features, &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); + // Say EHLO + if (perform_server_greeting(fd, &features) != 0) { + syslog(LOG_ERR, "Could not perform server greeting at %s [%s]: %s", + host->host, host->addr, neterr); + return -1; + } /* * Use SMTP authentication if the user defined an entry for the remote * or smarthost */ SLIST_FOREACH(a, &authusers, next) { if (strcmp(a->host, host->host) == 0) { do_auth = 1; break; } } if (do_auth == 1) { /* * 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); + error = smtp_login(fd, a->login, a->password, &features); if (error < 0) { syslog(LOG_ERR, "remote delivery failed:" " SMTP login failed: %m"); snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host); error = -1; goto out; } /* SMTP login is not available, so try without */ else if (error > 0) { syslog(LOG_WARNING, "SMTP login not available. Trying without."); } } /* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */ send_remote_command(fd, "MAIL FROM:<%s>", it->sender); READ_REMOTE_CHECK("MAIL FROM", 2); /* XXX send ESMTP ORCPT */ - send_remote_command(fd, "RCPT TO:<%s>", it->addr); - READ_REMOTE_CHECK("RCPT TO", 2); + if ((addrtmp = strdup(it->addr)) == NULL) { + syslog(LOG_CRIT, "remote delivery deferred: unable to allocate memory"); + error = 1; + goto out; + } + to_addr = strtok(addrtmp, ","); + while (to_addr != NULL) { + send_remote_command(fd, "RCPT TO:<%s>", to_addr); + READ_REMOTE_CHECK("RCPT TO", 2); + to_addr = strtok(NULL, ","); + } send_remote_command(fd, "DATA"); READ_REMOTE_CHECK("DATA", 3); error = 0; while (!feof(it->mailf)) { if (fgets(line, sizeof(line), it->mailf) == NULL) break; linelen = strlen(line); if (linelen == 0 || line[linelen - 1] != '\n') { syslog(LOG_CRIT, "remote delivery failed: corrupted queue file"); snprintf(errmsg, sizeof(errmsg), "corrupted queue file"); error = -1; goto out; } /* Remove trailing \n's and escape leading dots */ trim_line(line); /* * If the first character is a dot, we escape it so the line * length increases */ if (line[0] == '.') linelen++; if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) { syslog(LOG_NOTICE, "remote delivery deferred: write error"); error = 1; goto out; } } send_remote_command(fd, "."); READ_REMOTE_CHECK("final DATA", 2); send_remote_command(fd, "QUIT"); if (read_remote(fd, 0, NULL) != 2) syslog(LOG_INFO, "remote delivery succeeded but QUIT failed: %s", neterr); out: + free(addrtmp); close_connection(fd); return (error); } int deliver_remote(struct qitem *it) { struct mx_hostentry *hosts, *h; const char *host; int port; int error = 1, smarthost = 0; port = SMTP_PORT; /* Smarthost support? */ if (config.smarthost != NULL) { host = config.smarthost; port = config.port; syslog(LOG_INFO, "using smarthost (%s:%i)", host, port); smarthost = 1; } else { host = strrchr(it->addr, '@'); /* Should not happen */ if (host == NULL) { snprintf(errmsg, sizeof(errmsg), "Internal error: badly formed address %s", it->addr); return(-1); } else { /* Step over the @ */ host++; } } error = dns_get_mx_list(host, port, &hosts, smarthost); if (error) { snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host); syslog(LOG_NOTICE, "remote delivery %s: DNS lookup failure: host %s not found", error < 0 ? "failed" : "deferred", host); return (error); } for (h = hosts; *h->host != 0; h++) { switch (deliver_to_host(it, h)) { case 0: /* success */ error = 0; goto out; case 1: /* temp failure */ error = 1; break; default: /* perm failure */ error = -1; goto out; } } out: free(hosts); return (error); } diff --git a/contrib/dma/spool.c b/contrib/dma/spool.c index 55436e5bec87..b794db8b02d3 100644 --- a/contrib/dma/spool.c +++ b/contrib/dma/spool.c @@ -1,442 +1,444 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "dfcompat.h" #include #include +#include #include #include #include #include #include #include #include +#include #include #include #include "dma.h" /* * Spool file format: * * 'Q'id files (queue): * Organized like an RFC822 header, field: value. Ignores unknown fields. * ID: id * Sender: envelope-from * Recipient: envelope-to * * 'M'id files (data): * mail data * * Each queue file needs to have a corresponding data file. * One data file might be shared by linking it several times. * * Queue ids are unique, formed from the inode of the data file * and a unique identifier. */ int newspoolf(struct queue *queue) { char fn[PATH_MAX+1]; struct stat st; struct stritem *t; int fd; if (snprintf(fn, sizeof(fn), "%s/%s", config.spooldir, "tmp_XXXXXXXXXX") <= 0) return (-1); fd = mkstemp(fn); if (fd < 0) return (-1); /* XXX group rights */ if (fchmod(fd, 0660) < 0) goto fail; if (flock(fd, LOCK_EX) == -1) goto fail; queue->tmpf = strdup(fn); if (queue->tmpf == NULL) goto fail; /* * Assign queue id */ if (fstat(fd, &st) != 0) goto fail; if (asprintf(&queue->id, "%"PRIxMAX, (uintmax_t)st.st_ino) < 0) goto fail; queue->mailf = fdopen(fd, "r+"); if (queue->mailf == NULL) goto fail; t = malloc(sizeof(*t)); if (t != NULL) { t->str = queue->tmpf; SLIST_INSERT_HEAD(&tmpfs, t, next); } return (0); fail: if (queue->mailf != NULL) fclose(queue->mailf); close(fd); unlink(fn); return (-1); } static int writequeuef(struct qitem *it) { int error; int queuefd; queuefd = open_locked(it->queuefn, O_CREAT|O_EXCL|O_RDWR, 0660); if (queuefd == -1) return (-1); if (fchmod(queuefd, 0660) < 0) return (-1); it->queuef = fdopen(queuefd, "w+"); if (it->queuef == NULL) return (-1); error = fprintf(it->queuef, "ID: %s\n" "Sender: %s\n" "Recipient: %s\n", it->queueid, it->sender, it->addr); if (error <= 0) return (-1); if (fflush(it->queuef) != 0 || fsync(fileno(it->queuef)) != 0) return (-1); return (0); } static struct qitem * readqueuef(struct queue *queue, char *queuefn) { char line[1000]; struct queue itmqueue; FILE *queuef = NULL; char *s; char *queueid = NULL, *sender = NULL, *addr = NULL; struct qitem *it = NULL; bzero(&itmqueue, sizeof(itmqueue)); LIST_INIT(&itmqueue.queue); queuef = fopen(queuefn, "r"); if (queuef == NULL) goto out; while (!feof(queuef)) { if (fgets(line, sizeof(line), queuef) == NULL || line[0] == 0) break; line[strlen(line) - 1] = 0; /* chop newline */ s = strchr(line, ':'); if (s == NULL) goto malformed; *s = 0; s++; while (isspace(*s)) s++; s = strdup(s); if (s == NULL) goto malformed; if (strcmp(line, "ID") == 0) { queueid = s; } else if (strcmp(line, "Sender") == 0) { sender = s; } else if (strcmp(line, "Recipient") == 0) { addr = s; } else { syslog(LOG_DEBUG, "ignoring unknown queue info `%s' in `%s'", line, queuefn); free(s); } } if (queueid == NULL || sender == NULL || addr == NULL || *queueid == 0 || *addr == 0) { malformed: errno = EINVAL; syslog(LOG_ERR, "malformed queue file `%s'", queuefn); goto out; } if (add_recp(&itmqueue, addr, 0) != 0) goto out; it = LIST_FIRST(&itmqueue.queue); it->sender = sender; sender = NULL; it->queueid = queueid; queueid = NULL; it->queuefn = queuefn; queuefn = NULL; LIST_INSERT_HEAD(&queue->queue, it, next); out: if (sender != NULL) free(sender); if (queueid != NULL) free(queueid); if (addr != NULL) free(addr); if (queuef != NULL) fclose(queuef); return (it); } int linkspool(struct queue *queue) { struct stat st; struct qitem *it; if (fflush(queue->mailf) != 0 || fsync(fileno(queue->mailf)) != 0) goto delfiles; syslog(LOG_INFO, "new mail from user=%s uid=%d envelope_from=<%s>", username, getuid(), queue->sender); LIST_FOREACH(it, &queue->queue, next) { if (asprintf(&it->queueid, "%s.%"PRIxPTR, queue->id, (uintptr_t)it) <= 0) goto delfiles; if (asprintf(&it->queuefn, "%s/Q%s", config.spooldir, it->queueid) <= 0) goto delfiles; if (asprintf(&it->mailfn, "%s/M%s", config.spooldir, it->queueid) <= 0) goto delfiles; /* Neither file may not exist yet */ if (stat(it->queuefn, &st) == 0 || stat(it->mailfn, &st) == 0) goto delfiles; if (writequeuef(it) != 0) goto delfiles; if (link(queue->tmpf, it->mailfn) != 0) goto delfiles; } LIST_FOREACH(it, &queue->queue, next) { syslog(LOG_INFO, "mail to=<%s> queued as %s", it->addr, it->queueid); } unlink(queue->tmpf); return (0); delfiles: LIST_FOREACH(it, &queue->queue, next) { unlink(it->mailfn); unlink(it->queuefn); } return (-1); } int load_queue(struct queue *queue) { struct stat sb; struct qitem *it; DIR *spooldir; struct dirent *de; char *queuefn; char *mailfn; bzero(queue, sizeof(*queue)); LIST_INIT(&queue->queue); spooldir = opendir(config.spooldir); if (spooldir == NULL) err(EX_NOINPUT, "reading queue"); while ((de = readdir(spooldir)) != NULL) { queuefn = NULL; mailfn = NULL; /* ignore non-queue files */ if (de->d_name[0] != 'Q') continue; if (asprintf(&queuefn, "%s/Q%s", config.spooldir, de->d_name + 1) < 0) goto fail; if (asprintf(&mailfn, "%s/M%s", config.spooldir, de->d_name + 1) < 0) goto fail; /* * Some file systems don't provide a de->d_type, so we have to * do an explicit stat on the queue file. * Move on if it turns out to be something else than a file. */ if (stat(queuefn, &sb) != 0) goto skip_item; if (!S_ISREG(sb.st_mode)) { errno = EINVAL; goto skip_item; } if (stat(mailfn, &sb) != 0) goto skip_item; it = readqueuef(queue, queuefn); if (it == NULL) goto skip_item; it->mailfn = mailfn; continue; skip_item: syslog(LOG_INFO, "could not pick up queue file: `%s'/`%s': %m", queuefn, mailfn); if (queuefn != NULL) free(queuefn); if (mailfn != NULL) free(mailfn); } closedir(spooldir); return (0); fail: return (-1); } void delqueue(struct qitem *it) { unlink(it->mailfn); unlink(it->queuefn); if (it->queuef != NULL) fclose(it->queuef); if (it->mailf != NULL) fclose(it->mailf); free(it); } int acquirespool(struct qitem *it) { int queuefd; if (it->queuef == NULL) { queuefd = open_locked(it->queuefn, O_RDWR|O_NONBLOCK); if (queuefd < 0) goto fail; it->queuef = fdopen(queuefd, "r+"); if (it->queuef == NULL) goto fail; } if (it->mailf == NULL) { it->mailf = fopen(it->mailfn, "r"); if (it->mailf == NULL) goto fail; } return (0); fail: if (errno == EWOULDBLOCK) return (1); syslog(LOG_INFO, "could not acquire queue file: %m"); return (-1); } void dropspool(struct queue *queue, struct qitem *keep) { struct qitem *it; LIST_FOREACH(it, &queue->queue, next) { if (it == keep) continue; if (it->queuef != NULL) fclose(it->queuef); if (it->mailf != NULL) fclose(it->mailf); } } int flushqueue_since(unsigned int period) { struct stat st; struct timeval now; char *flushfn = NULL; if (asprintf(&flushfn, "%s/%s", config.spooldir, SPOOL_FLUSHFILE) < 0) return (0); if (stat(flushfn, &st) < 0) { free(flushfn); return (0); } free(flushfn); flushfn = NULL; if (gettimeofday(&now, 0) != 0) return (0); /* Did the flush file get touched within the last period seconds? */ if (st.st_mtim.tv_sec + (int)period >= now.tv_sec) return (1); else return (0); } int flushqueue_signal(void) { char *flushfn = NULL; int fd; if (asprintf(&flushfn, "%s/%s", config.spooldir, SPOOL_FLUSHFILE) < 0) return (-1); fd = open(flushfn, O_CREAT|O_WRONLY|O_TRUNC, 0660); free(flushfn); if (fd < 0) { syslog(LOG_ERR, "could not open flush file: %m"); return (-1); } close(fd); return (0); } diff --git a/contrib/dma/util.c b/contrib/dma/util.c index 2abaec7b483a..d41abe8a55e7 100644 --- a/contrib/dma/util.c +++ b/contrib/dma/util.c @@ -1,347 +1,367 @@ /* * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon Schubert <2@0x2c.org>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include "dma.h" const char * hostname(void) { #ifndef HOST_NAME_MAX #define HOST_NAME_MAX 255 #endif static char name[HOST_NAME_MAX+1]; static int initialized = 0; char *s; if (initialized) return (name); if (config.mailname == NULL || !*config.mailname) goto local; if (config.mailname[0] == '/') { /* * If the mailname looks like an absolute path, * treat it as a file. */ FILE *fp; fp = fopen(config.mailname, "r"); if (fp == NULL) goto local; s = fgets(name, sizeof(name), fp); fclose(fp); if (s == NULL) goto local; for (s = name; *s != 0 && (isalnum(*s) || strchr("_.-", *s)); ++s) /* NOTHING */; *s = 0; if (!*name) goto local; initialized = 1; return (name); } else { snprintf(name, sizeof(name), "%s", config.mailname); initialized = 1; return (name); } local: + snprintf(name, sizeof(name), "%s", systemhostname()); + + initialized = 1; + return (name); +} + +const char * +systemhostname(void) +{ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif + static char name[HOST_NAME_MAX+1]; + static int initialized = 0; + char *s; + + if (initialized) + return (name); + if (gethostname(name, sizeof(name)) != 0) *name = 0; /* * gethostname() is allowed to truncate name without NUL-termination * and at the same time not return an error. */ name[sizeof(name) - 1] = 0; for (s = name; *s != 0 && (isalnum(*s) || strchr("_.-", *s)); ++s) /* NOTHING */; *s = 0; if (!*name) snprintf(name, sizeof(name), "unknown-hostname"); initialized = 1; return (name); } void setlogident(const char *fmt, ...) { static char tag[50]; snprintf(tag, sizeof(tag), "%s", logident_base); if (fmt != NULL) { va_list ap; char sufx[50]; va_start(ap, fmt); vsnprintf(sufx, sizeof(sufx), fmt, ap); va_end(ap); snprintf(tag, sizeof(tag), "%s[%s]", logident_base, sufx); } closelog(); openlog(tag, 0, LOG_MAIL); } void errlog(int exitcode, const char *fmt, ...) { int oerrno = errno; va_list ap; char outs[ERRMSG_SIZE]; outs[0] = 0; if (fmt != NULL) { va_start(ap, fmt); vsnprintf(outs, sizeof(outs), fmt, ap); va_end(ap); } errno = oerrno; if (*outs != 0) { syslog(LOG_ERR, "%s: %m", outs); fprintf(stderr, "%s: %s: %s\n", getprogname(), outs, strerror(oerrno)); } else { syslog(LOG_ERR, "%m"); fprintf(stderr, "%s: %s\n", getprogname(), strerror(oerrno)); } exit(exitcode); } void errlogx(int exitcode, const char *fmt, ...) { va_list ap; char outs[ERRMSG_SIZE]; outs[0] = 0; if (fmt != NULL) { va_start(ap, fmt); vsnprintf(outs, sizeof(outs), fmt, ap); va_end(ap); } if (*outs != 0) { syslog(LOG_ERR, "%s", outs); fprintf(stderr, "%s: %s\n", getprogname(), outs); } else { syslog(LOG_ERR, "Unknown error"); fprintf(stderr, "%s: Unknown error\n", getprogname()); } exit(exitcode); } static int check_username(const char *name, uid_t ckuid) { struct passwd *pwd; if (name == NULL) return (0); pwd = getpwnam(name); if (pwd == NULL || pwd->pw_uid != ckuid) return (0); snprintf(username, sizeof(username), "%s", name); return (1); } void set_username(void) { struct passwd *pwd; useruid = getuid(); if (check_username(getlogin(), useruid)) return; if (check_username(getenv("LOGNAME"), useruid)) return; if (check_username(getenv("USER"), useruid)) return; pwd = getpwuid(useruid); if (pwd != NULL && pwd->pw_name != NULL && pwd->pw_name[0] != '\0') { if (check_username(pwd->pw_name, useruid)) return; } snprintf(username, sizeof(username), "uid=%ld", (long)useruid); } void deltmp(void) { struct stritem *t; SLIST_FOREACH(t, &tmpfs, next) { unlink(t->str); } } static sigjmp_buf sigbuf; static int sigbuf_valid; static void sigalrm_handler(int signo) { (void)signo; /* so that gcc doesn't complain */ if (sigbuf_valid) siglongjmp(sigbuf, 1); } int do_timeout(int timeout, int dojmp) { struct sigaction act; int ret = 0; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (timeout) { act.sa_handler = sigalrm_handler; if (sigaction(SIGALRM, &act, NULL) != 0) syslog(LOG_WARNING, "can not set signal handler: %m"); if (dojmp) { ret = sigsetjmp(sigbuf, 1); if (ret) goto disable; /* else just programmed */ sigbuf_valid = 1; } alarm(timeout); } else { disable: alarm(0); act.sa_handler = SIG_IGN; if (sigaction(SIGALRM, &act, NULL) != 0) syslog(LOG_WARNING, "can not remove signal handler: %m"); sigbuf_valid = 0; } return (ret); } int open_locked(const char *fname, int flags, ...) { int mode = 0; if (flags & O_CREAT) { va_list ap; va_start(ap, flags); mode = va_arg(ap, int); va_end(ap); } #ifndef O_EXLOCK int fd, save_errno; fd = open(fname, flags, mode); if (fd < 0) return(fd); if (flock(fd, LOCK_EX|((flags & O_NONBLOCK)? LOCK_NB: 0)) < 0) { save_errno = errno; close(fd); errno = save_errno; return(-1); } return(fd); #else return(open(fname, flags|O_EXLOCK, mode)); #endif } char * rfc822date(void) { static char str[50]; size_t error; time_t now; now = time(NULL); error = strftime(str, sizeof(str), "%a, %d %b %Y %T %z", localtime(&now)); if (error == 0) strcpy(str, "(date fail)"); return (str); } int strprefixcmp(const char *str, const char *prefix) { return (strncasecmp(str, prefix, strlen(prefix))); } void init_random(void) { unsigned int seed; int rf; rf = open("/dev/urandom", O_RDONLY); if (rf == -1) rf = open("/dev/random", O_RDONLY); if (!(rf != -1 && read(rf, &seed, sizeof(seed)) == sizeof(seed))) seed = (time(NULL) ^ getpid()) + (uintptr_t)&seed; srandom(seed); if (rf != -1) close(rf); }