util.c revision 216370
1171626Scognet/*
2171626Scognet * Copyright (c) 1980, 1993
3171626Scognet *	The Regents of the University of California.  All rights reserved.
4171626Scognet *
5171626Scognet * Redistribution and use in source and binary forms, with or without
6171626Scognet * modification, are permitted provided that the following conditions
7171626Scognet * are met:
8171626Scognet * 1. Redistributions of source code must retain the above copyright
9171626Scognet *    notice, this list of conditions and the following disclaimer.
10171626Scognet * 2. Redistributions in binary form must reproduce the above copyright
11171626Scognet *    notice, this list of conditions and the following disclaimer in the
12171626Scognet *    documentation and/or other materials provided with the distribution.
13171626Scognet * 4. Neither the name of the University nor the names of its contributors
14171626Scognet *    may be used to endorse or promote products derived from this software
15171626Scognet *    without specific prior written permission.
16171626Scognet *
17171626Scognet * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18171626Scognet * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19171626Scognet * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20171626Scognet * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21171626Scognet * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22171626Scognet * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23171626Scognet * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24171626Scognet * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25171626Scognet * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26171626Scognet * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27171626Scognet * SUCH DAMAGE.
28171626Scognet */
29171626Scognet
30171626Scognet#ifndef lint
31171626Scognet#if 0
32171626Scognetstatic char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
33171626Scognet#endif
34171626Scognet#endif /* not lint */
35171626Scognet#include <sys/cdefs.h>
36171626Scognet__FBSDID("$FreeBSD: head/usr.bin/mail/util.c 216370 2010-12-11 08:32:16Z joel $");
37171626Scognet
38171626Scognet#include <sys/time.h>
39171626Scognet
40171626Scognet#include "rcv.h"
41171626Scognet#include "extern.h"
42171626Scognet
43236987Simp/*
44171626Scognet * Mail -- a mail program
45171626Scognet *
46171626Scognet * Auxiliary functions.
47171626Scognet */
48171626Scognet
49171626Scognetstatic char *save2str(char *, char *);
50171626Scognet
51171626Scognet/*
52171626Scognet * Return a pointer to a dynamic copy of the argument.
53171626Scognet */
54171626Scognetchar *
55171626Scognetsavestr(str)
56171626Scognet	char *str;
57171626Scognet{
58171626Scognet	char *new;
59171626Scognet	int size = strlen(str) + 1;
60171626Scognet
61171626Scognet	if ((new = salloc(size)) != NULL)
62171626Scognet		bcopy(str, new, size);
63171626Scognet	return (new);
64171626Scognet}
65171626Scognet
66171626Scognet/*
67171626Scognet * Make a copy of new argument incorporating old one.
68171626Scognet */
69171626Scognetstatic char *
70171626Scognetsave2str(str, old)
71171626Scognet	char *str, *old;
72171626Scognet{
73171626Scognet	char *new;
74171626Scognet	int newsize = strlen(str) + 1;
75171626Scognet	int oldsize = old ? strlen(old) + 1 : 0;
76171626Scognet
77171626Scognet	if ((new = salloc(newsize + oldsize)) != NULL) {
78171626Scognet		if (oldsize) {
79171626Scognet			bcopy(old, new, oldsize);
80171626Scognet			new[oldsize - 1] = ' ';
81257660Sian		}
82171626Scognet		bcopy(str, new + oldsize, newsize);
83171626Scognet	}
84171626Scognet	return (new);
85171626Scognet}
86171626Scognet
87171626Scognet/*
88171626Scognet * Touch the named message by setting its MTOUCH flag.
89261649Sian * Touched messages have the effect of not being sent
90171626Scognet * back to the system mailbox on exit.
91171626Scognet */
92171626Scognetvoid
93171626Scognettouch(mp)
94171626Scognet	struct message *mp;
95171626Scognet{
96171626Scognet
97171626Scognet	mp->m_flag |= MTOUCH;
98171626Scognet	if ((mp->m_flag & MREAD) == 0)
99171626Scognet		mp->m_flag |= MREAD|MSTATUS;
100171626Scognet}
101171626Scognet
102171626Scognet/*
103171626Scognet * Test to see if the passed file name is a directory.
104171626Scognet * Return true if it is.
105171626Scognet */
106171626Scognetint
107171626Scognetisdir(name)
108171626Scognet	char name[];
109171626Scognet{
110171626Scognet	struct stat sbuf;
111171626Scognet
112171626Scognet	if (stat(name, &sbuf) < 0)
113171626Scognet		return (0);
114171626Scognet	return (S_ISDIR(sbuf.st_mode));
115171626Scognet}
116171626Scognet
117171626Scognet/*
118171626Scognet * Count the number of arguments in the given string raw list.
119171626Scognet */
120171626Scognetint
121257660Sianargcount(argv)
122171626Scognet	char **argv;
123171626Scognet{
124171626Scognet	char **ap;
125171626Scognet
126171626Scognet	for (ap = argv; *ap++ != NULL;)
127265852Sian		;
128171626Scognet	return (ap - argv - 1);
129171626Scognet}
130171626Scognet
131171626Scognet/*
132171626Scognet * Return the desired header line from the passed message
133171626Scognet * pointer (or NULL if the desired header field is not available).
134171626Scognet */
135171626Scognetchar *
136171626Scognethfield(field, mp)
137171626Scognet	const char *field;
138265852Sian	struct message *mp;
139171626Scognet{
140172297Scognet	FILE *ibuf;
141172297Scognet	char linebuf[LINESIZE];
142172297Scognet	int lc;
143172297Scognet	char *hfield;
144172297Scognet	char *colon, *oldhfield = NULL;
145265852Sian
146172297Scognet	ibuf = setinput(mp);
147236987Simp	if ((lc = mp->m_lines - 1) < 0)
148171626Scognet		return (NULL);
149171626Scognet	if (readline(ibuf, linebuf, LINESIZE) < 0)
150171626Scognet		return (NULL);
151171626Scognet	while (lc > 0) {
152171626Scognet		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
153171626Scognet			return (oldhfield);
154171626Scognet		if ((hfield = ishfield(linebuf, colon, field)) != NULL)
155171626Scognet			oldhfield = save2str(hfield, oldhfield);
156171626Scognet	}
157171626Scognet	return (oldhfield);
158171626Scognet}
159171626Scognet
160171626Scognet/*
161236524Simp * Return the next header field found in the given message.
162171626Scognet * Return >= 0 if something found, < 0 elsewise.
163171626Scognet * "colon" is set to point to the colon in the header.
164194784Sjeff * Must deal with \ continuations & other such fraud.
165177883Simp */
166171626Scognetint
167171626Scognetgethfield(f, linebuf, rem, colon)
168171626Scognet	FILE *f;
169171626Scognet	char linebuf[];
170171626Scognet	int rem;
171171626Scognet	char **colon;
172171626Scognet{
173171626Scognet	char line2[LINESIZE];
174237040Simp	char *cp, *cp2;
175261649Sian	int c;
176171626Scognet
177171626Scognet	for (;;) {
178171626Scognet		if (--rem < 0)
179171626Scognet			return (-1);
180220836Spluknet		if ((c = readline(f, linebuf, LINESIZE)) <= 0)
181220836Spluknet			return (-1);
182220836Spluknet		for (cp = linebuf; isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
183171626Scognet		    cp++)
184171626Scognet			;
185171626Scognet		if (*cp != ':' || cp == linebuf)
186171626Scognet			continue;
187171626Scognet		/*
188171626Scognet		 * I guess we got a headline.
189171626Scognet		 * Handle wraparounding
190171626Scognet		 */
191171626Scognet		*colon = cp;
192171626Scognet		cp = linebuf + c;
193171626Scognet		for (;;) {
194171626Scognet			while (--cp >= linebuf && (*cp == ' ' || *cp == '\t'))
195171626Scognet				;
196171626Scognet			cp++;
197171626Scognet			if (rem <= 0)
198171626Scognet				break;
199171626Scognet			ungetc(c = getc(f), f);
200171626Scognet			if (c != ' ' && c != '\t')
201171626Scognet				break;
202171626Scognet			if ((c = readline(f, line2, LINESIZE)) < 0)
203171626Scognet				break;
204171626Scognet			rem--;
205236987Simp			for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++)
206171626Scognet				;
207171626Scognet			c -= cp2 - line2;
208171626Scognet			if (cp + c >= linebuf + LINESIZE - 2)
209171626Scognet				break;
210171626Scognet			*cp++ = ' ';
211171626Scognet			bcopy(cp2, cp, c);
212171626Scognet			cp += c;
213171626Scognet		}
214171626Scognet		*cp = 0;
215171626Scognet		return (rem);
216171626Scognet	}
217171626Scognet	/* NOTREACHED */
218194784Sjeff}
219194784Sjeff
220194784Sjeff/*
221194784Sjeff * Check whether the passed line is a header line of
222171626Scognet * the desired breed.  Return the field body, or 0.
223171626Scognet */
224171626Scognet
225171626Scognetchar*
226171626Scognetishfield(linebuf, colon, field)
227217688Spluknet	char linebuf[];
228171626Scognet	char *colon;
229171626Scognet	const char *field;
230171626Scognet{
231171626Scognet	char *cp = colon;
232171626Scognet
233171626Scognet	*cp = 0;
234171626Scognet	if (strcasecmp(linebuf, field) != 0) {
235171626Scognet		*cp = ':';
236171626Scognet		return (0);
237171626Scognet	}
238171626Scognet	*cp = ':';
239171626Scognet	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
240171626Scognet		;
241171626Scognet	return (cp);
242171626Scognet}
243171626Scognet
244171626Scognet/*
245171626Scognet * Copy a string and lowercase the result.
246171626Scognet * dsize: space left in buffer (including space for NULL)
247171626Scognet */
248236987Simpvoid
249171626Scognetistrncpy(dest, src, dsize)
250171626Scognet	char *dest;
251171626Scognet	const char *src;
252171626Scognet	size_t dsize;
253171626Scognet{
254171626Scognet
255171626Scognet	strlcpy(dest, src, dsize);
256171626Scognet	while (*dest)
257171626Scognet		*dest++ = tolower((unsigned char)*dest);
258171626Scognet}
259257660Sian
260171626Scognet/*
261171626Scognet * The following code deals with input stacking to do source
262171626Scognet * commands.  All but the current file pointer are saved on
263171626Scognet * the stack.
264171626Scognet */
265171626Scognet
266171626Scognetstatic	int	ssp;			/* Top of file stack */
267171626Scognetstruct sstack {
268171626Scognet	FILE	*s_file;		/* File we were in. */
269171626Scognet	int	s_cond;			/* Saved state of conditionals */
270171626Scognet	int	s_loading;		/* Loading .mailrc, etc. */
271171626Scognet};
272171626Scognet#define	SSTACK_SIZE	64		/* XXX was NOFILE. */
273171626Scognetstatic struct sstack sstack[SSTACK_SIZE];
274171626Scognet
275171626Scognet/*
276171626Scognet * Pushdown current input file and switch to a new one.
277171626Scognet * Set the global flag "sourcing" so that others will realize
278171626Scognet * that they are no longer reading from a tty (in all probability).
279171626Scognet */
280240802Sandrewint
281171626Scognetsource(arglist)
282171626Scognet	char **arglist;
283171626Scognet{
284171626Scognet	FILE *fi;
285171626Scognet	char *cp;
286171626Scognet
287171626Scognet	if ((cp = expand(*arglist)) == NULL)
288171626Scognet		return (1);
289185513Sstas	if ((fi = Fopen(cp, "r")) == NULL) {
290171626Scognet		warn("%s", cp);
291171626Scognet		return (1);
292171626Scognet	}
293258412Sian	if (ssp >= SSTACK_SIZE - 1) {
294258412Sian		printf("Too much \"sourcing\" going on.\n");
295171626Scognet		(void)Fclose(fi);
296171626Scognet		return (1);
297171626Scognet	}
298171626Scognet	sstack[ssp].s_file = input;
299171626Scognet	sstack[ssp].s_cond = cond;
300171626Scognet	sstack[ssp].s_loading = loading;
301171626Scognet	ssp++;
302171626Scognet	loading = 0;
303236828Sandrew	cond = CANY;
304171626Scognet	input = fi;
305171626Scognet	sourcing++;
306171626Scognet	return (0);
307171626Scognet}
308261642Sian
309256712Scognet/*
310247046Salc * Pop the current input back to the previous level.
311171626Scognet * Update the "sourcing" flag as appropriate.
312217688Spluknet */
313171626Scognetint
314261698Sianunstack()
315261698Sian{
316261698Sian	if (ssp <= 0) {
317261698Sian		printf("\"Source\" stack over-pop.\n");
318261698Sian		sourcing = 0;
319261698Sian		return (1);
320261698Sian	}
321261698Sian	(void)Fclose(input);
322261698Sian	if (cond != CANY)
323261698Sian		printf("Unmatched \"if\"\n");
324261698Sian	ssp--;
325261698Sian	cond = sstack[ssp].s_cond;
326266850Scognet	loading = sstack[ssp].s_loading;
327266850Scognet	input = sstack[ssp].s_file;
328266850Scognet	if (ssp == 0)
329266850Scognet		sourcing = loading;
330261698Sian	return (0);
331261698Sian}
332261698Sian
333261698Sian/*
334171626Scognet * Touch the indicated file.
335171626Scognet * This is nifty for the shell.
336171626Scognet */
337171626Scognetvoid
338171626Scognetalter(name)
339	char *name;
340{
341	struct stat sb;
342	struct timeval tv[2];
343
344	if (stat(name, &sb))
345		return;
346	(void)gettimeofday(&tv[0], (struct timezone *)NULL);
347	tv[0].tv_sec++;
348	TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim);
349	(void)utimes(name, tv);
350}
351
352/*
353 * Get sender's name from this message.  If the message has
354 * a bunch of arpanet stuff in it, we may have to skin the name
355 * before returning it.
356 */
357char *
358nameof(mp, reptype)
359	struct message *mp;
360	int reptype;
361{
362	char *cp, *cp2;
363
364	cp = skin(name1(mp, reptype));
365	if (reptype != 0 || charcount(cp, '!') < 2)
366		return (cp);
367	cp2 = strrchr(cp, '!');
368	cp2--;
369	while (cp2 > cp && *cp2 != '!')
370		cp2--;
371	if (*cp2 == '!')
372		return (cp2 + 1);
373	return (cp);
374}
375
376/*
377 * Start of a "comment".
378 * Ignore it.
379 */
380char *
381skip_comment(cp)
382	char *cp;
383{
384	int nesting = 1;
385
386	for (; nesting > 0 && *cp; cp++) {
387		switch (*cp) {
388		case '\\':
389			if (cp[1])
390				cp++;
391			break;
392		case '(':
393			nesting++;
394			break;
395		case ')':
396			nesting--;
397			break;
398		}
399	}
400	return (cp);
401}
402
403/*
404 * Skin an arpa net address according to the RFC 822 interpretation
405 * of "host-phrase."
406 */
407char *
408skin(name)
409	char *name;
410{
411	char *nbuf, *bufend, *cp, *cp2;
412	int c, gotlt, lastsp;
413
414	if (name == NULL)
415		return (NULL);
416	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
417	    && strchr(name, ' ') == NULL)
418		return (name);
419
420	/* We assume that length(input) <= length(output) */
421	if ((nbuf = malloc(strlen(name) + 1)) == NULL)
422		err(1, "Out of memory");
423	gotlt = 0;
424	lastsp = 0;
425	bufend = nbuf;
426	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
427		switch (c) {
428		case '(':
429			cp = skip_comment(cp);
430			lastsp = 0;
431			break;
432
433		case '"':
434			/*
435			 * Start of a "quoted-string".
436			 * Copy it in its entirety.
437			 */
438			while ((c = *cp) != '\0') {
439				cp++;
440				if (c == '"')
441					break;
442				if (c != '\\')
443					*cp2++ = c;
444				else if ((c = *cp) != '\0') {
445					*cp2++ = c;
446					cp++;
447				}
448			}
449			lastsp = 0;
450			break;
451
452		case ' ':
453			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
454				cp += 3, *cp2++ = '@';
455			else
456			if (cp[0] == '@' && cp[1] == ' ')
457				cp += 2, *cp2++ = '@';
458			else
459				lastsp = 1;
460			break;
461
462		case '<':
463			cp2 = bufend;
464			gotlt++;
465			lastsp = 0;
466			break;
467
468		case '>':
469			if (gotlt) {
470				gotlt = 0;
471				while ((c = *cp) != '\0' && c != ',') {
472					cp++;
473					if (c == '(')
474						cp = skip_comment(cp);
475					else if (c == '"')
476						while ((c = *cp) != '\0') {
477							cp++;
478							if (c == '"')
479								break;
480							if (c == '\\' && *cp != '\0')
481								cp++;
482						}
483				}
484				lastsp = 0;
485				break;
486			}
487			/* FALLTHROUGH */
488
489		default:
490			if (lastsp) {
491				lastsp = 0;
492				*cp2++ = ' ';
493			}
494			*cp2++ = c;
495			if (c == ',' && !gotlt &&
496			    (*cp == ' ' || *cp == '"' || *cp == '<')) {
497				*cp2++ = ' ';
498				while (*cp == ' ')
499					cp++;
500				lastsp = 0;
501				bufend = cp2;
502			}
503		}
504	}
505	*cp2 = '\0';
506
507	if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
508		nbuf = cp;
509	return (nbuf);
510}
511
512/*
513 * Fetch the sender's name from the passed message.
514 * Reptype can be
515 *	0 -- get sender's name for display purposes
516 *	1 -- get sender's name for reply
517 *	2 -- get sender's name for Reply
518 */
519char *
520name1(mp, reptype)
521	struct message *mp;
522	int reptype;
523{
524	char namebuf[LINESIZE];
525	char linebuf[LINESIZE];
526	char *cp, *cp2;
527	FILE *ibuf;
528	int first = 1;
529
530	if ((cp = hfield("from", mp)) != NULL)
531		return (cp);
532	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
533		return (cp);
534	ibuf = setinput(mp);
535	namebuf[0] = '\0';
536	if (readline(ibuf, linebuf, LINESIZE) < 0)
537		return (savestr(namebuf));
538newname:
539	for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++)
540		;
541	for (; *cp == ' ' || *cp == '\t'; cp++)
542		;
543	for (cp2 = &namebuf[strlen(namebuf)];
544	    *cp != '\0' && *cp != ' ' && *cp != '\t' &&
545	    cp2 < namebuf + LINESIZE - 1;)
546		*cp2++ = *cp++;
547	*cp2 = '\0';
548	if (readline(ibuf, linebuf, LINESIZE) < 0)
549		return (savestr(namebuf));
550	if ((cp = strchr(linebuf, 'F')) == NULL)
551		return (savestr(namebuf));
552	if (strncmp(cp, "From", 4) != 0)
553		return (savestr(namebuf));
554	while ((cp = strchr(cp, 'r')) != NULL) {
555		if (strncmp(cp, "remote", 6) == 0) {
556			if ((cp = strchr(cp, 'f')) == NULL)
557				break;
558			if (strncmp(cp, "from", 4) != 0)
559				break;
560			if ((cp = strchr(cp, ' ')) == NULL)
561				break;
562			cp++;
563			if (first) {
564				cp2 = namebuf;
565				first = 0;
566			} else
567				cp2 = strrchr(namebuf, '!') + 1;
568			strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
569			strcat(namebuf, "!");
570			goto newname;
571		}
572		cp++;
573	}
574	return (savestr(namebuf));
575}
576
577/*
578 * Count the occurances of c in str
579 */
580int
581charcount(str, c)
582	char *str;
583	int c;
584{
585	char *cp;
586	int i;
587
588	for (i = 0, cp = str; *cp != '\0'; cp++)
589		if (*cp == c)
590			i++;
591	return (i);
592}
593
594/*
595 * See if the given header field is supposed to be ignored.
596 */
597int
598isign(field, ignore)
599	const char *field;
600	struct ignoretab ignore[2];
601{
602	char realfld[LINESIZE];
603
604	if (ignore == ignoreall)
605		return (1);
606	/*
607	 * Lower-case the string, so that "Status" and "status"
608	 * will hash to the same place.
609	 */
610	istrncpy(realfld, field, sizeof(realfld));
611	if (ignore[1].i_count > 0)
612		return (!member(realfld, ignore + 1));
613	else
614		return (member(realfld, ignore));
615}
616
617int
618member(realfield, table)
619	char *realfield;
620	struct ignoretab *table;
621{
622	struct ignore *igp;
623
624	for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link)
625		if (*igp->i_field == *realfield &&
626		    equal(igp->i_field, realfield))
627			return (1);
628	return (0);
629}
630