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