xref: /original-bsd/usr.bin/mail/optim.c (revision 0a83ae40)
1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  */
6 
7 #ifndef lint
8 static char *sccsid = "@(#)optim.c	5.8 (Berkeley) 09/28/87";
9 #endif not lint
10 
11 /*
12  * Mail -- a program for sending and receiving mail.
13  *
14  * Network name modification routines.
15  */
16 
17 #include "rcv.h"
18 #include "configdefs.h"
19 
20 /*
21  * Map a name into the correct network "view" of the
22  * name.  This is done by prepending the name with the
23  * network address of the sender, then optimizing away
24  * nonsense.
25  */
26 
27 char *
28 netmap(name, from)
29 	char name[], from[];
30 {
31 	char nbuf[BUFSIZ], ret[BUFSIZ];
32 	register char *cp;
33 
34 	if (strlen(from) == 0)
35 		return(name);
36 	if (any('@', name) || any('%', name))
37 		return(savestr(arpafix(name, from)));
38 	cp = revarpa(from);
39 	if (cp == NOSTR)
40 		return(name);
41 	strcpy(nbuf, cp);
42 	cp = &nbuf[strlen(nbuf) - 1];
43 	while (!any(*cp, metanet) && cp > nbuf)
44 		cp--;
45 	if (cp == nbuf)
46 		return(name);
47 	*++cp = 0;
48 	if ((cp = revarpa(name)) != NOSTR)
49 		strcat(nbuf, cp);
50 	optim(nbuf, ret);
51 	cp = revarpa(ret);
52 	if (cp && !icequal(name, cp))
53 		return(savestr(cp));
54 	return(name);
55 }
56 
57 /*
58  * Turn a network machine name into a unique character
59  */
60 netlook(machine, attnet)
61 	char machine[];
62 {
63 	register struct netmach *np;
64 	register char *cp, *cp2;
65 	char nbuf[BUFSIZ];
66 
67 	/*
68 	 * Make into lower case.
69 	 */
70 
71 	for (cp = machine, cp2 = nbuf; *cp; *cp2++ = little(*cp++))
72 		if (cp2 >= &nbuf[sizeof(nbuf)-1])
73 			break;
74 	*cp2 = 0;
75 
76 	/*
77 	 * If a single letter machine, look through those first.
78 	 */
79 
80 	if (strlen(nbuf) == 1)
81 		for (np = netmach; np->nt_mid != 0; np++)
82 			if (np->nt_mid == nbuf[0])
83 				return(nbuf[0]);
84 
85 	/*
86 	 * Look for usual name
87 	 */
88 
89 	for (np = netmach; np->nt_mid != 0; np++)
90 		if (strcmp(np->nt_machine, nbuf) == 0)
91 			return(np->nt_mid);
92 
93 	/*
94 	 * Look in side hash table.
95 	 */
96 
97 	return(mstash(nbuf, attnet));
98 }
99 
100 /*
101  * Make a little character.
102  */
103 
104 little(c)
105 	register int c;
106 {
107 
108 	if (c >= 'A' && c <= 'Z')
109 		c += 'a' - 'A';
110 	return(c);
111 }
112 
113 /*
114  * Turn a network unique character identifier into a network name.
115  */
116 
117 char *
118 netname(mid)
119 {
120 	register struct netmach *np;
121 	char *mlook();
122 
123 	if (mid & 0200)
124 		return(mlook(mid));
125 	for (np = netmach; np->nt_mid != 0; np++)
126 		if (np->nt_mid == mid)
127 			return(np->nt_machine);
128 	return(NOSTR);
129 }
130 
131 /*
132  * Deal with arpa net addresses.  The way this is done is strange.
133  * In particular, if the destination arpa net host is not Berkeley,
134  * then the address is correct as stands.  Otherwise, we strip off
135  * the trailing @Berkeley, then cook up a phony person for it to
136  * be from and optimize the result.
137  */
138 char *
139 arpafix(name, from)
140 	char name[];
141 	char from[];
142 {
143 	register char *cp;
144 	register int arpamach;
145 	char newname[BUFSIZ];
146 	char fake[5];
147 	char fakepath[20];
148 
149 	if (debug) {
150 		fprintf(stderr, "arpafix(%s, %s)\n", name, from);
151 	}
152 	cp = rindex(name, '@');
153 	if (cp == NOSTR)
154 		cp = rindex(name, '%');
155 	if (cp == NOSTR) {
156 		fprintf(stderr, "Somethings amiss -- no @ or %% in arpafix\n");
157 		return(name);
158 	}
159 	cp++;
160 	arpamach = netlook(cp, '@');
161 	if (arpamach == 0) {
162 		if (debug)
163 			fprintf(stderr, "machine %s unknown, uses: %s\n", cp, name);
164 		return(name);
165 	}
166 	if (((nettype(arpamach) & nettype(LOCAL)) & ~AN) == 0) {
167 		if (debug)
168 			fprintf(stderr, "machine %s known but remote, uses: %s\n",
169 			    cp, name);
170 		return(name);
171 	}
172 	strcpy(newname, name);
173 	cp = rindex(newname, '@');
174 	if (cp == NOSTR)
175 		cp = rindex(newname, '%');
176 	*cp = 0;
177 	fake[0] = arpamach;
178 	fake[1] = ':';
179 	fake[2] = LOCAL;
180 	fake[3] = ':';
181 	fake[4] = 0;
182 	prefer(fake);
183 	strcpy(fakepath, netname(fake[0]));
184 	stradd(fakepath, fake[1]);
185 	strcat(fakepath, "daemon");
186 	if (debug)
187 		fprintf(stderr, "machine local, call netmap(%s, %s)\n",
188 		    newname, fakepath);
189 	return(netmap(newname, fakepath));
190 }
191 
192 /*
193  * Take a network machine descriptor and find the types of connected
194  * nets and return it.
195  */
196 
197 nettype(mid)
198 {
199 	register struct netmach *np;
200 
201 	if (mid & 0200)
202 		return(mtype(mid));
203 	for (np = netmach; np->nt_mid != 0; np++)
204 		if (np->nt_mid == mid)
205 			return(np->nt_type);
206 	return(0);
207 }
208 
209 /*
210  * Hashing routines to salt away machines seen scanning
211  * networks paths that we don't know about.
212  */
213 
214 #define	XHSIZE		19		/* Size of extra hash table */
215 #define	NXMID		(XHSIZE*3/4)	/* Max extra machines */
216 
217 struct xtrahash {
218 	char	*xh_name;		/* Name of machine */
219 	short	xh_mid;			/* Machine ID */
220 	short	xh_attnet;		/* Attached networks */
221 } xtrahash[XHSIZE];
222 
223 struct xtrahash	*xtab[XHSIZE];		/* F: mid-->machine name */
224 
225 short	midfree;			/* Next free machine id */
226 
227 /*
228  * Initialize the extra host hash table.
229  * Called by sreset.
230  */
231 
232 minit()
233 {
234 	register struct xtrahash *xp, **tp;
235 
236 	midfree = 0;
237 	tp = &xtab[0];
238 	for (xp = &xtrahash[0]; xp < &xtrahash[XHSIZE]; xp++) {
239 		xp->xh_name = NOSTR;
240 		xp->xh_mid = 0;
241 		xp->xh_attnet = 0;
242 		*tp++ = (struct xtrahash *) 0;
243 	}
244 }
245 
246 /*
247  * Stash a net name in the extra host hash table.
248  * If a new entry is put in the hash table, deduce what
249  * net the machine is attached to from the net character.
250  *
251  * If the machine is already known, add the given attached
252  * net to those already known.
253  */
254 
255 mstash(name, attnet)
256 	char name[];
257 {
258 	register struct xtrahash *xp;
259 	struct xtrahash *xlocate();
260 	int x;
261 
262 	xp = xlocate(name);
263 	if (xp == (struct xtrahash *) 0) {
264 		printf("Ran out of machine id spots\n");
265 		return(0);
266 	}
267 	if (xp->xh_name == NOSTR) {
268 		if (midfree >= XHSIZE) {
269 			printf("Out of machine ids\n");
270 			return(0);
271 		}
272 		xtab[midfree] = xp;
273 		xp->xh_name = savestr(name);
274 		xp->xh_mid = 0200 + midfree++;
275 	}
276 	x = ntype(attnet);
277 	if (x == 0)
278 		xp->xh_attnet |= SN;
279 	else
280 		xp->xh_attnet |= x;
281 	return(xp->xh_mid);
282 }
283 
284 /*
285  * Search for the given name in the hash table
286  * and return the pointer to it if found, or to the first
287  * empty slot if not found.
288  *
289  * If no free slots can be found, return 0.
290  */
291 
292 struct xtrahash *
293 xlocate(name)
294 	char name[];
295 {
296 	register int h, q, i;
297 	register char *cp;
298 	register struct xtrahash *xp;
299 
300 	for (h = 0, cp = name; *cp; h = (h << 2) + *cp++)
301 		;
302 	if (h < 0 && (h = -h) < 0)
303 		h = 0;
304 	h = h % XHSIZE;
305 	cp = name;
306 	for (i = 0, q = 0; q < XHSIZE; i++, q = i * i) {
307 		xp = &xtrahash[(h + q) % XHSIZE];
308 		if (xp->xh_name == NOSTR)
309 			return(xp);
310 		if (strcmp(cp, xp->xh_name) == 0)
311 			return(xp);
312 		if (h - q < 0)
313 			h += XHSIZE;
314 		xp = &xtrahash[(h - q) % XHSIZE];
315 		if (xp->xh_name == NOSTR)
316 			return(xp);
317 		if (strcmp(cp, xp->xh_name) == 0)
318 			return(xp);
319 	}
320 	return((struct xtrahash *) 0);
321 }
322 
323 /*
324  * Return the name from the extra host hash table corresponding
325  * to the passed machine id.
326  */
327 
328 char *
329 mlook(mid)
330 {
331 	register int m;
332 
333 	if ((mid & 0200) == 0)
334 		return(NOSTR);
335 	m = mid & 0177;
336 	if (m >= midfree) {
337 		printf("Use made of undefined machine id\n");
338 		return(NOSTR);
339 	}
340 	return(xtab[m]->xh_name);
341 }
342 
343 /*
344  * Return the bit mask of net's that the given extra host machine
345  * id has so far.
346  */
347 
348 mtype(mid)
349 {
350 	register int m;
351 
352 	if ((mid & 0200) == 0)
353 		return(0);
354 	m = mid & 0177;
355 	if (m >= midfree) {
356 		printf("Use made of undefined machine id\n");
357 		return(0);
358 	}
359 	return(xtab[m]->xh_attnet);
360 }
361 
362 /*
363  * Take a network name and optimize it.  This gloriously messy
364  * operation takes place as follows:  the name with machine names
365  * in it is tokenized by mapping each machine name into a single
366  * character machine id (netlook).  The separator characters (network
367  * metacharacters) are left intact.  The last component of the network
368  * name is stripped off and assumed to be the destination user name --
369  * it does not participate in the optimization.  As an example, the
370  * name "research!vax135!research!ucbvax!bill" becomes, tokenized,
371  * "r!x!r!v!" and "bill"  A low level routine, optim1, fixes up the
372  * network part (eg, "r!x!r!v!"), then we convert back to network
373  * machine names and tack the user name on the end.
374  *
375  * The result of this is copied into the parameter "name"
376  */
377 
378 optim(net, name)
379 	char net[], name[];
380 {
381 	char netcomp[BUFSIZ], netstr[40], xfstr[40];
382 	register char *cp, *cp2;
383 	register int c;
384 
385 	strcpy(netstr, "");
386 	cp = net;
387 	for (;;) {
388 		/*
389 		 * Rip off next path component into netcomp
390 		 */
391 		cp2 = netcomp;
392 		while (*cp && !any(*cp, metanet))
393 			*cp2++ = *cp++;
394 		*cp2 = 0;
395 		/*
396 		 * If we hit null byte, then we just scanned
397 		 * the destination user name.  Go off and optimize
398 		 * if its so.
399 		 */
400 		if (*cp == 0)
401 			break;
402 		if ((c = netlook(netcomp, *cp)) == 0) {
403 			printf("No host named \"%s\"\n", netcomp);
404 err:
405 			strcpy(name, net);
406 			return;
407 		}
408 		stradd(netstr, c);
409 		stradd(netstr, *cp++);
410 		/*
411 		 * If multiple network separators given,
412 		 * throw away the extras.
413 		 */
414 		while (any(*cp, metanet))
415 			cp++;
416 	}
417 	if (strlen(netcomp) == 0) {
418 		printf("net name syntax\n");
419 		goto err;
420 	}
421 	optim1(netstr, xfstr);
422 
423 	/*
424 	 * Convert back to machine names.
425 	 */
426 
427 	cp = xfstr;
428 	strcpy(name, "");
429 	while (*cp) {
430 		if ((cp2 = netname(*cp++)) == NOSTR) {
431 			printf("Made up bad net name\n");
432 			printf("Machine code %c (0%o)\n", cp[-1], cp[-1]);
433 			printf("Sorry -- dumping now.  Alert K. Shoens\n");
434 			core();
435 			goto err;
436 		}
437 		strcat(name, cp2);
438 		stradd(name, *cp++);
439 	}
440 	strcat(name, netcomp);
441 }
442 
443 /*
444  * Take a string of network machine id's and separators and
445  * optimize them.  We process these by pulling off maximal
446  * leading strings of the same type, passing these to the appropriate
447  * optimizer and concatenating the results.
448  */
449 
450 optim1(netstr, name)
451 	char netstr[], name[];
452 {
453 	char path[40], rpath[40];
454 	register char *cp, *cp2;
455 	register int tp, nc;
456 
457 	cp = netstr;
458 	prefer(cp);
459 	strcpy(name, "");
460 	/*
461 	 * If the address ultimately points back to us,
462 	 * just return a null network path.
463 	 */
464 	if (strlen(cp) > 1 && cp[strlen(cp) - 2] == LOCAL)
465 		return;
466 	while (*cp != 0) {
467 		strcpy(path, "");
468 		tp = ntype(cp[1]);
469 		nc = cp[1];
470 		while (*cp && tp == ntype(cp[1])) {
471 			stradd(path, *cp++);
472 			cp++;
473 		}
474 		switch (netkind(tp)) {
475 		default:
476 			strcpy(rpath, path);
477 			break;
478 
479 		case IMPLICIT:
480 			optimimp(path, rpath);
481 			break;
482 
483 		case EXPLICIT:
484 			optimex(path, rpath);
485 			break;
486 		}
487 		for (cp2 = rpath; *cp2 != 0; cp2++) {
488 			stradd(name, *cp2);
489 			stradd(name, nc);
490 		}
491 	}
492 	optiboth(name);
493 	prefer(name);
494 }
495 
496 /*
497  * Return the network of the separator --
498  *	AN for arpa net
499  *	BN for Bell labs net
500  *	SN for Schmidt (berkeley net)
501  *	0 if we don't know.
502  */
503 
504 ntype(nc)
505 	register int nc;
506 {
507 	register struct ntypetab *np;
508 
509 	for (np = ntypetab; np->nt_char != 0; np++)
510 		if (np->nt_char == nc)
511 			return(np->nt_bcode);
512 	return(0);
513 }
514 
515 /*
516  * Return the kind of routing used for the particular net
517  * EXPLICIT means explicitly routed
518  * IMPLICIT means implicitly routed
519  * 0 means don't know
520  */
521 
522 netkind(nt)
523 	register int nt;
524 {
525 	register struct nkindtab *np;
526 
527 	for (np = nkindtab; np->nk_type != 0; np++)
528 		if (np->nk_type == nt)
529 			return(np->nk_kind);
530 	return(0);
531 }
532 
533 /*
534  * Do name optimization for an explicitly routed network (eg BTL network).
535  */
536 
537 optimex(net, name)
538 	char net[], name[];
539 {
540 	register char *cp, *rp;
541 	register int m;
542 
543 	strcpy(name, net);
544 	cp = name;
545 	if (strlen(cp) == 0)
546 		return(-1);
547 	if (cp[strlen(cp)-1] == LOCAL) {
548 		name[0] = 0;
549 		return(0);
550 	}
551 	for (cp = name; *cp; cp++) {
552 		m = *cp;
553 		rp = rindex(cp+1, m);
554 		if (rp != NOSTR)
555 			strcpy(cp, rp);
556 	}
557 	return(0);
558 }
559 
560 /*
561  * Do name optimization for implicitly routed network (eg, arpanet,
562  * Berkeley network)
563  */
564 
565 optimimp(net, name)
566 	char net[], name[];
567 {
568 	register char *cp;
569 	register int m;
570 
571 	cp = net;
572 	if (strlen(cp) == 0)
573 		return(-1);
574 	m = cp[strlen(cp) - 1];
575 	if (m == LOCAL) {
576 		strcpy(name, "");
577 		return(0);
578 	}
579 	name[0] = m;
580 	name[1] = 0;
581 	return(0);
582 }
583 
584 /*
585  * Perform global optimization on the given network path.
586  * The trick here is to look ahead to see if there are any loops
587  * in the path and remove them.  The interpretation of loops is
588  * more strict here than in optimex since both the machine and net
589  * type must match.
590  */
591 
592 optiboth(net)
593 	char net[];
594 {
595 	register char *cp, *cp2;
596 	char *rpair();
597 
598 	cp = net;
599 	if (strlen(cp) == 0)
600 		return;
601 	if ((strlen(cp) % 2) != 0) {
602 		printf("Strange arg to optiboth\n");
603 		return;
604 	}
605 	while (*cp) {
606 		cp2 = rpair(cp+2, *cp);
607 		if (cp2 != NOSTR)
608 			strcpy(cp, cp2);
609 		cp += 2;
610 	}
611 }
612 
613 /*
614  * Find the rightmost instance of the given (machine, type) pair.
615  */
616 
617 char *
618 rpair(str, mach)
619 	char str[];
620 {
621 	register char *cp, *last;
622 
623 	cp = str;
624 	last = NOSTR;
625 	while (*cp) {
626 		if (*cp == mach)
627 			last = cp;
628 		cp += 2;
629 	}
630 	return(last);
631 }
632 
633 /*
634  * Change the network separators in the given network path
635  * to the preferred network transmission means.
636  */
637 
638 prefer(name)
639 	char name[];
640 {
641 	register char *cp;
642 	register int state, n;
643 
644 	state = LOCAL;
645 	for (cp = name; *cp; cp += 2) {
646 		n = best(state, *cp);
647 		if (n)
648 			cp[1] = n;
649 		state = *cp;
650 	}
651 }
652 
653 /*
654  * Return the best network separator for the given machine pair.
655  */
656 
657 best(src, dest)
658 {
659 	register int dtype, stype;
660 	register struct netorder *np;
661 
662 	stype = nettype(src);
663 	dtype = nettype(dest);
664 	fflush(stdout);
665 	if (stype == 0 || dtype == 0) {
666 		printf("ERROR:  unknown internal machine id\n");
667 		return(0);
668 	}
669 	if ((stype & dtype) == 0)
670 		return(0);
671 	np = &netorder[0];
672 	while ((np->no_stat & stype & dtype) == 0)
673 		np++;
674 	return(np->no_char);
675 }
676 
677 #ifdef	GETHOST
678 /*
679  * Initialize the network name of the current host.
680  */
681 inithost()
682 {
683 	register struct netmach *np;
684 	static char host[64];
685 
686 	gethostname(host, sizeof host);
687 	for (np = netmach; np->nt_machine != 0; np++)
688 		if (strcmp(np->nt_machine, EMPTY) == 0)
689 			break;
690 	if (np->nt_machine == 0) {
691 		printf("Cannot find empty slot for dynamic host entry\n");
692 		exit(1);
693 	}
694 	np->nt_machine = host;
695 }
696 #endif	GETHOST
697 
698 /*
699  * Code to twist around arpa net names.
700  */
701 
702 #define WORD 257			/* Token for a string */
703 
704 static	char netbuf[256];
705 static	char *yylval;
706 
707 /*
708  * Reverse all of the arpa net addresses in the given name to
709  * be of the form "host @ user" instead of "user @ host"
710  * This function is its own inverse.
711  */
712 
713 char *
714 revarpa(str)
715 	char str[];
716 {
717 
718 	if (yyinit(str) < 0)
719 		return(NOSTR);
720 	if (name())
721 		return(NOSTR);
722 	if (strcmp(str, netbuf) == 0)
723 		return(str);
724 	return(savestr(netbuf));
725 }
726 
727 /*
728  * Parse (by recursive descent) network names, using the following grammar:
729  *	name:
730  *		term {':' term}
731  *		term {'^' term}
732  *		term {'!' term}
733  *		term '@' name
734  *		term '%' name
735  *
736  *	term:
737  *		string of characters.
738  */
739 
740 name()
741 {
742 	register int t;
743 	register char *cp;
744 
745 	for (;;) {
746 		t = yylex();
747 		if (t != WORD)
748 			return(-1);
749 		cp = yylval;
750 		t = yylex();
751 		switch (t) {
752 		case 0:
753 			strcat(netbuf, cp);
754 			return(0);
755 
756 		case '@':
757 		case '%':
758 			if (name())
759 				return(-1);
760 			stradd(netbuf, '@');
761 			strcat(netbuf, cp);
762 			return(0);
763 
764 		case WORD:
765 			return(-1);
766 
767 		default:
768 			strcat(netbuf, cp);
769 			stradd(netbuf, t);
770 		}
771 	}
772 }
773 
774 /*
775  * Scanner for network names.
776  */
777 
778 static	char *charp;			/* Current input pointer */
779 static	int nexttok;			/* Salted away next token */
780 
781 /*
782  * Initialize the network name scanner.
783  */
784 
785 yyinit(str)
786 	char str[];
787 {
788 	static char lexbuf[BUFSIZ];
789 
790 	netbuf[0] = 0;
791 	if (strlen(str) >= sizeof lexbuf - 1)
792 		return(-1);
793 	nexttok = 0;
794 	strcpy(lexbuf, str);
795 	charp = lexbuf;
796 	return(0);
797 }
798 
799 /*
800  * Scan and return a single token.
801  * yylval is set to point to a scanned string.
802  */
803 
804 yylex()
805 {
806 	register char *cp, *dot;
807 	register int s;
808 
809 	if (nexttok) {
810 		s = nexttok;
811 		nexttok = 0;
812 		return(s);
813 	}
814 	cp = charp;
815 	while (*cp && isspace(*cp))
816 		cp++;
817 	if (*cp == 0)
818 		return(0);
819 	if (any(*cp, metanet)) {
820 		charp = cp+1;
821 		return(*cp);
822 	}
823 	dot = cp;
824 	while (*cp && !any(*cp, metanet) && !any(*cp, " \t"))
825 		cp++;
826 	if (any(*cp, metanet))
827 		nexttok = *cp;
828 	if (*cp == 0)
829 		charp = cp;
830 	else
831 		charp = cp+1;
832 	*cp = 0;
833 	yylval = dot;
834 	return(WORD);
835 }
836