freebsd-dev/usr.bin/rlogin/rlogin.c
ache 350c60cdc2 Back out speed reducing to 38400 for old remote rlogind.
I do some digging out on this subject and found that remote
rlogind may reduce big speeds to 38400 by itself and (as more often
rlogind variant) speed setting ioctl fails, so speed left on 9600.
In all cases it doesn't do any real harm.
1995-08-03 23:54:49 +00:00

950 lines
20 KiB
C

/*
* Copyright (c) 1983, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1983, 1990, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)rlogin.c 8.1 (Berkeley) 6/6/93";
#endif /* not lint */
/*
* rlogin - remote login
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include <setjmp.h>
#include <sgtty.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#ifdef KERBEROS
#include <kerberosIV/des.h>
#include <kerberosIV/krb.h>
#include "krb.h"
CREDENTIALS cred;
Key_schedule schedule;
int use_kerberos = 1, doencrypt;
char dst_realm_buf[REALM_SZ], *dest_realm = NULL;
#endif
#ifndef TIOCPKT_WINDOW
#define TIOCPKT_WINDOW 0x80
#endif
/* concession to Sun */
#ifndef SIGUSR1
#define SIGUSR1 30
#endif
int eight, litout, rem;
int noescape;
u_char escapechar = '~';
char *speeds[] = {
"0", "50", "75", "110", "134", "150", "200", "300", "600", "1200",
"1800", "2400", "4800", "9600", "19200", "38400", "57600", "115200"
};
#ifdef OLDSUN
struct winsize {
unsigned short ws_row, ws_col;
unsigned short ws_xpixel, ws_ypixel;
};
#else
#define get_window_size(fd, wp) ioctl(fd, TIOCGWINSZ, wp)
#endif
struct winsize winsize;
void catch_child __P((int));
void copytochild __P((int));
__dead void doit __P((long));
__dead void done __P((int));
void echo __P((char));
u_int getescape __P((char *));
void lostpeer __P((int));
void mode __P((int));
void msg __P((char *));
void oob __P((int));
int reader __P((int));
void sendwindow __P((void));
void setsignal __P((int));
void sigwinch __P((int));
void stop __P((char));
__dead void usage __P((void));
void writer __P((void));
void writeroob __P((int));
#ifdef KERBEROS
void warning __P((const char *, ...));
#endif
#ifdef OLDSUN
int get_window_size __P((int, struct winsize *));
#endif
int
main(argc, argv)
int argc;
char *argv[];
{
extern char *optarg;
extern int optind;
struct passwd *pw;
struct servent *sp;
struct sgttyb ttyb;
long omask;
int argoff, ch, dflag, Dflag, one, uid;
char *host, *p, *user, term[1024];
argoff = dflag = Dflag = 0;
one = 1;
host = user = NULL;
if (p = rindex(argv[0], '/'))
++p;
else
p = argv[0];
if (strcmp(p, "rlogin"))
host = p;
/* handle "rlogin host flags" */
if (!host && argc > 2 && argv[1][0] != '-') {
host = argv[1];
argoff = 1;
}
#ifdef KERBEROS
#define OPTIONS "8DEKLde:k:l:x"
#else
#define OPTIONS "8DEKLde:l:"
#endif
while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != EOF)
switch(ch) {
case '8':
eight = 1;
break;
case 'D':
Dflag = 1;
break;
case 'E':
noescape = 1;
break;
case 'K':
#ifdef KERBEROS
use_kerberos = 0;
#endif
break;
case 'L':
litout = 1;
break;
case 'd':
dflag = 1;
break;
case 'e':
noescape = 0;
escapechar = getescape(optarg);
break;
#ifdef KERBEROS
case 'k':
dest_realm = dst_realm_buf;
(void)strncpy(dest_realm, optarg, REALM_SZ);
break;
#endif
case 'l':
user = optarg;
break;
#ifdef CRYPT
#ifdef KERBEROS
case 'x':
doencrypt = 1;
break;
#endif
#endif
case '?':
default:
usage();
}
optind += argoff;
argc -= optind;
argv += optind;
/* if haven't gotten a host yet, do so */
if (!host && !(host = *argv++))
usage();
if (*argv)
usage();
if (!(pw = getpwuid(uid = getuid()))) {
(void)fprintf(stderr, "rlogin: unknown user id.\n");
exit(1);
}
if (!user)
user = pw->pw_name;
sp = NULL;
#ifdef KERBEROS
if (use_kerberos) {
sp = getservbyname((doencrypt ? "eklogin" : "klogin"), "tcp");
if (sp == NULL) {
use_kerberos = 0;
warning("can't get entry for %s/tcp service",
doencrypt ? "eklogin" : "klogin");
}
}
#endif
if (sp == NULL)
sp = getservbyname("login", "tcp");
if (sp == NULL) {
(void)fprintf(stderr, "rlogin: login/tcp: unknown service.\n");
exit(1);
}
(void)strcpy(term, (p = getenv("TERM")) ? p : "network");
if (ioctl(0, TIOCGETP, &ttyb) == 0) {
(void)strcat(term, "/");
(void)strcat(term, speeds[(int)ttyb.sg_ospeed]);
}
(void)get_window_size(0, &winsize);
(void)signal(SIGPIPE, lostpeer);
/* will use SIGUSR1 for window size hack, so hold it off */
omask = sigblock(sigmask(SIGURG) | sigmask(SIGUSR1));
/*
* We set SIGURG and SIGUSR1 below so that an
* incoming signal will be held pending rather than being
* discarded. Note that these routines will be ready to get
* a signal by the time that they are unblocked below.
*/
(void)signal(SIGURG, copytochild);
(void)signal(SIGUSR1, writeroob);
#ifdef KERBEROS
try_connect:
if (use_kerberos) {
struct hostent *hp;
/* Fully qualify hostname (needed for krb_realmofhost). */
hp = gethostbyname(host);
if (hp != NULL && !(host = strdup(hp->h_name))) {
(void)fprintf(stderr, "rlogin: %s\n",
strerror(ENOMEM));
exit(1);
}
rem = KSUCCESS;
errno = 0;
if (dest_realm == NULL)
dest_realm = krb_realmofhost(host);
#ifdef CRYPT
if (doencrypt) {
rem = krcmd_mutual(&host, sp->s_port, user, term, 0,
dest_realm, &cred, schedule);
des_set_key(cred.session, schedule);
} else
#endif /* CRYPT */
rem = krcmd(&host, sp->s_port, user, term, 0,
dest_realm);
if (rem < 0) {
use_kerberos = 0;
sp = getservbyname("login", "tcp");
if (sp == NULL) {
(void)fprintf(stderr,
"rlogin: unknown service login/tcp.\n");
exit(1);
}
if (errno == ECONNREFUSED)
warning("remote host doesn't support Kerberos");
if (errno == ENOENT)
warning("can't provide Kerberos auth data");
goto try_connect;
}
} else {
#ifdef CRYPT
if (doencrypt) {
(void)fprintf(stderr,
"rlogin: the -x flag requires Kerberos authentication.\n");
exit(1);
}
#endif /* CRYPT */
rem = rcmd(&host, sp->s_port, pw->pw_name, user, term, 0);
}
#else
rem = rcmd(&host, sp->s_port, pw->pw_name, user, term, 0);
#endif /* KERBEROS */
if (rem < 0)
exit(1);
if (dflag &&
setsockopt(rem, SOL_SOCKET, SO_DEBUG, &one, sizeof(one)) < 0)
(void)fprintf(stderr, "rlogin: setsockopt: %s.\n",
strerror(errno));
if (Dflag &&
setsockopt(rem, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0)
perror("rlogin: setsockopt NODELAY (ignored)");
one = IPTOS_LOWDELAY;
if (setsockopt(rem, IPPROTO_IP, IP_TOS, (char *)&one, sizeof(int)) < 0)
perror("rlogin: setsockopt TOS (ignored)");
(void)setuid(uid);
doit(omask);
/*NOTREACHED*/
}
int child, defflags, deflflags, tabflag;
char deferase, defkill;
struct tchars deftc;
struct ltchars defltc;
struct tchars notc = { -1, -1, -1, -1, -1, -1 };
struct ltchars noltc = { -1, -1, -1, -1, -1, -1 };
void
doit(omask)
long omask;
{
struct sgttyb sb;
(void)ioctl(0, TIOCGETP, (char *)&sb);
defflags = sb.sg_flags;
tabflag = defflags & TBDELAY;
defflags &= ECHO | CRMOD;
deferase = sb.sg_erase;
defkill = sb.sg_kill;
(void)ioctl(0, TIOCLGET, &deflflags);
(void)ioctl(0, TIOCGETC, &deftc);
notc.t_startc = deftc.t_startc;
notc.t_stopc = deftc.t_stopc;
(void)ioctl(0, TIOCGLTC, &defltc);
(void)signal(SIGINT, SIG_IGN);
setsignal(SIGHUP);
setsignal(SIGQUIT);
child = fork();
if (child == -1) {
(void)fprintf(stderr, "rlogin: fork: %s.\n", strerror(errno));
done(1);
}
if (child == 0) {
mode(1);
if (reader(omask) == 0) {
msg("connection closed.");
exit(0);
}
sleep(1);
msg("\007connection closed.");
exit(1);
}
/*
* We may still own the socket, and may have a pending SIGURG (or might
* receive one soon) that we really want to send to the reader. When
* one of these comes in, the trap copytochild simply copies such
* signals to the child. We can now unblock SIGURG and SIGUSR1
* that were set above.
*/
(void)sigsetmask(omask);
(void)signal(SIGCHLD, catch_child);
writer();
msg("closed connection.");
done(0);
}
/* trap a signal, unless it is being ignored. */
void
setsignal(sig)
int sig;
{
int omask = sigblock(sigmask(sig));
if (signal(sig, exit) == SIG_IGN)
(void)signal(sig, SIG_IGN);
(void)sigsetmask(omask);
}
__dead void
done(status)
int status;
{
int w, wstatus;
mode(0);
if (child > 0) {
/* make sure catch_child does not snap it up */
(void)signal(SIGCHLD, SIG_DFL);
if (kill(child, SIGKILL) >= 0)
while ((w = wait(&wstatus)) > 0 && w != child);
}
exit(status);
}
int dosigwinch;
/*
* This is called when the reader process gets the out-of-band (urgent)
* request to turn on the window-changing protocol.
*/
void
writeroob(signo)
int signo;
{
if (dosigwinch == 0) {
sendwindow();
(void)signal(SIGWINCH, sigwinch);
}
dosigwinch = 1;
}
void
catch_child(signo)
int signo;
{
union wait status;
int pid;
for (;;) {
pid = wait3((int *)&status, WNOHANG|WUNTRACED, NULL);
if (pid == 0)
return;
/* if the child (reader) dies, just quit */
if (pid < 0 || (pid == child && !WIFSTOPPED(status)))
done((int)(status.w_termsig | status.w_retcode));
}
/* NOTREACHED */
}
/*
* writer: write to remote: 0 -> line.
* ~. terminate
* ~^Z suspend rlogin process.
* ~<delayed-suspend char> suspend rlogin process, but leave reader alone.
*/
void
writer()
{
register int bol, local, n;
char c;
bol = 1; /* beginning of line */
local = 0;
for (;;) {
n = read(STDIN_FILENO, &c, 1);
if (n <= 0) {
if (n < 0 && errno == EINTR)
continue;
break;
}
/*
* If we're at the beginning of the line and recognize a
* command character, then we echo locally. Otherwise,
* characters are echo'd remotely. If the command character
* is doubled, this acts as a force and local echo is
* suppressed.
*/
if (bol) {
bol = 0;
if (!noescape && c == escapechar) {
local = 1;
continue;
}
} else if (local) {
local = 0;
if (c == '.' || c == deftc.t_eofc) {
echo(c);
break;
}
if (c == defltc.t_suspc || c == defltc.t_dsuspc) {
bol = 1;
echo(c);
stop(c);
continue;
}
if (c != escapechar)
#ifdef CRYPT
#ifdef KERBEROS
if (doencrypt)
(void)des_write(rem,
(char *)&escapechar, 1);
else
#endif
#endif
(void)write(rem, &escapechar, 1);
}
#ifdef CRYPT
#ifdef KERBEROS
if (doencrypt) {
if (des_write(rem, &c, 1) == 0) {
msg("line gone");
break;
}
} else
#endif
#endif
if (write(rem, &c, 1) == 0) {
msg("line gone");
break;
}
bol = c == defkill || c == deftc.t_eofc ||
c == deftc.t_intrc || c == defltc.t_suspc ||
c == '\r' || c == '\n';
}
}
void
#if __STDC__
echo(register char c)
#else
echo(c)
register char c;
#endif
{
register char *p;
char buf[8];
p = buf;
c &= 0177;
*p++ = escapechar;
if (c < ' ') {
*p++ = '^';
*p++ = c + '@';
} else if (c == 0177) {
*p++ = '^';
*p++ = '?';
} else
*p++ = c;
*p++ = '\r';
*p++ = '\n';
(void)write(STDOUT_FILENO, buf, p - buf);
}
void
#if __STDC__
stop(char cmdc)
#else
stop(cmdc)
char cmdc;
#endif
{
mode(0);
(void)signal(SIGCHLD, SIG_IGN);
(void)kill(cmdc == defltc.t_suspc ? 0 : getpid(), SIGTSTP);
(void)signal(SIGCHLD, catch_child);
mode(1);
sigwinch(0); /* check for size changes */
}
void
sigwinch(signo)
int signo;
{
struct winsize ws;
if (dosigwinch && get_window_size(0, &ws) == 0 &&
bcmp(&ws, &winsize, sizeof(ws))) {
winsize = ws;
sendwindow();
}
}
/*
* Send the window size to the server via the magic escape
*/
void
sendwindow()
{
struct winsize *wp;
char obuf[4 + sizeof (struct winsize)];
wp = (struct winsize *)(obuf+4);
obuf[0] = 0377;
obuf[1] = 0377;
obuf[2] = 's';
obuf[3] = 's';
wp->ws_row = htons(winsize.ws_row);
wp->ws_col = htons(winsize.ws_col);
wp->ws_xpixel = htons(winsize.ws_xpixel);
wp->ws_ypixel = htons(winsize.ws_ypixel);
#ifdef CRYPT
#ifdef KERBEROS
if(doencrypt)
(void)des_write(rem, obuf, sizeof(obuf));
else
#endif
#endif
(void)write(rem, obuf, sizeof(obuf));
}
/*
* reader: read from remote: line -> 1
*/
#define READING 1
#define WRITING 2
jmp_buf rcvtop;
int ppid, rcvcnt, rcvstate;
char rcvbuf[8 * 1024];
void
oob(signo)
int signo;
{
struct sgttyb sb;
int atmark, n, out, rcvd;
char waste[BUFSIZ], mark;
out = O_RDWR;
rcvd = 0;
while (recv(rem, &mark, 1, MSG_OOB) < 0) {
switch (errno) {
case EWOULDBLOCK:
/*
* Urgent data not here yet. It may not be possible
* to send it yet if we are blocked for output and
* our input buffer is full.
*/
if (rcvcnt < sizeof(rcvbuf)) {
n = read(rem, rcvbuf + rcvcnt,
sizeof(rcvbuf) - rcvcnt);
if (n <= 0)
return;
rcvd += n;
} else {
n = read(rem, waste, sizeof(waste));
if (n <= 0)
return;
}
continue;
default:
return;
}
}
if (mark & TIOCPKT_WINDOW) {
/* Let server know about window size changes */
(void)kill(ppid, SIGUSR1);
}
if (!eight && (mark & TIOCPKT_NOSTOP)) {
(void)ioctl(0, TIOCGETP, (char *)&sb);
sb.sg_flags &= ~CBREAK;
sb.sg_flags |= RAW;
(void)ioctl(0, TIOCSETN, (char *)&sb);
notc.t_stopc = -1;
notc.t_startc = -1;
(void)ioctl(0, TIOCSETC, (char *)&notc);
}
if (!eight && (mark & TIOCPKT_DOSTOP)) {
(void)ioctl(0, TIOCGETP, (char *)&sb);
sb.sg_flags &= ~RAW;
sb.sg_flags |= CBREAK;
(void)ioctl(0, TIOCSETN, (char *)&sb);
notc.t_stopc = deftc.t_stopc;
notc.t_startc = deftc.t_startc;
(void)ioctl(0, TIOCSETC, (char *)&notc);
}
if (mark & TIOCPKT_FLUSHWRITE) {
(void)ioctl(1, TIOCFLUSH, (char *)&out);
for (;;) {
if (ioctl(rem, SIOCATMARK, &atmark) < 0) {
(void)fprintf(stderr, "rlogin: ioctl: %s.\n",
strerror(errno));
break;
}
if (atmark)
break;
n = read(rem, waste, sizeof (waste));
if (n <= 0)
break;
}
/*
* Don't want any pending data to be output, so clear the recv
* buffer. If we were hanging on a write when interrupted,
* don't want it to restart. If we were reading, restart
* anyway.
*/
rcvcnt = 0;
longjmp(rcvtop, 1);
}
/* oob does not do FLUSHREAD (alas!) */
/*
* If we filled the receive buffer while a read was pending, longjmp
* to the top to restart appropriately. Don't abort a pending write,
* however, or we won't know how much was written.
*/
if (rcvd && rcvstate == READING)
longjmp(rcvtop, 1);
}
/* reader: read from remote: line -> 1 */
int
reader(omask)
int omask;
{
int pid, n, remaining;
char *bufp;
#if BSD >= 43 || defined(SUNOS4)
pid = getpid(); /* modern systems use positives for pid */
#else
pid = -getpid(); /* old broken systems use negatives */
#endif
(void)signal(SIGTTOU, SIG_IGN);
(void)signal(SIGURG, oob);
ppid = getppid();
(void)fcntl(rem, F_SETOWN, pid);
(void)setjmp(rcvtop);
(void)sigsetmask(omask);
bufp = rcvbuf;
for (;;) {
while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) {
rcvstate = WRITING;
n = write(STDOUT_FILENO, bufp, remaining);
if (n < 0) {
if (errno != EINTR)
return (-1);
continue;
}
bufp += n;
}
bufp = rcvbuf;
rcvcnt = 0;
rcvstate = READING;
#ifdef CRYPT
#ifdef KERBEROS
if (doencrypt)
rcvcnt = des_read(rem, rcvbuf, sizeof(rcvbuf));
else
#endif
#endif
rcvcnt = read(rem, rcvbuf, sizeof (rcvbuf));
if (rcvcnt == 0)
return (0);
if (rcvcnt < 0) {
if (errno == EINTR)
continue;
(void)fprintf(stderr, "rlogin: read: %s.\n",
strerror(errno));
return (-1);
}
}
}
void
mode(f)
int f;
{
struct ltchars *ltc;
struct sgttyb sb;
struct tchars *tc;
int lflags;
(void)ioctl(0, TIOCGETP, (char *)&sb);
(void)ioctl(0, TIOCLGET, (char *)&lflags);
switch(f) {
case 0:
sb.sg_flags &= ~(CBREAK|RAW|TBDELAY);
sb.sg_flags |= defflags|tabflag;
tc = &deftc;
ltc = &defltc;
sb.sg_kill = defkill;
sb.sg_erase = deferase;
lflags = deflflags;
break;
case 1:
sb.sg_flags |= (eight ? RAW : CBREAK);
sb.sg_flags &= ~defflags;
/* preserve tab delays, but turn off XTABS */
if ((sb.sg_flags & TBDELAY) == XTABS)
sb.sg_flags &= ~TBDELAY;
tc = &notc;
ltc = &noltc;
sb.sg_kill = sb.sg_erase = -1;
if (litout)
lflags |= LLITOUT;
break;
default:
return;
}
(void)ioctl(0, TIOCSLTC, (char *)ltc);
(void)ioctl(0, TIOCSETC, (char *)tc);
(void)ioctl(0, TIOCSETN, (char *)&sb);
(void)ioctl(0, TIOCLSET, (char *)&lflags);
}
void
lostpeer(signo)
int signo;
{
(void)signal(SIGPIPE, SIG_IGN);
msg("\007connection closed.");
done(1);
}
/* copy SIGURGs to the child process. */
void
copytochild(signo)
int signo;
{
(void)kill(child, SIGURG);
}
void
msg(str)
char *str;
{
(void)fprintf(stderr, "rlogin: %s\r\n", str);
}
#ifdef KERBEROS
/* VARARGS */
void
#if __STDC__
warning(const char *fmt, ...)
#else
warning(fmt, va_alist)
char *fmt;
va_dcl
#endif
{
va_list ap;
(void)fprintf(stderr, "rlogin: warning, using standard rlogin: ");
#ifdef __STDC__
va_start(ap, fmt);
#else
va_start(ap);
#endif
vfprintf(stderr, fmt, ap);
va_end(ap);
(void)fprintf(stderr, ".\n");
}
#endif
__dead void
usage()
{
(void)fprintf(stderr,
"usage: rlogin [ -%s]%s[-e char] [ -l username ] host\n",
#ifdef KERBEROS
#ifdef CRYPT
"8DEKLx", " [-k realm] ");
#else
"8DEKL", " [-k realm] ");
#endif
#else
"8DEL", " ");
#endif
exit(1);
}
/*
* The following routine provides compatibility (such as it is) between older
* Suns and others. Suns have only a `ttysize', so we convert it to a winsize.
*/
#ifdef OLDSUN
int
get_window_size(fd, wp)
int fd;
struct winsize *wp;
{
struct ttysize ts;
int error;
if ((error = ioctl(0, TIOCGSIZE, &ts)) != 0)
return (error);
wp->ws_row = ts.ts_lines;
wp->ws_col = ts.ts_cols;
wp->ws_xpixel = 0;
wp->ws_ypixel = 0;
return (0);
}
#endif
u_int
getescape(p)
register char *p;
{
long val;
int len;
if ((len = strlen(p)) == 1) /* use any single char, including '\' */
return ((u_int)*p);
/* otherwise, \nnn */
if (*p == '\\' && len >= 2 && len <= 4) {
val = strtol(++p, NULL, 8);
for (;;) {
if (!*++p)
return ((u_int)val);
if (*p < '0' || *p > '8')
break;
}
}
msg("illegal option value -- e");
usage();
/* NOTREACHED */
}