mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
1247 lines
38 KiB
C
1247 lines
38 KiB
C
//
|
|
// 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_to.h> // INPUT_*
|
|
#include <config.h> // ROOTID
|
|
#include <properties.h> // P_* macros
|
|
#define NEED_PROTOTYPES
|
|
#include <thing/properties.h> // SetProp() proto
|
|
#define NEED_PRIVATE_PROTOTYPES
|
|
#include <telnetneg.h>
|
|
|
|
#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..<end] + ({ -SE });
|
|
|
|
if (ts[TELOPT_XDISPLOC, TS_SB])
|
|
ret += ({ SB, TELOPT_XDISPLOC, TELQUAL_IS }) +
|
|
to_array(ts[TELOPT_XDISPLOC, TS_SB])[0..<end] + ({ -SE });
|
|
|
|
if (ts[TELOPT_TTYPE, TS_SB])
|
|
ret += ({ SB, TELOPT_TTYPE, TELQUAL_IS }) +
|
|
to_array(ts[TELOPT_TTYPE, TS_SB][1][0])[0..<end] + ({ -SE });
|
|
|
|
if (ts[TELOPT_NAWS, TS_SB])
|
|
ret += ({ SB, TELOPT_NAWS, ts[TELOPT_NAWS, TS_SB][0]/0x100,
|
|
ts[TELOPT_NAWS, TS_SB][0]%0x100,
|
|
ts[TELOPT_NAWS, TS_SB][1]/0x100,
|
|
ts[TELOPT_NAWS, TS_SB][1]%0x100, -SE });
|
|
|
|
if (ts[TELOPT_LINEMODE, TS_SB]) {
|
|
ret += ({ SB, TELOPT_LINEMODE, LM_MODE,
|
|
ts[TELOPT_LINEMODE, TS_SB][LM_MODE], -SE });
|
|
if (ts[TELOPT_LINEMODE, TS_SB][LM_FORWARDMASK] == YES)
|
|
ret += ({ SB, TELOPT_LINEMODE, DO, LM_FORWARDMASK, 0, 0x24, -SE });
|
|
if (sizeof(ts[TELOPT_LINEMODE, TS_SB][LM_SLC])) {
|
|
ret += ({ SB, TELOPT_LINEMODE, LM_SLC });
|
|
foreach (int i, int j, int k : ts[TELOPT_LINEMODE, TS_SB][LM_SLC])
|
|
ret += ({ i, j, k });
|
|
ret += ({ -SE });
|
|
}
|
|
}
|
|
|
|
// We dont send SB ENVIRON or SB NEWENV
|
|
|
|
// quote IAC and SE
|
|
// we use -SE for unquotable SEs
|
|
for (s = sizeof(ret); s--;) {
|
|
if (ret[s] == IAC) ret[s..s] = ({ IAC, IAC });
|
|
if (ret[s] == SE) ret[s..s] = ({ SE, SE });
|
|
if (ret[s] == -SE) ret[s] = SE;
|
|
}
|
|
|
|
send(({ IAC, SB, option, TELQUAL_IS }) + ret + ({ IAC, SE }));
|
|
|
|
}
|
|
|
|
private void sb_naws(int command, int option, int* optargs) {
|
|
int cols, lines, *old;
|
|
|
|
if (sizeof(optargs) != 4) return tel_error("SB NAWS with wrong size");
|
|
|
|
cols = optargs[1] + optargs[0] * 256;
|
|
lines = optargs[3] + optargs[2] * 256;
|
|
old = ts[option, TS_SB];
|
|
ts[option, TS_SB] = ({ cols, lines });
|
|
|
|
ts[TS_EXTRA, TSE_LOG] +=
|
|
" Window size: " + cols + " cols, " + lines + " lines\n";
|
|
|
|
// inform mudlib
|
|
if (pointerp(old)) {
|
|
if (old[0] != cols) SetProp(P_TTY_COLS, cols);
|
|
if (old[1] != lines) SetProp(P_TTY_ROWS, lines);
|
|
}
|
|
}
|
|
|
|
private void sb_xdisp(int command, int option, int* optargs) {
|
|
string value;
|
|
int len;
|
|
|
|
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 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]);
|
|
}
|