xref: /netbsd-src/usr.bin/mail/support.c (revision 1ca5c1b28139779176bd5c13ad7c5f25c0bcd5f8)
1 /*	$NetBSD: support.c,v 1.1 2001/10/19 02:46:19 tv Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/cdefs.h>
37 #ifndef lint
38 #if 0
39 static char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
40 #else
41 __RCSID("$NetBSD: support.c,v 1.1 2001/10/19 02:46:19 tv Exp $");
42 #endif
43 #endif /* not lint */
44 
45 #include "rcv.h"
46 #include "extern.h"
47 
48 /*
49  * Mail -- a mail program
50  *
51  * Auxiliary functions.
52  */
53 static char *save2str __P((char *, char *));
54 
55 /*
56  * Return a pointer to a dynamic copy of the argument.
57  */
58 char *
59 savestr(str)
60 	const char *str;
61 {
62 	char *new;
63 	int size = strlen(str) + 1;
64 
65 	if ((new = salloc(size)) != NOSTR)
66 		memmove(new, str, size);
67 	return new;
68 }
69 
70 /*
71  * Make a copy of new argument incorporating old one.
72  */
73 static char *
74 save2str(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)) != NOSTR) {
82 		if (oldsize) {
83 			memmove(new, old, oldsize);
84 			new[oldsize - 1] = ' ';
85 		}
86 		memmove(new + oldsize, str, 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  */
96 void
97 touch(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  */
110 int
111 isdir(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  */
124 int
125 argcount(argv)
126 	char **argv;
127 {
128 	char **ap;
129 
130 	for (ap = argv; *ap++ != NOSTR;)
131 		;
132 	return ap - argv - 1;
133 }
134 
135 /*
136  * Return the desired header line from the passed message
137  * pointer (or NOSTR if the desired header field is not available).
138  */
139 char *
140 hfield(field, mp)
141 	char field[];
142 	struct message *mp;
143 {
144 	FILE *ibuf;
145 	char linebuf[LINESIZE];
146 	int lc;
147 	char *hfield;
148 	char *colon, *oldhfield = NOSTR;
149 
150 	ibuf = setinput(mp);
151 	if ((lc = mp->m_lines - 1) < 0)
152 		return NOSTR;
153 	if (readline(ibuf, linebuf, LINESIZE) < 0)
154 		return NOSTR;
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  */
170 int
171 gethfield(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 			memmove(cp, cp2, 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 
229 char*
230 ishfield(linebuf, colon, field)
231 	char linebuf[], field[];
232 	char *colon;
233 {
234 	char *cp = colon;
235 
236 	*cp = 0;
237 	if (strcasecmp(linebuf, field) != 0) {
238 		*cp = ':';
239 		return 0;
240 	}
241 	*cp = ':';
242 	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
243 		;
244 	return cp;
245 }
246 
247 /*
248  * Copy a string, lowercasing it as we go.
249  */
250 void
251 istrcpy(dest, src)
252 	char *dest, *src;
253 {
254 
255 	do {
256 		if (isupper((unsigned char)*src))
257 			*dest++ = tolower(*src);
258 		else
259 			*dest++ = *src;
260 	} while (*src++ != 0);
261 }
262 
263 /*
264  * The following code deals with input stacking to do source
265  * commands.  All but the current file pointer are saved on
266  * the stack.
267  */
268 
269 static	int	ssp;			/* Top of file stack */
270 struct sstack {
271 	FILE	*s_file;		/* File we were in. */
272 	int	s_cond;			/* Saved state of conditionals */
273 	int	s_loading;		/* Loading .mailrc, etc. */
274 } sstack[NOFILE];
275 
276 /*
277  * Pushdown current input file and switch to a new one.
278  * Set the global flag "sourcing" so that others will realize
279  * that they are no longer reading from a tty (in all probability).
280  */
281 int
282 source(v)
283 	void *v;
284 {
285 	char **arglist = v;
286 	FILE *fi;
287 	char *cp;
288 
289 	if ((cp = expand(*arglist)) == NOSTR)
290 		return(1);
291 	if ((fi = Fopen(cp, "r")) == NULL) {
292 		perror(cp);
293 		return(1);
294 	}
295 	if (ssp >= NOFILE - 1) {
296 		printf("Too much \"sourcing\" going on.\n");
297 		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  */
315 int
316 unstack()
317 {
318 	if (ssp <= 0) {
319 		printf("\"Source\" stack over-pop.\n");
320 		sourcing = 0;
321 		return(1);
322 	}
323 	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  */
339 void
340 alter(name)
341 	char *name;
342 {
343 	struct stat sb;
344 	struct timeval tv[2];
345 
346 	if (stat(name, &sb))
347 		return;
348 	(void) gettimeofday(&tv[0], (struct timezone *)0);
349 	tv[0].tv_sec++;
350 	TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
351 	(void) utimes(name, tv);
352 }
353 
354 /*
355  * Examine the passed line buffer and
356  * return true if it is all blanks and tabs.
357  */
358 int
359 blankline(linebuf)
360 	char linebuf[];
361 {
362 	char *cp;
363 
364 	for (cp = linebuf; *cp; cp++)
365 		if (*cp != ' ' && *cp != '\t')
366 			return(0);
367 	return(1);
368 }
369 
370 /*
371  * Get sender's name from this message.  If the message has
372  * a bunch of arpanet stuff in it, we may have to skin the name
373  * before returning it.
374  */
375 char *
376 nameof(mp, reptype)
377 	struct message *mp;
378 	int reptype;
379 {
380 	char *cp, *cp2;
381 
382 	cp = skin(name1(mp, reptype));
383 	if (reptype != 0 || charcount(cp, '!') < 2)
384 		return(cp);
385 	cp2 = strrchr(cp, '!');
386 	cp2--;
387 	while (cp2 > cp && *cp2 != '!')
388 		cp2--;
389 	if (*cp2 == '!')
390 		return(cp2 + 1);
391 	return(cp);
392 }
393 
394 /*
395  * Start of a "comment".
396  * Ignore it.
397  */
398 char *
399 skip_comment(cp)
400 	char *cp;
401 {
402 	int nesting = 1;
403 
404 	for (; nesting > 0 && *cp; cp++) {
405 		switch (*cp) {
406 		case '\\':
407 			if (cp[1])
408 				cp++;
409 			break;
410 		case '(':
411 			nesting++;
412 			break;
413 		case ')':
414 			nesting--;
415 			break;
416 		}
417 	}
418 	return cp;
419 }
420 
421 /*
422  * Skin an arpa net address according to the RFC 822 interpretation
423  * of "host-phrase."
424  */
425 char *
426 skin(name)
427 	char *name;
428 {
429 	int c;
430 	char *cp, *cp2;
431 	char *bufend;
432 	int gotlt, lastsp;
433 	char nbuf[BUFSIZ];
434 
435 	if (name == NOSTR)
436 		return(NOSTR);
437 	if (strchr(name, '(') == NOSTR && strchr(name, '<') == NOSTR
438 	    && strchr(name, ' ') == NOSTR)
439 		return(name);
440 	gotlt = 0;
441 	lastsp = 0;
442 	bufend = nbuf;
443 	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
444 		switch (c) {
445 		case '(':
446 			cp = skip_comment(cp);
447 			lastsp = 0;
448 			break;
449 
450 		case '"':
451 			/*
452 			 * Start of a "quoted-string".
453 			 * Copy it in its entirety.
454 			 */
455 			while ((c = *cp) != '\0') {
456 				cp++;
457 				if (c == '"')
458 					break;
459 				if (c != '\\')
460 					*cp2++ = c;
461 				else if ((c = *cp) != '\0') {
462 					*cp2++ = c;
463 					cp++;
464 				}
465 			}
466 			lastsp = 0;
467 			break;
468 
469 		case ' ':
470 			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
471 				cp += 3, *cp2++ = '@';
472 			else
473 			if (cp[0] == '@' && cp[1] == ' ')
474 				cp += 2, *cp2++ = '@';
475 			else
476 				lastsp = 1;
477 			break;
478 
479 		case '<':
480 			cp2 = bufend;
481 			gotlt++;
482 			lastsp = 0;
483 			break;
484 
485 		case '>':
486 			if (gotlt) {
487 				gotlt = 0;
488 				while ((c = *cp) && c != ',') {
489 					cp++;
490 					if (c == '(')
491 						cp = skip_comment(cp);
492 					else if (c == '"')
493 						while ((c = *cp) != '\0') {
494 							cp++;
495 							if (c == '"')
496 								break;
497 							if (c == '\\' && *cp)
498 								cp++;
499 						}
500 				}
501 				lastsp = 0;
502 				break;
503 			}
504 			/* Fall into . . . */
505 
506 		default:
507 			if (lastsp) {
508 				lastsp = 0;
509 				*cp2++ = ' ';
510 			}
511 			*cp2++ = c;
512 			if (c == ',' && !gotlt) {
513 				*cp2++ = ' ';
514 				for (; *cp == ' '; cp++)
515 					;
516 				lastsp = 0;
517 				bufend = cp2;
518 			}
519 		}
520 	}
521 	*cp2 = 0;
522 
523 	return(savestr(nbuf));
524 }
525 
526 /*
527  * Fetch the sender's name from the passed message.
528  * Reptype can be
529  *	0 -- get sender's name for display purposes
530  *	1 -- get sender's name for reply
531  *	2 -- get sender's name for Reply
532  */
533 char *
534 name1(mp, reptype)
535 	struct message *mp;
536 	int reptype;
537 {
538 	char namebuf[LINESIZE];
539 	char linebuf[LINESIZE];
540 	char *cp, *cp2;
541 	FILE *ibuf;
542 	int first = 1;
543 
544 	if ((cp = hfield("from", mp)) != NOSTR)
545 		return cp;
546 	if (reptype == 0 && (cp = hfield("sender", mp)) != NOSTR)
547 		return cp;
548 	ibuf = setinput(mp);
549 	namebuf[0] = '\0';
550 	if (readline(ibuf, linebuf, LINESIZE) < 0)
551 		return(savestr(namebuf));
552 newname:
553 	for (cp = linebuf; *cp && *cp != ' '; cp++)
554 		;
555 	for (; *cp == ' ' || *cp == '\t'; cp++)
556 		;
557 	for (cp2 = &namebuf[strlen(namebuf)];
558 	     *cp && *cp != ' ' && *cp != '\t' && cp2 < namebuf + LINESIZE - 1;)
559 		*cp2++ = *cp++;
560 	*cp2 = '\0';
561 	if (readline(ibuf, linebuf, LINESIZE) < 0)
562 		return(savestr(namebuf));
563 	if ((cp = strchr(linebuf, 'F')) == NULL)
564 		return(savestr(namebuf));
565 	if (strncmp(cp, "From", 4) != 0)
566 		return(savestr(namebuf));
567 	while ((cp = strchr(cp, 'r')) != NULL) {
568 		if (strncmp(cp, "remote", 6) == 0) {
569 			if ((cp = strchr(cp, 'f')) == NULL)
570 				break;
571 			if (strncmp(cp, "from", 4) != 0)
572 				break;
573 			if ((cp = strchr(cp, ' ')) == NULL)
574 				break;
575 			cp++;
576 			if (first) {
577 				cp2 = namebuf;
578 				first = 0;
579 			} else
580 				cp2 = strrchr(namebuf, '!') + 1;
581 			while (*cp && cp2 < namebuf + LINESIZE - 1)
582 				*cp2++ = *cp++;
583 			if (cp2 < namebuf + LINESIZE - 1)
584 				*cp2++ = '!';
585 			*cp2 = '\0';
586 			if (cp2 < namebuf + LINESIZE - 1)
587 				goto newname;
588 			else
589 				break;
590 		}
591 		cp++;
592 	}
593 	return(savestr(namebuf));
594 }
595 
596 /*
597  * Count the occurances of c in str
598  */
599 int
600 charcount(str, c)
601 	char *str;
602 	int c;
603 {
604 	char *cp;
605 	int i;
606 
607 	for (i = 0, cp = str; *cp; cp++)
608 		if (*cp == c)
609 			i++;
610 	return(i);
611 }
612 
613 /*
614  * Are any of the characters in the two strings the same?
615  */
616 int
617 anyof(s1, s2)
618 	char *s1, *s2;
619 {
620 
621 	while (*s1)
622 		if (strchr(s2, *s1++))
623 			return 1;
624 	return 0;
625 }
626 
627 /*
628  * Convert c to upper case
629  */
630 int
631 upcase(c)
632 	int c;
633 {
634 
635 	if (islower(c))
636 		return toupper(c);
637 	return c;
638 }
639 
640 /*
641  * Copy s1 to s2, return pointer to null in s2.
642  */
643 char *
644 copy(s1, s2)
645 	char *s1, *s2;
646 {
647 
648 	while ((*s2++ = *s1++) != '\0')
649 		;
650 	return s2 - 1;
651 }
652 
653 /*
654  * See if the given header field is supposed to be ignored.
655  */
656 int
657 isign(field, ignore)
658 	char *field;
659 	struct ignoretab ignore[2];
660 {
661 	char realfld[LINESIZE];
662 
663 	if (ignore == ignoreall)
664 		return 1;
665 	/*
666 	 * Lower-case the string, so that "Status" and "status"
667 	 * will hash to the same place.
668 	 */
669 	istrcpy(realfld, field);
670 	if (ignore[1].i_count > 0)
671 		return (!member(realfld, ignore + 1));
672 	else
673 		return (member(realfld, ignore));
674 }
675 
676 int
677 member(realfield, table)
678 	char *realfield;
679 	struct ignoretab *table;
680 {
681 	struct ignore *igp;
682 
683 	for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
684 		if (*igp->i_field == *realfield &&
685 		    equal(igp->i_field, realfield))
686 			return (1);
687 	return (0);
688 }
689