* 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.38 (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
= 128; /* low, high pty numbers */
pfrontp
= pbackp
= ptyobuf
;
nfrontp
= nbackp
= netobuf
;
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)) {
if (**argv
== '\0' && argc
)
if ((lowpty
> highpty
) || (lowpty
< 0) || (highpty
> 999)) {
fprintf(stderr
, "Usage: telnetd [-debug] [-h] ");
fprintf(stderr
, "[-Iinitid] ");
fprintf(stderr
, "[-l] [-rlowpty-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");
* 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
};
willoption(TELOPT_TTYPE
, 1);
willoption(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
];
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 (!myopts
[TELOPT_ECHO
])
* 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).
willoption(TELOPT_ECHO
, 1);
if (hisopts
[TELOPT_LINEMODE
] == OPT_NO
) {
/* Query the peer for linemode support by trying to negotiate
willoption(TELOPT_LINEMODE
, 1); /* send do linemode */
* Send along a couple of other options that we wish to negotiate.
willoption(TELOPT_NAWS
, 1);
dooption(TELOPT_STATUS
, 1);
flowmode
= 1; /* default flow control state */
willoption(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
])
* 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
)
willoption(TELOPT_TM
, 1);
# 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
);
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
);
* 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);
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").
(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 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 */
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,
pcc
= term_output(ptyibuf
, ptyibuf2
,
if ((&netobuf
[BUFSIZ
] - nfrontp
) < 2)
c
= *ptyip
++ & 0377, pcc
--;
myopts
[TELOPT_BINARY
] == OPT_NO
&& newmap
)
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)
* 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
? *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
? *slctab
[SLC_ABORT
].sptr
: '\034';
ptyflush(); /* half-hearted */
(void) ioctl(pty
, TCSIG
, (char *)SIGTSTP
);
*pfrontp
++ = slctab
[SLC_SUSP
].sptr
? *slctab
[SLC_SUSP
].sptr
: '\032';
*pfrontp
++ = slctab
[SLC_EOF
].sptr
? *slctab
[SLC_EOF
].sptr
: '\004';