Enterprise, my ass

Es war nur eine vergleichsweise unscheinbare Supportanfrage. Über das Webmail-System könnten gerade keine Mails mehr verschickt werden – es gäbe einen Authentifizierungsfehler. Merkwürdig ist das vor allem aus einem Grund: Für SMTP AUTH werden die gleichen Zugangsdaten verwendet wie für IMAP, und die stimmen – sonst hätte sich der User gar nicht erst am Webmail-System einloggen können. Die Möglichkeit, für SMTP AUTH davon abweichende Daten einzustellen, gibt es überhaupt nicht. Wie kann es also sein, dass Zugangsdaten für IMAP funktionieren, aber für SMTP AUTH nicht mehr?

Praktischerweise hat Roundcube die Option $rcmail_config['smtp_debug'], mit der man zu Debugzwecken die gesamte SMTP-Session loggen kann. Das habe ich gemacht. So sah’s aus:

[09-Aug-2012 12:16:47 +0200]: Recv: 220 helium.uberspace.de ESMTP
[09-Aug-2012 12:16:47 +0200]: Send: EHLO webmail.taurus.uberspace.de
[09-Aug-2012 12:16:47 +0200]: Recv: 250-helium.uberspace.de
[09-Aug-2012 12:16:47 +0200]: Recv: 250-STARTTLS
[09-Aug-2012 12:16:47 +0200]: Recv: 250-PIPELINING
[09-Aug-2012 12:16:47 +0200]: Recv: 250-8BITMIME
[09-Aug-2012 12:16:47 +0200]: Recv: 250-SIZE 0
[09-Aug-2012 12:16:47 +0200]: Recv: 250 AUTH LOGIN PLAIN
[09-Aug-2012 12:16:47 +0200]: Send: AUTH LOGIN
[09-Aug-2012 12:16:47 +0200]: Recv: 334 VXNlcm5hbWU6
[09-Aug-2012 12:16:47 +0200]: Send: [...]
[09-Aug-2012 12:16:47 +0200]: Recv: 334 UGFzc3dvcmQ6
[09-Aug-2012 12:16:47 +0200]: Send: [...]
[09-Aug-2012 12:16:52 +0200]: Recv: 535 authentication failed (#5.7.1)
[09-Aug-2012 12:16:52 +0200]: Send: RSET
[09-Aug-2012 12:16:52 +0200]: Recv: 250 flushed
[09-Aug-2012 12:16:52 +0200]: Send: QUIT
[09-Aug-2012 12:16:52 +0200]: Recv: 221 helium.uberspace.de

Nun, das ist interessant. Am EHLO ist nämlich gut zu erkennen, dass wir uns hier auf taurus befinden. Der SMTP-Server meldet sich allerdings mit dem Namen helium, was zwar auch einer unserer Server ist (nämlich der, auf dem Uberspace.de selbst läuft), aber eben ein anderer. Wie zur..? Schnell die config/main.inc.php kontrolliert, aber dort steht ganz klar:

$rcmail_config['smtp_server'] = 'localhost';

Wie kann also der Host taurus, wenn er localhost kontaktiert, auf einem ganz anderen Host rauskommen?! Selbstverständlich ist in der /etc/hosts durchaus fest 127.0.0.1 localhost eingetragen; es kann also eigentlich nicht mal ein DNS-Problem sein.

Ich habe mich dann mit strace an den PHP-Prozess geklemmt, erneut versucht eine Mail zu versenden und zu beobachten, was passiert. Was klar erkennbar war: Es wurde eine DNS-Auflösung von localhost.uberspace.de gemacht, die dann 82.98.87.96 zurücklieferte – die IP von helium.

Nachdem ich ein bisschen Debugging in die PHP-Klassen Net_SMTP und Net_Socket reingehackt hatte (die Roundcube für den Mailversand verwendet), war klar, dass hier nicht plötzlich etwas anderes passiert als sonst. Das wäre auch unlogisch gewesen, aber seien wir ehrlich, das ganze Problem ist schließlich unlogisch. Also habe ich erstmal versucht, das Problem zu isolieren, mit folgendem Testscript:

[webmail@taurus ~]$ cat test.php
<?php echo gethostbyname("localhost"); ?>

Ist klar, oder? Gut, dann führen wir das mal aus:

[webmail@taurus ~]$ php test.php 
82.98.87.96

What! The! Fuck! Wie kann denn bitte das sein? Es geht doch nicht von heute auf morgen in einem unangetasteten PHP die gethostbyname-Funktion plötzlich kaputtgehen …

Eine überraschend kurze Recherche nach „php gethostbyname localhost“ brachte mich auf einen Forenbeitrag mit dem vielsagenden Titel kernel upgraded, now php_network_getaddresses can’t resolve localhost, der auch schnell deutlich machte, dass es weniger der Kernel war, sondern:

I had a similar problem yesterday on Centos 5. It turns out a sudo updated corrupted the selinux context of /etc/nsswitch.conf which made all localhost lookups fail. See if it works with selinux set to permissive. If so, try restorecon /etc/nsswitch.conf and reset selinux to enforcing. Good luck.

Nun setzen wir allerdings SELinux bei uns gar nicht ein, insofern konnte es kein Problem des security contexts sein – auch wenn das durchaus bei anderen auch ein Problem war, zu dem Red Hat kurz darauf einen Patch herausgegeben hat. Und dieses bereits gepatchte sudo-RPM ist auch das, was wir hier einsetzen – also das, in dem jener Bug eigentlich schon gefixt war. Aber ein sudo-Update haben wir durchaus auch gemacht. Gerade eben nämlich. Und jetzt geht localhost nicht mehr.

Der Bugreport wies aber indirekt auf ein anderes Problem hin: Das SELinux-Problem bestand nämlich darin, dass die /etc/nsswitch.conf plötzlich auf rpm_script_tmp_t gelabelt war. Das macht man ja nun nicht einfach so zum Spaß. Vielmehr roch es danach, dass das sudo-Update irgendwie an der Datei rumfummelt, und zwar, in dem es eine temporäre Datei erstellt (die dann korrekt das Label rpm_script_tmp_t erhält) und jene dann mittels mv auf die eigentliche Datei drüberbewegt wird. Und da die /etc/nsswitch.conf kein Bestandteil des sudo-RPMs ist, kann es eigentlich nur eine Stelle geben, mit der sudo an der Datei rummachen kann:

[root@taurus ~]# rpm -q --scripts sudo
postinstall scriptlet (using /bin/sh):
/bin/chmod 0440 /etc/sudoers || :

#
# rhbz#841070: remove the old line, the new one won't be matched
#              by the old regexp and won't be deleted on upgrade
#
if grep -q '^sudoers: files ldap$' "/etc/nsswitch.conf"; then
   NSSWITCH_TMPFILE=$(mktemp)
   grep -v '^sudoers: files ldap$' "/etc/nsswitch.conf" > "$NSSWITCH_TMPFILE" && \
   mv -f "$NSSWITCH_TMPFILE" "/etc/nsswitch.conf"
   restorecon "/etc/nsswitch.conf"
fi

if ! grep -q '^[[:space:]]*sudoers:' "/etc/nsswitch.conf"; then
   # No "sudoers:" line in nsswitch.conf, add a default one
   echo "sudoers:  files ldap" >> "/etc/nsswitch.conf"
   restorecon "/etc/nsswitch.conf"
fi
[...]

Wir sehen hier also, dass dann, wenn in der /etc/nsswitch.conf die Zeile sudoers: files ldap steht, jene entfernt wird und im zweiten Codeblock durch sudoers: files ldap ersetzt wird. Ja: Die einzige Änderung beläuft sich darauf, dass hier nun zwei Leerzeichen drinstehen, und die Historie dieser Änderung ist wiederum eine ganz eigene Horrorgeschichte.

Was hier gut erkennbar ist: Es wird eine temporäre Datei mit mktemp angelegt, um den bisherigen Inhalt der /etc/nsswitch.conf abzüglich der zu entfernenden Zeile reinzuschreiben, die neue Zeile zu ergänzen und den Inhalt zurückzuschreiben. mktemp also. In dessen man page klar zu lesen ist:

If mktemp can successfully generate a unique filename, the file (or directory) is created with file permissions such that it is only readable and writable by its owner (unless the -u flag is given) and the filename is printed to standard output.

Und genau so – ohne irgendein chmod – wird die Datei dann anschließend auf die /etc/nsswitch.conf umbenannt: „only readable and writable by its owner“, und der wäre dann in diesem Fall root. Und das bringt dann diesen „lustigen“ Effekt mit sich (hier mal an einem Test-Host verifiziert und nicht mehr auf taurus):

[root@lyra ~]# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 lyra.uberspace.de ESMTP

vs.

[webmail@lyra ~]$ telnet localhost 25
Trying 82.98.87.96...
Connected to localhost.
Escape character is '^]'.
220 helium.selfhost.de ESMTP

Wenn User nämlich die /etc/nsswitch.conf nicht mehr lesen können, in der die Zeile hosts: files dns steht und gethostbyname dazu anweist, erst in der /etc/hosts zu schauen, bevor es das DNS bemüht, dann spielt schon mal keine Rolle mehr, was in der /etc/hosts so drinsteht. Und wenn der aufzulösende Hostname keinen Punkt beinhaltet, verhält sich gethostbyname wie folgt:

If name doesn’t end in a dot and the environment variable HOSTALIASES is set, the alias file pointed to by HOSTALIASES will first be searched for name (see hostname(7) for the file format). The current domain and its parents are searched unless name ends in a dot.

Das bedeutet: gethostbyname versucht gar nicht erst, localhost via DNS aufzulösen (denn unser DNS-Resolver würde dafür direkt 127.0.0.1 liefern), sondern hängt direkt den eigenen Domainnamen an – in diesem Fall uberspace.de, so dass also faktisch via DNS nach localhost.uberspace.de gesucht wurde, was wegen eines Wildcard-Records im DNS dann auf die IP 82.98.87.96 auflöste.

Beim CentOS-Projekt gibt es einen Bug-Report dazu: Post-install script for sudo sets /etc/nsswitch.conf to mode 600. Er wurde auch upstream gemeldet, also im Red-Hat-Bugtracker, aber das Problem ist immer noch offen: Wer also heute unter CentOS 5 sein sudo updated, kann auch morgen noch localhost nicht mehr oder falsch auflösen. Vor diesem Hintergrund muss man fast froh über das wenig intuitive Verhalten von MySQL sein, das den Hostnamen localhost als implizite Aufforderung ansieht, sich doch bitte über einen Unix-Socket mit dem Server zu verbinden (möchte man eine TCP-Verbindung, muss man explizit 127.0.0.1 angeben), so dass alle Verbindungen zum MySQL-Server auf localhost von diesem Problem unbeeinträchtigt waren.

Hand aufs Herz, Bugs dieses Kalibers hätte ich nicht in einer Distribution erwartet, die ihren Fokus explizit nicht darauf hat, immer die neuesten Versionen von allem einzusetzen, sondern die bestehenden Versionen so sensibel wie möglich durch Backporting von Bugfixes zu pflegen, damit nur ja keine unvorhergesehenen Probleme auftreten – zumal es ja nicht mal ein Bug in sudo war, sondern sudo lediglich der Transporteur für einen Haufen abenteuerliches und unvorsichtiges Shellscripting war. Ich kann nur hoffen, dass das eine ärgerliche Ausnahme bleibt – und vor allem schnell gefixt wird.

Update 16.08.2012: Wunderbar, in aktualisierten sudo-RPMs findet sich nun im %postinstall:

#
# rhbz#846631: fix permissions on nsswitch.conf
#
if [ "$(stat -c '%a' "/etc/nsswitch.conf")" = "600" ]; then
	chmod 0644 "/etc/nsswitch.conf" || :
fi

4 Antworten auf „Enterprise, my ass“

  1. Auch ein schönes Enterprise Feature.

    Update von RHEL6.2 auf RHEL6.3 ändert die Permission der Infiniband Devices.

  2. Wow, erst 2 (alte) Posts gelesen und ich liebe den Blog jetzt schon. Ein Königreich für einen Job wie diesen…

  3. „Die einzige Änderung beläuft sich darauf, dass hier nun zwei Leerzeichen drinstehen, und die Historie dieser Änderung ist wiederum eine ganz eigene Horrorgeschichte.“

    Diese Horrorgeschichte würde mich auch mal sehr interessieren… Wo kann mensch das nachlesen?

  4. Puh, ich fürchte, das kann ich über ein Jahr nach diesem Artikel selbst nicht mehr wirklich reproduzieren. Tendenziell dürfte sich das auf einen Bugzilla-Eintrag bezogen haben, aber das hab ich echt nicht mehr präsent, sorry.

Kommentare sind geschlossen.