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