Projekt Mepevea – D’r Zoch kütt!

Als umweltbewusster oder zumindest geiziger Mitarbeiter des Öffentlichen Dienstes verzichtet man in der Regel bei längerer Anfahrt zum Arbeitsplatz auf den privaten PKW und nimmt freudig am öffentlichen Personennahverkehr (ÖPNV) teil. Sprich: Die Deutsche Bahn (und in Köln auch die KVB) ist unser Freund! Gerüchteweise sind die bereitgestellten Verkehrsmittel nicht immer dann vor Ort, wenn man es laut Fahrplan erwarten könnte. Damit man die daraus resultierende Wartezeit nicht am Bahnsteig, sondern am Frühstückstisch bzw. im bequemen Bürosessel verbringen kann, sind aktuelle Informationen über die Verspätungen unerlässlich.

Nun hat sich in der Vergangenheit der Service der DB dahingehend deutlich verbessert. So sind die Verspätungsinformationen inzwischen minutengenau und in Realzeit sowohl im Web als auch mittels der App „DB Navigator“ abrufbar. Der o.g. Mitarbeiter des Ö.D. ist allerdings nicht nur geizig (jaja, und umweltbewusst), sondern auch klickfaul und noch dazu ein Spielkind. So kam ich auf die Idee, sowohl in meinem trauten Heim als auch im Büro mittels ohnehin vorhandener Technik einen (für mich) optimalen Anzeigebildschirm zu basteln.

Dieser sollte nicht nur die aktuellen Verspätungen meiner Zugverbindungen, sondern auch weitere interessante Informationen anzeigen, genauer gesagt: Aktuelle Nachrichten, Wettervorhersage und (zuhause) zusätzlich das Kamerabild einer per WLAN verbundenen IP-Kamera. Als Hardware kamen ein günstiger und dank Notebook-Anschaffung ohnehin kaum noch gebrauchter PC-Bildschirm sowie zeitgemäß ein Raspberry Pi zum Einsatz. Das System sollte in jedem Fall ohne weitere Peripherie, speziell ohne Maus und Tastatur, auskommen. Softwareseitig setzte ich daher auf Google Chrome im Kiosk-Modus. Mittels der Erweiterung „Easy Auto Refresh“ kann man dafür sorgen, dass Chrome die angezeigte Seite automatisch einmal pro Minute neu lädt. Das Kamerabild läuft ohnehin im Streaming-Mode.

Der graphische Desktop des Raspi musste so eingestellt werden, dass er sich nicht automatisch abschaltet. Die Kontrolle über die Anzeige sollte ausschließlich per Ein/Aus-Knopf des Monitors ablaufen. Dies erreicht man über die eine Einstellung in LightDM.

Da ich mir die Installation und Konfiguration eines Webservers sparen wollte, verwende ich eine einfache lokale HTML-Seite auf dem Raspi. Die beiden gewünschten Elemente „Aktuelle Nachrichten“ und „Wettervorhersage“ sind sehr leicht über passende Widgets realisierbar. Ich habe hierzu die Angebote von wetterdienst.de und rp-online genutzt, es gibt jedoch zahlreiche weitere Anbieter.

mepevea

Richtig interessant wurde es dann bei der Einbindung der Verspätungsanzeige. Wie ich feststellen musste, bietet die Bahn leider keine geeignete API zu diesem Zweck. Mir blieb nichts anderes übrig als die entsprechende Webseite zu parsen. Diese Erkenntnis war die Geburtsstunde von Projekt „Mepevea“ (MEin PErsönlicher VErspätungsAnzeiger).

Wie erwähnt wollte ich auf die Installation und den Betrieb eines Webservers verzichten. Die Anzeige soll ja ohnehin nur für mich persönlich laufen. Daher musste ich die eigentliche Logik nebst Parser in ein Pythonskript packen, welches per Cronjob aufgerufen wird (ja, ich arbeite unter Linux und ignoriere Windows seit Jahren – die Portierung sollte aber kein großes Problem darstellen). Als Basismodul für den Parser dient natürlich „BeautifulSoup“, darüber hinaus werden urllib zum Abruf der Seite und einige weitere Module benötigt. Der Start lautet also:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import bs4, urllib2, time, fileinput, sys, urllib

„fileinput“ verwende ich, um später den <div>-Block im HTML durch die korrekten Daten auszutauschen, z.B.:

for line in fileinput.FileInput("/home/pi/anzeige/bahnlinks.html",inplace=1):
if line.startswith('<div id="bahn">'):
   line = text
   sys.stdout.write(line)

Natürlich macht es Sinn, abhängig vom Wochentag und der Tageszeit die Anzeige zu variieren (Hinfahrt, Rückfahrt, Abend/Wochenende), also z.B.:

timestamp = time.localtime(time.time())
if timestamp[6] > 4:
   textlist.append("<b>Bahnanzeige erst am Montag wieder! Schönes Wochenende!</b>")

Hier wird schon klar: Individuelle Anpassung ist unerlässlich und ich kann die Beispiele nur anreißen. Keine Sorge: Am Ende werde ich als „großes Beispiel“ mein komplettes Skript bereitstellen.

Zentrales Element des Skriptes ist die Parserfunktion. Sie erhält als Parameter die URL der Bahn (dazu später) und jagt sie durch BeautifulSoup:

def parser(url):
   page = urllib2.urlopen(url).read()
   soup = bs4.BeautifulSoup(page)

Man möge mir an dieser Stelle glauben, dass wir die spannenden Inhalte erhalten, wenn wir nach den Keywords, genauer gesagt den <td>-Klassen „overview timelink“ und „overview tprt“ suchen:


zeilen = soup.find_all('td', {"class" : "overview timelink"})
verspaetungen = soup.find_all('td', {"class" : "overview tprt"})

Schon hier erkannt man, wo das größte Problem unserer schönen Bastelei liegt: Sollte die Bahn die Klassennamen aus irgendwelchen Gründen ändern, funktioniert natürlich nichts mehr. Das gleiche gilt für die URLs und die HTML-Struktur. Genau aus diesem Grund gibt es ja i.d.R. kapselnde APIs, aber die stehen hier wie gesagt nicht zur Verfügung.

Standardmäßig erhält man von der Bahn die nächsten drei Züge ab dem definierten Zeitpunkt. Ich habe die finale Version noch so erweitert, dass man dies variieren kann, aber das würde hier zu weit führen. Ebenso müsste ich nun eigentlich auf die Details von BeautifulSoup eingehen, um den folgenden Codeblock zu erläutern. Aber auch dies möchte ich mir sparen und auf die gute Online-Dokumentation des Moduls verweisen. Unsere Verbindungen sowie die aktuellen Verspätungen erhalten wir so:


parsedtext = ''
zaehler = 0
for zeile in zeilen:
   for zelle in zeile.children:
      parsedtext += zelle.contents[0]
   parsedtext += '<span style="color: red;">'
   for verspaetung in verspaetungen[zaehler].children:
      if str(verspaetungen[zaehler]).count("okmsg") > 1 or str(verspaetungen[zaehler]).count("red") > 1:
         parsedtext += verspaetung.contents[0]
         break
   parsedtext += '</span>'
   zaehler += 1

Ich bin mir zu 99% sicher, dass dies nicht die eleganteste Version ist, um die Informationen zu erhalten und aufzubereiten. Aber sie funktioniert. Wer das Ganze kürzer, schöner und verständlicher hinbekommt, ohne dass die Funktionalität leidet, möge sich bei mir melden.

Kommen wir nun zu den benötigten URLs. In einer ersten Version hatte ich pro Zug eine URL auf Basis des Bahntools „query2.exe“ verwendet, die auch deutlich einfacher zu parsen war (Anmerkung: Bitte von der Endung „.exe“ nicht täuschen lassen: Es handelt sich um einen Webservice, nicht um ein lokales Programm.). Leider musste ich feststellen, dass die Bahn bei jeder (geplanten) Mini-Fahrplanänderung die URL komplett verändert. Auf Dauer war das also leider keine Lösung. Stattdessen verwende ich nun die „Vorstufe“ namens „query.exe“. Diese hat klar definierte und – hoffentlich – dauerhaft beständige Parameter. Als Parameter benötigen wir den Code des Startbahnhofs, den Code des Zielbahnhofs und die Startzeit.

Während die Startzeit natürlich jedem selbst überlassen bleibt und einfach in der Form hh:mm verwendet wird, muss man sich die Codes (sog. IBNR) der Bahnhöfe einmalig heraussuchen. Dies geht zum Glück sehr einfach mittels einer Onlinesuche.

Lautet die IBNR des Startbahnhofs bspw. 8000208, die des Zielbahnhofs 8000133 und die gewünschte Startzeit ist 17:00 Uhr, lautet die gesuchte URL:

http://reiseauskunft.bahn.de/bin/query.exe/dox?S=8000208&Z=8000133&time=17:00&start=1

Damit lässt sich nun für jede beliebige Verbindung und Kombination von Tageszeiten ein passender Anzeiger (eben ein „Mepevea“) bauen.

Für weitere Ideen, Verbesserungsvorschläge etc. bin ich jederzeit dankbar. Und wenn jemand die Bahn überreden könnte, doch mal eine entsprechende API bereitzustellen, das wäre ein Traum. 😉

Wie versprochen: Den vollständigen Text des Skriptes sowie eine Beispiel-HTML-Seite findet man unter http://dl.distinguish.de/mepevea.zip

Add filter pane to your scaffolding list template

The filter pane plugin is one of my favorite plugins, to give the user the capability to filter lists.

But it’s a boring task, to add the filter pane to each scaffolded view. Especially you’ve to redo all this work, after regenerating the list view. But Customizing the Scaffolding templates will get you rid of this. All you have to do is:

  • Install the filter pane plugin, if not already done:
    grails install-plugin filterpane
  • Copy the the templates used by Grails during code generation to your project directory, if not already done:
    grails install-templates
  • Customize the controller template src/templates/scaffolding/Controller.groovy:
    class ${className}Controller {
       def filterPaneService
       ...
       def filter = {
          if(!params.max) params.max = 10
          render( view:'list',
                  model:[ ${propertyName}List: filterPaneService.filter( params, ${className} ),
                          ${propertyName}Total: filterPaneService.count( params, ${className} ),
                 filterParams: org.grails.plugin.filterpane.FilterPaneUtils.extractFilterParams(params),
                 params:params ] )
       }
  • Customize the list template viewsrc/templates/scaffolding/list.gsp:
    <head>
        ...
        <filterpane:includes />
    </head>
    <body>
        ...
        <div class="pagination">
           <g:paginate total="\${${propertyName}Total}" params="\${filterParams}"/>
           <filterpane:filterButton />
        </div>
        </div>
        <filterpane:filterPane domain="${domainClass.fullName}"    />
    </body>
    

That’s all!

Perl-Modul SAVI-Perl mit aktueller Sophos-Version für Linux verwenden

Wir betreiben neben Ironport für die zentralen Mailserver noch eine weitere Anti-Spam- und Anti-Viren-Infrastruktur als „Altlast“ für Institute, die keinen LDAP-Server betreiben. Dort wird als Anti-Virus-Software seit vielen Jahren ClamAV und Sophos Antivirus eingesetzt. Für Letzteres musste jetzt ein Update installiert werden, weil die alte Version demnächst nicht mehr unterstützt wird. Sophos wird bei uns mit dem Perl-Modul SAVI-Perl in amavisd-new eingebunden. Das ging nach dem Update zunächst nicht mehr, obwohl ich das Modul gegen die neue Sophosversion gebaut hatte. Die Ursache ist, dass sich der Pfad der .vdl- und .ide-Dateien geändert hat. Zwar kann man SAVI-Perl beim Kommando load_data diesen Pfad übergeben, aber (unsere Version von) amavisd-new unterstützt das nicht. Die einfachste Lösung war deshalb ein symbolischer Link vom aktuellen Pfad /opt/sophos/lib/sav auf den bisherigen Pfad /opt/sophos/sav.

Willkommen in der schönen neuen 64-Bit-Welt

Seit Montag habe ich einen neuen iMac mit Core i5-Prozessor. Meine bisherigen Systeme bei der Arbeit waren ein PowerMac G5 und ein MacBook der ersten Generation, also noch mit dem ersten Core-Prozessor, der nur 32-bittig war. Der PowerMac unterstützte zwar 64 Bit, aber damals war Apple mit Mac OS X noch nicht so weit. Nur für Intel-Prozessoren des Typs Core 2 Duo und höher gibt es seit Mac OS X 10.6 (Snow Leopard) GUI-Programme im 64-Bit-Modus.

Warum erzähle ich das? Ein von mir geschriebenes QuickLook-Plugin funktionierte auf dem iMac nicht mehr. Mein erster Verdacht ging natürlich dahin, dass es sich um ein 64-Bit-Problem handele. Das war zwar gar nicht so, aber ein solches gab es auch …

Mir war zuerst nicht aufgefallen, dass die Dokumente, auf die ich das Plugin anwenden wollte, noch auf dem PowerMac erstellt worden waren. Nach einiger Zeit habe ich begriffen, dass das Problem darin lag, dass das Dokument in „native byte order“ geschrieben war. Neu auf dem iMac erstellte Dokumente funktionierten eigentlich sofort, da das Plugin nur in einer 32 Bit-Version vorlag. Da ich dann aber der Vollständigkeit halber das Plugin neu kompiliert hatte, so dass es als „universal binary“ vorlag, hatte ich mir erfolgreich selbst eine Falle gestellt. Denn jetzt hatte ich wirklich ein 64-Bit-Problem.

Der Datentyp long hat auf 32-Bit-Systemen eine Länge von 4 Byte, auf 64-Bit-Systemen hingegen eine von 8 Byte. Weil ich das Plugin nicht mit Sicht auf 64 Bit entwickelt hatte, wurden falsche Werte gelesen.

Um sowohl den Umstieg von PPC auf Intel als auch den von 32 Bit auf 64 Bit zu unterstützen, musste ich:

– statt long den Datentyp int32_t verwenden
– die Bytereihenfolge vertauschen, wenn der gelesene Wert zu hoch war

Für Neugierige: mehr Informationen zu dem Plugin.

Debugging für Fortgeschrittene: Post-Mortem einer Java-Bugsuche

Damit hatten wir nicht gerechnet: nachdem Snow Leopard von Apple veröffentlicht wurde, zeigte sich, dass unsere UKLAN-Admin-Anwendung darunter nicht lief. Die Version von Java, die mit Snow Leopard auf Macs erstmalig der Standard ist, ist Java 6. Diese Version hatten Kollegen mit Windows- und Linux-Rechner schon längst ohne Probleme im Einsatz, so dass nicht zu erwarten gewesen war, dass das auf Macs anders sein könnte.
Das Problem lag bei der Authentifizierung. Beim Anmeldeversuch kam die Meldung, dass diese gescheitert sei. Im Log konnte man zu der Exception einen recht langen Stacktrace sehen. Die entscheidende Meldung lautete:

java.lang.IllegalArgumentException: EncryptionKey: Key bytes cannot be null!

Der Fehler trat in einer internen Javaklasse auf, die nie explizit verwendet werden soll: sun.security.krb5.EncryptionKey. Das allein machte für mich klar, dass es sich um keinen Fehler in unserem Programm handeln konnte, sondern entweder einen im Betriebssystem oder im mitgelieferten Java. Da abzusehen war, dass das Problem durch Apple nicht kurzfristig gelöst werden würde, habe ich mich auf die Suche nach der eigentlichen Ursache gemacht. Am Anfang der Suche standen Beobachtungen:

  • Wenn man ein falsches Passwort eingab, wurde das korrekt als falsch erkannt. Das Problem trat also nur auf, wenn die Authentifizierung eigentlich erfolgreich war.
  • Wenn man bereits ein Kerberos-Ticket hatte, konnte das (nach einer kleinen Programmänderung) verwendet werden, so dass das Passwort gar nicht geprüft werden musste.

Die zweite Erkenntnis hat uns zumindest einen ersten Workaround verschafft, wenngleich der für die Anwender etwas lästig war. Die eigentliche Ursache war hingegen immer noch unklar. Durch die tatkräftige Hilfe eines Forummitglieds auf forums.sun.com bin ich in mehreren Schritten zu weiteren Erkenntnissen gekommen:

  • Die Exception wurde nur durch manche Ciphers verursacht – wie sich später zeigte, sind es die AES-basierten.
  • Unmittelbare Ursache der Exception war ein leeres Salt – dass die AES-Ciphers das nicht mögen, war auf verschiedenen Plattformen reproduzierbar.
  • Nach einer ganzen Weile wurde mir klar, dass die Authentifizierung manchmal klappte.

Das letzte Indiz wies mir den Weg. Wir befragen im Programm zunächst das DNS, welche Server im Active Directory der Uni als KDCs zur Verfügung stehen. Derzeit sind das sechs Stück:

% host -t srv _kerberos._udp.ad.uni-koeln.de
_kerberos._udp.ad.uni-koeln.de has SRV record 0 100 88 ads5.ad.uni-koeln.de.
_kerberos._udp.ad.uni-koeln.de has SRV record 0 100 88 advdc1.ad.uni-koeln.de.
_kerberos._udp.ad.uni-koeln.de has SRV record 0 100 88 rzkvdc1.ad.uni-koeln.de.
_kerberos._udp.ad.uni-koeln.de has SRV record 0 100 88 rzkvdc2.ad.uni-koeln.de.
_kerberos._udp.ad.uni-koeln.de has SRV record 0 100 88 rzkvdc3.ad.uni-koeln.de.
_kerberos._udp.ad.uni-koeln.de has SRV record 0 100 88 ads4.ad.uni-koeln.de.

Da die Authentifizierung manchmal klappte, lag der Verdacht nahe, dass nur manche der Server das Problem provozieren. Also habe ich die Server der Reihe nach fest im Programm verdrahtet. Dabei zeigte sich, dass Authentifizierung nur bei einem der sechs Server möglich war – wohlgemerkt, nur unter Java 6 auf Macs! Auf allen anderen Systemen funktionierten alle sechs Server. Wie ich vom zuständigen Kollegen erfuhr, läuft der funktionierende Server unter Windows Server 2008, die anderen hingegen noch unter Windows Server 2003. Aber in was unterschied sich die Authentifizierung? Mit Wireshark ließ sich beobachten, dass die Antwort des W2K8-Servers einen Unterschied aufwies.

W2K3:
Encryption type: rc4-hmac (23)
Salt:
Encryption type: des-cbc-md5 (3)
Salt: ...

W2K8:
Encryption type: rc4-hmac (23)
Encryption type: des-cbc-md5 (3)
Salt: ...

Hm, ein leeres Salt – war da nicht was? Richtig, das leere Salt produziert eine Exception. Aber wieso wird hier das leere Salt in der Antwort des Servers übernommen, auf anderen Plattformen aber nicht? Die Klasse mit dem relevanten Code gehört zum dem kleinen Teil, der nicht im Source verfügbar ist – mit Ausnahme von OpenJDK. Also habe ich dort nachgesehen. In Zeile 360 beginnt der Code, der das Salt auswertet, das der KDC in seiner Antwort übergibt:

360 // update salt in PrincipalName
361 byte[] newSalt = error.getSalt();
362 if (newSalt != null && newSalt.length > 0) {
363 princ.setSalt(new String(newSalt));
364 }

Im OpenJDK wird also explizit getestet, dass der String eine Länge > 0 hat. Hatte Apple diesen Test etwa entfernt? Ich hätte es bei der Spekulation belassen müssen, wenn ich nicht auf das javap-Kommando hingewiesen worden wäre. Damit kann man Javaklassen disassemblieren. Das funktioniert auch für die Systemklassen. Und da konnte man folgenden Unterschied erkennen:

javap -v sun.security.krb5.Credentials

JDK 5
85: invokevirtual #77 // Method sun/security/krb5/internal/KRBError.getSalt:()[B
88: astore 6
90: aload 6
92: ifnull 114
95: aload 6
97: arraylength
98: ifle 114
101: aload_0
102: new #44 // class java/lang/String
105: dup
106: aload 6
108: invokespecial #78 // Method java/lang/String."":([B)V
111: invokevirtual #79 // Method sun/security/krb5/PrincipalName.setSalt:(Ljava/lang/String;)V

In Zeile 98 wird die Länge getestet. Ist sie 0, springt das Programm zu Zeile 114, also hinter die setSalt()-Anweisung.

JDK 6
85: invokevirtual #81; //Method sun/security/krb5/internal/KRBError.getSalt:()[B
88: ifnull 107
91: aload_0
92: new #51; //class java/lang/String
95: dup
96: aload 5
98: invokevirtual #81; //Method sun/security/krb5/internal/KRBError.getSalt:()[B
101: invokespecial #82; //Method java/lang/String."":([B)V
104: invokevirtual #83; //Method sun/security/krb5/PrincipalName.setSalt:(Ljava/lang/String;)V
107: aload_2
108: ifnull 131
111: aload_2
112: aload_0
113: invokevirtual #84; //Method sun/security/krb5/PrincipalName.getSalt:()Ljava/lang/String;

Hier fehlt der Längentest! Der Vergleich mit einem Windowsrechner zeigte, dass das dortige Java 6 den Test enthielt. Es handelt sich also zweifelsfrei um einen Bug in Apples Javaversion. Nachdem ich all diese Rechercheergebnisse an Apple gemeldet hatte, wurde der Bug dort auch sofort anerkannt. Bis Apple einen Fix rausbringt, könnte allerdings erfahrungsgemäß noch einige Zeit vergehen. Da die Ursache jetzt aber erkannt war, konnte ich einen Workaround implementieren, der ohne Mitwirkung der Anwender funktioniert: wenn die besagte Exception auftritt, wird die Authentifizierung einfach nochmal durchgeführt, dann aber immer gegen den W2K8-Server. Der ist zwar jetzt ein „single point of failure“ für Macs mit Java 6, aber damit können wir leben.

Der Workaround ist nur in der Testversion von UKLAN-Admin enthalten.

Der geschilderte Fall zeigt, wie verzwickt die Fehlersuche sein kann. Der Fehler tritt schließlich nur auf, wenn man

  • einen Mac einsetzt, der Java 6 als präferiertes Java eingestellt hat (d.h. alle Macs mit 10.6, sehr wenige mit 10.5)
  • Kerberos 5 zur Authentifizierung nutzt
  • kein anderweitig bezogenes Kerberos-Ticket besitzt
  • keinen Windows 2008 Server hat (wie es mit anderen KDCs als W2K3 aussieht, weiß ich allerdings nicht)
  • man nicht die Cipherliste in /Library/Preferences/edu.mit.Kerberos durch einen Eintrag für default_tkt_enctypes auf nicht-AES-Ciphers eingeschränkt hat (der Trick klappt allerdings nicht für WebStart-Programme)


Flattr this

DNS-Abfragen mit JNDI

Für unser Java-Projekt benötigen wir genau eine DNS-Abfrage. In der Vergangenheit haben wir dazu dnsjava verwendet, aber eigentlich ist es natürlich Overkill, für eine einzige Abfrage eine komplette zusätzliche Library mitzuschleppen. Deshalb war ich froh zu entdecken, dass DNS mittlerweile mit Java-Bordmitteln funktioniert. Es ist ein bisschen versteckt, weil es Teil der JNDI-Extension ist, die für Verzeichnisdienste aller Art benutzt werden kann.

Wenn man aber erstmal weiß, dass damit auch DNS gemeint ist, geht der Rest ziemlich einfach, wenn man davon absieht, dass man sich das eigentliche Ergebnis mit einer Regular Expression extrahieren muss:

DirContext ictx = new InitialDirContext();
Attribute myAttr = ictx.getAttributes("dns:/_kerberos._udp.ad.uni-koeln.de",
        new String[] {"SRV"}).get("SRV");
NamingEnumeration<?> myEnum = myAttr.getAll();
Pattern p = Pattern.compile(".*\s(\p{Alpha}.*$)");
Matcher m;
while (myEnum.hasMoreElements()) {
      m = p.matcher(myEnum.next().toString());
      if (m.find()) {
   	  myKRB_Servers.append(m.group(1)+":");
      }
}
System.setProperty("java.security.krb5.kdc", myKRB_Servers.toString());

Wir setzen auf diese Weise die Adressen der jeweils aktuellen Kerberos5-Server im Active Directory der Uni. Möglicherweise geht das noch eleganter, aber schon so ist es eine deutliche Verbesserung gegenüber dem vorigen Konstrukt.

Probleme mit Unicode bei OTRS-Update beseitigen

Nach einem Update auf die OTRS-Version 2.3.4 war die Administrationsseite unseres OTRS (a.k.a. SysConfig) nicht mehr erreichbar. Es stellte sich heraus, dass diese wohl gegen den Einsatz von Unicode, also UTF-8 rebellierte, eine temporäre Umstellung auf Latin-1 förderte die Adminseite wieder ans Tageslicht. Weitere Nachforschungen ergaben, dass ein Auskommentieren des ConfigCaches in der Datei /opt/otrs/Kernel/System/Config.pm auch wieder UTF-8 ermöglichte. Das System wurde hierdurch jedoch verlangsamt. Die optimale Lösung liegt wohl darin, alle Dateien, die mit „SysConfig“ beginnen, unterhalb von /opt/otrs/var/tmp zu löschen und OTRS sowie den dazugehörigen Apache neuzustarten. Dabei wird der Cache dann mit der korrekten Codierung reinitialisiert.

Das böse „F“-Wort

Als Mitarbeiter des Rechenzentrums etwas über Filesharing zu schreiben ist wohl so als ob der Papst ein Lehrvideo zur Benutzung von Kondomen auf YouTube einstellt, aber ich bin ja schon lange für ein drittes vatikanisches Konzil. 🙂 P2P-Filesharing verströmt bekanntlich seit Langem den Duft des Illegalen, seine legalen Anwendungsbereiche dürfen dabei aber nicht übersehen werden und sind inzwischen sogar Gegenstand wissenschaftlicher Untersuchungen.

Hier soll es nun um ein Filesharing-Tool gehen, dessen Dienste ich speziell in LAN-Umgebungen nicht mehr missen möchte. Der konkrete Anlass war die Versammlung eines halben Dutzends bier- und chipsbewaffneter Endzwanziger nebst mehr oder minder moderner Unterhaltungselektronik in meinem Keller, kurz und neudeutsch: Eine LAN-Party. Aufgrund des höchst unterschiedlichen Alters des mitgeführten Equipments – die Spanne reichte von cebitfrisch bis prähistorisch – erwies es sich als erstaunlich schwierig, die neuesten Warcraftkarten oder die letzten Partybilder untereinander auszutauschen. Windows-Dateifreigaben mussten erstmal eingerichtet werden, waren dann über verschiedene Versionen (98, 2000, XP, Vista) aber auch nicht erreichbar, Linux wollte auch nicht so recht mitspielen und wenn dann noch diverse Personal Firewalls etwas zu sagen haben, kann man die Sache komplett vergessen. Also wurden schließlich USB-Sticks oder externe Festplatten herumgereicht, alles sehr nervig und unflexibel.

Ich machte mich bei nächster Gelegenheit auf die Suche nach einem Tool, welches dieses Dilemna eleganter lösen sollte. Und ich fand Lanshark, ein Programm des Schweizers Jonas Wagner. Die Programmversion 0.0.2 stimmte mich zwar etwas bedenklich, aber die verwendete Programmiersprache Python gab Anlass zur Hoffnung, dass ich das Programm selbst für meine Zwecke anpassen konnte. Das war im Endeffekt aber gar nicht nötig, da Lanshark bereits in der vorliegenden Version genau das Werkzeug war, nach dem ich gesucht hatte. Das Programm ist unter der GPL freigegeben, die Installation ist kinderleicht, Lanshark sucht im lokalen Netz selbständig nach Austauschpartnern, die Oberfläche ist übersichtlich und es gibt ein paar weitere nette Features (UTF8-Support oder eine Resumefunktion zum Beispiel). Daher beschränke ich meine Beteiligung am Projekt momentan auf die Pflege des Pakets für Debian/Ubuntu. Lanshark ist darüber hinaus für Windows, Gentoo und als tar-Ball für andere Linuxderivate verfügbar.

Abgesehen davon, dass es für LAN-Partys sehr nützlich ist, verwende ich Lanshark auch manchmal bei der Arbeit, z.B. um Dateien schnell und unkompliziert auf mehrere virtuelle Maschinen zu verteilen (wenn AFS nicht zur Verfügung steht). Also: Testen und immer schön artig (und legal) bleiben!

Python 3000 erschienen

Selten wurde das neue Release einer Programmiersprache so kontrovers diskutiert wie bei Python 3000 (auch „Py3K“ genannt). Und das hat seinen Grund, schließlich ist die neue Version in einigen häufig genutzten Bestandteilen inkompatibel zu den Vorgängern. Die Wandlung der „print“-Anweisung hin zur Funktion ist wohl das berühmteste Beispiel, aber darüber hinaus gibt es weitere wichtige Änderungen, u.a.:

  • viele Methoden und Funktionen liefern statt Listen nun Iteratoren oder „Views“ zurück (z.B. map, filter, dict.keys)
  • syntaktische Änderungen, z.B. beim Exception Handling („as“ statt Komma)
  • Unicode ist nun der Standard zur Zeichenkodierung (siehe das „Unicode-Howto“)
  • Divisionen liefern nun Fließkommazahlen, keine abgeschnittenen Integers
  • „old-style-classes“, also Klassen, die nicht von einer Basisklasse abgeleitet wurden, sind nicht mehr erlaubt
  • einige Funktionen und Methoden wurden umbenannt („raw_input“ wurde zu „input“) oder komplett entfernt („has_key“, „file“)
  • neue nützliche Helfer wie die „with“-Anweisung oder das „multiprocessing“-Paket sind hinzugekommen

Eine komplette Liste der Änderungen findet man unter: http://docs.python.org/dev/3.0/whatsnew/3.0.html. Zusätzlich sollte man beachten, dass die kürzlich erschienene Version 2.6 bereits einige Backports von Python 3000 beinhaltete, sofern diese kompatibel mit Python 2.x waren. Die (entsprechend langen) Release Notes zu Python 2.6 findet man unter: http://docs.python.org/dev/whatsnew/2.6.html

Wer seine bestehenden Skripte auf Kompatibilität zu Python 3000 prüfen möchte, kann dies unter Python 2.6 mit dem Kommandozeilenparameter „-3“ tun. Wer seine Skripte automatisch in Python 3000 portieren möchte, kann hierfür das Tool „2to3“ verwenden. Häufig ist manuelles Nacharbeiten aber dennoch erforderlich.

Perl und Unicode

Obwohl ich eigentlich ganz gut verstehe, wie Unicode in seinen verschiedenen Ausprägungen funktioniert, war mir (und ist zum Teil noch) ein Rätsel, wie Perl damit umgeht. In der Vergangenheit hatte ich vornehmlich das Problem, dass Skripte obskure Unicode-Fehlermeldungen an Stellen produzierten, an denen ich garantiert nicht mit Unicode arbeiten wollte. Ursache ist die Verwendung eines Unicode-Locales unter Linux, z.B. das bei uns standardmäßig eingesetzte „de_DE.UTF-8“. Dafür benutze ich seit einiger Zeit diesen Quickfix:

if (defined $ENV{"LANG"}) {
exec 'env', 'LANG=C', $0, @ARGV unless $ENV{"LANG"} eq "C";
}

Damit wird das Skript garantiert im Locale „C“ ausgeführt.

Jetzt hatte ich zum ersten Mal eine Situation, in der ich wirklich Unicode benutzen wollte. Es ging darum, Umlaute im Input in die Umschreibung „ae“ etc. umzuwandeln. Mein erster Versuch dafür war grundsätzlich richtig:

$input =~ s/ä/ae/g;

Das funktionierte aber nicht, d.h. die Umlaute blieben erhalten. Nach etwas Suchen habe ich gefunden, dass man das Pragma utf8 setzen muss, wenn solche Zeichen im Skripttext auftauchen. So funktioniert es also:

use utf8;

$input =~ s/ä/ae/g;

NB: das setzt voraus, dass das Skript mit einem UTF8-fähigen Editor geschrieben und gespeichert ist.