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