Coverage Report

Created: 2026-07-30 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/tst-address.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-commands.h"
8
#include "sieve-stringlist.h"
9
#include "sieve-code.h"
10
#include "sieve-comparators.h"
11
#include "sieve-match-types.h"
12
#include "sieve-message.h"
13
#include "sieve-address.h"
14
#include "sieve-address-parts.h"
15
#include "sieve-validator.h"
16
#include "sieve-generator.h"
17
#include "sieve-interpreter.h"
18
#include "sieve-dump.h"
19
#include "sieve-match.h"
20
21
#include <stdio.h>
22
23
/*
24
 * Address test
25
 *
26
 * Syntax:
27
 *    address [ADDRESS-PART] [COMPARATOR] [MATCH-TYPE]
28
 *       <header-list: string-list> <key-list: string-list>
29
 */
30
31
static bool
32
tst_address_registered(struct sieve_validator *valdtr,
33
           const struct sieve_extension *ext,
34
           struct sieve_command_registration *cmd_reg);
35
static bool
36
tst_address_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
37
static bool
38
tst_address_generate(const struct sieve_codegen_env *cgenv,
39
         struct sieve_command *ctx);
40
41
const struct sieve_command_def tst_address = {
42
  .identifier = "address",
43
  .type = SCT_TEST,
44
  .positional_args = 2,
45
  .subtests = 0,
46
  .block_allowed = FALSE,
47
  .block_required = FALSE,
48
  .registered = tst_address_registered,
49
  .validate = tst_address_validate,
50
  .generate = tst_address_generate
51
};
52
53
/*
54
 * Address operation
55
 */
56
57
static bool
58
tst_address_operation_dump(const struct sieve_dumptime_env *denv,
59
         sieve_size_t *address);
60
static int
61
tst_address_operation_execute(const struct sieve_runtime_env *renv,
62
            sieve_size_t *address);
63
64
const struct sieve_operation_def tst_address_operation = {
65
  .mnemonic = "ADDRESS",
66
  .code = SIEVE_OPERATION_ADDRESS,
67
  .dump = tst_address_operation_dump,
68
  .execute = tst_address_operation_execute
69
};
70
71
/*
72
 * Test registration
73
 */
74
75
static bool
76
tst_address_registered(struct sieve_validator *valdtr,
77
           const struct sieve_extension *ext ATTR_UNUSED,
78
           struct sieve_command_registration *cmd_reg)
79
0
{
80
  /* The order of these is not significant */
81
0
  sieve_comparators_link_tag(valdtr, cmd_reg,
82
0
           SIEVE_MATCH_OPT_COMPARATOR);
83
0
  sieve_match_types_link_tags(valdtr, cmd_reg,
84
0
            SIEVE_MATCH_OPT_MATCH_TYPE);
85
0
  sieve_address_parts_link_tags(valdtr, cmd_reg,
86
0
              SIEVE_AM_OPT_ADDRESS_PART);
87
0
  return TRUE;
88
0
}
89
90
/*
91
 * Validation
92
 */
93
94
/* List of valid headers:
95
     Implementations MUST restrict the address test to headers that contain
96
     addresses, but MUST include at least From, To, Cc, Bcc, Sender,
97
     Resent-From, and Resent-To, and it SHOULD include any other header that
98
     utilizes an "address-list" structured header body.
99
100
   This list explicitly does not contain the envelope-to and return-path
101
   headers. The envelope test must be used to test against these addresses.
102
103
   FIXME: this restriction is somewhat odd. Sieve list advises to allow any
104
          other header as long as its content matches the address-list grammar.
105
 */
106
static const char *const _allowed_headers[] = {
107
  /* Required */
108
  "from", "to", "cc", "bcc", "sender", "resent-from", "resent-to",
109
110
  /* Additional (RFC 822 / RFC 2822) */
111
  "reply-to", "resent-reply-to", "resent-sender", "resent-cc",
112
  "resent-bcc",
113
114
  /* Non-standard (RFC 2076, draft-palme-mailext-headers-08.txt) */
115
  "for-approval", "for-handling", "for-comment", "apparently-to",
116
  "errors-to", "delivered-to", "return-receipt-to", "x-admin",
117
  "read-receipt-to", "x-confirm-reading-to", "return-receipt-requested",
118
  "registered-mail-reply-requested-by", "mail-followup-to",
119
  "mail-reply-to", "abuse-reports-to", "x-complaints-to",
120
  "x-report-abuse-to",
121
122
  /* Undocumented */
123
  "x-beenthere", "x-original-from", "x-original-to",
124
125
  NULL
126
};
127
128
static int
129
_header_is_allowed(void *context ATTR_UNUSED, struct sieve_ast_argument *arg)
130
0
{
131
0
  if (sieve_argument_is_string_literal(arg)) {
132
0
    const char *header = sieve_ast_strlist_strc(arg);
133
134
0
    const char *const *hdsp = _allowed_headers;
135
0
    while (*hdsp != NULL) {
136
0
      if (strcasecmp(*hdsp, header) == 0)
137
0
        return 1;
138
0
      hdsp++;
139
0
    }
140
0
    return 0;
141
0
  }
142
0
  return 1;
143
0
}
144
145
static bool
146
tst_address_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
147
0
{
148
0
  struct sieve_ast_argument *arg = tst->first_positional;
149
0
  struct sieve_ast_argument *header;
150
0
  struct sieve_comparator cmp_default =
151
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
152
0
  struct sieve_match_type mcht_default =
153
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
154
155
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg, "header list",
156
0
            1, SAAT_STRING_LIST))
157
0
    return FALSE;
158
159
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
160
0
    return FALSE;
161
162
0
  if (!sieve_command_verify_headers_argument(valdtr, arg))
163
0
    return FALSE;
164
165
  /* Check if supplied header names are allowed
166
       FIXME: verify dynamic header names at runtime
167
   */
168
0
  header = arg;
169
0
  if (sieve_ast_stringlist_map(&header, NULL, _header_is_allowed) <= 0) {
170
0
    i_assert(header != NULL);
171
0
    sieve_argument_validate_error(
172
0
      valdtr, header,
173
0
      "specified header '%s' is not allowed for the address test",
174
0
      str_sanitize(sieve_ast_strlist_strc(header), 64));
175
0
    return FALSE;
176
0
  }
177
178
  /* Check key list */
179
180
0
  arg = sieve_ast_argument_next(arg);
181
182
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg, "key list", 2,
183
0
            SAAT_STRING_LIST))
184
0
    return FALSE;
185
186
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
187
0
    return FALSE;
188
189
  /* Validate the key argument to a specified match type */
190
0
  return sieve_match_type_validate(valdtr, tst, arg,
191
0
           &mcht_default, &cmp_default);
192
0
}
193
194
/*
195
 * Code generation
196
 */
197
198
static bool
199
tst_address_generate(const struct sieve_codegen_env *cgenv,
200
         struct sieve_command *tst)
201
0
{
202
0
  sieve_operation_emit(cgenv->sblock, NULL, &tst_address_operation);
203
204
  /* Generate arguments */
205
0
  return sieve_generate_arguments(cgenv, tst, NULL);
206
0
}
207
208
/*
209
 * Code dump
210
 */
211
212
static bool
213
tst_address_operation_dump(const struct sieve_dumptime_env *denv,
214
         sieve_size_t *address)
215
0
{
216
0
  sieve_code_dumpf(denv, "ADDRESS");
217
0
  sieve_code_descend(denv);
218
219
  /* Handle any optional arguments */
220
0
  if (sieve_message_opr_optional_dump(denv, address, NULL) != 0)
221
0
    return FALSE;
222
223
0
  return (sieve_opr_stringlist_dump(denv, address, "header list") &&
224
0
    sieve_opr_stringlist_dump(denv, address, "key list"));
225
0
}
226
227
/*
228
 * Code execution
229
 */
230
231
static int
232
tst_address_operation_execute(const struct sieve_runtime_env *renv,
233
            sieve_size_t *address)
234
0
{
235
0
  struct sieve_comparator cmp =
236
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
237
0
  struct sieve_match_type mcht =
238
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
239
0
  struct sieve_address_part addrp =
240
0
    SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
241
0
  struct sieve_stringlist *hdr_list, *hdr_value_list, *value_list,
242
0
        *key_list;
243
0
  struct sieve_address_list *addr_list;
244
0
  ARRAY_TYPE(sieve_message_override) svmos;
245
0
  int match, ret;
246
247
  /* Read optional operands */
248
0
  i_zero(&svmos);
249
0
  if (sieve_message_opr_optional_read(renv, address, NULL, &ret,
250
0
              &addrp, &mcht, &cmp, &svmos) < 0)
251
0
    return ret;
252
253
  /* Read header-list */
254
0
  if ((ret = sieve_opr_stringlist_read(renv, address, "header-list",
255
0
               &hdr_list)) <= 0)
256
0
    return ret;
257
258
  /* Read key-list */
259
0
  if ((ret = sieve_opr_stringlist_read(renv, address, "key-list",
260
0
               &key_list)) <= 0)
261
0
    return ret;
262
263
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "address test");
264
265
  /* Get header */
266
0
  sieve_runtime_trace_descend(renv);
267
0
  if ((ret = sieve_message_get_header_fields(
268
0
    renv, hdr_list, &svmos, FALSE, &hdr_value_list)) <= 0)
269
0
    return ret;
270
0
  sieve_runtime_trace_ascend(renv);
271
272
  /* Create value stringlist */
273
0
  addr_list = sieve_header_address_list_create(renv, hdr_value_list);
274
0
  value_list = sieve_address_part_stringlist_create(
275
0
    renv, &addrp, addr_list);
276
277
  /* Perform match */
278
0
  if ((match = sieve_match(renv, &mcht, &cmp,
279
0
         value_list, key_list, &ret)) < 0)
280
0
    return ret;
281
282
  /* Set test result for subsequent conditional jump */
283
0
  sieve_interpreter_set_test_result(renv->interp, match > 0);
284
0
  return SIEVE_EXEC_OK;
285
0
}