aboutsummaryrefslogtreecommitdiffstats
path: root/usr.sbin/smtpd/mda_variables.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/smtpd/mda_variables.c')
-rw-r--r--usr.sbin/smtpd/mda_variables.c374
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;
+}