blob: 42d18c4b81fceb71c0b9d5a300d142111e65e33f [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// telnetneg.c -- Verwaltung von Telnet-Negotiations
4//
5// $Id$
6
7/* Das Original wurde von Marcus@Tapp zur Verfuegung gestellt. */
8/* Angepasst fuer die MG-Mudlib von Ringor@MG */
9/* Weitgehend ueberarbeitet von Zesstra@MG */
10
11#pragma strict_types,save_types
12#pragma range_check
13#pragma no_clone
14#pragma no_shadow
15#pragma pedantic
16
17inherit "/secure/telnetneg-structs.c";
18
19#define NEED_PROTOTYPES
20#include "/secure/telnetneg.h"
21#undef NEED_PROTOTYPES
Zesstra57cdbc32020-01-20 23:17:10 +010022#include <configuration.h>
MG Mud User88f12472016-06-24 23:31:02 +020023
24// unterstuetzte Optionen:
Zesstra57cdbc32020-01-20 23:17:10 +010025// TELOPT_EOR, TELOPT_NAWS, TELOPT_LINEMODE, TELOPT_TTYPE, TELOPT_BINARY,
26// TELOPT_CHARSET
MG Mud User88f12472016-06-24 23:31:02 +020027
28//#define __DEBUG__ 1
29
30#ifdef __DEBUG__
31#define DEBUG(x) if (interactive(this_object()))\
32 tell_object(this_object(),"TN: " + x + "\n")
33#define DTN(x,y) _debug_print(x,y)
34#else
35# define DEBUG(x)
36# define DTN(x,y)
37#endif
38
Zesstra57cdbc32020-01-20 23:17:10 +010039// first element "" to yield the separator
40#define OFFERED_CHARSETS ({"", "UTF-8", "ISO8859-15", "LATIN-9", "ISO8859-1",\
41 "LATIN1", "WINDOWS-1252", "US-ASCII"})
MG Mud User88f12472016-06-24 23:31:02 +020042
43// Aus mini_props.c:
44public varargs mixed Query( string str, int type );
45public varargs mixed Set( string str, mixed value, int type );
46
47private nosave mapping TN = ([]);
48nosave string *Terminals;
49
50// Prototypen
51private void eval_naws(int *optargs);
52
53#ifdef __DEBUG__
54// Gibts einige Konstanten mit sym. Namen aus.
55private string dtranslate(int i) {
56 switch(i) {
57 case IAC: return "IAC";
58 case DONT: return "DONT";
59 case DO: return "DO";
60 case WONT: return "WONT";
61 case WILL: return "WILL";
62 case SB: return "SB";
63 case SE: return "SE";
64 case EOR: return "EOR";
65 case TELOPT_LINEMODE: return "TELOPT_LINEMODE";
66 case TELOPT_XDISPLOC: return "TELOPT_XDISPLOC";
67 case TELOPT_ENVIRON: return "TELOPT_ENVIRON";
68 case TELOPT_NEWENV: return "TELOPT_NEWENV";
69 case TELOPT_EOR: return "TELOPT_EOR";
70 case TELOPT_NAWS: return "TELOPT_NAWS";
71 case TELOPT_TSPEED: return "TELOPT_TSPEED";
72 case TELOPT_TTYPE: return "TELOPT_TTYPE";
73 case TELOPT_ECHO: return "TELOPT_ECHO";
74 case TELOPT_SGA: return "TELOPT_SGA";
75 case TELOPT_NAMS: return "TELOPT_NAMS";
76 case TELOPT_STATUS: return "TELOPT_STATUS";
77 case TELOPT_TM: return "TELOPT_TM";
Zesstra65e9f1a2020-01-17 22:50:14 +010078 case TELOPT_BINARY: return "TELOPT_BINARY";
Zesstra57cdbc32020-01-20 23:17:10 +010079 case TELOPT_CHARSET: return "TELOPT_CHARSET";
MG Mud User88f12472016-06-24 23:31:02 +020080 case TELOPT_COMPRESS2: return "TELOPT_COMPRESS2";
81 case TELOPT_MSP: return "TELOPT_MSP";
82 case TELOPT_MXP: return "TELOPT_MXP";
83 case TELOPT_ATCP: return "TELOPT_ATCP";
84 case TELOPT_GMCP: return "TELOPT_GMCP";
85 case TELOPT_MSSP: return "TELOPT_MSSP";
86 }
87 return to_string(i);
88}
89
90// Gibt <arr> halbwegs lesbar an this_object() aus.
91private void _debug_print(string x, int *arr) {
Zesstra65e9f1a2020-01-17 22:50:14 +010092 if (sizeof(arr) >1 && arr[1] == SB && arr[<1] != SE)
MG Mud User88f12472016-06-24 23:31:02 +020093 arr += ({IAC, SE});
94 closure map_int = function string (int i)
95 { if (i >= 32 && i <= 126) return sprintf("%c",i);
96 return "["+to_string(i)+"]";
97 };
98 if (sizeof(arr)<=5) {
99 foreach(int c : arr)
100 x += " " + dtranslate(c);
101 }
102 else {
103 x += dtranslate(arr[0]) + " " + dtranslate(arr[1]) + " "
104 + dtranslate(arr[2]) + " "
105 + implode(map(arr[3..<3], map_int)," ")
106 + " " + dtranslate(arr[<2]) + " " + dtranslate(arr[<1]);
107 }
108 DEBUG(x);
109}
110#endif
111
112protected varargs int send_telnet_neg(int *arr, int bm_flags)
113{
114 if ( sizeof(arr) < 2 )
115 return efun::binary_message(arr,bm_flags);
116
117 struct telopt_s opt = TN[arr[1]];
118
119 switch (arr[0]){
120 case DO:
121 case DONT:
122 (opt->lo_wishes)->remoteside = arr[0];
123 arr = ({IAC}) + arr;
124 break;
125 case WILL:
126 case WONT:
127 (opt->lo_wishes)->localside = arr[0];
128 arr = ({IAC}) + arr;
129 break;
130 case SB:
131 (opt->lo_wishes)->sbdata = arr[0..];
132 arr = ({IAC}) + arr + ({IAC, SE});
133 break;
134 default:
135 break;
136 }
137 DTN("send_tn: ",arr);
138 return efun::binary_message(arr, bm_flags);
139}
140
Zesstrae06d75a2019-09-26 21:02:53 +0200141protected varargs int send_telnet_neg_str(bytes str, int bm_flags) {
MG Mud User88f12472016-06-24 23:31:02 +0200142#ifdef __DEBUG__
143 // Debugausgaben zur Zeit nur fuer arraybasierte Variante
144 return send_telnet_neg(to_array(str), bm_flags);
145#else
146 if ( sizeof(str) < 2 )
147 return efun::binary_message(str, bm_flags);
148
149 struct telopt_s opt = TN[str[1]];
150
151 switch (str[0]) {
152 case DO:
153 case DONT:
154 (opt->lo_wishes)->remoteside = str[0];
Zesstra9ebed822019-11-27 19:50:17 +0100155 str = to_bytes(({IAC})) + str;
MG Mud User88f12472016-06-24 23:31:02 +0200156 break;
157 case WILL:
158 case WONT:
159 (opt->lo_wishes)->localside = str[0];
Zesstra9ebed822019-11-27 19:50:17 +0100160 str = to_bytes(({IAC})) + str;
MG Mud User88f12472016-06-24 23:31:02 +0200161 break;
162 case SB:
Zesstra9ebed822019-11-27 19:50:17 +0100163 (opt->lo_wishes)->sbdata = to_array(str[1..]);
164 str = to_bytes(({IAC})) + str + to_bytes(({IAC,SE}));
MG Mud User88f12472016-06-24 23:31:02 +0200165 break;
166 default:
167 break;
168 }
169
170 return efun::binary_message(str, bm_flags);
171#endif // __DEBUG__
172}
173
174// Startet eine Verhandlung, um den Status einer Option zu aendern.
175// Wenn bereits eine Verhandlung laeuft, wird nichts gemacht und -1
176// zurueckgeben.
177// Wenn die Verhandlung keine Aenderung vom Status quo zum Ziel hat, wird
178// nichts gemacht und -2 zurueckgegeben.
179// Ansonsten ist die Rueckgabe die Anzahl der uebermittelten Zeichen.
180// <action>: WILL: Option soll auf dieser Seite eingeschaltet werden.
181// WONT: Option soll auf dieser Seite ausgeschaltet werden.
182// DO : Option soll auf der anderen Seite eingeschaltet werden.
183// DONT: Option soll auf der anderen Seite ausgeschaltet werden.
184protected int do_telnet_neg(int option, int action) {
185 struct telopt_s opt = TN[option];
186 if (!structp(opt))
187 {
188 opt = (<telopt_s> option: option,
189 re_wishes: (<to_state_s>),
190 lo_wishes: (<to_state_s>),
191 state: (<to_state_s>)
192 );
193 TN[option] = opt;
194 }
195 // es wird nur geprueft, ob wir bereits eine Verhandlung begonnen haben
196 // (lo_wishes), weil reinkommende remote Wuensche letztendlich sofort durch
197 // unsere Antwort erledigt sind.
198 switch(action)
199 {
200 case WILL:
201 if (opt->lo_wishes->localside != 0)
202 return -1;
203 if (opt->state->localside)
204 return -2;
205 return send_telnet_neg( ({ WILL, option }) );
206 break;
207 case WONT:
208 if (opt->lo_wishes->localside != 0)
209 return -1;
210 if (!opt->state->localside)
211 return -2;
212 return send_telnet_neg( ({ WONT, option }) );
213 break;
214 case DO:
215 if (opt->lo_wishes->remoteside != 0)
216 return -1;
217 if (opt->state->remoteside)
218 return -2;
219 return send_telnet_neg( ({ DO, option }) );
220 break;
221 case DONT:
222 if (opt->lo_wishes->remoteside != 0)
223 return -1;
224 if (!opt->state->remoteside)
225 return -2;
226 return send_telnet_neg( ({ DONT, option }) );
227 break;
228 }
229 raise_error(sprintf("Unsupported telnet negotation action in "
230 "do_telnet_neg(): %d\n",action));
231}
232
233// LOCAL Standard Handlers //
234private void _std_lo_handler_eor(struct telopt_s opt, int action) {
235 // tatsaechlich nix zu tun. Handler ist nur da, damit die Option auf dieser
236 // Seite aktiviert wird. Die Arbeit erledigt print_prompt.
237 return;
238}
239
240private void _std_lo_handler_mssp(struct telopt_s opt, int action) {
241 // nur einschalten ist interessant.
242 if (action != LOCALON)
243 return;
244 // Krams senden, wenn Objekt geladen. Sonst wieder abschalten (kommt
245 // hoffentlich nicht vor)...
246 object mssp = find_object("/secure/misc/mssp");
247 if (!mssp)
248 send_telnet_neg( ({WONT, TELOPT_MSSP }) );
249 else
250 {
Zesstra9ebed822019-11-27 19:50:17 +0100251 send_telnet_neg_str(
252 to_bytes(({SB, TELOPT_MSSP}))
253 + to_bytes(sprintf("%s", mssp->get_telnegs_str()),
254 "ASCII//TRANSLIT"));
MG Mud User88f12472016-06-24 23:31:02 +0200255 // die Daten brauchen wir nicht mehr
256 opt->lo_wishes->sbdata = 0;
257 }
258}
259
260
261// REMOTE Standard Handlers //
262private void _std_re_handler_tm(struct telopt_s opt, int action,
263 int *data)
264{
265 // egal, was geantwortet wurde, es gibt nen Hinweis auf die round-trip-time.
266 // Wenn ein Array in opt->data[1] steht, rechnen wir das aus und schreiben es
267 // in opt->data[0] als Ergebnis rein.
268 if (pointerp(opt->data) && pointerp(opt->data[1]))
269 {
270 int *ut = utime();
271 int *start = opt->data[1];
272 int res = (ut[0] - start[0]) * 1000000;
273 res += ut[1] - start[1];
274 opt->data[0] = res;
275 opt->data[1] = 0;
276 DEBUG("RTT: "+res);
277 }
278 return;
279}
280
281private void _std_re_handler_naws(struct telopt_s opt, int action,
282 int *data)
283{
284 if (action == SB)
285 {
286 eval_naws(data);
287 }
288}
289
290private void _std_re_handler_linemode(struct telopt_s opt, int action,
291 int *data)
292{
293 if (action == REMOTEON)
294 {
295 // see /doc/concepts/negotiations. We use only the minimum
296 // needed for linemode: switching on local commandline-editing
297 // for the client.
298 send_telnet_neg(({ SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT }));
299 // flush on 0d and 0a...
300 // TODO: what does this exactly do?
301 send_telnet_neg(({ SB, TELOPT_LINEMODE, DO, LM_FORWARDMASK, 0,
302 0x40|0x08 }));
303 //Gna...
304 opt->lo_wishes->sbdata = ({MODE_EDIT});
305 }
306}
307
308private void _std_re_handler_ttype(struct telopt_s opt, int action,
309 int *data)
310{
311 if (action == SB)
312 {
313 //TODO: get rid of this hysterical stuff...
314 //NOTE: We do not do multiple SB SENDs due to some weird
315 //bugs in IBM3270 emulating telnets which crash if we
316 //do that.
317 if ( sizeof(data) < 1 )
318 return;
319
320 if ( data[0] != TELQUAL_IS )
321 return;
322
323 string tmpterminal = lower_case( to_string(data[1..]) );
324 if ( !Terminals )
325 Terminals = ({ tmpterminal });
326 else
327 Terminals += ({ tmpterminal });
328
329 if ( Query(P_TTY_TYPE) )
330 Set( P_TTY_TYPE, Terminals[0] );
331 }
332 else if (action == REMOTEON)
333 {
334 send_telnet_neg(({ SB, TELOPT_TTYPE, TELQUAL_SEND }));
335 }
336}
337
Zesstra65e9f1a2020-01-17 22:50:14 +0100338// Der Handler fuer die BINARY option, wenn sie auf Clientseite
339// aktiviert/deaktivert wird, d.h. der Client sendet jetzt Binaerdaten statt
340// NVT-ASCII. Im Normalfall muessen wir im Handler nix machen. (SB gibts hier
341// nicht.)
342private void _std_re_handler_binary(struct telopt_s opt, int action,
343 int *data)
344{
Zesstra57cdbc32020-01-20 23:17:10 +0100345 DTN("binary handler client",({action}));
Zesstra65e9f1a2020-01-17 22:50:14 +0100346}
347
348// Der Handler fuer die BINARY option, wenn sie auf unserer Seite
349// aktiviert/deaktivert wird, d.h. wir senden jetzt Binaerdaten statt
350// NVT-ASCII. Im Normalfall muessen wir im Handler nix machen. (SB gibts hier
351// nicht.)
352private void _std_lo_handler_binary(struct telopt_s opt, int action,
353 int *data)
354{
Zesstra57cdbc32020-01-20 23:17:10 +0100355 DTN("binary handler mg",({action}));
Zesstra65e9f1a2020-01-17 22:50:14 +0100356}
357
Zesstra57cdbc32020-01-20 23:17:10 +0100358private int activate_charset(struct telopt_s opt, string charset)
359{
360 // Wenn der Client die Option nicht BINARY nicht unterstuetzt/will, duerfen
361 // wir auch keine nicht-ASCII-Zeichen uebertragen. In diesem Fall ist der
362 // einzige akzeptable Zeichensatz (US-)ASCII.
363 struct telopt_s binary = TN[TELOPT_BINARY];
364 if ( (!binary->state->remoteside || !binary->state->localside)
365 && (upper_case(charset) != "US-ASCII"
366 && upper_case(charset) != "ASCII") )
367 {
368 return 0;
369 }
370 // Wenn der Zeichensatz keine //-Variante ist, machen wir den zu
371 // einer. Das verhindert letztlich eine Menge Laufzeitfehler, wenn ein
372 // Zeichen mal nicht darstellbar ist.
373 if (strstr(charset, "//") == -1)
374 charset += "//TRANSLIT";
375 // Falls das zu sehr scrollt, weil Clients staendig ungueltige/nicht
376 // verwendbare Zeichensaetz schicken, muss das publish weg und ggf. sogar
377 // ein nolog hin...
378 if (!catch(configure_interactive(this_object(), IC_ENCODING, charset);
379 publish))
380 {
381 m_delete(opt->data, "failed_negotiations");
382 opt->data["accepted_charset"] = interactive_info(this_player(),
383 IC_ENCODING);
384 return 1;
385 }
386 return 0;
387}
388#define REQUEST 1
389#define ACCEPTED 2
390#define REJECTED 3
391#define TTABLE_IS 4
392#define TTABLE_REJECTED 5
393// Der Handler fuer die CHARSET option, wenn sie auf/fuer Clientseite
394// aktiviert/deaktivert wird oder fuer empfangene SB.
395private void _std_re_handler_charset(struct telopt_s opt, int action,
396 int *data)
397{
398 DTN("charset handler client",({action}));
399
400 // Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
401 // CHARSET REQUEST schicken (weil wir haben ihm auch schon ein DO
402 // geschickt).
403 if (action == REMOTEON)
404 {
405 if (!mappingp(opt->data))
406 opt->data = ([]);
407 }
408 else if (action == REMOTEOFF)
409 {
410 // Wenn auch auf mg-seite aus, kann data geloescht werden.
411 if (!opt->state->localside)
412 opt->data = 0;
413 }
414 else if (action == SB)
415 {
416 mapping statedata = opt->data;
417 // <data> is the part following IAC SB TELOPT_CHARSET
418 switch(data[0])
419 {
420 case REQUEST:
421 // is the client allowed to REQUEST?
422 if (opt->state->remoteside)
423 return;
424 // And enough data?
425 if (sizeof(data) > 1 )
426 {
427 DTN("re_charset request:",data);
428 string *suggestions = explode(to_text(data[2..], "ASCII"),
429 sprintf("%c",data[1]));
430 // Wenn UTF-8 drin vorkommt, nehmen wir das. (Gross-/Kleinschreibung
431 // ist egal, aber wir muessen einen identischen String
432 // zurueckschicken). (Gemischte Schreibweise: *ignorier* *stoehn*)
433 string *selected = suggestions & ({"UTF-8","utf-8"});
434 if (sizeof(selected)
435 && activate_charset(opt, selected[0]))
436 {
437 send_telnet_neg(({ SB, TELOPT_CHARSET, ACCEPTED,
438 to_array(selected[0]) }));
439 return;
440 }
441 else
442 {
443 // die ersten 10 Vorschlaege durchprobieren
444 foreach(string cs : suggestions[0..min(sizeof(suggestions)-1, 10)])
445 {
446 if (activate_charset(opt, cs))
447 {
448 send_telnet_neg(({ SB, TELOPT_CHARSET, ACCEPTED,
449 to_array(cs) }));
450 return; // yeay, found one!
451 }
452 }
453 // none acceptable
454 send_telnet_neg(({ SB, TELOPT_CHARSET, REJECTED }));
455 ++opt->data["failed_negotiations"];
456 // fall-through, no return;
457 }
458 }
459 else // malformed message
460 {
461 send_telnet_neg(({ SB, TELOPT_CHARSET, REJECTED }));
462 ++opt->data["failed_negotiations"];
463 // fall-through, no return;
464 }
465 // when arriving here, the negotiation was not successful. Check if
466 // too many unsuccesful tries in a row.
467 if (opt->data["failed_negotiations"] > 10)
468 {
469 send_telnet_neg(({ TELOPT_CHARSET, DONT }));
470 send_telnet_neg(({ TELOPT_CHARSET, WONT }));
471 }
472 break;
473 case ACCEPTED:
474 // great - the client accepted one of our suggested charsets.
475 // Negotiation concluded. However, check if we REQUESTed a charset in
476 // the first place... And if the accepted one is one of our
477 // suggestions
478 if (sizeof(data) > 1)
479 {
480 DTN("re_charset accepted:",data);
481 string charset = upper_case(to_text(data[1..], "ASCII"));
482 string *offered = statedata["offered"];
483 // in any case, we don't need the key in the future.
484 m_delete(statedata, "offered");
485 if (pointerp(offered) && member(offered, charset) > -1)
486 {
487 activate_charset(opt, charset);
488 return;
489 }
490 // else: client did not sent us back one of our suggestions or we
491 // did not REQUEST. :-(
492 }
493 ++opt->data["failed_negotiations"];
494 // else? Huh. malformed message.
495 break;
496 case REJECTED:
497 // none of our suggested charsets were acceptable. Negotiation is
498 // concluded, we keep the current charset (and maybe we will receive a
499 // suggestion of the client)
500 if (member(statedata, "offered"))
501 m_delete(statedata, "offered");
502 ++opt->data["failed_negotiations"];
503 DTN("re_charset_rejected:",data);
504 break;
505 case TTABLE_IS:
506 // we plainly don't support TTABLES
507 send_telnet_neg(({ SB, TELOPT_CHARSET, TTABLE_REJECTED }));
508 ++opt->data["failed_negotiations"];
509 break;
510 }
511 }
512}
513
514// Der Handler fuer die BINARY option, wenn sie auf/fuer unserere Seite
515// aktiviert/deaktivert wird.
516private void _std_lo_handler_charset(struct telopt_s opt, int action,
517 int *data)
518{
519 DTN("charset handler mg",({action}));
520 if (action == LOCALON)
521 {
522 // Ab diesem Moment duerfen wir dem Client einen CHARSET REQUEST schicken
523 // (denn wir haben auch schon ein DO erhalten). Und das tun wir auch
524 // direkt.
525 if (!mappingp(opt->data))
526 opt->data = ([ "offered": OFFERED_CHARSETS ]);
527 else
528 opt->data["offered"] = OFFERED_CHARSETS;
529 send_telnet_neg(({ SB, TELOPT_CHARSET, REQUEST })
530 + to_array(implode(opt->data["offered"], ";"))) ;
531 }
532 else if (action == LOCALOFF)
533 {
534 // ok, keine REQUESTS mehr nach dem LOCALOFF, aber viel muss nicht getan
535 // werden. Wenn auch auf client-seite aus, kann data geloescht werden.
536 if (!opt->state->remoteside)
537 opt->data = 0;
538 }
539 // und SB gibt es nicht in diesem Handler.
540}
541#undef REQUEST
542#undef ACCEPTED
543#undef REJECTED
544#undef TTABLE-IS
545#undef TTABLE-REJECTED
546
547
MG Mud User88f12472016-06-24 23:31:02 +0200548// Bindet/registriert Handler fuer die jew. Telnet Option. (Oder loescht sie
549// auch wieder.) Je nach <initneg> wird versucht, die Option neu zu
550// verhandeln.
551protected int bind_telneg_handler(int option, closure re, closure lo,
552 int initneg)
553{
554 struct telopt_s opt = TN[option];
555 if (!structp(opt))
556 {
557 opt = (<telopt_s> option: option,
558 re_wishes: (<to_state_s>),
559 lo_wishes: (<to_state_s>),
560 state: (<to_state_s>)
561 );
562 TN[option] = opt;
563 }
564
565 opt->remotehandler = re;
566 if (initneg)
567 {
568 if (re)
569 do_telnet_neg(option, DO);
570 else
571 do_telnet_neg(option, DONT );
572 }
573
574 opt->localhandler = lo;
575 if (initneg)
576 {
577 if (lo)
578 do_telnet_neg(option, WILL);
579 else
580 do_telnet_neg(option, WONT);
581 }
582 return 1;
583}
584
585
586// Mal unsere Wuensche an den Client schicken und die Standardhandler
Zesstra01b2b742020-01-20 23:53:01 +0100587// registrieren. Hierbei bei Bedarf neue Verhandlungen starten. Es wird hier
588// aber nur ein Basissatz an Optionen verhandelt, der Rest kommt spaeter
589// nachdem das Spielerobjekt die Verbindung hat (in startup_telnet_negs())
MG Mud User88f12472016-06-24 23:31:02 +0200590// Gerufen aus login.c nach Verbindungsaufbau.
591// Bemerkung: das Spielerobjekt bietet evt. noch zusaetzliche Telnetoptionen
592// an, die dann ueber startup_telnet_negs() (im Spielerobjekt)
593// laufen.
594protected void SendTelopts()
595{
Zesstra65e9f1a2020-01-17 22:50:14 +0100596 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
597 #'_std_lo_handler_binary, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200598 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200599 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 1);
600 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 1);
601 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 1);
Zesstra65e9f1a2020-01-17 22:50:14 +0100602 if (find_object("/secure/misc/mssp"))
603 bind_telneg_handler(TELOPT_MSSP, 0, #'_std_lo_handler_mssp, 1);
Zesstra57cdbc32020-01-20 23:17:10 +0100604 // und auch CHARSET wird verzoegert bis das Spielerobjekt da ist.
MG Mud User88f12472016-06-24 23:31:02 +0200605}
606
607
608// Bindet die Standardhandler _aus diesem_ Programm (und ueberschreibt dabei
609// ggf. andere). Hierbei werden nur die Handler neu gebunden, keine neuen
610// Verhandlungen initiiert.
611// gerufen aus base.c indirekt via startup_telnet_negs().
612protected void _bind_telneg_std_handlers() {
Zesstra65e9f1a2020-01-17 22:50:14 +0100613 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
614 #'_std_lo_handler_binary, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200615 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200616 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 0);
617 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 0);
618 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 0);
Zesstra65e9f1a2020-01-17 22:50:14 +0100619 // Besondere Situation: MSSP ist nach Spielerlogin eigentlich uninteressant.
620 // Daher sparen wir uns das im Kontext des Spielerobjekts und schalten es
621 // einfach wieder aus.
622 bind_telneg_handler(TELOPT_MSSP, 0, 0, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200623}
624
625
626// Ruft die entsprechenden handler von der Telnet Option.
627// Wenn es keinen handler (mehr) gibt, wird die Option auch auf der jeweiligen
628// Seite ausgeschaltet. Deshalb MUSS lo_wishes und re_wishes vom Aufrufer VOR
629// DEM AUFRUF zurueckgesetzt worden sein!
630// <action>: 'LOCALON': Option wurde auf unserer Seite eingeschaltet
631// 'LOCALOFF': Option wurde auf unserer Seite ausgeschaltet
632// 'REMOTEON': Option wurde auf Clientseite eingeschaltet
633// 'REMOTEOFF': Option wurde auf Clientseite ausgeschaltet
634// 'SB': Suboption negotiation Daten wurden empfangen
635// <data>: die per SB empfangenen Daten (unverarbeitet)
636private void _call_handler(struct telopt_s opt, int action, int *data) {
637 switch(action)
638 {
639 case REMOTEON:
640 case REMOTEOFF:
641 case SB:
642 if (opt->remotehandler)
643 {
644 funcall(opt->remotehandler, opt, action, data);
645 }
646 else
647 {
648 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
649 // dass nur verhandelt wird, wenn die Option an ist.)
650 do_telnet_neg( opt->option, DONT );
651 }
652 break;
653 case LOCALON:
654 case LOCALOFF:
655 if (opt->localhandler)
656 {
657 funcall(opt->localhandler, opt, action);
658 }
659 else
660 {
661 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
662 // dass nur verhandelt wird, wenn die Option an ist.)
663 do_telnet_neg( opt->option, WONT );
664 }
665 break;
666 }
667}
668
669// Gerufen vom Driver, wenn neue telnet options reinkommen.
670void
671telnet_neg(int command, int option, int *optargs)
672{
673 DTN("recv_tn: ", ({IAC, command, option}) + (optargs||({})));
674
675 struct telopt_s opt = TN[option];
676 if (!structp(opt))
677 {
678 opt = (<telopt_s> option: option,
679 re_wishes: (<to_state_s>),
680 lo_wishes: (<to_state_s>),
681 state: (<to_state_s>)
682 );
683 TN[option] = opt;
684 }
685
686 // Was will der Client tun?
687 if (command == WONT)
688 {
689 // Client will die Option auf seiner Seite abschalten. Wir MUESSEN das
690 // akzeptieren.
691 // Wir muessen das allerdings ignorieren, wenn die Option bereits aus
692 // ist.
693 if (opt->state->remoteside==0)
694 {
695 // Ausnahme fuer TELOPT_TM, da das kaum ein Client kann und fuer RTT
696 // es eigentlich auch egal ist, was zurueck kommt: der handler wird
697 // zumindest doch gerufen zum Ausrechnen der RTT
698 if (option == TELOPT_TM)
699 _call_handler(opt, REMOTEOFF, 0);
700 // ansonsten aber wirklich ignorieren. ;)
701 return;
702 }
703 opt->re_wishes->remoteside = command;
704 // Bestaetigung auf ein WONT senden, wenn wir nicht selber schon ein
705 // DONT geschickt hatten.
706 if (opt->lo_wishes->remoteside != DONT) {
707 send_telnet_neg( ({DONT, option}) );
708 }
709 // Wir haben jetzt auf jeden Fall ein DONT gesendet und ein WONT
710 // erhalten. Damit ist die Option jetzt auf der clientseite aus.
711 // Ausserdem setzen wir die Wishes zurueck.
712 opt->re_wishes->remoteside = 0;
713 opt->lo_wishes->remoteside = 0;
714 if (opt->state->remoteside != 0)
715 {
716 opt->state->remoteside = 0;
717 _call_handler(opt, REMOTEOFF, 0);
718 }
719 } // WONT vom Client verarbeitet
720 else if ( command == WILL)
721 {
722 // Wenn die Option bereits an ist, muessen wir dies ignorieren.
723 if (opt->state->remoteside == 1)
724 {
725 // Ausnahme fuer TELOPT_TM, der handler wird zumindest doch gerufen
726 // zum Ausrechnen der RTT. Diese Option laesst sich ohnehin
727 // aktivieren, auch wenn sie schon an ist.
728 if (option == TELOPT_TM)
729 _call_handler(opt, REMOTEON, 0);
730 // sonst aber wirklich ignorieren. ;-)
731 return;
732 }
733 opt->re_wishes->remoteside = command;
734 if ( opt->lo_wishes->remoteside == 0 )
735 {
736 // Der Client will, wir haben noch nix dazu gesagt. (Mit unserer
737 // Antwort ist die Verhandlung uebrigens beendet.)
738 // Wenn es einen remotehandler fuer die Option gibt, schalten wir
739 // sie ein...
740 if (opt->remotehandler)
741 {
742 send_telnet_neg(({DO, option}));
743 // Option jetzt an der Clientseite an.
744 opt->re_wishes->remoteside = 0;
745 opt->lo_wishes->remoteside = 0;
746 if (opt->state->remoteside != 1)
747 {
748 opt->state->remoteside = 1;
749 _call_handler(opt, REMOTEON, 0);
750 }
751 }
752 else
753 {
754 // sonst verweigern wir das einschalten (die meisten Optionen
755 // auf Clientseite sind fuer uns eh egal).
756 send_telnet_neg(({DONT, option}));
757 // Option jetzt an der Clientseite aus.
758 opt->re_wishes->remoteside = 0;
759 opt->lo_wishes->remoteside = 0;
760 if (opt->state->remoteside != 0)
761 {
762 opt->state->remoteside = 0;
763 _call_handler(opt, REMOTEOFF, 0);
764 }
765 }
766 }
767 else if ( opt->lo_wishes->remoteside == DO)
768 {
769 // Wir haben haben bereits per DO angefordert, d.h. das ist die
770 // Clientbestaetigung - wir duerfen nicht bestaetigen und die
771 // Option ist jetzt clientseitig aktiv. Verhandlung beendet.
772 opt->re_wishes->remoteside = 0;
773 opt->lo_wishes->remoteside = 0;
774 if (opt->state->remoteside != 1)
775 {
776 opt->state->remoteside = 1;
777 _call_handler(opt, REMOTEON, 0);
778 }
779 } // if (DO)
780 else {
781 // Mhmm. Wir hatten ein DONT gesendet, aber der Client hat mit WILL
782 // geantwortet. Das darf er eigentlich gar nicht.
783 //TODO: was sollte man jetzt eigentlich tun? Erstmal wiederholen wir
784 //das DONT...
785 send_telnet_neg( ({DONT, option}) );
786 }
787
788 return;
789 } // WILL vom Client verarbeitet
790 // Was sollen wir (nicht) fuer den Client tun?
791 else if ( command == DONT)
792 {
793 // Client will, dass wir etwas nicht tun. Wir MUESSEN das akzeptieren.
794 // wenn die Option auf unserer Seite aber schon aus ist, muessen wir
795 // dies ignorieren.
796 if (opt->state->localside == 0)
797 return;
798
799 opt->re_wishes->localside = command;
800 // Wenn wir noch kein WONT gesendet haben, senden wir das jetzt als
801 // Bestaetigung.
802 if (opt->lo_wishes->localside = WONT)
803 send_telnet_neg( ({WONT, option}) );
804 // Verhandlung beendet, Option is auf unserer Seite jetzt aus.
805 // Wuensche auch wieder zuruecksetzen.
806 opt->re_wishes->localside = 0;
807 opt->lo_wishes->localside = 0;
808 if (opt->state->localside != 0)
809 {
810 opt->state->localside = 0;
811 _call_handler(opt, LOCALOFF, 0);
812 }
813 }
814 else if ( command == DO )
815 {
816 // Client will, dass wir option tun. Mal schauen, wie wir dazu stehen.
817 // wenn die Option auf unserer Seite aber schon an ist, muessen wir
818 // dies ignorieren.
819 if (opt->state->localside == 1)
820 return;
821
822 opt->re_wishes->localside = command;
823
824 if ( opt->lo_wishes->localside == 0 ) {
825 // wir haben unsere Wuensche noch nicht geaeussert. Sobald wir
826 // bestaetigen, ist die Option auf unserer Seite an/aus und die
827 // Verhandlungen beendet.
828 // in jedem Fall die Wuensche zuruecksetzen
829 opt->re_wishes->localside = 0;
830 opt->lo_wishes->localside = 0;
831 if (opt->localhandler)
832 {
833 send_telnet_neg(({WILL, option}));
834 opt->state->localside = 1;
835 _call_handler(opt, LOCALON, 0);
836 }
837 else
838 {
839 send_telnet_neg(({WONT, option}));
840 opt->state->localside = 0;
841 _call_handler(opt, LOCALOFF, 0);
842 }
843 }
844 else if (opt->lo_wishes->localside == WILL ) {
845 // wir haben schon WILL gesendet, welches der Client jetzt
846 // bestaetigt hat (d.h. die Option ist jetzt auf dieser Seite an),
847 // wir bestaetigen das aber nicht (nochmal).
848 opt->re_wishes->localside = 0;
849 opt->lo_wishes->localside = 0;
850 if (opt->state->localside != 1)
851 {
852 opt->state->localside = 1;
853 _call_handler(opt, LOCALON, 0);
854 }
855 }
856 else {
857 // Mhmm. Wir haben ein WONT gesendet, der Client hat mit DO
858 // geantwortet. Das darf er eigentlich nicht.
859 // TODO: Was tun?
860 send_telnet_neg ( ({WONT, option}) );
861 }
862 // fertig mit DO
863 return;
864 }
865 // bleibt noch SB ueber
866 else if ( command == SB )
867 {
868 opt->re_wishes->sbdata = optargs;
869 _call_handler(opt, SB, optargs);
870 return;
871 } // if ( command == SB )
872}
873
874// wird nur in base.c gerufen, wenn die Verbindung an das Spielerobjekt
875// uebergeben wurde.
876// es uebertraegt unter anderem den Telnet Option Zustand aus login.c (das ist
877// dann previous_object()) in das Spielerobjekt (welches dann this_object())
878// ist!
879protected void
880startup_telnet_negs()
881{
882 int* optargs;
883
884 Set( P_TTY_TYPE, 0 ); //avoid ANY mistakes... Wird unten neu gesetzt.
885 // Daten aus dem Loginobjekt uebertragen. Das ist wichtig! (Dabei wird dann
886 // auch der Status von der letzten Session ueberschrieben.)
887 TN = (mapping) previous_object()->query_telnet_neg();
888 // bevor irgendwas anderes gemacht wird, werden erstmal die Standardhandler
889 // gesetzt. Die sind naemlich in diesem Objekt jetzt erstmal kaputt, weil
890 // sie im Loginobjekt gerufen werden.
891 _bind_telneg_std_handlers();
892 // dann restliche Daten aus dem Loginobjekt holen.
893 Terminals = (string *) previous_object()->query_terminals();
894 Set( P_TTY_COLS, previous_object()->Query(P_TTY_COLS) );
895 Set( P_TTY_ROWS, previous_object()->Query(P_TTY_ROWS) );
896
897 struct telopt_s opt = TN[TELOPT_NAWS];
898 if (optargs = (opt->re_wishes)->sbdata) {
899 eval_naws(optargs);
900 }
901
902 if ( pointerp(Terminals) && sizeof(Terminals)) {
903 if ( Terminals[0][0..3] == "dec-" )
904 Terminals[0] = Terminals[0][4..];
905
906 if ( Terminals[0] == "linux" )
907 Terminals[0] = "vt100";
908
909 Set( P_TTY_TYPE, Terminals[0] );
910 }
Zesstra01b2b742020-01-20 23:53:01 +0100911 // fuer TELOPT_TM jetzt keine Verhandlung anstossen, aber Handler
912 // registrieren.
913 bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
Zesstra57cdbc32020-01-20 23:17:10 +0100914 // und zum Schluss wird der Support fuer CHARSET aktiviert.
915 bind_telneg_handler(TELOPT_CHARSET, #'_std_re_handler_charset,
916 #'_std_lo_handler_charset, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200917}
918
919// somehow completely out of the ordinary options processing/negotiation. But
920// the only purpose is to transmit something over the wire which is not shown,
921// but (likely) answered by the other device.
922protected void send_telnet_timing_mark() {
923 struct telopt_s opt = TN[TELOPT_TM];
924 if (pointerp(opt->data))
925 opt->data[1] = utime();
926 else
927 opt->data = ({ 0, utime() });
928 // absichtlich nicht do_telnet_ne() verwendet, da dies nicht senden wuerde,
929 // haette der Client schonmal mit WILL geantwortet. TELOPT_TM ist aber eine
930 // Option, bei der man das darf...
931 send_telnet_neg( ({DO, TELOPT_TM}) );
932}
933
934/* Is called from the H_PRINT_PROMPT driver hook and appends the IAC EOR if
935 * the client supports it.
936 */
937void print_prompt(string prompt) {
938// if (extern_call() && previous_object()!=this_object())
939// return;
940
941 // ggf. Uhrzeit in den prompt reinschreiben.
942 prompt = regreplace(prompt,"\\t",strftime("%H:%M"),0);
943 // Prompt senden
944 tell_object(this_object(), prompt);
945 // Und EOR senden, falls vom Client gewuenscht.
946 struct telopt_s opt = TN[TELOPT_EOR];
947 if (opt->state->localside == 1)
948 {
949 binary_message(({IAC, EOR}), 1);
950 DTN("tn_eor ",({IAC,EOR}));
951 }
952}
953
954// Helper
955private void eval_naws(int *optargs) {
956 int l, c;
957
958 if ( sizeof(optargs) != 4 )
959 {
960 tell_object(this_object(),
961 break_string( sprintf("Dein Client hat einen Fehler beim"
962 +"Aushandeln der TELOPT_NAWS - er hat"
963 +"IAC SB %O IAC SE gesendet!\n",
964 optargs), 78,
965 "Der GameDriver teilt Dir mit: " ));
966 // und dem Client sagen, dass er den Schrott nicht mehr uebertragen
967 // soll (falls wir das nicht schon gemacht haben).
968 struct telopt_s opt = TN[TELOPT_NAWS];
969 if (opt->state->remoteside == WILL
970 && opt->lo_wishes->remoteside != DONT)
971 send_telnet_neg(( {DONT, TELOPT_NAWS}) );
972 return;
973 }
974
975 if ( interactive(this_object()) ){
976 if ( !optargs[1] )
977 c = optargs[0];
978 else
979 c = optargs[1] + optargs[0] * 256;
980
981 if ( c < 35 ){
982 if (Query(P_TTY_SHOW))
983 tell_object( this_object(),
984 break_string("Dein Fenster ist schmaler als"
985 +" 35 Zeichen? Du scherzt. ;-)"
986 +" Ich benutze den Standardwert"
987 +" von 80 Zeichen.\n", 78,
988 "Der GameDriver teilt Dir mit: ")
989 );
990 c = 80;
991 }
992
993 if ( !optargs[3] )
994 l = optargs[2];
995 else
996 l = 256 * optargs[2] + optargs[3];
997
998 if ( l > 100 ){
999 //TODO: remove
1000 l = 100;
1001 if (Query(P_TTY_SHOW))
1002 tell_object( this_object(),
1003 break_string("Tut mir leid, aber ich kann"
1004 +" nur bis zu 100 Zeilen"
1005 +" verwalten.\n", (c ? c-2 : 78),
1006 "Der GameDriver teilt Dir mit: " )
1007 );
1008 }
1009
1010 if ( l < 3 ){
1011 if (Query(P_TTY_SHOW))
1012 tell_object( this_object(),
1013 break_string("Du willst weniger als drei"
1014 +" Zeilen benutzen? Glaub ich"
1015 +" Dir nicht - ich benutze den"
1016 +" Standardwert von 24"
1017 +" Zeilen.\n", (c ? c-2 : 78),
1018 "Der GameDriver teilt Dir mit: " )
1019 );
1020 l = 24;
1021 }
1022
1023 if ( ((int) Query(P_TTY_ROWS) != l) ||
1024 ((int) Query(P_TTY_COLS) != c) ){
1025 Set( P_TTY_ROWS, l );
1026 Set( P_TTY_COLS, c );
1027
1028 if (Query(P_TTY_SHOW))
1029 tell_object( this_object(),
1030 break_string("Du hast Deine Fenstergroesse auf"
1031 +" "+l+" Zeilen und "+c+
1032 " Spalten geaendert.\n", c-2,
1033 "Der GameDriver teilt Dir mit: ")
1034 );
1035 }
1036 }
1037}
1038
MG Mud User88f12472016-06-24 23:31:02 +02001039// Query-/Set-Methoden
1040// Und wenn hier einer von aussen dran rumpfuscht, werde ich sauer.
1041mapping
1042query_telnet_neg()
1043{
MG Mud User88f12472016-06-24 23:31:02 +02001044 return TN;
1045}
1046
1047// siehe oben
1048string *
1049query_terminals() {
1050 return Terminals;
1051}
1052
1053public int _query_p_lib_telnet_rttime()
1054{
1055 struct telopt_s opt = TN[TELOPT_TM];
1056 if (opt && pointerp(opt->data))
1057 return (opt->data)[0];
1058 return 0;
1059}
1060