#include "telnetd.h"
RCSID("$Id$");
unsigned char doopt[] = { IAC, DO, '%', 'c', 0 };
unsigned char dont[] = { IAC, DONT, '%', 'c', 0 };
unsigned char will[] = { IAC, WILL, '%', 'c', 0 };
unsigned char wont[] = { IAC, WONT, '%', 'c', 0 };
int not42 = 1;
unsigned char subbuffer[1024*64], *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)) { \
*subpointer++ = (c); \
}
#define SB_GET() ((*subpointer++)&0xff)
#define SB_EOF() (subpointer >= subend)
#define SB_LEN() (subend - subpointer)
#ifdef ENV_HACK
unsigned char *subsave;
#define SB_SAVE() subsave = subpointer;
#define SB_RESTORE() subpointer = subsave;
#endif
#define TS_DATA 0
#define TS_IAC 1
#define TS_CR 2
#define TS_SB 3
#define TS_SE 4
#define TS_WILL 5
#define TS_WONT 6
#define TS_DO 7
#define TS_DONT 8
void
telrcv(void)
{
int c;
static int state = TS_DATA;
while (ncc > 0) {
if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
break;
c = *netip++ & 0377, ncc--;
#ifdef ENCRYPTION
if (decrypt_input)
c = (*decrypt_input)(c);
#endif
switch (state) {
case TS_CR:
state = TS_DATA;
if ((c == 0) || (c == '\n')) {
break;
}
case TS_DATA:
if (c == IAC) {
state = TS_IAC;
break;
}
if ((c == '\r') && his_state_is_wont(TELOPT_BINARY)) {
int nc = *netip;
#ifdef ENCRYPTION
if (decrypt_input)
nc = (*decrypt_input)(nc & 0xff);
#endif
{
#ifdef ENCRYPTION
if (decrypt_input)
(void)(*decrypt_input)(-1);
#endif
state = TS_CR;
}
}
*pfrontp++ = c;
break;
case TS_IAC:
gotiac: switch (c) {
case IP:
DIAG(TD_OPTIONS,
printoption("td: recv IAC", c));
interrupt();
break;
case BREAK:
DIAG(TD_OPTIONS,
printoption("td: recv IAC", c));
sendbrk();
break;
case AYT:
DIAG(TD_OPTIONS,
printoption("td: recv IAC", c));
recv_ayt();
break;
case AO:
{
DIAG(TD_OPTIONS,
printoption("td: recv IAC", c));
ptyflush();
init_termbuf();
if (slctab[SLC_AO].sptr &&
*slctab[SLC_AO].sptr != (cc_t)(_POSIX_VDISABLE)) {
*pfrontp++ =
(unsigned char)*slctab[SLC_AO].sptr;
}
netclear();
output_data ("%c%c", IAC, DM);
neturg = nfrontp-1;
DIAG(TD_OPTIONS,
printoption("td: send IAC", DM));
break;
}
case EC:
case EL:
{
cc_t ch;
DIAG(TD_OPTIONS,
printoption("td: recv IAC", c));
ptyflush();
init_termbuf();
if (c == EC)
ch = *slctab[SLC_EC].sptr;
else
ch = *slctab[SLC_EL].sptr;
if (ch != (cc_t)(_POSIX_VDISABLE))
*pfrontp++ = (unsigned char)ch;
break;
}
case DM:
DIAG(TD_OPTIONS,
printoption("td: recv IAC", c));
SYNCHing = stilloob(net);
settimer(gotDM);
break;
case SB:
state = TS_SB;
SB_CLEAR();
continue;
case WILL:
state = TS_WILL;
continue;
case WONT:
state = TS_WONT;
continue;
case DO:
state = TS_DO;
continue;
case DONT:
state = TS_DONT;
continue;
case EOR:
if (his_state_is_will(TELOPT_EOR))
doeof();
break;
case xEOF:
doeof();
break;
case SUSP:
sendsusp();
break;
case ABORT:
sendbrk();
break;
case IAC:
*pfrontp++ = c;
break;
}
state = TS_DATA;
break;
case TS_SB:
if (c == IAC) {
state = TS_SE;
} else {
SB_ACCUM(c);
}
break;
case TS_SE:
if (c != SE) {
if (c != IAC) {
SB_ACCUM(IAC);
SB_ACCUM(c);
subpointer -= 2;
SB_TERM();
suboption();
state = TS_IAC;
goto gotiac;
}
SB_ACCUM(c);
state = TS_SB;
} else {
SB_ACCUM(IAC);
SB_ACCUM(SE);
subpointer -= 2;
SB_TERM();
suboption();
state = TS_DATA;
}
break;
case TS_WILL:
willoption(c);
state = TS_DATA;
continue;
case TS_WONT:
wontoption(c);
if (c==TELOPT_ENCRYPT && his_do_dont_is_changing(TELOPT_ENCRYPT) )
dontoption(c);
state = TS_DATA;
continue;
case TS_DO:
dooption(c);
state = TS_DATA;
continue;
case TS_DONT:
dontoption(c);
state = TS_DATA;
continue;
default:
syslog(LOG_ERR, "telnetd: panic state=%d\n", state);
printf("telnetd: panic state=%d\n", state);
exit(1);
}
}
}
void
send_do(int option, int init)
{
if (init) {
if ((do_dont_resp[option] == 0 && his_state_is_will(option)) ||
his_want_state_is_will(option))
return;
if (option == TELOPT_TM)
set_his_want_state_wont(option);
else
set_his_want_state_will(option);
do_dont_resp[option]++;
}
output_data((const char *)doopt, option);
DIAG(TD_OPTIONS, printoption("td: send do", option));
}
#ifdef AUTHENTICATION
extern void auth_request(void);
#endif
#ifdef ENCRYPTION
extern void encrypt_send_support(void);
#endif
void
willoption(int option)
{
int changeok = 0;
void (*func)(void) = NULL;
DIAG(TD_OPTIONS, printoption("td: recv will", option));
if (do_dont_resp[option]) {
do_dont_resp[option]--;
if (do_dont_resp[option] && his_state_is_will(option))
do_dont_resp[option]--;
}
if (do_dont_resp[option] == 0) {
if (his_want_state_is_wont(option)) {
switch (option) {
case TELOPT_BINARY:
init_termbuf();
tty_binaryin(1);
set_termbuf();
changeok++;
break;
case TELOPT_ECHO:
not42 = 0;
break;
case TELOPT_TM:
return;
case TELOPT_LFLOW:
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;
case TELOPT_TTYPE:
case TELOPT_SGA:
case TELOPT_NAWS:
case TELOPT_TSPEED:
case TELOPT_XDISPLOC:
case TELOPT_NEW_ENVIRON:
case TELOPT_OLD_ENVIRON:
changeok++;
break;
#ifdef AUTHENTICATION
case TELOPT_AUTHENTICATION:
func = auth_request;
changeok++;
break;
#endif
#ifdef ENCRYPTION
case TELOPT_ENCRYPT:
func = encrypt_send_support;
changeok++;
break;
#endif
default:
break;
}
if (changeok) {
set_his_want_state_will(option);
send_do(option, 0);
} else {
do_dont_resp[option]++;
send_dont(option, 0);
}
} else {
switch (option) {
case TELOPT_ECHO:
not42 = 0;
send_dont(option, 1);
break;
#ifdef AUTHENTICATION
case TELOPT_AUTHENTICATION:
func = auth_request;
break;
#endif
#ifdef ENCRYPTION
case TELOPT_ENCRYPT:
func = encrypt_send_support;
break;
#endif
case TELOPT_LFLOW:
func = flowstat;
break;
}
}
}
set_his_state_will(option);
if (func)
(*func)();
}
void
send_dont(int option, int init)
{
if (init) {
if ((do_dont_resp[option] == 0 && his_state_is_wont(option)) ||
his_want_state_is_wont(option))
return;
set_his_want_state_wont(option);
do_dont_resp[option]++;
}
output_data((const char *)dont, option);
DIAG(TD_OPTIONS, printoption("td: send dont", option));
}
void
wontoption(int option)
{
DIAG(TD_OPTIONS, printoption("td: recv wont", option));
if (do_dont_resp[option]) {
do_dont_resp[option]--;
if (do_dont_resp[option] && his_state_is_wont(option))
do_dont_resp[option]--;
}
if (do_dont_resp[option] == 0) {
if (his_want_state_is_will(option)) {
switch (option) {
case TELOPT_ECHO:
not42 = 1;
break;
case TELOPT_BINARY:
init_termbuf();
tty_binaryin(0);
set_termbuf();
break;
case TELOPT_TM:
set_his_want_state_wont(TELOPT_TM);
return;
case TELOPT_LFLOW:
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;
break;
#ifdef AUTHENTICATION
case TELOPT_AUTHENTICATION:
auth_finished(0, AUTH_REJECT);
break;
#endif
case TELOPT_TTYPE:
settimer(ttypesubopt);
break;
case TELOPT_TSPEED:
settimer(tspeedsubopt);
break;
case TELOPT_XDISPLOC:
settimer(xdisplocsubopt);
break;
case TELOPT_OLD_ENVIRON:
settimer(oenvironsubopt);
break;
case TELOPT_NEW_ENVIRON:
settimer(environsubopt);
break;
default:
break;
}
set_his_want_state_wont(option);
if (his_state_is_will(option))
send_dont(option, 0);
} else {
switch (option) {
case TELOPT_TM:
break;
#ifdef AUTHENTICATION
case TELOPT_AUTHENTICATION:
auth_finished(0, AUTH_REJECT);
break;
#endif
default:
break;
}
}
}
set_his_state_wont(option);
}
void
send_will(int option, int init)
{
if (init) {
if ((will_wont_resp[option] == 0 && my_state_is_will(option))||
my_want_state_is_will(option))
return;
set_my_want_state_will(option);
will_wont_resp[option]++;
}
output_data ((const char *)will, option);
DIAG(TD_OPTIONS, printoption("td: send will", option));
}
int turn_on_sga = 0;
void
dooption(int option)
{
int changeok = 0;
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))) {
switch (option) {
case TELOPT_ECHO:
{
init_termbuf();
tty_setecho(1);
set_termbuf();
}
changeok++;
break;
case TELOPT_BINARY:
init_termbuf();
tty_binaryout(1);
set_termbuf();
changeok++;
break;
case TELOPT_SGA:
turn_on_sga = 0;
changeok++;
break;
case TELOPT_STATUS:
changeok++;
break;
case TELOPT_TM:
send_will(option, 0);
set_my_want_state_wont(option);
set_my_state_wont(option);
return;
case TELOPT_LOGOUT:
set_my_want_state_will(TELOPT_LOGOUT);
send_will(TELOPT_LOGOUT, 0);
set_my_state_will(TELOPT_LOGOUT);
netflush();
cleanup(0);
break;
#ifdef ENCRYPTION
case TELOPT_ENCRYPT:
changeok++;
break;
#endif
case TELOPT_LINEMODE:
case TELOPT_TTYPE:
case TELOPT_NAWS:
case TELOPT_TSPEED:
case TELOPT_LFLOW:
case TELOPT_XDISPLOC:
#ifdef TELOPT_ENVIRON
case TELOPT_NEW_ENVIRON:
#endif
case TELOPT_OLD_ENVIRON:
default:
break;
}
if (changeok) {
set_my_want_state_will(option);
send_will(option, 0);
} else {
will_wont_resp[option]++;
send_wont(option, 0);
}
}
set_my_state_will(option);
}
void
send_wont(int option, int init)
{
if (init) {
if ((will_wont_resp[option] == 0 && my_state_is_wont(option)) ||
my_want_state_is_wont(option))
return;
set_my_want_state_wont(option);
will_wont_resp[option]++;
}
output_data ((const char *)wont, option);
DIAG(TD_OPTIONS, printoption("td: send wont", option));
}
void
dontoption(int 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))) {
switch (option) {
case TELOPT_BINARY:
init_termbuf();
tty_binaryout(0);
set_termbuf();
break;
case TELOPT_ECHO:
{
init_termbuf();
tty_setecho(0);
set_termbuf();
}
break;
case TELOPT_SGA:
set_my_want_state_wont(option);
if (my_state_is_will(option))
send_wont(option, 0);
set_my_state_wont(option);
if (turn_on_sga ^= 1)
send_will(option, 1);
return;
default:
break;
}
set_my_want_state_wont(option);
if (my_state_is_will(option))
send_wont(option, 0);
}
set_my_state_wont(option);
}
#ifdef ENV_HACK
int env_ovar = -1;
int env_ovalue = -1;
#else
# define env_ovar OLD_ENV_VAR
# define env_ovalue OLD_ENV_VALUE
#endif
void
suboption(void)
{
int subchar;
DIAG(TD_OPTIONS, {netflush(); printsub('<', subpointer, SB_LEN()+2);});
subchar = SB_GET();
switch (subchar) {
case TELOPT_TSPEED: {
int xspeed, rspeed;
if (his_state_is_wont(TELOPT_TSPEED))
break;
settimer(tspeedsubopt);
if (SB_EOF() || SB_GET() != TELQUAL_IS)
return;
xspeed = atoi((char *)subpointer);
while (SB_GET() != ',' && !SB_EOF());
if (SB_EOF())
return;
rspeed = atoi((char *)subpointer);
clientstat(TELOPT_TSPEED, xspeed, rspeed);
break;
}
case TELOPT_TTYPE: {
char *p;
if (his_state_is_wont(TELOPT_TTYPE))
break;
settimer(ttypesubopt);
if (SB_EOF() || SB_GET() != TELQUAL_IS) {
return;
}
p = terminaltype;
while ((p < (terminaltype + sizeof terminaltype-1)) &&
!SB_EOF()) {
int c;
c = SB_GET();
if (isupper(c)) {
c = tolower(c);
}
*p++ = c;
}
*p = 0;
break;
}
case TELOPT_NAWS: {
int xwinsize, ywinsize;
if (his_state_is_wont(TELOPT_NAWS))
break;
if (SB_EOF())
return;
xwinsize = SB_GET() << 8;
if (SB_EOF())
return;
xwinsize |= SB_GET();
if (SB_EOF())
return;
ywinsize = SB_GET() << 8;
if (SB_EOF())
return;
ywinsize |= SB_GET();
clientstat(TELOPT_NAWS, xwinsize, ywinsize);
break;
}
case TELOPT_STATUS: {
int mode;
if (SB_EOF())
break;
mode = SB_GET();
switch (mode) {
case TELQUAL_SEND:
if (my_state_is_will(TELOPT_STATUS))
send_status();
break;
case TELQUAL_IS:
break;
default:
break;
}
break;
}
case TELOPT_XDISPLOC: {
if (SB_EOF() || SB_GET() != TELQUAL_IS)
return;
settimer(xdisplocsubopt);
subpointer[SB_LEN()] = '\0';
esetenv("DISPLAY", (char *)subpointer, 1);
break;
}
#ifdef TELOPT_NEW_ENVIRON
case TELOPT_NEW_ENVIRON:
#endif
case TELOPT_OLD_ENVIRON: {
int c;
char *cp, *varp, *valp;
if (SB_EOF())
return;
c = SB_GET();
if (c == TELQUAL_IS) {
if (subchar == TELOPT_OLD_ENVIRON)
settimer(oenvironsubopt);
else
settimer(environsubopt);
} else if (c != TELQUAL_INFO) {
return;
}
#ifdef TELOPT_NEW_ENVIRON
if (subchar == TELOPT_NEW_ENVIRON) {
while (!SB_EOF()) {
c = SB_GET();
if ((c == NEW_ENV_VAR) || (c == ENV_USERVAR))
break;
}
} else
#endif
{
#ifdef ENV_HACK
if (env_ovar < 0) {
int last = -1;
int empty = 0;
int got_var = 0, got_value = 0, got_uservar = 0;
SB_SAVE();
while (!SB_EOF()) {
c = SB_GET();
switch(c) {
case OLD_ENV_VAR:
if (last < 0 || last == OLD_ENV_VAR
|| (empty && (last == OLD_ENV_VALUE)))
goto env_ovar_ok;
got_var++;
last = OLD_ENV_VAR;
break;
case OLD_ENV_VALUE:
if (last < 0 || last == OLD_ENV_VALUE
|| (empty && (last == OLD_ENV_VAR)))
goto env_ovar_wrong;
got_value++;
last = OLD_ENV_VALUE;
break;
case ENV_USERVAR:
if (last != ENV_USERVAR)
got_uservar++;
if (empty) {
if (last == OLD_ENV_VALUE)
goto env_ovar_ok;
if (last == OLD_ENV_VAR)
goto env_ovar_wrong;
}
last = ENV_USERVAR;
break;
case ENV_ESC:
if (!SB_EOF())
c = SB_GET();
default:
empty = 0;
continue;
}
empty = 1;
}
if (empty) {
if (last == OLD_ENV_VALUE)
goto env_ovar_ok;
if (last == OLD_ENV_VAR)
goto env_ovar_wrong;
}
if (got_uservar + got_var == got_value) {
env_ovar_ok:
env_ovar = OLD_ENV_VAR;
env_ovalue = OLD_ENV_VALUE;
} else if (got_uservar + got_value == got_var) {
env_ovar_wrong:
env_ovar = OLD_ENV_VALUE;
env_ovalue = OLD_ENV_VAR;
DIAG(TD_OPTIONS, {
output_data("ENVIRON VALUE and VAR are reversed!\r\n");
});
}
}
SB_RESTORE();
#endif
while (!SB_EOF()) {
c = SB_GET();
if ((c == env_ovar) || (c == ENV_USERVAR))
break;
}
}
if (SB_EOF())
return;
cp = varp = (char *)subpointer;
valp = 0;
while (!SB_EOF()) {
c = SB_GET();
if (subchar == TELOPT_OLD_ENVIRON) {
if (c == env_ovar)
c = NEW_ENV_VAR;
else if (c == env_ovalue)
c = NEW_ENV_VALUE;
}
switch (c) {
case NEW_ENV_VALUE:
*cp = '\0';
cp = valp = (char *)subpointer;
break;
case NEW_ENV_VAR:
case ENV_USERVAR:
*cp = '\0';
if (valp)
esetenv(varp, valp, 1);
else
unsetenv(varp);
cp = varp = (char *)subpointer;
valp = 0;
break;
case ENV_ESC:
if (SB_EOF())
break;
c = SB_GET();
default:
*cp++ = c;
break;
}
}
*cp = '\0';
if (valp)
esetenv(varp, valp, 1);
else
unsetenv(varp);
break;
}
#ifdef AUTHENTICATION
case TELOPT_AUTHENTICATION:
if (SB_EOF())
break;
switch(SB_GET()) {
case TELQUAL_SEND:
case TELQUAL_REPLY:
break;
case TELQUAL_IS:
auth_is(subpointer, SB_LEN());
break;
case TELQUAL_NAME:
auth_name(subpointer, SB_LEN());
break;
}
break;
#endif
#ifdef ENCRYPTION
case TELOPT_ENCRYPT:
if (SB_EOF())
break;
switch(SB_GET()) {
case ENCRYPT_SUPPORT:
encrypt_support(subpointer, SB_LEN());
break;
case ENCRYPT_IS:
encrypt_is(subpointer, SB_LEN());
break;
case ENCRYPT_REPLY:
encrypt_reply(subpointer, SB_LEN());
break;
case ENCRYPT_START:
encrypt_start(subpointer, SB_LEN());
break;
case ENCRYPT_END:
if (require_encryption)
fatal(net, "Output encryption is not possible to turn off");
encrypt_end();
break;
case ENCRYPT_REQSTART:
encrypt_request_start(subpointer, SB_LEN());
break;
case ENCRYPT_REQEND:
if (require_encryption)
fatal(net, "Input encryption is not possible to turn off");
encrypt_request_end();
break;
case ENCRYPT_ENC_KEYID:
encrypt_enc_keyid(subpointer, SB_LEN());
break;
case ENCRYPT_DEC_KEYID:
encrypt_dec_keyid(subpointer, SB_LEN());
break;
default:
break;
}
break;
#endif
default:
break;
}
}
void
doclientstat(void)
{
clientstat(TELOPT_LINEMODE, WILL, 0);
}
#undef ADD
#define ADD(c) *ncp++ = c
#define ADD_DATA(c) { *ncp++ = c; if (c == SE || c == IAC) *ncp++ = c; }
void
send_status(void)
{
unsigned char statusbuf[256];
unsigned char *ncp;
unsigned char i;
ncp = statusbuf;
netflush();
ADD(IAC);
ADD(SB);
ADD(TELOPT_STATUS);
ADD(TELQUAL_IS);
for (i = 0; i < (unsigned char)NTELOPTS; i++) {
if (my_want_state_is_will(i)) {
ADD(WILL);
ADD_DATA(i);
}
if (his_want_state_is_will(i)) {
ADD(DO);
ADD_DATA(i);
}
}
if (his_want_state_is_will(TELOPT_LFLOW)) {
ADD(SB);
ADD(TELOPT_LFLOW);
if (flowmode) {
ADD(LFLOW_ON);
} else {
ADD(LFLOW_OFF);
}
ADD(SE);
if (restartany >= 0) {
ADD(SB);
ADD(TELOPT_LFLOW);
if (restartany) {
ADD(LFLOW_RESTART_ANY);
} else {
ADD(LFLOW_RESTART_XON);
}
ADD(SE);
}
}
ADD(IAC);
ADD(SE);
writenet(statusbuf, ncp - statusbuf);
netflush();
DIAG(TD_OPTIONS,
{printsub('>', statusbuf, ncp - statusbuf); netflush();});
}