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