xref: /openbsd-src/usr.sbin/smtpd/mda_variables.c (revision 4e1ee0786f11cc571bd0be17d38e46f635c719fc)
1 /*	$OpenBSD: mda_variables.c,v 1.7 2021/06/14 17:58:15 eric 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
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;
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 (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
163 			return -1;
164 		string = tmp;
165 	}
166 
167 	/*  apply modifiers */
168 	if (mods != NULL) {
169 		do {
170 			if ((sep = strchr(mods, '|')) != NULL)
171 				*sep++ = '\0';
172 			for (i = 0; (size_t)i < nitems(token_modifiers); ++i) {
173 				if (!strcasecmp(token_modifiers[i].name, mods)) {
174 					if (token_modifiers[i].f == NULL) {
175 						raw = 1;
176 						break;
177 					}
178 					if (!token_modifiers[i].f(tmp, sizeof tmp))
179 						return -1; /* modifier error */
180 					break;
181 				}
182 			}
183 			if ((size_t)i == nitems(token_modifiers))
184 				return -1; /* modifier not found */
185 		} while ((mods = sep) != NULL);
186 	}
187 
188 	if (!raw && replace)
189 		for (i = 0; (size_t)i < strlen(tmp); ++i)
190 			if (strchr(MAILADDR_ESCAPE, tmp[i]))
191 				tmp[i] = ':';
192 
193 	/* expanded string is empty */
194 	i = strlen(string);
195 	if (i == 0)
196 		return 0;
197 
198 	/* begin offset beyond end of string */
199 	if (begoff >= i)
200 		return -1;
201 
202 	/* end offset beyond end of string, make it end of string */
203 	if (endoff >= i)
204 		endoff = i - 1;
205 
206 	/* negative begin offset, make it relative to end of string */
207 	if (begoff < 0)
208 		begoff += i;
209 	/* negative end offset, make it relative to end of string,
210 	 * note that end offset is inclusive.
211 	 */
212 	if (endoff < 0)
213 		endoff += i - 1;
214 
215 	/* check that final offsets are valid */
216 	if (begoff < 0 || endoff < 0 || endoff < begoff)
217 		return -1;
218 	endoff += 1; /* end offset is inclusive */
219 
220 	/* check that substring does not exceed destination buffer length */
221 	i = endoff - begoff;
222 	if ((size_t)i + 1 >= len)
223 		return -1;
224 
225 	string += begoff;
226 	for (; i; i--) {
227 		*dest = *string;
228 		dest++;
229 		string++;
230 	}
231 
232 	return endoff - begoff;
233 }
234 
235 
236 ssize_t
237 mda_expand_format(char *buf, size_t len, const struct deliver *dlv,
238     const struct userinfo *ui, const char *mda_command)
239 {
240 	char		tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
241 	char		exptok[EXPAND_BUFFER];
242 	ssize_t		exptoklen;
243 	char		token[MAXTOKENLEN];
244 	size_t		ret, tmpret;
245 
246 	if (len < sizeof tmpbuf) {
247 		log_warnx("mda_expand_format: tmp buffer < rule buffer");
248 		return -1;
249 	}
250 
251 	memset(tmpbuf, 0, sizeof tmpbuf);
252 	pbuf = buf;
253 	ptmp = tmpbuf;
254 	ret = tmpret = 0;
255 
256 	/* special case: ~/ only allowed expanded at the beginning */
257 	if (strncmp(pbuf, "~/", 2) == 0) {
258 		tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory);
259 		if (tmpret >= sizeof tmpbuf) {
260 			log_warnx("warn: user directory for %s too large",
261 			    ui->directory);
262 			return 0;
263 		}
264 		ret  += tmpret;
265 		ptmp += tmpret;
266 		pbuf += 2;
267 	}
268 
269 
270 	/* expansion loop */
271 	for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) {
272 		if (*pbuf == '%' && *(pbuf + 1) == '%') {
273 			*ptmp++ = *pbuf++;
274 			pbuf  += 1;
275 			tmpret = 1;
276 			continue;
277 		}
278 
279 		if (*pbuf != '%' || *(pbuf + 1) != '{') {
280 			*ptmp++ = *pbuf++;
281 			tmpret = 1;
282 			continue;
283 		}
284 
285 		/* %{...} otherwise fail */
286 		if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL)
287 			return 0;
288 
289 		/* extract token from %{token} */
290 		if ((size_t)(ebuf - pbuf) - 1 >= sizeof token)
291 			return 0;
292 
293 		memcpy(token, pbuf+2, ebuf-pbuf-1);
294 		if (strchr(token, '}') == NULL)
295 			return 0;
296 		*strchr(token, '}') = '\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
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
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
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