* Copyright (c) 1983, 1986 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
"@(#) Copyright (c) 1983, 1986 Regents of the University of California.\n\
static char sccsid
[] = "@(#)telnetd.c 5.30 (Berkeley) %G%";
#define OPT_NO 0 /* won't do this option */
#define OPT_YES 1 /* will do this option */
#define OPT_YES_BUT_ALWAYS_LOOK 2
#define OPT_NO_BUT_ALWAYS_LOOK 3
char doopt
[] = { IAC
, DO
, '%', 'c', 0 };
char dont
[] = { IAC
, DONT
, '%', 'c', 0 };
char will
[] = { IAC
, WILL
, '%', 'c', 0 };
char wont
[] = { IAC
, WONT
, '%', 'c', 0 };
* I/O data buffers, pointers, and counters.
char ptyibuf
[BUFSIZ
], *ptyip
= ptyibuf
;
char ptyobuf
[BUFSIZ
], *pfrontp
= ptyobuf
, *pbackp
= ptyobuf
;
char netibuf
[BUFSIZ
], *netip
= netibuf
;
#define NIACCUM(c) { *netip++ = c; \
char netobuf
[BUFSIZ
], *nfrontp
= netobuf
, *nbackp
= netobuf
;
char *neturg
= 0; /* one past last bye of urgent data */
/* the remote system seems to NOT be an old 4.2 */
#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r"
/* buffer for sub-options */
char subbuffer
[100], *subpointer
= subbuffer
, *subend
= subbuffer
;
#define SB_CLEAR() subpointer = subbuffer;
#define SB_TERM() { subend = subpointer; SB_CLEAR(); }
#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { \
#define SB_GET() ((*subpointer++)&0xff)
#define SB_EOF() (subpointer >= subend)
int SYNCHing
= 0; /* we are in TELNET SYNCH mode */
* The following are some clocks used to decide how to interpret
* the relationship between various variables.
system
, /* what the current time is */
echotoggle
, /* last time user entered echo character */
modenegotiated
, /* last time operating mode negotiated */
didnetreceive
, /* last time we read data from network */
ttypeopt
, /* ttype will/won't received */
ttypesubopt
, /* ttype subopt is received */
getterminal
, /* time started to get terminal information */
gotDM
; /* when did we last see a data mark */
#define settimer(x) (clocks.x = ++clocks.system)
#define sequenceIs(x,y) (clocks.x < clocks.y)
static struct sockaddr_in sin
= { AF_INET
};
sp
= getservbyname("telnet", "tcp");
fprintf(stderr
, "telnetd: tcp/telnet: unknown service\n");
sin
.sin_port
= sp
->s_port
;
sin
.sin_port
= atoi(*argv
);
sin
.sin_port
= htons((u_short
)sin
.sin_port
);
s
= socket(AF_INET
, SOCK_STREAM
, 0);
perror("telnetd: socket");;
if (bind(s
, &sin
, sizeof sin
) < 0) {
ns
= accept(s
, &sin
, &foo
);
#endif /* defined(DEBUG) */
openlog("telnetd", LOG_PID
| LOG_ODELAY
, LOG_DAEMON
);
if (getpeername(0, &from
, &fromlen
) < 0) {
fprintf(stderr
, "%s: ", argv
[0]);
if (setsockopt(0, SOL_SOCKET
, SO_KEEPALIVE
, &on
, sizeof (on
)) < 0) {
syslog(LOG_WARNING
, "setsockopt (SO_KEEPALIVE): %m");
* A small subroutine to flush the network output buffer, get some data
* from the network, and pass it through the telnet state machine. We
* also flush the pty input buffer (by dropping its data) if it becomes
ncc
= read(net
, netibuf
, sizeof netibuf
);
syslog(LOG_INFO
, "ttloop: read: %m\n");
syslog(LOG_INFO
, "ttloop: peer died: %m\n");
telrcv(); /* state machine */
pfrontp
= pbackp
= ptyobuf
;
* Ask the other end to send along its terminal type.
* Output is the variable terminaltype filled in.
static char sbuf
[] = { IAC
, DO
, TELOPT_TTYPE
};
bcopy(sbuf
, nfrontp
, sizeof sbuf
);
hisopts
[TELOPT_TTYPE
] = OPT_YES_BUT_ALWAYS_LOOK
;
while (sequenceIs(ttypeopt
, getterminal
)) {
if (hisopts
[TELOPT_TTYPE
] == OPT_YES
) {
static char sbbuf
[] = { IAC
, SB
, TELOPT_TTYPE
, TELQUAL_SEND
, IAC
, SE
};
bcopy(sbbuf
, nfrontp
, sizeof sbbuf
);
while (sequenceIs(ttypesubopt
, getterminal
)) {
* Get a pty, scan input lines.
char *host
, *inet_ntoa();
for (c
= 'p'; c
<= 's'; c
++) {
line
[strlen("/dev/pty")] = c
;
line
[strlen("/dev/ptyp")] = '0';
if (stat(line
, &stb
) < 0)
for (i
= 0; i
< 16; i
++) {
line
[sizeof("/dev/ptyp") - 1] = "0123456789abcdef"[i
];
fatal(f
, "All network ports in use");
line
[strlen("/dev/")] = 't';
t
= open("/dev/tty", O_RDWR
);
(void)signal(SIGHUP
, SIG_IGN
);
(void)signal(SIGHUP
, SIG_DFL
);
b
.sg_flags
= CRMOD
|XTABS
|ANYP
;
hp
= gethostbyaddr(&who
->sin_addr
, sizeof (struct in_addr
),
host
= inet_ntoa(who
->sin_addr
);
envinit
[0] = terminaltype
;
* -h : pass on name of host.
* WARNING: -h is accepted by login if and only if
* -p : don't clobber the environment (so terminal type stays set).
execl("/bin/login", "login", "-h", host
,
terminaltype
? "-p" : 0, 0);
fatalperror(f
, "/bin/login");
(void) sprintf(buf
, "telnetd: %s.\r\n", msg
);
(void) write(f
, buf
, strlen(buf
));
extern char *sys_errlist
[];
(void) sprintf(buf
, "%s: %s\r\n", msg
, sys_errlist
[errno
]);
* Check a descriptor to see if out of band data exists on it.
int s
; /* socket number */
static struct timeval timeout
= { 0 };
value
= select(s
+1, (fd_set
*)0, (fd_set
*)0, &excepts
, &timeout
);
} while ((value
== -1) && (errno
== EINTR
));
fatalperror(pty
, "select");
if (FD_ISSET(s
, &excepts
)) {
* Main loop. Select from pty and network, and
* hand data to telnet receiver finite state machine.
char hostname
[MAXHOSTNAMELEN
];
#if defined(SO_OOBINLINE)
setsockopt(net
, SOL_SOCKET
, SO_OOBINLINE
, &on
, sizeof on
);
#endif /* defined(SO_OOBINLINE) */
signal(SIGTSTP
, SIG_IGN
);
* Ignoring SIGTTOU keeps the kernel from blocking us
* in ttioctl() in /sys/tty.c.
signal(SIGTTOU
, SIG_IGN
);
signal(SIGCHLD
, cleanup
);
* Request to do remote echo and to suppress go ahead.
if (!myopts
[TELOPT_ECHO
]) {
if (!myopts
[TELOPT_SGA
]) {
* Is the client side a 4.2 (NOT 4.3) system? We need to know this
* because 4.2 clients are unable to deal with TCP urgent data.
* To find out, we send out a "DO ECHO". If the remote system
* answers "WILL ECHO" it is probably a 4.2 client, and we note
* that fact ("WILL ECHO" ==> that the client will echo what
* WE, the server, sends it; it does NOT mean that the client will
* echo the terminal input).
(void) sprintf(nfrontp
, doopt
, TELOPT_ECHO
);
nfrontp
+= sizeof doopt
-2;
hisopts
[TELOPT_ECHO
] = OPT_YES_BUT_ALWAYS_LOOK
;
* Show banner that getty never gave.
* We put the banner in the pty input buffer. This way, it
* gets carriage return null processing, etc., just like all
* other pty --> client data.
gethostname(hostname
, sizeof (hostname
));
if (getent(defent
, "default") == 1) {
sprintf(ptyibuf
+1, BANNER
, hostname
);
ptyip
= ptyibuf
+1; /* Prime the pump */
pcc
= strlen(ptyip
); /* ditto */
/* Clear ptybuf[0] - where the packet information is received */
* Call telrcv() once to pick up anything received during
* terminal type negotiation.
fd_set ibits
, obits
, xbits
;
* Never look for input if there's still
* stuff in the corresponding output buffer
if (nfrontp
- nbackp
|| pcc
> 0) {
if (pfrontp
- pbackp
|| ncc
> 0) {
if ((c
= select(16, &ibits
, &obits
, &xbits
,
(struct timeval
*)0)) < 1) {
if (FD_ISSET(net
, &xbits
)) {
* Something to read from the network...
if (FD_ISSET(net
, &ibits
)) {
#if !defined(SO_OOBINLINE)
* In 4.2 (and 4.3 beta) systems, the
* OOB indication and data handling in the kernel
* is such that if two separate TCP Urgent requests
* come in, one byte of TCP data will be overlaid.
* This is fatal for Telnet, but we try to live
* In addition, in 4.2 (and...), a special protocol
* is needed to pick up the TCP Urgent data in
* What we do is: if we think we are in urgent
* mode, we look to see if we are "at the mark".
* If we are, we do an OOB receive. If we run
* this twice, we will do the OOB receive twice,
* but the second will fail, since the second
* time we were "at the mark", but there wasn't
* any data there (the kernel doesn't reset
* "at the mark" until we do a normal read).
* Once we've read the OOB data, we go ahead
* There is also another problem, which is that
* since the OOB byte we read doesn't put us
* out of OOB state, and since that byte is most
* likely the TELNET DM (data mark), we would
* stay in the TELNET SYNCH (SYNCHing) state.
* So, clocks to the rescue. If we've "just"
* received a DM, then we test for the
* presence of OOB data when the receive OOB
* fails (and AFTER we did the normal mode read
* to clear "at the mark").
ioctl(net
, SIOCATMARK
, (char *)&atmark
);
ncc
= recv(net
, netibuf
, sizeof (netibuf
), MSG_OOB
);
if ((ncc
== -1) && (errno
== EINVAL
)) {
ncc
= read(net
, netibuf
, sizeof (netibuf
));
if (sequenceIs(didnetreceive
, gotDM
)) {
SYNCHing
= stilloob(net
);
ncc
= read(net
, netibuf
, sizeof (netibuf
));
ncc
= read(net
, netibuf
, sizeof (netibuf
));
#else /* !defined(SO_OOBINLINE)) */
ncc
= read(net
, netibuf
, sizeof (netibuf
));
#endif /* !defined(SO_OOBINLINE)) */
if (ncc
< 0 && errno
== EWOULDBLOCK
)
* Something to read from the pty...
if (FD_ISSET(p
, &xbits
)) {
if (read(p
, ptyibuf
, 1) != 1) {
if (FD_ISSET(p
, &ibits
)) {
pcc
= read(p
, ptyibuf
, BUFSIZ
);
if (pcc
< 0 && errno
== EWOULDBLOCK
)
if (ptyibuf
[0] & TIOCPKT_FLUSHWRITE
) {
netclear(); /* clear buffer back */
neturg
= nfrontp
-1; /* off by one XXX */
if ((&netobuf
[BUFSIZ
] - nfrontp
) < 2)
c
= *ptyip
++ & 0377, pcc
--;
/* Don't do CR-NUL if we are in binary mode */
if ((c
== '\r') && (myopts
[TELOPT_BINARY
] == OPT_NO
)) {
if (pcc
> 0 && ((*ptyip
& 0377) == '\n')) {
*nfrontp
++ = *ptyip
++ & 0377;
if (FD_ISSET(f
, &obits
) && (nfrontp
- nbackp
) > 0)
if (FD_ISSET(p
, &obits
) && (pfrontp
- pbackp
) > 0)
#define TS_DATA 0 /* base state */
#define TS_IAC 1 /* look for double IAC's */
#define TS_CR 2 /* CR-LF ->'s CR */
#define TS_SB 3 /* throw away begin's... */
#define TS_SE 4 /* ...end's (suboption negotiation) */
#define TS_WILL 5 /* will option negotiation */
#define TS_WONT 6 /* wont " */
#define TS_DO 7 /* do " */
#define TS_DONT 8 /* dont " */
static int state
= TS_DATA
;
if ((&ptyobuf
[BUFSIZ
] - pfrontp
) < 2)
c
= *netip
++ & 0377, ncc
--;
/* Strip off \n or \0 after a \r */
if ((c
== 0) || (c
== '\n')) {
* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
* We USED to map \r\n ==> \n, since \r\n says
* that we want to be in column 1 of the next
* printable line, and \n is the standard
* unix way of saying that (\r is only good
* if CRMOD is set, which it normally is).
if ((c
== '\r') && (hisopts
[TELOPT_BINARY
] == OPT_NO
)) {
* Send the process on the pty side an
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
strcpy(nfrontp
, "\r\n[Yes]\r\n");
ptyflush(); /* half-hearted */
ioctl(pty
, TIOCGLTC
, &tmpltc
);
if (tmpltc
.t_flushc
!= '\377') {
*pfrontp
++ = tmpltc
.t_flushc
;
netclear(); /* clear buffer back */
neturg
= nfrontp
-1; /* off by one XXX */
ptyflush(); /* half-hearted */
ioctl(pty
, TIOCGETP
, &b
);
* Check for urgent data...
SYNCHing
= stilloob(net
);
* Begin option subnegotiation...
suboption(); /* handle sub-option */
if (hisopts
[c
] != OPT_YES
)
if (hisopts
[c
] != OPT_NO
)
if (myopts
[c
] != OPT_YES
)
if (myopts
[c
] != OPT_NO
) {
syslog(LOG_ERR
, "telnetd: panic state=%d\n", state
);
printf("telnetd: panic state=%d\n", state
);
not42
= 0; /* looks like a 4.2 system */
* Now, in a 4.2 system, to break them out of ECHOing
* (to the terminal) mode, we need to send a "WILL ECHO".
if (myopts
[TELOPT_ECHO
] == OPT_YES
) {
if (hisopts
[TELOPT_TTYPE
] == OPT_YES_BUT_ALWAYS_LOOK
) {
hisopts
[TELOPT_TTYPE
] = OPT_YES
;
hisopts
[option
] = OPT_YES
;
hisopts
[option
] = OPT_NO
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (dont
) - 2;
not42
= 1; /* doesn't seem to be a 4.2 system */
hisopts
[option
] = OPT_NO
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
myopts
[option
] = OPT_YES
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
case TELOPT_ECHO
: /* we should stop echoing */
myopts
[option
] = OPT_YES
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (wont
) - 2;
* Look at the sub-option buffer, and try to be helpful to the other
* Currently we recognize:
case TELOPT_TTYPE
: { /* Yaaaay! */
static char terminalname
[5+41] = "TERM=";
if (SB_GET() != TELQUAL_IS
) {
return; /* ??? XXX but, this is the most robust */
terminaltype
= terminalname
+strlen(terminalname
);
while ((terminaltype
< (terminalname
+ sizeof terminalname
-1)) &&
*terminaltype
++ = c
; /* accumulate name */
terminaltype
= terminalname
;
ioctl(pty
, TIOCGETP
, &b
);
ioctl(pty
, TIOCSETP
, &b
);
* Send interrupt to process on other side of pty.
* If it is in raw mode, just write NULL;
* otherwise, write intr char.
ptyflush(); /* half-hearted */
ioctl(pty
, TIOCGETP
, &b
);
*pfrontp
++ = ioctl(pty
, TIOCGETC
, &tchars
) < 0 ?
* Send quit to process on other side of pty.
* If it is in raw mode, just write NULL;
* otherwise, write quit char.
ptyflush(); /* half-hearted */
ioctl(pty
, TIOCGETP
, &b
);
*pfrontp
++ = ioctl(pty
, TIOCGETC
, &tchars
) < 0 ?
if ((n
= pfrontp
- pbackp
) > 0)
n
= write(pty
, pbackp
, n
);
pbackp
= pfrontp
= ptyobuf
;
* Return the address of the next "item" in the TELNET data
* stream. This will be the address of the next character if
* the current address is a user data character, or it will
* be the address of the character following the TELNET command
* if the current address is a TELNET IAC ("I Am a Command")
if ((*current
&0xff) != IAC
) {
switch (*(current
+1)&0xff) {
case SB
: /* loop forever looking for the SE */
register char *look
= current
+2;
if ((*look
++&0xff) == IAC
) {
if ((*look
++&0xff) == SE
) {
* We are about to do a TELNET SYNCH operation. Clear
* the path to the network.
* Things are a bit tricky since we may have sent the first
* byte or so of a previous TELNET command into the network.
* So, we have to scan the network buffer from the beginning
* until we are up to where we want to be.
* A side effect of what we do, just to keep things
* simple, is to clear the urgent data pointer. The principal
* caller should be setting the urgent data pointer AFTER calling
register char *thisitem
, *next
;
#define wewant(p) ((nfrontp > p) && ((*p&0xff) == IAC) && \
((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL))
while ((next
= nextitem(thisitem
)) <= nbackp
) {
/* Now, thisitem is first before/at boundary. */
good
= netobuf
; /* where the good bytes go */
while (nfrontp
> thisitem
) {
} while (wewant(next
) && (nfrontp
> next
));
bcopy(thisitem
, good
, length
);
thisitem
= nextitem(thisitem
);
nfrontp
= good
; /* next byte to be sent */
* Send as much data as possible to the network,
* handling requests for urgent data.
if ((n
= nfrontp
- nbackp
) > 0) {
* if no urgent data, or if the other side appears to be an
* old 4.2 client (and thus unable to survive TCP urgent data),
* write the entire buffer in non-OOB mode.
if ((neturg
== 0) || (not42
== 0)) {
n
= write(net
, nbackp
, n
); /* normal write */
* In 4.2 (and 4.3) systems, there is some question about
* what byte in a sendOOB operation is the "OOB" data.
* To make ourselves compatible, we only send ONE byte
* out of band, the one WE THINK should be OOB (though
* we really have more the TCP philosophy of urgent data
* rather than the Unix philosophy of OOB data).
n
= send(net
, nbackp
, n
-1, 0); /* send URGENT all by itself */
n
= send(net
, nbackp
, n
, MSG_OOB
); /* URGENT data */
if (errno
== EWOULDBLOCK
)
/* should blow this guy away... */
nbackp
= nfrontp
= netobuf
;
p
= line
+ sizeof("/dev/") - 1;
register char *res
= editedhost
;
if (res
== &editedhost
[sizeof editedhost
- 1]) {
strncpy(res
, host
, sizeof editedhost
- (res
- editedhost
) - 1);
editedhost
[sizeof editedhost
- 1] = '\0';
static char *putlocation
;
slash
= rindex(line
, '/');