1*e6c7c102Sjsg /* $OpenBSD: mda_variables.c,v 1.10 2024/04/23 13:34:51 jsg Exp $ */
21fa3e601Sgilles
31fa3e601Sgilles /*
41fa3e601Sgilles * Copyright (c) 2011-2017 Gilles Chehade <gilles@poolp.org>
51fa3e601Sgilles * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
61fa3e601Sgilles *
71fa3e601Sgilles * Permission to use, copy, modify, and distribute this software for any
81fa3e601Sgilles * purpose with or without fee is hereby granted, provided that the above
91fa3e601Sgilles * copyright notice and this permission notice appear in all copies.
101fa3e601Sgilles *
111fa3e601Sgilles * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
121fa3e601Sgilles * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
131fa3e601Sgilles * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
141fa3e601Sgilles * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
151fa3e601Sgilles * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
161fa3e601Sgilles * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
171fa3e601Sgilles * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
181fa3e601Sgilles */
191fa3e601Sgilles
201fa3e601Sgilles #include <stdlib.h>
211fa3e601Sgilles #include <string.h>
221fa3e601Sgilles
231fa3e601Sgilles #include "smtpd.h"
241fa3e601Sgilles #include "log.h"
251fa3e601Sgilles
261fa3e601Sgilles #define EXPAND_DEPTH 10
271fa3e601Sgilles
28f68f476fSgilles ssize_t mda_expand_format(char *, size_t, const struct deliver *,
29d0df4e7aSgilles const struct userinfo *, const char *);
30f68f476fSgilles static ssize_t mda_expand_token(char *, size_t, const char *,
31d0df4e7aSgilles const struct deliver *, const struct userinfo *, const char *);
321fa3e601Sgilles static int mod_lowercase(char *, size_t);
331fa3e601Sgilles static int mod_uppercase(char *, size_t);
341fa3e601Sgilles static int mod_strip(char *, size_t);
351fa3e601Sgilles
361fa3e601Sgilles static struct modifiers {
371fa3e601Sgilles char *name;
381fa3e601Sgilles int (*f)(char *buf, size_t len);
391fa3e601Sgilles } token_modifiers[] = {
401fa3e601Sgilles { "lowercase", mod_lowercase },
411fa3e601Sgilles { "uppercase", mod_uppercase },
421fa3e601Sgilles { "strip", mod_strip },
431fa3e601Sgilles { "raw", NULL }, /* special case, must stay last */
441fa3e601Sgilles };
451fa3e601Sgilles
461fa3e601Sgilles #define MAXTOKENLEN 128
471fa3e601Sgilles
48f68f476fSgilles 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)491fa3e601Sgilles mda_expand_token(char *dest, size_t len, const char *token,
50d0df4e7aSgilles const struct deliver *dlv, const struct userinfo *ui, const char *mda_command)
511fa3e601Sgilles {
521fa3e601Sgilles char rtoken[MAXTOKENLEN];
531fa3e601Sgilles char tmp[EXPAND_BUFFER];
541750b248Smillert const char *string = NULL;
551fa3e601Sgilles char *lbracket, *rbracket, *content, *sep, *mods;
561fa3e601Sgilles ssize_t i;
571fa3e601Sgilles ssize_t begoff, endoff;
581fa3e601Sgilles const char *errstr = NULL;
591fa3e601Sgilles int replace = 1;
601fa3e601Sgilles int raw = 0;
611fa3e601Sgilles
621fa3e601Sgilles begoff = 0;
631fa3e601Sgilles endoff = EXPAND_BUFFER;
641fa3e601Sgilles mods = NULL;
651fa3e601Sgilles
661fa3e601Sgilles if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken)
67f68f476fSgilles return -1;
681fa3e601Sgilles
691fa3e601Sgilles /* token[x[:y]] -> extracts optional x and y, converts into offsets */
701fa3e601Sgilles if ((lbracket = strchr(rtoken, '[')) &&
711fa3e601Sgilles (rbracket = strchr(rtoken, ']'))) {
721fa3e601Sgilles /* ] before [ ... or empty */
731fa3e601Sgilles if (rbracket < lbracket || rbracket - lbracket <= 1)
74f68f476fSgilles return -1;
751fa3e601Sgilles
761fa3e601Sgilles *lbracket = *rbracket = '\0';
771fa3e601Sgilles content = lbracket + 1;
781fa3e601Sgilles
791fa3e601Sgilles if ((sep = strchr(content, ':')) == NULL)
801fa3e601Sgilles endoff = begoff = strtonum(content, -EXPAND_BUFFER,
811fa3e601Sgilles EXPAND_BUFFER, &errstr);
821fa3e601Sgilles else {
831fa3e601Sgilles *sep = '\0';
841fa3e601Sgilles if (content != sep)
851fa3e601Sgilles begoff = strtonum(content, -EXPAND_BUFFER,
861fa3e601Sgilles EXPAND_BUFFER, &errstr);
871fa3e601Sgilles if (*(++sep)) {
881fa3e601Sgilles if (errstr == NULL)
891fa3e601Sgilles endoff = strtonum(sep, -EXPAND_BUFFER,
901fa3e601Sgilles EXPAND_BUFFER, &errstr);
911fa3e601Sgilles }
921fa3e601Sgilles }
931fa3e601Sgilles if (errstr)
94f68f476fSgilles return -1;
951fa3e601Sgilles
961fa3e601Sgilles /* token:mod_1,mod_2,mod_n -> extract modifiers */
971fa3e601Sgilles mods = strchr(rbracket + 1, ':');
981fa3e601Sgilles } else {
991fa3e601Sgilles if ((mods = strchr(rtoken, ':')) != NULL)
1001fa3e601Sgilles *mods++ = '\0';
1011fa3e601Sgilles }
1021fa3e601Sgilles
1031fa3e601Sgilles /* token -> expanded token */
1041fa3e601Sgilles if (!strcasecmp("sender", rtoken)) {
1051fa3e601Sgilles if (snprintf(tmp, sizeof tmp, "%s@%s",
106a8e22235Sgilles dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp)
107f68f476fSgilles return -1;
108fceb4d40Sgilles if (strcmp(tmp, "@") == 0)
109fceb4d40Sgilles (void)strlcpy(tmp, "", sizeof tmp);
1101fa3e601Sgilles string = tmp;
1111fa3e601Sgilles }
1121fa3e601Sgilles else if (!strcasecmp("rcpt", rtoken)) {
1131fa3e601Sgilles if (snprintf(tmp, sizeof tmp, "%s@%s",
114a8e22235Sgilles dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp)
115f68f476fSgilles return -1;
116fceb4d40Sgilles if (strcmp(tmp, "@") == 0)
117fceb4d40Sgilles (void)strlcpy(tmp, "", sizeof tmp);
118a8e22235Sgilles string = tmp;
119a8e22235Sgilles }
120a8e22235Sgilles else if (!strcasecmp("dest", rtoken)) {
121a8e22235Sgilles if (snprintf(tmp, sizeof tmp, "%s@%s",
122a8e22235Sgilles dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp)
123f68f476fSgilles return -1;
124fceb4d40Sgilles if (strcmp(tmp, "@") == 0)
125fceb4d40Sgilles (void)strlcpy(tmp, "", sizeof tmp);
1261fa3e601Sgilles string = tmp;
1271fa3e601Sgilles }
1281fa3e601Sgilles else if (!strcasecmp("sender.user", rtoken))
129a8e22235Sgilles string = dlv->sender.user;
1301fa3e601Sgilles else if (!strcasecmp("sender.domain", rtoken))
131a8e22235Sgilles string = dlv->sender.domain;
1321fa3e601Sgilles else if (!strcasecmp("user.username", rtoken))
1331fa3e601Sgilles string = ui->username;
1341fa3e601Sgilles else if (!strcasecmp("user.directory", rtoken)) {
1351fa3e601Sgilles string = ui->directory;
1361fa3e601Sgilles replace = 0;
1371fa3e601Sgilles }
1381fa3e601Sgilles else if (!strcasecmp("rcpt.user", rtoken))
139a8e22235Sgilles string = dlv->rcpt.user;
1401fa3e601Sgilles else if (!strcasecmp("rcpt.domain", rtoken))
141a8e22235Sgilles string = dlv->rcpt.domain;
142a8e22235Sgilles else if (!strcasecmp("dest.user", rtoken))
143a8e22235Sgilles string = dlv->dest.user;
144a8e22235Sgilles else if (!strcasecmp("dest.domain", rtoken))
145a8e22235Sgilles string = dlv->dest.domain;
146d0df4e7aSgilles else if (!strcasecmp("mda", rtoken)) {
147d0df4e7aSgilles string = mda_command;
148d0df4e7aSgilles replace = 0;
149d0df4e7aSgilles }
150fceb4d40Sgilles else if (!strcasecmp("mbox.from", rtoken)) {
151fceb4d40Sgilles if (snprintf(tmp, sizeof tmp, "%s@%s",
152fceb4d40Sgilles dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp)
153f68f476fSgilles return -1;
154fceb4d40Sgilles if (strcmp(tmp, "@") == 0)
155fceb4d40Sgilles (void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp);
156fceb4d40Sgilles string = tmp;
157fceb4d40Sgilles }
1581fa3e601Sgilles else
159f68f476fSgilles return -1;
1601fa3e601Sgilles
1611fa3e601Sgilles if (string != tmp) {
1621750b248Smillert if (string == NULL)
1631750b248Smillert return -1;
1641fa3e601Sgilles if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
165f68f476fSgilles return -1;
1661fa3e601Sgilles string = tmp;
1671fa3e601Sgilles }
1681fa3e601Sgilles
1691fa3e601Sgilles /* apply modifiers */
1701fa3e601Sgilles if (mods != NULL) {
1711fa3e601Sgilles do {
1721fa3e601Sgilles if ((sep = strchr(mods, '|')) != NULL)
1731fa3e601Sgilles *sep++ = '\0';
1741fa3e601Sgilles for (i = 0; (size_t)i < nitems(token_modifiers); ++i) {
1751fa3e601Sgilles if (!strcasecmp(token_modifiers[i].name, mods)) {
1761fa3e601Sgilles if (token_modifiers[i].f == NULL) {
1771fa3e601Sgilles raw = 1;
1781fa3e601Sgilles break;
1791fa3e601Sgilles }
1801fa3e601Sgilles if (!token_modifiers[i].f(tmp, sizeof tmp))
181f68f476fSgilles return -1; /* modifier error */
1821fa3e601Sgilles break;
1831fa3e601Sgilles }
1841fa3e601Sgilles }
1851fa3e601Sgilles if ((size_t)i == nitems(token_modifiers))
186f68f476fSgilles return -1; /* modifier not found */
1871fa3e601Sgilles } while ((mods = sep) != NULL);
1881fa3e601Sgilles }
1891fa3e601Sgilles
1901fa3e601Sgilles if (!raw && replace)
1911fa3e601Sgilles for (i = 0; (size_t)i < strlen(tmp); ++i)
1921fa3e601Sgilles if (strchr(MAILADDR_ESCAPE, tmp[i]))
1931fa3e601Sgilles tmp[i] = ':';
1941fa3e601Sgilles
1951fa3e601Sgilles /* expanded string is empty */
1961fa3e601Sgilles i = strlen(string);
1971fa3e601Sgilles if (i == 0)
1981fa3e601Sgilles return 0;
1991fa3e601Sgilles
2001fa3e601Sgilles /* begin offset beyond end of string */
2011fa3e601Sgilles if (begoff >= i)
202f68f476fSgilles return -1;
2031fa3e601Sgilles
2041fa3e601Sgilles /* end offset beyond end of string, make it end of string */
2051fa3e601Sgilles if (endoff >= i)
2061fa3e601Sgilles endoff = i - 1;
2071fa3e601Sgilles
2081fa3e601Sgilles /* negative begin offset, make it relative to end of string */
2091fa3e601Sgilles if (begoff < 0)
2101fa3e601Sgilles begoff += i;
2111fa3e601Sgilles /* negative end offset, make it relative to end of string,
2121fa3e601Sgilles * note that end offset is inclusive.
2131fa3e601Sgilles */
2141fa3e601Sgilles if (endoff < 0)
2151fa3e601Sgilles endoff += i - 1;
2161fa3e601Sgilles
2171fa3e601Sgilles /* check that final offsets are valid */
2181fa3e601Sgilles if (begoff < 0 || endoff < 0 || endoff < begoff)
219f68f476fSgilles return -1;
2201fa3e601Sgilles endoff += 1; /* end offset is inclusive */
2211fa3e601Sgilles
2221fa3e601Sgilles /* check that substring does not exceed destination buffer length */
2231fa3e601Sgilles i = endoff - begoff;
2241fa3e601Sgilles if ((size_t)i + 1 >= len)
225f68f476fSgilles return -1;
2261fa3e601Sgilles
2271fa3e601Sgilles string += begoff;
2281fa3e601Sgilles for (; i; i--) {
229dd7527c8Sgilles *dest = *string;
2301fa3e601Sgilles dest++;
2311fa3e601Sgilles string++;
2321fa3e601Sgilles }
2331fa3e601Sgilles
2341fa3e601Sgilles return endoff - begoff;
2351fa3e601Sgilles }
2361fa3e601Sgilles
2371fa3e601Sgilles
238f68f476fSgilles ssize_t
mda_expand_format(char * buf,size_t len,const struct deliver * dlv,const struct userinfo * ui,const char * mda_command)239a8e22235Sgilles mda_expand_format(char *buf, size_t len, const struct deliver *dlv,
240d0df4e7aSgilles const struct userinfo *ui, const char *mda_command)
2411fa3e601Sgilles {
2421fa3e601Sgilles char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
2431fa3e601Sgilles char exptok[EXPAND_BUFFER];
244f68f476fSgilles ssize_t exptoklen;
2451fa3e601Sgilles char token[MAXTOKENLEN];
2469002671cSmillert size_t ret, tmpret, toklen;
2471fa3e601Sgilles
2481fa3e601Sgilles if (len < sizeof tmpbuf) {
2491fa3e601Sgilles log_warnx("mda_expand_format: tmp buffer < rule buffer");
250f68f476fSgilles return -1;
2511fa3e601Sgilles }
2521fa3e601Sgilles
2531fa3e601Sgilles memset(tmpbuf, 0, sizeof tmpbuf);
2541fa3e601Sgilles pbuf = buf;
2551fa3e601Sgilles ptmp = tmpbuf;
2561fa3e601Sgilles ret = tmpret = 0;
2571fa3e601Sgilles
2581fa3e601Sgilles /* special case: ~/ only allowed expanded at the beginning */
2591fa3e601Sgilles if (strncmp(pbuf, "~/", 2) == 0) {
2601fa3e601Sgilles tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory);
2611fa3e601Sgilles if (tmpret >= sizeof tmpbuf) {
2621fa3e601Sgilles log_warnx("warn: user directory for %s too large",
2631fa3e601Sgilles ui->directory);
2641fa3e601Sgilles return 0;
2651fa3e601Sgilles }
2661fa3e601Sgilles ret += tmpret;
2671fa3e601Sgilles ptmp += tmpret;
2681fa3e601Sgilles pbuf += 2;
2691fa3e601Sgilles }
2701fa3e601Sgilles
2711fa3e601Sgilles /* expansion loop */
2721fa3e601Sgilles for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) {
2731fa3e601Sgilles if (*pbuf == '%' && *(pbuf + 1) == '%') {
2741fa3e601Sgilles *ptmp++ = *pbuf++;
2751fa3e601Sgilles pbuf += 1;
2761fa3e601Sgilles tmpret = 1;
2771fa3e601Sgilles continue;
2781fa3e601Sgilles }
2791fa3e601Sgilles
2801fa3e601Sgilles if (*pbuf != '%' || *(pbuf + 1) != '{') {
2811fa3e601Sgilles *ptmp++ = *pbuf++;
2821fa3e601Sgilles tmpret = 1;
2831fa3e601Sgilles continue;
2841fa3e601Sgilles }
2851fa3e601Sgilles
2861fa3e601Sgilles /* %{...} otherwise fail */
2879002671cSmillert if ((ebuf = strchr(pbuf+2, '}')) == NULL)
2881fa3e601Sgilles return 0;
2891fa3e601Sgilles
2901fa3e601Sgilles /* extract token from %{token} */
2919002671cSmillert toklen = ebuf - (pbuf+2);
2929002671cSmillert if (toklen >= sizeof token)
2931fa3e601Sgilles return 0;
2941fa3e601Sgilles
2959002671cSmillert memcpy(token, pbuf+2, toklen);
2969002671cSmillert token[toklen] = '\0';
2971fa3e601Sgilles
298a8e22235Sgilles exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv,
299d0df4e7aSgilles ui, mda_command);
300f68f476fSgilles if (exptoklen == -1)
301f68f476fSgilles return -1;
3021fa3e601Sgilles
3031fa3e601Sgilles /* writing expanded token at ptmp will overflow tmpbuf */
304f68f476fSgilles if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen)
305f68f476fSgilles return -1;
3061fa3e601Sgilles
3071fa3e601Sgilles memcpy(ptmp, exptok, exptoklen);
3081fa3e601Sgilles pbuf = ebuf + 1;
3091fa3e601Sgilles ptmp += exptoklen;
3101fa3e601Sgilles tmpret = exptoklen;
3111fa3e601Sgilles }
3121fa3e601Sgilles if (ret >= sizeof tmpbuf)
313f68f476fSgilles return -1;
3141fa3e601Sgilles
3151fa3e601Sgilles if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
316f68f476fSgilles return -1;
3171fa3e601Sgilles
3181fa3e601Sgilles return ret;
3191fa3e601Sgilles }
3201fa3e601Sgilles
3211fa3e601Sgilles static int
mod_lowercase(char * buf,size_t len)3221fa3e601Sgilles mod_lowercase(char *buf, size_t len)
3231fa3e601Sgilles {
3241fa3e601Sgilles char tmp[EXPAND_BUFFER];
3251fa3e601Sgilles
3261fa3e601Sgilles if (!lowercase(tmp, buf, sizeof tmp))
3271fa3e601Sgilles return 0;
3281fa3e601Sgilles if (strlcpy(buf, tmp, len) >= len)
3291fa3e601Sgilles return 0;
3301fa3e601Sgilles return 1;
3311fa3e601Sgilles }
3321fa3e601Sgilles
3331fa3e601Sgilles static int
mod_uppercase(char * buf,size_t len)3341fa3e601Sgilles mod_uppercase(char *buf, size_t len)
3351fa3e601Sgilles {
3361fa3e601Sgilles char tmp[EXPAND_BUFFER];
3371fa3e601Sgilles
3381fa3e601Sgilles if (!uppercase(tmp, buf, sizeof tmp))
3391fa3e601Sgilles return 0;
3401fa3e601Sgilles if (strlcpy(buf, tmp, len) >= len)
3411fa3e601Sgilles return 0;
3421fa3e601Sgilles return 1;
3431fa3e601Sgilles }
3441fa3e601Sgilles
3451fa3e601Sgilles static int
mod_strip(char * buf,size_t len)3461fa3e601Sgilles mod_strip(char *buf, size_t len)
3471fa3e601Sgilles {
3481fa3e601Sgilles char *tag, *at;
3491fa3e601Sgilles unsigned int i;
3501fa3e601Sgilles
3511fa3e601Sgilles /* gilles+hackers -> gilles */
3521fa3e601Sgilles if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) {
3531fa3e601Sgilles /* gilles+hackers@poolp.org -> gilles@poolp.org */
3541fa3e601Sgilles if ((at = strchr(tag, '@')) != NULL) {
3551fa3e601Sgilles for (i = 0; i <= strlen(at); ++i)
3561fa3e601Sgilles tag[i] = at[i];
3571fa3e601Sgilles } else
3581fa3e601Sgilles *tag = '\0';
3591fa3e601Sgilles }
3601fa3e601Sgilles return 1;
3611fa3e601Sgilles }
362