Experimenteller Support fuer Telnetoption START_TLS.

Diese Telnetoption kann von vom Client benutzt werden,
um eine unsichere Verbindung zu einer TLS-Verbindung
zu machen. Nach Aktivierung beim Client synchronieren
sich Server und Client und starten dann die TLS-
Verhandlung. Anschliessend wird die nun TLS-
Verbindung als neue Verbindung behandelt und der
Login-Prozess neu gestartet.

Change-Id: I9bf79d611311c46b1ef8317e26806a95fc94379d
diff --git a/secure/login.c b/secure/login.c
index a958b91..bd8726e 100644
--- a/secure/login.c
+++ b/secure/login.c
@@ -33,9 +33,11 @@
 #include <properties.h>
 #include <moving.h>
 #include "/secure/wizlevels.h"
+
 #include <telnet.h>
 #include <defines.h>
 #include <input_to.h>
+#include <configuration.h>
 
 inherit "/secure/mini_props.c";
 inherit "/secure/telnetneg.c";
@@ -135,47 +137,109 @@
         return 0;
 }
 
+// Callback when a TLS connection negotiation was finished. This handler is
+// called for negotiations startet by the telnet option START_TLS.
+protected void tls_init_callback(int handshake_result)
+{
+  if (handshake_result < 0)
+  {
+    // Fehler im Vebindungsaufbau
+    write(break_string(sprintf(
+           "Can't establish a TLS/SSL encrypted connection: %s."
+           "Disconnecting now. If this error persists, please "
+           "disable the usage of TLS or STARTTLS in your client.",
+           tls_error(handshake_result)),78));
+    // Disconnect
+    destruct(this_object());
+    return;
+  }
+  // In this case, we will treat the newly negotiated TLS connection as as new
+  // connection and just start over again by calling logon(). And re-enable
+  // the telnet machine of the driver of course.
+  configure_interactive(this_object(), IC_TELNET_ENABLED, 1);
+  logon();
+}
+
+// Called from the telnetneg handler for TELOPT_STARTTLS to initiate the TLS
+// connection negotiation.
+protected void init_tls()
+{
+  ::init_tls();
+  configure_interactive(this_object(), IC_TELNET_ENABLED, 0);
+  tls_init_connection(this_object(), #'tls_init_callback);
+}
+
+// Wenn der Client via STARTTLS eine TLS negotiation angestossen hat und
+// die noch laeuft, darf keine Ausgabe erfolgen. In diesem Fall wird das
+// Loginverfahren ausgesetzt, bis die TLS-Verhandlung abgeschlossen ist.
+// Danach wird es fortgesetzt bzw. neugestartet. Dies gilt auch fuer Fall,
+// dass STARTTLS verhandelt wurde, aber die TLS-Verhandlung noch nicht
+// laeuft. (Bemerkung: beides pruefen ist nicht ueberfluessig. Den Zustand
+// der Telnet-Option muss man pruefen, weil der Client evtl. seine
+// Verhandlung noch nicht signalisiert hat (FOLLOWS vom Client) und die
+// efun muss man pruefen, weil nach Empfang von FOLLOWS vom Client der
+// Status der Telnet-Optiosn resettet wurde - standardkonform.)
+private int check_tls_negotiation()
+{
+  struct telopt_s s_tls = query_telnet_neg()[TELOPT_STARTTLS];
+  if (tls_query_connection_state(this_object()) < 0
+      || (structp(s_tls) && s_tls->state->remoteside) )
+    return 1;
+
+  return 0;
+}
 
 /*
  * This is the function that gets called by /secure/master for every user
  */
 public nomask int logon()
 {
+    set_next_reset(300); // Timeout fuer Loginverfahren
     loginname = "logon";
     newbie=0;
     realip="";
 
-    // als erstes wird ein Lookup gemacht, ob die Quelladresse ein
-    // Tor-Exitnode ist, der erlaubt, zu uns zu kommunizieren. Das Lookup ist
-    // asynchron und braucht eine Weile, wenn das Ergebnis noch nicht gecacht
-    // ist. An dieser Stelle wird das Ergebnis nicht ausgewertet. Achja, wie
+    SendTelopts();
+    // In theory, we should not send anything if SendTelops() offers
+    // TELOPT_STARTTLS. However, some clients to not answer unknown telnet
+    // options and it would introduce a delay in any case. Therefore we send
+    // the welcome message anway, unless we received a WILL from the Client.
+
+    // Es wird ein Lookup gemacht, ob die Quelladresse ein Tor-Exitnode ist,
+    // der erlaubt, zu uns zu kommunizieren. Das Lookup ist asynchron und
+    // braucht eine Weile, wenn das Ergebnis noch nicht gecacht ist.
+    // An dieser Stelle wird das Ergebnis nicht ausgewertet. Achja, wie
     // machen das natuerlich nicht fuer die IP vom Mudrechner...
     if (query_ip_number(this_object()) != "87.79.24.60")
     {
-      "/p/daemon/dnslookup"->check_tor(query_ip_number(this_object()),query_mud_port());
+      "/p/daemon/dnslookup"->check_tor(query_ip_number(this_object()),
+          query_mud_port());
       "/p/daemon/dnslookup"->check_dnsbl(query_ip_number(this_object()));
     }
+
+    // ggf. muss TLS (initiiert durch STARTTLS) noch ausverhandelt werden.
+    if (check_tls_negotiation())
+      return 1; // Verbindung behalten
+
     printf("HTTP/1.0 302 Found\n"
          "Location: http://mg.mud.de/\n\n"
          "NetCologne, Koeln, Germany. Local time: %s\n\n"
          MUDNAME" LDmud, NATIVE mode, driver version %s\n\n",
          strftime("%c"), __VERSION__);
 
-    SendTelopts();
-
     if ( check_too_many_logons() ){
         destruct(this_object());
         return 0;
     }
 
     // ist die Verbindung schon wieder weg?
-    if (objectp(this_object()) && interactive(this_object())) {
-      cat( "/etc/WELCOME" );
-    }
+    if (!objectp(this_object()) || !interactive(this_object()))
+      return 0;
 
+
+    cat( "/etc/WELCOME" );
     input_to( "logon2", INPUT_PROMPT,
         "Wie heisst Du denn (\"neu\" fuer neuen Spieler)? ");
-    set_next_reset(300);
     return 1;
 }
 
@@ -279,7 +343,6 @@
     return 1;
 }
 
-
 static void logon2( string str )
 {
     int i, arg;
diff --git a/secure/telnetneg.c b/secure/telnetneg.c
index 42d18c4..1b175a5 100644
--- a/secure/telnetneg.c
+++ b/secure/telnetneg.c
@@ -511,7 +511,7 @@
   }
 }
 
-// Der Handler fuer die BINARY option, wenn sie auf/fuer unserere Seite
+// Der Handler fuer die CHARSET option, wenn sie auf/fuer unserere Seite
 // aktiviert/deaktivert wird.
 private void _std_lo_handler_charset(struct telopt_s opt, int action,
                                    int *data)
@@ -544,6 +544,58 @@
 #undef TTABLE-IS
 #undef TTABLE-REJECTED
 
+// Called from the telnetneg handler for TELOPT_STARTTLS to initiate the TLS
+// connection negotiation.
+protected void init_tls()
+{
+  // Dabei muss unser ganzer Telnet-Option-State muss zurueckgesetzt werden.
+  // Ja, wirklich! (Keine Sorge, der client muss das auch tun.)
+  TN = ([]);
+}
+
+#ifdef __TLS__
+// Der Handler fuer STARTTLS, wenn es auf der Clientseite
+// deaktiviert/aktiviert wird. Es wird nur auf der Clientseite aktiviert, der
+// Server darf kein WILL senden. Nach Aktivierung muessen wir ein FOLLOWS
+// senden.
+#define FOLLOWS 1
+private void _std_re_handler_starttls(struct telopt_s opt, int action,
+                                      int *data)
+{
+  DTN("starttls handler client",({action}));
+
+  // Wenn action == REMOTEON: Ab diesem Moment darf uns der Client einen
+  // STARTTLS FOLLOWS senden (weil wir haben ihm auch schon ein DO
+  // geschickt). Wir sollen ihm aber jetzt auch ein FOLLOWS senden. Sobald wir
+  // das gesendet haben und ein FOLLOWS erhalten haben, geht die Negotiation
+  // los.
+  if (action  == REMOTEON)
+  {
+    send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
+    opt->data = 1; // Nur ein Flag, dass wir es gesendet haben.
+  }
+  else if (action == REMOTEOFF)
+  {
+    // data zuruecksetzen, sonst muessen wir nix machen.
+    opt->data = 0;
+  }
+  else if (action == SB)
+  {
+    if (data[0] == FOLLOWS)
+    {
+      // FOLLOWS empfangen. Wenn wir noch kein FOLLOWS gesendet haben, tun wir
+      // das jetzt.
+      if (!opt->data)
+        send_telnet_neg(({ SB, TELOPT_STARTTLS, FOLLOWS }));
+      // Jetzt wird die Verhandlung auf unserer Seite gestartet, der Client
+      // macht das entweder schon oder spaetestens, wenn er unser FOLLOWS
+      // empfangen kann.
+      init_tls();
+    }
+  }
+}
+#undef FOLLOWS
+#endif // __TLS__
 
 // Bindet/registriert Handler fuer die jew. Telnet Option. (Oder loescht sie
 // auch wieder.) Je nach <initneg> wird versucht, die Option neu zu
@@ -593,6 +645,15 @@
 //            laufen.
 protected void SendTelopts()
 {
+#if __TLS__
+  // If this is a non-TLS-connection, we offer STARTTLS, but wait for the
+  // client to ask for it.
+  if (tls_available() && tls_query_connection_state() == 0)
+  {
+    bind_telneg_handler(TELOPT_STARTTLS, #'_std_re_handler_starttls,
+                        0, 0);
+  }
+#endif
   bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
                       #'_std_lo_handler_binary, 1);
   bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 1);
@@ -604,12 +665,14 @@
   // und auch CHARSET wird verzoegert bis das Spielerobjekt da ist.
 }
 
-
 // Bindet die Standardhandler _aus diesem_ Programm (und ueberschreibt dabei
 // ggf. andere). Hierbei werden nur die Handler neu gebunden, keine neuen
 // Verhandlungen initiiert.
 // gerufen aus base.c indirekt via startup_telnet_negs().
-protected void _bind_telneg_std_handlers() {
+protected void _bind_telneg_std_handlers()
+{
+  // BTW: es ist absicht, im Spielerobjekt keinen Support fuer STARTTLS mehr
+  // anzubieten.
   bind_telneg_handler(TELOPT_BINARY, #'_std_re_handler_binary,
                       #'_std_lo_handler_binary, 0);
   bind_telneg_handler(TELOPT_EOR, 0, #'_std_lo_handler_eor, 0);