From d38539d80511f5f603eb076cdc91cbdb5d69e1b4 Mon Sep 17 00:00:00 2001 From: bapt Date: Thu, 20 Feb 2014 22:39:55 +0000 Subject: [PATCH] Import dma 89702b7f14 (2013-02-13) into vendors --- BSDmakefile | 28 ++ INSTALL | 28 ++ LICENSE | 109 ++++++ Makefile | 111 ++++++ Makefile.etc | 15 + README.markdown | 32 ++ TODO | 5 + VERSION | 1 + aliases_parse.y | 112 +++++++ aliases_scan.l | 24 ++ auth.conf | 4 + base64.c | 135 ++++++++ conf.c | 246 ++++++++++++++ crypto.c | 312 +++++++++++++++++ debian/NEWS | 29 ++ debian/README.Debian | 12 + debian/changelog | 297 +++++++++++++++++ debian/compat | 1 + debian/control | 37 ++ debian/copyright | 91 +++++ debian/dma-migrate.dirs | 2 + debian/dma-migrate.install | 1 + debian/dma-migrate.manpages | 1 + debian/dma.dirs | 4 + debian/dma.links | 6 + debian/dma.lintian-overrides | 8 + debian/migrate/Makefile | 4 + debian/migrate/NEWS | 6 + debian/migrate/dma-migrate.8 | 98 ++++++ debian/migrate/dma-migrate.c | 413 +++++++++++++++++++++++ debian/rules | 49 +++ debian/source/format | 1 + debian/source/options | 2 + dfcompat.c | 122 +++++++ dfcompat.h | 24 ++ dma-mbox-create.c | 160 +++++++++ dma.8 | 363 ++++++++++++++++++++ dma.c | 631 +++++++++++++++++++++++++++++++++++ dma.conf | 66 ++++ dma.h | 237 +++++++++++++ dns.c | 280 ++++++++++++++++ get-version.sh | 9 + local.c | 253 ++++++++++++++ mail.c | 457 +++++++++++++++++++++++++ net.c | 546 ++++++++++++++++++++++++++++++ spool.c | 440 ++++++++++++++++++++++++ test/quote.rfc2822 | 23 ++ util.c | 345 +++++++++++++++++++ 48 files changed, 6180 insertions(+) create mode 100644 BSDmakefile create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile.etc create mode 100644 README.markdown create mode 100644 TODO create mode 100644 VERSION create mode 100644 aliases_parse.y create mode 100644 aliases_scan.l create mode 100644 auth.conf create mode 100644 base64.c create mode 100644 conf.c create mode 100644 crypto.c create mode 100644 debian/NEWS create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dma-migrate.dirs create mode 100644 debian/dma-migrate.install create mode 100644 debian/dma-migrate.manpages create mode 100644 debian/dma.dirs create mode 100644 debian/dma.links create mode 100644 debian/dma.lintian-overrides create mode 100644 debian/migrate/Makefile create mode 100644 debian/migrate/NEWS create mode 100644 debian/migrate/dma-migrate.8 create mode 100644 debian/migrate/dma-migrate.c create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/source/options create mode 100644 dfcompat.c create mode 100644 dfcompat.h create mode 100644 dma-mbox-create.c create mode 100644 dma.8 create mode 100644 dma.c create mode 100644 dma.conf create mode 100644 dma.h create mode 100644 dns.c create mode 100755 get-version.sh create mode 100644 local.c create mode 100644 mail.c create mode 100644 net.c create mode 100644 spool.c create mode 100644 test/quote.rfc2822 create mode 100644 util.c diff --git a/BSDmakefile b/BSDmakefile new file mode 100644 index 000000000000..af3a0ac7c34f --- /dev/null +++ b/BSDmakefile @@ -0,0 +1,28 @@ +# $DragonFly: src/libexec/dma/Makefile,v 1.5 2008/09/19 00:36:57 corecode Exp $ +# + +version!= sh get-version.sh + +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 +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?= 6 + +.include diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000000..3c52dc8e8c7f --- /dev/null +++ b/INSTALL @@ -0,0 +1,28 @@ +Installing 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..b89e5bcd16f3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,109 @@ +Copyright (c) 2008 The DragonFly Project. +Copyright (c) 2008-2011, Simon Schubert <2@0x2c.org>. +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. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000000..aed2ef7246cf --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +# +# Depending on your operating system, you might want to influence +# the conditional inclusion of some helper functions: +# +# Define HAVE_* (in caps) if your system already provides: +# reallocf +# strlcpy +# getprogname +# + +SH?= sh + +version= $(shell ${SH} get-version.sh) +debversion= $(shell ${SH} get-version.sh | sed -Ee 's/^v//;s/[.]([[:digit:]]+)[.](g[[:xdigit:]]+)$$/+\1+\2/') + +CC?= gcc +CFLAGS?= -O -pipe +LDADD?= -lssl -lcrypto -lresolv + +CFLAGS+= -Wall -DDMA_VERSION='"${version}"' -DLIBEXEC_PATH='"${LIBEXEC}"' -DCONF_PATH='"${CONFDIR}"' + +INSTALL?= install -p +CHGRP?= chgrp +CHMOD?= chmod + +PREFIX?= /usr/local +SBIN?= ${PREFIX}/sbin +LIBEXEC?= ${PREFIX}/lib +CONFDIR?= /etc/dma +MAN?= ${PREFIX}/share/man +VAR?= /var +DMASPOOL?= ${VAR}/spool/dma +VARMAIL?= ${VAR}/mail +SYMLINK?= -s # or empty to create hard link + +YACC?= yacc +LEX?= lex +LN?= ln + +OBJS= aliases_parse.o aliases_scan.o base64.o conf.o crypto.o +OBJS+= dma.o dns.o local.o mail.o net.o spool.o util.o +OBJS+= dfcompat.o + +all: dma dma-mbox-create + +clean: + -rm -f .depend dma dma-mbox-create *.[do] + -rm -f aliases_parse.[ch] aliases_scan.c + +install: all + ${INSTALL} -d ${DESTDIR}${SBIN} + ${INSTALL} -d ${DESTDIR}${MAN}/man8 ${DESTDIR}${LIBEXEC} + ${INSTALL} -m 2755 -o root -g mail dma ${DESTDIR}${SBIN} + ${INSTALL} -m 4754 -o root -g mail dma-mbox-create ${DESTDIR}${LIBEXEC} + ${INSTALL} -m 0644 dma.8 ${DESTDIR}${MAN}/man8/ + +sendmail-link: + cd ${DESTDIR}${SBIN} && ${LN} ${SYMLINK} dma sendmail + +mailq-link: + cd ${DESTDIR}${SBIN} && ${LN} ${SYMLINK} dma mailq + +install-spool-dirs: + ${INSTALL} -d -m 2775 -o root -g mail ${DESTDIR}${DMASPOOL} + ${INSTALL} -d -m 2775 -o root -g mail ${DESTDIR}${VARMAIL} + +permissions: + -${CHGRP} mail ${DESTDIR}${VARMAIL}/* + -${CHMOD} g+w ${DESTDIR}${VARMAIL}/* + -${CHMOD} 660 ${DESTDIR}${DMASPOOL}/flush + +install-etc: + ${INSTALL} -d ${DESTDIR}${CONFDIR} + @if [ -e ${DESTDIR}${CONFDIR}/dma.conf ]; then \ + echo "Not overwriting ${DESTDIR}${CONFDIR}/dma.conf."; \ + else \ + echo ${INSTALL} -m 644 -o root -g mail dma.conf ${DESTDIR}${CONFDIR}; \ + ${INSTALL} -m 644 -o root -g mail dma.conf ${DESTDIR}${CONFDIR}; \ + fi + @if [ -e ${DESTDIR}${CONFDIR}/auth.conf ]; then \ + echo "Not overwriting ${DESTDIR}${CONFDIR}/auth.conf."; \ + else \ + echo ${INSTALL} -m 640 -o root -g mail auth.conf ${DESTDIR}${CONFDIR}; \ + ${INSTALL} -m 640 -o root -g mail auth.conf ${DESTDIR}${CONFDIR}; \ + fi + +aliases_parse.c: aliases_parse.y + ${YACC} -d -o aliases_parse.c aliases_parse.y + +aliases_scan.c: aliases_scan.l + ${LEX} -t aliases_scan.l > aliases_scan.c + +.SUFFIXES: .c .o + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -include dfcompat.h -o $@ -c $< + +dma: ${OBJS} + ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD} + + +dch: + dch --release-heuristic changelog -v ${debversion} + + +ppa: + @if [ -z '${DEB_DIST}' ]; then echo "please set DEB_DIST to build"; exit 1; fi + dch -v "${debversion}~${DEB_DIST}" -D ${DEB_DIST} "${DEB_DIST} build" -b + debuild -S -sa + ver=$$(dpkg-parsechangelog -n1 | awk '$$1 == "Version:" { print $$2 }'); \ + dput ppa:corecode/dma ../dma_$${ver}_source.changes diff --git a/Makefile.etc b/Makefile.etc new file mode 100644 index 000000000000..fca23b94a604 --- /dev/null +++ b/Makefile.etc @@ -0,0 +1,15 @@ +# $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 diff --git a/README.markdown b/README.markdown new file mode 100644 index 000000000000..13ff20c9aa41 --- /dev/null +++ b/README.markdown @@ -0,0 +1,32 @@ +dma -- 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 +-------- + + 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> diff --git a/TODO b/TODO new file mode 100644 index 000000000000..01de465d66af --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +- unquote/handle quoted local recipients +- use proper sysexit codes +- handle/use ESMTP extensions +- .forward support +- suggest way to run a queue flush on boot diff --git a/VERSION b/VERSION new file mode 100644 index 000000000000..490a0cdcc139 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +v0.9 diff --git a/aliases_parse.y b/aliases_parse.y new file mode 100644 index 000000000000..a5a9e7b32f07 --- /dev/null +++ b/aliases_parse.y @@ -0,0 +1,112 @@ +%{ + +#include +#include +#include +#include "dma.h" + +extern int yylineno; +static void yyerror(const char *); +int yywrap(void); +int yylex(void); + +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; + } + ; + +%% diff --git a/aliases_scan.l b/aliases_scan.l new file mode 100644 index 000000000000..809d1e168731 --- /dev/null +++ b/aliases_scan.l @@ -0,0 +1,24 @@ +%{ + +#include +#include "aliases_parse.h" + +#define YY_NO_INPUT + +int yylex(void); +%} + +%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; + +%% diff --git a/auth.conf b/auth.conf new file mode 100644 index 000000000000..393a80a8ab5f --- /dev/null +++ b/auth.conf @@ -0,0 +1,4 @@ +# $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 diff --git a/base64.c b/base64.c new file mode 100644 index 000000000000..663763e8c773 --- /dev/null +++ b/base64.c @@ -0,0 +1,135 @@ +/* + * 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; +} + diff --git a/conf.c b/conf.c new file mode 100644 index 000000000000..919ab7c4d77e --- /dev/null +++ b/conf.c @@ -0,0 +1,246 @@ +/* + * 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(1); + } + 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(1, "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(1, 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(1, "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(1, "can not open config `%s'", config_path); + /* NOTREACHED */ + } + + while (!feof(conf)) { + if (fgets(line, sizeof(line), conf) == NULL) + break; + lineno++; + + chomp(line); + + /* We hit a comment */ + if (strchr(line, '#')) + *strchr(line, '#') = 0; + + data = line; + word = strsep(&data, EQS); + + /* Ignore empty lines */ + if (word == NULL || *word == 0) + continue; + + if (data != NULL && *data != 0) + data = strdup(data); + else + data = NULL; + + if (strcmp(word, "SMARTHOST") == 0 && data != NULL) + config.smarthost = data; + else if (strcmp(word, "PORT") == 0 && data != NULL) + config.port = atoi(data); + else if (strcmp(word, "ALIASES") == 0 && data != NULL) + config.aliases = data; + else if (strcmp(word, "SPOOLDIR") == 0 && data != NULL) + config.spooldir = data; + else if (strcmp(word, "AUTHPATH") == 0 && data != NULL) + config.authpath= data; + else if (strcmp(word, "CERTFILE") == 0 && data != NULL) + config.certfile = data; + else if (strcmp(word, "MAILNAME") == 0 && data != NULL) + config.mailname = data; + else if (strcmp(word, "MASQUERADE") == 0 && data != NULL) { + char *user = NULL, *host = NULL; + if (strrchr(data, '@')) { + host = strrchr(data, '@'); + *host = 0; + host++; + user = data; + } else { + host = data; + } + if (host && *host == 0) + host = NULL; + if (user && *user == 0) + user = NULL; + config.masquerade_host = host; + config.masquerade_user = user; + } else if (strcmp(word, "STARTTLS") == 0 && data == NULL) + config.features |= STARTTLS; + else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) + 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(1, "syntax error in %s:%d", config_path, lineno); + /* NOTREACHED */ + } + } + + if ((config.features & NULLCLIENT) && config.smarthost == NULL) { + errlogx(1, "%s: NULLCLIENT requires SMARTHOST", config_path); + /* NOTREACHED */ + } + + fclose(conf); +} diff --git a/crypto.c b/crypto.c new file mode 100644 index 000000000000..897b55bfdcfc --- /dev/null +++ b/crypto.c @@ -0,0 +1,312 @@ +/* + * 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" + +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 +smtp_init_crypto(int fd, int feature) +{ + 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(); + + meth = TLSv1_client_method(); + + 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; + + send_remote_command(fd, "EHLO %s", hostname()); + if (read_remote(fd, 0, NULL) == 2) { + send_remote_command(fd, "STARTTLS"); + if (read_remote(fd, 0, NULL) != 2) { + if ((feature & TLS_OPP) == 0) { + syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available: %s", neterr); + return (1); + } else { + syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available: %s", neterr); + return (0); + } + } + } + /* End of TLS init phase, enable SSL_write/read */ + config.features &= ~NOSSL; + } + + config.ssl = SSL_new(ctx); + if (config.ssl == NULL) { + syslog(LOG_NOTICE, "remote delivery deferred: SSL struct creation failed: %s", + ssl_errstr()); + return (1); + } + + /* Set ssl to work in client mode */ + SSL_set_connect_state(config.ssl); + + /* Set fd for SSL in/output */ + error = SSL_set_fd(config.ssl, fd); + if (error == 0) { + syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s", + ssl_errstr()); + return (1); + } + + /* Open SSL connection */ + error = SSL_connect(config.ssl); + if (error < 0) { + 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()); + } + X509_free(cert); + + return (0); +} + +/* + * hmac_md5() taken out of RFC 2104. This RFC was written by H. Krawczyk, + * M. Bellare and R. Canetti. + * + * text pointer to data stream + * text_len length of data stream + * key pointer to authentication key + * key_len length of authentication key + * digest caller digest to be filled int + */ +void +hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len, + unsigned char* digest) +{ + MD5_CTX context; + unsigned char k_ipad[65]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[65]; /* outer padding - + * key XORd with opad + */ + unsigned char tk[16]; + int i; + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + + MD5_CTX tctx; + + MD5_Init(&tctx); + MD5_Update(&tctx, key, key_len); + MD5_Final(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + bzero( k_ipad, sizeof k_ipad); + bzero( k_opad, sizeof k_opad); + bcopy( key, k_ipad, key_len); + bcopy( key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + MD5_Init(&context); /* init context for 1st + * pass */ + MD5_Update(&context, k_ipad, 64); /* start with inner pad */ + MD5_Update(&context, text, text_len); /* then text of datagram */ + MD5_Final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + MD5_Init(&context); /* init context for 2nd + * pass */ + MD5_Update(&context, k_opad, 64); /* start with outer pad */ + MD5_Update(&context, digest, 16); /* then results of 1st + * hash */ + MD5_Final(digest, &context); /* finish up 2nd pass */ +} + +/* + * CRAM-MD5 authentication + */ +int +smtp_auth_md5(int fd, char *login, char *password) +{ + unsigned char digest[BUF_SIZE]; + char buffer[BUF_SIZE], ascii_digest[33]; + char *temp; + int len, i; + static char hextab[] = "0123456789abcdef"; + + temp = calloc(BUF_SIZE, 1); + memset(buffer, 0, sizeof(buffer)); + memset(digest, 0, sizeof(digest)); + memset(ascii_digest, 0, sizeof(ascii_digest)); + + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH CRAM-MD5"); + if (read_remote(fd, sizeof(buffer), buffer) != 3) { + syslog(LOG_DEBUG, "smarthost authentication:" + " AUTH cram-md5 not available: %s", neterr); + /* if cram-md5 is not available */ + free(temp); + return (-1); + } + + /* skip 3 char status + 1 char space */ + base64_decode(buffer + 4, temp); + hmac_md5((unsigned char *)temp, strlen(temp), + (unsigned char *)password, strlen(password), digest); + free(temp); + + ascii_digest[32] = 0; + for (i = 0; i < 16; i++) { + ascii_digest[2*i] = hextab[digest[i] >> 4]; + ascii_digest[2*i+1] = hextab[digest[i] & 15]; + } + + /* prepare answer */ + snprintf(buffer, BUF_SIZE, "%s %s", login, ascii_digest); + + /* encode answer */ + len = base64_encode(buffer, strlen(buffer), &temp); + if (len < 0) { + syslog(LOG_ERR, "can not encode auth reply: %m"); + return (-1); + } + + /* send answer */ + send_remote_command(fd, "%s", temp); + free(temp); + if (read_remote(fd, 0, NULL) != 2) { + syslog(LOG_WARNING, "remote delivery deferred:" + " AUTH cram-md5 failed: %s", neterr); + return (-2); + } + + return (0); +} diff --git a/debian/NEWS b/debian/NEWS new file mode 100644 index 000000000000..36ed11a44026 --- /dev/null +++ b/debian/NEWS @@ -0,0 +1,29 @@ +dma (0.0.2010.06.17-3) unstable; urgency=low + + The default delivery mode has been changed to immediate, as it is in + the upstream version of dma; the DEFER keyword is now disabled by default + in dma.conf. + + -- Peter Pentchev Tue, 27 Jul 2010 13:26:48 +0300 + +dma (0.0.2010.06.17-1) unstable; urgency=low + + The dma spool directory format has changed. The Debian package of dma now + recommends a separate package containing the dma-migrate utility; if it is + present, it will be invoked at each periodic dma queue flush and attempt to + convert the existing old-style queued messages to the new format. In most + cases, this should not incur any performance penalties in normal operation, + since dma-migrate will scan the spool directory and ignore any new messages + (they should already be in the new format); however, if it appears that + the periodic queue flush runs take longer than usual to start up, you may + remove the dma-migrate package once you have ascertained that your queue + directory (/var/spool/dma) only contains files with names beginning with + the letters M or Q. + + This version of dma knows how to perform MX lookups, so remote delivery is + now possible directly, not through a smarthost. However, a smarthost setup + might still be preferred on many systems for various reasons - e.g. dynamic + address assignment, a central outgoing mailserver, a roaming laptop, etc. + + -- Peter Pentchev Mon, 21 Jun 2010 11:03:57 +0300 + diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 000000000000..564aaa75be70 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,12 @@ +dma for Debian +-------------- + +Smarthost operation by default - needs to be configured! + +After first installing dma, you need to configure it for proper operation - +whether it should deliver all outgoing e-mail messages through a single +smarthost or attempt to contact the remote mail servers directly. This should +be configured through the debconf questions, but you may change the setting +using the SMARTHOST directive in the /etc/dma/dma.conf file. + + -- Simon Schubert <2@0x2c.org> Fri, 29 Oct 2010 00:25:48 +0200 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 000000000000..4d5530043529 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,297 @@ +dma (0.9) unstable; urgency=low + + [ Ed Maste ] + * Be explicit about missing user. + * Allow DMA_ROOT_USER & DMA_GROUP to be overridden. + * Add compat #ifdef for older OpenSSL + * Add CONF_DIR, as in Makefile + * More detailed error message for tmp file failure. + + [ Simon Schubert ] + * spool.c: bzero contents of pointer + + -- Simon Schubert <2@0x2c.org> Mon, 03 Jun 2013 15:58:44 +0200 + +dma (0.8) unstable; urgency=low + + [ Gert van den Berg ] + * Added some more documentation on compiling + * Make Makefile and README consistent with INSTALL + + [ Sascha Wildner ] + * dma.8: Fix a few small issues. + + [ Simon Schubert ] + * dma.8: we only have 2 config files at the moment + * Merge pull request #2 from mohag/master + * don't treat -options following -q as argument to it + * deliver_remote: propagate back DNS errors + * don't complain when we can't lock a queue file during flush + * implement queue flushing prod + + -- Simon Schubert <2@0x2c.org> Fri, 30 Mar 2012 12:03:54 +0200 + +dma (0.7) unstable; urgency=low + + [ Simon Schubert ] + * add semicolon before date in Received: header + * parse_conf: fix bug with masqueraded domains + * clear up warnings found by clang static analysis + * mark printf-alike functions + + -- Simon Schubert <2@0x2c.org> Tue, 03 Jan 2012 14:53:43 +0100 + +dma (0.6) unstable; urgency=low + + [ Simon Schubert ] + * deliver_local: quote "From " more liberally + + -- Simon Schubert <2@0x2c.org> Wed, 07 Dec 2011 12:42:22 +0100 + +dma (0.5) unstable; urgency=low + + [ Simon Schubert ] + * implement masquerading using the MASQUERADE config option + * access config files at CONF_PATH, add makefile target to install conf files + * implement the "*" catch-all alias + + -- Simon Schubert <2@0x2c.org> Wed, 16 Nov 2011 13:34:43 +0100 + +dma (0.4) unstable; urgency=low + + [ Simon Schubert ] + * Makefile: put libraries at the end when linking + * LICENSE: add + * Merge commit 'refs/merge-requests/3' of git://gitorious.org/dma/dma + * Merge commit 'refs/merge-requests/4' of git://gitorious.org/dma/dma + * Add symlink for sendmail which is expected by many packages + * Makefile: create spool directories in a separate target + * Makefile: add symlink for mailq + * README: elaborate, use markdown + + [ Peter Pentchev ] + * Fix straight SSL/TLS delivery to remote MX's. + * Fix a minor memory leak discovered by cppcheck. + + -- Simon Schubert <2@0x2c.org> Wed, 16 Nov 2011 00:08:28 +0100 + +dma (0.3) unstable; urgency=low + + [ Simon Schubert ] + * todo: we create mboxes properly now + * dma-mbox-create: group mail only needs to write to mboxes + * errlog: preserve errno + * dma-mbox-create: add error/status logging + * dns_get_mx_list: handle errors properly + + [ Peter Pentchev ] + * Make add_host() really return an error code. + + [ Simon Schubert ] + * readmail: accept mail without newline at the end + + [ Peter Pentchev ] + * In OpenSSL 1.0, TLSv1_client_method() returns a const pointer. + + [ Simon Schubert ] + * make dma compile again on DragonFly + * parse_addrs: fix parsing for multiple + + -- Simon Schubert <2@0x2c.org> Sat, 09 Jul 2011 02:38:05 +0200 + +dma (0.2) unstable; urgency=low + + * Update to dma 0.2 + - debian: better mark as UNRELEASED + - add ppa makefile recipe + - TODO: suggest way to run a queue flush on boot + - partially adopt 34-manpage-defaults.patch: AUTHPATH is not set by default + - Revert "debian: better mark as UNRELEASED" + - setlogident: openlog() wants a static variable + - writequeuef: create files with g+rw + - drop privileges when run by root + - implement mbox creation via setuid helper + - debian: build with consistent flags + - debian: remove unused files + - debian: fix lintian warnings and errors + - make ppa: force lower version number + - make ppa: proper name + + -- Simon Schubert <2@0x2c.org> Sun, 31 Oct 2010 23:57:50 +0100 + +dma (0.1) unstable; urgency=low + + * Update dma to 0.1 + + -- Simon Schubert <2@0x2c.org> Fri, 29 Oct 2010 00:57:26 +0200 + +dma (0.0.2010.06.17-6) unstable; urgency=low + + * Add the 37-gnu-hurd patch to really fix the FTBFS on GNU/Hurd. + * Convert several shell output assignments from = to := + * Switch to bzip2 compression for the Debian tarball. + + -- Peter Pentchev Sun, 17 Oct 2010 00:08:33 +0300 + +dma (0.0.2010.06.17-5) unstable; urgency=low + + * Only use SA_NOCLDWAIT if available to fix the Hurd FTBFS. + + -- Peter Pentchev Thu, 07 Oct 2010 11:42:23 +0300 + +dma (0.0.2010.06.17-4) unstable; urgency=low + + * Fix an infinite loop in dma-migrate if char is unsigned. + Closes: #599172 + * Switch to Git and point the Vcs-* fields to Gitorious. + + -- Peter Pentchev Wed, 06 Oct 2010 17:30:29 +0300 + +dma (0.0.2010.06.17-3) unstable; urgency=low + + * Update the debconf translations: + - French by Steve Petruzzello; Closes: #587883 + * Bump Standards-Version to 3.9.1 with no changes. + * Disable deferred delivery by default, as in the upstream version: + - in the 03-debian-locations patch, comment the DEFER keyword in dma.conf, + as it is upstream + - refresh the 11-double-bounce.patch + - reword the README.Debian section on deferred delivery a bit + - add a news blurb + + -- Peter Pentchev Tue, 27 Jul 2010 13:34:27 +0300 + +dma (0.0.2010.06.17-2) unstable; urgency=low + + * Quick on the heels of -1 to fix a momentary lapse of reason on my part: + in the 03-debian-locations patch, revert part of the 0.0.2010.06.17-1 + change: do not set a un-overrideable default for the deferred delivery! + Closes: #587593 + * Update the debconf translations: + - Japanese by Hideki Yamane; Closes: #587543 + + -- Peter Pentchev Wed, 30 Jun 2010 11:59:46 +0300 + +dma (0.0.2010.06.17-1) unstable; urgency=low + + * New upstream version: + - no longer reports the remote port number; Closes: #544820 + - fixes some queue locking problems; Closes: #582593 + - adapt the rules file to use the GNU Makefile instead of the BSD one + - drop pmake from Build-Depends + - remove the 01-debian-build patch, overtaken by upstream changes + - in the 03-debian-locations patch, make the Debian defaults actual + defaults for the dma binary, not just in the dma.conf file + - adapt the 04-debian-setgid patch for the GNU Makefile + - in the 10-liblockfile patch, change the GNU Makefile, too + - enhance the 11-double-bounce patch a bit: + - use dma's own delqueue() function instead of a naive unlink() so + all the queue files are cleaned up + - document the Debian default for DBOUNCEPROG in the manual page + - resurrect the 13-hardening patch, correcting a couple of + unchecked asprintf(3) and vasprintf(3) invocations + - the functionality of the 20-parse-recipient patch was implemented + upstream in a different way, so replace it with a small bugfix + - remove the 22-openfiles patch, overtaken by upstream changes + - in the 24-random-message-id patch, change the GNU Makefile, too + - add the 27-int-size patch to cast a variable to the correct type + - add the 28-valid-recipient patch to fix parsing recipients out of + the message body + - add the 29-double-free patch to fix a double-free error + - add the 30-ldflags patch to honor LDFLAGS if specified + - refresh the 09-typos, 17-mailname, 23-dirent-d_type, and + 25-unsupported-starttls patches + - teach the dbounce-simple-safecat handler about the M*/Q* spool + files scheme + * Bump Standards-Version to 3.9.0 with no changes. + * Update the copyright file: + - bring it up to the latest revision of the DEP 5 + - update the upstream copyright notices + - bump the year on my copyright notice + * Remove the diffsrc rules target which was intended for my own + internal use, but has outlived its time. + * Use dpkg-buildflags from dpkg-dev >= 1.15.7~ to get the default + values for CFLAGS, CPPFLAGS, and LDFLAGS; no longer rely on + dpkg-buildpackage to set them by default. + * Add the dma-migrate utility (in a separate binary package) to convert + spool files to the new queue format (M* and Q* files for each message) + * Add a dma-migrate invocation to dma.cron.d + * Shorten the Vcs-Browser URL. + * Add the 31-sigalrm-backoff patch to force a delivery attempt on SIGALRM. + * Properly substitute the debconf-supplied values for DBOUNCEPROG and + SMARTHOST into dma.conf even if they are empty without generating + an invalid dma.conf file. + * Remove the smarthost default; dma does MX lookups now, so it doesn't + really *need* one anymore. + * Reword the debconf relayhost question - dma does not really need + a smarthost anymore. + * Update the debconf translations: + - Bulgarian + - Portuguese by Américo Monteiro + - German by Helge Kreutzmann; Closes: #586531 + - Russian by Yuri Kozlov; Closes: #586579 + - Czech by Michal Simunek; Closes: #586791 + - Swedish by Martin Bagge; Closes: #586825 + - Spanish by Francisco Javier Cuadrado; Closes: #587088 + * Update the smarthost configuration information in README.Debian. + * Add the 32-comment-uncomment patch to correct the manual page wording. + * Add the 33-opportunistic-tls patch to allow remote connections to proceed + even if the STARTTLS negotiation fails. + * Fix the 25-unsupported-starttls patch to actually error out if the SSL + negotiation fails. + * Forward all the non-Debian-specific patches upstream to Simon Schubert. + * Add the 34-manpage-defaults patch to properly document what dma will use + as default values if not specified in the config file instead of what + the default config file specifies. Closes: #544748 + * Add the 35-delivery-retry patch to try local deliveries a bit more often + and to randomize the delivery timeout to avoid locking issues. + + -- Peter Pentchev Mon, 28 Jun 2010 23:26:36 +0300 + +dma (0.0.2009.07.17-3) unstable; urgency=low + + * Really install the files in /etc/dma/ as root/mail/640 and + change the ownership of the existing files in the postinst script + when upgrading from older versions. Closes: #544664 + * Install the /usr/bin/mailq and /usr/bin/newaliases symlinks. + Closes: #558421 + * Switch to the 3.0 (quilt) source format. + * Update the debconf translations: + - add German. Closes: #552754 + - add Japanese. Closes: #554515 + - remove a double space and unfuzzy the translations. Closes: #552586 + * Fix a crash when the SMTP server does not support STARTTLS. + Closes: #547594 + * Always use the user-supplied value from the debconf query for + the smarthost and the double-bounce program. This may result in + debconf overriding a manually-edited config file, so add a note to + dma.conf stating that these values are handled via debconf. + Closes: #544663 + * Fix a misspelling of dma/dbounceprog as dma/defer in the debconf + configuration script. Oops. + + -- Peter Pentchev Sat, 19 Dec 2009 14:35:10 +0200 + +dma (0.0.2009.07.17-2) unstable; urgency=low + + * Allow the spool directory to live on a filesystem that does not + set the d_type member of the dirent structure, like XFS. + Closes: #544357 + * Randomize the Message-Id a bit more. Closes: #544475 + * Bump Standards-Version to 3.8.3 with no changes. + * Only enable the build hardening wrapper if the "hardening" build + option is specified. + * Switch the copyright file header from the Wiki to DEP 5. + * Remove the manual page ".Dx" patch - the groff version in Squeeze + knows about the .Dx mdoc macro. Add a lintian override for + the "Unknown DragonFly version" error. + * Convert the patch file headers to the DEP 3 format. + + -- Peter Pentchev Tue, 01 Sep 2009 13:36:33 +0300 + +dma (0.0.2009.07.17-1) unstable; urgency=low + + * Initial release + (Closes: #511410, #533458, #533614, #533890, #534101, #534860) + + -- Peter Pentchev Tue, 11 Aug 2009 16:08:41 +0300 diff --git a/debian/compat b/debian/compat new file mode 100644 index 000000000000..7f8f011eb73d --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 000000000000..6eeea08aa7c0 --- /dev/null +++ b/debian/control @@ -0,0 +1,37 @@ +Source: dma +Section: mail +Priority: optional +Maintainer: Peter Pentchev +DM-Upload-Allowed: yes +Build-Depends: debhelper (>= 7.0.50), byacc, dpkg-dev (>= 1.15.7~), flex, hardening-wrapper, libssl-dev, po-debconf +Standards-Version: 3.9.1 +Homepage: http://devel.ringlet.net/mail/dma/ +Vcs-Git: git://gitorious.org/dma-roam/pkg-debian.git +Vcs-Browser: http://gitorious.org/dma-roam/pkg-debian + +Package: dma +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Provides: mail-transport-agent +Conflicts: mail-transport-agent +Replaces: mail-transport-agent +Recommends: dma-migrate +Description: lightweight mail transport agent + The DragonFly Mail Agent is a small Mail Transport Agent (MTA), + designed for home and office use. It accepts mails from local Mail + User Agents (MUA) and delivers them either to local mailboxes or + remote SMTP servers. Remote delivery includes support for features + such as TLS/SSL and SMTP authentication. + . + dma is not intended as a replacement for full-featured MTAs like + Sendmail, Postfix, or Exim. Consequently, dma does not listen on + port 25 for incoming connections. + +Package: dma-migrate +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Enhances: dma +Description: migration utility for the DragonFly Mail Agent's spool files + The dma-migrate utility examines the DragonFly Mail Agent's mail queue + and performs any conversions from old message file formats to the most + recent one as needed. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 000000000000..4ad8fbb7a80f --- /dev/null +++ b/debian/copyright @@ -0,0 +1,91 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: dma + +Files: base64.c +Copyright: Copyright (c) 1995-2001 Kungliga Tekniska Högskolan + (Royal Institute of Technology, Stockholm, Sweden). + All rights reserved. +License: BSD-3 + +Files: conf.c crypto.c net.c +Copyright: 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. +License: BSD-3 + +Files: dfcompat.c +Copyright: Copyright (c) 1998 Todd C. Miller + Copyright (c) 1998, M. Warner Losh All rights reserved. +License: BSD-1 BSD-2 + +Files: dma.8 +Copyright: Copyright (c) 2008 The DragonFly Project. All rights reserved. +License: BSD-3 + +Files: dma.c dns.c mail.c spool.c util.c +Copyright: Copyright (c) 2008 The DragonFly Project. All rights reserved. + This code is derived from software contributed to The DragonFly Project + by Simon 'corecode' Schubert . +License: BSD-3 + +Files: dma.h +Copyright: Copyright (c) 2008 The DragonFly Project. All rights reserved. + This code is derived from software contributed to The DragonFly Project + by Simon 'corecode' Schubert and + Matthias Schmidt . +License: BSD-3 + +Files: debian/* +Copyright: Copyright (c) 2009, 2010 Peter Pentchev. +License: BSD-3 + +License: BSD-3 + 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. + +License: BSD-1 + 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. + +License: BSD-2 + 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. diff --git a/debian/dma-migrate.dirs b/debian/dma-migrate.dirs new file mode 100644 index 000000000000..b174b445af42 --- /dev/null +++ b/debian/dma-migrate.dirs @@ -0,0 +1,2 @@ +usr/sbin +usr/share/man/man8 diff --git a/debian/dma-migrate.install b/debian/dma-migrate.install new file mode 100644 index 000000000000..1c35b0afb178 --- /dev/null +++ b/debian/dma-migrate.install @@ -0,0 +1 @@ +debian/migrate/dma-migrate usr/sbin diff --git a/debian/dma-migrate.manpages b/debian/dma-migrate.manpages new file mode 100644 index 000000000000..3629dbe3a288 --- /dev/null +++ b/debian/dma-migrate.manpages @@ -0,0 +1 @@ +debian/migrate/dma-migrate.8 diff --git a/debian/dma.dirs b/debian/dma.dirs new file mode 100644 index 000000000000..f10c0054ee33 --- /dev/null +++ b/debian/dma.dirs @@ -0,0 +1,4 @@ +etc/dma +usr/sbin +usr/share/lintian/overrides +usr/share/man/man8 diff --git a/debian/dma.links b/debian/dma.links new file mode 100644 index 000000000000..d9ac22a2cdc2 --- /dev/null +++ b/debian/dma.links @@ -0,0 +1,6 @@ +usr/sbin/dma usr/bin/mailq +usr/sbin/dma usr/bin/newaliases +usr/sbin/dma usr/sbin/sendmail +usr/share/man/man8/dma.8 usr/share/man/man8/mailq.8 +usr/share/man/man8/dma.8 usr/share/man/man8/newaliases.8 +usr/share/man/man8/dma.8 usr/share/man/man8/sendmail.8 diff --git a/debian/dma.lintian-overrides b/debian/dma.lintian-overrides new file mode 100644 index 000000000000..03f5d6eb8369 --- /dev/null +++ b/debian/dma.lintian-overrides @@ -0,0 +1,8 @@ +dma: manpage-has-errors-from-man *Unknown DragonFly version* +dma: non-standard-dir-in-var var/mail/ +dma: non-standard-dir-perm var/spool/dma/ 0770 != 0755 +dma: non-standard-dir-perm var/spool/dma/ 2775 != 0755 +dma: non-standard-file-perm etc/dma/auth.conf 0640 != 0644 +dma: no-upstream-changelog +dma: setgid-binary usr/sbin/dma 2755 root/mail +dma: setuid-binary usr/lib/dma-mbox-create 4754 root/mail diff --git a/debian/migrate/Makefile b/debian/migrate/Makefile new file mode 100644 index 000000000000..cc7d3476c008 --- /dev/null +++ b/debian/migrate/Makefile @@ -0,0 +1,4 @@ +all: dma-migrate + +clean: + rm -f dma-migrate dma-migrate.o diff --git a/debian/migrate/NEWS b/debian/migrate/NEWS new file mode 100644 index 000000000000..29dca0dd335b --- /dev/null +++ b/debian/migrate/NEWS @@ -0,0 +1,6 @@ +Change log for dma-migrate, the DragonFly Mail Agent queue migration utility. + +0.01 not yet ;) + - first public release + +Comments: Peter Pentchev diff --git a/debian/migrate/dma-migrate.8 b/debian/migrate/dma-migrate.8 new file mode 100644 index 000000000000..e40acef5b13d --- /dev/null +++ b/debian/migrate/dma-migrate.8 @@ -0,0 +1,98 @@ +.\" Copyright (c) 2010 Peter Pentchev +.\" 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. +.\" +.Dd May 11, 2009 +.Dt dma-migrate 8 +.Os +.Sh NAME +.Nm dma-migrate +.Nd convert the DragonFly Mail Agent's queue files +.Sh SYNOPSIS +.Nm +.Op Fl v +.Op Fl d Ar spooldir +.Nm +.Op Fl h | Fl V +.Sh DESCRIPTION +The +.Nm +utility is used to convert the mail queue files in the +.Xr dma 8 +spool directory to the latest spool directory format supported by +the installed version of +.Xr dma 8 . +Currently it only handles the conversion from a single file containing +both message and delivery metadata to the M/Q format. +.Pp +The following command-line options are available: +.Bl -tag -width indent +.It Fl d +Specify the location of the +.Xr dma 8 +spool directory, default +.Pa /var/spool/dma . +.It Fl h +Display usage information and exit. +.It Fl V +Display program version information and exit. +.It Fl v +Verbose output - display diagnostic messages. +.El +.Sh ENVIRONMENT +The operation of the +.Nm +utility is currently not influenced by environment variables. +.Sh FILES +The +.Nm +utility looks for the +.Xr dma 8 +mail queue files in the +.Pa /var/spool/dma +directory, unless another location is specified by the +.Fl d +command-line option. +.Sh EXIT STATUS +The +.Nm +utility will scan the whole spool directory and attempt to convert all +suitable files there. +If there are no files to be converted, or if all the conversions are +successful, it will complete with an exit code of zero. +If any conversion errors are encountered, a message will be displayed +to the standard error stream and +.Nm +will exit with an exit code of 1 after attempting to convert the rest of +the suitable files in the spool directory. +.Sh SEE ALSO +.Xr dma 8 +.Sh STANDARDS +No standards documentation was harmed in the process of creating +.Nm . +.Sh BUGS +Please report any bugs in +.Nm +to the author. +.Sh AUTHOR +.An Peter Pentchev Aq roam@ringlet.net diff --git a/debian/migrate/dma-migrate.c b/debian/migrate/dma-migrate.c new file mode 100644 index 000000000000..56bfa2adbf87 --- /dev/null +++ b/debian/migrate/dma-migrate.c @@ -0,0 +1,413 @@ +/*- + * Copyright (c) 2010 Peter Pentchev + * 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __printflike +#ifdef __GNUC__ +#define __printflike(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +#define __printflike(fmtarg, firstvararg) +#endif +#endif + +#define DEFAULT_SPOOLDIR "/var/spool/dma" + +static int verbose = 0; +static char copybuf[BUFSIZ]; + +static int dma_migrate(int, const char *); + +static int open_locked(const char *, int, ...); +static void cleanup_file(int, char *); + +static void usage(int); +static void version(void); +static void debug(const char *, ...) __printflike(1, 2); + +int +main(int argc, char **argv) +{ + const char *spooldir; + int hflag, Vflag, errs, fd, res; + int ch; + DIR *d; + struct dirent *e; + struct stat sb; + + srandom((unsigned long)((time(NULL) ^ getpid()) + ((uintptr_t)argv))); + + hflag = Vflag = 0; + spooldir = DEFAULT_SPOOLDIR; + while (ch = getopt(argc, argv, "d:hVv"), ch != -1) + switch (ch) { + case 'd': + spooldir = optarg; + break; + + case 'h': + hflag = 1; + break; + + case 'V': + Vflag = 1; + break; + + case 'v': + verbose = 1; + break; + + case '?': + default: + usage(1); + /* NOTREACHED */ + } + if (Vflag) + version(); + if (hflag) + usage(0); + if (hflag || Vflag) + exit(0); + + argc -= optind; + argv += optind; + + /* Let's roll! */ + if (chdir(spooldir) == -1) + err(1, "Could not change into spool directory %s", spooldir); + if (d = opendir("."), d == NULL) + err(1, "Could not read spool directory %s", spooldir); + errs = 0; + while (e = readdir(d), e != NULL) { + /* Do we care about this entry? */ + debug("Read a directory entry: %s\n", e->d_name); + if (strncmp(e->d_name, "tmp_", 4) == 0 || + e->d_name[0] == 'M' || e->d_name[0] == 'Q' || + (e->d_type != DT_REG && e->d_type != DT_UNKNOWN)) + continue; + if (e->d_type == DT_UNKNOWN) + if (stat(e->d_name, &sb) == -1 || !S_ISREG(sb.st_mode)) + continue; + debug("- want to process it\n"); + + /* Try to lock it - skip it if dma is delivering the message */ + if (fd = open_locked(e->d_name, O_RDONLY|O_NDELAY), fd == -1) { + debug("- seems to be locked, skipping\n"); + continue; + } + + /* Okay, convert it to the M/Q schema */ + res = dma_migrate(fd, e->d_name); + close(fd); + if (res == -1) + errs++; + } + if (errs) + debug("Finished, %d conversion errors\n", errs); + else + debug("Everything seems to be all right\n"); + return (errs && 1); +} + +static int +dma_migrate(int fd, const char *fname) +{ + const char *id; + char *mname, *qname, *tempname, *sender, *recp, *line, *recpline; + int mfd, qfd, tempfd; + struct stat sb; + FILE *fp, *qfp, *mfp; + size_t sz, len; + static regex_t *qidreg = NULL; + + mfd = tempfd = qfd = -1; + mname = qname = sender = recp = line = NULL; + fp = qfp = NULL; + + if (fstat(fd, &sb) == -1) { + warn("Could not fstat(%s)", fname); + return (-1); + } + /* + * Let's just blithely assume that the queue ID *is* the filename, + * since that's the way dma did things so far. + * Well, okay, let's check it. + */ + if (qidreg == NULL) { + regex_t *nreg; + + if ((nreg = malloc(sizeof(*qidreg))) == NULL) { + warn("Could not allocate memory for a regex"); + return (-1); + } + if (regcomp(nreg, "^[a-fA-F0-9]\\+\\.[a-fA-F0-9]\\+$", 0) + != 0) { + warnx("Could not compile a dma queue ID regex"); + free(nreg); + return (-1); + } + qidreg = nreg; + } + if (regexec(qidreg, fname, 0, NULL, 0) != 0) { + warnx("The name '%s' is not a valid dma queue ID", fname); + return (-1); + } + id = fname; + debug(" - queue ID %s\n", id); + if (asprintf(&mname, "M%s", id) == -1 || + asprintf(&tempname, "tmp_%s", id) == -1 || + asprintf(&qname, "Q%s", id) == -1 || + mname == NULL || tempname == NULL || qname == NULL) + goto fail; + + /* Create the message placeholder early to avoid races */ + mfd = open_locked(mname, O_CREAT | O_EXCL | O_RDWR, 0600); + if (mfd == -1) { + warn("Could not create temporary file %s", mname); + goto fail; + } + if (stat(qname, &sb) != -1 || errno != ENOENT || + stat(tempname, &sb) != -1 || errno != ENOENT) { + warnx("Some of the queue files for %s already exist", fname); + goto fail; + } + debug(" - mfd %d names %s, %s, %s\n", mfd, mname, tempname, qname); + + fp = fdopen(fd, "r"); + if (fp == NULL) { + warn("Could not reopen the descriptor for %s", fname); + goto fail; + } + + /* Parse the header of the old-format message file */ + /* ...sender... */ + if (getline(&sender, &sz, fp) == -1) { + warn("Could not read the initial line from %s", fname); + goto fail; + } + sz = strlen(sender); + while (sz > 0 && (sender[sz - 1] == '\n' || sender[sz - 1] == '\r')) + sender[--sz] = '\0'; + if (sz == 0) { + warnx("Empty sender line in %s", fname); + goto fail; + } + debug(" - sender %s\n", sender); + /* ...recipient(s)... */ + len = strlen(fname); + recpline = NULL; + while (1) { + if (getline(&line, &sz, fp) == -1) { + warn("Could not read a recipient line from %s", fname); + goto fail; + } + sz = strlen(line); + while (sz > 0 && + (line[sz - 1] == '\n' || line[sz - 1] == '\r')) + line[--sz] = '\0'; + if (sz == 0) { + free(line); + line = NULL; + break; + } + if (recp == NULL && + strncmp(line, fname, len) == 0 && line[len] == ' ') { + recp = line + len + 1; + recpline = line; + } else { + free(line); + } + line = NULL; + } + if (recp == NULL) { + warnx("Could not find its own recipient line in %s", fname); + goto fail; + } + /* ..phew, finished with the header. */ + + tempfd = open_locked(tempname, O_CREAT | O_EXCL | O_RDWR, 0600); + if (tempfd == -1) { + warn("Could not create a queue file for %s", fname); + goto fail; + } + qfp = fdopen(tempfd, "w"); + if (qfp == NULL) { + warn("Could not fdopen(%s) for %s", tempname, fname); + goto fail; + } + mfp = fdopen(mfd, "w"); + if (mfp == NULL) { + warn("Could not fdopen(%s) for %s", mname, fname); + goto fail; + } + fprintf(qfp, "ID: %s\nSender: %s\nRecipient: %s\n", id, sender, recp); + fflush(qfp); + fsync(tempfd); + + /* Copy the message file over to mname */ + while ((sz = fread(copybuf, 1, sizeof(copybuf), fp)) > 0) + if (fwrite(copybuf, 1, sz, mfp) != sz) { + warn("Could not copy the message from %s to %s", + fname, mname); + goto fail; + } + if (ferror(fp)) { + warn("Could not read the full message from %s", fname); + goto fail; + } + fflush(mfp); + fsync(mfd); + + if (rename(tempname, qname) == -1) { + warn("Could not rename the queue file for %s", fname); + goto fail; + } + qfd = tempfd; + tempfd = -1; + if (unlink(fname) == -1) { + warn("Could not remove the old converted file %s", fname); + goto fail; + } + + fclose(fp); + fclose(qfp); + free(sender); + free(line); + free(recpline); + free(mname); + free(qname); + free(tempname); + return (0); + +fail: + if (fp != NULL) + fclose(fp); + if (qfp != NULL) + fclose(qfp); + if (sender != NULL) + free(sender); + if (line != NULL) + free(line); + if (recpline != NULL) + free(recpline); + cleanup_file(mfd, mname); + cleanup_file(qfd, qname); + cleanup_file(tempfd, tempname); + return (-1); +} + +static void +cleanup_file(int fd, char *fname) +{ + if (fd != -1) { + close(fd); + unlink(fname); + } + if (fname != NULL) + free(fname); +} + +static void +usage(int ferr) +{ + const char *s = + "Usage:\tdma-migrate [-hVv] [-d spooldir]\n" + "\t-d\tspecify the spool directory (" DEFAULT_SPOOLDIR ")\n" + "\t-h\tdisplay program usage information and exit\n" + "\t-V\tdisplay program version information and exit\n" + "\t-v\tverbose operation - display diagnostic messages"; + + if (ferr) + errx(1, "%s", s); + puts(s); +} + +static void +version(void) +{ + printf("dma-migrate 0.01 (dma 0.0.2010.06.17)\n"); +} + +static void +debug(const char *fmt, ...) +{ + va_list v; + + if (verbose < 1) + return; + va_start(v, fmt); + vfprintf(stderr, fmt, v); + va_end(v); +} + +static int +open_locked(const char *fname, int flags, ...) +{ + int mode = 0; +#ifndef O_EXLOCK + int fd, save_errno; +#endif + + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + +#ifndef O_EXLOCK + 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 +} diff --git a/debian/rules b/debian/rules new file mode 100755 index 000000000000..461a0a8503f6 --- /dev/null +++ b/debian/rules @@ -0,0 +1,49 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Debian build rules for dma, the DragonFly mail agent + +DDIR= $(CURDIR)/debian +D= $(DDIR)/dma + +BUILDDEFS= DESTDIR=$D PREFIX=/usr + +CFLAGS:= $(shell dpkg-buildflags --get CFLAGS) +CPPFLAGS:= $(shell dpkg-buildflags --get CPPFLAGS) +LDFLAGS:= $(shell dpkg-buildflags --get LDFLAGS) + +CONFFILES= dma.conf auth.conf + +ifneq (,$(filter werror,$(DEB_BUILD_OPTIONS))) + CFLAGS+= -Werror +endif +ifneq (,$(filter nostrip,$(DEB_BUILD_OPTIONS))) + export STRIPFLAG= +endif +ifneq (,$(filter hardening,$(DEB_BUILD_OPTIONS))) +export DEB_BUILD_HARDENING=1 +else +export DEB_BUILD_HARDENING=0 +endif + +export CFLAGS CPPFLAGS LDFLAGS + +override_dh_auto_build: + $(MAKE) -f Makefile ${BUILDDEFS} + $(MAKE) -C $(DDIR)/migrate + +override_dh_auto_clean: + $(MAKE) -f Makefile clean + $(MAKE) -C $(DDIR)/migrate clean + +override_dh_auto_install: + $(MAKE) -f Makefile ${BUILDDEFS} install sendmail-link mailq-link install-spool-dirs install-etc + +override_dh_fixperms: + dh_fixperms -Xusr/sbin/dma -Xusr/lib/dma-mbox-create -Xvar/spool/dma -Xetc/dma + +override_dh_installchangelogs: + dh_installchangelogs -p dma + dh_installchangelogs -p dma-migrate debian/migrate/NEWS + +%: + dh $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 000000000000..89ae9db8f88b --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 000000000000..779dbfd0ca3a --- /dev/null +++ b/debian/source/options @@ -0,0 +1,2 @@ +compression = "bzip2" +compression-level = 9 diff --git a/dfcompat.c b/dfcompat.c new file mode 100644 index 000000000000..014fa88b8d49 --- /dev/null +++ b/dfcompat.c @@ -0,0 +1,122 @@ +#ifndef HAVE_STRLCPY + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ + * $FreeBSD$ + * $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$ + * $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 */ diff --git a/dfcompat.h b/dfcompat.h new file mode 100644 index 000000000000..bb0a0bbd0d5e --- /dev/null +++ b/dfcompat.h @@ -0,0 +1,24 @@ +#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 */ diff --git a/dma-mbox-create.c b/dma-mbox-create.c new file mode 100644 index 000000000000..c84652b74d47 --- /dev/null +++ b/dma-mbox-create.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2010 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 'corecode' Schubert . + * + * 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 "dma.h" + + +static void +logfail(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(1); +} + +/* + * 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 error; + char fn[PATH_MAX+1]; + int f; + + openlog("dma-mbox-create", 0, LOG_MAIL); + + errno = 0; + gr = getgrnam(DMA_GROUP); + if (!gr) + logfail("cannot find dma group `%s'", DMA_GROUP); + + mail_gid = gr->gr_gid; + + if (setgid(mail_gid) != 0) + logfail("cannot set gid to %d (%s)", mail_gid, DMA_GROUP); + if (getegid() != mail_gid) + logfail("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("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("path separator in username `%s'", user); + exit(1); + } + + /* verify the user exists */ + errno = 0; + pw = getpwnam(user); + if (!pw) + logfail("cannot find user `%s'", user); + + user_uid = pw->pw_uid; + + error = snprintf(fn, sizeof(fn), "%s/%s", _PATH_MAILDIR, user); + if (error < 0 || (size_t)error >= sizeof(fn)) { + if (error >= 0) { + errno = 0; + logfail("mbox path too long"); + } + logfail("cannot build mbox path for `%s/%s'", _PATH_MAILDIR, user); + } + + f = open(fn, O_RDONLY|O_CREAT, 0600); + if (f < 0) + logfail("cannot open mbox `%s'", fn); + + if (fchown(f, user_uid, mail_gid)) + logfail("cannot change owner of mbox `%s'", fn); + + if (fchmod(f, 0620)) + logfail("cannot change permissions of mbox `%s'", fn); + + /* file should be present with the right owner and permissions */ + + syslog(LOG_NOTICE, "successfully created mbox for `%s'", user); + + return (0); +} diff --git a/dma.8 b/dma.8 new file mode 100644 index 000000000000..5d786c6be2ca --- /dev/null +++ b/dma.8 @@ -0,0 +1,363 @@ +.\" +.\" 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 environment variable +.Ev EMAIL . +.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 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 is overridden by 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 +.Fl f +flag is used. +.El +.Sh SEE ALSO +.Xr mailaddr 7 , +.Xr mailwrapper 8 , +.Xr sendmail 8 +.Rs +.%A "J. B. Postel" +.%T "Simple Mail Transfer Protocol" +.%O RFC 821 +.Re +.Rs +.%A "J. Myers" +.%T "SMTP Service Extension for Authentication" +.%O RFC 2554 +.Re +.Rs +.%A "P. Hoffman" +.%T "SMTP Service Extension for Secure SMTP over TLS" +.%O RFC 2487 +.Re +.Sh HISTORY +The +.Nm +utility first appeared in +.Dx 1.11 . +.Sh AUTHORS +.An -nosplit +.Nm +was written by +.An Matthias Schmidt Aq Mt matthias@dragonflybsd.org +and +.An Simon Schubert Aq Mt 2@0x2c.org . diff --git a/dma.c b/dma.c new file mode 100644 index 000000000000..e6462f22544c --- /dev/null +++ b/dma.c @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert . + * + * 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 "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, +}; + + +static void +sighup_handler(int signo) +{ + (void)signo; /* so that gcc doesn't complain */ +} + +static char * +set_from(struct queue *queue, const char *osender) +{ + const char *addr; + char *sender; + + if (osender) { + addr = osender; + } else if (getenv("EMAIL") != NULL) { + addr = getenv("EMAIL"); + } else { + if (config.masquerade_user) + addr = config.masquerade_user; + 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(1); + } + 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(1); + 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(0); + syslog(LOG_WARNING, "could not lock queue file"); + exit(1); + default: + exit(1); + } + dropspool(queue, it); + return (it); + + default: + /* + * Parent: + * + * fork next child + */ + break; + } + } + + syslog(LOG_CRIT, "reached dead code"); + exit(1); +} + +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, "trying delivery"); + + if (it->remote) + error = deliver_remote(it); + else + error = deliver_local(it); + + switch (error) { + case 0: + delqueue(it); + syslog(LOG_INFO, "delivery successful"); + exit(0); + + case 1: + if (stat(it->queuefn, &st) != 0) { + syslog(LOG_ERR, "lost queue file `%s'", it->queuefn); + exit(1); + } + 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(1, "user '%s' not found", DMA_ROOT_USER); + else + err(1, "cannot drop root privileges"); + } + + if (setuid(pw->pw_uid) != 0) + err(1, "cannot drop root privileges"); + + if (geteuid() == 0 || getuid() == 0) + errx(1, "cannot drop root privileges"); + } + + atexit(deltmp); + init_random(); + + bzero(&queue, sizeof(queue)); + LIST_INIT(&queue.queue); + + if (strcmp(argv[0], "mailq") == 0) { + argv++; argc--; + showq = 1; + if (argc != 0) + errx(1, "invalid arguments"); + goto skipopts; + } else if (strcmp(argv[0], "newaliases") == 0) { + logident_base = "dma"; + setlogident(NULL); + + if (read_aliases() != 0) + errx(1, "could not parse aliases file `%s'", config.aliases); + exit(0); + } + + 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(1); + } + } + argc -= optind; + argv += optind; + opterr = 1; + + if (argc != 0 && (showq || doqueue)) + errx(1, "sending mail and queue operations are mutually exclusive"); + + if (showq + doqueue > 1) + errx(1, "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(1, "can not load queue"); + show_queue(&queue); + return (0); + } + + if (doqueue) { + flushqueue_signal(); + if (load_queue(&queue) < 0) + errlog(1, "can not load queue"); + run_queue(&queue); + return (0); + } + + if (read_aliases() != 0) + errlog(1, "could not parse aliases file `%s'", config.aliases); + + if ((sender = set_from(&queue, sender)) == NULL) + errlog(1, NULL); + + if (newspoolf(&queue) != 0) + errlog(1, "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(1, "invalid recipient `%s'", argv[i]); + } + + if (LIST_EMPTY(&queue.queue) && !recp_from_header) + errlogx(1, "no recipients"); + + if (readmail(&queue, nodot, recp_from_header) != 0) + errlog(1, "can not read mail"); + + if (LIST_EMPTY(&queue.queue)) + errlogx(1, "no recipients"); + + if (linkspool(&queue) != 0) + errlog(1, "can not create spools"); + + /* From here on the mail is safe. */ + + if (config.features & DEFER || queue_only) + return (0); + + run_queue(&queue); + + /* NOTREACHED */ + return (0); +} diff --git a/dma.conf b/dma.conf new file mode 100644 index 000000000000..1cc2bf5bc843 --- /dev/null +++ b/dma.conf @@ -0,0 +1,66 @@ +# $DragonFly: src/etc/dma/dma.conf,v 1.2 2008/02/04 10:11:41 matthias Exp $ +# +# Your smarthost (also called relayhost). Leave blank if you don't want +# smarthost support. +# NOTE: on Debian systems this is handled via debconf! +# Please use dpkg-reconfigure dma to change this value. +#SMARTHOST + +# Use this SMTP port. Most users will be fine with the default (25) +#PORT 25 + +# Path to your alias file. Just stay with the default. +#ALIASES /etc/aliases + +# Path to your spooldir. Just stay with the default. +#SPOOLDIR /var/spool/dma + +# SMTP authentication +#AUTHPATH /etc/dma/auth.conf + +# Uncomment if yout want TLS/SSL support +#SECURETRANSFER + +# Uncomment if you want STARTTLS support (only used in combination with +# SECURETRANSFER) +#STARTTLS + +# Uncomment if you have specified STARTTLS above and it should be allowed +# to fail ("opportunistic TLS", use an encrypted connection when available +# but allow an unencrypted one to servers that do not support it) +#OPPORTUNISTIC_TLS + +# Path to your local SSL certificate +#CERTFILE + +# If you want to use plain text SMTP login without using encryption, change +# the SECURE entry below to INSECURE. Otherwise plain login will only work +# over a secure connection. Use this option with caution. +#SECURE + +# Uncomment if you want to defer your mails. This is useful if you are +# behind a dialup line. You have to submit your mails manually with dma -q +#DEFER + +# Uncomment if you want the bounce message to include the complete original +# message, not just the headers. +#FULLBOUNCE + +# The internet hostname dma uses to identify the host. +# If not set or empty, the result of gethostname(2) is used. +# If MAILNAME is an absolute path to a file, the first line of this file +# will be used as the hostname. +#MAILNAME mail.example.net + +# Masquerade envelope from addresses with this address/hostname. +# Use this if mails are not accepted by destination mail servers because +# your sender domain is invalid. +# By default, MASQUERADE is not set. +# Format: MASQUERADE [user@][host] +# Examples: +# MASQUERADE john@ on host "hamlet" will send all mails as john@hamlet +# MASQUERADE percolator will send mails as $username@percolator, e.g. fish@percolator +# MASQUERADE herb@ert will send all mails as herb@ert + +# Directly forward the mail to the SMARTHOST bypassing aliases and local delivery +#NULLCLIENT diff --git a/dma.h b/dma.h new file mode 100644 index 000000000000..4ce00944a5ee --- /dev/null +++ b/dma.h @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert 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 + +#define VERSION "DragonFly Mail Agent " DMA_VERSION + +#define BUF_SIZE 2048 +#define ERRMSG_SIZE 200 +#define USERNAME_SIZE 50 +#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; + + /* 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; +}; + + +/* 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); +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); + +/* 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 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 diff --git a/dns.c b/dns.c new file mode 100644 index 000000000000..fc5213f1ca60 --- /dev/null +++ b/dns.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert + * + * 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 +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; + int err; + + 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); + err = getaddrinfo(host, servname, &hints, &res0); + if (err) + return (err == EAI_AGAIN ? 1 : -1); + + for (res = res0; res != NULL; res = res->ai_next) { + if (*ps + 1 >= roundup(*ps, count_inc)) { + size_t newsz = roundup(*ps + 2, count_inc); + *he = reallocf(*he, newsz * sizeof(**he)); + if (*he == NULL) + goto out; + } + + p = &(*he)[*ps]; + strlcpy(p->host, host, sizeof(p->host)); + p->pref = pref; + p->ai = *res; + p->ai.ai_addr = NULL; + bcopy(res->ai_addr, &p->sa, p->ai.ai_addrlen); + + getnameinfo((struct sockaddr *)&p->sa, p->ai.ai_addrlen, + p->addr, sizeof(p->addr), + NULL, 0, NI_NUMERICHOST); + + (*ps)++; + } + freeaddrinfo(res0); + + return (0); + +out: + if (res0 != NULL) + freeaddrinfo(res0); + return (1); +} + +int +dns_get_mx_list(const char *host, int port, struct mx_hostentry **he, int no_mx) +{ + char outname[MAXDNAME]; + ns_msg msg; + ns_rr rr; + const char *searchhost; + const unsigned char *cp; + unsigned char *ans; + struct mx_hostentry *hosts = NULL; + size_t nhosts = 0; + size_t anssz; + int pref; + int cname_recurse; + int have_mx = 0; + int err; + int i; + + res_init(); + searchhost = host; + cname_recurse = 0; + + anssz = 65536; + ans = malloc(anssz); + if (ans == NULL) + return (1); + + if (no_mx) + goto out; + +repeat: + err = res_search(searchhost, ns_c_in, ns_t_mx, ans, anssz); + if (err < 0) { + switch (h_errno) { + case NO_DATA: + /* + * Host exists, but no MX (or CNAME) entry. + * Not an error, use host name instead. + */ + goto out; + case TRY_AGAIN: + /* transient error */ + goto transerr; + case NO_RECOVERY: + case HOST_NOT_FOUND: + default: + errno = ENOENT; + goto err; + } + } + + if (!ns_initparse(ans, anssz, &msg)) + goto transerr; + + switch (ns_msg_getflag(msg, ns_f_rcode)) { + case ns_r_noerror: + break; + case ns_r_nxdomain: + goto err; + default: + goto transerr; + } + + for (i = 0; i < ns_msg_count(msg, ns_s_an); i++) { + if (ns_parserr(&msg, ns_s_an, i, &rr)) + goto transerr; + + cp = ns_rr_rdata(rr); + + switch (ns_rr_type(rr)) { + case ns_t_mx: + have_mx = 1; + pref = ns_get16(cp); + cp += 2; + err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), + cp, outname, sizeof(outname)); + if (err < 0) + goto transerr; + + err = add_host(pref, outname, port, &hosts, &nhosts); + if (err == -1) + goto err; + break; + + case ns_t_cname: + err = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), + cp, outname, sizeof(outname)); + if (err < 0) + goto transerr; + + /* Prevent a CNAME loop */ + if (cname_recurse++ > 10) + goto err; + + searchhost = outname; + goto repeat; + + default: + break; + } + } + +out: + err = 0; + if (0) { +transerr: + if (nhosts == 0) + err = 1; + } + if (0) { +err: + err = -1; + } + + free(ans); + + if (err == 0) { + if (!have_mx) { + /* + * If we didn't find any MX, use the hostname instead. + */ + err = add_host(0, host, port, &hosts, &nhosts); + } else if (nhosts == 0) { + /* + * We did get MX, but couldn't resolve any of them + * due to transient errors. + */ + err = 1; + } + } + + if (nhosts > 0) { + qsort(hosts, nhosts, sizeof(*hosts), sort_pref); + /* terminate list */ + *hosts[nhosts].host = 0; + } else { + if (hosts != NULL) + free(hosts); + hosts = NULL; + } + + *he = hosts; + return (err); + + free(ans); + if (hosts != NULL) + free(hosts); + return (err); +} + +#if defined(TESTING) +int +main(int argc, char **argv) +{ + struct mx_hostentry *he, *p; + int err; + + err = dns_get_mx_list(argv[1], 53, &he, 0); + if (err) + return (err); + + for (p = he; *p->host != 0; p++) { + printf("%d\t%s\t%s\n", p->pref, p->host, p->addr); + } + + return (0); +} +#endif diff --git a/get-version.sh b/get-version.sh new file mode 100755 index 000000000000..aecc3331f446 --- /dev/null +++ b/get-version.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +gitver=$(git describe 2>/dev/null | tr - .) +filever=$(cat VERSION) + +version=${gitver} +: ${version:=$filever} + +echo "$version" diff --git a/local.c b/local.c new file mode 100644 index 000000000000..6a6407ef8652 --- /dev/null +++ b/local.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert . + * + * 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 "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(1); + + 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\t%s", newline, sender, ctime(&now)); + if (error < 0 || (size_t)error >= sizeof(line)) { + syslog(LOG_NOTICE, "local delivery deferred: can not write header: %m"); + goto out; + } + if (write(mbox, line, error) != error) + goto wrerror; + + while (!feof(it->mailf)) { + if (fgets(line, sizeof(line), it->mailf) == NULL) + break; + linelen = strlen(line); + if (linelen == 0 || line[linelen - 1] != '\n') { + syslog(LOG_CRIT, "local delivery failed: corrupted queue file"); + snprintf(errmsg, sizeof(errmsg), "corrupted queue file"); + error = -1; + goto chop; + } + + /* + * mboxro processing: + * - escape lines that start with "From " with a > sign. + * - be reversable by escaping lines that contain an arbitrary + * number of > signs, followed by "From ", i.e. />*From / in regexp. + * - strict mbox processing only requires escaping after empty lines, + * yet most MUAs seem to relax this requirement and will treat any + * line starting with "From " as the beginning of a new mail. + */ + if ((!MBOX_STRICT || hadnl) && + strncmp(&line[strspn(line, ">")], "From ", 5) == 0) { + const char *gt = ">"; + + if (write(mbox, gt, 1) != 1) + goto wrerror; + hadnl = 0; + } else if (strcmp(line, "\n") == 0) { + hadnl = 1; + } else { + hadnl = 0; + } + if ((size_t)write(mbox, line, linelen) != linelen) + goto wrerror; + } + close(mbox); + return (0); + +wrerror: + syslog(LOG_ERR, "local delivery failed: write error: %m"); + error = 1; +chop: + if (ftruncate(mbox, mboxlen) != 0) + syslog(LOG_WARNING, "error recovering mbox `%s': %m", fn); +out: + close(mbox); + return (error); +} diff --git a/mail.c b/mail.c new file mode 100644 index 000000000000..9cbde41d98f1 --- /dev/null +++ b/mail.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert . + * + * 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 "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(1); + } + + 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(1); +} + +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(1, NULL); + + if (add_recp(queue, addr, EXPAND_WILDCARD) != 0) + errlogx(1, "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_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(1, "bad mail input format"); + 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_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(1, "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(1, "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); +} diff --git a/net.c b/net.c new file mode 100644 index 000000000000..d95a7884b87e --- /dev/null +++ b/net.c @@ -0,0 +1,546 @@ +/* + * 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 "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) +{ + char *temp; + int len, res = 0; + + res = smtp_auth_md5(fd, login, password); + if (res == 0) { + return (0); + } else if (res == -2) { + /* + * If the return code is -2, then then the login attempt failed, + * do not try other login mechanisms + */ + return (1); + } + + if ((config.features & INSECURE) != 0 || + (config.features & SECURETRANS) != 0) { + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH LOGIN"); + if (read_remote(fd, 0, NULL) != 3) { + syslog(LOG_NOTICE, "remote delivery deferred:" + " AUTH login not available: %s", + neterr); + 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 int +deliver_to_host(struct qitem *it, struct mx_hostentry *host) +{ + struct authuser *a; + char line[1000]; + size_t linelen; + int fd, error = 0, do_auth = 0, res = 0; + + if (fseek(it->mailf, 0, SEEK_SET) != 0) { + snprintf(errmsg, sizeof(errmsg), "can not seek: %s", strerror(errno)); + return (-1); + } + + fd = open_connection(host); + if (fd < 0) + return (1); + +#define READ_REMOTE_CHECK(c, exp) \ + res = read_remote(fd, 0, NULL); \ + if (res == 5) { \ + syslog(LOG_ERR, "remote delivery to %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + snprintf(errmsg, sizeof(errmsg), "%s [%s] did not like our %s:\n%s", \ + host->host, host->addr, c, neterr); \ + return (-1); \ + } else if (res != exp) { \ + syslog(LOG_NOTICE, "remote delivery deferred: %s [%s] failed after %s: %s", \ + host->host, host->addr, c, neterr); \ + return (1); \ + } + + /* Check first reply from remote host */ + if ((config.features & SECURETRANS) == 0 || + (config.features & STARTTLS) != 0) { + config.features |= NOSSL; + READ_REMOTE_CHECK("connect", 2); + + config.features &= ~NOSSL; + } + + if ((config.features & SECURETRANS) != 0) { + error = smtp_init_crypto(fd, config.features); + if (error == 0) + syslog(LOG_DEBUG, "SSL initialization successful"); + else + goto out; + + if ((config.features & STARTTLS) == 0) + READ_REMOTE_CHECK("connect", 2); + } + + /* XXX allow HELO fallback */ + /* XXX record ESMTP keywords */ + send_remote_command(fd, "EHLO %s", hostname()); + READ_REMOTE_CHECK("EHLO", 2); + + /* + * Use SMTP authentication if the user defined an entry for the remote + * or smarthost + */ + 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); + if (error < 0) { + syslog(LOG_ERR, "remote delivery failed:" + " SMTP login failed: %m"); + snprintf(errmsg, sizeof(errmsg), "SMTP login to %s failed", host->host); + return (-1); + } + /* SMTP login is not available, so try without */ + else if (error > 0) { + syslog(LOG_WARNING, "SMTP login not available. Trying without."); + } + } + + /* XXX send ESMTP ENVID, RET (FULL/HDRS) and 8BITMIME */ + send_remote_command(fd, "MAIL FROM:<%s>", it->sender); + READ_REMOTE_CHECK("MAIL FROM", 2); + + /* XXX send ESMTP ORCPT */ + send_remote_command(fd, "RCPT TO:<%s>", it->addr); + READ_REMOTE_CHECK("RCPT TO", 2); + + 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: + + close_connection(fd); + return (error); +} + +int +deliver_remote(struct qitem *it) +{ + struct mx_hostentry *hosts, *h; + const char *host; + int port; + int error = 1, smarthost = 0; + + port = SMTP_PORT; + + /* Smarthost support? */ + if (config.smarthost != NULL) { + host = config.smarthost; + port = config.port; + syslog(LOG_INFO, "using smarthost (%s:%i)", host, port); + smarthost = 1; + } else { + host = strrchr(it->addr, '@'); + /* Should not happen */ + if (host == NULL) { + snprintf(errmsg, sizeof(errmsg), "Internal error: badly formed address %s", + it->addr); + return(-1); + } else { + /* Step over the @ */ + host++; + } + } + + error = dns_get_mx_list(host, port, &hosts, smarthost); + if (error) { + snprintf(errmsg, sizeof(errmsg), "DNS lookup failure: host %s not found", host); + syslog(LOG_NOTICE, "remote delivery %s: DNS lookup failure: host %s not found", + error < 0 ? "failed" : "deferred", + host); + return (error); + } + + for (h = hosts; *h->host != 0; h++) { + switch (deliver_to_host(it, h)) { + case 0: + /* success */ + error = 0; + goto out; + case 1: + /* temp failure */ + error = 1; + break; + default: + /* perm failure */ + error = -1; + goto out; + } + } +out: + free(hosts); + + return (error); +} diff --git a/spool.c b/spool.c new file mode 100644 index 000000000000..416b5fa8f0ee --- /dev/null +++ b/spool.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert . + * + * 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 "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(1, "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); +} diff --git a/test/quote.rfc2822 b/test/quote.rfc2822 new file mode 100644 index 000000000000..f0f52fd1e202 --- /dev/null +++ b/test/quote.rfc2822 @@ -0,0 +1,23 @@ +From: 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. diff --git a/util.c b/util.c new file mode 100644 index 000000000000..a139b20d3831 --- /dev/null +++ b/util.c @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2008 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Simon 'corecode' Schubert . + * + * 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 "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); +}