Coverage Report

Created: 2026-07-16 07:09

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