blob: c5da0d298f463f9cb218483f57f1b8250487665c [file] [log] [blame]
// MorgenGrauen MUDlib
//
// telnetneg.c -- Verwaltung von Telnet-Negotiations
//
// $Id$
/* Das Original wurde von Marcus@Tapp zur Verfuegung gestellt. */
/* Angepasst fuer die MG-Mudlib von Ringor@MG */
/* Weitgehend ueberarbeitet von Zesstra@MG */
#pragma strict_types,save_types
#pragma range_check
#pragma no_clone
#pragma no_shadow
#pragma pedantic
inherit "/secure/telnetneg-structs.c";
#define NEED_PROTOTYPES
#include "/secure/telnetneg.h"
#undef NEED_PROTOTYPES
#include <configuration.h>
// unterstuetzte Optionen:
// TELOPT_EOR, TELOPT_NAWS, TELOPT_LINEMODE, TELOPT_TTYPE, TELOPT_BINARY,
// TELOPT_CHARSET
//#define __DEBUG__ 1
#ifdef __DEBUG__
#define DEBUG(x) if (interactive(this_object()))\
tell_object(this_object(),"TN: " + x + "\n")
#define DTN(x,y) _debug_print(x,y)
#else
# define DEBUG(x)
# define DTN(x,y)
#endif
// first element "" to yield the separator
#define OFFERED_CHARSETS ({"", "UTF-8", "ISO8859-15", "LATIN-9", "ISO8859-1",\
"LATIN1", "WINDOWS-1252", "US-ASCII"})
// Aus mini_props.c:
public varargs mixed Query( string str, int type );
public varargs mixed Set( string str, mixed value, int type );
private nosave mapping TN = ([]);
nosave string *Terminals;
// Prototypen
private void eval_naws(int *optargs);
#ifdef __DEBUG__
// Gibts einige Konstanten mit sym. Namen aus.
private string dtranslate(int i) {
switch(i) {
case IAC: return "IAC";
case DONT: return "DONT";
case DO: return "DO";
case WONT: return "WONT";
case WILL: return "WILL";
case SB: return "SB";
case SE: return "SE";
case EOR: return "EOR";
case TELOPT_LINEMODE: return "TELOPT_LINEMODE";
case TELOPT_XDISPLOC: return "TELOPT_XDISPLOC";
case TELOPT_ENVIRON: return "TELOPT_ENVIRON";
case TELOPT_NEWENV: return "TELOPT_NEWENV";
case TELOPT_EOR: return "TELOPT_EOR";
case TELOPT_NAWS: return "TELOPT_NAWS";
case TELOPT_TSPEED: return "TELOPT_TSPEED";
case TELOPT_TTYPE: return "TELOPT_TTYPE";
case TELOPT_ECHO: return "TELOPT_ECHO";
case TELOPT_SGA: return "TELOPT_SGA";
case TELOPT_NAMS: return "TELOPT_NAMS";
case TELOPT_STATUS: return "TELOPT_STATUS";
case TELOPT_TM: return "TELOPT_TM";
case TELOPT_BINARY: return "TELOPT_BINARY";
case TELOPT_CHARSET: return "TELOPT_CHARSET";
case TELOPT_COMPRESS2: return "TELOPT_COMPRESS2";
case TELOPT_MSP: return "TELOPT_MSP";
case TELOPT_MXP: return "TELOPT_MXP";
case TELOPT_ATCP: return "TELOPT_ATCP";
case TELOPT_GMCP: return "TELOPT_GMCP";
case TELOPT_MSSP: return "TELOPT_MSSP";
}
return to_string(i);
}
// Gibt <arr> halbwegs lesbar an this_object() aus.
private void _debug_print(string x, int *arr) {
if (sizeof(arr) >1 && arr[1] == SB && arr[<1] != SE)
arr += ({IAC, SE});
closure map_int = function string (int i)
{ if (i >= 32 && i <= 126) return sprintf("%c",i);
return "["+to_string(i)+"]";
};
if (sizeof(arr)<=5) {
foreach(int c : arr)
x += " " + dtranslate(c);
}
else {
x += dtranslate(arr[0]) + " " + dtranslate(arr[1]) + " "
+ dtranslate(arr[2]) + " "
+ implode(map(arr[3..<3], map_int)," ")
+ " " + dtranslate(arr[<2]) + " " + dtranslate(arr[<1]);
}
DEBUG(x);
}
#endif
protected varargs int send_telnet_neg(int *arr, int bm_flags)
{
if ( sizeof(arr) < 2 )
return efun::binary_message(arr,bm_flags);
struct telopt_s opt = TN[arr[1]];
switch (arr[0]){
case DO:
case DONT:
(opt->lo_wishes)->remoteside = arr[0];
arr = ({IAC}) + arr;
break;
case WILL:
case WONT:
(opt->lo_wishes)->localside = arr[0];
arr = ({IAC}) + arr;
break;
case SB:
(opt->lo_wishes)->sbdata = arr[0..];
arr = ({IAC}) + arr + ({IAC, SE});
break;
default:
break;
}
DTN("send_tn: ",arr);
return efun::binary_message(arr, bm_flags);
}
protected varargs int send_telnet_neg_str(bytes str, int bm_flags)
{
#ifdef __DEBUG__
// Debugausgaben zur Zeit nur fuer arraybasierte Variante
return send_telnet_neg(to_array(str), bm_flags);
#else
if ( sizeof(str) < 2 )
return efun::binary_message(str, bm_flags);
struct telopt_s opt = TN[str[1]];
switch (str[0]) {
case DO:
case DONT:
(opt->lo_wishes)->remoteside = str[0];
str = to_bytes(({IAC})) + str;
break;
case WILL:
case WONT:
(opt->lo_wishes)->localside = str[0];
str = to_bytes(({IAC})) + str;
break;
case SB:
(opt->lo_wishes)->sbdata = to_array(str[1..]);
str = to_bytes(({IAC})) + str + to_bytes(({IAC,SE}));
break;
default:
break;
}
return efun::binary_message(str, bm_flags);
#endif // __DEBUG__
}
// Wenn der Client via STARTTLS eine TLS negotiation angestossen hat und
// die noch laeuft, darf keine Ausgabe erfolgen. In diesem Fall wird das
// Loginverfahren ausgesetzt, bis die TLS-Verhandlung abgeschlossen ist.
// Danach wird es fortgesetzt bzw. neugestartet. Dies gilt auch fuer Fall,
// dass STARTTLS verhandelt wurde, aber die TLS-Verhandlung noch nicht
// laeuft. (Bemerkung: beides pruefen ist nicht ueberfluessig. Den Zustand
// der Telnet-Option muss man pruefen, weil der Client evtl. seine
// Verhandlung noch nicht signalisiert hat (FOLLOWS vom Client) und die
// efun muss man pruefen, weil nach Empfang von FOLLOWS vom Client der
// Status der Telnet-Optiosn resettet wurde - standardkonform.)
protected int check_tls_negotiation()
{
struct telopt_s s_tls = TN[TELOPT_STARTTLS];
if (tls_query_connection_state(this_object()) < 0
|| (structp(s_tls) && s_tls->state->remoteside) )
{
debug_message("In TLS negotiation.\n");
return 1;
}
return 0;
}
// Startet eine Verhandlung, um den Status einer Option zu aendern.
// Wenn bereits eine Verhandlung laeuft, wird nichts gemacht und -1
// zurueckgeben.
// Wenn die Verhandlung keine Aenderung vom Status quo zum Ziel hat, wird
// nichts gemacht und -2 zurueckgegeben.
// Ansonsten ist die Rueckgabe die Anzahl der uebermittelten Zeichen.
// <action>: WILL: Option soll auf dieser Seite eingeschaltet werden.
// WONT: Option soll auf dieser Seite ausgeschaltet werden.
// DO : Option soll auf der anderen Seite eingeschaltet werden.
// DONT: Option soll auf der anderen Seite ausgeschaltet werden.
protected int do_telnet_neg(int option, int action) {
// ggf. muss TLS (initiiert durch STARTTLS) noch ausverhandelt werden. In
// dem Fall nix verhandeln/senden, was nicht STARTTLS ist.
if (option != TELOPT_STARTTLS && check_tls_negotiation())
return 0;
struct telopt_s opt = TN[option];
if (!structp(opt))
{
opt = (<telopt_s> option: option,
re_wishes: (<to_state_s>),
lo_wishes: (<to_state_s>),
state: (<to_state_s>)
);
TN[option] = opt;
}
// es wird nur geprueft, ob wir bereits eine Verhandlung begonnen haben
// (lo_wishes), weil reinkommende remote Wuensche letztendlich sofort durch
// unsere Antwort erledigt sind.
switch(action)
{
case WILL:
if (opt->lo_wishes->localside != 0)
return -1;
if (opt->state->localside)
return -2;
return send_telnet_neg( ({ WILL, option }) );
break;
case WONT:
if (opt->lo_wishes->localside != 0)
return -1;
if (!opt->state->localside)
return -2;
return send_telnet_neg( ({ WONT, option }) );
break;
case DO:
if (opt->lo_wishes->remoteside != 0)
return -1;
if (opt->state->remoteside)
return -2;
return send_telnet_neg( ({ DO, option }) );
break;
case DONT:
if (opt->lo_wishes->remoteside != 0)
return -1;
if (!opt->state->remoteside)
return -2;
return send_telnet_neg( ({ DONT, option }) );
break;
}
raise_error(sprintf("Unsupported telnet negotation action in "
"do_telnet_neg(): %d\n",action));
}
// LOCAL Standard Handlers //
private void _std_lo_handler_eor(struct telopt_s opt, int action) {
// tatsaechlich nix zu tun. Handler ist nur da, damit die Option auf dieser
// Seite aktiviert wird. Die Arbeit erledigt print_prompt.
return;
}
private void _std_lo_handler_mssp(struct telopt_s opt, int action) {
// nur einschalten ist interessant.
if (action != LOCALON)
return;
// Krams senden, wenn Objekt geladen. Sonst wieder abschalten (kommt
// hoffentlich nicht vor)...
object mssp = find_object("/secure/misc/mssp");
if (!mssp)
send_telnet_neg( ({WONT, TELOPT_MSSP }) );
else
{
send_telnet_neg_str(
to_bytes(({SB, TELOPT_MSSP}))
+ to_bytes(sprintf("%s", ({string})mssp->get_telnegs_str()),
"ASCII//TRANSLIT"));
// die Daten brauchen wir nicht mehr
opt->lo_wishes->sbdata = 0;
}
}
// REMOTE Standard Handlers //
private void _std_re_handler_tm(struct telopt_s opt, int action,
int *data)
{
// egal, was geantwortet wurde, es gibt nen Hinweis auf die round-trip-time.
// Wenn ein Array in opt->data[1] steht, rechnen wir das aus und schreiben es
// in opt->data[0] als Ergebnis rein.
if (pointerp(opt->data) && pointerp(opt->data[1]))
{
int *ut = utime();
int *start = opt->data[1];
int res = (ut[0] - start[0]) * 1000000;
res += ut[1] - start[1];
opt->data[0] = res;
opt->data[1] = 0;
DEBUG("RTT: "+res);
}
return;
}
private void _std_re_handler_naws(struct telopt_s opt, int action,
int *data)
{
if (action == SB)
{
eval_naws(data);
}
}
private void _std_re_handler_linemode(struct telopt_s opt, int action,
int *data)
{
if (action == REMOTEON)
{
// see /doc/concepts/negotiations. We use only the minimum
// needed for linemode: switching on local commandline-editing
// for the client.
send_telnet_neg(({ SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT }));
// flush on 0d and 0a...
// TODO: what does this exactly do?
send_telnet_neg(({ SB, TELOPT_LINEMODE, DO, LM_FORWARDMASK, 0,
0x40|0x08 }));
//Gna...
opt->lo_wishes->sbdata = ({MODE_EDIT});
}
}
private void _std_re_handler_ttype(struct telopt_s opt, int action,
int *data)
{
if (action == SB)
{
//TODO: get rid of this hysterical stuff...
//NOTE: We do not do multiple SB SENDs due to some weird
//bugs in IBM3270 emulating telnets which crash if we
//do that.
if ( sizeof(data) < 1 )
return;
if ( data[0] != TELQUAL_IS )
return;
string tmpterminal = lower_case( to_string(data[1..]) );
if ( !Terminals )
Terminals = ({ tmpterminal });
else
Terminals += ({ tmpterminal });
if ( Query(P_TTY_TYPE) )
Set( P_TTY_TYPE, Terminals[0] );
}
else if (action == REMOTEON)
{
send_telnet_neg(({ SB, TELOPT_TTYPE, TELQUAL_SEND }));
}
}
// Der Handler fuer die BINARY option, wenn sie auf Clientseite
// aktiviert/deaktivert wird, d.h. der Client sendet jetzt Binaerdaten statt
// NVT-ASCII. Im Normalfall muessen wir im Handler nix machen. (SB gibts hier
// nicht.)
private void _std_re_handler_binary(struct telopt_s opt, int action,
int *data)
{
DTN("binary handler client",({action}));
}
// Der Handler fuer die BINARY option, wenn sie auf unserer Seite
// aktiviert/deaktivert wird, d.h. wir senden jetzt Binaerdaten statt
// NVT-ASCII. Im Normalfall muessen wir im Handler nix machen. (SB gibts hier
// nicht.)
private void _std_lo_handler_binary(struct telopt_s opt, int action,
int *data)
{
DTN("binary handler mg",({action}));
}
private int activate_charset(struct telopt_s opt, string charset)
{
// Wenn der Client die Option nicht BINARY nicht unterstuetzt/will, duerfen
// wir auch keine nicht-ASCII-Zeichen uebertragen. In diesem Fall ist der
// einzige akzeptable Zeichensatz (US-)ASCII.
struct telopt_s binary = TN[TELOPT_BINARY];
if ( (!binary->state->remoteside || !binary->state->localside)
&& (upper_case(charset) != "US-ASCII"
&& upper_case(charset) != "ASCII") )
{
return 0;
}
// Wenn der Zeichensatz keine //-Variante ist, machen wir den zu
// einer. Das verhindert letztlich eine Menge Laufzeitfehler, wenn ein
// Zeichen mal nicht darstellbar ist.
if (strstr(charset, "//") == -1)
charset += "//TRANSLIT";
// Falls das zu sehr scrollt, weil Clients staendig ungueltige/nicht
// verwendbare Zeichensaetz schicken, muss das publish weg und ggf. sogar
// ein nolog hin...
if (!catch(configure_interactive(this_object(), IC_ENCODING, charset);
publish))
{
m_delete(opt->data, "failed_negotiations");
opt->data["accepted_charset"] = interactive_info(this_player(),
IC_ENCODING);
return 1;
}
return 0;
}
#define REQUEST 1
#define ACCEPTED 2
#define REJECTED 3
#define TTABLE_IS 4
#define TTABLE_REJECTED 5
// Der Handler fuer die CHARSET option, wenn sie auf/fuer Clientseite
// aktiviert/deaktivert wird oder fuer empfangene SB.
private void _std_re_handler_charset(struct telopt_s opt, int action,
int *data)
{
DTN("charset handler client",({action}));
// Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
// CHARSET REQUEST schicken (weil wir haben ihm auch schon ein DO
// geschickt).
if (action == REMOTEON)
{
if (!mappingp(opt->data))
opt->data = ([]);
}
else if (action == REMOTEOFF)
{
// Wenn auch auf mg-seite aus, kann data geloescht werden.
if (!opt->state->localside)
opt->data = 0;
}
else if (action == SB)
{
mapping statedata = opt->data;
// <data> is the part following IAC SB TELOPT_CHARSET
switch(data[0])
{
case REQUEST:
// is the client allowed to REQUEST?
if (opt->state->remoteside)
return;
// And enough data?
if (sizeof(data) > 1 )
{
DTN("re_charset request:",data);
string *suggestions = explode(to_text(data[2..], "ASCII"),
sprintf("%c",data[1]));
// Wenn UTF-8 drin vorkommt, nehmen wir das. (Gross-/Kleinschreibung
// ist egal, aber wir muessen einen identischen String
// zurueckschicken). (Gemischte Schreibweise: *ignorier* *stoehn*)
string *selected = suggestions & ({"UTF-8","utf-8"});
if (sizeof(selected)
&& activate_charset(opt, selected[0]))
{
send_telnet_neg(({ SB, TELOPT_CHARSET, ACCEPTED,
to_array(selected[0]) }));
return;
}
else
{
// die ersten 10 Vorschlaege durchprobieren
foreach(string cs : suggestions[0..min(sizeof(suggestions)-1, 10)])
{
if (activate_charset(opt, cs))
{
send_telnet_neg(({ SB, TELOPT_CHARSET, ACCEPTED,
to_array(cs) }));
return; // yeay, found one!
}
}
// none acceptable
send_telnet_neg(({ SB, TELOPT_CHARSET, REJECTED }));
++opt->data["failed_negotiations"];
// fall-through, no return;
}
}
else // malformed message
{
send_telnet_neg(({ SB, TELOPT_CHARSET, REJECTED }));
++opt->data["failed_negotiations"];
// fall-through, no return;
}
// when arriving here, the negotiation was not successful. Check if
// too many unsuccesful tries in a row.
if (opt->data["failed_negotiations"] > 10)
{
send_telnet_neg(({ TELOPT_CHARSET, DONT }));
send_telnet_neg(({ TELOPT_CHARSET, WONT }));
}
break;
case ACCEPTED:
// great - the client accepted one of our suggested charsets.
// Negotiation concluded. However, check if we REQUESTed a charset in
// the first place... And if the accepted one is one of our
// suggestions
if (sizeof(data) > 1)
{
DTN("re_charset accepted:",data);
string charset = upper_case(to_text(data[1..], "ASCII"));
string *offered = statedata["offered"];
// in any case, we don't need the key in the future.
m_delete(statedata, "offered");
if (pointerp(offered) && member(offered, charset) > -1)
{
activate_charset(opt, charset);
return;
}
// else: client did not sent us back one of our suggestions or we
// did not REQUEST. :-(
}
++opt->data["failed_negotiations"];
// else? Huh. malformed message.
break;
case REJECTED:
// none of our suggested charsets were acceptable. Negotiation is
// concluded, we keep the current charset (and maybe we will receive a
// suggestion of the client)
if (member(statedata, "offered"))
m_delete(statedata, "offered");
++opt->data["failed_negotiations"];
DTN("re_charset_rejected:",data);
break;
case TTABLE_IS:
// we plainly don't support TTABLES
send_telnet_neg(({ SB, TELOPT_CHARSET, TTABLE_REJECTED }));
++opt->data["failed_negotiations"];
break;
}
}
}
// Der Handler fuer die CHARSET option, wenn sie auf/fuer unserere Seite
// aktiviert/deaktivert wird.
private void _std_lo_handler_charset(struct telopt_s opt, int action,
int *data)
{
DTN("charset handler mg",({action}));
if (action == LOCALON)
{
// Ab diesem Moment duerfen wir dem Client einen CHARSET REQUEST schicken
// (denn wir haben auch schon ein DO erhalten). Und das tun wir auch
// direkt.
if (!mappingp(opt->data))
opt->data = ([ "offered": OFFERED_CHARSETS ]);
else
opt->data["offered"] = OFFERED_CHARSETS;
send_telnet_neg(({ SB, TELOPT_CHARSET, REQUEST })
+ to_array(implode(opt->data["offered"], ";"))) ;
}
else if (action == LOCALOFF)
{
// ok, keine REQUESTS mehr nach dem LOCALOFF, aber viel muss nicht getan
// werden. Wenn auch auf client-seite aus, kann data geloescht werden.
if (!opt->state->remoteside)
opt->data = 0;
}
// und SB gibt es nicht in diesem Handler.
}
#undef REQUEST
#undef ACCEPTED
#undef REJECTED
#undef TTABLE-IS
#undef TTABLE-REJECTED
// Called from the telnetneg handler for TELOPT_STARTTLS to initiate the TLS
// connection negotiation.
protected void init_tls()
{
// Dabei muss unser ganzer Telnet-Option-State muss zurueckgesetzt werden.
// Ja, wirklich! (Keine Sorge, der client muss das auch tun.)
TN = ([]);
}
#ifdef __TLS__
// Der Handler fuer STARTTLS, wenn es auf der Clientseite
// deaktiviert/aktiviert wird. Es wird nur auf der Clientseite aktiviert, der
// Server darf kein WILL senden. Nach Aktivierung muessen wir ein FOLLOWS
// senden.
#define FOLLOWS 1
private void _std_re_handler_starttls(struct telopt_s opt, int action,
int *data)
{
DTN("starttls handler client",({action}));
// Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
// STARTTLS FOLLOWS senden (weil wir haben ihm auch schon ein DO
// geschickt). Wir sollen ihm aber jetzt auch ein FOLLOWS senden. Sobald wir
// das gesendet haben und ein FOLLOWS erhalten haben, geht die Negotiation
// los.
if (action == REMOTEON)
{
send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
opt->data = 1; // Nur ein Flag, dass wir es gesendet haben.
}
else if (action == REMOTEOFF)
{
// data zuruecksetzen, sonst muessen wir nix machen.
opt->data = 0;
}
else if (action == SB)
{
if (data[0] == FOLLOWS)
{
// FOLLOWS empfangen. Wenn wir noch kein FOLLOWS gesendet haben, tun wir
// das jetzt.
if (!opt->data)
send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
// Jetzt wird die Verhandlung auf unserer Seite gestartet, der Client
// macht das entweder schon oder spaetestens, wenn er unser FOLLOWS
// empfangen kann.
init_tls();
}
}
}
#undef FOLLOWS
#endif // __TLS__
// Bindet/registriert Handler fuer die jew. Telnet Option. (Oder loescht sie
// auch wieder.) Je nach <initneg> wird versucht, die Option neu zu
// verhandeln.
protected int bind_telneg_handler(int option, closure re, closure lo,
int initneg)
{
struct telopt_s opt = TN[option];
if (!structp(opt))
{
opt = (<telopt_s> option: option,
re_wishes: (<to_state_s>),
lo_wishes: (<to_state_s>),
state: (<to_state_s>)
);
TN[option] = opt;
}
opt->remotehandler = re;
if (initneg)
{
if (re)
do_telnet_neg(option, DO);
else
do_telnet_neg(option, DONT );
}
opt->localhandler = lo;
if (initneg)
{
if (lo)
do_telnet_neg(option, WILL);
else
do_telnet_neg(option, WONT);
}
return 1;
}
// Mal unsere Wuensche an den Client schicken und die Standardhandler
// registrieren. Hierbei bei Bedarf neue Verhandlungen starten. Es wird hier
// aber nur ein Basissatz an Optionen verhandelt, der Rest kommt spaeter
// nachdem das Spielerobjekt die Verbindung hat (in startup_telnet_negs())
// Gerufen aus login.c nach Verbindungsaufbau.
// Bemerkung: das Spielerobjekt bietet evt. noch zusaetzliche Telnetoptionen
// an, die dann ueber startup_telnet_negs() (im Spielerobjekt)
// laufen.
protected void SendTelopts()
{
#if __TLS__
// If this is a non-TLS-connection, we offer STARTTLS, but wait for the
// client to ask for it.
if (tls_available() && tls_query_connection_state() == 0)
{
bind_telneg_handler(TELOPT_STARTTLS, #'_std_re_handler_starttls,
0, 1);
}
#endif
bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
#'_std_lo_handler_binary, 1);
bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 1);
bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 1);
bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 1);
if (find_object("/secure/misc/mssp"))
bind_telneg_handler(TELOPT_MSSP, 0, #'_std_lo_handler_mssp, 1);
// und auch CHARSET wird verzoegert bis das Spielerobjekt da ist.
}
// Bindet die Standardhandler _aus diesem_ Programm (und ueberschreibt dabei
// ggf. andere). Hierbei werden nur die Handler neu gebunden, keine neuen
// Verhandlungen initiiert.
// gerufen aus base.c indirekt via startup_telnet_negs().
protected void _bind_telneg_std_handlers()
{
// BTW: es ist absicht, im Spielerobjekt keinen Support fuer STARTTLS mehr
// anzubieten.
bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
#'_std_lo_handler_binary, 0);
bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 0);
bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 0);
bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 0);
bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 0);
// Besondere Situation: MSSP ist nach Spielerlogin eigentlich uninteressant.
// Daher sparen wir uns das im Kontext des Spielerobjekts und schalten es
// einfach wieder aus.
bind_telneg_handler(TELOPT_MSSP, 0, 0, 0);
}
// Ruft die entsprechenden handler von der Telnet Option.
// Wenn es keinen handler (mehr) gibt, wird die Option auch auf der jeweiligen
// Seite ausgeschaltet. Deshalb MUSS lo_wishes und re_wishes vom Aufrufer VOR
// DEM AUFRUF zurueckgesetzt worden sein!
// <action>: 'LOCALON': Option wurde auf unserer Seite eingeschaltet
// 'LOCALOFF': Option wurde auf unserer Seite ausgeschaltet
// 'REMOTEON': Option wurde auf Clientseite eingeschaltet
// 'REMOTEOFF': Option wurde auf Clientseite ausgeschaltet
// 'SB': Suboption negotiation Daten wurden empfangen
// <data>: die per SB empfangenen Daten (unverarbeitet)
private void _call_handler(struct telopt_s opt, int action, int *data) {
switch(action)
{
case REMOTEON:
case REMOTEOFF:
case SB:
if (opt->remotehandler)
{
funcall(opt->remotehandler, opt, action, data);
}
else
{
// ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
// dass nur verhandelt wird, wenn die Option an ist.)
do_telnet_neg( opt->option, DONT );
}
break;
case LOCALON:
case LOCALOFF:
if (opt->localhandler)
{
funcall(opt->localhandler, opt, action);
}
else
{
// ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
// dass nur verhandelt wird, wenn die Option an ist.)
do_telnet_neg( opt->option, WONT );
}
break;
}
}
// Gerufen vom Driver, wenn neue telnet options reinkommen.
void
telnet_neg(int command, int option, int *optargs)
{
DTN("recv_tn: ", ({IAC, command, option}) + (optargs||({})));
// ggf. muss TLS (initiiert durch STARTTLS) noch ausverhandelt werden. In
// dem Fall muessen wir alles ignorieren, was nicht STARTTLS ist.
if (option != TELOPT_STARTTLS && check_tls_negotiation())
return;
struct telopt_s opt = TN[option];
if (!structp(opt))
{
opt = (<telopt_s> option: option,
re_wishes: (<to_state_s>),
lo_wishes: (<to_state_s>),
state: (<to_state_s>)
);
TN[option] = opt;
}
// Was will der Client tun?
if (command == WONT)
{
// Client will die Option auf seiner Seite abschalten. Wir MUESSEN das
// akzeptieren.
// Wir muessen das allerdings ignorieren, wenn die Option bereits aus
// ist.
if (opt->state->remoteside==0)
{
// Ausnahme fuer TELOPT_TM, da das kaum ein Client kann und fuer RTT
// es eigentlich auch egal ist, was zurueck kommt: der handler wird
// zumindest doch gerufen zum Ausrechnen der RTT
if (option == TELOPT_TM)
_call_handler(opt, REMOTEOFF, 0);
// ansonsten aber wirklich ignorieren. ;)
return;
}
opt->re_wishes->remoteside = command;
// Bestaetigung auf ein WONT senden, wenn wir nicht selber schon ein
// DONT geschickt hatten.
if (opt->lo_wishes->remoteside != DONT) {
send_telnet_neg( ({DONT, option}) );
}
// Wir haben jetzt auf jeden Fall ein DONT gesendet und ein WONT
// erhalten. Damit ist die Option jetzt auf der clientseite aus.
// Ausserdem setzen wir die Wishes zurueck.
opt->re_wishes->remoteside = 0;
opt->lo_wishes->remoteside = 0;
if (opt->state->remoteside != 0)
{
opt->state->remoteside = 0;
_call_handler(opt, REMOTEOFF, 0);
}
} // WONT vom Client verarbeitet
else if ( command == WILL)
{
// Wenn die Option bereits an ist, muessen wir dies ignorieren.
if (opt->state->remoteside == 1)
{
// Ausnahme fuer TELOPT_TM, der handler wird zumindest doch gerufen
// zum Ausrechnen der RTT. Diese Option laesst sich ohnehin
// aktivieren, auch wenn sie schon an ist.
if (option == TELOPT_TM)
_call_handler(opt, REMOTEON, 0);
// sonst aber wirklich ignorieren. ;-)
return;
}
opt->re_wishes->remoteside = command;
if ( opt->lo_wishes->remoteside == 0 )
{
// Der Client will, wir haben noch nix dazu gesagt. (Mit unserer
// Antwort ist die Verhandlung uebrigens beendet.)
// Wenn es einen remotehandler fuer die Option gibt, schalten wir
// sie ein...
if (opt->remotehandler)
{
send_telnet_neg(({DO, option}));
// Option jetzt an der Clientseite an.
opt->re_wishes->remoteside = 0;
opt->lo_wishes->remoteside = 0;
if (opt->state->remoteside != 1)
{
opt->state->remoteside = 1;
_call_handler(opt, REMOTEON, 0);
}
}
else
{
// sonst verweigern wir das einschalten (die meisten Optionen
// auf Clientseite sind fuer uns eh egal).
send_telnet_neg(({DONT, option}));
// Option jetzt an der Clientseite aus.
opt->re_wishes->remoteside = 0;
opt->lo_wishes->remoteside = 0;
if (opt->state->remoteside != 0)
{
opt->state->remoteside = 0;
_call_handler(opt, REMOTEOFF, 0);
}
}
}
else if ( opt->lo_wishes->remoteside == DO)
{
// Wir haben haben bereits per DO angefordert, d.h. das ist die
// Clientbestaetigung - wir duerfen nicht bestaetigen und die
// Option ist jetzt clientseitig aktiv. Verhandlung beendet.
opt->re_wishes->remoteside = 0;
opt->lo_wishes->remoteside = 0;
if (opt->state->remoteside != 1)
{
opt->state->remoteside = 1;
_call_handler(opt, REMOTEON, 0);
}
} // if (DO)
else {
// Mhmm. Wir hatten ein DONT gesendet, aber der Client hat mit WILL
// geantwortet. Das darf er eigentlich gar nicht.
//TODO: was sollte man jetzt eigentlich tun? Erstmal wiederholen wir
//das DONT...
send_telnet_neg( ({DONT, option}) );
}
return;
} // WILL vom Client verarbeitet
// Was sollen wir (nicht) fuer den Client tun?
else if ( command == DONT)
{
// Client will, dass wir etwas nicht tun. Wir MUESSEN das akzeptieren.
// wenn die Option auf unserer Seite aber schon aus ist, muessen wir
// dies ignorieren.
if (opt->state->localside == 0)
return;
opt->re_wishes->localside = command;
// Wenn wir noch kein WONT gesendet haben, senden wir das jetzt als
// Bestaetigung.
if (opt->lo_wishes->localside = WONT)
send_telnet_neg( ({WONT, option}) );
// Verhandlung beendet, Option is auf unserer Seite jetzt aus.
// Wuensche auch wieder zuruecksetzen.
opt->re_wishes->localside = 0;
opt->lo_wishes->localside = 0;
if (opt->state->localside != 0)
{
opt->state->localside = 0;
_call_handler(opt, LOCALOFF, 0);
}
}
else if ( command == DO )
{
// Client will, dass wir option tun. Mal schauen, wie wir dazu stehen.
// wenn die Option auf unserer Seite aber schon an ist, muessen wir
// dies ignorieren.
if (opt->state->localside == 1)
return;
opt->re_wishes->localside = command;
if ( opt->lo_wishes->localside == 0 ) {
// wir haben unsere Wuensche noch nicht geaeussert. Sobald wir
// bestaetigen, ist die Option auf unserer Seite an/aus und die
// Verhandlungen beendet.
// in jedem Fall die Wuensche zuruecksetzen
opt->re_wishes->localside = 0;
opt->lo_wishes->localside = 0;
if (opt->localhandler)
{
send_telnet_neg(({WILL, option}));
opt->state->localside = 1;
_call_handler(opt, LOCALON, 0);
}
else
{
send_telnet_neg(({WONT, option}));
opt->state->localside = 0;
_call_handler(opt, LOCALOFF, 0);
}
}
else if (opt->lo_wishes->localside == WILL ) {
// wir haben schon WILL gesendet, welches der Client jetzt
// bestaetigt hat (d.h. die Option ist jetzt auf dieser Seite an),
// wir bestaetigen das aber nicht (nochmal).
opt->re_wishes->localside = 0;
opt->lo_wishes->localside = 0;
if (opt->state->localside != 1)
{
opt->state->localside = 1;
_call_handler(opt, LOCALON, 0);
}
}
else {
// Mhmm. Wir haben ein WONT gesendet, der Client hat mit DO
// geantwortet. Das darf er eigentlich nicht.
// TODO: Was tun?
send_telnet_neg ( ({WONT, option}) );
}
// fertig mit DO
return;
}
// bleibt noch SB ueber
else if ( command == SB )
{
opt->re_wishes->sbdata = optargs;
_call_handler(opt, SB, optargs);
return;
} // if ( command == SB )
}
// wird nur in base.c gerufen, wenn die Verbindung an das Spielerobjekt
// uebergeben wurde.
// es uebertraegt unter anderem den Telnet Option Zustand aus login.c (das ist
// dann previous_object()) in das Spielerobjekt (welches dann this_object())
// ist!
protected void
startup_telnet_negs()
{
int* optargs;
Set( P_TTY_TYPE, 0 ); //avoid ANY mistakes... Wird unten neu gesetzt.
// Daten aus dem Loginobjekt uebertragen. Das ist wichtig! (Dabei wird dann
// auch der Status von der letzten Session ueberschrieben.)
TN = ({mapping}) previous_object()->query_telnet_neg();
// bevor irgendwas anderes gemacht wird, werden erstmal die Standardhandler
// gesetzt. Die sind naemlich in diesem Objekt jetzt erstmal kaputt, weil
// sie im Loginobjekt gerufen werden.
_bind_telneg_std_handlers();
// dann restliche Daten aus dem Loginobjekt holen.
Terminals = ({string *}) previous_object()->query_terminals();
Set( P_TTY_COLS, ({int})previous_object()->Query(P_TTY_COLS) );
Set( P_TTY_ROWS, ({int})previous_object()->Query(P_TTY_ROWS) );
struct telopt_s opt = TN[TELOPT_NAWS];
if (optargs = (opt->re_wishes)->sbdata) {
eval_naws(optargs);
}
if ( pointerp(Terminals) && sizeof(Terminals)) {
if ( Terminals[0][0..3] == "dec-" )
Terminals[0] = Terminals[0][4..];
if ( Terminals[0] == "linux" )
Terminals[0] = "vt100";
Set( P_TTY_TYPE, Terminals[0] );
}
// fuer TELOPT_TM jetzt keine Verhandlung anstossen, aber Handler
// registrieren.
bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
// und zum Schluss wird der Support fuer CHARSET aktiviert.
bind_telneg_handler(TELOPT_CHARSET, #'_std_re_handler_charset,
#'_std_lo_handler_charset, 1);
}
// somehow completely out of the ordinary options processing/negotiation. But
// the only purpose is to transmit something over the wire which is not shown,
// but (likely) answered by the other device.
protected void send_telnet_timing_mark() {
struct telopt_s opt = TN[TELOPT_TM];
if (pointerp(opt->data))
opt->data[1] = utime();
else
opt->data = ({ 0, utime() });
// absichtlich nicht do_telnet_ne() verwendet, da dies nicht senden wuerde,
// haette der Client schonmal mit WILL geantwortet. TELOPT_TM ist aber eine
// Option, bei der man das darf...
send_telnet_neg( ({DO, TELOPT_TM}) );
}
/* Is called from the H_PRINT_PROMPT driver hook and appends the IAC EOR if
* the client supports it.
*/
void print_prompt(string prompt) {
// if (extern_call() && previous_object()!=this_object())
// return;
// ggf. Uhrzeit in den prompt reinschreiben.
prompt = regreplace(prompt,"\\t",strftime("%H:%M"),0);
// Prompt senden
tell_object(this_object(), prompt);
// Und EOR senden, falls vom Client gewuenscht.
struct telopt_s opt = TN[TELOPT_EOR];
if (structp(opt) && opt->state->localside == 1)
{
binary_message(({IAC, EOR}), 1);
DTN("tn_eor ",({IAC,EOR}));
}
}
// Helper
private void eval_naws(int *optargs) {
int l, c;
if ( sizeof(optargs) != 4 )
{
tell_object(this_object(),
break_string( sprintf("Dein Client hat einen Fehler beim"
+"Aushandeln der TELOPT_NAWS - er hat"
+"IAC SB %O IAC SE gesendet!\n",
optargs), 78,
"Der GameDriver teilt Dir mit: " ));
// und dem Client sagen, dass er den Schrott nicht mehr uebertragen
// soll (falls wir das nicht schon gemacht haben).
struct telopt_s opt = TN[TELOPT_NAWS];
if (opt->state->remoteside == WILL
&& opt->lo_wishes->remoteside != DONT)
send_telnet_neg(( {DONT, TELOPT_NAWS}) );
return;
}
if ( interactive(this_object()) ){
if ( !optargs[1] )
c = optargs[0];
else
c = optargs[1] + optargs[0] * 256;
if ( c < 35 ){
if (Query(P_TTY_SHOW))
tell_object( this_object(),
break_string("Dein Fenster ist schmaler als"
+" 35 Zeichen? Du scherzt. ;-)"
+" Ich benutze den Standardwert"
+" von 80 Zeichen.\n", 78,
"Der GameDriver teilt Dir mit: ")
);
c = 80;
}
if ( !optargs[3] )
l = optargs[2];
else
l = 256 * optargs[2] + optargs[3];
if ( l > 100 ){
//TODO: remove
l = 100;
if (Query(P_TTY_SHOW))
tell_object( this_object(),
break_string("Tut mir leid, aber ich kann"
+" nur bis zu 100 Zeilen"
+" verwalten.\n", (c ? c-2 : 78),
"Der GameDriver teilt Dir mit: " )
);
}
if ( l < 3 ){
if (Query(P_TTY_SHOW))
tell_object( this_object(),
break_string("Du willst weniger als drei"
+" Zeilen benutzen? Glaub ich"
+" Dir nicht - ich benutze den"
+" Standardwert von 24"
+" Zeilen.\n", (c ? c-2 : 78),
"Der GameDriver teilt Dir mit: " )
);
l = 24;
}
if ( (({int}) Query(P_TTY_ROWS) != l) ||
(({int}) Query(P_TTY_COLS) != c) ){
Set( P_TTY_ROWS, l );
Set( P_TTY_COLS, c );
if (Query(P_TTY_SHOW))
tell_object( this_object(),
break_string("Du hast Deine Fenstergroesse auf"
+" "+l+" Zeilen und "+c+
" Spalten geaendert.\n", c-2,
"Der GameDriver teilt Dir mit: ")
);
}
}
}
// Query-/Set-Methoden
// Und wenn hier einer von aussen dran rumpfuscht, werde ich sauer.
mapping
query_telnet_neg()
{
return TN;
}
// siehe oben
string *
query_terminals() {
return Terminals;
}
public int _query_p_lib_telnet_rttime()
{
struct telopt_s opt = TN[TELOPT_TM];
if (opt && pointerp(opt->data))
return (opt->data)[0];
return 0;
}