* Copyright (c) 1989 Regents of the University of California.
* %sccs.include.redist.c%
"@(#) Copyright (c) 1989 Regents of the University of California.\n\
static char sccsid
[] = "@(#)telnetd.c 5.46 (Berkeley) %G%";
* pointers, and counters.
char ptyibuf
[BUFSIZ
], *ptyip
= ptyibuf
;
int hostinfo
= 1; /* do we print login banner? */
extern int newmap
; /* nonzero if \n maps to ^M^J */
int lowpty
= 0, highpty
; /* low, high pty numbers */
char **t_aliases
; /* alias list */
char *t_proto
; /* protocol */
int t_tos
; /* Type Of Service bits */
gettosbyname(name
, proto
)
te
.t_tos
= 020; /* Low Delay bit */
#if defined(HAS_IP_TOS) || defined(NEED_GETTOS)
#endif /* defined(HAS_IP_TOS) || defined(NEED_GETTOS) */
pfrontp
= pbackp
= ptyobuf
;
nfrontp
= nbackp
= netobuf
;
* Get number of pty's before trying to process options,
* which may include changing pty range.
if (argc
> 0 && strcmp(*argv
, "-debug") == 0) {
if (argc
> 0 && !strcmp(*argv
, "-l")) {
if (argc
> 0 && !strcmp(*argv
, "-h")) {
if (argc
> 0 && !strncmp(*argv
, "-r", 2)) {
* Allow the specification of alterations to the pty search
* range. It is legal to specify only one, and not change the
* other from its default.
if (**argv
== '\0' && argc
)
if ((lowpty
> highpty
) || (lowpty
< 0) || (highpty
> 32767)) {
if (argc
> 0 && !strncmp(*argv
, "-I", 2)) {
* Check for desired diagnostics capabilities.
if (argc
> 0 && !strncmp(*argv
, "-D", 2)) {
if (!strcmp(*argv
, "report")) {
diagnostic
|= TD_REPORT
|TD_OPTIONS
;
} else if (!strcmp(*argv
, "exercise")) {
diagnostic
|= TD_EXERCISE
;
} else if (!strcmp(*argv
, "netdata")) {
diagnostic
|= TD_NETDATA
;
} else if (!strcmp(*argv
, "ptydata")) {
diagnostic
|= TD_PTYDATA
;
} else if (!strcmp(*argv
, "options")) {
diagnostic
|= TD_OPTIONS
;
if (argc
> 0 && !strncmp(*argv
, "-B", 2)) {
if (argc
> 0 && **argv
== '-') {
fprintf(stderr
, "telnetd: %s: unknown option\n", *argv
+1);
static struct sockaddr_in sin
= { AF_INET
};
if (sp
= getservbyname(*argv
, "tcp")) {
sin
.sin_port
= sp
->s_port
;
sin
.sin_port
= atoi(*argv
);
if ((int)sin
.sin_port
<= 0) {
fprintf(stderr
, "telnetd: %s: bad port #\n", *argv
);
sin
.sin_port
= htons((u_short
)sin
.sin_port
);
sp
= getservbyname("telnet", "tcp");
fprintf(stderr
, "telnetd: tcp/telnet: unknown service\n");
sin
.sin_port
= sp
->s_port
;
s
= socket(AF_INET
, SOCK_STREAM
, 0);
perror("telnetd: socket");;
(void) setsockopt(s
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
));
if (bind(s
, (struct sockaddr
*)&sin
, sizeof sin
) < 0) {
ns
= accept(s
, (struct sockaddr
*)&sin
, &foo
);
openlog("telnetd", LOG_PID
| LOG_ODELAY
, LOG_DAEMON
);
if (getpeername(0, (struct sockaddr
*)&from
, &fromlen
) < 0) {
fprintf(stderr
, "%s: ", progname
);
if (setsockopt(0, SOL_SOCKET
, SO_KEEPALIVE
, &on
, sizeof (on
)) < 0) {
syslog(LOG_WARNING
, "setsockopt (SO_KEEPALIVE): %m");
#if defined(HAS_IP_TOS) || defined(NEED_GETTOS)
if ((tp
= gettosbyname("telnet", "tcp")) &&
(setsockopt(0, IPPROTO_IP
, IP_TOS
, &tp
->t_tos
, sizeof(int)) < 0))
syslog(LOG_WARNING
, "setsockopt (IP_TOS): %m");
#endif /* defined(HAS_IP_TOS) || defined(NEED_GETTOS) */
fprintf(stderr
, "Usage: telnetd [-debug] [-h]");
fprintf(stderr
, " [-Iinitid]");
fprintf(stderr
, " [-D (options|report|exercise|netdata|ptydata)]");
fprintf(stderr
, " [-l]");
fprintf(stderr
, " [-r[lowpty]-[highpty]]");
fprintf(stderr
, " [-B]");
fprintf(stderr
, " [port]\n");
* Ask the other end to send along its terminal type and speed.
* Output is the variable terminaltype filled in.
static char ttytype_sbbuf
[] = { IAC
, SB
, TELOPT_TTYPE
, TELQUAL_SEND
, IAC
, SE
};
send_do(TELOPT_TTYPE
, 1);
send_do(TELOPT_TSPEED
, 1);
send_do(TELOPT_XDISPLOC
, 1);
send_do(TELOPT_ENVIRON
, 1);
while (his_will_wont_is_changing(TELOPT_TTYPE
) ||
his_will_wont_is_changing(TELOPT_TSPEED
) ||
his_will_wont_is_changing(TELOPT_XDISPLOC
) ||
his_will_wont_is_changing(TELOPT_ENVIRON
)) {
if (his_state_is_will(TELOPT_TSPEED
)) {
static char sbbuf
[] = { IAC
, SB
, TELOPT_TSPEED
, TELQUAL_SEND
, IAC
, SE
};
bcopy(sbbuf
, nfrontp
, sizeof sbbuf
);
if (his_state_is_will(TELOPT_XDISPLOC
)) {
static char sbbuf
[] = { IAC
, SB
, TELOPT_XDISPLOC
, TELQUAL_SEND
, IAC
, SE
};
bcopy(sbbuf
, nfrontp
, sizeof sbbuf
);
if (his_state_is_will(TELOPT_ENVIRON
)) {
static char sbbuf
[] = { IAC
, SB
, TELOPT_ENVIRON
, TELQUAL_SEND
, IAC
, SE
};
bcopy(sbbuf
, nfrontp
, sizeof sbbuf
);
if (his_state_is_will(TELOPT_TTYPE
)) {
bcopy(ttytype_sbbuf
, nfrontp
, sizeof ttytype_sbbuf
);
nfrontp
+= sizeof ttytype_sbbuf
;
if (his_state_is_will(TELOPT_TSPEED
)) {
while (sequenceIs(tspeedsubopt
, baseline
))
if (his_state_is_will(TELOPT_XDISPLOC
)) {
while (sequenceIs(xdisplocsubopt
, baseline
))
if (his_state_is_will(TELOPT_ENVIRON
)) {
while (sequenceIs(environsubopt
, baseline
))
if (his_state_is_will(TELOPT_TTYPE
)) {
char first
[256], last
[256];
while (sequenceIs(ttypesubopt
, baseline
))
* If the other side has already disabled the option, then
* we have to just go with what we (might) have already gotten.
if (his_state_is_will(TELOPT_TTYPE
) && !terminaltypeok(terminaltype
)) {
(void) strncpy(first
, terminaltype
, sizeof(first
));
* Save the unknown name, and request the next name.
(void) strncpy(last
, terminaltype
, sizeof(last
));
if (terminaltypeok(terminaltype
))
if ((strncmp(last
, terminaltype
, sizeof(last
)) == 0) ||
his_state_is_wont(TELOPT_TTYPE
)) {
* We've hit the end. If this is the same as
* the first name, just go with it.
if (strncmp(first
, terminaltype
, sizeof(first
) == 0))
* Get the terminal name one more time, so that
* RFC1091 compliant telnets will cycle back to
if (strncmp(first
, terminaltype
, sizeof(first
) != 0))
(void) strncpy(terminaltype
, first
, sizeof(first
));
} /* end of getterminaltype */
* If the client turned off the option,
* we can't send another request, so we
if (his_state_is_wont(TELOPT_TTYPE
))
bcopy(ttytype_sbbuf
, nfrontp
, sizeof ttytype_sbbuf
);
nfrontp
+= sizeof ttytype_sbbuf
;
while (sequenceIs(ttypesubopt
, baseline
))
if (terminaltype
== NULL
)
* tgetent() will return 1 if the type is known, and
* 0 if it is not known. If it returns -1, it couldn't
* open the database. But if we can't open the database,
* it won't help to say we failed, because we won't be
* able to verify anything else. So, we treat -1 like 1.
if (tgetent(buf
, s
) == 0)
* Get a pty, scan input lines.
char *host
, *inet_ntoa();
if (openpty(&pty
, &t
, line
, NULL
, NULL
) == -1)
fatal(net
, "All network ports in use");
* Find an available pty to use.
fatal(net
, "All network ports in use");
/* get name of connected client */
hp
= gethostbyaddr((char *)&who
->sin_addr
, sizeof (struct in_addr
),
host
= inet_ntoa(who
->sin_addr
);
setenv("TERM", terminaltype
? terminaltype
: "network", 1);
* Start up the login process on the slave side of the terminal
telnet(net
, pty
); /* begin server processing */
#define MAXHOSTNAMELEN 64
* Main loop. Select from pty and network, and
* hand data to telnet receiver finite state machine.
char hostname
[MAXHOSTNAMELEN
];
#if defined(CRAY2) && defined(UNICOS5)
int interrupt(), sendbrk();
* Initialize the slc mapping table.
* Do some tests where it is desireable to wait for a response.
* Rather than doing them slowly, one at a time, do them all
if (my_state_is_wont(TELOPT_SGA
))
send_will(TELOPT_SGA
, 1);
* 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).
if (his_state_is_wont(TELOPT_LINEMODE
)) {
/* Query the peer for linemode support by trying to negotiate
send_do(TELOPT_LINEMODE
, 1); /* send do linemode */
* Send along a couple of other options that we wish to negotiate.
send_will(TELOPT_STATUS
, 1);
flowmode
= 1; /* default flow control state */
send_do(TELOPT_LFLOW
, 1);
* Spin, waiting for a response from the DO ECHO. However,
* some REALLY DUMB telnets out there might not respond
* to the DO ECHO. So, we spin looking for NAWS, (most dumb
* telnets so far seem to respond with WONT for a DO that
* they don't understand...) because by the time we get the
* response, it will already have processed the DO ECHO.
while (his_will_wont_is_changing(TELOPT_NAWS
))
* The client might have sent a WILL NAWS as part of its
* startup code; if so, we'll be here before we get the
* response to the DO ECHO. We'll make the assumption
* that any implementation that understands about NAWS
* is a modern enough implementation that it will respond
* to our DO ECHO request; hence we'll do another spin
* waiting for the ECHO option to settle down, which is
* what we wanted to do in the first place...
if (his_want_state_is_will(TELOPT_ECHO
) &&
his_state_is_will(TELOPT_NAWS
)) {
while (his_will_wont_is_changing(TELOPT_ECHO
))
* On the off chance that the telnet client is broken and does not
* respond to the DO ECHO we sent, (after all, we did send the
* DO NAWS negotiation after the DO ECHO, and we won't get here
* until a response to the DO NAWS comes back) simulate the
* receipt of a will echo. This will also send a WONT ECHO
* to the client, since we assume that the client failed to
* respond because it believes that it is already in DO ECHO
* mode, which we do not want.
if (his_want_state_is_will(TELOPT_ECHO
)) {
if (diagnostic
& TD_OPTIONS
) {
sprintf(nfrontp
, "td: simulating recv\r\n");
nfrontp
+= strlen(nfrontp
);
* Finally, to clean things up, we turn on our echo. This
* will break stupid 4.2 telnets out of local terminal echo.
if (my_state_is_wont(TELOPT_ECHO
))
send_will(TELOPT_ECHO
, 1);
* Turn on packet mode, and default to line at at time mode.
(void) ioctl(p
, TIOCPKT
, (char *)&on
);
* Continuing line mode support. If client does not support
* real linemode, attempt to negotiate kludge linemode by sending
* the do timing mark sequence.
if (lmodetype
< REAL_LINEMODE
)
# endif /* KLUDGELINEMODE */
* Call telrcv() once to pick up anything received during
* terminal type negotiation, 4.2/4.3 determination, and
(void) ioctl(f
, FIONBIO
, (char *)&on
);
(void) ioctl(p
, FIONBIO
, (char *)&on
);
#if defined(CRAY2) && defined(UNICOS5)
init_termdriver(f
, p
, interrupt
, sendbrk
);
#if defined(SO_OOBINLINE)
(void) setsockopt(net
, SOL_SOCKET
, SO_OOBINLINE
, &on
, sizeof on
);
#endif /* defined(SO_OOBINLINE) */
(void) signal(SIGTSTP
, SIG_IGN
);
* Ignoring SIGTTOU keeps the kernel from blocking us
* in ttioct() in /sys/tty.c.
(void) signal(SIGTTOU
, SIG_IGN
);
(void) signal(SIGCHLD
, cleanup
);
#if defined(CRAY2) && defined(UNICOS5)
* Cray-2 will send a signal when pty modes are changed by slave
* side. Set up signal handler now.
if ((int)signal(SIGUSR1
, termstat
) < 0)
else if (ioctl(p
, TCSIGME
, (char *)SIGUSR1
) < 0)
* Make processing loop check terminal characteristics early on.
#if defined(TIOCSCTTY) && defined(CRAY)
* 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.
(void) gethostname(hostname
, sizeof (hostname
));
if (getent(defent
, "default") == 1) {
(void) strcpy(hostname
, HN
);
(void) strncat(ptyibuf2
, ptyip
, pcc
+1);
* Last check to make sure all our states are correct.
if (diagnostic
& TD_REPORT
) {
sprintf(nfrontp
, "td: Entering processing loop\r\n");
nfrontp
+= strlen(nfrontp
);
fd_set ibits
, obits
, xbits
;
#if defined(CRAY2) && defined(UNICOS5)
#endif /* defined(CRAY2) && defined(UNICOS5) */
* 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").
(void) 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
)
if (diagnostic
& (TD_REPORT
| TD_NETDATA
)) {
sprintf(nfrontp
, "td: netread %d chars\r\n", ncc
);
nfrontp
+= strlen(nfrontp
);
if (diagnostic
& TD_NETDATA
) {
printdata("nd", netip
, ncc
);
* Something to read from the pty...
if (FD_ISSET(p
, &ibits
)) {
pcc
= read(p
, ptyibuf
, BUFSIZ
);
if (pcc
< 0 && errno
== EWOULDBLOCK
)
#if !defined(CRAY2) || !defined(UNICOS5)
* If ioctl from pty, pass it through net
if (ptyibuf
[0] & TIOCPKT_IOCTL
) {
copy_termbuf(ptyibuf
+1, pcc
-1);
if (ptyibuf
[0] & TIOCPKT_FLUSHWRITE
) {
netclear(); /* clear buffer back */
* We really should have this in, but
* there are client telnets on some
* operating systems get screwed up
* royally if we send them urgent
* mode data. So, for now, we'll not
neturg
= nfrontp
-1; /* off by one XXX */
if (his_state_is_will(TELOPT_LFLOW
) &&
(TIOCPKT_NOSTOP
|TIOCPKT_DOSTOP
))) {
(void) sprintf(nfrontp
, "%c%c%c%c%c%c",
ptyibuf
[0] & TIOCPKT_DOSTOP
? 1 : 0,
#else /* defined(CRAY2) && defined(UNICOS5) */
pcc
= term_output(&unptyip
, ptyibuf2
,
#endif /* defined(CRAY2) && defined(UNICOS5) */
if ((&netobuf
[BUFSIZ
] - nfrontp
) < 2)
c
= *ptyip
++ & 0377, pcc
--;
#if defined(CRAY2) && defined(UNICOS5)
my_state_is_wont(TELOPT_BINARY
) && newmap
)
#endif /* defined(CRAY2) && defined(UNICOS5) */
if ((c
== '\r') && (my_state_is_wont(TELOPT_BINARY
))) {
if (pcc
> 0 && ((*ptyip
& 0377) == '\n')) {
*nfrontp
++ = *ptyip
++ & 0377;
#if defined(CRAY2) && defined(UNICOS5)
* If chars were left over from the terminal driver,
if (!uselinemode
&& unpcc
) {
#endif /* defined(CRAY2) && defined(UNICOS5) */
if (FD_ISSET(f
, &obits
) && (nfrontp
- nbackp
) > 0)
if (FD_ISSET(p
, &obits
) && (pfrontp
- pbackp
) > 0)
* 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 */
(void) ioctl(pty
, TCSIG
, (char *)SIGINT
);
*pfrontp
++ = slctab
[SLC_IP
].sptr
?
(unsigned char)*slctab
[SLC_IP
].sptr
: '\177';
* 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 */
(void) ioctl(pty
, TCSIG
, (char *)SIGQUIT
);
*pfrontp
++ = slctab
[SLC_ABORT
].sptr
?
(unsigned char)*slctab
[SLC_ABORT
].sptr
: '\034';
ptyflush(); /* half-hearted */
(void) ioctl(pty
, TCSIG
, (char *)SIGTSTP
);
*pfrontp
++ = slctab
[SLC_SUSP
].sptr
?
(unsigned char)*slctab
[SLC_SUSP
].sptr
: '\032';
#if defined(USE_TERMIO) && defined(SYSV_TERMIO)
#if defined(USE_TERMIO) && defined(SYSV_TERMIO)
*pfrontp
++ = slctab
[SLC_EOF
].sptr
?
(unsigned char)*slctab
[SLC_EOF
].sptr
: '\004';