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