Coverage Report

Created: 2026-05-16 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2
 */
3
4
#include "lib.h"
5
6
#include "sieve-common.h"
7
#include "sieve-extensions.h"
8
#include "sieve-commands.h"
9
#include "sieve-stringlist.h"
10
#include "sieve-code.h"
11
#include "sieve-comparators.h"
12
#include "sieve-match-types.h"
13
#include "sieve-address-parts.h"
14
#include "sieve-validator.h"
15
#include "sieve-generator.h"
16
#include "sieve-interpreter.h"
17
#include "sieve-dump.h"
18
#include "sieve-match.h"
19
20
#include "ext-spamvirustest-common.h"
21
22
/*
23
 * Tests
24
 */
25
26
static bool tst_spamvirustest_validate
27
  (struct sieve_validator *valdtr, struct sieve_command *tst);
28
static bool tst_spamvirustest_generate
29
  (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
30
static bool tst_spamvirustest_registered
31
  (struct sieve_validator *valdtr, const struct sieve_extension *ext,
32
    struct sieve_command_registration *cmd_reg);
33
34
/* Spamtest test
35
 *
36
 * Syntax:
37
 *   spamtest [":percent"] [COMPARATOR] [MATCH-TYPE] <value: string>
38
 */
39
40
const struct sieve_command_def spamtest_test = {
41
  .identifier = "spamtest",
42
  .type = SCT_TEST,
43
  .positional_args = 1,
44
  .subtests = 0,
45
  .block_allowed = FALSE,
46
  .block_required = FALSE,
47
  .registered = tst_spamvirustest_registered,
48
  .validate = tst_spamvirustest_validate,
49
  .generate = tst_spamvirustest_generate
50
};
51
52
/* Virustest test
53
 *
54
 * Syntax:
55
 *   virustest [COMPARATOR] [MATCH-TYPE] <value: string>
56
 */
57
58
const struct sieve_command_def virustest_test = {
59
  .identifier = "virustest",
60
  .type = SCT_TEST,
61
  .positional_args = 1,
62
  .subtests = 0,
63
  .block_allowed = FALSE,
64
  .block_required = FALSE,
65
  .registered = tst_spamvirustest_registered,
66
  .validate = tst_spamvirustest_validate,
67
  .generate = tst_spamvirustest_generate
68
};
69
70
/*
71
 * Tagged arguments
72
 */
73
74
static bool tst_spamtest_validate_percent_tag
75
  (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
76
    struct sieve_command *tst);
77
78
static const struct sieve_argument_def spamtest_percent_tag = {
79
  .identifier = "percent",
80
  .validate = tst_spamtest_validate_percent_tag
81
};
82
83
/*
84
 * Spamtest and virustest operations
85
 */
86
87
static bool tst_spamvirustest_operation_dump
88
  (const struct sieve_dumptime_env *denv, sieve_size_t *address);
89
static int tst_spamvirustest_operation_execute
90
  (const struct sieve_runtime_env *renv, sieve_size_t *address);
91
92
const struct sieve_operation_def spamtest_operation = {
93
  .mnemonic = "SPAMTEST",
94
  .ext_def = &spamtest_extension,
95
  .dump = tst_spamvirustest_operation_dump,
96
  .execute = tst_spamvirustest_operation_execute
97
};
98
99
const struct sieve_operation_def virustest_operation = {
100
  .mnemonic = "VIRUSTEST",
101
  .ext_def = &virustest_extension,
102
  .dump = tst_spamvirustest_operation_dump,
103
  .execute = tst_spamvirustest_operation_execute
104
};
105
106
/*
107
 * Optional operands
108
 */
109
110
enum tst_spamvirustest_optional {
111
  OPT_SPAMTEST_PERCENT = SIEVE_MATCH_OPT_LAST,
112
  OPT_SPAMTEST_LAST
113
};
114
115
/*
116
 * Test registration
117
 */
118
119
static bool tst_spamvirustest_registered
120
(struct sieve_validator *valdtr, const struct sieve_extension *ext,
121
  struct sieve_command_registration *cmd_reg)
122
0
{
123
0
  sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
124
0
  sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
125
126
0
  if ( sieve_extension_is(ext, spamtestplus_extension) ||
127
0
    sieve_extension_is(ext, spamtest_extension) ) {
128
0
    sieve_validator_register_tag
129
0
      (valdtr, cmd_reg, ext, &spamtest_percent_tag, OPT_SPAMTEST_PERCENT);
130
0
  }
131
132
0
  return TRUE;
133
0
}
134
135
/*
136
 * Validation
137
 */
138
139
static bool tst_spamtest_validate_percent_tag
140
(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
141
  struct sieve_command *tst)
142
0
{
143
0
  if ( !sieve_extension_is(tst->ext, spamtestplus_extension) ) {
144
0
    sieve_argument_validate_error(valdtr, *arg,
145
0
      "the spamtest test only accepts the :percent argument when "
146
0
      "the spamtestplus extension is active");
147
0
    return FALSE;
148
0
  }
149
150
  /* Skip tag */
151
0
  *arg = sieve_ast_argument_next(*arg);
152
153
0
  return TRUE;
154
0
}
155
156
static bool tst_spamvirustest_validate
157
(struct sieve_validator *valdtr, struct sieve_command *tst)
158
0
{
159
0
  struct sieve_ast_argument *arg = tst->first_positional;
160
0
  const struct sieve_match_type mcht_default =
161
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
162
0
  const struct sieve_comparator cmp_default =
163
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
164
165
  /* Check value */
166
167
0
  if ( !sieve_validate_positional_argument
168
0
    (valdtr, tst, arg, "value", 1, SAAT_STRING) ) {
169
0
    return FALSE;
170
0
  }
171
172
0
  if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
173
0
    return FALSE;
174
175
  /* Validate the key argument to a specified match type */
176
0
  return sieve_match_type_validate
177
0
    (valdtr, tst, arg, &mcht_default, &cmp_default);
178
0
}
179
180
/*
181
 * Code generation
182
 */
183
184
static bool tst_spamvirustest_generate
185
(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
186
0
{
187
0
  if ( sieve_command_is(tst, spamtest_test) )
188
0
    sieve_operation_emit(cgenv->sblock, tst->ext, &spamtest_operation);
189
0
  else if ( sieve_command_is(tst, virustest_test) )
190
0
    sieve_operation_emit(cgenv->sblock, tst->ext, &virustest_operation);
191
0
  else
192
0
    i_unreached();
193
194
  /* Generate arguments */
195
0
  return sieve_generate_arguments(cgenv, tst, NULL);
196
0
}
197
198
/*
199
 * Code dump
200
 */
201
202
static bool tst_spamvirustest_operation_dump
203
(const struct sieve_dumptime_env *denv, sieve_size_t *address)
204
0
{
205
0
  int opt_code = 0;
206
0
  const struct sieve_operation *op = denv->oprtn;
207
208
0
  sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
209
0
  sieve_code_descend(denv);
210
211
  /* Optional operands */
212
0
  for (;;) {
213
0
    int opt;
214
215
0
    if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code)) < 0 )
216
0
      return FALSE;
217
218
0
    if ( opt == 0 ) break;
219
220
0
    switch ( opt_code ) {
221
0
    case OPT_SPAMTEST_PERCENT:
222
0
      sieve_code_dumpf(denv, "percent");
223
0
      break;
224
0
    default:
225
0
      return FALSE;
226
0
    }
227
0
  }
228
229
0
  return
230
0
    sieve_opr_string_dump(denv, address, "value");
231
0
}
232
233
/*
234
 * Code execution
235
 */
236
237
static int tst_spamvirustest_operation_execute
238
(const struct sieve_runtime_env *renv, sieve_size_t *address)
239
0
{
240
0
  const struct sieve_operation *op = renv->oprtn;
241
0
  const struct sieve_extension *this_ext = op->ext;
242
0
  int opt_code = 0;
243
0
  struct sieve_match_type mcht =
244
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
245
0
  struct sieve_comparator cmp =
246
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
247
0
  bool percent = FALSE;
248
0
  struct sieve_stringlist *value_list, *key_list;
249
0
  const char *score_value;
250
0
  int match, ret;
251
252
  /* Read optional operands */
253
0
  for (;;) {
254
0
    int opt;
255
256
0
    if ( (opt=sieve_match_opr_optional_read
257
0
      (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
258
0
      return ret;
259
260
0
    if ( opt == 0 ) break;
261
262
0
    switch ( opt_code ) {
263
0
    case OPT_SPAMTEST_PERCENT:
264
0
      percent = TRUE;
265
0
      break;
266
0
    default:
267
0
      sieve_runtime_trace_error(renv, "unknown optional operand");
268
0
      return SIEVE_EXEC_BIN_CORRUPT;
269
0
    }
270
0
  }
271
272
  /* Read value part */
273
0
  if ( (ret=sieve_opr_stringlist_read(renv, address, "value", &key_list)) <= 0 )
274
0
    return ret;
275
276
  /* Perform test */
277
278
0
  if ( sieve_operation_is(op, spamtest_operation) ) {
279
0
    sieve_runtime_trace
280
0
      (renv, SIEVE_TRLVL_TESTS, "spamtest test [percent=%s]",
281
0
        ( percent ? "true" : "false" ));
282
0
  } else {
283
0
    sieve_runtime_trace
284
0
      (renv, SIEVE_TRLVL_TESTS, "virustest test");
285
0
  }
286
287
  /* Get score value */
288
0
  sieve_runtime_trace_descend(renv);
289
0
  if ( (ret=ext_spamvirustest_get_value
290
0
    (renv, this_ext, percent, &score_value)) <= 0 )
291
0
    return ret;
292
0
  sieve_runtime_trace_ascend(renv);
293
294
  /* Construct value list */
295
0
  value_list = sieve_single_stringlist_create_cstr(renv, score_value, TRUE);
296
297
  /* Perform match */
298
0
  if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
299
0
    return ret;
300
301
  /* Set test result for subsequent conditional jump */
302
0
  sieve_interpreter_set_test_result(renv->interp, match > 0);
303
0
  return SIEVE_EXEC_OK;
304
0
}