/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 | } |