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/plugins/include/ext-include-common.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "str-sanitize.h"
6
#include "home-expand.h"
7
#include "settings.h"
8
9
#include "sieve-common.h"
10
#include "sieve-error.h"
11
#include "sieve-script.h"
12
#include "sieve-storage.h"
13
#include "sieve-ast.h"
14
#include "sieve-binary.h"
15
#include "sieve-commands.h"
16
#include "sieve-validator.h"
17
#include "sieve-generator.h"
18
#include "sieve-interpreter.h"
19
20
#include "ext-include-common.h"
21
#include "ext-include-binary.h"
22
#include "ext-include-variables.h"
23
24
25
/*
26
 * Forward declarations
27
 */
28
29
/* Generator context */
30
31
struct ext_include_generator_context {
32
  unsigned int nesting_depth;
33
  enum ext_include_script_location location;
34
  const char *script_name;
35
  struct sieve_script *script;
36
  struct ext_include_generator_context *parent;
37
};
38
39
static inline struct ext_include_generator_context *
40
ext_include_get_generator_context(const struct sieve_extension *ext_this,
41
          struct sieve_generator *gentr);
42
43
/* Interpreter context */
44
45
struct ext_include_interpreter_global {
46
  ARRAY(struct sieve_script *) included_scripts;
47
48
  struct sieve_variable_scope_binary *var_scope;
49
  struct sieve_variable_storage *var_storage;
50
};
51
52
struct ext_include_interpreter_context {
53
  struct ext_include_interpreter_context *parent;
54
  struct ext_include_interpreter_global *global;
55
56
  struct sieve_interpreter *interp;
57
  pool_t pool;
58
59
  unsigned int nesting_depth;
60
61
  struct sieve_script *script;
62
  const struct ext_include_script_info *script_info;
63
64
  /* Persistent sieve_execute_env for the currently active child
65
     sub-interpreter. The child interpreter stores a pointer to this
66
     in its runenv, so it must outlive the child. Reused across
67
     successive children, since at most one child is active at a time. */
68
  struct sieve_execute_env child_eenv;
69
70
  const struct ext_include_script_info *include;
71
  bool returned;
72
};
73
74
/*
75
 * Extension configuration
76
 */
77
78
/* Extension hooks */
79
80
int ext_include_load(const struct sieve_extension *ext, void **context_r)
81
0
{
82
0
  struct sieve_instance *svinst = ext->svinst;
83
0
  const struct sieve_extension *var_ext;
84
0
  const struct ext_include_settings *set;
85
0
  struct ext_include_context *extctx;
86
0
  const char *error;
87
88
  /* Extension dependencies */
89
0
  if (sieve_ext_variables_get_extension(ext->svinst, &var_ext) < 0)
90
0
    return -1;
91
92
0
  if (settings_get(svinst->event, &ext_include_setting_parser_info, 0,
93
0
       &set, &error) < 0) {
94
0
    e_error(svinst->event, "%s", error);
95
0
    return -1;
96
0
  }
97
98
0
  extctx = i_new(struct ext_include_context, 1);
99
0
  extctx->var_ext = var_ext;
100
0
  extctx->set = set;
101
102
0
  *context_r = extctx;
103
0
  return 0;
104
0
}
105
106
void ext_include_unload(const struct sieve_extension *ext)
107
0
{
108
0
  struct ext_include_context *extctx = ext->context;
109
110
0
  if (extctx == NULL)
111
0
    return;
112
0
  sieve_storage_unref(&extctx->personal_storage);
113
0
  settings_free(extctx->set);
114
0
  i_free(extctx);
115
0
}
116
117
/*
118
 * Script access
119
 */
120
121
static int
122
ext_include_open_script_personal(struct sieve_instance *svinst,
123
         struct ext_include_context *extctx,
124
         const char *cause, const char *script_name,
125
         struct sieve_script **script_r,
126
         enum sieve_error *error_code_r)
127
0
{
128
0
  if (extctx->personal_storage == NULL &&
129
0
      sieve_storage_create_personal(svinst, NULL, cause, 0,
130
0
            &extctx->personal_storage,
131
0
            error_code_r) < 0)
132
0
    return -1;
133
134
0
  return sieve_storage_open_script(extctx->personal_storage, script_name,
135
0
           script_r, error_code_r);
136
0
}
137
138
static int
139
ext_include_open_script_global(struct sieve_instance *svinst,
140
             const char *cause, const char *script_name,
141
             struct sieve_script **script_r,
142
             enum sieve_error *error_code_r)
143
0
{
144
0
  return sieve_script_create_open(svinst, cause,
145
0
          SIEVE_STORAGE_TYPE_GLOBAL, script_name,
146
0
          script_r, error_code_r, NULL);
147
0
}
148
149
int ext_include_open_script(const struct sieve_extension *ext,
150
          enum ext_include_script_location location,
151
          const char *cause, const char *script_name,
152
          struct sieve_script **script_r,
153
          enum sieve_error *error_code_r)
154
0
{
155
0
  struct sieve_instance *svinst = ext->svinst;
156
0
  struct ext_include_context *extctx = ext->context;
157
0
  int ret;
158
159
0
  *script_r = NULL;
160
0
  switch (location) {
161
0
  case EXT_INCLUDE_LOCATION_PERSONAL:
162
0
    ret = ext_include_open_script_personal(svinst, extctx, cause,
163
0
                   script_name,
164
0
                   script_r, error_code_r);
165
0
    break;
166
0
  case EXT_INCLUDE_LOCATION_GLOBAL:
167
0
    ret = ext_include_open_script_global(svinst, cause, script_name,
168
0
                 script_r, error_code_r);
169
0
    break;
170
0
  default:
171
0
    i_unreached();
172
0
  }
173
0
  return ret;
174
0
}
175
176
/*
177
 * AST context management
178
 */
179
180
static void
181
ext_include_ast_free(const struct sieve_extension *ext ATTR_UNUSED,
182
         struct sieve_ast *ast ATTR_UNUSED, void *context)
183
0
{
184
0
  struct ext_include_ast_context *actx =
185
0
    (struct ext_include_ast_context *)context;
186
0
  struct sieve_script **scripts;
187
0
  unsigned int count, i;
188
189
  /* Unreference included scripts */
190
0
  scripts = array_get_modifiable(&actx->included_scripts, &count);
191
0
  for (i = 0; i < count; i++) {
192
0
    sieve_script_unref(&scripts[i]);
193
0
  }
194
195
  /* Unreference variable scopes */
196
0
  if (actx->global_vars != NULL)
197
0
    sieve_variable_scope_unref(&actx->global_vars);
198
0
}
199
200
static const struct sieve_ast_extension include_ast_extension = {
201
  &include_extension,
202
  ext_include_ast_free
203
};
204
205
struct ext_include_ast_context *
206
ext_include_create_ast_context(const struct sieve_extension *this_ext,
207
             struct sieve_ast *ast, struct sieve_ast *parent)
208
0
{
209
0
  struct ext_include_ast_context *actx;
210
211
0
  pool_t pool = sieve_ast_pool(ast);
212
0
  actx = p_new(pool, struct ext_include_ast_context, 1);
213
0
  p_array_init(&actx->included_scripts, pool, 32);
214
215
0
  if (parent != NULL) {
216
0
    struct ext_include_ast_context *parent_ctx =
217
0
      (struct ext_include_ast_context *)
218
0
      sieve_ast_extension_get_context(parent, this_ext);
219
220
0
    actx->global_vars = parent_ctx->global_vars;
221
0
    i_assert(actx->global_vars != NULL);
222
223
0
    sieve_variable_scope_ref(actx->global_vars);
224
0
  } else {
225
0
    struct ext_include_context *extctx =
226
0
      ext_include_get_context(this_ext);
227
228
0
    actx->global_vars = sieve_variable_scope_create(
229
0
      this_ext->svinst, extctx->var_ext, this_ext);
230
0
  }
231
232
0
  sieve_ast_extension_register(ast, this_ext, &include_ast_extension,
233
0
             actx);
234
0
  return actx;
235
0
}
236
237
struct ext_include_ast_context *
238
ext_include_get_ast_context(const struct sieve_extension *this_ext,
239
          struct sieve_ast *ast)
240
0
{
241
0
  struct ext_include_ast_context *actx =
242
0
    (struct ext_include_ast_context *)
243
0
    sieve_ast_extension_get_context(ast, this_ext);
244
245
0
  if (actx != NULL)
246
0
    return actx;
247
0
  return ext_include_create_ast_context(this_ext, ast, NULL);
248
0
}
249
250
void ext_include_ast_link_included_script(
251
  const struct sieve_extension *this_ext, struct sieve_ast *ast,
252
  struct sieve_script *script)
253
0
{
254
0
  struct ext_include_ast_context *actx =
255
0
    ext_include_get_ast_context(this_ext, ast);
256
257
0
  array_append(&actx->included_scripts, &script, 1);
258
0
}
259
260
bool ext_include_validator_have_variables(
261
  const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
262
0
{
263
0
  struct ext_include_context *extctx = ext_include_get_context(this_ext);
264
265
0
  return sieve_ext_variables_is_active(extctx->var_ext, valdtr);
266
0
}
267
268
/*
269
 * Generator context management
270
 */
271
272
static struct ext_include_generator_context *
273
ext_include_create_generator_context(
274
  struct sieve_generator *gentr,
275
  struct ext_include_generator_context *parent,
276
  enum ext_include_script_location location, const char *script_name,
277
  struct sieve_script *script)
278
0
{
279
0
  struct ext_include_generator_context *ctx;
280
281
0
  pool_t pool = sieve_generator_pool(gentr);
282
0
  ctx = p_new(pool, struct ext_include_generator_context, 1);
283
0
  ctx->parent = parent;
284
0
  ctx->location = location;
285
0
  ctx->script_name = p_strdup(pool, script_name);
286
0
  ctx->script = script;
287
0
  if (parent == NULL)
288
0
    ctx->nesting_depth = 0;
289
0
  else
290
0
    ctx->nesting_depth = parent->nesting_depth + 1;
291
292
0
  return ctx;
293
0
}
294
295
static inline struct ext_include_generator_context *
296
ext_include_get_generator_context(const struct sieve_extension *this_ext,
297
          struct sieve_generator *gentr)
298
0
{
299
0
  return (struct ext_include_generator_context *)
300
0
    sieve_generator_extension_get_context(gentr, this_ext);
301
0
}
302
303
static inline void
304
ext_include_initialize_generator_context(
305
  const struct sieve_extension *this_ext, struct sieve_generator *gentr,
306
  struct ext_include_generator_context *parent,
307
  enum ext_include_script_location location, const char *script_name,
308
  struct sieve_script *script)
309
0
{
310
0
  sieve_generator_extension_set_context(
311
0
    gentr, this_ext,
312
0
    ext_include_create_generator_context(gentr, parent,
313
0
                 location, script_name,
314
0
                 script));
315
0
}
316
317
void ext_include_register_generator_context(
318
  const struct sieve_extension *this_ext,
319
  const struct sieve_codegen_env *cgenv)
320
0
{
321
0
  struct ext_include_generator_context *ctx =
322
0
    ext_include_get_generator_context(this_ext, cgenv->gentr);
323
324
  /* Initialize generator context if necessary */
325
0
  if (ctx == NULL) {
326
0
    i_assert(cgenv->script != NULL);
327
328
0
    enum ext_include_script_location location;
329
0
    const char *storage_type =
330
0
      sieve_script_storage_type(cgenv->script);
331
332
0
    if (strcasecmp(storage_type,
333
0
             SIEVE_STORAGE_TYPE_PERSONAL) == 0)
334
0
      location = EXT_INCLUDE_LOCATION_PERSONAL;
335
0
    else if (strcasecmp(storage_type,
336
0
            SIEVE_STORAGE_TYPE_GLOBAL) == 0)
337
0
      location = EXT_INCLUDE_LOCATION_GLOBAL;
338
0
    else
339
0
      location = EXT_INCLUDE_LOCATION_INVALID;
340
341
0
    ctx = ext_include_create_generator_context(
342
0
      cgenv->gentr, NULL, location,
343
0
      sieve_script_name(cgenv->script), cgenv->script);
344
345
0
    sieve_generator_extension_set_context(
346
0
      cgenv->gentr, this_ext, ctx);
347
0
  }
348
349
  /* Initialize ast context if necessary */
350
0
  (void)ext_include_get_ast_context(this_ext, cgenv->ast);
351
0
  (void)ext_include_binary_init(this_ext, cgenv->sbin, cgenv->ast);
352
0
}
353
354
/*
355
 * Runtime initialization
356
 */
357
358
static int
359
ext_include_runtime_init(const struct sieve_extension *this_ext,
360
       const struct sieve_runtime_env *renv,
361
       void *context, bool deferred ATTR_UNUSED)
362
0
{
363
0
  struct ext_include_interpreter_context *ctx =
364
0
    (struct ext_include_interpreter_context *)context;
365
0
  struct ext_include_context *extctx = ext_include_get_context(this_ext);
366
367
0
  if (ctx->parent == NULL) {
368
0
    ctx->global = p_new(ctx->pool,
369
0
            struct ext_include_interpreter_global, 1);
370
0
    p_array_init(&ctx->global->included_scripts, ctx->pool, 10);
371
372
0
    ctx->global->var_scope =
373
0
      ext_include_binary_get_global_scope(
374
0
        this_ext, renv->sbin);
375
0
    ctx->global->var_storage =
376
0
      sieve_variable_storage_create(extctx->var_ext,
377
0
                  ctx->pool,
378
0
                  ctx->global->var_scope);
379
0
  } else {
380
0
    ctx->global = ctx->parent->global;
381
0
  }
382
383
0
  sieve_ext_variables_runtime_set_storage(extctx->var_ext, renv, this_ext,
384
0
            ctx->global->var_storage);
385
0
  return SIEVE_EXEC_OK;
386
0
}
387
388
static struct sieve_interpreter_extension include_interpreter_extension = {
389
  .ext_def = &include_extension,
390
  .run = ext_include_runtime_init
391
};
392
393
/*
394
 * Interpreter context management
395
 */
396
397
static struct ext_include_interpreter_context *
398
ext_include_interpreter_context_create(
399
  struct sieve_interpreter *interp,
400
  struct ext_include_interpreter_context *parent,
401
  struct sieve_script *script,
402
  const struct ext_include_script_info *sinfo)
403
0
{
404
0
  struct ext_include_interpreter_context *ctx;
405
406
0
  pool_t pool = sieve_interpreter_pool(interp);
407
0
  ctx = p_new(pool, struct ext_include_interpreter_context, 1);
408
0
  ctx->pool = pool;
409
0
  ctx->parent = parent;
410
0
  ctx->interp = interp;
411
0
  ctx->script = script;
412
0
  ctx->script_info = sinfo;
413
414
0
  if (parent == NULL)
415
0
    ctx->nesting_depth = 0;
416
0
  else
417
0
    ctx->nesting_depth = parent->nesting_depth + 1;
418
0
  return ctx;
419
0
}
420
421
static inline struct ext_include_interpreter_context *
422
ext_include_get_interpreter_context(const struct sieve_extension *this_ext,
423
            struct sieve_interpreter *interp)
424
0
{
425
0
  return (struct ext_include_interpreter_context *)
426
0
    sieve_interpreter_extension_get_context(interp, this_ext);
427
0
}
428
429
static inline struct ext_include_interpreter_context *
430
ext_include_interpreter_context_init_child(
431
  const struct sieve_extension *this_ext,
432
  struct sieve_interpreter *interp,
433
  struct ext_include_interpreter_context *parent,
434
  struct sieve_script *script,
435
  const struct ext_include_script_info *sinfo)
436
0
{
437
0
  struct ext_include_interpreter_context *ctx =
438
0
    ext_include_interpreter_context_create(interp, parent,
439
0
                   script, sinfo);
440
441
0
  sieve_interpreter_extension_register(interp, this_ext,
442
0
               &include_interpreter_extension,
443
0
               ctx);
444
0
  return ctx;
445
0
}
446
447
void ext_include_interpreter_context_init(
448
  const struct sieve_extension *this_ext,
449
  struct sieve_interpreter *interp)
450
0
{
451
0
  struct ext_include_interpreter_context *ctx =
452
0
    ext_include_get_interpreter_context(this_ext, interp);
453
454
  /* Is this is the top-level interpreter ? */
455
0
  if (ctx == NULL) {
456
0
    struct sieve_script *script;
457
458
    /* Initialize top context */
459
0
    script = sieve_interpreter_script(interp);
460
0
    ctx = ext_include_interpreter_context_create(interp, NULL,
461
0
                   script, NULL);
462
463
0
    sieve_interpreter_extension_register(
464
0
      interp, this_ext, &include_interpreter_extension,
465
0
      ctx);
466
0
  }
467
0
}
468
469
struct sieve_variable_storage *
470
ext_include_interpreter_get_global_variables(
471
  const struct sieve_extension *this_ext,
472
  struct sieve_interpreter *interp)
473
0
{
474
0
  struct ext_include_interpreter_context *ctx =
475
0
    ext_include_get_interpreter_context(this_ext, interp);
476
477
0
  return ctx->global->var_storage;
478
0
}
479
480
/*
481
 * Including a script during code generation
482
 */
483
484
int ext_include_generate_include(
485
  const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
486
  enum ext_include_script_location location, const char *script_name,
487
  enum ext_include_flags flags, struct sieve_script *script,
488
  const struct ext_include_script_info **included_r)
489
0
{
490
0
  const struct sieve_extension *this_ext = cmd->ext;
491
0
  struct ext_include_context *extctx = this_ext->context;
492
0
  int result = 1;
493
0
  struct sieve_ast *ast;
494
0
  struct sieve_binary *sbin = cgenv->sbin;
495
0
  struct sieve_generator *gentr = cgenv->gentr;
496
0
  struct ext_include_binary_context *binctx;
497
0
  struct sieve_generator *subgentr;
498
0
  struct ext_include_generator_context *ctx =
499
0
    ext_include_get_generator_context(this_ext, gentr);
500
0
  struct ext_include_generator_context *pctx;
501
0
  struct sieve_error_handler *ehandler =
502
0
    sieve_generator_error_handler(gentr);
503
0
  struct ext_include_script_info *included;
504
505
0
  *included_r = NULL;
506
507
  /* Just to be sure: do not include more scripts when errors have occured
508
     already.
509
   */
510
0
  if (sieve_get_errors(ehandler) > 0)
511
0
    return -1;
512
513
  /* Limit nesting level */
514
0
  if (ctx->nesting_depth >= extctx->set->max_nesting_depth) {
515
0
    sieve_command_generate_error(
516
0
      gentr, cmd,
517
0
      "cannot nest includes deeper than %d levels",
518
0
      extctx->set->max_nesting_depth);
519
0
    return -1;
520
0
  }
521
522
  /* Check for circular include */
523
0
  if ((flags & EXT_INCLUDE_FLAG_ONCE) == 0) {
524
0
    pctx = ctx;
525
0
    while (pctx != NULL) {
526
0
      if (pctx->location == location &&
527
0
          strcmp(pctx->script_name, script_name) == 0 &&
528
0
          (pctx->script == NULL || script == NULL ||
529
0
           sieve_script_equals(pctx->script, script))) {
530
        /* Just drop circular include when uploading
531
           inactive script;  not an error
532
         */
533
0
        if ((cgenv->flags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 &&
534
0
            (cgenv->flags & SIEVE_COMPILE_FLAG_ACTIVATED) == 0) {
535
0
          sieve_command_generate_warning(
536
0
            gentr, cmd,
537
0
            "circular include (ignored during upload)");
538
0
          return 0;
539
0
        }
540
541
0
        sieve_command_generate_error(gentr, cmd,
542
0
                   "circular include");
543
0
        return -1;
544
0
      }
545
546
0
      pctx = pctx->parent;
547
0
    }
548
0
  }
549
550
  /* Get binary context */
551
0
  binctx = ext_include_binary_init(this_ext, sbin, cgenv->ast);
552
553
  /* Is the script already compiled into the current binary? */
554
0
  included = ext_include_binary_script_get_include_info(
555
0
    binctx, location, script_name);
556
0
  if (included != NULL) {
557
    /* Yes, only update flags */
558
0
    if ((flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0)
559
0
      included->flags &= ENUM_NEGATE(EXT_INCLUDE_FLAG_OPTIONAL);
560
0
    if ((flags & EXT_INCLUDE_FLAG_ONCE) == 0)
561
0
      included->flags &= ENUM_NEGATE(EXT_INCLUDE_FLAG_ONCE);
562
0
  } else  {
563
0
    enum sieve_compile_flags cpflags = cgenv->flags;
564
565
    /* No, include new script */
566
567
    /* Check whether include limit is exceeded */
568
0
    if (ext_include_binary_script_get_count(binctx) >=
569
0
        extctx->set->max_includes) {
570
0
      sieve_command_generate_error(
571
0
        gentr, cmd, "failed to include script '%s': "
572
0
        "no more than %u includes allowed",
573
0
        str_sanitize(script_name, 80),
574
0
        extctx->set->max_includes);
575
0
      return -1;
576
0
    }
577
578
    /* Allocate a new block in the binary and mark the script as
579
       included. */
580
0
    if (script == NULL) {
581
      /* Just making an empty entry to mark a missing script
582
       */
583
0
      i_assert((flags & EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD) != 0 ||
584
0
         (flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0);
585
0
      included = ext_include_binary_script_include(
586
0
        binctx, location, script_name, flags, NULL,
587
0
        NULL);
588
0
      result = 0;
589
0
    } else {
590
0
      struct sieve_binary_block *inc_block =
591
0
        sieve_binary_block_create(sbin);
592
593
      /* Real include */
594
0
      included = ext_include_binary_script_include(
595
0
        binctx, location, script_name, flags, script,
596
0
        inc_block);
597
598
      /* Parse */
599
0
      if ((ast = sieve_parse(script, ehandler,
600
0
                 NULL)) == NULL) {
601
0
        sieve_command_generate_error(
602
0
          gentr, cmd,
603
0
          "failed to parse included script '%s'",
604
0
          str_sanitize(script_name, 80));
605
0
        return -1;
606
0
      }
607
608
      /* Included scripts inherit global variable scope */
609
0
      (void)ext_include_create_ast_context(
610
0
        this_ext, ast, cmd->ast_node->ast);
611
612
0
      if (location == EXT_INCLUDE_LOCATION_GLOBAL) {
613
0
        cpflags &=
614
0
          ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
615
0
      } else {
616
0
        cpflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
617
0
      }
618
619
      /* Validate */
620
0
      if (!sieve_validate(ast, ehandler, cpflags, NULL)) {
621
0
        sieve_command_generate_error(
622
0
          gentr, cmd,
623
0
          "failed to validate included script '%s'",
624
0
          str_sanitize(script_name, 80));
625
0
        sieve_ast_unref(&ast);
626
0
        return -1;
627
0
      }
628
629
      /* Generate
630
631
         FIXME: It might not be a good idea to recurse code
632
         generation for included scripts.
633
       */
634
0
      subgentr = sieve_generator_create(ast, ehandler, cpflags);
635
0
      ext_include_initialize_generator_context(
636
0
        cmd->ext, subgentr, ctx, location, script_name,
637
0
        script);
638
639
0
      if (sieve_generator_run(subgentr, &inc_block) == NULL) {
640
0
        sieve_command_generate_error(
641
0
          gentr, cmd,
642
0
          "failed to generate code for included script '%s'",
643
0
          str_sanitize(script_name, 80));
644
0
        result = -1;
645
0
      }
646
647
0
      sieve_generator_free(&subgentr);
648
649
      /* Cleanup */
650
0
      sieve_ast_unref(&ast);
651
0
    }
652
0
  }
653
654
0
  if (result > 0)
655
0
    *included_r = included;
656
0
  return result;
657
0
}
658
659
/*
660
 * Executing an included script during interpretation
661
 */
662
663
static bool
664
ext_include_runtime_check_circular(
665
  struct ext_include_interpreter_context *ctx,
666
  const struct ext_include_script_info *include)
667
0
{
668
0
  struct ext_include_interpreter_context *pctx;
669
670
0
  pctx = ctx;
671
0
  while (pctx != NULL) {
672
673
0
    if (sieve_script_equals(include->script, pctx->script))
674
0
      return TRUE;
675
676
0
    pctx = pctx->parent;
677
0
  }
678
679
0
  return FALSE;
680
0
}
681
682
static bool
683
ext_include_runtime_include_mark(struct ext_include_interpreter_context *ctx,
684
         const struct ext_include_script_info *include,
685
         bool once)
686
0
{
687
0
  struct sieve_script *const *includes;
688
0
  unsigned int count, i;
689
690
0
  includes = array_get(&ctx->global->included_scripts, &count);
691
0
  for (i = 0; i < count; i++) {
692
0
    if (sieve_script_equals(include->script, includes[i]))
693
0
      return (!once);
694
0
  }
695
696
0
  array_append(&ctx->global->included_scripts, &include->script, 1);
697
0
  return TRUE;
698
0
}
699
700
int ext_include_execute_include(const struct sieve_runtime_env *renv,
701
        unsigned int include_id,
702
        enum ext_include_flags flags)
703
0
{
704
0
  const struct sieve_execute_env *eenv = renv->exec_env;
705
0
  const struct sieve_extension *this_ext = renv->oprtn->ext;
706
0
  int result = SIEVE_EXEC_OK;
707
0
  struct ext_include_interpreter_context *ctx;
708
0
  const struct ext_include_script_info *included;
709
0
  struct ext_include_binary_context *binctx =
710
0
    ext_include_binary_get_context(this_ext, renv->sbin);
711
0
  bool once = ((flags & EXT_INCLUDE_FLAG_ONCE) != 0);
712
0
  unsigned int block_id;
713
714
  /* Check for invalid include id (== corrupt binary) */
715
0
  included = ext_include_binary_script_get_included(binctx, include_id);
716
0
  if (included == NULL) {
717
0
    sieve_runtime_trace_error(
718
0
      renv, "include: include id %d is invalid", include_id);
719
0
    return SIEVE_EXEC_BIN_CORRUPT;
720
0
  }
721
0
  if (included->block == NULL) {
722
0
    if (!HAS_ALL_BITS(included->flags, EXT_INCLUDE_FLAG_OPTIONAL)) {
723
0
      sieve_runtime_trace_error(
724
0
        renv, "include: include record %d is corrupt "
725
0
        "(block is missing while include is not optional)",
726
0
        include_id);
727
0
      return SIEVE_EXEC_BIN_CORRUPT;
728
729
0
    }
730
0
    return SIEVE_EXEC_OK;
731
0
  }
732
733
0
  ctx = ext_include_get_interpreter_context(this_ext, renv->interp);
734
0
  block_id = sieve_binary_block_get_id(included->block);
735
736
  /* If :once modifier is specified, check for duplicate include */
737
0
  if (ext_include_runtime_include_mark(ctx, included, once)) {
738
0
    sieve_runtime_trace(
739
0
      renv, SIEVE_TRLVL_NONE,
740
0
      "include: start script '%s' [inc id: %d, block: %d]",
741
0
      sieve_script_name(included->script),
742
0
      include_id, block_id);
743
0
  } else {
744
    /* skip */
745
0
    sieve_runtime_trace(
746
0
      renv, SIEVE_TRLVL_NONE,
747
0
      "include: skipped include for script '%s' "
748
0
      "[inc id: %d, block: %d]; already run once",
749
0
      sieve_script_name(included->script),
750
0
      include_id, block_id);
751
0
    return result;
752
0
  }
753
754
  /* Check circular include during interpretation as well.
755
   * Let's not trust binaries.
756
   */
757
0
  if (ext_include_runtime_check_circular(ctx, included)) {
758
0
    sieve_runtime_trace_error(renv,
759
0
      "include: circular include of script '%s' "
760
0
      "[inc id: %d, block: %d]",
761
0
      sieve_script_name(included->script),
762
0
      include_id, block_id);
763
764
    /* Situation has no valid way to emerge at runtime */
765
0
    return SIEVE_EXEC_BIN_CORRUPT;
766
0
  }
767
768
0
  if (ctx->parent == NULL) {
769
0
    struct ext_include_interpreter_context *curctx = NULL;
770
0
    struct sieve_error_handler *ehandler = renv->ehandler;
771
0
    struct sieve_interpreter *subinterp;
772
0
    bool interrupted = FALSE;
773
774
    /* We are the top-level interpreter instance */
775
0
    if (result == SIEVE_EXEC_OK) {
776
0
      ctx->child_eenv = *eenv;
777
778
0
      if (included->location != EXT_INCLUDE_LOCATION_GLOBAL) {
779
0
        ctx->child_eenv.flags |=
780
0
          SIEVE_EXECUTE_FLAG_NOGLOBAL;
781
0
      } else {
782
0
        ctx->child_eenv.flags &=
783
0
          ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
784
0
      }
785
786
      /* Create interpreter for top-level included script
787
         (first sub-interpreter)
788
       */
789
0
      subinterp = sieve_interpreter_create_for_block(
790
0
        included->block, included->script, renv->interp,
791
0
        &ctx->child_eenv, ehandler);
792
0
      if (subinterp != NULL) {
793
0
        curctx = ext_include_interpreter_context_init_child(
794
0
          this_ext, subinterp, ctx, included->script,
795
0
          included);
796
797
        /* Activate and start the top-level included script */
798
0
        result = sieve_interpreter_start(
799
0
          subinterp, renv->result, &interrupted);
800
0
      } else {
801
0
        result = SIEVE_EXEC_BIN_CORRUPT;
802
0
      }
803
0
    }
804
805
    /* Included scripts can have includes of their own. This is not
806
       implemented recursively. Rather, the sub-interpreter
807
       interrupts and defers the include to the top-level
808
       interpreter, which is here. */
809
0
    if (result == SIEVE_EXEC_OK && interrupted &&
810
0
        !curctx->returned) {
811
0
      while (result == SIEVE_EXEC_OK) {
812
0
        if (((interrupted && curctx->returned) ||
813
0
             (!interrupted)) &&
814
0
            curctx->parent != NULL) {
815
0
          const struct ext_include_script_info *ended_script =
816
0
            curctx->script_info;
817
818
          /* Sub-interpreter ended or executed
819
             return */
820
821
          /* Ascend interpreter stack */
822
0
          curctx = curctx->parent;
823
0
          sieve_interpreter_free(&subinterp);
824
825
0
          sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
826
0
            "include: script '%s' ended "
827
0
            "[inc id: %d, block: %d]",
828
0
            sieve_script_name(ended_script->script),
829
0
            ended_script->id,
830
0
            sieve_binary_block_get_id(ended_script->block));
831
832
          /* This is the top-most sub-interpreter,
833
             bail out */
834
0
          if (curctx->parent == NULL)
835
0
            break;
836
837
0
          subinterp = curctx->interp;
838
839
          /* Continue parent */
840
0
          curctx->include = NULL;
841
0
          curctx->returned = FALSE;
842
843
0
          result = sieve_interpreter_continue(
844
0
            subinterp, &interrupted);
845
0
        } else {
846
0
          if (curctx->include != NULL) {
847
            /* Sub-include requested */
848
849
0
            if (result == SIEVE_EXEC_OK) {
850
0
              curctx->child_eenv = *eenv;
851
852
0
              if (curctx->include->location != EXT_INCLUDE_LOCATION_GLOBAL) {
853
0
                curctx->child_eenv.flags |=
854
0
                  SIEVE_EXECUTE_FLAG_NOGLOBAL;
855
0
              } else {
856
0
                curctx->child_eenv.flags &=
857
0
                  ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
858
0
              }
859
860
              /* Create sub-interpreter */
861
0
              subinterp = sieve_interpreter_create_for_block(
862
0
                curctx->include->block, curctx->include->script,
863
0
                curctx->interp, &curctx->child_eenv, ehandler);
864
0
              if (subinterp != NULL) {
865
0
                curctx = ext_include_interpreter_context_init_child(
866
0
                  this_ext, subinterp, curctx,
867
0
                  curctx->include->script, curctx->include);
868
869
                /* Start the sub-include's interpreter */
870
0
                curctx->include = NULL;
871
0
                curctx->returned = FALSE;
872
0
                result = sieve_interpreter_start(
873
0
                  subinterp, renv->result, &interrupted);
874
0
              } else {
875
0
                result = SIEVE_EXEC_BIN_CORRUPT;
876
0
              }
877
0
            }
878
0
          } else {
879
            /* Sub-interpreter was interrupted outside
880
               this extension, probably stop command was
881
               executed. Generate an interrupt ourselves,
882
               ending all script execution. */
883
0
            sieve_interpreter_interrupt(renv->interp);
884
0
            break;
885
0
          }
886
0
        }
887
0
      }
888
0
    }
889
890
    /* Free any sub-interpreters that might still be active */
891
0
    while (curctx != NULL && curctx->parent != NULL) {
892
0
      struct ext_include_interpreter_context *nextctx =
893
0
        curctx->parent;
894
0
      struct sieve_interpreter *killed_interp = curctx->interp;
895
0
      const struct ext_include_script_info *ended_script =
896
0
        curctx->script_info;
897
898
      /* This kills curctx too */
899
0
      sieve_interpreter_free(&killed_interp);
900
901
0
      sieve_runtime_trace(
902
0
        renv, SIEVE_TRLVL_NONE,
903
0
        "include: script '%s' ended [id: %d, block: %d]",
904
0
        sieve_script_name(ended_script->script),
905
0
        ended_script->id,
906
0
        sieve_binary_block_get_id(ended_script->block));
907
908
      /* Luckily we recorded the parent earlier */
909
0
      curctx = nextctx;
910
0
    }
911
912
0
  } else {
913
    /* We are an included script already, defer inclusion to main
914
       interpreter */
915
0
    ctx->include = included;
916
0
    sieve_interpreter_interrupt(renv->interp);
917
0
  }
918
919
0
  return result;
920
0
}
921
922
void ext_include_execute_return(const struct sieve_runtime_env *renv)
923
0
{
924
0
  const struct sieve_extension *this_ext = renv->oprtn->ext;
925
0
  struct ext_include_interpreter_context *ctx =
926
0
    ext_include_get_interpreter_context(this_ext, renv->interp);
927
928
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
929
0
          "return: exiting included script");
930
0
  ctx->returned = TRUE;
931
0
  sieve_interpreter_interrupt(renv->interp);
932
0
}