]> git.ipfire.org Git - people/ms/dma.git/commitdiff
Add the DragonFly Mail Agent dma(8) to the base.
authorMatthias Schmidt <matthias@dragonflybsd.org>
Sat, 2 Feb 2008 18:20:51 +0000 (18:20 +0000)
committerMatthias Schmidt <matthias@dragonflybsd.org>
Sat, 2 Feb 2008 18:20:51 +0000 (18:20 +0000)
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@
Makefile [new file with mode: 0644]
aliases_parse.y [new file with mode: 0644]
aliases_scan.l [new file with mode: 0644]
base64.c [new file with mode: 0644]
conf.c [new file with mode: 0644]
crypto.c [new file with mode: 0644]
dma.8 [new file with mode: 0644]
dma.c [new file with mode: 0644]
dma.h [new file with mode: 0644]
net.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..34d3cb9
--- /dev/null
+++ b/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 <bsd.prog.mk>
diff --git a/aliases_parse.y b/aliases_parse.y
new file mode 100644 (file)
index 0000000..e13a6ef
--- /dev/null
@@ -0,0 +1,107 @@
+%{
+/* $DragonFly: src/libexec/dma/aliases_parse.y,v 1.1 2008/02/02 18:20:51 matthias Exp $ */
+
+#include <err.h>
+#include <string.h>
+#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 <ident> T_IDENT
+%token T_ERROR
+%token T_EOF 0
+
+%type <strit> dests
+%type <alias> 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/aliases_scan.l b/aliases_scan.l
new file mode 100644 (file)
index 0000000..7846b30
--- /dev/null
@@ -0,0 +1,20 @@
+%{
+/* $DragonFly: src/libexec/dma/aliases_scan.l,v 1.1 2008/02/02 18:20:51 matthias Exp $ */
+
+#include <string.h>
+#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;
+<<EOF>>                                return T_EOF;
+
+%%
diff --git a/base64.c b/base64.c
new file mode 100644 (file)
index 0000000..610aa3c
--- /dev/null
+++ b/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 <stdlib.h>
+#include <string.h>
+
+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/conf.c b/conf.c
new file mode 100644 (file)
index 0000000..02a7ca9
--- /dev/null
+++ b/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 <matthias@dragonflybsd.org>, 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 <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <stdarg.h>
+
+#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/crypto.c b/crypto.c
new file mode 100644 (file)
index 0000000..e509c94
--- /dev/null
+++ b/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 <matthias@dragonflybsd.org>, 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 <openssl/x509.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+
+#include <syslog.h>
+
+#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/dma.8 b/dma.8
new file mode 100644 (file)
index 0000000..aace6b3
--- /dev/null
+++ b/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/dma.c b/dma.c
new file mode 100644 (file)
index 0000000..f53908c
--- /dev/null
+++ b/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 <corecode@fs.ei.tum.de>.
+ *
+ * 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 <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_CRYPTO
+#include <openssl/ssl.h>
+#endif /* HAVE_CRYPTO */
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#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
+ * ...
+ * <empty line>
+ * 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/dma.h b/dma.h
new file mode 100644 (file)
index 0000000..00d31d9
--- /dev/null
+++ b/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 <corecode@fs.ei.tum.de> and
+ * Matthias Schmidt <matthias@dragonflybsd.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $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 <openssl/ssl.h>
+#endif /* HAVE_CRYPTO */
+
+#include <sys/queue.h>
+#include <stdio.h>
+
+
+#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/net.c b/net.c
new file mode 100644 (file)
index 0000000..3e8fe1e
--- /dev/null
+++ b/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 <matthias@dragonflybsd.org>, 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 <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_CRYPTO
+#include <openssl/ssl.h>
+#endif /* HAVE_CRYPTO */
+
+#include <netdb.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#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);
+}
+