// // Wunderland Mudlib // // secure/telnetneg.c -- Manage telnet negotations // // This telnet implementation is 'unconditionally compliant' // to RFC 1143 and so follows all rules of the telnet protocol RFC 854. // The driver itself is not quite compliant. // // Find the newest version of this file at // http://wl.mud.de/mud/doc/wwwstd/standardobjekte.html // // $Log: telnetneg.c,v $ // Revision 1.1.1.1 2006/07/10 02:42:09 lynx // ldmud 3.3.714 // // Revision 1.25 2003/07/07 07:08:52 Fiona // LM_SLC more save against protocol violations // #pragma strict_types #include // INPUT_* #include // ROOTID #include // P_* macros #define NEED_PROTOTYPES #include // SetProp() proto #define NEED_PRIVATE_PROTOTYPES #include #define GET_ALL_TTYPES // ******************** Telnet State Machine ******************** // // The following code is the implementation of the telnet state // machine as suggested with RFC 1143. // // The machine is used with three functions: // set_telnet() requests a change in the telnet state // set_callback() sets our prefered telnet states and a callback // function which is called on telnet state changes // query_telnet() query state and sb info // // The driver communicates with the engine through the H_TELNET_NEG // and got_telnet(). // // Do this in logon() to turn IAC quoting on: // set_connection_charset(({255})*32, 1); // // Do this in logon() to start telnet negotiation: // set_telnet(WILL, TELOPT_ECHO); // set_telnet(WONT, TELOPT_ECHO); // alternatively you could do the following, which is not as robust // set_telnet(WILL, TELOPT_EOR); nosave mapping ts; // Complete telnet negotation state // Set preferences and callbacks // // r_a_cb is the preference for the state on the remote side. It could be // DO, DONT or a callback closure which decides if we get a request. // l_a_cb is the same for the local side. // change_cb is called if the state changes to NO or YES (real change) // sb_cb is called with incoming subnegotiations. private void set_callback(int opt, mixed r_a_cb, mixed l_a_cb, closure change_cb, closure sb_cb) { if (r_a_cb == DO) r_a_cb = 1; else if (r_a_cb == DONT) r_a_cb = 0; if (l_a_cb == WILL) l_a_cb = 1; else if (l_a_cb == WONT) l_a_cb = 0; ts[opt, TS_R_AGREE] = r_a_cb; ts[opt, TS_L_AGREE] = l_a_cb; ts[opt, TS_CB] = change_cb; ts[opt, TS_SBCB] = sb_cb; } // Try to change an option // True is returned if the option indeed changes static int set_telnet(int command, int option) { int state, ok; state = ts[option, TS_STATE]; switch (command) { case DO: if (Q_REMOTER(state) == REJECTED) break; switch (Q_REMOTE(state)) { case YES: break; // ignore, it's already enabled case NO: state = S_REMOTE(state, WANT_YES); send(({ IAC, DO, option })); ok = 1; break; case WANT_NO: if (Q_REMOTEQ(state) == Q_EMPTY) { state = S_REMOTEQ(state, Q_OPPOSITE); ok = 1; } else { // ignore, request sent already } break; case WANT_YES: if (Q_REMOTEQ(state) == Q_EMPTY) { // ignore, request sent already } else { state = S_REMOTEQ(state, Q_EMPTY); ok = 1; } break; } break; case DONT: switch (Q_REMOTE(state)) { case NO: break; // ignore, it's already disabled case YES: state = S_REMOTE(state, WANT_NO); send(({ IAC, DONT, option })); ok = 1; break; case WANT_NO: if (Q_REMOTEQ(state) == Q_EMPTY) { // ignore, request sent already } else { state = S_REMOTEQ(state, Q_EMPTY); ok = 1; } break; case WANT_YES: if (Q_REMOTEQ(state) == Q_EMPTY) { state = S_REMOTEQ(state, Q_OPPOSITE); ok = 1; } else { // ignore, request sent already } break; } break; case WILL: if (Q_LOCALR(state) == REJECTED) break; switch (Q_LOCAL(state)) { case NO: state = S_LOCAL(state, WANT_YES); send(({ IAC, WILL, option })); ok = 1; break; case YES: break; // ignore, it's already enabled case WANT_NO: if (Q_LOCALQ(state) == Q_EMPTY) { state = S_LOCALQ(state, Q_OPPOSITE); ok = 1; } else { // ignore, request sent already } break; case WANT_YES: if (Q_LOCALQ(state) == Q_EMPTY) { // ignore, request sent already } else { state = S_LOCALQ(state, Q_EMPTY); ok = 1; } break; } break; case WONT: switch (Q_LOCAL(state)) { case NO: break; // ignore, it's already disabled case YES: state = S_LOCAL(state, WANT_NO); send(({ IAC, WONT, option })); ok = 1; break; case WANT_NO: if (Q_LOCALQ(state) == Q_EMPTY) { // ignore, request sent already } else { state = S_LOCALQ(state, Q_EMPTY); ok = 1; } break; case WANT_YES: if (Q_LOCALQ(state) == Q_EMPTY) { state = S_LOCALQ(state, Q_OPPOSITE); ok = 1; } else { // ignore, request sent already } break; } break; } ts[option, TS_STATE] = state; return ok; } // Request information on an option. sb has to be called by reference. public int query_telnet(int option, mixed sb) { if (!intp(option) || option < 0 || !member(ts, option)) { sb = 0; return 0; } sb = copy(ts[option, TS_SB]); return ts[option, TS_STATE]; } // Got telnet negotations from the driver void got_telnet(int command, int option, int *optargs) { int state, i, j, c, l, *xx, full; mixed agree; string log; // Set the hook as follows. // set_driver_hook(H_TELNET_NEG, "got_telnet"); // Make shure it's a driver apply. Hope this never breaks ;) if (caller_stack_depth()) return; log = ts[TS_EXTRA, TSE_LOG]; if (strlen(log) > 4000) log = "..." + log[<3800..]; log += "got " + telnet_to_text(command, option, optargs) + "\n"; ts[TS_EXTRA, TSE_LOG] = log; state = ts[option, TS_STATE]; switch (command) { case WILL: switch (Q_REMOTE(state)) { case NO: agree = ts[option, TS_R_AGREE]; if (closurep(agree)) agree = funcall(agree, command, option); if (agree) { state = S_REMOTE(state, YES); ts[option, TS_STATE] = state; send(({ IAC, DO, option })); agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); } else { send(({ IAC, DONT, option })); } break; case YES: break; // ignore case WANT_NO: if (Q_REMOTEQ(state) == Q_EMPTY) { tel_error("DONT answered by WILL"); state = S_REMOTE(state, NO); } else { tel_error("DONT answered by WILL"); state = S_REMOTE(state, YES); state = S_REMOTEQ(state, Q_EMPTY); } ts[option, TS_STATE] = state; break; case WANT_YES: state = S_REMOTE(state, YES); ts[option, TS_STATE] = state; agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); if (Q_REMOTEQ(state) != Q_EMPTY) { state = S_REMOTE(state, WANT_NO); state = S_REMOTEQ(state, Q_EMPTY); ts[option, TS_STATE] = state; send(({ IAC, DONT, option })); } break; } break; case WONT: switch (Q_REMOTE(state)) { case NO: break; // ignore case YES: state = S_REMOTE(state, NO); ts[option, TS_STATE] = state; send(({ IAC, DONT, option })); agree = ts[option, TS_CB]; if (closurep(agree)) agree = funcall(agree, command, option); break; case WANT_NO: state = S_REMOTE(state, NO); ts[option, TS_STATE] = state; agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); if (Q_REMOTEQ(state) != Q_EMPTY) { state = S_REMOTE(state, WANT_YES); state = S_REMOTEQ(state, Q_EMPTY); ts[option, TS_STATE] = state; send(({ IAC, DO, option })); } break; case WANT_YES: if (option == TELOPT_TM) { // *sigh* TM is not as all other options are agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); } else state = S_REMOTER(state, REJECTED); state = S_REMOTE(state, NO); state = S_REMOTEQ(state, Q_EMPTY); ts[option, TS_STATE] = state; break; } break; case DO: switch (Q_LOCAL(state)) { case NO: agree = ts[option, TS_L_AGREE]; if (closurep(agree)) agree = funcall(agree, command, option); if (agree) { state = S_LOCAL(state, YES); ts[option, TS_STATE] = state; send(({ IAC, WILL, option })); agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); } else { send(({ IAC, WONT, option })); } break; case YES: break; // ignore case WANT_NO: if (Q_LOCALQ(state) == Q_EMPTY) { tel_error("WONT answered by DO"); state = S_LOCAL(state, NO); } else { tel_error("WONT answered by DO"); state = S_LOCAL(state, YES); state = S_LOCALQ(state, Q_EMPTY); } ts[option, TS_STATE] = state; break; case WANT_YES: state = S_LOCAL(state, YES); ts[option, TS_STATE] = state; agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); if (Q_LOCALQ(state) != Q_EMPTY) { state = S_LOCAL(state, WANT_NO); state = S_LOCALQ(state, Q_EMPTY); ts[option, TS_STATE] = state; send(({ IAC, WONT, option })); } break; } break; case DONT: switch (Q_LOCAL(state)) { case NO: break; // ignore case YES: state = S_LOCAL(state, NO); ts[option, TS_STATE] = state; send(({ IAC, WONT, option })); agree = ts[option, TS_CB]; if (closurep(agree)) agree = funcall(agree, command, option); break; case WANT_NO: state = S_LOCAL(state, NO); ts[option, TS_STATE] = state; agree = ts[option, TS_CB]; if (closurep(agree)) funcall(agree, command, option); if (Q_LOCALQ(state) != Q_EMPTY) { state = S_LOCAL(state, WANT_YES); state = S_LOCALQ(state, Q_EMPTY); ts[option, TS_STATE] = state; send(({ IAC, WILL, option })); } break; case WANT_YES: state = S_LOCAL(state, NO); state = S_LOCALQ(state, Q_EMPTY); state = S_LOCALR(state, REJECTED); ts[option, TS_STATE] = state; break; } break; case SB: agree = ts[option, TS_SBCB]; if (closurep(agree)) funcall(agree, command, option, optargs); break; } // Do full negotation only if client can telnetneg, means we got some // telnetneg message from it. If we never got one we assume it can't // do it. The negotiation is triggered by one single telnetneg request // in the login process. if (!ts[TS_EXTRA, TSE_TELNETNEG]) start_telnetneg(); } // This function is used to transfer the telnet state engine's state // from one object to an other, used for player renew and so on // // The transfer has to be done before the exec(E), so that both objects // work with the same copy of ts mapping transfer_ts(mapping old_ts) { int opt; if (!previous_object() || getuid(previous_object()) != ROOTID) return 0; if (!old_ts) return ts; // use callbacks of THIS object foreach (opt : ts) { if (opt < 0) continue; old_ts[opt, TS_R_AGREE] = ts[opt, TS_R_AGREE]; old_ts[opt, TS_L_AGREE] = ts[opt, TS_L_AGREE]; old_ts[opt, TS_CB] = ts[opt, TS_CB]; old_ts[opt, TS_SBCB] = ts[opt, TS_SBCB]; } ts = old_ts; return 0; } // All telnet negotations are sent through this function private int send(int* x) { string log; int *y, i, j; if (x[1] != SB) y = x[3..]; else { y = x[3..<3]; j = sizeof(y) - 1; for (i = 0; i < j; ++i) { // undo 0xff quoting if (y[i] == IAC && y[i+1] == 0xff) { y[i..i+1] = ({ 0xff }); --j; } } } log = ts[TS_EXTRA, TSE_LOG]; if (strlen(log) > 4000) log = "..." + log[<3800..]; log += "sent " + telnet_to_text(x[1], x[2], y) +"\n"; ts[TS_EXTRA, TSE_LOG] = log; return efun::binary_message(x); } // TODO: it's not quite complete private string telnet_to_text(int command, int option, int* args) { string d_txt; int i, j; d_txt = TELCMD2STRING(IAC) + " " + TELCMD2STRING(command) + " " + TELOPT2STRING(option); if (args && sizeof(args)) { if (command == SB && option == TELOPT_LINEMODE) { switch (args[0]) { case LM_MODE: if (sizeof(args) > 1) { d_txt += " MODE" + (args[1] & MODE_EDIT ? " EDIT" : " NOEDIT") + (args[1] & MODE_TRAPSIG ? " TRAPSIG" : "") + (args[1] & MODE_SOFT_TAB ? " SOFT_TAB" : "") + (args[1] & MODE_LIT_ECHO ? " LIT_ECHO" : "") + (args[1] & MODE_ACK ? " ACK" : ""); if (sizeof(args) == 2) return d_txt; args = args[2..]; } break; case WILL: case WONT: case DO: case DONT: d_txt += " " + TELCMD2STRING(args[0]); args = args[1..]; if (sizeof(args) > 0 && args[0] == LM_FORWARDMASK) { d_txt += " FORWARDMASK"; args = args[1..]; } break; case LM_SLC: j = sizeof(args) - 2; d_txt += " SLC"; for (i = 1; i < j; i += 3) { d_txt += "\n "; d_txt += sprintf("%-6s %-23s %02x", SLC2STRING(args[i]), SLC_FLAGNAME[args[i+1] & SLC_LEVELBITS] + (args[i+1] & SLC_FLUSHOUT ? " FOUT" : "") + (args[i+1] & SLC_FLUSHIN ? " FIN" : "") + (args[i+1] & SLC_ACK ? " ACK" : ""), args[i+2]); } if (i > j) return d_txt; args = args[i-3..]; // dump rest (is error) break; } } if (command == SB && option != TELOPT_NAWS && option != TELOPT_LINEMODE) { d_txt += " " + TELQUAL2STRING(args[0]); if (sizeof(args) > 1) d_txt += " (" + implode(map(args[1..], (: sprintf("%02x", $1) :)), ",") + ")"; } else if (sizeof(args)) d_txt += " (" + implode(map(args, (: sprintf("%02x", $1) :)), ",") + ")"; } return d_txt; } // Full negotiation tries to set all our preferences // Options with closures as preference are not actively enabled. private void start_telnetneg() { int opt, *options; ts[TS_EXTRA, TSE_TELNETNEG] = 1; options = m_indices(ts) - ({ TS_EXTRA }); foreach (opt : options) { if (ts[opt, TS_R_AGREE] == 1) set_telnet(DO, opt); if (ts[opt, TS_L_AGREE] == 1) set_telnet(WILL, opt); } } // Telnet protocoll violations end up here private void tel_error(string err) { string log; log = ts[TS_EXTRA, TSE_LOG]; if (strlen(log) > 4000) log = "..." + log[<3800..]; log += "ERR " + err + "\n"; ts[TS_EXTRA, TSE_LOG] = log; } // ******************** Telnet Server Engine ******************** // // The following code is the telnet server using the telnet state machine. // It has to follow the some basic rules that cannot be assured by the // state machine to be complient to RFC 854. The most important is to // enable only options which are fully implemented. // // State changes are requested by the driver with the H_NOECHO hook // and set_noecho(). // // Here is defined how the mud will act in the negotiation. nosave int* tm_t; void create() { if (!ts) { ts = m_allocate(7, TS_SIZE); ts[TS_EXTRA, TSE_LOG] = ""; } // set how we would like the options' states set_callback(TELOPT_NAWS, DO, WONT, 0, #'sb_naws); set_callback(TELOPT_STATUS , DONT, WILL, 0, #'sb_status); set_callback(TELOPT_TTYPE, DO, WONT, #'start_sb, #'sb_ttype); set_callback(TELOPT_TSPEED, DO, WONT, #'start_sb, #'sb_tspeed); set_callback(TELOPT_NEWENV, DO, WONT, #'start_sb, #'sb_env); set_callback(TELOPT_ENVIRON, DO, WONT, #'start_sb, #'sb_env); set_callback(TELOPT_XDISPLOC, DO, WONT, #'start_sb, #'sb_xdisp); set_callback(TELOPT_EOR, DONT, WILL, #'start_eor, 0); set_callback(TELOPT_LINEMODE, DO, WONT, #'start_lm, #'sb_line); set_callback(TELOPT_TM , #'neg_tm, #'neg_tm, #'neg_tm, 0); set_callback(TELOPT_BINARY, #'neg_bin, #'neg_bin, 0, 0); set_callback(TELOPT_SGA, #'neg_sga, #'neg_sga, #'cb_sga, 0); set_callback(TELOPT_ECHO, #'neg_echo, WONT, #'cb_echo, 0); } // Change the NOECHO and CHARMODE state, called indirectly thru input_to() void set_noecho(int flag) { // Security check needs H_NOECHO to be specified as follows, // with lfun in the master: // // set_driver_hook(H_NOECHO, #'noecho_hook); // // void noecho_hook(int flag, int ob) { // if (ob) ob->set_noecho(flag); // } int i, j; if (object_name(previous_object()) != __MASTER_OBJECT__) return; if (!ts[TS_EXTRA, TSE_TELNETNEG]) return; // client does not telnetneg flag &= 255; i = Q_TSE_NOECHO; // Check if a change in NOECHO is requested if (flag & INPUT_NOECHO) { if (i == NO || i == WANT_NO) { S_TSE_NOECHO(WANT_YES); // If CHARMODE and NOECHO are requested (as usual), enter NOECHO // only if we could enter CHARMODE, else the player would be blind. if (!(flag & INPUT_CHARMODE)) { ts[TS_EXTRA, TSE_LOG] += "* trying to get NOECHO\n"; set_telnet(WILL, TELOPT_ECHO); } } } else { if (i == YES || i == WANT_YES) { ts[TS_EXTRA, TSE_LOG] += "* trying to leave NOECHO\n"; S_TSE_NOECHO(WANT_NO); set_telnet(WONT, TELOPT_ECHO); } } i = Q_TSE_SGA_CHAR; j = Q_TSE_LM_CHAR; // Check if a change in CHARMODE is requested // When the client knows LINEMODE, use it. If not use old (sga) charmode. if (flag & INPUT_CHARMODE) { if (i != YES && j != YES && i != WANT_YES && j != WANT_YES) { ts[TS_EXTRA, TSE_LOG] += "* trying to get CHARMODE\n"; if (Q_REMOTE(ts[TELOPT_LINEMODE, TS_STATE] == YES)) { S_TSE_LM_CHAR(WANT_YES); send(({ IAC, SB, TELOPT_LINEMODE, LM_MODE, MODE_SOFT_TAB | MODE_LIT_ECHO, IAC, SE })); } else { S_TSE_SGA_CHAR(WANT_YES); set_telnet(WILL, TELOPT_SGA); set_telnet(DO, TELOPT_SGA); } } } else { if (i == YES || j == YES || i == WANT_YES || j == WANT_YES) { ts[TS_EXTRA, TSE_LOG] += "* trying to leave CHARMODE\n"; // check both flags independently if (j == YES || j == WANT_YES) { S_TSE_LM_CHAR(WANT_NO); send(({ IAC, SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT | MODE_SOFT_TAB | MODE_LIT_ECHO, IAC, SE })); } if (i == YES || i == WANT_YES) { S_TSE_SGA_CHAR(WANT_NO); set_telnet(WONT, TELOPT_SGA); set_telnet(DONT, TELOPT_SGA); } } } } // // The callback functions // // neg_*() are called to decide if we say 'no' or 'yes' to an incoming // option change request private int neg_sga(int command, int option) { // Remote SGA is allowed for old charmode. // Local SGA is allowed for noecho mode (due to the SGA/ECHO ambiguity, // see RFC 857). It is also allowed if we are in old charmode // because some clients think it's needed. int i; if (command == DO) { i = Q_TSE_NOECHO; if (i == WANT_YES || i == YES) return 1; } // else WILL i = Q_TSE_SGA_CHAR; if (i == WANT_YES || i == YES) return 1; return 0; } private int neg_echo(int command, int option) { // Remote ECHO is never allowed (why should we like to get our own text) // Local ECHO is allowed for noecho mode // only called for DO int i; i = Q_TSE_NOECHO; if (i == WANT_YES || i == YES) return 1; return 0; } private int neg_bin(int command, int option) { // We like to have it enabled, but we don't want to have it enabled // with start_telnetneg(). // BINARY actively enabled only it the TTYPE is "xterm". // Some clients cant even answer a BINARY request properly (they have // to embed a '\0' into the stream). The RFC says, that it is needed // for 8 bit data path. Most clients just use this anyway and send // for example german umlauts. Strictly it's forbidden, though. Some // clients (BSD telnet for example) do honor this state and need // BINARY to send umlauts. return 1; } private int neg_tm(int command, int option) { int *t, i; // reset state immediately if (command == DO || command == DONT) { i = ts[option, TS_STATE]; i = S_LOCAL(i, NO); i = S_REMOTE(i, NO); i = S_REMOTER(i, 0); ts[option, TS_STATE] = i; } if (command == WILL || command == WONT) { i = ts[option, TS_STATE]; i = S_LOCAL(i, NO); i = S_REMOTE(i, NO); i = S_REMOTER(i, 0); ts[option, TS_STATE] = i; // got an answer t = utime(); i = (t[0]-tm_t[0])*1000 + (t[1]-tm_t[1])/1000; tm_t = 0; i -= 11; // interne Verzoegerung (muss man messen!) if (i < 1) i = 1; tell_object(this_object(), "Hurrikap teilt Dir mit: " "Daten benoetigen von Dir zum Mud und zurueck "+i+" ms.\n"); } return 1; } // start_*() and cb_*() are called on/after succesfull option changes // We do the appropriate things then. private void start_sb(int command, int option) { // If we got a WILL, request subnegotiation if (command == WILL) send(({ IAC, SB, option, TELQUAL_SEND, IAC, SE })); } private void start_lm(int command, int option) { if (command != WILL) return; if (!ts[option, TS_SB]) ts[option, TS_SB] = allocate(LM_SLC+1); send(({ IAC, SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT | MODE_SOFT_TAB | MODE_LIT_ECHO, IAC, SE })); ts[option, TS_SB][LM_MODE] = MODE_EDIT | MODE_SOFT_TAB | MODE_LIT_ECHO; // Flush on every control character // remember to prepend 0xff with IACs send(({ IAC, SB, TELOPT_LINEMODE, DO, LM_FORWARDMASK, IAC, 0xff, IAC, 0xff, IAC, 0xff, IAC, 0xff, IAC, SE })); ts[option, TS_SB][LM_FORWARDMASK] = WANT_YES; } private void start_eor(int command, int option) { // If we are allowed to use EOR whilst displaying a possible prompt // line, mark is as such. This happens unually only at the login. if (command == DO) efun::binary_message(({ IAC, EOR })); // Adjust prompt method to appropriate function if (command == DO || command == DONT) modify_prompt(); } private void cb_sga(int command, int option) { switch (command) { case WILL: if (Q_TSE_SGA_CHAR == WANT_YES) { S_TSE_SGA_CHAR(YES); ts[TS_EXTRA, TSE_LOG] += "* CHARMODE established\n"; if (Q_TSE_NOECHO == WANT_YES) { ts[TS_EXTRA, TSE_LOG] += "* trying to get NOECHO\n"; set_telnet(WILL, TELOPT_ECHO); } } break; case WONT: switch (Q_TSE_SGA_CHAR) { case WANT_NO: ts[TS_EXTRA, TSE_LOG] += "* CHARMODE left\n"; break; case WANT_YES: ts[TS_EXTRA, TSE_LOG] += "* CHARMODE denied\n"; break; case YES: ts[TS_EXTRA, TSE_LOG] += "* CHARMODE forcefully left\n"; break; } S_TSE_SGA_CHAR(NO); break; } } private void cb_echo(int command, int option) { switch (command) { case DO: if (Q_TSE_NOECHO == WANT_YES) { S_TSE_NOECHO(YES); ts[TS_EXTRA, TSE_LOG] += "* NOECHO established\n"; } break; case DONT: switch (Q_TSE_NOECHO) { case WANT_NO: ts[TS_EXTRA, TSE_LOG] += "* NOECHO mode left\n"; break; case WANT_YES: ts[TS_EXTRA, TSE_LOG] += "* NOECHO denied\n"; break; case YES: ts[TS_EXTRA, TSE_LOG] += "* NOECHO forcefully left\n"; break; } S_TSE_NOECHO(NO); break; } } // sb_*() handle the different incoming subnegotiations private void sb_status(int command, int option, int* optargs) { int *ret, opt, state, s, end; if (sizeof(optargs) != 1) return; if (optargs[0] != TELQUAL_SEND) return; ret = ({}); foreach (opt : ts) { if (opt < 0) continue; state = ts[opt, TS_STATE]; s = Q_LOCAL(state); if (s == YES || s == WANT_NO) ret += ({ WILL, opt }); s = Q_REMOTE(state); if (s == YES || s == WANT_NO) ret += ({ DO, opt }); } // some driver include the string end '\0' end = sizeof(to_array("")) + 1; if (ts[TELOPT_TSPEED, TS_SB]) ret += ({ SB, TELOPT_TSPEED, TELQUAL_IS }) + to_array(ts[TELOPT_TSPEED, TS_SB][0]+","+ ts[TELOPT_TSPEED, TS_SB][1])[0..= ' ' && $1 <= '~' :))); len = strlen(value); if (strlen(value) != sizeof(optargs)-1) tel_error("SB XDISPLOC contained bad characters"); if (!len) return tel_error("SB XDISPLOC empty, ignoring"); ts[option, TS_SB] = value; ts[TS_EXTRA, TSE_LOG] += " XDISPLOC is " + value + "\n"; } private void sb_ttype(int command, int option, int* optargs) { string value; int len, i; mixed* all; if (sizeof(optargs) < 2) return; if (optargs[0] != TELQUAL_IS) return; value = to_string(filter(optargs[1..], (: $1 >= ' ' && $1 <= '~' :))); len = strlen(value); if (strlen(value) != sizeof(optargs)-1) tel_error("SB TTYPE contained bad characters"); if (!len) return tel_error("SB TTYPE empty, ignoring"); value = lower_case(value); ts[TS_EXTRA, TSE_LOG] += " TTYPE is " + value + "\n"; all = ts[option, TS_SB]; if (!all) { all = ({ 0, ({}) }); // inform mudlib SetProp(P_TTY_TYPE, value); } #ifdef GET_ALL_TTYPES // Clients may provide different terminal types which are synonyms to // the native one (RFC 930). It is also possible to have a client which // switches terminal emulation along with the given (non synonymous) // terminal type names (RFC 1091). All these types are collected if // GET_ALL_TTYPES is defined. // // The drawback is, that the cycling thru all terminal emulations takes // some time, especially on slow links. MS windows 2000's and XP's // telnet have an emulation with key on-off sequences rather that chars. // Commands typed while TTYPE is negotiated may be wrong with that clients. i = member(all[1], value); if (i < 0) { all[1] += ({ value }); // get next TTYPE if (Q_REMOTE(ts[option, TS_STATE]) == YES) start_sb(WILL, option); } else { // switch back to first if (i == 0) { // ok, initial type re-established ts[TS_EXTRA, TSE_LOG] += " TTYPE in client set\n"; return; } switch (++all[0]) { case 1: if (Q_REMOTE(ts[option, TS_STATE]) == YES) start_sb(WILL, option); break; // just retry case 2: tel_error("could not get initial TTYPE, may be rfc930 client"); if (value != "vtnt") break; tel_error("trying trick to restart TTYPE list on windows client"); set_telnet(DONT, TELOPT_TTYPE); set_telnet(DO, TELOPT_TTYPE); break; case 3: tel_error("giving up on TTYPE"); break; } } #endif ts[option, TS_SB] = all; // Some clients cannot answer BINARY correctly. Xterm should be // able to. Also most clients ignore this option and just use a 8 bit // data path; only BSD telnet is known to need BINARY in order to // send umlauts. if (lower_case(value) == "xterm") set_telnet(DO, TELOPT_BINARY); } private void sb_tspeed(int command, int option, int* optargs) { int i, j, os; os = sizeof(optargs); if (os < 2) return; if (optargs[0] != TELQUAL_IS) return; for (i = 1; i < os; ++i) { if (optargs[i]==',') break; if (optargs[i] < '0' || optargs[i] > '9') return tel_error("SB TSPEED invalid character"); } if (i == 1) return tel_error("SB TSPEED empty transmit speed"); if (i == os) return tel_error("SB TSPEED no comma found"); for (j = i+1; j < os; ++j) { if (optargs[j] < '0' || optargs[j] > '9') return tel_error("SB TSPEED invalid character"); } if (i+1 >= os) return tel_error("SB TSPEED empty receive speed"); ts[option, TS_SB] = ({ to_int(to_string(optargs[1..i-1])), to_int(to_string(optargs[i+1..])) }); ts[TS_EXTRA, TSE_LOG] += " Transmit speed " + ts[option, TS_SB][0] + ", receive speed " + ts[option, TS_SB][1] + "\n"; } private void sb_env(int command, int option, int* optargs) { int i, j, os; mixed mix; mapping env; string s; os = sizeof(optargs); if (os < 2) return; if (optargs[0] != TELQUAL_IS && optargs[0] != TELQUAL_INFO) return; mix = ({}); for (i = j = 1; j < os; ++j) { if (optargs[j] == ENV_ESC) { ++j; continue; } if (optargs[j] <= ENV_USERVAR) { if (i != j) { s = to_string(filter(optargs[i..j-1], #'>=, ' ')); if (strlen(s) != j-i) tel_error("SB ENV contained bad characters"); mix += ({ s }); } mix += ({ optargs[j] }); i = j + 1; continue; } } if (i < j-1) { s = to_string(filter(optargs[i..j-1], #'>=, ' ')); if (strlen(s) != j-i) tel_error("SB ENV contained bad characters"); mix += ({ s }); } // Due to some weird implementation mistakes (see RFC 1571) // we can't be shure if VALUE or VAR are valid or swapped. // So we don't distuingush VAR and USERVAR and assume that every // variable's name is followed by its value. s = 0; env = ([]); os = sizeof(mix); for (i = 0; i < os; ++i) { if (!s && stringp(mix[i])) s = mix[i]; else if (s) { if (stringp(mix[i])) { env[s] = mix[i]; s = 0; } else if (!stringp(mix[i-1])) { env[s] = 1; // defined but no value assigned s = 0; } } } ts[option, TS_SB] = env; foreach (s, mix : env) { ts[TS_EXTRA, TSE_LOG] += " Environment " + s +" => " + (stringp(mix) ? mix : "defined") + "\n"; } } private void sb_line(int command, int option, int* optargs) { // Maybe LINEMODE should be turned of on errors, but then... I never saw any. int state, *xx, i, j; if (!ts[option, TS_SB]) ts[option, TS_SB] = allocate(LM_SLC+1); if (sizeof(optargs) < 2) return; switch (optargs[0]) { case LM_MODE: if (!(optargs[1] & MODE_ACK)) return tel_error("SB LINEMODE MODE with no ACK set (ignoring)"); if (optargs[1] & MODE_EDIT) switch (Q_TSE_LM_CHAR) { case YES: return tel_error("SB LINEMODE MODE EDIT unexpected"); case WANT_YES: return tel_error("SB LINEMODE MODE EDIT invalid answer"); case WANT_NO: S_TSE_LM_CHAR(NO); ts[TS_EXTRA, TSE_LOG] += "* CHARMODE left\n"; break; } else switch (Q_TSE_LM_CHAR) { case NO: return tel_error("SB LINEMODE MODE EDIT unexpected"); case WANT_NO: return tel_error("SB LINEMODE MODE EDIT invalid answer"); case WANT_YES: S_TSE_LM_CHAR(YES); ts[TS_EXTRA, TSE_LOG] += "* CHARMODE established\n"; if (Q_TSE_NOECHO == WANT_YES) { ts[TS_EXTRA, TSE_LOG] += "* trying to get NOECHO\n"; set_telnet(WILL, TELOPT_ECHO); } break; } ts[option, TS_SB][LM_MODE] = optargs[1] & ~MODE_ACK; break; // server does not use forwardmask case DO: case DONT: if (optargs[1] != LM_FORWARDMASK) { return tel_error("SB LINEMODE " + TELCMD2STRING(optargs[0]) + sprintf(" %02x unknown (ignored)", optargs[1])); } return tel_error("SB LINEMODE DO/DONT FORWARDMASK not " "allowed for client"); return; // client is requested to use forwardmask case WILL: if (optargs[1] != LM_FORWARDMASK) { return tel_error("SB LINEMODE " + TELCMD2STRING(optargs[0]) + sprintf(" %02x unknown (ignored)", optargs[1])); } state = ts[option, TS_SB][LM_FORWARDMASK]; switch (state) { case NO: tel_error("got spontan WILL FORWARDMASK (denied)"); break; case YES: // is already set, ignore break; case WANT_NO: tel_error("got WILL on DONT"); break; case WANT_YES: ts[option, TS_SB][LM_FORWARDMASK] = YES; break; } return; case WONT: if (optargs[1] != LM_FORWARDMASK) { return tel_error("SB LINEMODE " + TELCMD2STRING(optargs[0]) + sprintf(" %02x unknown (ignored)", optargs[1])); } state = ts[option, TS_SB][LM_FORWARDMASK]; switch (state) { case YES: send(({ IAC, SB, option, DONT, LM_FORWARDMASK, IAC, SE })); ts[option, TS_SB][LM_FORWARDMASK] = NO; break; case NO: // is already set, ignore break; case WANT_YES: case WANT_NO: ts[option, TS_SB][LM_FORWARDMASK] = NO; break; } return; case LM_SLC: if (sizeof(optargs) < 4) return tel_error("SB too short"); if (!ts[option, TS_SB][LM_SLC]) ts[option, TS_SB][LM_SLC] = ([ :2 ]); xx = ({ IAC, SB, TELOPT_LINEMODE, LM_SLC }); // Request for our preferences? // All the rest (especially the cursor movement) we would like // to be escape sequences, so no SLCs for that if (optargs[1] == 0 && optargs[2] == SLC_DEFAULT && optargs[3] == 0) { for (i = SLC_SYNCH; i <= SLC_SUSP; ++i) xx += ({ i, SLC_NOSUPPORT, 0 }); xx += ({ SLC_EC, SLC_DEFAULT, 127, SLC_EL, SLC_DEFAULT, 21, SLC_EW, SLC_DEFAULT, 23, SLC_XON, SLC_NOSUPPORT, 0, SLC_XOFF, SLC_NOSUPPORT, 0, SLC_FORW1, SLC_NOSUPPORT, 0, SLC_FORW2, SLC_NOSUPPORT, 0, IAC, SE }); send(xx); break; } // Answer to client's preferences // We dont want any of the session control keys (Synch - Susp) // Everything else is just acknoledged optargs = optargs[1..]; j = sizeof(optargs) - 3; for (i = 0; i < j; i+=3) { if (optargs[i+1] & SLC_ACK) { m_add(ts[option, TS_SB][LM_SLC], optargs[i], optargs[i+1] & ~SLC_ACK, optargs[i+2]); continue; } if (optargs[i+1] == SLC_NOSUPPORT) continue; if (optargs[i] > SLC_SUSP) { m_add(ts[option, TS_SB][LM_SLC], optargs[i], optargs[i+1] & ~SLC_ACK, optargs[i+2]); xx += ({ optargs[i], optargs[i+1] | SLC_ACK, optargs[i+2] }); } else xx += ({ optargs[i], SLC_NOSUPPORT, 0 }); } i = sizeof(xx); if (i > 4) { for (; --i;) if (xx[i] == 0xff) xx[i..i] = ({ IAC, 0xff }); send(xx + ({ IAC, SE })); } return; } } // helper functions, called from mudlib void send_telopt_tm() { tm_t = utime(); set_telnet(DO, TELOPT_TM); } void _dumptelnegs() { printf(ts[TS_EXTRA, TSE_LOG]); }