1 /* $Id$ */
2 
3 /*
4  * Copyright (c) 2006 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <fnmatch.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #include "fdm.h"
26 #include "fetch.h"
27 #include "match.h"
28 
29 void		 apply_result(struct expritem *, int *, int);
30 
31 struct replstrs	*find_delivery_users(struct mail_ctx *, struct action *, int *);
32 int		 fill_from_strings(struct mail_ctx *, struct rule *,
33 		     struct replstrs *);
34 int		 fill_from_string(struct mail_ctx *, struct rule *,
35 		     struct replstr *);
36 int		 fill_from_action(struct mail_ctx *, struct rule *,
37 		     struct action *, struct replstrs *);
38 
39 int		 start_action(struct mail_ctx *, struct deliver_ctx *);
40 int		 finish_action(struct deliver_ctx *, struct msg *,
41 		    struct msgbuf *);
42 
43 #define ACTION_DONE 0
44 #define ACTION_ERROR 1
45 #define ACTION_PARENT 2
46 
47 /*
48  * Number of chained actions. Limit on recursion with things like:
49  *
50  *	action "name" { action "name" }
51  */
52 u_int	chained;
53 
54 /* Check mail against next rule or part of expression. */
55 int
mail_match(struct mail_ctx * mctx,struct msg * msg,struct msgbuf * msgbuf)56 mail_match(struct mail_ctx *mctx, struct msg *msg, struct msgbuf *msgbuf)
57 {
58 	struct account	*a = mctx->account;
59 	struct mail	*m = mctx->mail;
60 	struct expritem	*ei;
61 	struct replstrs	*users;
62 	int		 should_free, this = -1, error = MAIL_CONTINUE;
63 	char		 desc[DESCBUFSIZE];
64 
65 	set_wrapped(m, ' ');
66 
67 	/* If blocked, check for msgs from parent. */
68 	if (mctx->msgid != 0) {
69 		if (msg == NULL || msg->id != mctx->msgid)
70 			return (MAIL_BLOCKED);
71 		mctx->msgid = 0;
72 
73 		if (msg->type != MSG_DONE)
74 			fatalx("unexpected message");
75 		if (msgbuf->buf != NULL && msgbuf->len != 0) {
76 			strb_destroy(&m->tags);
77 			m->tags = msgbuf->buf;
78 			reset_tags(&m->tags);
79 		}
80 
81 		ei = mctx->expritem;
82 		switch (msg->data.error) {
83 		case MATCH_ERROR:
84 			return (MAIL_ERROR);
85 		case MATCH_TRUE:
86 			this = 1;
87 			break;
88 		case MATCH_FALSE:
89 			this = 0;
90 			break;
91 		default:
92 			fatalx("unexpected response");
93 		}
94 		apply_result(ei, &mctx->result, this);
95 
96 		goto next_expritem;
97 	}
98 
99 	/* Check for completion and end of ruleset. */
100 	if (mctx->done)
101 		return (MAIL_DONE);
102 	if (mctx->rule == NULL) {
103 		switch (conf.impl_act) {
104 		case DECISION_NONE:
105 			log_warnx("%s: reached end of ruleset. no "
106 			    "unmatched-mail option; keeping mail",  a->name);
107 			m->decision = DECISION_KEEP;
108 			break;
109 		case DECISION_KEEP:
110 			log_debug2("%s: reached end of ruleset. keeping mail",
111 			    a->name);
112 			m->decision = DECISION_KEEP;
113 			break;
114 		case DECISION_DROP:
115 			log_debug2("%s: reached end of ruleset. dropping mail",
116 			    a->name);
117 			m->decision = DECISION_DROP;
118 			break;
119 		}
120 		return (MAIL_DONE);
121 	}
122 
123 	/* Expression not started. Start it. */
124 	if (mctx->expritem == NULL) {
125 		/* Start the expression. */
126 		mctx->result = 0;
127 		mctx->expritem = TAILQ_FIRST(mctx->rule->expr);
128 	}
129 
130 	/* Check this expression item and adjust the result. */
131 	ei = mctx->expritem;
132 
133 	/* Handle short-circuit evaluation. */
134 	switch (ei->op) {
135 	case OP_NONE:
136 		break;
137 	case OP_AND:
138 		/* And and the result is already false. */
139 		if (!mctx->result)
140 			goto next_expritem;
141 		break;
142 	case OP_OR:
143 		/* Or and the result is already true. */
144 		if (mctx->result)
145 			goto next_expritem;
146 		break;
147 	}
148 
149 	switch (ei->match->match(mctx, ei)) {
150 	case MATCH_ERROR:
151 		return (MAIL_ERROR);
152 	case MATCH_PARENT:
153 		return (MAIL_BLOCKED);
154 	case MATCH_TRUE:
155 		this = 1;
156 		break;
157 	case MATCH_FALSE:
158 		this = 0;
159 		break;
160 	default:
161 		fatalx("unexpected op");
162 	}
163 	apply_result(ei, &mctx->result, this);
164 
165 	ei->match->desc(ei, desc, sizeof desc);
166 	log_debug3("%s: tried %s, result now %d", a->name, desc, mctx->result);
167 
168 next_expritem:
169 	/* Move to the next item. If there is one, then return. */
170 	mctx->expritem = TAILQ_NEXT(mctx->expritem, entry);
171 	if (mctx->expritem != NULL)
172 		return (MAIL_CONTINUE);
173 
174 	log_debug3("%s: finished rule %u, result %d", a->name, mctx->rule->idx,
175 	    mctx->result);
176 
177 	/* If the result was false, skip to find the next rule. */
178 	if (!mctx->result)
179 		goto next_rule;
180 	log_debug2("%s: matched to rule %u", a->name, mctx->rule->idx);
181 
182 	/*
183 	 * If this rule is stop, mark the context so when we get back after
184 	 * delivery we know to stop.
185 	 */
186 	if (mctx->rule->stop)
187 		mctx->done = 1;
188 
189 	/* Handle nested rules. */
190 	if (!TAILQ_EMPTY(&mctx->rule->rules)) {
191 		log_debug2("%s: entering nested rules", a->name);
192 
193 		/*
194 		 * Stack the current rule (we are at the end of it so the
195 		 * the expritem must be NULL already).
196 		 */
197 		ARRAY_ADD(&mctx->stack, mctx->rule);
198 
199 		/* Continue with the first rule of the nested list. */
200 		mctx->rule = TAILQ_FIRST(&mctx->rule->rules);
201 		return (MAIL_CONTINUE);
202 	}
203 	mctx->matched = 1;
204 
205 	/* Handle lambda actions. */
206 	if (mctx->rule->lambda != NULL) {
207 		users = find_delivery_users(mctx, NULL, &should_free);
208 
209 		chained = MAXACTIONCHAIN;
210 		if (fill_from_action(mctx,
211 		    mctx->rule, mctx->rule->lambda, users) != 0) {
212 			if (should_free)
213 				ARRAY_FREEALL(users);
214 			return (MAIL_ERROR);
215 		}
216 
217 		if (should_free)
218 			ARRAY_FREEALL(users);
219 		error = MAIL_DELIVER;
220 	}
221 
222 	/* Fill the delivery action queue. */
223 	if (!ARRAY_EMPTY(mctx->rule->actions)) {
224 		chained = MAXACTIONCHAIN;
225 		if (fill_from_strings(mctx,
226 		    mctx->rule, mctx->rule->actions) != 0)
227 			return (MAIL_ERROR);
228 		error = MAIL_DELIVER;
229 	}
230 
231 next_rule:
232 	/* Move to the next rule. */
233 	mctx->ruleidx = mctx->rule->idx;	/* save last index */
234 	mctx->rule = TAILQ_NEXT(mctx->rule, entry);
235 
236 	/* If no more rules, try to move up the stack. */
237 	while (mctx->rule == NULL) {
238 		if (ARRAY_EMPTY(&mctx->stack))
239 			break;
240 		log_debug2("%s: exiting nested rules", a->name);
241 		mctx->rule = ARRAY_LAST(&mctx->stack);
242 		mctx->rule = TAILQ_NEXT(mctx->rule, entry);
243 		ARRAY_TRUNC(&mctx->stack, 1);
244 	}
245 
246 	return (error);
247 }
248 
249 void
apply_result(struct expritem * ei,int * result,int this)250 apply_result(struct expritem *ei, int *result, int this)
251 {
252 	if (ei->inverted)
253 		this = !this;
254 	switch (ei->op) {
255 	case OP_NONE:
256 		*result = this;
257 		break;
258 	case OP_OR:
259 		*result = *result || this;
260 		break;
261 	case OP_AND:
262 		*result = *result && this;
263 		break;
264 	}
265 }
266 
267 /* Run next delivery action. */
268 int
mail_deliver(struct mail_ctx * mctx,struct msg * msg,struct msgbuf * msgbuf)269 mail_deliver(struct mail_ctx *mctx, struct msg *msg, struct msgbuf *msgbuf)
270 {
271 	struct account		*a = mctx->account;
272 	struct mail		*m = mctx->mail;
273 	struct deliver_ctx	*dctx;
274 
275 	set_wrapped(m, '\n');
276 
277 	/* If blocked, check for msgs from parent. */
278 	if (mctx->msgid != 0) {
279 		if (msg == NULL || msg->id != mctx->msgid)
280 			return (MAIL_BLOCKED);
281 		mctx->msgid = 0;
282 
283 		/* Got message. Finish delivery. */
284 		dctx = TAILQ_FIRST(&mctx->dqueue);
285 		if (finish_action(dctx, msg, msgbuf) == ACTION_ERROR)
286 			return (MAIL_ERROR);
287 
288 		/* Move on to dequeue this delivery action. */
289 		goto done;
290 	}
291 
292 	/* Check if delivery is complete. */
293 	if (TAILQ_EMPTY(&mctx->dqueue))
294 		return (MAIL_MATCH);
295 
296 	/* Get the first delivery action and start it. */
297 	dctx = TAILQ_FIRST(&mctx->dqueue);
298 	switch (start_action(mctx, dctx)) {
299 	case ACTION_ERROR:
300 		return (MAIL_ERROR);
301 	case ACTION_PARENT:
302 		return (MAIL_BLOCKED);
303 	}
304 
305 done:
306 	/* Remove completed action from queue. */
307 	TAILQ_REMOVE(&mctx->dqueue, dctx, entry);
308 	log_debug("%s: message %u delivered (rule %u, %s) in %.3f seconds",
309 	    a->name, m->idx, dctx->rule->idx,
310 	    dctx->actitem->deliver->name, get_time() - dctx->tim);
311 	user_free(dctx->udata);
312 	xfree(dctx);
313 	return (MAIL_CONTINUE);
314 }
315 
316 struct replstrs *
find_delivery_users(struct mail_ctx * mctx,struct action * t,int * should_free)317 find_delivery_users(struct mail_ctx *mctx, struct action *t, int *should_free)
318 {
319 	struct account	*a = mctx->account;
320 	struct rule	*r = mctx->rule;
321 	struct replstrs	*users;
322 
323 	*should_free = 0;
324 	users = NULL;
325 	if (r->users != NULL)			/* rule comes first */
326 		users = r->users;
327 	else if (t != NULL && t->users != NULL)	/* then action */
328 		users = t->users;
329 	else if (a->users != NULL)		/* then account */
330 		users = a->users;
331 	if (users == NULL) {
332 		*should_free = 1;
333 		users = xmalloc(sizeof *users);
334 		ARRAY_INIT(users);
335 		ARRAY_EXPAND(users, 1);
336 		ARRAY_LAST(users).str = conf.def_user;
337 	}
338 
339 	return (users);
340 }
341 
342 int
fill_from_strings(struct mail_ctx * mctx,struct rule * r,struct replstrs * rsa)343 fill_from_strings(struct mail_ctx *mctx, struct rule *r, struct replstrs *rsa)
344 {
345 	struct account	*a = mctx->account;
346 	u_int		  i;
347 	struct replstr	*rs;
348 
349 	chained--;
350 	if (chained == 0) {
351 		log_warnx("%s: too many chained actions", a->name);
352 		return (-1);
353 	}
354 
355 	for (i = 0; i < ARRAY_LENGTH(rsa); i++) {
356 		rs = &ARRAY_ITEM(rsa, i);
357 		if (fill_from_string(mctx, r, rs) != 0)
358 			return (-1);
359 	}
360 
361 	return (0);
362 }
363 
364 int
fill_from_string(struct mail_ctx * mctx,struct rule * r,struct replstr * rs)365 fill_from_string(struct mail_ctx *mctx, struct rule *r, struct replstr *rs)
366 {
367 	struct account	*a = mctx->account;
368 	struct mail	*m = mctx->mail;
369 	struct action	*t;
370 	struct actions	*ta;
371 	u_int		 i;
372 	char		*s;
373 	struct replstrs *users;
374 	int		 should_free;
375 
376 	s = replacestr(rs, m->tags, m, &m->rml);
377 
378 	log_debug2("%s: looking for actions matching: %s", a->name, s);
379 	ta = match_actions(s);
380 	if (ARRAY_EMPTY(ta))
381 		goto empty;
382 	xfree(s);
383 
384 	log_debug2("%s: found %u actions", a->name, ARRAY_LENGTH(ta));
385 	for (i = 0; i < ARRAY_LENGTH(ta); i++) {
386 		t = ARRAY_ITEM(ta, i);
387 		users = find_delivery_users(mctx, t, &should_free);
388 
389 		if (fill_from_action(mctx, r, t, users) != 0) {
390 			if (should_free)
391 				ARRAY_FREEALL(users);
392 			ARRAY_FREEALL(ta);
393 			return (-1);
394 		}
395 
396 		if (should_free)
397 			ARRAY_FREEALL(users);
398 	}
399 
400 	ARRAY_FREEALL(ta);
401 	return (0);
402 
403 empty:
404 	log_warnx("%s: no actions matching: %s (%s)", a->name, s, rs->str);
405 	xfree(s);
406 	ARRAY_FREEALL(ta);
407 	return (-1);
408 }
409 
410 int
fill_from_action(struct mail_ctx * mctx,struct rule * r,struct action * t,struct replstrs * users)411 fill_from_action(struct mail_ctx *mctx, struct rule *r, struct action *t,
412     struct replstrs *users)
413 {
414 	struct account			*a = mctx->account;
415 	struct mail			*m = mctx->mail;
416 	struct deliver_action_data	*data;
417 	struct actitem			*ti;
418 	struct deliver_ctx		*dctx;
419 	u_int				 i;
420 	char				*user;
421 	struct userdata			*udata;
422 
423 	for (i = 0; i < ARRAY_LENGTH(users); i++) {
424 		user = replacestr(&ARRAY_ITEM(users, i), m->tags, m, &m->rml);
425 		if (user == NULL || *user == '\0') {
426 			if (user != NULL)
427 				xfree(user);
428 			log_warnx("%s: empty user", a->name);
429 			return (-1);
430 		}
431 		if ((udata = user_lookup(user, conf.user_order)) == NULL) {
432 			log_warnx("%s: bad user: %s", a->name, user);
433 			xfree(user);
434 			return (-1);
435 		}
436 		xfree(user);
437 
438 		TAILQ_FOREACH(ti, t->list, entry) {
439 			if (ti->deliver == NULL) {
440 				data = ti->data;
441 				if (fill_from_strings(
442 				    mctx, r, data->actions) != 0) {
443 					user_free(udata);
444 					return (-1);
445 				}
446 				continue;
447 			}
448 
449 			dctx = xcalloc(1, sizeof *dctx);
450 			dctx->action = t;
451 			dctx->actitem = ti;
452 			dctx->account = a;
453 			dctx->rule = r;
454 			dctx->mail = m;
455 
456 			dctx->udata = user_copy(udata);
457 
458 			log_debug3("%s: action %s:%u (%s), user %s", a->name,
459 			    t->name, ti->idx, ti->deliver->name,
460 			    ARRAY_ITEM(users, i).str);
461 			TAILQ_INSERT_TAIL(&mctx->dqueue, dctx, entry);
462 		}
463 
464 		user_free(udata);
465 	}
466 
467 	return (0);
468 }
469 
470 int
start_action(struct mail_ctx * mctx,struct deliver_ctx * dctx)471 start_action(struct mail_ctx *mctx, struct deliver_ctx *dctx)
472 {
473 	struct account	*a = dctx->account;
474 	struct action	*t = dctx->action;
475 	struct actitem	*ti = dctx->actitem;
476 	struct mail	*m = dctx->mail;
477 	struct msg	 msg;
478 	struct msgbuf	 msgbuf;
479 
480 	dctx->tim = get_time();
481 	log_debug2("%s: message %u, running action %s:%u (%s) as user %s",
482 	    a->name, m->idx, t->name, ti->idx, ti->deliver->name,
483 	    dctx->udata->name);
484 	add_tag(&m->tags, "action", "%s", t->name);
485 	add_tag(&m->tags, "rule", "%u", mctx->ruleidx);
486 
487 	update_tags(&m->tags, dctx->udata);
488 
489 	/* Just deliver now for in-child delivery. */
490 	if (ti->deliver->type == DELIVER_INCHILD) {
491 		if (ti->deliver->deliver(dctx, ti) != DELIVER_SUCCESS) {
492 			reset_tags(&m->tags);
493 			return (ACTION_ERROR);
494 		}
495 
496 		reset_tags(&m->tags);
497 		return (ACTION_DONE);
498 	}
499 
500 	memset(&msg, 0, sizeof msg);
501 	msg.type = MSG_ACTION;
502 	msg.id = m->idx;
503 
504 	msg.data.account = a;
505 	msg.data.actitem = ti;
506 
507 	msg.data.uid = dctx->udata->uid;
508 	msg.data.gid = dctx->udata->gid;
509 
510 	msgbuf.buf = m->tags;
511 	msgbuf.len = STRB_SIZE(m->tags);
512 
513 	mail_send(m, &msg);
514 
515 	log_debug3("%s: sending action to parent", a->name);
516 	if (privsep_send(mctx->io, &msg, &msgbuf) != 0)
517 		fatalx("privsep_send error");
518 
519 	mctx->msgid = msg.id;
520 
521 	reset_tags(&m->tags);
522 	return (ACTION_PARENT);
523 }
524 
525 int
finish_action(struct deliver_ctx * dctx,struct msg * msg,struct msgbuf * msgbuf)526 finish_action(struct deliver_ctx *dctx, struct msg *msg, struct msgbuf *msgbuf)
527 {
528 	struct account	*a = dctx->account;
529 	struct actitem	*ti = dctx->actitem;
530 	struct mail	*m = dctx->mail;
531 	u_int		 lines;
532 
533 	if (msgbuf->buf != NULL && msgbuf->len != 0) {
534 		strb_destroy(&m->tags);
535 		m->tags = msgbuf->buf;
536 		reset_tags(&m->tags);
537 	}
538 
539 	if (msg->data.error != 0)
540 		return (ACTION_ERROR);
541 
542 	if (ti->deliver->type != DELIVER_WRBACK)
543 		return (ACTION_DONE);
544 
545 	if (mail_receive(m, msg, 1) != 0) {
546 		log_warn("%s: can't receive mail", a->name);
547 		return (ACTION_ERROR);
548 	}
549 	log_debug2("%s: message %u, received modified mail: size %zu, body %zd",
550 	    a->name, m->idx, m->size, m->body);
551 
552 	/* Trim from line. */
553 	trim_from(m);
554 
555 	/* Recreate the wrapped array. */
556 	lines = fill_wrapped(m);
557 	log_debug2("%s: found %u wrapped lines", a->name, lines);
558 
559 	return (ACTION_DONE);
560 }
561