Coverage Report

Created: 2026-08-08 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str-sanitize.h"
5
#include "mail-storage.h"
6
#include "mail-namespace.h"
7
8
#include "sieve-common.h"
9
#include "sieve-actions.h"
10
#include "sieve-extensions.h"
11
#include "sieve-commands.h"
12
#include "sieve-stringlist.h"
13
#include "sieve-code.h"
14
#include "sieve-validator.h"
15
#include "sieve-generator.h"
16
#include "sieve-interpreter.h"
17
#include "sieve-dump.h"
18
19
#include "ext-special-use-common.h"
20
21
/*
22
 * specialuse_exists command
23
 *
24
 * Syntax:
25
 *    specialuse_exists [<mailbox: string>]
26
 *                      <special-use-flags: string-list>
27
 */
28
29
static bool
30
tst_specialuse_exists_validate(struct sieve_validator *valdtr,
31
             struct sieve_command *tst);
32
static bool
33
tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv,
34
             struct sieve_command *ctx);
35
36
const struct sieve_command_def specialuse_exists_test = {
37
  .identifier = "specialuse_exists",
38
  .type = SCT_TEST,
39
  .positional_args = -1, /* We check positional arguments ourselves */
40
  .subtests = 0,
41
  .block_allowed = FALSE,
42
  .block_required = FALSE,
43
  .validate = tst_specialuse_exists_validate,
44
  .generate = tst_specialuse_exists_generate,
45
};
46
47
/*
48
 * Mailboxexists operation
49
 */
50
51
static bool
52
tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv,
53
             sieve_size_t *address);
54
static int
55
tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv,
56
          sieve_size_t *address);
57
58
const struct sieve_operation_def specialuse_exists_operation = {
59
  .mnemonic = "SPECIALUSE_EXISTS",
60
  .ext_def = &special_use_extension,
61
  .dump = tst_specialuse_exists_operation_dump,
62
  .execute = tst_specialuse_exists_operation_execute,
63
};
64
65
/*
66
 * Test validation
67
 */
68
69
struct _validate_context {
70
  struct sieve_validator *valdtr;
71
  struct sieve_command *tst;
72
};
73
74
static int
75
tst_specialuse_exists_flag_validate(void *context,
76
            struct sieve_ast_argument *arg)
77
0
{
78
0
  struct _validate_context *valctx = (struct _validate_context *)context;
79
80
0
  if (sieve_argument_is_string_literal(arg)) {
81
0
    const char *flag = sieve_ast_argument_strc(arg);
82
83
0
    if (!ext_special_use_flag_valid(flag)) {
84
0
      sieve_argument_validate_error(
85
0
        valctx->valdtr, arg, "%s test: "
86
0
        "invalid special-use flag '%s' specified",
87
0
        sieve_command_identifier(valctx->tst),
88
0
        str_sanitize(flag, 64));
89
0
    }
90
0
  }
91
92
0
  return 1;
93
0
}
94
95
static bool
96
tst_specialuse_exists_validate(struct sieve_validator *valdtr,
97
             struct sieve_command *tst)
98
0
{
99
0
  struct sieve_ast_argument *arg = tst->first_positional;
100
0
  struct sieve_ast_argument *arg2;
101
0
  struct sieve_ast_argument *aarg;
102
0
  struct _validate_context valctx;
103
104
0
  if (arg == NULL) {
105
0
    sieve_command_validate_error(
106
0
      valdtr, tst, "the %s %s expects at least one argument, "
107
0
      "but none was found",
108
0
      sieve_command_identifier(tst),
109
0
      sieve_command_type_name(tst));
110
0
    return FALSE;
111
0
  }
112
113
0
  if (sieve_ast_argument_type(arg) != SAAT_STRING &&
114
0
      sieve_ast_argument_type(arg) != SAAT_STRING_LIST) {
115
0
    sieve_argument_validate_error(
116
0
      valdtr, arg,
117
0
      "the %s %s expects either a string (mailbox) or "
118
0
      "a string-list (special-use flags) as first argument, "
119
0
      "but %s was found",
120
0
      sieve_command_identifier(tst),
121
0
      sieve_command_type_name(tst),
122
0
      sieve_ast_argument_name(arg));
123
0
    return FALSE;
124
0
  }
125
126
0
  arg2 = sieve_ast_argument_next(arg);
127
0
  if (arg2 != NULL) {
128
    /* First, check syntax sanity */
129
0
    if (sieve_ast_argument_type(arg) != SAAT_STRING) {
130
0
      sieve_argument_validate_error(
131
0
        valdtr, arg,
132
0
        "if a second argument is specified for the %s %s, "
133
0
        "the first must be a string (mailbox), "
134
0
        "but %s was found",
135
0
        sieve_command_identifier(tst),
136
0
        sieve_command_type_name(tst),
137
0
        sieve_ast_argument_name(arg));
138
0
      return FALSE;
139
0
    }
140
0
    if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
141
0
      return FALSE;
142
143
    /* Check name validity when mailbox argument is not a variable */
144
0
    if (sieve_argument_is_string_literal(arg)) {
145
0
      const char *mailbox = sieve_ast_argument_strc(arg);
146
0
      const char *error;
147
148
0
      if (!sieve_mailbox_check_name(mailbox, &error)) {
149
0
        sieve_argument_validate_warning(
150
0
          valdtr, arg, "%s test: "
151
0
          "invalid mailbox name '%s' specified: %s",
152
0
          sieve_command_identifier(tst),
153
0
          str_sanitize(mailbox, 256), error);
154
0
      }
155
0
    }
156
157
0
    if (sieve_ast_argument_type(arg2) != SAAT_STRING &&
158
0
        sieve_ast_argument_type(arg2) != SAAT_STRING_LIST) {
159
0
      sieve_argument_validate_error(
160
0
        valdtr, arg2,
161
0
        "the %s %s expects a string list (special-use flags) as "
162
0
        "second argument when two arguments are specified, "
163
0
        "but %s was found",
164
0
        sieve_command_identifier(tst),
165
0
        sieve_command_type_name(tst),
166
0
        sieve_ast_argument_name(arg2));
167
0
      return FALSE;
168
0
    }
169
0
  } else
170
0
    arg2 = arg;
171
172
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg2, FALSE))
173
0
    return FALSE;
174
175
0
  aarg = arg2;
176
0
  i_zero(&valctx);
177
0
  valctx.valdtr = valdtr;
178
0
  valctx.tst = tst;
179
180
0
  return (sieve_ast_stringlist_map(
181
0
    &aarg, &valctx,
182
0
    tst_specialuse_exists_flag_validate) >= 0);
183
0
}
184
185
/*
186
 * Test generation
187
 */
188
189
static bool
190
tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv,
191
             struct sieve_command *tst)
192
0
{
193
0
  struct sieve_ast_argument *arg = tst->first_positional;
194
0
  struct sieve_ast_argument *arg2;
195
196
0
  sieve_operation_emit(cgenv->sblock,
197
0
    tst->ext, &specialuse_exists_operation);
198
199
  /* Generate arguments */
200
0
  arg2 = sieve_ast_argument_next(arg);
201
0
  if (arg2 != NULL) {
202
0
    if (!sieve_generate_argument(cgenv, arg, tst))
203
0
      return FALSE;
204
0
  } else {
205
0
    sieve_opr_omitted_emit(cgenv->sblock);
206
0
    arg2 = arg;
207
0
  }
208
0
  return sieve_generate_argument(cgenv, arg2, tst);
209
0
}
210
211
/*
212
 * Code dump
213
 */
214
215
static bool
216
tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv,
217
             sieve_size_t *address)
218
0
{
219
0
  struct sieve_operand oprnd;
220
221
0
  sieve_code_dumpf(denv, "SPECIALUSE_EXISTS");
222
0
  sieve_code_descend(denv);
223
224
0
  sieve_code_mark(denv);
225
0
  if (!sieve_operand_read(denv->sblock, address, NULL, &oprnd)) {
226
0
    sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
227
0
    return FALSE;
228
0
  }
229
230
0
  if (!sieve_operand_is_omitted(&oprnd)) {
231
0
    return (sieve_opr_string_dump_data(denv, &oprnd,
232
0
               address, "mailbox") &&
233
0
            sieve_opr_stringlist_dump(denv, address,
234
0
              "special-use-flags"));
235
0
  }
236
237
0
  return sieve_opr_stringlist_dump(denv, address, "special-use-flags");
238
0
}
239
240
/*
241
 * Code execution
242
 */
243
244
static int
245
tst_specialuse_find_mailbox(const struct sieve_runtime_env *renv,
246
          const char *mailbox, struct mailbox **box_r)
247
0
{
248
0
  const struct sieve_execute_env *eenv = renv->exec_env;
249
0
  struct mail_user *user = eenv->scriptenv->user;
250
0
  struct mailbox *box;
251
0
  bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
252
0
  enum mail_error error_code;
253
0
  const char *error;
254
255
0
  *box_r = NULL;
256
257
0
  if (user == NULL)
258
0
    return 0;
259
260
  /* Open the box */
261
0
  box = mailbox_alloc_for_user(user, mailbox, MAILBOX_FLAG_POST_SESSION);
262
0
  if (mailbox_open(box) < 0) {
263
0
    error = mailbox_get_last_internal_error(box, &error_code);
264
265
0
    if (trace) {
266
0
      sieve_runtime_trace(
267
0
        renv, 0, "mailbox '%s' cannot be opened: %s",
268
0
        str_sanitize(mailbox, 256), error);
269
0
    }
270
271
0
    mailbox_free(&box);
272
273
0
    if (error_code == MAIL_ERROR_TEMP) {
274
0
      sieve_runtime_error(
275
0
        renv, NULL, "specialuse_exists test: "
276
0
        "failed to open mailbox '%s': %s",
277
0
        str_sanitize(mailbox, 256), error);
278
0
      return -1;
279
0
    }
280
0
    return 0;
281
0
  }
282
283
  /* Also fail when it is readonly */
284
0
  if (mailbox_is_readonly(box)) {
285
0
    if (trace) {
286
0
      sieve_runtime_trace(
287
0
        renv, 0, "mailbox '%s' is read-only",
288
0
        str_sanitize(mailbox, 256));
289
0
    }
290
291
0
    mailbox_free(&box);
292
0
    return 0;
293
0
  }
294
295
0
  *box_r = box;
296
0
  return 1;
297
0
}
298
299
static int
300
tst_specialuse_find_specialuse(const struct sieve_runtime_env *renv,
301
             const char *special_use)
302
0
{
303
0
  const struct sieve_execute_env *eenv = renv->exec_env;
304
0
  struct mail_user *user = eenv->scriptenv->user;
305
0
  struct mailbox *box;
306
0
  bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
307
0
  enum mail_error error_code;
308
0
  const char *error;
309
310
0
  if (user == NULL)
311
0
    return 0;
312
313
  /* Open the box */
314
0
  box = mailbox_alloc_for_user(user, special_use,
315
0
             (MAILBOX_FLAG_POST_SESSION |
316
0
              MAILBOX_FLAG_SPECIAL_USE));
317
0
  if (mailbox_open(box) < 0) {
318
0
    error = mailbox_get_last_internal_error(box, &error_code);
319
320
0
    if (trace) {
321
0
      sieve_runtime_trace(
322
0
        renv, 0, "mailbox with special-use flag '%s' "
323
0
        "cannot be opened: %s",
324
0
        str_sanitize(special_use, 64), error);
325
0
    }
326
327
0
    mailbox_free(&box);
328
329
0
    if (error_code == MAIL_ERROR_TEMP) {
330
0
      sieve_runtime_error(
331
0
        renv, NULL, "specialuse_exists test: "
332
0
        "failed to open mailbox with special-use flag'%s': %s",
333
0
        str_sanitize(special_use, 64), error);
334
0
      return -1;
335
0
    }
336
0
    return 0;
337
0
  }
338
339
  /* Also fail when it is readonly */
340
0
  if (mailbox_is_readonly(box)) {
341
0
    if (trace) {
342
0
      sieve_runtime_trace(
343
0
        renv, 0,
344
0
        "mailbox with special-use flag '%s' is read-only",
345
0
        str_sanitize(special_use, 64));
346
0
    }
347
348
0
    mailbox_free(&box);
349
0
    return 0;
350
0
  }
351
352
0
  mailbox_free(&box);
353
0
  return 1;
354
0
}
355
356
static int
357
tst_specialuse_exists_check_flag(const struct sieve_runtime_env *renv,
358
         struct mailbox *box, const char *use_flag,
359
         bool trace, bool *all_exist_r)
360
0
{
361
0
  int ret;
362
363
0
  if (!ext_special_use_flag_valid(use_flag)) {
364
0
    sieve_runtime_error(
365
0
      renv, NULL, "specialuse_exists test: "
366
0
      "invalid special-use flag '%s' specified",
367
0
      str_sanitize(use_flag, 64));
368
0
    return SIEVE_EXEC_FAILURE;
369
0
  }
370
371
0
  if (box != NULL) {
372
    /* Mailbox has this SPECIAL-USE flag? */
373
0
    if (!mailbox_has_special_use(box, use_flag)) {
374
0
      *all_exist_r = FALSE;
375
0
      return SIEVE_EXEC_OK;
376
0
    }
377
0
  } else {
378
    /* Is there mailbox with this SPECIAL-USE flag? */
379
0
    ret = tst_specialuse_find_specialuse(renv, use_flag);
380
0
    if (ret < 0)
381
0
      return SIEVE_EXEC_TEMP_FAILURE;
382
0
    if (ret == 0) {
383
0
      *all_exist_r = FALSE;
384
0
      return SIEVE_EXEC_OK;
385
0
    }
386
0
  }
387
388
0
  if (trace) {
389
0
    sieve_runtime_trace(
390
0
      renv, 0, "special-use flag '%s' exists",
391
0
      str_sanitize(use_flag, 80));
392
0
  }
393
394
0
  return SIEVE_EXEC_OK;
395
0
}
396
397
static int
398
tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv,
399
          sieve_size_t *address)
400
0
{
401
0
  struct sieve_operand oprnd;
402
0
  struct sieve_stringlist *special_use_flags;
403
0
  string_t *mailbox, *special_use_flag;
404
0
  struct mailbox *box = NULL;
405
0
  const char *error;
406
0
  bool trace = FALSE, all_exist = TRUE;
407
0
  int ret;
408
409
  /*
410
   * Read operands
411
   */
412
413
  /* Read bare operand (two types possible) */
414
0
  ret = sieve_operand_runtime_read(renv, address, NULL, &oprnd);
415
0
  if (ret <= 0)
416
0
    return ret;
417
418
  /* Mailbox operand (optional) */
419
0
  mailbox = NULL;
420
0
  if (!sieve_operand_is_omitted(&oprnd)) {
421
    /* Read the mailbox operand */
422
0
    ret = sieve_opr_string_read_data(renv, &oprnd, address,
423
0
             "mailbox", &mailbox);
424
0
    if (ret <= 0)
425
0
      return ret;
426
427
    /* Read flag list */
428
0
    ret = sieve_opr_stringlist_read(renv, address,
429
0
            "special-use-flags",
430
0
            &special_use_flags);
431
0
    if (ret <= 0)
432
0
      return ret;
433
434
  /* Flag-list operand */
435
0
  } else {
436
    /* Read flag list */
437
0
    ret = sieve_opr_stringlist_read(renv, address,
438
0
            "special-use-flags",
439
0
            &special_use_flags);
440
0
    if (ret <= 0)
441
0
      return ret;
442
0
  }
443
444
  /*
445
   * Perform operation
446
   */
447
448
0
  if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
449
0
    sieve_runtime_trace(renv, 0, "specialuse_exists test");
450
0
    sieve_runtime_trace_descend(renv);
451
452
0
    trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
453
0
  }
454
455
0
  if (mailbox != NULL) {
456
0
    if (!sieve_mailbox_check_name(str_c(mailbox), &error)) {
457
0
      sieve_runtime_warning(
458
0
        renv, NULL, "specialuse_exists test: "
459
0
        "invalid mailbox name '%s' specified: %s",
460
0
        str_sanitize(str_c(mailbox), 256), error);
461
0
      sieve_interpreter_set_test_result(renv->interp, FALSE);
462
0
      return SIEVE_EXEC_OK;
463
0
    }
464
465
0
    if (tst_specialuse_find_mailbox(renv, str_c(mailbox), &box) < 0)
466
0
      return SIEVE_EXEC_TEMP_FAILURE;
467
0
  }
468
469
0
  if (box == NULL && mailbox != NULL) {
470
0
    sieve_runtime_trace(
471
0
      renv, 0, "mailbox '%s' is not accessible",
472
0
      str_sanitize(str_c(mailbox), 80));
473
0
    sieve_interpreter_set_test_result(renv->interp, FALSE);
474
0
    return SIEVE_EXEC_OK;
475
0
  }
476
477
0
  if (mailbox != NULL) {
478
0
    sieve_runtime_trace(
479
0
      renv, 0, "mailbox '%s' is accessible",
480
0
      str_sanitize(str_c(mailbox), 80));
481
0
  }
482
483
0
  ret = 0;
484
0
  special_use_flag = NULL;
485
0
  while (all_exist &&
486
0
         (ret = sieve_stringlist_next_item(
487
0
    special_use_flags, &special_use_flag)) > 0) {
488
0
    const char *use_flag = str_c(special_use_flag);
489
490
0
    ret = tst_specialuse_exists_check_flag(
491
0
      renv, box, use_flag, trace, &all_exist);
492
0
    if (ret <= 0) {
493
0
      if (box != NULL) {
494
        /* Close mailbox */
495
0
        mailbox_free(&box);
496
0
      }
497
0
      return ret;
498
0
    }
499
0
  }
500
501
0
  if (box != NULL) {
502
    /* Close mailbox */
503
0
    mailbox_free(&box);
504
0
  }
505
506
0
  if (ret < 0) {
507
0
    sieve_runtime_trace_error(
508
0
      renv, "invalid special-use flag item");
509
0
    return SIEVE_EXEC_BIN_CORRUPT;
510
0
  }
511
512
0
  if (trace) {
513
0
    if (all_exist) {
514
0
      sieve_runtime_trace(
515
0
        renv, 0, "all special-use flags are set");
516
0
    } else {
517
0
      sieve_runtime_trace(
518
0
        renv, 0, "some special-use are not set");
519
0
    }
520
0
  }
521
522
0
  sieve_interpreter_set_test_result(renv->interp, all_exist);
523
0
  return SIEVE_EXEC_OK;
524
0
}