cmd1.c: expand() may fail
[s-mailx.git] / head.c
blob4fff43b1273f1fb3750c07d6c1e392fda8ffa651
1 /*
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.
6 */
7 /*
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
13 * are met:
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
37 * SUCH DAMAGE.
40 #include "rcv.h"
41 #include "extern.h"
43 #include <time.h>
45 #ifdef USE_IDNA
46 # include <idna.h>
47 # include <stringprep.h>
48 # include <tld.h>
49 #endif
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,
60 char **colon);
61 #ifdef USE_IDNA
62 static struct addrguts * idna_apply(struct addrguts *agp);
63 #endif
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
72 * it.
74 /*ARGSUSED 2*/
75 int
76 is_head(char *linebuf, size_t linelen)
78 char *cp;
79 (void)linelen;
81 cp = linebuf;
82 if (*cp++ != 'F' || *cp++ != 'r' || *cp++ != 'o' || *cp++ != 'm' ||
83 *cp++ != ' ')
84 return (0);
85 return(1);
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.
94 void
95 parse(char *line, size_t linelen, struct headline *hl, char *pbuf)
97 char *cp;
98 char *sp;
99 char *word;
101 hl->l_from = NULL;
102 hl->l_tty = NULL;
103 hl->l_date = NULL;
104 cp = line;
105 sp = pbuf;
106 word = ac_alloc(linelen + 1);
108 * Skip over "From" first.
110 cp = nextword(cp, word);
111 cp = nextword(cp, word);
112 if (*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);
118 if (cp != NULL)
119 hl->l_date = copyin(cp, &sp);
120 else
121 hl->l_date = catgets(catd, CATSET, 213, "<Unknown date>");
122 ac_free(word);
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.
131 static char *
132 copyin(char *src, char **space)
134 char *cp;
135 char *top;
137 top = cp = *space;
138 while ((*cp++ = *src++) != '\0')
140 *space = cp;
141 return (top);
144 #ifdef notdef
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
157 * ' ' A space
158 * '0' A digit
159 * 'O' An optional digit or space
160 * ':' A colon
161 * '+' A sign
162 * 'N' A new line
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",
188 NULL,
191 static int
192 is_date(char *date)
194 int ret = 0, form = 0;
196 while (tmztype[form]) {
197 if ( (ret = cmatch(date, tmztype[form])) == 1 )
198 break;
199 form++;
202 return ret;
206 * Match the given string (cp) against the given template (tp).
207 * Return 1 if they match, 0 if they don't
209 static int
210 cmatch(char *cp, char *tp)
212 int c;
214 while (*cp && *tp)
215 switch (*tp++) {
216 case 'a':
217 if (c = *cp++, !lowerchar(c))
218 return 0;
219 break;
220 case 'A':
221 if (c = *cp++, !upperchar(c))
222 return 0;
223 break;
224 case ' ':
225 if (*cp++ != ' ')
226 return 0;
227 break;
228 case '0':
229 if (c = *cp++, !digitchar(c))
230 return 0;
231 break;
232 case 'O':
233 if (c = *cp, c != ' ' && !digitchar(c))
234 return 0;
235 cp++;
236 break;
237 case ':':
238 if (*cp++ != ':')
239 return 0;
240 break;
241 case '+':
242 if (*cp != '+' && *cp != '-')
243 return 0;
244 cp++;
245 break;
246 case 'N':
247 if (*cp++ != '\n')
248 return 0;
249 break;
251 if (*cp || *tp)
252 return 0;
253 return (1);
255 #endif /* notdef */
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.
262 static char *
263 nextword(char *wp, char *wbuf)
265 int c;
267 if (wp == NULL) {
268 *wbuf = 0;
269 return (NULL);
271 while ((c = *wp++) != '\0' && !blankchar(c)) {
272 *wbuf++ = c;
273 if (c == '"') {
274 while ((c = *wp++) != '\0' && c != '"')
275 *wbuf++ = c;
276 if (c == '"')
277 *wbuf++ = c;
278 else
279 wp--;
282 *wbuf = '\0';
283 for (; blankchar(c); c = *wp++)
285 if (c == 0)
286 return (NULL);
287 return (wp - 1);
290 void
291 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
293 char *linebuf = NULL;
294 size_t linesize = 0;
295 int seenfields = 0;
296 char *colon, *cp, *value;
297 struct header nh;
298 struct header *hq = &nh;
299 int lc, c;
301 memset(hq, 0, sizeof *hq);
302 for (lc = 0; readline(fp, &linebuf, &linesize) > 0; lc++);
303 rewind(fp);
304 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
305 if ((value = thisfield(linebuf, "to")) != NULL) {
306 seenfields++;
307 hq->h_to = cat(hq->h_to, checkaddrs(
308 lextract(value, GTO|GFULL)));
309 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
310 seenfields++;
311 hq->h_cc = cat(hq->h_cc, checkaddrs(
312 lextract(value, GCC|GFULL)));
313 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
314 seenfields++;
315 hq->h_bcc = cat(hq->h_bcc, checkaddrs(
316 lextract(value, GBCC|GFULL)));
317 } else if ((value = thisfield(linebuf, "from")) != NULL) {
318 seenfields++;
319 hq->h_from = cat(hq->h_from, checkaddrs(
320 lextract(value, GEXTRA|GFULL)));
321 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
322 seenfields++;
323 hq->h_replyto = cat(hq->h_replyto, checkaddrs(
324 lextract(value, GEXTRA|GFULL)));
325 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
326 seenfields++;
327 hq->h_sender = cat(hq->h_sender, checkaddrs(
328 lextract(value, GEXTRA|GFULL)));
329 } else if ((value = thisfield(linebuf,
330 "organization")) != NULL) {
331 seenfields++;
332 for (cp = value; blankchar(*cp & 0377); cp++);
333 hq->h_organization = hq->h_organization ?
334 save2str(hq->h_organization, cp) :
335 savestr(cp);
336 } else if ((value = thisfield(linebuf, "subject")) != NULL ||
337 (value = thisfield(linebuf, "subj")) != NULL) {
338 seenfields++;
339 for (cp = value; blankchar(*cp & 0377); cp++);
340 hq->h_subject = hq->h_subject ?
341 save2str(hq->h_subject, cp) :
342 savestr(cp);
343 } else
344 fprintf(stderr, catgets(catd, CATSET, 266,
345 "Ignoring header field \"%s\"\n"),
346 linebuf);
349 * In case the blank line after the header has been edited out.
350 * Otherwise, fetch the header separator.
352 if (linebuf) {
353 if (linebuf[0] != '\0') {
354 for (cp = linebuf; *(++cp) != '\0'; );
355 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
356 } else {
357 if ((c = getc(fp)) != '\n' && c != EOF)
358 ungetc(c, fp);
361 if (seenfields) {
362 hp->h_to = hq->h_to;
363 hp->h_cc = hq->h_cc;
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;
370 } else
371 fprintf(stderr, catgets(catd, CATSET, 267,
372 "Restoring deleted header lines\n"));
373 if (linebuf)
374 free(linebuf);
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.
383 char *
384 hfield_mult(char *field, struct message *mp, int mult)
386 FILE *ibuf;
387 char *linebuf = NULL;
388 size_t linesize = 0;
389 int lc;
390 char *hfield;
391 char *colon, *oldhfield = NULL;
393 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
394 return NULL;
395 if ((lc = mp->m_lines - 1) < 0)
396 return NULL;
397 if ((mp->m_flag & MNOFROM) == 0) {
398 if (readline(ibuf, &linebuf, &linesize) < 0) {
399 if (linebuf)
400 free(linebuf);
401 return NULL;
404 while (lc > 0) {
405 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon))
406 < 0) {
407 if (linebuf)
408 free(linebuf);
409 return oldhfield;
411 if ((hfield = thisfield(linebuf, field)) != NULL) {
412 oldhfield = save2str(hfield, oldhfield);
413 if (mult == 0)
414 break;
417 if (linebuf)
418 free(linebuf);
419 return 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.
428 static int
429 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
431 char *line2 = NULL;
432 size_t line2size = 0;
433 char *cp, *cp2;
434 int c, isenc;
436 if (*linebuf == NULL)
437 *linebuf = srealloc(*linebuf, *linesize = 1);
438 **linebuf = '\0';
439 for (;;) {
440 if (--rem < 0)
441 return -1;
442 if ((c = readline(f, linebuf, linesize)) <= 0)
443 return -1;
444 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
445 if (cp > *linebuf)
446 while (blankchar(*cp & 0377))
447 cp++;
448 if (*cp != ':' || cp == *linebuf)
449 continue;
451 * I guess we got a headline.
452 * Handle wraparounding
454 *colon = cp;
455 cp = *linebuf + c;
456 for (;;) {
457 isenc = 0;
458 while (--cp >= *linebuf && blankchar(*cp & 0377));
459 cp++;
460 if (rem <= 0)
461 break;
462 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
463 isenc |= 1;
464 ungetc(c = getc(f), f);
465 if (!blankchar(c))
466 break;
467 if ((c = readline(f, &line2, &line2size)) < 0)
468 break;
469 rem--;
470 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
471 c -= cp2 - line2;
472 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
473 isenc |= 2;
474 if (cp + c >= *linebuf + *linesize - 2) {
475 size_t diff = cp - *linebuf;
476 size_t colondiff = *colon - *linebuf;
477 *linebuf = srealloc(*linebuf,
478 *linesize += c + 2);
479 cp = &(*linebuf)[diff];
480 *colon = &(*linebuf)[colondiff];
482 if (isenc != 3)
483 *cp++ = ' ';
484 memcpy(cp, cp2, c);
485 cp += c;
487 *cp = 0;
488 if (line2)
489 free(line2);
490 return rem;
492 /* NOTREACHED */
496 * Check whether the passed line is a header line of
497 * the desired breed. Return the field body, or 0.
499 char *
500 thisfield(const char *linebuf, const char *field)
502 while (lowerconv(*linebuf&0377) == lowerconv(*field&0377)) {
503 linebuf++;
504 field++;
506 if (*field != '\0')
507 return NULL;
508 while (blankchar(*linebuf&0377))
509 linebuf++;
510 if (*linebuf++ != ':')
511 return NULL;
512 while (blankchar(*linebuf&0377))
513 linebuf++;
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.
522 char *
523 nameof(struct message *mp, int reptype)
525 char *cp, *cp2;
527 cp = skin(name1(mp, reptype));
528 if (reptype != 0 || charcount(cp, '!') < 2)
529 return(cp);
530 cp2 = strrchr(cp, '!');
531 cp2--;
532 while (cp2 > cp && *cp2 != '!')
533 cp2--;
534 if (*cp2 == '!')
535 return(cp2 + 1);
536 return(cp);
540 * Start of a "comment".
541 * Ignore it.
543 char const *
544 skip_comment(char const *cp)
546 int nesting = 1;
548 for (; nesting > 0 && *cp; cp++) {
549 switch (*cp) {
550 case '\\':
551 if (cp[1])
552 cp++;
553 break;
554 case '(':
555 nesting++;
556 break;
557 case ')':
558 nesting--;
559 break;
562 return (cp);
566 * Return the start of a route-addr (address in angle brackets),
567 * if present.
569 char *
570 routeaddr(const char *name)
572 const char *np, *rp = NULL;
574 for (np = name; *np; np++) {
575 switch (*np) {
576 case '(':
577 np = skip_comment(&np[1]) - 1;
578 break;
579 case '"':
580 while (*np) {
581 if (*++np == '"')
582 break;
583 if (*np == '\\' && np[1])
584 np++;
586 break;
587 case '<':
588 rp = np;
589 break;
590 case '>':
591 return (char *)rp;
594 return NULL;
598 * Check if a name's address part contains invalid characters.
600 int
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;
605 unsigned int c;
606 char const *fmt = "'\\x%02X'", *cs;
608 if ((f & NAME_ADDRSPEC_INVALID) == 0 || ! putmsg ||
609 (f & NAME_ADDRSPEC_ERR_EMPTY) != 0)
610 goto jleave;
612 if (f & NAME_ADDRSPEC_ERR_IDNA)
613 cs = tr(284, "Invalid domain name: \"%s\", character %s\n"),
614 fmt = "'\\U%04X'",
615 ok8bit = 0;
616 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
617 cs = tr(142, "\"%s\" contains invalid %s sequence\n");
618 else
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);
624 else
625 snprintf(cbuf, sizeof cbuf, fmt, c);
627 fprintf(stderr, cs, name, cbuf);
628 jleave:
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.
636 char *
637 skinned_name(struct name const*np) /* TODO !HAVE_ASSERTS legacy */
639 #ifdef HAVE_ASSERTS
640 assert(np->n_flags & NAME_SKINNED);
641 return (np->n_name);
642 #else
643 return ((np->n_flags & NAME_SKINNED) ? np->n_name : skin(np->n_name));
644 #endif
648 * Skin an arpa net address according to the RFC 822 interpretation
649 * of "host-phrase."
651 char *
652 skin(char *name)
654 struct addrguts ag;
656 if (name == NULL)
657 return (NULL);
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);
663 return (name);
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.
671 #ifdef USE_IDNA
672 static struct addrguts *
673 idna_apply(struct addrguts *agp)
675 char *idna_utf8, *idna_ascii, *cs;
676 uint32_t *idna_uni;
677 size_t sz, i;
678 int strict = (value("idna-strict-checks") != NULL);
680 sz = agp->ag_slen - agp->ag_sdom_start;
681 assert(sz > 0);
682 idna_utf8 = ac_alloc(sz + 1);
683 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
684 idna_utf8[sz] = '\0';
686 if (! utf8) {
687 char *tmp = stringprep_locale_to_utf8(idna_utf8);
688 ac_free(idna_utf8);
689 idna_utf8 = tmp;
690 if (idna_utf8 == NULL) {
691 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA |
692 NAME_ADDRSPEC_ERR_CHAR;
693 goto jleave;
697 if (idna_to_ascii_8z(idna_utf8, &idna_ascii,
698 strict ? IDNA_USE_STD3_ASCII_RULES : 0)
699 != IDNA_SUCCESS) {
700 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA |
701 NAME_ADDRSPEC_ERR_CHAR;
702 goto jleave1;
705 idna_uni = NULL;
706 if (! strict)
707 goto jset;
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;
716 goto jleave2;
719 i = (size_t)tld_check_4z(idna_uni, &sz, NULL);
720 free(idna_uni);
721 if (i != TLD_SUCCESS) {
722 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
723 idna_uni[sz]);
724 goto jleave2;
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);
733 i += sz;
734 cs[i] = '\0';
736 agp->ag_skinned = cs;
737 agp->ag_slen = i;
738 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
739 NAME_NAME_SALLOC|NAME_SKINNED|NAME_IDNA, 0);
741 jleave2:
742 free(idna_ascii);
743 jleave1:
744 if (utf8)
745 ac_free(idna_utf8);
746 else
747 free(idna_utf8);
748 jleave:
749 return (agp);
751 #endif
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).
759 static int
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;
764 #ifdef USE_IDNA
765 char use_idna = (value("idna-disable") == NULL);
766 #endif
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,
774 goto jleave;
777 /* If the field is not a recipient, it cannot be a file or a pipe */
778 if (! skinned)
779 goto jaddr_check;
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.
794 if (*addr == '|') {
795 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
796 goto jleave;
798 if (memchr(addr, '@', agp->ag_slen) == NULL) {
799 if (*addr == '+')
800 goto jisfile;
801 for (p = addr; (c.c = *p); ++p) {
802 if (c.c == '!' || c.c == '%')
803 break;
804 if (c.c == '/') {
805 jisfile: agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
806 goto jleave;
811 jaddr_check:
812 in_quote = in_domain = hadat = 0;
814 for (p = addr; (c.c = *p++) != '\0';) {
815 if (c.c == '"') {
816 in_quote = ! in_quote;
817 } else if (c.u < 040 || c.u >= 0177) {
818 #ifdef USE_IDNA
819 if (in_domain && use_idna) {
820 if (use_idna == 1)
821 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
822 NAME_ADDRSPEC_ERR_IDNA, c.u);
823 use_idna = 2;
824 } else
825 #endif
826 break;
827 } else if (in_domain == 2) {
828 if ((c.c == ']' && *p != '\0') || c.c == '\\' ||
829 whitechar(c.c))
830 break;
831 } else if (in_quote && in_domain == 0) {
832 /*EMPTY*/;
833 } else if (c.c == '\\' && *p != '\0') {
834 ++p;
835 } else if (c.c == '@') {
836 if (hadat++) {
837 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
838 NAME_ADDRSPEC_ERR_ATSEQ, c.u);
839 goto jleave;
841 agp->ag_sdom_start = (size_t)(p - addr);
842 in_domain = (*p == '[') ? 2 : 1;
843 continue;
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 == ']')
848 break;
849 hadat = 0;
852 if (c.c != '\0') {
853 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR,
854 c.u);
855 goto jleave;
858 #ifdef USE_IDNA
859 if (use_idna == 2)
860 agp = idna_apply(agp);
861 #endif
863 jleave:
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)
877 char const *cp;
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 */
886 agp->ag_slen = 0;
887 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
888 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY,
890 return (1);
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;*/
905 cp2 = bufend = nbuf;
906 gotlt = gotaddr = lastsp = 0;
908 for (cp = name++; (c = *cp++) != '\0'; ) {
909 switch (c) {
910 case '(':
911 cp = skip_comment(cp);
912 lastsp = 0;
913 break;
914 case '"':
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..
923 *cp2++ = c;
924 while ((c = *cp) != '\0') { /* TODO improve */
925 cp++;
926 if (c == '"') {
927 *cp2++ = c;
928 break;
930 if (c != '\\')
931 *cp2++ = c;
932 else if ((c = *cp) != '\0') {
933 *cp2++ = c;
934 cp++;
937 lastsp = 0;
938 break;
939 case ' ':
940 case '\t':
941 if (gotaddr == 1) {
942 gotaddr = 2;
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++ = '@';
949 else
950 lastsp = 1;
951 break;
952 case '<':
953 agp->ag_iaddr_start = (size_t)(cp - (name - 1));
954 cp2 = bufend;
955 gotlt = gotaddr = 1;
956 lastsp = 0;
957 break;
958 case '>':
959 if (gotlt) {
960 /* (addrspec_check() verifies these later!) */
961 agp->ag_iaddr_aend = (size_t)(cp - name);
962 gotlt = 0;
963 while ((c = *cp) != '\0' && c != ',') {
964 cp++;
965 if (c == '(')
966 cp = skip_comment(cp);
967 else if (c == '"')
968 while ((c = *cp) != '\0') {
969 cp++;
970 if (c == '"')
971 break;
972 if (c == '\\' && *cp)
973 cp++;
976 lastsp = 0;
977 break;
979 /* FALLTRHOUGH */
980 default:
981 if (lastsp) {
982 lastsp = 0;
983 if (gotaddr)
984 *cp2++ = ' ';
986 *cp2++ = c;
987 if (c == ',') {
988 if (! gotlt) {
989 *cp2++ = ' ';
990 for (; blankchar(*cp); ++cp)
992 lastsp = 0;
993 bufend = cp2;
995 } else if (! gotaddr) {
996 gotaddr = 1;
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);
1006 ac_free(nbuf);
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.
1014 char *
1015 realname(char *name)
1017 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1018 char *rname, *rp;
1019 struct str in, out;
1020 int quoted, good, nogood;
1022 if (name == NULL)
1023 return NULL;
1024 for (cp = (char*)name; *cp; cp++) {
1025 switch (*cp) {
1026 case '(':
1027 if (cstart)
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);
1034 cstart = cp++;
1035 cp = skip_comment(cp);
1036 cend = cp--;
1037 if (cend <= cstart)
1038 cend = cstart = NULL;
1039 break;
1040 case '"':
1041 while (*cp) {
1042 if (*++cp == '"')
1043 break;
1044 if (*cp == '\\' && cp[1])
1045 cp++;
1047 break;
1048 case '<':
1049 if (cp > name) {
1050 cstart = name;
1051 cend = cp;
1053 break;
1054 case ',':
1056 * More than one address. Just use the first one.
1058 goto brk;
1061 brk: if (cstart == NULL) {
1062 if (*name == '<')
1064 * If name contains only a route-addr, the
1065 * surrounding angle brackets don't serve any
1066 * useful purpose when displaying, so they
1067 * are removed.
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).
1079 quoted = 0;
1080 for (cp = cstart; cp < cend; cp++) {
1081 if (*cp == '(' && !quoted) {
1082 cq = skip_comment(++cp);
1083 if (--cq > cend)
1084 cq = cend;
1085 while (cp < cq) {
1086 if (*cp == '\\' && &cp[1] < cq)
1087 cp++;
1088 *rp++ = *cp++;
1090 } else if (*cp == '\\' && &cp[1] < cend)
1091 *rp++ = *++cp;
1092 else if (*cp == '"') {
1093 quoted = !quoted;
1094 continue;
1095 } else
1096 *rp++ = *cp;
1098 *rp = '\0';
1099 in.s = rname;
1100 in.l = rp - rname;
1101 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1102 ac_free(rname);
1103 rname = savestr(out.s);
1104 free(out.s);
1105 while (blankchar(*rname & 0377))
1106 rname++;
1107 for (rp = rname; *rp; rp++);
1108 while (--rp >= rname && blankchar(*rp & 0377))
1109 *rp = '\0';
1110 if (rp == rname)
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
1117 * instead.
1119 good = 0;
1120 nogood = 0;
1121 for (rp = rname; *rp && rp < &rname[20]; rp++)
1122 if (*rp == '?' || blankchar(*rp & 0377))
1123 nogood++;
1124 else
1125 good++;
1126 if (good*3 < nogood)
1127 return prstr(skin(name));
1128 return rname;
1132 * Fetch the sender's name from the passed message.
1133 * Reptype can be
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
1138 char *
1139 name1(struct message *mp, int reptype)
1141 char *namebuf;
1142 size_t namesize;
1143 char *linebuf = NULL;
1144 size_t linesize = 0;
1145 char *cp, *cp2;
1146 FILE *ibuf;
1147 int first = 1;
1149 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1150 return cp;
1151 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL &&
1152 *cp != '\0')
1153 return cp;
1154 namebuf = smalloc(namesize = 1);
1155 namebuf[0] = 0;
1156 if (mp->m_flag & MNOFROM)
1157 goto out;
1158 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1159 goto out;
1160 if (readline(ibuf, &linebuf, &linesize) < 0)
1161 goto out;
1162 newname:
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;)
1170 *cp2++ = *cp++;
1171 *cp2 = '\0';
1172 if (readline(ibuf, &linebuf, &linesize) < 0)
1173 goto out;
1174 if ((cp = strchr(linebuf, 'F')) == NULL)
1175 goto out;
1176 if (strncmp(cp, "From", 4) != 0)
1177 goto out;
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)
1183 break;
1184 if (strncmp(cp, "from", 4) != 0)
1185 break;
1186 if ((cp = strchr(cp, ' ')) == NULL)
1187 break;
1188 cp++;
1189 if (first) {
1190 strncpy(namebuf, cp, namesize);
1191 first = 0;
1192 } else {
1193 cp2=strrchr(namebuf, '!')+1;
1194 strncpy(cp2, cp, (namebuf+namesize)-cp2);
1196 namebuf[namesize-2]='\0';
1197 strcat(namebuf, "!");
1198 goto newname;
1200 cp++;
1202 out:
1203 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1204 *cp == '\0')
1205 cp = savestr(namebuf);
1206 if (linebuf)
1207 free(linebuf);
1208 free(namebuf);
1209 return cp;
1212 static int
1213 msgidnextc(const char **cp, int *status)
1215 int c;
1217 for (;;) {
1218 if (*status & 01) {
1219 if (**cp == '"') {
1220 *status &= ~01;
1221 (*cp)++;
1222 continue;
1224 if (**cp == '\\') {
1225 (*cp)++;
1226 if (**cp == '\0')
1227 goto eof;
1229 goto dfl;
1231 switch (**cp) {
1232 case '(':
1233 *cp = skip_comment(&(*cp)[1]);
1234 continue;
1235 case '>':
1236 case '\0':
1237 eof:
1238 return '\0';
1239 case '"':
1240 (*cp)++;
1241 *status |= 01;
1242 continue;
1243 case '@':
1244 *status |= 02;
1245 /*FALLTHRU*/
1246 default:
1247 dfl:
1248 c = *(*cp)++ & 0377;
1249 return *status & 02 ? lowerconv(c) : c;
1254 int
1255 msgidcmp(const char *s1, const char *s2)
1257 int q1 = 0, q2 = 0;
1258 int c1, c2;
1260 do {
1261 c1 = msgidnextc(&s1, &q1);
1262 c2 = msgidnextc(&s2, &q2);
1263 if (c1 != c2)
1264 return c1 - c2;
1265 } while (c1 && c2);
1266 return c1 - c2;
1270 * Count the occurances of c in str
1272 static int
1273 charcount(char *str, int c)
1275 char *cp;
1276 int i;
1278 for (i = 0, cp = str; *cp; cp++)
1279 if (*cp == c)
1280 i++;
1281 return(i);
1285 * See if the given header field is supposed to be ignored.
1288 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
1290 char *realfld;
1291 int ret;
1293 if (ignore == NULL)
1294 return 0;
1295 if (ignore == allignore)
1296 return 1;
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);
1305 else
1306 ret = member(realfld, ignore);
1307 ac_free(realfld);
1308 return ret;
1311 int
1312 member(char *realfield, struct ignoretab *table)
1314 struct ignore *igp;
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)
1319 return (1);
1320 return (0);
1324 * Fake Sender for From_ lines if missing, e. g. with POP3.
1326 char *
1327 fakefrom(struct message *mp)
1329 char *name;
1331 if (((name = skin(hfield1("return-path", mp))) == NULL ||
1332 *name == '\0' ) &&
1333 ((name = skin(hfield1("from", mp))) == NULL ||
1334 *name == '\0'))
1335 name = "-";
1336 return name;
1339 char *
1340 fakedate(time_t t)
1342 char *cp, *cq;
1344 cp = ctime(&t);
1345 for (cq = cp; *cq && *cq != '\n'; cq++);
1346 *cq = '\0';
1347 return savestr(cp);
1350 static char const *
1351 nexttoken(char const *cp)
1353 for (;;) {
1354 if (*cp == '\0')
1355 return NULL;
1356 if (*cp == '(') {
1357 int nesting = 0;
1359 while (*cp != '\0') {
1360 switch (*cp++) {
1361 case '(':
1362 nesting++;
1363 break;
1364 case ')':
1365 nesting--;
1366 break;
1368 if (nesting <= 0)
1369 break;
1371 } else if (blankchar(*cp & 0377) || *cp == ',')
1372 cp++;
1373 else
1374 break;
1376 return cp;
1380 * From username Fri Jan 2 20:13:51 2004
1381 * | | | | |
1382 * 0 5 10 15 20
1384 time_t
1385 unixtime(char const *from)
1387 char const *fp;
1388 char *xp;
1389 time_t t;
1390 int i, year, month, day, hour, minute, second;
1391 int tzdiff;
1392 struct tm *tmptr;
1394 for (fp = from; *fp && *fp != '\n'; fp++);
1395 fp -= 24;
1396 if (fp - from < 7)
1397 goto invalid;
1398 if (fp[3] != ' ')
1399 goto invalid;
1400 for (i = 0; month_names[i]; i++)
1401 if (strncmp(&fp[4], month_names[i], 3) == 0)
1402 break;
1403 if (month_names[i] == 0)
1404 goto invalid;
1405 month = i + 1;
1406 if (fp[7] != ' ')
1407 goto invalid;
1408 day = strtol(&fp[8], &xp, 10);
1409 if (*xp != ' ' || xp != &fp[10])
1410 goto invalid;
1411 hour = strtol(&fp[11], &xp, 10);
1412 if (*xp != ':' || xp != &fp[13])
1413 goto invalid;
1414 minute = strtol(&fp[14], &xp, 10);
1415 if (*xp != ':' || xp != &fp[16])
1416 goto invalid;
1417 second = strtol(&fp[17], &xp, 10);
1418 if (*xp != ' ' || xp != &fp[19])
1419 goto invalid;
1420 year = strtol(&fp[20], &xp, 10);
1421 if (xp != &fp[24])
1422 goto invalid;
1423 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1424 (time_t)-1)
1425 goto invalid;
1426 tzdiff = t - mktime(gmtime(&t));
1427 tmptr = localtime(&t);
1428 if (tmptr->tm_isdst > 0)
1429 tzdiff += 3600;
1430 t -= tzdiff;
1431 return t;
1432 invalid:
1433 time(&t);
1434 return t;
1437 time_t
1438 rfctime(char const *date)
1440 char const *cp = date;
1441 char *x;
1442 time_t t;
1443 int i, year, month, day, hour, minute, second;
1445 if ((cp = nexttoken(cp)) == NULL)
1446 goto invalid;
1447 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1448 alphachar(cp[2] & 0377) && cp[3] == ',') {
1449 if ((cp = nexttoken(&cp[4])) == NULL)
1450 goto invalid;
1452 day = strtol(cp, &x, 10);
1453 if ((cp = nexttoken(x)) == NULL)
1454 goto invalid;
1455 for (i = 0; month_names[i]; i++) {
1456 if (strncmp(cp, month_names[i], 3) == 0)
1457 break;
1459 if (month_names[i] == NULL)
1460 goto invalid;
1461 month = i + 1;
1462 if ((cp = nexttoken(&cp[3])) == NULL)
1463 goto invalid;
1464 year = strtol(cp, &x, 10);
1465 if ((cp = nexttoken(x)) == NULL)
1466 goto invalid;
1467 hour = strtol(cp, &x, 10);
1468 if (*x != ':')
1469 goto invalid;
1470 cp = &x[1];
1471 minute = strtol(cp, &x, 10);
1472 if (*x == ':') {
1473 cp = &x[1];
1474 second = strtol(cp, &x, 10);
1475 } else
1476 second = 0;
1477 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1478 (time_t)-1)
1479 goto invalid;
1480 if ((cp = nexttoken(x)) != NULL) {
1481 int sign = -1;
1482 char buf[3];
1484 switch (*cp) {
1485 case '-':
1486 sign = 1;
1487 /*FALLTHRU*/
1488 case '+':
1489 cp++;
1491 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1492 digitchar(cp[2] & 0377) &&
1493 digitchar(cp[3] & 0377)) {
1494 buf[2] = '\0';
1495 buf[0] = cp[0];
1496 buf[1] = cp[1];
1497 t += strtol(buf, NULL, 10) * sign * 3600;
1498 buf[0] = cp[2];
1499 buf[1] = cp[3];
1500 t += strtol(buf, NULL, 10) * sign * 60;
1503 return t;
1504 invalid:
1505 return 0;
1508 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1510 time_t
1511 combinetime(int year, int month, int day, int hour, int minute, int second)
1513 time_t t;
1515 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1516 return -1;
1517 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1518 if (year < 70)
1519 year += 2000;
1520 else if (year < 1900)
1521 year += 1900;
1522 if (month > 1)
1523 t += 86400 * 31;
1524 if (month > 2)
1525 t += 86400 * (leapyear(year) ? 29 : 28);
1526 if (month > 3)
1527 t += 86400 * 31;
1528 if (month > 4)
1529 t += 86400 * 30;
1530 if (month > 5)
1531 t += 86400 * 31;
1532 if (month > 6)
1533 t += 86400 * 30;
1534 if (month > 7)
1535 t += 86400 * 31;
1536 if (month > 8)
1537 t += 86400 * 31;
1538 if (month > 9)
1539 t += 86400 * 30;
1540 if (month > 10)
1541 t += 86400 * 31;
1542 if (month > 11)
1543 t += 86400 * 30;
1544 year -= 1900;
1545 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1546 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1547 return t;
1550 void
1551 substdate(struct message *m)
1553 char const *cp;
1554 time_t now;
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.
1562 time(&now);
1563 if ((cp = hfield1("received", m)) != NULL) {
1564 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1566 cp++;
1567 while (alnumchar(*cp & 0377));
1569 if (cp && *++cp)
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)
1576 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");
1585 return 1;
1587 if (senderfield && senderfield->n_flink) {
1588 fprintf(stderr, "The Sender: field may contain "
1589 "only one address.\n");
1590 return 2;
1592 return 0;
1595 char *
1596 getsender(struct message *mp)
1598 char *cp;
1599 struct name *np;
1601 if ((cp = hfield1("from", mp)) == NULL ||
1602 (np = lextract(cp, GEXTRA|GSKIN)) == NULL)
1603 return NULL;
1604 return np->n_flink != NULL ? skin(hfield1("sender", mp)) : np->n_name;