diff options
Diffstat (limited to 'usr.sbin/smtpd/mda_variables.c')
-rw-r--r-- | usr.sbin/smtpd/mda_variables.c | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/mda_variables.c b/usr.sbin/smtpd/mda_variables.c new file mode 100644 index 00000000..b672e492 --- /dev/null +++ b/usr.sbin/smtpd/mda_variables.c @@ -0,0 +1,374 @@ +/* $OpenBSD: mda_variables.c,v 1.6 2019/09/19 07:35:36 gilles Exp $ */ + +/* + * Copyright (c) 2011-2017 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.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 <netinet/in.h> + +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define EXPAND_DEPTH 10 + +ssize_t mda_expand_format(char *, size_t, const struct deliver *, + const struct userinfo *, const char *); +static ssize_t mda_expand_token(char *, size_t, const char *, + const struct deliver *, const struct userinfo *, const char *); +static int mod_lowercase(char *, size_t); +static int mod_uppercase(char *, size_t); +static int mod_strip(char *, size_t); + +static struct modifiers { + char *name; + int (*f)(char *buf, size_t len); +} token_modifiers[] = { + { "lowercase", mod_lowercase }, + { "uppercase", mod_uppercase }, + { "strip", mod_strip }, + { "raw", NULL }, /* special case, must stay last */ +}; + +#define MAXTOKENLEN 128 + +static ssize_t +mda_expand_token(char *dest, size_t len, const char *token, + const struct deliver *dlv, const struct userinfo *ui, const char *mda_command) +{ + char rtoken[MAXTOKENLEN]; + char tmp[EXPAND_BUFFER]; + const char *string; + char *lbracket, *rbracket, *content, *sep, *mods; + ssize_t i; + ssize_t begoff, endoff; + const char *errstr = NULL; + int replace = 1; + int raw = 0; + + begoff = 0; + endoff = EXPAND_BUFFER; + mods = NULL; + + if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken) + return -1; + + /* token[x[:y]] -> extracts optional x and y, converts into offsets */ + if ((lbracket = strchr(rtoken, '[')) && + (rbracket = strchr(rtoken, ']'))) { + /* ] before [ ... or empty */ + if (rbracket < lbracket || rbracket - lbracket <= 1) + return -1; + + *lbracket = *rbracket = '\0'; + content = lbracket + 1; + + if ((sep = strchr(content, ':')) == NULL) + endoff = begoff = strtonum(content, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + else { + *sep = '\0'; + if (content != sep) + begoff = strtonum(content, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + if (*(++sep)) { + if (errstr == NULL) + endoff = strtonum(sep, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + } + } + if (errstr) + return -1; + + /* token:mod_1,mod_2,mod_n -> extract modifiers */ + mods = strchr(rbracket + 1, ':'); + } else { + if ((mods = strchr(rtoken, ':')) != NULL) + *mods++ = '\0'; + } + + /* token -> expanded token */ + if (!strcasecmp("sender", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("rcpt", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("dest", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("sender.user", rtoken)) + string = dlv->sender.user; + else if (!strcasecmp("sender.domain", rtoken)) + string = dlv->sender.domain; + else if (!strcasecmp("user.username", rtoken)) + string = ui->username; + else if (!strcasecmp("user.directory", rtoken)) { + string = ui->directory; + replace = 0; + } + else if (!strcasecmp("rcpt.user", rtoken)) + string = dlv->rcpt.user; + else if (!strcasecmp("rcpt.domain", rtoken)) + string = dlv->rcpt.domain; + else if (!strcasecmp("dest.user", rtoken)) + string = dlv->dest.user; + else if (!strcasecmp("dest.domain", rtoken)) + string = dlv->dest.domain; + else if (!strcasecmp("mda", rtoken)) { + string = mda_command; + replace = 0; + } + else if (!strcasecmp("mbox.from", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp); + string = tmp; + } + else + return -1; + + if (string != tmp) { + if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) + return -1; + string = tmp; + } + + /* apply modifiers */ + if (mods != NULL) { + do { + if ((sep = strchr(mods, '|')) != NULL) + *sep++ = '\0'; + for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { + if (!strcasecmp(token_modifiers[i].name, mods)) { + if (token_modifiers[i].f == NULL) { + raw = 1; + break; + } + if (!token_modifiers[i].f(tmp, sizeof tmp)) + return -1; /* modifier error */ + break; + } + } + if ((size_t)i == nitems(token_modifiers)) + return -1; /* modifier not found */ + } while ((mods = sep) != NULL); + } + + if (!raw && replace) + for (i = 0; (size_t)i < strlen(tmp); ++i) + if (strchr(MAILADDR_ESCAPE, tmp[i])) + tmp[i] = ':'; + + /* expanded string is empty */ + i = strlen(string); + if (i == 0) + return 0; + + /* begin offset beyond end of string */ + if (begoff >= i) + return -1; + + /* end offset beyond end of string, make it end of string */ + if (endoff >= i) + endoff = i - 1; + + /* negative begin offset, make it relative to end of string */ + if (begoff < 0) + begoff += i; + /* negative end offset, make it relative to end of string, + * note that end offset is inclusive. + */ + if (endoff < 0) + endoff += i - 1; + + /* check that final offsets are valid */ + if (begoff < 0 || endoff < 0 || endoff < begoff) + return -1; + endoff += 1; /* end offset is inclusive */ + + /* check that substring does not exceed destination buffer length */ + i = endoff - begoff; + if ((size_t)i + 1 >= len) + return -1; + + string += begoff; + for (; i; i--) { + *dest = *string; + dest++; + string++; + } + + return endoff - begoff; +} + + +ssize_t +mda_expand_format(char *buf, size_t len, const struct deliver *dlv, + const struct userinfo *ui, const char *mda_command) +{ + char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; + char exptok[EXPAND_BUFFER]; + ssize_t exptoklen; + char token[MAXTOKENLEN]; + size_t ret, tmpret; + + if (len < sizeof tmpbuf) { + log_warnx("mda_expand_format: tmp buffer < rule buffer"); + return -1; + } + + memset(tmpbuf, 0, sizeof tmpbuf); + pbuf = buf; + ptmp = tmpbuf; + ret = tmpret = 0; + + /* special case: ~/ only allowed expanded at the beginning */ + if (strncmp(pbuf, "~/", 2) == 0) { + tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); + if (tmpret >= sizeof tmpbuf) { + log_warnx("warn: user directory for %s too large", + ui->directory); + return 0; + } + ret += tmpret; + ptmp += tmpret; + pbuf += 2; + } + + + /* expansion loop */ + for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { + if (*pbuf == '%' && *(pbuf + 1) == '%') { + *ptmp++ = *pbuf++; + pbuf += 1; + tmpret = 1; + continue; + } + + if (*pbuf != '%' || *(pbuf + 1) != '{') { + *ptmp++ = *pbuf++; + tmpret = 1; + continue; + } + + /* %{...} otherwise fail */ + if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL) + return 0; + + /* extract token from %{token} */ + if ((size_t)(ebuf - pbuf) - 1 >= sizeof token) + return 0; + + memcpy(token, pbuf+2, ebuf-pbuf-1); + if (strchr(token, '}') == NULL) + return 0; + *strchr(token, '}') = '\0'; + + exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv, + ui, mda_command); + if (exptoklen == -1) + return -1; + + /* writing expanded token at ptmp will overflow tmpbuf */ + if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen) + return -1; + + memcpy(ptmp, exptok, exptoklen); + pbuf = ebuf + 1; + ptmp += exptoklen; + tmpret = exptoklen; + } + if (ret >= sizeof tmpbuf) + return -1; + + if ((ret = strlcpy(buf, tmpbuf, len)) >= len) + return -1; + + return ret; +} + +static int +mod_lowercase(char *buf, size_t len) +{ + char tmp[EXPAND_BUFFER]; + + if (!lowercase(tmp, buf, sizeof tmp)) + return 0; + if (strlcpy(buf, tmp, len) >= len) + return 0; + return 1; +} + +static int +mod_uppercase(char *buf, size_t len) +{ + char tmp[EXPAND_BUFFER]; + + if (!uppercase(tmp, buf, sizeof tmp)) + return 0; + if (strlcpy(buf, tmp, len) >= len) + return 0; + return 1; +} + +static int +mod_strip(char *buf, size_t len) +{ + char *tag, *at; + unsigned int i; + + /* gilles+hackers -> gilles */ + if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) { + /* gilles+hackers@poolp.org -> gilles@poolp.org */ + if ((at = strchr(tag, '@')) != NULL) { + for (i = 0; i <= strlen(at); ++i) + tag[i] = at[i]; + } else + *tag = '\0'; + } + return 1; +} |