xref: /freebsd/usr.bin/mail/util.c (revision 0e1497aefd602cea581d2380d22e67dfdcac6b4e)
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  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #ifndef lint
31 #if 0
32 static char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
33 #endif
34 #endif /* not lint */
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 #include <sys/time.h>
39 
40 #include "rcv.h"
41 #include "extern.h"
42 
43 /*
44  * Mail -- a mail program
45  *
46  * Auxiliary functions.
47  */
48 
49 static char *save2str(char *, char *);
50 
51 /*
52  * Return a pointer to a dynamic copy of the argument.
53  */
54 char *
55 savestr(str)
56 	char *str;
57 {
58 	char *new;
59 	int size = strlen(str) + 1;
60 
61 	if ((new = salloc(size)) != NULL)
62 		bcopy(str, new, size);
63 	return (new);
64 }
65 
66 /*
67  * Make a copy of new argument incorporating old one.
68  */
69 static char *
70 save2str(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)) != NULL) {
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  */
92 void
93 touch(mp)
94 	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  */
106 int
107 isdir(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  */
120 int
121 argcount(argv)
122 	char **argv;
123 {
124 	char **ap;
125 
126 	for (ap = argv; *ap++ != NULL;)
127 		;
128 	return (ap - argv - 1);
129 }
130 
131 /*
132  * Return the desired header line from the passed message
133  * pointer (or NULL if the desired header field is not available).
134  */
135 char *
136 hfield(field, mp)
137 	const char *field;
138 	struct message *mp;
139 {
140 	FILE *ibuf;
141 	char linebuf[LINESIZE];
142 	int lc;
143 	char *hfield;
144 	char *colon, *oldhfield = NULL;
145 
146 	ibuf = setinput(mp);
147 	if ((lc = mp->m_lines - 1) < 0)
148 		return (NULL);
149 	if (readline(ibuf, linebuf, LINESIZE) < 0)
150 		return (NULL);
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  */
166 int
167 gethfield(f, linebuf, rem, colon)
168 	FILE *f;
169 	char linebuf[];
170 	int rem;
171 	char **colon;
172 {
173 	char line2[LINESIZE];
174 	char *cp, *cp2;
175 	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((unsigned char)*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 
225 char*
226 ishfield(linebuf, colon, field)
227 	char linebuf[];
228 	char *colon;
229 	const char *field;
230 {
231 	char *cp = colon;
232 
233 	*cp = 0;
234 	if (strcasecmp(linebuf, field) != 0) {
235 		*cp = ':';
236 		return (0);
237 	}
238 	*cp = ':';
239 	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
240 		;
241 	return (cp);
242 }
243 
244 /*
245  * Copy a string and lowercase the result.
246  * dsize: space left in buffer (including space for NULL)
247  */
248 void
249 istrncpy(dest, src, dsize)
250 	char *dest;
251 	const char *src;
252 	size_t dsize;
253 {
254 
255 	strlcpy(dest, src, dsize);
256 	while (*dest)
257 		*dest++ = tolower((unsigned char)*dest);
258 }
259 
260 /*
261  * The following code deals with input stacking to do source
262  * commands.  All but the current file pointer are saved on
263  * the stack.
264  */
265 
266 static	int	ssp;			/* Top of file stack */
267 struct sstack {
268 	FILE	*s_file;		/* File we were in. */
269 	int	s_cond;			/* Saved state of conditionals */
270 	int	s_loading;		/* Loading .mailrc, etc. */
271 };
272 #define	SSTACK_SIZE	64		/* XXX was NOFILE. */
273 static struct sstack sstack[SSTACK_SIZE];
274 
275 /*
276  * Pushdown current input file and switch to a new one.
277  * Set the global flag "sourcing" so that others will realize
278  * that they are no longer reading from a tty (in all probability).
279  */
280 int
281 source(arglist)
282 	char **arglist;
283 {
284 	FILE *fi;
285 	char *cp;
286 
287 	if ((cp = expand(*arglist)) == NULL)
288 		return (1);
289 	if ((fi = Fopen(cp, "r")) == NULL) {
290 		warn("%s", cp);
291 		return (1);
292 	}
293 	if (ssp >= SSTACK_SIZE - 1) {
294 		printf("Too much \"sourcing\" going on.\n");
295 		(void)Fclose(fi);
296 		return (1);
297 	}
298 	sstack[ssp].s_file = input;
299 	sstack[ssp].s_cond = cond;
300 	sstack[ssp].s_loading = loading;
301 	ssp++;
302 	loading = 0;
303 	cond = CANY;
304 	input = fi;
305 	sourcing++;
306 	return (0);
307 }
308 
309 /*
310  * Pop the current input back to the previous level.
311  * Update the "sourcing" flag as appropriate.
312  */
313 int
314 unstack()
315 {
316 	if (ssp <= 0) {
317 		printf("\"Source\" stack over-pop.\n");
318 		sourcing = 0;
319 		return (1);
320 	}
321 	(void)Fclose(input);
322 	if (cond != CANY)
323 		printf("Unmatched \"if\"\n");
324 	ssp--;
325 	cond = sstack[ssp].s_cond;
326 	loading = sstack[ssp].s_loading;
327 	input = sstack[ssp].s_file;
328 	if (ssp == 0)
329 		sourcing = loading;
330 	return (0);
331 }
332 
333 /*
334  * Touch the indicated file.
335  * This is nifty for the shell.
336  */
337 void
338 alter(name)
339 	char *name;
340 {
341 	struct stat sb;
342 	struct timeval tv[2];
343 
344 	if (stat(name, &sb))
345 		return;
346 	(void)gettimeofday(&tv[0], (struct timezone *)NULL);
347 	tv[0].tv_sec++;
348 	TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim);
349 	(void)utimes(name, tv);
350 }
351 
352 /*
353  * Get sender's name from this message.  If the message has
354  * a bunch of arpanet stuff in it, we may have to skin the name
355  * before returning it.
356  */
357 char *
358 nameof(mp, reptype)
359 	struct message *mp;
360 	int reptype;
361 {
362 	char *cp, *cp2;
363 
364 	cp = skin(name1(mp, reptype));
365 	if (reptype != 0 || charcount(cp, '!') < 2)
366 		return (cp);
367 	cp2 = strrchr(cp, '!');
368 	cp2--;
369 	while (cp2 > cp && *cp2 != '!')
370 		cp2--;
371 	if (*cp2 == '!')
372 		return (cp2 + 1);
373 	return (cp);
374 }
375 
376 /*
377  * Start of a "comment".
378  * Ignore it.
379  */
380 char *
381 skip_comment(cp)
382 	char *cp;
383 {
384 	int nesting = 1;
385 
386 	for (; nesting > 0 && *cp; cp++) {
387 		switch (*cp) {
388 		case '\\':
389 			if (cp[1])
390 				cp++;
391 			break;
392 		case '(':
393 			nesting++;
394 			break;
395 		case ')':
396 			nesting--;
397 			break;
398 		}
399 	}
400 	return (cp);
401 }
402 
403 /*
404  * Skin an arpa net address according to the RFC 822 interpretation
405  * of "host-phrase."
406  */
407 char *
408 skin(name)
409 	char *name;
410 {
411 	char *nbuf, *bufend, *cp, *cp2;
412 	int c, gotlt, lastsp;
413 
414 	if (name == NULL)
415 		return (NULL);
416 	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
417 	    && strchr(name, ' ') == NULL)
418 		return (name);
419 
420 	/* We assume that length(input) <= length(output) */
421 	if ((nbuf = malloc(strlen(name) + 1)) == NULL)
422 		err(1, "Out of memory");
423 	gotlt = 0;
424 	lastsp = 0;
425 	bufend = nbuf;
426 	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
427 		switch (c) {
428 		case '(':
429 			cp = skip_comment(cp);
430 			lastsp = 0;
431 			break;
432 
433 		case '"':
434 			/*
435 			 * Start of a "quoted-string".
436 			 * Copy it in its entirety.
437 			 */
438 			while ((c = *cp) != '\0') {
439 				cp++;
440 				if (c == '"')
441 					break;
442 				if (c != '\\')
443 					*cp2++ = c;
444 				else if ((c = *cp) != '\0') {
445 					*cp2++ = c;
446 					cp++;
447 				}
448 			}
449 			lastsp = 0;
450 			break;
451 
452 		case ' ':
453 			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
454 				cp += 3, *cp2++ = '@';
455 			else
456 			if (cp[0] == '@' && cp[1] == ' ')
457 				cp += 2, *cp2++ = '@';
458 			else
459 				lastsp = 1;
460 			break;
461 
462 		case '<':
463 			cp2 = bufend;
464 			gotlt++;
465 			lastsp = 0;
466 			break;
467 
468 		case '>':
469 			if (gotlt) {
470 				gotlt = 0;
471 				while ((c = *cp) != '\0' && c != ',') {
472 					cp++;
473 					if (c == '(')
474 						cp = skip_comment(cp);
475 					else if (c == '"')
476 						while ((c = *cp) != '\0') {
477 							cp++;
478 							if (c == '"')
479 								break;
480 							if (c == '\\' && *cp != '\0')
481 								cp++;
482 						}
483 				}
484 				lastsp = 0;
485 				break;
486 			}
487 			/* FALLTHROUGH */
488 
489 		default:
490 			if (lastsp) {
491 				lastsp = 0;
492 				*cp2++ = ' ';
493 			}
494 			*cp2++ = c;
495 			if (c == ',' && !gotlt &&
496 			    (*cp == ' ' || *cp == '"' || *cp == '<')) {
497 				*cp2++ = ' ';
498 				while (*cp == ' ')
499 					cp++;
500 				lastsp = 0;
501 				bufend = cp2;
502 			}
503 		}
504 	}
505 	*cp2 = '\0';
506 
507 	if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
508 		nbuf = cp;
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  */
519 char *
520 name1(mp, reptype)
521 	struct message *mp;
522 	int reptype;
523 {
524 	char namebuf[LINESIZE];
525 	char linebuf[LINESIZE];
526 	char *cp, *cp2;
527 	FILE *ibuf;
528 	int first = 1;
529 
530 	if ((cp = hfield("from", mp)) != NULL)
531 		return (cp);
532 	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
533 		return (cp);
534 	ibuf = setinput(mp);
535 	namebuf[0] = '\0';
536 	if (readline(ibuf, linebuf, LINESIZE) < 0)
537 		return (savestr(namebuf));
538 newname:
539 	for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++)
540 		;
541 	for (; *cp == ' ' || *cp == '\t'; cp++)
542 		;
543 	for (cp2 = &namebuf[strlen(namebuf)];
544 	    *cp != '\0' && *cp != ' ' && *cp != '\t' &&
545 	    cp2 < namebuf + LINESIZE - 1;)
546 		*cp2++ = *cp++;
547 	*cp2 = '\0';
548 	if (readline(ibuf, linebuf, LINESIZE) < 0)
549 		return (savestr(namebuf));
550 	if ((cp = strchr(linebuf, 'F')) == NULL)
551 		return (savestr(namebuf));
552 	if (strncmp(cp, "From", 4) != 0)
553 		return (savestr(namebuf));
554 	while ((cp = strchr(cp, 'r')) != NULL) {
555 		if (strncmp(cp, "remote", 6) == 0) {
556 			if ((cp = strchr(cp, 'f')) == NULL)
557 				break;
558 			if (strncmp(cp, "from", 4) != 0)
559 				break;
560 			if ((cp = strchr(cp, ' ')) == NULL)
561 				break;
562 			cp++;
563 			if (first) {
564 				cp2 = namebuf;
565 				first = 0;
566 			} else
567 				cp2 = strrchr(namebuf, '!') + 1;
568 			strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
569 			strcat(namebuf, "!");
570 			goto newname;
571 		}
572 		cp++;
573 	}
574 	return (savestr(namebuf));
575 }
576 
577 /*
578  * Count the occurances of c in str
579  */
580 int
581 charcount(str, c)
582 	char *str;
583 	int c;
584 {
585 	char *cp;
586 	int i;
587 
588 	for (i = 0, cp = str; *cp != '\0'; cp++)
589 		if (*cp == c)
590 			i++;
591 	return (i);
592 }
593 
594 /*
595  * See if the given header field is supposed to be ignored.
596  */
597 int
598 isign(field, ignore)
599 	const char *field;
600 	struct ignoretab ignore[2];
601 {
602 	char realfld[LINESIZE];
603 
604 	if (ignore == ignoreall)
605 		return (1);
606 	/*
607 	 * Lower-case the string, so that "Status" and "status"
608 	 * will hash to the same place.
609 	 */
610 	istrncpy(realfld, field, sizeof(realfld));
611 	if (ignore[1].i_count > 0)
612 		return (!member(realfld, ignore + 1));
613 	else
614 		return (member(realfld, ignore));
615 }
616 
617 int
618 member(realfield, table)
619 	char *realfield;
620 	struct ignoretab *table;
621 {
622 	struct ignore *igp;
623 
624 	for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link)
625 		if (*igp->i_field == *realfield &&
626 		    equal(igp->i_field, realfield))
627 			return (1);
628 	return (0);
629 }
630