Select Git revision
TELNET.pmod
-
Henrik (Grubba) Grubbström authored
Strengthened the type of set_nonblocking() somewhat. Splitted the implementation of Stdio.TELNET.Readline into two parts. Stdio.TELNET.Readline now utilizes subtyped objects to avoid callback confusion. Added some Autodoc mk II markup. Rev: lib/modules/Protocols.pmod/TELNET.pmod:1.33
Henrik (Grubba) Grubbström authoredStrengthened the type of set_nonblocking() somewhat. Splitted the implementation of Stdio.TELNET.Readline into two parts. Stdio.TELNET.Readline now utilizes subtyped objects to avoid callback confusion. Added some Autodoc mk II markup. Rev: lib/modules/Protocols.pmod/TELNET.pmod:1.33
TELNET.pmod 33.24 KiB
//
// $Id: TELNET.pmod,v 1.33 2008/12/08 19:52:15 grubba Exp $
//
// The TELNET protocol as described by RFC 764 and others.
//
// Henrik Grubbström <grubba@roxen.com> 1998-04-04
//
#pike __REAL_VERSION__
// #define TELNET_DEBUG
#ifdef TELNET_DEBUG
#define DWRITE(X) werror(X)
#else
#define DWRITE(X)
#endif /* TELNET_DEBUG */
//! Implements TELNET as described by RFC 764 and RFC 854
//!
//! Also implements the Q method of TELNET option negotiation
//! as specified by RFC 1143.
/* Extract from RFC 1143:
*
* EXAMPLE STATE MACHINE FOR THE Q METHOD OF IMPLEMENTING
* TELNET OPTION NEGOTIATION
*
* There are two sides, we (us) and he (him). We keep four variables:
*
* us: state of option on our side (NO/WANTNO/WANTYES/YES)
* usq: a queue bit (EMPTY/OPPOSITE) if us is WANTNO or WANTYES
* him: state of option on his side
* himq: a queue bit if him is WANTNO or WANTYES
*
* An option is enabled if and only if its state is YES. Note that
* us/usq and him/himq could be combined into two six-choice states.
*
* "Error" below means that producing diagnostic information may be a
* good idea, though it isn't required.
*
* Upon receipt of WILL, we choose based upon him and himq:
* NO If we agree that he should enable, him=YES, send
* DO; otherwise, send DONT.
* YES Ignore.
* WANTNO EMPTY Error: DONT answered by WILL. him=NO.
* OPPOSITE Error: DONT answered by WILL. him=YES*,
* himq=EMPTY.
* WANTYES EMPTY him=YES.
* OPPOSITE him=WANTNO, himq=EMPTY, send DONT.
*
* * This behavior is debatable; DONT will never be answered by WILL
* over a reliable connection between TELNETs compliant with this
* RFC, so this was chosen (1) not to generate further messages,
* because if we know we're dealing with a noncompliant TELNET we
* shouldn't trust it to be sensible; (2) to empty the queue
* sensibly.
*
* Upon receipt of WONT, we choose based upon him and himq:
* NO Ignore.
* YES him=NO, send DONT.
* WANTNO EMPTY him=NO.
* OPPOSITE him=WANTYES, himq=NONE, send DO.
* WANTYES EMPTY him=NO.*
* OPPOSITE him=NO, himq=NONE.**
*
* * Here is the only spot a length-two queue could be useful; after
* a WILL negotiation was refused, a queue of WONT WILL would mean
* to request the option again. This seems of too little utility
* and too much potential waste; there is little chance that the
* other side will change its mind immediately.
*
* ** Here we don't have to generate another request because we've
* been "refused into" the correct state anyway.
*
* If we decide to ask him to enable:
* NO him=WANTYES, send DO.
* YES Error: Already enabled.
* WANTNO EMPTY If we are queueing requests, himq=OPPOSITE;
* otherwise, Error: Cannot initiate new request
* in the middle of negotiation.
* OPPOSITE Error: Already queued an enable request.
* WANTYES EMPTY Error: Already negotiating for enable.
* OPPOSITE himq=EMPTY.
*
* If we decide to ask him to disable:
* NO Error: Already disabled.
* YES him=WANTNO, send DONT.
* WANTNO EMPTY Error: Already negotiating for disable.
* OPPOSITE himq=EMPTY.
* WANTYES EMPTY If we are queueing requests, himq=OPPOSITE;
* otherwise, Error: Cannot initiate new request
* in the middle of negotiation.
* OPPOSITE Error: Already queued a disable request.
*
* We handle the option on our side by the same procedures, with DO-
* WILL, DONT-WONT, him-us, himq-usq swapped.
*/
//! Table of IAC-codes.
class TelnetCodes {
constant IAC = 255; //! interpret as command.
constant DONT = 254; //! you are not to use option
constant DO = 253; //! please, you use option
constant WONT = 252; //! I won't use option
constant WILL = 251; //! I will use option
constant SB = 250; //! interpret as subnegotiation
constant GA = 249; //! you may reverse the line
constant EL = 248; //! erase the current line
constant EC = 247; //! erase the current character
constant AYT = 246; //! are you there
constant AO = 245; //! abort output--but let prog finish
constant IP = 244; //! interrupt process--permanently
constant BREAK = 243; //! break
constant DM = 242; //! data mark--for connect. cleaning
constant NOP = 241; //! nop
constant SE = 240; //! end sub negotiation
constant EOR = 239; //! end of record (transparent mode)
constant ABORT = 238; //! Abort process
constant SUSP = 237; //! Suspend process
constant xEOF = 236; //! End of file: EOF is already used...
constant SYNCH = 242; //! for telfunc calls
};
inherit TelnetCodes;
//! Table of TELNET options.
class Telopts {
constant TELOPT_BINARY = 0; //! 8-bit data path
constant TELOPT_ECHO = 1; //! echo
constant TELOPT_RCP = 2; //! prepare to reconnect
constant TELOPT_SGA = 3; //! suppress go ahead
constant TELOPT_NAMS = 4; //! approximate message size
constant TELOPT_STATUS = 5; //! give status
constant TELOPT_TM = 6; //! timing mark
constant TELOPT_RCTE = 7; //! remote controlled transmission and echo
constant TELOPT_NAOL = 8; //! negotiate about output line width
constant TELOPT_NAOP = 9; //! negotiate about output page size
constant TELOPT_NAOCRD = 10; //! negotiate about CR disposition
constant TELOPT_NAOHTS = 11; //! negotiate about horizontal tabstops
constant TELOPT_NAOHTD = 12; //! negotiate about horizontal tab disposition
constant TELOPT_NAOFFD = 13; //! negotiate about formfeed disposition
constant TELOPT_NAOVTS = 14; //! negotiate about vertical tab stops
constant TELOPT_NAOVTD = 15; //! negotiate about vertical tab disposition
constant TELOPT_NAOLFD = 16; //! negotiate about output LF disposition
constant TELOPT_XASCII = 17; //! extended ascic character set
constant TELOPT_LOGOUT = 18; //! force logout
constant TELOPT_BM = 19; //! byte macro
constant TELOPT_DET = 20; //! data entry terminal
constant TELOPT_SUPDUP = 21; //! supdup protocol
constant TELOPT_SUPDUPOUTPUT = 22; //! supdup output
constant TELOPT_SNDLOC = 23; //! send location
constant TELOPT_TTYPE = 24; //! terminal type
constant TELOPT_EOR = 25; //! end or record
constant TELOPT_TUID = 26; //! TACACS user identification
constant TELOPT_OUTMRK = 27; //! output marking
constant TELOPT_TTYLOC = 28; //! terminal location number
constant TELOPT_3270REGIME = 29; //! 3270 regime
constant TELOPT_X3PAD = 30; //! X.3 PAD
constant TELOPT_NAWS = 31; //! window size
constant TELOPT_TSPEED = 32; //! terminal speed
constant TELOPT_LFLOW = 33; //! remote flow control
constant TELOPT_LINEMODE = 34; //! Linemode option
constant TELOPT_XDISPLOC = 35; //! X Display Location
constant TELOPT_OLD_ENVIRON = 36; //! Old - Environment variables
constant TELOPT_AUTHENTICATION = 37; //! Authenticate
constant TELOPT_ENCRYPT = 38; //! Encryption option
constant TELOPT_NEW_ENVIRON = 39; //! New - Environment variables
constant TELOPT_EXOPL = 255; //! extended-options-list
};
inherit Telopts;
mapping lookup_telopt=mkmapping(values(Telopts()),indices(Telopts()));
mapping lookup_telnetcodes=mkmapping(values(TelnetCodes()),indices(TelnetCodes()));
/* sub-option qualifiers */
constant TELQUAL_IS= 0; //! option is...
constant TELQUAL_SEND= 1; //! send option
constant TELQUAL_INFO= 2; //! ENVIRON: informational version of IS
constant TELQUAL_REPLY= 2; //! AUTHENTICATION: client version of IS
constant TELQUAL_NAME= 3; //! AUTHENTICATION: client version of IS
constant LFLOW_OFF= 0; //! Disable remote flow control
constant LFLOW_ON= 1; //! Enable remote flow control
constant LFLOW_RESTART_ANY= 2; //! Restart output on any char
constant LFLOW_RESTART_XON= 3; //! Restart output only on XON
constant LM_MODE = 1;
constant LM_FORWARDMASK = 2;
constant LM_SLC = 3;
constant MODE_EDIT = 0x01;
constant MODE_TRAPSIG = 0x02;
constant MODE_ACK = 0x04;
constant MODE_SOFT_TAB = 0x08;
constant MODE_LIT_ECHO = 0x10;
constant MODE_MASK = 0x1f;
/* Not part of protocol, but needed to simplify things... */
constant MODE_FLOW = 0x0100;
constant MODE_ECHO = 0x0200;
constant MODE_INBIN = 0x0400;
constant MODE_OUTBIN = 0x0800;
constant MODE_FORCE = 0x1000;
constant SLC_SYNCH= 1;
constant SLC_BRK= 2;
constant SLC_IP= 3;
constant SLC_AO= 4;
constant SLC_AYT= 5;
constant SLC_EOR= 6;
constant SLC_ABORT= 7;
constant SLC_EOF= 8;
constant SLC_SUSP= 9;
constant SLC_EC= 10;
constant SLC_EL= 11;
constant SLC_EW= 12;
constant SLC_RP= 13;
constant SLC_LNEXT= 14;
constant SLC_XON= 15;
constant SLC_XOFF= 16;
constant SLC_FORW1= 17;
constant SLC_FORW2= 18;
constant NSLC= 18;
constant SLC_NOSUPPORT= 0;
constant SLC_CANTCHANGE= 1;
constant SLC_VARIABLE= 2;
constant SLC_DEFAULT= 3;
constant SLC_LEVELBITS= 0x03;
constant SLC_FUNC= 0;
constant SLC_FLAGS= 1;
constant SLC_VALUE= 2;
constant SLC_ACK= 0x80;
constant SLC_FLUSHIN= 0x40;
constant SLC_FLUSHOUT= 0x20;
constant OLD_ENV_VAR= 1;
constant OLD_ENV_VALUE= 0;
constant NEW_ENV_VAR= 0;
constant NEW_ENV_VALUE= 1;
constant ENV_ESC= 2;
constant ENV_USERVAR= 3;
/*
* AUTHENTICATION suboptions
*/
/*
* Who is authenticating who ...
*/
constant AUTH_WHO_CLIENT= 0; //! Client authenticating server
constant AUTH_WHO_SERVER= 1; //! Server authenticating client
constant AUTH_WHO_MASK= 1;
/*
* amount of authentication done
*/
constant AUTH_HOW_ONE_WAY= 0;
constant AUTH_HOW_MUTUAL= 2;
constant AUTH_HOW_MASK= 2;
constant AUTHTYPE_NULL= 0;
constant AUTHTYPE_KERBEROS_V4= 1;
constant AUTHTYPE_KERBEROS_V5= 2;
constant AUTHTYPE_SPX= 3;
constant AUTHTYPE_MINK= 4;
constant AUTHTYPE_CNT= 5;
constant AUTHTYPE_TEST= 99;
/*
* ENCRYPTion suboptions
*/
constant ENCRYPT_IS= 0; /* I pick encryption type ... */
constant ENCRYPT_SUPPORT= 1; /* I support encryption types ... */
constant ENCRYPT_REPLY= 2; /* Initial setup response */
constant ENCRYPT_START= 3; /* Am starting to send encrypted */
constant ENCRYPT_END= 4; /* Am ending encrypted */
constant ENCRYPT_REQSTART= 5; /* Request you start encrypting */
constant ENCRYPT_REQEND= 6; /* Request you send encrypting */
constant ENCRYPT_ENC_KEYID= 7;
constant ENCRYPT_DEC_KEYID= 8;
constant ENCRYPT_CNT= 9;
constant ENCTYPE_ANY= 0;
constant ENCTYPE_DES_CFB64= 1;
constant ENCTYPE_DES_OFB64= 2;
constant ENCTYPE_CNT= 3;
#define C(X) sprintf("%c",X)
#define C2(X,Y) sprintf("%c%c",X,Y)
#define C3(X,Y,Z) sprintf("%c%c%c",X,Y,Z)
//! Implementation of the TELNET protocol.
class protocol
{
//! The connection.
protected object fd;
//! Mapping containing extra callbacks.
protected mapping cb;
//! Value to send to the callbacks.
protected mixed id;
//! Write callback.
protected function(mixed|void:string) write_cb;
//! Read callback.
protected function(mixed,string:void) read_cb;
//! Close callback.
protected function(mixed|void:void) close_cb;
// See RFC 1143 for the use and meaning of these.
protected constant UNKNOWN = 0;
protected constant YES = 1;
protected constant NO = 2;
protected constant WANT = 4;
protected constant OPPOSITE = 8;
//! Negotiation states of all WILL/WON'T options.
//! See RFC 1143 for a description of the states.
protected array(int) remote_options = allocate(256,NO);
protected array(int) local_options = allocate(256,NO);
//! Data queued to be sent.
protected string to_send = "";
//! Indicates that connection should be closed
protected int done;
//! Tells if we have set the nonblocking write callback or not.
protected int nonblocking_write;
//! Turns on the write callback if apropriate.
protected void enable_write()
{
DWRITE("TELNET: enable_write()\n");
if (!nonblocking_write && (write_cb || sizeof(to_send) || done)) {
DWRITE("TELNET: enable_write(): Enabling non-blocking() in enable_write()\n");
fd->set_nonblocking(got_data, send_data, close_cb, got_oob);
nonblocking_write = 1;
} else {
DWRITE("TELNET: enable_write(): Calling send_data()\n");
send_data();
}
}
//! Turns off the write callback if apropriate.
protected void disable_write()
{
DWRITE("TELNET: disable_write()\n");
if (!write_cb && !sizeof(to_send) && !done && nonblocking_write) {
DWRITE("TELNET: disable_write(): Calling fd->set_nonblocking()\n");
fd->set_nonblocking(got_data, 0, close_cb, got_oob);
nonblocking_write = 0;
}
}
//! Queues data to be sent to the other end of the connection.
//!
//! @param s
//! String to send.
void write(string s)
{
DWRITE(sprintf("TELNET: writing :%O\n",s));
to_send += replace(s, C(IAC), C2(IAC,IAC));
enable_write();
}
//! Queues raw data to be sent to the other end of the connection.
//!
//! @param s
//! String with raw telnet data to send.
void write_raw(string s)
{
to_send += s;
enable_write();
}
//! Closes the connetion neatly
void close()
{
done=1;
enable_write();
}
//! Callback called when it is clear to send data over the connection.
//! This function does the actual sending.
protected void send_data()
{
DWRITE("TELNET: Entering send_data()\n");
if (!sizeof(to_send)) {
DWRITE("TELNET: Nothing to send!\n");
if (write_cb) {
DWRITE("TELNET: We have a write callback!\n");
// FIXME: What if the callback calls something that calls
// write() or write_raw() above?
if(!(to_send = write_cb(id)))
{
DWRITE("TELNET: Write callback did not write anything!\n");
done=1;
to_send="";
}
}
}
if (sizeof(to_send))
{
DWRITE(sprintf("TELNET: We now have data to send! (%d bytes)\n",
sizeof(to_send)));
if (to_send[0] == 242) {
// DataMark needs extra quoting... Stupid.
DWRITE("TELNET: Found datamark @ offset 0!\n");
to_send = C2(IAC,NOP) + to_send;
}
int n = fd->write(to_send);
to_send = to_send[n..];
} else if(done) {
DWRITE("TELNET: Closing fd!\n");
fd->close();
fd=0;
nonblocking_write=0;
return;
}
disable_write();
}
//! Sends a TELNET synch command.
void send_synch()
{
// Clear send-queue.
to_send = "";
if (fd->write_oob) {
fd->write_oob(C(IAC));
fd->write(C(DM));
} else {
// Fallback...
fd->write(C2(IAC,DM));
}
}
//! @decl void send_DO(int option)
//!
//! Starts negotiation to enable a TELNET option.
//!
//! @param option
//! The option to enable.
//! @decl void send_DONT(int option)
//!
//! Starts negotiation to disable a TELNET option.
//!
//! @param option
//! The option to disable.
#define CONTROL(OPTIONS,DO,DONT,WILL,WONT,YES,NO) \
void send_##DO(int option) \
{ \
if ((option < 0) || (option > 255)) { \
error( "Bad TELNET option #%d\n", option); \
} \
DWRITE(sprintf("TELNET: send_" #DO "(%s) state is %d\n", \
lookup_telopt[option] || (string)option, \
OPTIONS##_options[option])); \
switch(OPTIONS##_options[option]) { \
case NO: \
case UNKNOWN: \
OPTIONS##_options[option]= WANT | YES; \
DWRITE(sprintf("TELNET: => " #DO " %s\n", \
lookup_telopt[option] || (string)option)); \
to_send += sprintf("%c%c%c",IAC,DO,option); \
break; \
\
case YES: /* Already enabled */ \
case WANT | YES: /* Will be enabled soon */ \
case WANT | NO | OPPOSITE: /* Already queued an enable request. */ \
break; \
\
case WANT | NO: \
case WANT | YES | OPPOSITE: \
OPTIONS##_options[option]^=OPPOSITE; \
break; \
\
default: \
error("TELNET: Strange remote_options[%d]=%d\n", \
option, remote_options[option]); \
/* ERROR: weird state! */ \
break; \
} \
enable_write(); \
}
//! @ignore
CONTROL(remote,DO,DONT,WILL,WONT,YES,NO)
CONTROL(remote,DONT,DO,WONT,WILL,NO,YES)
CONTROL(local,WILL,WONT,DO,DONT,YES,NO)
CONTROL(local,WONT,WILL,DONT,DO,NO,YES)
//! @endignore
void remote_option_callback(int opt, int onoff)
{
call_callback("Remote "+ lookup_telopt[opt], onoff);
call_callback("Remote option", onoff);
}
void local_option_callback(int opt, int onoff)
{
call_callback("Local "+ lookup_telopt[opt], onoff);
call_callback("Local option", onoff);
}
void set_remote_option(int opt, int state)
{
remote_options[opt]=state;
switch(state)
{
case YES: remote_option_callback(opt,1); break;
case NO: remote_option_callback(opt,0); break;
}
}
void set_local_option(int opt, int state)
{
local_options[opt]=state;
switch(state)
{
case YES: local_option_callback(opt,1); break;
case NO: local_option_callback(opt,0); break;
}
}
int WILL_callback(int opt)
{
return call_callback(WILL,opt);
}
int DO_callback(int opt)
{
return call_callback(DO,opt);
}
void send_SB(int|string ... args)
{
to_send+=
C2(IAC,SB)+
replace(Array.map(args,lambda(int|string s)
{
return sprintf(intp(s)?"%c":"%s",s);
})*"",C(IAC),C2(IAC,IAC))+
C2(IAC,SE);
enable_write();
}
//! Indicates whether we are in synch-mode or not.
protected int synch = 0;
protected mixed call_callback(mixed what, mixed ... args)
{
if(mixed cb=cb[what])
{
mixed err=catch {
return cb(@args);
};
if(err)
{
throw(err);
}else{
if(!this) return 0;
throw(err);
}
}
switch(what)
{
case BREAK:
destruct(this);
throw(0);
break;
case AYT:
to_send += C2(IAC,NOP); // NOP
enable_write();
break;
}
}
//! Callback called when Out-Of-Band data has been received.
//!
//! @param ignored
//! The id from the connection.
//!
//! @param s
//! The Out-Of-Band data received.
//!
protected void got_oob(mixed ignored, string s)
{
#ifdef TELNET_DEBUG
werror("TELNET: got_oob(\"%s\")\n",Array.map(values(s),lambda(int s) {
switch(s)
{
case ' '..'z':
return sprintf("%c",s);
default:
return sprintf("\\0x%02x",s);
}
})*"");
#endif
synch = synch || (s == C(IAC));
call_callback("URG",id,s);
}
//! Calls @[read_cb()].
//!
//! Specifically provided for overloading
//!
protected void call_read_cb(string data)
{
DWRITE("TELNET: Fnurgel!\n");
if(read_cb && sizeof(data)) read_cb(id,data);
}
//! Callback called when normal data has been received.
//! This function also does most of the TELNET protocol parsing.
//!
//! @param ignored
//! The id from the connection.
//! @param s
//! The received data.
//!
protected void got_data(mixed ignored, string line)
{
#ifdef TELNET_DEBUG
werror("TELNET: got_data(%O)\n",line);
#endif
if (sizeof(line) && (line[0] == DM)) {
DWRITE("TELNET: Data Mark\n");
// Data Mark handing.
line = line[1..];
synch = 0;
}
if (has_value(line, C(IAC))) {
array a = line / C(IAC);
string parsed_line = a[0];
int i;
for (i=1; i < sizeof(a); i++) {
string part = a[i];
if (sizeof(part)) {
DWRITE(sprintf("TELNET: Code %s\n",
lookup_telnetcodes[part[0]] || (string)part[0]));
switch (part[0]) {
default:
call_callback(part[0]);
a[i] = a[i][1..];
break;
// FIXME, find true end of subnegotiation!
case SB:
call_callback(SB,part[1..]);
a[i] = "";
break;
case EC: // Erase Character
for (int j=i; j--;) {
if (sizeof(a[j])) {
a[j] = a[j][..<1];
break;
}
}
a[i] = a[i][1..];
break;
#if 0
case EL: // Erase Line
for (int j=0; j < i; j++) {
a[j] = "";
}
a[i] = a[i][1..];
break;
#endif
#define HANDLE(OPTIONS,WILL,WONT,DO,DONT) \
case WILL: \
{ \
int option = a[i][1]; \
int state = OPTIONS##_options[option]; \
a[i] = a[i][2..]; \
\
DWRITE(sprintf(#WILL " %s, state 0x%04x\n", \
lookup_telopt[option], state)); \
\
switch(state) { \
case NO: \
case UNKNOWN: \
if (WILL##_callback(option)) \
{ \
/* Agree about enabling */ \
state=YES; \
send_##DO(option); \
} else { \
state=NO; \
send_##DONT(option); \
} \
break; \
\
case YES: \
/* Ignore */ \
break; \
\
case WANT | NO: \
state=NO; \
break; \
\
case WANT | YES: \
case WANT | NO | OPPOSITE: \
state=YES; \
break; \
\
case WANT | YES | OPPOSITE: \
state=WANT | NO; \
send_##DONT(option); \
break; \
\
default: \
error("TELNET: Strange remote_options[%d]=%d\n", \
option,remote_options[option]); \
/* Weird state ! */ \
} \
DWRITE(sprintf("TELNET: => " #WILL " %s, state 0x%04x\n", \
lookup_telopt[option], state)); \
set_##OPTIONS##_option(option,state); \
break; \
} \
\
case WONT: \
{ \
int option = a[i][1]; \
int state = OPTIONS##_options[option]; \
a[i] = a[i][2..]; \
\
DWRITE(sprintf(#WONT " %s, state 0x%04x\n", \
lookup_telopt[option], state)); \
\
switch(state) \
{ \
case UNKNOWN: \
case NO: \
state=NO; \
break; \
\
case YES: \
state=NO; \
send_##DONT(option); \
break; \
\
case WANT | NO: \
state=NO; \
break; \
\
case WANT | NO | OPPOSITE: \
state=WANT | YES; \
send_##DO(option); \
break; \
\
case WANT | YES: \
case WANT | YES | OPPOSITE: \
state=NO; \
break; \
\
default: \
error("TELNET: Strange remote_options[%d]=%d\n", \
option, remote_options[option]); \
/* Weird state */ \
} \
\
DWRITE(sprintf("TELNET: => " #WONT " %s, state 0x%04x\n", \
lookup_telopt[option], state)); \
set_##OPTIONS##_option(option,state); \
} \
break
HANDLE(remote,WILL,WONT,DO,DONT);
HANDLE(local,DO,DONT,WILL,WONT);
case DM: // Data Mark
if (synch) {
for (int j=0; j < i; j++) {
a[j] = "";
}
}
a[i] = a[i][1..];
synch = 0;
break;
}
} else {
// IAC IAC => IAC
a[i] = C(IAC);
i++;
}
}
// werror("%O\n",a);
line = a * "";
}
if ((!synch)) {
#ifdef TELNET_DEBUG
werror("TELNET: calling read_callback(X,%O)\n", line);
#endif
call_read_cb(line);
}
enable_write();
}
void set_read_callback(function(mixed,string:void) r_cb)
{
read_cb = r_cb;
fd->set_read_callback(got_data);
fd->set_read_oob_callback(got_oob);
}
function(mixed,string:void) query_read_callback()
{
return read_cb;
}
//! Sets the callback to be called when it is clear to send.
//!
//! @param w_cb
//! The new write callback.
void set_write_callback(function(mixed|void:string) w_cb)
{
DWRITE(sprintf("TELNET: set_write_callback(%O)\n", w_cb));
write_cb = w_cb;
if (w_cb) {
enable_write();
} else {
disable_write();
}
}
function(mixed|void:string) query_write_callback()
{
return write_cb;
}
void set_close_callback(function(mixed|void:void) c_cb)
{
close_cb = c_cb;
fd->set_close_callback(close_cb);
}
function(mixed|void:void) query_close_callback()
{
return close_cb;
}
//! Called when the initial setup is done.
//!
//! Created specifically for overloading
//!
void setup()
{
}
//! Creates a TELNET protocol handler, and sets its callbacks.
//!
//! @param f
//! File to use for the connection.
//! @param r_cb
//! Function to call when data has arrived.
//! @param w_cb
//! Function to call when the send buffer is empty.
//! @param c_cb
//! Function to call when the connection is closed.
//! @param callbacks
//! Mapping with callbacks for the various TELNET commands.
//! @param new_id
//! Value to send to the various callbacks.
//!
void create(object f,
function(mixed,string:void) r_cb,
function(mixed|void:string) w_cb,
function(mixed|void:void) c_cb,
mapping callbacks, mixed|void new_id)
{
fd = f;
cb = callbacks;
read_cb = r_cb;
write_cb = w_cb;
close_cb = c_cb;
id = new_id;
fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob);
nonblocking_write = !!w_cb;
setup();
}
//! Change the asynchronous I/O callbacks.
//!
//! @param r_cb
//! Function to call when data has arrived.
//! @param w_cb
//! Function to call when the send buffer is empty.
//! @param c_cb
//! Function to call when the connection is closed.
//!
//! @seealso
//! @[create()]
void set_nonblocking(function(mixed,string:void) r_cb,
function(mixed|void:string) w_cb,
function(mixed|void:void) c_cb)
{
read_cb = r_cb;
write_cb = w_cb;
close_cb = c_cb;
DWRITE(sprintf("TELNET: set_nonblocking(): "
"Calling fd->set_nonblocking() %O %O\n",
w_cb, w_cb || send_data));
fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob);
nonblocking_write = !!w_cb;
}
void set_blocking()
{
read_cb = 0;
write_cb = 0;
close_cb = 0;
fd->set_blocking();
nonblocking_write = 0;
}
string query_address(int|void remote)
{
return fd->query_address(remote);
}
}
//! Line-oriented TELNET protocol handler.
class LineMode
{
//! Based on the generic TELNET protocol handler.
inherit protocol;
protected string line_buffer="";
protected void call_read_cb(string data)
{
if(read_cb)
{
DWRITE(sprintf("TELNET: Line callback... %O\n",data));
data=replace(data,
({"\r\n", "\n", "\r", "\r\0"}),
({"\r", "\r", "\r", "\r",}));
line_buffer+=data;
array(string) tmp=line_buffer/"\r";
line_buffer=tmp[-1];
for(int e=0;e<sizeof(tmp)-1;e++) read_cb(id,tmp[e]+"\n");
}
}
//! Perform the initial TELNET handshaking for LINEMODE.
void setup()
{
send_DO(TELOPT_BINARY);
send_DO(TELOPT_SGA);
send_DONT(TELOPT_LINEMODE);
send_WILL(TELOPT_SGA);
}
}
// Note: We hide the implementation detail that Protocols.TELNET.Readline
// is implemented in two classes from the documentation.
//! @class Readline
//! Line-oriented TELNET protocol handler with @[Stdio.Readline] support.
//! @ignore
static class Low_Readline
{
//! @endignore
//! Based on the Line-oriented TELNET protocol handler.
inherit LineMode;
object readline;
string term;
int width=80;
int height=24;
int icanon;
mapping tcgetattr()
{
return ([
"rows":height,
"columns":width,
"ECHO":local_options[TELOPT_ECHO],
"ICANON": icanon,
]);
}
protected void call_read_cb(string data)
{
if(read_cb)
{
if(!icanon)
{
if(sizeof(data)) read_cb(id,data);
}else{
DWRITE(sprintf("TELNET: Line callback... %O\n",data));
data=replace(data,
({"\r\n","\r\n","\r","\r\0"}),
({"\r", "\r", "\r","\r",}));
line_buffer+=data;
array(string) tmp=line_buffer/"\r";
line_buffer=tmp[-1];
for(int e=0;e<sizeof(tmp)-1;e++)
{
read_cb(id,tmp[e]+"\n");
write(prompt);
}
}
}
}
int tcsetattr(mapping options)
{
( options->ECHO ? send_WONT : send_WILL )(TELOPT_ECHO);
( (icanon=options->ICANON) ? send_DONT : send_DO )(TELOPT_LINEMODE);
}
void set_secret(int onoff)
{
DWRITE(sprintf("TELNET: set_secret(%d)\n", onoff));
if(readline)
{
DWRITE("TELNET: setting secret via Stdio.Readline\n");
readline->set_echo(!onoff);
}else{
DWRITE("TELNET: setting secret via telnet option\n");
( onoff ? send_WILL : send_WONT )(TELOPT_ECHO);
}
DWRITE(sprintf("TELNET: LOCAL TELNET ECHO STATE IS %O\n", local_options[TELOPT_ECHO]));
DWRITE(sprintf("TELNET: REMOTE TELNET ECHO STATE IS %O\n", remote_options[TELOPT_ECHO]));
}
protected function(mixed,string:void) read_cb2;
protected function(mixed|void:string) write_cb2;
protected function(mixed:void) close_cb2;
protected void readline_callback(string data)
{
read_cb2(id,data+"\n");
}
protected void readline_write_callback()
{
string data = write_cb2(id);
if (!data) {
close();
} else if (sizeof(data)) {
readline->write(data);
}
}
protected void readline_close_callback(string data)
{
close_cb2(id);
}
protected string prompt="";
protected mixed call_callback(mixed what, mixed ... args)
{
switch(what)
{
case SB:
string data=args[0];
DWRITE(sprintf("TELNET: SB callback %O\n",data));
switch(data[0])
{
case TELOPT_TTYPE:
if(data[1]==TELQUAL_IS)
{
if(!readline)
{
term=data[2..];
DWRITE(sprintf("TELNET.Readline: Enabling READLINE, term=%s\n",
term));
// This fix for the secret mode might not
// be the best way to do things, but it seems to
// work.
int secret_mode = local_options[TELOPT_ECHO] != NO;
set_secret(0);
readline=Stdio.Readline(this_program::this, lower_case(term));
set_secret(secret_mode);
readline->get_input_controller()->set_close_callback(readline_close_callback);
DWRITE("TELNET.Readline: calling readline->set_nonblocking()\n");
readline->set_nonblocking(readline_callback);
DWRITE(sprintf("TELNET: Setting the readline prompt to %O\n", prompt));
readline->set_prompt(prompt);
DWRITE(sprintf("TELNET: LOCAL TELNET ECHO STATE IS %O\n", local_options[TELOPT_ECHO]));
DWRITE(sprintf("TELNET: REMOTE TELNET ECHO STATE IS %O\n", remote_options[TELOPT_ECHO]));
readline->enable_history(200);
/* enable data processing */
}
}
break;
case TELOPT_NAWS:
if(sscanf(data[1..],"%2c%2c",width,height)==2)
if(readline)
readline->redisplay();
break;
}
}
}
void remote_option_callback(int opt, int onoff)
{
switch(opt)
{
case TELOPT_TTYPE:
if(onoff)
{
send_SB(TELOPT_TTYPE,TELQUAL_SEND);
}else{
werror("Revert to line mode not yet operational.\n");
/* revert to stupid line mode */
}
}
::remote_option_callback(opt,onoff);
}
void setup()
{
send_DO(TELOPT_SGA);
send_DO(TELOPT_BINARY);
send_DO(TELOPT_NAWS);
send_DO(TELOPT_TTYPE);
/* disable data processing */
}
//! Write a message.
void message(string s,void|int word_wrap)
{
if(readline)
{
readline->write(s,word_wrap);
}else{
write(replace(s,"\n","\r\n"));
}
}
//! Set the readline prompt.
void set_prompt(string s)
{
if(readline)
{
DWRITE(sprintf("TELNET: Setting readline prompt to %O\n", s));
prompt=s;
readline->set_prompt(prompt);
}else{
if(prompt!=s)
{
DWRITE(sprintf("TELNET: Setting prompt without readline to %O\n", s));
// What is the point of this if-statement?
// I think that write(s) should be called every time!
// Agehall 2004-04-25
//
// No idea; it looks like it checks if the old prompt
// is a prefix of the new prompt. It also probably ought
// to use message() rather than write().
// Grubba 2008-12-04
if(s[..sizeof(prompt)-1]==prompt)
write(s);
prompt=s;
}
}
}
//! Close the connection.
void close()
{
readline->set_blocking();
readline=0;
::close();
}
//! @ignore
}
class Readline
{
// Local inherit, so that we won't disturb the low-level functions
// used by Stdio.Readline, when we define the same for high-level use.
local inherit Low_Readline;
//! @endignore
//! Change the asynchronous I/O callbacks.
//!
//! @param r_cb
//! Function to call when data has arrived.
//! @param w_cb
//! Function to call when the send buffer is empty.
//! @param c_cb
//! Function to call when the connection is closed.
//!
//! @seealso
//! @[create()]
void set_nonblocking(function(mixed,string:void) r_cb,
function(mixed|void:string) w_cb,
function(mixed|void:void) c_cb)
{
read_cb2 = r_cb;
write_cb2 = w_cb;
close_cb2 = c_cb;
if (!readline) {
::set_nonblocking(r_cb, w_cb, c_cb);
} else {
::set_write_callback(w_cb);
}
}
void set_read_callback(function(mixed,string:void) r_cb)
{
read_cb2 = r_cb;
if (!readline) ::set_read_callback(read_cb2);
}
function(mixed,string:void) query_read_callback()
{
return read_cb2;
}
void set_write_callback(function(mixed|void:string) w_cb)
{
write_cb2 = w_cb;
::set_write_callback(write_cb2);
}
function(mixed|void:string) query_write_callback()
{
return write_cb2;
}
void set_close_callback(function(mixed|void:void) c_cb)
{
close_cb2 = c_cb;
if (!readline) ::set_close_callback(close_cb2);
}
function(mixed|void:void) query_close_callback()
{
return close_cb2;
}
void set_blocking()
{
read_cb2 = 0;
write_cb2 = 0;
close_cb2 = 0;
if (!readline) {
::set_blocking();
}
}
//! Queues data to be sent to the other end of the connection.
//!
//! @param s
//! String to send.
void write(string s)
{
if (readline) readline->write(s);
else ::write(s);
}
//! @ignore
}
//! @endignore
//! @endclass