1 |
|
|
/* $OpenBSD: mda_variables.c,v 1.1 2017/05/26 21:30:00 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 |
|
|
size_t mda_expand_format(char *, size_t, const struct envelope *, |
40 |
|
|
const struct userinfo *); |
41 |
|
|
static size_t mda_expand_token(char *, size_t, const char *, |
42 |
|
|
const struct envelope *, const struct userinfo *); |
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 size_t |
60 |
|
|
mda_expand_token(char *dest, size_t len, const char *token, |
61 |
|
|
const struct envelope *ep, const struct userinfo *ui) |
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 0; |
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 0; |
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 0; |
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 |
|
|
ep->sender.user, ep->sender.domain) >= (int)sizeof tmp) |
118 |
|
|
return 0; |
119 |
|
|
string = tmp; |
120 |
|
|
} |
121 |
|
|
else if (!strcasecmp("dest", rtoken)) { |
122 |
|
|
if (snprintf(tmp, sizeof tmp, "%s@%s", |
123 |
|
|
ep->dest.user, ep->dest.domain) >= (int)sizeof tmp) |
124 |
|
|
return 0; |
125 |
|
|
string = tmp; |
126 |
|
|
} |
127 |
|
|
else if (!strcasecmp("rcpt", rtoken)) { |
128 |
|
|
if (snprintf(tmp, sizeof tmp, "%s@%s", |
129 |
|
|
ep->rcpt.user, ep->rcpt.domain) >= (int)sizeof tmp) |
130 |
|
|
return 0; |
131 |
|
|
string = tmp; |
132 |
|
|
} |
133 |
|
|
else if (!strcasecmp("sender.user", rtoken)) |
134 |
|
|
string = ep->sender.user; |
135 |
|
|
else if (!strcasecmp("sender.domain", rtoken)) |
136 |
|
|
string = ep->sender.domain; |
137 |
|
|
else if (!strcasecmp("user.username", rtoken)) |
138 |
|
|
string = ui->username; |
139 |
|
|
else if (!strcasecmp("user.directory", rtoken)) { |
140 |
|
|
string = ui->directory; |
141 |
|
|
replace = 0; |
142 |
|
|
} |
143 |
|
|
else if (!strcasecmp("dest.user", rtoken)) |
144 |
|
|
string = ep->dest.user; |
145 |
|
|
else if (!strcasecmp("dest.domain", rtoken)) |
146 |
|
|
string = ep->dest.domain; |
147 |
|
|
else if (!strcasecmp("rcpt.user", rtoken)) |
148 |
|
|
string = ep->rcpt.user; |
149 |
|
|
else if (!strcasecmp("rcpt.domain", rtoken)) |
150 |
|
|
string = ep->rcpt.domain; |
151 |
|
|
else |
152 |
|
|
return 0; |
153 |
|
|
|
154 |
|
|
if (string != tmp) { |
155 |
|
|
if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) |
156 |
|
|
return 0; |
157 |
|
|
string = tmp; |
158 |
|
|
} |
159 |
|
|
|
160 |
|
|
/* apply modifiers */ |
161 |
|
|
if (mods != NULL) { |
162 |
|
|
do { |
163 |
|
|
if ((sep = strchr(mods, '|')) != NULL) |
164 |
|
|
*sep++ = '\0'; |
165 |
|
|
for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { |
166 |
|
|
if (!strcasecmp(token_modifiers[i].name, mods)) { |
167 |
|
|
if (token_modifiers[i].f == NULL) { |
168 |
|
|
raw = 1; |
169 |
|
|
break; |
170 |
|
|
} |
171 |
|
|
if (!token_modifiers[i].f(tmp, sizeof tmp)) |
172 |
|
|
return 0; /* modifier error */ |
173 |
|
|
break; |
174 |
|
|
} |
175 |
|
|
} |
176 |
|
|
if ((size_t)i == nitems(token_modifiers)) |
177 |
|
|
return 0; /* modifier not found */ |
178 |
|
|
} while ((mods = sep) != NULL); |
179 |
|
|
} |
180 |
|
|
|
181 |
|
|
if (!raw && replace) |
182 |
|
|
for (i = 0; (size_t)i < strlen(tmp); ++i) |
183 |
|
|
if (strchr(MAILADDR_ESCAPE, tmp[i])) |
184 |
|
|
tmp[i] = ':'; |
185 |
|
|
|
186 |
|
|
/* expanded string is empty */ |
187 |
|
|
i = strlen(string); |
188 |
|
|
if (i == 0) |
189 |
|
|
return 0; |
190 |
|
|
|
191 |
|
|
/* begin offset beyond end of string */ |
192 |
|
|
if (begoff >= i) |
193 |
|
|
return 0; |
194 |
|
|
|
195 |
|
|
/* end offset beyond end of string, make it end of string */ |
196 |
|
|
if (endoff >= i) |
197 |
|
|
endoff = i - 1; |
198 |
|
|
|
199 |
|
|
/* negative begin offset, make it relative to end of string */ |
200 |
|
|
if (begoff < 0) |
201 |
|
|
begoff += i; |
202 |
|
|
/* negative end offset, make it relative to end of string, |
203 |
|
|
* note that end offset is inclusive. |
204 |
|
|
*/ |
205 |
|
|
if (endoff < 0) |
206 |
|
|
endoff += i - 1; |
207 |
|
|
|
208 |
|
|
/* check that final offsets are valid */ |
209 |
|
|
if (begoff < 0 || endoff < 0 || endoff < begoff) |
210 |
|
|
return 0; |
211 |
|
|
endoff += 1; /* end offset is inclusive */ |
212 |
|
|
|
213 |
|
|
/* check that substring does not exceed destination buffer length */ |
214 |
|
|
i = endoff - begoff; |
215 |
|
|
if ((size_t)i + 1 >= len) |
216 |
|
|
return 0; |
217 |
|
|
|
218 |
|
|
string += begoff; |
219 |
|
|
for (; i; i--) { |
220 |
|
|
*dest = (replace && *string == '/') ? ':' : *string; |
221 |
|
|
dest++; |
222 |
|
|
string++; |
223 |
|
|
} |
224 |
|
|
|
225 |
|
|
return endoff - begoff; |
226 |
|
|
} |
227 |
|
|
|
228 |
|
|
|
229 |
|
|
size_t |
230 |
|
|
mda_expand_format(char *buf, size_t len, const struct envelope *ep, |
231 |
|
|
const struct userinfo *ui) |
232 |
|
|
{ |
233 |
|
|
char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; |
234 |
|
|
char exptok[EXPAND_BUFFER]; |
235 |
|
|
size_t exptoklen; |
236 |
|
|
char token[MAXTOKENLEN]; |
237 |
|
|
size_t ret, tmpret; |
238 |
|
|
|
239 |
|
|
if (len < sizeof tmpbuf) { |
240 |
|
|
log_warnx("mda_expand_format: tmp buffer < rule buffer"); |
241 |
|
|
return 0; |
242 |
|
|
} |
243 |
|
|
|
244 |
|
|
memset(tmpbuf, 0, sizeof tmpbuf); |
245 |
|
|
pbuf = buf; |
246 |
|
|
ptmp = tmpbuf; |
247 |
|
|
ret = tmpret = 0; |
248 |
|
|
|
249 |
|
|
/* special case: ~/ only allowed expanded at the beginning */ |
250 |
|
|
if (strncmp(pbuf, "~/", 2) == 0) { |
251 |
|
|
tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); |
252 |
|
|
if (tmpret >= sizeof tmpbuf) { |
253 |
|
|
log_warnx("warn: user directory for %s too large", |
254 |
|
|
ui->directory); |
255 |
|
|
return 0; |
256 |
|
|
} |
257 |
|
|
ret += tmpret; |
258 |
|
|
ptmp += tmpret; |
259 |
|
|
pbuf += 2; |
260 |
|
|
} |
261 |
|
|
|
262 |
|
|
|
263 |
|
|
/* expansion loop */ |
264 |
|
|
for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { |
265 |
|
|
if (*pbuf == '%' && *(pbuf + 1) == '%') { |
266 |
|
|
*ptmp++ = *pbuf++; |
267 |
|
|
pbuf += 1; |
268 |
|
|
tmpret = 1; |
269 |
|
|
continue; |
270 |
|
|
} |
271 |
|
|
|
272 |
|
|
if (*pbuf != '%' || *(pbuf + 1) != '{') { |
273 |
|
|
*ptmp++ = *pbuf++; |
274 |
|
|
tmpret = 1; |
275 |
|
|
continue; |
276 |
|
|
} |
277 |
|
|
|
278 |
|
|
/* %{...} otherwise fail */ |
279 |
|
|
if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL) |
280 |
|
|
return 0; |
281 |
|
|
|
282 |
|
|
/* extract token from %{token} */ |
283 |
|
|
if ((size_t)(ebuf - pbuf) - 1 >= sizeof token) |
284 |
|
|
return 0; |
285 |
|
|
|
286 |
|
|
memcpy(token, pbuf+2, ebuf-pbuf-1); |
287 |
|
|
if (strchr(token, '}') == NULL) |
288 |
|
|
return 0; |
289 |
|
|
*strchr(token, '}') = '\0'; |
290 |
|
|
|
291 |
|
|
exptoklen = mda_expand_token(exptok, sizeof exptok, token, ep, |
292 |
|
|
ui); |
293 |
|
|
if (exptoklen == 0) |
294 |
|
|
return 0; |
295 |
|
|
|
296 |
|
|
/* writing expanded token at ptmp will overflow tmpbuf */ |
297 |
|
|
if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= exptoklen) |
298 |
|
|
return 0; |
299 |
|
|
|
300 |
|
|
memcpy(ptmp, exptok, exptoklen); |
301 |
|
|
pbuf = ebuf + 1; |
302 |
|
|
ptmp += exptoklen; |
303 |
|
|
tmpret = exptoklen; |
304 |
|
|
} |
305 |
|
|
if (ret >= sizeof tmpbuf) |
306 |
|
|
return 0; |
307 |
|
|
|
308 |
|
|
if ((ret = strlcpy(buf, tmpbuf, len)) >= len) |
309 |
|
|
return 0; |
310 |
|
|
|
311 |
|
|
return ret; |
312 |
|
|
} |
313 |
|
|
|
314 |
|
|
static int |
315 |
|
|
mod_lowercase(char *buf, size_t len) |
316 |
|
|
{ |
317 |
|
|
char tmp[EXPAND_BUFFER]; |
318 |
|
|
|
319 |
|
|
if (!lowercase(tmp, buf, sizeof tmp)) |
320 |
|
|
return 0; |
321 |
|
|
if (strlcpy(buf, tmp, len) >= len) |
322 |
|
|
return 0; |
323 |
|
|
return 1; |
324 |
|
|
} |
325 |
|
|
|
326 |
|
|
static int |
327 |
|
|
mod_uppercase(char *buf, size_t len) |
328 |
|
|
{ |
329 |
|
|
char tmp[EXPAND_BUFFER]; |
330 |
|
|
|
331 |
|
|
if (!uppercase(tmp, buf, sizeof tmp)) |
332 |
|
|
return 0; |
333 |
|
|
if (strlcpy(buf, tmp, len) >= len) |
334 |
|
|
return 0; |
335 |
|
|
return 1; |
336 |
|
|
} |
337 |
|
|
|
338 |
|
|
static int |
339 |
|
|
mod_strip(char *buf, size_t len) |
340 |
|
|
{ |
341 |
|
|
char *tag, *at; |
342 |
|
|
unsigned int i; |
343 |
|
|
|
344 |
|
|
/* gilles+hackers -> gilles */ |
345 |
|
|
if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) { |
346 |
|
|
/* gilles+hackers@poolp.org -> gilles@poolp.org */ |
347 |
|
|
if ((at = strchr(tag, '@')) != NULL) { |
348 |
|
|
for (i = 0; i <= strlen(at); ++i) |
349 |
|
|
tag[i] = at[i]; |
350 |
|
|
} else |
351 |
|
|
*tag = '\0'; |
352 |
|
|
} |
353 |
|
|
return 1; |
354 |
|
|
} |