xref: /illumos-gate/usr/src/cmd/mailx/names.c (revision 44bc9120699af80bb18366ca474cb2c618608ca9)
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 /*
42  * mailx -- a modified version of a University of California at Berkeley
43  *	mail program
44  *
45  * Handle name lists.
46  */
47 
48 #include "rcv.h"
49 #include <locale.h>
50 
51 static struct name	*nalloc(char str[]);
52 static int		isfileaddr(char *name);
53 static int		lengthof(struct name *name);
54 static struct name	*gexpand(struct name *nlist, struct grouphead *gh,
55     int metoo, int arg_ntype);
56 static char		*norm(register char *user, register char *ubuf,
57     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 them 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 	if (value("expandaddr") == NOSTR)
216 		return (nout);
217 
218 	for (np = names; np != NIL; np = np->n_flink) {
219 		if (!isfileaddr(np->n_name) && np->n_name[0] != '|')
220 			continue;
221 		nout++;
222 		ispipe = np->n_name[0] == '|';
223 		if (ispipe)
224 			fname = np->n_name+1;
225 		else
226 			fname = safeexpand(np->n_name);
227 
228 		/*
229 		 * See if we have copied the complete message out yet.
230 		 * If not, do so.
231 		 */
232 
233 		if (image < 0) {
234 			fd = open(tempEdit, O_CREAT|O_EXCL|O_APPEND|O_WRONLY,
235 			    0600);
236 			if ((fd  < 0) && (errno == EEXIST)) {
237 				if ((fd = open(tempEdit, O_APPEND|O_WRONLY,
238 				    0600)) < 0) {
239 					perror(tempEdit);
240 					senderr++;
241 					goto cant;
242 				}
243 			}
244 			if ((fout = fdopen(fd, "a")) == NULL) {
245 				perror(tempEdit);
246 				senderr++;
247 				goto cant;
248 			}
249 			image = open(tempEdit, O_RDWR);
250 			unlink(tempEdit);
251 			if (image < 0) {
252 				perror(tempEdit);
253 				senderr++;
254 				goto cant;
255 			} else {
256 				rewind(fo);
257 				time(&now);
258 				date = ctime(&now);
259 				fprintf(fout, "From %s %s", myname, date);
260 				while ((c = getc(fo)) != EOF)
261 					putc(c, fout);
262 				rewind(fo);
263 				fflush(fout);
264 				if (fferror(fout))
265 					perror(tempEdit);
266 				fclose(fout);
267 			}
268 		}
269 
270 		/*
271 		 * Now either copy "image" to the desired file
272 		 * or give it as the standard input to the desired
273 		 * program as appropriate.
274 		 */
275 
276 		if (ispipe) {
277 			wait((int *)NULL);
278 			switch (fork()) {
279 			case 0:
280 				sigchild();
281 				sigset(SIGHUP, SIG_IGN);
282 				sigset(SIGINT, SIG_IGN);
283 				sigset(SIGQUIT, SIG_IGN);
284 				close(0);
285 				dup(image);
286 				close(image);
287 				lseek(0, 0L, 0);
288 				if ((shell = value("SHELL")) == NOSTR ||
289 				    *shell == '\0')
290 					shell = SHELL;
291 				(void) execlp(shell, shell, "-c", fname,
292 				    (char *)0);
293 				perror(shell);
294 				exit(1);
295 				break;
296 
297 			case (pid_t)-1:
298 				perror("fork");
299 				senderr++;
300 				goto cant;
301 			}
302 		} else {
303 			if ((fout = fopen(fname, "a")) == NULL) {
304 				perror(fname);
305 				senderr++;
306 				goto cant;
307 			}
308 			fin = Fdopen(image, "r");
309 			if (fin == NULL) {
310 				fprintf(stderr,
311 				    gettext("Can't reopen image\n"));
312 				fclose(fout);
313 				senderr++;
314 				goto cant;
315 			}
316 			rewind(fin);
317 #ifdef preSVr4
318 			putc(getc(fin), fout);
319 			while (fgets(line, sizeof (line), fin)) {
320 				if (strncmp(line, "From ", 5) == 0)
321 					putc('>', fout);
322 				fputs(line, fout);
323 			}
324 #else
325 			while ((c = getc(fin)) != EOF)
326 				putc(c, fout);
327 #endif
328 			putc('\n', fout);
329 			fflush(fout);
330 			if (fferror(fout))
331 				senderr++, perror(fname);
332 			fclose(fout);
333 			fclose(fin);
334 		}
335 cant:
336 		/*
337 		 * In days of old we removed the entry from the
338 		 * the list; now for sake of header expansion
339 		 * we leave it in and mark it as deleted.
340 		 */
341 
342 #ifdef CRAZYWOW
343 		{
344 		register struct name *t, *x;
345 
346 		if (np == top) {
347 			top = np->n_flink;
348 			if (top != NIL)
349 				top->n_blink = NIL;
350 			np = top;
351 			continue;
352 		}
353 		x = np->n_blink;
354 		t = np->n_flink;
355 		x->n_flink = t;
356 		if (t != NIL)
357 			t->n_blink = x;
358 		np = t;
359 		}
360 #endif
361 
362 		np->n_type |= GDEL;
363 	}
364 	if (image >= 0) {
365 		close(image);
366 		image = -1;
367 	}
368 	return (nout);
369 }
370 
371 /*
372  * Determine if the passed address is a local "send to file" address.
373  * If any of the network metacharacters precedes any slashes, it can't
374  * be a filename.  We cheat with .'s to allow path names like ./...
375  * If "fcc" has been unset, then short-circuit those tests, but not
376  * the +... test.
377  */
378 static int
379 isfileaddr(char *name)
380 {
381 	register char *cp;
382 	char *fcc = value("fcc");
383 
384 	if (any('@', name))
385 		return (0);
386 	if (*name == '+')
387 		return (1);
388 	if (fcc == NOSTR)
389 		return (0);
390 	for (cp = name; *cp; cp++) {
391 		if (*cp == '.')
392 			continue;
393 		if (any(*cp, metanet))
394 			return (0);
395 		if (*cp == '/')
396 			return (1);
397 	}
398 	return (0);
399 }
400 
401 /*
402  * Map all of the aliased users in the invoker's mailrc
403  * file and insert them into the list.
404  * Changed after all these months of service to recursively
405  * expand names (2/14/80).
406  */
407 
408 struct name *
409 usermap(struct name *names)
410 {
411 	register struct name *newnames, *np, *cp;
412 	struct grouphead *gh;
413 	register int metoo;
414 
415 	newnames = NIL;
416 	np = names;
417 	metoo = (value("metoo") != NOSTR);
418 	while (np != NIL) {
419 		if (np->n_name[0] == '\\') {
420 			cp = np->n_flink;
421 			newnames = put(newnames, np);
422 			np = cp;
423 			continue;
424 		}
425 		gh = findgroup(np->n_name);
426 		cp = np->n_flink;
427 		if (gh != NOGRP)
428 			newnames = gexpand(newnames, gh, metoo, np->n_type);
429 		else
430 			newnames = put(newnames, np);
431 		np = cp;
432 	}
433 	return (newnames);
434 }
435 
436 /*
437  * Recursively expand a group name.  We limit the expansion to some
438  * fixed level to keep things from going haywire.
439  * Direct recursion is not expanded for convenience.
440  */
441 
442 static struct name *
443 gexpand(struct name *nlist, struct grouphead *gh, int metoo, int arg_ntype)
444 {
445 	short ntype = (short)arg_ntype;
446 	struct mgroup *gp;
447 	struct grouphead *ngh;
448 	struct name *np;
449 	static int depth;
450 	register char *cp;
451 
452 	if (depth > MAXEXP) {
453 		printf(gettext("Expanding alias to depth larger than %d\n"),
454 		    MAXEXP);
455 		return (nlist);
456 	}
457 	depth++;
458 	for (gp = gh->g_list; gp != NOGE; gp = gp->ge_link) {
459 		cp = gp->ge_name;
460 		if (*cp == '\\')
461 			goto quote;
462 		if (strcmp(cp, gh->g_name) == 0)
463 			goto quote;
464 		if ((ngh = findgroup(cp)) != NOGRP) {
465 			nlist = gexpand(nlist, ngh, metoo, ntype);
466 			continue;
467 		}
468 quote:
469 		np = nalloc(cp);
470 		np->n_type = ntype;
471 		/*
472 		 * At this point should allow to expand
473 		 * to self if only person in group
474 		 */
475 		if (gp == gh->g_list && gp->ge_link == NOGE)
476 			goto skip;
477 		if (!metoo && samebody(myname, gp->ge_name, FALSE))
478 			np->n_type |= GDEL;
479 skip:
480 		nlist = put(nlist, np);
481 	}
482 	depth--;
483 	return (nlist);
484 }
485 
486 /*
487  * Normalize a network name for comparison purposes.
488  */
489 static char *
490 norm(register char *user, register char *ubuf, int nbangs)
491 {
492 	register char *cp;
493 	int inubuf = 0;
494 
495 	while (*user++ == '!')
496 		;
497 	user--;
498 	if (!strchr(user, '!')) {
499 		snprintf(ubuf, BUFSIZ, "%s!%s", host, user);
500 		user = ubuf;
501 		inubuf++;
502 	}
503 	if (nbangs) {
504 		cp = user + strlen(user);
505 		while (nbangs--)
506 			while (cp > user && *--cp != '!')
507 				;
508 		user = (cp > user) ? ++cp : cp;
509 		/*
510 		 * Now strip off all Internet-type
511 		 * hosts.
512 		 */
513 		if ((cp = strchr(user, '%')) == NOSTR)
514 			cp = strchr(user, '@');
515 		if (cp != NOSTR) {
516 			if (!inubuf) {
517 				strncpy(ubuf, user, cp - user);
518 				ubuf[cp - user] = '\0';
519 				user = ubuf;
520 			} else
521 				*cp = '\0';
522 		}
523 	}
524 	return (user);
525 }
526 
527 /*
528  * Implement allnet options.
529  */
530 int
531 samebody(register char *user, register char *addr, int fuzzy)
532 {
533 	char ubuf[BUFSIZ], abuf[BUFSIZ];
534 	char *allnet = value("allnet");
535 	int nbangs = allnet ? (strcmp(allnet, "uucp") == 0) ? 2 : 1 : 0;
536 
537 	if (fuzzy && value("fuzzymatch")) {
538 		int i;
539 
540 		(void) strlcpy(ubuf, user, BUFSIZ);
541 		for (i = 0; ubuf[i]; i++)
542 			ubuf[i] = tolower(ubuf[i]);
543 		(void) strlcpy(abuf, addr, BUFSIZ);
544 		for (i = 0; abuf[i]; i++)
545 			abuf[i] = tolower(abuf[i]);
546 		return (strstr(abuf, ubuf) != NOSTR);
547 	}
548 	user = norm(user, ubuf, nbangs);
549 	addr = norm(addr, abuf, nbangs);
550 	return (strcmp(user, addr) == 0);
551 }
552 
553 /*
554  * Compute the length of the passed name list and
555  * return it.
556  */
557 static int
558 lengthof(struct name *name)
559 {
560 	register struct name *np;
561 	register int c;
562 
563 	for (c = 0, np = name; np != NIL; c++, np = np->n_flink)
564 		;
565 	return (c);
566 }
567 
568 /*
569  * Concatenate the two passed name lists, return the result.
570  */
571 
572 struct name *
573 cat(struct name *n1, struct name *n2)
574 {
575 	register struct name *tail;
576 
577 	if (n1 == NIL)
578 		return (n2);
579 	if (n2 == NIL)
580 		return (n1);
581 	tail = tailof(n1);
582 	tail->n_flink = n2;
583 	n2->n_blink = tail;
584 	return (n1);
585 }
586 
587 /*
588  * Unpack the name list onto a vector of strings.
589  * Return an error if the name list won't fit.
590  */
591 
592 char **
593 unpack(struct name *np)
594 {
595 	register char **ap, **top;
596 	register struct name *n;
597 	char hbuf[10];
598 	int t, extra, metoo, verbose;
599 
600 	n = np;
601 	if ((t = lengthof(n)) == 0)
602 		panic("No names to unpack");
603 
604 	/*
605 	 * Compute the number of extra arguments we will need.  We need at least
606 	 * 3 extra -- one for "mail", one for a terminating -- to stop sendmail
607 	 * option processing, and one for the terminating 0 pointer.
608 	 *
609 	 * Additional spots may be needed to pass along -r and -f to the host
610 	 * mailer.
611 	 */
612 
613 	extra = 3;
614 
615 	if (rflag != NOSTR)
616 		extra += 2;
617 	extra++;
618 	metoo = value("metoo") != NOSTR;
619 	if (metoo)
620 		extra++;
621 	verbose = value("verbose") != NOSTR;
622 	if (verbose)
623 		extra++;
624 	if (hflag)
625 		extra += 2;
626 	top = (char **)salloc((t + extra) * sizeof (char *));
627 	ap = top;
628 	*ap++ = "sendmail";
629 	if (rflag != NOSTR) {
630 		*ap++ = "-r";
631 		*ap++ = rflag;
632 	}
633 	*ap++ = "-i";
634 	if (metoo)
635 		*ap++ = "-m";
636 	if (verbose)
637 		*ap++ = "-v";
638 	if (hflag) {
639 		*ap++ = "-h";
640 		snprintf(hbuf, sizeof (hbuf), "%d", hflag);
641 		*ap++ = savestr(hbuf);
642 	}
643 	*ap++ = "--";
644 	while (n != NIL) {
645 		if (n->n_type & GDEL) {
646 			n = n->n_flink;
647 			continue;
648 		}
649 		*ap++ = n->n_name;
650 		n = n->n_flink;
651 	}
652 	*ap = NOSTR;
653 	return (top);
654 }
655 
656 /*
657  * See if the user named himself as a destination
658  * for outgoing mail.  If so, set the global flag
659  * selfsent so that we avoid removing his mailbox.
660  */
661 
662 void
663 mechk(struct name *names)
664 {
665 	register struct name *np;
666 
667 	for (np = names; np != NIL; np = np->n_flink)
668 		if ((np->n_type & GDEL) == 0 &&
669 		    samebody(np->n_name, myname, FALSE)) {
670 			selfsent++;
671 			return;
672 		}
673 }
674 
675 /*
676  * Remove all of the duplicates from the passed name list by
677  * insertion sorting them, then checking for dups.
678  * Return the head of the new list.
679  */
680 
681 struct name *
682 elide(struct name *names)
683 {
684 	register struct name *np, *t, *newnames;
685 	struct name *x;
686 
687 	if (names == NIL)
688 		return (NIL);
689 	newnames = names;
690 	np = names;
691 	np = np->n_flink;
692 	if (np != NIL)
693 		np->n_blink = NIL;
694 	newnames->n_flink = NIL;
695 	while (np != NIL) {
696 		t = newnames;
697 		while (strcmp(t->n_name, np->n_name) < 0) {
698 			if (t->n_flink == NIL)
699 				break;
700 			t = t->n_flink;
701 		}
702 
703 		/*
704 		 * If we ran out of t's, put the new entry after
705 		 * the current value of t.
706 		 */
707 
708 		if (strcmp(t->n_name, np->n_name) < 0) {
709 			t->n_flink = np;
710 			np->n_blink = t;
711 			t = np;
712 			np = np->n_flink;
713 			t->n_flink = NIL;
714 			continue;
715 		}
716 
717 		/*
718 		 * Otherwise, put the new entry in front of the
719 		 * current t.  If at the front of the list,
720 		 * the new guy becomes the new head of the list.
721 		 */
722 
723 		if (t == newnames) {
724 			t = np;
725 			np = np->n_flink;
726 			t->n_flink = newnames;
727 			newnames->n_blink = t;
728 			t->n_blink = NIL;
729 			newnames = t;
730 			continue;
731 		}
732 
733 		/*
734 		 * The normal case -- we are inserting into the
735 		 * middle of the list.
736 		 */
737 
738 		x = np;
739 		np = np->n_flink;
740 		x->n_flink = t;
741 		x->n_blink = t->n_blink;
742 		t->n_blink->n_flink = x;
743 		t->n_blink = x;
744 	}
745 
746 	/*
747 	 * Now the list headed up by new is sorted.
748 	 * Go through it and remove duplicates.
749 	 * Remember the best "type" among all the
750 	 * duplicates of a name.
751 	 */
752 
753 	np = newnames;
754 	while (np != NIL) {
755 		int type;
756 
757 		t = np;
758 		type = np->n_type;
759 		while (t->n_flink != NIL &&
760 		    strcmp(np->n_name, t->n_flink->n_name) == 0) {
761 			t = t->n_flink;
762 			/* "To" before "Cc" before "Bcc" */
763 			if (t->n_type < type)
764 				type = t->n_type;
765 		}
766 		if (t == np || t == NIL) {
767 			np = np->n_flink;
768 			continue;
769 		}
770 
771 		/*
772 		 * Now t points to the last entry with the same name
773 		 * as np.  Make np point beyond t.
774 		 */
775 
776 		np->n_flink = t->n_flink;
777 		if (t->n_flink != NIL)
778 			t->n_flink->n_blink = np;
779 		np->n_type = type;
780 		np = np->n_flink;
781 	}
782 	return (newnames);
783 }
784 
785 /*
786  * Put another node onto a list of names and return
787  * the list.
788  */
789 
790 static struct name *
791 put(struct name *list, struct name *node)
792 {
793 	node->n_flink = list;
794 	node->n_blink = NIL;
795 	if (list != NIL)
796 		list->n_blink = node;
797 	return (node);
798 }
799 
800 
801 /*
802  * Delete the given name from a namelist.
803  */
804 struct name *
805 delname(register struct name *np, char name[])
806 {
807 	register struct name *p;
808 
809 	for (p = np; p != NIL; p = p->n_flink)
810 		if (samebody(name, p->n_name, FALSE)) {
811 			if (p->n_blink == NIL) {
812 				if (p->n_flink != NIL)
813 					p->n_flink->n_blink = NIL;
814 				np = p->n_flink;
815 				continue;
816 			}
817 			if (p->n_flink == NIL) {
818 				if (p->n_blink != NIL)
819 					p->n_blink->n_flink = NIL;
820 				continue;
821 			}
822 			p->n_blink->n_flink = p->n_flink;
823 			p->n_flink->n_blink = p->n_blink;
824 		}
825 	return (np);
826 }
827 
828 /*
829  * Call the given routine on each element of the name
830  * list, replacing said value if need be.
831  */
832 
833 void
834 mapf(register struct name *np, char *from)
835 {
836 	register struct name *p;
837 
838 	if (debug) fprintf(stderr, "mapf %lx, %s\n", (long)np, from);
839 	for (p = np; p != NIL; p = p->n_flink)
840 		if ((p->n_type & GDEL) == 0) {
841 			p->n_name = netmap(p->n_name, from);
842 			p->n_full = splice(p->n_name, p->n_full);
843 		}
844 	if (debug) fprintf(stderr, "mapf %s done\n", from);
845 }
846