Coverage Report

Created: 2026-06-09 06:49

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