pax_global_header00006660000000000000000000000064136165177750014534gustar00rootroot0000000000000052 comment=55d22c24f989e0828d5d47227ad9ce17fc82b619 dma-0.13/000077500000000000000000000000001361651777500122205ustar00rootroot00000000000000dma-0.13/.gitignore000066400000000000000000000001071361651777500142060ustar00rootroot00000000000000*.o dma dma-mbox-create aliases_parse.c aliases_parse.h aliases_scan.c dma-0.13/INSTALL000066400000000000000000000021101361651777500132430ustar00rootroot00000000000000Installing DMA: =============== On most systems (with a development environment installed) you should be able to compile DMA with: make Once it have compiled it successfully, you can install it with: make install sendmail-link mailq-link install-spool-dirs install-etc Troubleshooting: ---------------- On systems that do not default to a compatible "make" version, try using "gmake" or "pmake" instead of "make". Some known examples of this: * Solaris 9 * Solaris 10 Check that you have the following commands installed: * cc - gcc is known to work * lex - flex is known to work * yacc - bison is kjnown to work * make - BSD make and GNU make is knwon to work * sh - Need to be POSIX compliant, dash, bash known to work * install - GNU and BSD versions known to work * openssl - Add the header location to C_INCLUDE_PATH if you get errors about "err.h" If you have all of these tools installed, set the CC, YACC, INSTALL, LEX and SH variable to point to the relevant location and command. Example: make CC=gcc YACC=bison LEX=/usr/bin/flex SH=/bin/bash INSTALL=/usr/bin/install dma-0.13/LICENSE000066400000000000000000000125001361651777500132230ustar00rootroot00000000000000Copyright (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>. 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. Copyright (c) 1995-2001 Kungliga Tekniska Högskolan (Royal Institute of Technology, Stockholm, Sweden). 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 Institute 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 INSTITUTE 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 INSTITUTE 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. 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. 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. dma-0.13/Makefile000066400000000000000000000060031361651777500136570ustar00rootroot00000000000000# # 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 -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 dma-0.13/Makefile.etc000066400000000000000000000004541361651777500144350ustar00rootroot00000000000000# $DragonFly: src/etc/dma/Makefile,v 1.3 2008/02/12 22:10:20 matthias Exp $ FILESDIR= /etc/dma SHAREOWN= root SHAREGRP= mail FILESMODE= 640 .if !exists(${DESTDIR}/etc/dma/auth.conf) FILES+= auth.conf .endif .if !exists(${DESTDIR}/etc/dma/dma.conf) FILES+= dma.conf .endif .include dma-0.13/README.markdown000066400000000000000000000014131361651777500147200ustar00rootroot00000000000000dma -- DragonFly Mail Agent =========================== dma 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. dma is not intended as a replacement for real, big MTAs like sendmail(8) or postfix(1). Consequently, dma does not listen on port 25 for incoming connections. Building -------- In Linux: make In BSD: cd bsd && make Installation ------------ make install sendmail-link mailq-link install-spool-dirs install-etc See INSTALL for requirements and configuration options. Contact ------- Simon Schubert <2@0x2c.org> dma-0.13/TODO000066400000000000000000000002051361651777500127050ustar00rootroot00000000000000- unquote/handle quoted local recipients - handle/use ESMTP extensions - .forward support - suggest way to run a queue flush on boot dma-0.13/VERSION000066400000000000000000000000061361651777500132640ustar00rootroot00000000000000v0.13 dma-0.13/aliases_parse.y000066400000000000000000000030201361651777500152200ustar00rootroot00000000000000%{ #include #include #include #include "dma.h" extern int yylineno; static void yyerror(const char *); static void yyerror(const char *msg) { /** * Because we do error '\n' below, we need to report the error * one line above of what yylineno points to. */ syslog(LOG_CRIT, "aliases line %d: %s", yylineno - 1, msg); fprintf(stderr, "aliases line %d: %s\n", yylineno - 1, msg); } int yywrap(void) { return (1); } %} %union { char *ident; struct stritem *strit; struct alias *alias; } %token T_IDENT %token T_ERROR %token T_EOF 0 %type dests %type alias aliases %% start : aliases T_EOF { LIST_FIRST(&aliases) = $1; } aliases : /* EMPTY */ { $$ = NULL; } | alias aliases { if ($2 != NULL && $1 != NULL) LIST_INSERT_AFTER($2, $1, next); else if ($2 == NULL) $2 = $1; $$ = $2; } ; alias : T_IDENT ':' dests '\n' { struct alias *al; if ($1 == NULL) YYABORT; al = calloc(1, sizeof(*al)); if (al == NULL) YYABORT; al->alias = $1; SLIST_FIRST(&al->dests) = $3; $$ = al; } | error '\n' { YYABORT; } ; dests : T_IDENT { struct stritem *it; if ($1 == NULL) YYABORT; it = calloc(1, sizeof(*it)); if (it == NULL) YYABORT; it->str = $1; $$ = it; } | T_IDENT ',' dests { struct stritem *it; if ($1 == NULL) YYABORT; it = calloc(1, sizeof(*it)); if (it == NULL) YYABORT; it->str = $1; SLIST_NEXT(it, next) = $3; $$ = it; } ; %% dma-0.13/aliases_scan.l000066400000000000000000000007171361651777500150270ustar00rootroot00000000000000%{ #include #include "dma.h" #include "aliases_parse.h" #define YY_NO_INPUT %} %option yylineno %option nounput %% [^:,#[:space:][:cntrl:]]+ {yylval.ident = strdup(yytext); return T_IDENT;} ^([[:blank:]]*(#.*)?\n)+ ;/* ignore empty lines */ [:,\n] return yytext[0]; (\n?[[:blank:]]+|#.*)+ ;/* ignore whitespace and continuation */ \\\n ;/* ignore continuation. not allowed in comments */ . return T_ERROR; <> return T_EOF; %% dma-0.13/auth.conf000066400000000000000000000002721361651777500140310ustar00rootroot00000000000000# $DragonFly: src/etc/dma/auth.conf,v 1.1 2008/02/02 18:24:00 matthias Exp $ # # SMTP authentication entries (currently AUTH LOGIN only) # Format: user|my.smarthost.example.com:password dma-0.13/base64.c000066400000000000000000000066561361651777500134650ustar00rootroot00000000000000/* * Copyright (c) 1995-2001 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * 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 Institute 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 INSTITUTE 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 INSTITUTE 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 "dma.h" static char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static int pos(char c) { char *p; for (p = base64_chars; *p; p++) if (*p == c) return p - base64_chars; return -1; } int base64_encode(const void *data, int size, char **str) { char *s, *p; int i; int c; const unsigned char *q; p = s = (char *) malloc(size * 4 / 3 + 4); if (p == NULL) return -1; q = (const unsigned char *) data; i = 0; for (i = 0; i < size;) { c = q[i++]; c *= 256; if (i < size) c += q[i]; i++; c *= 256; if (i < size) c += q[i]; i++; p[0] = base64_chars[(c & 0x00fc0000) >> 18]; p[1] = base64_chars[(c & 0x0003f000) >> 12]; p[2] = base64_chars[(c & 0x00000fc0) >> 6]; p[3] = base64_chars[(c & 0x0000003f) >> 0]; if (i > size) p[3] = '='; if (i > size + 1) p[2] = '='; p += 4; } *p = 0; *str = s; return strlen(s); } #define DECODE_ERROR 0xffffffff static unsigned int token_decode(const char *token) { int i; unsigned int val = 0; int marker = 0; if (strlen(token) < 4) return DECODE_ERROR; for (i = 0; i < 4; i++) { val *= 64; if (token[i] == '=') marker++; else if (marker > 0) return DECODE_ERROR; else val += pos(token[i]); } if (marker > 2) return DECODE_ERROR; return (marker << 24) | val; } int base64_decode(const char *str, void *data) { const char *p; unsigned char *q; q = data; for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { unsigned int val = token_decode(p); unsigned int marker = (val >> 24) & 0xff; if (val == DECODE_ERROR) return -1; *q++ = (val >> 16) & 0xff; if (marker < 2) *q++ = (val >> 8) & 0xff; if (marker < 1) *q++ = val & 0xff; } return q - (unsigned char *) data; } dma-0.13/bsd/000077500000000000000000000000001361651777500127705ustar00rootroot00000000000000dma-0.13/bsd/Makefile000066400000000000000000000000661361651777500144320ustar00rootroot00000000000000SUBDIR= dma dma-mbox-create .include dma-0.13/bsd/dma-mbox-create/000077500000000000000000000000001361651777500157355ustar00rootroot00000000000000dma-0.13/bsd/dma-mbox-create/Makefile000066400000000000000000000007321361651777500173770ustar00rootroot00000000000000# version!= sh ../../get-version.sh ../../VERSION CFLAGS+= -I${.CURDIR}/../.. CFLAGS+= -DHAVE_REALLOCF -DHAVE_STRLCPY -DHAVE_GETPROGNAME CFLAGS+= -DLIBEXEC_PATH='"${LIBEXEC}"' -DDMA_VERSION='"${version}"' CFLAGS+= -DCONF_PATH='"${CONFDIR}"' PROG= dma-mbox-create .PATH: ${.CURDIR}/../.. SRCS+= dma-mbox-create.c NOMAN= MK_MAN= no PREFIX?= /usr/local LIBEXEC?= ${PREFIX}/libexec CONFDIR?= ${PREFIX}/etc/dma BINOWN= root BINGRP= mail BINMODE=4554 .include dma-0.13/bsd/dma/000077500000000000000000000000001361651777500135315ustar00rootroot00000000000000dma-0.13/bsd/dma/Makefile000066400000000000000000000012721361651777500151730ustar00rootroot00000000000000# $DragonFly: src/libexec/dma/Makefile,v 1.5 2008/09/19 00:36:57 corecode Exp $ # version!= sh ../../get-version.sh ../../VERSION CFLAGS+= -I${.CURDIR}/../.. CFLAGS+= -DHAVE_REALLOCF -DHAVE_STRLCPY -DHAVE_GETPROGNAME CFLAGS+= -DLIBEXEC_PATH='"${LIBEXEC}"' -DDMA_VERSION='"${version}"' CFLAGS+= -DCONF_PATH='"${CONFDIR}"' DPADD= ${LIBSSL} ${LIBCRYPTO} LDADD= -lssl -lcrypto PROG= dma .PATH: ${.CURDIR}/../.. SRCS= aliases_parse.y aliases_scan.l base64.c conf.c crypto.c SRCS+= dma.c dns.c local.c mail.c net.c spool.c util.c MAN= dma.8 PREFIX?= /usr/local LIBEXEC?= ${PREFIX}/libexec CONFDIR?= ${PREFIX}/etc/dma BINOWN= root BINGRP= mail BINMODE=2555 WARNS?= 5 .include dma-0.13/conf.c000066400000000000000000000153301361651777500133130ustar00rootroot00000000000000/* * 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, NULL); 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, "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"); } for (unsigned int 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; 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); } dma-0.13/crypto.c000066400000000000000000000240101361651777500137010ustar00rootroot00000000000000/* * 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); } 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, 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) && (feature & STARTTLS) != 0) { /* TLS init phase, disable SSL_write */ config.features |= NOSSL; 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); } } } /* 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 != 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); } dma-0.13/dfcompat.c000066400000000000000000000073371361651777500141730ustar00rootroot00000000000000#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: 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: 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) 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 */ dma-0.13/dfcompat.h000066400000000000000000000006261361651777500141720ustar00rootroot00000000000000#ifndef DFCOMPAT_H #define DFCOMPAT_H #define _GNU_SOURCE #include #ifndef __DECONST #define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) #endif #ifndef HAVE_STRLCPY size_t strlcpy(char *, const char *, size_t); #endif #ifndef HAVE_REALLOCF void *reallocf(void *, size_t); #endif #ifndef HAVE_GETPROGNAME const char *getprogname(void); #endif #endif /* DFCOMPAT_H */ dma-0.13/dma-mbox-create.c000066400000000000000000000103661361651777500153370ustar00rootroot00000000000000/* * Copyright (c) 2010-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. */ /* * This binary is setuid root. Use extreme caution when touching * user-supplied information. Keep the root window as small as possible. */ #include #include #include #include #include #include #include #include #include #include #include #include "dma.h" static void logfail(int exitcode, const char *fmt, ...) { int oerrno = errno; va_list ap; char outs[1024]; 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, errno ? "%s: %m" : "%s", outs); else syslog(LOG_ERR, errno ? "%m" : "unknown error"); exit(exitcode); } /* * Create a mbox in /var/mail for a given user, or make sure * the permissions are correct for dma. */ int main(int argc, char **argv) { const char *user; struct passwd *pw; struct group *gr; uid_t user_uid; gid_t mail_gid; int f, maildirfd; openlog("dma-mbox-create", 0, LOG_MAIL); errno = 0; gr = getgrnam(DMA_GROUP); if (!gr) logfail(EX_CONFIG, "cannot find dma group `%s'", DMA_GROUP); mail_gid = gr->gr_gid; if (setgid(mail_gid) != 0) logfail(EX_NOPERM, "cannot set gid to %d (%s)", mail_gid, DMA_GROUP); if (getegid() != mail_gid) logfail(EX_NOPERM, "cannot set gid to %d (%s), still at %d", mail_gid, DMA_GROUP, getegid()); /* * We take exactly one argument: the username. */ if (argc != 2) { errno = 0; logfail(EX_USAGE, "no arguments"); } user = argv[1]; syslog(LOG_NOTICE, "creating mbox for `%s'", user); /* the username may not contain a pathname separator */ if (strchr(user, '/')) { errno = 0; logfail(EX_DATAERR, "path separator in username `%s'", user); exit(1); } /* verify the user exists */ errno = 0; pw = getpwnam(user); if (!pw) logfail(EX_NOUSER, "cannot find user `%s'", user); maildirfd = open(_PATH_MAILDIR, O_RDONLY); if (maildirfd < 0) logfail(EX_NOINPUT, "cannot open maildir %s", _PATH_MAILDIR); user_uid = pw->pw_uid; f = openat(maildirfd, user, O_RDONLY|O_CREAT|O_NOFOLLOW, 0600); if (f < 0) logfail(EX_NOINPUT, "cannot open mbox `%s'", user); if (fchown(f, user_uid, mail_gid)) logfail(EX_OSERR, "cannot change owner of mbox `%s'", user); if (fchmod(f, 0620)) logfail(EX_OSERR, "cannot change permissions of mbox `%s'", user); /* file should be present with the right owner and permissions */ syslog(LOG_NOTICE, "successfully created mbox for `%s'", user); return (0); } dma-0.13/dma.8000066400000000000000000000216571361651777500130650ustar00rootroot00000000000000.\" .\" 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 February 13, 2014 .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 don't attempt to deliver it. See also the .Sq DEFER config file setting below. .El .Pp All other .Ar mode Ns s are are ignored. .It Fl D Don't 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 .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 don't 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 SECURETRANS 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 SECURETRANS . .It Ic FINGERPRINT Xo 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 SECURETRANS 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 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 .Ql Sm off Va username @percolator . .Sm on .It Ic NULLCLIENT Xo .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 . dma-0.13/dma.c000066400000000000000000000322271361651777500131330ustar00rootroot00000000000000/* * 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 (config.masquerade_user) { addr = config.masquerade_user; } else if (osender) { addr = osender; } else if (getenv("EMAIL") != NULL) { addr = getenv("EMAIL"); } else { 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: delqueue(it); syslog(LOG_INFO, "<%s> delivery successful", it->addr); 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; struct queue queue; int i, ch; int nodot = 0, showq = 0, queue_only = 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) { 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(NULL); if (read_aliases() != 0) errx(EX_SOFTWARE, "could not parse aliases file `%s'", config.aliases); exit(EX_OK); } 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 */ 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 */ 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 */ 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 */ 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(NULL); 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 ((sender = set_from(&queue, sender)) == NULL) errlog(EX_SOFTWARE, NULL); 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); } dma-0.13/dma.conf000066400000000000000000000047361361651777500136420ustar00rootroot00000000000000# $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 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 dma-0.13/dma.h000066400000000000000000000152301361651777500131330ustar00rootroot00000000000000/* * 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 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, 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); 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 dma-0.13/dns.c000066400000000000000000000152341361651777500131550ustar00rootroot00000000000000/* * 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 dma-0.13/get-version.sh000066400000000000000000000002611361651777500150150ustar00rootroot00000000000000#!/bin/sh tmp=$1 file=${tmp:=VERSION} gitver=$(git describe 2>/dev/null | tr - .) filever=$(cat ${file} 2>/dev/null) version=${gitver} : ${version:=$filever} echo "$version" dma-0.13/local.c000066400000000000000000000151451361651777500134640ustar00rootroot00000000000000/* * 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); 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 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); } dma-0.13/mail.c000066400000000000000000000231641361651777500133140ustar00rootroot00000000000000/* * 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" 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" "\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, 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 */ break; default: ps->state = QUIT; if (ps->pos != 0) goto newaddr; return (0); } 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, NULL); if (add_recp(queue, addr, EXPAND_WILDCARD) != 0) errlogx(EX_DATAERR, "invalid recipient `%s'", addr); goto again; } int readmail(struct queue *queue, int nodot, int recp_from_header) { struct parse_state parse_state; char line[1000]; /* by RFC2822 */ size_t linelen; 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; 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" "\t%s\n", username, useruid, queue->sender, queue->id, hostname(), VERSION, rfc822date()); if ((ssize_t)error < 0) return (-1); while (!feof(stdin)) { if (fgets(line, sizeof(line) - 1, stdin) == NULL) 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) { /* * 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(line, sizeof(line), "Date: %s\n", rfc822date()); } else if (!had_messagid) { /* XXX msgid, assign earlier and log? */ had_messagid = 1; snprintf(line, sizeof(line), "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(line, sizeof(line), "From: <%s>\n", queue->sender); } if (fwrite(line, strlen(line), 1, queue->mailf) != 1) return (-1); } strcpy(line, "\n"); } if (!nodot && linelen == 2 && line[0] == '.') break; if (!nocopy) { if (fwrite(line, strlen(line), 1, queue->mailf) != 1) return (-1); } } return (0); } dma-0.13/net.c000066400000000000000000000415231361651777500131570ustar00rootroot00000000000000/* * 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) && ((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)); 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) && (config.features & NOSSL) == 0) { if ((rlen = SSL_read(config.ssl, buff + len, sizeof(buff) - len)) == -1) { strncpy(neterr, ssl_errstr(), sizeof(neterr)); goto error; } } else { if ((rlen = read(fd, buff + len, sizeof(buff) - len)) == -1) { strncpy(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, const struct smtp_features* features) { char *temp; int len, res = 0; // 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); } } // LOGIN if (features->auth.login) { 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); return (1); } len = base64_encode(login, strlen(login), &temp); if (len < 0) { encerr: syslog(LOG_ERR, "can not encode auth reply: %m"); return (1); } send_remote_command(fd, "%s", temp); 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); } } 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) && ((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; 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) \ 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 || (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, &features); if (error == 0) syslog(LOG_DEBUG, "SSL initialization successful"); else goto out; if ((config.features & STARTTLS) == 0) READ_REMOTE_CHECK("connect", 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, &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 */ 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); } dma-0.13/spool.c000066400000000000000000000230411361651777500135200ustar00rootroot00000000000000/* * 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 + 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); } dma-0.13/test/000077500000000000000000000000001361651777500131775ustar00rootroot00000000000000dma-0.13/test/quote.rfc2822000066400000000000000000000004411361651777500153450ustar00rootroot00000000000000From: test To: test Subject: testing! From what i can see... He came along, from far away. The rabbit comes out From his rabbit hole. Fromtal nudity! > From the previous mail >From the quote From here From there >>>From everywhere. last line. dma-0.13/util.c000066400000000000000000000163311361651777500133450ustar00rootroot00000000000000/* * 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: 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); }