// // $Id: TELNET.pmod,v 1.1 1998/04/04 14:23:18 grubba Exp $ // // The TELNET protocol as described by RFC 764 and others. // // Henrik Grubbstr�m <grubba@idonex.se> 1998-04-04 // #ifdef TELNET_DEBUG #define DWRITE(X) werror(X) #else #define DWRITE(X) #endif /* TELNET_DEBUG */ //. o protocol //. Implementation of the TELNET protocol. class protocol { //. + fd //. The connection. static object fd; //. + cb //. Mapping containing extra callbacks. static private mapping cb; //. + id //. Value to send to the callbacks. static private mixed id; //. + write_cb //. Write callback. static private function(mixed|void:string) write_cb; //. + read_cb //. Read callback. static private function(mixed, string:void) read_cb; //. + close_cb //. Close callback. static private function(mixed|void:void) close_cb; //. + TelnetCodes //. Mapping of telnet codes to their abreviations. static private constant TelnetCodes = ([ 236:"EOF", // End Of File 237:"SUSP", // Suspend Process 238:"ABORT", // Abort Process 239:"EOR", // End Of Record // The following ones are specified in RFC 959: 240:"SE", // Subnegotiation End 241:"NOP", // No Operation 242:"DM", // Data Mark 243:"BRK", // Break 244:"IP", // Interrupt Process 245:"AO", // Abort Output 246:"AYT", // Are You There 247:"EC", // Erase Character 248:"EL", // Erase Line 249:"GA", // Go Ahead 250:"SB", // Subnegotiation 251:"WILL", // Desire to begin/confirmation of option 252:"WON'T", // Refusal to begin/continue option 253:"DO", // Request to begin/confirmation of option 254:"DON'T", // Demand/confirmation of stop of option 255:"IAC", // Interpret As Command ]); //. + to_send //. Data queued to be sent. static private string to_send = ""; //. - send //. Queues data to be sent to the other end of the connection. //. > s - String to send. static private void send(string s) { to_send += s; } //. - send_data //. Callback called when it is clear to send data over the connection. //. This function does the actual sending. static private void send_data() { if (!sizeof(to_send)) { to_send = write_cb(id); } if (!to_send) { // Support for delayed close. fd->close(); fd = 0; } else if (sizeof(to_send)) { int n = fd->write(to_send); to_send = to_send[n..]; } } //. + default_cb //. Mapping with the default handlers for TELNET commands. static private mapping(string:function) default_cb = ([ "BRK":lambda() { destruct(); throw(0); }, "AYT":lambda() { send("\377\361"); // NOP }, "WILL":lambda(int code) { send(sprintf("\377\376%c", code)); // DON'T xxx }, "DO":lambda(int code) { send(sprintf("\377\374%c", code)); // WON'T xxx }, ]); //. + synch //. Indicates wether we are in synch-mode or not. static private int synch = 0; //. - got_oob //. Callback called when Out-Of-Band data has been received. //. > ignored - The id from the connection. //. > s - The Out-Of-Band data received. static private void got_oob(mixed ignored, string s) { DWRITE(sprintf("TELNET: got_oob(\"%s\")\n", s)); synch = synch || (s == "\377"); if (cb["URG"]) { cb["URG"](id, s); } } //. + rest //. Contains data left over from the line-buffering. static private string rest = ""; //. - got_data //. Callback called when normal data has been received. //. This function also does most of the TELNET protocol parsing. //. > ignored - The id from the connection. //. > s - The received data. static private void got_data(mixed ignored, string s) { DWRITE(sprintf("TELNET: got_data(\"%s\")\n", s)); if (sizeof(s) && (s[0] == 242)) { DWRITE("TELNET: Data Mark\n"); // Data Mark handing. s = s[1..]; sync = 0; } // A single read() can contain multiple or partial commands // RFC 1123 4.1.2.10 array lines = s/"\r\n"; int lineno; for(lineno = 0; lineno < sizeof(lines); lineno++) { string line = lines[lineno]; if (search(line, "\377") != -1) { array a = line / "\377"; string parsed_line = a[0]; int i; for (i=1; i < sizeof(a); i++) { string part = a[i]; if (sizeof(part)) { string name = TelnetCodes[part[0]]; DWRITE(sprintf("TELNET: Code %s\n", name || "Unknown")); int j; function fun; switch (name) { case 0: // FIXME: Should probably have a warning here. break; default: if (fun = (cb[name] || default_cb[name])) { mixed err = catch { fun(); }; if (err) { throw(err); } else if (!zero_type(err)) { // We were just destructed. return; } } a[i] = a[i][1..]; break; case "EC": // Erase Character for (j=i; j--;) { if (sizeof(a[j])) { a[j] = a[j][..sizeof(a[j])-2]; break; } } a[i] = a[i][1..]; break; case "EL": // Erase Line for (j=0; j < i; j++) { a[j] = ""; } a[i] = a[i][1..]; break; case "WILL": case "WON'T": case "DO": case "DON'T": if (fun = (cb[name] || default_cb[name])) { fun(a[i][1]); } a[i] = a[i][2..]; break; case "DM": // Data Mark if (sync) { for (j=0; j < i; j++) { a[j] = ""; } } a[i] = a[i][1..]; sync = 0; break; } } else { a[i] = "\377"; i++; } } line = a * ""; } if (!lineno) { line = rest + line; } if (lineno < (sizeof(lines)-1)) { if ((!sync) && read_cb) { DWRITE(sprintf("TELNET: Calling read_callback(X, \"%s\")\n", line)); read_cb(id, line); } } else { DWRITE(sprintf("TELNET: Partial line is \"%s\"\n", line)); rest = line; } } } //. - set_write_callback //. Sets the callback to be called when it is clear to send. //. > w_cb - The new read callback. void set_write_callback(function(mixed|void:string) w_cb) { write_cb = w_cb; fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob); } //. - create //. Creates a TELNET protocol handler, and sets its callbacks. //. > f - File to use for the connection. //. > r_cb - Function to call when data has arrived. //. > w_cb - Function to call when data can be sent. //. > c_cb - Function to call when the connection is closed. //. > callbacks - Mapping with callbacks for the various TELNET commands. //. > 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); } }