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