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