xref: /openbsd-src/usr.sbin/smtpd/lka_session.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: lka_session.c,v 1.80 2016/08/31 10:18:08 gilles Exp $	*/
2 
3 /*
4  * Copyright (c) 2011 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 #include <sys/wait.h>
25 
26 #include <netinet/in.h>
27 
28 #include <ctype.h>
29 #include <errno.h>
30 #include <event.h>
31 #include <imsg.h>
32 #include <resolv.h>
33 #include <pwd.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <limits.h>
40 
41 #include "smtpd.h"
42 #include "log.h"
43 
44 #define	EXPAND_DEPTH	10
45 
46 #define	F_WAITING	0x01
47 
48 struct lka_session {
49 	uint64_t		 id; /* given by smtp */
50 
51 	TAILQ_HEAD(, envelope)	 deliverylist;
52 	struct expand		 expand;
53 
54 	int			 flags;
55 	int			 error;
56 	const char		*errormsg;
57 	struct envelope		 envelope;
58 	struct xnodes		 nodes;
59 	/* waiting for fwdrq */
60 	struct rule		*rule;
61 	struct expandnode	*node;
62 };
63 
64 static void lka_expand(struct lka_session *, struct rule *,
65     struct expandnode *);
66 static void lka_submit(struct lka_session *, struct rule *,
67     struct expandnode *);
68 static void lka_resume(struct lka_session *);
69 static size_t lka_expand_format(char *, size_t, const struct envelope *,
70     const struct userinfo *);
71 
72 static int mod_lowercase(char *, size_t);
73 static int mod_uppercase(char *, size_t);
74 static int mod_strip(char *, size_t);
75 
76 struct modifiers {
77 	char	*name;
78 	int	(*f)(char *buf, size_t len);
79 } token_modifiers[] = {
80 	{ "lowercase",	mod_lowercase },
81 	{ "uppercase",	mod_uppercase },
82 	{ "strip",	mod_strip },
83 	{ "raw",	NULL },		/* special case, must stay last */
84 };
85 
86 static int		init;
87 static struct tree	sessions;
88 
89 #define	MAXTOKENLEN	128
90 
91 void
92 lka_session(uint64_t id, struct envelope *envelope)
93 {
94 	struct lka_session	*lks;
95 	struct expandnode	 xn;
96 
97 	if (init == 0) {
98 		init = 1;
99 		tree_init(&sessions);
100 	}
101 
102 	lks = xcalloc(1, sizeof(*lks), "lka_session");
103 	lks->id = id;
104 	RB_INIT(&lks->expand.tree);
105 	TAILQ_INIT(&lks->deliverylist);
106 	tree_xset(&sessions, lks->id, lks);
107 
108 	lks->envelope = *envelope;
109 
110 	TAILQ_INIT(&lks->nodes);
111 	memset(&xn, 0, sizeof xn);
112 	xn.type = EXPAND_ADDRESS;
113 	xn.u.mailaddr = lks->envelope.rcpt;
114 	lks->expand.rule = NULL;
115 	lks->expand.queue = &lks->nodes;
116 	expand_insert(&lks->expand, &xn);
117 	lka_resume(lks);
118 }
119 
120 void
121 lka_session_forward_reply(struct forward_req *fwreq, int fd)
122 {
123 	struct lka_session     *lks;
124 	struct rule	       *rule;
125 	struct expandnode      *xn;
126 	int			ret;
127 
128 	lks = tree_xget(&sessions, fwreq->id);
129 	xn = lks->node;
130 	rule = lks->rule;
131 
132 	lks->flags &= ~F_WAITING;
133 
134 	switch (fwreq->status) {
135 	case 0:
136 		/* permanent failure while lookup ~/.forward */
137 		log_trace(TRACE_EXPAND, "expand: ~/.forward failed for user %s",
138 		    fwreq->user);
139 		lks->error = LKA_PERMFAIL;
140 		break;
141 	case 1:
142 		if (fd == -1) {
143 			if (lks->expand.rule->r_forwardonly) {
144 				log_trace(TRACE_EXPAND, "expand: no .forward "
145 				    "for user %s on forward-only rule", fwreq->user);
146 				lks->error = LKA_TEMPFAIL;
147 			}
148 			else if (lks->expand.rule->r_action == A_NONE) {
149 				log_trace(TRACE_EXPAND, "expand: no .forward "
150 				    "for user %s and no default action on rule", fwreq->user);
151 				lks->error = LKA_PERMFAIL;
152 			}
153 			else {
154 				log_trace(TRACE_EXPAND, "expand: no .forward for "
155 				    "user %s, just deliver", fwreq->user);
156 				lka_submit(lks, rule, xn);
157 			}
158 		}
159 		else {
160 			/* expand for the current user and rule */
161 			lks->expand.rule = rule;
162 			lks->expand.parent = xn;
163 			lks->expand.alias = 0;
164 			xn->mapping = rule->r_mapping;
165 			xn->userbase = rule->r_userbase;
166 			/* forwards_get() will close the descriptor no matter what */
167 			ret = forwards_get(fd, &lks->expand);
168 			if (ret == -1) {
169 				log_trace(TRACE_EXPAND, "expand: temporary "
170 				    "forward error for user %s", fwreq->user);
171 				lks->error = LKA_TEMPFAIL;
172 			}
173 			else if (ret == 0) {
174 				if (lks->expand.rule->r_forwardonly) {
175 					log_trace(TRACE_EXPAND, "expand: empty .forward "
176 					    "for user %s on forward-only rule", fwreq->user);
177 					lks->error = LKA_TEMPFAIL;
178 				}
179 				else if (lks->expand.rule->r_action == A_NONE) {
180 					log_trace(TRACE_EXPAND, "expand: empty .forward "
181 					    "for user %s and no default action on rule", fwreq->user);
182 					lks->error = LKA_PERMFAIL;
183 				}
184 				else {
185 					log_trace(TRACE_EXPAND, "expand: empty .forward "
186 					    "for user %s, just deliver", fwreq->user);
187 					lka_submit(lks, rule, xn);
188 				}
189 			}
190 		}
191 		break;
192 	default:
193 		/* temporary failure while looking up ~/.forward */
194 		lks->error = LKA_TEMPFAIL;
195 	}
196 
197 	lka_resume(lks);
198 }
199 
200 static void
201 lka_resume(struct lka_session *lks)
202 {
203 	struct envelope		*ep;
204 	struct expandnode	*xn;
205 
206 	if (lks->error)
207 		goto error;
208 
209 	/* pop next node and expand it */
210 	while ((xn = TAILQ_FIRST(&lks->nodes))) {
211 		TAILQ_REMOVE(&lks->nodes, xn, tq_entry);
212 		lka_expand(lks, xn->rule, xn);
213 		if (lks->flags & F_WAITING)
214 			return;
215 		if (lks->error)
216 			goto error;
217 	}
218 
219 	/* delivery list is empty, reject */
220 	if (TAILQ_FIRST(&lks->deliverylist) == NULL) {
221 		log_trace(TRACE_EXPAND, "expand: lka_done: expanded to empty "
222 		    "delivery list");
223 		lks->error = LKA_PERMFAIL;
224 	}
225     error:
226 	if (lks->error) {
227 		m_create(p_pony, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1);
228 		m_add_id(p_pony, lks->id);
229 		m_add_int(p_pony, lks->error);
230 
231 		if (lks->errormsg)
232 			m_add_string(p_pony, lks->errormsg);
233 		else {
234 			if (lks->error == LKA_PERMFAIL)
235 				m_add_string(p_pony, "550 Invalid recipient");
236 			else if (lks->error == LKA_TEMPFAIL)
237 				m_add_string(p_pony, "451 Temporary failure");
238 		}
239 
240 		m_close(p_pony);
241 		while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) {
242 			TAILQ_REMOVE(&lks->deliverylist, ep, entry);
243 			free(ep);
244 		}
245 	}
246 	else {
247 		/* Process the delivery list and submit envelopes to queue */
248 		while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) {
249 			TAILQ_REMOVE(&lks->deliverylist, ep, entry);
250 			m_create(p_queue, IMSG_LKA_ENVELOPE_SUBMIT, 0, 0, -1);
251 			m_add_id(p_queue, lks->id);
252 			m_add_envelope(p_queue, ep);
253 			m_close(p_queue);
254 			free(ep);
255 		}
256 
257 		m_create(p_queue, IMSG_LKA_ENVELOPE_COMMIT, 0, 0, -1);
258 		m_add_id(p_queue, lks->id);
259 		m_close(p_queue);
260 	}
261 
262 	expand_clear(&lks->expand);
263 	tree_xpop(&sessions, lks->id);
264 	free(lks);
265 }
266 
267 static void
268 lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
269 {
270 	struct forward_req	fwreq;
271 	struct envelope		ep;
272 	struct expandnode	node;
273 	struct mailaddr		maddr;
274 	int			r;
275 	union lookup		lk;
276 	char		       *tag;
277 
278 	if (xn->depth >= EXPAND_DEPTH) {
279 		log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep.");
280 		lks->error = LKA_PERMFAIL;
281 		return;
282 	}
283 
284 	switch (xn->type) {
285 	case EXPAND_INVALID:
286 	case EXPAND_INCLUDE:
287 		fatalx("lka_expand: unexpected type");
288 		break;
289 
290 	case EXPAND_ADDRESS:
291 
292 		log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s "
293 		    "[depth=%d]",
294 		    xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth);
295 
296 		/* Pass the node through the ruleset */
297 		ep = lks->envelope;
298 		ep.dest = xn->u.mailaddr;
299 		if (xn->parent) /* nodes with parent are forward addresses */
300 			ep.flags |= EF_INTERNAL;
301 		rule = ruleset_match(&ep);
302 		if (rule == NULL || rule->r_decision == R_REJECT) {
303 			lks->error = (errno == EAGAIN) ?
304 			    LKA_TEMPFAIL : LKA_PERMFAIL;
305 			break;
306 		}
307 
308 		xn->mapping = rule->r_mapping;
309 		xn->userbase = rule->r_userbase;
310 
311 		if (rule->r_action == A_RELAY || rule->r_action == A_RELAYVIA) {
312 			lka_submit(lks, rule, xn);
313 		}
314 		else if (rule->r_desttype == DEST_VDOM) {
315 			/* expand */
316 			lks->expand.rule = rule;
317 			lks->expand.parent = xn;
318 			lks->expand.alias = 1;
319 
320 			/* temporary replace the mailaddr with a copy where
321 			 * we eventually strip the '+'-part before lookup.
322 			 */
323 			maddr = xn->u.mailaddr;
324 			xlowercase(maddr.user, xn->u.mailaddr.user,
325 			    sizeof maddr.user);
326 			r = aliases_virtual_get(&lks->expand, &maddr);
327 			if (r == -1) {
328 				lks->error = LKA_TEMPFAIL;
329 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
330 				    "error in virtual alias lookup");
331 			}
332 			else if (r == 0) {
333 				lks->error = LKA_PERMFAIL;
334 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
335 				    "no aliases for virtual");
336 			}
337 		}
338 		else {
339 			lks->expand.rule = rule;
340 			lks->expand.parent = xn;
341 			lks->expand.alias = 1;
342 			memset(&node, 0, sizeof node);
343 			node.type = EXPAND_USERNAME;
344 			xlowercase(node.u.user, xn->u.mailaddr.user,
345 			    sizeof node.u.user);
346 			node.mapping = rule->r_mapping;
347 			node.userbase = rule->r_userbase;
348 			expand_insert(&lks->expand, &node);
349 		}
350 		break;
351 
352 	case EXPAND_USERNAME:
353 		log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s "
354 		    "[depth=%d]", xn->u.user, xn->depth);
355 
356 		if (xn->sameuser) {
357 			log_trace(TRACE_EXPAND, "expand: lka_expand: same "
358 			    "user, submitting");
359 			lka_submit(lks, rule, xn);
360 			break;
361 		}
362 
363 		/* expand aliases with the given rule */
364 		lks->expand.rule = rule;
365 		lks->expand.parent = xn;
366 		lks->expand.alias = 1;
367 		xn->mapping = rule->r_mapping;
368 		xn->userbase = rule->r_userbase;
369 		if (rule->r_mapping) {
370 			r = aliases_get(&lks->expand, xn->u.user);
371 			if (r == -1) {
372 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
373 				    "error in alias lookup");
374 				lks->error = LKA_TEMPFAIL;
375 			}
376 			if (r)
377 				break;
378 		}
379 
380 		/* gilles+hackers@ -> gilles@ */
381 		if ((tag = strchr(xn->u.user, *env->sc_subaddressing_delim)) != NULL)
382 			*tag++ = '\0';
383 
384 		r = table_lookup(rule->r_userbase, NULL, xn->u.user, K_USERINFO, &lk);
385 		if (r == -1) {
386 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
387 			    "backend error while searching user");
388 			lks->error = LKA_TEMPFAIL;
389 			break;
390 		}
391 		if (r == 0) {
392 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
393 			    "user-part does not match system user");
394 			lks->error = LKA_PERMFAIL;
395 			break;
396 		}
397 
398 		/* no aliases found, query forward file */
399 		lks->rule = rule;
400 		lks->node = xn;
401 
402 		memset(&fwreq, 0, sizeof(fwreq));
403 		fwreq.id = lks->id;
404 		(void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user));
405 		(void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory));
406 		fwreq.uid = lk.userinfo.uid;
407 		fwreq.gid = lk.userinfo.gid;
408 
409 		m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1,
410 		    &fwreq, sizeof(fwreq));
411 		lks->flags |= F_WAITING;
412 		break;
413 
414 	case EXPAND_FILENAME:
415 		if (rule->r_forwardonly) {
416 			log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule");
417 			lks->error = LKA_TEMPFAIL;
418 			break;
419 		}
420 		log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s "
421 		    "[depth=%d]", xn->u.buffer, xn->depth);
422 		lka_submit(lks, rule, xn);
423 		break;
424 
425 	case EXPAND_ERROR:
426 		if (rule->r_forwardonly) {
427 			log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule");
428 			lks->error = LKA_TEMPFAIL;
429 			break;
430 		}
431 		log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s "
432 		    "[depth=%d]", xn->u.buffer, xn->depth);
433 		if (xn->u.buffer[0] == '4')
434 			lks->error = LKA_TEMPFAIL;
435 		else if (xn->u.buffer[0] == '5')
436 			lks->error = LKA_PERMFAIL;
437 		lks->errormsg = xn->u.buffer;
438 		break;
439 
440 	case EXPAND_FILTER:
441 		if (rule->r_forwardonly) {
442 			log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule");
443 			lks->error = LKA_TEMPFAIL;
444 			break;
445 		}
446 		log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s "
447 		    "[depth=%d]", xn->u.buffer, xn->depth);
448 		lka_submit(lks, rule, xn);
449 		break;
450 
451 	case EXPAND_MAILDIR:
452 		log_trace(TRACE_EXPAND, "expand: lka_expand: maildir: %s "
453 		    "[depth=%d]", xn->u.buffer, xn->depth);
454 		r = table_lookup(rule->r_userbase, NULL,
455 		    xn->parent->u.user, K_USERINFO, &lk);
456 		if (r == -1) {
457 			log_trace(TRACE_EXPAND, "expand: lka_expand: maildir: "
458 			    "backend error while searching user");
459 			lks->error = LKA_TEMPFAIL;
460 			break;
461 		}
462 		if (r == 0) {
463 			log_trace(TRACE_EXPAND, "expand: lka_expand: maildir: "
464 			    "user-part does not match system user");
465 			lks->error = LKA_PERMFAIL;
466 			break;
467 		}
468 
469 		lka_submit(lks, rule, xn);
470 		break;
471 	}
472 }
473 
474 static struct expandnode *
475 lka_find_ancestor(struct expandnode *xn, enum expand_type type)
476 {
477 	while (xn && (xn->type != type))
478 		xn = xn->parent;
479 	if (xn == NULL) {
480 		log_warnx("warn: lka_find_ancestor: no ancestors of type %d",
481 		    type);
482 		fatalx(NULL);
483 	}
484 	return (xn);
485 }
486 
487 static void
488 lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
489 {
490 	union lookup		 lk;
491 	struct envelope		*ep;
492 	struct expandnode	*xn2;
493 	int			 r;
494 
495 	ep = xmemdup(&lks->envelope, sizeof *ep, "lka_submit");
496 	ep->expire = rule->r_qexpire;
497 
498 	switch (rule->r_action) {
499 	case A_RELAY:
500 	case A_RELAYVIA:
501 		if (xn->type != EXPAND_ADDRESS)
502 			fatalx("lka_deliver: expect address");
503 		ep->type = D_MTA;
504 		ep->dest = xn->u.mailaddr;
505 		ep->agent.mta.relay = rule->r_value.relayhost;
506 
507 		/* only rewrite if not a bounce */
508 		if (ep->sender.user[0] && rule->r_as && rule->r_as->user[0])
509 			(void)strlcpy(ep->sender.user, rule->r_as->user,
510 			    sizeof ep->sender.user);
511 		if (ep->sender.user[0] && rule->r_as && rule->r_as->domain[0])
512 			(void)strlcpy(ep->sender.domain, rule->r_as->domain,
513 			    sizeof ep->sender.domain);
514 		break;
515 	case A_NONE:
516 	case A_MBOX:
517 	case A_MAILDIR:
518 	case A_FILENAME:
519 	case A_MDA:
520 	case A_LMTP:
521 		ep->type = D_MDA;
522 		ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr;
523 
524 		/* set username */
525 		if ((xn->type == EXPAND_FILTER || xn->type == EXPAND_FILENAME)
526 		    && xn->alias) {
527 			(void)strlcpy(ep->agent.mda.username, SMTPD_USER,
528 			    sizeof(ep->agent.mda.username));
529 		}
530 		else {
531 			xn2 = lka_find_ancestor(xn, EXPAND_USERNAME);
532 			(void)strlcpy(ep->agent.mda.username, xn2->u.user,
533 			    sizeof(ep->agent.mda.username));
534 		}
535 
536 		r = table_lookup(rule->r_userbase, NULL, ep->agent.mda.username,
537 		    K_USERINFO, &lk);
538 		if (r <= 0) {
539 			lks->error = (r == -1) ? LKA_TEMPFAIL : LKA_PERMFAIL;
540 			free(ep);
541 			return;
542 		}
543 		(void)strlcpy(ep->agent.mda.usertable, rule->r_userbase->t_name,
544 		    sizeof ep->agent.mda.usertable);
545 		(void)strlcpy(ep->agent.mda.username, lk.userinfo.username,
546 		    sizeof ep->agent.mda.username);
547 		strlcpy(ep->agent.mda.delivery_user, rule->r_delivery_user,
548 		    sizeof ep->agent.mda.delivery_user);
549 
550 		if (xn->type == EXPAND_FILENAME) {
551 			ep->agent.mda.method = A_FILENAME;
552 			(void)strlcpy(ep->agent.mda.buffer, xn->u.buffer,
553 			    sizeof ep->agent.mda.buffer);
554 		}
555 		else if (xn->type == EXPAND_FILTER) {
556 			ep->agent.mda.method = A_MDA;
557 			(void)strlcpy(ep->agent.mda.buffer, xn->u.buffer,
558 			    sizeof ep->agent.mda.buffer);
559 		}
560 		else if (xn->type == EXPAND_USERNAME) {
561 			ep->agent.mda.method = rule->r_action;
562 			(void)strlcpy(ep->agent.mda.buffer, rule->r_value.buffer,
563 			    sizeof ep->agent.mda.buffer);
564 		}
565 		else if (xn->type == EXPAND_MAILDIR) {
566 			ep->agent.mda.method = A_MAILDIR;
567 			(void)strlcpy(ep->agent.mda.buffer, xn->u.buffer,
568 			    sizeof ep->agent.mda.buffer);
569 		}
570 		else
571 			fatalx("lka_deliver: bad node type");
572 
573 		r = lka_expand_format(ep->agent.mda.buffer,
574 		    sizeof(ep->agent.mda.buffer), ep, &lk.userinfo);
575 		if (!r) {
576 			lks->error = LKA_TEMPFAIL;
577 			log_warnx("warn: format string error while"
578 			    " expanding for user %s", ep->agent.mda.username);
579 			free(ep);
580 			return;
581 		}
582 		break;
583 	default:
584 		fatalx("lka_submit: bad rule action");
585 	}
586 
587 	TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry);
588 }
589 
590 
591 static size_t
592 lka_expand_token(char *dest, size_t len, const char *token,
593     const struct envelope *ep, const struct userinfo *ui)
594 {
595 	char		rtoken[MAXTOKENLEN];
596 	char		tmp[EXPAND_BUFFER];
597 	const char     *string;
598 	char	       *lbracket, *rbracket, *content, *sep, *mods;
599 	ssize_t		i;
600 	ssize_t		begoff, endoff;
601 	const char     *errstr = NULL;
602 	int		replace = 1;
603 	int		raw = 0;
604 
605 	begoff = 0;
606 	endoff = EXPAND_BUFFER;
607 	mods = NULL;
608 
609 	if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken)
610 		return 0;
611 
612 	/* token[x[:y]] -> extracts optional x and y, converts into offsets */
613 	if ((lbracket = strchr(rtoken, '[')) &&
614 	    (rbracket = strchr(rtoken, ']'))) {
615 		/* ] before [ ... or empty */
616 		if (rbracket < lbracket || rbracket - lbracket <= 1)
617 			return 0;
618 
619 		*lbracket = *rbracket = '\0';
620 		 content  = lbracket + 1;
621 
622 		 if ((sep = strchr(content, ':')) == NULL)
623 			 endoff = begoff = strtonum(content, -EXPAND_BUFFER,
624 			     EXPAND_BUFFER, &errstr);
625 		 else {
626 			 *sep = '\0';
627 			 if (content != sep)
628 				 begoff = strtonum(content, -EXPAND_BUFFER,
629 				     EXPAND_BUFFER, &errstr);
630 			 if (*(++sep)) {
631 				 if (errstr == NULL)
632 					 endoff = strtonum(sep, -EXPAND_BUFFER,
633 					     EXPAND_BUFFER, &errstr);
634 			 }
635 		 }
636 		 if (errstr)
637 			 return 0;
638 
639 		 /* token:mod_1,mod_2,mod_n -> extract modifiers */
640 		 mods = strchr(rbracket + 1, ':');
641 	} else {
642 		if ((mods = strchr(rtoken, ':')) != NULL)
643 			*mods++ = '\0';
644 	}
645 
646 	/* token -> expanded token */
647 	if (!strcasecmp("sender", rtoken)) {
648 		if (snprintf(tmp, sizeof tmp, "%s@%s",
649 			ep->sender.user, ep->sender.domain) >= (int)sizeof tmp)
650 			return 0;
651 		string = tmp;
652 	}
653 	else if (!strcasecmp("dest", rtoken)) {
654 		if (snprintf(tmp, sizeof tmp, "%s@%s",
655 			ep->dest.user, ep->dest.domain) >= (int)sizeof tmp)
656 			return 0;
657 		string = tmp;
658 	}
659 	else if (!strcasecmp("rcpt", rtoken)) {
660 		if (snprintf(tmp, sizeof tmp, "%s@%s",
661 			ep->rcpt.user, ep->rcpt.domain) >= (int)sizeof tmp)
662 			return 0;
663 		string = tmp;
664 	}
665 	else if (!strcasecmp("sender.user", rtoken))
666 		string = ep->sender.user;
667 	else if (!strcasecmp("sender.domain", rtoken))
668 		string = ep->sender.domain;
669 	else if (!strcasecmp("user.username", rtoken))
670 		string = ui->username;
671 	else if (!strcasecmp("user.directory", rtoken)) {
672 		string = ui->directory;
673 		replace = 0;
674 	}
675 	else if (!strcasecmp("dest.user", rtoken))
676 		string = ep->dest.user;
677 	else if (!strcasecmp("dest.domain", rtoken))
678 		string = ep->dest.domain;
679 	else if (!strcasecmp("rcpt.user", rtoken))
680 		string = ep->rcpt.user;
681 	else if (!strcasecmp("rcpt.domain", rtoken))
682 		string = ep->rcpt.domain;
683 	else
684 		return 0;
685 
686 	if (string != tmp) {
687 		if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
688 			return 0;
689 		string = tmp;
690 	}
691 
692 	/*  apply modifiers */
693 	if (mods != NULL) {
694 		do {
695 			if ((sep = strchr(mods, '|')) != NULL)
696 				*sep++ = '\0';
697 			for (i = 0; (size_t)i < nitems(token_modifiers); ++i) {
698 				if (!strcasecmp(token_modifiers[i].name, mods)) {
699 					if (token_modifiers[i].f == NULL) {
700 						raw = 1;
701 						break;
702 					}
703 					if (!token_modifiers[i].f(tmp, sizeof tmp))
704 						return 0; /* modifier error */
705 					break;
706 				}
707 			}
708 			if ((size_t)i == nitems(token_modifiers))
709 				return 0; /* modifier not found */
710 		} while ((mods = sep) != NULL);
711 	}
712 
713 	if (!raw && replace)
714 		for (i = 0; (size_t)i < strlen(tmp); ++i)
715 			if (strchr(MAILADDR_ESCAPE, tmp[i]))
716 				tmp[i] = ':';
717 
718 	/* expanded string is empty */
719 	i = strlen(string);
720 	if (i == 0)
721 		return 0;
722 
723 	/* begin offset beyond end of string */
724 	if (begoff >= i)
725 		return 0;
726 
727 	/* end offset beyond end of string, make it end of string */
728 	if (endoff >= i)
729 		endoff = i - 1;
730 
731 	/* negative begin offset, make it relative to end of string */
732 	if (begoff < 0)
733 		begoff += i;
734 	/* negative end offset, make it relative to end of string,
735 	 * note that end offset is inclusive.
736 	 */
737 	if (endoff < 0)
738 		endoff += i - 1;
739 
740 	/* check that final offsets are valid */
741 	if (begoff < 0 || endoff < 0 || endoff < begoff)
742 		return 0;
743 	endoff += 1; /* end offset is inclusive */
744 
745 	/* check that substring does not exceed destination buffer length */
746 	i = endoff - begoff;
747 	if ((size_t)i + 1 >= len)
748 		return 0;
749 
750 	string += begoff;
751 	for (; i; i--) {
752 		*dest = (replace && *string == '/') ? ':' : *string;
753 		dest++;
754 		string++;
755 	}
756 
757 	return endoff - begoff;
758 }
759 
760 
761 static size_t
762 lka_expand_format(char *buf, size_t len, const struct envelope *ep,
763     const struct userinfo *ui)
764 {
765 	char		tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
766 	char		exptok[EXPAND_BUFFER];
767 	size_t		exptoklen;
768 	char		token[MAXTOKENLEN];
769 	size_t		ret, tmpret;
770 
771 	if (len < sizeof tmpbuf) {
772 		log_warnx("lka_expand_format: tmp buffer < rule buffer");
773 		return 0;
774 	}
775 
776 	memset(tmpbuf, 0, sizeof tmpbuf);
777 	pbuf = buf;
778 	ptmp = tmpbuf;
779 	ret = tmpret = 0;
780 
781 	/* special case: ~/ only allowed expanded at the beginning */
782 	if (strncmp(pbuf, "~/", 2) == 0) {
783 		tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory);
784 		if (tmpret >= sizeof tmpbuf) {
785 			log_warnx("warn: user directory for %s too large",
786 			    ui->directory);
787 			return 0;
788 		}
789 		ret  += tmpret;
790 		ptmp += tmpret;
791 		pbuf += 2;
792 	}
793 
794 
795 	/* expansion loop */
796 	for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) {
797 		if (*pbuf == '%' && *(pbuf + 1) == '%') {
798 			*ptmp++ = *pbuf++;
799 			pbuf  += 1;
800 			tmpret = 1;
801 			continue;
802 		}
803 
804 		if (*pbuf != '%' || *(pbuf + 1) != '{') {
805 			*ptmp++ = *pbuf++;
806 			tmpret = 1;
807 			continue;
808 		}
809 
810 		/* %{...} otherwise fail */
811 		if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL)
812 			return 0;
813 
814 		/* extract token from %{token} */
815 		if ((size_t)(ebuf - pbuf) - 1 >= sizeof token)
816 			return 0;
817 
818 		memcpy(token, pbuf+2, ebuf-pbuf-1);
819 		if (strchr(token, '}') == NULL)
820 			return 0;
821 		*strchr(token, '}') = '\0';
822 
823 		exptoklen = lka_expand_token(exptok, sizeof exptok, token, ep,
824 		    ui);
825 		if (exptoklen == 0)
826 			return 0;
827 
828 		/* writing expanded token at ptmp will overflow tmpbuf */
829 		if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= exptoklen)
830 			return 0;
831 
832 		memcpy(ptmp, exptok, exptoklen);
833 		pbuf   = ebuf + 1;
834 		ptmp  += exptoklen;
835 		tmpret = exptoklen;
836 	}
837 	if (ret >= sizeof tmpbuf)
838 		return 0;
839 
840 	if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
841 		return 0;
842 
843 	return ret;
844 }
845 
846 static int
847 mod_lowercase(char *buf, size_t len)
848 {
849 	char tmp[EXPAND_BUFFER];
850 
851 	if (!lowercase(tmp, buf, sizeof tmp))
852 		return 0;
853 	if (strlcpy(buf, tmp, len) >= len)
854 		return 0;
855 	return 1;
856 }
857 
858 static int
859 mod_uppercase(char *buf, size_t len)
860 {
861 	char tmp[EXPAND_BUFFER];
862 
863 	if (!uppercase(tmp, buf, sizeof tmp))
864 		return 0;
865 	if (strlcpy(buf, tmp, len) >= len)
866 		return 0;
867 	return 1;
868 }
869 
870 static int
871 mod_strip(char *buf, size_t len)
872 {
873 	char *tag, *at;
874 	unsigned int i;
875 
876 	/* gilles+hackers -> gilles */
877 	if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) {
878 		/* gilles+hackers@poolp.org -> gilles@poolp.org */
879 		if ((at = strchr(tag, '@')) != NULL) {
880 			for (i = 0; i <= strlen(at); ++i)
881 				tag[i] = at[i];
882 		} else
883 			*tag = '\0';
884 	}
885 	return 1;
886 }
887