Coverage Report

Created: 2026-05-16 06:58

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