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