diff options
Diffstat (limited to 'foobar/portable/smtpd/envelope.c')
-rw-r--r-- | foobar/portable/smtpd/envelope.c | 786 |
1 files changed, 786 insertions, 0 deletions
diff --git a/foobar/portable/smtpd/envelope.c b/foobar/portable/smtpd/envelope.c new file mode 100644 index 00000000..35d98b79 --- /dev/null +++ b/foobar/portable/smtpd/envelope.c @@ -0,0 +1,786 @@ +/* $OpenBSD: envelope.c,v 1.47 2019/11/25 14:18:32 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2011-2013 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +static int envelope_ascii_load(struct envelope *, struct dict *); +static void envelope_ascii_dump(const struct envelope *, char **, size_t *, + const char *); + +void +envelope_set_errormsg(struct envelope *e, char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = vsnprintf(e->errorline, sizeof(e->errorline), fmt, ap); + va_end(ap); + + /* this should not happen */ + if (ret < 0) + err(1, "vsnprintf"); + + if ((size_t)ret >= sizeof(e->errorline)) + (void)strlcpy(e->errorline + (sizeof(e->errorline) - 4), + "...", 4); +} + +void +envelope_set_esc_class(struct envelope *e, enum enhanced_status_class class) +{ + e->esc_class = class; +} + +void +envelope_set_esc_code(struct envelope *e, enum enhanced_status_code code) +{ + e->esc_code = code; +} + +static int +envelope_buffer_to_dict(struct dict *d, const char *ibuf, size_t buflen) +{ + static char lbuf[sizeof(struct envelope)]; + size_t len; + char *buf, *field, *nextline; + + memset(lbuf, 0, sizeof lbuf); + if (strlcpy(lbuf, ibuf, sizeof lbuf) >= sizeof lbuf) + goto err; + buf = lbuf; + + while (buflen > 0) { + len = strcspn(buf, "\n"); + buf[len] = '\0'; + nextline = buf + len + 1; + buflen -= (nextline - buf); + + field = buf; + while (*buf && (isalnum((unsigned char)*buf) || *buf == '-')) + buf++; + if (!*buf) + goto err; + + /* skip whitespaces before separator */ + while (*buf && isspace((unsigned char)*buf)) + *buf++ = 0; + + /* we *want* ':' */ + if (*buf != ':') + goto err; + *buf++ = 0; + + /* skip whitespaces after separator */ + while (*buf && isspace((unsigned char)*buf)) + *buf++ = 0; + dict_set(d, field, buf); + buf = nextline; + } + + return (1); + +err: + return (0); +} + +int +envelope_load_buffer(struct envelope *ep, const char *ibuf, size_t buflen) +{ + struct dict d; + const char *val, *errstr; + long long version; + int ret = 0; + + dict_init(&d); + if (!envelope_buffer_to_dict(&d, ibuf, buflen)) { + log_debug("debug: cannot parse envelope to dict"); + goto end; + } + + val = dict_get(&d, "version"); + if (val == NULL) { + log_debug("debug: envelope version not found"); + goto end; + } + version = strtonum(val, 1, 64, &errstr); + if (errstr) { + log_debug("debug: cannot parse envelope version: %s", val); + goto end; + } + + if (version != SMTPD_ENVELOPE_VERSION) { + log_debug("debug: bad envelope version %lld", version); + goto end; + } + + memset(ep, 0, sizeof *ep); + ret = envelope_ascii_load(ep, &d); + if (ret) + ep->version = SMTPD_ENVELOPE_VERSION; +end: + while (dict_poproot(&d, NULL)) + ; + return (ret); +} + +int +envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) +{ + char *p = dest; + + envelope_ascii_dump(ep, &dest, &len, "version"); + envelope_ascii_dump(ep, &dest, &len, "dispatcher"); + envelope_ascii_dump(ep, &dest, &len, "tag"); + envelope_ascii_dump(ep, &dest, &len, "type"); + envelope_ascii_dump(ep, &dest, &len, "smtpname"); + envelope_ascii_dump(ep, &dest, &len, "helo"); + envelope_ascii_dump(ep, &dest, &len, "hostname"); + envelope_ascii_dump(ep, &dest, &len, "username"); + envelope_ascii_dump(ep, &dest, &len, "errorline"); + envelope_ascii_dump(ep, &dest, &len, "sockaddr"); + envelope_ascii_dump(ep, &dest, &len, "sender"); + envelope_ascii_dump(ep, &dest, &len, "rcpt"); + envelope_ascii_dump(ep, &dest, &len, "dest"); + envelope_ascii_dump(ep, &dest, &len, "ctime"); + envelope_ascii_dump(ep, &dest, &len, "last-try"); + envelope_ascii_dump(ep, &dest, &len, "last-bounce"); + envelope_ascii_dump(ep, &dest, &len, "ttl"); + envelope_ascii_dump(ep, &dest, &len, "retry"); + envelope_ascii_dump(ep, &dest, &len, "flags"); + envelope_ascii_dump(ep, &dest, &len, "dsn-notify"); + envelope_ascii_dump(ep, &dest, &len, "dsn-ret"); + envelope_ascii_dump(ep, &dest, &len, "dsn-envid"); + envelope_ascii_dump(ep, &dest, &len, "dsn-orcpt"); + envelope_ascii_dump(ep, &dest, &len, "esc-class"); + envelope_ascii_dump(ep, &dest, &len, "esc-code"); + + switch (ep->type) { + case D_MDA: + envelope_ascii_dump(ep, &dest, &len, "mda-exec"); + envelope_ascii_dump(ep, &dest, &len, "mda-subaddress"); + envelope_ascii_dump(ep, &dest, &len, "mda-user"); + break; + case D_MTA: + break; + case D_BOUNCE: + envelope_ascii_dump(ep, &dest, &len, "bounce-ttl"); + envelope_ascii_dump(ep, &dest, &len, "bounce-delay"); + envelope_ascii_dump(ep, &dest, &len, "bounce-type"); + break; + default: + return (0); + } + + if (dest == NULL) + return (0); + + return (dest - p); +} + +static int +ascii_load_uint8(uint8_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_uint16(uint16_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xffff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_uint32(uint32_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xffffffff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_time(time_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, LLONG_MAX, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_type(enum delivery_type *dest, char *buf) +{ + if (strcasecmp(buf, "mda") == 0) + *dest = D_MDA; + else if (strcasecmp(buf, "mta") == 0) + *dest = D_MTA; + else if (strcasecmp(buf, "bounce") == 0) + *dest = D_BOUNCE; + else + return 0; + return 1; +} + +static int +ascii_load_string(char *dest, char *buf, size_t len) +{ + if (strlcpy(dest, buf, len) >= len) + return 0; + return 1; +} + +static int +ascii_load_sockaddr(struct sockaddr_storage *ss, char *buf) +{ + struct sockaddr_in6 ssin6; + struct sockaddr_in ssin; + + memset(&ssin, 0, sizeof ssin); + memset(&ssin6, 0, sizeof ssin6); + + if (!strcmp("local", buf)) { + ss->ss_family = AF_LOCAL; + } + else if (strncasecmp("IPv6:", buf, 5) == 0) { + /* XXX - remove this after 6.6 release */ + if (inet_pton(AF_INET6, buf + 5, &ssin6.sin6_addr) != 1) + return 0; + ssin6.sin6_family = AF_INET6; + memcpy(ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in6); +#endif + } + else if (buf[0] == '[' && buf[strlen(buf)-1] == ']') { + buf[strlen(buf)-1] = '\0'; + if (inet_pton(AF_INET6, buf+1, &ssin6.sin6_addr) != 1) + return 0; + ssin6.sin6_family = AF_INET6; + memcpy(ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in6); +#endif + } + else { + if (inet_pton(AF_INET, buf, &ssin.sin_addr) != 1) + return 0; + ssin.sin_family = AF_INET; + memcpy(ss, &ssin, sizeof(ssin)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in); +#endif + } + return 1; +} + +static int +ascii_load_mailaddr(struct mailaddr *dest, char *buf) +{ + if (!text_to_mailaddr(dest, buf)) + return 0; + return 1; +} + +static int +ascii_load_flags(enum envelope_flags *dest, char *buf) +{ + char *flag; + + while ((flag = strsep(&buf, " ,|")) != NULL) { + if (strcasecmp(flag, "authenticated") == 0) + *dest |= EF_AUTHENTICATED; + else if (strcasecmp(flag, "enqueued") == 0) + ; + else if (strcasecmp(flag, "bounce") == 0) + *dest |= EF_BOUNCE; + else if (strcasecmp(flag, "internal") == 0) + *dest |= EF_INTERNAL; + else + return 0; + } + return 1; +} + +static int +ascii_load_bounce_type(enum bounce_type *dest, char *buf) +{ + if (strcasecmp(buf, "error") == 0 || strcasecmp(buf, "failed") == 0) + *dest = B_FAILED; + else if (strcasecmp(buf, "warn") == 0 || + strcasecmp(buf, "delayed") == 0) + *dest = B_DELAYED; + else if (strcasecmp(buf, "dsn") == 0 || + strcasecmp(buf, "delivered") == 0) + *dest = B_DELIVERED; + else + return 0; + return 1; +} + +static int +ascii_load_dsn_ret(enum dsn_ret *ret, char *buf) +{ + if (strcasecmp(buf, "HDRS") == 0) + *ret = DSN_RETHDRS; + else if (strcasecmp(buf, "FULL") == 0) + *ret = DSN_RETFULL; + else + return 0; + return 1; +} + +static int +ascii_load_field(const char *field, struct envelope *ep, char *buf) +{ + if (strcasecmp("dispatcher", field) == 0) + return ascii_load_string(ep->dispatcher, buf, + sizeof ep->dispatcher); + + if (strcasecmp("bounce-delay", field) == 0) + return ascii_load_time(&ep->agent.bounce.delay, buf); + + if (strcasecmp("bounce-ttl", field) == 0) + return ascii_load_time(&ep->agent.bounce.ttl, buf); + + if (strcasecmp("bounce-type", field) == 0) + return ascii_load_bounce_type(&ep->agent.bounce.type, buf); + + if (strcasecmp("ctime", field) == 0) + return ascii_load_time(&ep->creation, buf); + + if (strcasecmp("dest", field) == 0) + return ascii_load_mailaddr(&ep->dest, buf); + + if (strcasecmp("username", field) == 0) + return ascii_load_string(ep->username, buf, sizeof(ep->username)); + + if (strcasecmp("errorline", field) == 0) + return ascii_load_string(ep->errorline, buf, + sizeof ep->errorline); + + if (strcasecmp("ttl", field) == 0) + return ascii_load_time(&ep->ttl, buf); + + if (strcasecmp("flags", field) == 0) + return ascii_load_flags(&ep->flags, buf); + + if (strcasecmp("helo", field) == 0) + return ascii_load_string(ep->helo, buf, sizeof ep->helo); + + if (strcasecmp("hostname", field) == 0) + return ascii_load_string(ep->hostname, buf, + sizeof ep->hostname); + + if (strcasecmp("last-bounce", field) == 0) + return ascii_load_time(&ep->lastbounce, buf); + + if (strcasecmp("last-try", field) == 0) + return ascii_load_time(&ep->lasttry, buf); + + if (strcasecmp("retry", field) == 0) + return ascii_load_uint16(&ep->retry, buf); + + if (strcasecmp("rcpt", field) == 0) + return ascii_load_mailaddr(&ep->rcpt, buf); + + if (strcasecmp("mda-exec", field) == 0) + return ascii_load_string(ep->mda_exec, buf, sizeof(ep->mda_exec)); + + if (strcasecmp("mda-subaddress", field) == 0) + return ascii_load_string(ep->mda_subaddress, buf, sizeof(ep->mda_subaddress)); + + if (strcasecmp("mda-user", field) == 0) + return ascii_load_string(ep->mda_user, buf, sizeof(ep->mda_user)); + + if (strcasecmp("sender", field) == 0) + return ascii_load_mailaddr(&ep->sender, buf); + + if (strcasecmp("smtpname", field) == 0) + return ascii_load_string(ep->smtpname, buf, + sizeof(ep->smtpname)); + + if (strcasecmp("sockaddr", field) == 0) + return ascii_load_sockaddr(&ep->ss, buf); + + if (strcasecmp("tag", field) == 0) + return ascii_load_string(ep->tag, buf, sizeof ep->tag); + + if (strcasecmp("type", field) == 0) + return ascii_load_type(&ep->type, buf); + + if (strcasecmp("version", field) == 0) + return ascii_load_uint32(&ep->version, buf); + + if (strcasecmp("dsn-notify", field) == 0) + return ascii_load_uint8(&ep->dsn_notify, buf); + + if (strcasecmp("dsn-orcpt", field) == 0) + return ascii_load_mailaddr(&ep->dsn_orcpt, buf); + + if (strcasecmp("dsn-ret", field) == 0) + return ascii_load_dsn_ret(&ep->dsn_ret, buf); + + if (strcasecmp("dsn-envid", field) == 0) + return ascii_load_string(ep->dsn_envid, buf, + sizeof(ep->dsn_envid)); + + if (strcasecmp("esc-class", field) == 0) + return ascii_load_uint8(&ep->esc_class, buf); + + if (strcasecmp("esc-code", field) == 0) + return ascii_load_uint8(&ep->esc_code, buf); + + return (0); +} + +static int +envelope_ascii_load(struct envelope *ep, struct dict *d) +{ + const char *field; + char *value; + void *hdl; + + hdl = NULL; + while (dict_iter(d, &hdl, &field, (void **)&value)) + if (!ascii_load_field(field, ep, value)) + goto err; + + return (1); + +err: + log_warnx("envelope: invalid field \"%s\"", field); + return (0); +} + + +static int +ascii_dump_uint8(uint8_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_uint16(uint16_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_uint32(uint32_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_time(time_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%lld", (long long) src); +} + +static int +ascii_dump_string(const char *src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%s", src); +} + +static int +ascii_dump_type(enum delivery_type type, char *dest, size_t len) +{ + char *p = NULL; + + switch (type) { + case D_MDA: + p = "mda"; + break; + case D_MTA: + p = "mta"; + break; + case D_BOUNCE: + p = "bounce"; + break; + default: + return 0; + } + + return bsnprintf(dest, len, "%s", p); +} + +static int +ascii_dump_mailaddr(const struct mailaddr *addr, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%s@%s", + addr->user, addr->domain); +} + +static int +ascii_dump_flags(enum envelope_flags flags, char *buf, size_t len) +{ + size_t cpylen = 0; + + buf[0] = '\0'; + if (flags) { + if (flags & EF_AUTHENTICATED) + cpylen = strlcat(buf, "authenticated", len); + if (flags & EF_BOUNCE) { + if (buf[0] != '\0') + (void)strlcat(buf, " ", len); + cpylen = strlcat(buf, "bounce", len); + } + if (flags & EF_INTERNAL) { + if (buf[0] != '\0') + (void)strlcat(buf, " ", len); + cpylen = strlcat(buf, "internal", len); + } + } + + return cpylen < len ? 1 : 0; +} + +static int +ascii_dump_bounce_type(enum bounce_type type, char *dest, size_t len) +{ + char *p = NULL; + + switch (type) { + case B_FAILED: + p = "failed"; + break; + case B_DELAYED: + p = "delayed"; + break; + case B_DELIVERED: + p = "delivered"; + break; + default: + return 0; + } + return bsnprintf(dest, len, "%s", p); +} + + +static int +ascii_dump_dsn_ret(enum dsn_ret flag, char *dest, size_t len) +{ + size_t cpylen = 0; + + dest[0] = '\0'; + if (flag == DSN_RETFULL) + cpylen = strlcat(dest, "FULL", len); + else if (flag == DSN_RETHDRS) + cpylen = strlcat(dest, "HDRS", len); + + return cpylen < len ? 1 : 0; +} + +static int +ascii_dump_field(const char *field, const struct envelope *ep, + char *buf, size_t len) +{ + if (strcasecmp(field, "dispatcher") == 0) + return ascii_dump_string(ep->dispatcher, buf, len); + + if (strcasecmp(field, "bounce-delay") == 0) { + if (ep->agent.bounce.type != B_DELAYED) + return (1); + return ascii_dump_time(ep->agent.bounce.delay, buf, len); + } + + if (strcasecmp(field, "bounce-ttl") == 0) { + if (ep->agent.bounce.type != B_DELAYED) + return (1); + return ascii_dump_time(ep->agent.bounce.ttl, buf, len); + } + + if (strcasecmp(field, "bounce-type") == 0) + return ascii_dump_bounce_type(ep->agent.bounce.type, buf, len); + + if (strcasecmp(field, "ctime") == 0) + return ascii_dump_time(ep->creation, buf, len); + + if (strcasecmp(field, "dest") == 0) + return ascii_dump_mailaddr(&ep->dest, buf, len); + + if (strcasecmp(field, "username") == 0) { + if (ep->username[0]) + return ascii_dump_string(ep->username, buf, len); + return 1; + } + + if (strcasecmp(field, "errorline") == 0) + return ascii_dump_string(ep->errorline, buf, len); + + if (strcasecmp(field, "ttl") == 0) + return ascii_dump_time(ep->ttl, buf, len); + + if (strcasecmp(field, "flags") == 0) + return ascii_dump_flags(ep->flags, buf, len); + + if (strcasecmp(field, "helo") == 0) + return ascii_dump_string(ep->helo, buf, len); + + if (strcasecmp(field, "hostname") == 0) + return ascii_dump_string(ep->hostname, buf, len); + + if (strcasecmp(field, "last-bounce") == 0) + return ascii_dump_time(ep->lastbounce, buf, len); + + if (strcasecmp(field, "last-try") == 0) + return ascii_dump_time(ep->lasttry, buf, len); + + if (strcasecmp(field, "retry") == 0) + return ascii_dump_uint16(ep->retry, buf, len); + + if (strcasecmp(field, "rcpt") == 0) + return ascii_dump_mailaddr(&ep->rcpt, buf, len); + + if (strcasecmp(field, "mda-exec") == 0) { + if (ep->mda_exec[0]) + return ascii_dump_string(ep->mda_exec, buf, len); + return 1; + } + + if (strcasecmp(field, "mda-subaddress") == 0) { + if (ep->mda_subaddress[0]) + return ascii_dump_string(ep->mda_subaddress, buf, len); + return 1; + } + + if (strcasecmp(field, "mda-user") == 0) { + if (ep->mda_user[0]) + return ascii_dump_string(ep->mda_user, buf, len); + return 1; + } + + if (strcasecmp(field, "sender") == 0) + return ascii_dump_mailaddr(&ep->sender, buf, len); + + if (strcasecmp(field, "smtpname") == 0) + return ascii_dump_string(ep->smtpname, buf, len); + + if (strcasecmp(field, "sockaddr") == 0) + return ascii_dump_string(ss_to_text(&ep->ss), buf, len); + + if (strcasecmp(field, "tag") == 0) + return ascii_dump_string(ep->tag, buf, len); + + if (strcasecmp(field, "type") == 0) + return ascii_dump_type(ep->type, buf, len); + + if (strcasecmp(field, "version") == 0) + return ascii_dump_uint32(SMTPD_ENVELOPE_VERSION, buf, len); + + if (strcasecmp(field, "dsn-notify") == 0) + return ascii_dump_uint8(ep->dsn_notify, buf, len); + + if (strcasecmp(field, "dsn-ret") == 0) + return ascii_dump_dsn_ret(ep->dsn_ret, buf, len); + + if (strcasecmp(field, "dsn-orcpt") == 0) { + if (ep->dsn_orcpt.user[0] && ep->dsn_orcpt.domain[0]) + return ascii_dump_mailaddr(&ep->dsn_orcpt, buf, len); + return 1; + } + + if (strcasecmp(field, "dsn-envid") == 0) + return ascii_dump_string(ep->dsn_envid, buf, len); + + if (strcasecmp(field, "esc-class") == 0) { + if (ep->esc_class) + return ascii_dump_uint8(ep->esc_class, buf, len); + return 1; + } + + if (strcasecmp(field, "esc-code") == 0) { + /* this is not a pasto, we dump esc_code if esc_class is !0 */ + if (ep->esc_class) + return ascii_dump_uint8(ep->esc_code, buf, len); + return 1; + } + + return (0); +} + +static void +envelope_ascii_dump(const struct envelope *ep, char **dest, size_t *len, + const char *field) +{ + char buf[8192]; + int l; + + if (*dest == NULL) + return; + + memset(buf, 0, sizeof buf); + if (!ascii_dump_field(field, ep, buf, sizeof buf)) + goto err; + if (buf[0] == '\0') + return; + + l = snprintf(*dest, *len, "%s: %s\n", field, buf); + if (l < 0 || (size_t) l >= *len) + goto err; + *dest += l; + *len -= l; + + return; +err: + *dest = NULL; +} |