Coverage Report

Created: 2026-09-01 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/include/cmd-include.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str-sanitize.h"
5
6
#include "sieve-common.h"
7
#include "sieve-script.h"
8
#include "sieve-ast.h"
9
#include "sieve-code.h"
10
#include "sieve-extensions.h"
11
#include "sieve-commands.h"
12
#include "sieve-validator.h"
13
#include "sieve-binary.h"
14
#include "sieve-generator.h"
15
#include "sieve-interpreter.h"
16
#include "sieve-dump.h"
17
18
#include "ext-include-common.h"
19
#include "ext-include-binary.h"
20
21
/*
22
 * Include command
23
 *
24
 * Syntax:
25
 *   include [LOCATION] [":once"] [":optional"] <value: string>
26
 *
27
 * [LOCATION]:
28
 *   ":personal" / ":global"
29
 */
30
31
static bool
32
cmd_include_registered(struct sieve_validator *valdtr,
33
           const struct sieve_extension *ext,
34
           struct sieve_command_registration *cmd_reg);
35
static bool
36
cmd_include_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
37
       struct sieve_command *cmd);
38
static bool
39
cmd_include_validate(struct sieve_validator *valdtr,
40
         struct sieve_command *cmd);
41
static bool
42
cmd_include_generate(const struct sieve_codegen_env *cgenv,
43
         struct sieve_command *ctx);
44
45
const struct sieve_command_def cmd_include = {
46
  .identifier = "include",
47
  .type = SCT_COMMAND,
48
  .positional_args = 1,
49
  .subtests = 0,
50
  .block_allowed = FALSE,
51
  .block_required = FALSE,
52
  .registered = cmd_include_registered,
53
  .pre_validate = cmd_include_pre_validate,
54
  .validate = cmd_include_validate,
55
  .generate = cmd_include_generate,
56
};
57
58
/*
59
 * Include operation
60
 */
61
62
static bool
63
opc_include_dump(const struct sieve_dumptime_env *denv, sieve_size_t *address);
64
static int
65
opc_include_execute(const struct sieve_runtime_env *renv,
66
        sieve_size_t *address);
67
68
const struct sieve_operation_def include_operation = {
69
  .mnemonic = "include",
70
  .ext_def = &include_extension,
71
  .code = EXT_INCLUDE_OPERATION_INCLUDE,
72
  .dump = opc_include_dump,
73
  .execute = opc_include_execute,
74
};
75
76
/*
77
 * Context structures
78
 */
79
80
struct cmd_include_context_data {
81
  enum ext_include_script_location location;
82
  const char *script_name;
83
  struct sieve_script *script;
84
  enum ext_include_flags flags;
85
86
  bool location_assigned:1;
87
};
88
89
/*
90
 * Tagged arguments
91
 */
92
93
static bool
94
cmd_include_validate_location_tag(struct sieve_validator *valdtr,
95
          struct sieve_ast_argument **arg,
96
          struct sieve_command *cmd);
97
98
static const struct sieve_argument_def include_personal_tag = {
99
  .identifier = "personal",
100
  .validate = cmd_include_validate_location_tag,
101
};
102
103
static const struct sieve_argument_def include_global_tag = {
104
  .identifier = "global",
105
  .validate = cmd_include_validate_location_tag,
106
};
107
108
static bool
109
cmd_include_validate_boolean_tag(struct sieve_validator *valdtr,
110
         struct sieve_ast_argument **arg,
111
         struct sieve_command *cmd);
112
113
static const struct sieve_argument_def include_once_tag = {
114
  .identifier = "once",
115
  .validate = cmd_include_validate_boolean_tag,
116
};
117
118
static const struct sieve_argument_def include_optional_tag = {
119
  .identifier = "optional",
120
  .validate = cmd_include_validate_boolean_tag,
121
};
122
123
/*
124
 * Tag validation
125
 */
126
127
static bool
128
cmd_include_validate_location_tag(struct sieve_validator *valdtr,
129
          struct sieve_ast_argument **arg,
130
          struct sieve_command *cmd)
131
0
{
132
0
  struct cmd_include_context_data *ctx_data =
133
0
    (struct cmd_include_context_data *)cmd->data;
134
135
0
  if (ctx_data->location_assigned) {
136
0
    sieve_argument_validate_error(
137
0
      valdtr, *arg,
138
0
      "include: cannot use location tags ':personal' and ':global' "
139
0
      "multiple times");
140
0
    return FALSE;
141
0
  }
142
143
0
  if (sieve_argument_is(*arg, include_personal_tag))
144
0
    ctx_data->location = EXT_INCLUDE_LOCATION_PERSONAL;
145
0
  else if (sieve_argument_is(*arg, include_global_tag))
146
0
    ctx_data->location = EXT_INCLUDE_LOCATION_GLOBAL;
147
0
  else
148
0
    return FALSE;
149
150
0
  ctx_data->location_assigned = TRUE;
151
152
  /* Delete this tag (for now) */
153
0
  *arg = sieve_ast_arguments_detach(*arg, 1);
154
155
0
  return TRUE;
156
0
}
157
158
static bool
159
cmd_include_validate_boolean_tag(struct sieve_validator *valdtr ATTR_UNUSED,
160
         struct sieve_ast_argument **arg,
161
         struct sieve_command *cmd)
162
0
{
163
0
  struct cmd_include_context_data *ctx_data =
164
0
    (struct cmd_include_context_data *)cmd->data;
165
166
0
  if (sieve_argument_is(*arg, include_once_tag))
167
0
    ctx_data->flags |= EXT_INCLUDE_FLAG_ONCE;
168
0
  else
169
0
    ctx_data->flags |= EXT_INCLUDE_FLAG_OPTIONAL;
170
171
  /* Delete this tag (for now) */
172
0
  *arg = sieve_ast_arguments_detach(*arg, 1);
173
174
0
  return TRUE;
175
0
}
176
177
/*
178
 * Command registration
179
 */
180
181
static bool
182
cmd_include_registered(struct sieve_validator *valdtr,
183
           const struct sieve_extension *ext,
184
           struct sieve_command_registration *cmd_reg)
185
0
{
186
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
187
0
             &include_personal_tag, 0);
188
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
189
0
             &include_global_tag, 0);
190
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
191
0
             &include_once_tag, 0);
192
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
193
0
             &include_optional_tag, 0);
194
195
0
  return TRUE;
196
0
}
197
198
/*
199
 * Command validation
200
 */
201
202
static bool
203
cmd_include_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
204
       struct sieve_command *cmd)
205
0
{
206
0
  struct cmd_include_context_data *ctx_data;
207
208
  /* Assign context */
209
0
  ctx_data = p_new(sieve_command_pool(cmd),
210
0
       struct cmd_include_context_data, 1);
211
0
  ctx_data->location = EXT_INCLUDE_LOCATION_PERSONAL;
212
0
  cmd->data = ctx_data;
213
214
0
  return TRUE;
215
0
}
216
217
static bool
218
cmd_include_validate(struct sieve_validator *valdtr,
219
         struct sieve_command *cmd)
220
0
{
221
0
  const struct sieve_extension *this_ext = cmd->ext;
222
0
  struct sieve_ast_argument *arg = cmd->first_positional;
223
0
  struct cmd_include_context_data *ctx_data =
224
0
    (struct cmd_include_context_data *)cmd->data;
225
0
  struct sieve_script *script;
226
0
  const char *script_name;
227
0
  enum sieve_error error_code = SIEVE_ERROR_NONE;
228
229
  /* Check argument */
230
0
  if (!sieve_validate_positional_argument(valdtr, cmd, arg, "value",
231
0
            1, SAAT_STRING))
232
0
    return FALSE;
233
234
0
  if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
235
0
    return FALSE;
236
237
  /*
238
   * Variables are not allowed.
239
   */
240
0
  if (!sieve_argument_is_string_literal(arg)) {
241
0
    sieve_argument_validate_error(
242
0
      valdtr, arg,
243
0
      "the include command requires a constant string for its value argument");
244
0
    return FALSE;
245
0
  }
246
247
  /* Find the script */
248
249
0
  script_name = sieve_ast_argument_strc(arg);
250
251
0
  if (!sieve_script_name_is_valid(script_name)) {
252
0
    sieve_argument_validate_error(
253
0
      valdtr, arg, "include: invalid script name '%s'",
254
0
      str_sanitize(script_name, 80));
255
0
    return FALSE;
256
0
  }
257
258
  /* Open script */
259
0
  if (ext_include_open_script(this_ext, ctx_data->location,
260
0
            sieve_validator_script_cause(valdtr),
261
0
            script_name, &script, &error_code) < 0) {
262
0
    if (error_code != SIEVE_ERROR_NOT_FOUND) {
263
0
      sieve_argument_validate_error(
264
0
        valdtr, arg,
265
0
        "failed to access included %s script '%s' "
266
0
        "(refer to server log for more information)",
267
0
        ext_include_script_location_name(ctx_data->location),
268
0
        str_sanitize(script_name, 80));
269
0
      return FALSE;
270
    /* Not found */
271
0
    } else {
272
0
      enum sieve_compile_flags cpflags =
273
0
        sieve_validator_compile_flags(valdtr);
274
275
0
      if ((ctx_data->flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0) {
276
        /* :optional */
277
0
      } else if ((cpflags & SIEVE_COMPILE_FLAG_UPLOADED) != 0) {
278
        /* Script is being uploaded */
279
0
        sieve_argument_validate_warning(
280
0
          valdtr, arg,
281
0
          "included %s script '%s' does not exist (ignored during upload)",
282
0
          ext_include_script_location_name(ctx_data->location),
283
0
          str_sanitize(script_name, 80));
284
0
        ctx_data->flags |= EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD;
285
286
0
      } else {
287
        /* Should have existed */
288
0
        sieve_argument_validate_error(
289
0
          valdtr, arg,
290
0
          "included %s script '%s' does not exist",
291
0
          ext_include_script_location_name(ctx_data->location),
292
0
          str_sanitize(script_name, 80));
293
0
        return FALSE;
294
0
      }
295
0
    }
296
0
  }
297
298
0
  ext_include_ast_link_included_script(cmd->ext, cmd->ast_node->ast, script);
299
0
  ctx_data->script_name = p_strdup(sieve_command_pool(cmd), script_name);
300
0
  ctx_data->script = script;
301
302
0
  (void)sieve_ast_arguments_detach(arg, 1);
303
0
  return TRUE;
304
0
}
305
306
/*
307
 * Code Generation
308
 */
309
310
static bool
311
cmd_include_generate(const struct sieve_codegen_env *cgenv,
312
         struct sieve_command *cmd)
313
0
{
314
0
  struct cmd_include_context_data *ctx_data =
315
0
    (struct cmd_include_context_data *)cmd->data;
316
0
  const struct ext_include_script_info *included;
317
0
  int ret;
318
319
  /* Compile (if necessary) and include the script into the binary.
320
     This yields the id of the binary block containing the compiled byte
321
     code. */
322
0
  ret = ext_include_generate_include(cgenv, cmd, ctx_data->location,
323
0
             ctx_data->script_name,
324
0
             ctx_data->flags, ctx_data->script,
325
0
             &included);
326
0
  if (ret < 0)
327
0
    return FALSE;
328
0
  if (ret > 0) {
329
0
    (void)sieve_operation_emit(cgenv->sblock, cmd->ext,
330
0
             &include_operation);
331
0
    (void)sieve_binary_emit_unsigned(cgenv->sblock, included->id);
332
0
    (void)sieve_binary_emit_byte(cgenv->sblock, ctx_data->flags);
333
0
  }
334
0
  return TRUE;
335
0
}
336
337
/*
338
 * Code dump
339
 */
340
341
static bool opc_include_dump(const struct sieve_dumptime_env *denv,
342
           sieve_size_t *address)
343
0
{
344
0
  const struct ext_include_script_info *included;
345
0
  struct ext_include_binary_context *binctx;
346
0
  unsigned int include_id, flags;
347
348
0
  sieve_code_dumpf(denv, "INCLUDE:");
349
350
0
  sieve_code_mark(denv);
351
0
  if (!sieve_binary_read_unsigned(denv->sblock, address, &include_id))
352
0
    return FALSE;
353
0
  if (!sieve_binary_read_byte(denv->sblock, address, &flags))
354
0
    return FALSE;
355
356
0
  binctx = ext_include_binary_get_context(denv->oprtn->ext, denv->sbin);
357
0
  included = ext_include_binary_script_get_included(binctx, include_id);
358
0
  if (included == NULL)
359
0
    return FALSE;
360
0
  if (included->block == NULL) {
361
0
    if (!HAS_ALL_BITS(included->flags, EXT_INCLUDE_FLAG_OPTIONAL))
362
0
      return FALSE;
363
364
0
    sieve_code_descend(denv);
365
0
    sieve_code_dumpf(
366
0
      denv, "script: %s(optional) [ID: %d, BLOCK: -]",
367
0
      ((flags & EXT_INCLUDE_FLAG_ONCE) != 0 ? "(once) " : ""),
368
0
      include_id);
369
0
    return TRUE;
370
0
  }
371
372
0
  sieve_code_descend(denv);
373
0
  sieve_code_dumpf(
374
0
    denv, "script: '%s' %s%s[ID: %d, BLOCK: %d]",
375
0
    sieve_script_label(included->script),
376
0
    ((flags & EXT_INCLUDE_FLAG_ONCE) != 0 ? "(once) " : ""),
377
0
    ((flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0 ? "(optional) " : ""),
378
0
    include_id, sieve_binary_block_get_id(included->block));
379
380
0
  return TRUE;
381
0
}
382
383
/*
384
 * Execution
385
 */
386
387
static int
388
opc_include_execute(const struct sieve_runtime_env *renv,
389
        sieve_size_t *address)
390
0
{
391
0
  unsigned int include_id, flags;
392
393
0
  if (!sieve_binary_read_unsigned(renv->sblock, address, &include_id)) {
394
0
    sieve_runtime_trace_error(renv, "invalid include-id operand");
395
0
    return SIEVE_EXEC_BIN_CORRUPT;
396
0
  }
397
398
0
  if (!sieve_binary_read_unsigned(renv->sblock, address, &flags)) {
399
0
    sieve_runtime_trace_error(renv, "invalid flags operand");
400
0
    return SIEVE_EXEC_BIN_CORRUPT;
401
0
  }
402
403
0
  return ext_include_execute_include(renv, include_id,
404
0
             (enum ext_include_flags)flags);
405
0
}