xref: /openbsd/usr.bin/mail/names.c (revision 3aaa63eb)
1 /*	$OpenBSD: names.c,v 1.25 2019/06/28 13:35:02 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 *
nalloc(char * str,int ntype)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 *
tailof(struct name * 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 *
extract(char * line,int ntype)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 = malloc(strlen(line) + 1)) == NULL)
92 		err(1, "malloc");
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 *
detract(struct name * np,int ntype)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 *
yankword(char * ap,char * wbuf)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 *
outof(struct name * names,FILE * fo,struct header * hp)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 	if (value("expandaddr") == NULL)
213 		return(names);
214 
215 	top = names;
216 	np = names;
217 	(void)time(&now);
218 	date = ctime(&now);
219 	while (np != NULL) {
220 		if (!isfileaddr(np->n_name) && np->n_name[0] != '|') {
221 			np = np->n_flink;
222 			continue;
223 		}
224 		ispipe = np->n_name[0] == '|';
225 		if (ispipe)
226 			fname = np->n_name+1;
227 		else
228 			fname = expand(np->n_name);
229 
230 		/*
231 		 * See if we have copied the complete message out yet.
232 		 * If not, do so.
233 		 */
234 		if (image < 0) {
235 			int fd;
236 			char tempname[PATHSIZE];
237 
238 			(void)snprintf(tempname, sizeof(tempname),
239 			    "%s/mail.ReXXXXXXXXXX", tmpdir);
240 			if ((fd = mkstemp(tempname)) == -1 ||
241 			    (fout = Fdopen(fd, "a")) == NULL) {
242 				warn("%s", tempname);
243 				senderr++;
244 				goto cant;
245 			}
246 			image = open(tempname, O_RDWR | O_CLOEXEC);
247 			(void)rm(tempname);
248 			if (image == -1) {
249 				warn("%s", tempname);
250 				senderr++;
251 				(void)Fclose(fout);
252 				goto cant;
253 			}
254 			fprintf(fout, "From %s %s", myname, date);
255 			puthead(hp, fout, GTO|GSUBJECT|GCC|GNL);
256 			while ((c = getc(fo)) != EOF)
257 				(void)putc(c, fout);
258 			rewind(fo);
259 			(void)putc('\n', fout);
260 			(void)fflush(fout);
261 			if (ferror(fout))
262 				warn("%s", tempname);
263 			(void)Fclose(fout);
264 		}
265 
266 		/*
267 		 * Now either copy "image" to the desired file
268 		 * or give it as the standard input to the desired
269 		 * program as appropriate.
270 		 */
271 		if (ispipe) {
272 			pid_t pid;
273 			char *shell;
274 			sigset_t nset;
275 
276 			/*
277 			 * XXX
278 			 * We can't really reuse the same image file,
279 			 * because multiple piped recipients will
280 			 * share the same lseek location and trample
281 			 * on one another.
282 			 */
283 			shell = value("SHELL");
284 			sigemptyset(&nset);
285 			sigaddset(&nset, SIGHUP);
286 			sigaddset(&nset, SIGINT);
287 			sigaddset(&nset, SIGQUIT);
288 			pid = start_command(shell, &nset,
289 				image, -1, "-c", fname, NULL);
290 			if (pid < 0) {
291 				senderr++;
292 				goto cant;
293 			}
294 			free_child(pid);
295 		} else {
296 			int f;
297 			if ((fout = Fopen(fname, "a")) == NULL) {
298 				warn("%s", fname);
299 				senderr++;
300 				goto cant;
301 			}
302 			if ((f = dup(image)) == -1) {
303 				warn("dup");
304 				fin = NULL;
305 			} else
306 				fin = Fdopen(f, "r");
307 			if (fin == NULL) {
308 				fputs("Can't reopen image\n", stderr);
309 				(void)Fclose(fout);
310 				senderr++;
311 				goto cant;
312 			}
313 			rewind(fin);
314 			while ((c = getc(fin)) != EOF)
315 				(void)putc(c, fout);
316 			if (ferror(fout)) {
317 				senderr++;
318 				warn("%s", fname);
319 			}
320 			(void)Fclose(fout);
321 			(void)Fclose(fin);
322 		}
323 cant:
324 		/*
325 		 * In days of old we removed the entry from the
326 		 * the list; now for sake of header expansion
327 		 * we leave it in and mark it as deleted.
328 		 */
329 		np->n_type |= GDEL;
330 		np = np->n_flink;
331 	}
332 	if (image >= 0) {
333 		(void)close(image);
334 		image = -1;
335 	}
336 	return(top);
337 }
338 
339 /*
340  * Determine if the passed address is a local "send to file" address.
341  * If any of the network metacharacters precedes any slashes, it can't
342  * be a filename.  We cheat with .'s to allow path names like ./...
343  */
344 int
isfileaddr(char * name)345 isfileaddr(char *name)
346 {
347 	char *cp;
348 
349 	if (*name == '+')
350 		return(1);
351 	for (cp = name; *cp; cp++) {
352 		if (*cp == '!' || *cp == '%' || *cp == '@')
353 			return(0);
354 		if (*cp == '/')
355 			return(1);
356 	}
357 	return(0);
358 }
359 
360 /*
361  * Map all of the aliased users in the invoker's mailrc
362  * file and insert them into the list.
363  * Changed after all these months of service to recursively
364  * expand names (2/14/80).
365  */
366 struct name *
usermap(struct name * names)367 usermap(struct name *names)
368 {
369 	struct name *new, *np, *cp;
370 	struct grouphead *gh;
371 	int metoo;
372 
373 	new = NULL;
374 	np = names;
375 	metoo = (value("metoo") != NULL);
376 	while (np != NULL) {
377 		if (np->n_name[0] == '\\') {
378 			cp = np->n_flink;
379 			new = put(new, np);
380 			np = cp;
381 			continue;
382 		}
383 		gh = findgroup(np->n_name);
384 		cp = np->n_flink;
385 		if (gh != NULL)
386 			new = gexpand(new, gh, metoo, np->n_type);
387 		else
388 			new = put(new, np);
389 		np = cp;
390 	}
391 	return(new);
392 }
393 
394 /*
395  * Recursively expand a group name.  We limit the expansion to some
396  * fixed level to keep things from going haywire.
397  * Direct recursion is not expanded for convenience.
398  */
399 struct name *
gexpand(struct name * nlist,struct grouphead * gh,int metoo,int ntype)400 gexpand(struct name *nlist, struct grouphead *gh, int metoo, int ntype)
401 {
402 	struct group *gp;
403 	struct grouphead *ngh;
404 	struct name *np;
405 	static int depth;
406 	char *cp;
407 
408 	if (depth > MAXEXP) {
409 		printf("Expanding alias to depth larger than %d\n", MAXEXP);
410 		return(nlist);
411 	}
412 	depth++;
413 	for (gp = gh->g_list; gp != NULL; gp = gp->ge_link) {
414 		cp = gp->ge_name;
415 		if (*cp == '\\')
416 			goto quote;
417 		if (strcmp(cp, gh->g_name) == 0)
418 			goto quote;
419 		if ((ngh = findgroup(cp)) != NULL) {
420 			nlist = gexpand(nlist, ngh, metoo, ntype);
421 			continue;
422 		}
423 quote:
424 		np = nalloc(cp, ntype);
425 		/*
426 		 * At this point should allow to expand
427 		 * to self if only person in group
428 		 */
429 		if (gp == gh->g_list && gp->ge_link == NULL)
430 			goto skip;
431 		if (!metoo && strcmp(cp, myname) == 0)
432 			np->n_type |= GDEL;
433 skip:
434 		nlist = put(nlist, np);
435 	}
436 	depth--;
437 	return(nlist);
438 }
439 
440 /*
441  * Concatenate the two passed name lists, return the result.
442  */
443 struct name *
cat(struct name * n1,struct name * n2)444 cat(struct name *n1, struct name *n2)
445 {
446 	struct name *tail;
447 
448 	if (n1 == NULL)
449 		return(n2);
450 	if (n2 == NULL)
451 		return(n1);
452 	tail = tailof(n1);
453 	tail->n_flink = n2;
454 	n2->n_blink = tail;
455 	return(n1);
456 }
457 
458 /*
459  * Remove all of the duplicates from the passed name list by
460  * insertion sorting them, then checking for dups.
461  * Return the head of the new list.
462  */
463 struct name *
elide(struct name * names)464 elide(struct name *names)
465 {
466 	struct name *np, *t, *new;
467 	struct name *x;
468 
469 	if (names == NULL)
470 		return(NULL);
471 	new = names;
472 	np = names;
473 	np = np->n_flink;
474 	if (np != NULL)
475 		np->n_blink = NULL;
476 	new->n_flink = NULL;
477 	while (np != NULL) {
478 		t = new;
479 		while (strcasecmp(t->n_name, np->n_name) < 0) {
480 			if (t->n_flink == NULL)
481 				break;
482 			t = t->n_flink;
483 		}
484 
485 		/*
486 		 * If we ran out of t's, put the new entry after
487 		 * the current value of t.
488 		 */
489 		if (strcasecmp(t->n_name, np->n_name) < 0) {
490 			t->n_flink = np;
491 			np->n_blink = t;
492 			t = np;
493 			np = np->n_flink;
494 			t->n_flink = NULL;
495 			continue;
496 		}
497 
498 		/*
499 		 * Otherwise, put the new entry in front of the
500 		 * current t.  If at the front of the list,
501 		 * the new guy becomes the new head of the list.
502 		 */
503 		if (t == new) {
504 			t = np;
505 			np = np->n_flink;
506 			t->n_flink = new;
507 			new->n_blink = t;
508 			t->n_blink = NULL;
509 			new = t;
510 			continue;
511 		}
512 
513 		/*
514 		 * The normal case -- we are inserting into the
515 		 * middle of the list.
516 		 */
517 		x = np;
518 		np = np->n_flink;
519 		x->n_flink = t;
520 		x->n_blink = t->n_blink;
521 		t->n_blink->n_flink = x;
522 		t->n_blink = x;
523 	}
524 
525 	/*
526 	 * Now the list headed up by new is sorted.
527 	 * Go through it and remove duplicates.
528 	 */
529 	np = new;
530 	while (np != NULL) {
531 		t = np;
532 		while (t->n_flink != NULL &&
533 		       strcasecmp(np->n_name, t->n_flink->n_name) == 0)
534 			t = t->n_flink;
535 		if (t == np || t == NULL) {
536 			np = np->n_flink;
537 			continue;
538 		}
539 
540 		/*
541 		 * Now t points to the last entry with the same name
542 		 * as np.  Make np point beyond t.
543 		 */
544 		np->n_flink = t->n_flink;
545 		if (t->n_flink != NULL)
546 			t->n_flink->n_blink = np;
547 		np = np->n_flink;
548 	}
549 	return(new);
550 }
551 
552 /*
553  * Put another node onto a list of names and return
554  * the list.
555  */
556 struct name *
put(struct name * list,struct name * node)557 put(struct name *list, struct name *node)
558 {
559 	node->n_flink = list;
560 	node->n_blink = NULL;
561 	if (list != NULL)
562 		list->n_blink = node;
563 	return(node);
564 }
565 
566 /*
567  * Determine the number of undeleted elements in
568  * a name list and return it.
569  */
570 int
count(struct name * np)571 count(struct name *np)
572 {
573 	int c;
574 
575 	for (c = 0; np != NULL; np = np->n_flink)
576 		if ((np->n_type & GDEL) == 0)
577 			c++;
578 	return(c);
579 }
580 
581 /*
582  * Delete the given name from a namelist.
583  */
584 struct name *
delname(struct name * np,const char * name)585 delname(struct name *np, const char *name)
586 {
587 	struct name *p;
588 
589 	for (p = np; p != NULL; p = p->n_flink)
590 		if ((strcasecmp(p->n_name, name) == 0) ||
591 		    (value("allnet") &&
592 		    strncasecmp(p->n_name, name, strlen(name)) == 0 &&
593 		    *(p->n_name+strlen(name)) == '@')) {
594 			if (p->n_blink == NULL) {
595 				if (p->n_flink != NULL)
596 					p->n_flink->n_blink = NULL;
597 				np = p->n_flink;
598 				continue;
599 			}
600 			if (p->n_flink == NULL) {
601 				if (p->n_blink != NULL)
602 					p->n_blink->n_flink = NULL;
603 				continue;
604 			}
605 			p->n_blink->n_flink = p->n_flink;
606 			p->n_flink->n_blink = p->n_blink;
607 		}
608 	return(np);
609 }
610 
611 /*
612  * Pretty print a name list
613  * Uncomment it if you need it.
614  */
615 #if 0
616 void
617 prettyprint(struct name *name)
618 {
619 	struct name *np;
620 
621 	np = name;
622 	while (np != NULL) {
623 		fprintf(stderr, "%s(%d) ", np->n_name, np->n_type);
624 		np = np->n_flink;
625 	}
626 	putc('\n', stderr);
627 }
628 #endif
629