2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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
47 # include <stringprep.h>
52 * Mail -- a mail program
54 * Routines for processing and detecting headlines.
57 static char * copyin(char *src
, char **space
);
58 static char * nextword(char *wp
, char *wbuf
);
59 static int gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
,
62 static struct addrguts
* idna_apply(struct addrguts
*agp
);
64 static int addrspec_check(int doskin
, struct addrguts
*agp
);
65 static int msgidnextc(const char **cp
, int *status
);
66 static int charcount(char *str
, int c
);
69 * See if the passed line buffer is a mail header.
70 * Return true if yes. POSIX.2 leaves the content
71 * following 'From ' unspecified, so don't care about
76 is_head(char *linebuf
, size_t linelen
)
82 if (*cp
++ != 'F' || *cp
++ != 'r' || *cp
++ != 'o' || *cp
++ != 'm' ||
89 * Split a headline into its useful components.
90 * Copy the line into dynamic string space, then set
91 * pointers into the copied line in the passed headline
92 * structure. Actually, it scans.
95 parse(char *line
, size_t linelen
, struct headline
*hl
, char *pbuf
)
106 word
= ac_alloc(linelen
+ 1);
108 * Skip over "From" first.
110 cp
= nextword(cp
, word
);
111 cp
= nextword(cp
, word
);
113 hl
->l_from
= copyin(word
, &sp
);
114 if (cp
!= NULL
&& cp
[0] == 't' && cp
[1] == 't' && cp
[2] == 'y') {
115 cp
= nextword(cp
, word
);
116 hl
->l_tty
= copyin(word
, &sp
);
119 hl
->l_date
= copyin(cp
, &sp
);
121 hl
->l_date
= catgets(catd
, CATSET
, 213, "<Unknown date>");
126 * Copy the string on the left into the string on the right
127 * and bump the right (reference) string pointer by the length.
128 * Thus, dynamically allocate space in the right string, copying
129 * the left string into it.
132 copyin(char *src
, char **space
)
138 while ((*cp
++ = *src
++) != '\0')
145 static int cmatch(char *, char *);
147 * Test to see if the passed string is a ctime(3) generated
148 * date string as documented in the manual. The template
149 * below is used as the criterion of correctness.
150 * Also, we check for a possible trailing time zone using
151 * the tmztype template.
155 * 'A' An upper case char
156 * 'a' A lower case char
159 * 'O' An optional digit or space
164 static char *tmztype
[] = {
165 "Aaa Aaa O0 00:00:00 0000",
166 "Aaa Aaa O0 00:00 0000",
167 "Aaa Aaa O0 00:00:00 AAA 0000",
168 "Aaa Aaa O0 00:00 AAA 0000",
170 * Sommer time, e.g. MET DST
172 "Aaa Aaa O0 00:00:00 AAA AAA 0000",
173 "Aaa Aaa O0 00:00 AAA AAA 0000",
175 * time zone offset, e.g.
176 * +0200 or +0200 MET or +0200 MET DST
178 "Aaa Aaa O0 00:00:00 +0000 0000",
179 "Aaa Aaa O0 00:00 +0000 0000",
180 "Aaa Aaa O0 00:00:00 +0000 AAA 0000",
181 "Aaa Aaa O0 00:00 +0000 AAA 0000",
182 "Aaa Aaa O0 00:00:00 +0000 AAA AAA 0000",
183 "Aaa Aaa O0 00:00 +0000 AAA AAA 0000",
185 * time zone offset without time zone specification (pine)
187 "Aaa Aaa O0 00:00:00 0000 +0000",
194 int ret
= 0, form
= 0;
196 while (tmztype
[form
]) {
197 if ( (ret
= cmatch(date
, tmztype
[form
])) == 1 )
206 * Match the given string (cp) against the given template (tp).
207 * Return 1 if they match, 0 if they don't
210 cmatch(char *cp
, char *tp
)
217 if (c
= *cp
++, !lowerchar(c
))
221 if (c
= *cp
++, !upperchar(c
))
229 if (c
= *cp
++, !digitchar(c
))
233 if (c
= *cp
, c
!= ' ' && !digitchar(c
))
242 if (*cp
!= '+' && *cp
!= '-')
258 * Collect a liberal (space, tab delimited) word into the word buffer
259 * passed. Also, return a pointer to the next word following that,
260 * or NULL if none follow.
263 nextword(char *wp
, char *wbuf
)
271 while ((c
= *wp
++) != '\0' && !blankchar(c
)) {
274 while ((c
= *wp
++) != '\0' && c
!= '"')
283 for (; blankchar(c
); c
= *wp
++)
291 extract_header(FILE *fp
, struct header
*hp
) /* XXX no header occur-cnt check */
293 char *linebuf
= NULL
;
296 char *colon
, *cp
, *value
;
298 struct header
*hq
= &nh
;
301 memset(hq
, 0, sizeof *hq
);
302 for (lc
= 0; readline(fp
, &linebuf
, &linesize
) > 0; lc
++);
304 while ((lc
= gethfield(fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
305 if ((value
= thisfield(linebuf
, "to")) != NULL
) {
307 hq
->h_to
= cat(hq
->h_to
, checkaddrs(
308 lextract(value
, GTO
|GFULL
)));
309 } else if ((value
= thisfield(linebuf
, "cc")) != NULL
) {
311 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(
312 lextract(value
, GCC
|GFULL
)));
313 } else if ((value
= thisfield(linebuf
, "bcc")) != NULL
) {
315 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(
316 lextract(value
, GBCC
|GFULL
)));
317 } else if ((value
= thisfield(linebuf
, "from")) != NULL
) {
319 hq
->h_from
= cat(hq
->h_from
, checkaddrs(
320 lextract(value
, GEXTRA
|GFULL
)));
321 } else if ((value
= thisfield(linebuf
, "reply-to")) != NULL
) {
323 hq
->h_replyto
= cat(hq
->h_replyto
, checkaddrs(
324 lextract(value
, GEXTRA
|GFULL
)));
325 } else if ((value
= thisfield(linebuf
, "sender")) != NULL
) {
327 hq
->h_sender
= cat(hq
->h_sender
, checkaddrs(
328 lextract(value
, GEXTRA
|GFULL
)));
329 } else if ((value
= thisfield(linebuf
,
330 "organization")) != NULL
) {
332 for (cp
= value
; blankchar(*cp
& 0377); cp
++);
333 hq
->h_organization
= hq
->h_organization
?
334 save2str(hq
->h_organization
, cp
) :
336 } else if ((value
= thisfield(linebuf
, "subject")) != NULL
||
337 (value
= thisfield(linebuf
, "subj")) != NULL
) {
339 for (cp
= value
; blankchar(*cp
& 0377); cp
++);
340 hq
->h_subject
= hq
->h_subject
?
341 save2str(hq
->h_subject
, cp
) :
344 fprintf(stderr
, catgets(catd
, CATSET
, 266,
345 "Ignoring header field \"%s\"\n"),
349 * In case the blank line after the header has been edited out.
350 * Otherwise, fetch the header separator.
353 if (linebuf
[0] != '\0') {
354 for (cp
= linebuf
; *(++cp
) != '\0'; );
355 fseek(fp
, (long)-(1 + cp
- linebuf
), SEEK_CUR
);
357 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
364 hp
->h_bcc
= hq
->h_bcc
;
365 hp
->h_from
= hq
->h_from
;
366 hp
->h_replyto
= hq
->h_replyto
;
367 hp
->h_sender
= hq
->h_sender
;
368 hp
->h_organization
= hq
->h_organization
;
369 hp
->h_subject
= hq
->h_subject
;
371 fprintf(stderr
, catgets(catd
, CATSET
, 267,
372 "Restoring deleted header lines\n"));
378 * Return the desired header line from the passed message
379 * pointer (or NULL if the desired header field is not available).
380 * If mult is zero, return the content of the first matching header
381 * field only, the content of all matching header fields else.
384 hfield_mult(char *field
, struct message
*mp
, int mult
)
387 char *linebuf
= NULL
;
391 char *colon
, *oldhfield
= NULL
;
393 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
395 if ((lc
= mp
->m_lines
- 1) < 0)
397 if ((mp
->m_flag
& MNOFROM
) == 0) {
398 if (readline(ibuf
, &linebuf
, &linesize
) < 0) {
405 if ((lc
= gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
))
411 if ((hfield
= thisfield(linebuf
, field
)) != NULL
) {
412 oldhfield
= save2str(hfield
, oldhfield
);
423 * Return the next header field found in the given message.
424 * Return >= 0 if something found, < 0 elsewise.
425 * "colon" is set to point to the colon in the header.
426 * Must deal with \ continuations & other such fraud.
429 gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
, char **colon
)
432 size_t line2size
= 0;
436 if (*linebuf
== NULL
)
437 *linebuf
= srealloc(*linebuf
, *linesize
= 1);
442 if ((c
= readline(f
, linebuf
, linesize
)) <= 0)
444 for (cp
= *linebuf
; fieldnamechar(*cp
& 0377); cp
++);
446 while (blankchar(*cp
& 0377))
448 if (*cp
!= ':' || cp
== *linebuf
)
451 * I guess we got a headline.
452 * Handle wraparounding
458 while (--cp
>= *linebuf
&& blankchar(*cp
& 0377));
462 if (cp
-8 >= *linebuf
&& cp
[-1] == '=' && cp
[-2] == '?')
464 ungetc(c
= getc(f
), f
);
467 if ((c
= readline(f
, &line2
, &line2size
)) < 0)
470 for (cp2
= line2
; blankchar(*cp2
& 0377); cp2
++);
472 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
474 if (cp
+ c
>= *linebuf
+ *linesize
- 2) {
475 size_t diff
= cp
- *linebuf
;
476 size_t colondiff
= *colon
- *linebuf
;
477 *linebuf
= srealloc(*linebuf
,
479 cp
= &(*linebuf
)[diff
];
480 *colon
= &(*linebuf
)[colondiff
];
496 * Check whether the passed line is a header line of
497 * the desired breed. Return the field body, or 0.
500 thisfield(const char *linebuf
, const char *field
)
502 while (lowerconv(*linebuf
&0377) == lowerconv(*field
&0377)) {
508 while (blankchar(*linebuf
&0377))
510 if (*linebuf
++ != ':')
512 while (blankchar(*linebuf
&0377))
514 return (char *)linebuf
;
518 * Get sender's name from this message. If the message has
519 * a bunch of arpanet stuff in it, we may have to skin the name
520 * before returning it.
523 nameof(struct message
*mp
, int reptype
)
527 cp
= skin(name1(mp
, reptype
));
528 if (reptype
!= 0 || charcount(cp
, '!') < 2)
530 cp2
= strrchr(cp
, '!');
532 while (cp2
> cp
&& *cp2
!= '!')
540 * Start of a "comment".
544 skip_comment(char const *cp
)
548 for (; nesting
> 0 && *cp
; cp
++) {
566 * Return the start of a route-addr (address in angle brackets),
570 routeaddr(const char *name
)
572 const char *np
, *rp
= NULL
;
574 for (np
= name
; *np
; np
++) {
577 np
= skip_comment(&np
[1]) - 1;
583 if (*np
== '\\' && np
[1])
598 * Check if a name's address part contains invalid characters.
601 is_addr_invalid(struct name
*np
, int putmsg
)
603 char cbuf
[sizeof "'\\U12340'"], *name
= np
->n_name
;
604 int f
= np
->n_flags
, ok8bit
= 1;
606 char const *fmt
= "'\\x%02X'", *cs
;
608 if ((f
& NAME_ADDRSPEC_INVALID
) == 0 || ! putmsg
||
609 (f
& NAME_ADDRSPEC_ERR_EMPTY
) != 0)
612 if (f
& NAME_ADDRSPEC_ERR_IDNA
)
613 cs
= tr(284, "Invalid domain name: \"%s\", character %s\n"),
616 else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
617 cs
= tr(142, "\"%s\" contains invalid %s sequence\n");
619 cs
= tr(143, "\"%s\" contains invalid character %s\n");
621 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
622 if (ok8bit
&& c
>= 040 && c
<= 0177)
623 snprintf(cbuf
, sizeof cbuf
, "'%c'", c
);
625 snprintf(cbuf
, sizeof cbuf
, fmt
, c
);
627 fprintf(stderr
, cs
, name
, cbuf
);
629 return ((f
& NAME_ADDRSPEC_INVALID
) != 0);
633 * Returned the skinned n_name, use the cached value if available.
634 * Note well that it may *not* create a duplicate.
637 skinned_name(struct name
const*np
) /* TODO !HAVE_ASSERTS legacy */
640 assert(np
->n_flags
& NAME_SKINNED
);
643 return ((np
->n_flags
& NAME_SKINNED
) ? np
->n_name
: skin(np
->n_name
));
648 * Skin an arpa net address according to the RFC 822 interpretation
659 (void)addrspec_with_guts(1, name
, &ag
);
660 name
= ag
.ag_skinned
;
661 if ((ag
.ag_n_flags
& NAME_NAME_SALLOC
) == 0)
662 name
= savestrbuf(name
, ag
.ag_slen
);
667 * Convert the domain part of a skinned address to IDNA.
668 * If an error occurs before Unicode information is available, revert the IDNA
669 * error to a normal CHAR one so that the error message doesn't talk Unicode.
672 static struct addrguts
*
673 idna_apply(struct addrguts
*agp
)
675 char *idna_utf8
, *idna_ascii
, *cs
;
678 int strict
= (value("idna-strict-checks") != NULL
);
680 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
682 idna_utf8
= ac_alloc(sz
+ 1);
683 memcpy(idna_utf8
, agp
->ag_skinned
+ agp
->ag_sdom_start
, sz
);
684 idna_utf8
[sz
] = '\0';
687 char *tmp
= stringprep_locale_to_utf8(idna_utf8
);
690 if (idna_utf8
== NULL
) {
691 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
|
692 NAME_ADDRSPEC_ERR_CHAR
;
697 if (idna_to_ascii_8z(idna_utf8
, &idna_ascii
,
698 strict
? IDNA_USE_STD3_ASCII_RULES
: 0)
700 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
|
701 NAME_ADDRSPEC_ERR_CHAR
;
710 * Due to normalization that may have occurred we must convert back to
711 * be able to check for top level domain issues
713 if (idna_to_unicode_8z4z(idna_ascii
, &idna_uni
, 0) != IDNA_SUCCESS
) {
714 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
|
715 NAME_ADDRSPEC_ERR_CHAR
;
719 i
= (size_t)tld_check_4z(idna_uni
, &sz
, NULL
);
721 if (i
!= TLD_SUCCESS
) {
722 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_IDNA
,
727 jset
: /* Replace the domain part of .ag_skinned with IDNA version */
728 sz
= strlen(idna_ascii
);
729 i
= agp
->ag_sdom_start
;
730 cs
= salloc(agp
->ag_slen
- i
+ sz
+ 1);
731 memcpy(cs
, agp
->ag_skinned
, i
);
732 memcpy(cs
+ i
, idna_ascii
, sz
);
736 agp
->ag_skinned
= cs
;
738 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
739 NAME_NAME_SALLOC
|NAME_SKINNED
|NAME_IDNA
, 0);
754 * Classify and check a (possibly skinned) header body according to RFC
755 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
756 * also a file or a pipe command, so check that first, then.
757 * Otherwise perform content checking and isolate the domain part (for IDNA).
760 addrspec_check(int skinned
, struct addrguts
*agp
)
762 char *addr
, *p
, in_quote
, in_domain
, hadat
;
763 union {char c
; unsigned char u
;} c
;
765 char use_idna
= (value("idna-disable") == NULL
);
768 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
769 addr
= agp
->ag_skinned
;
771 if (agp
->ag_iaddr_aend
- agp
->ag_iaddr_start
== 0) {
772 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
,
777 /* If the field is not a recipient, it cannot be a file or a pipe */
782 * Excerpt from nail.1:
784 * Recipient address specifications
785 * The rules are: Any name which starts with a `|' character specifies
786 * a pipe, the command string following the `|' is executed and
787 * the message is sent to its standard input; any other name which
788 * contains a `@' character is treated as a mail address; any other
789 * name which starts with a `+' character specifies a folder name; any
790 * other name which contains a `/' character but no `!' or `%'
791 * character before also specifies a folder name; what remains is
792 * treated as a mail address.
795 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
798 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
801 for (p
= addr
; (c
.c
= *p
); ++p
) {
802 if (c
.c
== '!' || c
.c
== '%')
805 jisfile
: agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
812 in_quote
= in_domain
= hadat
= 0;
814 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
816 in_quote
= ! in_quote
;
817 } else if (c
.u
< 040 || c
.u
>= 0177) {
819 if (in_domain
&& use_idna
) {
821 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
822 NAME_ADDRSPEC_ERR_IDNA
, c
.u
);
827 } else if (in_domain
== 2) {
828 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' ||
831 } else if (in_quote
&& in_domain
== 0) {
833 } else if (c
.c
== '\\' && *p
!= '\0') {
835 } else if (c
.c
== '@') {
837 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
838 NAME_ADDRSPEC_ERR_ATSEQ
, c
.u
);
841 agp
->ag_sdom_start
= (size_t)(p
- addr
);
842 in_domain
= (*p
== '[') ? 2 : 1;
844 } else if (c
.c
== '(' || c
.c
== ')' ||
845 c
.c
== '<' || c
.c
== '>' ||
846 c
.c
== ',' || c
.c
== ';' || c
.c
== ':' ||
847 c
.c
== '\\' || c
.c
== '[' || c
.c
== ']')
853 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
,
860 agp
= idna_apply(agp
);
864 return ((agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) != 0);
868 * Skin *name* and extract the *addr-spec* according to RFC 5322. TODO 822:5322
869 * Store the result in .ag_skinned and also fill in those .ag_ fields that have
870 * actually been seen.
871 * Return 0 if something good has been parsed, 1 if fun didn't exactly know how
872 * to deal with the input, or if that was plain invalid.
875 addrspec_with_guts(int doskin
, char const *name
, struct addrguts
*agp
)
878 char *cp2
, *bufend
, *nbuf
, c
;
879 char gotlt
, gotaddr
, lastsp
;
881 memset(agp
, 0, sizeof *agp
);
883 if ((agp
->ag_input
= name
) == NULL
|| /* XXX ever? */
884 (agp
->ag_ilen
= strlen(name
)) == 0) {
885 agp
->ag_skinned
= ""; /* NAME_SALLOC not set */
887 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
888 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
,
893 if (! doskin
|| ! anyof(name
, "(< ")) {
894 /*agp->ag_iaddr_start = 0;*/
895 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
896 agp
->ag_skinned
= (char*)name
; /* XXX (NAME_SALLOC not set) */
897 agp
->ag_slen
= agp
->ag_ilen
;
898 agp
->ag_n_flags
= NAME_SKINNED
;
899 return (addrspec_check(doskin
, agp
));
902 /* Something makes us think we have to perform the skin operation */
903 nbuf
= ac_alloc(agp
->ag_ilen
+ 1);
904 /*agp->ag_iaddr_start = 0;*/
906 gotlt
= gotaddr
= lastsp
= 0;
908 for (cp
= name
++; (c
= *cp
++) != '\0'; ) {
911 cp
= skip_comment(cp
);
916 * Start of a "quoted-string".
917 * Copy it in its entirety.
918 * XXX RFC: quotes are "semantically invisible"
919 * XXX But it was explicitly added (Changelog.Heirloom,
920 * XXX [9.23] released 11/15/00, "Do not remove quotes
921 * XXX when skinning names"? No more info..
924 while ((c
= *cp
) != '\0') { /* TODO improve */
932 else if ((c
= *cp
) != '\0') {
943 agp
->ag_iaddr_aend
= (size_t)(cp
- name
);
945 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
946 cp
+= 3, *cp2
++ = '@';
947 else if (cp
[0] == '@' && blankchar(cp
[1]))
948 cp
+= 2, *cp2
++ = '@';
953 agp
->ag_iaddr_start
= (size_t)(cp
- (name
- 1));
960 /* (addrspec_check() verifies these later!) */
961 agp
->ag_iaddr_aend
= (size_t)(cp
- name
);
963 while ((c
= *cp
) != '\0' && c
!= ',') {
966 cp
= skip_comment(cp
);
968 while ((c
= *cp
) != '\0') {
972 if (c
== '\\' && *cp
)
990 for (; blankchar(*cp
); ++cp
)
995 } else if (! gotaddr
) {
997 agp
->ag_iaddr_start
= (size_t)(cp
- name
);
1001 agp
->ag_slen
= (size_t)(cp2
- nbuf
);
1002 if (agp
->ag_iaddr_aend
== 0)
1003 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1005 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
1007 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
1008 return (addrspec_check(doskin
, agp
));
1012 * Fetch the real name from an internet mail address field.
1015 realname(char *name
)
1017 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
1020 int quoted
, good
, nogood
;
1024 for (cp
= (char*)name
; *cp
; cp
++) {
1029 * More than one comment in address, doesn't
1030 * make sense to display it without context.
1031 * Return the entire field,
1033 return mime_fromaddr(name
);
1035 cp
= skip_comment(cp
);
1038 cend
= cstart
= NULL
;
1044 if (*cp
== '\\' && cp
[1])
1056 * More than one address. Just use the first one.
1061 brk
: if (cstart
== NULL
) {
1064 * If name contains only a route-addr, the
1065 * surrounding angle brackets don't serve any
1066 * useful purpose when displaying, so they
1069 return prstr(skin(name
));
1070 return mime_fromaddr(name
);
1072 rp
= rname
= ac_alloc(cend
- cstart
+ 1);
1074 * Strip quotes. Note that quotes that appear within a MIME-
1075 * encoded word are not stripped. The idea is to strip only
1076 * syntactical relevant things (but this is not necessarily
1077 * the most sensible way in practice).
1080 for (cp
= cstart
; cp
< cend
; cp
++) {
1081 if (*cp
== '(' && !quoted
) {
1082 cq
= skip_comment(++cp
);
1086 if (*cp
== '\\' && &cp
[1] < cq
)
1090 } else if (*cp
== '\\' && &cp
[1] < cend
)
1092 else if (*cp
== '"') {
1101 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1103 rname
= savestr(out
.s
);
1105 while (blankchar(*rname
& 0377))
1107 for (rp
= rname
; *rp
; rp
++);
1108 while (--rp
>= rname
&& blankchar(*rp
& 0377))
1111 return mime_fromaddr(name
);
1113 * mime_fromhdr() has converted all nonprintable characters to
1114 * question marks now. These and blanks are considered uninteresting;
1115 * if the displayed part of the real name contains more than 25% of
1116 * them, it is probably better to display the plain email address
1121 for (rp
= rname
; *rp
&& rp
< &rname
[20]; rp
++)
1122 if (*rp
== '?' || blankchar(*rp
& 0377))
1126 if (good
*3 < nogood
)
1127 return prstr(skin(name
));
1132 * Fetch the sender's name from the passed message.
1134 * 0 -- get sender's name for display purposes
1135 * 1 -- get sender's name for reply
1136 * 2 -- get sender's name for Reply
1139 name1(struct message
*mp
, int reptype
)
1143 char *linebuf
= NULL
;
1144 size_t linesize
= 0;
1149 if ((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
1151 if (reptype
== 0 && (cp
= hfield1("sender", mp
)) != NULL
&&
1154 namebuf
= smalloc(namesize
= 1);
1156 if (mp
->m_flag
& MNOFROM
)
1158 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1160 if (readline(ibuf
, &linebuf
, &linesize
) < 0)
1163 if (namesize
<= linesize
)
1164 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1165 for (cp
= linebuf
; *cp
&& *cp
!= ' '; cp
++)
1167 for (; blankchar(*cp
& 0377); cp
++);
1168 for (cp2
= &namebuf
[strlen(namebuf
)];
1169 *cp
&& !blankchar(*cp
& 0377) && cp2
< namebuf
+ namesize
- 1;)
1172 if (readline(ibuf
, &linebuf
, &linesize
) < 0)
1174 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
1176 if (strncmp(cp
, "From", 4) != 0)
1178 if (namesize
<= linesize
)
1179 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1180 while ((cp
= strchr(cp
, 'r')) != NULL
) {
1181 if (strncmp(cp
, "remote", 6) == 0) {
1182 if ((cp
= strchr(cp
, 'f')) == NULL
)
1184 if (strncmp(cp
, "from", 4) != 0)
1186 if ((cp
= strchr(cp
, ' ')) == NULL
)
1190 strncpy(namebuf
, cp
, namesize
);
1193 cp2
=strrchr(namebuf
, '!')+1;
1194 strncpy(cp2
, cp
, (namebuf
+namesize
)-cp2
);
1196 namebuf
[namesize
-2]='\0';
1197 strcat(namebuf
, "!");
1203 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
1205 cp
= savestr(namebuf
);
1213 msgidnextc(const char **cp
, int *status
)
1233 *cp
= skip_comment(&(*cp
)[1]);
1248 c
= *(*cp
)++ & 0377;
1249 return *status
& 02 ? lowerconv(c
) : c
;
1255 msgidcmp(const char *s1
, const char *s2
)
1261 c1
= msgidnextc(&s1
, &q1
);
1262 c2
= msgidnextc(&s2
, &q2
);
1270 * Count the occurances of c in str
1273 charcount(char *str
, int c
)
1278 for (i
= 0, cp
= str
; *cp
; cp
++)
1285 * See if the given header field is supposed to be ignored.
1288 is_ign(char *field
, size_t fieldlen
, struct ignoretab ignore
[2])
1295 if (ignore
== allignore
)
1298 * Lower-case the string, so that "Status" and "status"
1299 * will hash to the same place.
1301 realfld
= ac_alloc(fieldlen
+ 1);
1302 i_strcpy(realfld
, field
, fieldlen
+ 1);
1303 if (ignore
[1].i_count
> 0)
1304 ret
= !member(realfld
, ignore
+ 1);
1306 ret
= member(realfld
, ignore
);
1312 member(char *realfield
, struct ignoretab
*table
)
1316 for (igp
= table
->i_head
[hash(realfield
)]; igp
!= 0; igp
= igp
->i_link
)
1317 if (*igp
->i_field
== *realfield
&&
1318 strcmp(igp
->i_field
, realfield
) == 0)
1324 * Fake Sender for From_ lines if missing, e. g. with POP3.
1327 fakefrom(struct message
*mp
)
1331 if (((name
= skin(hfield1("return-path", mp
))) == NULL
||
1333 ((name
= skin(hfield1("from", mp
))) == NULL
||
1345 for (cq
= cp
; *cq
&& *cq
!= '\n'; cq
++);
1351 nexttoken(char const *cp
)
1359 while (*cp
!= '\0') {
1371 } else if (blankchar(*cp
& 0377) || *cp
== ',')
1380 * From username Fri Jan 2 20:13:51 2004
1385 unixtime(char const *from
)
1390 int i
, year
, month
, day
, hour
, minute
, second
;
1394 for (fp
= from
; *fp
&& *fp
!= '\n'; fp
++);
1400 for (i
= 0; month_names
[i
]; i
++)
1401 if (strncmp(&fp
[4], month_names
[i
], 3) == 0)
1403 if (month_names
[i
] == 0)
1408 day
= strtol(&fp
[8], &xp
, 10);
1409 if (*xp
!= ' ' || xp
!= &fp
[10])
1411 hour
= strtol(&fp
[11], &xp
, 10);
1412 if (*xp
!= ':' || xp
!= &fp
[13])
1414 minute
= strtol(&fp
[14], &xp
, 10);
1415 if (*xp
!= ':' || xp
!= &fp
[16])
1417 second
= strtol(&fp
[17], &xp
, 10);
1418 if (*xp
!= ' ' || xp
!= &fp
[19])
1420 year
= strtol(&fp
[20], &xp
, 10);
1423 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) ==
1426 tzdiff
= t
- mktime(gmtime(&t
));
1427 tmptr
= localtime(&t
);
1428 if (tmptr
->tm_isdst
> 0)
1438 rfctime(char const *date
)
1440 char const *cp
= date
;
1443 int i
, year
, month
, day
, hour
, minute
, second
;
1445 if ((cp
= nexttoken(cp
)) == NULL
)
1447 if (alphachar(cp
[0] & 0377) && alphachar(cp
[1] & 0377) &&
1448 alphachar(cp
[2] & 0377) && cp
[3] == ',') {
1449 if ((cp
= nexttoken(&cp
[4])) == NULL
)
1452 day
= strtol(cp
, &x
, 10);
1453 if ((cp
= nexttoken(x
)) == NULL
)
1455 for (i
= 0; month_names
[i
]; i
++) {
1456 if (strncmp(cp
, month_names
[i
], 3) == 0)
1459 if (month_names
[i
] == NULL
)
1462 if ((cp
= nexttoken(&cp
[3])) == NULL
)
1464 year
= strtol(cp
, &x
, 10);
1465 if ((cp
= nexttoken(x
)) == NULL
)
1467 hour
= strtol(cp
, &x
, 10);
1471 minute
= strtol(cp
, &x
, 10);
1474 second
= strtol(cp
, &x
, 10);
1477 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) ==
1480 if ((cp
= nexttoken(x
)) != NULL
) {
1491 if (digitchar(cp
[0] & 0377) && digitchar(cp
[1] & 0377) &&
1492 digitchar(cp
[2] & 0377) &&
1493 digitchar(cp
[3] & 0377)) {
1497 t
+= strtol(buf
, NULL
, 10) * sign
* 3600;
1500 t
+= strtol(buf
, NULL
, 10) * sign
* 60;
1508 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1511 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
)
1515 if (second
< 0 || minute
< 0 || hour
< 0 || day
< 1)
1517 t
= second
+ minute
* 60 + hour
* 3600 + (day
- 1) * 86400;
1520 else if (year
< 1900)
1525 t
+= 86400 * (leapyear(year
) ? 29 : 28);
1545 t
+= (year
- 70) * 31536000 + ((year
- 69) / 4) * 86400 -
1546 ((year
- 1) / 100) * 86400 + ((year
+ 299) / 400) * 86400;
1551 substdate(struct message
*m
)
1557 * Determine the date to print in faked 'From ' lines. This is
1558 * traditionally the date the message was written to the mail
1559 * file. Try to determine this using RFC message header fields,
1560 * or fall back to current time.
1563 if ((cp
= hfield1("received", m
)) != NULL
) {
1564 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
1567 while (alnumchar(*cp
& 0377));
1570 m
->m_time
= rfctime(cp
);
1572 if (m
->m_time
== 0 || m
->m_time
> now
)
1573 if ((cp
= hfield1("date", m
)) != NULL
)
1574 m
->m_time
= rfctime(cp
);
1575 if (m
->m_time
== 0 || m
->m_time
> now
)
1580 check_from_and_sender(struct name
*fromfield
, struct name
*senderfield
)
1582 if (fromfield
&& fromfield
->n_flink
&& senderfield
== NULL
) {
1583 fprintf(stderr
, "A Sender: field is required with multiple "
1584 "addresses in From: field.\n");
1587 if (senderfield
&& senderfield
->n_flink
) {
1588 fprintf(stderr
, "The Sender: field may contain "
1589 "only one address.\n");
1596 getsender(struct message
*mp
)
1601 if ((cp
= hfield1("from", mp
)) == NULL
||
1602 (np
= lextract(cp
, GEXTRA
|GSKIN
)) == NULL
)
1604 return np
->n_flink
!= NULL
? skin(hfield1("sender", mp
)) : np
->n_name
;