Archiv ‘Web’

Montag, 5. Februar 2024

Pro Senectute: Web-Formular lässt keine HTML Spezialzeichen zu

Tags: ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 31. Januar 2024

Firefox nervt mit Übersetzungs-Pop-Up

Man öffne about.config und wechsle browser.translations.automaticallyPopup auf false, um dem Spuk ein Ende zu bereiten.

Tags: , ,
Labels: IT, Web

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 5. November 2023

CSS: Nummern in Einheitsbreite

Wow. Auch nach ca. 23 Jahren mit CSS rumwursteln kann man immer wieder neues lernen …

.numbers {font-variant: tabular-nums;}

Tags: , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Samstag, 11. Februar 2023

PHP Race Condition beim Schreiben einer Datei

Mit monit überwache ich die Grösse der Datei /var/log/php.log auf meinen Web-Servern und alarmiere mich, wenn die Datei die Grösse von 1 MB überschreitet. Das geht natürlich nur, wenn man mit logrotate sicherstellt, dass die Datei täglich rotiert wird. Und wenn man so (defensiv) in PHP programmiert, dass das Log nicht infos, notices, warnings und errors vollgepflastert wird. Um es noch schwieriger zu machen: Ich habe in php.ini error_reporting = E_ALL aktiviert, denn das motiviert Web-Entwickler (mich), ihren Code sauber zu halten.

Vor einigen Tagen machte ich mich daran, ein immer wiederkehrendes Problem zu lösen, welches das Log füllte: Ich greife die Werte meiner ZigBee Temperatur-Sensoren ab und speichere sie auf einem Server in eine Datenbank. Gleichzeitig cache ich den aktuellsten Wert eines Sensors als JSON auch unter dem Web-Root eines Virtualhosts, damit ich den aktuellen Wert rasch und umkompliziert per HTTP abfragen kann.

Immer wieder kam dabei vor, dass beim Dekodieren solcher JSON-Dateien ein Fehler auftrat. Schlussendlich programmierte ich eine Routine, die solche JSONs mit falscher Syntax entdeckte und kurzerhand löschte. Dementsprechend führten Anfragen für die Cache-Dateien bestimmter Sensoren anschliessend zu dutzenden Einträgen in php.log, weil die Cache-Datei nicht mehr gefunden werden konnte:

[08-Feb-2023 19:06:34 Europe/Zurich] E - json_decode() returned null for path "/var/www/app/cache/BathroomWindow.json" but payload is 128 bytes. JSON error: "Syntax error". Unlinking file assuming it is malformed. in /var/www/app/class.php:258 for URI  (Referer: unknown) from IP 10.1.2.3 with User Agent "Mozilla/4.0 (compatible; cron/SERVER/USER/cron.sh)"
...
[08-Feb-2023 19:07:19 Europe/Zurich] E - Path "/var/www/app/cache/BathroomWindow.json" not found in /var/www/app/class.php:247 for URI  (Referer: unknown) from IP 10.1.2.3 with User Agent "Mozilla/4.0 (compatible; cron/SERVER/USER/cron.sh)"

Erst wenn der Sensor wieder ein Lebenszeichen sendete, konnte das Cache-File neu erstellt werden, und die Fehlermeldungen hörten auf.

Doch wieso war die JSON-Syntax zerschossen? Um dem auf den Grund zu gehen, baute ich in die Subroutine, welche die Syntaxprüfung durchführt, noch eine zusätzliche Zeile hinzu, welches den Inhalt der kaputten JSON-Datei in das Log schrieb. Hier ein Beispielresultat:

[09-Feb-2023 20:06:29 Europe/Zurich] E - JSON payload: "{"utime":1675969548,"sid":"158d000XXXXXXX","deviceName":"Office Mario Door","open":true,"deviceType":"magnet","battery":100}":100}" in /var/www/app/class.php:259 for URI  (Referer: unknown) from IP 10.1.2.3 with User Agent "Mozilla/4.0 (compatible; cron/SERVER/USER/cron.sh)"

Das letzte ":100} sollte wirklich nicht dort stehen und machte keinen Sinn. Es sah so aus, als ob die Datei zwei Mal geschrieben wurde: Zuerst mit einem längeren String, und gleich darauf mit einem kürzeren String. In der Tat ist es so, dass die ZigBee-Sensore manchmal innert Sekunden zwei oder mehrere Male Werte absenden. Könnte ich also einer Race Condition beim Schreiben von Dateie aufgesessen sein?

Wie man das löst? Wenn Prozess A die Cache-Datei schreibt, muss sie für andere Prozesse gesperrt werden. Tatsächlich kennt PHP das Flag LOCK_EX für file_put_contents(), und seit ich dieses mitgebe, hat sich das Problem (zumindest in den letzten 48 Stunden) nicht mehr wiederholt:

...
$res = file_put_contents($filename, $jsonOut, LOCK_EX);
...

Tags: , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 6. Juni 2021

PHP-Scripts mit Xdebug profilen

Einleitung: Xdebug: Documentation » Profiling

Hierzu muss man PHP Xdebug auf seinem System installiert haben. Anschliessend lässt man ein PHP-Script folgendermassen laufen:

$ php -d xdebug.profiler_enable=On index.php

Nachdem das Script durchgelaufen ist, findet sich unter /tmp eine Datei im Namensformat cachegrind.out.%ZUFALLSNUMMER%. Bei mir war die Datei satte 140MB gross.

Doch was man nun damit? Zuerst einmal auf den Mac kopiert, dann mit qcachegrind (eigentlich: kcachegrind) analysiert. Dieses installiert man sich mit MacPorts:

# port install qcachegrind

Mittels Spotlight sucht man nach „qcachegrind“ und startet die präsentierte App. Man wählt cachegrind.out.%ZUFALLSNUMMER% und erhält folgendes zu Gesicht:

Tags: , , , , , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 3. Dezember 2020

PHPs session_start() meldet „Permission denied (13)“

Ein kürzlich live gegangenes Web-Projekt, welches bei Hostpoint gehostet wird, füllt mir das PHP Error Log mit folgendem Müll:

...
[03-Dec-2020 13:32:32 Europe/Zurich] PHP Notice:  session_start() [function.session-start.php]: ps_files_cleanup_dir: opendir(/var/cache/php-sessions) failed: Permission denied (13) in /home/tenant/file.php on line 14
...

Die Lösung liegt darin, in der Datei .user.ini im Web-Root des Projekts mit dem Parameter session.save_path zu ergänzen und diesen Eintrag auf ein Verzeichnis im eigenen Tenant zeigen zu lassen. Ich empfehle, das Verzeichnis nur für den Besitzer les- und schreibbar zu machen, d.h. auf linuxisch chmod 700 anzuwenden, damit die Berechtigungsmaske drwx------ lautet.

...
[PHP]
error_reporting = E_ALL
display_errors = Off
error_log = /home/tenant/var/log/domain.php.err
session.save_path = /home/tenant/tmp

Tags: , , , , ,
Labels: Linux, Web

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 14. Oktober 2020

Credit Suisse Direct: Keine Textsuche möglich

Es ist 2020, aber die Entwickler der Schweizerischen Grossbank schaffen es nicht, Buchungen ausschliesslich nach einem bestimmten Text zu durchsuchen. Ich muss zwingend noch weitere Suchkriterien angeben (bspw. Mindest- und Maximalbetrag), sonst kann ich das Suchformular nicht absenden:

Tags: , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 14. Oktober 2020

Gegenstände auf Bildern vermessen

Kürzlich, beim Stöbern auf IKEA entdeckt: GRUNDVATTNET
Durchschlag, grau
für 4.95 CHF. Interessant! Könnte das auch etwas für unser Spülbecken in der Küche sein?

Die Masse des Hilfsmittels sind auf der Artikelseite angegeben, doch mir fehlt als Angabe die Länge der verkürzten Unterseite. Passt das Ding in unser Spülbecken oder nicht?

Ich lade mir eine Kopie des hochauflösenden Bildes herunter und mache mich auf die Suche nach einer Web-Seite, mit welcher man Gegenstände auf Bildern vermessen kann. Denn die Länge an der Oberseite des Gegenstandes kenne ich ja: 47 Zentimeter.

Ich werde bald fündig: Measure in Photo Online

Ich lade das Photo hoch, erstelle eine Linie über die Länge des Gegenstandes und gebe die Referenzlänge dieser Linie ein. Danach zeichne ich einige andere Linien, und sofort zeigt mir das Tool die reale Länge dieser Linien an:

Fazit: Mittlerweile habe ich den Gegenstand gekauft. Und ja, das Ding passt mit 35 Zentimeter Länge tatsächlich haargenau in unser Spülbecken. Das lässt mich vermuten, dass Spülbecken in Europe offenbar normiert sind.

Tags: , , , , , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Samstag, 23. Mai 2020

Zwei Cyon IMAP-INBOXen innerhalb von drei Wochen zerschossen

Seit 2012 bin ich glücklicher Kunde von Cyon, eines schweizerischen Hosting-Anbieters. In den letzten drei Wochen hat sich das Erlebnis etwas getrübt.

Wichtig: Die unten beschriebene Problematik hatte ich in den letzten acht Jahren nie, und schon gar nicht innerhalb von drei Wochen zwei Mal. imapfilter hat bisher auch immer reibungslos funktioniert.

Das erste Problem

Am Morgen des Samstag, 9. Mai 2020 konnte ich plötzlich nicht mehr auf meine E-Mails in der INBOX von Konto A zugreifen. Weder in Apple Mail (meinem hauptsächlichen E-Mail-Client), noch über RainLoop (Webmail), noch mit imapfilter, welches ich verwende, um E-Mails automatisiert in Unterordner zu verschieben (oder im Falle von Spam: zu löschen).

Das alle 5 Minuten laufende imapfilter, gepaart mit Healthchecks, ist übrigens auch ein optimales „Frühwarnsystem“, welches mir meldet, wenn mit dem Mailserver etwas nicht stimmt.

Das Log von RainLoop (unter RAINLOOPROOT/data/_data_/_default_/logs) zeigte den Fehler schön auf:

...
[08:55:36.698][1c50d1ff] IMAP[DATA]: > TAG3 SELECT "INBOX"\r\n
[08:55:36.960][1c50d1ff] IMAP[ERROR]: Stream Meta: Array
...
[08:55:36.961][1c50d1ff] IMAP[ERROR]: MailSo\Net\Exceptions\SocketReadException: MailSo-Net-Exceptions-SocketReadException (NetClient.php ~ 523) in %PATH%/rainloop/v/0.0.0/app/libraries/MailSo/Net/NetClient.php:523
Stack trace:
...

Und so sah das Symptom bei imapfilter aus:

...
Sat May  9 10:41:38 2020: reading data through SSL; the connection has been closed cleanly
...

Diese Meldung wurde in einer Schleife ausgegeben, und zwar bei jedem Befehl, der E-Mails filtern und — falls vorhanden — verschieben sollte.

Als ich imapfilter im Debug-Modus laufen liess …

$ imapfilter -v -l log.txt -d debug.txt recipe.imapfilter

… tauchte folgende erhellende Nachricht im Debug Log (debug.txt) auf:

IMAP (5): 107C NO [UNAVAILABLE] Maximum number of connections from user+IP exceeded (mail_max_userip_connections=75)

Weil die IMAP-INBOX nicht abrufbar war, steckten meine Scripts in Endlosschleifen fest, welche die Zahl der Verbindungen pro E-Mail-Konto und IP voll ausschöpfte. Es waren also keine Verbindungen mehr möglich.

Als erstes schaltete ich deshalb den imapfilter-Cronjob ab, um dem IMAP-Server eine Verschnaufspause zu gönnen. Dann wurde rasch klar, dass die E-Mails in der INBOX aus irgendeinem Grund nicht aufgelistet werden konnten. Ich hatte in den zwei Tagen zuvor einige Anpassungen am imapfilter-Konfiguration vorgenommen; eine Möglichkeit war, dass die Verschiebeaktionen dabei zu einer korrupten INBOX-Datei geführt haben könnten.

Obwohl das Problem an einem Wochenende auftrat, schrieb ich dem Support — wohlwissentlich, dass ich erst am Montag eine Antwort erhalten würde. Vielleicht würde ja trotzdem eine gute Seele reinschauen … leider nein, The Computer Says No.

Ich kämpfte deshalb auf eigener Faust weiter: Nach viel Pröbeln wählte ich die Holzhammer-Methode: Nach einem lokalen Backup des kaputten Dovecot-Verzeichnisses löschte ich via SSH alle Dateien im INBOX-Verzeichnis (Pfad: siehe unten), ein frischer Start, sozusagen. Seither funktioniert die INBOX wieder.

Glück im Unglück: Da ich bei Konto A versuche, Inbox Zero anzuwenden, waren nur vier E-Mails von der Löschaktion betroffen, von denen ich die von Apple Mail lokal gespeicherten .emlx an einen sicheren Ort kopieren konnte.

Das zweite Problem

Kurz nach Mitternacht in der Nacht von Donnerstag auf Freitag, 22. Mai 2020 traten gemäss imapfilter Probleme mit Konto B auf. Ich merkte dieses am späteren Freitag-Morgen, als ich mit meinen Mail-Clients nicht mehr auf meine E-Mails in der INBOX von Konto B zugreifen konnte. Dieses Mal gab es aber keine Überlastung der erlaubten Verbindungen. imapfilter meldete bei Abfragen auf die INBOX:

...
Fri May 22 14:47:16 2020: IMAP (5): 127C NO [SERVERBUG] Internal error occurred. Refer to server log for more information. [2020-05-22 14:47:16] (0.040 + 0.000 + 0.039 secs).
...

Ohne Zugriff auf die Logs konnte ich „SERVERBUG“ nicht weiter eingrenzen. Erneut schrieb ich eine E-Mail an den Support (Versand um 15:19 Uhr), und erhielt um 16:57 Uhr eine Antwort. Leider in der Form „Have you tried turning it off and on again?“. Die Hoffnung war verloren, dass mir Cyon noch vor dem Wochenende beim Debugging helfen konnte — somit sah ich ca. 56 Stunden ohne E-Mail entgegen.

Hilfreich war in der ersten Antwort des Supporters einzig der Tipp, anstelle von RainLoop doch bitte das „offizielle“ Webmail von Cyon zu verwenden: webmail.cyon.ch, welches auf RoundCube basiert. Gesagt, getan. So konnte ich dem Supporter klipp und klar belegen, dass das Problem auf der Serverseite lag. Wenn ich die INBOX anwählte, erschien am unteren Bildschirmrand ganz in rot folgende Fehlermeldung:

Serverfehler: UID SORT: Internal error occurred. Refer to server log for more information. [2020-05-22 17:59:38] (0.041 + 0.000 + 0.040 secs)

Leider gab es seither keine Antwort mehr, weshalb ich mir erneut selber helfen musste (wieso passieren diese Fehler immer auf’s Wochnenende hin?!)

Das Problem war dieses Mal etwas anderer Natur — Apple Mail und imapfilter zeigten partout keine E-Mails mehr in der INBOX an; die Unterordner konnten hingegen abgerufen werden. RoundCube hingegen zeigte die INBOX etwas erratisch manchmal an, manchmal nicht.

Die schlussendliche Lösung:

  • Per SSH auf den Shared Hosting-Server einloggen
  • Alle dovecot-Prozesse abschiessen mittels
    $ ps ax | grep -i dovecot | grep -v grep | awk '{print $1}' | xargs kill
  • Die (möglicherweise korrupte?) dovecot.index Datei löschen
  • In Cyons RainLoop Webmail einloggen
  • Die E-Mails der letzten Tage im problematischen Mail-Ordner von Hand durchgehen; solche, die nicht angezeigt werden konnten, habe ich kurzerhand gelöscht
  • Einen neuen Unterordner erstellen; bspw. BACKUPYYYYMMDD
  • Alle verbleibenden E-Mails im problematischen Ordner auswählen und in den Backup-Ordner verschieben (ich hatte Angst, dass der Verschiebeprozess nicht funktionieren würde, klappte in dem Fall aber problemlos)
  • Zurück auf SSH wechseln
  • Im Verzeichnis des problematischen Emailordners rm -rf * ausführen, um alle vorhandenen Dateien zu löschen (ACHTUNG: Wer diesen Befehl am falschen Ort ausführt, löscht sich sein gesamtes Benutzerverzeichnis)
  • RoundCube, RainLoop Apple Mail und imapfilter wieder starten resp. öffnen — der problematische Ordner ist jetzt zwar leer, aber kann immerhin wieder ohne Fehlermeldungen abgefragt werden. Im Hintergrund erstellt Dovecot alle benötigten Dateien wieder.
  • Falls gewünscht die E-Mails aus dem Backup-Ordner zurück in den (neu erstellten) ehemals problematischen Ordner verschieben oder kopieren (ACHTUNG: Es könnte sein, dass eine ganz bestimmte E-Mail im Backup Dovecot zum Straucheln bringt; in dem Fall würde man mit der Rückkopieraktion das Problem wieder von vorne starten … bei mir war das glücklicherweise nicht der Fall)

Epilog

Ohne Zugriff auf die IMAP-Logs ist mir aber weiterhin nicht möglich herauszufinden, was denn nun wirklich genau das Problem war. Und so besteht zu befürchten, dass das Problem jede Minute erneut auftreten kann.

Und nein, ich gebe nicht Cyon die Schuld: Es könnte sein, dass mein spezielles Setup mit 5-minütigen imapfilter-Verbindungen die Ursache hinter den Problemen ist. Bspw. eine Anpassung, welche tausende E-Mails verschiebt und der Mailserver noch am kopieren ist, wenn der nächste imapfilter-Prozess bereits wieder startet und gerade in Kopie befindliche E-Mails irgendwohin kopiert. Oder ein Software-Update meiner Debian-Systeme in den letzten drei Wochen.

Es könnte aber auch sein, dass der Fehler bei Cyon zu suchen ist — d.h. ein kaputter RAM-Baustein, der beim Schreiben IMAP-Ordnerdateien korrumpiert, oder das Storage-System, welches zu schreibende Daten verfälscht, oder ein Update von Dovecot, nach welchem sich die Software nicht mehr wie gewohnt verhält. Oder eine Inkompatibilität zwischen der neuen Dovecot-Version mit dem bisherigen imapfilter; oder mit dem neuen imapfilter und der bisherigen Dovecot-Version.

Cyons E-Mail-Infrastruktur

Was ich bei diesen Problemen über Cyons E-Mail-Infrastruktur gelernt habe:

  • Cyon verwendet Dovecot als IMAP-Server
  • Ist man per SSH auf dem Cyon-Server eingeloggt, kann man sich die (eigenen) laufenden IMAP-Server-Prozesse mit folgendem Befehl anzeigen lassen. Die Zahl der Prozesse ist meines Erachtens proportional zur Anzahl der Clients, die gerade per IMAP E-Mails abfragen (mindestens 1 Prozess, es können aber durchaus auch mehrere pro Client sein).
    $ ps ax | grep -i dovecot
    3662642 ?        S      0:00 dovecot/imap
    3662645 ?        S      0:01 dovecot/imap
    3662646 ?        S      0:00 dovecot/imap
    3662706 ?        S      0:00 dovecot/imap
    3663038 ?        S      0:00 dovecot/imap
    3664246 ?        S      0:00 dovecot/imap
    3674314 ?        S      0:00 dovecot/imap
    3674316 ?        S      0:00 dovecot/imap
    3674317 ?        S      0:00 dovecot/imap
    3674449 ?        S      0:00 dovecot/imap
  • Die Mail-Daten eines Benutzers liegen in einer Ordnerstruktur unter /userdata01/%CYONUSER%/mail
  • Die Mail-Ordner einer E-Mailadresse %EMAILUSER%@%DOMAIN% finden sich unter /userdata01/%CYONUSER%/mail/%DOMAIN%/%EMAILUSER%/mailboxes
  • In einem Mail-Ordner finden sich normalerweise folgende Dateien:
    drwxr-x--x 2 user user    158 May 23 17:02 ./
    drwxr-x--x 3 user user     32 May 23 16:43 ../
    -rw-r----- 1 user user  15024 May 23 16:41 dovecot.index
    -rw-r----- 1 user user  15024 May 23 16:41 dovecot.index.backup
    -rw-r----- 1 user user 391208 May 23 17:34 dovecot.index.cache
    -rw-r----- 1 user user   2144 May 23 17:02 dovecot.index.log
    -rw-r----- 1 user user  32928 May 23 16:41 dovecot.index.log.2
  • Löscht man die Cache-Datei dovecot.cache in einem Mailordner (kann mehrere hundert Megabytes oder sogar Gigabytes gross sein), wird diese von Dovecot nicht automatisch neu generiert. Nichts geht mehr; d.h. der Ordner wird als leer angezeigt.
  • Löscht man die Index-Datei dovecot.index, wird diese von Dovecot automatisch neu generiert. Beim zweiten Problem half dies tatsächlich, dass ich wieder Zugriff auf E-Mails erhielt.

Tags: , , , , , , ,
Labels: IT, Schweiz, Web

Keine Kommentare | neuen Kommentar verfassen