xref: /openbsd-src/usr.sbin/smtpd/mda_variables.c (revision e6c7c102cf5d9891f32552a42895134a59937045)
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