xref: /openbsd-src/usr.sbin/smtpd/lka_session.c (revision a95ad3cfedee581b2a15135113fbf81ea5c3867d)
1 /*	$OpenBSD: lka_session.c,v 1.100 2024/02/02 23:33:42 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 <errno.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "smtpd.h"
25 #include "log.h"
26 
27 #define	EXPAND_DEPTH	10
28 
29 #define	F_WAITING	0x01
30 
31 struct lka_session {
32 	uint64_t		 id; /* given by smtp */
33 
34 	TAILQ_HEAD(, envelope)	 deliverylist;
35 	struct expand		 expand;
36 
37 	int			 flags;
38 	int			 error;
39 	const char		*errormsg;
40 	struct envelope		 envelope;
41 	struct xnodes		 nodes;
42 	/* waiting for fwdrq */
43 	struct rule		*rule;
44 	struct expandnode	*node;
45 };
46 
47 static void lka_expand(struct lka_session *, struct rule *,
48     struct expandnode *);
49 static void lka_submit(struct lka_session *, struct rule *,
50     struct expandnode *);
51 static void lka_resume(struct lka_session *);
52 
53 static int		init;
54 static struct tree	sessions;
55 
56 void
lka_session(uint64_t id,struct envelope * envelope)57 lka_session(uint64_t id, struct envelope *envelope)
58 {
59 	struct lka_session	*lks;
60 	struct expandnode	 xn;
61 
62 	if (init == 0) {
63 		init = 1;
64 		tree_init(&sessions);
65 	}
66 
67 	lks = xcalloc(1, sizeof(*lks));
68 	lks->id = id;
69 	RB_INIT(&lks->expand.tree);
70 	TAILQ_INIT(&lks->deliverylist);
71 	tree_xset(&sessions, lks->id, lks);
72 
73 	lks->envelope = *envelope;
74 
75 	TAILQ_INIT(&lks->nodes);
76 	memset(&xn, 0, sizeof xn);
77 	xn.type = EXPAND_ADDRESS;
78 	xn.u.mailaddr = lks->envelope.rcpt;
79 	lks->expand.parent = NULL;
80 	lks->expand.rule = NULL;
81 	lks->expand.queue = &lks->nodes;
82 	expand_insert(&lks->expand, &xn);
83 	lka_resume(lks);
84 }
85 
86 void
lka_session_forward_reply(struct forward_req * fwreq,int fd)87 lka_session_forward_reply(struct forward_req *fwreq, int fd)
88 {
89 	struct lka_session     *lks;
90 	struct dispatcher      *dsp;
91 	struct rule	       *rule;
92 	struct expandnode      *xn;
93 	int			ret;
94 
95 	lks = tree_xget(&sessions, fwreq->id);
96 	xn = lks->node;
97 	rule = lks->rule;
98 
99 	lks->flags &= ~F_WAITING;
100 
101 	switch (fwreq->status) {
102 	case 0:
103 		/* permanent failure while lookup ~/.forward */
104 		log_trace(TRACE_EXPAND, "expand: ~/.forward failed for user %s",
105 		    fwreq->user);
106 		lks->error = LKA_PERMFAIL;
107 		break;
108 	case 1:
109 		if (fd == -1) {
110 			dsp = dict_get(env->sc_dispatchers, lks->rule->dispatcher);
111 			if (dsp->u.local.forward_only) {
112 				log_trace(TRACE_EXPAND, "expand: no .forward "
113 				    "for user %s on forward-only rule", fwreq->user);
114 				lks->error = LKA_TEMPFAIL;
115 			}
116 			else if (dsp->u.local.expand_only) {
117 				log_trace(TRACE_EXPAND, "expand: no .forward "
118 				    "for user %s and no default action on rule", fwreq->user);
119 				lks->error = LKA_PERMFAIL;
120 			}
121 			else {
122 				log_trace(TRACE_EXPAND, "expand: no .forward for "
123 				    "user %s, just deliver", fwreq->user);
124 				lka_submit(lks, rule, xn);
125 			}
126 		}
127 		else {
128 			dsp = dict_get(env->sc_dispatchers, rule->dispatcher);
129 
130 			/* expand for the current user and rule */
131 			lks->expand.rule = rule;
132 			lks->expand.parent = xn;
133 
134 			/* forwards_get() will close the descriptor no matter what */
135 			ret = forwards_get(fd, &lks->expand);
136 			if (ret == -1) {
137 				log_trace(TRACE_EXPAND, "expand: temporary "
138 				    "forward error for user %s", fwreq->user);
139 				lks->error = LKA_TEMPFAIL;
140 			}
141 			else if (ret == 0) {
142 				if (dsp->u.local.forward_only) {
143 					log_trace(TRACE_EXPAND, "expand: empty .forward "
144 					    "for user %s on forward-only rule", fwreq->user);
145 					lks->error = LKA_TEMPFAIL;
146 				}
147 				else if (dsp->u.local.expand_only) {
148 					log_trace(TRACE_EXPAND, "expand: empty .forward "
149 					    "for user %s and no default action on rule", fwreq->user);
150 					lks->error = LKA_PERMFAIL;
151 				}
152 				else {
153 					log_trace(TRACE_EXPAND, "expand: empty .forward "
154 					    "for user %s, just deliver", fwreq->user);
155 					lka_submit(lks, rule, xn);
156 				}
157 			}
158 		}
159 		break;
160 	default:
161 		/* temporary failure while looking up ~/.forward */
162 		lks->error = LKA_TEMPFAIL;
163 	}
164 
165 	if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL)
166 		lks->errormsg = "424 4.2.4 Mailing list expansion problem";
167 	if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL)
168 		lks->errormsg = "524 5.2.4 Mailing list expansion problem";
169 
170 	lka_resume(lks);
171 }
172 
173 static void
lka_resume(struct lka_session * lks)174 lka_resume(struct lka_session *lks)
175 {
176 	struct envelope		*ep;
177 	struct expandnode	*xn;
178 
179 	if (lks->error)
180 		goto error;
181 
182 	/* pop next node and expand it */
183 	while ((xn = TAILQ_FIRST(&lks->nodes))) {
184 		TAILQ_REMOVE(&lks->nodes, xn, tq_entry);
185 		lka_expand(lks, xn->rule, xn);
186 		if (lks->flags & F_WAITING)
187 			return;
188 		if (lks->error)
189 			goto error;
190 	}
191 
192 	/* delivery list is empty, reject */
193 	if (TAILQ_FIRST(&lks->deliverylist) == NULL) {
194 		log_trace(TRACE_EXPAND, "expand: lka_done: expanded to empty "
195 		    "delivery list");
196 		lks->error = LKA_PERMFAIL;
197 		lks->errormsg = "524 5.2.4 Mailing list expansion problem";
198 	}
199     error:
200 	if (lks->error) {
201 		m_create(p_dispatcher, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1);
202 		m_add_id(p_dispatcher, lks->id);
203 		m_add_int(p_dispatcher, lks->error);
204 
205 		if (lks->errormsg)
206 			m_add_string(p_dispatcher, lks->errormsg);
207 		else {
208 			if (lks->error == LKA_PERMFAIL)
209 				m_add_string(p_dispatcher, "550 Invalid recipient");
210 			else if (lks->error == LKA_TEMPFAIL)
211 				m_add_string(p_dispatcher, "451 Temporary failure");
212 		}
213 
214 		m_close(p_dispatcher);
215 		while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) {
216 			TAILQ_REMOVE(&lks->deliverylist, ep, entry);
217 			free(ep);
218 		}
219 	}
220 	else {
221 		/* Process the delivery list and submit envelopes to queue */
222 		while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) {
223 			TAILQ_REMOVE(&lks->deliverylist, ep, entry);
224 			m_create(p_queue, IMSG_LKA_ENVELOPE_SUBMIT, 0, 0, -1);
225 			m_add_id(p_queue, lks->id);
226 			m_add_envelope(p_queue, ep);
227 			m_close(p_queue);
228 			free(ep);
229 		}
230 
231 		m_create(p_queue, IMSG_LKA_ENVELOPE_COMMIT, 0, 0, -1);
232 		m_add_id(p_queue, lks->id);
233 		m_close(p_queue);
234 	}
235 
236 	expand_clear(&lks->expand);
237 	tree_xpop(&sessions, lks->id);
238 	free(lks);
239 }
240 
241 static void
lka_expand(struct lka_session * lks,struct rule * rule,struct expandnode * xn)242 lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
243 {
244 	struct forward_req	fwreq;
245 	struct envelope		ep;
246 	struct expandnode	node;
247 	struct mailaddr		maddr;
248 	struct dispatcher      *dsp;
249 	struct table	       *userbase;
250 	int			r;
251 	union lookup		lk;
252 	char		       *tag;
253 	const char	       *srs_decoded;
254 
255 	if (xn->depth >= EXPAND_DEPTH) {
256 		log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep.");
257 		lks->error = LKA_PERMFAIL;
258 		lks->errormsg = "524 5.2.4 Mailing list expansion problem";
259 		return;
260 	}
261 
262 	switch (xn->type) {
263 	case EXPAND_INVALID:
264 	case EXPAND_INCLUDE:
265 		fatalx("lka_expand: unexpected type");
266 		break;
267 
268 	case EXPAND_ADDRESS:
269 
270 		log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s "
271 		    "[depth=%d]",
272 		    xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth);
273 
274 
275 		ep = lks->envelope;
276 		ep.dest = xn->u.mailaddr;
277 		if (xn->parent) /* nodes with parent are forward addresses */
278 			ep.flags |= EF_INTERNAL;
279 
280 		/* handle SRS */
281 		if (env->sc_srs_key != NULL &&
282 		    ep.sender.user[0] == '\0' &&
283 		    (strncasecmp(ep.dest.user, "SRS0=", 5) == 0 ||
284 			strncasecmp(ep.dest.user, "SRS1=", 5) == 0)) {
285 			srs_decoded = srs_decode(mailaddr_to_text(&ep.dest));
286 			if (srs_decoded &&
287 			    text_to_mailaddr(&ep.dest, srs_decoded)) {
288 				/* flag envelope internal and override dest */
289 				ep.flags |= EF_INTERNAL;
290 				xn->u.mailaddr = ep.dest;
291 				lks->envelope = ep;
292 			}
293 			else {
294 				log_warn("SRS failed to decode: %s",
295 				    mailaddr_to_text(&ep.dest));
296 			}
297 		}
298 
299 		/* Pass the node through the ruleset */
300 		rule = ruleset_match(&ep);
301 		if (rule == NULL || rule->reject) {
302 			lks->error = (errno == EAGAIN) ?
303 			    LKA_TEMPFAIL : LKA_PERMFAIL;
304 			break;
305 		}
306 
307 		dsp = dict_xget(env->sc_dispatchers, rule->dispatcher);
308 		if (dsp->type == DISPATCHER_REMOTE) {
309 			lka_submit(lks, rule, xn);
310 		}
311 		else if (dsp->u.local.table_virtual) {
312 			/* expand */
313 			lks->expand.rule = rule;
314 			lks->expand.parent = xn;
315 
316 			/* temporary replace the mailaddr with a copy where
317 			 * we eventually strip the '+'-part before lookup.
318 			 */
319 			maddr = xn->u.mailaddr;
320 			xlowercase(maddr.user, xn->u.mailaddr.user,
321 			    sizeof maddr.user);
322 			r = aliases_virtual_get(&lks->expand, &maddr);
323 			if (r == -1) {
324 				lks->error = LKA_TEMPFAIL;
325 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
326 				    "error in virtual alias lookup");
327 			}
328 			else if (r == 0) {
329 				lks->error = LKA_PERMFAIL;
330 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
331 				    "no aliases for virtual");
332 			}
333 			if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL)
334 				lks->errormsg = "424 4.2.4 Mailing list expansion problem";
335 			if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL)
336 				lks->errormsg = "524 5.2.4 Mailing list expansion problem";
337 		}
338 		else {
339 			lks->expand.rule = rule;
340 			lks->expand.parent = xn;
341 			xn->rule = rule;
342 
343 			memset(&node, 0, sizeof node);
344 			node.type = EXPAND_USERNAME;
345 			xlowercase(node.u.user, xn->u.mailaddr.user,
346 			    sizeof node.u.user);
347 			expand_insert(&lks->expand, &node);
348 		}
349 		break;
350 
351 	case EXPAND_USERNAME:
352 		log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s "
353 		    "[depth=%d, sameuser=%d]",
354 		    xn->u.user, xn->depth, xn->sameuser);
355 
356 		/* expand aliases with the given rule */
357 		dsp = dict_xget(env->sc_dispatchers, rule->dispatcher);
358 
359 		lks->expand.rule = rule;
360 		lks->expand.parent = xn;
361 
362 		if (!xn->sameuser &&
363 		    (dsp->u.local.table_alias || dsp->u.local.table_virtual)) {
364 			if (dsp->u.local.table_alias)
365 				r = aliases_get(&lks->expand, xn->u.user);
366 			if (dsp->u.local.table_virtual)
367 				r = aliases_virtual_get(&lks->expand, &xn->u.mailaddr);
368 			if (r == -1) {
369 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
370 				    "error in alias lookup");
371 				lks->error = LKA_TEMPFAIL;
372 				if (lks->errormsg == NULL)
373 					lks->errormsg = "424 4.2.4 Mailing list expansion problem";
374 			}
375 			if (r)
376 				break;
377 		}
378 
379 		/* gilles+hackers@ -> gilles@ */
380 		if ((tag = strchr(xn->u.user, *env->sc_subaddressing_delim)) != NULL) {
381 			*tag++ = '\0';
382 			(void)strlcpy(xn->subaddress, tag, sizeof xn->subaddress);
383 		}
384 
385 		userbase = table_find(env, dsp->u.local.table_userbase);
386 		r = table_lookup(userbase, K_USERINFO, xn->u.user, &lk);
387 		if (r == -1) {
388 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
389 			    "backend error while searching user");
390 			lks->error = LKA_TEMPFAIL;
391 			break;
392 		}
393 		if (r == 0) {
394 			log_trace(TRACE_EXPAND, "expand: lka_expand: "
395 			    "user-part does not match system user");
396 			lks->error = LKA_PERMFAIL;
397 			break;
398 		}
399 		xn->realuser = 1;
400 		xn->realuser_uid = lk.userinfo.uid;
401 
402 		if (xn->sameuser && xn->parent->forwarded) {
403 			log_trace(TRACE_EXPAND, "expand: lka_expand: same "
404 			    "user, submitting");
405 			lka_submit(lks, rule, xn);
406 			break;
407 		}
408 
409 
410 		/* when alternate delivery user is provided,
411 		 * skip other users forward files.
412 		 */
413 		if (dsp->u.local.user) {
414 			if (strcmp(dsp->u.local.user, xn->u.user) != 0) {
415 				log_trace(TRACE_EXPAND, "expand: lka_expand: "
416 				    "alternate delivery user mismatch recipient "
417 				    "user, skip .forward, submitting");
418 				lka_submit(lks, rule, xn);
419 				break;
420 			}
421 		}
422 
423 		/* no aliases found, query forward file */
424 		lks->rule = rule;
425 		lks->node = xn;
426 		xn->forwarded = 1;
427 
428 		memset(&fwreq, 0, sizeof(fwreq));
429 		fwreq.id = lks->id;
430 		(void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user));
431 		(void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory));
432 		fwreq.uid = lk.userinfo.uid;
433 		fwreq.gid = lk.userinfo.gid;
434 
435 		m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1,
436 		    &fwreq, sizeof(fwreq));
437 		lks->flags |= F_WAITING;
438 		break;
439 
440 	case EXPAND_FILENAME:
441 		if (xn->parent->realuser && xn->parent->realuser_uid == 0) {
442 			log_trace(TRACE_EXPAND, "expand: filename not allowed in root's forward");
443 			lks->error = LKA_TEMPFAIL;
444 			break;
445 		}
446 
447 		dsp = dict_xget(env->sc_dispatchers, rule->dispatcher);
448 		if (dsp->u.local.forward_only) {
449 			log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule");
450 			lks->error = LKA_TEMPFAIL;
451 			break;
452 		}
453 		log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s "
454 		    "[depth=%d]", xn->u.buffer, xn->depth);
455 		lka_submit(lks, rule, xn);
456 		break;
457 
458 	case EXPAND_ERROR:
459 		dsp = dict_xget(env->sc_dispatchers, rule->dispatcher);
460 		if (dsp->u.local.forward_only) {
461 			log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule");
462 			lks->error = LKA_TEMPFAIL;
463 			break;
464 		}
465 		log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s "
466 		    "[depth=%d]", xn->u.buffer, xn->depth);
467 		if (xn->u.buffer[0] == '4')
468 			lks->error = LKA_TEMPFAIL;
469 		else if (xn->u.buffer[0] == '5')
470 			lks->error = LKA_PERMFAIL;
471 		lks->errormsg = xn->u.buffer;
472 		break;
473 
474 	case EXPAND_FILTER:
475 		if (xn->parent->realuser && xn->parent->realuser_uid == 0) {
476 			log_trace(TRACE_EXPAND, "expand: filter not allowed in root's forward");
477 			lks->error = LKA_TEMPFAIL;
478 			break;
479 		}
480 
481 		dsp = dict_xget(env->sc_dispatchers, rule->dispatcher);
482 		if (dsp->u.local.forward_only) {
483 			log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule");
484 			lks->error = LKA_TEMPFAIL;
485 			break;
486 		}
487 		log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s "
488 		    "[depth=%d]", xn->u.buffer, xn->depth);
489 		lka_submit(lks, rule, xn);
490 		break;
491 	}
492 }
493 
494 static struct expandnode *
lka_find_ancestor(struct expandnode * xn,enum expand_type type)495 lka_find_ancestor(struct expandnode *xn, enum expand_type type)
496 {
497 	while (xn && (xn->type != type))
498 		xn = xn->parent;
499 	if (xn == NULL) {
500 		log_warnx("warn: lka_find_ancestor: no ancestors of type %d",
501 		    type);
502 		fatalx(NULL);
503 	}
504 	return (xn);
505 }
506 
507 static void
lka_submit(struct lka_session * lks,struct rule * rule,struct expandnode * xn)508 lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn)
509 {
510 	struct envelope		*ep;
511 	struct dispatcher	*dsp;
512 	const char		*user;
513 	const char		*format;
514 
515 	ep = xmemdup(&lks->envelope, sizeof *ep);
516 	(void)strlcpy(ep->dispatcher, rule->dispatcher, sizeof ep->dispatcher);
517 
518 	dsp = dict_xget(env->sc_dispatchers, ep->dispatcher);
519 
520 	switch (dsp->type) {
521 	case DISPATCHER_REMOTE:
522 		if (xn->type != EXPAND_ADDRESS)
523 			fatalx("lka_deliver: expect address");
524 		ep->type = D_MTA;
525 		ep->dest = xn->u.mailaddr;
526 		break;
527 
528 	case DISPATCHER_BOUNCE:
529 	case DISPATCHER_LOCAL:
530 		if (xn->type != EXPAND_USERNAME &&
531 		    xn->type != EXPAND_FILENAME &&
532 		    xn->type != EXPAND_FILTER)
533 			fatalx("lka_deliver: wrong type: %d", xn->type);
534 
535 		ep->type = D_MDA;
536 		ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr;
537 		if (xn->type == EXPAND_USERNAME) {
538 			(void)strlcpy(ep->mda_user, xn->u.user, sizeof(ep->mda_user));
539 			(void)strlcpy(ep->mda_subaddress, xn->subaddress, sizeof(ep->mda_subaddress));
540 		}
541 		else {
542 			user = !xn->parent->realuser ?
543 			    SMTPD_USER :
544 			    xn->parent->u.user;
545 			(void)strlcpy(ep->mda_user, user, sizeof (ep->mda_user));
546 
547 			/* this battle needs to be fought ... */
548 			if (xn->type == EXPAND_FILTER &&
549 			    strcmp(ep->mda_user, SMTPD_USER) == 0)
550 				log_warnx("commands executed from aliases "
551 				    "run with %s privileges", SMTPD_USER);
552 
553 			format = "%s";
554 			if (xn->type == EXPAND_FILENAME)
555 				format = "/usr/libexec/mail.mboxfile -f %%{mbox.from} %s";
556 			(void)snprintf(ep->mda_exec, sizeof(ep->mda_exec),
557 			    format, xn->u.buffer);
558 		}
559 		break;
560 	}
561 
562 	TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry);
563 }
564