xref: /illumos-gate/usr/src/cmd/mailx/names.c (revision 45526e9775395f5d44bad3f5430041f32c84ce1e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2001 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 /*
32  * University Copyright- Copyright (c) 1982, 1986, 1988
33  * The Regents of the University of California
34  * All Rights Reserved
35  *
36  * University Acknowledgment- Portions of this document are derived from
37  * software developed by the University of California, Berkeley, and its
38  * contributors.
39  */
40 
41 #pragma ident	"%Z%%M%	%I%	%E% SMI"
42 
43 /*
44  * mailx -- a modified version of a University of California at Berkeley
45  *	mail program
46  *
47  * Handle name lists.
48  */
49 
50 #include "rcv.h"
51 #include <locale.h>
52 
53 static struct name	*nalloc(char str[]);
54 static int		isfileaddr(char *name);
55 static int		lengthof(struct name *name);
56 static struct name	*gexpand(struct name *nlist, struct grouphead *gh, int metoo, int arg_ntype);
57 static char		*norm(register char *user, register char *ubuf, int nbangs);
58 static struct name	*put(struct name *list, struct name *node);
59 
60 /*
61  * Allocate a single element of a name list,
62  * initialize its name field to the passed
63  * name and return it.
64  */
65 
66 static struct name *
67 nalloc(char str[])
68 {
69 	register struct name *np;
70 
71 	np = (struct name *) salloc(sizeof *np);
72 	np->n_flink = NIL;
73 	np->n_blink = NIL;
74 	np->n_type = -1;
75 	np->n_full = savestr(str);
76 	np->n_name = skin(np->n_full);
77 	return(np);
78 }
79 
80 /*
81  * Find the tail of a list and return it.
82  */
83 
84 struct name *
85 tailof(struct name *name)
86 {
87 	register struct name *np;
88 
89 	np = name;
90 	if (np == NIL)
91 		return(NIL);
92 	while (np->n_flink != NIL)
93 		np = np->n_flink;
94 	return(np);
95 }
96 
97 /*
98  * Extract a list of names from a line,
99  * and make a list of names from it.
100  * Return the list or NIL if none found.
101  */
102 
103 struct name *
104 extract(char line[], int arg_ntype)
105 {
106 	short ntype = (short)arg_ntype;
107 	register char *cp;
108 	register struct name *top, *np, *t;
109 	char nbuf[BUFSIZ], abuf[BUFSIZ];
110 	int comma;
111 
112 	if (line == NOSTR || strlen(line) == 0)
113 		return(NIL);
114 	comma = docomma(line);
115 	top = NIL;
116 	np = NIL;
117 	cp = line;
118 	while ((cp = yankword(cp, nbuf, sizeof (nbuf), comma)) != NOSTR) {
119 		if (np != NIL && equal(nbuf, "at")) {
120 			nstrcpy(abuf, sizeof (abuf), nbuf);
121 			if ((cp = yankword(cp, nbuf, sizeof (nbuf),
122 				comma)) == NOSTR) {
123 				nstrcpy(nbuf, sizeof (nbuf), abuf);
124 				goto normal;
125 			}
126 			snprintf(abuf, sizeof (abuf), "%s@%s", np->n_name,
127 				nbuf);
128 			np->n_name = savestr(abuf);
129 			continue;
130 		}
131 normal:
132 		t = nalloc(nbuf);
133 		t->n_type = ntype;
134 		if (top == NIL)
135 			top = t;
136 		else
137 			np->n_flink = t;
138 		t->n_blink = np;
139 		np = t;
140 	}
141 	return(top);
142 }
143 
144 /*
145  * Turn a list of names into a string of the same names.
146  */
147 
148 char *
149 detract(register struct name *np, int ntype)
150 {
151 	register int s;
152 	register char *cp, *top;
153 	register struct name *p;
154 
155 	if (np == NIL)
156 		return(NOSTR);
157 	s = 0;
158 	for (p = np; p != NIL; p = p->n_flink) {
159 		if ((ntype && (p->n_type & GMASK) != ntype)
160 		 || (p->n_type & GDEL))
161 			continue;
162 		s += strlen(p->n_full) + 2;
163 	}
164 	if (s == 0)
165 		return(NOSTR);
166 	top = (char *)salloc((unsigned)(++s));
167 	cp = top;
168 	for (p = np; p != NIL; p = p->n_flink) {
169 		if ((ntype && (p->n_type & GMASK) != ntype)
170 		 || (p->n_type & GDEL))
171 			continue;
172 		cp = copy(p->n_full, cp);
173 		*cp++ = ',';
174 		*cp++ = ' ';
175 	}
176 	*cp = 0;
177 	return(top);
178 }
179 
180 struct name *
181 outpre(struct name *to)
182 {
183 	register struct name *np;
184 
185 	for (np = to; np; np = np->n_flink)
186 		if (isfileaddr(np->n_name))
187 			np->n_type |= GDEL;
188 	return to;
189 }
190 
191 /*
192  * For each recipient in the passed name list with a /
193  * in the name, append the message to the end of the named file
194  * and remove him from the recipient list.
195  *
196  * Recipients whose name begins with | are piped through the given
197  * program and removed.
198  */
199 
200 int
201 outof(struct name *names, FILE *fo)
202 {
203 	register int c;
204 	register struct name *np;
205 	time_t now;
206 	char *date, *fname, *shell;
207 	FILE *fout, *fin;
208 	int ispipe;
209 	int nout = 0;
210 	int fd = 0;
211 #ifdef preSVr4
212 	char line[BUFSIZ];
213 #endif
214 
215 	for (np = names; np != NIL; np = np->n_flink) {
216 		if (!isfileaddr(np->n_name) && np->n_name[0] != '|')
217 			continue;
218 		nout++;
219 		ispipe = np->n_name[0] == '|';
220 		if (ispipe)
221 			fname = np->n_name+1;
222 		else
223 			fname = safeexpand(np->n_name);
224 
225 		/*
226 		 * See if we have copied the complete message out yet.
227 		 * If not, do so.
228 		 */
229 
230 		if (image < 0) {
231 			fd = open(tempEdit, O_CREAT|O_EXCL|O_APPEND|O_WRONLY,
232 					0600);
233 			if ((fd  < 0) && (errno == EEXIST)) {
234 				if ((fd = open(tempEdit, O_APPEND|O_WRONLY,
235 						0600)) < 0) {
236 					perror(tempEdit);
237 					senderr++;
238 					goto cant;
239 				}
240 			}
241 			if ((fout = fdopen(fd, "a")) == NULL) {
242 				perror(tempEdit);
243 				senderr++;
244 				goto cant;
245 			}
246 			image = open(tempEdit, O_RDWR);
247 			unlink(tempEdit);
248 			if (image < 0) {
249 				perror(tempEdit);
250 				senderr++;
251 				goto cant;
252 			} else {
253 				rewind(fo);
254 				time(&now);
255 				date = ctime(&now);
256 				fprintf(fout, "From %s %s", myname, date);
257 				while ((c = getc(fo)) != EOF)
258 					putc(c, fout);
259 				rewind(fo);
260 				fflush(fout);
261 				if (fferror(fout))
262 					perror(tempEdit);
263 				fclose(fout);
264 			}
265 		}
266 
267 		/*
268 		 * Now either copy "image" to the desired file
269 		 * or give it as the standard input to the desired
270 		 * program as appropriate.
271 		 */
272 
273 		if (ispipe) {
274 			wait((int *)NULL);
275 			switch (fork()) {
276 			case 0:
277 				sigchild();
278 				sigset(SIGHUP, SIG_IGN);
279 				sigset(SIGINT, SIG_IGN);
280 				sigset(SIGQUIT, SIG_IGN);
281 				close(0);
282 				dup(image);
283 				close(image);
284 				lseek(0, 0L, 0);
285 				if ((shell = value("SHELL")) == NOSTR || *shell=='\0')
286 					shell = SHELL;
287 				(void) execlp(shell, shell, "-c", fname, (char *)0);
288 				perror(shell);
289 				exit(1);
290 				break;
291 
292 			case (pid_t)-1:
293 				perror("fork");
294 				senderr++;
295 				goto cant;
296 			}
297 		}
298 		else {
299 			if ((fout = fopen(fname, "a")) == NULL) {
300 				perror(fname);
301 				senderr++;
302 				goto cant;
303 			}
304 			fin = Fdopen(image, "r");
305 			if (fin == NULL) {
306 				fprintf(stderr,
307 				    gettext("Can't reopen image\n"));
308 				fclose(fout);
309 				senderr++;
310 				goto cant;
311 			}
312 			rewind(fin);
313 #ifdef preSVr4
314 			putc(getc(fin), fout);
315 			while (fgets(line, sizeof line, fin)) {
316 				if (!strncmp(line, "From ", 5))
317 					putc('>', fout);
318 				fputs(line, fout);
319 			}
320 #else
321 			while ((c = getc(fin)) != EOF)
322 				putc(c, fout);
323 #endif
324 			putc('\n', fout);
325 			fflush(fout);
326 			if (fferror(fout))
327 				senderr++, perror(fname);
328 			fclose(fout);
329 			fclose(fin);
330 		}
331 cant:
332 		/*
333 		 * In days of old we removed the entry from the
334 		 * the list; now for sake of header expansion
335 		 * we leave it in and mark it as deleted.
336 		 */
337 
338 #ifdef CRAZYWOW
339 		{
340 		register struct name *t, *x;
341 
342 		if (np == top) {
343 			top = np->n_flink;
344 			if (top != NIL)
345 				top->n_blink = NIL;
346 			np = top;
347 			continue;
348 		}
349 		x = np->n_blink;
350 		t = np->n_flink;
351 		x->n_flink = t;
352 		if (t != NIL)
353 			t->n_blink = x;
354 		np = t;
355 		}
356 #endif
357 
358 		np->n_type |= GDEL;
359 	}
360 	if (image >= 0) {
361 		close(image);
362 		image = -1;
363 	}
364 	return(nout);
365 }
366 
367 /*
368  * Determine if the passed address is a local "send to file" address.
369  * If any of the network metacharacters precedes any slashes, it can't
370  * be a filename.  We cheat with .'s to allow path names like ./...
371  * If "fcc" has been unset, then short-circuit those tests, but not
372  * the +... test.
373  */
374 static int
375 isfileaddr(char *name)
376 {
377 	register char *cp;
378 	char *fcc = value("fcc");
379 
380 	if (any('@', name))
381 		return(0);
382 	if (*name == '+')
383 		return(1);
384 	if (fcc == NOSTR)
385 		return(0);
386 	for (cp = name; *cp; cp++) {
387 		if (*cp == '.')
388 			continue;
389 		if (any(*cp, metanet))
390 			return(0);
391 		if (*cp == '/')
392 			return(1);
393 	}
394 	return(0);
395 }
396 
397 /*
398  * Map all of the aliased users in the invoker's mailrc
399  * file and insert them into the list.
400  * Changed after all these months of service to recursively
401  * expand names (2/14/80).
402  */
403 
404 struct name *
405 usermap(struct name *names)
406 {
407 	register struct name *newnames, *np, *cp;
408 	struct grouphead *gh;
409 	register int metoo;
410 
411 	newnames = NIL;
412 	np = names;
413 	metoo = (value("metoo") != NOSTR);
414 	while (np != NIL) {
415 		if (np->n_name[0] == '\\') {
416 			cp = np->n_flink;
417 			newnames = put(newnames, np);
418 			np = cp;
419 			continue;
420 		}
421 		gh = findgroup(np->n_name);
422 		cp = np->n_flink;
423 		if (gh != NOGRP)
424 			newnames = gexpand(newnames, gh, metoo, np->n_type);
425 		else
426 			newnames = put(newnames, np);
427 		np = cp;
428 	}
429 	return(newnames);
430 }
431 
432 /*
433  * Recursively expand a group name.  We limit the expansion to some
434  * fixed level to keep things from going haywire.
435  * Direct recursion is not expanded for convenience.
436  */
437 
438 static struct name *
439 gexpand(struct name *nlist, struct grouphead *gh, int metoo, int arg_ntype)
440 {
441 	short ntype = (short)arg_ntype;
442 	struct mgroup *gp;
443 	struct grouphead *ngh;
444 	struct name *np;
445 	static int depth;
446 	register char *cp;
447 
448 	if (depth > MAXEXP) {
449 		printf(gettext("Expanding alias to depth larger than %d\n"),
450 		    MAXEXP);
451 		return(nlist);
452 	}
453 	depth++;
454 	for (gp = gh->g_list; gp != NOGE; gp = gp->ge_link) {
455 		cp = gp->ge_name;
456 		if (*cp == '\\')
457 			goto quote;
458 		if (strcmp(cp, gh->g_name) == 0)
459 			goto quote;
460 		if ((ngh = findgroup(cp)) != NOGRP) {
461 			nlist = gexpand(nlist, ngh, metoo, ntype);
462 			continue;
463 		}
464 quote:
465 		np = nalloc(cp);
466 		np->n_type = ntype;
467 		/*
468 		 * At this point should allow to expand
469 		 * to self if only person in group
470 		 */
471 		if (gp == gh->g_list && gp->ge_link == NOGE)
472 			goto skip;
473 		if (!metoo && samebody(myname, gp->ge_name, FALSE))
474 			np->n_type |= GDEL;
475 skip:
476 		nlist = put(nlist, np);
477 	}
478 	depth--;
479 	return(nlist);
480 }
481 
482 /*
483  * Normalize a network name for comparison purposes.
484  */
485 static char *
486 norm(register char *user, register char *ubuf, int nbangs)
487 {
488 	register char *cp;
489 	int inubuf = 0;
490 
491 	while (*user++ == '!');
492 	user--;
493 	if (!strchr(user, '!')) {
494 		snprintf(ubuf, BUFSIZ, "%s!%s", host, user);
495 		user = ubuf;
496 		inubuf++;
497 	}
498 	if (nbangs) {
499 		cp = user + strlen(user);
500 		while (nbangs--)
501 			while (cp > user && *--cp != '!');
502 		user = (cp > user) ? ++cp : cp;
503 		/*
504 		 * Now strip off all Internet-type
505 		 * hosts.
506 		 */
507 		if ((cp = strchr(user, '%')) == NOSTR)
508 			cp = strchr(user, '@');
509 		if (cp != NOSTR) {
510 			if (!inubuf) {
511 				strncpy(ubuf, user, cp - user);
512 				ubuf[cp - user] = '\0';
513 				user = ubuf;
514 			} else
515 				*cp = '\0';
516 		}
517 	}
518 	return user;
519 }
520 
521 /*
522  * Implement allnet options.
523  */
524 int
525 samebody(register char *user, register char *addr, int fuzzy)
526 {
527 	char ubuf[BUFSIZ], abuf[BUFSIZ];
528 	char *allnet = value("allnet");
529 	int nbangs = allnet ? !strcmp(allnet, "uucp") ? 2 : 1 : 0;
530 
531 	if (fuzzy && value("fuzzymatch")) {
532 		int i;
533 
534 		(void) strlcpy(ubuf, user, BUFSIZ);
535 		for (i = 0; ubuf[i]; i++)
536 			ubuf[i] = tolower(ubuf[i]);
537 		(void) strlcpy(abuf, addr, BUFSIZ);
538 		for (i = 0; abuf[i]; i++)
539 			abuf[i] = tolower(abuf[i]);
540 		return (strstr(abuf, ubuf) != NOSTR);
541 	}
542 	user = norm(user, ubuf, nbangs);
543 	addr = norm(addr, abuf, nbangs);
544 	return strcmp(user, addr) == 0;
545 }
546 
547 /*
548  * Compute the length of the passed name list and
549  * return it.
550  */
551 static int
552 lengthof(struct name *name)
553 {
554 	register struct name *np;
555 	register int c;
556 
557 	for (c = 0, np = name; np != NIL; c++, np = np->n_flink)
558 		;
559 	return(c);
560 }
561 
562 /*
563  * Concatenate the two passed name lists, return the result.
564  */
565 
566 struct name *
567 cat(struct name *n1, struct name *n2)
568 {
569 	register struct name *tail;
570 
571 	if (n1 == NIL)
572 		return(n2);
573 	if (n2 == NIL)
574 		return(n1);
575 	tail = tailof(n1);
576 	tail->n_flink = n2;
577 	n2->n_blink = tail;
578 	return(n1);
579 }
580 
581 /*
582  * Unpack the name list onto a vector of strings.
583  * Return an error if the name list won't fit.
584  */
585 
586 char **
587 unpack(struct name *np)
588 {
589 	register char **ap, **top;
590 	register struct name *n;
591 	char hbuf[10];
592 	int t, extra, metoo, verbose;
593 
594 	n = np;
595 	if ((t = lengthof(n)) == 0)
596 		panic("No names to unpack");
597 
598 	/*
599 	 * Compute the number of extra arguments we will need.
600 	 * We need at least 2 extra -- one for "mail" and one for
601 	 * the terminating 0 pointer.
602 	 * Additional spots may be needed to pass along -r and -f to
603 	 * the host mailer.
604 	 */
605 
606 	extra = 2;
607 
608 	if (rflag != NOSTR)
609 		extra += 2;
610 #ifdef SENDMAIL
611 	extra++;
612 	metoo = value("metoo") != NOSTR;
613 	if (metoo)
614 		extra++;
615 	verbose = value("verbose") != NOSTR;
616 	if (verbose)
617 		extra++;
618 	if (hflag)
619 		extra += 2;
620 #endif /* SENDMAIL */
621 	top = (char **) salloc((t + extra) * sizeof (char *));
622 	ap = top;
623 	*ap++ = "mail";
624 	if (rflag != NOSTR) {
625 		*ap++ = "-r";
626 		*ap++ = rflag;
627 	}
628 #ifdef SENDMAIL
629 	*ap++ = "-i";
630 	if (metoo)
631 		*ap++ = "-m";
632 	if (verbose)
633 		*ap++ = "-v";
634 	if (hflag) {
635 		*ap++ = "-h";
636 		snprintf(hbuf, sizeof (hbuf), "%d", hflag);
637 		*ap++ = savestr(hbuf);
638 	}
639 #endif /* SENDMAIL */
640 	while (n != NIL) {
641 		if (n->n_type & GDEL) {
642 			n = n->n_flink;
643 			continue;
644 		}
645 		*ap++ = n->n_name;
646 		n = n->n_flink;
647 	}
648 	*ap = NOSTR;
649 	return(top);
650 }
651 
652 /*
653  * See if the user named himself as a destination
654  * for outgoing mail.  If so, set the global flag
655  * selfsent so that we avoid removing his mailbox.
656  */
657 
658 void
659 mechk(struct name *names)
660 {
661 	register struct name *np;
662 
663 	for (np = names; np != NIL; np = np->n_flink)
664 		if ((np->n_type & GDEL) == 0 &&
665 		    samebody(np->n_name, myname, FALSE)) {
666 			selfsent++;
667 			return;
668 		}
669 }
670 
671 /*
672  * Remove all of the duplicates from the passed name list by
673  * insertion sorting them, then checking for dups.
674  * Return the head of the new list.
675  */
676 
677 struct name *
678 elide(struct name *names)
679 {
680 	register struct name *np, *t, *newnames;
681 	struct name *x;
682 
683 	if (names == NIL)
684 		return(NIL);
685 	newnames = names;
686 	np = names;
687 	np = np->n_flink;
688 	if (np != NIL)
689 		np->n_blink = NIL;
690 	newnames->n_flink = NIL;
691 	while (np != NIL) {
692 		t = newnames;
693 		while (strcmp(t->n_name, np->n_name) < 0) {
694 			if (t->n_flink == NIL)
695 				break;
696 			t = t->n_flink;
697 		}
698 
699 		/*
700 		 * If we ran out of t's, put the new entry after
701 		 * the current value of t.
702 		 */
703 
704 		if (strcmp(t->n_name, np->n_name) < 0) {
705 			t->n_flink = np;
706 			np->n_blink = t;
707 			t = np;
708 			np = np->n_flink;
709 			t->n_flink = NIL;
710 			continue;
711 		}
712 
713 		/*
714 		 * Otherwise, put the new entry in front of the
715 		 * current t.  If at the front of the list,
716 		 * the new guy becomes the new head of the list.
717 		 */
718 
719 		if (t == newnames) {
720 			t = np;
721 			np = np->n_flink;
722 			t->n_flink = newnames;
723 			newnames->n_blink = t;
724 			t->n_blink = NIL;
725 			newnames = t;
726 			continue;
727 		}
728 
729 		/*
730 		 * The normal case -- we are inserting into the
731 		 * middle of the list.
732 		 */
733 
734 		x = np;
735 		np = np->n_flink;
736 		x->n_flink = t;
737 		x->n_blink = t->n_blink;
738 		t->n_blink->n_flink = x;
739 		t->n_blink = x;
740 	}
741 
742 	/*
743 	 * Now the list headed up by new is sorted.
744 	 * Go through it and remove duplicates.
745 	 * Remember the best "type" among all the
746 	 * duplicates of a name.
747 	 */
748 
749 	np = newnames;
750 	while (np != NIL) {
751 		int type;
752 
753 		t = np;
754 		type = np->n_type;
755 		while (t->n_flink!=NIL &&
756 		    strcmp(np->n_name, t->n_flink->n_name) == 0) {
757 			t = t->n_flink;
758 			/* "To" before "Cc" before "Bcc" */
759 			if (t->n_type < type)
760 				type = t->n_type;
761 		}
762 		if (t == np || t == NIL) {
763 			np = np->n_flink;
764 			continue;
765 		}
766 
767 		/*
768 		 * Now t points to the last entry with the same name
769 		 * as np.  Make np point beyond t.
770 		 */
771 
772 		np->n_flink = t->n_flink;
773 		if (t->n_flink != NIL)
774 			t->n_flink->n_blink = np;
775 		np->n_type = type;
776 		np = np->n_flink;
777 	}
778 	return(newnames);
779 }
780 
781 /*
782  * Put another node onto a list of names and return
783  * the list.
784  */
785 
786 static struct name *
787 put(struct name *list, struct name *node)
788 {
789 	node->n_flink = list;
790 	node->n_blink = NIL;
791 	if (list != NIL)
792 		list->n_blink = node;
793 	return(node);
794 }
795 
796 
797 /*
798  * Delete the given name from a namelist.
799  */
800 struct name *
801 delname(register struct name *np, char name[])
802 {
803 	register struct name *p;
804 
805 	for (p = np; p != NIL; p = p->n_flink)
806 		if (samebody(name, p->n_name, FALSE)) {
807 			if (p->n_blink == NIL) {
808 				if (p->n_flink != NIL)
809 					p->n_flink->n_blink = NIL;
810 				np = p->n_flink;
811 				continue;
812 			}
813 			if (p->n_flink == NIL) {
814 				if (p->n_blink != NIL)
815 					p->n_blink->n_flink = NIL;
816 				continue;
817 			}
818 			p->n_blink->n_flink = p->n_flink;
819 			p->n_flink->n_blink = p->n_blink;
820 		}
821 	return(np);
822 }
823 
824 /*
825  * Call the given routine on each element of the name
826  * list, replacing said value if need be.
827  */
828 
829 void
830 mapf(register struct name *np, char *from)
831 {
832 	register struct name *p;
833 
834 	if (debug) fprintf(stderr, "mapf %lx, %s\n", (long)np, from);
835 	for (p = np; p != NIL; p = p->n_flink)
836 		if ((p->n_type & GDEL) == 0) {
837 			p->n_name = netmap(p->n_name, from);
838 			p->n_full = splice(p->n_name, p->n_full);
839 		}
840 	if (debug) fprintf(stderr, "mapf %s done\n", from);
841 }
842