* Copyright (c) 1989 Regents of the University of California.
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)state.c 5.9 (Berkeley) %G%";
#if defined(AUTHENTICATE)
#include <libtelnet/auth.h>
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
unsigned char subbuffer
[512], *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 SB_LEN() (subend - subpointer)
#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 defined(CRAY2) && defined(UNICOS5)
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') && his_state_is_wont(TELOPT_BINARY
)) {
nc
= (*decrypt_input
)(nc
& 0xff);
* If we are operating in linemode,
* convert to local end-of-line.
if (linemode
&& (ncc
> 0) && (('\n' == nc
) ||
((0 == nc
) && tty_iscrnl())) ) {
(void)(*decrypt_input
)(-1);
* Send the process on the pty side an
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
printoption("td: recv IAC", c
));
printoption("td: recv IAC", c
));
printoption("td: recv IAC", c
));
printoption("td: recv IAC", c
));
ptyflush(); /* half-hearted */
if (slctab
[SLC_AO
].sptr
&&
*slctab
[SLC_AO
].sptr
!= (cc_t
)(_POSIX_VDISABLE
)) {
(unsigned char)*slctab
[SLC_AO
].sptr
;
netclear(); /* clear buffer back */
neturg
= nfrontp
-1; /* off by one XXX */
printoption("td: send IAC", DM
));
printoption("td: recv IAC", c
));
ptyflush(); /* half-hearted */
ch
= *slctab
[SLC_EC
].sptr
;
ch
= *slctab
[SLC_EL
].sptr
;
if (ch
!= (cc_t
)(_POSIX_VDISABLE
))
*pfrontp
++ = (unsigned char)ch
;
* Check for urgent data...
printoption("td: recv IAC", c
));
SYNCHing
= stilloob(net
);
* Begin option subnegotiation...
if (his_state_is_will(TELOPT_EOR
))
* 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
);
#if defined(CRAY2) && defined(UNICOS5)
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
)
#endif /* defined(CRAY2) && defined(UNICOS5) */
* The will/wont/do/dont state machines are based on Dave Borman's
* Telnet option processing state machine.
* 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
if ((do_dont_resp
[option
] == 0 && his_state_is_will(option
)) ||
his_want_state_is_will(option
))
* Special case for TELOPT_TM: We send a DO, but pretend
* that we sent a DONT, so that we can send more DOs if
set_his_want_state_wont(option
);
set_his_want_state_will(option
);
(void) sprintf(nfrontp
, doopt
, option
);
nfrontp
+= sizeof (dont
) - 2;
DIAG(TD_OPTIONS
, printoption("td: send do", option
));
extern void auth_request();
extern void doclientstat();
extern void encrypt_send_support();
* process input from peer.
DIAG(TD_OPTIONS
, printoption("td: recv will", option
));
if (do_dont_resp
[option
]) {
if (do_dont_resp
[option
] && his_state_is_will(option
))
if (do_dont_resp
[option
] == 0) {
if (his_want_state_is_wont(option
)) {
* See comments below for more info.
not42
= 0; /* looks like a 4.2 system */
#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
* 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);
send_wont(TELOPT_SGA
, 1);
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
* We never respond to a WILL TM, and
* we leave the state WONT.
* 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
;
* Note client's desire to use linemode.
lmodetype
= REAL_LINEMODE
;
# endif /* KLUDGELINEMODE */
case TELOPT_AUTHENTICATION
:
func
= encrypt_send_support
;
set_his_want_state_will(option
);
* Option processing that should happen when
* we receive conformation of a change in
* state that we had requested.
not42
= 0; /* looks like a 4.2 system */
* Egads, he responded "WILL ECHO". Turn
* "WILL ECHO". Kludge upon kludge!
* A 4.2 client is now echoing user input at
* the tty. This is probably undesireable and
* it should be stopped. The client will
* respond WONT TM to the DO TM that we send to
* check for kludge linemode. When the WONT TM
* arrives, linemode will be turned off and a
* change propogated to the pty. This change
* will cause us to process the new pty state
* in localstat(), which will notice that
* linemode is off and send a WILL ECHO
* so that we are properly in character mode and
* Note client's desire to use linemode.
lmodetype
= REAL_LINEMODE
;
# endif /* KLUDGELINEMODE */
case TELOPT_AUTHENTICATION
:
func
= encrypt_send_support
;
set_his_state_will(option
);
} /* end of willoption */
if ((do_dont_resp
[option
] == 0 && his_state_is_wont(option
)) ||
his_want_state_is_wont(option
))
set_his_want_state_wont(option
);
(void) sprintf(nfrontp
, dont
, option
);
nfrontp
+= sizeof (doopt
) - 2;
DIAG(TD_OPTIONS
, printoption("td: send dont", option
));
DIAG(TD_OPTIONS
, printoption("td: recv wont", option
));
if (do_dont_resp
[option
]) {
if (do_dont_resp
[option
] && his_state_is_wont(option
))
if (do_dont_resp
[option
] == 0) {
if (his_want_state_is_will(option
)) {
/* it is always ok to change to negative state */
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
)
lmodetype
= KLUDGE_LINEMODE
;
# endif /* KLUDGELINEMODE */
clientstat(TELOPT_LINEMODE
, WONT
, 0);
* 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
set_his_want_state_wont(TELOPT_TM
);
* 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 defined(AUTHENTICATE)
case TELOPT_AUTHENTICATION
:
auth_finished(0, AUTH_REJECT
);
* For options that we might spin waiting for
* sub-negotiation, if the client turns off the
* option rather than responding to the request,
* we have to treat it here as if we got a response
* to the sub-negotiation, (by updating the timers)
* so that we'll break out of the loop.
settimer(xdisplocsubopt
);
set_his_want_state_wont(option
);
if (his_state_is_will(option
))
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
if (lmodetype
< REAL_LINEMODE
) {
clientstat(TELOPT_LINEMODE
, WONT
, 0);
send_will(TELOPT_SGA
, 1);
send_will(TELOPT_ECHO
, 1);
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
#if defined(AUTHENTICATE)
case TELOPT_AUTHENTICATION
:
auth_finished(0, AUTH_REJECT
);
set_his_state_wont(option
);
} /* end of wontoption */
if ((will_wont_resp
[option
] == 0 && my_state_is_will(option
))||
my_want_state_is_will(option
))
set_my_want_state_will(option
);
will_wont_resp
[option
]++;
(void) sprintf(nfrontp
, will
, option
);
nfrontp
+= sizeof (doopt
) - 2;
DIAG(TD_OPTIONS
, printoption("td: send will", option
));
#if !defined(LINEMODE) || !defined(KLUDGELINEMODE)
* When we get a DONT SGA, we will try once to turn it
* back on. If the other side responds DONT SGA, we
* leave it at that. This is so that when we talk to
* clients that understand KLUDGELINEMODE but not LINEMODE,
* we'll keep them in char-at-a-time mode.
DIAG(TD_OPTIONS
, printoption("td: recv do", option
));
if (will_wont_resp
[option
]) {
will_wont_resp
[option
]--;
if (will_wont_resp
[option
] && my_state_is_will(option
))
will_wont_resp
[option
]--;
if ((will_wont_resp
[option
] == 0) && (my_want_state_is_wont(option
))) {
if (lmodetype
== NO_LINEMODE
)
if (his_state_is_wont(TELOPT_LINEMODE
))
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
* If kludge linemode is in use, then we must
* process an incoming do SGA for linemode
if (lmodetype
== KLUDGE_LINEMODE
) {
* Receipt of "do SGA" in kludge
* linemode is the peer asking us to
* turn off linemode. Make note of
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
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
* Special case for TM. We send a WILL, but
* pretend we sent a WONT.
set_my_want_state_wont(option
);
set_my_state_wont(option
);
* When we get a LOGOUT option, respond
* with a WILL LOGOUT, make sure that
* it gets written out to the network,
* and then just go away...
set_my_want_state_will(TELOPT_LOGOUT
);
send_will(TELOPT_LOGOUT
, 0);
set_my_state_will(TELOPT_LOGOUT
);
set_my_want_state_will(option
);
will_wont_resp
[option
]++;
set_my_state_will(option
);
if ((will_wont_resp
[option
] == 0 && my_state_is_wont(option
)) ||
my_want_state_is_wont(option
))
set_my_want_state_wont(option
);
will_wont_resp
[option
]++;
(void) sprintf(nfrontp
, wont
, option
);
nfrontp
+= sizeof (wont
) - 2;
DIAG(TD_OPTIONS
, printoption("td: send wont", option
));
DIAG(TD_OPTIONS
, printoption("td: recv dont", option
));
if (will_wont_resp
[option
]) {
will_wont_resp
[option
]--;
if (will_wont_resp
[option
] && my_state_is_wont(option
))
will_wont_resp
[option
]--;
if ((will_wont_resp
[option
] == 0) && (my_want_state_is_will(option
))) {
case TELOPT_ECHO
: /* we should stop echoing */
if (lmodetype
== NO_LINEMODE
)
if (his_state_is_wont(TELOPT_LINEMODE
))
#if defined(LINEMODE) && defined(KLUDGELINEMODE)
* If kludge linemode is in use, then we
* must process an incoming do SGA for
if (lmodetype
== KLUDGE_LINEMODE
) {
* The client is asking us to turn
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.
set_my_want_state_wont(option
);
if (my_state_is_will(option
))
set_my_state_wont(option
);
#endif /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
set_my_want_state_wont(option
);
if (my_state_is_will(option
))
set_my_state_wont(option
);
} /* end of dontoption */
* Look at the sub-option buffer, and try to be helpful to the other
* Currently we recognize:
DIAG(TD_OPTIONS
, {netflush(); printsub('<', subpointer
, SB_LEN()+2);});
register int xspeed
, rspeed
;
if (his_state_is_wont(TELOPT_TSPEED
)) /* Ignore if option disabled */
if (SB_EOF() || SB_GET() != TELQUAL_IS
)
xspeed
= atoi((char *)subpointer
);
while (SB_GET() != ',' && !SB_EOF());
rspeed
= atoi((char *)subpointer
);
clientstat(TELOPT_TSPEED
, xspeed
, rspeed
);
} /* end of case TELOPT_TSPEED */
case TELOPT_TTYPE
: { /* Yaaaay! */
static char terminalname
[41];
if (his_state_is_wont(TELOPT_TTYPE
)) /* Ignore if option disabled */
if (SB_EOF() || SB_GET() != TELQUAL_IS
) {
return; /* ??? XXX but, this is the most robust */
terminaltype
= terminalname
;
while ((terminaltype
< (terminalname
+ sizeof terminalname
-1)) &&
*terminaltype
++ = c
; /* accumulate name */
terminaltype
= terminalname
;
} /* end of case TELOPT_TTYPE */
register int xwinsize
, ywinsize
;
if (his_state_is_wont(TELOPT_NAWS
)) /* Ignore if option disabled */
xwinsize
= SB_GET() << 8;
ywinsize
= SB_GET() << 8;
clientstat(TELOPT_NAWS
, xwinsize
, ywinsize
);
} /* end of case TELOPT_NAWS */
if (his_state_is_wont(TELOPT_LINEMODE
)) /* Ignore if option disabled */
* Process linemode suboptions.
break; /* garbage was sent */
request
= SB_GET(); /* get will/wont */
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 (my_state_is_will(TELOPT_STATUS
))
} /* end of case TELOPT_STATUS */
if (SB_EOF() || SB_GET() != TELQUAL_IS
)
settimer(xdisplocsubopt
);
subpointer
[SB_LEN()] = '\0';
(void)setenv("DISPLAY", (char *)subpointer
, 1);
} /* end of case TELOPT_XDISPLOC */
register char *cp
, *varp
, *valp
;
else if (c
!= TELQUAL_INFO
)
while (!SB_EOF() && SB_GET() != ENV_VAR
)
cp
= varp
= (char *)subpointer
;
cp
= valp
= (char *)subpointer
;
(void)setenv(varp
, valp
, 1);
cp
= varp
= (char *)subpointer
;
(void)setenv(varp
, valp
, 1);
} /* end of case TELOPT_ENVIRON */
#if defined(AUTHENTICATE)
case TELOPT_AUTHENTICATION
:
* These are sent by us and cannot be sent by
auth_is(subpointer
, SB_LEN());
encrypt_support(subpointer
, SB_LEN());
encrypt_is(subpointer
, SB_LEN());
encrypt_reply(subpointer
, SB_LEN());
* We can always send an REQEND so that we cannot
* get stuck encrypting. We should only get this
* if we have been able to get in the correct mode
clientstat(TELOPT_LINEMODE
, WILL
, 0);
#define ADD(c) *ncp++ = c;
#define ADD_DATA(c) { *ncp++ = c; if (c == SE) *ncp++ = c; }
unsigned char statusbuf
[256];
register unsigned char *ncp
;
register unsigned char i
;
netflush(); /* get rid of anything waiting to go out */
* We check the want_state rather than the current state,
* because if we received a DO/WILL for an option that we
* don't support, and the other side didn't send a DONT/WONT
* in response to our WONT/DONT, then the "state" will be
* WILL/DO, and the "want_state" will be WONT/DONT. We
* need to go by the latter.
for (i
= 0; i
< NTELOPTS
; i
++) {
if (my_want_state_is_will(i
)) {
if (his_want_state_is_will(i
)) {
if (his_want_state_is_will(TELOPT_LFLOW
)) {
if (his_want_state_is_will(TELOPT_LINEMODE
)) {
for (cpe
= cp
+ len
; cp
< cpe
; cp
++)
writenet(statusbuf
, ncp
- statusbuf
);
netflush(); /* Send it on its way */
{printsub('>', statusbuf
, ncp
- statusbuf
); netflush();});