| // 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; |
| } |
| |