* Copyright (c) 1989 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) 1989 Regents of the University of California.\n\
static char sccsid
[] = "@(#)telnetd.c 5.42 (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 */
#if defined(IP_TOS) && defined(NEED_GETTOS)
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 */
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)) {
fprintf(stderr
, "Usage: telnetd [-debug] [-h] ");
fprintf(stderr
, "[-Iinitid] ");
fprintf(stderr
, "[-l] [-r[lowpty]-[highpty]] [port]\n");
if (argc
> 0 && !strncmp(*argv
, "-I", 2)) {
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");
"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 ((tp
= gettosbyname("telnet", "tcp")) &&
(setsockopt(0, IPPROTO_IP
, IP_TOS
, &tp
->t_tos
, sizeof(int)) < 0))
syslog(LOG_WARNING
, "setsockopt (IP_TOS): %m");
* 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);
while ((hiswants
[TELOPT_TTYPE
] != hisopts
[TELOPT_TTYPE
]) ||
(hiswants
[TELOPT_TSPEED
] != hisopts
[TELOPT_TSPEED
])) {
if (hisopts
[TELOPT_TSPEED
] == OPT_YES
) {
static char sbbuf
[] = { IAC
, SB
, TELOPT_TSPEED
, TELQUAL_SEND
, IAC
, SE
};
bcopy(sbbuf
, nfrontp
, sizeof sbbuf
);
if (hisopts
[TELOPT_TTYPE
] == OPT_YES
) {
bcopy(ttytype_sbbuf
, nfrontp
, sizeof ttytype_sbbuf
);
nfrontp
+= sizeof ttytype_sbbuf
;
if (hisopts
[TELOPT_TSPEED
] == OPT_YES
) {
while (sequenceIs(tspeedsubopt
, baseline
))
if (hisopts
[TELOPT_TTYPE
] == OPT_YES
) {
char first
[256], last
[256];
while (sequenceIs(ttypesubopt
, baseline
))
if (!terminaltypeok(&terminaltype
[5])) {
(void) strncpy(first
, terminaltype
, sizeof(first
));
* Save the unknown name, and request the next name.
(void) strncpy(last
, terminaltype
, sizeof(last
));
if (terminaltypeok(&terminaltype
[5]))
if (strncmp(last
, terminaltype
, sizeof(last
)) == 0) {
* 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 type, 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 */
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();
* 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
);
if (terminaltype
== NULL
)
terminaltype
= "TERM=network";
* 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
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 (hisopts
[TELOPT_LINEMODE
] == OPT_NO
) {
/* 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 (hiswants
[TELOPT_NAWS
] != hisopts
[TELOPT_NAWS
])
* 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 (hiswants
[TELOPT_ECHO
] == OPT_YES
) {
* Finally, to clean things up, we turn on our echo. This
* will break stupid 4.2 telnets out of local terminal echo.
if (!myopts
[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.
* 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.
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
)
* 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 (hisopts
[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)
myopts
[TELOPT_BINARY
] == OPT_NO
&& newmap
)
#endif /* defined(CRAY2) && defined(UNICOS5) */
if ((c
== '\r') && (myopts
[TELOPT_BINARY
] == OPT_NO
)) {
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';