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