1/*
2 * Copyright (c) 1980, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35#if 0
36static char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
37#endif
38__attribute__((__used__))
39static const char rcsid[] =
40  "$FreeBSD: src/usr.bin/mail/aux.c,v 1.13 2002/08/25 13:22:47 charnier Exp $";
41#endif /* not lint */
42
43#include <sys/cdefs.h>
44#include <sys/time.h>
45
46#include "rcv.h"
47#include "extern.h"
48
49/*
50 * Mail -- a mail program
51 *
52 * Auxiliary functions.
53 */
54
55static char *save2str(char *, char *);
56
57/*
58 * Return a pointer to a dynamic copy of the argument.
59 */
60char *
61savestr(str)
62	char *str;
63{
64	char *new;
65	int size = strlen(str) + 1;
66
67	if ((new = salloc(size)) != NULL)
68		bcopy(str, new, size);
69	return (new);
70}
71
72/*
73 * Make a copy of new argument incorporating old one.
74 */
75char *
76save2str(str, old)
77	char *str, *old;
78{
79	char *new;
80	int newsize = strlen(str) + 1;
81	int oldsize = old ? strlen(old) + 1 : 0;
82
83	if ((new = salloc(newsize + oldsize)) != NULL) {
84		if (oldsize) {
85			bcopy(old, new, oldsize);
86			new[oldsize - 1] = ' ';
87		}
88		bcopy(str, new + oldsize, newsize);
89	}
90	return (new);
91}
92
93/*
94 * Touch the named message by setting its MTOUCH flag.
95 * Touched messages have the effect of not being sent
96 * back to the system mailbox on exit.
97 */
98void
99touch(mp)
100	struct message *mp;
101{
102
103	mp->m_flag |= MTOUCH;
104	if ((mp->m_flag & MREAD) == 0)
105		mp->m_flag |= MREAD|MSTATUS;
106}
107
108/*
109 * Test to see if the passed file name is a directory.
110 * Return true if it is.
111 */
112int
113isdir(name)
114	char name[];
115{
116	struct stat sbuf;
117
118	if (stat(name, &sbuf) < 0)
119		return (0);
120	return (S_ISDIR(sbuf.st_mode));
121}
122
123/*
124 * Count the number of arguments in the given string raw list.
125 */
126int
127argcount(argv)
128	char **argv;
129{
130	char **ap;
131
132	for (ap = argv; *ap++ != NULL;)
133		;
134	return (ap - argv - 1);
135}
136
137/*
138 * Return the desired header line from the passed message
139 * pointer (or NULL if the desired header field is not available).
140 */
141char *
142hfield(field, mp)
143	const char *field;
144	struct message *mp;
145{
146	FILE *ibuf;
147	char linebuf[LINESIZE];
148	int lc;
149	char *hfield;
150	char *colon, *oldhfield = NULL;
151
152	ibuf = setinput(mp);
153	if ((lc = mp->m_lines - 1) < 0)
154		return (NULL);
155	if (readline(ibuf, linebuf, LINESIZE) < 0)
156		return (NULL);
157	while (lc > 0) {
158		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
159			return (oldhfield);
160		if ((hfield = ishfield(linebuf, colon, field)) != NULL)
161			oldhfield = save2str(hfield, oldhfield);
162	}
163	return (oldhfield);
164}
165
166/*
167 * Return the next header field found in the given message.
168 * Return >= 0 if something found, < 0 elsewise.
169 * "colon" is set to point to the colon in the header.
170 * Must deal with \ continuations & other such fraud.
171 */
172int
173gethfield(f, linebuf, rem, colon)
174	FILE *f;
175	char linebuf[];
176	int rem;
177	char **colon;
178{
179	char line2[LINESIZE];
180	char *cp, *cp2;
181	int c;
182
183	for (;;) {
184		if (--rem < 0)
185			return (-1);
186		if ((c = readline(f, linebuf, LINESIZE)) <= 0)
187			return (-1);
188		for (cp = linebuf; isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
189		    cp++)
190			;
191		if (*cp != ':' || cp == linebuf)
192			continue;
193		/*
194		 * I guess we got a headline.
195		 * Handle wraparounding
196		 */
197		*colon = cp;
198		cp = linebuf + c;
199		for (;;) {
200			while (--cp >= linebuf && (*cp == ' ' || *cp == '\t'))
201				;
202			cp++;
203			if (rem <= 0)
204				break;
205			ungetc(c = getc(f), f);
206			if (c != ' ' && c != '\t')
207				break;
208			if ((c = readline(f, line2, LINESIZE)) < 0)
209				break;
210			rem--;
211			for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++)
212				;
213			c -= cp2 - line2;
214			if (cp + c >= linebuf + LINESIZE - 2)
215				break;
216			*cp++ = ' ';
217			bcopy(cp2, cp, c);
218			cp += c;
219		}
220		*cp = 0;
221		return (rem);
222	}
223	/* NOTREACHED */
224}
225
226/*
227 * Check whether the passed line is a header line of
228 * the desired breed.  Return the field body, or 0.
229 */
230
231char*
232ishfield(linebuf, colon, field)
233	char linebuf[];
234	char *colon;
235	const char *field;
236{
237	char *cp = colon;
238
239	*cp = 0;
240	if (strcasecmp(linebuf, field) != 0) {
241		*cp = ':';
242		return (0);
243	}
244	*cp = ':';
245	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
246		;
247	return (cp);
248}
249
250/*
251 * Copy a string and lowercase the result.
252 * dsize: space left in buffer (including space for NULL)
253 */
254void
255istrncpy(dest, src, dsize)
256	char *dest;
257	const char *src;
258	size_t dsize;
259{
260
261	strlcpy(dest, src, dsize);
262	while (*dest) {
263		*dest = tolower((unsigned char)*dest);
264		dest++;
265	}
266}
267
268/*
269 * The following code deals with input stacking to do source
270 * commands.  All but the current file pointer are saved on
271 * the stack.
272 */
273
274static	int	ssp;			/* Top of file stack */
275struct sstack {
276	FILE	*s_file;		/* File we were in. */
277	int	s_cond;			/* Saved state of conditionals */
278	int	s_loading;		/* Loading .mailrc, etc. */
279};
280#define	SSTACK_SIZE	64		/* XXX was NOFILE. */
281static struct sstack sstack[SSTACK_SIZE];
282
283/*
284 * Pushdown current input file and switch to a new one.
285 * Set the global flag "sourcing" so that others will realize
286 * that they are no longer reading from a tty (in all probability).
287 */
288int
289source(arglist)
290	char **arglist;
291{
292	FILE *fi;
293	char *cp;
294
295	if ((cp = expand(*arglist)) == NULL)
296		return (1);
297	if ((fi = Fopen(cp, "r")) == NULL) {
298		warn("%s", cp);
299		return (1);
300	}
301	if (ssp >= SSTACK_SIZE - 1) {
302		printf("Too much \"sourcing\" going on.\n");
303		(void)Fclose(fi);
304		return (1);
305	}
306	sstack[ssp].s_file = input;
307	sstack[ssp].s_cond = cond;
308	sstack[ssp].s_loading = loading;
309	ssp++;
310	loading = 0;
311	cond = CANY;
312	input = fi;
313	sourcing++;
314	return (0);
315}
316
317/*
318 * Pop the current input back to the previous level.
319 * Update the "sourcing" flag as appropriate.
320 */
321int
322unstack()
323{
324	if (ssp <= 0) {
325		printf("\"Source\" stack over-pop.\n");
326		sourcing = 0;
327		return (1);
328	}
329	(void)Fclose(input);
330	if (cond != CANY)
331		printf("Unmatched \"if\"\n");
332	ssp--;
333	cond = sstack[ssp].s_cond;
334	loading = sstack[ssp].s_loading;
335	input = sstack[ssp].s_file;
336	if (ssp == 0)
337		sourcing = loading;
338	return (0);
339}
340
341/*
342 * Touch the indicated file.
343 * This is nifty for the shell.
344 */
345void
346alter(name)
347	char *name;
348{
349	struct stat sb;
350	struct timeval tv[2];
351
352	if (stat(name, &sb))
353		return;
354	(void)gettimeofday(&tv[0], (struct timezone *)NULL);
355	tv[0].tv_sec++;
356	TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
357	(void)utimes(name, tv);
358}
359
360/*
361 * Get sender's name from this message.  If the message has
362 * a bunch of arpanet stuff in it, we may have to skin the name
363 * before returning it.
364 */
365char *
366nameof(mp, reptype)
367	struct message *mp;
368	int reptype;
369{
370	char *cp, *cp2;
371
372	cp = skin(name1(mp, reptype));
373	if (reptype != 0 || charcount(cp, '!') < 2)
374		return (cp);
375	cp2 = strrchr(cp, '!');
376	cp2--;
377	while (cp2 > cp && *cp2 != '!')
378		cp2--;
379	if (*cp2 == '!')
380		return (cp2 + 1);
381	return (cp);
382}
383
384/*
385 * Start of a "comment".
386 * Ignore it.
387 */
388char *
389skip_comment(cp)
390	char *cp;
391{
392	int nesting = 1;
393
394	for (; nesting > 0 && *cp; cp++) {
395		switch (*cp) {
396		case '\\':
397			if (cp[1])
398				cp++;
399			break;
400		case '(':
401			nesting++;
402			break;
403		case ')':
404			nesting--;
405			break;
406		}
407	}
408	return (cp);
409}
410
411/*
412 * Skin an arpa net address according to the RFC 822 interpretation
413 * of "host-phrase."
414 */
415char *
416skin(name)
417	char *name;
418{
419	char *nbuf, *bufend, *cp, *cp2;
420	int c, gotlt, lastsp;
421
422	if (name == NULL)
423		return (NULL);
424	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
425	    && strchr(name, ' ') == NULL)
426		return (name);
427
428	/* We assume that length(input) <= length(output) */
429	if ((nbuf = malloc(strlen(name) + 1)) == NULL)
430		err(1, "Out of memory");
431	gotlt = 0;
432	lastsp = 0;
433	bufend = nbuf;
434	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
435		switch (c) {
436		case '(':
437			cp = skip_comment(cp);
438			lastsp = 0;
439			break;
440
441		case '"':
442			/*
443			 * Start of a "quoted-string".
444			 * Copy it in its entirety.
445			 */
446			while ((c = *cp) != '\0') {
447				cp++;
448				if (c == '"')
449					break;
450				if (c != '\\')
451					*cp2++ = c;
452				else if ((c = *cp) != '\0') {
453					*cp2++ = c;
454					cp++;
455				}
456			}
457			lastsp = 0;
458			break;
459
460		case ' ':
461			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
462				cp += 3, *cp2++ = '@';
463			else
464			if (cp[0] == '@' && cp[1] == ' ')
465				cp += 2, *cp2++ = '@';
466			else
467				lastsp = 1;
468			break;
469
470		case '<':
471			cp2 = bufend;
472			gotlt++;
473			lastsp = 0;
474			break;
475
476		case '>':
477			if (gotlt) {
478				gotlt = 0;
479				while ((c = *cp) != '\0' && c != ',') {
480					cp++;
481					if (c == '(')
482						cp = skip_comment(cp);
483					else if (c == '"')
484						while ((c = *cp) != '\0') {
485							cp++;
486							if (c == '"')
487								break;
488							if (c == '\\' && *cp != '\0')
489								cp++;
490						}
491				}
492				lastsp = 0;
493				break;
494			}
495			/* FALLTHROUGH */
496
497		default:
498			if (lastsp) {
499				lastsp = 0;
500				*cp2++ = ' ';
501			}
502			*cp2++ = c;
503			if (c == ',' && *cp == ' ' && !gotlt) {
504				*cp2++ = ' ';
505				while (*++cp == ' ')
506					;
507				lastsp = 0;
508				bufend = cp2;
509			}
510		}
511	}
512	*cp2 = '\0';
513
514	if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
515		nbuf = cp;
516	return (nbuf);
517}
518
519/*
520 * Fetch the sender's name from the passed message.
521 * Reptype can be
522 *	0 -- get sender's name for display purposes
523 *	1 -- get sender's name for reply
524 *	2 -- get sender's name for Reply
525 */
526char *
527name1(mp, reptype)
528	struct message *mp;
529	int reptype;
530{
531	char namebuf[LINESIZE];
532	char linebuf[LINESIZE];
533	char *cp, *cp2;
534	FILE *ibuf;
535	int first = 1;
536
537	if ((cp = hfield("from", mp)) != NULL)
538		return (cp);
539	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
540		return (cp);
541	ibuf = setinput(mp);
542	namebuf[0] = '\0';
543	if (readline(ibuf, linebuf, LINESIZE) < 0)
544		return (savestr(namebuf));
545newname:
546	for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++)
547		;
548	for (; *cp == ' ' || *cp == '\t'; cp++)
549		;
550	for (cp2 = &namebuf[strlen(namebuf)];
551	    *cp != '\0' && *cp != ' ' && *cp != '\t' &&
552	    cp2 < namebuf + LINESIZE - 1;)
553		*cp2++ = *cp++;
554	*cp2 = '\0';
555	if (readline(ibuf, linebuf, LINESIZE) < 0)
556		return (savestr(namebuf));
557	if ((cp = strchr(linebuf, 'F')) == NULL)
558		return (savestr(namebuf));
559	if (strncmp(cp, "From", 4) != 0)
560		return (savestr(namebuf));
561	while ((cp = strchr(cp, 'r')) != NULL) {
562		if (strncmp(cp, "remote", 6) == 0) {
563			if ((cp = strchr(cp, 'f')) == NULL)
564				break;
565			if (strncmp(cp, "from", 4) != 0)
566				break;
567			if ((cp = strchr(cp, ' ')) == NULL)
568				break;
569			cp++;
570			if (first) {
571				cp2 = namebuf;
572				first = 0;
573			} else
574				cp2 = strrchr(namebuf, '!') + 1;
575			strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
576			strcat(namebuf, "!");
577			goto newname;
578		}
579		cp++;
580	}
581	return (savestr(namebuf));
582}
583
584/*
585 * Count the occurances of c in str
586 */
587int
588charcount(str, c)
589	char *str;
590	int c;
591{
592	char *cp;
593	int i;
594
595	for (i = 0, cp = str; *cp != '\0'; cp++)
596		if (*cp == c)
597			i++;
598	return (i);
599}
600
601/*
602 * See if the given header field is supposed to be ignored.
603 */
604int
605isign(field, ignore)
606	const char *field;
607	struct ignoretab ignore[2];
608{
609	char realfld[LINESIZE];
610
611	if (ignore == ignoreall)
612		return (1);
613	/*
614	 * Lower-case the string, so that "Status" and "status"
615	 * will hash to the same place.
616	 */
617	istrncpy(realfld, field, sizeof(realfld));
618	if (ignore[1].i_count > 0)
619		return (!member(realfld, ignore + 1));
620	else
621		return (member(realfld, ignore));
622}
623
624int
625member(realfield, table)
626	char *realfield;
627	struct ignoretab *table;
628{
629	struct ignore *igp;
630
631	for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link)
632		if (*igp->i_field == *realfield &&
633		    equal(igp->i_field, realfield))
634			return (1);
635	return (0);
636}
637