1 /*
2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
3 *
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 */
6 /*
7 * Copyright (c) 1980, 1993
8 * The Regents of the University of California. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38
39 #ifndef lint
40 #ifdef DOSCCS
41 static char sccsid[] = "@(#)head.c 2.17 (gritter) 3/4/06";
42 #endif
43 #endif /* not lint */
44
45 #include "rcv.h"
46 #include "extern.h"
47 #include <time.h>
48
49 /*
50 * Mail -- a mail program
51 *
52 * Routines for processing and detecting headlines.
53 */
54
55 static char *copyin(char *src, char **space);
56 static char *nextword(char *wp, char *wbuf);
57 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
58 char **colon);
59 static int msgidnextc(const char **cp, int *status);
60 static int charcount(char *str, int c);
61
62 /*
63 * See if the passed line buffer is a mail header.
64 * Return true if yes. POSIX.2 leaves the content
65 * following 'From ' unspecified, so don't care about
66 * it.
67 */
68 /*ARGSUSED 2*/
69 int
is_head(char * linebuf,size_t linelen)70 is_head(char *linebuf, size_t linelen)
71 {
72 char *cp;
73
74 cp = linebuf;
75 if (*cp++ != 'F' || *cp++ != 'r' || *cp++ != 'o' || *cp++ != 'm' ||
76 *cp++ != ' ')
77 return (0);
78 return(1);
79 }
80
81 /*
82 * Split a headline into its useful components.
83 * Copy the line into dynamic string space, then set
84 * pointers into the copied line in the passed headline
85 * structure. Actually, it scans.
86 */
87 void
parse(char * line,size_t linelen,struct headline * hl,char * pbuf)88 parse(char *line, size_t linelen, struct headline *hl, char *pbuf)
89 {
90 char *cp;
91 char *sp;
92 char *word;
93
94 hl->l_from = NULL;
95 hl->l_tty = NULL;
96 hl->l_date = NULL;
97 cp = line;
98 sp = pbuf;
99 word = ac_alloc(linelen + 1);
100 /*
101 * Skip over "From" first.
102 */
103 cp = nextword(cp, word);
104 cp = nextword(cp, word);
105 if (*word)
106 hl->l_from = copyin(word, &sp);
107 if (cp != NULL && cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
108 cp = nextword(cp, word);
109 hl->l_tty = copyin(word, &sp);
110 }
111 if (cp != NULL)
112 hl->l_date = copyin(cp, &sp);
113 else
114 hl->l_date = catgets(catd, CATSET, 213, "<Unknown date>");
115 ac_free(word);
116 }
117
118 /*
119 * Copy the string on the left into the string on the right
120 * and bump the right (reference) string pointer by the length.
121 * Thus, dynamically allocate space in the right string, copying
122 * the left string into it.
123 */
124 static char *
copyin(char * src,char ** space)125 copyin(char *src, char **space)
126 {
127 char *cp;
128 char *top;
129
130 top = cp = *space;
131 while ((*cp++ = *src++) != '\0')
132 ;
133 *space = cp;
134 return (top);
135 }
136
137 #ifdef notdef
138 static int cmatch(char *, char *);
139 /*
140 * Test to see if the passed string is a ctime(3) generated
141 * date string as documented in the manual. The template
142 * below is used as the criterion of correctness.
143 * Also, we check for a possible trailing time zone using
144 * the tmztype template.
145 */
146
147 /*
148 * 'A' An upper case char
149 * 'a' A lower case char
150 * ' ' A space
151 * '0' A digit
152 * 'O' An optional digit or space
153 * ':' A colon
154 * '+' A sign
155 * 'N' A new line
156 */
157 static char *tmztype[] = {
158 "Aaa Aaa O0 00:00:00 0000",
159 "Aaa Aaa O0 00:00 0000",
160 "Aaa Aaa O0 00:00:00 AAA 0000",
161 "Aaa Aaa O0 00:00 AAA 0000",
162 /*
163 * Sommer time, e.g. MET DST
164 */
165 "Aaa Aaa O0 00:00:00 AAA AAA 0000",
166 "Aaa Aaa O0 00:00 AAA AAA 0000",
167 /*
168 * time zone offset, e.g.
169 * +0200 or +0200 MET or +0200 MET DST
170 */
171 "Aaa Aaa O0 00:00:00 +0000 0000",
172 "Aaa Aaa O0 00:00 +0000 0000",
173 "Aaa Aaa O0 00:00:00 +0000 AAA 0000",
174 "Aaa Aaa O0 00:00 +0000 AAA 0000",
175 "Aaa Aaa O0 00:00:00 +0000 AAA AAA 0000",
176 "Aaa Aaa O0 00:00 +0000 AAA AAA 0000",
177 /*
178 * time zone offset without time zone specification (pine)
179 */
180 "Aaa Aaa O0 00:00:00 0000 +0000",
181 NULL,
182 };
183
184 static int
is_date(char * date)185 is_date(char *date)
186 {
187 int ret = 0, form = 0;
188
189 while (tmztype[form]) {
190 if ( (ret = cmatch(date, tmztype[form])) == 1 )
191 break;
192 form++;
193 }
194
195 return ret;
196 }
197
198 /*
199 * Match the given string (cp) against the given template (tp).
200 * Return 1 if they match, 0 if they don't
201 */
202 static int
cmatch(char * cp,char * tp)203 cmatch(char *cp, char *tp)
204 {
205 int c;
206
207 while (*cp && *tp)
208 switch (*tp++) {
209 case 'a':
210 if (c = *cp++, !lowerchar(c))
211 return 0;
212 break;
213 case 'A':
214 if (c = *cp++, !upperchar(c))
215 return 0;
216 break;
217 case ' ':
218 if (*cp++ != ' ')
219 return 0;
220 break;
221 case '0':
222 if (c = *cp++, !digitchar(c))
223 return 0;
224 break;
225 case 'O':
226 if (c = *cp, c != ' ' && !digitchar(c))
227 return 0;
228 cp++;
229 break;
230 case ':':
231 if (*cp++ != ':')
232 return 0;
233 break;
234 case '+':
235 if (*cp != '+' && *cp != '-')
236 return 0;
237 cp++;
238 break;
239 case 'N':
240 if (*cp++ != '\n')
241 return 0;
242 break;
243 }
244 if (*cp || *tp)
245 return 0;
246 return (1);
247 }
248 #endif /* notdef */
249
250 /*
251 * Collect a liberal (space, tab delimited) word into the word buffer
252 * passed. Also, return a pointer to the next word following that,
253 * or NULL if none follow.
254 */
255 static char *
nextword(char * wp,char * wbuf)256 nextword(char *wp, char *wbuf)
257 {
258 int c;
259
260 if (wp == NULL) {
261 *wbuf = 0;
262 return (NULL);
263 }
264 while ((c = *wp++) != '\0' && !blankchar(c)) {
265 *wbuf++ = c;
266 if (c == '"') {
267 while ((c = *wp++) != '\0' && c != '"')
268 *wbuf++ = c;
269 if (c == '"')
270 *wbuf++ = c;
271 else
272 wp--;
273 }
274 }
275 *wbuf = '\0';
276 for (; blankchar(c); c = *wp++)
277 ;
278 if (c == 0)
279 return (NULL);
280 return (wp - 1);
281 }
282
283 void
extract_header(FILE * fp,struct header * hp)284 extract_header(FILE *fp, struct header *hp)
285 {
286 char *linebuf = NULL;
287 size_t linesize = 0;
288 int seenfields = 0;
289 char *colon, *cp, *value;
290 struct header nh;
291 struct header *hq = &nh;
292 int lc, c;
293
294 memset(hq, 0, sizeof *hq);
295 for (lc = 0; readline(fp, &linebuf, &linesize) > 0; lc++);
296 rewind(fp);
297 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
298 if ((value = thisfield(linebuf, "to")) != NULL) {
299 seenfields++;
300 hq->h_to = checkaddrs(cat(hq->h_to,
301 sextract(value, GTO|GFULL)));
302 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
303 seenfields++;
304 hq->h_cc = checkaddrs(cat(hq->h_cc,
305 sextract(value, GCC|GFULL)));
306 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
307 seenfields++;
308 hq->h_bcc = checkaddrs(cat(hq->h_bcc,
309 sextract(value, GBCC|GFULL)));
310 } else if ((value = thisfield(linebuf, "from")) != NULL) {
311 seenfields++;
312 hq->h_from = checkaddrs(cat(hq->h_from,
313 sextract(value, GEXTRA|GFULL)));
314 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
315 seenfields++;
316 hq->h_replyto = checkaddrs(cat(hq->h_replyto,
317 sextract(value, GEXTRA|GFULL)));
318 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
319 seenfields++;
320 hq->h_sender = checkaddrs(cat(hq->h_sender,
321 sextract(value, GEXTRA|GFULL)));
322 } else if ((value = thisfield(linebuf,
323 "organization")) != NULL) {
324 seenfields++;
325 for (cp = value; blankchar(*cp & 0377); cp++);
326 hq->h_organization = hq->h_organization ?
327 save2str(hq->h_organization, cp) :
328 savestr(cp);
329 } else if ((value = thisfield(linebuf, "subject")) != NULL ||
330 (value = thisfield(linebuf, "subj")) != NULL) {
331 seenfields++;
332 for (cp = value; blankchar(*cp & 0377); cp++);
333 hq->h_subject = hq->h_subject ?
334 save2str(hq->h_subject, cp) :
335 savestr(cp);
336 } else
337 fprintf(stderr, catgets(catd, CATSET, 266,
338 "Ignoring header field \"%s\"\n"),
339 linebuf);
340 }
341 /*
342 * In case the blank line after the header has been edited out.
343 * Otherwise, fetch the header separator.
344 */
345 if (linebuf) {
346 if (linebuf[0] != '\0') {
347 for (cp = linebuf; *(++cp) != '\0'; );
348 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
349 } else {
350 if ((c = getc(fp)) != '\n' && c != EOF)
351 ungetc(c, fp);
352 }
353 }
354 if (seenfields) {
355 hp->h_to = hq->h_to;
356 hp->h_cc = hq->h_cc;
357 hp->h_bcc = hq->h_bcc;
358 hp->h_from = hq->h_from;
359 hp->h_replyto = hq->h_replyto;
360 hp->h_sender = hq->h_sender;
361 hp->h_organization = hq->h_organization;
362 hp->h_subject = hq->h_subject;
363 } else
364 fprintf(stderr, catgets(catd, CATSET, 267,
365 "Restoring deleted header lines\n"));
366 if (linebuf)
367 free(linebuf);
368 }
369
370 /*
371 * Return the desired header line from the passed message
372 * pointer (or NULL if the desired header field is not available).
373 * If mult is zero, return the content of the first matching header
374 * field only, the content of all matching header fields else.
375 */
376 char *
hfield_mult(char * field,struct message * mp,int mult)377 hfield_mult(char *field, struct message *mp, int mult)
378 {
379 FILE *ibuf;
380 char *linebuf = NULL;
381 size_t linesize = 0;
382 int lc;
383 char *hfield;
384 char *colon, *oldhfield = NULL;
385
386 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
387 return NULL;
388 if ((lc = mp->m_lines - 1) < 0)
389 return NULL;
390 if ((mp->m_flag & MNOFROM) == 0) {
391 if (readline(ibuf, &linebuf, &linesize) < 0) {
392 if (linebuf)
393 free(linebuf);
394 return NULL;
395 }
396 }
397 while (lc > 0) {
398 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon))
399 < 0) {
400 if (linebuf)
401 free(linebuf);
402 return oldhfield;
403 }
404 if ((hfield = thisfield(linebuf, field)) != NULL) {
405 oldhfield = save2str(hfield, oldhfield);
406 if (mult == 0)
407 break;
408 }
409 }
410 if (linebuf)
411 free(linebuf);
412 return oldhfield;
413 }
414
415 /*
416 * Return the next header field found in the given message.
417 * Return >= 0 if something found, < 0 elsewise.
418 * "colon" is set to point to the colon in the header.
419 * Must deal with \ continuations & other such fraud.
420 */
421 static int
gethfield(FILE * f,char ** linebuf,size_t * linesize,int rem,char ** colon)422 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
423 {
424 char *line2 = NULL;
425 size_t line2size = 0;
426 char *cp, *cp2;
427 int c, isenc;
428
429 if (*linebuf == NULL)
430 *linebuf = srealloc(*linebuf, *linesize = 1);
431 **linebuf = '\0';
432 for (;;) {
433 if (--rem < 0)
434 return -1;
435 if ((c = readline(f, linebuf, linesize)) <= 0)
436 return -1;
437 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
438 if (cp > *linebuf)
439 while (blankchar(*cp & 0377))
440 cp++;
441 if (*cp != ':' || cp == *linebuf)
442 continue;
443 /*
444 * I guess we got a headline.
445 * Handle wraparounding
446 */
447 *colon = cp;
448 cp = *linebuf + c;
449 for (;;) {
450 isenc = 0;
451 while (--cp >= *linebuf && blankchar(*cp & 0377));
452 cp++;
453 if (rem <= 0)
454 break;
455 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
456 isenc |= 1;
457 ungetc(c = getc(f), f);
458 if (!blankchar(c))
459 break;
460 if ((c = readline(f, &line2, &line2size)) < 0)
461 break;
462 rem--;
463 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
464 c -= cp2 - line2;
465 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
466 isenc |= 2;
467 if (cp + c >= *linebuf + *linesize - 2) {
468 size_t diff = cp - *linebuf;
469 size_t colondiff = *colon - *linebuf;
470 *linebuf = srealloc(*linebuf,
471 *linesize += c + 2);
472 cp = &(*linebuf)[diff];
473 *colon = &(*linebuf)[colondiff];
474 }
475 if (isenc != 3)
476 *cp++ = ' ';
477 memcpy(cp, cp2, c);
478 cp += c;
479 }
480 *cp = 0;
481 if (line2)
482 free(line2);
483 return rem;
484 }
485 /* NOTREACHED */
486 }
487
488 /*
489 * Check whether the passed line is a header line of
490 * the desired breed. Return the field body, or 0.
491 */
492 char *
thisfield(const char * linebuf,const char * field)493 thisfield(const char *linebuf, const char *field)
494 {
495 while (lowerconv(*linebuf&0377) == lowerconv(*field&0377)) {
496 linebuf++;
497 field++;
498 }
499 if (*field != '\0')
500 return NULL;
501 while (blankchar(*linebuf&0377))
502 linebuf++;
503 if (*linebuf++ != ':')
504 return NULL;
505 while (blankchar(*linebuf&0377))
506 linebuf++;
507 return (char *)linebuf;
508 }
509
510 /*
511 * Get sender's name from this message. If the message has
512 * a bunch of arpanet stuff in it, we may have to skin the name
513 * before returning it.
514 */
515 char *
nameof(struct message * mp,int reptype)516 nameof(struct message *mp, int reptype)
517 {
518 char *cp, *cp2;
519
520 cp = skin(name1(mp, reptype));
521 if (reptype != 0 || charcount(cp, '!') < 2)
522 return(cp);
523 cp2 = strrchr(cp, '!');
524 cp2--;
525 while (cp2 > cp && *cp2 != '!')
526 cp2--;
527 if (*cp2 == '!')
528 return(cp2 + 1);
529 return(cp);
530 }
531
532 /*
533 * Start of a "comment".
534 * Ignore it.
535 */
536 char *
skip_comment(const char * cp)537 skip_comment(const char *cp)
538 {
539 int nesting = 1;
540
541 for (; nesting > 0 && *cp; cp++) {
542 switch (*cp) {
543 case '\\':
544 if (cp[1])
545 cp++;
546 break;
547 case '(':
548 nesting++;
549 break;
550 case ')':
551 nesting--;
552 break;
553 }
554 }
555 return (char *)cp;
556 }
557
558 /*
559 * Return the start of a route-addr (address in angle brackets),
560 * if present.
561 */
562 char *
routeaddr(const char * name)563 routeaddr(const char *name)
564 {
565 const char *np, *rp = NULL;
566
567 for (np = name; *np; np++) {
568 switch (*np) {
569 case '(':
570 np = skip_comment(&np[1]) - 1;
571 break;
572 case '"':
573 while (*np) {
574 if (*++np == '"')
575 break;
576 if (*np == '\\' && np[1])
577 np++;
578 }
579 break;
580 case '<':
581 rp = np;
582 break;
583 case '>':
584 return (char *)rp;
585 }
586 }
587 return NULL;
588 }
589
590 /*
591 * Skin an arpa net address according to the RFC 822 interpretation
592 * of "host-phrase."
593 */
594 char *
skin(char * name)595 skin(char *name)
596 {
597 int c;
598 char *cp, *cp2;
599 char *bufend;
600 int gotlt, lastsp;
601 char *nbuf;
602
603 if (name == NULL)
604 return(NULL);
605 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
606 && strchr(name, ' ') == NULL)
607 return(name);
608 gotlt = 0;
609 lastsp = 0;
610 nbuf = ac_alloc(strlen(name) + 1);
611 bufend = nbuf;
612 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
613 switch (c) {
614 case '(':
615 cp = skip_comment(cp);
616 lastsp = 0;
617 break;
618
619 case '"':
620 /*
621 * Start of a "quoted-string".
622 * Copy it in its entirety.
623 */
624 *cp2++ = c;
625 while ((c = *cp) != '\0') {
626 cp++;
627 if (c == '"') {
628 *cp2++ = c;
629 break;
630 }
631 if (c != '\\')
632 *cp2++ = c;
633 else if ((c = *cp) != '\0') {
634 *cp2++ = c;
635 cp++;
636 }
637 }
638 lastsp = 0;
639 break;
640
641 case ' ':
642 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
643 cp += 3, *cp2++ = '@';
644 else
645 if (cp[0] == '@' && cp[1] == ' ')
646 cp += 2, *cp2++ = '@';
647 #if 0
648 /*
649 * RFC 822 specifies spaces are STRIPPED when
650 * in an adress specifier.
651 */
652 else
653 lastsp = 1;
654 #endif
655 break;
656
657 case '<':
658 cp2 = bufend;
659 gotlt++;
660 lastsp = 0;
661 break;
662
663 case '>':
664 if (gotlt) {
665 gotlt = 0;
666 while ((c = *cp) != '\0' && c != ',') {
667 cp++;
668 if (c == '(')
669 cp = skip_comment(cp);
670 else if (c == '"')
671 while ((c = *cp) != '\0') {
672 cp++;
673 if (c == '"')
674 break;
675 if (c == '\\' && *cp)
676 cp++;
677 }
678 }
679 lastsp = 0;
680 break;
681 }
682 /* Fall into . . . */
683
684 default:
685 if (lastsp) {
686 lastsp = 0;
687 *cp2++ = ' ';
688 }
689 *cp2++ = c;
690 if (c == ',' && !gotlt) {
691 *cp2++ = ' ';
692 for (; *cp == ' '; cp++)
693 ;
694 lastsp = 0;
695 bufend = cp2;
696 }
697 }
698 }
699 *cp2 = 0;
700 cp = savestr(nbuf);
701 ac_free(nbuf);
702 return cp;
703 }
704
705 /*
706 * Fetch the real name from an internet mail address field.
707 */
708 char *
realname(char * name)709 realname(char *name)
710 {
711 char *cstart = NULL, *cend = NULL, *cp, *cq;
712 char *rname, *rp;
713 struct str in, out;
714 int quoted, good, nogood;
715
716 if (name == NULL)
717 return NULL;
718 for (cp = name; *cp; cp++) {
719 switch (*cp) {
720 case '(':
721 if (cstart)
722 /*
723 * More than one comment in address, doesn't
724 * make sense to display it without context.
725 * Return the entire field,
726 */
727 return mime_fromaddr(name);
728 cstart = cp++;
729 cp = skip_comment(cp);
730 cend = cp--;
731 if (cend <= cstart)
732 cend = cstart = NULL;
733 break;
734 case '"':
735 while (*cp) {
736 if (*++cp == '"')
737 break;
738 if (*cp == '\\' && cp[1])
739 cp++;
740 }
741 break;
742 case '<':
743 if (cp > name) {
744 cstart = name;
745 cend = cp;
746 }
747 break;
748 case ',':
749 /*
750 * More than one address. Just use the first one.
751 */
752 goto brk;
753 }
754 }
755 brk: if (cstart == NULL) {
756 if (*name == '<')
757 /*
758 * If name contains only a route-addr, the
759 * surrounding angle brackets don't serve any
760 * useful purpose when displaying, so they
761 * are removed.
762 */
763 return prstr(skin(name));
764 return mime_fromaddr(name);
765 }
766 rp = rname = ac_alloc(cend - cstart + 1);
767 /*
768 * Strip quotes. Note that quotes that appear within a MIME-
769 * encoded word are not stripped. The idea is to strip only
770 * syntactical relevant things (but this is not necessarily
771 * the most sensible way in practice).
772 */
773 quoted = 0;
774 for (cp = cstart; cp < cend; cp++) {
775 if (*cp == '(' && !quoted) {
776 cq = skip_comment(++cp);
777 if (--cq > cend)
778 cq = cend;
779 while (cp < cq) {
780 if (*cp == '\\' && &cp[1] < cq)
781 cp++;
782 *rp++ = *cp++;
783 }
784 } else if (*cp == '\\' && &cp[1] < cend)
785 *rp++ = *++cp;
786 else if (*cp == '"') {
787 quoted = !quoted;
788 continue;
789 } else
790 *rp++ = *cp;
791 }
792 *rp = '\0';
793 in.s = rname;
794 in.l = rp - rname;
795 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
796 ac_free(rname);
797 rname = savestr(out.s);
798 free(out.s);
799 while (blankchar(*rname & 0377))
800 rname++;
801 for (rp = rname; *rp; rp++);
802 while (--rp >= rname && blankchar(*rp & 0377))
803 *rp = '\0';
804 if (rp == rname)
805 return mime_fromaddr(name);
806 /*
807 * mime_fromhdr() has converted all nonprintable characters to
808 * question marks now. These and blanks are considered uninteresting;
809 * if the displayed part of the real name contains more than 25% of
810 * them, it is probably better to display the plain email address
811 * instead.
812 */
813 good = 0;
814 nogood = 0;
815 for (rp = rname; *rp && rp < &rname[20]; rp++)
816 if (*rp == '?' || blankchar(*rp & 0377))
817 nogood++;
818 else
819 good++;
820 if (good*3 < nogood)
821 return prstr(skin(name));
822 return rname;
823 }
824
825 /*
826 * Fetch the sender's name from the passed message.
827 * Reptype can be
828 * 0 -- get sender's name for display purposes
829 * 1 -- get sender's name for reply
830 * 2 -- get sender's name for Reply
831 */
832 char *
name1(struct message * mp,int reptype)833 name1(struct message *mp, int reptype)
834 {
835 char *namebuf;
836 size_t namesize;
837 char *linebuf = NULL;
838 size_t linesize = 0;
839 char *cp, *cp2;
840 FILE *ibuf;
841 int first = 1;
842
843 if ((cp = hfield("from", mp)) != NULL && *cp != '\0')
844 return cp;
845 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL &&
846 *cp != '\0')
847 return cp;
848 namebuf = smalloc(namesize = 1);
849 namebuf[0] = 0;
850 if (mp->m_flag & MNOFROM)
851 goto out;
852 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
853 goto out;
854 if (readline(ibuf, &linebuf, &linesize) < 0)
855 goto out;
856 newname:
857 if (namesize <= linesize)
858 namebuf = srealloc(namebuf, namesize = linesize + 1);
859 for (cp = linebuf; *cp && *cp != ' '; cp++)
860 ;
861 for (; blankchar(*cp & 0377); cp++);
862 for (cp2 = &namebuf[strlen(namebuf)];
863 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
864 *cp2++ = *cp++;
865 *cp2 = '\0';
866 if (readline(ibuf, &linebuf, &linesize) < 0)
867 goto out;
868 if ((cp = strchr(linebuf, 'F')) == NULL)
869 goto out;
870 if (strncmp(cp, "From", 4) != 0)
871 goto out;
872 if (namesize <= linesize)
873 namebuf = srealloc(namebuf, namesize = linesize + 1);
874 while ((cp = strchr(cp, 'r')) != NULL) {
875 if (strncmp(cp, "remote", 6) == 0) {
876 if ((cp = strchr(cp, 'f')) == NULL)
877 break;
878 if (strncmp(cp, "from", 4) != 0)
879 break;
880 if ((cp = strchr(cp, ' ')) == NULL)
881 break;
882 cp++;
883 if (first) {
884 strncpy(namebuf, cp, namesize);
885 first = 0;
886 } else {
887 cp2=strrchr(namebuf, '!')+1;
888 strncpy(cp2, cp, (namebuf+namesize)-cp2);
889 }
890 namebuf[namesize-2]='\0';
891 strcat(namebuf, "!");
892 goto newname;
893 }
894 cp++;
895 }
896 out:
897 if (*namebuf != '\0' || ((cp = hfield("return-path", mp))) == NULL ||
898 *cp == '\0')
899 cp = savestr(namebuf);
900 if (linebuf)
901 free(linebuf);
902 free(namebuf);
903 return cp;
904 }
905
906 static int
msgidnextc(const char ** cp,int * status)907 msgidnextc(const char **cp, int *status)
908 {
909 int c;
910
911 for (;;) {
912 if (*status & 01) {
913 if (**cp == '"') {
914 *status &= ~01;
915 (*cp)++;
916 continue;
917 }
918 if (**cp == '\\') {
919 (*cp)++;
920 if (**cp == '\0')
921 goto eof;
922 }
923 goto dfl;
924 }
925 switch (**cp) {
926 case '(':
927 *cp = skip_comment(&(*cp)[1]);
928 continue;
929 case '>':
930 case '\0':
931 eof:
932 return '\0';
933 case '"':
934 (*cp)++;
935 *status |= 01;
936 continue;
937 case '@':
938 *status |= 02;
939 /*FALLTHRU*/
940 default:
941 dfl:
942 c = *(*cp)++ & 0377;
943 return *status & 02 ? lowerconv(c) : c;
944 }
945 }
946 }
947
948 int
msgidcmp(const char * s1,const char * s2)949 msgidcmp(const char *s1, const char *s2)
950 {
951 int q1 = 0, q2 = 0;
952 int c1, c2;
953
954 do {
955 c1 = msgidnextc(&s1, &q1);
956 c2 = msgidnextc(&s2, &q2);
957 if (c1 != c2)
958 return c1 - c2;
959 } while (c1 && c2);
960 return c1 - c2;
961 }
962
963 /*
964 * Count the occurances of c in str
965 */
966 static int
charcount(char * str,int c)967 charcount(char *str, int c)
968 {
969 char *cp;
970 int i;
971
972 for (i = 0, cp = str; *cp; cp++)
973 if (*cp == c)
974 i++;
975 return(i);
976 }
977
978 /*
979 * See if the given header field is supposed to be ignored.
980 */
981 int
is_ign(char * field,size_t fieldlen,struct ignoretab ignore[2])982 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
983 {
984 char *realfld;
985 int ret;
986
987 if (ignore == NULL)
988 return 0;
989 if (ignore == allignore)
990 return 1;
991 /*
992 * Lower-case the string, so that "Status" and "status"
993 * will hash to the same place.
994 */
995 realfld = ac_alloc(fieldlen + 1);
996 i_strcpy(realfld, field, fieldlen + 1);
997 if (ignore[1].i_count > 0)
998 ret = !member(realfld, ignore + 1);
999 else
1000 ret = member(realfld, ignore);
1001 ac_free(realfld);
1002 return ret;
1003 }
1004
1005 int
member(char * realfield,struct ignoretab * table)1006 member(char *realfield, struct ignoretab *table)
1007 {
1008 struct ignore *igp;
1009
1010 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1011 if (*igp->i_field == *realfield &&
1012 equal(igp->i_field, realfield))
1013 return (1);
1014 return (0);
1015 }
1016
1017 /*
1018 * Fake Sender for From_ lines if missing, e. g. with POP3.
1019 */
1020 char *
fakefrom(struct message * mp)1021 fakefrom(struct message *mp)
1022 {
1023 char *name;
1024
1025 if (((name = skin(hfield("return-path", mp))) == NULL ||
1026 *name == '\0' ) &&
1027 ((name = skin(hfield("from", mp))) == NULL ||
1028 *name == '\0'))
1029 name = "-";
1030 return name;
1031 }
1032
1033 char *
fakedate(time_t t)1034 fakedate(time_t t)
1035 {
1036 char *cp, *cq;
1037
1038 cp = ctime(&t);
1039 for (cq = cp; *cq && *cq != '\n'; cq++);
1040 *cq = '\0';
1041 return savestr(cp);
1042 }
1043
1044 char *
nexttoken(char * cp)1045 nexttoken(char *cp)
1046 {
1047 for (;;) {
1048 if (*cp == '\0')
1049 return NULL;
1050 if (*cp == '(') {
1051 int nesting = 0;
1052
1053 while (*cp != '\0') {
1054 switch (*cp++) {
1055 case '(':
1056 nesting++;
1057 break;
1058 case ')':
1059 nesting--;
1060 break;
1061 }
1062 if (nesting <= 0)
1063 break;
1064 }
1065 } else if (blankchar(*cp & 0377) || *cp == ',')
1066 cp++;
1067 else
1068 break;
1069 }
1070 return cp;
1071 }
1072
1073 /*
1074 * From username Fri Jan 2 20:13:51 2004
1075 * | | | | |
1076 * 0 5 10 15 20
1077 */
1078 time_t
unixtime(char * from)1079 unixtime(char *from)
1080 {
1081 char *fp, *xp;
1082 time_t t;
1083 int i, year, month, day, hour, minute, second;
1084 int tzdiff;
1085 struct tm *tmptr;
1086
1087 for (fp = from; *fp && *fp != '\n'; fp++);
1088 fp -= 24;
1089 if (fp - from < 7)
1090 goto invalid;
1091 if (fp[3] != ' ')
1092 goto invalid;
1093 for (i = 0; month_names[i]; i++)
1094 if (strncmp(&fp[4], month_names[i], 3) == 0)
1095 break;
1096 if (month_names[i] == 0)
1097 goto invalid;
1098 month = i + 1;
1099 if (fp[7] != ' ')
1100 goto invalid;
1101 day = strtol(&fp[8], &xp, 10);
1102 if (*xp != ' ' || xp != &fp[10])
1103 goto invalid;
1104 hour = strtol(&fp[11], &xp, 10);
1105 if (*xp != ':' || xp != &fp[13])
1106 goto invalid;
1107 minute = strtol(&fp[14], &xp, 10);
1108 if (*xp != ':' || xp != &fp[16])
1109 goto invalid;
1110 second = strtol(&fp[17], &xp, 10);
1111 if (*xp != ' ' || xp != &fp[19])
1112 goto invalid;
1113 year = strtol(&fp[20], &xp, 10);
1114 if (xp != &fp[24])
1115 goto invalid;
1116 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1117 (time_t)-1)
1118 goto invalid;
1119 tmptr = localtime(&t);
1120 tzdiff = tmptr->tm_gmtoff; /* seconds east of GMT */
1121 t -= tzdiff;
1122 return t;
1123 invalid:
1124 time(&t);
1125 return t;
1126 }
1127
1128 time_t
rfctime(char * date)1129 rfctime(char *date)
1130 {
1131 char *cp = date, *x;
1132 time_t t;
1133 int i, year, month, day, hour, minute, second;
1134
1135 if ((cp = nexttoken(cp)) == NULL)
1136 goto invalid;
1137 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1138 alphachar(cp[2] & 0377) && cp[3] == ',') {
1139 if ((cp = nexttoken(&cp[4])) == NULL)
1140 goto invalid;
1141 }
1142 day = strtol(cp, &x, 10);
1143 if ((cp = nexttoken(x)) == NULL)
1144 goto invalid;
1145 for (i = 0; month_names[i]; i++) {
1146 if (strncmp(cp, month_names[i], 3) == 0)
1147 break;
1148 }
1149 if (month_names[i] == NULL)
1150 goto invalid;
1151 month = i + 1;
1152 if ((cp = nexttoken(&cp[3])) == NULL)
1153 goto invalid;
1154 year = strtol(cp, &x, 10);
1155 if ((cp = nexttoken(x)) == NULL)
1156 goto invalid;
1157 hour = strtol(cp, &x, 10);
1158 if (*x != ':')
1159 goto invalid;
1160 cp = &x[1];
1161 minute = strtol(cp, &x, 10);
1162 if (*x == ':') {
1163 cp = &x[1];
1164 second = strtol(cp, &x, 10);
1165 } else
1166 second = 0;
1167 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1168 (time_t)-1)
1169 goto invalid;
1170 if ((cp = nexttoken(x)) != NULL) {
1171 int sign = -1;
1172 char buf[3];
1173
1174 switch (*cp) {
1175 case '-':
1176 sign = 1;
1177 /*FALLTHRU*/
1178 case '+':
1179 cp++;
1180 }
1181 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1182 digitchar(cp[2] & 0377) &&
1183 digitchar(cp[3] & 0377)) {
1184 buf[2] = '\0';
1185 buf[0] = cp[0];
1186 buf[1] = cp[1];
1187 t += strtol(buf, NULL, 10) * sign * 3600;
1188 buf[0] = cp[2];
1189 buf[1] = cp[3];
1190 t += strtol(buf, NULL, 10) * sign * 60;
1191 }
1192 }
1193 return t;
1194 invalid:
1195 return 0;
1196 }
1197
1198 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1199
1200 time_t
combinetime(int year,int month,int day,int hour,int minute,int second)1201 combinetime(int year, int month, int day, int hour, int minute, int second)
1202 {
1203 time_t t;
1204
1205 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1206 return -1;
1207 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1208 if (year < 70)
1209 year += 2000;
1210 else if (year < 1900)
1211 year += 1900;
1212 if (month > 1)
1213 t += 86400 * 31;
1214 if (month > 2)
1215 t += 86400 * (leapyear(year) ? 29 : 28);
1216 if (month > 3)
1217 t += 86400 * 31;
1218 if (month > 4)
1219 t += 86400 * 30;
1220 if (month > 5)
1221 t += 86400 * 31;
1222 if (month > 6)
1223 t += 86400 * 30;
1224 if (month > 7)
1225 t += 86400 * 31;
1226 if (month > 8)
1227 t += 86400 * 31;
1228 if (month > 9)
1229 t += 86400 * 30;
1230 if (month > 10)
1231 t += 86400 * 31;
1232 if (month > 11)
1233 t += 86400 * 30;
1234 year -= 1900;
1235 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1236 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1237 return t;
1238 }
1239
1240 void
substdate(struct message * m)1241 substdate(struct message *m)
1242 {
1243 char *cp;
1244 time_t now;
1245
1246 /*
1247 * Determine the date to print in faked 'From ' lines. This is
1248 * traditionally the date the message was written to the mail
1249 * file. Try to determine this using RFC message header fields,
1250 * or fall back to current time.
1251 */
1252 time(&now);
1253 if ((cp = hfield_mult("received", m, 0)) != NULL) {
1254 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1255 do
1256 cp++;
1257 while (alnumchar(*cp & 0377));
1258 }
1259 if (cp && *++cp)
1260 m->m_time = rfctime(cp);
1261 }
1262 if (m->m_time == 0 || m->m_time > now)
1263 if ((cp = hfield("date", m)) != NULL)
1264 m->m_time = rfctime(cp);
1265 if (m->m_time == 0 || m->m_time > now)
1266 m->m_time = now;
1267 }
1268
1269 int
check_from_and_sender(struct name * fromfield,struct name * senderfield)1270 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1271 {
1272 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1273 fprintf(stderr, "A Sender: field is required with multiple "
1274 "addresses in From: field.\n");
1275 return 1;
1276 }
1277 if (senderfield && senderfield->n_flink) {
1278 fprintf(stderr, "The Sender: field may contain "
1279 "only one address.\n");
1280 return 2;
1281 }
1282 return 0;
1283 }
1284
1285 char *
getsender(struct message * mp)1286 getsender(struct message *mp)
1287 {
1288 char *cp;
1289 struct name *np;
1290
1291 if ((cp = hfield("from", mp)) == NULL ||
1292 (np = sextract(cp, GEXTRA|GSKIN)) == NULL)
1293 return NULL;
1294 return np->n_flink != NULL ? skin(hfield("sender", mp)) : np->n_name;
1295 }
1296