xref: /original-bsd/usr.bin/rlogin/rlogin.c (revision 630dccfa)
1 /*
2  * Copyright (c) 1983, 1990 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1983, 1990 The Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)rlogin.c	5.33.1.1 (Berkeley) 08/20/91";
16 #endif /* not lint */
17 
18 /*
19  * $Source: mit/rlogin/RCS/rlogin.c,v $
20  * $Header: mit/rlogin/RCS/rlogin.c,v 5.2 89/07/26 12:11:21 kfall
21  *	Exp Locker: kfall $
22  */
23 
24 /*
25  * rlogin - remote login
26  */
27 #include <sys/param.h>
28 #include <sys/file.h>
29 #include <sys/socket.h>
30 #include <sys/signal.h>
31 #include <sys/time.h>
32 #include <sys/resource.h>
33 #include <sys/wait.h>
34 
35 #include <netinet/in.h>
36 #include <netinet/in_systm.h>
37 #include <netinet/ip.h>
38 #include <netdb.h>
39 
40 #include <sgtty.h>
41 #include <setjmp.h>
42 #include <varargs.h>
43 #include <errno.h>
44 #include <pwd.h>
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <string.h>
48 
49 #ifdef KERBEROS
50 #include <kerberosIV/des.h>
51 #include <kerberosIV/krb.h>
52 
53 CREDENTIALS cred;
54 Key_schedule schedule;
55 int use_kerberos = 1, doencrypt;
56 char dst_realm_buf[REALM_SZ], *dest_realm = NULL;
57 extern char *krb_realmofhost();
58 #endif
59 
60 #ifndef TIOCPKT_WINDOW
61 #define	TIOCPKT_WINDOW	0x80
62 #endif
63 
64 /* concession to Sun */
65 #ifndef SIGUSR1
66 #define	SIGUSR1	30
67 #endif
68 
69 extern int errno;
70 int eight, litout, rem;
71 
72 int noescape;
73 u_char escapechar = '~';
74 
75 char *speeds[] = {
76 	"0", "50", "75", "110", "134", "150", "200", "300", "600", "1200",
77 	"1800", "2400", "4800", "9600", "19200", "38400"
78 };
79 
80 #ifdef sun
81 struct winsize {
82 	unsigned short ws_row, ws_col;
83 	unsigned short ws_xpixel, ws_ypixel;
84 };
85 #endif
86 struct	winsize winsize;
87 
88 #ifndef sun
89 #define	get_window_size(fd, wp)	ioctl(fd, TIOCGWINSZ, wp)
90 #endif
91 
92 void exit();
93 
94 main(argc, argv)
95 	int argc;
96 	char **argv;
97 {
98 	extern char *optarg;
99 	extern int optind;
100 	struct passwd *pw;
101 	struct servent *sp;
102 	struct sgttyb ttyb;
103 	long omask;
104 	int argoff, ch, dflag, one, uid;
105 	char *host, *p, *user, term[1024];
106 	void lostpeer();
107 	u_char getescape();
108 	char *getenv();
109 
110 	argoff = dflag = 0;
111 	one = 1;
112 	host = user = NULL;
113 
114 	if (p = rindex(argv[0], '/'))
115 		++p;
116 	else
117 		p = argv[0];
118 
119 	if (strcmp(p, "rlogin"))
120 		host = p;
121 
122 	/* handle "rlogin host flags" */
123 	if (!host && argc > 2 && argv[1][0] != '-') {
124 		host = argv[1];
125 		argoff = 1;
126 	}
127 
128 #ifdef KERBEROS
129 #define	OPTIONS	"8EKLde:k:l:x"
130 #else
131 #define	OPTIONS	"8EKLde:l:"
132 #endif
133 	while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != EOF)
134 		switch(ch) {
135 		case '8':
136 			eight = 1;
137 			break;
138 		case 'E':
139 			noescape = 1;
140 			break;
141 		case 'K':
142 #ifdef KERBEROS
143 			use_kerberos = 0;
144 #endif
145 			break;
146 		case 'L':
147 			litout = 1;
148 			break;
149 		case 'd':
150 			dflag = 1;
151 			break;
152 		case 'e':
153 			escapechar = getescape(optarg);
154 			break;
155 #ifdef KERBEROS
156 		case 'k':
157 			dest_realm = dst_realm_buf;
158 			(void)strncpy(dest_realm, optarg, REALM_SZ);
159 			break;
160 #endif
161 		case 'l':
162 			user = optarg;
163 			break;
164 		case '?':
165 		default:
166 			usage();
167 		}
168 	optind += argoff;
169 	argc -= optind;
170 	argv += optind;
171 
172 	/* if haven't gotten a host yet, do so */
173 	if (!host && !(host = *argv++))
174 		usage();
175 
176 	if (*argv)
177 		usage();
178 
179 	if (!(pw = getpwuid(uid = getuid()))) {
180 		(void)fprintf(stderr, "rlogin: unknown user id.\n");
181 		exit(1);
182 	}
183 	if (!user)
184 		user = pw->pw_name;
185 
186 	sp = NULL;
187 #ifdef KERBEROS
188 	if (use_kerberos) {
189 		sp = getservbyname((doencrypt ? "eklogin" : "klogin"), "tcp");
190 		if (sp == NULL) {
191 			use_kerberos = 0;
192 			warning("can't get entry for %s/tcp service",
193 			    doencrypt ? "eklogin" : "klogin");
194 		}
195 	}
196 #endif
197 	if (sp == NULL)
198 		sp = getservbyname("login", "tcp");
199 	if (sp == NULL) {
200 		(void)fprintf(stderr, "rlogin: login/tcp: unknown service.\n");
201 		exit(1);
202 	}
203 
204 	(void)strcpy(term, (p = getenv("TERM")) ? p : "network");
205 	if (ioctl(0, TIOCGETP, &ttyb) == 0) {
206 		(void)strcat(term, "/");
207 		(void)strcat(term, speeds[ttyb.sg_ospeed]);
208 	}
209 
210 	(void)get_window_size(0, &winsize);
211 
212 	(void)signal(SIGPIPE, lostpeer);
213 	/* will use SIGUSR1 for window size hack, so hold it off */
214 	omask = sigblock(sigmask(SIGURG) | sigmask(SIGUSR1));
215 
216 #ifdef KERBEROS
217 try_connect:
218 	if (use_kerberos) {
219 		rem = KSUCCESS;
220 		errno = 0;
221 		if (dest_realm == NULL)
222 			dest_realm = krb_realmofhost(host);
223 
224 			rem = krcmd(&host, sp->s_port, user, term, 0,
225 			    dest_realm);
226 		if (rem < 0) {
227 			use_kerberos = 0;
228 			sp = getservbyname("login", "tcp");
229 			if (sp == NULL) {
230 				(void)fprintf(stderr,
231 				    "rlogin: unknown service login/tcp.\n");
232 				exit(1);
233 			}
234 			if (errno == ECONNREFUSED)
235 				warning("remote host doesn't support Kerberos");
236 			if (errno == ENOENT)
237 				warning("can't provide Kerberos auth data");
238 			goto try_connect;
239 		}
240 	} else {
241 		rem = rcmd(&host, sp->s_port, pw->pw_name, user, term, 0);
242 	}
243 #else
244 	rem = rcmd(&host, sp->s_port, pw->pw_name, user, term, 0);
245 #endif /* KERBEROS */
246 
247 	if (rem < 0)
248 		exit(1);
249 
250 	if (dflag &&
251 	    setsockopt(rem, SOL_SOCKET, SO_DEBUG, &one, sizeof(one)) < 0)
252 		(void)fprintf(stderr, "rlogin: setsockopt: %s.\n",
253 		    strerror(errno));
254 	one = IPTOS_LOWDELAY;
255 	if (setsockopt(rem, IPPROTO_IP, IP_TOS, (char *)&one, sizeof(int)) < 0)
256 		perror("rlogin: setsockopt TOS (ignored)");
257 
258 	(void)setuid(uid);
259 	doit(omask);
260 	/*NOTREACHED*/
261 }
262 
263 int child, defflags, deflflags, tabflag;
264 char deferase, defkill;
265 struct tchars deftc;
266 struct ltchars defltc;
267 struct tchars notc = { -1, -1, -1, -1, -1, -1 };
268 struct ltchars noltc = { -1, -1, -1, -1, -1, -1 };
269 
270 doit(omask)
271 	long omask;
272 {
273 	struct sgttyb sb;
274 	void catch_child(), copytochild(), exit(), writeroob();
275 
276 	(void)ioctl(0, TIOCGETP, (char *)&sb);
277 	defflags = sb.sg_flags;
278 	tabflag = defflags & TBDELAY;
279 	defflags &= ECHO | CRMOD;
280 	deferase = sb.sg_erase;
281 	defkill = sb.sg_kill;
282 	(void)ioctl(0, TIOCLGET, (char *)&deflflags);
283 	(void)ioctl(0, TIOCGETC, (char *)&deftc);
284 	notc.t_startc = deftc.t_startc;
285 	notc.t_stopc = deftc.t_stopc;
286 	(void)ioctl(0, TIOCGLTC, (char *)&defltc);
287 	(void)signal(SIGINT, SIG_IGN);
288 	setsignal(SIGHUP, exit);
289 	setsignal(SIGQUIT, exit);
290 	child = fork();
291 	if (child == -1) {
292 		(void)fprintf(stderr, "rlogin: fork: %s.\n", strerror(errno));
293 		done(1);
294 	}
295 	if (child == 0) {
296 		mode(1);
297 		if (reader(omask) == 0) {
298 			msg("connection closed.");
299 			exit(0);
300 		}
301 		sleep(1);
302 		msg("\007connection closed.");
303 		exit(1);
304 	}
305 
306 	/*
307 	 * We may still own the socket, and may have a pending SIGURG (or might
308 	 * receive one soon) that we really want to send to the reader.  Set a
309 	 * trap that simply copies such signals to the child.
310 	 */
311 	(void)signal(SIGURG, copytochild);
312 	(void)signal(SIGUSR1, writeroob);
313 	(void)sigsetmask(omask);
314 	(void)signal(SIGCHLD, catch_child);
315 	writer();
316 	msg("closed connection.");
317 	done(0);
318 }
319 
320 /* trap a signal, unless it is being ignored. */
321 setsignal(sig, act)
322 	int sig;
323 	void (*act)();
324 {
325 	int omask = sigblock(sigmask(sig));
326 
327 	if (signal(sig, act) == SIG_IGN)
328 		(void)signal(sig, SIG_IGN);
329 	(void)sigsetmask(omask);
330 }
331 
332 done(status)
333 	int status;
334 {
335 	int w, wstatus;
336 
337 	mode(0);
338 	if (child > 0) {
339 		/* make sure catch_child does not snap it up */
340 		(void)signal(SIGCHLD, SIG_DFL);
341 		if (kill(child, SIGKILL) >= 0)
342 			while ((w = wait(&wstatus)) > 0 && w != child);
343 	}
344 	exit(status);
345 }
346 
347 int dosigwinch;
348 void sigwinch();
349 
350 /*
351  * This is called when the reader process gets the out-of-band (urgent)
352  * request to turn on the window-changing protocol.
353  */
354 void
355 writeroob()
356 {
357 	if (dosigwinch == 0) {
358 		sendwindow();
359 		(void)signal(SIGWINCH, sigwinch);
360 	}
361 	dosigwinch = 1;
362 }
363 
364 void
365 catch_child()
366 {
367 	union wait status;
368 	int pid;
369 
370 	for (;;) {
371 		pid = wait3((int *)&status,
372 		    WNOHANG|WUNTRACED, (struct rusage *)0);
373 		if (pid == 0)
374 			return;
375 		/* if the child (reader) dies, just quit */
376 		if (pid < 0 || pid == child && !WIFSTOPPED(status))
377 			done((int)(status.w_termsig | status.w_retcode));
378 	}
379 	/* NOTREACHED */
380 }
381 
382 /*
383  * writer: write to remote: 0 -> line.
384  * ~.				terminate
385  * ~^Z				suspend rlogin process.
386  * ~<delayed-suspend char>	suspend rlogin process, but leave reader alone.
387  */
388 writer()
389 {
390 	register int bol, local, n;
391 	char c;
392 
393 	bol = 1;			/* beginning of line */
394 	local = 0;
395 	for (;;) {
396 		n = read(STDIN_FILENO, &c, 1);
397 		if (n <= 0) {
398 			if (n < 0 && errno == EINTR)
399 				continue;
400 			break;
401 		}
402 		/*
403 		 * If we're at the beginning of the line and recognize a
404 		 * command character, then we echo locally.  Otherwise,
405 		 * characters are echo'd remotely.  If the command character
406 		 * is doubled, this acts as a force and local echo is
407 		 * suppressed.
408 		 */
409 		if (bol) {
410 			bol = 0;
411 			if (!noescape && c == escapechar) {
412 				local = 1;
413 				continue;
414 			}
415 		} else if (local) {
416 			local = 0;
417 			if (c == '.' || c == deftc.t_eofc) {
418 				echo(c);
419 				break;
420 			}
421 			if (c == defltc.t_suspc || c == defltc.t_dsuspc) {
422 				bol = 1;
423 				echo(c);
424 				stop(c);
425 				continue;
426 			}
427 			if (c != escapechar)
428 					(void)write(rem, &escapechar, 1);
429 		}
430 
431 			if (write(rem, &c, 1) == 0) {
432 				msg("line gone");
433 				break;
434 			}
435 		bol = c == defkill || c == deftc.t_eofc ||
436 		    c == deftc.t_intrc || c == defltc.t_suspc ||
437 		    c == '\r' || c == '\n';
438 	}
439 }
440 
441 echo(c)
442 register char c;
443 {
444 	register char *p;
445 	char buf[8];
446 
447 	p = buf;
448 	c &= 0177;
449 	*p++ = escapechar;
450 	if (c < ' ') {
451 		*p++ = '^';
452 		*p++ = c + '@';
453 	} else if (c == 0177) {
454 		*p++ = '^';
455 		*p++ = '?';
456 	} else
457 		*p++ = c;
458 	*p++ = '\r';
459 	*p++ = '\n';
460 	(void)write(STDOUT_FILENO, buf, p - buf);
461 }
462 
463 stop(cmdc)
464 	char cmdc;
465 {
466 	mode(0);
467 	(void)signal(SIGCHLD, SIG_IGN);
468 	(void)kill(cmdc == defltc.t_suspc ? 0 : getpid(), SIGTSTP);
469 	(void)signal(SIGCHLD, catch_child);
470 	mode(1);
471 	sigwinch();			/* check for size changes */
472 }
473 
474 void
475 sigwinch()
476 {
477 	struct winsize ws;
478 
479 	if (dosigwinch && get_window_size(0, &ws) == 0 &&
480 	    bcmp(&ws, &winsize, sizeof(ws))) {
481 		winsize = ws;
482 		sendwindow();
483 	}
484 }
485 
486 /*
487  * Send the window size to the server via the magic escape
488  */
489 sendwindow()
490 {
491 	struct winsize *wp;
492 	char obuf[4 + sizeof (struct winsize)];
493 
494 	wp = (struct winsize *)(obuf+4);
495 	obuf[0] = 0377;
496 	obuf[1] = 0377;
497 	obuf[2] = 's';
498 	obuf[3] = 's';
499 	wp->ws_row = htons(winsize.ws_row);
500 	wp->ws_col = htons(winsize.ws_col);
501 	wp->ws_xpixel = htons(winsize.ws_xpixel);
502 	wp->ws_ypixel = htons(winsize.ws_ypixel);
503 
504 		(void)write(rem, obuf, sizeof(obuf));
505 }
506 
507 /*
508  * reader: read from remote: line -> 1
509  */
510 #define	READING	1
511 #define	WRITING	2
512 
513 jmp_buf rcvtop;
514 int ppid, rcvcnt, rcvstate;
515 char rcvbuf[8 * 1024];
516 
517 void
518 oob()
519 {
520 	struct sgttyb sb;
521 	int atmark, n, out, rcvd;
522 	char waste[BUFSIZ], mark;
523 
524 	out = O_RDWR;
525 	rcvd = 0;
526 	while (recv(rem, &mark, 1, MSG_OOB) < 0)
527 		switch (errno) {
528 		case EWOULDBLOCK:
529 			/*
530 			 * Urgent data not here yet.  It may not be possible
531 			 * to send it yet if we are blocked for output and
532 			 * our input buffer is full.
533 			 */
534 			if (rcvcnt < sizeof(rcvbuf)) {
535 				n = read(rem, rcvbuf + rcvcnt,
536 				    sizeof(rcvbuf) - rcvcnt);
537 				if (n <= 0)
538 					return;
539 				rcvd += n;
540 			} else {
541 				n = read(rem, waste, sizeof(waste));
542 				if (n <= 0)
543 					return;
544 			}
545 			continue;
546 		default:
547 			return;
548 	}
549 	if (mark & TIOCPKT_WINDOW) {
550 		/* Let server know about window size changes */
551 		(void)kill(ppid, SIGUSR1);
552 	}
553 	if (!eight && (mark & TIOCPKT_NOSTOP)) {
554 		(void)ioctl(0, TIOCGETP, (char *)&sb);
555 		sb.sg_flags &= ~CBREAK;
556 		sb.sg_flags |= RAW;
557 		(void)ioctl(0, TIOCSETN, (char *)&sb);
558 		notc.t_stopc = -1;
559 		notc.t_startc = -1;
560 		(void)ioctl(0, TIOCSETC, (char *)&notc);
561 	}
562 	if (!eight && (mark & TIOCPKT_DOSTOP)) {
563 		(void)ioctl(0, TIOCGETP, (char *)&sb);
564 		sb.sg_flags &= ~RAW;
565 		sb.sg_flags |= CBREAK;
566 		(void)ioctl(0, TIOCSETN, (char *)&sb);
567 		notc.t_stopc = deftc.t_stopc;
568 		notc.t_startc = deftc.t_startc;
569 		(void)ioctl(0, TIOCSETC, (char *)&notc);
570 	}
571 	if (mark & TIOCPKT_FLUSHWRITE) {
572 		(void)ioctl(1, TIOCFLUSH, (char *)&out);
573 		for (;;) {
574 			if (ioctl(rem, SIOCATMARK, &atmark) < 0) {
575 				(void)fprintf(stderr, "rlogin: ioctl: %s.\n",
576 				    strerror(errno));
577 				break;
578 			}
579 			if (atmark)
580 				break;
581 			n = read(rem, waste, sizeof (waste));
582 			if (n <= 0)
583 				break;
584 		}
585 		/*
586 		 * Don't want any pending data to be output, so clear the recv
587 		 * buffer.  If we were hanging on a write when interrupted,
588 		 * don't want it to restart.  If we were reading, restart
589 		 * anyway.
590 		 */
591 		rcvcnt = 0;
592 		longjmp(rcvtop, 1);
593 	}
594 
595 	/* oob does not do FLUSHREAD (alas!) */
596 
597 	/*
598 	 * If we filled the receive buffer while a read was pending, longjmp
599 	 * to the top to restart appropriately.  Don't abort a pending write,
600 	 * however, or we won't know how much was written.
601 	 */
602 	if (rcvd && rcvstate == READING)
603 		longjmp(rcvtop, 1);
604 }
605 
606 /* reader: read from remote: line -> 1 */
607 reader(omask)
608 	int omask;
609 {
610 	void oob();
611 
612 #if !defined(BSD) || BSD < 43
613 	int pid = -getpid();
614 #else
615 	int pid = getpid();
616 #endif
617 	int n, remaining;
618 	char *bufp = rcvbuf;
619 
620 	(void)signal(SIGTTOU, SIG_IGN);
621 	(void)signal(SIGURG, oob);
622 	ppid = getppid();
623 	(void)fcntl(rem, F_SETOWN, pid);
624 	(void)setjmp(rcvtop);
625 	(void)sigsetmask(omask);
626 	for (;;) {
627 		while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) {
628 			rcvstate = WRITING;
629 			n = write(STDOUT_FILENO, bufp, remaining);
630 			if (n < 0) {
631 				if (errno != EINTR)
632 					return(-1);
633 				continue;
634 			}
635 			bufp += n;
636 		}
637 		bufp = rcvbuf;
638 		rcvcnt = 0;
639 		rcvstate = READING;
640 
641 			rcvcnt = read(rem, rcvbuf, sizeof (rcvbuf));
642 		if (rcvcnt == 0)
643 			return (0);
644 		if (rcvcnt < 0) {
645 			if (errno == EINTR)
646 				continue;
647 			(void)fprintf(stderr, "rlogin: read: %s.\n",
648 			    strerror(errno));
649 			return(-1);
650 		}
651 	}
652 }
653 
654 mode(f)
655 {
656 	struct ltchars *ltc;
657 	struct sgttyb sb;
658 	struct tchars *tc;
659 	int lflags;
660 
661 	(void)ioctl(0, TIOCGETP, (char *)&sb);
662 	(void)ioctl(0, TIOCLGET, (char *)&lflags);
663 	switch(f) {
664 	case 0:
665 		sb.sg_flags &= ~(CBREAK|RAW|TBDELAY);
666 		sb.sg_flags |= defflags|tabflag;
667 		tc = &deftc;
668 		ltc = &defltc;
669 		sb.sg_kill = defkill;
670 		sb.sg_erase = deferase;
671 		lflags = deflflags;
672 		break;
673 	case 1:
674 		sb.sg_flags |= (eight ? RAW : CBREAK);
675 		sb.sg_flags &= ~defflags;
676 		/* preserve tab delays, but turn off XTABS */
677 		if ((sb.sg_flags & TBDELAY) == XTABS)
678 			sb.sg_flags &= ~TBDELAY;
679 		tc = &notc;
680 		ltc = &noltc;
681 		sb.sg_kill = sb.sg_erase = -1;
682 		if (litout)
683 			lflags |= LLITOUT;
684 		break;
685 	default:
686 		return;
687 	}
688 	(void)ioctl(0, TIOCSLTC, (char *)ltc);
689 	(void)ioctl(0, TIOCSETC, (char *)tc);
690 	(void)ioctl(0, TIOCSETN, (char *)&sb);
691 	(void)ioctl(0, TIOCLSET, (char *)&lflags);
692 }
693 
694 void
695 lostpeer()
696 {
697 	(void)signal(SIGPIPE, SIG_IGN);
698 	msg("\007connection closed.");
699 	done(1);
700 }
701 
702 /* copy SIGURGs to the child process. */
703 void
704 copytochild()
705 {
706 	(void)kill(child, SIGURG);
707 }
708 
709 msg(str)
710 	char *str;
711 {
712 	(void)fprintf(stderr, "rlogin: %s\r\n", str);
713 }
714 
715 #ifdef KERBEROS
716 /* VARARGS */
717 warning(va_alist)
718 va_dcl
719 {
720 	va_list ap;
721 	char *fmt;
722 
723 	(void)fprintf(stderr, "rlogin: warning, using standard rlogin: ");
724 	va_start(ap);
725 	fmt = va_arg(ap, char *);
726 	vfprintf(stderr, fmt, ap);
727 	va_end(ap);
728 	(void)fprintf(stderr, ".\n");
729 }
730 #endif
731 
732 usage()
733 {
734 	(void)fprintf(stderr,
735 	    "usage: rlogin [ -%s]%s[-e char] [ -l username ] host\n",
736 #ifdef KERBEROS
737 	    "8EL", " [-k realm] ");
738 #else
739 	    "8EL", " ");
740 #endif
741 	exit(1);
742 }
743 
744 /*
745  * The following routine provides compatibility (such as it is) between 4.2BSD
746  * Suns and others.  Suns have only a `ttysize', so we convert it to a winsize.
747  */
748 #ifdef sun
749 get_window_size(fd, wp)
750 	int fd;
751 	struct winsize *wp;
752 {
753 	struct ttysize ts;
754 	int error;
755 
756 	if ((error = ioctl(0, TIOCGSIZE, &ts)) != 0)
757 		return(error);
758 	wp->ws_row = ts.ts_lines;
759 	wp->ws_col = ts.ts_cols;
760 	wp->ws_xpixel = 0;
761 	wp->ws_ypixel = 0;
762 	return(0);
763 }
764 #endif
765 
766 u_char
767 getescape(p)
768 	register char *p;
769 {
770 	long val;
771 	int len;
772 
773 	if ((len = strlen(p)) == 1)	/* use any single char, including '\' */
774 		return((u_char)*p);
775 					/* otherwise, \nnn */
776 	if (*p == '\\' && len >= 2 && len <= 4) {
777 		val = strtol(++p, (char **)NULL, 8);
778 		for (;;) {
779 			if (!*++p)
780 				return((u_char)val);
781 			if (*p < '0' || *p > '8')
782 				break;
783 		}
784 	}
785 	msg("illegal option value -- e");
786 	usage();
787 	/* NOTREACHED */
788 }
789