aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGilles Chehade <gilles@poolp.org>2017-05-26 23:41:31 +0200
committerGilles Chehade <gilles@poolp.org>2017-05-26 23:42:42 +0200
commitd3677a516b6fc41493539e34f4881bdd334d272f (patch)
treef24e4b9c9192ff9dfaad823c6f8f6537bb2012ae
parentsync with OpenBSD: (diff)
downloadOpenSMTPD-d3677a516b6fc41493539e34f4881bdd334d272f.tar.xz
OpenSMTPD-d3677a516b6fc41493539e34f4881bdd334d272f.zip
move variables expansion outside of lka_session into its own file
as a first mechanical step towards moving expansion to the mda process
-rw-r--r--smtpd/lka_session.c320
-rw-r--r--smtpd/mda_variables.c354
-rw-r--r--smtpd/smtpd.h7
-rw-r--r--smtpd/smtpd/CVS/Entries2
-rw-r--r--smtpd/smtpd/Makefile3
5 files changed, 365 insertions, 321 deletions
diff --git a/smtpd/lka_session.c b/smtpd/lka_session.c
index e70f359d..d12b3c56 100644
--- a/smtpd/lka_session.c
+++ b/smtpd/lka_session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: lka_session.c,v 1.80 2016/08/31 10:18:08 gilles Exp $ */
+/* $OpenBSD: lka_session.c,v 1.81 2017/05/26 21:30:00 gilles Exp $ */
/*
* Copyright (c) 2011 Gilles Chehade <gilles@poolp.org>
@@ -66,28 +66,10 @@ static void lka_expand(struct lka_session *, struct rule *,
static void lka_submit(struct lka_session *, struct rule *,
struct expandnode *);
static void lka_resume(struct lka_session *);
-static size_t lka_expand_format(char *, size_t, const struct envelope *,
- const struct userinfo *);
-
-static int mod_lowercase(char *, size_t);
-static int mod_uppercase(char *, size_t);
-static int mod_strip(char *, size_t);
-
-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 */
-};
static int init;
static struct tree sessions;
-#define MAXTOKENLEN 128
-
void
lka_session(uint64_t id, struct envelope *envelope)
{
@@ -570,7 +552,7 @@ lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
else
fatalx("lka_deliver: bad node type");
- r = lka_expand_format(ep->agent.mda.buffer,
+ r = mda_expand_format(ep->agent.mda.buffer,
sizeof(ep->agent.mda.buffer), ep, &lk.userinfo);
if (!r) {
lks->error = LKA_TEMPFAIL;
@@ -586,301 +568,3 @@ lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry);
}
-
-
-static size_t
-lka_expand_token(char *dest, size_t len, const char *token,
- const struct envelope *ep, const struct userinfo *ui)
-{
- 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 0;
-
- /* 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 0;
-
- *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 0;
-
- /* 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",
- ep->sender.user, ep->sender.domain) >= (int)sizeof tmp)
- return 0;
- string = tmp;
- }
- else if (!strcasecmp("dest", rtoken)) {
- if (snprintf(tmp, sizeof tmp, "%s@%s",
- ep->dest.user, ep->dest.domain) >= (int)sizeof tmp)
- return 0;
- string = tmp;
- }
- else if (!strcasecmp("rcpt", rtoken)) {
- if (snprintf(tmp, sizeof tmp, "%s@%s",
- ep->rcpt.user, ep->rcpt.domain) >= (int)sizeof tmp)
- return 0;
- string = tmp;
- }
- else if (!strcasecmp("sender.user", rtoken))
- string = ep->sender.user;
- else if (!strcasecmp("sender.domain", rtoken))
- string = ep->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("dest.user", rtoken))
- string = ep->dest.user;
- else if (!strcasecmp("dest.domain", rtoken))
- string = ep->dest.domain;
- else if (!strcasecmp("rcpt.user", rtoken))
- string = ep->rcpt.user;
- else if (!strcasecmp("rcpt.domain", rtoken))
- string = ep->rcpt.domain;
- else
- return 0;
-
- if (string != tmp) {
- if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
- return 0;
- 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 0; /* modifier error */
- break;
- }
- }
- if ((size_t)i == nitems(token_modifiers))
- return 0; /* 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 0;
-
- /* 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 0;
- 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 0;
-
- string += begoff;
- for (; i; i--) {
- *dest = (replace && *string == '/') ? ':' : *string;
- dest++;
- string++;
- }
-
- return endoff - begoff;
-}
-
-
-static size_t
-lka_expand_format(char *buf, size_t len, const struct envelope *ep,
- const struct userinfo *ui)
-{
- char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
- char exptok[EXPAND_BUFFER];
- size_t exptoklen;
- char token[MAXTOKENLEN];
- size_t ret, tmpret;
-
- if (len < sizeof tmpbuf) {
- log_warnx("lka_expand_format: tmp buffer < rule buffer");
- return 0;
- }
-
- 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 = lka_expand_token(exptok, sizeof exptok, token, ep,
- ui);
- if (exptoklen == 0)
- return 0;
-
- /* writing expanded token at ptmp will overflow tmpbuf */
- if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= exptoklen)
- return 0;
-
- memcpy(ptmp, exptok, exptoklen);
- pbuf = ebuf + 1;
- ptmp += exptoklen;
- tmpret = exptoklen;
- }
- if (ret >= sizeof tmpbuf)
- return 0;
-
- if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
- return 0;
-
- 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;
-}
diff --git a/smtpd/mda_variables.c b/smtpd/mda_variables.c
new file mode 100644
index 00000000..aa30e047
--- /dev/null
+++ b/smtpd/mda_variables.c
@@ -0,0 +1,354 @@
+/* $OpenBSD: mda_variables.c,v 1.1 2017/05/26 21:30:00 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 <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
+
+size_t mda_expand_format(char *, size_t, const struct envelope *,
+ const struct userinfo *);
+static size_t mda_expand_token(char *, size_t, const char *,
+ const struct envelope *, const struct userinfo *);
+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 size_t
+mda_expand_token(char *dest, size_t len, const char *token,
+ const struct envelope *ep, const struct userinfo *ui)
+{
+ 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 0;
+
+ /* 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 0;
+
+ *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 0;
+
+ /* 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",
+ ep->sender.user, ep->sender.domain) >= (int)sizeof tmp)
+ return 0;
+ string = tmp;
+ }
+ else if (!strcasecmp("dest", rtoken)) {
+ if (snprintf(tmp, sizeof tmp, "%s@%s",
+ ep->dest.user, ep->dest.domain) >= (int)sizeof tmp)
+ return 0;
+ string = tmp;
+ }
+ else if (!strcasecmp("rcpt", rtoken)) {
+ if (snprintf(tmp, sizeof tmp, "%s@%s",
+ ep->rcpt.user, ep->rcpt.domain) >= (int)sizeof tmp)
+ return 0;
+ string = tmp;
+ }
+ else if (!strcasecmp("sender.user", rtoken))
+ string = ep->sender.user;
+ else if (!strcasecmp("sender.domain", rtoken))
+ string = ep->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("dest.user", rtoken))
+ string = ep->dest.user;
+ else if (!strcasecmp("dest.domain", rtoken))
+ string = ep->dest.domain;
+ else if (!strcasecmp("rcpt.user", rtoken))
+ string = ep->rcpt.user;
+ else if (!strcasecmp("rcpt.domain", rtoken))
+ string = ep->rcpt.domain;
+ else
+ return 0;
+
+ if (string != tmp) {
+ if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
+ return 0;
+ 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 0; /* modifier error */
+ break;
+ }
+ }
+ if ((size_t)i == nitems(token_modifiers))
+ return 0; /* 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 0;
+
+ /* 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 0;
+ 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 0;
+
+ string += begoff;
+ for (; i; i--) {
+ *dest = (replace && *string == '/') ? ':' : *string;
+ dest++;
+ string++;
+ }
+
+ return endoff - begoff;
+}
+
+
+size_t
+mda_expand_format(char *buf, size_t len, const struct envelope *ep,
+ const struct userinfo *ui)
+{
+ char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
+ char exptok[EXPAND_BUFFER];
+ size_t exptoklen;
+ char token[MAXTOKENLEN];
+ size_t ret, tmpret;
+
+ if (len < sizeof tmpbuf) {
+ log_warnx("mda_expand_format: tmp buffer < rule buffer");
+ return 0;
+ }
+
+ 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, ep,
+ ui);
+ if (exptoklen == 0)
+ return 0;
+
+ /* writing expanded token at ptmp will overflow tmpbuf */
+ if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= exptoklen)
+ return 0;
+
+ memcpy(ptmp, exptok, exptoklen);
+ pbuf = ebuf + 1;
+ ptmp += exptoklen;
+ tmpret = exptoklen;
+ }
+ if (ret >= sizeof tmpbuf)
+ return 0;
+
+ if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
+ return 0;
+
+ 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;
+}
diff --git a/smtpd/smtpd.h b/smtpd/smtpd.h
index a4895156..f158efa7 100644
--- a/smtpd/smtpd.h
+++ b/smtpd/smtpd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: smtpd.h,v 1.531 2017/05/22 13:43:15 gilles Exp $ */
+/* $OpenBSD: smtpd.h,v 1.532 2017/05/26 21:30:00 gilles Exp $ */
/*
* Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
@@ -1248,6 +1248,11 @@ void mda_postprivdrop(void);
void mda_imsg(struct mproc *, struct imsg *);
+/* mda_variables.c */
+size_t mda_expand_format(char *, size_t, const struct envelope *,
+ const struct userinfo *);
+
+
/* makemap.c */
int makemap(int, char **);
diff --git a/smtpd/smtpd/CVS/Entries b/smtpd/smtpd/CVS/Entries
index f7b0bba1..669ea77a 100644
--- a/smtpd/smtpd/CVS/Entries
+++ b/smtpd/smtpd/CVS/Entries
@@ -1,2 +1,2 @@
-/Makefile/1.86/Thu May 25 20:28:40 2017//
+/Makefile/1.87/Fri May 26 21:41:15 2017//
D
diff --git a/smtpd/smtpd/Makefile b/smtpd/smtpd/Makefile
index 314fe2a5..a1d1f1d7 100644
--- a/smtpd/smtpd/Makefile
+++ b/smtpd/smtpd/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.86 2017/05/22 13:43:15 gilles Exp $
+# $OpenBSD: Makefile,v 1.87 2017/05/26 21:30:00 gilles Exp $
.PATH: ${.CURDIR}/..
@@ -27,6 +27,7 @@ SRCS+= lka_session.c
SRCS+= log.c
SRCS+= mailaddr.c
SRCS+= mda.c
+SRCS+= mda_variables.c
SRCS+= mproc.c
SRCS+= mta.c
SRCS+= mta_session.c