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