From 40b5a5636a67144e252146b9d0e1a47723dbb284 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sat, 2 Feb 2008 18:20:51 +0000 Subject: [PATCH] Add the DragonFly Mail Agent dma(8) to the base. 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 authen- tication (AUTH LOGIN only). dma is not intended as a replacement for real, big MTAs like sendmail(8) or postfix(8). Consequently, dma does not listen on port 25 for incoming connections. Current list of features: - Local mail delivery with alias-support - Remote mail delivery either direct or via a smarthost - TLS/SSL and STARTTLS support for encrypted connections - virtualusers (address rewriting) support - SMTP authentication (currently only plain SMTP login) - Sendmail compatible command line options - IPv6 support Code-collaboration-with: codecode@ Man-page-collaboration-with: swildner@ Approved-by: dillon@ --- libexec/Makefile | 3 +- libexec/dma/Makefile | 18 + libexec/dma/aliases_parse.y | 107 +++++ libexec/dma/aliases_scan.l | 20 + libexec/dma/base64.c | 81 ++++ libexec/dma/conf.c | 250 ++++++++++++ libexec/dma/crypto.c | 184 +++++++++ libexec/dma/dma.8 | 255 ++++++++++++ libexec/dma/dma.c | 967 ++++++++++++++++++++++++++++++++++++++++++++ libexec/dma/dma.h | 155 +++++++ libexec/dma/net.c | 434 ++++++++++++++++++++ 11 files changed, 2473 insertions(+), 1 deletion(-) create mode 100644 libexec/dma/Makefile create mode 100644 libexec/dma/aliases_parse.y create mode 100644 libexec/dma/aliases_scan.l create mode 100644 libexec/dma/base64.c create mode 100644 libexec/dma/conf.c create mode 100644 libexec/dma/crypto.c create mode 100644 libexec/dma/dma.8 create mode 100644 libexec/dma/dma.c create mode 100644 libexec/dma/dma.h create mode 100644 libexec/dma/net.c diff --git a/libexec/Makefile b/libexec/Makefile index 78bb65195e..f3fe58d810 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -1,11 +1,12 @@ # @(#)Makefile 8.1 (Berkeley) 6/4/93 # $FreeBSD: src/libexec/Makefile,v 1.42.2.5 2002/11/12 17:32:48 obrien Exp $ -# $DragonFly: src/libexec/Makefile,v 1.12 2007/08/02 10:27:01 swildner Exp $ +# $DragonFly: src/libexec/Makefile,v 1.13 2008/02/02 18:20:51 matthias Exp $ # Present but disabled: kpasswdd SUBDIR= atrun \ bootpd \ comsat \ + dma \ fingerd \ ftpd \ ftp-proxy \ diff --git a/libexec/dma/Makefile b/libexec/dma/Makefile new file mode 100644 index 0000000000..34d3cb9077 --- /dev/null +++ b/libexec/dma/Makefile @@ -0,0 +1,18 @@ +# $DragonFly: src/libexec/dma/Makefile,v 1.1 2008/02/02 18:20:51 matthias Exp $ +# + +CFLAGS+= -DHAVE_CRYPTO -DHAVE_INET6 + +DPADD= ${LIBSSL} ${LIBCRYPTO} +LDADD= -lssl -lcrypto + +PROG= dma +SRCS= base64.c conf.c crypto.c net.c dma.c aliases_scan.l aliases_parse.y +MAN= dma.8 + +BINOWN= root +BINGRP= mail +BINMODE=2555 +WARNS?= 1 + +.include diff --git a/libexec/dma/aliases_parse.y b/libexec/dma/aliases_parse.y new file mode 100644 index 0000000000..e13a6ef67c --- /dev/null +++ b/libexec/dma/aliases_parse.y @@ -0,0 +1,107 @@ +%{ +/* $DragonFly: src/libexec/dma/aliases_parse.y,v 1.1 2008/02/02 18:20:51 matthias Exp $ */ + +#include +#include +#include "dma.h" + +extern int yylineno; +void yyerror(const char *); +int yywrap(void); + +void +yyerror(const char *msg) +{ + warnx("aliases line %d: %s", yylineno, 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' + { + yyerrok; + $$ = NULL; + } + ; + +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/libexec/dma/aliases_scan.l b/libexec/dma/aliases_scan.l new file mode 100644 index 0000000000..7846b30d9e --- /dev/null +++ b/libexec/dma/aliases_scan.l @@ -0,0 +1,20 @@ +%{ +/* $DragonFly: src/libexec/dma/aliases_scan.l,v 1.1 2008/02/02 18:20:51 matthias Exp $ */ + +#include +#include "aliases_parse.h" +%} + +%option yylineno + +%% + +[^:,#[:space:][:cntrl:]]+ {yylval.ident = strdup(yytext); return T_IDENT;} +[:,\n] return yytext[0]; +^([[:blank:]]*(#.*)?\n)+ ;/* ignore empty lines */ +(\n?[[:blank:]]+|#.*)+ ;/* ignore whitespace and continuation */ +\\\n ;/* ignore continuation. not allowed in comments */ +. return T_ERROR; +<> return T_EOF; + +%% diff --git a/libexec/dma/base64.c b/libexec/dma/base64.c new file mode 100644 index 0000000000..610aa3c313 --- /dev/null +++ b/libexec/dma/base64.c @@ -0,0 +1,81 @@ +/* + * 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. + * + * $DragonFly: src/libexec/dma/base64.c,v 1.1 2008/02/02 18:20:51 matthias Exp $ + */ + +#include +#include + +int base64_encode(const void *data, int size, char **str); + +static char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +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); +} + diff --git a/libexec/dma/conf.c b/libexec/dma/conf.c new file mode 100644 index 0000000000..02a7ca9f92 --- /dev/null +++ b/libexec/dma/conf.c @@ -0,0 +1,250 @@ +/* + * 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. + * + * $DragonFly: src/libexec/dma/conf.c,v 1.1 2008/02/02 18:20:51 matthias Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#include "dma.h" + +#define DP ": \t\n" +#define EQS " \t\n" + +extern struct virtusers virtusers; +extern struct authusers authusers; + +/* + * Remove trailing \n's + */ +void +trim_line(char *line) +{ + size_t linelen; + char *p; + + p = line; + + 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] = '.'; + } +} + +/* + * Add a virtual user entry to the list of virtual users + */ +static void +add_virtuser(char *login, char *address) +{ + struct virtuser *v; + + v = malloc(sizeof(struct virtuser)); + v->login = strdup(login); + v->address = strdup(address); + SLIST_INSERT_HEAD(&virtusers, v, next); +} + +/* + * Read the virtual user table + */ +int +parse_virtuser(const char *path) +{ + FILE *v; + char *word; + char *data; + char line[2048]; + + v = fopen(path, "r"); + if (v == NULL) + return (-1); + + while (!feof(v)) { + fgets(line, sizeof(line), v); + /* We hit a comment */ + if (strchr(line, '#')) + *strchr(line, '#') = 0; + if ((word = strtok(line, DP)) != NULL) { + data = strtok(NULL, DP); + if (data != NULL) { + add_virtuser(word, data); + } + } + } + + fclose(v); + return (0); +} + +/* + * Add entry to the SMTP auth user list + */ +static void +add_smtp_auth_user(char *userstring, char *password) +{ + struct authuser *a; + char *temp; + + a = malloc(sizeof(struct virtuser)); + a->password= strdup(password); + + temp = strrchr(userstring, '|'); + if (temp == NULL) + errx(1, "auth.conf file in wrong format"); + + a->host = strdup(temp+1); + a->login = strdup(strtok(userstring, "|")); + if (a->login == NULL) + errx(1, "auth.conf file in wrong format"); + + SLIST_INSERT_HEAD(&authusers, a, next); +} + +/* + * Read the SMTP authentication config file + */ +int +parse_authfile(const char *path) +{ + FILE *a; + char *word; + char *data; + char line[2048]; + + a = fopen(path, "r"); + if (a == NULL) + return (1); + + while (!feof(a)) { + fgets(line, sizeof(line), a); + /* We hit a comment */ + if (strchr(line, '#')) + *strchr(line, '#') = 0; + if ((word = strtok(line, DP)) != NULL) { + data = strtok(NULL, DP); + if (data != NULL) { + add_smtp_auth_user(word, data); + } + } + } + + fclose(a); + return (0); +} + +/* + * XXX TODO + * Check if the user supplied a value. If not, fill in default + * Check for bad things[TM] + */ +int +parse_conf(const char *config_path, struct config *config) +{ + char *word; + char *data; + FILE *conf; + char line[2048]; + + conf = fopen(config_path, "r"); + if (conf == NULL) + return (-1); + + /* Reset features */ + config->features = 0; + + while (!feof(conf)) { + fgets(line, sizeof(line), conf); + /* We hit a comment */ + if (strchr(line, '#')) + *strchr(line, '#') = 0; + if ((word = strtok(line, EQS)) != NULL) { + data = strtok(NULL, EQS); + if (strcmp(word, "SMARTHOST") == 0) { + if (data != NULL) + config->smarthost = strdup(data); + } + else if (strcmp(word, "PORT") == 0) { + if (data != NULL) + config->port = atoi(strdup(data)); + } + else if (strcmp(word, "ALIASES") == 0) { + if (data != NULL) + config->aliases = strdup(data); + } + else if (strcmp(word, "SPOOLDIR") == 0) { + if (data != NULL) + config->spooldir = strdup(data); + } + else if (strcmp(word, "VIRTPATH") == 0) { + if (data != NULL) + config->virtualpath = strdup(data); + } + else if (strcmp(word, "AUTHPATH") == 0) { + if (data != NULL) + config->authpath= strdup(data); + } + else if (strcmp(word, "CERTFILE") == 0) { + if (data != NULL) + config->certfile = strdup(data); + } + else if (strcmp(word, "VIRTUAL") == 0) + config->features |= VIRTUAL; + else if (strcmp(word, "STARTTLS") == 0) + config->features |= STARTTLS; + else if (strcmp(word, "SECURETRANSFER") == 0) + config->features |= SECURETRANS; + else if (strcmp(word, "DEFER") == 0) + config->features |= DEFER; + } + } + + fclose(conf); + return (0); +} + diff --git a/libexec/dma/crypto.c b/libexec/dma/crypto.c new file mode 100644 index 0000000000..e509c94755 --- /dev/null +++ b/libexec/dma/crypto.c @@ -0,0 +1,184 @@ +/* + * 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. + * + * $DragonFly: src/libexec/dma/crypto.c,v 1.1 2008/02/02 18:20:51 matthias Exp $ + */ + +#ifdef HAVE_CRYPTO + +#include +#include +#include +#include +#include + +#include + +#include "dma.h" + +extern struct config *config; + +static int +init_cert_file(struct qitem *it, 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, "%s: SSL: Cannot load certificate: %s", + it->queueid, path); + return (-1); + } + + /* Add private key to ctx */ + error = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM); + if (error < 1) { + syslog(LOG_ERR, "%s: SSL: Cannot load private key: %s", + it->queueid, path); + 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, "%s: SSL: Cannot check private key: %s", + it->queueid, path); + return (-1); + } + + return (0); +} + +int +smtp_init_crypto(struct qitem *it, int fd, int feature) +{ + SSL_CTX *ctx = NULL; + SSL_METHOD *meth = NULL; + X509 *cert; + char buf[2048]; + int error; + + /* Init SSL library */ + SSL_library_init(); + + meth = TLSv1_client_method(); + + ctx = SSL_CTX_new(meth); + if (ctx == NULL) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " SSL init failed: %m", it->queueid); + return (2); + } + + /* User supplied a certificate */ + if (config->certfile != NULL) + init_cert_file(it, ctx, config->certfile); + + /* + * 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 |= TLSINIT; + + send_remote_command(fd, "EHLO %s", hostname()); + if (check_for_smtp_error(fd, buf) == 0) { + send_remote_command(fd, "STARTTLS"); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery failed:" + " STARTTLS not available: %m", it->queueid); + config->features &= ~TLSINIT; + return (-1); + } + } + /* End of TLS init phase, enable SSL_write/read */ + config->features &= ~TLSINIT; + } + + config->ssl = SSL_new(ctx); + if (config->ssl == NULL) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " SSL struct creation failed:", it->queueid); + return (2); + } + + /* 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) { + error = SSL_get_error(config->ssl, error); + syslog(LOG_ERR, "%s: remote delivery deferred:" + " SSL set fd failed (%d): %m", it->queueid, error); + return (2); + } + + /* Open SSL connection */ + error = SSL_connect(config->ssl); + if (error < 0) { + syslog(LOG_ERR, "%s: remote delivery failed:" + " SSL handshake fataly failed: %m", it->queueid); + return (-1); + } + + /* Get peer certificate */ + cert = SSL_get_peer_certificate(config->ssl); + if (cert == NULL) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " Peer did not provied certificate: %m", it->queueid); + } + X509_free(cert); + + return (0); +} + +#if 0 +/* + * CRAM-MD5 authentication + * + * XXX TODO implement me, I don't have a mail server with CRAM-MD5 available + */ +int +smtp_auth_md5(int fd, char *login, char *password) +{ +} +#endif /* 0 */ + +#endif /* HAVE_CRYPTO */ diff --git a/libexec/dma/dma.8 b/libexec/dma/dma.8 new file mode 100644 index 0000000000..aace6b3d70 --- /dev/null +++ b/libexec/dma/dma.8 @@ -0,0 +1,255 @@ +.\" +.\" 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. +.\" +.\" $DragonFly: src/libexec/dma/dma.8,v 1.1 2008/02/02 18:20:51 matthias Exp $ +.\" +.Dd February 02, 2008 +.Dt DMA 8 +.Os +.Sh NAME +.Nm dma +.Nd DragonFly Mail Agent +.Sh SYNOPSIS +.Nm +.Op Fl Diq +.Op Fl A Ar mode +.Op Fl b Ar mode +.Op Fl f Ar sender +.Op Fl L Ar tag +.Op Fl o Ar option +.Op Fl r Ar sender +.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 (AUTH LOGIN only). +.Pp +.Nm +is not intended as a replacement for real, big MTAs like +.Xr sendmail 8 +or +.Xr postfix 8 . +Consequently, +.Nm +does not listen on port 25 for incoming connections. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl A Ar mode +.Fl A Ns Ar c +acts as a compatibility option for sendmail. +.It Fl b Ar mode +Specifying +.Fl b Ns Ar p +will list all mails currently stored in the mail queue. +All other modes are are ignored. +.It Fl D +Don't run in the background. +Useful for debugging. +.It Fl f Ar sender +Set sender address to +.Ar sender . +.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 Ar option +Specifying +.Fl o Ns Ar i +is synonymous to +.Fl i . +All other options are ignored. +.It Fl q +Process saved messages in the queue. +.It Fl r Ar sender +Same as +.Fl f . +.El +.Sh CONFIGURATION +.Nm +can be configured with three config files: +.Pp +.Bl -bullet -compact +.It +auth.conf +.It +dma.conf +.It +virtusertable +.El +.Pp +These three files are stored per default in +.Pa /etc/dma . +However every user can install it's own config files in +.Pa $HOME/.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 . +.Pp +.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 if a smarthost is set or not. +.It Ic ALIASES Xo +(string, default=/etc/mail/aliases) +.Xc +Path to the local aliases file. +Just stick with the default. +.It Ic SPOOLDIR Xo +(string, default=/var/spool/dma) +.Xc +Path to +.Nm Ap s +spool directory. +Just stick with the default. +.It Ic VIRTPATH Xo +(string, default=/etc/dma/virtusertable) +.Xc +Path to the +.Sq virtusertable +file. +If you have your config in +.Pa $HOME/.dma +be sure to change this path accordingly. +.It Ic AUTHPATH Xo +(string, default=/etc/dma/auth.conf) +.Xc +Path to the +.Sq auth.conf +file. +If you have your config in +.Pa $HOME/.dma +be sure to change this path accordingly. +.It Ic VIRTUAL Xo +(boolean, default=commented) +.Xc +Comment if you want virtual user support. +.It Ic SECURETRANS Xo +(boolean, default=commented) +.Xc +Comment if you want TLS/SSL secured transfer. +.It Ic STARTTLS Xo +(boolean, default=commented) +.Xc +Comment if you want to use STARTTLS. +Only useful together with +.Sq SECURETRANS . +.It Ic CERTFILE Xo +(string, default=empty) +.Xc +Path to your SSL certificate file. +.It Ic DEFER Xo +(boolean, default=commented) +.Xc +Comment 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. +.El +.Ss virtusertable +The +.Pa virtusertable +file specifies a virtual user table. +Each line has the format +.Dq Li localuser:mail-address . +Some smarthosts do not accept mails from unresolvable email address +(e.g. user@localhost) so you have to rewrite your outgoing email +address to a valid address. +.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 +.Nm +was written by +.An Matthias Schmidt Aq matthias@dragonflybsd.org +and +.An Simon Schubert Aq corecode@dragonflybsd.org . diff --git a/libexec/dma/dma.c b/libexec/dma/dma.c new file mode 100644 index 0000000000..f53908c4c2 --- /dev/null +++ b/libexec/dma/dma.c @@ -0,0 +1,967 @@ +/* + * 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. + * + * $DragonFly: src/libexec/dma/dma.c,v 1.1 2008/02/02 18:20:51 matthias Exp $ + */ + +#include +#include +#include +#include + +#ifdef HAVE_CRYPTO +#include +#endif /* HAVE_CRYPTO */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dma.h" + + + +static void deliver(struct qitem *); +static int add_recp(struct queue *, const char *, const char *, int); + +struct aliases aliases = LIST_HEAD_INITIALIZER(aliases); +static struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs); +struct virtusers virtusers = LIST_HEAD_INITIALIZER(virtusers); +struct authusers authusers = LIST_HEAD_INITIALIZER(authusers); +static int daemonize = 1; +struct config *config; + +char * +hostname(void) +{ + static char name[MAXHOSTNAMELEN+1]; + + if (gethostname(name, sizeof(name)) != 0) + strcpy(name, "(unknown hostname)"); + + return name; +} + +static char * +set_from(const char *osender) +{ + struct virtuser *v; + char *sender; + + if ((config->features & VIRTUAL) != 0) { + SLIST_FOREACH(v, &virtusers, next) { + if (strcmp(v->login, getlogin()) == 0) { + sender = strdup(v->address); + if (sender == NULL) + return(NULL); + goto out; + } + } + } + + if (osender) { + sender = strdup(osender); + if (sender == NULL) + return (NULL); + } else { + if (asprintf(&sender, "%s@%s", getlogin(), hostname()) <= 0) + return (NULL); + } + + if (strchr(sender, '\n') != NULL) { + errno = EINVAL; + return (NULL); + } + +out: + return (sender); +} + +static int +read_aliases(void) +{ + yyin = fopen(config->aliases, "r"); + if (yyin == NULL) + return (0); /* not fatal */ + if (yyparse()) + return (-1); /* fatal error, probably malloc() */ + fclose(yyin); + return (0); +} + +static int +add_recp(struct queue *queue, const char *str, const char *sender, int expand) +{ + struct qitem *it, *tit; + struct stritem *sit; + struct alias *al; + 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 = 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); + if (strrchr(it->addr, '@') == NULL) { + it->remote = 0; + if (expand) { + LIST_FOREACH(al, &aliases, next) { + if (strcmp(al->alias, it->addr) != 0) + continue; + SLIST_FOREACH(sit, &al->dests, next) { + if (add_recp(queue, sit->str, sender, 1) != 0) + return (-1); + } + aliased = 1; + } + if (aliased) { + LIST_REMOVE(it, next); + } else { + /* Local destination, check */ + pw = getpwnam(it->addr); + if (pw == NULL) + goto out; + endpwent(); + } + } + } else { + it->remote = 1; + } + + return (0); + +out: + free(it->addr); + free(it); + return (-1); +} + +static void +deltmp(void) +{ + struct stritem *t; + + SLIST_FOREACH(t, &tmpfs, next) { + unlink(t->str); + } +} + +static int +gentempf(struct queue *queue) +{ + char fn[PATH_MAX+1]; + 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); + queue->mailfd = fd; + queue->tmpf = strdup(fn); + if (queue->tmpf == NULL) { + unlink(fn); + return (-1); + } + t = malloc(sizeof(*t)); + if (t != NULL) { + t->str = queue->tmpf; + SLIST_INSERT_HEAD(&tmpfs, t, next); + } + return (0); +} + +/* + * spool file format: + * + * envelope-from + * queue-id1 envelope-to1 + * queue-id2 envelope-to2 + * ... + * + * mail data + * + * queue ids are unique, formed from the inode of the spool file + * and a unique identifier. + */ +static int +preparespool(struct queue *queue, const char *sender) +{ + char line[1000]; /* by RFC2822 */ + struct stat st; + int error; + struct qitem *it; + FILE *queuef; + off_t hdrlen; + + error = snprintf(line, sizeof(line), "%s\n", sender); + if (error < 0 || (size_t)error >= sizeof(line)) { + errno = E2BIG; + return (-1); + } + if (write(queue->mailfd, line, error) != error) + return (-1); + + queuef = fdopen(queue->mailfd, "r+"); + if (queuef == NULL) + return (-1); + + /* + * Assign queue id to each dest. + */ + if (fstat(queue->mailfd, &st) != 0) + return (-1); + queue->id = st.st_ino; + LIST_FOREACH(it, &queue->queue, next) { + if (asprintf(&it->queueid, "%"PRIxMAX".%"PRIxPTR, + queue->id, (uintptr_t)it) <= 0) + return (-1); + if (asprintf(&it->queuefn, "%s/%s", + config->spooldir, it->queueid) <= 0) + return (-1); + /* File may not exist yet */ + if (stat(it->queuefn, &st) == 0) + return (-1); + it->queuef = queuef; + error = snprintf(line, sizeof(line), "%s %s\n", + it->queueid, it->addr); + if (error < 0 || (size_t)error >= sizeof(line)) + return (-1); + if (write(queue->mailfd, line, error) != error) + return (-1); + } + line[0] = '\n'; + if (write(queue->mailfd, line, 1) != 1) + return (-1); + + hdrlen = lseek(queue->mailfd, 0, SEEK_CUR); + LIST_FOREACH(it, &queue->queue, next) { + it->hdrlen = hdrlen; + } + return (0); +} + +static 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); +} + +static int +readmail(struct queue *queue, const char *sender, int nodot) +{ + char line[1000]; /* by RFC2822 */ + size_t linelen; + int error; + + error = snprintf(line, sizeof(line), "\ +Received: from %s (uid %d)\n\ +\t(envelope-from %s)\n\ +\tid %"PRIxMAX"\n\ +\tby %s (%s)\n\ +\t%s\n", + getlogin(), getuid(), + sender, + queue->id, + hostname(), VERSION, + rfc822date()); + if (error < 0 || (size_t)error >= sizeof(line)) + return (-1); + if (write(queue->mailfd, line, error) != error) + return (-1); + + while (!feof(stdin)) { + if (fgets(line, sizeof(line), stdin) == NULL) + break; + linelen = strlen(line); + if (linelen == 0 || line[linelen - 1] != '\n') { + errno = EINVAL; /* XXX mark permanent errors */ + return (-1); + } + if (!nodot && linelen == 2 && line[0] == '.') + break; + if ((size_t)write(queue->mailfd, line, linelen) != linelen) + return (-1); + } + if (fsync(queue->mailfd) != 0) + return (-1); + return (0); +} + +static int +linkspool(struct queue *queue) +{ + struct qitem *it; + + LIST_FOREACH(it, &queue->queue, next) { + if (link(queue->tmpf, it->queuefn) != 0) + goto delfiles; + } + unlink(queue->tmpf); + return (0); + +delfiles: + LIST_FOREACH(it, &queue->queue, next) { + unlink(it->queuefn); + } + 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_flags = SA_NOCLDWAIT; + 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) + return (it); + + pid = fork(); + switch (pid) { + case -1: + syslog(LOG_ERR, "can not fork: %m"); + exit(1); + break; + + case 0: + /* + * Child: + * + * return and deliver mail + */ + return (it); + + default: + /* + * Parent: + * + * fork next child + */ + break; + } + } + + syslog(LOG_CRIT, "reached dead code"); + exit(1); +} + +static void +bounce(struct qitem *it, const char *reason) +{ + struct queue bounceq; + struct qitem *bit; + char line[1000]; + int error; + + /* Don't bounce bounced mails */ + if (it->sender[0] == 0) { + syslog(LOG_CRIT, "%s: delivery panic: can't bounce a bounce", + it->queueid); + exit(1); + } + + syslog(LOG_ERR, "%s: delivery failed, bouncing", + it->queueid); + + LIST_INIT(&bounceq.queue); + if (add_recp(&bounceq, it->sender, "", 1) != 0) + goto fail; + if (gentempf(&bounceq) != 0) + goto fail; + if (preparespool(&bounceq, "") != 0) + goto fail; + + bit = LIST_FIRST(&bounceq.queue); + error = fprintf(bit->queuef, "\ +Received: from MAILER-DAEMON\n\ +\tid %"PRIxMAX"\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: <%"PRIxMAX"@%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\ +Message headers follow.\n\ +\n\ +", + bounceq.id, + hostname(), VERSION, + rfc822date(), + it->addr, + it->sender, + bounceq.id, hostname(), + rfc822date(), + VERSION, hostname(), + it->addr, + reason); + if (error < 0) + goto fail; + if (fflush(bit->queuef) != 0) + goto fail; + + if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) + goto fail; + while (!feof(it->queuef)) { + if (fgets(line, sizeof(line), it->queuef) == NULL) + break; + if (line[0] == '\n') + break; + write(bounceq.mailfd, line, strlen(line)); + } + if (fsync(bounceq.mailfd) != 0) + goto fail; + if (linkspool(&bounceq) != 0) + goto fail; + /* bounce is safe */ + + unlink(it->queuefn); + fclose(it->queuef); + + bit = go_background(&bounceq); + deliver(bit); + /* NOTREACHED */ + +fail: + syslog(LOG_CRIT, "%s: error creating bounce: %m", it->queueid); + unlink(it->queuefn); + exit(1); +} + +static int +deliver_local(struct qitem *it, const char **errmsg) +{ + char fn[PATH_MAX+1]; + char line[1000]; + size_t linelen; + int mbox; + int error; + 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_ERR, "%s: local delivery deferred: %m", + it->queueid); + return (1); + } + + /* mailx removes users mailspool file if empty, so open with O_CREAT */ + mbox = open(fn, O_WRONLY | O_EXLOCK | O_APPEND | O_CREAT); + if (mbox < 0) { + syslog(LOG_ERR, "%s: local delivery deferred: can not open `%s': %m", + it->queueid, fn); + return (1); + } + mboxlen = lseek(mbox, 0, SEEK_CUR); + + if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) { + syslog(LOG_ERR, "%s: local delivery deferred: can not seek: %m", + it->queueid); + return (1); + } + + error = snprintf(line, sizeof(line), "From %s\t%s", it->sender, ctime(&now)); + if (error < 0 || (size_t)error >= sizeof(line)) { + syslog(LOG_ERR, "%s: local delivery deferred: can not write header: %m", + it->queueid); + return (1); + } + if (write(mbox, line, error) != error) + goto wrerror; + + while (!feof(it->queuef)) { + if (fgets(line, sizeof(line), it->queuef) == NULL) + break; + linelen = strlen(line); + if (linelen == 0 || line[linelen - 1] != '\n') { + syslog(LOG_CRIT, "%s: local delivery failed: corrupted queue file", + it->queueid); + *errmsg = "corrupted queue file"; + error = -1; + goto chop; + } + + if (strncmp(line, "From ", 5) == 0) { + const char *gt = ">"; + + if (write(mbox, gt, 1) != 1) + goto wrerror; + } + if ((size_t)write(mbox, line, linelen) != linelen) + goto wrerror; + } + line[0] = '\n'; + if (write(mbox, line, 1) != 1) + goto wrerror; + close(mbox); + return (0); + +wrerror: + syslog(LOG_ERR, "%s: local delivery failed: write error: %m", + it->queueid); + error = 1; +chop: + if (ftruncate(mbox, mboxlen) != 0) + syslog(LOG_WARNING, "%s: error recovering mbox `%s': %m", + it->queueid, fn); + close(mbox); + return (error); +} + +static void +deliver(struct qitem *it) +{ + int error; + unsigned int backoff = MIN_RETRY; + const char *errmsg = "unknown bounce reason"; + struct timeval now; + struct stat st; + + syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>", + it->queueid, it->sender, it->addr); + +retry: + syslog(LOG_INFO, "%s: trying delivery", + it->queueid); + + if (it->remote) + error = deliver_remote(it, &errmsg); + else + error = deliver_local(it, &errmsg); + + switch (error) { + case 0: + unlink(it->queuefn); + syslog(LOG_INFO, "%s: delivery successful", + it->queueid); + exit(0); + + case 1: + if (stat(it->queuefn, &st) != 0) { + syslog(LOG_ERR, "%s: lost queue file `%s'", + it->queueid, it->queuefn); + exit(1); + } + if (gettimeofday(&now, NULL) == 0 && + (now.tv_sec - st.st_mtimespec.tv_sec > MAX_TIMEOUT)) { + char *msg; + + if (asprintf(&msg, + "Could not deliver for the last %d seconds. Giving up.", + MAX_TIMEOUT) > 0) + errmsg = msg; + goto bounce; + } + sleep(backoff); + backoff *= 2; + if (backoff > MAX_RETRY) + backoff = MAX_RETRY; + goto retry; + + case -1: + default: + break; + } + +bounce: + bounce(it, errmsg); + /* NOTREACHED */ +} + +static void +load_queue(struct queue *queue) +{ + struct stat st; + struct qitem *it; + //struct queue queue, itmqueue; + struct queue itmqueue; + DIR *spooldir; + struct dirent *de; + char line[1000]; + char *fn; + FILE *queuef; + char *sender; + char *addr; + char *queueid; + char *queuefn; + off_t hdrlen; + int fd; + + LIST_INIT(&queue->queue); + + spooldir = opendir(config->spooldir); + if (spooldir == NULL) + err(1, "reading queue"); + + while ((de = readdir(spooldir)) != NULL) { + sender = NULL; + queuef = NULL; + queueid = NULL; + queuefn = NULL; + fn = NULL; + LIST_INIT(&itmqueue.queue); + + /* ignore temp files */ + if (strncmp(de->d_name, "tmp_", 4) == 0 || + de->d_type != DT_REG) + continue; + if (asprintf(&queuefn, "%s/%s", config->spooldir, de->d_name) < 0) + goto fail; + fd = open(queuefn, O_RDONLY|O_EXLOCK|O_NONBLOCK); + if (fd < 0) { + /* Ignore locked files */ + if (errno == EWOULDBLOCK) + continue; + goto skip_item; + } + + queuef = fdopen(fd, "r"); + if (queuef == NULL) + goto skip_item; + if (fgets(line, sizeof(line), queuef) == NULL || + line[0] == 0) + goto skip_item; + line[strlen(line) - 1] = 0; /* chop newline */ + sender = strdup(line); + if (sender == NULL) + goto skip_item; + + for (;;) { + if (fgets(line, sizeof(line), queuef) == NULL || + line[0] == 0) + goto skip_item; + if (line[0] == '\n') + break; + line[strlen(line) - 1] = 0; + queueid = strdup(line); + if (queueid == NULL) + goto skip_item; + addr = strchr(queueid, ' '); + if (addr == NULL) + goto skip_item; + *addr++ = 0; + if (fn != NULL) + free(fn); + if (asprintf(&fn, "%s/%s", config->spooldir, queueid) < 0) + goto skip_item; + /* Item has already been delivered? */ + if (stat(fn, &st) != 0) + continue; + if (add_recp(&itmqueue, addr, sender, 0) != 0) + goto skip_item; + it = LIST_FIRST(&itmqueue.queue); + it->queuef = queuef; + it->queueid = queueid; + it->queuefn = fn; + fn = NULL; + } + if (LIST_EMPTY(&itmqueue.queue)) { + warnx("queue file without items: `%s'", queuefn); + goto skip_item2; + } + hdrlen = ftell(queuef); + while ((it = LIST_FIRST(&itmqueue.queue)) != NULL) { + it->hdrlen = hdrlen; + LIST_REMOVE(it, next); + LIST_INSERT_HEAD(&queue->queue, it, next); + } + continue; + +skip_item: + warn("reading queue: `%s'", queuefn); +skip_item2: + if (sender != NULL) + free(sender); + if (queuefn != NULL) + free(queuefn); + if (fn != NULL) + free(fn); + if (queueid != NULL) + free(queueid); + close(fd); + } + closedir(spooldir); + return; + +fail: + err(1, "reading queue"); +} + +static 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; + + if (LIST_EMPTY(&queue->queue)) { + printf("Mail queue is empty\n"); + return; + } + + LIST_FOREACH(it, &queue->queue, next) { + printf("\ +ID\t: %s\n\ +From\t: %s\n\ +To\t: %s\n--\n", it->queueid, it->sender, it->addr); + } +} + +/* + * TODO: + * + * - alias processing + * - use group permissions + * - proper sysexit codes + */ + +int +main(int argc, char **argv) +{ + char *sender = NULL; + char tag[255]; + char confpath[PATH_MAX]; + struct qitem *it; + struct queue queue; + struct queue lqueue; + struct stat sb; + int i, ch; + int nodot = 0, doqueue = 0, showq = 0; + + atexit(deltmp); + LIST_INIT(&queue.queue); + snprintf(tag, 254, "dma"); + + while ((ch = getopt(argc, argv, "A:b:Df:iL:o:qr:")) != -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 FALLTRHOUGH */ + case 'D': + daemonize = 0; + break; + case 'L': + if (optarg != NULL) + snprintf(tag, 254, "%s", optarg); + break; + case 'f': + case 'r': + sender = optarg; + break; + + case 'o': + /* -oX is being ignored, except for -oi */ + if (optarg[0] != 'i') + break; + /* else FALLTRHOUGH */ + case 'i': + nodot = 1; + break; + + case 'q': + doqueue = 1; + break; + + default: + exit(1); + } + } + argc -= optind; + argv += optind; + + openlog(tag, LOG_PID | LOG_PERROR, LOG_MAIL); + + config = malloc(sizeof(struct config)); + if (config == NULL) + errx(1, "Cannot allocate enough memory"); + + memset(config, 0, sizeof(struct config)); + + /* Check if the user has its own config files */ + snprintf(confpath, PATH_MAX, "%s/.dma/dma.conf", getenv("HOME")); + if (stat(confpath, &sb) < 0) + snprintf(confpath, PATH_MAX, "%s", CONF_PATH); + + if (parse_conf(confpath, config) < 0) { + free(config); + errx(1, "reading config file"); + } + + if (config->features & VIRTUAL) + if (parse_virtuser(config->virtualpath) < 0) + errx(1, "error reading virtual user file: %s", + config->virtualpath); + + if (parse_authfile(config->authpath) < 0) + err(1, "reading SMTP authentication file"); + + if (showq) { + if (argc != 0) + errx(1, "sending mail and displaying queue is" + " mutually exclusive"); + load_queue(&lqueue); + show_queue(&lqueue); + return (0); + } + + if (doqueue) { + if (argc != 0) + errx(1, "sending mail and queue pickup is mutually exclusive"); + load_queue(&lqueue); + run_queue(&lqueue); + return (0); + } + + if (read_aliases() != 0) + err(1, "reading aliases"); + + if ((sender = set_from(sender)) == NULL) + err(1, "setting from address"); + + for (i = 0; i < argc; i++) { + if (add_recp(&queue, argv[i], sender, 1) != 0) + errx(1, "invalid recipient `%s'\n", argv[i]); + } + + if (LIST_EMPTY(&queue.queue)) + errx(1, "no recipients"); + + if (gentempf(&queue) != 0) + err(1, "create temp file"); + + if (preparespool(&queue, sender) != 0) + err(1, "creating spools (1)"); + + if (readmail(&queue, sender, nodot) != 0) + err(1, "reading mail"); + + if (linkspool(&queue) != 0) + err(1, "creating spools (2)"); + + /* From here on the mail is safe. */ + + if (config->features & DEFER) + return (0); + + it = go_background(&queue); + deliver(it); + + /* NOTREACHED */ + + return (0); +} diff --git a/libexec/dma/dma.h b/libexec/dma/dma.h new file mode 100644 index 0000000000..00d31d9990 --- /dev/null +++ b/libexec/dma/dma.h @@ -0,0 +1,155 @@ +/* + * 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. + * + * $DragonFly: src/libexec/dma/dma.h,v 1.1 2008/02/02 18:20:51 matthias Exp $ + */ + +#ifndef DMA_H +#define DMA_H + +#ifdef HAVE_CRYPTO +#include +#endif /* HAVE_CRYPTO */ + +#include +#include + + +#define VERSION "DragonFly Mail Agent 1.0" + +#define BUF_SIZE 2048 +#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 PATH_MAX 1024 /* Max path len */ +#define CONF_PATH "/etc/dma/dma.conf" /* /etc/dma/dma.conf */ +#define SMTP_PORT 25 /* default SMTP port */ +#define CON_TIMEOUT 120 /* Connection timeout */ + +#define VIRTUAL 0x1 /* Support for address rewrites */ +#define STARTTLS 0x2 /* StartTLS support */ +#define SECURETRANS 0x4 /* SSL/TLS in general */ +#define TLSINIT 0x8 /* Flag for TLS init phase */ +#define DEFER 0x10 /* Defer mails */ + +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 *queueid; + FILE *queuef; + off_t hdrlen; + int remote; +}; +LIST_HEAD(queueh, qitem); + +struct queue { + struct queueh queue; + uintmax_t id; + int mailfd; + char *tmpf; +}; + +struct config { + char *smarthost; + int port; + char *aliases; + char *spooldir; + char *virtualpath; + char *authpath; + char *certfile; + int features; +#ifdef HAVE_CRYPTO + SSL *ssl; +#endif /* HAVE_CRYPTO */ +}; + + +struct virtuser { + SLIST_ENTRY(virtuser) next; + char *login; + char *address; +}; +SLIST_HEAD(virtusers, virtuser); + +struct authuser { + SLIST_ENTRY(authuser) next; + char *login; + char *password; + char *host; +}; +SLIST_HEAD(authusers, authuser); + +extern struct aliases aliases; + +/* aliases_parse.y */ +extern int yyparse(void); +extern FILE *yyin; + +/* conf.c */ +extern void trim_line(char *); +extern int parse_conf(const char *, struct config *); +extern int parse_virtuser(const char *); +extern int parse_authfile(const char *); + +/* crypto.c */ +#ifdef HAVE_CRYPTO +extern int smtp_init_crypto(struct qitem *, int, int); +#endif /* HAVE_CRYPTO */ + +/* net.c */ +extern int check_for_smtp_error(int, char *); +extern ssize_t send_remote_command(int, const char*, ...); +extern int deliver_remote(struct qitem *, const char **); + +/* base64.c */ +extern int base64_encode(const void *, int, char **); + +/* dma.c */ +extern char * hostname(void); +#endif diff --git a/libexec/dma/net.c b/libexec/dma/net.c new file mode 100644 index 0000000000..3e8fe1eafa --- /dev/null +++ b/libexec/dma/net.c @@ -0,0 +1,434 @@ +/* + * 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. + * + * $DragonFly: src/libexec/dma/net.c,v 1.1 2008/02/02 18:20:51 matthias Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CRYPTO +#include +#endif /* HAVE_CRYPTO */ + +#include +#include +#include +#include +#include + +#include "dma.h" + +extern struct config *config; +extern struct authusers authusers; +static jmp_buf timeout_alarm; + +static void +sig_alarm(int signo) +{ + longjmp(timeout_alarm, 1); +} + +ssize_t +send_remote_command(int fd, const char* fmt, ...) +{ + va_list va; + char cmd[4096]; + ssize_t len = 0; + + va_start(va, fmt); + vsprintf(cmd, fmt, va); + + if (((config->features & SECURETRANS) != 0) && + ((config->features & TLSINIT) == 0)) { + len = SSL_write(config->ssl, (const char*)cmd, strlen(cmd)); + SSL_write(config->ssl, "\r\n", 2); + } + else { + len = write(fd, cmd, strlen(cmd)); + write(fd, "\r\n", 2); + } + va_end(va); + + return (len+2); +} + +static int +read_remote_command(int fd, char *buff) +{ + ssize_t len; + + if (signal(SIGALRM, sig_alarm) == SIG_ERR) { + syslog(LOG_ERR, "SIGALRM error: %m"); + } + if (setjmp(timeout_alarm) != 0) { + syslog(LOG_ERR, "Timeout reached"); + return (1); + } + alarm(CON_TIMEOUT); + + /* + * According to RFC 821 a reply can consists of multiple lines, so + * so read until the 4th char of the reply code is != '-' + */ + if (((config->features & SECURETRANS) != 0) && + ((config->features & TLSINIT) == 0)) + do { + len = SSL_read(config->ssl, buff, BUF_SIZE); + } while (len > 3 && buff[3] == '-'); + else + do { + len = read(fd, buff, BUF_SIZE); + } while (len > 3 && buff[3] == '-'); + + alarm(0); + + return (0); +} + +int +check_for_smtp_error(int fd, char *buff) +{ + if (read_remote_command(fd, buff) < 0) + return (-1); + + /* We received a 5XX reply thus an error happend */ + if (strncmp(buff, "5", 1) == 0) { + syslog(LOG_ERR, "SMTP error : %s", buff); + return (-1); + } + return (0); +} + +/* + * Handle SMTP authentication + * + * XXX TODO: give me AUTH CRAM-MD5 + */ +static int +smtp_login(struct qitem *it, int fd, char *login, char* password) +{ + char buf[2048]; + char *temp; + int len; + + /* Send AUTH command according to RFC 2554 */ + send_remote_command(fd, "AUTH LOGIN"); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " AUTH login not available: %m", it->queueid); + return (1); + } + + len = base64_encode(login, strlen(login), &temp); + if (len <= 0) + return (-1); + + send_remote_command(fd, "%s", temp); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " AUTH login failed: %m", it->queueid); + return (-1); + } + + len = base64_encode(password, strlen(password), &temp); + if (len <= 0) + return (-1); + + send_remote_command(fd, "%s", temp); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " AUTH password failed: %m", it->queueid); + return (-1); + } + + return (0); +} + +static int +open_connection(struct qitem *it, const char *host) +{ +#ifdef HAVE_INET6 + struct addrinfo hints, *res, *res0; + char servname[128]; + const char *errmsg = NULL; +#else + struct hostent *hn; + struct sockaddr_in addr; +#endif + int fd, error = 0, port; + + if (config->port != NULL) + port = config->port; + else + port = SMTP_PORT; + +#ifdef HAVE_INET6 + /* Shamelessly taken from getaddrinfo(3) */ + 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); + error = getaddrinfo(host, servname, &hints, &res0); + if (error) { + syslog(LOG_ERR, "%s: remote delivery deferred: " + "%s: %m", it->queueid, gai_strerror(error)); + return (-1); + } + fd = -1; + for (res = res0; res; res = res->ai_next) { + fd=socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd < 0) { + errmsg = "socket failed"; + continue; + } + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + errmsg = "connect failed"; + close(fd); + fd = -1; + continue; + } + break; + } + if (fd < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: %s (%s:%s)", + it->queueid, errmsg, host, servname); + freeaddrinfo(res0); + return (-1); + } + freeaddrinfo(res0); +#else + memset(&addr, 0, sizeof(addr)); + fd = socket(AF_INET, SOCK_STREAM, 0); + addr.sin_family = AF_INET; + + addr.sin_port = htons(port); + error = inet_pton(AF_INET, host, &addr.sin_addr); + if (error < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: " + "address conversion failed: %m", it->queueid); + return (1); + } + hn = gethostbyname(host); + if (hn == NULL) { + syslog(LOG_ERR, "%s: remote delivery deferred: cannot resolve " + "hostname (%s) %m", it->queueid, host); + return (-1); + } else { + memcpy(&addr.sin_addr, hn->h_addr, sizeof(struct in_addr)); + if (hn->h_length != 4) + return (-1); + } + + error = connect(fd, (struct sockaddr *) &addr, sizeof(addr)); + if (error < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: " + "connection failed : %m", it->queueid); + return (-1); + } +#endif + return (fd); +} + +int +deliver_remote(struct qitem *it, const char **errmsg) +{ + struct authuser *a; + char *host, buf[2048], line[1000]; + int fd, error = 0, do_auth = 0; + size_t linelen; + + host = strrchr(it->addr, '@'); + /* Should not happen */ + if (host == NULL) + return(-1); + else + /* Step over the @ */ + host++; + + /* Smarthost support? */ + if (config->smarthost != NULL && strlen(config->smarthost) > 0) { + syslog(LOG_INFO, "%s: using smarthost (%s)", + it->queueid, config->smarthost); + host = config->smarthost; + } + + fd = open_connection(it, host); + if (fd < 0) + return (1); + +#ifdef HAVE_CRYPTO + if ((config->features & SECURETRANS) != 0) { + error = smtp_init_crypto(it, fd, config->features); + if (error >= 0) + syslog(LOG_INFO, "%s: SSL initialization sucessful", + it->queueid); + else + goto out; + } + + /* + * If the user doesn't want STARTTLS, but SSL encryption, we + * have to enable SSL first, then send EHLO + */ + if (((config->features & STARTTLS) == 0) && + ((config->features & SECURETRANS) != 0)) { + send_remote_command(fd, "EHLO %s", hostname()); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: " + " EHLO failed: %m", it->queueid); + return (-1); + } + } +#endif /* HAVE_CRYPTO */ + if (((config->features & SECURETRANS) == 0)) { + send_remote_command(fd, "EHLO %s", hostname()); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: " + " EHLO failed: %m", it->queueid); + return (-1); + } + } + + /* + * Use SMTP authentication if the user defined an entry for the remote + * or smarthost + */ + SLIST_FOREACH(a, &authusers, next) { + if (strcmp(a->host, host) == 0) { + do_auth = 1; + break; + } + } + + if (do_auth == 1) { + syslog(LOG_INFO, "%s: Use SMTP authentication", it->queueid); + error = smtp_login(it, fd, a->login, a->password); + if (error < 0) { + syslog(LOG_ERR, "%s: remote delivery failed:" + " SMTP login failed: %m", it->queueid); + return (-1); + } + /* SMTP login is not available, so try without */ + else if (error > 0) + syslog(LOG_ERR, "%s: SMTP login not available. Try without", + it->queueid); + } + + send_remote_command(fd, "MAIL FROM:<%s>", it->sender); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " MAIL FROM failed: %m", it->queueid); + return (1); + } + + /* XXX TODO: + * Iterate over all recepients and open only one connection + */ + send_remote_command(fd, "RCPT TO:<%s>", it->addr); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " RCPT TO failed: %m", it->queueid); + return (1); + } + + send_remote_command(fd, "DATA"); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred:" + " DATA failed: %m", it->queueid); + return (1); + } + + if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: cannot seek: %m", + it->queueid); + return (1); + } + + while (!feof(it->queuef)) { + if (fgets(line, sizeof(line), it->queuef) == NULL) + break; + linelen = strlen(line); + if (linelen == 0 || line[linelen - 1] != '\n') { + syslog(LOG_CRIT, "%s: remote delivery failed:" + "corrupted queue file", it->queueid); + *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_ERR, "%s: remote delivery deferred: " + "write error", it->queueid); + error = 1; + goto out; + } + } + + send_remote_command(fd, "."); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: %m", + it->queueid); + return (1); + } + + send_remote_command(fd, "QUIT"); + if (check_for_smtp_error(fd, buf) < 0) { + syslog(LOG_ERR, "%s: remote delivery deferred: " + "QUIT failed: %m", it->queueid); + return (1); + } +out: + + close(fd); + return (error); +} + -- 2.11.4.GIT