Hallo Heiko,

Heiko Schlittermann (Sat, Feb 13, 2021 at 12:32:14PM +0100):
> Hallo Bert,
> noch ein paar weitere unsortierte Gedanken:
> 
> - Vewenden die MySQL/MariaDB Libraries nicht auch
>   Konfigurationsfiles (mehr oder weniger implizit), in denen man dann
>   solche Dinge erledigen könnte?

Die Konfigurationsfiles stecken in den mariadb-server und mariadb-client
Paketen, exim4-daemon-heavy hat nur ein Depends: libmariadb3.

> - Möchte MariaDB nicht API-kompatibel zu MySQL sein?
>   Exim behandelt aktuell beide Libraries gleich (bis auf etwas Magie zum
>   ermitteln der aktuellen Version, da gab es wohl unterschiedliche
>   Defines in den beiden Libs)
> 

Anscheinend nicht ;) Die nutzen auch eine mysql_optionsv() Methode
anstelle der mysql_options(). Und ja das Versionsschema ist schräg.

> > > Vielleicht
> > >         mysql_servers = <; [tls://]localhost[:port]/DB/User/Passwort
> > 
> > Das würde dann das Verhalten implizieren TLS zu erzwingen wenn tls://
> > als Protokoll angegeben wurde und darauf bestehen verschlüsselt zu
> > kommuizieren?
> 
> So war der Gedanke. Aber wie Du selbst weiter unten ausführst, wird es
> schnell unübersichtlich, wenn man da noch weitere Optionen mit einbauen
> möchte (CA, Client-Cert, Ciphers, …)

Dann lege ich mal vor (siehe Patch im Anhang).

Folgende Umstände sind derzeit zu beachten:

  - Connection String wird von links nach rechts geparsed und es werden
    4 Argumente erwartet: Host Datenbank Username Passwort.

  - Das Passwort mag auch Slashes enthalten, es wird einfach der Rest
    des Strings nach dem 3. Slash verwendet

  - Doppelpunkte! Kann man nicht verwenden, der String aus der
    Konfiguration in "mysql_servers" ist komplett, als "server" in
    perform_mysql_search() kommt dann nur ein Substring bis zum ersten
    ":" an. (habe nicht in lf_sqlperform() geschaut, aber vermute das
    dort ein bisschen zuviel santized wird).

    Ich gehe davon aus, dass ein Passwort mit ":" nicht funktioniert:)

Also tls:// ist eine schlechte Idee. Habe jetzt die Syntax auf tls@host
definiert (finde ich lesbar: verbinde mit host via tls.)

Das sollte auch nicht zu Irritationen führen, man 7 hostname:

"Valid characters for hostnames are ASCII(7) letters from a to z, the
digits from 0 to 9, and the hyphen (-)."

> 
> > Hier geht es dann weiter und man müsste mindestens prüfen ob das Server
> > Zertifikat durch eine vertrauenswürdige CA ausgestellt wurde (und ggf.
> > noch nicht revoked). Dafür brauchen wir:
> > 
> >   MYSQL_OPT_SSL_CA || MYSQL_OPT_SSL_CAPATH
> >   MYSQL_OPT_SSL_CRL || MYSQL_OPT_SSL_CRLPATH
> > 
> > Worauf ich hinaus will: mit mysql_servers ist es bestimmt nicht getan.
> 
> Full Ack.
> 
> >   tls_mysql_servers // für den connection String
> >   tls_mysql_servers_verify // default true, anderenfalls fallback
> >     unencrypted
> 
> Na, aber z.B. kann man den MySQL-Server auch direkt beim Lookup mitgeben
> 
>         … = ${lookup mysql,servers=… {SELECT ...}}
> und ich glaube, auch so:
>         … = mysql,servers=…;SELECT ...
> 
> Es wäre also gut, wenn man alle erforderliche Information in *einen*
> String bekäme.
> 
>         mysql_servers = host[:port]/db/user/password/tls=required,ca=…,

Das wäre jetzt "mysql_servers = [tls@]host[:port]/db/user/password"

> Dann gäbe es nur ein Problem, wenn das Passwort einen Slash enthält,
> aber ich denke, das Problem haben wir jetzt schon (habe jetzt nicht in
> den Sourcen nachgeschaut, ob wir das mit Quting oder Escaping umgehen
> könnten.

Das Problem würde sich erst ergeben, wenn man die TLS Optionen als 5.
Argument einführen möchte und dann muss man die bisherige Logik unnötig
verkomplizieren.

> 
> Wenn also alle erforderliche Info in einem String steckt, könnten wir
> das auch problemlos bei der expliziten Verwendung unterbringen. Wird
> natürlich dann nicht mehr schön
> 
>         … = ${lookup 
> mysql,servers=localhost/db/user/password/tls=required,ca=…{SELECT …}}
> 

Sollte auch mit "…,servers=[tls@]host[:port]/db/user/password" klappen.

> Aber wir haben ja Macros.
> 
> Zur Zertifikatsprüfung: Ich würde jetzt mal unterstellen, dass die MySQL
> libs sich da mit irgendwelchen dazugenommen TLS libs auch selbst
> kümmern, oder? Wenn man an die Verbindungsdetails dran kommt, könnte man
> aber sicher auch Zertifikats-Prüfungs-Funktionen nutzen, die der Exim
> schon bringt (wobei auch der m.W. sich dort auf GnuTLS bzw. OpenSSL
> verlässt).
> 

Habe bisher nur ein Standard dpkg-buildpackage -uc -us mit GnuTLS
getestet.

07:34:32 10541 MYSQL new connection: host=localhost port=0 socket=NULL 
database=mail user=mail
07:34:32 10541 GnuTLS<3>: ASSERT: 
../../../lib/x509/common.c[_gnutls_x509_get_raw_field2]:1575
[…]
07:34:32 10541 GnuTLS<2>: issuer in verification was not found or insecure; 
trying against trust list
[…]
07:34:32 10541 MYSQL connection: TLS negotiated cipher is DHE-RSA-AES256-SHA

Abgesehen von der "issuer in verification was not found or insecure"
Meldung passiert hier erstmal nichts an Verifikation. Hier müsste man
also Hand anlegen und schauen wie das in den transports mit
host_require_tls funktioniert.

> 
> > Denke die Ausführungen erklären was ich meinte, es ging mir darum wie
> > dreckig man sich die Finger machen müsste:)
> 
> Ich glaube, es wird nicht soo schlimm.

Ja Hände waschen mit Seife hat zunächst gereicht;)

> -- 
> Heiko

Gruß Bert
--- exim4-4.92.orig/src/lookups/mysql.c
+++ exim4-4.92/src/lookups/mysql.c
@@ -146,6 +146,12 @@ gstring * result = NULL;
 mysql_connection *cn;
 uschar *server_copy = NULL;
 uschar *sdata[4];
+BOOL req_tls = FALSE;
+
+/* Require TLS if hostname is prefixed by "tls@" */
+
+if (Ustrlen(server) > 4 && Ustrncmp(server, US"tls@", 4) == 0)
+  req_tls = TRUE;
 
 /* Disaggregate the parameters from the server argument. The order is host,
 database, user, password. We can write to the string, since it is in a
@@ -166,7 +172,10 @@ for (i = 3; i > 0; i--)
   sdata[i] = pp;
   if (i == 3) server_copy = string_copy(server);  /* sans password */
   }
-sdata[0] = server;   /* What's left at the start */
+if (req_tls)
+  sdata[0] = server + 4; /* Strip tls@ prefix from hostname */
+else
+  sdata[0] = server;   /* What's left at the start */
 
 /* See if we have a cached connection to the server */
 
@@ -234,6 +243,22 @@ if (!cn)
 
   mysql_handle = store_get(sizeof(MYSQL));
   mysql_init(mysql_handle);
+
+  /* Prepare and enforce TLS connection if requested */
+
+  if (req_tls)
+    {
+
+#if defined                    MARIADB_VERSION_ID
+  my_bool enforce = 1;
+  mysql_optionsv(mysql_handle, MYSQL_OPT_SSL_ENFORCE, (void *) &enforce);
+
+#elif defined                  MYSQL_VERSION_ID
+  mysql_options(mysql_handle,  MYSQL_OPT_SSL_MODE, SSL_MODE_REQUIRED);
+
+#endif
+    }
+
   mysql_options(mysql_handle, MYSQL_READ_DEFAULT_GROUP, CS group);
   if (mysql_real_connect(mysql_handle,
       /*  host        user         passwd     database */
@@ -246,6 +271,21 @@ if (!cn)
     goto MYSQL_EXIT;
     }
 
+  /* Verify TLS connection has been negotiated as requested */
+
+  if (req_tls)
+    {
+    uschar *cipher = mysql_get_ssl_cipher(mysql_handle);
+    if (cipher == NULL)
+      {
+        *errmsg = US"MYSQL connection: TLS negotiation failed";
+        *defer_break = FALSE;
+        goto MYSQL_EXIT;
+      }
+    DEBUG(D_lookup)
+      debug_printf("MYSQL connection: TLS negotiated cipher is %s\n", cipher);
+    }
+
   /* Add the connection to the cache */
 
   cn = store_get(sizeof(mysql_connection));

Antwort per Email an