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