Coverage Report

Created: 2026-07-30 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-smtp/smtp-address.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "message-address.h"
6
#include "smtp-parser.h"
7
#include "smtp-address.h"
8
9
/* From RFC 5321:
10
11
   Reverse-path     = Path / "<>"
12
   Forward-path     = Path
13
14
   Path             = "<" [ A-d-l ":" ] Mailbox ">"
15
   A-d-l            = At-domain *( "," At-domain )
16
                    ; Note that this form, the so-called "source
17
                    ; route", MUST BE accepted, SHOULD NOT be
18
                    ; generated, and SHOULD be ignored.
19
   At-domain        = "@" Domain
20
21
   Domain           = sub-domain *("." sub-domain)
22
23
   sub-domain       = Let-dig [Ldh-str]
24
   Let-dig          = ALPHA / DIGIT
25
   Ldh-str          = *( ALPHA / DIGIT / "-" ) Let-dig
26
27
   address-literal  = "[" ( IPv4-address-literal /
28
                      IPv6-address-literal /
29
                      General-address-literal ) "]"
30
                    ; See Section 4.1.3
31
32
   Mailbox          = Local-part "@" ( Domain / address-literal )
33
34
   Local-part       = Dot-string / Quoted-string
35
                    ; MAY be case-sensitive
36
   Dot-string       = Atom *("."  Atom)
37
   Atom             = 1*atext
38
 */
39
40
/*
41
 * SMTP address parsing
42
 */
43
44
struct smtp_address_parser {
45
  struct smtp_parser parser;
46
47
  struct smtp_address address;
48
  const unsigned char *address_end;
49
50
  bool parse:1;
51
  bool path:1;
52
  bool parsed_any:1;
53
  bool totally_broken:1;
54
};
55
56
static int
57
smtp_parser_parse_dot_string(struct smtp_parser *parser, const char **value_r)
58
22.7k
{
59
22.7k
  const unsigned char *pbegin = parser->cur;
60
61
  /* Dot-string = Atom *("." Atom)
62
   */
63
64
  /* NOTE: this deviates from Dot-String syntax to allow some Japanese
65
     mail addresses with dots at non-standard places to be accepted. */
66
67
22.7k
  if (parser->cur >= parser->end ||
68
22.2k
      (!smtp_char_is_atext(*parser->cur) && *parser->cur != '.'))
69
9.72k
    return 0;
70
13.0k
  parser->cur++;
71
72
207k
  while (parser->cur < parser->end &&
73
206k
         (smtp_char_is_atext(*parser->cur) || *parser->cur == '.'))
74
194k
    parser->cur++;
75
76
13.0k
  if (value_r != NULL)
77
13.0k
    *value_r = t_strndup(pbegin, parser->cur - pbegin);
78
13.0k
  return 1;
79
22.7k
}
80
81
static int
82
smtp_parse_localpart(struct smtp_parser *parser, const char **localpart_r)
83
29.5k
{
84
29.5k
  int ret;
85
86
29.5k
  if ((ret = smtp_parser_parse_quoted_string(parser, localpart_r)) != 0)
87
6.78k
    return ret;
88
89
22.7k
  return smtp_parser_parse_dot_string(parser, localpart_r);
90
29.5k
}
91
92
static int
93
smtp_address_parser_find_end(struct smtp_address_parser *aparser,
94
           enum smtp_address_parse_flags flags)
95
0
{
96
0
  struct smtp_parser *parser = &aparser->parser;
97
0
  const char *begin = (const char *)parser->begin, *end;
98
0
  const char **address_p = NULL;
99
100
0
  if (aparser->address_end != NULL)
101
0
    return 0;
102
103
0
  if (aparser->parse &&
104
0
      HAS_ALL_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW))
105
0
    address_p = &aparser->address.raw;
106
0
  if (smtp_address_parse_any(begin, address_p, &end) < 0) {
107
0
    parser->error = "Invalid character";
108
0
    aparser->totally_broken = TRUE;
109
0
    return -1;
110
0
  }
111
0
  aparser->parsed_any = TRUE;
112
0
  aparser->address_end = (const unsigned char *)end;
113
0
  if (aparser->path) {
114
0
    i_assert(aparser->address_end > parser->begin);
115
0
    aparser->address_end--;
116
0
  }
117
0
  return 0;
118
0
}
119
120
static int
121
smtp_parse_mailbox(struct smtp_address_parser *aparser,
122
       enum smtp_address_parse_flags flags)
123
29.5k
{
124
29.5k
  struct smtp_parser *parser = &aparser->parser;
125
29.5k
  const char **value = NULL;
126
29.5k
  const unsigned char *p, *dp;
127
29.5k
  int ret;
128
129
  /* Mailbox = Local-part "@" ( Domain / address-literal )
130
   */
131
132
29.5k
  value = (aparser->parse ? &aparser->address.localpart : NULL);
133
29.5k
  if ((flags & SMTP_ADDRESS_PARSE_FLAG_STRICT) != 0 ||
134
29.5k
      (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART) == 0 ||
135
29.5k
      aparser->path || *parser->cur == '\"') {
136
29.5k
    if ((ret = smtp_parse_localpart(parser, value)) <= 0)
137
9.93k
      return ret;
138
29.5k
  } else {
139
    /* find the end of the address */
140
0
    if (smtp_address_parser_find_end(aparser, flags) < 0)
141
0
      return -1;
142
    /* use the right-most '@' as separator */
143
0
    dp = aparser->address_end - 1;
144
    /* an empty address leaves address_end at cur, so clamp to
145
       avoid dp < cur underflowing into p_strdup_until() below */
146
0
    if (dp < parser->cur)
147
0
      dp = parser->cur;
148
0
    while (dp > parser->cur && *dp != '@')
149
0
      dp--;
150
0
    if (dp == parser->cur)
151
0
      dp = aparser->address_end;
152
    /* check whether the resulting localpart could be encoded as
153
       quoted string */
154
0
    for (p = parser->cur; p < dp; p++) {
155
0
      if (!smtp_char_is_qtext(*p) &&
156
0
          !smtp_char_is_qpair(*p)) {
157
0
        parser->error =
158
0
          "Invalid character in localpart";
159
0
        return -1;
160
0
      }
161
0
    }
162
0
    if (aparser->parse) {
163
0
      aparser->address.localpart =
164
0
        p_strdup_until(parser->pool, parser->cur, dp);
165
0
    }
166
0
    parser->cur = dp;
167
0
  }
168
169
19.5k
  if ((parser->cur >= parser->end || *parser->cur != '@') &&
170
2.57k
      (flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART) == 0) {
171
867
    if (parser->cur >= parser->end ||
172
487
        (aparser->path && *parser->cur == '>'))
173
577
      parser->error = "Missing domain";
174
290
    else
175
290
      parser->error = "Invalid character in localpart";
176
867
    return -1;
177
867
  }
178
179
18.7k
  if (parser->cur >= parser->end || *parser->cur != '@')
180
1.71k
    return 1;
181
17.0k
  parser->cur++;
182
183
17.0k
  value = (aparser->parse ? &aparser->address.domain : NULL);
184
17.0k
  if ((ret = smtp_parser_parse_domain(parser, value)) == 0 &&
185
1.43k
      (ret = smtp_parser_parse_address_literal(
186
1.43k
    parser, value, NULL)) == 0) {
187
976
    if (parser->cur >= parser->end ||
188
624
        (aparser->path && *parser->cur == '>')) {
189
624
      parser->error = "Missing domain after '@'";
190
624
      return -1;
191
624
    } else {
192
352
      parser->error = "Invalid domain";
193
352
      return -1;
194
352
    }
195
976
  }
196
16.0k
  return ret;
197
17.0k
}
198
199
static int smtp_parse_source_route(struct smtp_parser *parser)
200
30.8k
{
201
  /* Source-route = [ A-d-l ":" ]
202
     A-d-l        = At-domain *( "," At-domain )
203
     At-domain    = "@" Domain
204
   */
205
206
  /* "@" Domain */
207
30.8k
  if (parser->cur >= parser->end || *parser->cur != '@')
208
29.2k
    return 0;
209
1.58k
  parser->cur++;
210
211
2.39k
  for (;;) {
212
    /* Domain */
213
2.39k
    if (smtp_parser_parse_domain(parser, NULL) <= 0) {
214
286
      parser->error =
215
286
        "Missing domain after '@' in source route";
216
286
      return -1;
217
286
    }
218
219
    /* *( "," At-domain ) */
220
2.10k
    if (parser->cur >= parser->end || *parser->cur != ',')
221
1.02k
      break;
222
1.08k
    parser->cur++;
223
224
    /* "@" Domain */
225
1.08k
    if (parser->cur >= parser->end || *parser->cur != '@') {
226
281
      parser->error = "Missing '@' after ',' in source route";
227
281
      return -1;
228
281
    }
229
803
    parser->cur++;
230
803
  }
231
232
  /* ":" */
233
1.02k
  if (parser->cur >= parser->end || *parser->cur != ':') {
234
751
    parser->error = "Missing ':' at end of source route";
235
751
    return -1;
236
751
  }
237
271
  parser->cur++;
238
271
  return 1;
239
1.02k
}
240
241
static int
242
smtp_parse_path(struct smtp_address_parser *aparser,
243
    enum smtp_address_parse_flags flags)
244
31.1k
{
245
31.1k
  struct smtp_parser *parser = &aparser->parser;
246
31.1k
  int ret, sret = 0;
247
248
  /* Path = "<" [ A-d-l ":" ] Mailbox ">"
249
   */
250
251
  /* "<" */
252
31.1k
  if (parser->cur < parser->end && *parser->cur == '<') {
253
30.8k
    aparser->path = TRUE;
254
30.8k
    parser->cur++;
255
30.8k
  } else if ((flags & SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL) == 0) {
256
301
    return 0;
257
301
  }
258
259
  /* [ A-d-l ":" ] */
260
30.8k
  if (aparser->path && (sret = smtp_parse_source_route(parser)) < 0)
261
1.31k
    return -1;
262
263
  /* Mailbox */
264
29.5k
  if ((ret = smtp_parse_mailbox(aparser, flags)) < 0)
265
2.52k
    return -1;
266
27.0k
  if (ret == 0) {
267
9.72k
    if (parser->cur < parser->end && *parser->cur == '>') {
268
8.94k
      if (sret > 0) {
269
240
        parser->error =
270
240
          "Path only consists of source route";
271
240
        return -1;
272
240
      }
273
8.70k
      if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY)
274
8.70k
        == 0) {
275
314
        parser->error = "Null path not allowed";
276
314
        return -1;
277
314
      }
278
8.70k
    } else {
279
776
      parser->error = "Invalid character in localpart";
280
776
      return -1;
281
776
    }
282
9.72k
  }
283
284
  /* ">" */
285
25.6k
  if (aparser->path) {
286
25.6k
    if (parser->cur >= parser->end || *parser->cur != '>') {
287
768
      parser->error = "Missing '>' at end of path";
288
768
      return -1;
289
768
    }
290
24.9k
    parser->cur++;
291
24.9k
  } else if (parser->cur < parser->end && *parser->cur == '>') {
292
0
    parser->error = "Unmatched '>' at end of path";
293
0
    return -1;
294
0
  }
295
24.9k
  return 1;
296
25.6k
}
297
298
int smtp_address_parse_mailbox(pool_t pool, const char *mailbox,
299
             enum smtp_address_parse_flags flags,
300
             struct smtp_address **address_r,
301
             const char **error_r)
302
0
{
303
0
  struct smtp_address_parser aparser;
304
0
  int ret;
305
306
0
  if (address_r != NULL)
307
0
    *address_r = NULL;
308
0
  if (error_r != NULL)
309
0
    *error_r = NULL;
310
311
0
  if (error_r != NULL)
312
0
    *error_r = NULL;
313
314
0
  if ((mailbox == NULL || *mailbox == '\0')) {
315
0
    if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) == 0) {
316
0
      if (error_r != NULL)
317
0
        *error_r = "Mailbox is empty string";
318
0
      return -1;
319
0
    }
320
321
0
    if (address_r != NULL)
322
0
      *address_r = p_new(pool, struct smtp_address, 1);
323
0
    return 0;
324
0
  }
325
326
0
  i_zero(&aparser);
327
0
  smtp_parser_init(&aparser.parser, pool_datastack_create(), mailbox);
328
0
  aparser.address_end = aparser.parser.end;
329
0
  aparser.parse = (address_r != NULL);
330
331
0
  if ((ret = smtp_parse_mailbox(&aparser, flags)) <= 0) {
332
0
    if (error_r != NULL) {
333
0
      *error_r = (ret < 0 ? aparser.parser.error :
334
0
        "Invalid character in localpart");
335
0
    }
336
0
    return -1;
337
0
  }
338
0
  if (aparser.parser.cur != aparser.parser.end) {
339
0
    if (error_r != NULL)
340
0
      *error_r = "Invalid character in mailbox";
341
0
    return -1;
342
0
  }
343
344
0
  if (address_r != NULL)
345
0
    *address_r = smtp_address_clone(pool, &aparser.address);
346
0
  return 0;
347
0
}
348
349
static int
350
smtp_address_parse_path_broken(struct smtp_address_parser *aparser,
351
             enum smtp_address_parse_flags flags,
352
             const char **endp_r) ATTR_NULL(3)
353
6.23k
{
354
6.23k
  struct smtp_parser *parser = &aparser->parser;
355
6.23k
  const char *begin = (const char *)parser->begin, *end;
356
6.23k
  const char *raw = aparser->address.raw;
357
6.23k
  const char **address_p = NULL;
358
359
6.23k
  i_zero(&aparser->address);
360
6.23k
  aparser->address.raw = raw;
361
362
6.23k
  if (aparser->totally_broken ||
363
6.23k
      HAS_NO_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN))
364
6.23k
    return -1;
365
0
  if (*begin != '<' &&
366
0
      HAS_NO_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL)) {
367
    /* brackets missing; totally broken */
368
0
    return -1;
369
0
  }
370
0
  i_assert(aparser->parse);
371
0
  if (aparser->parsed_any) {
372
0
    if (endp_r != NULL)
373
0
      *endp_r = (const char *)aparser->address_end;
374
0
    return 0;
375
0
  }
376
377
0
  if (HAS_ALL_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW))
378
0
    address_p = &aparser->address.raw;
379
0
  if (smtp_address_parse_any(begin, address_p, &end) < 0) {
380
    /* totally broken */
381
0
    return -1;
382
0
  }
383
0
  if (endp_r != NULL)
384
0
    *endp_r = end;
385
0
  return 0;
386
0
}
387
388
int smtp_address_parse_path_full(pool_t pool, const char *path,
389
         enum smtp_address_parse_flags flags,
390
         struct smtp_address **address_r,
391
         const char **error_r, const char **endp_r)
392
31.9k
{
393
31.9k
  struct smtp_address_parser aparser;
394
31.9k
  int ret;
395
396
31.9k
  if (address_r != NULL)
397
31.9k
    *address_r = NULL;
398
31.9k
  if (error_r != NULL)
399
31.9k
    *error_r = NULL;
400
31.9k
  if (endp_r != NULL)
401
31.9k
    *endp_r = NULL;
402
403
31.9k
  if (path == NULL || *path == '\0') {
404
798
    if ((flags & SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY) == 0 ||
405
798
        (flags & SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL) == 0) {
406
798
      if (error_r != NULL)
407
798
        *error_r = "Path is empty string";
408
798
      return -1;
409
798
    }
410
0
    if (address_r != NULL)
411
0
      *address_r = p_new(pool, struct smtp_address, 1);
412
0
    if (endp_r != NULL)
413
0
      *endp_r = path;
414
0
    return 0;
415
798
  }
416
417
31.1k
  i_zero(&aparser);
418
31.1k
  smtp_parser_init(&aparser.parser, pool_datastack_create(), path);
419
31.1k
  aparser.address_end = (endp_r != NULL ? NULL : aparser.parser.end);
420
31.1k
  aparser.parse = (address_r != NULL);
421
422
31.1k
  if ((ret = smtp_parse_path(&aparser, flags)) <= 0) {
423
6.23k
    if (error_r != NULL) {
424
6.23k
      *error_r = (ret < 0 ? aparser.parser.error :
425
6.23k
        "Missing '<' at beginning of path");
426
6.23k
    }
427
6.23k
    ret = -1;
428
24.9k
  } else if (endp_r != NULL) {
429
24.9k
    if (aparser.parser.cur == aparser.parser.end ||
430
8.04k
        *aparser.parser.cur == ' ' ||
431
24.9k
        HAS_NO_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_IGNORE_BROKEN)) {
432
24.9k
      *endp_r = (const char *)aparser.parser.cur;
433
24.9k
      ret = 0;
434
24.9k
    } else {
435
0
      if (error_r != NULL)
436
0
        *error_r = "Invalid character in path";
437
0
      ret = -1;
438
0
    }
439
24.9k
  } else if (aparser.parser.cur == aparser.parser.end) {
440
0
    ret = 0;
441
0
  } else {
442
0
    if (error_r != NULL)
443
0
      *error_r = "Invalid character in path";
444
0
    ret = -1;
445
0
  }
446
447
31.1k
  if (ret < 0) {
448
    /* normal parsing failed */
449
6.23k
    if (smtp_address_parse_path_broken(&aparser, flags,
450
6.23k
               endp_r) < 0) {
451
      /* failed to parse it as a broken address as well */
452
6.23k
      return -1;
453
6.23k
    }
454
    /* broken address */
455
24.9k
  } else if (HAS_ALL_BITS(flags, SMTP_ADDRESS_PARSE_FLAG_PRESERVE_RAW) &&
456
12.5k
       aparser.address.localpart != NULL) {
457
4.16k
    if (aparser.path &&
458
4.16k
        ((const unsigned char *)(path + 1) < aparser.parser.cur)) {
459
4.16k
      aparser.address.raw = t_strdup_until(
460
4.16k
        path + 1, aparser.parser.cur - 1);
461
4.16k
    } else {
462
0
      aparser.address.raw = t_strdup_until(
463
0
        path, aparser.parser.cur);
464
0
    }
465
4.16k
  }
466
467
24.9k
  if (address_r != NULL)
468
24.9k
    *address_r = smtp_address_clone(pool, &aparser.address);
469
24.9k
  return ret;
470
31.1k
}
471
472
int smtp_address_parse_path(pool_t pool, const char *path,
473
          enum smtp_address_parse_flags flags,
474
          struct smtp_address **address_r,
475
          const char **error_r)
476
0
{
477
0
  return smtp_address_parse_path_full(pool, path, flags,
478
0
              address_r, error_r, NULL);
479
0
}
480
481
int smtp_address_parse_username(pool_t pool, const char *username,
482
        struct smtp_address **address_r,
483
        const char **error_r)
484
0
{
485
0
  enum smtp_address_parse_flags flags =
486
0
    SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
487
0
    SMTP_ADDRESS_PARSE_FLAG_ALLOW_BAD_LOCALPART;
488
489
0
  struct smtp_address_parser aparser;
490
0
  int ret;
491
492
0
  if (address_r != NULL)
493
0
    *address_r = NULL;
494
0
  if (error_r != NULL)
495
0
    *error_r = NULL;
496
497
0
  if ((username == NULL || *username == '\0')) {
498
0
    if (error_r != NULL)
499
0
      *error_r = "Username is empty string";
500
0
    return -1;
501
0
  }
502
503
0
  i_zero(&aparser);
504
0
  smtp_parser_init(&aparser.parser, pool_datastack_create(), username);
505
0
  aparser.address_end = aparser.parser.end;
506
0
  aparser.parse = (address_r != NULL);
507
508
0
  if ((ret = smtp_parse_mailbox(&aparser, flags)) <= 0) {
509
0
    if (error_r != NULL) {
510
0
      *error_r = (ret < 0 ? aparser.parser.error :
511
0
        "Invalid character in user name");
512
0
    }
513
0
    return -1;
514
0
  }
515
0
  if (aparser.parser.cur != aparser.parser.end) {
516
0
    if (error_r != NULL)
517
0
      *error_r = "Invalid character in user name";
518
0
    return -1;
519
0
  }
520
521
0
  if (address_r != NULL)
522
0
    *address_r = smtp_address_clone(pool, &aparser.address);
523
0
  return 0;
524
0
}
525
526
void smtp_address_detail_parse(pool_t pool, const char *delimiters,
527
             struct smtp_address *address,
528
             const char **username_r, char *delim_r,
529
             const char **detail_r)
530
0
{
531
0
  const char *localpart;
532
0
  const char *user, *p;
533
0
  size_t idx;
534
535
0
  i_assert(!smtp_address_isnull(address));
536
537
0
  localpart = address->localpart;
538
0
  user = localpart;
539
0
  *detail_r = "";
540
0
  *delim_r = '\0';
541
542
  /* first character that matches the recipient_delimiter */
543
0
  idx = strcspn(localpart, delimiters);
544
0
  p = (localpart[idx] != '\0' ? &localpart[idx] : NULL);
545
546
0
  if (p != NULL) {
547
0
    *delim_r = *p;
548
    /* user+detail */
549
0
    user = p_strdup_until(pool, localpart, p);
550
0
    *detail_r = p+1;
551
0
  }
552
553
0
  if (address->domain == NULL || *address->domain == '\0')
554
0
    *username_r = user;
555
0
  else if (strchr(user, '@') == NULL) {
556
    /* username is just glued to the domain... no SMTP escaping */
557
0
    *username_r = p_strconcat(pool, user, "@", address->domain,
558
0
            NULL);
559
0
  } else {
560
0
    struct smtp_address uaddr;
561
562
    /* username contains '@'; apply escaping */
563
0
    smtp_address_init(&uaddr, user, address->domain);
564
0
    if (pool->datastack_pool)
565
0
      *username_r = smtp_address_encode(&uaddr);
566
0
    else {
567
0
      *username_r =
568
0
        p_strdup(pool, smtp_address_encode(&uaddr));
569
0
    }
570
0
  }
571
0
}
572
573
void smtp_address_detail_parse_temp(const char *delimiters,
574
            struct smtp_address *address,
575
            const char **username_r, char *delim_r,
576
            const char **detail_r)
577
0
{
578
0
  smtp_address_detail_parse(pool_datastack_create(), delimiters,
579
0
          address, username_r, delim_r, detail_r);
580
0
}
581
582
int smtp_address_parse_any(const char *in, const char **address_r,
583
         const char **endp_r)
584
0
{
585
0
  const unsigned char *p, *pend, *poffset;
586
0
  bool path = FALSE;
587
0
  bool quoted = FALSE;
588
589
0
  if (endp_r != NULL)
590
0
    *endp_r = in;
591
592
0
  poffset = p = (const unsigned char *)in;
593
0
  pend = p + strlen(in);
594
0
  if (*p  == '<') {
595
0
    path = TRUE;
596
0
    p++;
597
0
    poffset = p;
598
0
  }
599
0
  if (*p == '"') {
600
0
    quoted = TRUE;
601
0
    p++;
602
0
  }
603
604
0
  while (p < pend) {
605
0
    if (quoted && *p == '\\') {
606
0
      p++;
607
0
      if (p == pend || *p < 0x20)
608
0
        return -1;
609
0
      p++;
610
0
      if (p == pend)
611
0
        break;
612
0
    }
613
0
    switch (*p) {
614
0
    case '"':
615
0
      quoted = FALSE;
616
0
      break;
617
0
    case ' ':
618
0
      if (!quoted) {
619
0
        if (path)
620
0
          return -1;
621
0
        if (address_r != NULL)
622
0
          *address_r = t_strdup_until(poffset, p);
623
0
        if (endp_r != NULL)
624
0
          *endp_r = (const char *)p;
625
0
        return 0;
626
0
      }
627
0
      break;
628
0
    case '>':
629
0
      if (!quoted) {
630
0
        if (address_r != NULL)
631
0
          *address_r = t_strdup_until(poffset, p);
632
0
        if (endp_r != NULL)
633
0
          *endp_r = (const char *)(p + 1);
634
0
        return 0;
635
0
      }
636
0
      break;
637
0
    default:
638
0
      if (*p < 0x20)
639
0
        return -1;
640
0
      break;
641
0
    }
642
0
    p++;
643
0
  }
644
0
  if (quoted || path)
645
0
    return -1;
646
0
  if (address_r != NULL)
647
0
    *address_r = t_strdup_until(poffset, p);
648
0
  if (endp_r != NULL)
649
0
    *endp_r = (const char *)p;
650
0
  return 0;
651
0
}
652
653
/*
654
 * SMTP address construction
655
 */
656
657
void smtp_address_write(string_t *out, const struct smtp_address *address)
658
      ATTR_NULL(2)
659
50.9k
{
660
50.9k
  bool quoted = FALSE;
661
50.9k
  const unsigned char *p, *pend, *pblock;
662
50.9k
  size_t begin;
663
664
50.9k
  if (smtp_address_isnull(address))
665
14.8k
    return;
666
36.0k
  begin = str_len(out);
667
668
  /* encode localpart */
669
36.0k
  p = (const unsigned char *)address->localpart;
670
36.0k
  pend = p + strlen(address->localpart);
671
36.0k
  pblock = p;
672
139k
  while (p < pend) {
673
2.00M
    while (p < pend && smtp_char_is_atext(*p))
674
1.87M
      p++;
675
676
125k
    if (!quoted && p < pend && (*p != '.' || p == pblock)) {
677
6.71k
      quoted = TRUE;
678
6.71k
      str_insert(out, begin, "\"");
679
6.71k
    }
680
681
125k
    str_append_data(out, pblock, p - pblock);
682
125k
    if (p >= pend)
683
22.0k
      break;
684
685
103k
    if (!quoted) {
686
10.8k
      str_append_c(out, '.');
687
93.0k
    } else {
688
93.0k
      i_assert(smtp_char_is_qpair(*p));
689
93.0k
      if (!smtp_char_is_qtext(*p))
690
4.29k
        str_append_c(out, '\\');
691
93.0k
      str_append_c(out, *p);
692
93.0k
    }
693
694
103k
    p++;
695
103k
    pblock = p;
696
103k
  }
697
698
36.0k
  if (p == pblock && !quoted) {
699
8.72k
    quoted = TRUE;
700
8.72k
    str_insert(out, begin, "\"");
701
8.72k
  }
702
703
36.0k
  if (quoted)
704
15.4k
    str_append_c(out, '\"');
705
706
36.0k
  if (address->domain == NULL || *address->domain == '\0')
707
765
    return;
708
709
35.3k
  str_append_c(out, '@');
710
35.3k
  str_append(out, address->domain);
711
35.3k
}
712
713
void smtp_address_write_path(string_t *out, const struct smtp_address *address)
714
0
{
715
0
  str_append_c(out, '<');
716
0
  smtp_address_write(out, address);
717
0
  str_append_c(out, '>');
718
0
}
719
720
const char *smtp_address_encode(const struct smtp_address *address)
721
50.9k
{
722
50.9k
  string_t *str = t_str_new(256);
723
50.9k
  smtp_address_write(str, address);
724
50.9k
  return str_c(str);
725
50.9k
}
726
727
const char *smtp_address_encode_path(const struct smtp_address *address)
728
0
{
729
0
  string_t *str = t_str_new(256);
730
0
  smtp_address_write_path(str, address);
731
0
  return str_c(str);
732
0
}
733
734
const char *smtp_address_encode_raw(const struct smtp_address *address)
735
4.40k
{
736
4.40k
  if (address != NULL && address->raw != NULL && *address->raw != '\0')
737
897
    return address->raw;
738
739
3.50k
  return smtp_address_encode(address);
740
4.40k
}
741
742
const char *smtp_address_encode_raw_path(const struct smtp_address *address)
743
0
{
744
0
  if (address != NULL && address->raw != NULL && *address->raw != '\0')
745
0
    return t_strconcat("<", address->raw, ">", NULL);
746
747
0
  return smtp_address_encode_path(address);
748
0
}
749
750
/*
751
 * SMTP address manipulation
752
 */
753
754
void smtp_address_init(struct smtp_address *address,
755
           const char *localpart, const char *domain)
756
0
{
757
0
  i_zero(address);
758
0
  if (localpart == NULL || *localpart == '\0')
759
0
    return;
760
761
0
  address->localpart = localpart;
762
0
  if (domain != NULL && *domain != '\0')
763
0
    address->domain = domain;
764
0
}
765
766
int smtp_address_init_from_msg(struct smtp_address *address,
767
             const struct message_address *msg_addr)
768
0
{
769
0
  const unsigned char *p;
770
771
0
  i_zero(address);
772
0
  if (msg_addr->mailbox == NULL || *msg_addr->mailbox == '\0')
773
0
    return 0;
774
775
  /* The message_address_parse() function allows UTF-8 codepoints in
776
     the localpart. For SMTP addresses that is not an option, so we
777
     need to check this upon conversion. */
778
0
  for (p = (const unsigned char *)msg_addr->mailbox; *p != '\0'; p++) {
779
0
    if (!smtp_char_is_qpair(*p))
780
0
      return -1;
781
0
  }
782
783
0
  address->localpart = msg_addr->mailbox;
784
0
  if (msg_addr->domain != NULL && *msg_addr->domain != '\0')
785
0
    address->domain = msg_addr->domain;
786
0
  return 0;
787
0
}
788
789
struct smtp_address *
790
smtp_address_clone(pool_t pool, const struct smtp_address *src)
791
61.4k
{
792
61.4k
  struct smtp_address *new;
793
61.4k
  size_t size, lpsize = 0, dsize = 0, rsize = 0;
794
61.4k
  char *data, *localpart = NULL, *domain = NULL, *raw = NULL;
795
796
61.4k
  if (src == NULL)
797
15.3k
    return NULL;
798
799
  /* @UNSAFE */
800
801
46.1k
  size = sizeof(struct smtp_address);
802
46.1k
  if (!smtp_address_isnull(src)) {
803
29.8k
    lpsize = strlen(src->localpart) + 1;
804
29.8k
    size = MALLOC_ADD(size, lpsize);
805
29.8k
  }
806
46.1k
  if (src->domain != NULL && *src->domain != '\0') {
807
28.5k
    dsize = strlen(src->domain) + 1;
808
28.5k
    size = MALLOC_ADD(size, dsize);
809
28.5k
  }
810
46.1k
  if (src->raw != NULL && *src->raw != '\0') {
811
6.58k
    rsize = strlen(src->raw) + 1;
812
6.58k
    size = MALLOC_ADD(size, rsize);
813
6.58k
  }
814
815
46.1k
  data = p_malloc(pool, size);
816
46.1k
  new = (struct smtp_address *)data;
817
46.1k
  if (lpsize > 0) {
818
29.8k
    localpart = PTR_OFFSET(data, sizeof(*new));
819
29.8k
    memcpy(localpart, src->localpart, lpsize);
820
29.8k
  }
821
46.1k
  if (dsize > 0) {
822
28.5k
    domain = PTR_OFFSET(data, sizeof(*new) + lpsize);
823
28.5k
    memcpy(domain, src->domain, dsize);
824
28.5k
  }
825
46.1k
  if (rsize > 0) {
826
6.58k
    raw = PTR_OFFSET(data, sizeof(*new) + lpsize + dsize);
827
6.58k
    memcpy(raw, src->raw, rsize);
828
6.58k
  }
829
46.1k
  new->localpart = localpart;
830
46.1k
  new->domain = domain;
831
46.1k
  new->raw = raw;
832
833
46.1k
  return new;
834
61.4k
}
835
836
struct smtp_address *
837
smtp_address_create(pool_t pool, const char *localpart, const char *domain)
838
0
{
839
0
  struct smtp_address addr;
840
841
0
  smtp_address_init(&addr, localpart, domain);
842
0
  return smtp_address_clone(pool, &addr);
843
0
}
844
845
846
int smtp_address_create_from_msg(pool_t pool,
847
         const struct message_address *msg_addr,
848
         struct smtp_address **address_r)
849
0
{
850
0
  struct smtp_address addr;
851
852
0
  if (smtp_address_init_from_msg(&addr, msg_addr) < 0) {
853
0
    *address_r = NULL;
854
0
    return -1;
855
0
  }
856
0
  *address_r = smtp_address_clone(pool, &addr);
857
0
  return 0;
858
0
}
859
860
struct smtp_address *smtp_address_clone_temp(const struct smtp_address *src)
861
0
{
862
0
  struct smtp_address *new;
863
864
0
  if (src == NULL)
865
0
    return NULL;
866
867
0
  new = t_new(struct smtp_address, 1);
868
0
  new->localpart = t_strdup_empty(src->localpart);
869
0
  new->domain = t_strdup_empty(src->domain);
870
0
  new->raw = t_strdup_empty(src->raw);
871
0
  return new;
872
0
}
873
874
struct smtp_address *
875
smtp_address_create_temp(const char *localpart, const char *domain)
876
0
{
877
0
  struct smtp_address addr;
878
879
0
  smtp_address_init(&addr, localpart, domain);
880
0
  return smtp_address_clone_temp(&addr);
881
0
}
882
883
int  smtp_address_create_from_msg_temp(const struct message_address *msg_addr,
884
               struct smtp_address **address_r)
885
0
{
886
0
  struct smtp_address addr;
887
888
0
  if (smtp_address_init_from_msg(&addr, msg_addr) < 0) {
889
0
    *address_r = NULL;
890
0
    return -1;
891
0
  }
892
0
  *address_r = smtp_address_clone_temp(&addr);
893
0
  return 0;
894
0
}
895
896
struct smtp_address *
897
smtp_address_add_detail(pool_t pool, const struct smtp_address *address,
898
      const char *detail, char delim_c)
899
0
{
900
0
  struct smtp_address *new_addr;
901
0
  const char delim[] = {delim_c, '\0'};
902
903
0
  i_assert(!smtp_address_isnull(address));
904
905
0
  new_addr = p_new(pool, struct smtp_address, 1);
906
0
  new_addr->localpart = p_strconcat(pool, address->localpart, delim,
907
0
            detail, NULL);
908
0
  new_addr->domain = p_strdup_empty(pool, address->domain);
909
910
0
  return new_addr;
911
0
}
912
913
struct smtp_address *
914
smtp_address_add_detail_temp(const struct smtp_address *address,
915
           const char *detail, char delim_c)
916
0
{
917
0
  struct smtp_address *new_addr;
918
0
  const char delim[] = {delim_c, '\0'};
919
920
0
  i_assert(!smtp_address_isnull(address));
921
922
0
  new_addr = t_new(struct smtp_address, 1);
923
0
  new_addr->localpart = t_strconcat(address->localpart, delim, detail,
924
0
            NULL);
925
0
  new_addr->domain = t_strdup_empty(address->domain);
926
927
0
  return new_addr;
928
0
}
929
930
int smtp_address_cmp(const struct smtp_address *address1,
931
         const struct smtp_address *address2)
932
0
{
933
0
  bool null1, null2;
934
0
  int ret;
935
936
0
  null1 = smtp_address_isnull(address1);
937
0
  null2 = smtp_address_isnull(address2);
938
0
  if (null1)
939
0
    return (null2 ? 0 : -1);
940
0
  else if (null2)
941
0
    return 1;
942
0
  if ((ret = null_strcasecmp(address1->domain, address2->domain)) != 0)
943
0
    return ret;
944
0
  return null_strcmp(address1->localpart, address2->localpart);
945
0
}
946
947
int smtp_address_cmp_icase(const struct smtp_address *address1,
948
        const struct smtp_address *address2)
949
0
{
950
0
  bool null1, null2;
951
0
  int ret;
952
953
0
  null1 = smtp_address_isnull(address1);
954
0
  null2 = smtp_address_isnull(address2);
955
0
  if (null1)
956
0
    return (null2 ? 0 : -1);
957
0
  else if (null2)
958
0
    return 1;
959
0
  if ((ret = null_strcasecmp(address1->domain, address2->domain)) != 0)
960
0
    return ret;
961
0
  return null_strcasecmp(address1->localpart, address2->localpart);
962
0
}