blob: f09a681116bbdbde31c7b94de2b56d3b63574a2f [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
Zesstra4c418f92020-02-03 20:14:35 +0100141protected varargs int send_telnet_neg_str(bytes str, int bm_flags)
142{
MG Mud User88f12472016-06-24 23:31:02 +0200143#ifdef __DEBUG__
144 // Debugausgaben zur Zeit nur fuer arraybasierte Variante
145 return send_telnet_neg(to_array(str), bm_flags);
146#else
147 if ( sizeof(str) < 2 )
148 return efun::binary_message(str, bm_flags);
149
150 struct telopt_s opt = TN[str[1]];
151
152 switch (str[0]) {
153 case DO:
154 case DONT:
155 (opt->lo_wishes)->remoteside = str[0];
Zesstra9ebed822019-11-27 19:50:17 +0100156 str = to_bytes(({IAC})) + str;
MG Mud User88f12472016-06-24 23:31:02 +0200157 break;
158 case WILL:
159 case WONT:
160 (opt->lo_wishes)->localside = str[0];
Zesstra9ebed822019-11-27 19:50:17 +0100161 str = to_bytes(({IAC})) + str;
MG Mud User88f12472016-06-24 23:31:02 +0200162 break;
163 case SB:
Zesstra9ebed822019-11-27 19:50:17 +0100164 (opt->lo_wishes)->sbdata = to_array(str[1..]);
165 str = to_bytes(({IAC})) + str + to_bytes(({IAC,SE}));
MG Mud User88f12472016-06-24 23:31:02 +0200166 break;
167 default:
168 break;
169 }
170
171 return efun::binary_message(str, bm_flags);
172#endif // __DEBUG__
173}
174
Zesstra4c418f92020-02-03 20:14:35 +0100175// Wenn der Client via STARTTLS eine TLS negotiation angestossen hat und
176// die noch laeuft, darf keine Ausgabe erfolgen. In diesem Fall wird das
177// Loginverfahren ausgesetzt, bis die TLS-Verhandlung abgeschlossen ist.
178// Danach wird es fortgesetzt bzw. neugestartet. Dies gilt auch fuer Fall,
179// dass STARTTLS verhandelt wurde, aber die TLS-Verhandlung noch nicht
180// laeuft. (Bemerkung: beides pruefen ist nicht ueberfluessig. Den Zustand
181// der Telnet-Option muss man pruefen, weil der Client evtl. seine
182// Verhandlung noch nicht signalisiert hat (FOLLOWS vom Client) und die
183// efun muss man pruefen, weil nach Empfang von FOLLOWS vom Client der
184// Status der Telnet-Optiosn resettet wurde - standardkonform.)
185protected int check_tls_negotiation()
186{
187 struct telopt_s s_tls = TN[TELOPT_STARTTLS];
188 if (tls_query_connection_state(this_object()) < 0
189 || (structp(s_tls) && s_tls->state->remoteside) )
190 {
191 debug_message("In TLS negotiation.\n");
192 return 1;
193 }
194 return 0;
195}
196
MG Mud User88f12472016-06-24 23:31:02 +0200197// Startet eine Verhandlung, um den Status einer Option zu aendern.
198// Wenn bereits eine Verhandlung laeuft, wird nichts gemacht und -1
199// zurueckgeben.
200// Wenn die Verhandlung keine Aenderung vom Status quo zum Ziel hat, wird
201// nichts gemacht und -2 zurueckgegeben.
202// Ansonsten ist die Rueckgabe die Anzahl der uebermittelten Zeichen.
203// <action>: WILL: Option soll auf dieser Seite eingeschaltet werden.
204// WONT: Option soll auf dieser Seite ausgeschaltet werden.
205// DO : Option soll auf der anderen Seite eingeschaltet werden.
206// DONT: Option soll auf der anderen Seite ausgeschaltet werden.
207protected int do_telnet_neg(int option, int action) {
Zesstra4c418f92020-02-03 20:14:35 +0100208
209 // ggf. muss TLS (initiiert durch STARTTLS) noch ausverhandelt werden. In
210 // dem Fall nix verhandeln/senden, was nicht STARTTLS ist.
211 if (option != TELOPT_STARTTLS && check_tls_negotiation())
212 return 0;
213
MG Mud User88f12472016-06-24 23:31:02 +0200214 struct telopt_s opt = TN[option];
215 if (!structp(opt))
216 {
217 opt = (<telopt_s> option: option,
218 re_wishes: (<to_state_s>),
219 lo_wishes: (<to_state_s>),
220 state: (<to_state_s>)
221 );
222 TN[option] = opt;
223 }
224 // es wird nur geprueft, ob wir bereits eine Verhandlung begonnen haben
225 // (lo_wishes), weil reinkommende remote Wuensche letztendlich sofort durch
226 // unsere Antwort erledigt sind.
227 switch(action)
228 {
229 case WILL:
230 if (opt->lo_wishes->localside != 0)
231 return -1;
232 if (opt->state->localside)
233 return -2;
234 return send_telnet_neg( ({ WILL, option }) );
235 break;
236 case WONT:
237 if (opt->lo_wishes->localside != 0)
238 return -1;
239 if (!opt->state->localside)
240 return -2;
241 return send_telnet_neg( ({ WONT, option }) );
242 break;
243 case DO:
244 if (opt->lo_wishes->remoteside != 0)
245 return -1;
246 if (opt->state->remoteside)
247 return -2;
248 return send_telnet_neg( ({ DO, option }) );
249 break;
250 case DONT:
251 if (opt->lo_wishes->remoteside != 0)
252 return -1;
253 if (!opt->state->remoteside)
254 return -2;
255 return send_telnet_neg( ({ DONT, option }) );
256 break;
257 }
258 raise_error(sprintf("Unsupported telnet negotation action in "
259 "do_telnet_neg(): %d\n",action));
260}
261
262// LOCAL Standard Handlers //
263private void _std_lo_handler_eor(struct telopt_s opt, int action) {
264 // tatsaechlich nix zu tun. Handler ist nur da, damit die Option auf dieser
265 // Seite aktiviert wird. Die Arbeit erledigt print_prompt.
266 return;
267}
268
269private void _std_lo_handler_mssp(struct telopt_s opt, int action) {
270 // nur einschalten ist interessant.
271 if (action != LOCALON)
272 return;
273 // Krams senden, wenn Objekt geladen. Sonst wieder abschalten (kommt
274 // hoffentlich nicht vor)...
275 object mssp = find_object("/secure/misc/mssp");
276 if (!mssp)
277 send_telnet_neg( ({WONT, TELOPT_MSSP }) );
278 else
279 {
Zesstra9ebed822019-11-27 19:50:17 +0100280 send_telnet_neg_str(
281 to_bytes(({SB, TELOPT_MSSP}))
282 + to_bytes(sprintf("%s", mssp->get_telnegs_str()),
283 "ASCII//TRANSLIT"));
MG Mud User88f12472016-06-24 23:31:02 +0200284 // die Daten brauchen wir nicht mehr
285 opt->lo_wishes->sbdata = 0;
286 }
287}
288
289
290// REMOTE Standard Handlers //
291private void _std_re_handler_tm(struct telopt_s opt, int action,
292 int *data)
293{
294 // egal, was geantwortet wurde, es gibt nen Hinweis auf die round-trip-time.
295 // Wenn ein Array in opt->data[1] steht, rechnen wir das aus und schreiben es
296 // in opt->data[0] als Ergebnis rein.
297 if (pointerp(opt->data) && pointerp(opt->data[1]))
298 {
299 int *ut = utime();
300 int *start = opt->data[1];
301 int res = (ut[0] - start[0]) * 1000000;
302 res += ut[1] - start[1];
303 opt->data[0] = res;
304 opt->data[1] = 0;
305 DEBUG("RTT: "+res);
306 }
307 return;
308}
309
310private void _std_re_handler_naws(struct telopt_s opt, int action,
311 int *data)
312{
313 if (action == SB)
314 {
315 eval_naws(data);
316 }
317}
318
319private void _std_re_handler_linemode(struct telopt_s opt, int action,
320 int *data)
321{
322 if (action == REMOTEON)
323 {
324 // see /doc/concepts/negotiations. We use only the minimum
325 // needed for linemode: switching on local commandline-editing
326 // for the client.
327 send_telnet_neg(({ SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT }));
328 // flush on 0d and 0a...
329 // TODO: what does this exactly do?
330 send_telnet_neg(({ SB, TELOPT_LINEMODE, DO, LM_FORWARDMASK, 0,
331 0x40|0x08 }));
332 //Gna...
333 opt->lo_wishes->sbdata = ({MODE_EDIT});
334 }
335}
336
337private void _std_re_handler_ttype(struct telopt_s opt, int action,
338 int *data)
339{
340 if (action == SB)
341 {
342 //TODO: get rid of this hysterical stuff...
343 //NOTE: We do not do multiple SB SENDs due to some weird
344 //bugs in IBM3270 emulating telnets which crash if we
345 //do that.
346 if ( sizeof(data) < 1 )
347 return;
348
349 if ( data[0] != TELQUAL_IS )
350 return;
351
352 string tmpterminal = lower_case( to_string(data[1..]) );
353 if ( !Terminals )
354 Terminals = ({ tmpterminal });
355 else
356 Terminals += ({ tmpterminal });
357
358 if ( Query(P_TTY_TYPE) )
359 Set( P_TTY_TYPE, Terminals[0] );
360 }
361 else if (action == REMOTEON)
362 {
363 send_telnet_neg(({ SB, TELOPT_TTYPE, TELQUAL_SEND }));
364 }
365}
366
Zesstra65e9f1a2020-01-17 22:50:14 +0100367// Der Handler fuer die BINARY option, wenn sie auf Clientseite
368// aktiviert/deaktivert wird, d.h. der Client sendet jetzt Binaerdaten statt
369// NVT-ASCII. Im Normalfall muessen wir im Handler nix machen. (SB gibts hier
370// nicht.)
371private void _std_re_handler_binary(struct telopt_s opt, int action,
372 int *data)
373{
Zesstra57cdbc32020-01-20 23:17:10 +0100374 DTN("binary handler client",({action}));
Zesstra65e9f1a2020-01-17 22:50:14 +0100375}
376
377// Der Handler fuer die BINARY option, wenn sie auf unserer Seite
378// aktiviert/deaktivert wird, d.h. wir senden jetzt Binaerdaten statt
379// NVT-ASCII. Im Normalfall muessen wir im Handler nix machen. (SB gibts hier
380// nicht.)
381private void _std_lo_handler_binary(struct telopt_s opt, int action,
382 int *data)
383{
Zesstra57cdbc32020-01-20 23:17:10 +0100384 DTN("binary handler mg",({action}));
Zesstra65e9f1a2020-01-17 22:50:14 +0100385}
386
Zesstra57cdbc32020-01-20 23:17:10 +0100387private int activate_charset(struct telopt_s opt, string charset)
388{
389 // Wenn der Client die Option nicht BINARY nicht unterstuetzt/will, duerfen
390 // wir auch keine nicht-ASCII-Zeichen uebertragen. In diesem Fall ist der
391 // einzige akzeptable Zeichensatz (US-)ASCII.
392 struct telopt_s binary = TN[TELOPT_BINARY];
393 if ( (!binary->state->remoteside || !binary->state->localside)
394 && (upper_case(charset) != "US-ASCII"
395 && upper_case(charset) != "ASCII") )
396 {
397 return 0;
398 }
399 // Wenn der Zeichensatz keine //-Variante ist, machen wir den zu
400 // einer. Das verhindert letztlich eine Menge Laufzeitfehler, wenn ein
401 // Zeichen mal nicht darstellbar ist.
402 if (strstr(charset, "//") == -1)
403 charset += "//TRANSLIT";
404 // Falls das zu sehr scrollt, weil Clients staendig ungueltige/nicht
405 // verwendbare Zeichensaetz schicken, muss das publish weg und ggf. sogar
406 // ein nolog hin...
407 if (!catch(configure_interactive(this_object(), IC_ENCODING, charset);
408 publish))
409 {
410 m_delete(opt->data, "failed_negotiations");
411 opt->data["accepted_charset"] = interactive_info(this_player(),
412 IC_ENCODING);
413 return 1;
414 }
415 return 0;
416}
417#define REQUEST 1
418#define ACCEPTED 2
419#define REJECTED 3
420#define TTABLE_IS 4
421#define TTABLE_REJECTED 5
422// Der Handler fuer die CHARSET option, wenn sie auf/fuer Clientseite
423// aktiviert/deaktivert wird oder fuer empfangene SB.
424private void _std_re_handler_charset(struct telopt_s opt, int action,
425 int *data)
426{
427 DTN("charset handler client",({action}));
428
429 // Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
430 // CHARSET REQUEST schicken (weil wir haben ihm auch schon ein DO
431 // geschickt).
432 if (action == REMOTEON)
433 {
434 if (!mappingp(opt->data))
435 opt->data = ([]);
436 }
437 else if (action == REMOTEOFF)
438 {
439 // Wenn auch auf mg-seite aus, kann data geloescht werden.
440 if (!opt->state->localside)
441 opt->data = 0;
442 }
443 else if (action == SB)
444 {
445 mapping statedata = opt->data;
446 // <data> is the part following IAC SB TELOPT_CHARSET
447 switch(data[0])
448 {
449 case REQUEST:
450 // is the client allowed to REQUEST?
451 if (opt->state->remoteside)
452 return;
453 // And enough data?
454 if (sizeof(data) > 1 )
455 {
456 DTN("re_charset request:",data);
457 string *suggestions = explode(to_text(data[2..], "ASCII"),
458 sprintf("%c",data[1]));
459 // Wenn UTF-8 drin vorkommt, nehmen wir das. (Gross-/Kleinschreibung
460 // ist egal, aber wir muessen einen identischen String
461 // zurueckschicken). (Gemischte Schreibweise: *ignorier* *stoehn*)
462 string *selected = suggestions & ({"UTF-8","utf-8"});
463 if (sizeof(selected)
464 && activate_charset(opt, selected[0]))
465 {
466 send_telnet_neg(({ SB, TELOPT_CHARSET, ACCEPTED,
467 to_array(selected[0]) }));
468 return;
469 }
470 else
471 {
472 // die ersten 10 Vorschlaege durchprobieren
473 foreach(string cs : suggestions[0..min(sizeof(suggestions)-1, 10)])
474 {
475 if (activate_charset(opt, cs))
476 {
477 send_telnet_neg(({ SB, TELOPT_CHARSET, ACCEPTED,
478 to_array(cs) }));
479 return; // yeay, found one!
480 }
481 }
482 // none acceptable
483 send_telnet_neg(({ SB, TELOPT_CHARSET, REJECTED }));
484 ++opt->data["failed_negotiations"];
485 // fall-through, no return;
486 }
487 }
488 else // malformed message
489 {
490 send_telnet_neg(({ SB, TELOPT_CHARSET, REJECTED }));
491 ++opt->data["failed_negotiations"];
492 // fall-through, no return;
493 }
494 // when arriving here, the negotiation was not successful. Check if
495 // too many unsuccesful tries in a row.
496 if (opt->data["failed_negotiations"] > 10)
497 {
498 send_telnet_neg(({ TELOPT_CHARSET, DONT }));
499 send_telnet_neg(({ TELOPT_CHARSET, WONT }));
500 }
501 break;
502 case ACCEPTED:
503 // great - the client accepted one of our suggested charsets.
504 // Negotiation concluded. However, check if we REQUESTed a charset in
505 // the first place... And if the accepted one is one of our
506 // suggestions
507 if (sizeof(data) > 1)
508 {
509 DTN("re_charset accepted:",data);
510 string charset = upper_case(to_text(data[1..], "ASCII"));
511 string *offered = statedata["offered"];
512 // in any case, we don't need the key in the future.
513 m_delete(statedata, "offered");
514 if (pointerp(offered) && member(offered, charset) > -1)
515 {
516 activate_charset(opt, charset);
517 return;
518 }
519 // else: client did not sent us back one of our suggestions or we
520 // did not REQUEST. :-(
521 }
522 ++opt->data["failed_negotiations"];
523 // else? Huh. malformed message.
524 break;
525 case REJECTED:
526 // none of our suggested charsets were acceptable. Negotiation is
527 // concluded, we keep the current charset (and maybe we will receive a
528 // suggestion of the client)
529 if (member(statedata, "offered"))
530 m_delete(statedata, "offered");
531 ++opt->data["failed_negotiations"];
532 DTN("re_charset_rejected:",data);
533 break;
534 case TTABLE_IS:
535 // we plainly don't support TTABLES
536 send_telnet_neg(({ SB, TELOPT_CHARSET, TTABLE_REJECTED }));
537 ++opt->data["failed_negotiations"];
538 break;
539 }
540 }
541}
542
Zesstrae037c1d2020-02-02 22:02:14 +0100543// Der Handler fuer die CHARSET option, wenn sie auf/fuer unserere Seite
Zesstra57cdbc32020-01-20 23:17:10 +0100544// aktiviert/deaktivert wird.
545private void _std_lo_handler_charset(struct telopt_s opt, int action,
546 int *data)
547{
548 DTN("charset handler mg",({action}));
549 if (action == LOCALON)
550 {
551 // Ab diesem Moment duerfen wir dem Client einen CHARSET REQUEST schicken
552 // (denn wir haben auch schon ein DO erhalten). Und das tun wir auch
553 // direkt.
554 if (!mappingp(opt->data))
555 opt->data = ([ "offered": OFFERED_CHARSETS ]);
556 else
557 opt->data["offered"] = OFFERED_CHARSETS;
558 send_telnet_neg(({ SB, TELOPT_CHARSET, REQUEST })
559 + to_array(implode(opt->data["offered"], ";"))) ;
560 }
561 else if (action == LOCALOFF)
562 {
563 // ok, keine REQUESTS mehr nach dem LOCALOFF, aber viel muss nicht getan
564 // werden. Wenn auch auf client-seite aus, kann data geloescht werden.
565 if (!opt->state->remoteside)
566 opt->data = 0;
567 }
568 // und SB gibt es nicht in diesem Handler.
569}
570#undef REQUEST
571#undef ACCEPTED
572#undef REJECTED
573#undef TTABLE-IS
574#undef TTABLE-REJECTED
575
Zesstrae037c1d2020-02-02 22:02:14 +0100576// Called from the telnetneg handler for TELOPT_STARTTLS to initiate the TLS
577// connection negotiation.
578protected void init_tls()
579{
580 // Dabei muss unser ganzer Telnet-Option-State muss zurueckgesetzt werden.
581 // Ja, wirklich! (Keine Sorge, der client muss das auch tun.)
582 TN = ([]);
583}
584
585#ifdef __TLS__
586// Der Handler fuer STARTTLS, wenn es auf der Clientseite
587// deaktiviert/aktiviert wird. Es wird nur auf der Clientseite aktiviert, der
588// Server darf kein WILL senden. Nach Aktivierung muessen wir ein FOLLOWS
589// senden.
590#define FOLLOWS 1
591private void _std_re_handler_starttls(struct telopt_s opt, int action,
592 int *data)
593{
594 DTN("starttls handler client",({action}));
595
596 // Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
597 // STARTTLS FOLLOWS senden (weil wir haben ihm auch schon ein DO
598 // geschickt). Wir sollen ihm aber jetzt auch ein FOLLOWS senden. Sobald wir
599 // das gesendet haben und ein FOLLOWS erhalten haben, geht die Negotiation
600 // los.
601 if (action == REMOTEON)
602 {
603 send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
604 opt->data = 1; // Nur ein Flag, dass wir es gesendet haben.
605 }
606 else if (action == REMOTEOFF)
607 {
608 // data zuruecksetzen, sonst muessen wir nix machen.
609 opt->data = 0;
610 }
611 else if (action == SB)
612 {
613 if (data[0] == FOLLOWS)
614 {
615 // FOLLOWS empfangen. Wenn wir noch kein FOLLOWS gesendet haben, tun wir
616 // das jetzt.
617 if (!opt->data)
618 send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
619 // Jetzt wird die Verhandlung auf unserer Seite gestartet, der Client
620 // macht das entweder schon oder spaetestens, wenn er unser FOLLOWS
621 // empfangen kann.
622 init_tls();
623 }
624 }
625}
626#undef FOLLOWS
627#endif // __TLS__
Zesstra57cdbc32020-01-20 23:17:10 +0100628
MG Mud User88f12472016-06-24 23:31:02 +0200629// Bindet/registriert Handler fuer die jew. Telnet Option. (Oder loescht sie
630// auch wieder.) Je nach <initneg> wird versucht, die Option neu zu
631// verhandeln.
632protected int bind_telneg_handler(int option, closure re, closure lo,
633 int initneg)
634{
635 struct telopt_s opt = TN[option];
636 if (!structp(opt))
637 {
638 opt = (<telopt_s> option: option,
639 re_wishes: (<to_state_s>),
640 lo_wishes: (<to_state_s>),
641 state: (<to_state_s>)
642 );
643 TN[option] = opt;
644 }
645
646 opt->remotehandler = re;
647 if (initneg)
648 {
649 if (re)
650 do_telnet_neg(option, DO);
651 else
652 do_telnet_neg(option, DONT );
653 }
654
655 opt->localhandler = lo;
656 if (initneg)
657 {
658 if (lo)
659 do_telnet_neg(option, WILL);
660 else
661 do_telnet_neg(option, WONT);
662 }
663 return 1;
664}
665
666
667// Mal unsere Wuensche an den Client schicken und die Standardhandler
Zesstra01b2b742020-01-20 23:53:01 +0100668// registrieren. Hierbei bei Bedarf neue Verhandlungen starten. Es wird hier
669// aber nur ein Basissatz an Optionen verhandelt, der Rest kommt spaeter
670// nachdem das Spielerobjekt die Verbindung hat (in startup_telnet_negs())
MG Mud User88f12472016-06-24 23:31:02 +0200671// Gerufen aus login.c nach Verbindungsaufbau.
672// Bemerkung: das Spielerobjekt bietet evt. noch zusaetzliche Telnetoptionen
673// an, die dann ueber startup_telnet_negs() (im Spielerobjekt)
674// laufen.
675protected void SendTelopts()
676{
Zesstrae037c1d2020-02-02 22:02:14 +0100677#if __TLS__
678 // If this is a non-TLS-connection, we offer STARTTLS, but wait for the
679 // client to ask for it.
680 if (tls_available() && tls_query_connection_state() == 0)
681 {
682 bind_telneg_handler(TELOPT_STARTTLS, #'_std_re_handler_starttls,
Zesstra56493232020-02-03 20:15:51 +0100683 0, 1);
Zesstrae037c1d2020-02-02 22:02:14 +0100684 }
685#endif
Zesstra65e9f1a2020-01-17 22:50:14 +0100686 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
687 #'_std_lo_handler_binary, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200688 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
MG Mud User88f12472016-06-24 23:31:02 +0200689 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 1);
690 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 1);
691 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 1);
Zesstra65e9f1a2020-01-17 22:50:14 +0100692 if (find_object("/secure/misc/mssp"))
693 bind_telneg_handler(TELOPT_MSSP, 0, #'_std_lo_handler_mssp, 1);
Zesstra57cdbc32020-01-20 23:17:10 +0100694 // und auch CHARSET wird verzoegert bis das Spielerobjekt da ist.
MG Mud User88f12472016-06-24 23:31:02 +0200695}
696
MG Mud User88f12472016-06-24 23:31:02 +0200697// Bindet die Standardhandler _aus diesem_ Programm (und ueberschreibt dabei
698// ggf. andere). Hierbei werden nur die Handler neu gebunden, keine neuen
699// Verhandlungen initiiert.
700// gerufen aus base.c indirekt via startup_telnet_negs().
Zesstrae037c1d2020-02-02 22:02:14 +0100701protected void _bind_telneg_std_handlers()
702{
703 // BTW: es ist absicht, im Spielerobjekt keinen Support fuer STARTTLS mehr
704 // anzubieten.
Zesstra65e9f1a2020-01-17 22:50:14 +0100705 bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
706 #'_std_lo_handler_binary, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200707 bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200708 bind_telneg_handler(TELOPT_NAWS, #'_std_re_handler_naws, 0, 0);
709 bind_telneg_handler(TELOPT_LINEMODE, #'_std_re_handler_linemode, 0, 0);
710 bind_telneg_handler(TELOPT_TTYPE, #'_std_re_handler_ttype, 0, 0);
Zesstra65e9f1a2020-01-17 22:50:14 +0100711 // Besondere Situation: MSSP ist nach Spielerlogin eigentlich uninteressant.
712 // Daher sparen wir uns das im Kontext des Spielerobjekts und schalten es
713 // einfach wieder aus.
714 bind_telneg_handler(TELOPT_MSSP, 0, 0, 0);
MG Mud User88f12472016-06-24 23:31:02 +0200715}
716
717
718// Ruft die entsprechenden handler von der Telnet Option.
719// Wenn es keinen handler (mehr) gibt, wird die Option auch auf der jeweiligen
720// Seite ausgeschaltet. Deshalb MUSS lo_wishes und re_wishes vom Aufrufer VOR
721// DEM AUFRUF zurueckgesetzt worden sein!
722// <action>: 'LOCALON': Option wurde auf unserer Seite eingeschaltet
723// 'LOCALOFF': Option wurde auf unserer Seite ausgeschaltet
724// 'REMOTEON': Option wurde auf Clientseite eingeschaltet
725// 'REMOTEOFF': Option wurde auf Clientseite ausgeschaltet
726// 'SB': Suboption negotiation Daten wurden empfangen
727// <data>: die per SB empfangenen Daten (unverarbeitet)
728private void _call_handler(struct telopt_s opt, int action, int *data) {
729 switch(action)
730 {
731 case REMOTEON:
732 case REMOTEOFF:
733 case SB:
734 if (opt->remotehandler)
735 {
736 funcall(opt->remotehandler, opt, action, data);
737 }
738 else
739 {
740 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
741 // dass nur verhandelt wird, wenn die Option an ist.)
742 do_telnet_neg( opt->option, DONT );
743 }
744 break;
745 case LOCALON:
746 case LOCALOFF:
747 if (opt->localhandler)
748 {
749 funcall(opt->localhandler, opt, action);
750 }
751 else
752 {
753 // ok, geht nicht. Ggf. Abschalten (do_telnet_neg passt auf,
754 // dass nur verhandelt wird, wenn die Option an ist.)
755 do_telnet_neg( opt->option, WONT );
756 }
757 break;
758 }
759}
760
761// Gerufen vom Driver, wenn neue telnet options reinkommen.
762void
763telnet_neg(int command, int option, int *optargs)
764{
765 DTN("recv_tn: ", ({IAC, command, option}) + (optargs||({})));
766
Zesstra4c418f92020-02-03 20:14:35 +0100767 // ggf. muss TLS (initiiert durch STARTTLS) noch ausverhandelt werden. In
768 // dem Fall muessen wir alles ignorieren, was nicht STARTTLS ist.
769 if (option != TELOPT_STARTTLS && check_tls_negotiation())
770 return;
771
MG Mud User88f12472016-06-24 23:31:02 +0200772 struct telopt_s opt = TN[option];
773 if (!structp(opt))
774 {
775 opt = (<telopt_s> option: option,
776 re_wishes: (<to_state_s>),
777 lo_wishes: (<to_state_s>),
778 state: (<to_state_s>)
779 );
780 TN[option] = opt;
781 }
782
783 // Was will der Client tun?
784 if (command == WONT)
785 {
786 // Client will die Option auf seiner Seite abschalten. Wir MUESSEN das
787 // akzeptieren.
788 // Wir muessen das allerdings ignorieren, wenn die Option bereits aus
789 // ist.
790 if (opt->state->remoteside==0)
791 {
792 // Ausnahme fuer TELOPT_TM, da das kaum ein Client kann und fuer RTT
793 // es eigentlich auch egal ist, was zurueck kommt: der handler wird
794 // zumindest doch gerufen zum Ausrechnen der RTT
795 if (option == TELOPT_TM)
796 _call_handler(opt, REMOTEOFF, 0);
797 // ansonsten aber wirklich ignorieren. ;)
798 return;
799 }
800 opt->re_wishes->remoteside = command;
801 // Bestaetigung auf ein WONT senden, wenn wir nicht selber schon ein
802 // DONT geschickt hatten.
803 if (opt->lo_wishes->remoteside != DONT) {
804 send_telnet_neg( ({DONT, option}) );
805 }
806 // Wir haben jetzt auf jeden Fall ein DONT gesendet und ein WONT
807 // erhalten. Damit ist die Option jetzt auf der clientseite aus.
808 // Ausserdem setzen wir die Wishes zurueck.
809 opt->re_wishes->remoteside = 0;
810 opt->lo_wishes->remoteside = 0;
811 if (opt->state->remoteside != 0)
812 {
813 opt->state->remoteside = 0;
814 _call_handler(opt, REMOTEOFF, 0);
815 }
816 } // WONT vom Client verarbeitet
817 else if ( command == WILL)
818 {
819 // Wenn die Option bereits an ist, muessen wir dies ignorieren.
820 if (opt->state->remoteside == 1)
821 {
822 // Ausnahme fuer TELOPT_TM, der handler wird zumindest doch gerufen
823 // zum Ausrechnen der RTT. Diese Option laesst sich ohnehin
824 // aktivieren, auch wenn sie schon an ist.
825 if (option == TELOPT_TM)
826 _call_handler(opt, REMOTEON, 0);
827 // sonst aber wirklich ignorieren. ;-)
828 return;
829 }
830 opt->re_wishes->remoteside = command;
831 if ( opt->lo_wishes->remoteside == 0 )
832 {
833 // Der Client will, wir haben noch nix dazu gesagt. (Mit unserer
834 // Antwort ist die Verhandlung uebrigens beendet.)
835 // Wenn es einen remotehandler fuer die Option gibt, schalten wir
836 // sie ein...
837 if (opt->remotehandler)
838 {
839 send_telnet_neg(({DO, option}));
840 // Option jetzt an der Clientseite an.
841 opt->re_wishes->remoteside = 0;
842 opt->lo_wishes->remoteside = 0;
843 if (opt->state->remoteside != 1)
844 {
845 opt->state->remoteside = 1;
846 _call_handler(opt, REMOTEON, 0);
847 }
848 }
849 else
850 {
851 // sonst verweigern wir das einschalten (die meisten Optionen
852 // auf Clientseite sind fuer uns eh egal).
853 send_telnet_neg(({DONT, option}));
854 // Option jetzt an der Clientseite aus.
855 opt->re_wishes->remoteside = 0;
856 opt->lo_wishes->remoteside = 0;
857 if (opt->state->remoteside != 0)
858 {
859 opt->state->remoteside = 0;
860 _call_handler(opt, REMOTEOFF, 0);
861 }
862 }
863 }
864 else if ( opt->lo_wishes->remoteside == DO)
865 {
866 // Wir haben haben bereits per DO angefordert, d.h. das ist die
867 // Clientbestaetigung - wir duerfen nicht bestaetigen und die
868 // Option ist jetzt clientseitig aktiv. Verhandlung beendet.
869 opt->re_wishes->remoteside = 0;
870 opt->lo_wishes->remoteside = 0;
871 if (opt->state->remoteside != 1)
872 {
873 opt->state->remoteside = 1;
874 _call_handler(opt, REMOTEON, 0);
875 }
876 } // if (DO)
877 else {
878 // Mhmm. Wir hatten ein DONT gesendet, aber der Client hat mit WILL
879 // geantwortet. Das darf er eigentlich gar nicht.
880 //TODO: was sollte man jetzt eigentlich tun? Erstmal wiederholen wir
881 //das DONT...
882 send_telnet_neg( ({DONT, option}) );
883 }
884
885 return;
886 } // WILL vom Client verarbeitet
887 // Was sollen wir (nicht) fuer den Client tun?
888 else if ( command == DONT)
889 {
890 // Client will, dass wir etwas nicht tun. Wir MUESSEN das akzeptieren.
891 // wenn die Option auf unserer Seite aber schon aus ist, muessen wir
892 // dies ignorieren.
893 if (opt->state->localside == 0)
894 return;
895
896 opt->re_wishes->localside = command;
897 // Wenn wir noch kein WONT gesendet haben, senden wir das jetzt als
898 // Bestaetigung.
899 if (opt->lo_wishes->localside = WONT)
900 send_telnet_neg( ({WONT, option}) );
901 // Verhandlung beendet, Option is auf unserer Seite jetzt aus.
902 // Wuensche auch wieder zuruecksetzen.
903 opt->re_wishes->localside = 0;
904 opt->lo_wishes->localside = 0;
905 if (opt->state->localside != 0)
906 {
907 opt->state->localside = 0;
908 _call_handler(opt, LOCALOFF, 0);
909 }
910 }
911 else if ( command == DO )
912 {
913 // Client will, dass wir option tun. Mal schauen, wie wir dazu stehen.
914 // wenn die Option auf unserer Seite aber schon an ist, muessen wir
915 // dies ignorieren.
916 if (opt->state->localside == 1)
917 return;
918
919 opt->re_wishes->localside = command;
920
921 if ( opt->lo_wishes->localside == 0 ) {
922 // wir haben unsere Wuensche noch nicht geaeussert. Sobald wir
923 // bestaetigen, ist die Option auf unserer Seite an/aus und die
924 // Verhandlungen beendet.
925 // in jedem Fall die Wuensche zuruecksetzen
926 opt->re_wishes->localside = 0;
927 opt->lo_wishes->localside = 0;
928 if (opt->localhandler)
929 {
930 send_telnet_neg(({WILL, option}));
931 opt->state->localside = 1;
932 _call_handler(opt, LOCALON, 0);
933 }
934 else
935 {
936 send_telnet_neg(({WONT, option}));
937 opt->state->localside = 0;
938 _call_handler(opt, LOCALOFF, 0);
939 }
940 }
941 else if (opt->lo_wishes->localside == WILL ) {
942 // wir haben schon WILL gesendet, welches der Client jetzt
943 // bestaetigt hat (d.h. die Option ist jetzt auf dieser Seite an),
944 // wir bestaetigen das aber nicht (nochmal).
945 opt->re_wishes->localside = 0;
946 opt->lo_wishes->localside = 0;
947 if (opt->state->localside != 1)
948 {
949 opt->state->localside = 1;
950 _call_handler(opt, LOCALON, 0);
951 }
952 }
953 else {
954 // Mhmm. Wir haben ein WONT gesendet, der Client hat mit DO
955 // geantwortet. Das darf er eigentlich nicht.
956 // TODO: Was tun?
957 send_telnet_neg ( ({WONT, option}) );
958 }
959 // fertig mit DO
960 return;
961 }
962 // bleibt noch SB ueber
963 else if ( command == SB )
964 {
965 opt->re_wishes->sbdata = optargs;
966 _call_handler(opt, SB, optargs);
967 return;
968 } // if ( command == SB )
969}
970
971// wird nur in base.c gerufen, wenn die Verbindung an das Spielerobjekt
972// uebergeben wurde.
973// es uebertraegt unter anderem den Telnet Option Zustand aus login.c (das ist
974// dann previous_object()) in das Spielerobjekt (welches dann this_object())
975// ist!
976protected void
977startup_telnet_negs()
978{
979 int* optargs;
980
981 Set( P_TTY_TYPE, 0 ); //avoid ANY mistakes... Wird unten neu gesetzt.
982 // Daten aus dem Loginobjekt uebertragen. Das ist wichtig! (Dabei wird dann
983 // auch der Status von der letzten Session ueberschrieben.)
984 TN = (mapping) previous_object()->query_telnet_neg();
985 // bevor irgendwas anderes gemacht wird, werden erstmal die Standardhandler
986 // gesetzt. Die sind naemlich in diesem Objekt jetzt erstmal kaputt, weil
987 // sie im Loginobjekt gerufen werden.
988 _bind_telneg_std_handlers();
989 // dann restliche Daten aus dem Loginobjekt holen.
990 Terminals = (string *) previous_object()->query_terminals();
991 Set( P_TTY_COLS, previous_object()->Query(P_TTY_COLS) );
992 Set( P_TTY_ROWS, previous_object()->Query(P_TTY_ROWS) );
993
994 struct telopt_s opt = TN[TELOPT_NAWS];
995 if (optargs = (opt->re_wishes)->sbdata) {
996 eval_naws(optargs);
997 }
998
999 if ( pointerp(Terminals) && sizeof(Terminals)) {
1000 if ( Terminals[0][0..3] == "dec-" )
1001 Terminals[0] = Terminals[0][4..];
1002
1003 if ( Terminals[0] == "linux" )
1004 Terminals[0] = "vt100";
1005
1006 Set( P_TTY_TYPE, Terminals[0] );
1007 }
Zesstra01b2b742020-01-20 23:53:01 +01001008 // fuer TELOPT_TM jetzt keine Verhandlung anstossen, aber Handler
1009 // registrieren.
1010 bind_telneg_handler(TELOPT_TM, #'_std_re_handler_tm, 0, 0);
Zesstra57cdbc32020-01-20 23:17:10 +01001011 // und zum Schluss wird der Support fuer CHARSET aktiviert.
1012 bind_telneg_handler(TELOPT_CHARSET, #'_std_re_handler_charset,
1013 #'_std_lo_handler_charset, 1);
MG Mud User88f12472016-06-24 23:31:02 +02001014}
1015
1016// somehow completely out of the ordinary options processing/negotiation. But
1017// the only purpose is to transmit something over the wire which is not shown,
1018// but (likely) answered by the other device.
1019protected void send_telnet_timing_mark() {
1020 struct telopt_s opt = TN[TELOPT_TM];
1021 if (pointerp(opt->data))
1022 opt->data[1] = utime();
1023 else
1024 opt->data = ({ 0, utime() });
1025 // absichtlich nicht do_telnet_ne() verwendet, da dies nicht senden wuerde,
1026 // haette der Client schonmal mit WILL geantwortet. TELOPT_TM ist aber eine
1027 // Option, bei der man das darf...
1028 send_telnet_neg( ({DO, TELOPT_TM}) );
1029}
1030
1031/* Is called from the H_PRINT_PROMPT driver hook and appends the IAC EOR if
1032 * the client supports it.
1033 */
1034void print_prompt(string prompt) {
1035// if (extern_call() && previous_object()!=this_object())
1036// return;
1037
1038 // ggf. Uhrzeit in den prompt reinschreiben.
1039 prompt = regreplace(prompt,"\\t",strftime("%H:%M"),0);
1040 // Prompt senden
1041 tell_object(this_object(), prompt);
1042 // Und EOR senden, falls vom Client gewuenscht.
1043 struct telopt_s opt = TN[TELOPT_EOR];
1044 if (opt->state->localside == 1)
1045 {
1046 binary_message(({IAC, EOR}), 1);
1047 DTN("tn_eor ",({IAC,EOR}));
1048 }
1049}
1050
1051// Helper
1052private void eval_naws(int *optargs) {
1053 int l, c;
1054
1055 if ( sizeof(optargs) != 4 )
1056 {
1057 tell_object(this_object(),
1058 break_string( sprintf("Dein Client hat einen Fehler beim"
1059 +"Aushandeln der TELOPT_NAWS - er hat"
1060 +"IAC SB %O IAC SE gesendet!\n",
1061 optargs), 78,
1062 "Der GameDriver teilt Dir mit: " ));
1063 // und dem Client sagen, dass er den Schrott nicht mehr uebertragen
1064 // soll (falls wir das nicht schon gemacht haben).
1065 struct telopt_s opt = TN[TELOPT_NAWS];
1066 if (opt->state->remoteside == WILL
1067 && opt->lo_wishes->remoteside != DONT)
1068 send_telnet_neg(( {DONT, TELOPT_NAWS}) );
1069 return;
1070 }
1071
1072 if ( interactive(this_object()) ){
1073 if ( !optargs[1] )
1074 c = optargs[0];
1075 else
1076 c = optargs[1] + optargs[0] * 256;
1077
1078 if ( c < 35 ){
1079 if (Query(P_TTY_SHOW))
1080 tell_object( this_object(),
1081 break_string("Dein Fenster ist schmaler als"
1082 +" 35 Zeichen? Du scherzt. ;-)"
1083 +" Ich benutze den Standardwert"
1084 +" von 80 Zeichen.\n", 78,
1085 "Der GameDriver teilt Dir mit: ")
1086 );
1087 c = 80;
1088 }
1089
1090 if ( !optargs[3] )
1091 l = optargs[2];
1092 else
1093 l = 256 * optargs[2] + optargs[3];
1094
1095 if ( l > 100 ){
1096 //TODO: remove
1097 l = 100;
1098 if (Query(P_TTY_SHOW))
1099 tell_object( this_object(),
1100 break_string("Tut mir leid, aber ich kann"
1101 +" nur bis zu 100 Zeilen"
1102 +" verwalten.\n", (c ? c-2 : 78),
1103 "Der GameDriver teilt Dir mit: " )
1104 );
1105 }
1106
1107 if ( l < 3 ){
1108 if (Query(P_TTY_SHOW))
1109 tell_object( this_object(),
1110 break_string("Du willst weniger als drei"
1111 +" Zeilen benutzen? Glaub ich"
1112 +" Dir nicht - ich benutze den"
1113 +" Standardwert von 24"
1114 +" Zeilen.\n", (c ? c-2 : 78),
1115 "Der GameDriver teilt Dir mit: " )
1116 );
1117 l = 24;
1118 }
1119
1120 if ( ((int) Query(P_TTY_ROWS) != l) ||
1121 ((int) Query(P_TTY_COLS) != c) ){
1122 Set( P_TTY_ROWS, l );
1123 Set( P_TTY_COLS, c );
1124
1125 if (Query(P_TTY_SHOW))
1126 tell_object( this_object(),
1127 break_string("Du hast Deine Fenstergroesse auf"
1128 +" "+l+" Zeilen und "+c+
1129 " Spalten geaendert.\n", c-2,
1130 "Der GameDriver teilt Dir mit: ")
1131 );
1132 }
1133 }
1134}
1135
MG Mud User88f12472016-06-24 23:31:02 +02001136// Query-/Set-Methoden
1137// Und wenn hier einer von aussen dran rumpfuscht, werde ich sauer.
1138mapping
1139query_telnet_neg()
1140{
MG Mud User88f12472016-06-24 23:31:02 +02001141 return TN;
1142}
1143
1144// siehe oben
1145string *
1146query_terminals() {
1147 return Terminals;
1148}
1149
1150public int _query_p_lib_telnet_rttime()
1151{
1152 struct telopt_s opt = TN[TELOPT_TM];
1153 if (opt && pointerp(opt->data))
1154 return (opt->data)[0];
1155 return 0;
1156}
1157