Coverage Report

Created: 2026-06-15 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/sieve-address.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2
 */
3
4
#include "lib.h"
5
#include "str.h"
6
#include "str-sanitize.h"
7
#include "rfc822-parser.h"
8
#include "message-address.h"
9
10
#include "sieve-common.h"
11
#include "sieve-runtime-trace.h"
12
13
#include "sieve-address.h"
14
15
#include <ctype.h>
16
17
/*
18
 * Header address list
19
 */
20
21
/* Forward declarations */
22
23
static int
24
sieve_header_address_list_next_string_item(struct sieve_stringlist *_strlist,
25
             string_t **str_r);
26
static int
27
sieve_header_address_list_next_item(struct sieve_address_list *_addrlist,
28
            struct smtp_address *addr_r,
29
            string_t **unparsed_r);
30
static void
31
sieve_header_address_list_reset(struct sieve_stringlist *_strlist);
32
static void
33
sieve_header_address_list_set_trace(struct sieve_stringlist *_strlist,
34
            bool trace);
35
36
/* Stringlist object */
37
38
struct sieve_header_address_list {
39
  struct sieve_address_list addrlist;
40
41
  struct sieve_stringlist *field_values;
42
  const struct message_address *cur_address;
43
};
44
45
struct sieve_address_list *
46
sieve_header_address_list_create(const struct sieve_runtime_env *renv,
47
         struct sieve_stringlist *field_values)
48
0
{
49
0
  struct sieve_header_address_list *addrlist;
50
51
0
  addrlist = t_new(struct sieve_header_address_list, 1);
52
0
  addrlist->addrlist.strlist.runenv = renv;
53
0
  addrlist->addrlist.strlist.exec_status = SIEVE_EXEC_OK;
54
0
  addrlist->addrlist.strlist.next_item =
55
0
    sieve_header_address_list_next_string_item;
56
0
  addrlist->addrlist.strlist.reset = sieve_header_address_list_reset;
57
0
  addrlist->addrlist.strlist.set_trace =
58
0
    sieve_header_address_list_set_trace;
59
0
  addrlist->addrlist.next_item = sieve_header_address_list_next_item;
60
0
  addrlist->field_values = field_values;
61
62
0
  return &addrlist->addrlist;
63
0
}
64
65
static int
66
sieve_header_address_list_next_address(
67
  struct sieve_header_address_list *addrlist, struct smtp_address *addr_r)
68
0
{
69
0
  struct smtp_address adummy;
70
0
  int ret = 0;
71
72
0
  if (addr_r == NULL)
73
0
    addr_r = &adummy;
74
75
0
  while (addrlist->cur_address != NULL) {
76
0
    const struct message_address *aitem = addrlist->cur_address;
77
78
0
    addrlist->cur_address = addrlist->cur_address->next;
79
80
0
    if (!aitem->invalid_syntax && aitem->domain != NULL &&
81
0
        smtp_address_init_from_msg(addr_r, aitem) >= 0)
82
0
      return 1;
83
0
    ret = -1;
84
0
  }
85
0
  return ret;
86
0
}
87
88
static int
89
sieve_header_address_list_next_item(struct sieve_address_list *_addrlist,
90
            struct smtp_address *addr_r,
91
            string_t **unparsed_r)
92
0
{
93
0
  struct sieve_header_address_list *addrlist =
94
0
    (struct sieve_header_address_list *)_addrlist;
95
0
  const struct sieve_runtime_env *runenv = _addrlist->strlist.runenv;
96
0
  string_t *value_item = NULL;
97
0
  bool trace = _addrlist->strlist.trace;
98
99
0
  if (addr_r != NULL)
100
0
    smtp_address_init(addr_r, NULL, NULL);
101
0
  if (unparsed_r != NULL)
102
0
    *unparsed_r = NULL;
103
104
0
  for (;;) {
105
0
    int ret;
106
107
0
    if ((ret = sieve_header_address_list_next_address(
108
0
      addrlist, addr_r)) < 0 &&
109
0
        value_item != NULL) {
110
      /* completely invalid address list is returned as-is */
111
0
      if (trace) {
112
0
        sieve_runtime_trace(
113
0
          runenv, 0,
114
0
          "invalid address value '%s'",
115
0
          str_sanitize(str_c(value_item), 80));
116
0
      }
117
0
      if (unparsed_r != NULL)
118
0
        *unparsed_r = value_item;
119
0
      return 1;
120
0
    }
121
0
    if (ret > 0) {
122
0
      if (trace) {
123
0
        sieve_runtime_trace(
124
0
          runenv, 0,
125
0
          "address value '%s'",
126
0
          str_sanitize(smtp_address_encode(addr_r),
127
0
                 80));
128
0
      }
129
0
      return 1;
130
0
    }
131
132
    /* Read next header value from source list */
133
0
    if ((ret = sieve_stringlist_next_item(addrlist->field_values,
134
0
                  &value_item)) <= 0)
135
0
      return ret;
136
0
    if (str_len(value_item) == 0) {
137
      /* empty header value is returned as-is */
138
0
      if (trace) {
139
0
        sieve_runtime_trace(runenv, 0,
140
0
                "empty address value");
141
0
      }
142
0
      addrlist->cur_address = NULL;
143
0
      if (unparsed_r != NULL)
144
0
        *unparsed_r = value_item;
145
0
      return 1;
146
0
    }
147
148
0
    if (trace) {
149
0
      sieve_runtime_trace(
150
0
        runenv, 0,
151
0
        "parsing address header value '%s'",
152
0
        str_sanitize(str_c(value_item), 80));
153
0
    }
154
155
0
    addrlist->cur_address = message_address_parse(
156
0
      pool_datastack_create(),
157
0
      (const unsigned char *)str_data(value_item),
158
0
      str_len(value_item), 256, 0);
159
0
  }
160
0
  i_unreached();
161
0
}
162
163
static int
164
sieve_header_address_list_next_string_item(struct sieve_stringlist *_strlist,
165
             string_t **str_r)
166
0
{
167
0
  struct sieve_address_list *addrlist =
168
0
    (struct sieve_address_list *)_strlist;
169
0
  struct smtp_address addr;
170
0
  int ret;
171
172
0
  if ((ret = sieve_header_address_list_next_item(
173
0
    addrlist, &addr, str_r)) <= 0)
174
0
    return ret;
175
176
0
  if (addr.localpart != NULL) {
177
0
    const char *addr_str = smtp_address_encode(&addr);
178
179
0
    if (str_r != NULL)
180
0
      *str_r = t_str_new_const(addr_str, strlen(addr_str));
181
0
  }
182
0
  return 1;
183
0
}
184
185
static void sieve_header_address_list_reset(struct sieve_stringlist *_strlist)
186
0
{
187
0
  struct sieve_header_address_list *addrlist =
188
0
    (struct sieve_header_address_list *)_strlist;
189
190
0
  sieve_stringlist_reset(addrlist->field_values);
191
0
  addrlist->cur_address = NULL;
192
0
}
193
194
static void
195
sieve_header_address_list_set_trace(struct sieve_stringlist *_strlist,
196
            bool trace)
197
0
{
198
0
  struct sieve_header_address_list *addrlist =
199
0
    (struct sieve_header_address_list *)_strlist;
200
201
0
  sieve_stringlist_set_trace(addrlist->field_values, trace);
202
0
}
203
204
/*
205
 * RFC 2822 addresses
206
 */
207
208
/* Mail message address according to RFC 2822 and implemented in the Dovecot
209
   message address parser:
210
211
     address         =       mailbox / group
212
     mailbox         =       name-addr / addr-spec
213
     name-addr       =       [display-name] angle-addr
214
     angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
215
     group           =       display-name ":" [mailbox-list / CFWS] ";" [CFWS]
216
     display-name    =       phrase
217
218
     addr-spec       =       local-part "@" domain
219
     local-part      =       dot-atom / quoted-string / obs-local-part
220
     domain          =       dot-atom / domain-literal / obs-domain
221
     domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
222
     dcontent        =       dtext / quoted-pair
223
     dtext           =       NO-WS-CTL /     ; Non white space controls
224
                             %d33-90 /       ; The rest of the US-ASCII
225
                             %d94-126        ;  characters not including "[",
226
                                             ;  "]", or "\"
227
228
     atext           =       ALPHA / DIGIT / ; Any character except controls,
229
                            "!" / "#" /     ;  SP, and specials.
230
                             "$" / "%" /     ;  Used for atoms
231
                             "&" / "'" /
232
                             "*" / "+" /
233
                             "-" / "/" /
234
                             "=" / "?" /
235
                             "^" / "_" /
236
                             "`" / "{" /
237
                             "|" / "}" /
238
                             "~"
239
     atom            =       [CFWS] 1*atext [CFWS]
240
     dot-atom        =       [CFWS] dot-atom-text [CFWS]
241
     dot-atom-text   =       1*atext *("." 1*atext)
242
     word            =       atom / quoted-string
243
     phrase          =       1*word / obs-phrase
244
245
   Message address specification as allowed bij the RFC 5228 SIEVE
246
   specification:
247
     sieve-address   =       addr-spec                  ; simple address
248
                             / phrase "<" addr-spec ">" ; name & addr-spec\
249
250
   Which concisely is about equal to:
251
     sieve-address   =       mailbox
252
 */
253
254
/*
255
 * Address parse context
256
 */
257
258
struct sieve_message_address_parser {
259
  struct rfc822_parser_context parser;
260
261
  string_t *str;
262
  string_t *local_part;
263
  string_t *domain;
264
265
  string_t *error;
266
};
267
268
/*
269
 * Error handling
270
 */
271
272
static inline void ATTR_FORMAT(2, 3)
273
sieve_address_error(struct sieve_message_address_parser *ctx,
274
        const char *fmt, ...)
275
0
{
276
0
  va_list args;
277
278
0
  if (str_len(ctx->error) == 0) {
279
0
    va_start(args, fmt);
280
0
    str_vprintfa(ctx->error, fmt, args);
281
0
    va_end(args);
282
0
  }
283
0
}
284
285
/*
286
   Partial RFC 2822 address parser
287
288
     FIXME: lots of overlap with dovecot/src/lib-mail/message-parser.c
289
            --> this implementation adds textual error reporting
290
            MERGE!
291
 */
292
293
static int check_local_part(struct sieve_message_address_parser *ctx)
294
0
{
295
0
  const unsigned char *p, *pend;
296
297
0
  p = str_data(ctx->local_part);
298
0
  pend = p + str_len(ctx->local_part);
299
0
  while (p < pend) {
300
0
    if (*p < 0x20 || *p > 0x7e)
301
0
      return -1;
302
0
    p++;
303
0
  }
304
0
  return 0;
305
0
}
306
307
static int parse_local_part(struct sieve_message_address_parser *ctx)
308
0
{
309
0
  int ret;
310
311
  /*
312
     local-part      = dot-atom / quoted-string / obs-local-part
313
     obs-local-part  = word *("." word)
314
  */
315
0
  if (ctx->parser.data == ctx->parser.end) {
316
0
    sieve_address_error(ctx, "empty local part");
317
0
    return -1;
318
0
  }
319
320
0
  str_truncate(ctx->local_part, 0);
321
0
  if (*ctx->parser.data == '"') {
322
0
    ret = rfc822_parse_quoted_string(&ctx->parser, ctx->local_part);
323
0
  } else {
324
0
    ret = -1;
325
    /* NOTE: this deviates from dot-atom syntax to allow some
326
       Japanese mail addresses with dots at non-standard places to
327
       be accepted. */
328
0
    do {
329
0
      while (*ctx->parser.data == '.') {
330
0
        str_append_c(ctx->local_part, '.');
331
0
        ctx->parser.data++;
332
0
        if (ctx->parser.data == ctx->parser.end) {
333
          /* @domain is missing, but local-part
334
             parsing was successful */
335
0
          return 0;
336
0
        }
337
0
        ret = 1;
338
0
      }
339
0
      if (*ctx->parser.data == '@')
340
0
        break;
341
0
      ret = rfc822_parse_atom(&ctx->parser, ctx->local_part);
342
0
    } while (ret > 0 && *ctx->parser.data == '.');
343
0
  }
344
345
0
  if (ret < 0 || check_local_part(ctx) < 0) {
346
0
    sieve_address_error(ctx, "invalid local part");
347
0
    return -1;
348
0
  }
349
0
  return ret;
350
0
}
351
352
static int parse_domain(struct sieve_message_address_parser *ctx)
353
0
{
354
0
  int ret;
355
356
0
  str_truncate(ctx->domain, 0);
357
0
  if ((ret = rfc822_parse_domain(&ctx->parser, ctx->domain)) < 0) {
358
0
    sieve_address_error(ctx, "invalid or missing domain");
359
0
    return -1;
360
0
  }
361
362
0
  return ret;
363
0
}
364
365
static int parse_addr_spec(struct sieve_message_address_parser *ctx)
366
0
{
367
  /* addr-spec       = local-part "@" domain */
368
0
  int ret;
369
370
0
  if ((ret = parse_local_part(ctx)) < 0)
371
0
    return ret;
372
373
0
  if (ret > 0 && *ctx->parser.data == '@') {
374
0
    return parse_domain(ctx);
375
0
  }
376
377
0
  sieve_address_error(
378
0
    ctx, "invalid or lonely local part '%s' (expecting '@')",
379
0
    str_sanitize(str_c(ctx->local_part), 80));
380
0
  return -1;
381
0
}
382
383
static int parse_mailbox(struct sieve_message_address_parser *ctx)
384
0
{
385
0
  int ret;
386
0
  const unsigned char *start;
387
388
  /* sieve-address   =       addr-spec                  ; simple address
389
                             / phrase "<" addr-spec ">" ; name & addr-spec
390
   */
391
392
  /* Record parser state in case we fail at our first attempt */
393
0
  start = ctx->parser.data;
394
395
  /* First try: phrase "<" addr-spec ">" ; name & addr-spec */
396
0
  str_truncate(ctx->str, 0);
397
0
  if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 ||
398
0
      *ctx->parser.data != '<') {
399
    /* Failed; try just bare addr-spec */
400
0
    ctx->parser.data = start;
401
0
    return parse_addr_spec(ctx);
402
0
  }
403
404
  /* "<" addr-spec ">" */
405
0
  ctx->parser.data++;
406
407
0
  if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0) {
408
0
    if (ret < 0)
409
0
      sieve_address_error(ctx, "invalid characters after <");
410
0
    return ret;
411
0
  }
412
413
0
  if (parse_addr_spec(ctx) < 0)
414
0
    return -1;
415
416
0
  if (*ctx->parser.data != '>') {
417
0
    sieve_address_error(ctx, "missing '>'");
418
0
    return -1;
419
0
  }
420
0
  ctx->parser.data++;
421
422
0
  if ((ret = rfc822_skip_lwsp(&ctx->parser)) < 0) {
423
0
    sieve_address_error(
424
0
      ctx, "address ends with invalid characters");
425
0
  }
426
0
  return ret;
427
0
}
428
429
static bool
430
parse_mailbox_address(struct sieve_message_address_parser *ctx,
431
          const unsigned char *address, unsigned int addr_size)
432
0
{
433
  /* Initialize parser */
434
435
0
  rfc822_parser_init(&ctx->parser, address, addr_size, NULL);
436
437
  /* Parse */
438
439
0
  rfc822_skip_lwsp(&ctx->parser);
440
441
0
  if (ctx->parser.data == ctx->parser.end) {
442
0
    sieve_address_error(ctx, "empty address");
443
0
    return FALSE;
444
0
  }
445
446
0
  if (parse_mailbox(ctx) < 0)
447
0
    return FALSE;
448
449
0
  if (ctx->parser.data != ctx->parser.end) {
450
0
    if (*ctx->parser.data == ',') {
451
0
      sieve_address_error(
452
0
        ctx, "not a single address (found ',')");
453
0
    } else {
454
0
      sieve_address_error(
455
0
        ctx, "address ends in invalid characters");
456
0
    }
457
0
    return FALSE;
458
0
  }
459
460
0
  if (str_len(ctx->domain) == 0) {
461
    /* Not gonna happen */
462
0
    sieve_address_error(ctx, "missing domain");
463
0
    return FALSE;
464
0
  }
465
466
0
  if (str_len(ctx->local_part) == 0) {
467
0
    sieve_address_error(ctx, "missing local part");
468
0
    return FALSE;
469
0
  }
470
0
  return TRUE;
471
0
}
472
473
static bool
474
sieve_address_do_validate(const unsigned char *address, size_t size,
475
        const char **error_r)
476
0
{
477
0
  struct sieve_message_address_parser ctx;
478
479
0
  *error_r = NULL;
480
481
0
  if (address == NULL) {
482
0
    *error_r = "null address";
483
0
    return FALSE;
484
0
  }
485
486
0
  i_zero(&ctx);
487
488
0
  ctx.local_part = t_str_new(128);
489
0
  ctx.domain = t_str_new(128);
490
0
  ctx.str = t_str_new(128);
491
0
  ctx.error = t_str_new(128);
492
493
0
  if (!parse_mailbox_address(&ctx, address, size)) {
494
0
    *error_r = str_c(ctx.error);
495
0
    return FALSE;
496
0
  }
497
498
0
  return TRUE;
499
0
}
500
501
static const struct smtp_address *
502
sieve_address_do_parse(const unsigned char *address, size_t size,
503
           const char **error_r)
504
0
{
505
0
  struct sieve_message_address_parser ctx;
506
507
0
  *error_r = NULL;
508
509
0
  if (address == NULL)
510
0
    return NULL;
511
512
0
  i_zero(&ctx);
513
514
0
  ctx.local_part = t_str_new(128);
515
0
  ctx.domain = t_str_new(128);
516
0
  ctx.str = t_str_new(128);
517
0
  ctx.error = t_str_new(128);
518
519
0
  if (!parse_mailbox_address(&ctx, address, size)) {
520
0
    *error_r = str_c(ctx.error);
521
0
    return NULL;
522
0
  }
523
524
0
  (void)str_lcase(str_c_modifiable(ctx.domain));
525
526
0
  return smtp_address_create_temp(str_c(ctx.local_part),
527
0
          str_c(ctx.domain));
528
0
}
529
530
/*
531
 * Sieve address
532
 */
533
534
const struct smtp_address *
535
sieve_address_parse(const char *address, const char **error_r)
536
0
{
537
0
  return sieve_address_do_parse((const unsigned char *)address,
538
0
              strlen(address), error_r);
539
0
}
540
541
const struct smtp_address *
542
sieve_address_parse_str(string_t *address, const char **error_r)
543
0
{
544
0
  return sieve_address_do_parse(str_data(address), str_len(address),
545
0
              error_r);
546
0
}
547
548
bool sieve_address_validate(const char *address, const char **error_r)
549
0
{
550
0
  return sieve_address_do_validate((const unsigned char *)address,
551
0
           strlen(address), error_r);
552
0
}
553
554
bool sieve_address_validate_str(string_t *address, const char **error_r)
555
0
{
556
0
  return sieve_address_do_validate(str_data(address), str_len(address),
557
0
           error_r);
558
0
}