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