* 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.
static char sccsid
[] = "@(#)state.c 5.1 (Berkeley) %G%";
char doopt
[] = { IAC
, DO
, '%', 'c', 0 };
char dont
[] = { IAC
, DONT
, '%', 'c', 0 };
char will
[] = { IAC
, WILL
, '%', 'c', 0 };
char wont
[] = { IAC
, WONT
, '%', 'c', 0 };
* Buffer for sub-options, and macros
* for suboptions buffer manipulations
char subbuffer
[100], *subpointer
= subbuffer
, *subend
= subbuffer
;
#define SB_CLEAR() subpointer = subbuffer;
#define SB_TERM() { subend = subpointer; SB_CLEAR(); }
#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof subbuffer)) { \
#define SB_GET() ((*subpointer++)&0xff)
#define SB_EOF() (subpointer >= subend)
#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
;
char *opfrontp
= pfrontp
;
if ((&ptyobuf
[BUFSIZ
] - pfrontp
) < 2)
c
= *netip
++ & 0377, ncc
--;
/* Strip off \n or \0 after a \r */
if ((c
== 0) || (c
== '\n')) {
* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
* We USED to 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 ((c
== '\r') && (hisopts
[TELOPT_BINARY
] == OPT_NO
)) {
* If we are operating in linemode,
* convert to local end-of-line.
if ((linemode
) && (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.
(void) strcpy(nfrontp
, "\r\n[Yes]\r\n");
ptyflush(); /* half-hearted */
if (slctab
[SLC_AO
].sptr
&&
*slctab
[SLC_AO
].sptr
!= '\377') {
*pfrontp
++ = *slctab
[SLC_AO
].sptr
;
netclear(); /* clear buffer back */
neturg
= nfrontp
-1; /* off by one XXX */
ptyflush(); /* half-hearted */
ch
= (c
== EC
) ? *slctab
[SLC_EC
].sptr
:
* Check for urgent data...
SYNCHing
= stilloob(net
);
* Begin option subnegotiation...
* Handle RFC 10xx Telnet linemode option additions
* to command stream (EOF, SUSP, ABORT).
* bad form of suboption negotiation.
* handle it in such a way as to avoid
* damage to local state. Parse
* suboption buffer found so far,
* then treat remaining stream as
* another command sequence.
suboption(); /* handle sub-option */
syslog(LOG_ERR
, "telnetd: panic state=%d\n", state
);
printf("telnetd: panic state=%d\n", state
);
char xptyobuf
[BUFSIZ
+NETSLOP
];
int n
= pfrontp
- opfrontp
, oc
;
bcopy(opfrontp
, xptyobuf
, n
);
pfrontp
+= term_input(xptyobuf
, pfrontp
, n
, BUFSIZ
+NETSLOP
,
for (cp
= xbuf2
; oc
> 0; --oc
)
if ((*nfrontp
++ = *cp
++) == IAC
)
* The will/wont/do/dont state machines are based on Dave Borman's
* Telnet option processing state machine. We keep track of the full
* state of the option negotiation with the following state variables
* myopts, hisopts - The last fully negotiated state for each
* side of the connection.
* mywants, hiswants - The state we wish to be in after a completed
* negotiation. (hiswants is slightly misleading,
* this is more precisely the state I want him to
* resp - We count the number of requests we have sent out.
* These correspond to the following states:
* my_state = the last negotiated state
* want_state = what I want the state to go to
* want_resp = how many requests I have sent
* All state defaults are negative, and resp defaults to 0.
* When initiating a request to change state to new_state:
* if ((want_resp == 0 && new_state == my_state) || want_state == new_state) {
* want_state = new_state;
* When receiving new_state:
* if (want_resp && (new_state == my_state))
* if ((want_resp == 0) && (new_state != want_state)) {
* if (ok_to_switch_to new_state)
* want_state = new_state;
* Note that new_state is implied in these functions by the function itself.
* will and do imply positive new_state, wont and dont imply negative.
* Finally, there is one catch. If we send a negative response to a
* positive request, my_state will be the positive while want_state will
* remain negative. my_state will revert to negative when the negative
* acknowlegment arrives from the peer. Thus, my_state generally tells
* us not only the last negotiated state, but also tells us what the peer
* wants to be doing as well. It is important to understand this difference
* as we may wish to be processing data streams based on our desired state
* (want_state) or based on what the peer thinks the state is (my_state).
* This all works fine because if the peer sends a positive request, the data
* that we receive prior to negative acknowlegment will probably be affected
* by the positive state, and we can process it as such (if we can; if we
* can't then it really doesn't matter). If it is that important, then the
* peer probably should be buffering until this option state negotiation
* In processing options, request signifies whether this is a request
* to send or a response. request is true if this is a request to
* send generated locally.
willoption(option
, request
)
* process input from peer.
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". Kludge upon kludge!
if (myopts
[TELOPT_ECHO
] == OPT_YES
) {
dooption(TELOPT_ECHO
, 1);
* Fool the state machine into sending a don't.
* This also allows the initial echo sending code to
* break out of the loop that it is
hiswants
[TELOPT_ECHO
] = OPT_NO
;
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
* This telnetd implementation does not really support
* timing marks, it just uses them to support the kludge
* linemode stuff. If we receive a will or wont TM in
* response to our do TM request that may have been sent
* to determine kludge linemode support, process it,
* otherwise TM should get a negative response back.
* Handle the linemode kludge stuff.
* If we are not currently supporting any
* linemode at all, then we assume that this
* is the client telling us to use kludge
* linemode in response to our query. Set the
* linemode type that is to be supported, note
* that the client wishes to use linemode, and
* eat the will TM as though it never arrived.
if (lmodetype
< KLUDGE_LINEMODE
) {
lmodetype
= KLUDGE_LINEMODE
;
clientstat(TELOPT_LINEMODE
, WILL
, 0);
dontoption(TELOPT_SGA
, 0);
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
* cheat the state machine so that it
* looks like we never sent the TM at
* all. The bad part of this is that
* if the client sends a will TM on his
* own to turn on linemode, then he
hiswants
[TELOPT_TM
] = OPT_NO
;
* If we are going to support flow control option,
* then don't worry peer that we can't change the
* flow control characters.
slctab
[SLC_XON
].defset
.flag
&= ~SLC_LEVELBITS
;
slctab
[SLC_XON
].defset
.flag
|= SLC_DEFAULT
;
slctab
[SLC_XOFF
].defset
.flag
&= ~SLC_LEVELBITS
;
slctab
[SLC_XOFF
].defset
.flag
|= SLC_DEFAULT
;
if (!((resp
[option
] == 0 && hisopts
[option
] == OPT_YES
) ||
hiswants
[option
] == OPT_YES
)) {
hiswants
[option
] = OPT_YES
;
if (resp
[option
] && hisopts
[option
] == OPT_YES
)
if ((resp
[option
] == 0) && (hiswants
[option
] != OPT_YES
)) {
hiswants
[option
] = OPT_YES
;
fmt
= (hiswants
[option
] ? doopt
: dont
);
hisopts
[option
] = OPT_YES
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (dont
) - 2;
* Handle other processing that should occur after we have
* responded to client input.
* Note client's desire to use linemode.
lmodetype
= REAL_LINEMODE
;
# endif /* KLUDGELINEMODE */
clientstat(TELOPT_LINEMODE
, WILL
, 0);
} /* end of willoption */
wontoption(option
, request
)
not42
= 1; /* doesn't seem to be a 4.2 system */
* If real linemode is supported, then client is
* asking to turn linemode off.
if (lmodetype
== REAL_LINEMODE
)
# endif /* KLUDGELINEMODE */
clientstat(TELOPT_LINEMODE
, WONT
, 0);
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
if (lmodetype
< REAL_LINEMODE
) {
clientstat(TELOPT_LINEMODE
, WONT
, 0);
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
* If we get a WONT TM, and had sent a DO TM, don't
* respond with a DONT TM, just leave it as is.
* Short circut the state machine to achive this.
* The bad part of this is that if the client sends
* a WONT TM on his own to turn off linemode, then he
hiswants
[TELOPT_TM
] = OPT_NO
;
* If we are not going to support flow control option,
* then let peer know that we can't change the
* flow control characters.
slctab
[SLC_XON
].defset
.flag
&= ~SLC_LEVELBITS
;
slctab
[SLC_XON
].defset
.flag
|= SLC_CANTCHANGE
;
slctab
[SLC_XOFF
].defset
.flag
&= ~SLC_LEVELBITS
;
slctab
[SLC_XOFF
].defset
.flag
|= SLC_CANTCHANGE
;
if (!((resp
[option
] == 0 && hisopts
[option
] == OPT_NO
) ||
hiswants
[option
] == OPT_NO
)) {
hiswants
[option
] = OPT_NO
;
if (resp
[option
] && hisopts
[option
] == OPT_NO
)
if ((resp
[option
] == 0) && (hiswants
[option
] != OPT_NO
)) {
/* it is always ok to change to negative state */
hiswants
[option
] = OPT_NO
;
hisopts
[option
] = OPT_NO
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
} /* end of wontoption */
dooption(option
, request
)
if (hisopts
[TELOPT_LINEMODE
] == OPT_NO
) {
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
* If kludge linemode is in use, then we must process
* an incoming do SGA for linemode purposes.
if (lmodetype
== KLUDGE_LINEMODE
) {
* Receipt of "do SGA" in kludge linemode
* is the peer asking us to turn off linemode.
* Make note of the request.
clientstat(TELOPT_LINEMODE
, WONT
, 0);
* If linemode did not get turned off then
* don't tell peer that we did. Breaking
* here forces a wont SGA to be returned.
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
if (!((resp
[option
] == 0 && myopts
[option
] == OPT_YES
) ||
mywants
[option
] == OPT_YES
)) {
mywants
[option
] = OPT_YES
;
if (resp
[option
] && myopts
[option
] == OPT_YES
)
if ((resp
[option
] == 0) && (mywants
[option
] != OPT_YES
)) {
mywants
[option
] = OPT_YES
;
fmt
= (mywants
[option
] ? will
: wont
);
myopts
[option
] = OPT_YES
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (doopt
) - 2;
dontoption(option
, request
)
case TELOPT_ECHO
: /* we should stop echoing */
if (hisopts
[TELOPT_LINEMODE
] == OPT_NO
) {
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
* If kludge linemode is in use, then we must process an
* incoming do SGA for linemode purposes.
if (lmodetype
== KLUDGE_LINEMODE
) {
* The client is asking us to turn linemode
clientstat(TELOPT_LINEMODE
, WILL
, 0);
* If we did not turn line mode on, then what do
* we say? Will SGA? This violates design of
* telnet. Gross. Very Gross.
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
if (!((resp
[option
] == 0 && myopts
[option
] == OPT_NO
) ||
mywants
[option
] == OPT_NO
)) {
mywants
[option
] = OPT_NO
;
if (resp
[option
] && myopts
[option
] == OPT_NO
)
if ((resp
[option
] == 0) && (mywants
[option
] != OPT_NO
)) {
mywants
[option
] = OPT_NO
;
(void) sprintf(nfrontp
, fmt
, option
);
nfrontp
+= sizeof (wont
) - 2;
} /* end of dontoption */
* Look at the sub-option buffer, and try to be helpful to the other
* Currently we recognize:
register int xspeed
, rspeed
;
if (hisopts
[TELOPT_TSPEED
] == OPT_NO
) /* Ignore if option disabled */
if (SB_EOF() || SB_GET() != TELQUAL_IS
)
xspeed
= atoi(subpointer
);
while (SB_GET() != ',' && !SB_EOF());
rspeed
= atoi(subpointer
);
clientstat(TELOPT_TSPEED
, xspeed
, rspeed
);
} /* end of case TELOPT_TSPEED */
case TELOPT_TTYPE
: { /* Yaaaay! */
static char terminalname
[5+41] = "TERM=";
if (hisopts
[TELOPT_TSPEED
] == OPT_NO
) /* Ignore if option disabled */
if (SB_GET() != TELQUAL_IS
) {
return; /* ??? XXX but, this is the most robust */
terminaltype
= terminalname
+sizeof("TERM=")-1;
while ((terminaltype
< (terminalname
+ sizeof terminalname
-1)) &&
*terminaltype
++ = c
; /* accumulate name */
terminaltype
= terminalname
;
} /* end of case TELOPT_TTYPE */
register int xwinsize
, ywinsize
;
if (hisopts
[TELOPT_NAWS
] == OPT_NO
) /* Ignore if option disabled */
xwinsize
= SB_GET() << 8;
ywinsize
= SB_GET() << 8;
clientstat(TELOPT_NAWS
, xwinsize
, ywinsize
);
} /* end of case TELOPT_NAWS */
if (hisopts
[TELOPT_LINEMODE
] == OPT_NO
) /* Ignore if option disabled */
* Process linemode suboptions.
if (SB_EOF()) break; /* garbage was sent */
request
= SB_GET(); /* get will/wont */
if (SB_EOF()) break; /* another garbage check */
if (request
== LM_SLC
) { /* SLC is not preceeded by WILL or WONT */
* Process suboption buffer of slc's
do_opt_slc(subpointer
, subend
- subpointer
);
} else if (request
== LM_MODE
) {
useeditmode
= SB_GET(); /* get mode flag */
clientstat(LM_MODE
, 0, 0);
switch (SB_GET()) { /* what suboption? */
* According to spec, only server can send request for
* forwardmask, and client can only return a positive response.
* So don't worry about it.
} /* end of case TELOPT_LINEMODE */
if (myopts
[TELOPT_STATUS
] == OPT_YES
)
#define ADD(c) *ncp++ = c;
#define ADD_DATA(c) { *ncp++ = c; if (c == SE) *ncp++ = c; }
netflush(); /* get rid of anything waiting to go out */
for (i
= 0; i
< NTELOPTS
; i
++) {
if (myopts
[i
] == OPT_YES
) {
if (hisopts
[i
] == OPT_YES
) {
if (hisopts
[TELOPT_LINEMODE
] == OPT_YES
) {
for (cpe
= cp
+ len
; cp
< cpe
; cp
++)
writenet(statusbuf
, ncp
- statusbuf
);
netflush(); /* Send it on its way */