xref: /freebsd/usr.bin/mail/util.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
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
36 static char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
37 #endif
38 #endif /* not lint */
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
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 
53 static char *save2str(char *, char *);
54 
55 /*
56  * Return a pointer to a dynamic copy of the argument.
57  */
58 char *
59 savestr(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  */
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)) != 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  */
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++ != 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  */
139 char *
140 hfield(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  */
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 			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 
229 char*
230 ishfield(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  */
252 void
253 istrncpy(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 
270 static	int	ssp;			/* Top of file stack */
271 struct 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. */
277 static 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  */
284 int
285 source(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  */
317 int
318 unstack()
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  */
341 void
342 alter(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  */
361 char *
362 nameof(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  */
384 char *
385 skip_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  */
411 char *
412 skin(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  */
522 char *
523 name1(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));
541 newname:
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  */
583 int
584 charcount(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  */
600 int
601 isign(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 
620 int
621 member(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