xref: /openbsd-src/usr.bin/mail/util.c (revision d9a51c353c88dac7b4a389c112b4cfe97b8e3a46)
1 /*	$OpenBSD: util.c,v 1.2 2022/12/26 19:16:01 jmc Exp $	*/
2 /*	$NetBSD: aux.c,v 1.5 1997/05/13 06:15:52 mikel Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include "rcv.h"
34 #include <fcntl.h>
35 #include "extern.h"
36 
37 /*
38  * Mail -- a mail program
39  *
40  * Auxiliary functions.
41  */
42 static char *save2str(char *, char *);
43 
44 /*
45  * Return a pointer to a dynamic copy of the argument.
46  */
47 char *
savestr(const char * str)48 savestr(const char *str)
49 {
50 	char *new;
51 	int size = strlen(str) + 1;
52 
53 	if ((new = salloc(size)) != NULL)
54 		(void)memcpy(new, str, size);
55 	return(new);
56 }
57 
58 /*
59  * Make a copy of new argument incorporating old one.
60  */
61 static char *
save2str(char * str,char * old)62 save2str(char *str, char *old)
63 {
64 	char *new;
65 	int newsize = strlen(str) + 1;
66 	int oldsize = old ? strlen(old) + 1 : 0;
67 
68 	if ((new = salloc(newsize + oldsize)) != NULL) {
69 		if (oldsize) {
70 			(void)memcpy(new, old, oldsize);
71 			new[oldsize - 1] = ' ';
72 		}
73 		(void)memcpy(new + oldsize, str, newsize);
74 	}
75 	return(new);
76 }
77 
78 /*
79  * Touch the named message by setting its MTOUCH flag.
80  * Touched messages have the effect of not being sent
81  * back to the system mailbox on exit.
82  */
83 void
touch(struct message * mp)84 touch(struct message *mp)
85 {
86 
87 	mp->m_flag |= MTOUCH;
88 	if ((mp->m_flag & MREAD) == 0)
89 		mp->m_flag |= MREAD|MSTATUS;
90 }
91 
92 /*
93  * Test to see if the passed file name is a directory.
94  * Return true if it is.
95  */
96 int
isdir(char * name)97 isdir(char *name)
98 {
99 	struct stat sbuf;
100 
101 	if (stat(name, &sbuf) == -1)
102 		return(0);
103 	return(S_ISDIR(sbuf.st_mode));
104 }
105 
106 /*
107  * Count the number of arguments in the given string raw list.
108  */
109 int
argcount(char ** argv)110 argcount(char **argv)
111 {
112 	char **ap;
113 
114 	for (ap = argv; *ap++ != NULL;)
115 		;
116 	return(ap - argv - 1);
117 }
118 
119 /*
120  * Return the desired header line from the passed message
121  * pointer (or NULL if the desired header field is not available).
122  */
123 char *
hfield(char * field,struct message * mp)124 hfield(char *field, struct message *mp)
125 {
126 	FILE *ibuf;
127 	char linebuf[LINESIZE];
128 	int lc;
129 	char *hfield;
130 	char *colon, *oldhfield = NULL;
131 
132 	ibuf = setinput(mp);
133 	if ((lc = mp->m_lines - 1) < 0)
134 		return(NULL);
135 	if (readline(ibuf, linebuf, LINESIZE, NULL) < 0)
136 		return(NULL);
137 	while (lc > 0) {
138 		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
139 			return(oldhfield);
140 		if ((hfield = ishfield(linebuf, colon, field)) != NULL)
141 			oldhfield = save2str(hfield, oldhfield);
142 	}
143 	return(oldhfield);
144 }
145 
146 /*
147  * Return the next header field found in the given message.
148  * Return >= 0 if something found, < 0 elsewise.
149  * "colon" is set to point to the colon in the header.
150  * Must deal with \ continuations & other such fraud.
151  */
152 int
gethfield(FILE * f,char * linebuf,int rem,char ** colon)153 gethfield(FILE *f, char *linebuf, int rem, char **colon)
154 {
155 	char line2[LINESIZE];
156 	char *cp, *cp2;
157 	int c;
158 
159 	for (;;) {
160 		if (--rem < 0)
161 			return(-1);
162 		if ((c = readline(f, linebuf, LINESIZE, NULL)) <= 0)
163 			return(-1);
164 		for (cp = linebuf;
165 		    isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
166 		    cp++)
167 			;
168 		if (*cp != ':' || cp == linebuf)
169 			continue;
170 		/*
171 		 * I guess we got a headline.
172 		 * Handle wraparounding
173 		 */
174 		*colon = cp;
175 		cp = linebuf + c;
176 		for (;;) {
177 			while (--cp >= linebuf && (*cp == ' ' || *cp == '\t'))
178 				;
179 			cp++;
180 			if (rem <= 0)
181 				break;
182 			ungetc(c = getc(f), f);
183 			if (c != ' ' && c != '\t')
184 				break;
185 			if ((c = readline(f, line2, LINESIZE, NULL)) < 0)
186 				break;
187 			rem--;
188 			for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++)
189 				;
190 			c -= cp2 - line2;
191 			if (cp + c >= linebuf + LINESIZE - 2)
192 				break;
193 			*cp++ = ' ';
194 			(void)memcpy(cp, cp2, c);
195 			cp += c;
196 		}
197 		*cp = 0;
198 		return(rem);
199 	}
200 	/* NOTREACHED */
201 }
202 
203 /*
204  * Check whether the passed line is a header line of
205  * the desired breed.  Return the field body, or 0.
206  */
207 
208 char*
ishfield(char * linebuf,char * colon,char * field)209 ishfield(char *linebuf, char *colon, char *field)
210 {
211 	char *cp = colon;
212 
213 	*cp = 0;
214 	if (strcasecmp(linebuf, field) != 0) {
215 		*cp = ':';
216 		return(0);
217 	}
218 	*cp = ':';
219 	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
220 		;
221 	return(cp);
222 }
223 
224 /*
225  * Copy a string, lowercasing it as we go.  ``dsize'' should be
226  * the real size (not len) of the dest string (guarantee NUL term).
227  */
228 size_t
istrlcpy(char * dst,const char * src,size_t dsize)229 istrlcpy(char *dst, const char *src, size_t dsize)
230 {
231 	char *d = dst;
232 	const char *s = src;
233 	size_t n = dsize;
234 
235 	/* Copy as many bytes as will fit */
236 	if (n != 0 && --n != 0) {
237 		do {
238 			if ((*d++ = tolower((unsigned char)*s++)) == 0)
239 				break;
240 		} while (--n != 0);
241 	}
242 
243 	/* Not enough room in dst, add NUL and traverse rest of src */
244 	if (n == 0) {
245 		if (dsize != 0)
246 			*d = '\0';		/* NUL-terminate dst */
247 		while (*s++)
248 			;
249 	}
250 
251 	return(s - src - 1);	/* count does not include NUL */
252 }
253 
254 /*
255  * The following code deals with input stacking to do source
256  * commands.  All but the current file pointer are saved on
257  * the stack.
258  */
259 static	int	ssp;			/* Top of file stack */
260 struct sstack {
261 	FILE	*s_file;		/* File we were in. */
262 	int	s_cond;			/* Saved state of conditionals */
263 	int	s_loading;		/* Loading .mailrc, etc. */
264 } sstack[OPEN_MAX];
265 
266 /*
267  * Pushdown current input file and switch to a new one.
268  * Set the global flag "sourcing" so that others will realize
269  * that they are no longer reading from a tty (in all probability).
270  */
271 int
source(void * v)272 source(void *v)
273 {
274 	char **arglist = v;
275 	FILE *fi;
276 	char *cp;
277 
278 	if ((cp = expand(*arglist)) == NULL)
279 		return(1);
280 	if ((fi = Fopen(cp, "r")) == NULL) {
281 		warn("%s", cp);
282 		return(1);
283 	}
284 	if (ssp >= OPEN_MAX - 1) {
285 		puts("Too much \"sourcing\" going on.");
286 		(void)Fclose(fi);
287 		return(1);
288 	}
289 	sstack[ssp].s_file = input;
290 	sstack[ssp].s_cond = cond;
291 	sstack[ssp].s_loading = loading;
292 	ssp++;
293 	loading = 0;
294 	cond = CANY;
295 	input = fi;
296 	sourcing++;
297 	return(0);
298 }
299 
300 /*
301  * Pop the current input back to the previous level.
302  * Update the "sourcing" flag as appropriate.
303  */
304 int
unstack(void)305 unstack(void)
306 {
307 
308 	if (ssp <= 0) {
309 		puts("\"Source\" stack over-pop.");
310 		sourcing = 0;
311 		return(1);
312 	}
313 	(void)Fclose(input);
314 	if (cond != CANY)
315 		puts("Unmatched \"if\"");
316 	ssp--;
317 	cond = sstack[ssp].s_cond;
318 	loading = sstack[ssp].s_loading;
319 	input = sstack[ssp].s_file;
320 	if (ssp == 0)
321 		sourcing = loading;
322 	return(0);
323 }
324 
325 /*
326  * Touch the indicated file.
327  * This is nifty for the shell.
328  */
329 void
alter(char * name)330 alter(char *name)
331 {
332 	struct timespec ts[2];
333 
334 	clock_gettime(CLOCK_REALTIME, &ts[0]);
335 	ts[0].tv_sec++;
336 	ts[1].tv_nsec = UTIME_OMIT;
337 	(void)utimensat(AT_FDCWD, name, ts, 0);
338 }
339 
340 /*
341  * Examine the passed line buffer and
342  * return true if it is all blanks and tabs.
343  */
344 int
blankline(char * linebuf)345 blankline(char *linebuf)
346 {
347 	char *cp;
348 
349 	for (cp = linebuf; *cp; cp++)
350 		if (*cp != ' ' && *cp != '\t')
351 			return(0);
352 	return(1);
353 }
354 
355 /*
356  * Get sender's name from this message.  If the message has
357  * a bunch of arpanet stuff in it, we may have to skin the name
358  * before returning it.
359  */
360 char *
nameof(struct message * mp,int reptype)361 nameof(struct message *mp, int reptype)
362 {
363 	char *cp, *cp2;
364 
365 	cp = skin(name1(mp, reptype));
366 	if (reptype != 0 || charcount(cp, '!') < 2)
367 		return(cp);
368 	cp2 = strrchr(cp, '!');
369 	cp2--;
370 	while (cp2 > cp && *cp2 != '!')
371 		cp2--;
372 	if (*cp2 == '!')
373 		return(cp2 + 1);
374 	return(cp);
375 }
376 
377 /*
378  * Start of a "comment".
379  * Ignore it.
380  */
381 char *
skip_comment(char * cp)382 skip_comment(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 *
skin(char * name)408 skin(char *name)
409 {
410 	char *nbuf, *bufend, *cp, *cp2;
411 	int c, gotlt, lastsp;
412 
413 	if (name == NULL)
414 		return(NULL);
415 	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
416 	    && strchr(name, ' ') == NULL)
417 		return(name);
418 
419 	/* We assume that length(input) <= length(output) */
420 	if ((nbuf = malloc(strlen(name) + 1)) == NULL)
421 		err(1, "malloc");
422 	gotlt = 0;
423 	lastsp = 0;
424 	bufend = nbuf;
425 	for (cp = name, cp2 = bufend; (c = (unsigned char)*cp++) != '\0'; ) {
426 		switch (c) {
427 		case '(':
428 			cp = skip_comment(cp);
429 			lastsp = 0;
430 			break;
431 
432 		case '"':
433 			/*
434 			 * Start of a "quoted-string".
435 			 * Copy it in its entirety.
436 			 */
437 			while ((c = (unsigned char)*cp) != '\0') {
438 				cp++;
439 				if (c == '"')
440 					break;
441 				if (c != '\\')
442 					*cp2++ = c;
443 				else if ((c = (unsigned char)*cp) != '\0') {
444 					*cp2++ = c;
445 					cp++;
446 				}
447 			}
448 			lastsp = 0;
449 			break;
450 
451 		case ' ':
452 			if (strncmp(cp, "at ", 3) == 0)
453 				cp += 3, *cp2++ = '@';
454 			else
455 			if (strncmp(cp, "@ ", 2) == 0)
456 				cp += 2, *cp2++ = '@';
457 			else
458 				lastsp = 1;
459 			break;
460 
461 		case '<':
462 			cp2 = bufend;
463 			gotlt++;
464 			lastsp = 0;
465 			break;
466 
467 		case '>':
468 			if (gotlt) {
469 				gotlt = 0;
470 				while ((c = (unsigned char)*cp) && c != ',') {
471 					cp++;
472 					if (c == '(')
473 						cp = skip_comment(cp);
474 					else if (c == '"')
475 						while ((c = (unsigned char)*cp) != '\0') {
476 							cp++;
477 							if (c == '"')
478 								break;
479 							if (c == '\\' && *cp)
480 								cp++;
481 						}
482 				}
483 				lastsp = 0;
484 				break;
485 			}
486 			/* Fall into . . . */
487 
488 		default:
489 			if (lastsp) {
490 				lastsp = 0;
491 				*cp2++ = ' ';
492 			}
493 			*cp2++ = c;
494 			if (c == ',' && *cp == ' ' && !gotlt) {
495 				*cp2++ = ' ';
496 				while (*++cp == ' ')
497 					;
498 				lastsp = 0;
499 				bufend = cp2;
500 			}
501 		}
502 	}
503 	*cp2 = 0;
504 
505 	if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
506 		nbuf = cp;
507 	return(nbuf);
508 }
509 
510 /*
511  * Fetch the sender's name from the passed message.
512  * Reptype can be
513  *	0 -- get sender's name for display purposes
514  *	1 -- get sender's name for reply
515  *	2 -- get sender's name for Reply
516  */
517 char *
name1(struct message * mp,int reptype)518 name1(struct message *mp, int reptype)
519 {
520 	char namebuf[LINESIZE];
521 	char linebuf[LINESIZE];
522 	char *cp, *cp2;
523 	FILE *ibuf;
524 	int first = 1;
525 
526 	if ((cp = hfield("from", mp)) != NULL)
527 		return(cp);
528 	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
529 		return(cp);
530 	ibuf = setinput(mp);
531 	namebuf[0] = '\0';
532 	if (readline(ibuf, linebuf, LINESIZE, NULL) < 0)
533 		return(savestr(namebuf));
534 newname:
535 	for (cp = linebuf; *cp && *cp != ' '; cp++)
536 		;
537 	for (; *cp == ' ' || *cp == '\t'; cp++)
538 		;
539 	for (cp2 = &namebuf[strlen(namebuf)];
540 	     *cp && *cp != ' ' && *cp != '\t' && cp2 < namebuf + LINESIZE - 1;)
541 		*cp2++ = *cp++;
542 	*cp2 = '\0';
543 	if (readline(ibuf, linebuf, LINESIZE, NULL) < 0)
544 		return(savestr(namebuf));
545 	if ((cp = strchr(linebuf, 'F')) == NULL)
546 		return(savestr(namebuf));
547 	if (strncmp(cp, "From", 4) != 0)
548 		return(savestr(namebuf));
549 	while ((cp = strchr(cp, 'r')) != NULL) {
550 		if (strncmp(cp, "remote", 6) == 0) {
551 			if ((cp = strchr(cp, 'f')) == NULL)
552 				break;
553 			if (strncmp(cp, "from", 4) != 0)
554 				break;
555 			if ((cp = strchr(cp, ' ')) == NULL)
556 				break;
557 			cp++;
558 			if (first) {
559 				cp2 = namebuf;
560 				first = 0;
561 			} else
562 				cp2 = strrchr(namebuf, '!') + 1;
563 			strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
564 			strlcat(namebuf, "!", sizeof(namebuf));
565 			goto newname;
566 		}
567 		cp++;
568 	}
569 	return(savestr(namebuf));
570 }
571 
572 /*
573  * Count the occurrences of c in str
574  */
575 int
charcount(char * str,int c)576 charcount(char *str, int c)
577 {
578 	char *cp;
579 	int i;
580 
581 	for (i = 0, cp = str; *cp; cp++)
582 		if (*cp == c)
583 			i++;
584 	return(i);
585 }
586 
587 /*
588  * Copy s1 to s2, return pointer to null in s2.
589  */
590 char *
copy(char * s1,char * s2)591 copy(char *s1, char *s2)
592 {
593 
594 	while ((*s2++ = *s1++) != '\0')
595 		;
596 	return(s2 - 1);
597 }
598 
599 /*
600  * See if the given header field is supposed to be ignored.
601  */
602 int
isign(char * field,struct ignoretab ignore[2])603 isign(char *field, 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 	istrlcpy(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
member(char * realfield,struct ignoretab * table)621 member(char *realfield, struct ignoretab *table)
622 {
623 	struct ignore *igp;
624 
625 	for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
626 		if (*igp->i_field == *realfield &&
627 		    equal(igp->i_field, realfield))
628 			return(1);
629 	return(0);
630 }
631 
632 void
clearnew(void)633 clearnew(void)
634 {
635 	struct message *mp;
636 
637 	for (mp = &message[0]; mp < &message[msgCount]; mp++) {
638 		if (mp->m_flag & MNEW) {
639 			mp->m_flag &= ~MNEW;
640 			mp->m_flag |= MSTATUS;
641 		}
642 	}
643 }
644