1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
46 # include <stringprep.h>
50 size_t tlen
; /* Length of .tdata */
51 char const *tdata
; /* Template date - see _cmatch_data[] */
55 * Template characters for cmatch_data.tdata:
56 * 'A' An upper case char
57 * 'a' A lower case char
60 * 'O' An optional digit or space
62 * '+' Either a plus or a minus sign
64 static struct cmatch_data
const _cmatch_data
[] = {
65 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
66 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
67 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
68 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
70 * RFC 822-alike From_ lines do not conform to RFC 4155, but seem to
71 * be used in the wild by UW-imap
73 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
74 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
75 * zone strings; note that 1. is strictly speaking not correct as some
76 * letters are not used, and 2. is not because only "UT" is defined */
77 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
78 { 28 - 2, __reuse
}, { 28 - 1, __reuse
}, { 28 - 0, __reuse
},
81 #define _DATE_MINLEN 21
83 /* Skip over "word" as found in From_ line */
84 static char const * _from__skipword(char const *wp
);
86 /* Match the date string against the date template (tp), return if match.
87 * See _cmatch_data[] for template character description */
88 static int _cmatch(size_t len
, char const *date
, char const *tp
);
90 /* Check wether date is a valid 'From_' date.
91 * (Rather ctime(3) generated dates, according to RFC 4155) */
92 static int _is_date(char const *date
);
94 /* Convert the domain part of a skinned address to IDNA.
95 * If an error occurs before Unicode information is available, revert the IDNA
96 * error to a normal CHAR one so that the error message doesn't talk Unicode */
98 static struct addrguts
* _idna_apply(struct addrguts
*agp
);
101 /* Classify and check a (possibly skinned) header body according to RFC
102 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
103 * also a file or a pipe command, so check that first, then.
104 * Otherwise perform content checking and isolate the domain part (for IDNA) */
105 static int _addrspec_check(int doskin
, struct addrguts
*agp
);
107 static int gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
,
109 static int msgidnextc(const char **cp
, int *status
);
110 static int charcount(char *str
, int c
);
113 _from__skipword(char const *wp
)
118 while ((c
= *wp
++) != '\0' && ! blankchar(c
)) {
120 while ((c
= *wp
++) != '\0' && c
!= '"')
126 for (; blankchar(c
); c
= *wp
++)
129 return (c
== 0 ? NULL
: wp
- 1);
133 _cmatch(size_t len
, char const *date
, char const *tp
)
157 if (c
!= ' ' && ! digitchar(c
))
165 if (c
!= '+' && c
!= '-')
176 _is_date(char const *date
)
178 struct cmatch_data
const *cmdp
;
179 size_t dl
= strlen(date
);
182 if (dl
>= _DATE_MINLEN
)
183 for (cmdp
= _cmatch_data
; cmdp
->tdata
!= NULL
; ++cmdp
)
184 if (dl
== cmdp
->tlen
&&
185 (ret
= _cmatch(dl
, date
, cmdp
->tdata
)))
191 static struct addrguts
*
192 _idna_apply(struct addrguts
*agp
)
194 char *idna_utf8
, *idna_ascii
, *cs
;
197 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
199 idna_utf8
= ac_alloc(sz
+ 1);
200 memcpy(idna_utf8
, agp
->ag_skinned
+ agp
->ag_sdom_start
, sz
);
201 idna_utf8
[sz
] = '\0';
203 /* GNU Libidn settles on top of iconv(3) without having any fallback,
204 * so let's just let it perform the charset conversion, if any should
207 char const *tcs
= charset_get_lc();
208 idna_ascii
= idna_utf8
;
209 idna_utf8
= stringprep_convert(idna_ascii
, "UTF-8", tcs
);
210 i
= (idna_utf8
== NULL
&& errno
== EINVAL
);
212 if (idna_utf8
== NULL
) {
214 fprintf(stderr
, tr(179,
215 "Cannot convert from %s to %s\n"),
217 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
|
218 NAME_ADDRSPEC_ERR_CHAR
;
223 if (idna_to_ascii_8z(idna_utf8
, &idna_ascii
, 0) != IDNA_SUCCESS
) {
224 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
|
225 NAME_ADDRSPEC_ERR_CHAR
;
229 /* Replace the domain part of .ag_skinned with IDNA version */
230 sz
= strlen(idna_ascii
);
231 i
= agp
->ag_sdom_start
;
232 cs
= salloc(agp
->ag_slen
- i
+ sz
+ 1);
233 memcpy(cs
, agp
->ag_skinned
, i
);
234 memcpy(cs
+ i
, idna_ascii
, sz
);
238 agp
->ag_skinned
= cs
;
240 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
241 NAME_NAME_SALLOC
|NAME_SKINNED
|NAME_IDNA
, 0);
255 _addrspec_check(int skinned
, struct addrguts
*agp
)
257 char *addr
, *p
, in_quote
, in_domain
, hadat
;
258 union {char c
; unsigned char u
;} c
;
260 uc_it use_idna
= !ok_blook(idna_disable
);
263 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
264 addr
= agp
->ag_skinned
;
266 if (agp
->ag_iaddr_aend
- agp
->ag_iaddr_start
== 0) {
267 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
,
272 /* If the field is not a recipient, it cannot be a file or a pipe */
277 * Excerpt from nail.1:
279 * Recipient address specifications
280 * The rules are: Any name which starts with a `|' character specifies
281 * a pipe, the command string following the `|' is executed and
282 * the message is sent to its standard input; any other name which
283 * contains a `@' character is treated as a mail address; any other
284 * name which starts with a `+' character specifies a folder name; any
285 * other name which contains a `/' character but no `!' or `%'
286 * character before also specifies a folder name; what remains is
287 * treated as a mail address.
290 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
293 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
296 for (p
= addr
; (c
.c
= *p
); ++p
) {
297 if (c
.c
== '!' || c
.c
== '%')
300 jisfile
: agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
307 in_quote
= in_domain
= hadat
= 0;
309 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
311 in_quote
= ! in_quote
;
312 } else if (c
.u
< 040 || c
.u
>= 0177) {
314 if (in_domain
&& use_idna
) {
316 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
317 NAME_ADDRSPEC_ERR_IDNA
, c
.u
);
322 } else if (in_domain
== 2) {
323 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' ||
326 } else if (in_quote
&& in_domain
== 0) {
328 } else if (c
.c
== '\\' && *p
!= '\0') {
330 } else if (c
.c
== '@') {
332 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
333 NAME_ADDRSPEC_ERR_ATSEQ
, c
.u
);
336 agp
->ag_sdom_start
= (size_t)(p
- addr
);
337 in_domain
= (*p
== '[') ? 2 : 1;
339 } else if (c
.c
== '(' || c
.c
== ')' ||
340 c
.c
== '<' || c
.c
== '>' ||
341 c
.c
== ',' || c
.c
== ';' || c
.c
== ':' ||
342 c
.c
== '\\' || c
.c
== '[' || c
.c
== ']')
348 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
,
355 agp
= _idna_apply(agp
);
359 return ((agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) != 0);
363 myaddrs(struct header
*hp
)
368 if (hp
!= NULL
&& (np
= hp
->h_from
) != NULL
) {
369 if ((rv
= np
->n_fullname
) != NULL
)
371 if ((rv
= np
->n_name
) != NULL
)
375 if ((rv
= ok_vlook(from
)) != NULL
)
378 /* When invoking *sendmail* directly, it's its task
379 * to generate an otherwise undeterminable From: address.
380 * However, if the user sets *hostname*, accept his desire */
381 if (ok_vlook(smtp
) != NULL
|| ok_vlook(hostname
) != NULL
) {
382 char *hn
= nodename(1);
383 size_t sz
= strlen(myname
) + strlen(hn
) + 2;
385 snprintf(rv
, sz
, "%s@%s", myname
, hn
);
392 myorigin(struct header
*hp
)
394 char const *ret
= NULL
, *ccp
;
397 if ((ccp
= myaddrs(hp
)) != NULL
&&
398 (np
= lextract(ccp
, GEXTRA
|GFULL
)) != NULL
)
399 ret
= np
->n_flink
!= NULL
? ok_vlook(sender
) : ccp
;
404 is_head(char const *linebuf
, size_t linelen
) /* XXX verbose WARN */
406 char date
[FROM_DATEBUF
];
408 return ((linelen
<= 5 || memcmp(linebuf
, "From ", 5) != 0 ||
409 ! extract_date_from_from_(linebuf
, linelen
, date
) ||
410 ! _is_date(date
)) ? 0 : 1);
414 extract_date_from_from_(char const *line
, size_t linelen
,
415 char datebuf
[FROM_DATEBUF
])
418 char const *cp
= line
;
421 cp
= _from__skipword(cp
);
425 cp
= _from__skipword(cp
);
428 if (cp
[0] == 't' && cp
[1] == 't' && cp
[2] == 'y') {
429 cp
= _from__skipword(cp
);
434 linelen
-= (size_t)(cp
- line
);
435 if (linelen
< _DATE_MINLEN
)
437 if (cp
[linelen
- 1] == '\n') {
439 /* (Rather IMAP/POP3 only) */
440 if (cp
[linelen
- 1] == '\r')
442 if (linelen
< _DATE_MINLEN
)
445 if (linelen
>= FROM_DATEBUF
)
449 jleave
: memcpy(datebuf
, cp
, linelen
);
450 datebuf
[linelen
] = '\0';
453 jerr
: cp
= tr(213, "<Unknown date>");
454 linelen
= strlen(cp
);
455 if (linelen
>= FROM_DATEBUF
)
456 linelen
= FROM_DATEBUF
;
461 extract_header(FILE *fp
, struct header
*hp
) /* XXX no header occur-cnt check */
463 struct header nh
, *hq
= &nh
;
464 char *linebuf
= NULL
, *colon
;
466 int seenfields
= 0, lc
, c
;
467 char const *val
, *cp
;
469 memset(hq
, 0, sizeof *hq
);
470 for (lc
= 0; readline_restart(fp
, &linebuf
, &linesize
, 0) > 0; lc
++)
473 while ((lc
= gethfield(fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
474 if ((val
= thisfield(linebuf
, "to")) != NULL
) {
476 hq
->h_to
= cat(hq
->h_to
, checkaddrs(
477 lextract(val
, GTO
|GFULL
)));
478 } else if ((val
= thisfield(linebuf
, "cc")) != NULL
) {
480 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(
481 lextract(val
, GCC
|GFULL
)));
482 } else if ((val
= thisfield(linebuf
, "bcc")) != NULL
) {
484 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(
485 lextract(val
, GBCC
|GFULL
)));
486 } else if ((val
= thisfield(linebuf
, "from")) != NULL
) {
488 hq
->h_from
= cat(hq
->h_from
, checkaddrs(
489 lextract(val
, GEXTRA
|GFULL
)));
490 } else if ((val
= thisfield(linebuf
, "reply-to")) != NULL
) {
492 hq
->h_replyto
= cat(hq
->h_replyto
, checkaddrs(
493 lextract(val
, GEXTRA
|GFULL
)));
494 } else if ((val
= thisfield(linebuf
, "sender")) != NULL
) {
496 hq
->h_sender
= cat(hq
->h_sender
, checkaddrs(
497 lextract(val
, GEXTRA
|GFULL
)));
498 } else if ((val
= thisfield(linebuf
,
499 "organization")) != NULL
) {
501 for (cp
= val
; blankchar(*cp
); cp
++)
503 hq
->h_organization
= hq
->h_organization
?
504 save2str(hq
->h_organization
, cp
) :
506 } else if ((val
= thisfield(linebuf
, "subject")) != NULL
||
507 (val
= thisfield(linebuf
, "subj")) != NULL
) {
509 for (cp
= val
; blankchar(*cp
); cp
++)
511 hq
->h_subject
= hq
->h_subject
?
512 save2str(hq
->h_subject
, cp
) :
515 fprintf(stderr
, tr(266,
516 "Ignoring header field \"%s\"\n"),
520 * In case the blank line after the header has been edited out.
521 * Otherwise, fetch the header separator.
524 if (linebuf
[0] != '\0') {
525 for (cp
= linebuf
; *(++cp
) != '\0'; );
526 fseek(fp
, (long)-(1 + cp
- linebuf
), SEEK_CUR
);
528 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
535 hp
->h_bcc
= hq
->h_bcc
;
536 hp
->h_from
= hq
->h_from
;
537 hp
->h_replyto
= hq
->h_replyto
;
538 hp
->h_sender
= hq
->h_sender
;
539 hp
->h_organization
= hq
->h_organization
;
540 hp
->h_subject
= hq
->h_subject
;
542 fprintf(stderr
, tr(267, "Restoring deleted header lines\n"));
548 * Return the desired header line from the passed message
549 * pointer (or NULL if the desired header field is not available).
550 * If mult is zero, return the content of the first matching header
551 * field only, the content of all matching header fields else.
554 hfield_mult(char const *field
, struct message
*mp
, int mult
)
559 char *linebuf
= NULL
, *colon
, *oldhfield
= NULL
;
562 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
564 if ((lc
= mp
->m_lines
- 1) < 0)
567 if ((mp
->m_flag
& MNOFROM
) == 0 &&
568 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
571 if ((lc
= gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
)) < 0)
573 if ((hfield
= thisfield(linebuf
, field
)) != NULL
) {
574 oldhfield
= save2str(hfield
, oldhfield
);
587 * Return the next header field found in the given message.
588 * Return >= 0 if something found, < 0 elsewise.
589 * "colon" is set to point to the colon in the header.
590 * Must deal with \ continuations & other such fraud.
593 gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
, char **colon
)
596 size_t line2size
= 0;
600 if (*linebuf
== NULL
)
601 *linebuf
= srealloc(*linebuf
, *linesize
= 1);
606 if ((c
= readline_restart(f
, linebuf
, linesize
, 0)) <= 0)
608 for (cp
= *linebuf
; fieldnamechar(*cp
& 0377); cp
++);
610 while (blankchar(*cp
& 0377))
612 if (*cp
!= ':' || cp
== *linebuf
)
615 * I guess we got a headline.
616 * Handle wraparounding
622 while (--cp
>= *linebuf
&& blankchar(*cp
& 0377));
626 if (cp
-8 >= *linebuf
&& cp
[-1] == '=' && cp
[-2] == '?')
628 ungetc(c
= getc(f
), f
);
631 c
= readline_restart(f
, &line2
, &line2size
, 0);
635 for (cp2
= line2
; blankchar(*cp2
& 0377); cp2
++);
637 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
639 if (cp
+ c
>= *linebuf
+ *linesize
- 2) {
640 size_t diff
= cp
- *linebuf
;
641 size_t colondiff
= *colon
- *linebuf
;
642 *linebuf
= srealloc(*linebuf
,
644 cp
= &(*linebuf
)[diff
];
645 *colon
= &(*linebuf
)[colondiff
];
661 * Check whether the passed line is a header line of
662 * the desired breed. Return the field body, or 0.
665 thisfield(char const *linebuf
, char const *field
)
667 while (lowerconv(*linebuf
) == lowerconv(*field
)) {
673 while (blankchar(*linebuf
))
675 if (*linebuf
++ != ':')
677 while (blankchar(*linebuf
))
683 * Get sender's name from this message. If the message has
684 * a bunch of arpanet stuff in it, we may have to skin the name
685 * before returning it.
688 nameof(struct message
*mp
, int reptype
)
692 cp
= skin(name1(mp
, reptype
));
693 if (reptype
!= 0 || charcount(cp
, '!') < 2)
695 cp2
= strrchr(cp
, '!');
697 while (cp2
> cp
&& *cp2
!= '!')
705 * Start of a "comment".
709 skip_comment(char const *cp
)
713 for (; nesting
> 0 && *cp
; cp
++) {
731 * Return the start of a route-addr (address in angle brackets),
735 routeaddr(char const *name
)
737 char const *np
, *rp
= NULL
;
739 for (np
= name
; *np
; np
++) {
742 np
= skip_comment(&np
[1]) - 1;
748 if (*np
== '\\' && np
[1])
763 * Check if a name's address part contains invalid characters.
766 is_addr_invalid(struct name
*np
, int putmsg
)
768 char cbuf
[sizeof "'\\U12340'"], *name
= np
->n_name
;
769 int f
= np
->n_flags
, ok8bit
= 1;
771 char const *fmt
= "'\\x%02X'", *cs
;
773 if ((f
& NAME_ADDRSPEC_INVALID
) == 0 || ! putmsg
||
774 (f
& NAME_ADDRSPEC_ERR_EMPTY
) != 0)
777 if (f
& NAME_ADDRSPEC_ERR_IDNA
)
778 cs
= tr(284, "Invalid domain name: \"%s\", character %s\n"),
781 else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
782 cs
= tr(142, "\"%s\" contains invalid %s sequence\n");
784 cs
= tr(143, "\"%s\" contains invalid character %s\n");
786 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
787 if (ok8bit
&& c
>= 040 && c
<= 0177)
788 snprintf(cbuf
, sizeof cbuf
, "'%c'", c
);
790 snprintf(cbuf
, sizeof cbuf
, fmt
, c
);
792 fprintf(stderr
, cs
, name
, cbuf
);
794 return ((f
& NAME_ADDRSPEC_INVALID
) != 0);
798 skin(char const *name
)
804 (void)addrspec_with_guts(1, name
, &ag
);
806 if ((ag
.ag_n_flags
& NAME_NAME_SALLOC
) == 0)
807 ret
= savestrbuf(ret
, ag
.ag_slen
);
812 /* TODO addrspec_with_guts: RFC 5322 */
814 addrspec_with_guts(int doskin
, char const *name
, struct addrguts
*agp
)
817 char *cp2
, *bufend
, *nbuf
, c
;
818 char gotlt
, gotaddr
, lastsp
;
820 memset(agp
, 0, sizeof *agp
);
822 if ((agp
->ag_input
= name
) == NULL
|| /* XXX ever? */
823 (agp
->ag_ilen
= strlen(name
)) == 0) {
824 agp
->ag_skinned
= UNCONST(""); /* ok: NAME_SALLOC is not set */
826 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
827 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
,
832 if (! doskin
|| ! anyof(name
, "(< ")) {
833 /*agp->ag_iaddr_start = 0;*/
834 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
835 agp
->ag_skinned
= UNCONST(name
); /* (NAME_SALLOC not set) */
836 agp
->ag_slen
= agp
->ag_ilen
;
837 agp
->ag_n_flags
= NAME_SKINNED
;
838 return _addrspec_check(doskin
, agp
);
841 /* Something makes us think we have to perform the skin operation */
842 nbuf
= ac_alloc(agp
->ag_ilen
+ 1);
843 /*agp->ag_iaddr_start = 0;*/
845 gotlt
= gotaddr
= lastsp
= 0;
847 for (cp
= name
++; (c
= *cp
++) != '\0'; ) {
850 cp
= skip_comment(cp
);
855 * Start of a "quoted-string".
856 * Copy it in its entirety.
857 * XXX RFC: quotes are "semantically invisible"
858 * XXX But it was explicitly added (Changelog.Heirloom,
859 * XXX [9.23] released 11/15/00, "Do not remove quotes
860 * XXX when skinning names"? No more info..
863 while ((c
= *cp
) != '\0') { /* TODO improve */
871 else if ((c
= *cp
) != '\0') {
882 agp
->ag_iaddr_aend
= (size_t)(cp
- name
);
884 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
885 cp
+= 3, *cp2
++ = '@';
886 else if (cp
[0] == '@' && blankchar(cp
[1]))
887 cp
+= 2, *cp2
++ = '@';
892 agp
->ag_iaddr_start
= (size_t)(cp
- (name
- 1));
899 /* (_addrspec_check() verifies these later!) */
900 agp
->ag_iaddr_aend
= (size_t)(cp
- name
);
902 while ((c
= *cp
) != '\0' && c
!= ',') {
905 cp
= skip_comment(cp
);
907 while ((c
= *cp
) != '\0') {
911 if (c
== '\\' && *cp
)
929 for (; blankchar(*cp
); ++cp
)
934 } else if (! gotaddr
) {
936 agp
->ag_iaddr_start
= (size_t)(cp
- name
);
940 agp
->ag_slen
= (size_t)(cp2
- nbuf
);
941 if (agp
->ag_iaddr_aend
== 0)
942 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
944 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
946 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
947 return _addrspec_check(doskin
, agp
);
951 * Fetch the real name from an internet mail address field.
954 realname(char const *name
)
956 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
959 int quoted
, good
, nogood
;
963 for (cp
= UNCONST(name
); *cp
; cp
++) {
968 * More than one comment in address, doesn't
969 * make sense to display it without context.
970 * Return the entire field,
972 return mime_fromaddr(name
);
974 cp
= skip_comment(cp
);
977 cend
= cstart
= NULL
;
983 if (*cp
== '\\' && cp
[1])
995 * More than one address. Just use the first one.
1000 brk
: if (cstart
== NULL
) {
1003 * If name contains only a route-addr, the
1004 * surrounding angle brackets don't serve any
1005 * useful purpose when displaying, so they
1008 return prstr(skin(name
));
1009 return mime_fromaddr(name
);
1011 rp
= rname
= ac_alloc(cend
- cstart
+ 1);
1013 * Strip quotes. Note that quotes that appear within a MIME-
1014 * encoded word are not stripped. The idea is to strip only
1015 * syntactical relevant things (but this is not necessarily
1016 * the most sensible way in practice).
1019 for (cp
= cstart
; cp
< cend
; cp
++) {
1020 if (*cp
== '(' && !quoted
) {
1021 cq
= skip_comment(++cp
);
1025 if (*cp
== '\\' && &cp
[1] < cq
)
1029 } else if (*cp
== '\\' && &cp
[1] < cend
)
1031 else if (*cp
== '"') {
1040 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1042 rname
= savestr(out
.s
);
1044 while (blankchar(*rname
& 0377))
1046 for (rp
= rname
; *rp
; rp
++);
1047 while (--rp
>= rname
&& blankchar(*rp
& 0377))
1050 return mime_fromaddr(name
);
1052 * mime_fromhdr() has converted all nonprintable characters to
1053 * question marks now. These and blanks are considered uninteresting;
1054 * if the displayed part of the real name contains more than 25% of
1055 * them, it is probably better to display the plain email address
1060 for (rp
= rname
; *rp
&& rp
< &rname
[20]; rp
++)
1061 if (*rp
== '?' || blankchar(*rp
& 0377))
1065 if (good
*3 < nogood
)
1066 return prstr(skin(name
));
1071 * Fetch the sender's name from the passed message.
1073 * 0 -- get sender's name for display purposes
1074 * 1 -- get sender's name for reply
1075 * 2 -- get sender's name for Reply
1078 name1(struct message
*mp
, int reptype
)
1082 char *linebuf
= NULL
;
1083 size_t linesize
= 0;
1088 if ((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
1090 if (reptype
== 0 && (cp
= hfield1("sender", mp
)) != NULL
&&
1093 namebuf
= smalloc(namesize
= 1);
1095 if (mp
->m_flag
& MNOFROM
)
1097 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1099 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1102 if (namesize
<= linesize
)
1103 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1104 for (cp
= linebuf
; *cp
&& *cp
!= ' '; cp
++)
1106 for (; blankchar(*cp
& 0377); cp
++);
1107 for (cp2
= &namebuf
[strlen(namebuf
)];
1108 *cp
&& !blankchar(*cp
& 0377) && cp2
< namebuf
+ namesize
- 1;)
1111 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1113 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
1115 if (strncmp(cp
, "From", 4) != 0)
1117 if (namesize
<= linesize
)
1118 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1119 while ((cp
= strchr(cp
, 'r')) != NULL
) {
1120 if (strncmp(cp
, "remote", 6) == 0) {
1121 if ((cp
= strchr(cp
, 'f')) == NULL
)
1123 if (strncmp(cp
, "from", 4) != 0)
1125 if ((cp
= strchr(cp
, ' ')) == NULL
)
1129 strncpy(namebuf
, cp
, namesize
);
1132 cp2
=strrchr(namebuf
, '!')+1;
1133 strncpy(cp2
, cp
, (namebuf
+namesize
)-cp2
);
1135 namebuf
[namesize
- 2] = '!';
1136 namebuf
[namesize
- 1] = '\0';
1142 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
1144 cp
= savestr(namebuf
);
1152 msgidnextc(const char **cp
, int *status
)
1172 *cp
= skip_comment(&(*cp
)[1]);
1187 c
= *(*cp
)++ & 0377;
1188 return *status
& 02 ? lowerconv(c
) : c
;
1194 msgidcmp(const char *s1
, const char *s2
)
1200 c1
= msgidnextc(&s1
, &q1
);
1201 c2
= msgidnextc(&s2
, &q2
);
1209 * Count the occurances of c in str
1212 charcount(char *str
, int c
)
1217 for (i
= 0, cp
= str
; *cp
; cp
++)
1224 * See if the given header field is supposed to be ignored.
1227 is_ign(char const *field
, size_t fieldlen
, struct ignoretab ignoret
[2])
1232 if (ignoret
== NULL
)
1234 if (ignoret
== allignore
)
1237 * Lower-case the string, so that "Status" and "status"
1238 * will hash to the same place.
1240 realfld
= ac_alloc(fieldlen
+ 1);
1241 i_strcpy(realfld
, field
, fieldlen
+ 1);
1242 if (ignoret
[1].i_count
> 0)
1243 ret
= !member(realfld
, ignoret
+ 1);
1245 ret
= member(realfld
, ignoret
);
1251 member(char const *realfield
, struct ignoretab
*table
)
1255 for (igp
= table
->i_head
[hash(realfield
)]; igp
!= 0; igp
= igp
->i_link
)
1256 if (*igp
->i_field
== *realfield
&&
1257 strcmp(igp
->i_field
, realfield
) == 0)
1263 * Fake Sender for From_ lines if missing, e. g. with POP3.
1266 fakefrom(struct message
*mp
)
1270 if (((name
= skin(hfield1("return-path", mp
))) == NULL
||
1272 ((name
= skin(hfield1("from", mp
))) == NULL
||
1275 * XXX MAILER-DAEMON is what an old MBOX manual page says.
1276 * RFC 4155 however requires a RFC 5322 (2822) conforming
1277 * "addr-spec", but we simply can't provide that
1279 name
= "MAILER-DAEMON";
1289 for (cq
= cp
; *cq
&& *cq
!= '\n'; ++cq
)
1296 nexttoken(char const *cp
)
1304 while (*cp
!= '\0') {
1316 } else if (blankchar(*cp
) || *cp
== ',')
1325 * From username Fri Jan 2 20:13:51 2004
1330 unixtime(char const *fromline
)
1335 int i
, year
, month
, day
, hour
, minute
, second
;
1339 for (fp
= fromline
; *fp
&& *fp
!= '\n'; fp
++);
1341 if (fp
- fromline
< 7)
1346 if (strncmp(&fp
[4], month_names
[i
], 3) == 0)
1348 if (month_names
[++i
][0] == '\0')
1354 day
= strtol(&fp
[8], &xp
, 10);
1355 if (*xp
!= ' ' || xp
!= &fp
[10])
1357 hour
= strtol(&fp
[11], &xp
, 10);
1358 if (*xp
!= ':' || xp
!= &fp
[13])
1360 minute
= strtol(&fp
[14], &xp
, 10);
1361 if (*xp
!= ':' || xp
!= &fp
[16])
1363 second
= strtol(&fp
[17], &xp
, 10);
1364 if (*xp
!= ' ' || xp
!= &fp
[19])
1366 year
= strtol(&fp
[20], &xp
, 10);
1369 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) ==
1372 tzdiff
= t
- mktime(gmtime(&t
));
1373 tmptr
= localtime(&t
);
1374 if (tmptr
->tm_isdst
> 0)
1384 rfctime(char const *date
)
1386 char const *cp
= date
;
1389 int i
, year
, month
, day
, hour
, minute
, second
;
1391 if ((cp
= nexttoken(cp
)) == NULL
)
1393 if (alphachar(cp
[0]) && alphachar(cp
[1]) && alphachar(cp
[2]) &&
1395 if ((cp
= nexttoken(&cp
[4])) == NULL
)
1398 day
= strtol(cp
, &x
, 10); /* XXX strtol */
1399 if ((cp
= nexttoken(x
)) == NULL
)
1402 if (strncmp(cp
, month_names
[i
], 3) == 0)
1404 if (month_names
[++i
][0] == '\0')
1408 if ((cp
= nexttoken(&cp
[3])) == NULL
)
1412 * Where a two or three digit year occurs in a date, the year is to be
1413 * interpreted as follows: If a two digit year is encountered whose
1414 * value is between 00 and 49, the year is interpreted by adding 2000,
1415 * ending up with a value between 2000 and 2049. If a two digit year
1416 * is encountered with a value between 50 and 99, or any three digit
1417 * year is encountered, the year is interpreted by adding 1900.
1419 year
= strtol(cp
, &x
, 10); /* XXX strtol */
1421 if (i
== 2 && year
>= 0 && year
<= 49)
1423 else if (i
== 3 || (i
== 2 && year
>= 50 && year
<= 99))
1425 if ((cp
= nexttoken(x
)) == NULL
)
1427 hour
= strtol(cp
, &x
, 10); /* XXX strtol */
1431 minute
= strtol(cp
, &x
, 10);
1434 second
= strtol(cp
, &x
, 10);
1437 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) ==
1440 if ((cp
= nexttoken(x
)) != NULL
) {
1451 if (digitchar(cp
[0]) && digitchar(cp
[1]) && digitchar(cp
[2]) &&
1456 t
+= strtol(buf
, NULL
, 10) * sign
* 3600;/*XXX strtrol*/
1459 t
+= strtol(buf
, NULL
, 10) * sign
* 60; /* XXX strtol*/
1461 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1462 * TODO once again, Christos Zoulas and NetBSD Mail have done
1463 * TODO a really good job already, but using strptime(3), which
1464 * TODO is not portable. Nonetheless, WE must improve, not
1465 * TODO at last because we simply ignore obsolete timezones!!
1466 * TODO See RFC 5322, 4.3! */
1473 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1476 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
)
1480 if (second
< 0 || minute
< 0 || hour
< 0 || day
< 1)
1482 t
= second
+ minute
* 60 + hour
* 3600 + (day
- 1) * 86400;
1486 t
+= 86400 * (is_leapyear(year
) ? 29 : 28);
1506 t
+= (year
- 70) * 31536000 + ((year
- 69) / 4) * 86400 -
1507 ((year
- 1) / 100) * 86400 + ((year
+ 299) / 400) * 86400;
1512 substdate(struct message
*m
)
1517 * Determine the date to print in faked 'From ' lines. This is
1518 * traditionally the date the message was written to the mail
1519 * file. Try to determine this using RFC message header fields,
1520 * or fall back to current time.
1522 if ((cp
= hfield1("received", m
)) != NULL
) {
1523 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
1526 while (alnumchar(*cp
));
1529 m
->m_time
= rfctime(cp
);
1531 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
) {
1532 if ((cp
= hfield1("date", m
)) != NULL
)
1533 m
->m_time
= rfctime(cp
);
1535 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
)
1536 m
->m_time
= time_current
.tc_time
;
1540 check_from_and_sender(struct name
*fromfield
, struct name
*senderfield
)
1542 if (fromfield
&& fromfield
->n_flink
&& senderfield
== NULL
) {
1543 fprintf(stderr
, "A Sender: field is required with multiple "
1544 "addresses in From: field.\n");
1547 if (senderfield
&& senderfield
->n_flink
) {
1548 fprintf(stderr
, "The Sender: field may contain "
1549 "only one address.\n");
1556 getsender(struct message
*mp
)
1561 if ((cp
= hfield1("from", mp
)) == NULL
||
1562 (np
= lextract(cp
, GEXTRA
|GSKIN
)) == NULL
)
1564 return np
->n_flink
!= NULL
? skin(hfield1("sender", mp
)) : np
->n_name
;
1568 grab_headers(struct header
*hp
, enum gfield gflags
, int subjfirst
)
1570 /* TODO grab_headers: again, check counts etc. against RFC;
1571 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1576 comma
= (ok_blook(bsdcompat
) || ok_blook(bsdmsgs
)) ? 0 : GCOMMA
;
1579 hp
->h_to
= grab_names("To: ", hp
->h_to
, comma
, GTO
|GFULL
);
1581 if (subjfirst
&& (gflags
& GSUBJECT
))
1582 hp
->h_subject
= readstr_input("Subject: ", hp
->h_subject
);
1585 hp
->h_cc
= grab_names("Cc: ", hp
->h_cc
, comma
, GCC
|GFULL
);
1588 hp
->h_bcc
= grab_names("Bcc: ", hp
->h_bcc
, comma
, GBCC
|GFULL
);
1590 if (gflags
& GEXTRA
) {
1591 if (hp
->h_from
== NULL
)
1592 hp
->h_from
= lextract(myaddrs(hp
), GEXTRA
|GFULL
);
1593 hp
->h_from
= grab_names("From: ", hp
->h_from
, comma
,
1595 if (hp
->h_replyto
== NULL
)
1596 hp
->h_replyto
= lextract(ok_vlook(replyto
),
1598 hp
->h_replyto
= grab_names("Reply-To: ", hp
->h_replyto
, comma
,
1600 if (hp
->h_sender
== NULL
)
1601 hp
->h_sender
= extract(ok_vlook(sender
), GEXTRA
|GFULL
);
1602 hp
->h_sender
= grab_names("Sender: ", hp
->h_sender
, comma
,
1604 if (hp
->h_organization
== NULL
)
1605 hp
->h_organization
= ok_vlook(ORGANIZATION
);
1606 hp
->h_organization
= readstr_input("Organization: ",
1607 hp
->h_organization
);
1610 if (! subjfirst
&& (gflags
& GSUBJECT
))
1611 hp
->h_subject
= readstr_input("Subject: ", hp
->h_subject
);