xref: /openbsd/usr.sbin/smtpd/lka_session.c (revision a6445c1d)
1 /*	$OpenBSD: lka_session.c,v 1.68 2014/07/08 13:49:09 eric 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 
40 #include "smtpd.h"
41 #include "log.h"
42 
43 #define	EXPAND_DEPTH	10
44 
45 #define	F_WAITING	0x01
46 
47 struct lka_session {
48 	uint64_t		 id; /* given by smtp */
49 
50 	TAILQ_HEAD(, envelope)	 deliverylist;
51 	struct expand		 expand;
52 
53 	int			 flags;
54 	int			 error;
55 	const char		*errormsg;
56 	struct envelope		 envelope;
57 	struct xnodes		 nodes;
58 	/* waiting for fwdrq */
59 	struct rule		*rule;
60 	struct expandnode	*node;
61 };
62 
63 static void lka_expand(struct lka_session *, struct rule *,
64     struct expandnode *);
65 static void lka_submit(struct lka_session *, struct rule *,
66     struct expandnode *);
67 static void lka_resume(struct lka_session *);
68 static size_t lka_expand_format(char *, size_t, const struct envelope *,
69     const struct userinfo *);
70 static void mailaddr_to_username(const struct mailaddr *, char *, size_t);
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 
277 	if (xn->depth >= EXPAND_DEPTH) {
278 		log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep.");
279 		lks->error = LKA_PERMFAIL;
280 		return;
281 	}
282 
283 	switch (xn->type) {
284 	case EXPAND_INVALID:
285 	case EXPAND_INCLUDE:
286 		fatalx("lka_expand: unexpected type");
287 		break;
288 
289 	case EXPAND_ADDRESS:
290 
291 		log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s "
292 		    "[depth=%d]",
293 		    xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth);
294 
295 		/* Pass the node through the ruleset */
296 		ep = lks->envelope;
297 		ep.dest = xn->u.mailaddr;
298 		if (xn->parent) /* nodes with parent are forward addresses */
299 			ep.flags |= EF_INTERNAL;
300 		rule = ruleset_match(&ep);
301 		if (rule == NULL || rule->r_decision == R_REJECT) {
302 			lks->error = (errno == EAGAIN) ?
303 			    LKA_TEMPFAIL : LKA_PERMFAIL;
304 			break;
305 		}
306 
307 		xn->mapping = rule->r_mapping;
308 		xn->userbase = rule->r_userbase;
309 
310 		if (rule->r_action == A_RELAY || rule->r_action == A_RELAYVIA) {
311 			lka_submit(lks, rule, xn);
312 		}
313 		else if (rule->r_desttype == DEST_VDOM) {
314 			/* expand */
315 			lks->expand.rule = rule;
316 			lks->expand.parent = xn;
317 			lks->expand.alias = 1;
318 
319 			/* temporary replace the mailaddr with a copy where
320 			 * we eventually strip the '+'-part before lookup.
321 			 */
322 			maddr = xn->u.mailaddr;
323 			mailaddr_to_username(&xn->u.mailaddr, maddr.user,
324 			    sizeof maddr.user);
325 
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 			mailaddr_to_username(&xn->u.mailaddr, node.u.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 		/* A username should not exceed the size of a system user */
381 		if (strlen(xn->u.user) >= sizeof fwreq.user) {
382 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
383 			    "user-part too long to be a system user");
384 			lks->error = LKA_PERMFAIL;
385 			break;
386 		}
387 
388 		r = table_lookup(rule->r_userbase, NULL, xn->u.user, K_USERINFO, &lk);
389 		if (r == -1) {
390 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
391 			    "backend error while searching user");
392 			lks->error = LKA_TEMPFAIL;
393 			break;
394 		}
395 		if (r == 0) {
396 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
397 			    "user-part does not match system user");
398 			lks->error = LKA_PERMFAIL;
399 			break;
400 		}
401 
402 		/* no aliases found, query forward file */
403 		lks->rule = rule;
404 		lks->node = xn;
405 
406 		memset(&fwreq, 0, sizeof(fwreq));
407 		fwreq.id = lks->id;
408 		(void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user));
409 		(void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory));
410 		fwreq.uid = lk.userinfo.uid;
411 		fwreq.gid = lk.userinfo.gid;
412 
413 		m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1,
414 		    &fwreq, sizeof(fwreq));
415 		lks->flags |= F_WAITING;
416 		break;
417 
418 	case EXPAND_FILENAME:
419 		if (rule->r_forwardonly) {
420 			log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule");
421 			lks->error = LKA_TEMPFAIL;
422 			break;
423 		}
424 		log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s "
425 		    "[depth=%d]", xn->u.buffer, xn->depth);
426 		lka_submit(lks, rule, xn);
427 		break;
428 
429 	case EXPAND_ERROR:
430 		if (rule->r_forwardonly) {
431 			log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule");
432 			lks->error = LKA_TEMPFAIL;
433 			break;
434 		}
435 		log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s "
436 		    "[depth=%d]", xn->u.buffer, xn->depth);
437 		if (xn->u.buffer[0] == '4')
438 			lks->error = LKA_TEMPFAIL;
439 		else if (xn->u.buffer[0] == '5')
440 			lks->error = LKA_PERMFAIL;
441 		lks->errormsg = xn->u.buffer;
442 		break;
443 
444 	case EXPAND_FILTER:
445 		if (rule->r_forwardonly) {
446 			log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule");
447 			lks->error = LKA_TEMPFAIL;
448 			break;
449 		}
450 		log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s "
451 		    "[depth=%d]", xn->u.buffer, xn->depth);
452 		lka_submit(lks, rule, xn);
453 		break;
454 	}
455 }
456 
457 static struct expandnode *
458 lka_find_ancestor(struct expandnode *xn, enum expand_type type)
459 {
460 	while (xn && (xn->type != type))
461 		xn = xn->parent;
462 	if (xn == NULL) {
463 		log_warnx("warn: lka_find_ancestor: no ancestors of type %d",
464 		    type);
465 		fatalx(NULL);
466 	}
467 	return (xn);
468 }
469 
470 static void
471 lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
472 {
473 	union lookup		 lk;
474 	struct envelope		*ep;
475 	struct expandnode	*xn2;
476 	int			 r;
477 
478 	ep = xmemdup(&lks->envelope, sizeof *ep, "lka_submit");
479 	ep->expire = rule->r_qexpire;
480 
481 	switch (rule->r_action) {
482 	case A_RELAY:
483 	case A_RELAYVIA:
484 		if (xn->type != EXPAND_ADDRESS)
485 			fatalx("lka_deliver: expect address");
486 		ep->type = D_MTA;
487 		ep->dest = xn->u.mailaddr;
488 		ep->agent.mta.relay = rule->r_value.relayhost;
489 
490 		/* only rewrite if not a bounce */
491 		if (ep->sender.user[0] && rule->r_as && rule->r_as->user[0])
492 			(void)strlcpy(ep->sender.user, rule->r_as->user,
493 			    sizeof ep->sender.user);
494 		if (ep->sender.user[0] && rule->r_as && rule->r_as->domain[0])
495 			(void)strlcpy(ep->sender.domain, rule->r_as->domain,
496 			    sizeof ep->sender.domain);
497 		break;
498 	case A_NONE:
499 	case A_MBOX:
500 	case A_MAILDIR:
501 	case A_FILENAME:
502 	case A_MDA:
503 	case A_LMTP:
504 		ep->type = D_MDA;
505 		ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr;
506 
507 		/* set username */
508 		if ((xn->type == EXPAND_FILTER || xn->type == EXPAND_FILENAME)
509 		    && xn->alias) {
510 			(void)strlcpy(ep->agent.mda.username, SMTPD_USER,
511 			    sizeof(ep->agent.mda.username));
512 		}
513 		else {
514 			xn2 = lka_find_ancestor(xn, EXPAND_USERNAME);
515 			(void)strlcpy(ep->agent.mda.username, xn2->u.user,
516 			    sizeof(ep->agent.mda.username));
517 		}
518 
519 		r = table_lookup(rule->r_userbase, NULL, ep->agent.mda.username,
520 		    K_USERINFO, &lk);
521 		if (r <= 0) {
522 			lks->error = (r == -1) ? LKA_TEMPFAIL : LKA_PERMFAIL;
523 			free(ep);
524 			return;
525 		}
526 		(void)strlcpy(ep->agent.mda.usertable, rule->r_userbase->t_name,
527 		    sizeof ep->agent.mda.usertable);
528 		(void)strlcpy(ep->agent.mda.username, lk.userinfo.username,
529 		    sizeof ep->agent.mda.username);
530 
531 		if (xn->type == EXPAND_FILENAME) {
532 			ep->agent.mda.method = A_FILENAME;
533 			(void)strlcpy(ep->agent.mda.buffer, xn->u.buffer,
534 			    sizeof ep->agent.mda.buffer);
535 		}
536 		else if (xn->type == EXPAND_FILTER) {
537 			ep->agent.mda.method = A_MDA;
538 			(void)strlcpy(ep->agent.mda.buffer, xn->u.buffer,
539 			    sizeof ep->agent.mda.buffer);
540 		}
541 		else if (xn->type == EXPAND_USERNAME) {
542 			ep->agent.mda.method = rule->r_action;
543 			(void)strlcpy(ep->agent.mda.buffer, rule->r_value.buffer,
544 			    sizeof ep->agent.mda.buffer);
545 		}
546 		else
547 			fatalx("lka_deliver: bad node type");
548 
549 		r = lka_expand_format(ep->agent.mda.buffer,
550 		    sizeof(ep->agent.mda.buffer), ep, &lk.userinfo);
551 		if (!r) {
552 			lks->error = LKA_TEMPFAIL;
553 			log_warnx("warn: format string error while"
554 			    " expanding for user %s", ep->agent.mda.username);
555 			free(ep);
556 			return;
557 		}
558 		break;
559 	default:
560 		fatalx("lka_submit: bad rule action");
561 	}
562 
563 	TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry);
564 }
565 
566 
567 static size_t
568 lka_expand_token(char *dest, size_t len, const char *token,
569     const struct envelope *ep, const struct userinfo *ui)
570 {
571 	char		rtoken[MAXTOKENLEN];
572 	char		tmp[EXPAND_BUFFER];
573 	const char     *string;
574 	char	       *lbracket, *rbracket, *content, *sep, *mods;
575 	ssize_t		i;
576 	ssize_t		begoff, endoff;
577 	const char     *errstr = NULL;
578 	int		replace = 1;
579 	int		raw = 0;
580 
581 	begoff = 0;
582 	endoff = EXPAND_BUFFER;
583 	mods = NULL;
584 
585 	if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken)
586 		return 0;
587 
588 	/* token[x[:y]] -> extracts optional x and y, converts into offsets */
589 	if ((lbracket = strchr(rtoken, '[')) &&
590 	    (rbracket = strchr(rtoken, ']'))) {
591 		/* ] before [ ... or empty */
592 		if (rbracket < lbracket || rbracket - lbracket <= 1)
593 			return 0;
594 
595 		*lbracket = *rbracket = '\0';
596 		 content  = lbracket + 1;
597 
598 		 if ((sep = strchr(content, ':')) == NULL)
599 			 endoff = begoff = strtonum(content, -EXPAND_BUFFER,
600 			     EXPAND_BUFFER, &errstr);
601 		 else {
602 			 *sep = '\0';
603 			 if (content != sep)
604 				 begoff = strtonum(content, -EXPAND_BUFFER,
605 				     EXPAND_BUFFER, &errstr);
606 			 if (*(++sep)) {
607 				 if (errstr == NULL)
608 					 endoff = strtonum(sep, -EXPAND_BUFFER,
609 					     EXPAND_BUFFER, &errstr);
610 			 }
611 		 }
612 		 if (errstr)
613 			 return 0;
614 
615 		 /* token:mod_1,mod_2,mod_n -> extract modifiers */
616 		 mods = strchr(rbracket + 1, ':');
617 	} else {
618 		if ((mods = strchr(rtoken, ':')) != NULL)
619 			*mods++ = '\0';
620 	}
621 
622 	/* token -> expanded token */
623 	if (! strcasecmp("sender", rtoken)) {
624 		if (snprintf(tmp, sizeof tmp, "%s@%s",
625 			ep->sender.user, ep->sender.domain) <= 0)
626 			return 0;
627 		string = tmp;
628 	}
629 	else if (! strcasecmp("dest", rtoken)) {
630 		if (snprintf(tmp, sizeof tmp, "%s@%s",
631 			ep->dest.user, ep->dest.domain) <= 0)
632 			return 0;
633 		string = tmp;
634 	}
635 	else if (! strcasecmp("rcpt", rtoken)) {
636 		if (snprintf(tmp, sizeof tmp, "%s@%s",
637 			ep->rcpt.user, ep->rcpt.domain) <= 0)
638 			return 0;
639 		string = tmp;
640 	}
641 	else if (! strcasecmp("sender.user", rtoken))
642 		string = ep->sender.user;
643 	else if (! strcasecmp("sender.domain", rtoken))
644 		string = ep->sender.domain;
645 	else if (! strcasecmp("user.username", rtoken))
646 		string = ui->username;
647 	else if (! strcasecmp("user.directory", rtoken)) {
648 		string = ui->directory;
649 		replace = 0;
650 	}
651 	else if (! strcasecmp("dest.user", rtoken))
652 		string = ep->dest.user;
653 	else if (! strcasecmp("dest.domain", rtoken))
654 		string = ep->dest.domain;
655 	else if (! strcasecmp("rcpt.user", rtoken))
656 		string = ep->rcpt.user;
657 	else if (! strcasecmp("rcpt.domain", rtoken))
658 		string = ep->rcpt.domain;
659 	else
660 		return 0;
661 
662 	if (string != tmp) {
663 		if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
664 			return 0;
665 		string = tmp;
666 	}
667 
668 	/*  apply modifiers */
669 	if (mods != NULL) {
670 		do {
671 			if ((sep = strchr(mods, '|')) != NULL)
672 				*sep++ = '\0';
673 			for (i = 0; (size_t)i < nitems(token_modifiers); ++i) {
674 				if (! strcasecmp(token_modifiers[i].name, mods)) {
675 					if (token_modifiers[i].f == NULL) {
676 						raw = 1;
677 						break;
678 					}
679 					if (! token_modifiers[i].f(tmp, sizeof tmp))
680 						return 0; /* modifier error */
681 					break;
682 				}
683 			}
684 			if ((size_t)i == nitems(token_modifiers))
685 				return 0; /* modifier not found */
686 		} while ((mods = sep) != NULL);
687 	}
688 
689 	if (! raw && replace)
690 		for (i = 0; (size_t)i < strlen(tmp); ++i)
691 			if (strchr(MAILADDR_ESCAPE, tmp[i]))
692 				tmp[i] = ':';
693 
694 	/* expanded string is empty */
695 	i = strlen(string);
696 	if (i == 0)
697 		return 0;
698 
699 	/* begin offset beyond end of string */
700 	if (begoff >= i)
701 		return 0;
702 
703 	/* end offset beyond end of string, make it end of string */
704 	if (endoff >= i)
705 		endoff = i - 1;
706 
707 	/* negative begin offset, make it relative to end of string */
708 	if (begoff < 0)
709 		begoff += i;
710 	/* negative end offset, make it relative to end of string,
711 	 * note that end offset is inclusive.
712 	 */
713 	if (endoff < 0)
714 		endoff += i - 1;
715 
716 	/* check that final offsets are valid */
717 	if (begoff < 0 || endoff < 0 || endoff < begoff)
718 		return 0;
719 	endoff += 1; /* end offset is inclusive */
720 
721 	/* check that substring does not exceed destination buffer length */
722 	i = endoff - begoff;
723 	if ((size_t)i + 1 >= len)
724 		return 0;
725 
726 	string += begoff;
727 	for (; i; i--) {
728 		*dest = (replace && *string == '/') ? ':' : *string;
729 		dest++;
730 		string++;
731 	}
732 
733 	return endoff - begoff;
734 }
735 
736 
737 static size_t
738 lka_expand_format(char *buf, size_t len, const struct envelope *ep,
739     const struct userinfo *ui)
740 {
741 	char		tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
742 	char		exptok[EXPAND_BUFFER];
743 	size_t		exptoklen;
744 	char		token[MAXTOKENLEN];
745 	size_t		ret, tmpret;
746 
747 	if (len < sizeof tmpbuf)
748 		fatalx("lka_expand_format: tmp buffer < rule buffer");
749 
750 	memset(tmpbuf, 0, sizeof tmpbuf);
751 	pbuf = buf;
752 	ptmp = tmpbuf;
753 	ret = tmpret = 0;
754 
755 	/* special case: ~/ only allowed expanded at the beginning */
756 	if (strncmp(pbuf, "~/", 2) == 0) {
757 		tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory);
758 		if (tmpret >= sizeof tmpbuf) {
759 			log_warnx("warn: user directory for %s too large",
760 			    ui->directory);
761 			return 0;
762 		}
763 		ret  += tmpret;
764 		ptmp += tmpret;
765 		pbuf += 2;
766 	}
767 
768 
769 	/* expansion loop */
770 	for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) {
771 		if (*pbuf == '%' && *(pbuf + 1) == '%') {
772 			*ptmp++ = *pbuf++;
773 			pbuf  += 1;
774 			tmpret = 1;
775 			continue;
776 		}
777 
778 		if (*pbuf != '%' || *(pbuf + 1) != '{') {
779 			*ptmp++ = *pbuf++;
780 			tmpret = 1;
781 			continue;
782 		}
783 
784 		/* %{...} otherwise fail */
785 		if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL)
786 			return 0;
787 
788 		/* extract token from %{token} */
789 		if ((size_t)(ebuf - pbuf) - 1 >= sizeof token)
790 			return 0;
791 
792 		memcpy(token, pbuf+2, ebuf-pbuf-1);
793 		if (strchr(token, '}') == NULL)
794 			return 0;
795 		*strchr(token, '}') = '\0';
796 
797 		exptoklen = lka_expand_token(exptok, sizeof exptok, token, ep,
798 		    ui);
799 		if (exptoklen == 0)
800 			return 0;
801 
802 		memcpy(ptmp, exptok, exptoklen);
803 		pbuf   = ebuf + 1;
804 		ptmp  += exptoklen;
805 		tmpret = exptoklen;
806 	}
807 	if (ret >= sizeof tmpbuf)
808 		return 0;
809 
810 	if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
811 		return 0;
812 
813 	return ret;
814 }
815 
816 static void
817 mailaddr_to_username(const struct mailaddr *maddr, char *dst, size_t len)
818 {
819 	char	*tag;
820 
821 	xlowercase(dst, maddr->user, len);
822 
823 	/* gilles+hackers@ -> gilles@ */
824 	if ((tag = strchr(dst, TAG_CHAR)) != NULL)
825 		*tag++ = '\0';
826 }
827 
828 static int
829 mod_lowercase(char *buf, size_t len)
830 {
831 	char tmp[EXPAND_BUFFER];
832 
833 	if (! lowercase(tmp, buf, sizeof tmp))
834 		return 0;
835 	if (strlcpy(buf, tmp, len) >= len)
836 		return 0;
837 	return 1;
838 }
839 
840 static int
841 mod_uppercase(char *buf, size_t len)
842 {
843 	char tmp[EXPAND_BUFFER];
844 
845 	if (! uppercase(tmp, buf, sizeof tmp))
846 		return 0;
847 	if (strlcpy(buf, tmp, len) >= len)
848 		return 0;
849 	return 1;
850 }
851 
852 static int
853 mod_strip(char *buf, size_t len)
854 {
855 	char *tag, *at;
856 	unsigned int i;
857 
858 	/* gilles+hackers -> gilles */
859 	if ((tag = strchr(buf, TAG_CHAR)) != NULL) {
860 		/* gilles+hackers@poolp.org -> gilles@poolp.org */
861 		if ((at = strchr(tag, '@')) != NULL) {
862 			for (i = 0; i <= strlen(at); ++i)
863 				tag[i] = at[i];
864 		} else
865 			*tag = '\0';
866 	}
867 	return 1;
868 }
869