Index: head/contrib/dma/conf.c =================================================================== --- head/contrib/dma/conf.c (revision 262294) +++ head/contrib/dma/conf.c (revision 262295) @@ -1,246 +1,246 @@ /* * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthias Schmidt , University of Marburg, * Germany. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "dma.h" #define DP ": \t" #define EQS " \t" /* * Remove trailing \n's */ void trim_line(char *line) { size_t linelen; char *p; if ((p = strchr(line, '\n'))) *p = (char)0; /* Escape leading dot in every case */ linelen = strlen(line); if (line[0] == '.') { if ((linelen + 2) > 1000) { syslog(LOG_CRIT, "Cannot escape leading dot. Buffer overflow"); exit(1); } memmove((line + 1), line, (linelen + 1)); line[0] = '.'; } } static void chomp(char *str) { size_t len = strlen(str); if (len == 0) return; if (str[len - 1] == '\n') str[len - 1] = 0; } /* * Read the SMTP authentication config file * * file format is: * user|host:password * * A line starting with # is treated as comment and ignored. */ void parse_authfile(const char *path) { char line[2048]; struct authuser *au; FILE *a; char *data; int lineno = 0; a = fopen(path, "r"); if (a == NULL) { errlog(1, "can not open auth file `%s'", path); /* NOTREACHED */ } while (!feof(a)) { if (fgets(line, sizeof(line), a) == NULL) break; lineno++; chomp(line); /* We hit a comment */ if (*line == '#') continue; /* Ignore empty lines */ if (*line == 0) continue; au = calloc(1, sizeof(*au)); if (au == NULL) - errlog(1, NULL); + errlog(1, "calloc failed"); data = strdup(line); au->login = strsep(&data, "|"); au->host = strsep(&data, DP); au->password = data; if (au->login == NULL || au->host == NULL || au->password == NULL) { errlogx(1, "syntax error in authfile %s:%d", path, lineno); /* NOTREACHED */ } SLIST_INSERT_HEAD(&authusers, au, next); } fclose(a); } /* * XXX TODO * Check for bad things[TM] */ void parse_conf(const char *config_path) { char *word; char *data; FILE *conf; char line[2048]; int lineno = 0; conf = fopen(config_path, "r"); if (conf == NULL) { /* Don't treat a non-existing config file as error */ if (errno == ENOENT) return; errlog(1, "can not open config `%s'", config_path); /* NOTREACHED */ } while (!feof(conf)) { if (fgets(line, sizeof(line), conf) == NULL) break; lineno++; chomp(line); /* We hit a comment */ if (strchr(line, '#')) *strchr(line, '#') = 0; data = line; word = strsep(&data, EQS); /* Ignore empty lines */ if (word == NULL || *word == 0) continue; if (data != NULL && *data != 0) data = strdup(data); else data = NULL; if (strcmp(word, "SMARTHOST") == 0 && data != NULL) config.smarthost = data; else if (strcmp(word, "PORT") == 0 && data != NULL) config.port = atoi(data); else if (strcmp(word, "ALIASES") == 0 && data != NULL) config.aliases = data; else if (strcmp(word, "SPOOLDIR") == 0 && data != NULL) config.spooldir = data; else if (strcmp(word, "AUTHPATH") == 0 && data != NULL) config.authpath= data; else if (strcmp(word, "CERTFILE") == 0 && data != NULL) config.certfile = data; else if (strcmp(word, "MAILNAME") == 0 && data != NULL) config.mailname = data; else if (strcmp(word, "MASQUERADE") == 0 && data != NULL) { char *user = NULL, *host = NULL; if (strrchr(data, '@')) { host = strrchr(data, '@'); *host = 0; host++; user = data; } else { host = data; } if (host && *host == 0) host = NULL; if (user && *user == 0) user = NULL; config.masquerade_host = host; config.masquerade_user = user; } else if (strcmp(word, "STARTTLS") == 0 && data == NULL) config.features |= STARTTLS; else if (strcmp(word, "OPPORTUNISTIC_TLS") == 0 && data == NULL) config.features |= TLS_OPP; else if (strcmp(word, "SECURETRANSFER") == 0 && data == NULL) config.features |= SECURETRANS; else if (strcmp(word, "DEFER") == 0 && data == NULL) config.features |= DEFER; else if (strcmp(word, "INSECURE") == 0 && data == NULL) config.features |= INSECURE; else if (strcmp(word, "FULLBOUNCE") == 0 && data == NULL) config.features |= FULLBOUNCE; else if (strcmp(word, "NULLCLIENT") == 0 && data == NULL) config.features |= NULLCLIENT; else { errlogx(1, "syntax error in %s:%d", config_path, lineno); /* NOTREACHED */ } } if ((config.features & NULLCLIENT) && config.smarthost == NULL) { errlogx(1, "%s: NULLCLIENT requires SMARTHOST", config_path); /* NOTREACHED */ } fclose(conf); } Index: head/contrib/dma/dma.c =================================================================== --- head/contrib/dma/dma.c (revision 262294) +++ head/contrib/dma/dma.c (revision 262295) @@ -1,631 +1,631 @@ /* * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon 'corecode' Schubert . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "dfcompat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dma.h" static void deliver(struct qitem *); struct aliases aliases = LIST_HEAD_INITIALIZER(aliases); struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs); struct authusers authusers = LIST_HEAD_INITIALIZER(authusers); char username[USERNAME_SIZE]; uid_t useruid; const char *logident_base; char errmsg[ERRMSG_SIZE]; static int daemonize = 1; static int doqueue = 0; struct config config = { .smarthost = NULL, .port = 25, .aliases = "/etc/aliases", .spooldir = "/var/spool/dma", .authpath = NULL, .certfile = NULL, .features = 0, .mailname = NULL, .masquerade_host = NULL, .masquerade_user = NULL, }; static void sighup_handler(int signo) { (void)signo; /* so that gcc doesn't complain */ } static char * set_from(struct queue *queue, const char *osender) { const char *addr; char *sender; if (osender) { addr = osender; } else if (getenv("EMAIL") != NULL) { addr = getenv("EMAIL"); } else { if (config.masquerade_user) addr = config.masquerade_user; else addr = username; } if (!strchr(addr, '@')) { const char *from_host = hostname(); if (config.masquerade_host) from_host = config.masquerade_host; if (asprintf(&sender, "%s@%s", addr, from_host) <= 0) return (NULL); } else { sender = strdup(addr); if (sender == NULL) return (NULL); } if (strchr(sender, '\n') != NULL) { errno = EINVAL; return (NULL); } queue->sender = sender; return (sender); } static int read_aliases(void) { yyin = fopen(config.aliases, "r"); if (yyin == NULL) { /* * Non-existing aliases file is not a fatal error */ if (errno == ENOENT) return (0); /* Other problems are. */ return (-1); } if (yyparse()) return (-1); /* fatal error, probably malloc() */ fclose(yyin); return (0); } static int do_alias(struct queue *queue, const char *addr) { struct alias *al; struct stritem *sit; int aliased = 0; LIST_FOREACH(al, &aliases, next) { if (strcmp(al->alias, addr) != 0) continue; SLIST_FOREACH(sit, &al->dests, next) { if (add_recp(queue, sit->str, EXPAND_ADDR) != 0) return (-1); } aliased = 1; } return (aliased); } int add_recp(struct queue *queue, const char *str, int expand) { struct qitem *it, *tit; struct passwd *pw; char *host; int aliased = 0; it = calloc(1, sizeof(*it)); if (it == NULL) return (-1); it->addr = strdup(str); if (it->addr == NULL) return (-1); it->sender = queue->sender; host = strrchr(it->addr, '@'); if (host != NULL && (strcmp(host + 1, hostname()) == 0 || strcmp(host + 1, "localhost") == 0)) { *host = 0; } LIST_FOREACH(tit, &queue->queue, next) { /* weed out duplicate dests */ if (strcmp(tit->addr, it->addr) == 0) { free(it->addr); free(it); return (0); } } LIST_INSERT_HEAD(&queue->queue, it, next); /** * Do local delivery if there is no @. * Do not do local delivery when NULLCLIENT is set. */ if (strrchr(it->addr, '@') == NULL && (config.features & NULLCLIENT) == 0) { it->remote = 0; if (expand) { aliased = do_alias(queue, it->addr); if (!aliased && expand == EXPAND_WILDCARD) aliased = do_alias(queue, "*"); if (aliased < 0) return (-1); if (aliased) { LIST_REMOVE(it, next); } else { /* Local destination, check */ pw = getpwnam(it->addr); if (pw == NULL) goto out; /* XXX read .forward */ endpwent(); } } } else { it->remote = 1; } return (0); out: free(it->addr); free(it); return (-1); } static struct qitem * go_background(struct queue *queue) { struct sigaction sa; struct qitem *it; pid_t pid; if (daemonize && daemon(0, 0) != 0) { syslog(LOG_ERR, "can not daemonize: %m"); exit(1); } daemonize = 0; bzero(&sa, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); LIST_FOREACH(it, &queue->queue, next) { /* No need to fork for the last dest */ if (LIST_NEXT(it, next) == NULL) goto retit; pid = fork(); switch (pid) { case -1: syslog(LOG_ERR, "can not fork: %m"); exit(1); break; case 0: /* * Child: * * return and deliver mail */ retit: /* * If necessary, acquire the queue and * mail files. * If this fails, we probably were raced by another * process. It is okay to be raced if we're supposed * to flush the queue. */ setlogident("%s", it->queueid); switch (acquirespool(it)) { case 0: break; case 1: if (doqueue) exit(0); syslog(LOG_WARNING, "could not lock queue file"); exit(1); default: exit(1); } dropspool(queue, it); return (it); default: /* * Parent: * * fork next child */ break; } } syslog(LOG_CRIT, "reached dead code"); exit(1); } static void deliver(struct qitem *it) { int error; unsigned int backoff = MIN_RETRY, slept; struct timeval now; struct stat st; snprintf(errmsg, sizeof(errmsg), "unknown bounce reason"); retry: syslog(LOG_INFO, "trying delivery"); if (it->remote) error = deliver_remote(it); else error = deliver_local(it); switch (error) { case 0: delqueue(it); syslog(LOG_INFO, "delivery successful"); exit(0); case 1: if (stat(it->queuefn, &st) != 0) { syslog(LOG_ERR, "lost queue file `%s'", it->queuefn); exit(1); } if (gettimeofday(&now, NULL) == 0 && (now.tv_sec - st.st_mtim.tv_sec > MAX_TIMEOUT)) { snprintf(errmsg, sizeof(errmsg), "Could not deliver for the last %d seconds. Giving up.", MAX_TIMEOUT); goto bounce; } for (slept = 0; slept < backoff;) { slept += SLEEP_TIMEOUT - sleep(SLEEP_TIMEOUT); if (flushqueue_since(slept)) { backoff = MIN_RETRY; goto retry; } } if (slept >= backoff) { /* pick the next backoff between [1.5, 2.5) times backoff */ backoff = backoff + backoff / 2 + random() % backoff; if (backoff > MAX_RETRY) backoff = MAX_RETRY; } goto retry; case -1: default: break; } bounce: bounce(it, errmsg); /* NOTREACHED */ } void run_queue(struct queue *queue) { struct qitem *it; if (LIST_EMPTY(&queue->queue)) return; it = go_background(queue); deliver(it); /* NOTREACHED */ } static void show_queue(struct queue *queue) { struct qitem *it; int locked = 0; /* XXX */ if (LIST_EMPTY(&queue->queue)) { printf("Mail queue is empty\n"); return; } LIST_FOREACH(it, &queue->queue, next) { printf("ID\t: %s%s\n" "From\t: %s\n" "To\t: %s\n", it->queueid, locked ? "*" : "", it->sender, it->addr); if (LIST_NEXT(it, next) != NULL) printf("--\n"); } } /* * TODO: * * - alias processing * - use group permissions * - proper sysexit codes */ int main(int argc, char **argv) { struct sigaction act; char *sender = NULL; struct queue queue; int i, ch; int nodot = 0, showq = 0, queue_only = 0; int recp_from_header = 0; set_username(); /* * We never run as root. If called by root, drop permissions * to the mail user. */ if (geteuid() == 0 || getuid() == 0) { struct passwd *pw; errno = 0; pw = getpwnam(DMA_ROOT_USER); if (pw == NULL) { if (errno == 0) errx(1, "user '%s' not found", DMA_ROOT_USER); else err(1, "cannot drop root privileges"); } if (setuid(pw->pw_uid) != 0) err(1, "cannot drop root privileges"); if (geteuid() == 0 || getuid() == 0) errx(1, "cannot drop root privileges"); } atexit(deltmp); init_random(); bzero(&queue, sizeof(queue)); LIST_INIT(&queue.queue); if (strcmp(argv[0], "mailq") == 0) { argv++; argc--; showq = 1; if (argc != 0) errx(1, "invalid arguments"); goto skipopts; } else if (strcmp(argv[0], "newaliases") == 0) { logident_base = "dma"; - setlogident(NULL); + setlogident("%s", logident_base); if (read_aliases() != 0) errx(1, "could not parse aliases file `%s'", config.aliases); exit(0); } opterr = 0; while ((ch = getopt(argc, argv, ":A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:tUV:vX:")) != -1) { switch (ch) { case 'A': /* -AX is being ignored, except for -A{c,m} */ if (optarg[0] == 'c' || optarg[0] == 'm') { break; } /* else FALLTRHOUGH */ case 'b': /* -bX is being ignored, except for -bp */ if (optarg[0] == 'p') { showq = 1; break; } else if (optarg[0] == 'q') { queue_only = 1; break; } /* else FALLTRHOUGH */ case 'D': daemonize = 0; break; case 'L': logident_base = optarg; break; case 'f': case 'r': sender = optarg; break; case 't': recp_from_header = 1; break; case 'o': /* -oX is being ignored, except for -oi */ if (optarg[0] != 'i') break; /* else FALLTRHOUGH */ case 'O': break; case 'i': nodot = 1; break; case 'q': /* Don't let getopt slup up other arguments */ if (optarg && *optarg == '-') optind--; doqueue = 1; break; /* Ignored options */ case 'B': case 'C': case 'd': case 'F': case 'h': case 'N': case 'n': case 'R': case 'U': case 'V': case 'v': case 'X': break; case ':': if (optopt == 'q') { doqueue = 1; break; } /* FALLTHROUGH */ default: fprintf(stderr, "invalid argument: `-%c'\n", optopt); exit(1); } } argc -= optind; argv += optind; opterr = 1; if (argc != 0 && (showq || doqueue)) errx(1, "sending mail and queue operations are mutually exclusive"); if (showq + doqueue > 1) errx(1, "conflicting queue operations"); skipopts: if (logident_base == NULL) logident_base = "dma"; - setlogident(NULL); + setlogident("%s", logident_base); act.sa_handler = sighup_handler; act.sa_flags = 0; sigemptyset(&act.sa_mask); if (sigaction(SIGHUP, &act, NULL) != 0) syslog(LOG_WARNING, "can not set signal handler: %m"); parse_conf(CONF_PATH "/dma.conf"); if (config.authpath != NULL) parse_authfile(config.authpath); if (showq) { if (load_queue(&queue) < 0) errlog(1, "can not load queue"); show_queue(&queue); return (0); } if (doqueue) { flushqueue_signal(); if (load_queue(&queue) < 0) errlog(1, "can not load queue"); run_queue(&queue); return (0); } if (read_aliases() != 0) errlog(1, "could not parse aliases file `%s'", config.aliases); if ((sender = set_from(&queue, sender)) == NULL) - errlog(1, NULL); + errlog(1, "set_from failed"); if (newspoolf(&queue) != 0) errlog(1, "can not create temp file in `%s'", config.spooldir); setlogident("%s", queue.id); for (i = 0; i < argc; i++) { if (add_recp(&queue, argv[i], EXPAND_WILDCARD) != 0) errlogx(1, "invalid recipient `%s'", argv[i]); } if (LIST_EMPTY(&queue.queue) && !recp_from_header) errlogx(1, "no recipients"); if (readmail(&queue, nodot, recp_from_header) != 0) errlog(1, "can not read mail"); if (LIST_EMPTY(&queue.queue)) errlogx(1, "no recipients"); if (linkspool(&queue) != 0) errlog(1, "can not create spools"); /* From here on the mail is safe. */ if (config.features & DEFER || queue_only) return (0); run_queue(&queue); /* NOTREACHED */ return (0); } Index: head/contrib/dma/mail.c =================================================================== --- head/contrib/dma/mail.c (revision 262294) +++ head/contrib/dma/mail.c (revision 262295) @@ -1,457 +1,457 @@ /* * Copyright (c) 2008 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Simon 'corecode' Schubert . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "dma.h" void bounce(struct qitem *it, const char *reason) { struct queue bounceq; char line[1000]; size_t pos; int error; /* Don't bounce bounced mails */ if (it->sender[0] == 0) { syslog(LOG_INFO, "can not bounce a bounce message, discarding"); exit(1); } bzero(&bounceq, sizeof(bounceq)); LIST_INIT(&bounceq.queue); bounceq.sender = ""; if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0) goto fail; if (newspoolf(&bounceq) != 0) goto fail; syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id); setlogident("%s", bounceq.id); error = fprintf(bounceq.mailf, "Received: from MAILER-DAEMON\n" "\tid %s\n" "\tby %s (%s);\n" "\t%s\n" "X-Original-To: <%s>\n" "From: MAILER-DAEMON <>\n" "To: %s\n" "Subject: Mail delivery failed\n" "Message-Id: <%s@%s>\n" "Date: %s\n" "\n" "This is the %s at %s.\n" "\n" "There was an error delivering your mail to <%s>.\n" "\n" "%s\n" "\n" "%s\n" "\n", bounceq.id, hostname(), VERSION, rfc822date(), it->addr, it->sender, bounceq.id, hostname(), rfc822date(), VERSION, hostname(), it->addr, reason, config.features & FULLBOUNCE ? "Original message follows." : "Message headers follow."); if (error < 0) goto fail; if (fseek(it->mailf, 0, SEEK_SET) != 0) goto fail; if (config.features & FULLBOUNCE) { while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) { if (fwrite(line, 1, pos, bounceq.mailf) != pos) goto fail; } } else { while (!feof(it->mailf)) { if (fgets(line, sizeof(line), it->mailf) == NULL) break; if (line[0] == '\n') break; if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1) goto fail; } } if (linkspool(&bounceq) != 0) goto fail; /* bounce is safe */ delqueue(it); run_queue(&bounceq); /* NOTREACHED */ fail: syslog(LOG_CRIT, "error creating bounce: %m"); delqueue(it); exit(1); } struct parse_state { char addr[1000]; int pos; enum { NONE = 0, START, MAIN, EOL, QUIT } state; int comment; int quote; int brackets; int esc; }; /* * Simplified RFC2822 header/address parsing. * We copy escapes and quoted strings directly, since * we have to pass them like this to the mail server anyways. * XXX local addresses will need treatment */ static int parse_addrs(struct parse_state *ps, char *s, struct queue *queue) { char *addr; again: switch (ps->state) { case NONE: return (-1); case START: /* init our data */ bzero(ps, sizeof(*ps)); /* skip over header name */ while (*s != ':') s++; s++; ps->state = MAIN; break; case MAIN: /* all fine */ break; case EOL: switch (*s) { case ' ': case '\t': s++; /* continue */ break; default: ps->state = QUIT; if (ps->pos != 0) goto newaddr; return (0); } case QUIT: return (0); } for (; *s != 0; s++) { if (ps->esc) { ps->esc = 0; switch (*s) { case '\r': case '\n': goto err; default: goto copy; } } if (ps->quote) { switch (*s) { case '"': ps->quote = 0; goto copy; case '\\': ps->esc = 1; goto copy; case '\r': case '\n': goto eol; default: goto copy; } } switch (*s) { case '(': ps->comment++; break; case ')': if (ps->comment) ps->comment--; else goto err; goto skip; case '"': ps->quote = 1; goto copy; case '\\': ps->esc = 1; goto copy; case '\r': case '\n': goto eol; } if (ps->comment) goto skip; switch (*s) { case ' ': case '\t': /* ignore whitespace */ goto skip; case '<': /* this is the real address now */ ps->brackets = 1; ps->pos = 0; goto skip; case '>': if (!ps->brackets) goto err; ps->brackets = 0; s++; goto newaddr; case ':': /* group - ignore */ ps->pos = 0; goto skip; case ',': case ';': /* * Next address, copy previous one. * However, we might be directly after * a
, or have two consecutive * commas. * Skip the comma unless there is * really something to copy. */ if (ps->pos == 0) goto skip; s++; goto newaddr; default: goto copy; } copy: if (ps->comment) goto skip; if (ps->pos + 1 == sizeof(ps->addr)) goto err; ps->addr[ps->pos++] = *s; skip: ; } eol: ps->state = EOL; return (0); err: ps->state = QUIT; return (-1); newaddr: ps->addr[ps->pos] = 0; ps->pos = 0; addr = strdup(ps->addr); if (addr == NULL) - errlog(1, NULL); + errlog(1, "strdup failed"); if (add_recp(queue, addr, EXPAND_WILDCARD) != 0) errlogx(1, "invalid recipient `%s'", addr); goto again; } int readmail(struct queue *queue, int nodot, int recp_from_header) { struct parse_state parse_state; char line[1000]; /* by RFC2822 */ size_t linelen; size_t error; int had_headers = 0; int had_from = 0; int had_messagid = 0; int had_date = 0; int had_last_line = 0; int nocopy = 0; parse_state.state = NONE; error = fprintf(queue->mailf, "Received: from %s (uid %d)\n" "\t(envelope-from %s)\n" "\tid %s\n" "\tby %s (%s);\n" "\t%s\n", username, useruid, queue->sender, queue->id, hostname(), VERSION, rfc822date()); if ((ssize_t)error < 0) return (-1); while (!feof(stdin)) { if (fgets(line, sizeof(line) - 1, stdin) == NULL) break; if (had_last_line) errlogx(1, "bad mail input format"); linelen = strlen(line); if (linelen == 0 || line[linelen - 1] != '\n') { /* * This line did not end with a newline character. * If we fix it, it better be the last line of * the file. */ line[linelen] = '\n'; line[linelen + 1] = 0; had_last_line = 1; } if (!had_headers) { /* * Unless this is a continuation, switch of * the Bcc: nocopy flag. */ if (!(line[0] == ' ' || line[0] == '\t')) nocopy = 0; if (strprefixcmp(line, "Date:") == 0) had_date = 1; else if (strprefixcmp(line, "Message-Id:") == 0) had_messagid = 1; else if (strprefixcmp(line, "From:") == 0) had_from = 1; else if (strprefixcmp(line, "Bcc:") == 0) nocopy = 1; if (parse_state.state != NONE) { if (parse_addrs(&parse_state, line, queue) < 0) { errlogx(1, "invalid address in header\n"); /* NOTREACHED */ } } if (recp_from_header && ( strprefixcmp(line, "To:") == 0 || strprefixcmp(line, "Cc:") == 0 || strprefixcmp(line, "Bcc:") == 0)) { parse_state.state = START; if (parse_addrs(&parse_state, line, queue) < 0) { errlogx(1, "invalid address in header\n"); /* NOTREACHED */ } } } if (strcmp(line, "\n") == 0 && !had_headers) { had_headers = 1; while (!had_date || !had_messagid || !had_from) { if (!had_date) { had_date = 1; snprintf(line, sizeof(line), "Date: %s\n", rfc822date()); } else if (!had_messagid) { /* XXX msgid, assign earlier and log? */ had_messagid = 1; snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n", (uintmax_t)time(NULL), queue->id, (uintmax_t)random(), hostname()); } else if (!had_from) { had_from = 1; snprintf(line, sizeof(line), "From: <%s>\n", queue->sender); } if (fwrite(line, strlen(line), 1, queue->mailf) != 1) return (-1); } strcpy(line, "\n"); } if (!nodot && linelen == 2 && line[0] == '.') break; if (!nocopy) { if (fwrite(line, strlen(line), 1, queue->mailf) != 1) return (-1); } } return (0); }