xref: /openbsd/usr.sbin/smtpd/mda_variables.c (revision e6c7c102)
1 /*	$OpenBSD: mda_variables.c,v 1.10 2024/04/23 13:34:51 jsg Exp $	*/
2 
3 /*
4  * Copyright (c) 2011-2017 Gilles Chehade <gilles@poolp.org>
5  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "smtpd.h"
24 #include "log.h"
25 
26 #define	EXPAND_DEPTH	10
27 
28 ssize_t mda_expand_format(char *, size_t, const struct deliver *,
29     const struct userinfo *, const char *);
30 static ssize_t mda_expand_token(char *, size_t, const char *,
31     const struct deliver *, const struct userinfo *, const char *);
32 static int mod_lowercase(char *, size_t);
33 static int mod_uppercase(char *, size_t);
34 static int mod_strip(char *, size_t);
35 
36 static struct modifiers {
37 	char	*name;
38 	int	(*f)(char *buf, size_t len);
39 } token_modifiers[] = {
40 	{ "lowercase",	mod_lowercase },
41 	{ "uppercase",	mod_uppercase },
42 	{ "strip",	mod_strip },
43 	{ "raw",	NULL },		/* special case, must stay last */
44 };
45 
46 #define	MAXTOKENLEN	128
47 
48 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)49 mda_expand_token(char *dest, size_t len, const char *token,
50     const struct deliver *dlv, const struct userinfo *ui, const char *mda_command)
51 {
52 	char		rtoken[MAXTOKENLEN];
53 	char		tmp[EXPAND_BUFFER];
54 	const char     *string = NULL;
55 	char	       *lbracket, *rbracket, *content, *sep, *mods;
56 	ssize_t		i;
57 	ssize_t		begoff, endoff;
58 	const char     *errstr = NULL;
59 	int		replace = 1;
60 	int		raw = 0;
61 
62 	begoff = 0;
63 	endoff = EXPAND_BUFFER;
64 	mods = NULL;
65 
66 	if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken)
67 		return -1;
68 
69 	/* token[x[:y]] -> extracts optional x and y, converts into offsets */
70 	if ((lbracket = strchr(rtoken, '[')) &&
71 	    (rbracket = strchr(rtoken, ']'))) {
72 		/* ] before [ ... or empty */
73 		if (rbracket < lbracket || rbracket - lbracket <= 1)
74 			return -1;
75 
76 		*lbracket = *rbracket = '\0';
77 		content  = lbracket + 1;
78 
79 		if ((sep = strchr(content, ':')) == NULL)
80 			endoff = begoff = strtonum(content, -EXPAND_BUFFER,
81 			    EXPAND_BUFFER, &errstr);
82 		else {
83 			*sep = '\0';
84 			if (content != sep)
85 				begoff = strtonum(content, -EXPAND_BUFFER,
86 				    EXPAND_BUFFER, &errstr);
87 			if (*(++sep)) {
88 				if (errstr == NULL)
89 					endoff = strtonum(sep, -EXPAND_BUFFER,
90 					    EXPAND_BUFFER, &errstr);
91 			}
92 		}
93 		if (errstr)
94 			return -1;
95 
96 		/* token:mod_1,mod_2,mod_n -> extract modifiers */
97 		mods = strchr(rbracket + 1, ':');
98 	} else {
99 		if ((mods = strchr(rtoken, ':')) != NULL)
100 			*mods++ = '\0';
101 	}
102 
103 	/* token -> expanded token */
104 	if (!strcasecmp("sender", rtoken)) {
105 		if (snprintf(tmp, sizeof tmp, "%s@%s",
106 			dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp)
107 			return -1;
108 		if (strcmp(tmp, "@") == 0)
109 			(void)strlcpy(tmp, "", sizeof tmp);
110 		string = tmp;
111 	}
112 	else if (!strcasecmp("rcpt", rtoken)) {
113 		if (snprintf(tmp, sizeof tmp, "%s@%s",
114 			dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp)
115 			return -1;
116 		if (strcmp(tmp, "@") == 0)
117 			(void)strlcpy(tmp, "", sizeof tmp);
118 		string = tmp;
119 	}
120 	else if (!strcasecmp("dest", rtoken)) {
121 		if (snprintf(tmp, sizeof tmp, "%s@%s",
122 			dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp)
123 			return -1;
124 		if (strcmp(tmp, "@") == 0)
125 			(void)strlcpy(tmp, "", sizeof tmp);
126 		string = tmp;
127 	}
128 	else if (!strcasecmp("sender.user", rtoken))
129 		string = dlv->sender.user;
130 	else if (!strcasecmp("sender.domain", rtoken))
131 		string = dlv->sender.domain;
132 	else if (!strcasecmp("user.username", rtoken))
133 		string = ui->username;
134 	else if (!strcasecmp("user.directory", rtoken)) {
135 		string = ui->directory;
136 		replace = 0;
137 	}
138 	else if (!strcasecmp("rcpt.user", rtoken))
139 		string = dlv->rcpt.user;
140 	else if (!strcasecmp("rcpt.domain", rtoken))
141 		string = dlv->rcpt.domain;
142 	else if (!strcasecmp("dest.user", rtoken))
143 		string = dlv->dest.user;
144 	else if (!strcasecmp("dest.domain", rtoken))
145 		string = dlv->dest.domain;
146 	else if (!strcasecmp("mda", rtoken)) {
147 		string = mda_command;
148 		replace = 0;
149 	}
150 	else if (!strcasecmp("mbox.from", rtoken)) {
151 		if (snprintf(tmp, sizeof tmp, "%s@%s",
152 			dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp)
153 			return -1;
154 		if (strcmp(tmp, "@") == 0)
155 			(void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp);
156 		string = tmp;
157 	}
158 	else
159 		return -1;
160 
161 	if (string != tmp) {
162 		if (string == NULL)
163 			return -1;
164 		if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
165 			return -1;
166 		string = tmp;
167 	}
168 
169 	/*  apply modifiers */
170 	if (mods != NULL) {
171 		do {
172 			if ((sep = strchr(mods, '|')) != NULL)
173 				*sep++ = '\0';
174 			for (i = 0; (size_t)i < nitems(token_modifiers); ++i) {
175 				if (!strcasecmp(token_modifiers[i].name, mods)) {
176 					if (token_modifiers[i].f == NULL) {
177 						raw = 1;
178 						break;
179 					}
180 					if (!token_modifiers[i].f(tmp, sizeof tmp))
181 						return -1; /* modifier error */
182 					break;
183 				}
184 			}
185 			if ((size_t)i == nitems(token_modifiers))
186 				return -1; /* modifier not found */
187 		} while ((mods = sep) != NULL);
188 	}
189 
190 	if (!raw && replace)
191 		for (i = 0; (size_t)i < strlen(tmp); ++i)
192 			if (strchr(MAILADDR_ESCAPE, tmp[i]))
193 				tmp[i] = ':';
194 
195 	/* expanded string is empty */
196 	i = strlen(string);
197 	if (i == 0)
198 		return 0;
199 
200 	/* begin offset beyond end of string */
201 	if (begoff >= i)
202 		return -1;
203 
204 	/* end offset beyond end of string, make it end of string */
205 	if (endoff >= i)
206 		endoff = i - 1;
207 
208 	/* negative begin offset, make it relative to end of string */
209 	if (begoff < 0)
210 		begoff += i;
211 	/* negative end offset, make it relative to end of string,
212 	 * note that end offset is inclusive.
213 	 */
214 	if (endoff < 0)
215 		endoff += i - 1;
216 
217 	/* check that final offsets are valid */
218 	if (begoff < 0 || endoff < 0 || endoff < begoff)
219 		return -1;
220 	endoff += 1; /* end offset is inclusive */
221 
222 	/* check that substring does not exceed destination buffer length */
223 	i = endoff - begoff;
224 	if ((size_t)i + 1 >= len)
225 		return -1;
226 
227 	string += begoff;
228 	for (; i; i--) {
229 		*dest = *string;
230 		dest++;
231 		string++;
232 	}
233 
234 	return endoff - begoff;
235 }
236 
237 
238 ssize_t
mda_expand_format(char * buf,size_t len,const struct deliver * dlv,const struct userinfo * ui,const char * mda_command)239 mda_expand_format(char *buf, size_t len, const struct deliver *dlv,
240     const struct userinfo *ui, const char *mda_command)
241 {
242 	char		tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
243 	char		exptok[EXPAND_BUFFER];
244 	ssize_t		exptoklen;
245 	char		token[MAXTOKENLEN];
246 	size_t		ret, tmpret, toklen;
247 
248 	if (len < sizeof tmpbuf) {
249 		log_warnx("mda_expand_format: tmp buffer < rule buffer");
250 		return -1;
251 	}
252 
253 	memset(tmpbuf, 0, sizeof tmpbuf);
254 	pbuf = buf;
255 	ptmp = tmpbuf;
256 	ret = tmpret = 0;
257 
258 	/* special case: ~/ only allowed expanded at the beginning */
259 	if (strncmp(pbuf, "~/", 2) == 0) {
260 		tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory);
261 		if (tmpret >= sizeof tmpbuf) {
262 			log_warnx("warn: user directory for %s too large",
263 			    ui->directory);
264 			return 0;
265 		}
266 		ret  += tmpret;
267 		ptmp += tmpret;
268 		pbuf += 2;
269 	}
270 
271 	/* expansion loop */
272 	for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) {
273 		if (*pbuf == '%' && *(pbuf + 1) == '%') {
274 			*ptmp++ = *pbuf++;
275 			pbuf  += 1;
276 			tmpret = 1;
277 			continue;
278 		}
279 
280 		if (*pbuf != '%' || *(pbuf + 1) != '{') {
281 			*ptmp++ = *pbuf++;
282 			tmpret = 1;
283 			continue;
284 		}
285 
286 		/* %{...} otherwise fail */
287 		if ((ebuf = strchr(pbuf+2, '}')) == NULL)
288 			return 0;
289 
290 		/* extract token from %{token} */
291 		toklen = ebuf - (pbuf+2);
292 		if (toklen >= sizeof token)
293 			return 0;
294 
295 		memcpy(token, pbuf+2, toklen);
296 		token[toklen] = '\0';
297 
298 		exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv,
299 		    ui, mda_command);
300 		if (exptoklen == -1)
301 			return -1;
302 
303 		/* writing expanded token at ptmp will overflow tmpbuf */
304 		if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen)
305 			return -1;
306 
307 		memcpy(ptmp, exptok, exptoklen);
308 		pbuf   = ebuf + 1;
309 		ptmp  += exptoklen;
310 		tmpret = exptoklen;
311 	}
312 	if (ret >= sizeof tmpbuf)
313 		return -1;
314 
315 	if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
316 		return -1;
317 
318 	return ret;
319 }
320 
321 static int
mod_lowercase(char * buf,size_t len)322 mod_lowercase(char *buf, size_t len)
323 {
324 	char tmp[EXPAND_BUFFER];
325 
326 	if (!lowercase(tmp, buf, sizeof tmp))
327 		return 0;
328 	if (strlcpy(buf, tmp, len) >= len)
329 		return 0;
330 	return 1;
331 }
332 
333 static int
mod_uppercase(char * buf,size_t len)334 mod_uppercase(char *buf, size_t len)
335 {
336 	char tmp[EXPAND_BUFFER];
337 
338 	if (!uppercase(tmp, buf, sizeof tmp))
339 		return 0;
340 	if (strlcpy(buf, tmp, len) >= len)
341 		return 0;
342 	return 1;
343 }
344 
345 static int
mod_strip(char * buf,size_t len)346 mod_strip(char *buf, size_t len)
347 {
348 	char *tag, *at;
349 	unsigned int i;
350 
351 	/* gilles+hackers -> gilles */
352 	if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) {
353 		/* gilles+hackers@poolp.org -> gilles@poolp.org */
354 		if ((at = strchr(tag, '@')) != NULL) {
355 			for (i = 0; i <= strlen(at); ++i)
356 				tag[i] = at[i];
357 		} else
358 			*tag = '\0';
359 	}
360 	return 1;
361 }
362