* 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.11 (Berkeley) %G%";
* Stripped-down telnet server.
#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r%s"
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
;
char netobuf
[BUFSIZ
], *nfrontp
= netobuf
, *nbackp
= netobuf
;
char *neturg
= 0; /* one past last bye of urgent data */
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");
char *envinit
[] = { "TERM=network", 0 };
* 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
[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
);
execl("/bin/login", "login", "-h", host
, 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.
signal(SIGTSTP
, SIG_IGN
);
signal(SIGCHLD
, cleanup
);
* Request to do remote echo and to suppress go ahead.
* Show banner that getty never gave.
gethostname(hostname
, sizeof (hostname
));
sprintf(nfrontp
, BANNER
, hostname
, "");
nfrontp
+= strlen(nfrontp
);
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(IOCTL_TO_DO_UNIX_OOB_IN_TCP_WAY)
* 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(IOCTL_TO_DO_UNIX_OOB_IN_TCP_WAY) */
ncc
= read(net
, netibuf
, sizeof (netibuf
));
#endif /* !defined(IOCTL_TO_DO_UNIX_OOB_IN_TCP_WAY) */
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_BEGINNEG 3 /* throw away begin's... */
#define TS_ENDNEG 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
] && 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
;
neturg
= nfrontp
-1; /* off by one XXX */
ptyflush(); /* half-hearted */
ioctl(pty
, TIOCGETP
, &b
);
* Check for urgent data...
SYNCHing
= stilloob(net
);
* Begin option subnegotiation...
state
= c
== SE
? TS_DATA
: TS_BEGINNEG
;
sprintf(nfrontp
, wont
, c
);
nfrontp
+= sizeof (wont
) - 2;
printf("telnetd: panic state=%d\n", state
);
sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (dont
) - 2;
sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
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 ?
if ((n
= pfrontp
- pbackp
) > 0)
n
= write(pty
, pbackp
, n
);
pbackp
= pfrontp
= ptyobuf
;
if ((n
= nfrontp
- nbackp
) > 0)
n
= write(net
, nbackp
, n
);
if (errno
== EWOULDBLOCK
)
/* should blow this guy away... */
nbackp
= nfrontp
= netobuf
;
* Send as much data as possible to the network,
* handling requests for urgent data.
if ((n
= nfrontp
- nbackp
) > 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';