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/ext-reject.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
/* Extension reject
4
 * ----------------
5
 *
6
 * Authors: Stephan Bosch
7
 * Specification: RFC 5429
8
 * Implementation: full
9
 * Status: testing
10
 *
11
 */
12
13
#include "lib.h"
14
#include "ioloop.h"
15
#include "hostpid.h"
16
#include "str-sanitize.h"
17
#include "message-date.h"
18
#include "message-size.h"
19
#include "istream.h"
20
#include "istream-header-filter.h"
21
22
#include "rfc2822.h"
23
24
#include "sieve-common.h"
25
#include "sieve-extensions.h"
26
#include "sieve-commands.h"
27
#include "sieve-code.h"
28
#include "sieve-actions.h"
29
#include "sieve-validator.h"
30
#include "sieve-generator.h"
31
#include "sieve-binary.h"
32
#include "sieve-interpreter.h"
33
#include "sieve-dump.h"
34
#include "sieve-result.h"
35
#include "sieve-message.h"
36
#include "sieve-smtp.h"
37
38
/*
39
 * Forward declarations
40
 */
41
42
static const struct sieve_command_def reject_command;
43
static const struct sieve_operation_def reject_operation;
44
45
static const struct sieve_command_def ereject_command;
46
static const struct sieve_operation_def ereject_operation;
47
48
/*
49
 * Extensions
50
 */
51
52
static bool
53
ext_reject_validator_validate(const struct sieve_extension *ext,
54
            struct sieve_validator *valdtr, void *context,
55
            struct sieve_ast_argument *require_arg,
56
            bool required);
57
static int
58
ext_reject_interpreter_run(const struct sieve_extension *this_ext,
59
         const struct sieve_runtime_env *renv,
60
         void *context, bool deferred);
61
62
/* Reject */
63
64
static bool
65
ext_reject_validator_load(const struct sieve_extension *ext,
66
        struct sieve_validator *valdtr);
67
static bool
68
ext_reject_interpreter_load(const struct sieve_extension *ext,
69
          const struct sieve_runtime_env *renv,
70
          sieve_size_t *address);
71
72
const struct sieve_extension_def reject_extension = {
73
  .name = "reject",
74
  .validator_load = ext_reject_validator_load,
75
  .interpreter_load = ext_reject_interpreter_load,
76
  SIEVE_EXT_DEFINE_OPERATION(reject_operation)
77
};
78
const struct sieve_validator_extension
79
reject_validator_extension = {
80
  .ext = &reject_extension,
81
  .validate = ext_reject_validator_validate
82
};
83
const struct sieve_interpreter_extension
84
reject_interpreter_extension = {
85
  .ext_def = &reject_extension,
86
  .run = ext_reject_interpreter_run
87
};
88
89
static bool
90
ext_reject_validator_load(const struct sieve_extension *ext,
91
        struct sieve_validator *valdtr)
92
0
{
93
  /* Register new command */
94
0
  sieve_validator_register_command(valdtr, ext, &reject_command);
95
96
0
  sieve_validator_extension_register(valdtr, ext,
97
0
             &reject_validator_extension, NULL);
98
0
  return TRUE;
99
0
}
100
101
static bool
102
ext_reject_interpreter_load(const struct sieve_extension *ext,
103
          const struct sieve_runtime_env *renv,
104
          sieve_size_t *address ATTR_UNUSED)
105
0
{
106
0
  sieve_interpreter_extension_register(renv->interp, ext,
107
0
               &reject_interpreter_extension,
108
0
               NULL);
109
0
  return TRUE;
110
0
}
111
112
/* EReject */
113
114
static bool
115
ext_ereject_validator_load(const struct sieve_extension *ext,
116
         struct sieve_validator *valdtr);
117
static bool
118
ext_ereject_interpreter_load(const struct sieve_extension *ext,
119
           const struct sieve_runtime_env *renv,
120
           sieve_size_t *address);
121
122
const struct sieve_extension_def ereject_extension = {
123
  .name = "ereject",
124
  .validator_load = ext_ereject_validator_load,
125
  .interpreter_load = ext_ereject_interpreter_load,
126
  SIEVE_EXT_DEFINE_OPERATION(ereject_operation)
127
};
128
const struct sieve_validator_extension
129
ereject_validator_extension = {
130
  .ext = &ereject_extension,
131
  .validate = ext_reject_validator_validate
132
};
133
const struct sieve_interpreter_extension
134
ereject_interpreter_extension = {
135
  .ext_def = &ereject_extension,
136
  .run = ext_reject_interpreter_run
137
};
138
139
static bool
140
ext_ereject_validator_load(const struct sieve_extension *ext,
141
         struct sieve_validator *valdtr)
142
0
{
143
  /* Register new command */
144
0
  sieve_validator_register_command(valdtr, ext, &ereject_command);
145
146
0
  sieve_validator_extension_register(valdtr, ext,
147
0
             &ereject_validator_extension, NULL);
148
0
  return TRUE;
149
0
}
150
151
static bool
152
ext_ereject_interpreter_load(const struct sieve_extension *ext,
153
           const struct sieve_runtime_env *renv,
154
           sieve_size_t *address ATTR_UNUSED)
155
0
{
156
0
  sieve_interpreter_extension_register(renv->interp, ext,
157
0
               &ereject_interpreter_extension,
158
0
               NULL);
159
0
  return TRUE;
160
0
}
161
162
/* Environment checking */
163
164
static bool
165
ext_reject_validator_validate(const struct sieve_extension *ext,
166
            struct sieve_validator *valdtr,
167
            void *context ATTR_UNUSED,
168
            struct sieve_ast_argument *require_arg,
169
            bool required)
170
0
{
171
0
  if (required) {
172
0
    enum sieve_compile_flags flags =
173
0
      sieve_validator_compile_flags(valdtr);
174
175
0
    if ((flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0) {
176
0
      sieve_argument_validate_error(
177
0
        valdtr, require_arg,
178
0
        "the %s extension cannot be used in this context "
179
0
        "(needs access to message envelope)",
180
0
        sieve_extension_name(ext));
181
0
      return FALSE;
182
0
    }
183
0
  }
184
0
  return TRUE;
185
0
}
186
187
static int
188
ext_reject_interpreter_run(const struct sieve_extension *ext,
189
         const struct sieve_runtime_env *renv,
190
         void *context ATTR_UNUSED, bool deferred)
191
0
{
192
0
  const struct sieve_execute_env *eenv = renv->exec_env;
193
194
0
  if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0) {
195
0
    if (!deferred) {
196
0
      sieve_runtime_error(
197
0
        renv, NULL,
198
0
        "the %s extension cannot be used in this context "
199
0
        "(needs access to message envelope)",
200
0
        sieve_extension_name(ext));
201
0
    }
202
0
    return SIEVE_EXEC_FAILURE;
203
0
  }
204
0
  return SIEVE_EXEC_OK;
205
0
}
206
207
/*
208
 * Commands
209
 */
210
211
/* Forward declarations */
212
213
static bool
214
cmd_reject_validate(struct sieve_validator *valdtr, struct sieve_command *cmd);
215
static bool
216
cmd_reject_generate(const struct sieve_codegen_env *cgenv,
217
        struct sieve_command *cmd);
218
219
/* Reject command
220
 *
221
 * Syntax:
222
 *   reject <reason: string>
223
 */
224
225
static const struct sieve_command_def reject_command = {
226
  .identifier = "reject",
227
  .type = SCT_COMMAND,
228
  .positional_args = 1,
229
  .subtests = 0,
230
  .block_allowed = FALSE,
231
  .block_required = FALSE,
232
  .validate = cmd_reject_validate,
233
  .generate = cmd_reject_generate
234
};
235
236
/* EReject command
237
 *
238
 * Syntax:
239
 *   ereject <reason: string>
240
 */
241
242
static const struct sieve_command_def ereject_command = {
243
  .identifier = "ereject",
244
  .type = SCT_COMMAND,
245
  .positional_args = 1,
246
  .subtests = 0,
247
  .block_allowed = FALSE,
248
  .block_required = FALSE,
249
  .validate = cmd_reject_validate,
250
  .generate = cmd_reject_generate,
251
};
252
253
/*
254
 * Operations
255
 */
256
257
/* Forward declarations */
258
259
static bool
260
ext_reject_operation_dump(const struct sieve_dumptime_env *denv,
261
        sieve_size_t *address);
262
static int
263
ext_reject_operation_execute(const struct sieve_runtime_env *renv,
264
           sieve_size_t *address);
265
266
/* Reject operation */
267
268
static const struct sieve_operation_def reject_operation = {
269
  .mnemonic = "REJECT",
270
  .ext_def = &reject_extension,
271
  .dump = ext_reject_operation_dump,
272
  .execute = ext_reject_operation_execute
273
};
274
275
/* EReject operation */
276
277
static const struct sieve_operation_def ereject_operation = {
278
  .mnemonic = "EREJECT",
279
  .ext_def = &ereject_extension,
280
  .dump = ext_reject_operation_dump,
281
  .execute = ext_reject_operation_execute
282
};
283
284
/*
285
 * Reject action
286
 */
287
288
static int
289
act_reject_check_duplicate(const struct sieve_runtime_env *renv,
290
         const struct sieve_action *act,
291
         const struct sieve_action *act_other);
292
int act_reject_check_conflict(const struct sieve_runtime_env *renv,
293
            const struct sieve_action *act,
294
            const struct sieve_action *act_other);
295
static void
296
act_reject_print(const struct sieve_action *action,
297
     const struct sieve_result_print_env *rpenv, bool *keep);
298
static int
299
act_reject_start(const struct sieve_action_exec_env *aenv, void **tr_context);
300
static int
301
act_reject_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
302
       bool *keep);
303
static int
304
act_reject_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
305
306
const struct sieve_action_def act_reject = {
307
  .name = "reject",
308
  .flags = SIEVE_ACTFLAG_SENDS_RESPONSE,
309
  .check_duplicate = act_reject_check_duplicate,
310
  .check_conflict = act_reject_check_conflict,
311
  .print = act_reject_print,
312
  .start = act_reject_start,
313
  .execute = act_reject_execute,
314
  .commit = act_reject_commit,
315
};
316
317
struct act_reject_context {
318
  const char *reason;
319
  bool ereject;
320
};
321
322
/*
323
 * Validation
324
 */
325
326
static bool
327
cmd_reject_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
328
0
{
329
0
  struct sieve_ast_argument *arg = cmd->first_positional;
330
331
0
  if (!sieve_validate_positional_argument(valdtr, cmd, arg, "reason",
332
0
            1, SAAT_STRING))
333
0
    return FALSE;
334
335
0
  return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
336
0
}
337
338
/*
339
 * Code generation
340
 */
341
342
static bool
343
cmd_reject_generate(const struct sieve_codegen_env *cgenv,
344
        struct sieve_command *cmd)
345
0
{
346
0
  if (sieve_command_is(cmd, reject_command)) {
347
0
    sieve_operation_emit(cgenv->sblock, cmd->ext,
348
0
             &reject_operation);
349
0
  } else {
350
0
    sieve_operation_emit(cgenv->sblock, cmd->ext,
351
0
             &ereject_operation);
352
0
  }
353
354
  /* Generate arguments */
355
0
  return sieve_generate_arguments(cgenv, cmd, NULL);
356
0
}
357
358
/*
359
 * Code dump
360
 */
361
362
static bool
363
ext_reject_operation_dump(const struct sieve_dumptime_env *denv,
364
        sieve_size_t *address)
365
0
{
366
0
  sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
367
0
  sieve_code_descend(denv);
368
369
0
  if (sieve_action_opr_optional_dump(denv, address, NULL) != 0)
370
0
    return FALSE;
371
372
0
  return sieve_opr_string_dump(denv, address, "reason");
373
0
}
374
375
/*
376
 * Interpretation
377
 */
378
379
static int
380
ext_reject_operation_execute(const struct sieve_runtime_env *renv,
381
           sieve_size_t *address)
382
0
{
383
0
  const struct sieve_operation *oprtn = renv->oprtn;
384
0
  const struct sieve_extension *this_ext = oprtn->ext;
385
0
  struct sieve_side_effects_list *slist = NULL;
386
0
  struct act_reject_context *act;
387
0
  string_t *reason;
388
0
  pool_t pool;
389
0
  int ret;
390
391
  /*
392
   * Read data
393
   */
394
395
  /* Optional operands (side effects only) */
396
0
  if (sieve_action_opr_optional_read(renv, address, NULL,
397
0
             &ret, &slist) != 0)
398
0
    return ret;
399
400
  /* Read rejection reason */
401
0
  if ((ret = sieve_opr_string_read(renv, address, "reason",
402
0
           &reason)) <= 0)
403
0
    return ret;
404
405
  /*
406
   * Perform operation
407
   */
408
409
0
  if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
410
0
    if (sieve_operation_is(oprtn, ereject_operation))
411
0
      sieve_runtime_trace(renv, 0, "ereject action");
412
0
    else
413
0
      sieve_runtime_trace(renv, 0, "reject action");
414
415
0
    sieve_runtime_trace_descend(renv);
416
0
    sieve_runtime_trace(renv, 0, "reject message with reason '%s'",
417
0
            str_sanitize(str_c(reason), 64));
418
0
  }
419
420
  /* Add reject action to the result */
421
0
  pool = sieve_result_pool(renv->result);
422
0
  act = p_new(pool, struct act_reject_context, 1);
423
0
  act->reason = p_strdup(pool, str_c(reason));
424
0
  act->ereject = sieve_operation_is(oprtn, ereject_operation);
425
426
0
  if (sieve_result_add_action(renv, this_ext,
427
0
            (act->ereject ? "ereject" : "reject"),
428
0
            &act_reject, slist, act,
429
0
            0, FALSE) < 0)
430
0
    return SIEVE_EXEC_FAILURE;
431
0
  return SIEVE_EXEC_OK;
432
0
}
433
434
/*
435
 * Action implementation
436
 */
437
438
struct act_reject_transaction {
439
  bool ignore_reject:1;
440
};
441
442
static int
443
act_reject_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
444
         const struct sieve_action *act,
445
         const struct sieve_action *act_other)
446
0
{
447
0
  if (!sieve_action_is_executed(act_other, renv->result)) {
448
0
    sieve_runtime_error(
449
0
      renv, act->location,
450
0
      "duplicate reject/ereject action not allowed "
451
0
      "(previously triggered one was here: %s)",
452
0
      act_other->location);
453
0
    return -1;
454
0
  }
455
456
0
  return 1;
457
0
}
458
459
int act_reject_check_conflict(const struct sieve_runtime_env *renv,
460
            const struct sieve_action *act,
461
            const struct sieve_action *act_other)
462
0
{
463
0
  if ((act_other->def->flags & SIEVE_ACTFLAG_TRIES_DELIVER) > 0) {
464
0
    if (!sieve_action_is_executed(act_other, renv->result)) {
465
0
      sieve_runtime_error(
466
0
        renv, act->location,
467
0
        "reject/ereject action conflicts with other action: "
468
0
        "the %s action (%s) tries to deliver the message",
469
0
        act_other->def->name, act_other->location);
470
0
      return -1;
471
0
    }
472
0
  }
473
474
0
  if ((act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0) {
475
0
    struct act_reject_context *rj_ctx;
476
477
0
    if (!sieve_action_is_executed(act_other, renv->result)) {
478
0
      sieve_runtime_error(
479
0
        renv, act->location,
480
0
        "reject/ereject action conflicts with other action: "
481
0
        "the %s action (%s) also sends a response to the sender",
482
0
        act_other->def->name, act_other->location);
483
0
      return -1;
484
0
    }
485
486
    /* Conflicting action was already executed, transform reject
487
     * into discard equivalent.
488
     */
489
0
    rj_ctx = (struct act_reject_context *)act->context;
490
0
    rj_ctx->reason = NULL;
491
0
  }
492
493
0
  return 0;
494
0
}
495
496
static void
497
act_reject_print(const struct sieve_action *action,
498
     const struct sieve_result_print_env *rpenv, bool *keep)
499
0
{
500
0
  struct act_reject_context *rj_ctx =
501
0
    (struct act_reject_context *)action->context;
502
503
0
  if (rj_ctx->reason != NULL) {
504
0
    sieve_result_action_printf(
505
0
      rpenv, "reject message with reason: %s",
506
0
      str_sanitize(rj_ctx->reason, 128));
507
0
  } else {
508
0
    sieve_result_action_printf(
509
0
      rpenv,
510
0
      "reject message without sending a response (discard)");
511
0
  }
512
513
0
  *keep = FALSE;
514
0
}
515
516
static int
517
act_reject_start(const struct sieve_action_exec_env *aenv, void **tr_context)
518
0
{
519
0
  struct act_reject_transaction *trans;
520
0
  pool_t pool = sieve_result_pool(aenv->result);
521
522
  /* Create transaction context */
523
0
  trans = p_new(pool, struct act_reject_transaction, 1);
524
0
  *tr_context = trans;
525
526
0
  return SIEVE_EXEC_OK;
527
0
}
528
529
static int
530
act_reject_execute(const struct sieve_action_exec_env *aenv,
531
       void *tr_context, bool *keep)
532
0
{
533
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
534
0
  struct act_reject_context *rj_ctx =
535
0
    (struct act_reject_context *)aenv->action->context;
536
0
  struct act_reject_transaction *trans = tr_context;
537
0
  const struct smtp_address *sender, *recipient;
538
539
0
  sender = sieve_message_get_sender(aenv->msgctx);
540
0
  recipient = sieve_message_get_orig_recipient(aenv->msgctx);
541
542
0
  if ((eenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) {
543
0
    sieve_result_global_log(
544
0
      aenv, "not sending reject message (skipped)");
545
0
    trans->ignore_reject = TRUE;
546
0
    return SIEVE_EXEC_OK;
547
0
  }
548
0
  if (smtp_address_isnull(recipient)) {
549
0
    sieve_result_global_warning(
550
0
      aenv, "reject action aborted: envelope recipient is <>");
551
0
    trans->ignore_reject = TRUE;
552
0
    return SIEVE_EXEC_OK;
553
0
  }
554
0
  if (rj_ctx->reason == NULL) {
555
0
    sieve_result_global_log(
556
0
      aenv, "not sending reject message "
557
0
      "(would cause second response to sender)");
558
0
    trans->ignore_reject = TRUE;
559
0
    *keep = FALSE;
560
0
    return SIEVE_EXEC_OK;
561
0
  }
562
0
  if (smtp_address_isnull(sender)) {
563
0
    sieve_result_global_log(
564
0
      aenv, "not sending reject message to <>");
565
0
    trans->ignore_reject = TRUE;
566
0
    *keep = FALSE;
567
0
    return SIEVE_EXEC_OK;
568
0
  }
569
570
0
  *keep = FALSE;
571
0
  return SIEVE_EXEC_OK;
572
0
}
573
574
static int
575
act_reject_commit(const struct sieve_action_exec_env *aenv,
576
      void *tr_context ATTR_UNUSED)
577
0
{
578
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
579
0
  struct act_reject_context *rj_ctx =
580
0
    (struct act_reject_context *)aenv->action->context;
581
0
  struct act_reject_transaction *trans = tr_context;
582
0
  const struct smtp_address *sender, *recipient;
583
0
  int ret;
584
585
0
  sender = sieve_message_get_sender(aenv->msgctx);
586
0
  recipient = sieve_message_get_orig_recipient(aenv->msgctx);
587
588
0
  if (trans->ignore_reject)
589
0
    return SIEVE_EXEC_OK;
590
591
0
  if ((ret = sieve_action_reject_mail(aenv, recipient,
592
0
              rj_ctx->reason)) <= 0)
593
0
    return ret;
594
595
0
  eenv->exec_status->significant_action_executed = TRUE;
596
597
0
  struct event_passthrough *e = sieve_action_create_finish_event(aenv);
598
599
0
  sieve_result_event_log(aenv, e->event(),
600
0
             "rejected message from <%s> (%s)",
601
0
             smtp_address_encode(sender),
602
0
             (rj_ctx->ereject ? "ereject" : "reject"));
603
604
0
  return SIEVE_EXEC_OK;
605
0
}