* Copyright (c) 1983 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
"@(#) Copyright (c) 1983 Regents of the University of California.\n\
static char sccsid
[] = "@(#)telnetd.c 5.14 (Berkeley) %G%";
* Stripped-down telnet server.
#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r%s"
#define OPT_DONT 0 /* don't do this option */
#define OPT_WONT 0 /* won't do this option */
#define OPT_DO 1 /* do this option */
#define OPT_WILL 1 /* will do this option */
#define OPT_ALWAYS_LOOK 2 /* special case for echo */
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 */
char subbuffer
[100], *subpointer
, *subend
; /* buffer for sub-options */
#define SB_CLEAR() subpointer = subbuffer;
#define SB_TERM() subend = subpointer;
#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { \
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 */
gotDM
; /* when did we last see a data mark */
#define settimer(x) clocks.x = clocks.system++
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");
* Return next character from file descriptor.
* This is not meant to be very efficient, since it is only
int f
; /* the file descriptor */
if (read(f
, &input
, 1) != 1) {
syslog(LOG_ERR
, "read: %m\n");
* Get a pty, scan input lines.
char *host
, *inet_ntoa();
* Try to get a terminal type from the foreign host.
static char sbuf
[] = { IAC
, DO
, TELOPT_TTYPE
};
if (write(f
, sbuf
, sizeof sbuf
) == -1) {
syslog(LOG_ERR
, "write sbuf: %m\n");
for (;;) { /* ugly, but we are VERY early */
while ((c
= Get(f
)) != IAC
) {
if ((c
= Get(f
)) == WILL
) {
if ((c
= Get(f
)) == TELOPT_TTYPE
) {
static char sbbuf
[] = { IAC
, SB
, TELOPT_TTYPE
,
if (write(f
, sbbuf
, sizeof sbbuf
) == -1) {
syslog(LOG_ERR
, "write sbbuf: %m\n");
if ((c
= Get(f
)) == TELOPT_TTYPE
) {
terminaltype
= "TERM=network";
while ((c
= Get(f
)) != IAC
) {
if ((c
= Get(f
)) != SB
) {
} else if ((c
= Get(f
)) != TELOPT_TTYPE
) {
} else if ((c
= Get(f
)) != TELQUAL_IS
) {
static char terminalname
[5+41] = "TERM=";
terminaltype
= terminalname
+strlen(terminalname
);
(terminalname
+ sizeof terminalname
-1)) {
if ((c
= Get(f
)) == IAC
) {
if ((c
= Get(f
)) == SE
) {
*terminaltype
++ = IAC
; /* ? */
*terminaltype
++ = c
; /* accumulate name */
terminaltype
= terminalname
;
envinit
[0] = terminaltype
;
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
[strlen("/dev/ptyp")] = "0123456789abcdef"[i
];
fatal(f
, "All network ports in use");
line
[strlen("/dev/")] = 't';
t
= open("/dev/tty", O_RDWR
);
fatalperror(f
, line
, errno
);
b
.sg_flags
= CRMOD
|XTABS
|ANYP
;
hp
= gethostbyaddr(&who
->sin_addr
, sizeof (struct in_addr
),
host
= inet_ntoa(who
->sin_addr
);
fatalperror(f
, "fork", errno
);
* -h : pass on name of host.
* -p : don't clobber the environment (so terminal type stays set).
execl("/bin/login", "login", "-h", host
,
gotterminaltype
? "-p" : 0, 0);
fatalperror(f
, "/bin/login", errno
);
(void) sprintf(buf
, "telnetd: %s.\r\n", msg
);
(void) write(f
, buf
, strlen(buf
));
fatalperror(f
, msg
, errno
)
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", errno
);
if (FD_ISSET(s
, &excepts
)) {
* Main loop. Select from pty and network, and
* hand data to telnet receiver finite state machine.
#if defined(SO_OOBINLINE)
setsockopt(net
, SOL_SOCKET
, SO_OOBINLINE
, &on
, sizeof on
);
#endif /* defined(SO_OOBINLINE) */
signal(SIGTSTP
, SIG_IGN
);
signal(SIGCHLD
, cleanup
);
* Request to do remote echo and to suppress go ahead.
* 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).
sprintf(nfrontp
, doopt
, TELOPT_ECHO
);
nfrontp
+= sizeof doopt
-2;
hisopts
[TELOPT_ECHO
] = OPT_ALWAYS_LOOK
;
* Show banner that getty never gave.
gethostname(hostname
, sizeof (hostname
));
sprintf(nfrontp
, BANNER
, hostname
, "");
nfrontp
+= strlen(nfrontp
);
* 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 some early 4.3) 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 (clocks
.didnetreceive
< clocks
.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 ((&netobuf
[BUFSIZ
] - nfrontp
) < 2)
c
= *ptyip
++ & 0377, pcc
--;
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
--;
if ((c
== 0) || (c
== '\n')) {
* We 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 ((myopts
[TELOPT_BINARY
] == OPT_DONT
) && c
== '\r') {
if ((ncc
> 0) && ('\n' == *netip
)) {
* 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_WILL
)
if (hisopts
[c
] != OPT_WONT
)
if (myopts
[c
] != OPT_DONT
) {
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_DO
) {
hisopts
[option
] = OPT_WILL
;
hisopts
[option
] = OPT_WONT
;
sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (dont
) - 2;
not42
= 1; /* doesn't seem to be a 4.2 system */
hisopts
[option
] = OPT_WONT
;
sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
myopts
[option
] = OPT_DONT
;
sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
case TELOPT_ECHO
: /* we should stop echoing */
myopts
[option
] = OPT_DONT
;
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:
* (nothing - we only do terminal type at start-up time)
switch (subbuffer
[0]&0xff) {
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
;
char wtmpf
[] = "/usr/adm/wtmp";
char utmpf
[] = "/etc/utmp";
#define SCPYN(a, b) strncpy(a, b, sizeof(a))
#define SCMPN(a, b) strncmp(a, b, sizeof(a))
utmp
= (struct utmp
*)malloc(statbf
.st_size
);
syslog(LOG_ERR
, "utmp malloc failed");
if (statbf
.st_size
&& utmp
) {
nutmp
= read(f
, utmp
, statbf
.st_size
);
nutmp
/= sizeof(struct utmp
);
for (u
= utmp
; u
< &utmp
[nutmp
] ; u
++) {
if (SCMPN(u
->ut_line
, line
+5) ||
lseek(f
, ((long)u
)-((long)utmp
), L_SET
);
write(f
, (char *)u
, sizeof(wtmp
));
f
= open(wtmpf
, O_WRONLY
|O_APPEND
);
SCPYN(wtmp
.ut_line
, line
+5);
write(f
, (char *)&wtmp
, sizeof(wtmp
));
line
[strlen("/dev/")] = 'p';