Artikel mit ‘centos’ getagged

Enterprise, my ass

Freitag, 10. August 2012

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

FTP und NFS Mounts

Mittwoch, 16. Februar 2011

Vorsicht: Rant

Ich hasse FTP — die Warze unter den Dateiübertragungsprotokollen. Ich hasse es wirklich, leidenschaftlich und abgrundtief. Es ist ein veraltetes, unsicheres, umständliches, schlecht designtes Protokoll und es gibt so gut wie keine vernünftigen Implementierungen. Die Clients haben fast alle grauenvolle User Interfaces, sind langsam und gelegentlich auch noch instabil. Auf der Serverseite sieht es auch nicht viel besser aus, das meiste was es da gibt ist weder schnell noch schlank und selbst wenn es das ist ist es fast immer unsicher. (Wir verwenden als FTP-Server-Software übrigens vsftpd, der wenigstens in dem Ruf steht sicher zu sein und den man sich auch nicht so leicht unsicher konfigurieren kann. Lieber wäre uns aber, wenn wir einfach darauf verzichten könnten. Das beste FTP ist kein FTP.)

FTP überträgt alle Passwörter im Klartext, die etwas besser gesicherte Erweiterung FTPS (FTP mit SSL) gibt es zwar schon seit einer halben Ewigkeit, wird aber immer noch längst nicht von jedem genutzt und den meisten Clients kann man auch nicht oder nur schwer beibiegen, daß sie auf gar keinen Fall Passwörter im Klartext übermitteln sollen. Selbst wenn FTPS die Passwörter (oder genauer gesagt den gesamten Kommandokanal) verschlüsselt, verschlüsselt es dann aber nicht die übertragenen Dateien (den Datenkanal), die bei sehr vielen Webanwendungen aber wiederum Klartextpasswörter z.B. für MySQL-Datenbanken enthalten. „Secure“ FTP hat den gleichen Fehler, nur daß es statt SSL halt SSH benutzt um den Kommandokanal zu verschlüsseln, in jedem Fall verschlüsselt es nicht die übertragenen Dateien. Und über das Firewalling von FTP möchte ich gar nicht schreiben, da würde ich mir die Tastatur unter den Fingern zerlegen vor lauter in den Tastenanschlag einfließendem Ärger.

Ich kann auch nicht verstehen, wieso FTP immer noch eingesetzt wird. Ich weiß, daß viele Leute nur so auf die Dokumente auf ihren Webservern zugreifen können, aber ich weiß halt auch, daß enorm viele Programme auch Support für das SSH File Transfer Protocol (SFTP, nicht zu verwechseln mit „Secure“ FTP) oder sogar Secure CoPy (SCP) mitbringen und ich weiß auch daß es mit Putty und sshfs und noch ein paar anderen Programmen (deren Namen mir gerade nicht mehr einfallen) wirklich bequeme Methoden gibt Daten sicher zu übertragen. Es gibt auch haufenweise Anleitungen dazu, die Hälfte davon offenbar von entnervten Admins geschrieben, die sich große Mühe geben die Leute dazu zu bewegen doch bitte endlich kein FTP mehr zu benutzen. Und Webhostinganbieter die kein SSH anbieten, sondern nur FTP… gut ich arbeite bei einem Webhostinganbieter der genau das aus gutem Grund nicht tut, insofern ist es naheliegend, daß ich davon nichts halte, trotzdem: FTP ist sowas von alt und überholt, wenn ein Anbieter nichts neueres zu bieten hat, dann ist sein Angebot es einfach nicht wert. Einen Zahnarzt der außer Zähne ziehen keine neueren Lösungen zu bieten hat, würde man bei Zahnschmerzen ja auch nicht bevorzugt aufsuchen.

Warum FTP immer noch so weit verbreitet ist, will mir einfach nicht in den Kopf. Ich finde das schlimm. Es ärgert mich, daß ich mich mit dem Mist auseinandersetzen muß, es ärgert mich das ich dafür Support leisten muß. (Was nicht heißt, daß ich mich im Ungang mit solchen Supportanfragen dann nicht zusammenreiße und trotzdem schnell und freundlich weiterzuhelfen versuche. Den Ärger spare ich mir dann für Momente wie diesen hier auf.) Es ärgert mich, daß unsere Kunden sich damit rumquälen müssen. FTP soll endlich verschwinden. Telnet ist doch auch weitgehend verschwunden, warum dann nicht FTP?

So, nachdem ich mir nun Luft gemacht habe, kommen wir zum eigentlichen Thema:

Mein Kollege Matthias sprach mich neulich an, weil bei einem Kunden ein Problem mit einem Backup aufgetreten war. Offenbar dauerte das Runterladen der Backups per FTP sehr lange. Matthias hat das Problem auch sofort reproduzieren können und so wußten wir gleich, daß die Downloads nicht einfach lange dauerten, sondern im Gegenteil ganz normal funktionierten, aber vor Beginn jedes einzelnen Downloads eine Wartezeit von 30 Sekunden eingelegt wurde.

Kurze Erklärung des Setups: Die Backups liegen auf einem Backupserver, von wo aus wir sie per NFS auf dem Webserver den der Kunde nutzt einbinden, wobei NFS das Verzeichnis mit den Backups nur im Read-Only-Modus holt. Schreibzugriffe aufs Backup sind also vom Webserver aus nicht möglich.

Deswegen dachte ich auch im ersten Moment, daß die Verzögerung wohl eher daher rühren wird, daß entweder NFS irgendwie gestört ist oder der Backupserver zum Zeitpunkt der Downloads zu viel zu tun hatte. Der Backupserver hatte aber zu dem Zeitpunkt an dem mein Kollege und ich das testeten gerade nichts zu tun und NFS hat auch durchaus funktioniert. Wenn ich dieselben Dateien per SCP runtergeladen habe, dann gab es keine Verzögerung.

Ich habe also etwas in den Logs gestöbert und schließlich diese Einträge in /var/log/messages gefunden:


Feb 14 10:01:59 helium kernel: statd: server localhost not responding, timed out
Feb 14 10:01:59 helium kernel: lockd: cannot monitor 82.98.87.73
Feb 14 10:01:59 helium kernel: lockd: failed to monitor 82.98.87.73

(Am Rande sei hier mal angemerkt, daß das Deuten der Logausgaben von NFS eine arkane Kunst ist die ich — wie 99% der Nutzer und Entwickler von NFS — nicht beherrsche. In der Regel kann ein und diesselbe Logausgabe von NFS für mindestens ein Dutzend verschiedene Fehlerursachen stehen und in beinahe allen Fällen enthält der genaue Text der im Log landet inhaltlich keine sachdienlichen Hinweise zur Problemlokalisierung. Obendrein muß man bei NFS schon dankbar sein, wenn es überhaupt Logausgaben gibt. Mit anderen Worten ist das Logging von NFS das absolut allerletzte, wie wohl jeder der schonmal NFS Fehler debuggt hat und dazu im Internet nach Lösungen suchen mußte sofort bestätigen wird. Diese Logausgabe hier verdient besondere Erwähnung, weil sie ausnahmsweise in der Tat direkt etwas mit dem Problem zu tun hat und vergleichsweise leicht zu deuten ist. Genießt es, sowas sieht man nicht allzu oft.)

Davon auf die Spur gebracht hatte ich bald die Ursache des Problems ausgemacht und gelöst: FTP versucht hier einen Lock auf die Datei zu legen und da dies nicht funktioniert, wartet es auf einen Timeout und kopiert die Datei dann trotzdem. — Warum auch immer. Ich hätte ja ein anderes Verhalten erwartet, aber gut. Bleibt nur noch die Frage, warum FTP hier versucht einen Lock zu erlangen, denn es handelt sich ja um ein Read-Only gemountetes Verzeichnis. Eine ohnehin nur lesbare, aber nicht schreibbare Datei zu locken ist weitgehend sinnfrei. Wie auch immer, die Lösung war recht einfach:

chkconfig nfslock on; service nfslock start

Ich gestehe gerne ein, daß ich nfslock von Anfang an hätte laufen lassen sollen, da es prinzipiell für einen geordneten NFS-Betrieb schon dazu gehört. Mea culpa. Aber ich weigere mich zu begreifen, warum FTP hier einen Lock erreichen will. Ein Lock ist dafür gedacht, damit niemand anderes schreibend auf die Datei zugreift, für Lesezugriffe ist das gar nicht gedacht. Ich möchte an der Stelle nocheinmal darauf hinweisen, daß dieses Problem nicht auftritt, wenn ich diesselbe Datei lokal kopiere oder per SSH oder HTTP runterlade. Im Grunde hat mich FTP hier auf einen Konfigurationsfehler den ich bei NFS gemacht hatte hingewiesen und ich bin dafür auch dankbar — nur bin ich halt auch mittlerweile unfähig etwas über FTP zu sagen ohne mich erstmal ausgiebig über FTP an sich aufzuregen.

Gut, da ich es jetzt eh schonmal nachrecherchiert habe, kann ich mein Wissen hier ja nochmal teilen: Wenn man ein CentOS 5 nur als NFS Client benutzen möchte, dann sollte man den Dienst nfslock laufen lassen (geht aber in vielen Fällen auch ohne). Wenn man NFSv4 verwendet und auf’s ID Mapping wert legt, sollte man zusätzlich rpcidmapd laufen lassen. Wenn man NFSv4 verwendet und authentifizieren oder verschlüsseln möchte, dann sollte man zusätzlich rpcgssd laufen lassen. Wenn man mit CentOS 5 einen NFS Server betreiben möchte, dann muß man die Dienste portmap und nfs laufen lassen (die in dieser Reihenfolge zu starten sind). Auch hier brauch man bei NFSv4 für ID Mapping rpcidmapd, für Authentifikation und Verschlüsselung braucht man aber rpcsvcgssd statt rpcgssd.

Zum Abschluß noch ein weiterer Seitenhieb auf NFS. Beim Lesen der Manpage von rpc.lockd bin ich vor Lachen fast vom Stuhl gefallen, sowas schwammiges und uninformatives ist mir lange nicht mehr untergekommen:

The rpc.lockd program starts the NFS lock manager (NLM) on kernels that don’t start it automatically. However, since most kernels do start it automatically, rpc.lockd is usually not required. Even so, running it anyway is harmless.

Na dann ist ja alles klar.

Zum Stand von Maildir-Support bei CentOS 5

Montag, 14. Februar 2011

Wir setzen auf den meisten unserer Server qmail bzw. netqmail als MTA ein und erfreuen uns dabei an den vielen Vorzügen, die das Maildir-Format bietet. Zwar kommt dabei meistens ein zusätzliches Tool wie vpopmail oder vmailmgr zum Tragen, was virtualisierte Mailuser bietet; auf unserer Hosting-Plattform Uberspace.de bieten wir aber ganz offiziellen Support für ein „echtes“ Maildir des betreffenden Systemusers. Die netqmail-Dokumentation erläutert dazu in INSTALL.maildir:

The system administrator can set up Maildir as the default for everybody by creating a maildir in the new-user template directory and replacing ./Mailbox with ./Maildir/ in /var/qmail/rc.

Und so sieht das Setup bei uns dann auch aus: Ein /var/qmail/bin/maildirmake /etc/skel/Maildir legt ein Standard-Maildir an, was beim Anlegen eines neuen Users automatisch in dessen Home-Verzeichnis übertragen wird, und qmail-local stellt Mails, die an den Systemuser adressiert sind, dorthin zu.

Nicht völlig überraschend zeigt sich aber, dass traditionelle Mailboxen in einem zentralen Verzeichnis wie z.B. /var/spool/mail derart fest im Betriebssystem verankert sind, dass es mehr als einen Klimmzug braucht, das System sauber an Maildirs anzupassen. Das fängt schon ganz vorne an:

useradd

Beim Anlegen von Benutzern wird standardmäßig eine (leere) Mailbox in /var/spool/mail/$USER angelegt, bzw. in dem Verzeichnis, das in der /etc/login.defs (siehe dort) via MAIL_DIR angegeben ist. Maildirs werden aber nicht unterstützt; genausowenig Mailboxen, die nicht in einem zentralen Verzeichnis liegen, sondern im Home-Verzeichnis des Benutzers. Dieses Anlegen lässt sich nur über die Einstellung CREATE_MAIL_SPOOL=no in der /etc/default/useradd abschalten – was hier ja durchaus Sinn ergibt, denn durch das Anlegen eines Maildirs in /etc/skel haben wir diesen Punkt ohnehin erledigt. (Mit herkömmlichen Mailboxen funktionierte das nicht, da /etc/skel ja die Vorlage für das Home-Verzeichnis des Users ist, die herkömmlichen Mailboxen aber zentralisiert und damit außerhalb des Home-Verzeichnisses liegen.)

$MAIL

Diese Umgebungsvariable wird von verschiedenen Programmen benutzt, um zu identifizieren, wo denn die Mails eines Users liegen. Hier fackelt CentOS 5 nicht lange – in /etc/profile steht hart kodiert:

if [ -x /usr/bin/id ]; then
        USER="`id -un`"
        LOGNAME=$USER
        MAIL="/var/spool/mail/$USER"
fi

Das ist für unsere Zwecke ergo völlig unbrauchbar. Die /etc/profile selbst anzupassen, empfiehlt sich nicht, weil die Änderungen sonst bei jedem Update des setup-RPMs (zu dem diese Datei gehört) hinfällig wären. Stattdessen bietet sich ein Einzeiler in /etc/profile.d an:

# cat /etc/profile.d/maildir.sh 
export MAIL=~/Maildir/

/etc/login.defs

Die man page der login.defs erklärt offenherzig:

Much of the functionality that used to be provided by the shadow password suite is now handled by PAM. Thus, /etc/login.defs is no longer used by programs such as: login(1), passwd(1), su(1)

Trotzdem ist die Datei noch da und wird von einigen Programmen genutzt. Sie sieht im Kopf wie folgt aus:

# *REQUIRED*
#   Directory where mailboxes reside, _or_ name of file, relative to the
#   home directory.  If you _do_ define both, MAIL_DIR takes precedence.
#   QMAIL_DIR is for Qmail
#
#QMAIL_DIR      Maildir
MAIL_DIR        /var/spool/mail
#MAIL_FILE      .mail

Fängt man erstmal an, an dieser Datei – vermutlich erfolglos – herumzubasteln, bekommt man wirklich graue Haare. Die naheliegendste Variante, nämlich den Eintrag QMAIL_DIR durch Entfernen des Rautenzeichens zu aktivieren, ist überraschenderweise keine gute Idee; dass die man page diese Einstellung gar nicht erwähnt, lässt schon tief blicken. So sieht’s dann nämlich aus:

# useradd dummy
configuration error - unknown item 'QMAIL_DIR' (notify administrator)

Fan-tas-tisch. Die NEWS-Datei der shadow-utils erklärt denn auch, dass die Einstellung aus der login.defs entfernt wurde – und zwar 2005. Interessanterweise kennt die man page auch die Einstellung MAIL_FILE nicht, sondern ausschließlich MAIL_DIR. Sie weiß aber auch gleich zu verkünden, dass nur usermod und userdel diese Einstellung benutzen – und useradd sowieso nicht. Was aber sehen wir in src/useradd.c?

      if (strcasecmp (create_mail_spool, "yes") == 0) {
              spool = getdef_str ("MAIL_DIR") ? : "/var/mail";
              file = alloca (strlen (spool) + strlen (user_name) + 2);
              sprintf (file, "%s/%s", spool, user_name);
              fd = open (file, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0);
              [...]
      }

Ist also glatt gelogen; useradd benutzt die Einstellung sehr wohl. Die man page von useradd bekräftigt das auch und erwähnt nicht nur MAIL_DIR, sondern auch MAIL_FILE und behauptet:

The MAIL_DIR and MAIL_FILE variables are used by useradd, usermod, and userdel to create, move, or delete the user´s mail spool.

Das ist nun wiederum auch gelogen, denn wenn MAIL_FILE statt MAIL_DIR gesetzt ist, macht useradd damit … überhaupt nichts. Und auch usermod und userdel, die beim Löschen eines Users dessen Mailbox aus dem zentralen mit MAIL_DIR angegebenen Verzeichnis löschen, unternehmen in Bezug auf Mail schlicht gar nichts, wenn MAIL_FILE gesetzt ist.

Nun, noch weiter in die Tiefe wollen wir hier nicht gehen. Belassen wir es dabei, dass das, was in der /etc/login.defs steht, das, was in der zugehörigen man page steht, das, was in den man pages von useradd, usermod und userdel steht, und schließlich das, was im Code der shadow-utils steht, erheblich voneinander abweicht und sich oftmals sogar widerspricht – kurz, das Ganze funktioniert nur dann sauber, wenn man exakt den traditionellen Standard benutzt: Mailboxen in einem zentralen Verzeichnis.

mailx

CentOS benutzt das mailx-Paket, um den Befehl mail bereitzustellen. Die meisten kennen den Befehl vornehmlich zu dem Zweck, um auf der Kommandozeile direkt Mails via Pipe versenden zu können (some-command | mail recipient@domain.tld). Allerdings ist mail auch ein Mailreader, der auf die Umgebungsvariable MAIL zurückgreift – was von daher erstaunlich ist, weil diese Variable gerade nicht zu denen gehört, die mail laut man page konsultiert:

Mail utilizes the HOME, USER, SHELL, DEAD, PAGER, LISTER, EDITOR, VISUAL and MBOX environment variables.

Trotzdem wird sie benutzt und zeigt dann auch gleich, wieso hier ohnehin Hopfen und Malz verloren ist:

$ mail
/home/jonas/Maildir/: Is a directory

Das mailx-Paket von CentOS hat also schlicht keinen Maildir-Support. Mit dem aktualisierten Paket, das sich in CentOS 6 findet, wird das offenbar kein Problem mehr sein, aber darum soll es hier ja nicht gehen.

bash

Hier endlich mal ein Lichtblick: Die bash unterstützt die Umgebungsvariable MAIL und kann auch korrekt mit Maildirs umgehen. Sie benutzt es, um standardmäßig alle 60 Sekunden nach neuen Mails zu suchen und den Benutzer zu informieren, und das klappt auch:

$ echo | mail $USER ; sleep 60 ; true
You have new mail in /home/jonas/Maildir/

PAM

Möchte man auch direkt beim SSH-Login über neue Mails informiert werden, so kann dies zeitgemäß mit pam_mail.so erledigt werden. Dazu braucht’s nur eine Zeile (danach sshd-Neustart nicht vergessen, sonst wirkt es nicht):

$ tail -1 /etc/pam.d/sshd 
session    optional     pam_mail.so dir=~/Maildir

Die Angabe von dir ist nötig, weil pam_mail.so ansonsten ganz traditionell in /var/mail nach einer Mailbox oder einem Maildir sucht (mit anderen Worten: Überraschung! Dass evtl. ein anderes Verzeichnis als MAIL_DIR in /etc/login.defs definiert ist, spielt überhaupt keine Rolle). Es erkennt automatisch, ob es eine Mailbox oder ein Maildir ist, denn eine Mailbox wäre ja eine Datei, während ein Maildir ein Verzeichnis wäre. So sieht es dann in der Anwendung aus:

$ ssh jonas@helium.uberspace.de
You have new mail in folder /home/jonas/Maildir.

mkinitrd und Filesystem-Labels

Freitag, 09. Juli 2010

Au Backe, das hätte deutlich schiefer gehen können.

Nicht selten betreiben wir Server mit einem Xen-Kernel als Dom0 und binden die DomU aber nicht von einer lokalen Festplatte ein, sondern via iSCSI. Das stellt sich dann so dar, dass die Dom0 eine /dev/sda hat (z.B. eine eingebaute Festplatte); das iSCSI-Target wird dann eingebunden und steht als /dev/sdb bereit. Von diesem Device werden dann aber keine Partionen direkt gemountet, sondern das Device wird in der Xen-Konfiguration der DomU als phy:-Device angesprochen. So weit, so gut – klappt auch einwandfrei.

Nun hat der anaconda-Installer von CentOS 5 die Angewohnheit, für die Root- und die /boot-Partition Labels zu vergeben. Diese kann man auf der Shell z. B. mit e2label einsehen und auch nachträglich jederzeit ändern. In der /etc/fstab wird dann die Root-Partition nicht mit /dev/sda2 referenziert, sondern mit LABEL=/, weil die Partition eben das Label namens „/“ erhalten hat. Der Gedanke dahinter ist eigentlich gar nicht mal schlecht: Ändert ein Device seine Bezeichnung, beispielsweise weil ein zusätzliches Gerät am SCSI-Bus hängt und eine sda plötzlich eine sdb ist, so kann es dennoch unverändert angesprochen werden, weil es sein Label behält. Und das funktioniert auch prima.

Bis auf eins.

Wir führten auf einer Dom0 ein Kernel-Update durch. In diesem Zuge wurde auch eine neue initrd erstellt. Hierbei stellt mkinitrd alle für den Bootvorgang nötigen Module zusammen und strickt ein minimales init-Script. Das hat auch tausend Mal prima funktioniert – bis heute. Heute war nämlich gerade ein iSCSI-Target eingebunden – was an sich auch kein Problem ist, denn mkinitrd interessiert sich, was Treiber für Blockdevices angeht, eigentlich nur für die Devices, die in der /etc/fstab als zum Booten nötig referenziert werden. Nun nennt die /etc/fstab aber LABEL=/ als Root-Device, und damit nimmt das Unheil seinen Lauf. Zunächst einen Blick auf die Konfiguration:

# head -1 /etc/fstab 
LABEL=/ / ext3 defaults 1 1

# e2label /dev/sda2
/

# df -h | head -2
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda2              31G  1.9G   27G   7% /

Und jetzt das Problem. Es ist wie gesagt gerade ein iSCSI-Target eingebunden, das vom Kernel auf /dev/sdb gemappt wird und das nur eine Partition hat:

# ls -l /dev/disk/by-path/ip-*
lrwxrwxrwx 1 root root  9 Jul  8 22:32 /dev/disk/by-path/[...]-lun-0 -> ../../sdb
lrwxrwxrwx 1 root root 10 Jul  8 22:32 /dev/disk/by-path/[...]-lun-0-part1 -> ../../sdb1

Und eben jene Partition … hat auch das Label „/“:

# e2label /dev/sdb1
/

mkinitrd -v zeigt dann, dass es zwar versucht, das Richtige zu tun (nämlich herauszubekommen, was für eine Art Device die Root-Partition ist, damit es die dafür nötigen Treiber in die initrd einbinden kann, löst das Label aber prompt auf genau das falsche Device auf:

...
Found root device sdb1 for LABEL=/
Looking for driver for device sdb1
Found iscsi component /sys/devices/platform/host6/session5/target6:0:0/6:0:0:0
...

Damit erstellt es letztlich eine initrd mit dem falschen Root-Device – und baut dazu den gesamten iSCSI-Support mit ein, bis hin zu einem iscsistart-Aufruf, der die Zugangsdaten beinhaltet, die nötig sind, um das Target vom iSCSI-Host einbinden zu können. Ohne das -v dabei sieht man davon … nix. Vor allem sahen wir schon deshalb nichts davon, weil wir nicht mal mkinitrd selbst aufgerufen hatten – das hatte new-kernel-pkg aufgerufen, und auch das wiederum hatten wir nicht aktiv benutzt, sondern es befindet sich im %postinstall-Abschnitt des Kernel-RPMs, das wir gerade mit yum upgrade kernel-xen auf den neuesten Stand gebracht haben. Wie schon einige Male davor, immer ohne Probleme. Davor war eben zum Zeitpunkt der Kernel-Updates auch noch kein iSCSI-Target eingebunden.

Nach einem Reboot zeigte sich ein absurdes Bild. Beim Booten war deutlich erkennbar, wie sich der Server faktisch das iSCSI-Device holte und als /dev/sda vorzeigte; die lokale Festplatte war dadurch nun plötzlich /dev/sdb (in diesem Zusammenhang mag man ein zynisches „Ja und, dafür gibt’s doch die Labels!“ einwerfen. Sehr witzig, haha). Merkwürdigerweise war der Inhalt der Root-Partition definitiv der, der von der Root-Partition der lokalen Platte kam – obwohl jene laut mount das iSCSI-Device sein sollte. Kurz, das System befand sich in nicht wirklich definiertem Zustand, und darüberhinaus mussten wir uns darum sorgen, dass das fragliche iSCSI-Device, das gerade angeblich unsere Root-Partition darstellte (aber irgendwie eben auch wieder nicht) Schaden davontragen könnte, weil es nämlich parallel dazu auf einem anderen Xen-Host bereits eingebunden und aktiv in Benutzung war. Wir können uns bislang nicht erklären, wie es zu dieser merkwürdigen Differenz zwischen angeblichem und realem Mount-Zustand kommen konnte, aber auf wundersame Weise blieb das iSCSI-Target von diesem Schindluder völlig unbeeindruckt. Puh..!

Der Bugfix war letztlich trivial: Wir haben in der /etc/fstab der Dom0 alle Labels durch echte Devicenamen ersetzt und die initrd neu erstellt. Ein Reboot, und das System befand sich wieder in definiertem Zustand. Für heute: Mit einer großen Portion Glück davongekommen. Und viel über mkinitrd gelernt.

IPv6-Tunnel mit 6in4 unter CentOS

Mittwoch, 06. Januar 2010

Ich hab mir vor einer Weile bei tunnelbroker.net von Hurricane Electric einen Account geklickt und mir darüber einen IPv6-Tunnel zugelegt. IPv6-Tunnel sind derzeit leider noch ein notwendiges Übel, da bisher bei nahezu keinem DSL-Anbieter in Deutschland IPv6 verfügbar ist. Die wenigen die überhaupt IPv6 anbieten verlangen dafür meines Wissens unabhängig von der normalen Grundgebür eine Extra-Gebühr, außerdem sind es soweit ich es bisher mitbekommen habe ausnahmslos Reseller des ehemaligen Fernmeldeamtes der Bundespost.

Bei Hurricane Electric bekommt man kostenlos einen Account über den man bis zu vier IPv6-Tunnel nutzen kann. Dabei wird einem jeweils ein /64er Netz zugewiesen. Dessen Dimension muß man sich am Anfang erstmal verdeutlichen: Der gesamte IPv4-Adressraum ist 32 Bit lang, man bekommt nun ein Netz der doppelten Länge, hat also nicht rund 4 Milliarden Adressen zur Verfügung, sondern 4 Milliarden zum Quadrat. So astronomisch dies klingen mag: das ist die Standard-Netzgröße bei IPv6. Man kann das Netz verkleinern wenn man unbedingt möchte, es empfiehlt sich aber nicht. Erstens sind 64 Bit für das lokale Netz nur die Hälfte der verfügbaren Bits in einer IPv6-Adresse, d.h. es gibt nochmal 4 Milliarden zum Quadrat solcher Netze (also mehr als genug), zweitens bieten nur Netze mit dieser Standard-Größe das durchaus interessante Feature Autokonfiguration an.

Mit vier Tunneln kann man also vier /64er Netze bei Hurricane nutzen. Wem das noch nicht ausreicht, weil er z.B. hinter dem Tunnel mehrere /64er Subnetze betreiben möchte (z.B. ein WLAN, eine DMZ, ein internes Netz), der kann sich auch ein /48er IPv6-Netze erklicken, die dann ebenfalls über die Tunnel geroutet werden. Mit einem /48er verfügbt man dann über 32.768 /64er Subnetze.

Für den Tunnelaufbau werden bei Hurricane angenehmerweise sehr einfache und sehr direkte Konfigurationsanleitungen für eine Vielzahl von Betriebssystemen angeboten, wobei diese aber allesamt für ein Publikum gedacht sind das die Kommandozeile nicht scheut (dies dürfter allerdings 99% der derzeitigen IPv6-Enthusiasten abdecken).

Für Linux gibt es zwei Methoden. Hier zunächst die traditionelle:


ifconfig sit0 up
ifconfig sit0 inet6 tunnel ::$REMOTEIPV4
ifconfig sit1 up
ifconfig sit1 inet6 add $LOCALIPV4
route -A inet6 add ::/0 dev sit1

Und hier die moderne:


modprobe ipv6
ip tunnel add he-ipv6 mode sit remote $REMOTEIPV4 local $LOCALIPV4 ttl 255
ip link set he-ipv6 up
ip addr add $LOCALIPV6 dev he-ipv6
ip route add ::/0 dev he-ipv6
ip -f inet6 addr

(Hurricane gibt auf ihrer Webseite diese Befehle schon mit den richtigen IPs eingetragen aus, so daß man sie in die Kommandozeile kopieren kann.)

In beiden Fällen wird buchstäblich der Weg des geringsten Widerstands gegangen, anders als die meisten Anbieter von IPv6-Tunneln die ich bisher kennengelernt habe, wird einem nicht die Verwendung abstruser Daemons aufgezwungen, die einen dann gegenüber dem Tunnelserver aufwendig authentifizieren. (Das war früher besonders eklig, weil man die Software ganz oft noch selbst übersetzen mußte, dann init-Skripte schreiben mußte und sie oft nicht ganz einfach zu konfigurieren war.) Sollte man unter den für diese Aufgabe geeigneten Daemons einen brauchbaren finden, kann man diesen natürlich trotzdem verwenden.

Wer darauf verzichten kann, dem wird es leicht gemacht, denn unter https://ipv4.tunnelbroker.net/ipv4_end.php kann man seinen Tunnelendpunkt aktualisieren, wobei man von dort auch eine Anleitung und ggf. Fehlermeldungen bekommt. Die Anleitung ist wieder angenehm einfach:


Please use the format https://ipv4.tunnelbroker.net/ipv4_end.php?ipv4b=$IPV4ADDR&pass=$MD5PASS&user_id=$USERID&tunnel_id=$GTUNID
Where:

$IPV4ADDR = The new IPv4 Endpoint (AUTO to use the requesting client's IP address)
$MD5PASS = The MD5 Hash of your password
$USERID = The UserID from the main page of the tunnelbroker (not your username)
$GTUNID = The Global Tunnel ID from the tunnel_details page

Ich hab mir für diese Aufgabe ein Skript geschrieben, das hier heruntergeladen werden kann.

Nun muß man nur noch dafür sorgen, daß dieses Skript auch ausgeführt wird, wenn sich die lokale IPv4 ändert. Auf einem Laptop kann das durchaus komplizierter werden, aber auf einem „ortsstabilen“ Rechner läßt sich dies wunderbar mit cron oder am besten mit runwhen lösen, das ich an dieser Stelle mal wieder empfehlen möchte.

Am besten kombiniert man die Aktualisierung des Tunnelendpunktes gleich mit dem nächtlichen DSL-Reconnect. Ich habe auf meinem Heimserver ein Skript laufen, das morgens um 5 die DSL-Verbindung resettet um der leidigen Zwangstrennung durch den Provider zuvorzukommen und vor allem um diese Trennung auf eine Zeit zu verlegen, zu der ich mit hoher Wahrscheinlichkeit nicht am Rechner arbeite. Dieses Skript habe ich nun um eine Zeile erweitert, damit der IPv6-Tunnel aktualisiert wird, sobald die DSL-Verbindung wieder steht. (Dies ist natürlich nur nötig, wenn man keine statische IPv4 hat, also bei der Zwangstrennung seine IP wechselt. Ansonsten läuft der Tunnel ganz normal weiter.) Bei runwhen geht das sehr einfach mit einem svc -a /service/tunnelbroker-update. Der Vorteil von runwhen ist hierbei, daß man mit den bei daemontools oder runit üblichen Mitteln bequem ein Log schreiben lassen kann.

Blieb zuletzt nur noch ein Problem zu lösen: So schön einfach und direkt die von Hurricane empfohlenen Befehle für den Tunnelaufbau auch sein mögen, möchte ich trotzdem ungern dafür noch ein Skript schreiben, denn eigentlich bringt CentOS schon alle nötigen Tools mit um IPv6-Tunnel aufzubauen. Ich kann nachvollziehen, daß Hurricane nur den generellsten Weg beschreibt der in jeder Linux-Distribution funktionieren dürfte, aber für mich selbst wünsche ich mir doch was anderes und ich nehme dafür auch gerne in Kauf, daß ich mich erst durch die CentOS-Dokumentation wühlen mußte, um den richtigen Weg zu finden. Wühlen ist der richtige Ausdruck, denn die Online-Dokumentation ist in dem Punkt etwas dürftig. Aber die tatsächlich hilfreiche Dokumentation ist netterweise in jedem CentOS schon vorinstalliert: die Datei /usr/share/doc/initscripts-*/sysconfig.txt.

Um die nötigen Einstellungen zu erläutern möchte ich nochmal daran erinnern, wie laut der Anleitung von Hurricane der Tunnel aufgebaut wird: Zuerst wird das Interface sit0 auf den entfernten Endpunkt des Tunnels konfiguriert, dann das Interface sit1 auf den lokalen Endpunkt und schließlich wird die Route gesetzt.

Unter CentOS 5.4 gestaltet sich das nun leider etwas kontraintuitv. Man kann zwar sit0 auf der Kommandozeile anlegen, aber man sollte nicht versuchen dafür in /etc/sysconfig/network-scripts/ eine ifcfg-sit0 anzulegen. Sie wird im besten Fall ignoriert und führt im ungünstigsten Fall zu Fehlern. Statt dessen sollte man nur eine ifcfg-sit1 anlegen und Ihr in etwa diesen Inhalt geben:


# Hurricane Electric 6in4 tunnel
NAME="tunnelbroker.net"
TYPE=sit
DEVICETYPE=sit
DEVICE=sit1
ONBOOT=yes
BOOTPROTO=none
USERCTL=no
PEERDNS=no
IPV6INIT=yes
IPV6ADDR="$LOCALIPV6/64"
#IPV6_MTU=1280
#IPV6TO4_MTU="1280"
IPV6_ROUTER=yes
IPV6_AUTOCONF=no
IPV6TUNNELIPV4=$REMOTEIPV4

Ich hab wieder wie oben schon die IP-Adressen ggf. ersetzt. Zu beachten ist, daß IPV6ADDR="" die IPv6-Adresse in CIDR-Notation erwartet, also mit Angabe der Netzpräfixlänge.

Wenn das Interface sit1 hochgebracht wird, wird das Interface sit0 automatisch angelegt und korrekt konfiguriert.

Die hier verwendeten Optionen habe ich mir aus /usr/share/doc/initscripts-*/sysconfig.txt zusammengesucht. Mag sein, daß die eine oder andere nicht unbedingt notwendig ist, aber mit diesem Setup funktioniert mein Tunnel. Die Optionen IPV6_MTU und IP6TO4_MTU habe ich als Hinweis drin stehen lassen. Je nach Setup kann es sein, daß man an diesen Einstellung drehen muß, 1280 ist jeweils der niedrigste zulässige Wert.

In die /etc/sysconfig/network sollte man nun folgendes Eintragen:


NETWORKING_IPV6=yes
IPV6_AUTOTUNNEL=yes
IPV6_DEFAULTDEV="sit1"

Damit wird IPv6 generell aktiviert (ist es bei CentOS aber eigentlich sowieso, wenn ich das richtig sehe), das Tunneling aktiviert und schließlich dafür gesorgt, daß eine IPv6-Defaultroute angelegt wird, die über das richtige Interface läuft.

Ich hatte zu Anfang etwas Probleme mit dem Eintrag IPV6_DEFAULTDEV="sit1" und mußte da etwas rumprobieren. Eine Zwischenlösung bot die Datei /etc/sysconfig/static-routes-ipv6, in der ich die korrekte Default-Route zeitweilig eingetragen hatte. Beim Recherchieren zu diesem Problem bin ich über verschiedene Meldungen gestolpert, daß es in CentOS mit der IPv6-Default-Route wohl Probleme gäbe. Vor allem sollte wohl die Route sit1 ::/0 nicht funktionieren, statt dessen solle man sit1 2008::/3 verwenden. Das mag für frühere CentOS Versionen vielleicht richtig gewesen sein, für CentOS 5.4 kann ich das aber nicht bestätigen. Bei mir funktionierte die Route sit1 ::/0 einwandfrei und mit dem Eintrag IPV6_DEFAULTDEV="sit1" in der /etc/sysconfig/network wird diese Route auch vollkommen korrekt vom System erzeugt und eingetragen.

Wenn man nun das Netzwerk neu startet oder das Interface sit1 hochfährt, wird der Tunnel aufgebaut (man sollte vorher natürlich bei Hurricane die richtigen Daten hinterlegt haben, also z.B. einmal das tunnelbroker-update-Skript ausgeführt haben).

Um die Funktionstüchtigkeit des Tunnels zu testen braucht man z.T. andere Tools als bei IPv4, z.B. ping6 oder traceroute6. mtr akzeptiert den Schalter -6;, netstat und route benötigen den Schalter -A inet6.

Ich werde in nächster Zeit noch mehr dazu posten, wie man IPv6 von seinem Router zu seinen Endgeräten bringt, wie man djbdns IPv6 so halbwegs beibiegt (wer mit Daniel J. Bernstein vertraut ist, denn wird es nicht wundern: er mag IPv6 nicht und implementiert es nicht – ich kann seine Gründe nachvollziehen, finde das aber angesichts der mittlerweile geschaffenen Tatsachen etwas albern), was bei ip6tables zu beachten ist und wenn mir Zeit zum experimentieren bleibt, wie man MobileIPv6 nutzt.


Impressum