xref: /freebsd/usr.bin/mail/names.c (revision 3494f7c0)
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 /*
33  * Mail -- a mail program
34  *
35  * Handle name lists.
36  */
37 
38 #include "rcv.h"
39 #include <fcntl.h>
40 #include "extern.h"
41 
42 /*
43  * Allocate a single element of a name list,
44  * initialize its name field to the passed
45  * name and return it.
46  */
47 struct name *
48 nalloc(char str[], int ntype)
49 {
50 	struct name *np;
51 
52 	np = (struct name *)salloc(sizeof(*np));
53 	np->n_flink = NULL;
54 	np->n_blink = NULL;
55 	np->n_type = ntype;
56 	np->n_name = savestr(str);
57 	return (np);
58 }
59 
60 /*
61  * Find the tail of a list and return it.
62  */
63 struct name *
64 tailof(struct name *name)
65 {
66 	struct name *np;
67 
68 	np = name;
69 	if (np == NULL)
70 		return (NULL);
71 	while (np->n_flink != NULL)
72 		np = np->n_flink;
73 	return (np);
74 }
75 
76 /*
77  * Extract a list of names from a line,
78  * and make a list of names from it.
79  * Return the list or NULL if none found.
80  */
81 struct name *
82 extract(char *line, int ntype)
83 {
84 	char *cp, *nbuf;
85 	struct name *top, *np, *t;
86 
87 	if (line == NULL || *line == '\0')
88 		return (NULL);
89 	if ((nbuf = malloc(strlen(line) + 1)) == NULL)
90 		err(1, "Out of memory");
91 	top = NULL;
92 	np = NULL;
93 	cp = line;
94 	while ((cp = yankword(cp, nbuf)) != NULL) {
95 		t = nalloc(nbuf, ntype);
96 		if (top == NULL)
97 			top = t;
98 		else
99 			np->n_flink = t;
100 		t->n_blink = np;
101 		np = t;
102 	}
103 	(void)free(nbuf);
104 	return (top);
105 }
106 
107 /*
108  * Turn a list of names into a string of the same names.
109  */
110 char *
111 detract(struct name *np, int ntype)
112 {
113 	int s, comma;
114 	char *cp, *top;
115 	struct name *p;
116 
117 	comma = ntype & GCOMMA;
118 	if (np == NULL)
119 		return (NULL);
120 	ntype &= ~GCOMMA;
121 	s = 0;
122 	if (debug && comma)
123 		fprintf(stderr, "detract asked to insert commas\n");
124 	for (p = np; p != NULL; p = p->n_flink) {
125 		if (ntype && (p->n_type & GMASK) != ntype)
126 			continue;
127 		s += strlen(p->n_name) + 1;
128 		if (comma)
129 			s++;
130 	}
131 	if (s == 0)
132 		return (NULL);
133 	s += 2;
134 	top = salloc(s);
135 	cp = top;
136 	for (p = np; p != NULL; p = p->n_flink) {
137 		if (ntype && (p->n_type & GMASK) != ntype)
138 			continue;
139 		cp += strlcpy(cp, p->n_name, strlen(p->n_name) + 1);
140 		if (comma && p->n_flink != NULL)
141 			*cp++ = ',';
142 		*cp++ = ' ';
143 	}
144 	*--cp = '\0';
145 	if (comma && *--cp == ',')
146 		*cp = '\0';
147 	return (top);
148 }
149 
150 /*
151  * Grab a single word (liberal word)
152  * Throw away things between ()'s, and take anything between <>.
153  */
154 char *
155 yankword(char *ap, char *wbuf)
156 {
157 	char *cp, *cp2;
158 
159 	cp = ap;
160 	for (;;) {
161 		if (*cp == '\0')
162 			return (NULL);
163 		if (*cp == '(') {
164 			int nesting = 0;
165 
166 			while (*cp != '\0') {
167 				switch (*cp++) {
168 				case '(':
169 					nesting++;
170 					break;
171 				case ')':
172 					--nesting;
173 					break;
174 				}
175 				if (nesting <= 0)
176 					break;
177 			}
178 		} else if (*cp == ' ' || *cp == '\t' || *cp == ',')
179 			cp++;
180 		else
181 			break;
182 	}
183 	if (*cp ==  '<')
184 		for (cp2 = wbuf; *cp && (*cp2++ = *cp++) != '>';)
185 			;
186 	else
187 		for (cp2 = wbuf; *cp != '\0' && strchr(" \t,(", *cp) == NULL;
188 		    *cp2++ = *cp++)
189 			;
190 	*cp2 = '\0';
191 	return (cp);
192 }
193 
194 /*
195  * Grab a single login name (liberal word)
196  * Throw away things between ()'s, take anything between <>,
197  * and look for words before metacharacters %, @, !.
198  */
199 char *
200 yanklogin(char *ap, char *wbuf)
201 {
202 	char *cp, *cp2, *cp_temp;
203 	int n;
204 
205 	cp = ap;
206 	for (;;) {
207 		if (*cp == '\0')
208 			return (NULL);
209 		if (*cp == '(') {
210 			int nesting = 0;
211 
212 			while (*cp != '\0') {
213 				switch (*cp++) {
214 				case '(':
215 					nesting++;
216 					break;
217 				case ')':
218 					--nesting;
219 					break;
220 				}
221 				if (nesting <= 0)
222 					break;
223 			}
224 		} else if (*cp == ' ' || *cp == '\t' || *cp == ',')
225 			cp++;
226 		else
227 			break;
228 	}
229 
230 	/*
231 	 * Now, let's go forward till we meet the needed character,
232 	 * and step one word back.
233 	 */
234 
235 	/* First, remember current point. */
236 	cp_temp = cp;
237 	n = 0;
238 
239 	/*
240 	 * Note that we look ahead in a cycle. This is safe, since
241 	 * non-end of string is checked first.
242 	 */
243 	while(*cp != '\0' && strchr("@%!", *(cp + 1)) == NULL)
244 		cp++;
245 
246 	/*
247 	 * Now, start stepping back to the first non-word character,
248 	 * while counting the number of symbols in a word.
249 	 */
250 	while(cp != cp_temp && strchr(" \t,<>", *(cp - 1)) == NULL) {
251 		n++;
252 		cp--;
253 	}
254 
255 	/* Finally, grab the word forward. */
256 	cp2 = wbuf;
257 	while(n >= 0) {
258 		*cp2++=*cp++;
259 		n--;
260 	}
261 
262 	*cp2 = '\0';
263 	return (cp);
264 }
265 
266 /*
267  * For each recipient in the passed name list with a /
268  * in the name, append the message to the end of the named file
269  * and remove him from the recipient list.
270  *
271  * Recipients whose name begins with | are piped through the given
272  * program and removed.
273  */
274 struct name *
275 outof(struct name *names, FILE *fo, struct header *hp)
276 {
277 	int c, ispipe;
278 	struct name *np, *top;
279 	time_t now;
280 	char *date, *fname;
281 	FILE *fout, *fin;
282 
283 	top = names;
284 	np = names;
285 	(void)time(&now);
286 	date = ctime(&now);
287 	while (np != NULL) {
288 		if (!isfileaddr(np->n_name) && np->n_name[0] != '|') {
289 			np = np->n_flink;
290 			continue;
291 		}
292 		ispipe = np->n_name[0] == '|';
293 		if (ispipe)
294 			fname = np->n_name+1;
295 		else
296 			fname = expand(np->n_name);
297 
298 		/*
299 		 * See if we have copied the complete message out yet.
300 		 * If not, do so.
301 		 */
302 
303 		if (image < 0) {
304 			int fd;
305 			char tempname[PATHSIZE];
306 
307 			(void)snprintf(tempname, sizeof(tempname),
308 			    "%s/mail.ReXXXXXXXXXX", tmpdir);
309 			if ((fd = mkstemp(tempname)) == -1 ||
310 			    (fout = Fdopen(fd, "a")) == NULL) {
311 				warn("%s", tempname);
312 				senderr++;
313 				goto cant;
314 			}
315 			image = open(tempname, O_RDWR);
316 			(void)rm(tempname);
317 			if (image < 0) {
318 				warn("%s", tempname);
319 				senderr++;
320 				(void)Fclose(fout);
321 				goto cant;
322 			}
323 			(void)fcntl(image, F_SETFD, 1);
324 			fprintf(fout, "From %s %s", myname, date);
325 			puthead(hp, fout,
326 			    GTO|GSUBJECT|GCC|GREPLYTO|GINREPLYTO|GNL);
327 			while ((c = getc(fo)) != EOF)
328 				(void)putc(c, fout);
329 			rewind(fo);
330 			fprintf(fout, "\n");
331 			(void)fflush(fout);
332 			if (ferror(fout)) {
333 				warn("%s", tempname);
334 				senderr++;
335 				(void)Fclose(fout);
336 				goto cant;
337 			}
338 			(void)Fclose(fout);
339 		}
340 
341 		/*
342 		 * Now either copy "image" to the desired file
343 		 * or give it as the standard input to the desired
344 		 * program as appropriate.
345 		 */
346 
347 		if (ispipe) {
348 			int pid;
349 			char *sh;
350 			sigset_t nset;
351 
352 			/*
353 			 * XXX
354 			 * We can't really reuse the same image file,
355 			 * because multiple piped recipients will
356 			 * share the same lseek location and trample
357 			 * on one another.
358 			 */
359 			if ((sh = value("SHELL")) == NULL)
360 				sh = _PATH_CSHELL;
361 			(void)sigemptyset(&nset);
362 			(void)sigaddset(&nset, SIGHUP);
363 			(void)sigaddset(&nset, SIGINT);
364 			(void)sigaddset(&nset, SIGQUIT);
365 			pid = start_command(sh, &nset, image, -1, "-c", fname,
366 			    NULL);
367 			if (pid < 0) {
368 				senderr++;
369 				goto cant;
370 			}
371 			free_child(pid);
372 		} else {
373 			int f;
374 			if ((fout = Fopen(fname, "a")) == NULL) {
375 				warn("%s", fname);
376 				senderr++;
377 				goto cant;
378 			}
379 			if ((f = dup(image)) < 0) {
380 				warn("dup");
381 				fin = NULL;
382 			} else
383 				fin = Fdopen(f, "r");
384 			if (fin == NULL) {
385 				fprintf(stderr, "Can't reopen image\n");
386 				(void)Fclose(fout);
387 				senderr++;
388 				goto cant;
389 			}
390 			rewind(fin);
391 			while ((c = getc(fin)) != EOF)
392 				(void)putc(c, fout);
393 			if (ferror(fout)) {
394 				warnx("%s", fname);
395 				senderr++;
396 				(void)Fclose(fout);
397 				(void)Fclose(fin);
398 				goto cant;
399 			}
400 			(void)Fclose(fout);
401 			(void)Fclose(fin);
402 		}
403 cant:
404 		/*
405 		 * In days of old we removed the entry from the
406 		 * the list; now for sake of header expansion
407 		 * we leave it in and mark it as deleted.
408 		 */
409 		np->n_type |= GDEL;
410 		np = np->n_flink;
411 	}
412 	if (image >= 0) {
413 		(void)close(image);
414 		image = -1;
415 	}
416 	return (top);
417 }
418 
419 /*
420  * Determine if the passed address is a local "send to file" address.
421  * If any of the network metacharacters precedes any slashes, it can't
422  * be a filename.  We cheat with .'s to allow path names like ./...
423  */
424 int
425 isfileaddr(char *name)
426 {
427 	char *cp;
428 
429 	if (*name == '+')
430 		return (1);
431 	for (cp = name; *cp != '\0'; cp++) {
432 		if (*cp == '!' || *cp == '%' || *cp == '@')
433 			return (0);
434 		if (*cp == '/')
435 			return (1);
436 	}
437 	return (0);
438 }
439 
440 /*
441  * Map all of the aliased users in the invoker's mailrc
442  * file and insert them into the list.
443  * Changed after all these months of service to recursively
444  * expand names (2/14/80).
445  */
446 
447 struct name *
448 usermap(struct name *names)
449 {
450 	struct name *new, *np, *cp;
451 	struct grouphead *gh;
452 	int metoo;
453 
454 	new = NULL;
455 	np = names;
456 	metoo = (value("metoo") != NULL);
457 	while (np != NULL) {
458 		if (np->n_name[0] == '\\') {
459 			cp = np->n_flink;
460 			new = put(new, np);
461 			np = cp;
462 			continue;
463 		}
464 		gh = findgroup(np->n_name);
465 		cp = np->n_flink;
466 		if (gh != NULL)
467 			new = gexpand(new, gh, metoo, np->n_type);
468 		else
469 			new = put(new, np);
470 		np = cp;
471 	}
472 	return (new);
473 }
474 
475 /*
476  * Recursively expand a group name.  We limit the expansion to some
477  * fixed level to keep things from going haywire.
478  * Direct recursion is not expanded for convenience.
479  */
480 
481 struct name *
482 gexpand(struct name *nlist, struct grouphead *gh, int metoo, int ntype)
483 {
484 	struct group *gp;
485 	struct grouphead *ngh;
486 	struct name *np;
487 	static int depth;
488 	char *cp;
489 
490 	if (depth > MAXEXP) {
491 		printf("Expanding alias to depth larger than %d\n", MAXEXP);
492 		return (nlist);
493 	}
494 	depth++;
495 	for (gp = gh->g_list; gp != NULL; gp = gp->ge_link) {
496 		cp = gp->ge_name;
497 		if (*cp == '\\')
498 			goto quote;
499 		if (strcmp(cp, gh->g_name) == 0)
500 			goto quote;
501 		if ((ngh = findgroup(cp)) != NULL) {
502 			nlist = gexpand(nlist, ngh, metoo, ntype);
503 			continue;
504 		}
505 quote:
506 		np = nalloc(cp, ntype);
507 		/*
508 		 * At this point should allow to expand
509 		 * to self if only person in group
510 		 */
511 		if (gp == gh->g_list && gp->ge_link == NULL)
512 			goto skip;
513 		if (!metoo && strcmp(cp, myname) == 0)
514 			np->n_type |= GDEL;
515 skip:
516 		nlist = put(nlist, np);
517 	}
518 	depth--;
519 	return (nlist);
520 }
521 
522 /*
523  * Concatenate the two passed name lists, return the result.
524  */
525 struct name *
526 cat(struct name *n1, struct name *n2)
527 {
528 	struct name *tail;
529 
530 	if (n1 == NULL)
531 		return (n2);
532 	if (n2 == NULL)
533 		return (n1);
534 	tail = tailof(n1);
535 	tail->n_flink = n2;
536 	n2->n_blink = tail;
537 	return (n1);
538 }
539 
540 /*
541  * Unpack the name list onto a vector of strings.
542  * Return an error if the name list won't fit.
543  */
544 char **
545 unpack(struct name *np)
546 {
547 	char **ap, **top;
548 	struct name *n;
549 	int t, extra, metoo, verbose;
550 
551 	n = np;
552 	if ((t = count(n)) == 0)
553 		errx(1, "No names to unpack");
554 	/*
555 	 * Compute the number of extra arguments we will need.
556 	 * We need at least two extra -- one for "mail" and one for
557 	 * the terminating 0 pointer.  Additional spots may be needed
558 	 * to pass along -f to the host mailer.
559 	 */
560 	extra = 2;
561 	extra++;
562 	metoo = value("metoo") != NULL;
563 	if (metoo)
564 		extra++;
565 	verbose = value("verbose") != NULL;
566 	if (verbose)
567 		extra++;
568 	top = (char **)salloc((t + extra) * sizeof(*top));
569 	ap = top;
570 	*ap++ = "sendmail";
571 	*ap++ = "-i";
572 	if (metoo)
573 		*ap++ = "-m";
574 	if (verbose)
575 		*ap++ = "-v";
576 	for (; n != NULL; n = n->n_flink)
577 		if ((n->n_type & GDEL) == 0)
578 			*ap++ = n->n_name;
579 	*ap = NULL;
580 	return (top);
581 }
582 
583 /*
584  * Remove all of the duplicates from the passed name list by
585  * insertion sorting them, then checking for dups.
586  * Return the head of the new list.
587  */
588 struct name *
589 elide(struct name *names)
590 {
591 	struct name *np, *t, *new;
592 	struct name *x;
593 
594 	if (names == NULL)
595 		return (NULL);
596 	new = names;
597 	np = names;
598 	np = np->n_flink;
599 	if (np != NULL)
600 		np->n_blink = NULL;
601 	new->n_flink = NULL;
602 	while (np != NULL) {
603 		t = new;
604 		while (strcasecmp(t->n_name, np->n_name) < 0) {
605 			if (t->n_flink == NULL)
606 				break;
607 			t = t->n_flink;
608 		}
609 
610 		/*
611 		 * If we ran out of t's, put the new entry after
612 		 * the current value of t.
613 		 */
614 
615 		if (strcasecmp(t->n_name, np->n_name) < 0) {
616 			t->n_flink = np;
617 			np->n_blink = t;
618 			t = np;
619 			np = np->n_flink;
620 			t->n_flink = NULL;
621 			continue;
622 		}
623 
624 		/*
625 		 * Otherwise, put the new entry in front of the
626 		 * current t.  If at the front of the list,
627 		 * the new guy becomes the new head of the list.
628 		 */
629 
630 		if (t == new) {
631 			t = np;
632 			np = np->n_flink;
633 			t->n_flink = new;
634 			new->n_blink = t;
635 			t->n_blink = NULL;
636 			new = t;
637 			continue;
638 		}
639 
640 		/*
641 		 * The normal case -- we are inserting into the
642 		 * middle of the list.
643 		 */
644 
645 		x = np;
646 		np = np->n_flink;
647 		x->n_flink = t;
648 		x->n_blink = t->n_blink;
649 		t->n_blink->n_flink = x;
650 		t->n_blink = x;
651 	}
652 
653 	/*
654 	 * Now the list headed up by new is sorted.
655 	 * Go through it and remove duplicates.
656 	 */
657 
658 	np = new;
659 	while (np != NULL) {
660 		t = np;
661 		while (t->n_flink != NULL &&
662 		    strcasecmp(np->n_name, t->n_flink->n_name) == 0)
663 			t = t->n_flink;
664 		if (t == np || t == NULL) {
665 			np = np->n_flink;
666 			continue;
667 		}
668 
669 		/*
670 		 * Now t points to the last entry with the same name
671 		 * as np.  Make np point beyond t.
672 		 */
673 
674 		np->n_flink = t->n_flink;
675 		if (t->n_flink != NULL)
676 			t->n_flink->n_blink = np;
677 		np = np->n_flink;
678 	}
679 	return (new);
680 }
681 
682 /*
683  * Put another node onto a list of names and return
684  * the list.
685  */
686 struct name *
687 put(struct name *list, struct name *node)
688 {
689 	node->n_flink = list;
690 	node->n_blink = NULL;
691 	if (list != NULL)
692 		list->n_blink = node;
693 	return (node);
694 }
695 
696 /*
697  * Determine the number of undeleted elements in
698  * a name list and return it.
699  */
700 int
701 count(struct name *np)
702 {
703 	int c;
704 
705 	for (c = 0; np != NULL; np = np->n_flink)
706 		if ((np->n_type & GDEL) == 0)
707 			c++;
708 	return (c);
709 }
710 
711 /*
712  * Delete the given name from a namelist.
713  */
714 struct name *
715 delname(struct name *np, char name[])
716 {
717 	struct name *p;
718 
719 	for (p = np; p != NULL; p = p->n_flink)
720 		if (strcasecmp(p->n_name, name) == 0) {
721 			if (p->n_blink == NULL) {
722 				if (p->n_flink != NULL)
723 					p->n_flink->n_blink = NULL;
724 				np = p->n_flink;
725 				continue;
726 			}
727 			if (p->n_flink == NULL) {
728 				if (p->n_blink != NULL)
729 					p->n_blink->n_flink = NULL;
730 				continue;
731 			}
732 			p->n_blink->n_flink = p->n_flink;
733 			p->n_flink->n_blink = p->n_blink;
734 		}
735 	return (np);
736 }
737 
738 /*
739  * Pretty print a name list
740  * Uncomment it if you need it.
741  */
742 
743 /*
744 void
745 prettyprint(struct name *name)
746 {
747 	struct name *np;
748 
749 	np = name;
750 	while (np != NULL) {
751 		fprintf(stderr, "%s(%d) ", np->n_name, np->n_type);
752 		np = np->n_flink;
753 	}
754 	fprintf(stderr, "\n");
755 }
756 */
757