Coverage Report

Created: 2026-08-13 06:41

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
static bool
374
act_store_mailbox_alloc(const struct sieve_action_exec_env *aenv,
375
            const char *mailbox, struct mailbox **box_r,
376
      enum mail_error *error_code_r, const char **error_r)
377
0
{
378
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
379
0
  struct mailbox *box;
380
0
  struct mail_storage **storage = &(eenv->exec_status->last_storage);
381
0
  enum mailbox_flags flags = MAILBOX_FLAG_POST_SESSION;
382
383
0
  *box_r = NULL;
384
0
  *error_code_r = MAIL_ERROR_NONE;
385
0
  *error_r = NULL;
386
387
0
  if (!uni_utf8_str_is_valid(mailbox)) {
388
    /* Just a precaution; already (supposed to be) checked at
389
       compiletime/runtime.
390
     */
391
0
    *error_r = t_strdup_printf("mailbox name not utf-8: %s",
392
0
             mailbox);
393
0
    *error_code_r = MAIL_ERROR_PARAMS;
394
0
    return FALSE;
395
0
  }
396
397
0
  if (eenv->scriptenv->mailbox_autocreate)
398
0
    flags |= MAILBOX_FLAG_AUTO_CREATE;
399
0
  if (eenv->scriptenv->mailbox_autosubscribe)
400
0
    flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE;
401
0
  *box_r = box = mailbox_alloc_for_user(eenv->scriptenv->user, mailbox,
402
0
                flags);
403
0
  *storage = mailbox_get_storage(box);
404
405
0
  return TRUE;
406
0
}
407
408
static int
409
act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context)
410
0
{
411
0
  const struct sieve_action *action = aenv->action;
412
0
  struct act_store_context *ctx =
413
0
    (struct act_store_context *)action->context;
414
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
415
0
  const struct sieve_script_env *senv = eenv->scriptenv;
416
0
  struct act_store_transaction *trans;
417
0
  struct mailbox *box = NULL;
418
0
  pool_t pool = sieve_result_pool(aenv->result);
419
0
  const char *error = NULL;
420
0
  enum mail_error error_code = MAIL_ERROR_NONE;
421
0
  bool disabled = FALSE, alloc_failed = FALSE;
422
423
  /* If context is NULL, the store action is the result of (implicit)
424
     keep.
425
   */
426
0
  if (ctx == NULL) {
427
0
    ctx = p_new(pool, struct act_store_context, 1);
428
0
    ctx->mailbox =
429
0
      p_strdup(pool, SIEVE_SCRIPT_DEFAULT_MAILBOX(senv));
430
0
  }
431
432
0
  e_debug(aenv->event, "Start storing into mailbox %s", ctx->mailbox);
433
434
  /* Open the requested mailbox */
435
436
  /* NOTE: The caller of the sieve library is allowed to leave user set
437
     to NULL. This implementation will then skip actually storing the
438
     message.
439
   */
440
0
  if (senv->user != NULL) {
441
0
    if (!act_store_mailbox_alloc(aenv, ctx->mailbox, &box,
442
0
               &error_code, &error))
443
0
      alloc_failed = TRUE;
444
0
  } else {
445
0
    disabled = TRUE;
446
0
  }
447
448
  /* Create transaction context */
449
0
  trans = p_new(pool, struct act_store_transaction, 1);
450
451
0
  trans->context = ctx;
452
0
  trans->box = box;
453
0
  trans->flags = 0;
454
455
0
  trans->mailbox_name = ctx->mailbox;
456
0
  trans->mailbox_identifier =
457
0
    p_strdup_printf(pool, "'%s'", str_sanitize(ctx->mailbox, 256));
458
459
0
  trans->disabled = disabled;
460
461
0
  if (alloc_failed) {
462
0
    trans->error = p_strdup(pool, error);
463
0
    trans->error_code = error_code;
464
0
    e_debug(aenv->event, "Failed to open mailbox %s: %s",
465
0
      trans->mailbox_identifier, trans->error);
466
0
  } else {
467
0
    trans->error_code = MAIL_ERROR_NONE;
468
0
  }
469
470
0
  *tr_context = trans;
471
472
0
  switch (trans->error_code) {
473
0
  case MAIL_ERROR_NONE:
474
0
  case MAIL_ERROR_NOTFOUND:
475
0
    return SIEVE_EXEC_OK;
476
0
  case MAIL_ERROR_TEMP:
477
0
    return SIEVE_EXEC_TEMP_FAILURE;
478
0
  default:
479
0
    break;
480
0
  }
481
0
  return SIEVE_EXEC_FAILURE;
482
0
}
483
484
static struct mail_keywords *
485
act_store_keywords_create(const struct sieve_action_exec_env *aenv,
486
        ARRAY_TYPE(const_string) *keywords,
487
        struct mailbox *box, bool create_empty)
488
0
{
489
0
  struct mail_keywords *box_keywords = NULL;
490
0
  const char *const *kwds = NULL;
491
492
0
  if (!array_is_created(keywords) || array_count(keywords) == 0) {
493
0
    if (!create_empty)
494
0
      return NULL;
495
0
  } else {
496
0
    ARRAY_TYPE(const_string) valid_keywords;
497
0
    const char *error;
498
0
    unsigned int count, i;
499
500
0
    kwds = array_get(keywords, &count);
501
0
    t_array_init(&valid_keywords, count);
502
503
0
    for (i = 0; i < count; i++) {
504
0
      if (mailbox_keyword_is_valid(box, kwds[i], &error)) {
505
0
        array_append(&valid_keywords, &kwds[i], 1);
506
0
        continue;
507
0
      }
508
509
0
      sieve_result_warning(aenv,
510
0
        "specified IMAP keyword '%s' is invalid "
511
0
        "(ignored): %s",  str_sanitize(kwds[i], 64),
512
0
        sieve_error_from_external(error));
513
0
    }
514
515
0
    array_append_zero(keywords);
516
0
    kwds = array_idx(keywords, 0);
517
0
  }
518
519
0
  if (mailbox_keywords_create(box, kwds, &box_keywords) < 0) {
520
0
    sieve_result_error(
521
0
      aenv, "invalid keywords set for stored message");
522
0
    return NULL;
523
0
  }
524
525
0
  return box_keywords;
526
0
}
527
528
static bool have_equal_keywords(struct mail *mail, struct mail_keywords *new_kw)
529
0
{
530
0
  const ARRAY_TYPE(keyword_indexes) *old_kw_arr =
531
0
    mail_get_keyword_indexes(mail);
532
0
  const unsigned int *old_kw;
533
0
  unsigned int i, j;
534
535
0
  if (array_count(old_kw_arr) != new_kw->count)
536
0
    return FALSE;
537
0
  if (new_kw->count == 0)
538
0
    return TRUE;
539
540
0
  old_kw = array_front(old_kw_arr);
541
0
  for (i = 0; i < new_kw->count; i++) {
542
    /* new_kw->count equals old_kw's count and it's easier to use */
543
0
    for (j = 0; j < new_kw->count; j++) {
544
0
      if (old_kw[j] == new_kw->idx[i])
545
0
        break;
546
0
    }
547
0
    if (j == new_kw->count)
548
0
      return FALSE;
549
0
  }
550
0
  return TRUE;
551
0
}
552
553
static int
554
act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
555
      bool *keep)
556
0
{
557
0
  const struct sieve_action *action = aenv->action;
558
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
559
0
  struct act_store_transaction *trans =
560
0
    (struct act_store_transaction *)tr_context;
561
0
  struct mail *mail = (action->mail != NULL ?
562
0
           action->mail : eenv->msgdata->mail);
563
0
  struct mail_save_context *save_ctx;
564
0
  struct mail_keywords *keywords = NULL;
565
0
  struct mailbox *box;
566
0
  bool backends_equal = FALSE;
567
0
  int status = SIEVE_EXEC_OK;
568
569
  /* Verify transaction */
570
0
  if (trans == NULL)
571
0
    return SIEVE_EXEC_FAILURE;
572
0
  box = trans->box;
573
574
  /* Check whether we need to do anything */
575
0
  if (trans->disabled) {
576
0
    e_debug(aenv->event, "Skip storing into mailbox %s",
577
0
      trans->mailbox_identifier);
578
0
    *keep = FALSE;
579
0
    return SIEVE_EXEC_OK;
580
0
  }
581
582
  /* Exit early if mailbox is not available */
583
0
  if (box == NULL)
584
0
    return SIEVE_EXEC_FAILURE;
585
586
0
  e_debug(aenv->event, "Execute storing into mailbox %s",
587
0
    trans->mailbox_identifier);
588
589
  /* Mark attempt to use storage. Can only get here when all previous
590
     actions succeeded.
591
   */
592
0
  eenv->exec_status->last_storage = mailbox_get_storage(box);
593
594
  /* Open the mailbox (may already be open) */
595
0
  if (trans->error_code == MAIL_ERROR_NONE) {
596
0
    if (mailbox_open(box) < 0) {
597
0
      sieve_act_store_get_storage_error(aenv, trans);
598
0
      e_debug(aenv->event, "Failed to open mailbox %s: %s",
599
0
        trans->mailbox_identifier, trans->error);
600
0
    }
601
0
  }
602
603
  /* Exit early if transaction already failed */
604
0
  switch (trans->error_code) {
605
0
  case MAIL_ERROR_NONE:
606
0
    break;
607
0
  case MAIL_ERROR_TEMP:
608
0
    return SIEVE_EXEC_TEMP_FAILURE;
609
0
  default:
610
0
    return SIEVE_EXEC_FAILURE;
611
0
  }
612
613
  /* If the message originates from the target mailbox, only update the
614
     flags and keywords (if not read-only)
615
   */
616
0
  if (mailbox_backends_equal(box, mail->box)) {
617
0
    backends_equal = TRUE;
618
0
  } else {
619
0
    struct mail *real_mail;
620
621
0
    if (mail_get_backend_mail(mail, &real_mail) < 0)
622
0
      return SIEVE_EXEC_FAILURE;
623
0
    if (real_mail != mail &&
624
0
        mailbox_backends_equal(box, real_mail->box))
625
0
      backends_equal = TRUE;
626
0
  }
627
0
  if (backends_equal) {
628
0
    trans->redundant = TRUE;
629
630
0
    if (trans->flags_altered && !mailbox_is_readonly(mail->box)) {
631
0
      keywords = act_store_keywords_create(
632
0
        aenv, &trans->keywords, mail->box, TRUE);
633
634
0
      if (keywords != NULL) {
635
0
        if (!have_equal_keywords(mail, keywords)) {
636
0
          eenv->exec_status->significant_action_executed = TRUE;
637
0
          mail_update_keywords(mail, MODIFY_REPLACE, keywords);
638
0
        }
639
0
        mailbox_keywords_unref(&keywords);
640
0
      }
641
642
0
      if ((mail_get_flags(mail) & MAIL_FLAGS_NONRECENT) != trans->flags) {
643
0
        eenv->exec_status->significant_action_executed = TRUE;
644
0
        mail_update_flags(mail, MODIFY_REPLACE, trans->flags);
645
0
      }
646
0
    }
647
0
    e_debug(aenv->event, "Updated existing mail in mailbox %s",
648
0
      trans->mailbox_identifier);
649
0
    return SIEVE_EXEC_OK;
650
651
  /* If the message is modified, only store it in the source mailbox when
652
     it is not opened read-only. Mail structs of modified messages have
653
     their own mailbox, unrelated to the orignal mail, so this case needs
654
     to be handled separately.
655
   */
656
0
  } else if (mail != eenv->msgdata->mail &&
657
0
       mailbox_is_readonly(eenv->msgdata->mail->box) &&
658
0
       (mailbox_backends_equal(box, eenv->msgdata->mail->box))) {
659
0
    e_debug(aenv->event,
660
0
      "Not modifying exsiting mail in read-only mailbox %s",
661
0
      trans->mailbox_identifier);
662
0
    trans->redundant = TRUE;
663
0
    return SIEVE_EXEC_OK;
664
0
  }
665
666
  /* Mark attempt to store in default mailbox */
667
0
  if (strcmp(trans->context->mailbox,
668
0
       SIEVE_SCRIPT_DEFAULT_MAILBOX(eenv->scriptenv)) == 0)
669
0
    eenv->exec_status->tried_default_save = TRUE;
670
671
  /* Start mail transaction */
672
0
  trans->mail_trans = mailbox_transaction_begin(
673
0
    box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
674
675
  /* Store the message */
676
0
  save_ctx = mailbox_save_alloc(trans->mail_trans);
677
678
  /* Apply keywords and flags that side-effects may have added */
679
0
  if (trans->flags_altered) {
680
0
    keywords = act_store_keywords_create(aenv, &trans->keywords,
681
0
                 box, FALSE);
682
683
0
    if (trans->flags != 0 || keywords != NULL) {
684
0
      eenv->exec_status->significant_action_executed = TRUE;
685
0
      mailbox_save_set_flags(save_ctx, trans->flags, keywords);
686
0
    }
687
0
  } else {
688
0
    mailbox_save_copy_flags(save_ctx, mail);
689
0
  }
690
691
0
  if (mailbox_save_using_mail(&save_ctx, mail) < 0) {
692
0
    sieve_act_store_get_storage_error(aenv, trans);
693
0
    e_debug(aenv->event, "Failed to save to mailbox %s: %s",
694
0
      trans->mailbox_identifier, trans->error);
695
696
0
    status = (trans->error_code == MAIL_ERROR_TEMP ?
697
0
        SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
698
0
  } else {
699
0
    e_debug(aenv->event, "Saving to mailbox %s successful so far",
700
0
      trans->mailbox_identifier);
701
0
    eenv->exec_status->significant_action_executed = TRUE;
702
0
  }
703
704
  /* Deallocate keywords */
705
0
  if (keywords != NULL)
706
0
    mailbox_keywords_unref(&keywords);
707
708
  /* Cancel implicit keep if all went well so far */
709
0
  *keep = (status < SIEVE_EXEC_OK);
710
711
0
  return status;
712
0
}
713
714
static void
715
act_store_log_status(struct act_store_transaction *trans,
716
         const struct sieve_action_exec_env *aenv,
717
         bool rolled_back, bool status)
718
0
{
719
0
  const char *mailbox_name = trans->mailbox_name;
720
0
  const char *mailbox_identifier = trans->mailbox_identifier;
721
722
0
  if (trans->box != NULL) {
723
0
    const char *mailbox_vname = str_sanitize(mailbox_get_vname(trans->box), 128);
724
725
0
    if (strcmp(trans->mailbox_name, mailbox_vname) != 0) {
726
0
      mailbox_identifier = t_strdup_printf(
727
0
        "%s (%s)", mailbox_identifier,
728
0
        str_sanitize(mailbox_vname, 256));
729
0
    }
730
0
  }
731
732
  /* Store disabled? */
733
0
  if (trans->disabled) {
734
0
    sieve_result_global_log(aenv, "store into mailbox %s skipped",
735
0
          mailbox_identifier);
736
  /* Store redundant? */
737
0
  } else if (trans->redundant) {
738
0
    sieve_result_global_log(aenv, "left message in mailbox %s",
739
0
          mailbox_identifier);
740
  /* Store failed? */
741
0
  } else if (!status) {
742
0
    const char *errstr;
743
0
    enum mail_error error_code;
744
745
0
    if (trans->error == NULL)
746
0
      sieve_act_store_get_storage_error(aenv, trans);
747
748
0
    errstr = trans->error;
749
0
    error_code = trans->error_code;
750
751
0
    if (error_code == MAIL_ERROR_NOQUOTA) {
752
      /* Never log quota problems as error in global log */
753
0
      sieve_result_global_log_error(
754
0
        aenv, "failed to store into mailbox %s: %s",
755
0
        mailbox_identifier, errstr);
756
0
    } else if (error_code == MAIL_ERROR_NOTFOUND ||
757
0
         error_code == MAIL_ERROR_PARAMS ||
758
0
         error_code == MAIL_ERROR_PERM) {
759
0
      sieve_result_error(
760
0
        aenv, "failed to store into mailbox %s: %s",
761
0
        mailbox_identifier, errstr);
762
0
    } else {
763
0
      sieve_result_global_error(
764
0
        aenv, "failed to store into mailbox %s: %s",
765
0
        mailbox_identifier, errstr);
766
0
    }
767
  /* Store aborted? */
768
0
  } else if (rolled_back) {
769
0
    if (!aenv->action->keep) {
770
0
      sieve_result_global_log(
771
0
        aenv, "store into mailbox %s aborted",
772
0
        mailbox_identifier);
773
0
    } else {
774
0
      e_debug(aenv->event, "Store into mailbox %s aborted",
775
0
        mailbox_identifier);
776
0
    }
777
  /* Succeeded */
778
0
  } else {
779
0
    struct event_passthrough *e =
780
0
      sieve_action_create_finish_event(aenv)->
781
0
      add_str("fileinto_mailbox_name", mailbox_name)->
782
0
      add_str("fileinto_mailbox", mailbox_identifier);
783
0
    sieve_result_event_log(aenv, e->event(),
784
0
               "stored mail into mailbox %s",
785
0
               mailbox_identifier);
786
0
  }
787
0
}
788
789
static void act_store_cleanup(struct act_store_transaction *trans)
790
0
{
791
0
  if (trans->mail_trans != NULL)
792
0
    mailbox_transaction_rollback(&trans->mail_trans);
793
0
  if (trans->box != NULL)
794
0
    mailbox_free(&trans->box);
795
0
}
796
797
static int
798
act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
799
0
{
800
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
801
0
  struct act_store_transaction *trans =
802
0
    (struct act_store_transaction *)tr_context;
803
0
  bool bail_out = FALSE, status = TRUE;
804
0
  int ret = SIEVE_EXEC_OK;
805
806
  /* Verify transaction */
807
0
  if (trans == NULL)
808
0
    return SIEVE_EXEC_FAILURE;
809
810
0
  e_debug(aenv->event, "Commit storing into mailbox %s",
811
0
    trans->mailbox_identifier);
812
813
  /* Check whether we can commit this transaction */
814
0
  if (trans->error_code != MAIL_ERROR_NONE) {
815
    /* Transaction already failed */
816
0
    bail_out = TRUE;
817
0
    status = FALSE;
818
0
    if (trans->error_code == MAIL_ERROR_TEMP)
819
0
      ret = SIEVE_EXEC_TEMP_FAILURE;
820
0
    else
821
0
      ret = SIEVE_EXEC_FAILURE;
822
  /* Check whether we need to do anything */
823
0
  } else if (trans->disabled) {
824
    /* Nothing to do */
825
0
    bail_out = TRUE;
826
0
  } else if (trans->redundant) {
827
    /* This transaction is redundant */
828
0
    bail_out = TRUE;
829
0
    eenv->exec_status->keep_original = TRUE;
830
0
    eenv->exec_status->message_saved = TRUE;
831
0
  }
832
0
  if (bail_out) {
833
0
    act_store_log_status(trans, aenv, FALSE, status);
834
0
    act_store_cleanup(trans);
835
0
    return ret;
836
0
  }
837
838
0
  i_assert(trans->box != NULL);
839
0
  i_assert(trans->mail_trans != NULL);
840
841
  /* Mark attempt to use storage. Can only get here when all previous
842
     actions succeeded.
843
   */
844
0
  eenv->exec_status->last_storage = mailbox_get_storage(trans->box);
845
846
  /* Commit mailbox transaction */
847
0
  status = (mailbox_transaction_commit(&trans->mail_trans) == 0);
848
849
  /* Note the fact that the message was stored at least once */
850
0
  if (status)
851
0
    eenv->exec_status->message_saved = TRUE;
852
0
  else
853
0
    eenv->exec_status->store_failed = TRUE;
854
855
  /* Log our status */
856
0
  act_store_log_status(trans, aenv, FALSE, status);
857
858
  /* Clean up */
859
0
  act_store_cleanup(trans);
860
861
0
  if (status)
862
0
    return SIEVE_EXEC_OK;
863
864
0
  return (trans->error_code == MAIL_ERROR_TEMP ?
865
0
    SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
866
0
}
867
868
static void
869
act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
870
       bool success)
871
0
{
872
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
873
0
  struct act_store_transaction *trans =
874
0
    (struct act_store_transaction *)tr_context;
875
876
0
  if (trans == NULL)
877
0
    return;
878
879
0
  e_debug(aenv->event, "Roll back storing into mailbox %s",
880
0
    trans->mailbox_identifier);
881
882
0
  i_assert(trans->box != NULL);
883
884
0
  if (!success) {
885
0
    eenv->exec_status->last_storage =
886
0
      mailbox_get_storage(trans->box);
887
0
    eenv->exec_status->store_failed = TRUE;
888
0
  }
889
890
  /* Log status */
891
0
  act_store_log_status(trans, aenv, TRUE, success);
892
893
  /* Rollback mailbox transaction and clean up */
894
0
  act_store_cleanup(trans);
895
0
}
896
897
/*
898
 * Redirect action
899
 */
900
901
int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
902
             const char *name,
903
             struct sieve_side_effects_list *seffects,
904
             const struct smtp_address *to_address)
905
0
{
906
0
  const struct sieve_execute_env *eenv = renv->exec_env;
907
0
  struct sieve_instance *svinst = eenv->svinst;
908
0
  struct act_redirect_context *act;
909
0
  pool_t pool;
910
911
0
  pool = sieve_result_pool(renv->result);
912
0
  act = p_new(pool, struct act_redirect_context, 1);
913
0
  act->to_address = smtp_address_clone(pool, to_address);
914
915
0
  if (sieve_result_add_action(renv, NULL, name, &act_redirect, seffects,
916
0
            act, svinst->set->max_redirects,
917
0
            TRUE) < 0)
918
0
    return SIEVE_EXEC_FAILURE;
919
920
0
  return SIEVE_EXEC_OK;
921
0
}
922
923
/*
924
 * Action utility functions
925
 */
926
927
/* Rejecting the mail */
928
929
static int
930
sieve_action_do_reject_mail(const struct sieve_action_exec_env *aenv,
931
          const struct smtp_address *recipient,
932
          const char *reason)
933
0
{
934
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
935
0
  struct sieve_instance *svinst = eenv->svinst;
936
0
  const struct sieve_script_env *senv = eenv->scriptenv;
937
0
  const struct sieve_message_data *msgdata = eenv->msgdata;
938
0
  const struct smtp_address *sender, *orig_recipient;
939
0
  struct istream *input;
940
0
  struct ostream *output;
941
0
  struct sieve_smtp_context *sctx;
942
0
  const char *new_msgid, *boundary, *error;
943
0
  string_t *hdr;
944
0
  int ret;
945
946
0
  sender = sieve_message_get_sender(aenv->msgctx);
947
0
  orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
948
949
0
  sctx = sieve_smtp_start_single(senv, sender, NULL, &output);
950
951
  /* Just to be sure */
952
0
  if (sctx == NULL) {
953
0
    sieve_result_global_warning(
954
0
      aenv, "reject action has no means to send mail");
955
0
    return SIEVE_EXEC_OK;
956
0
  }
957
958
0
  new_msgid = sieve_message_get_new_id(svinst);
959
0
  boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
960
961
0
  hdr = t_str_new(512);
962
0
  rfc2822_header_write(hdr, "X-Sieve", SIEVE_IMPLEMENTATION);
963
0
  rfc2822_header_write(hdr, "Message-ID", new_msgid);
964
0
  rfc2822_header_write(hdr, "Date", message_date_create(ioloop_time));
965
0
  rfc2822_header_write(hdr, "From", sieve_get_postmaster_address(senv));
966
0
  rfc2822_header_printf(hdr, "To", "<%s>", smtp_address_encode(sender));
967
0
  rfc2822_header_write(hdr, "Subject", "Automatically rejected mail");
968
0
  rfc2822_header_write(hdr, "Auto-Submitted", "auto-replied (rejected)");
969
0
  rfc2822_header_write(hdr, "Precedence", "bulk");
970
971
0
  rfc2822_header_write(hdr, "MIME-Version", "1.0");
972
0
  rfc2822_header_printf(hdr, "Content-Type",
973
0
            "multipart/report; "
974
0
            "report-type=disposition-notification;\r\n"
975
0
            "boundary=\"%s\"", boundary);
976
977
0
  str_append(hdr, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
978
979
  /* Human readable status report */
980
0
  str_printfa(hdr, "--%s\r\n", boundary);
981
0
  rfc2822_header_write(hdr, "Content-Type", "text/plain; charset=utf-8");
982
0
  rfc2822_header_write(hdr, "Content-Disposition", "inline");
983
0
  rfc2822_header_write(hdr, "Content-Transfer-Encoding", "8bit");
984
985
0
  str_printfa(hdr, "\r\nYour message to <%s> was automatically rejected:\r\n"
986
0
        "%s\r\n", smtp_address_encode(recipient), reason);
987
988
  /* MDN status report */
989
0
  str_printfa(hdr, "--%s\r\n", boundary);
990
0
  rfc2822_header_write(hdr, "Content-Type",
991
0
           "message/disposition-notification");
992
0
  str_append(hdr, "\r\n");
993
0
  rfc2822_header_write(hdr,
994
0
           "Reporting-UA: %s; Dovecot Mail Delivery Agent",
995
0
           svinst->hostname);
996
0
  if (orig_recipient != NULL) {
997
0
    rfc2822_header_printf(hdr, "Original-Recipient", "rfc822; %s",
998
0
              smtp_address_encode(orig_recipient));
999
0
  }
1000
0
  rfc2822_header_printf(hdr, "Final-Recipient", "rfc822; %s",
1001
0
            smtp_address_encode(recipient));
1002
1003
0
  if (msgdata->id != NULL)
1004
0
    rfc2822_header_write(hdr, "Original-Message-ID", msgdata->id);
1005
0
  rfc2822_header_write(hdr, "Disposition",
1006
0
    "automatic-action/MDN-sent-automatically; deleted");
1007
0
  str_append(hdr, "\r\n");
1008
1009
  /* original message's headers */
1010
0
  str_printfa(hdr, "--%s\r\n", boundary);
1011
0
  rfc2822_header_write(hdr, "Content-Type", "message/rfc822");
1012
0
  str_append(hdr, "\r\n");
1013
0
  o_stream_nsend(output, str_data(hdr), str_len(hdr));
1014
1015
0
  if (mail_get_hdr_stream(msgdata->mail, NULL, &input) == 0) {
1016
    /* NOTE: If you add more headers, they need to be sorted. We'll
1017
       drop Content-Type because we're not including the message
1018
       body, and having a multipart Content-Type may confuse some
1019
       MIME parsers when they don't see the message boundaries.
1020
     */
1021
0
    static const char *const exclude_headers[] = {
1022
0
      "Content-Type"
1023
0
    };
1024
1025
0
    input = i_stream_create_header_filter(
1026
0
      input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR |
1027
0
             HEADER_FILTER_HIDE_BODY,
1028
0
      exclude_headers, N_ELEMENTS(exclude_headers),
1029
0
      *null_header_filter_callback, NULL);
1030
0
    o_stream_nsend_istream(output, input);
1031
0
    i_stream_unref(&input);
1032
0
  }
1033
1034
0
  str_truncate(hdr, 0);
1035
0
  str_printfa(hdr, "\r\n\r\n--%s--\r\n", boundary);
1036
0
  o_stream_nsend(output, str_data(hdr), str_len(hdr));
1037
1038
0
  if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
1039
0
    if (ret < 0) {
1040
0
      sieve_result_global_error(aenv,
1041
0
        "failed to send rejection message to <%s>: %s "
1042
0
        "(temporary failure)",
1043
0
        smtp_address_encode(sender),
1044
0
        str_sanitize(error, 512));
1045
0
    } else {
1046
0
      sieve_result_global_log_error(aenv,
1047
0
        "failed to send rejection message to <%s>: %s "
1048
0
        "(permanent failure)",
1049
0
        smtp_address_encode(sender),
1050
0
        str_sanitize(error, 512));
1051
0
    }
1052
0
    return SIEVE_EXEC_FAILURE;
1053
0
  }
1054
1055
0
  return SIEVE_EXEC_OK;
1056
0
}
1057
1058
int sieve_action_reject_mail(const struct sieve_action_exec_env *aenv,
1059
           const struct smtp_address *recipient,
1060
           const char *reason)
1061
0
{
1062
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
1063
0
  const struct sieve_script_env *senv = eenv->scriptenv;
1064
0
  int result;
1065
1066
0
  T_BEGIN {
1067
0
    if (senv->reject_mail != NULL) {
1068
0
      result = (senv->reject_mail(senv, recipient,
1069
0
                reason) >= 0 ?
1070
0
          SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE);
1071
0
    } else {
1072
0
      result = sieve_action_do_reject_mail(aenv, recipient,
1073
0
                   reason);
1074
0
    }
1075
0
  } T_END;
1076
1077
0
  return result;
1078
0
}
1079
1080
/*
1081
 * Mailbox
1082
 */
1083
1084
bool sieve_mailbox_check_name(const char *mailbox, const char **error_r)
1085
0
{
1086
0
  if (!uni_utf8_str_is_valid(mailbox)) {
1087
0
    *error_r = "invalid utf-8";
1088
0
    return FALSE;
1089
0
  }
1090
0
  return TRUE;
1091
0
}
1092
1093