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