]> git.ipfire.org Git - people/ms/dma.git/blobdiff - mail.c
Merge pull request #34 from mtremer/better-authentication
[people/ms/dma.git] / mail.c
diff --git a/mail.c b/mail.c
index dd317e5b309b9e660cad5ee6a82a0ecbbb1e3613..a6d11fcae685a9dd1440fd4782de4a5628826cee 100644 (file)
--- a/mail.c
+++ b/mail.c
@@ -1,8 +1,9 @@
 /*
+ * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>.
  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
  *
  * This code is derived from software contributed to The DragonFly Project
- * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>.
+ * by Simon Schubert <2@0x2c.org>.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -33,6 +34,8 @@
  */
 
 #include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
 #include <syslog.h>
 #include <unistd.h>
 
@@ -49,13 +52,13 @@ bounce(struct qitem *it, const char *reason)
        /* Don't bounce bounced mails */
        if (it->sender[0] == 0) {
                syslog(LOG_INFO, "can not bounce a bounce message, discarding");
-               exit(1);
+               exit(EX_SOFTWARE);
        }
 
        bzero(&bounceq, sizeof(bounceq));
        LIST_INIT(&bounceq.queue);
        bounceq.sender = "";
-       if (add_recp(&bounceq, it->sender, 1) != 0)
+       if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0)
                goto fail;
 
        if (newspoolf(&bounceq) != 0)
@@ -67,7 +70,7 @@ bounce(struct qitem *it, const char *reason)
        error = fprintf(bounceq.mailf,
                "Received: from MAILER-DAEMON\n"
                "\tid %s\n"
-               "\tby %s (%s)\n"
+               "\tby %s (%s);\n"
                "\t%s\n"
                "X-Original-To: <%s>\n"
                "From: MAILER-DAEMON <>\n"
@@ -94,15 +97,15 @@ bounce(struct qitem *it, const char *reason)
                VERSION, hostname(),
                it->addr,
                reason,
-               config->features & FULLBOUNCE ?
+               config.features & FULLBOUNCE ?
                    "Original message follows." :
                    "Message headers follow.");
        if (error < 0)
                goto fail;
 
-       if (fseek(it->mailf, it->hdrlen, SEEK_SET) != 0)
+       if (fseek(it->mailf, 0, SEEK_SET) != 0)
                goto fail;
-       if (config->features & FULLBOUNCE) {
+       if (config.features & FULLBOUNCE) {
                while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
                        if (fwrite(line, 1, pos, bounceq.mailf) != pos)
                                goto fail;
@@ -130,12 +133,218 @@ bounce(struct qitem *it, const char *reason)
 fail:
        syslog(LOG_CRIT, "error creating bounce: %m");
        delqueue(it);
-       exit(1);
+       exit(EX_IOERR);
+}
+
+struct parse_state {
+       char addr[1000];
+       int pos;
+
+       enum {
+               NONE = 0,
+               START,
+               MAIN,
+               EOL,
+               QUIT
+       } state;
+       int comment;
+       int quote;
+       int brackets;
+       int esc;
+};
+
+/*
+ * Simplified RFC2822 header/address parsing.
+ * We copy escapes and quoted strings directly, since
+ * we have to pass them like this to the mail server anyways.
+ * XXX local addresses will need treatment
+ */
+static int
+parse_addrs(struct parse_state *ps, char *s, struct queue *queue)
+{
+       char *addr;
+
+again:
+       switch (ps->state) {
+       case NONE:
+               return (-1);
+
+       case START:
+               /* init our data */
+               bzero(ps, sizeof(*ps));
+
+               /* skip over header name */
+               while (*s != ':')
+                       s++;
+               s++;
+               ps->state = MAIN;
+               break;
+
+       case MAIN:
+               /* all fine */
+               break;
+
+       case EOL:
+               switch (*s) {
+               case ' ':
+               case '\t':
+                       s++;
+                       /* continue */
+                       break;
+
+               default:
+                       ps->state = QUIT;
+                       if (ps->pos != 0)
+                               goto newaddr;
+                       return (0);
+               }
+
+       case QUIT:
+               return (0);
+       }
+
+       for (; *s != 0; s++) {
+               if (ps->esc) {
+                       ps->esc = 0;
+
+                       switch (*s) {
+                       case '\r':
+                       case '\n':
+                               goto err;
+
+                       default:
+                               goto copy;
+                       }
+               }
+
+               if (ps->quote) {
+                       switch (*s) {
+                       case '"':
+                               ps->quote = 0;
+                               goto copy;
+
+                       case '\\':
+                               ps->esc = 1;
+                               goto copy;
+
+                       case '\r':
+                       case '\n':
+                               goto eol;
+
+                       default:
+                               goto copy;
+                       }
+               }
+
+               switch (*s) {
+               case '(':
+                       ps->comment++;
+                       break;
+
+               case ')':
+                       if (ps->comment)
+                               ps->comment--;
+                       else
+                               goto err;
+                       goto skip;
+
+               case '"':
+                       ps->quote = 1;
+                       goto copy;
+
+               case '\\':
+                       ps->esc = 1;
+                       goto copy;
+
+               case '\r':
+               case '\n':
+                       goto eol;
+               }
+
+               if (ps->comment)
+                       goto skip;
+
+               switch (*s) {
+               case ' ':
+               case '\t':
+                       /* ignore whitespace */
+                       goto skip;
+
+               case '<':
+                       /* this is the real address now */
+                       ps->brackets = 1;
+                       ps->pos = 0;
+                       goto skip;
+
+               case '>':
+                       if (!ps->brackets)
+                               goto err;
+                       ps->brackets = 0;
+
+                       s++;
+                       goto newaddr;
+
+               case ':':
+                       /* group - ignore */
+                       ps->pos = 0;
+                       goto skip;
+
+               case ',':
+               case ';':
+                       /*
+                        * Next address, copy previous one.
+                        * However, we might be directly after
+                        * a <address>, or have two consecutive
+                        * commas.
+                        * Skip the comma unless there is
+                        * really something to copy.
+                        */
+                       if (ps->pos == 0)
+                               goto skip;
+                       s++;
+                       goto newaddr;
+
+               default:
+                       goto copy;
+               }
+
+copy:
+               if (ps->comment)
+                       goto skip;
+
+               if (ps->pos + 1 == sizeof(ps->addr))
+                       goto err;
+               ps->addr[ps->pos++] = *s;
+
+skip:
+               ;
+       }
+
+eol:
+       ps->state = EOL;
+       return (0);
+
+err:
+       ps->state = QUIT;
+       return (-1);
+
+newaddr:
+       ps->addr[ps->pos] = 0;
+       ps->pos = 0;
+       addr = strdup(ps->addr);
+       if (addr == NULL)
+               errlog(EX_SOFTWARE, NULL);
+
+       if (add_recp(queue, addr, EXPAND_WILDCARD) != 0)
+               errlogx(EX_DATAERR, "invalid recipient `%s'", addr);
+
+       goto again;
 }
 
 int
-readmail(struct queue *queue, int nodot)
+readmail(struct queue *queue, int nodot, int recp_from_header)
 {
+       struct parse_state parse_state;
        char line[1000];        /* by RFC2822 */
        size_t linelen;
        size_t error;
@@ -143,14 +352,18 @@ readmail(struct queue *queue, int nodot)
        int had_from = 0;
        int had_messagid = 0;
        int had_date = 0;
+       int had_last_line = 0;
+       int nocopy = 0;
+
+       parse_state.state = NONE;
 
        error = fprintf(queue->mailf,
                "Received: from %s (uid %d)\n"
                "\t(envelope-from %s)\n"
                "\tid %s\n"
-               "\tby %s (%s)\n"
+               "\tby %s (%s);\n"
                "\t%s\n",
-               username, getuid(),
+               username, useruid,
                queue->sender,
                queue->id,
                hostname(), VERSION,
@@ -159,21 +372,59 @@ readmail(struct queue *queue, int nodot)
                return (-1);
 
        while (!feof(stdin)) {
-               if (fgets(line, sizeof(line), stdin) == NULL)
+               if (fgets(line, sizeof(line) - 1, stdin) == NULL)
                        break;
+               if (had_last_line)
+                       errlogx(EX_DATAERR, "bad mail input format:"
+                               " from %s (uid %d) (envelope-from %s)",
+                               username, useruid, queue->sender);
                linelen = strlen(line);
                if (linelen == 0 || line[linelen - 1] != '\n') {
-                       errno = EINVAL;         /* XXX mark permanent errors */
-                       return (-1);
+                       /*
+                        * This line did not end with a newline character.
+                        * If we fix it, it better be the last line of
+                        * the file.
+                        */
+                       line[linelen] = '\n';
+                       line[linelen + 1] = 0;
+                       had_last_line = 1;
                }
                if (!had_headers) {
+                       /*
+                        * Unless this is a continuation, switch of
+                        * the Bcc: nocopy flag.
+                        */
+                       if (!(line[0] == ' ' || line[0] == '\t'))
+                               nocopy = 0;
+
                        if (strprefixcmp(line, "Date:") == 0)
                                had_date = 1;
                        else if (strprefixcmp(line, "Message-Id:") == 0)
                                had_messagid = 1;
                        else if (strprefixcmp(line, "From:") == 0)
                                had_from = 1;
+                       else if (strprefixcmp(line, "Bcc:") == 0)
+                               nocopy = 1;
+
+                       if (parse_state.state != NONE) {
+                               if (parse_addrs(&parse_state, line, queue) < 0) {
+                                       errlogx(EX_DATAERR, "invalid address in header\n");
+                                       /* NOTREACHED */
+                               }
+                       }
+
+                       if (recp_from_header && (
+                                       strprefixcmp(line, "To:") == 0 ||
+                                       strprefixcmp(line, "Cc:") == 0 ||
+                                       strprefixcmp(line, "Bcc:") == 0)) {
+                               parse_state.state = START;
+                               if (parse_addrs(&parse_state, line, queue) < 0) {
+                                       errlogx(EX_DATAERR, "invalid address in header\n");
+                                       /* NOTREACHED */
+                               }
+                       }
                }
+
                if (strcmp(line, "\n") == 0 && !had_headers) {
                        had_headers = 1;
                        while (!had_date || !had_messagid || !had_from) {
@@ -181,10 +432,13 @@ readmail(struct queue *queue, int nodot)
                                        had_date = 1;
                                        snprintf(line, sizeof(line), "Date: %s\n", rfc822date());
                                } else if (!had_messagid) {
-                                       /* XXX better msgid, assign earlier and log? */
+                                       /* XXX msgid, assign earlier and log? */
                                        had_messagid = 1;
-                                       snprintf(line, sizeof(line), "Message-Id: <%s@%s>\n",
-                                                queue->id, hostname());
+                                       snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n",
+                                                (uintmax_t)time(NULL),
+                                                queue->id,
+                                                (uintmax_t)random(),
+                                                hostname());
                                } else if (!had_from) {
                                        had_from = 1;
                                        snprintf(line, sizeof(line), "From: <%s>\n", queue->sender);
@@ -196,8 +450,10 @@ readmail(struct queue *queue, int nodot)
                }
                if (!nodot && linelen == 2 && line[0] == '.')
                        break;
-               if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
-                       return (-1);
+               if (!nocopy) {
+                       if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
+                               return (-1);
+               }
        }
 
        return (0);