blob: 1b175a5ca7c5b1194bddb346c4c4c2ab1b7ca5e9 [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
Zesstrae037c1d2020-02-02 22:02:14 +0100514// Der Handler fuer die CHARSET option, wenn sie auf/fuer unserere Seite
Zesstra57cdbc32020-01-20 23:17:10 +0100515// 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
Zesstrae037c1d2020-02-02 22:02:14 +0100547// Called from the telnetneg handler for TELOPT_STARTTLS to initiate the TLS
548// connection negotiation.
549protected void init_tls()
550{
551 // Dabei muss unser ganzer Telnet-Option-State muss zurueckgesetzt werden.
552 // Ja, wirklich! (Keine Sorge, der client muss das auch tun.)
553 TN = ([]);
554}
555
556#ifdef __TLS__
557// Der Handler fuer STARTTLS, wenn es auf der Clientseite
558// deaktiviert/aktiviert wird. Es wird nur auf der Clientseite aktiviert, der
559// Server darf kein WILL senden. Nach Aktivierung muessen wir ein FOLLOWS
560// senden.
561#define FOLLOWS 1
562private void _std_re_handler_starttls(struct telopt_s opt, int action,
563 int *data)
564{
565 DTN("starttls handler client",({action}));
566
567 // Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
568 // STARTTLS FOLLOWS senden (weil wir haben ihm auch schon ein DO
569 // geschickt). Wir sollen ihm aber jetzt auch ein FOLLOWS senden. Sobald wir
570 // das gesendet haben und ein FOLLOWS erhalten haben, geht die Negotiation
571 // los.
572 if (action == REMOTEON)
573 {
574 send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
575 opt->data = 1; // Nur ein Flag, dass wir es gesendet haben.
576 }
577 else if (action == REMOTEOFF)
578 {
579 // data zuruecksetzen, sonst muessen wir nix machen.
580 opt->data = 0;
581 }
582 else if (action == SB)
583 {
584 if (data[0] == FOLLOWS)
585 {
586 // FOLLOWS empfangen. Wenn wir noch kein FOLLOWS gesendet haben, tun wir
587 // das jetzt.
588 if (!opt->data)
589 send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
590 // Jetzt wird die Verhandlung auf unserer Seite gestartet, der Client
591 // macht das entweder schon oder spaetestens, wenn er unser FOLLOWS
592 // empfangen kann.
593 init_tls();
594 }
595 }
596}
597#undef FOLLOWS
598#endif // __TLS__
Zesstra57cdbc32020-01-20 23:17:10 +0100599
MG Mud User88f12472016-06-24 23:31:02 +0200600// Bindet/registriert Handler fuer die jew. Telnet Option. (Oder loescht sie
601// auch wieder.) Je nach <initneg> wird versucht, die Option neu zu
602// verhandeln.
603protected int bind_telneg_handler(int option, closure re, closure lo,
604 int initneg)
605{
606 struct telopt_s opt = TN[option];
607 if (!structp(opt))
608 {
609 opt = (<telopt_s> option: option,
610 re_wishes: (<to_state_s>),
611 lo_wishes: (<to_state_s>),
612 state: (<to_state_s>)
613 );
614 TN[option] = opt;
615 }
616
617 opt->remotehandler = re;
618 if (initneg)
619 {
620 if (re)
621 do_telnet_neg(option, DO);
622 else
623 do_telnet_neg(option, DONT );
624 }
625
626 opt->localhandler = lo;
627 if (initneg)
628 {
629 if (lo)
630 do_telnet_neg(option, WILL);
631 else
632 do_telnet_neg(option, WONT);
633 }
634 return 1;
635}
636
637
638// Mal unsere Wuensche an den Client schicken und die Standardhandler
Zesstra01b2b742020-01-20 23:53:01 +0100639// registrieren. Hierbei bei Bedarf neue Verhandlungen starten. Es wird hier
640// aber nur ein Basissatz an Optionen verhandelt, der Rest kommt spaeter
641// nachdem das Spielerobjekt die Verbindung hat (in startup_telnet_negs())
MG Mud User88f12472016-06-24 23:31:02 +0200642// Gerufen aus login.c nach Verbindungsaufbau.
643// Bemerkung: das Spielerobjekt bietet evt. noch zusaetzliche Telnetoptionen
644// an, die dann ueber startup_telnet_negs() (im Spielerobjekt)
645// laufen.
646protected void SendTelopts()
647{
Zesstrae037c1d2020-02-02 22:02:14 +0100648#if __TLS__
649 // If this is a non-TLS-connection, we offer STARTTLS, but wait for the
650 // client to ask for it.
651 if (tls_available() && tls_query_connection_state() == 0)
652 {
653 bind_telneg_handler(TELOPT_STARTTLS, #'_std_re_handler_starttls,
654 0, 0);
655 }
656#endif
Zesstra65e9f1a2020-01-17 22:50:14 +0100657 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
658 #'_std_lo_handler_binary, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200659 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200660 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 1);
661 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 1);
662 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 1);
Zesstra65e9f1a2020-01-17 22:50:14 +0100663 if (find_object("/secure/misc/mssp"))
664 bind_telneg_handler(TELOPT_MSSP, 0, #'_std_lo_handler_mssp, 1);
Zesstra57cdbc32020-01-20 23:17:10 +0100665 // und auch CHARSET wird verzoegert bis das Spielerobjekt da ist.
MG Mud User88f12472016-06-24 23:31:02 +0200666}
667
MG Mud User88f12472016-06-24 23:31:02 +0200668// Bindet die Standardhandler _aus diesem_ Programm (und ueberschreibt dabei
669// ggf. andere). Hierbei werden nur die Handler neu gebunden, keine neuen
670// Verhandlungen initiiert.
671// gerufen aus base.c indirekt via startup_telnet_negs().
Zesstrae037c1d2020-02-02 22:02:14 +0100672protected void _bind_telneg_std_handlers()
673{
674 // BTW: es ist absicht, im Spielerobjekt keinen Support fuer STARTTLS mehr
675 // anzubieten.
Zesstra65e9f1a2020-01-17 22:50:14 +0100676 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
677 #'_std_lo_handler_binary, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200678 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200679 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 0);
680 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 0);
681 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 0);
Zesstra65e9f1a2020-01-17 22:50:14 +0100682 // Besondere Situation: MSSP ist nach Spielerlogin eigentlich uninteressant.
683 // Daher sparen wir uns das im Kontext des Spielerobjekts und schalten es
684 // einfach wieder aus.
685 bind_telneg_handler(TELOPT_MSSP, 0, 0, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200686}
687
688
689// Ruft die entsprechenden handler von der Telnet Option.
690// Wenn es keinen handler (mehr) gibt, wird die Option auch auf der jeweiligen
691// Seite ausgeschaltet. Deshalb MUSS lo_wishes und re_wishes vom Aufrufer VOR
692// DEM AUFRUF zurueckgesetzt worden sein!
693// <action>: 'LOCALON': Option wurde auf unserer Seite eingeschaltet
694// 'LOCALOFF': Option wurde auf unserer Seite ausgeschaltet
695// 'REMOTEON': Option wurde auf Clientseite eingeschaltet
696// 'REMOTEOFF': Option wurde auf Clientseite ausgeschaltet
697// 'SB': Suboption negotiation Daten wurden empfangen
698// <data>: die per SB empfangenen Daten (unverarbeitet)
699private void _call_handler(struct telopt_s opt, int action, int *data) {
700 switch(action)
701 {
702 case REMOTEON:
703 case REMOTEOFF:
704 case SB:
705 if (opt->remotehandler)
706 {
707 funcall(opt->remotehandler, opt, action, data);
708 }
709 else
710 {
711 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
712 // dass nur verhandelt wird, wenn die Option an ist.)
713 do_telnet_neg( opt->option, DONT );
714 }
715 break;
716 case LOCALON:
717 case LOCALOFF:
718 if (opt->localhandler)
719 {
720 funcall(opt->localhandler, opt, action);
721 }
722 else
723 {
724 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
725 // dass nur verhandelt wird, wenn die Option an ist.)
726 do_telnet_neg( opt->option, WONT );
727 }
728 break;
729 }
730}
731
732// Gerufen vom Driver, wenn neue telnet options reinkommen.
733void
734telnet_neg(int command, int option, int *optargs)
735{
736 DTN("recv_tn: ", ({IAC, command, option}) + (optargs||({})));
737
738 struct telopt_s opt = TN[option];
739 if (!structp(opt))
740 {
741 opt = (<telopt_s> option: option,
742 re_wishes: (<to_state_s>),
743 lo_wishes: (<to_state_s>),
744 state: (<to_state_s>)
745 );
746 TN[option] = opt;
747 }
748
749 // Was will der Client tun?
750 if (command == WONT)
751 {
752 // Client will die Option auf seiner Seite abschalten. Wir MUESSEN das
753 // akzeptieren.
754 // Wir muessen das allerdings ignorieren, wenn die Option bereits aus
755 // ist.
756 if (opt->state->remoteside==0)
757 {
758 // Ausnahme fuer TELOPT_TM, da das kaum ein Client kann und fuer RTT
759 // es eigentlich auch egal ist, was zurueck kommt: der handler wird
760 // zumindest doch gerufen zum Ausrechnen der RTT
761 if (option == TELOPT_TM)
762 _call_handler(opt, REMOTEOFF, 0);
763 // ansonsten aber wirklich ignorieren. ;)
764 return;
765 }
766 opt->re_wishes->remoteside = command;
767 // Bestaetigung auf ein WONT senden, wenn wir nicht selber schon ein
768 // DONT geschickt hatten.
769 if (opt->lo_wishes->remoteside != DONT) {
770 send_telnet_neg( ({DONT, option}) );
771 }
772 // Wir haben jetzt auf jeden Fall ein DONT gesendet und ein WONT
773 // erhalten. Damit ist die Option jetzt auf der clientseite aus.
774 // Ausserdem setzen wir die Wishes zurueck.
775 opt->re_wishes->remoteside = 0;
776 opt->lo_wishes->remoteside = 0;
777 if (opt->state->remoteside != 0)
778 {
779 opt->state->remoteside = 0;
780 _call_handler(opt, REMOTEOFF, 0);
781 }
782 } // WONT vom Client verarbeitet
783 else if ( command == WILL)
784 {
785 // Wenn die Option bereits an ist, muessen wir dies ignorieren.
786 if (opt->state->remoteside == 1)
787 {
788 // Ausnahme fuer TELOPT_TM, der handler wird zumindest doch gerufen
789 // zum Ausrechnen der RTT. Diese Option laesst sich ohnehin
790 // aktivieren, auch wenn sie schon an ist.
791 if (option == TELOPT_TM)
792 _call_handler(opt, REMOTEON, 0);
793 // sonst aber wirklich ignorieren. ;-)
794 return;
795 }
796 opt->re_wishes->remoteside = command;
797 if ( opt->lo_wishes->remoteside == 0 )
798 {
799 // Der Client will, wir haben noch nix dazu gesagt. (Mit unserer
800 // Antwort ist die Verhandlung uebrigens beendet.)
801 // Wenn es einen remotehandler fuer die Option gibt, schalten wir
802 // sie ein...
803 if (opt->remotehandler)
804 {
805 send_telnet_neg(({DO, option}));
806 // Option jetzt an der Clientseite an.
807 opt->re_wishes->remoteside = 0;
808 opt->lo_wishes->remoteside = 0;
809 if (opt->state->remoteside != 1)
810 {
811 opt->state->remoteside = 1;
812 _call_handler(opt, REMOTEON, 0);
813 }
814 }
815 else
816 {
817 // sonst verweigern wir das einschalten (die meisten Optionen
818 // auf Clientseite sind fuer uns eh egal).
819 send_telnet_neg(({DONT, option}));
820 // Option jetzt an der Clientseite aus.
821 opt->re_wishes->remoteside = 0;
822 opt->lo_wishes->remoteside = 0;
823 if (opt->state->remoteside != 0)
824 {
825 opt->state->remoteside = 0;
826 _call_handler(opt, REMOTEOFF, 0);
827 }
828 }
829 }
830 else if ( opt->lo_wishes->remoteside == DO)
831 {
832 // Wir haben haben bereits per DO angefordert, d.h. das ist die
833 // Clientbestaetigung - wir duerfen nicht bestaetigen und die
834 // Option ist jetzt clientseitig aktiv. Verhandlung beendet.
835 opt->re_wishes->remoteside = 0;
836 opt->lo_wishes->remoteside = 0;
837 if (opt->state->remoteside != 1)
838 {
839 opt->state->remoteside = 1;
840 _call_handler(opt, REMOTEON, 0);
841 }
842 } // if (DO)
843 else {
844 // Mhmm. Wir hatten ein DONT gesendet, aber der Client hat mit WILL
845 // geantwortet. Das darf er eigentlich gar nicht.
846 //TODO: was sollte man jetzt eigentlich tun? Erstmal wiederholen wir
847 //das DONT...
848 send_telnet_neg( ({DONT, option}) );
849 }
850
851 return;
852 } // WILL vom Client verarbeitet
853 // Was sollen wir (nicht) fuer den Client tun?
854 else if ( command == DONT)
855 {
856 // Client will, dass wir etwas nicht tun. Wir MUESSEN das akzeptieren.
857 // wenn die Option auf unserer Seite aber schon aus ist, muessen wir
858 // dies ignorieren.
859 if (opt->state->localside == 0)
860 return;
861
862 opt->re_wishes->localside = command;
863 // Wenn wir noch kein WONT gesendet haben, senden wir das jetzt als
864 // Bestaetigung.
865 if (opt->lo_wishes->localside = WONT)
866 send_telnet_neg( ({WONT, option}) );
867 // Verhandlung beendet, Option is auf unserer Seite jetzt aus.
868 // Wuensche auch wieder zuruecksetzen.
869 opt->re_wishes->localside = 0;
870 opt->lo_wishes->localside = 0;
871 if (opt->state->localside != 0)
872 {
873 opt->state->localside = 0;
874 _call_handler(opt, LOCALOFF, 0);
875 }
876 }
877 else if ( command == DO )
878 {
879 // Client will, dass wir option tun. Mal schauen, wie wir dazu stehen.
880 // wenn die Option auf unserer Seite aber schon an ist, muessen wir
881 // dies ignorieren.
882 if (opt->state->localside == 1)
883 return;
884
885 opt->re_wishes->localside = command;
886
887 if ( opt->lo_wishes->localside == 0 ) {
888 // wir haben unsere Wuensche noch nicht geaeussert. Sobald wir
889 // bestaetigen, ist die Option auf unserer Seite an/aus und die
890 // Verhandlungen beendet.
891 // in jedem Fall die Wuensche zuruecksetzen
892 opt->re_wishes->localside = 0;
893 opt->lo_wishes->localside = 0;
894 if (opt->localhandler)
895 {
896 send_telnet_neg(({WILL, option}));
897 opt->state->localside = 1;
898 _call_handler(opt, LOCALON, 0);
899 }
900 else
901 {
902 send_telnet_neg(({WONT, option}));
903 opt->state->localside = 0;
904 _call_handler(opt, LOCALOFF, 0);
905 }
906 }
907 else if (opt->lo_wishes->localside == WILL ) {
908 // wir haben schon WILL gesendet, welches der Client jetzt
909 // bestaetigt hat (d.h. die Option ist jetzt auf dieser Seite an),
910 // wir bestaetigen das aber nicht (nochmal).
911 opt->re_wishes->localside = 0;
912 opt->lo_wishes->localside = 0;
913 if (opt->state->localside != 1)
914 {
915 opt->state->localside = 1;
916 _call_handler(opt, LOCALON, 0);
917 }
918 }
919 else {
920 // Mhmm. Wir haben ein WONT gesendet, der Client hat mit DO
921 // geantwortet. Das darf er eigentlich nicht.
922 // TODO: Was tun?
923 send_telnet_neg ( ({WONT, option}) );
924 }
925 // fertig mit DO
926 return;
927 }
928 // bleibt noch SB ueber
929 else if ( command == SB )
930 {
931 opt->re_wishes->sbdata = optargs;
932 _call_handler(opt, SB, optargs);
933 return;
934 } // if ( command == SB )
935}
936
937// wird nur in base.c gerufen, wenn die Verbindung an das Spielerobjekt
938// uebergeben wurde.
939// es uebertraegt unter anderem den Telnet Option Zustand aus login.c (das ist
940// dann previous_object()) in das Spielerobjekt (welches dann this_object())
941// ist!
942protected void
943startup_telnet_negs()
944{
945 int* optargs;
946
947 Set( P_TTY_TYPE, 0 ); //avoid ANY mistakes... Wird unten neu gesetzt.
948 // Daten aus dem Loginobjekt uebertragen. Das ist wichtig! (Dabei wird dann
949 // auch der Status von der letzten Session ueberschrieben.)
950 TN = (mapping) previous_object()->query_telnet_neg();
951 // bevor irgendwas anderes gemacht wird, werden erstmal die Standardhandler
952 // gesetzt. Die sind naemlich in diesem Objekt jetzt erstmal kaputt, weil
953 // sie im Loginobjekt gerufen werden.
954 _bind_telneg_std_handlers();
955 // dann restliche Daten aus dem Loginobjekt holen.
956 Terminals = (string *) previous_object()->query_terminals();
957 Set( P_TTY_COLS, previous_object()->Query(P_TTY_COLS) );
958 Set( P_TTY_ROWS, previous_object()->Query(P_TTY_ROWS) );
959
960 struct telopt_s opt = TN[TELOPT_NAWS];
961 if (optargs = (opt->re_wishes)->sbdata) {
962 eval_naws(optargs);
963 }
964
965 if ( pointerp(Terminals) && sizeof(Terminals)) {
966 if ( Terminals[0][0..3] == "dec-" )
967 Terminals[0] = Terminals[0][4..];
968
969 if ( Terminals[0] == "linux" )
970 Terminals[0] = "vt100";
971
972 Set( P_TTY_TYPE, Terminals[0] );
973 }
Zesstra01b2b742020-01-20 23:53:01 +0100974 // fuer TELOPT_TM jetzt keine Verhandlung anstossen, aber Handler
975 // registrieren.
976 bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
Zesstra57cdbc32020-01-20 23:17:10 +0100977 // und zum Schluss wird der Support fuer CHARSET aktiviert.
978 bind_telneg_handler(TELOPT_CHARSET, #'_std_re_handler_charset,
979 #'_std_lo_handler_charset, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200980}
981
982// somehow completely out of the ordinary options processing/negotiation. But
983// the only purpose is to transmit something over the wire which is not shown,
984// but (likely) answered by the other device.
985protected void send_telnet_timing_mark() {
986 struct telopt_s opt = TN[TELOPT_TM];
987 if (pointerp(opt->data))
988 opt->data[1] = utime();
989 else
990 opt->data = ({ 0, utime() });
991 // absichtlich nicht do_telnet_ne() verwendet, da dies nicht senden wuerde,
992 // haette der Client schonmal mit WILL geantwortet. TELOPT_TM ist aber eine
993 // Option, bei der man das darf...
994 send_telnet_neg( ({DO, TELOPT_TM}) );
995}
996
997/* Is called from the H_PRINT_PROMPT driver hook and appends the IAC EOR if
998 * the client supports it.
999 */
1000void print_prompt(string prompt) {
1001// if (extern_call() && previous_object()!=this_object())
1002// return;
1003
1004 // ggf. Uhrzeit in den prompt reinschreiben.
1005 prompt = regreplace(prompt,"\\t",strftime("%H:%M"),0);
1006 // Prompt senden
1007 tell_object(this_object(), prompt);
1008 // Und EOR senden, falls vom Client gewuenscht.
1009 struct telopt_s opt = TN[TELOPT_EOR];
1010 if (opt->state->localside == 1)
1011 {
1012 binary_message(({IAC, EOR}), 1);
1013 DTN("tn_eor ",({IAC,EOR}));
1014 }
1015}
1016
1017// Helper
1018private void eval_naws(int *optargs) {
1019 int l, c;
1020
1021 if ( sizeof(optargs) != 4 )
1022 {
1023 tell_object(this_object(),
1024 break_string( sprintf("Dein Client hat einen Fehler beim"
1025 +"Aushandeln der TELOPT_NAWS - er hat"
1026 +"IAC SB %O IAC SE gesendet!\n",
1027 optargs), 78,
1028 "Der GameDriver teilt Dir mit: " ));
1029 // und dem Client sagen, dass er den Schrott nicht mehr uebertragen
1030 // soll (falls wir das nicht schon gemacht haben).
1031 struct telopt_s opt = TN[TELOPT_NAWS];
1032 if (opt->state->remoteside == WILL
1033 && opt->lo_wishes->remoteside != DONT)
1034 send_telnet_neg(( {DONT, TELOPT_NAWS}) );
1035 return;
1036 }
1037
1038 if ( interactive(this_object()) ){
1039 if ( !optargs[1] )
1040 c = optargs[0];
1041 else
1042 c = optargs[1] + optargs[0] * 256;
1043
1044 if ( c < 35 ){
1045 if (Query(P_TTY_SHOW))
1046 tell_object( this_object(),
1047 break_string("Dein Fenster ist schmaler als"
1048 +" 35 Zeichen? Du scherzt. ;-)"
1049 +" Ich benutze den Standardwert"
1050 +" von 80 Zeichen.\n", 78,
1051 "Der GameDriver teilt Dir mit: ")
1052 );
1053 c = 80;
1054 }
1055
1056 if ( !optargs[3] )
1057 l = optargs[2];
1058 else
1059 l = 256 * optargs[2] + optargs[3];
1060
1061 if ( l > 100 ){
1062 //TODO: remove
1063 l = 100;
1064 if (Query(P_TTY_SHOW))
1065 tell_object( this_object(),
1066 break_string("Tut mir leid, aber ich kann"
1067 +" nur bis zu 100 Zeilen"
1068 +" verwalten.\n", (c ? c-2 : 78),
1069 "Der GameDriver teilt Dir mit: " )
1070 );
1071 }
1072
1073 if ( l < 3 ){
1074 if (Query(P_TTY_SHOW))
1075 tell_object( this_object(),
1076 break_string("Du willst weniger als drei"
1077 +" Zeilen benutzen? Glaub ich"
1078 +" Dir nicht - ich benutze den"
1079 +" Standardwert von 24"
1080 +" Zeilen.\n", (c ? c-2 : 78),
1081 "Der GameDriver teilt Dir mit: " )
1082 );
1083 l = 24;
1084 }
1085
1086 if ( ((int) Query(P_TTY_ROWS) != l) ||
1087 ((int) Query(P_TTY_COLS) != c) ){
1088 Set( P_TTY_ROWS, l );
1089 Set( P_TTY_COLS, c );
1090
1091 if (Query(P_TTY_SHOW))
1092 tell_object( this_object(),
1093 break_string("Du hast Deine Fenstergroesse auf"
1094 +" "+l+" Zeilen und "+c+
1095 " Spalten geaendert.\n", c-2,
1096 "Der GameDriver teilt Dir mit: ")
1097 );
1098 }
1099 }
1100}
1101
MG Mud User88f12472016-06-24 23:31:02 +02001102// Query-/Set-Methoden
1103// Und wenn hier einer von aussen dran rumpfuscht, werde ich sauer.
1104mapping
1105query_telnet_neg()
1106{
MG Mud User88f12472016-06-24 23:31:02 +02001107 return TN;
1108}
1109
1110// siehe oben
1111string *
1112query_terminals() {
1113 return Terminals;
1114}
1115
1116public int _query_p_lib_telnet_rttime()
1117{
1118 struct telopt_s opt = TN[TELOPT_TM];
1119 if (opt && pointerp(opt->data))
1120 return (opt->data)[0];
1121 return 0;
1122}
1123