blob: 4fea5d0ca2698ba190b6635edd11117967ddfd14 [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
587// registrieren. Hierbei bei Bedarf neue Verhandlungen starten.
588// Gerufen aus login.c nach Verbindungsaufbau.
589// Bemerkung: das Spielerobjekt bietet evt. noch zusaetzliche Telnetoptionen
590// an, die dann ueber startup_telnet_negs() (im Spielerobjekt)
591// laufen.
592protected void SendTelopts()
593{
Zesstra65e9f1a2020-01-17 22:50:14 +0100594 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
595 #'_std_lo_handler_binary, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200596 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200597 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 1);
598 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 1);
599 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 1);
Zesstra65e9f1a2020-01-17 22:50:14 +0100600 if (find_object("/secure/misc/mssp"))
601 bind_telneg_handler(TELOPT_MSSP, 0, #'_std_lo_handler_mssp, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200602 // fuer TELOPT_TM jetzt keine Verhandlung anstossen.
603 bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
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);
619 bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
Zesstra65e9f1a2020-01-17 22:50:14 +0100620 // Besondere Situation: MSSP ist nach Spielerlogin eigentlich uninteressant.
621 // Daher sparen wir uns das im Kontext des Spielerobjekts und schalten es
622 // einfach wieder aus.
623 bind_telneg_handler(TELOPT_MSSP, 0, 0, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200624}
625
626
627// Ruft die entsprechenden handler von der Telnet Option.
628// Wenn es keinen handler (mehr) gibt, wird die Option auch auf der jeweiligen
629// Seite ausgeschaltet. Deshalb MUSS lo_wishes und re_wishes vom Aufrufer VOR
630// DEM AUFRUF zurueckgesetzt worden sein!
631// <action>: 'LOCALON': Option wurde auf unserer Seite eingeschaltet
632// 'LOCALOFF': Option wurde auf unserer Seite ausgeschaltet
633// 'REMOTEON': Option wurde auf Clientseite eingeschaltet
634// 'REMOTEOFF': Option wurde auf Clientseite ausgeschaltet
635// 'SB': Suboption negotiation Daten wurden empfangen
636// <data>: die per SB empfangenen Daten (unverarbeitet)
637private void _call_handler(struct telopt_s opt, int action, int *data) {
638 switch(action)
639 {
640 case REMOTEON:
641 case REMOTEOFF:
642 case SB:
643 if (opt->remotehandler)
644 {
645 funcall(opt->remotehandler, opt, action, data);
646 }
647 else
648 {
649 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
650 // dass nur verhandelt wird, wenn die Option an ist.)
651 do_telnet_neg( opt->option, DONT );
652 }
653 break;
654 case LOCALON:
655 case LOCALOFF:
656 if (opt->localhandler)
657 {
658 funcall(opt->localhandler, opt, action);
659 }
660 else
661 {
662 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
663 // dass nur verhandelt wird, wenn die Option an ist.)
664 do_telnet_neg( opt->option, WONT );
665 }
666 break;
667 }
668}
669
670// Gerufen vom Driver, wenn neue telnet options reinkommen.
671void
672telnet_neg(int command, int option, int *optargs)
673{
674 DTN("recv_tn: ", ({IAC, command, option}) + (optargs||({})));
675
676 struct telopt_s opt = TN[option];
677 if (!structp(opt))
678 {
679 opt = (<telopt_s> option: option,
680 re_wishes: (<to_state_s>),
681 lo_wishes: (<to_state_s>),
682 state: (<to_state_s>)
683 );
684 TN[option] = opt;
685 }
686
687 // Was will der Client tun?
688 if (command == WONT)
689 {
690 // Client will die Option auf seiner Seite abschalten. Wir MUESSEN das
691 // akzeptieren.
692 // Wir muessen das allerdings ignorieren, wenn die Option bereits aus
693 // ist.
694 if (opt->state->remoteside==0)
695 {
696 // Ausnahme fuer TELOPT_TM, da das kaum ein Client kann und fuer RTT
697 // es eigentlich auch egal ist, was zurueck kommt: der handler wird
698 // zumindest doch gerufen zum Ausrechnen der RTT
699 if (option == TELOPT_TM)
700 _call_handler(opt, REMOTEOFF, 0);
701 // ansonsten aber wirklich ignorieren. ;)
702 return;
703 }
704 opt->re_wishes->remoteside = command;
705 // Bestaetigung auf ein WONT senden, wenn wir nicht selber schon ein
706 // DONT geschickt hatten.
707 if (opt->lo_wishes->remoteside != DONT) {
708 send_telnet_neg( ({DONT, option}) );
709 }
710 // Wir haben jetzt auf jeden Fall ein DONT gesendet und ein WONT
711 // erhalten. Damit ist die Option jetzt auf der clientseite aus.
712 // Ausserdem setzen wir die Wishes zurueck.
713 opt->re_wishes->remoteside = 0;
714 opt->lo_wishes->remoteside = 0;
715 if (opt->state->remoteside != 0)
716 {
717 opt->state->remoteside = 0;
718 _call_handler(opt, REMOTEOFF, 0);
719 }
720 } // WONT vom Client verarbeitet
721 else if ( command == WILL)
722 {
723 // Wenn die Option bereits an ist, muessen wir dies ignorieren.
724 if (opt->state->remoteside == 1)
725 {
726 // Ausnahme fuer TELOPT_TM, der handler wird zumindest doch gerufen
727 // zum Ausrechnen der RTT. Diese Option laesst sich ohnehin
728 // aktivieren, auch wenn sie schon an ist.
729 if (option == TELOPT_TM)
730 _call_handler(opt, REMOTEON, 0);
731 // sonst aber wirklich ignorieren. ;-)
732 return;
733 }
734 opt->re_wishes->remoteside = command;
735 if ( opt->lo_wishes->remoteside == 0 )
736 {
737 // Der Client will, wir haben noch nix dazu gesagt. (Mit unserer
738 // Antwort ist die Verhandlung uebrigens beendet.)
739 // Wenn es einen remotehandler fuer die Option gibt, schalten wir
740 // sie ein...
741 if (opt->remotehandler)
742 {
743 send_telnet_neg(({DO, option}));
744 // Option jetzt an der Clientseite an.
745 opt->re_wishes->remoteside = 0;
746 opt->lo_wishes->remoteside = 0;
747 if (opt->state->remoteside != 1)
748 {
749 opt->state->remoteside = 1;
750 _call_handler(opt, REMOTEON, 0);
751 }
752 }
753 else
754 {
755 // sonst verweigern wir das einschalten (die meisten Optionen
756 // auf Clientseite sind fuer uns eh egal).
757 send_telnet_neg(({DONT, option}));
758 // Option jetzt an der Clientseite aus.
759 opt->re_wishes->remoteside = 0;
760 opt->lo_wishes->remoteside = 0;
761 if (opt->state->remoteside != 0)
762 {
763 opt->state->remoteside = 0;
764 _call_handler(opt, REMOTEOFF, 0);
765 }
766 }
767 }
768 else if ( opt->lo_wishes->remoteside == DO)
769 {
770 // Wir haben haben bereits per DO angefordert, d.h. das ist die
771 // Clientbestaetigung - wir duerfen nicht bestaetigen und die
772 // Option ist jetzt clientseitig aktiv. Verhandlung beendet.
773 opt->re_wishes->remoteside = 0;
774 opt->lo_wishes->remoteside = 0;
775 if (opt->state->remoteside != 1)
776 {
777 opt->state->remoteside = 1;
778 _call_handler(opt, REMOTEON, 0);
779 }
780 } // if (DO)
781 else {
782 // Mhmm. Wir hatten ein DONT gesendet, aber der Client hat mit WILL
783 // geantwortet. Das darf er eigentlich gar nicht.
784 //TODO: was sollte man jetzt eigentlich tun? Erstmal wiederholen wir
785 //das DONT...
786 send_telnet_neg( ({DONT, option}) );
787 }
788
789 return;
790 } // WILL vom Client verarbeitet
791 // Was sollen wir (nicht) fuer den Client tun?
792 else if ( command == DONT)
793 {
794 // Client will, dass wir etwas nicht tun. Wir MUESSEN das akzeptieren.
795 // wenn die Option auf unserer Seite aber schon aus ist, muessen wir
796 // dies ignorieren.
797 if (opt->state->localside == 0)
798 return;
799
800 opt->re_wishes->localside = command;
801 // Wenn wir noch kein WONT gesendet haben, senden wir das jetzt als
802 // Bestaetigung.
803 if (opt->lo_wishes->localside = WONT)
804 send_telnet_neg( ({WONT, option}) );
805 // Verhandlung beendet, Option is auf unserer Seite jetzt aus.
806 // Wuensche auch wieder zuruecksetzen.
807 opt->re_wishes->localside = 0;
808 opt->lo_wishes->localside = 0;
809 if (opt->state->localside != 0)
810 {
811 opt->state->localside = 0;
812 _call_handler(opt, LOCALOFF, 0);
813 }
814 }
815 else if ( command == DO )
816 {
817 // Client will, dass wir option tun. Mal schauen, wie wir dazu stehen.
818 // wenn die Option auf unserer Seite aber schon an ist, muessen wir
819 // dies ignorieren.
820 if (opt->state->localside == 1)
821 return;
822
823 opt->re_wishes->localside = command;
824
825 if ( opt->lo_wishes->localside == 0 ) {
826 // wir haben unsere Wuensche noch nicht geaeussert. Sobald wir
827 // bestaetigen, ist die Option auf unserer Seite an/aus und die
828 // Verhandlungen beendet.
829 // in jedem Fall die Wuensche zuruecksetzen
830 opt->re_wishes->localside = 0;
831 opt->lo_wishes->localside = 0;
832 if (opt->localhandler)
833 {
834 send_telnet_neg(({WILL, option}));
835 opt->state->localside = 1;
836 _call_handler(opt, LOCALON, 0);
837 }
838 else
839 {
840 send_telnet_neg(({WONT, option}));
841 opt->state->localside = 0;
842 _call_handler(opt, LOCALOFF, 0);
843 }
844 }
845 else if (opt->lo_wishes->localside == WILL ) {
846 // wir haben schon WILL gesendet, welches der Client jetzt
847 // bestaetigt hat (d.h. die Option ist jetzt auf dieser Seite an),
848 // wir bestaetigen das aber nicht (nochmal).
849 opt->re_wishes->localside = 0;
850 opt->lo_wishes->localside = 0;
851 if (opt->state->localside != 1)
852 {
853 opt->state->localside = 1;
854 _call_handler(opt, LOCALON, 0);
855 }
856 }
857 else {
858 // Mhmm. Wir haben ein WONT gesendet, der Client hat mit DO
859 // geantwortet. Das darf er eigentlich nicht.
860 // TODO: Was tun?
861 send_telnet_neg ( ({WONT, option}) );
862 }
863 // fertig mit DO
864 return;
865 }
866 // bleibt noch SB ueber
867 else if ( command == SB )
868 {
869 opt->re_wishes->sbdata = optargs;
870 _call_handler(opt, SB, optargs);
871 return;
872 } // if ( command == SB )
873}
874
875// wird nur in base.c gerufen, wenn die Verbindung an das Spielerobjekt
876// uebergeben wurde.
877// es uebertraegt unter anderem den Telnet Option Zustand aus login.c (das ist
878// dann previous_object()) in das Spielerobjekt (welches dann this_object())
879// ist!
880protected void
881startup_telnet_negs()
882{
883 int* optargs;
884
885 Set( P_TTY_TYPE, 0 ); //avoid ANY mistakes... Wird unten neu gesetzt.
886 // Daten aus dem Loginobjekt uebertragen. Das ist wichtig! (Dabei wird dann
887 // auch der Status von der letzten Session ueberschrieben.)
888 TN = (mapping) previous_object()->query_telnet_neg();
889 // bevor irgendwas anderes gemacht wird, werden erstmal die Standardhandler
890 // gesetzt. Die sind naemlich in diesem Objekt jetzt erstmal kaputt, weil
891 // sie im Loginobjekt gerufen werden.
892 _bind_telneg_std_handlers();
893 // dann restliche Daten aus dem Loginobjekt holen.
894 Terminals = (string *) previous_object()->query_terminals();
895 Set( P_TTY_COLS, previous_object()->Query(P_TTY_COLS) );
896 Set( P_TTY_ROWS, previous_object()->Query(P_TTY_ROWS) );
897
898 struct telopt_s opt = TN[TELOPT_NAWS];
899 if (optargs = (opt->re_wishes)->sbdata) {
900 eval_naws(optargs);
901 }
902
903 if ( pointerp(Terminals) && sizeof(Terminals)) {
904 if ( Terminals[0][0..3] == "dec-" )
905 Terminals[0] = Terminals[0][4..];
906
907 if ( Terminals[0] == "linux" )
908 Terminals[0] = "vt100";
909
910 Set( P_TTY_TYPE, Terminals[0] );
911 }
Zesstra57cdbc32020-01-20 23:17:10 +0100912 // und zum Schluss wird der Support fuer CHARSET aktiviert.
913 bind_telneg_handler(TELOPT_CHARSET, #'_std_re_handler_charset,
914 #'_std_lo_handler_charset, 1);
915
MG Mud User88f12472016-06-24 23:31:02 +0200916}
917
918// somehow completely out of the ordinary options processing/negotiation. But
919// the only purpose is to transmit something over the wire which is not shown,
920// but (likely) answered by the other device.
921protected void send_telnet_timing_mark() {
922 struct telopt_s opt = TN[TELOPT_TM];
923 if (pointerp(opt->data))
924 opt->data[1] = utime();
925 else
926 opt->data = ({ 0, utime() });
927 // absichtlich nicht do_telnet_ne() verwendet, da dies nicht senden wuerde,
928 // haette der Client schonmal mit WILL geantwortet. TELOPT_TM ist aber eine
929 // Option, bei der man das darf...
930 send_telnet_neg( ({DO, TELOPT_TM}) );
931}
932
933/* Is called from the H_PRINT_PROMPT driver hook and appends the IAC EOR if
934 * the client supports it.
935 */
936void print_prompt(string prompt) {
937// if (extern_call() && previous_object()!=this_object())
938// return;
939
940 // ggf. Uhrzeit in den prompt reinschreiben.
941 prompt = regreplace(prompt,"\\t",strftime("%H:%M"),0);
942 // Prompt senden
943 tell_object(this_object(), prompt);
944 // Und EOR senden, falls vom Client gewuenscht.
945 struct telopt_s opt = TN[TELOPT_EOR];
946 if (opt->state->localside == 1)
947 {
948 binary_message(({IAC, EOR}), 1);
949 DTN("tn_eor ",({IAC,EOR}));
950 }
951}
952
953// Helper
954private void eval_naws(int *optargs) {
955 int l, c;
956
957 if ( sizeof(optargs) != 4 )
958 {
959 tell_object(this_object(),
960 break_string( sprintf("Dein Client hat einen Fehler beim"
961 +"Aushandeln der TELOPT_NAWS - er hat"
962 +"IAC SB %O IAC SE gesendet!\n",
963 optargs), 78,
964 "Der GameDriver teilt Dir mit: " ));
965 // und dem Client sagen, dass er den Schrott nicht mehr uebertragen
966 // soll (falls wir das nicht schon gemacht haben).
967 struct telopt_s opt = TN[TELOPT_NAWS];
968 if (opt->state->remoteside == WILL
969 && opt->lo_wishes->remoteside != DONT)
970 send_telnet_neg(( {DONT, TELOPT_NAWS}) );
971 return;
972 }
973
974 if ( interactive(this_object()) ){
975 if ( !optargs[1] )
976 c = optargs[0];
977 else
978 c = optargs[1] + optargs[0] * 256;
979
980 if ( c < 35 ){
981 if (Query(P_TTY_SHOW))
982 tell_object( this_object(),
983 break_string("Dein Fenster ist schmaler als"
984 +" 35 Zeichen? Du scherzt. ;-)"
985 +" Ich benutze den Standardwert"
986 +" von 80 Zeichen.\n", 78,
987 "Der GameDriver teilt Dir mit: ")
988 );
989 c = 80;
990 }
991
992 if ( !optargs[3] )
993 l = optargs[2];
994 else
995 l = 256 * optargs[2] + optargs[3];
996
997 if ( l > 100 ){
998 //TODO: remove
999 l = 100;
1000 if (Query(P_TTY_SHOW))
1001 tell_object( this_object(),
1002 break_string("Tut mir leid, aber ich kann"
1003 +" nur bis zu 100 Zeilen"
1004 +" verwalten.\n", (c ? c-2 : 78),
1005 "Der GameDriver teilt Dir mit: " )
1006 );
1007 }
1008
1009 if ( l < 3 ){
1010 if (Query(P_TTY_SHOW))
1011 tell_object( this_object(),
1012 break_string("Du willst weniger als drei"
1013 +" Zeilen benutzen? Glaub ich"
1014 +" Dir nicht - ich benutze den"
1015 +" Standardwert von 24"
1016 +" Zeilen.\n", (c ? c-2 : 78),
1017 "Der GameDriver teilt Dir mit: " )
1018 );
1019 l = 24;
1020 }
1021
1022 if ( ((int) Query(P_TTY_ROWS) != l) ||
1023 ((int) Query(P_TTY_COLS) != c) ){
1024 Set( P_TTY_ROWS, l );
1025 Set( P_TTY_COLS, c );
1026
1027 if (Query(P_TTY_SHOW))
1028 tell_object( this_object(),
1029 break_string("Du hast Deine Fenstergroesse auf"
1030 +" "+l+" Zeilen und "+c+
1031 " Spalten geaendert.\n", c-2,
1032 "Der GameDriver teilt Dir mit: ")
1033 );
1034 }
1035 }
1036}
1037
MG Mud User88f12472016-06-24 23:31:02 +02001038// Query-/Set-Methoden
1039// Und wenn hier einer von aussen dran rumpfuscht, werde ich sauer.
1040mapping
1041query_telnet_neg()
1042{
MG Mud User88f12472016-06-24 23:31:02 +02001043 return TN;
1044}
1045
1046// siehe oben
1047string *
1048query_terminals() {
1049 return Terminals;
1050}
1051
1052public int _query_p_lib_telnet_rttime()
1053{
1054 struct telopt_s opt = TN[TELOPT_TM];
1055 if (opt && pointerp(opt->data))
1056 return (opt->data)[0];
1057 return 0;
1058}
1059