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