Coverage Report

Created: 2026-10-17 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/sieve-actions.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "strfuncs.h"
6
#include "ioloop.h"
7
#include "hostpid.h"
8
#include "str-sanitize.h"
9
#include "unichar.h"
10
#include "istream.h"
11
#include "istream-header-filter.h"
12
#include "ostream.h"
13
#include "smtp-params.h"
14
#include "mail-storage.h"
15
#include "message-date.h"
16
#include "message-size.h"
17
18
#include "rfc2822.h"
19
20
#include "sieve-code.h"
21
#include "sieve-extensions.h"
22
#include "sieve-binary.h"
23
#include "sieve-interpreter.h"
24
#include "sieve-dump.h"
25
#include "sieve-result.h"
26
#include "sieve-actions.h"
27
#include "sieve-message.h"
28
#include "sieve-smtp.h"
29
30
/*
31
 * Action execution environment
32
 */
33
34
struct event_passthrough *
35
sieve_action_create_finish_event(const struct sieve_action_exec_env *aenv)
36
0
{
37
0
  struct event_passthrough *e =
38
0
    event_create_passthrough(aenv->event)->
39
0
    set_name("sieve_action_finished");
40
41
0
  return e;
42
0
}
43
44
/*
45
 * Action instance
46
 */
47
48
bool sieve_action_is_executed(const struct sieve_action *act,
49
            struct sieve_result *result)
50
0
{
51
0
  unsigned int cur_exec_seq = sieve_result_get_exec_seq(result);
52
53
0
  i_assert(act->exec_seq <= cur_exec_seq);
54
0
  return (act->exec_seq < cur_exec_seq);
55
0
}
56
57
/*
58
 * Side-effect operand
59
 */
60
61
const struct sieve_operand_class
62
sieve_side_effect_operand_class = { "SIDE-EFFECT" };
63
64
bool sieve_opr_side_effect_dump(const struct sieve_dumptime_env *denv,
65
        sieve_size_t *address)
66
0
{
67
0
  struct sieve_side_effect seffect;
68
0
  const struct sieve_side_effect_def *sdef;
69
70
0
  if (!sieve_opr_object_dump(denv, &sieve_side_effect_operand_class,
71
0
           address, &seffect.object))
72
0
    return FALSE;
73
74
0
  sdef = seffect.def =
75
0
    (const struct sieve_side_effect_def *)seffect.object.def;
76
77
0
  if (sdef->dump_context != NULL) {
78
0
    sieve_code_descend(denv);
79
0
    if (!sdef->dump_context(&seffect, denv, address))
80
0
      return FALSE;
81
0
    sieve_code_ascend(denv);
82
0
  }
83
0
  return TRUE;
84
0
}
85
86
int sieve_opr_side_effect_read(const struct sieve_runtime_env *renv,
87
             sieve_size_t *address,
88
             struct sieve_side_effect *seffect)
89
0
{
90
0
  const struct sieve_side_effect_def *sdef;
91
0
  int ret;
92
93
0
  seffect->context = NULL;
94
95
0
  if (!sieve_opr_object_read(renv, &sieve_side_effect_operand_class,
96
0
           address, &seffect->object))
97
0
    return SIEVE_EXEC_BIN_CORRUPT;
98
99
0
  sdef = seffect->def =
100
0
    (const struct sieve_side_effect_def *)seffect->object.def;
101
102
0
  if (sdef->read_context != NULL &&
103
0
      (ret = sdef->read_context(seffect, renv, address,
104
0
              &seffect->context)) <= 0)
105
0
    return ret;
106
0
  return SIEVE_EXEC_OK;
107
0
}
108
109
/*
110
 * Optional operands
111
 */
112
113
int sieve_action_opr_optional_dump(const struct sieve_dumptime_env *denv,
114
           sieve_size_t *address, signed int *opt_code)
115
0
{
116
0
  signed int _opt_code = 0;
117
0
  bool final = FALSE, opok = TRUE;
118
119
0
  if (opt_code == NULL) {
120
0
    opt_code = &_opt_code;
121
0
    final = TRUE;
122
0
  }
123
124
0
  while (opok) {
125
0
    int opt;
126
127
0
    opt = sieve_opr_optional_dump(denv, address, opt_code);
128
0
    if (opt <= 0)
129
0
      return opt;
130
131
0
    if (*opt_code == SIEVE_OPT_SIDE_EFFECT)
132
0
      opok = sieve_opr_side_effect_dump(denv, address);
133
0
    else
134
0
      return (final ? -1 : 1);
135
0
  }
136
137
0
  return -1;
138
0
}
139
140
int sieve_action_opr_optional_read(const struct sieve_runtime_env *renv,
141
           sieve_size_t *address,
142
           signed int *opt_code, int *exec_status,
143
           struct sieve_side_effects_list **list)
144
0
{
145
0
  signed int _opt_code = 0;
146
0
  bool final = FALSE;
147
0
  int ret;
148
149
0
  if (opt_code == NULL) {
150
0
    opt_code = &_opt_code;
151
0
    final = TRUE;
152
0
  }
153
154
0
  if (exec_status != NULL)
155
0
    *exec_status = SIEVE_EXEC_OK;
156
157
0
  for (;;) {
158
0
    int opt;
159
160
0
    opt = sieve_opr_optional_read(renv, address, opt_code);
161
0
    if (opt <= 0) {
162
0
      if (opt < 0 && exec_status != NULL)
163
0
        *exec_status = SIEVE_EXEC_BIN_CORRUPT;
164
0
      return opt;
165
0
    }
166
167
0
    if (*opt_code == SIEVE_OPT_SIDE_EFFECT) {
168
0
      struct sieve_side_effect seffect;
169
170
0
      i_assert(list != NULL);
171
172
0
      ret = sieve_opr_side_effect_read(renv, address,
173
0
               &seffect);
174
0
      if (ret <= 0) {
175
0
        if (exec_status != NULL)
176
0
          *exec_status = ret;
177
0
        return -1;
178
0
      }
179
180
0
      if (*list == NULL) {
181
0
        *list = sieve_side_effects_list_create(
182
0
          renv->result);
183
0
      }
184
185
0
      sieve_side_effects_list_add(*list, &seffect);
186
0
    } else {
187
0
      if (final) {
188
0
        sieve_runtime_trace_error(
189
0
          renv, "invalid optional operand");
190
0
        if (exec_status != NULL)
191
0
          *exec_status = SIEVE_EXEC_BIN_CORRUPT;
192
0
        return -1;
193
0
      }
194
0
      return 1;
195
0
    }
196
0
  }
197
198
0
  i_unreached();
199
0
}
200
201
/*
202
 * Store action
203
 */
204
205
/* Forward declarations */
206
207
static bool
208
act_store_equals(const struct sieve_script_env *senv,
209
     const struct sieve_action *act1,
210
     const struct sieve_action *act2);
211
212
static int
213
act_store_check_duplicate(const struct sieve_runtime_env *renv,
214
        const struct sieve_action *act,
215
        const struct sieve_action *act_other);
216
static void
217
act_store_print(const struct sieve_action *action,
218
    const struct sieve_result_print_env *rpenv, bool *keep);
219
220
static int
221
act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context);
222
static int
223
act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
224
      bool *keep);
225
static int
226
act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
227
static void
228
act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
229
       bool success);
230
231
/* Action object */
232
233
const struct sieve_action_def act_store = {
234
  .name = "store",
235
  .flags =
236
    SIEVE_ACTFLAG_TRIES_DELIVER |
237
    SIEVE_ACTFLAG_MAIL_STORAGE,
238
  .equals = act_store_equals,
239
  .check_duplicate = act_store_check_duplicate,
240
  .print = act_store_print,
241
  .start = act_store_start,
242
  .execute = act_store_execute,
243
  .commit = act_store_commit,
244
  .rollback = act_store_rollback,
245
};
246
247
/* API */
248
249
int sieve_act_store_add_to_result(const struct sieve_runtime_env *renv,
250
          const char *name,
251
          struct sieve_side_effects_list *seffects,
252
          const char *mailbox)
253
0
{
254
0
  pool_t pool;
255
0
  struct act_store_context *act;
256
257
  /* Add redirect action to the result */
258
0
  pool = sieve_result_pool(renv->result);
259
0
  act = p_new(pool, struct act_store_context, 1);
260
0
  act->mailbox = p_strdup(pool, mailbox);
261
262
0
  return sieve_result_add_action(renv, NULL, name, &act_store, seffects,
263
0
               act, 0, TRUE);
264
0
}
265
266
void sieve_act_store_add_flags(const struct sieve_action_exec_env *aenv,
267
             void *tr_context, const char *const *keywords,
268
             enum mail_flags flags)
269
0
{
270
0
  struct act_store_transaction *trans =
271
0
    (struct act_store_transaction *)tr_context;
272
273
0
  i_assert(trans != NULL);
274
275
  /* Assign mail keywords for subsequent mailbox_copy() */
276
0
  if (*keywords != NULL) {
277
0
    const char *const *kw;
278
279
0
    if (!array_is_created(&trans->keywords)) {
280
0
      pool_t pool = sieve_result_pool(aenv->result);
281
0
      p_array_init(&trans->keywords, pool, 2);
282
0
    }
283
284
0
    kw = keywords;
285
0
    while (*kw != NULL) {
286
0
      array_append(&trans->keywords, kw, 1);
287
0
      kw++;
288
0
    }
289
0
  }
290
291
  /* Assign mail flags for subsequent mailbox_copy() */
292
0
  trans->flags |= flags;
293
294
0
  trans->flags_altered = TRUE;
295
0
}
296
297
/* Equality */
298
299
static bool
300
act_store_equals(const struct sieve_script_env *senv,
301
     const struct sieve_action *act1,
302
     const struct sieve_action *act2)
303
0
{
304
0
  struct act_store_context *st_ctx1 =
305
0
    (act1 == NULL ?
306
0
     NULL : (struct act_store_context *)act1->context);
307
0
  struct act_store_context *st_ctx2 =
308
0
    (act2 == NULL ?
309
0
     NULL : (struct act_store_context *)act2->context);
310
0
  const char *mailbox1, *mailbox2;
311
312
  /* FIXME: consider namespace aliases */
313
314
0
  if (st_ctx1 == NULL && st_ctx2 == NULL)
315
0
    return TRUE;
316
317
0
  mailbox1 = (st_ctx1 == NULL ?
318
0
        SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx1->mailbox);
319
0
  mailbox2 = (st_ctx2 == NULL ?
320
0
        SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx2->mailbox);
321
322
0
  if (strcmp(mailbox1, mailbox2) == 0)
323
0
    return TRUE;
324
325
0
  return (strcasecmp(mailbox1, "INBOX") == 0 &&
326
0
    strcasecmp(mailbox2, "INBOX") == 0);
327
0
}
328
329
/* Result verification */
330
331
static int
332
act_store_check_duplicate(const struct sieve_runtime_env *renv,
333
        const struct sieve_action *act,
334
        const struct sieve_action *act_other)
335
0
{
336
0
  const struct sieve_execute_env *eenv = renv->exec_env;
337
338
0
  return (act_store_equals(eenv->scriptenv, act, act_other) ? 1 : 0);
339
0
}
340
341
/* Result printing */
342
343
static void
344
act_store_print(const struct sieve_action *action,
345
    const struct sieve_result_print_env *rpenv, bool *keep)
346
0
{
347
0
  struct act_store_context *ctx =
348
0
    (struct act_store_context *)action->context;
349
0
  const char *mailbox;
350
351
0
  mailbox = (ctx == NULL ?
352
0
       SIEVE_SCRIPT_DEFAULT_MAILBOX(rpenv->scriptenv) :
353
0
      ctx->mailbox);
354
355
0
  sieve_result_action_printf(rpenv, "store message in folder: %s",
356
0
           str_sanitize(mailbox, 128));
357
358
0
  *keep = FALSE;
359
0
}
360
361
/* Action implementation */
362
363
void sieve_act_store_get_storage_error(const struct sieve_action_exec_env *aenv,
364
               struct act_store_transaction *trans)
365
0
{
366
0
  pool_t pool = sieve_result_pool(aenv->result);
367
368
0
  trans->error = p_strdup(pool,
369
0
    mailbox_get_last_internal_error(trans->box,
370
0
            &trans->error_code));
371
0
}
372
373
int sieve_act_store_create_error_status(
374
  const struct sieve_action_exec_env *aenv, enum mail_error error_code)
375
0
{
376
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
377
378
0
  switch (error_code) {
379
0
  case MAIL_ERROR_NONE:
380
0
    return SIEVE_EXEC_OK;
381
0
  case MAIL_ERROR_TEMP:
382
0
    return SIEVE_EXEC_TEMP_FAILURE;
383
0
  case MAIL_ERROR_PERM:
384
    /* The mailbox cannot be created because of insufficient
385
       permissions. This is a permanent condition. For a user's
386
       personal script, fall through to implicit keep rather than
387
       deferring the message. For a global (administrator-defined)
388
       script, defer instead, so that the administrator notices the
389
       script is behaving in a way they did not intend. */
390
0
    if ((eenv->flags & SIEVE_EXECUTE_FLAG_NOGLOBAL) != 0)
391
0
      return SIEVE_EXEC_FAILURE;
392
0
    return SIEVE_EXEC_TEMP_FAILURE;
393
0
  default:
394
0
    return SIEVE_EXEC_FAILURE;
395
0
  }
396
0
}
397
398
int sieve_act_store_create_mailbox(const struct sieve_action_exec_env *aenv,
399
           struct act_store_transaction *trans)
400
0
{
401
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
402
0
  struct mailbox *box = trans->box;
403
404
0
  trans->error = NULL;
405
0
  trans->error_code = MAIL_ERROR_NONE;
406
407
  /* Create mailbox */
408
0
  if (mailbox_create(box, NULL, FALSE) < 0) {
409
0
    sieve_act_store_get_storage_error(aenv, trans);
410
0
    if (trans->error_code != MAIL_ERROR_EXISTS) {
411
0
      return sieve_act_store_create_error_status(
412
0
        aenv, trans->error_code);
413
0
    }
414
    /* Lost a race with another session; the mailbox exists now. */
415
0
    trans->error = NULL;
416
0
    trans->error_code = MAIL_ERROR_NONE;
417
0
  }
418
419
  /* Subscribe to it if necessary */
420
0
  if (eenv->scriptenv->mailbox_autosubscribe) {
421
0
    (void)mailbox_list_set_subscribed(
422
0
      mailbox_get_namespace(box)->list,
423
0
      mailbox_get_name(box), TRUE);
424
0
  }
425
426
  /* Try opening again */
427
0
  if (mailbox_open(box) < 0) {
428
0
    sieve_act_store_get_storage_error(aenv, trans);
429
0
    return sieve_act_store_create_error_status(
430
0
      aenv, trans->error_code);
431
0
  }
432
0
  return SIEVE_EXEC_OK;
433
0
}
434
435
static bool
436
act_store_mailbox_alloc(const struct sieve_action_exec_env *aenv,
437
            const char *mailbox, struct mailbox **box_r,
438
      enum mail_error *error_code_r, const char **error_r)
439
0
{
440
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
441
0
  struct mailbox *box;
442
0
  struct mail_storage **storage = &(eenv->exec_status->last_storage);
443
0
  enum mailbox_flags flags = MAILBOX_FLAG_POST_SESSION;
444
445
0
  *box_r = NULL;
446
0
  *error_code_r = MAIL_ERROR_NONE;
447
0
  *error_r = NULL;
448
449
0
  if (!uni_utf8_str_is_valid(mailbox)) {
450
    /* Just a precaution; already (supposed to be) checked at
451
       compiletime/runtime.
452
     */
453
0
    *error_r = t_strdup_printf("mailbox name not utf-8: %s",
454
0
             mailbox);
455
0
    *error_code_r = MAIL_ERROR_PARAMS;
456
0
    return FALSE;
457
0
  }
458
459
0
  *box_r = box = mailbox_alloc_for_user(eenv->scriptenv->user, mailbox,
460
0
                flags);
461
0
  *storage = mailbox_get_storage(box);
462
463
0
  return TRUE;
464
0
}
465
466
static int
467
act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context)
468
0
{
469
0
  const struct sieve_action *action = aenv->action;
470
0
  struct act_store_context *ctx =
471
0
    (struct act_store_context *)action->context;
472
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
473
0
  const struct sieve_script_env *senv = eenv->scriptenv;
474
0
  struct act_store_transaction *trans;
475
0
  struct mailbox *box = NULL;
476
0
  pool_t pool = sieve_result_pool(aenv->result);
477
0
  const char *error = NULL;
478
0
  enum mail_error error_code = MAIL_ERROR_NONE;
479
0
  bool disabled = FALSE, alloc_failed = FALSE;
480
481
  /* If context is NULL, the store action is the result of (implicit)
482
     keep.
483
   */
484
0
  if (ctx == NULL) {
485
0
    ctx = p_new(pool, struct act_store_context, 1);
486
0
    ctx->mailbox =
487
0
      p_strdup(pool, SIEVE_SCRIPT_DEFAULT_MAILBOX(senv));
488
0
  }
489
490
0
  e_debug(aenv->event, "Start storing into mailbox %s", ctx->mailbox);
491
492
  /* Open the requested mailbox */
493
494
  /* NOTE: The caller of the sieve library is allowed to leave user set
495
     to NULL. This implementation will then skip actually storing the
496
     message.
497
   */
498
0
  if (senv->user != NULL) {
499
0
    if (!act_store_mailbox_alloc(aenv, ctx->mailbox, &box,
500
0
               &error_code, &error))
501
0
      alloc_failed = TRUE;
502
0
  } else {
503
0
    disabled = TRUE;
504
0
  }
505
506
  /* Create transaction context */
507
0
  trans = p_new(pool, struct act_store_transaction, 1);
508
509
0
  trans->context = ctx;
510
0
  trans->box = box;
511
0
  trans->flags = 0;
512
513
0
  trans->mailbox_name = ctx->mailbox;
514
0
  trans->mailbox_identifier =
515
0
    p_strdup_printf(pool, "'%s'", str_sanitize(ctx->mailbox, 256));
516
517
0
  trans->disabled = disabled;
518
519
0
  if (alloc_failed) {
520
0
    trans->error = p_strdup(pool, error);
521
0
    trans->error_code = error_code;
522
0
    e_debug(aenv->event, "Failed to open mailbox %s: %s",
523
0
      trans->mailbox_identifier, trans->error);
524
0
  } else {
525
0
    trans->error_code = MAIL_ERROR_NONE;
526
0
  }
527
528
0
  *tr_context = trans;
529
530
0
  switch (trans->error_code) {
531
0
  case MAIL_ERROR_NONE:
532
0
  case MAIL_ERROR_NOTFOUND:
533
0
    return SIEVE_EXEC_OK;
534
0
  case MAIL_ERROR_TEMP:
535
0
    return SIEVE_EXEC_TEMP_FAILURE;
536
0
  default:
537
0
    break;
538
0
  }
539
0
  return SIEVE_EXEC_FAILURE;
540
0
}
541
542
static struct mail_keywords *
543
act_store_keywords_create(const struct sieve_action_exec_env *aenv,
544
        ARRAY_TYPE(const_string) *keywords,
545
        struct mailbox *box, bool create_empty)
546
0
{
547
0
  struct mail_keywords *box_keywords = NULL;
548
0
  const char *const *kwds = NULL;
549
550
0
  if (!array_is_created(keywords) || array_count(keywords) == 0) {
551
0
    if (!create_empty)
552
0
      return NULL;
553
0
  } else {
554
0
    ARRAY_TYPE(const_string) valid_keywords;
555
0
    const char *error;
556
0
    unsigned int count, i;
557
558
0
    kwds = array_get(keywords, &count);
559
0
    t_array_init(&valid_keywords, count);
560
561
0
    for (i = 0; i < count; i++) {
562
0
      if (mailbox_keyword_is_valid(box, kwds[i], &error)) {
563
0
        array_append(&valid_keywords, &kwds[i], 1);
564
0
        continue;
565
0
      }
566
567
0
      sieve_result_warning(aenv,
568
0
        "specified IMAP keyword '%s' is invalid "
569
0
        "(ignored): %s",  str_sanitize(kwds[i], 64),
570
0
        sieve_error_from_external(error));
571
0
    }
572
573
0
    array_append_zero(keywords);
574
0
    kwds = array_idx(keywords, 0);
575
0
  }
576
577
0
  if (mailbox_keywords_create(box, kwds, &box_keywords) < 0) {
578
0
    sieve_result_error(
579
0
      aenv, "invalid keywords set for stored message");
580
0
    return NULL;
581
0
  }
582
583
0
  return box_keywords;
584
0
}
585
586
static bool have_equal_keywords(struct mail *mail, struct mail_keywords *new_kw)
587
0
{
588
0
  const ARRAY_TYPE(keyword_indexes) *old_kw_arr =
589
0
    mail_get_keyword_indexes(mail);
590
0
  const unsigned int *old_kw;
591
0
  unsigned int i, j;
592
593
0
  if (array_count(old_kw_arr) != new_kw->count)
594
0
    return FALSE;
595
0
  if (new_kw->count == 0)
596
0
    return TRUE;
597
598
0
  old_kw = array_front(old_kw_arr);
599
0
  for (i = 0; i < new_kw->count; i++) {
600
    /* new_kw->count equals old_kw's count and it's easier to use */
601
0
    for (j = 0; j < new_kw->count; j++) {
602
0
      if (old_kw[j] == new_kw->idx[i])
603
0
        break;
604
0
    }
605
0
    if (j == new_kw->count)
606
0
      return FALSE;
607
0
  }
608
0
  return TRUE;
609
0
}
610
611
static int
612
act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
613
      bool *keep)
614
0
{
615
0
  const struct sieve_action *action = aenv->action;
616
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
617
0
  struct act_store_transaction *trans =
618
0
    (struct act_store_transaction *)tr_context;
619
0
  struct mail *mail = (action->mail != NULL ?
620
0
           action->mail : eenv->msgdata->mail);
621
0
  struct mail_save_context *save_ctx;
622
0
  struct mail_keywords *keywords = NULL;
623
0
  struct mailbox *box;
624
0
  bool backends_equal = FALSE;
625
0
  int status = SIEVE_EXEC_OK;
626
627
  /* Verify transaction */
628
0
  if (trans == NULL)
629
0
    return SIEVE_EXEC_FAILURE;
630
0
  box = trans->box;
631
632
  /* Check whether we need to do anything */
633
0
  if (trans->disabled) {
634
0
    e_debug(aenv->event, "Skip storing into mailbox %s",
635
0
      trans->mailbox_identifier);
636
0
    *keep = FALSE;
637
0
    return SIEVE_EXEC_OK;
638
0
  }
639
640
  /* Exit early if mailbox is not available */
641
0
  if (box == NULL)
642
0
    return SIEVE_EXEC_FAILURE;
643
644
0
  e_debug(aenv->event, "Execute storing into mailbox %s",
645
0
    trans->mailbox_identifier);
646
647
  /* Mark attempt to use storage. Can only get here when all previous
648
     actions succeeded.
649
   */
650
0
  eenv->exec_status->last_storage = mailbox_get_storage(box);
651
652
  /* Open the mailbox (may already be open) */
653
0
  if (trans->error_code == MAIL_ERROR_NONE) {
654
0
    if (mailbox_open(box) < 0) {
655
0
      sieve_act_store_get_storage_error(aenv, trans);
656
0
      e_debug(aenv->event, "Failed to open mailbox %s: %s",
657
0
        trans->mailbox_identifier, trans->error);
658
0
    }
659
0
  }
660
661
  /* Create the mailbox if it does not exist yet and autocreation is
662
     enabled (e.g. lda_mailbox_autocreate=yes). The :create side effect
663
     creates it earlier; this handles a plain fileinto. */
664
0
  if (trans->error_code == MAIL_ERROR_NOTFOUND &&
665
0
      eenv->scriptenv->mailbox_autocreate) {
666
0
    int cstatus = sieve_act_store_create_mailbox(aenv, trans);
667
0
    if (cstatus != SIEVE_EXEC_OK)
668
0
      return cstatus;
669
0
  }
670
671
  /* Exit early if transaction already failed */
672
0
  switch (trans->error_code) {
673
0
  case MAIL_ERROR_NONE:
674
0
    break;
675
0
  case MAIL_ERROR_TEMP:
676
0
    return SIEVE_EXEC_TEMP_FAILURE;
677
0
  default:
678
0
    return SIEVE_EXEC_FAILURE;
679
0
  }
680
681
  /* If the message originates from the target mailbox, only update the
682
     flags and keywords (if not read-only)
683
   */
684
0
  if (mailbox_backends_equal(box, mail->box)) {
685
0
    backends_equal = TRUE;
686
0
  } else {
687
0
    struct mail *real_mail;
688
689
0
    if (mail_get_backend_mail(mail, &real_mail) < 0)
690
0
      return SIEVE_EXEC_FAILURE;
691
0
    if (real_mail != mail &&
692
0
        mailbox_backends_equal(box, real_mail->box))
693
0
      backends_equal = TRUE;
694
0
  }
695
0
  if (backends_equal) {
696
0
    trans->redundant = TRUE;
697
698
0
    if (trans->flags_altered && !mailbox_is_readonly(mail->box)) {
699
0
      keywords = act_store_keywords_create(
700
0
        aenv, &trans->keywords, mail->box, TRUE);
701
702
0
      if (keywords != NULL) {
703
0
        if (!have_equal_keywords(mail, keywords)) {
704
0
          eenv->exec_status->significant_action_executed = TRUE;
705
0
          mail_update_keywords(mail, MODIFY_REPLACE, keywords);
706
0
        }
707
0
        mailbox_keywords_unref(&keywords);
708
0
      }
709
710
0
      if ((mail_get_flags(mail) & MAIL_FLAGS_NONRECENT) != trans->flags) {
711
0
        eenv->exec_status->significant_action_executed = TRUE;
712
0
        mail_update_flags(mail, MODIFY_REPLACE, trans->flags);
713
0
      }
714
0
    }
715
0
    e_debug(aenv->event, "Updated existing mail in mailbox %s",
716
0
      trans->mailbox_identifier);
717
0
    return SIEVE_EXEC_OK;
718
719
  /* If the message is modified, only store it in the source mailbox when
720
     it is not opened read-only. Mail structs of modified messages have
721
     their own mailbox, unrelated to the orignal mail, so this case needs
722
     to be handled separately.
723
   */
724
0
  } else if (mail != eenv->msgdata->mail &&
725
0
       mailbox_is_readonly(eenv->msgdata->mail->box) &&
726
0
       (mailbox_backends_equal(box, eenv->msgdata->mail->box))) {
727
0
    e_debug(aenv->event,
728
0
      "Not modifying exsiting mail in read-only mailbox %s",
729
0
      trans->mailbox_identifier);
730
0
    trans->redundant = TRUE;
731
0
    return SIEVE_EXEC_OK;
732
0
  }
733
734
  /* Mark attempt to store in default mailbox */
735
0
  if (strcmp(trans->context->mailbox,
736
0
       SIEVE_SCRIPT_DEFAULT_MAILBOX(eenv->scriptenv)) == 0)
737
0
    eenv->exec_status->tried_default_save = TRUE;
738
739
  /* Start mail transaction */
740
0
  trans->mail_trans = mailbox_transaction_begin(
741
0
    box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
742
743
  /* Store the message */
744
0
  save_ctx = mailbox_save_alloc(trans->mail_trans);
745
746
  /* Apply keywords and flags that side-effects may have added */
747
0
  if (trans->flags_altered) {
748
0
    keywords = act_store_keywords_create(aenv, &trans->keywords,
749
0
                 box, FALSE);
750
751
0
    if (trans->flags != 0 || keywords != NULL) {
752
0
      eenv->exec_status->significant_action_executed = TRUE;
753
0
      mailbox_save_set_flags(save_ctx, trans->flags, keywords);
754
0
    }
755
0
  } else {
756
0
    mailbox_save_copy_flags(save_ctx, mail);
757
0
  }
758
759
0
  if (mailbox_save_using_mail(&save_ctx, mail) < 0) {
760
0
    sieve_act_store_get_storage_error(aenv, trans);
761
0
    e_debug(aenv->event, "Failed to save to mailbox %s: %s",
762
0
      trans->mailbox_identifier, trans->error);
763
764
0
    status = (trans->error_code == MAIL_ERROR_TEMP ?
765
0
        SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
766
0
  } else {
767
0
    e_debug(aenv->event, "Saving to mailbox %s successful so far",
768
0
      trans->mailbox_identifier);
769
0
    eenv->exec_status->significant_action_executed = TRUE;
770
0
  }
771
772
  /* Deallocate keywords */
773
0
  if (keywords != NULL)
774
0
    mailbox_keywords_unref(&keywords);
775
776
  /* Cancel implicit keep if all went well so far */
777
0
  *keep = (status < SIEVE_EXEC_OK);
778
779
0
  return status;
780
0
}
781
782
static void
783
act_store_log_status(struct act_store_transaction *trans,
784
         const struct sieve_action_exec_env *aenv,
785
         bool rolled_back, bool status)
786
0
{
787
0
  const char *mailbox_name = trans->mailbox_name;
788
0
  const char *mailbox_identifier = trans->mailbox_identifier;
789
790
0
  if (trans->box != NULL) {
791
0
    const char *mailbox_vname = str_sanitize(mailbox_get_vname(trans->box), 128);
792
793
0
    if (strcmp(trans->mailbox_name, mailbox_vname) != 0) {
794
0
      mailbox_identifier = t_strdup_printf(
795
0
        "%s (%s)", mailbox_identifier,
796
0
        str_sanitize(mailbox_vname, 256));
797
0
    }
798
0
  }
799
800
  /* Store disabled? */
801
0
  if (trans->disabled) {
802
0
    sieve_result_global_log(aenv, "store into mailbox %s skipped",
803
0
          mailbox_identifier);
804
  /* Store redundant? */
805
0
  } else if (trans->redundant) {
806
0
    sieve_result_global_log(aenv, "left message in mailbox %s",
807
0
          mailbox_identifier);
808
  /* Store failed? */
809
0
  } else if (!status) {
810
0
    const char *errstr;
811
0
    enum mail_error error_code;
812
813
0
    if (trans->error == NULL)
814
0
      sieve_act_store_get_storage_error(aenv, trans);
815
816
0
    errstr = trans->error;
817
0
    error_code = trans->error_code;
818
819
0
    if (error_code == MAIL_ERROR_NOQUOTA) {
820
      /* Never log quota problems as error in global log */
821
0
      sieve_result_global_log_error(
822
0
        aenv, "failed to store into mailbox %s: %s",
823
0
        mailbox_identifier, errstr);
824
0
    } else if (error_code == MAIL_ERROR_NOTFOUND ||
825
0
         error_code == MAIL_ERROR_PARAMS ||
826
0
         error_code == MAIL_ERROR_PERM) {
827
0
      sieve_result_error(
828
0
        aenv, "failed to store into mailbox %s: %s",
829
0
        mailbox_identifier, errstr);
830
0
    } else {
831
0
      sieve_result_global_error(
832
0
        aenv, "failed to store into mailbox %s: %s",
833
0
        mailbox_identifier, errstr);
834
0
    }
835
  /* Store aborted? */
836
0
  } else if (rolled_back) {
837
0
    if (!aenv->action->keep) {
838
0
      sieve_result_global_log(
839
0
        aenv, "store into mailbox %s aborted",
840
0
        mailbox_identifier);
841
0
    } else {
842
0
      e_debug(aenv->event, "Store into mailbox %s aborted",
843
0
        mailbox_identifier);
844
0
    }
845
  /* Succeeded */
846
0
  } else {
847
0
    struct event_passthrough *e =
848
0
      sieve_action_create_finish_event(aenv)->
849
0
      add_str("fileinto_mailbox_name", mailbox_name)->
850
0
      add_str("fileinto_mailbox", mailbox_identifier);
851
0
    sieve_result_event_log(aenv, e->event(),
852
0
               "stored mail into mailbox %s",
853
0
               mailbox_identifier);
854
0
  }
855
0
}
856
857
static void act_store_cleanup(struct act_store_transaction *trans)
858
0
{
859
0
  if (trans->mail_trans != NULL)
860
0
    mailbox_transaction_rollback(&trans->mail_trans);
861
0
  if (trans->box != NULL)
862
0
    mailbox_free(&trans->box);
863
0
}
864
865
static int
866
act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
867
0
{
868
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
869
0
  struct act_store_transaction *trans =
870
0
    (struct act_store_transaction *)tr_context;
871
0
  bool bail_out = FALSE, status = TRUE;
872
0
  int ret = SIEVE_EXEC_OK;
873
874
  /* Verify transaction */
875
0
  if (trans == NULL)
876
0
    return SIEVE_EXEC_FAILURE;
877
878
0
  e_debug(aenv->event, "Commit storing into mailbox %s",
879
0
    trans->mailbox_identifier);
880
881
  /* Check whether we can commit this transaction */
882
0
  if (trans->error_code != MAIL_ERROR_NONE) {
883
    /* Transaction already failed */
884
0
    bail_out = TRUE;
885
0
    status = FALSE;
886
0
    if (trans->error_code == MAIL_ERROR_TEMP)
887
0
      ret = SIEVE_EXEC_TEMP_FAILURE;
888
0
    else
889
0
      ret = SIEVE_EXEC_FAILURE;
890
  /* Check whether we need to do anything */
891
0
  } else if (trans->disabled) {
892
    /* Nothing to do */
893
0
    bail_out = TRUE;
894
0
  } else if (trans->redundant) {
895
    /* This transaction is redundant */
896
0
    bail_out = TRUE;
897
0
    eenv->exec_status->keep_original = TRUE;
898
0
    eenv->exec_status->message_saved = TRUE;
899
0
  }
900
0
  if (bail_out) {
901
0
    act_store_log_status(trans, aenv, FALSE, status);
902
0
    act_store_cleanup(trans);
903
0
    return ret;
904
0
  }
905
906
0
  i_assert(trans->box != NULL);
907
0
  i_assert(trans->mail_trans != NULL);
908
909
  /* Mark attempt to use storage. Can only get here when all previous
910
     actions succeeded.
911
   */
912
0
  eenv->exec_status->last_storage = mailbox_get_storage(trans->box);
913
914
  /* Commit mailbox transaction */
915
0
  status = (mailbox_transaction_commit(&trans->mail_trans) == 0);
916
917
  /* Note the fact that the message was stored at least once */
918
0
  if (status)
919
0
    eenv->exec_status->message_saved = TRUE;
920
0
  else
921
0
    eenv->exec_status->store_failed = TRUE;
922
923
  /* Log our status */
924
0
  act_store_log_status(trans, aenv, FALSE, status);
925
926
  /* Clean up */
927
0
  act_store_cleanup(trans);
928
929
0
  if (status)
930
0
    return SIEVE_EXEC_OK;
931
932
0
  return (trans->error_code == MAIL_ERROR_TEMP ?
933
0
    SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
934
0
}
935
936
static void
937
act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
938
       bool success)
939
0
{
940
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
941
0
  struct act_store_transaction *trans =
942
0
    (struct act_store_transaction *)tr_context;
943
944
0
  if (trans == NULL)
945
0
    return;
946
947
0
  e_debug(aenv->event, "Roll back storing into mailbox %s",
948
0
    trans->mailbox_identifier);
949
950
0
  i_assert(trans->box != NULL);
951
952
0
  if (!success) {
953
0
    eenv->exec_status->last_storage =
954
0
      mailbox_get_storage(trans->box);
955
0
    eenv->exec_status->store_failed = TRUE;
956
0
  }
957
958
  /* Log status */
959
0
  act_store_log_status(trans, aenv, TRUE, success);
960
961
  /* Rollback mailbox transaction and clean up */
962
0
  act_store_cleanup(trans);
963
0
}
964
965
/*
966
 * Redirect action
967
 */
968
969
int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
970
             const char *name,
971
             struct sieve_side_effects_list *seffects,
972
             const struct smtp_address *to_address)
973
0
{
974
0
  const struct sieve_execute_env *eenv = renv->exec_env;
975
0
  struct sieve_instance *svinst = eenv->svinst;
976
0
  struct act_redirect_context *act;
977
0
  pool_t pool;
978
979
0
  pool = sieve_result_pool(renv->result);
980
0
  act = p_new(pool, struct act_redirect_context, 1);
981
0
  act->to_address = smtp_address_clone(pool, to_address);
982
983
0
  if (sieve_result_add_action(renv, NULL, name, &act_redirect, seffects,
984
0
            act, svinst->set->max_redirects,
985
0
            TRUE) < 0)
986
0
    return SIEVE_EXEC_FAILURE;
987
988
0
  return SIEVE_EXEC_OK;
989
0
}
990
991
/*
992
 * Action utility functions
993
 */
994
995
/* Rejecting the mail */
996
997
static int
998
sieve_action_do_reject_mail(const struct sieve_action_exec_env *aenv,
999
          const struct smtp_address *recipient,
1000
          const char *reason)
1001
0
{
1002
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
1003
0
  struct sieve_instance *svinst = eenv->svinst;
1004
0
  const struct sieve_script_env *senv = eenv->scriptenv;
1005
0
  const struct sieve_message_data *msgdata = eenv->msgdata;
1006
0
  const struct smtp_address *sender, *orig_recipient;
1007
0
  struct istream *input;
1008
0
  struct ostream *output;
1009
0
  struct sieve_smtp_context *sctx;
1010
0
  const char *new_msgid, *boundary, *error;
1011
0
  string_t *hdr;
1012
0
  int ret;
1013
1014
0
  sender = sieve_message_get_sender(aenv->msgctx);
1015
0
  orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
1016
1017
0
  sctx = sieve_smtp_start_single(senv, sender, NULL, &output);
1018
1019
  /* Just to be sure */
1020
0
  if (sctx == NULL) {
1021
0
    sieve_result_global_warning(
1022
0
      aenv, "reject action has no means to send mail");
1023
0
    return SIEVE_EXEC_OK;
1024
0
  }
1025
1026
0
  new_msgid = sieve_message_get_new_id(svinst);
1027
0
  boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
1028
1029
0
  hdr = t_str_new(512);
1030
0
  rfc2822_header_write(hdr, "X-Sieve", SIEVE_IMPLEMENTATION);
1031
0
  rfc2822_header_write(hdr, "Message-ID", new_msgid);
1032
0
  rfc2822_header_write(hdr, "Date", message_date_create(ioloop_time));
1033
0
  rfc2822_header_write(hdr, "From", sieve_get_postmaster_address(senv));
1034
0
  rfc2822_header_printf(hdr, "To", "<%s>", smtp_address_encode(sender));
1035
0
  rfc2822_header_write(hdr, "Subject", "Automatically rejected mail");
1036
0
  rfc2822_header_write(hdr, "Auto-Submitted", "auto-replied (rejected)");
1037
0
  rfc2822_header_write(hdr, "Precedence", "bulk");
1038
1039
0
  rfc2822_header_write(hdr, "MIME-Version", "1.0");
1040
0
  rfc2822_header_printf(hdr, "Content-Type",
1041
0
            "multipart/report; "
1042
0
            "report-type=disposition-notification;\r\n"
1043
0
            "boundary=\"%s\"", boundary);
1044
1045
0
  str_append(hdr, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
1046
1047
  /* Human readable status report */
1048
0
  str_printfa(hdr, "--%s\r\n", boundary);
1049
0
  rfc2822_header_write(hdr, "Content-Type", "text/plain; charset=utf-8");
1050
0
  rfc2822_header_write(hdr, "Content-Disposition", "inline");
1051
0
  rfc2822_header_write(hdr, "Content-Transfer-Encoding", "8bit");
1052
1053
0
  str_printfa(hdr, "\r\nYour message to <%s> was automatically rejected:\r\n"
1054
0
        "%s\r\n", smtp_address_encode(recipient), reason);
1055
1056
  /* MDN status report */
1057
0
  str_printfa(hdr, "--%s\r\n", boundary);
1058
0
  rfc2822_header_write(hdr, "Content-Type",
1059
0
           "message/disposition-notification");
1060
0
  str_append(hdr, "\r\n");
1061
0
  rfc2822_header_write(hdr,
1062
0
           "Reporting-UA: %s; Dovecot Mail Delivery Agent",
1063
0
           svinst->hostname);
1064
0
  if (orig_recipient != NULL) {
1065
0
    rfc2822_header_printf(hdr, "Original-Recipient", "rfc822; %s",
1066
0
              smtp_address_encode(orig_recipient));
1067
0
  }
1068
0
  rfc2822_header_printf(hdr, "Final-Recipient", "rfc822; %s",
1069
0
            smtp_address_encode(recipient));
1070
1071
0
  if (msgdata->id != NULL)
1072
0
    rfc2822_header_write(hdr, "Original-Message-ID", msgdata->id);
1073
0
  rfc2822_header_write(hdr, "Disposition",
1074
0
    "automatic-action/MDN-sent-automatically; deleted");
1075
0
  str_append(hdr, "\r\n");
1076
1077
  /* original message's headers */
1078
0
  str_printfa(hdr, "--%s\r\n", boundary);
1079
0
  rfc2822_header_write(hdr, "Content-Type", "message/rfc822");
1080
0
  str_append(hdr, "\r\n");
1081
0
  o_stream_nsend(output, str_data(hdr), str_len(hdr));
1082
1083
0
  if (mail_get_hdr_stream(msgdata->mail, NULL, &input) == 0) {
1084
    /* NOTE: If you add more headers, they need to be sorted. We'll
1085
       drop Content-Type because we're not including the message
1086
       body, and having a multipart Content-Type may confuse some
1087
       MIME parsers when they don't see the message boundaries.
1088
     */
1089
0
    static const char *const exclude_headers[] = {
1090
0
      "Content-Type"
1091
0
    };
1092
1093
0
    input = i_stream_create_header_filter(
1094
0
      input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR |
1095
0
             HEADER_FILTER_HIDE_BODY,
1096
0
      exclude_headers, N_ELEMENTS(exclude_headers),
1097
0
      *null_header_filter_callback, NULL);
1098
0
    o_stream_nsend_istream(output, input);
1099
0
    i_stream_unref(&input);
1100
0
  }
1101
1102
0
  str_truncate(hdr, 0);
1103
0
  str_printfa(hdr, "\r\n\r\n--%s--\r\n", boundary);
1104
0
  o_stream_nsend(output, str_data(hdr), str_len(hdr));
1105
1106
0
  if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
1107
0
    if (ret < 0) {
1108
0
      sieve_result_global_error(aenv,
1109
0
        "failed to send rejection message to <%s>: %s "
1110
0
        "(temporary failure)",
1111
0
        smtp_address_encode(sender),
1112
0
        str_sanitize(error, 512));
1113
0
    } else {
1114
0
      sieve_result_global_log_error(aenv,
1115
0
        "failed to send rejection message to <%s>: %s "
1116
0
        "(permanent failure)",
1117
0
        smtp_address_encode(sender),
1118
0
        str_sanitize(error, 512));
1119
0
    }
1120
0
    return SIEVE_EXEC_FAILURE;
1121
0
  }
1122
1123
0
  return SIEVE_EXEC_OK;
1124
0
}
1125
1126
int sieve_action_reject_mail(const struct sieve_action_exec_env *aenv,
1127
           const struct smtp_address *recipient,
1128
           const char *reason)
1129
0
{
1130
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
1131
0
  const struct sieve_script_env *senv = eenv->scriptenv;
1132
0
  int result;
1133
1134
0
  T_BEGIN {
1135
0
    if (senv->reject_mail != NULL) {
1136
0
      result = (senv->reject_mail(senv, recipient,
1137
0
                reason) >= 0 ?
1138
0
          SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE);
1139
0
    } else {
1140
0
      result = sieve_action_do_reject_mail(aenv, recipient,
1141
0
                   reason);
1142
0
    }
1143
0
  } T_END;
1144
1145
0
  return result;
1146
0
}
1147
1148
/*
1149
 * Mailbox
1150
 */
1151
1152
bool sieve_mailbox_check_name(const char *mailbox, const char **error_r)
1153
0
{
1154
0
  if (!uni_utf8_str_is_valid(mailbox)) {
1155
0
    *error_r = "invalid utf-8";
1156
0
    return FALSE;
1157
0
  }
1158
0
  return TRUE;
1159
0
}
1160
1161