xref: /freebsd/usr.bin/mail/util.c (revision 06c3fb2749bda94cb5201f81ffdb8fa6c3161b2e)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
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. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/time.h>
33 
34 #include <fcntl.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 static 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 
90 	mp->m_flag |= MTOUCH;
91 	if ((mp->m_flag & MREAD) == 0)
92 		mp->m_flag |= MREAD|MSTATUS;
93 }
94 
95 /*
96  * Test to see if the passed file name is a directory.
97  * Return true if it is.
98  */
99 int
100 isdir(char name[])
101 {
102 	struct stat sbuf;
103 
104 	if (stat(name, &sbuf) < 0)
105 		return (0);
106 	return (S_ISDIR(sbuf.st_mode));
107 }
108 
109 /*
110  * Count the number of arguments in the given string raw list.
111  */
112 int
113 argcount(char **argv)
114 {
115 	char **ap;
116 
117 	for (ap = argv; *ap++ != NULL;)
118 		;
119 	return (ap - argv - 1);
120 }
121 
122 /*
123  * Return the desired header line from the passed message
124  * pointer (or NULL if the desired header field is not available).
125  */
126 char *
127 hfield(const char *field, struct message *mp)
128 {
129 	FILE *ibuf;
130 	char linebuf[LINESIZE];
131 	int lc;
132 	char *hfield;
133 	char *colon, *oldhfield = NULL;
134 
135 	ibuf = setinput(mp);
136 	if ((lc = mp->m_lines - 1) < 0)
137 		return (NULL);
138 	if (readline(ibuf, linebuf, LINESIZE) < 0)
139 		return (NULL);
140 	while (lc > 0) {
141 		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
142 			return (oldhfield);
143 		if ((hfield = ishfield(linebuf, colon, field)) != NULL)
144 			oldhfield = save2str(hfield, oldhfield);
145 	}
146 	return (oldhfield);
147 }
148 
149 /*
150  * Return the next header field found in the given message.
151  * Return >= 0 if something found, < 0 elsewise.
152  * "colon" is set to point to the colon in the header.
153  * Must deal with \ continuations & other such fraud.
154  */
155 int
156 gethfield(FILE *f, char linebuf[], int rem, char **colon)
157 {
158 	char line2[LINESIZE];
159 	char *cp, *cp2;
160 	int c;
161 
162 	for (;;) {
163 		if (--rem < 0)
164 			return (-1);
165 		if ((c = readline(f, linebuf, LINESIZE)) <= 0)
166 			return (-1);
167 		for (cp = linebuf; isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
168 		    cp++)
169 			;
170 		if (*cp != ':' || cp == linebuf)
171 			continue;
172 		/*
173 		 * I guess we got a headline.
174 		 * Handle wraparounding
175 		 */
176 		*colon = cp;
177 		cp = linebuf + c;
178 		for (;;) {
179 			while (--cp >= linebuf && (*cp == ' ' || *cp == '\t'))
180 				;
181 			cp++;
182 			if (rem <= 0)
183 				break;
184 			ungetc(c = getc(f), f);
185 			if (c != ' ' && c != '\t')
186 				break;
187 			if ((c = readline(f, line2, LINESIZE)) < 0)
188 				break;
189 			rem--;
190 			for (cp2 = line2; *cp2 == ' ' || *cp2 == '\t'; cp2++)
191 				;
192 			c -= cp2 - line2;
193 			if (cp + c >= linebuf + LINESIZE - 2)
194 				break;
195 			*cp++ = ' ';
196 			bcopy(cp2, cp, c);
197 			cp += c;
198 		}
199 		*cp = 0;
200 		return (rem);
201 	}
202 	/* NOTREACHED */
203 }
204 
205 /*
206  * Check whether the passed line is a header line of
207  * the desired breed.  Return the field body, or 0.
208  */
209 
210 char*
211 ishfield(char *linebuf, char *colon, const char *field)
212 {
213 	char *cp = colon;
214 
215 	*cp = 0;
216 	if (strcasecmp(linebuf, field) != 0) {
217 		*cp = ':';
218 		return (0);
219 	}
220 	*cp = ':';
221 	for (cp++; *cp == ' ' || *cp == '\t'; cp++)
222 		;
223 	return (cp);
224 }
225 
226 /*
227  * Copy a string and lowercase the result.
228  * dsize: space left in buffer (including space for NULL)
229  */
230 void
231 istrncpy(char *dest, const char *src, size_t dsize)
232 {
233 
234 	strlcpy(dest, src, dsize);
235 	for (; *dest; dest++)
236 		*dest = tolower((unsigned char)*dest);
237 }
238 
239 /*
240  * The following code deals with input stacking to do source
241  * commands.  All but the current file pointer are saved on
242  * the stack.
243  */
244 
245 static	int	ssp;			/* Top of file stack */
246 struct sstack {
247 	FILE	*s_file;		/* File we were in. */
248 	int	s_cond;			/* Saved state of conditionals */
249 	int	s_loading;		/* Loading .mailrc, etc. */
250 };
251 #define	SSTACK_SIZE	64		/* XXX was NOFILE. */
252 static struct sstack sstack[SSTACK_SIZE];
253 
254 /*
255  * Pushdown current input file and switch to a new one.
256  * Set the global flag "sourcing" so that others will realize
257  * that they are no longer reading from a tty (in all probability).
258  */
259 int
260 source(void *arg)
261 {
262 	char **arglist = arg;
263 	FILE *fi;
264 	char *cp;
265 
266 	if ((cp = expand(*arglist)) == NULL)
267 		return (1);
268 	if ((fi = Fopen(cp, "r")) == NULL) {
269 		warn("%s", cp);
270 		return (1);
271 	}
272 	if (ssp >= SSTACK_SIZE - 1) {
273 		printf("Too much \"sourcing\" going on.\n");
274 		(void)Fclose(fi);
275 		return (1);
276 	}
277 	sstack[ssp].s_file = input;
278 	sstack[ssp].s_cond = cond;
279 	sstack[ssp].s_loading = loading;
280 	ssp++;
281 	loading = 0;
282 	cond = CANY;
283 	input = fi;
284 	sourcing++;
285 	return (0);
286 }
287 
288 /*
289  * Pop the current input back to the previous level.
290  * Update the "sourcing" flag as appropriate.
291  */
292 int
293 unstack(void)
294 {
295 	if (ssp <= 0) {
296 		printf("\"Source\" stack over-pop.\n");
297 		sourcing = 0;
298 		return (1);
299 	}
300 	(void)Fclose(input);
301 	if (cond != CANY)
302 		printf("Unmatched \"if\"\n");
303 	ssp--;
304 	cond = sstack[ssp].s_cond;
305 	loading = sstack[ssp].s_loading;
306 	input = sstack[ssp].s_file;
307 	if (ssp == 0)
308 		sourcing = loading;
309 	return (0);
310 }
311 
312 /*
313  * Touch the indicated file.
314  * This is nifty for the shell.
315  */
316 void
317 alter(char *name)
318 {
319 	struct timespec ts[2];
320 
321 	(void)clock_gettime(CLOCK_REALTIME, &ts[0]);
322 	ts[0].tv_sec++;
323 	ts[1].tv_sec = 0;
324 	ts[1].tv_nsec = UTIME_OMIT;
325 	(void)utimensat(AT_FDCWD, name, ts, 0);
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 == ',' && !gotlt &&
468 			    (*cp == ' ' || *cp == '"' || *cp == '<')) {
469 				*cp2++ = ' ';
470 				while (*cp == ' ')
471 					cp++;
472 				lastsp = 0;
473 				bufend = cp2;
474 			}
475 		}
476 	}
477 	*cp2 = '\0';
478 
479 	if ((cp = realloc(nbuf, strlen(nbuf) + 1)) != NULL)
480 		nbuf = cp;
481 	return (nbuf);
482 }
483 
484 /*
485  * Fetch the sender's name from the passed message.
486  * Reptype can be
487  *	0 -- get sender's name for display purposes
488  *	1 -- get sender's name for reply
489  *	2 -- get sender's name for Reply
490  */
491 char *
492 name1(struct message *mp, int reptype)
493 {
494 	char namebuf[LINESIZE];
495 	char linebuf[LINESIZE];
496 	char *cp, *cp2;
497 	FILE *ibuf;
498 	int first = 1;
499 
500 	if ((cp = hfield("from", mp)) != NULL)
501 		return (cp);
502 	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
503 		return (cp);
504 	ibuf = setinput(mp);
505 	namebuf[0] = '\0';
506 	if (readline(ibuf, linebuf, LINESIZE) < 0)
507 		return (savestr(namebuf));
508 newname:
509 	for (cp = linebuf; *cp != '\0' && *cp != ' '; cp++)
510 		;
511 	for (; *cp == ' ' || *cp == '\t'; cp++)
512 		;
513 	for (cp2 = &namebuf[strlen(namebuf)];
514 	    *cp != '\0' && *cp != ' ' && *cp != '\t' &&
515 	    cp2 < namebuf + LINESIZE - 1;)
516 		*cp2++ = *cp++;
517 	*cp2 = '\0';
518 	if (readline(ibuf, linebuf, LINESIZE) < 0)
519 		return (savestr(namebuf));
520 	if ((cp = strchr(linebuf, 'F')) == NULL)
521 		return (savestr(namebuf));
522 	if (strncmp(cp, "From", 4) != 0)
523 		return (savestr(namebuf));
524 	while ((cp = strchr(cp, 'r')) != NULL) {
525 		if (strncmp(cp, "remote", 6) == 0) {
526 			if ((cp = strchr(cp, 'f')) == NULL)
527 				break;
528 			if (strncmp(cp, "from", 4) != 0)
529 				break;
530 			if ((cp = strchr(cp, ' ')) == NULL)
531 				break;
532 			cp++;
533 			if (first) {
534 				cp2 = namebuf;
535 				first = 0;
536 			} else
537 				cp2 = strrchr(namebuf, '!') + 1;
538 			strlcpy(cp2, cp, sizeof(namebuf) - (cp2 - namebuf) - 1);
539 			strcat(namebuf, "!");
540 			goto newname;
541 		}
542 		cp++;
543 	}
544 	return (savestr(namebuf));
545 }
546 
547 /*
548  * Count the occurrences of c in str
549  */
550 int
551 charcount(char *str, int c)
552 {
553 	char *cp;
554 	int i;
555 
556 	for (i = 0, cp = str; *cp != '\0'; cp++)
557 		if (*cp == c)
558 			i++;
559 	return (i);
560 }
561 
562 /*
563  * See if the given header field is supposed to be ignored.
564  */
565 int
566 isign(const char *field, struct ignoretab ignore[2])
567 {
568 	char realfld[LINESIZE];
569 
570 	if (ignore == ignoreall)
571 		return (1);
572 	/*
573 	 * Lower-case the string, so that "Status" and "status"
574 	 * will hash to the same place.
575 	 */
576 	istrncpy(realfld, field, sizeof(realfld));
577 	if (ignore[1].i_count > 0)
578 		return (!member(realfld, ignore + 1));
579 	else
580 		return (member(realfld, ignore));
581 }
582 
583 int
584 member(char *realfield, struct ignoretab *table)
585 {
586 	struct ignore *igp;
587 
588 	for (igp = table->i_head[hash(realfield)]; igp != NULL; igp = igp->i_link)
589 		if (*igp->i_field == *realfield &&
590 		    equal(igp->i_field, realfield))
591 			return (1);
592 	return (0);
593 }
594