Clemens Gruber: Verzeichnisschutz .htaccess ohne Pop-Up / apache_lookup_uri()

Ich möchte .htaccess/.htpasswd zum Verzeichnisschutz verwenden. Allerdings soll zur Authentifizierung nicht das obligatorische Pop-Up zum Einsatz kommen, sondern aus CI-Gründen ein Formular, das sich "netter" in die Webseite integrieren lässt. Hier gibt es allerdings Probleme. Aber zunächst folgendes:

1. Warum .htaccess/.htpasswd?
Das Problem bei anderen Authentifizierungsmöglichkeiten z.B. über PHP- oder Perl-Skripts ist, dass hier vor jedem Skript/jeder Datei das Authentifizierungs-Script aufgerufen werden muss. Teilweise - z.B. bei reinen "PHP-Kulturen" geht das mit einem Eintrag in der .htaccess die bspw. die Zeile php_auto_prepend_file authentifizierung.php enthält. Bei "Mischkulturen," also  PHP und HTML, sHTML usf., geht das schon wieder nicht mehr; und bei "exotischen" Dateien wie PDF, doc, Binarys oder auch bei Grafiken geht das - meines Wissens, ich lasse mich da aber gerne korrigieren - überhaupt nicht. Ist - aus welchen Gründen auch immer - die URL (z.B. www.domain.de/geschuetzt/geschaeftsbericht.pdf) bekannt, hat jeder und jede Zugriff auf die entsprechende Datei.

2. Login und Passwort über ein Formular eingeben
Man kann nun wie im Beispiel unten eine URL der Form  http://login:passwort@www.domain.de/geschuetzt/
erzeugen und sich so mit einem eigenen Formular mit dem bestehenden .htaccess/.htpasswd-Schutz einzuloggen.

<html>
<head>
  <script language="JavaScript1.2">
    <!--
      function einloggen()
      {
        var url="http://" +document.input['login'].value+ ":"
            +document.input['passwort'].value+
            "@www.domain.de/geschuetzt/";
        self.location=url;
      }
    // -->
</script>
</head>

<body>
  <form name=input>
    Login:    <input name=login type=text size=10 maxlength=10>
    Passwort: <input name=passwort type=password size=10 maxlength=10>
    <input type=button value="OK" onClick="einloggen()">
  </form>
</body>
</html>

Das geht bei richtigem Login/Passwort ohne Probleme. Allerdings sieht es bei Falschangeben schlecht aus. Hier kommt wieder das Pop-Up und die Apache-seitige Error-Meldung 403. Scheinbar ist das Pop-Up auch nicht zu unterdrücken, in einem Posting (http://213.139.94.131/selfhtml/sfarchiv/2000_2/t13744.htm) hieß es: "Soweit ich weiß, tritt der 403-Fehler erst dann auf, wenn der Browser seinen lokalen retry-Dialog erfolglos beendet hat. Das aber ist eine Eigenschaft des Browsers und durch Serverkonfiguration nicht zu beeinflussen."

3. Anklopfen mit apache_lookup_uri($name)
Meine Idee war nun nicht direkt http://login:passwort@www.domain.de/geschuetzt/ die URL aufzurufen, sondern erst einmal "anzuklopfen" und bei entsprechendem "Alles richtig, herein!" die URL zu übergeben.

Unter PHP gibt es spezifische Funktionen für den Apache Webserver, u.a. die Funktion
apache_lookup_uri($name) mit $name = Name der URI, die ein Objekt (!) mit Elementen über die angegebene Quelle (URI) erzeugt, u.a. auch den Status des HTTP-Response bei Aufruf der angegebenen URI, dabei ist der Status das erste Element des Objekts.

Wenn man mit apache_lookup_uri("www.domain.de/geschuetzt ") den Status ausliest bekommt man also 401, und bei apache_lookup_uri("www.domain.de/_nicht_geschuetzt ")
200 zurück geliefert.

Nun war meine Idee, man könnte das auch mit login:passwort@www.domain.de/geschuetzt/
machen. Wird dann bei richtiger Passwort/Login-Kombination 200 als Status zurückgeliefert kann man auf die entsprechende Seite weiterleiten. Wenn nicht, wird einfach nochmals das Formular etwa mit der Bemerkung "Falsche Eingabe. Bitte noch einmal versuchen!" aufgerufen.

Aber es funktioniert nicht. Unten habe ich alle Elemente des mit apache_lookup_uri("test:test@stuff/test/index.html"); erzeugten Objekts ausgegeben. Scheinbar wird Login und Passwort einfach nicht richtig aufgelöst, sondern einmal anscheinend als Teil des Path interpretiert (unten bei URI), oder nur der Teil test:test@stuff (bei Filename) oder der Teil nach @stuff (bei path_info). Auch der Status ist falsch, de mit Login: test und PW: test kein Zugang möglich ist.

status => 200
the_request => GET /test HTTP/1.0
method => GET
uri => /test:test@stuff/test/index.html
filename => /[...]/www.[domain].de/test:test@stuff
path_info => /test/index.html
no_cache => 0
no_local_copy => 1
allowed => 0
sent_bodyct => 0
bytes_sent => 0
byterange => 0
clength => 0
unparsed_uri => /test:test@stuff/test/index.html
mtime => 990574711
request_time => 990576082

Ich habe das auch mit anderen Pfadangeben versucht:
apache_lookup_uri("test:test@/stuff/test/index.html");
apache_lookup_uri("test:test@http://www.[domain].de/stuff/test/index.html")
apache_lookup_uri("test:test@www.[domain].de/stuff/test/index.html")
apache_lookup_uri("test:test@/...pfad.../www.[domain].de/stuff/test/index.html")
apache_lookup_uri("test:test@...pfad.../www.[domain].de/stuff/test/index.html")

aber auch das war nichts!

Nur noch zur Information: das Skript lookup.php
...
$cl= apache_lookup_uri("test:test@stuff/test/index.html");
foreach ($cl as $k=>$elem) {echo "$k => $elem<br>";}
...
steht im root-Verzeichnis (www.[domain].de/lookup.php) das zu schützende Verzeichnis ist
www.[domain].de/stuff/test/

Fragen
Hat jemand eine Idee, wie man mit apache_lookup_uri() doch noch Login und Passwort einbauen kann?
Ist überhaupt die Überprüfung der Login-Daten bei der apache_lookup_uri()-Funktion theoretisch möglich?
Nach welchen Regeln wird die URI aufgelöst und was geschieht mit test:test@?
Gibt es so etwas auch bei Perl oder mit SSI-Syntax?
In welcher Reihenfolge läuft bei http://login:passwort@www.domain.de/geschuetzt/ die Authentifizierung und Überprüfung der Daten ab?

  1. Hallo Clemens,

    In welcher Reihenfolge läuft bei http://login:passwort@www.domain.de/geschuetzt/ die Authentifizierung und Überprüfung der Daten ab?

    Du weißt, dass ein URL in dieser Form nicht gültig ist?
    Nachzulesen im RFC 1738, Abschnitt 3.3: http://rfc.net/rfc1738.html

    Viele Grüße aus Dresden,
    Stefan Einspender

    1. Hallo Clemens,

      In welcher Reihenfolge läuft bei http://login:passwort@www.domain.de/geschuetzt/ die Authentifizierung und Überprüfung der Daten ab?

      Du weißt, dass ein URL in dieser Form nicht gültig ist?
      Nachzulesen im RFC 1738, Abschnitt 3.3: http://rfc.net/rfc1738.html

      Hi Stefan!

      Danke, für den Hinweis auf RFC 1738! Da steht dann auch:

      3.1. Common Internet Scheme Syntax

      While the syntax for the rest of the URL may vary depending on the
         particular scheme selected, URL schemes that involve the direct use
         of an IP-based protocol to a specified host on the Internet use a
         common syntax for the scheme-specific data:

      //<user>:<password>@<host>:<port>/<url-path>

      Some or all of the parts "<user>:<password>@", ":<password>",
         ":<port>", and "/<url-path>" may be excluded.  The scheme specific
         data start with a double slash "//" to indicate that it complies with
         the common Internet scheme syntax. The different components obey the
         following rules:

      user
              An optional user name. Some schemes (e.g., ftp) allow the
              specification of a user name.

      password
              An optional password. If present, it follows the user
              name separated from it by a colon.

      The user name (and password), if present, are followed by a
         commercial at-sign "@". Within the user and password field, any ":",
         "@", or "/" must be encoded.

      Und da hammer's dann:
      must be encoded.
              -------

      Da es auch in der Form http://login:passwort@www.domain.de/geschuetzt/ mit JavaScipt und bei direkter Eingabe geht bin ich fälschlicherweise davon ausgegangen, dass Apache und PHP das auch so schmeckt ;-)

      Meintest Du das fehlende encoding als ungültig oder den generelle Aufbau. Du hast auf Abschnitt 3.3 HTTP URL scheme verwiesen. Als Strato die sog. @-Domains einführte gabe es da eine Diskussion ob das nun RFC-konform sei oder nicht. Allerdings würde von Strato die Sache vor dem @ als Variable per JavaScript ausgelesen und dann zu entsprechenden Dateien weitergeleitet. Das hatte also nichts mehr mit Username und Passwort zu tun. Es kann natürlich sein, dass mit HTTP der Common Internet Scheme Syntax aus Abschnitt 3.1  seine Gültigkeit verliert und nur noch der in Abschnitt 3.3 spezifizierte Syntax gültigkeit hat.

    2. Nachtrag:

      Du hast also damals das Strato-Forum so aufgemischt (habe gerade Deinen Beitrag unter http://www.teamone.de/selfaktuell/forum/?m=126928&t=24362 gelesen) ;-) Fand ich schon interessant und habe mich auch über die Bemerkung bei 1&1 amüsiert. So, und jetzt sitze ich da und knabbere selber an dem Zeug rum. So schnell kann es gehen. Ich habe das @ mal als %40 codiert, geht aber auch nicht. Scheinbar hält sich Apache/PHP eher an die RFCs als so mancher Webbrowser.

      Dennoch Danke für die Hilfe.

      1. Hallo Clemens,

        Du hast also damals das Strato-Forum so aufgemischt (habe gerade Deinen Beitrag unter http://www.teamone.de/selfaktuell/forum/?m=126928&t=24362 gelesen) ;-)

        ja und zufällig habe ich in der Zeit auch die eine oder andere e-Mail
        an Leute verschickt, die wohl bei Heise arbeiten, die fanden es auch
        sehr interessant und so war dann auch Strato zu einer Erklärung "ge-
        zwungen" ;)

        Ich meinte übrigens, dass ein URL in der folgenden Form ungültig ist:
        http://bloedsinn:passwort@www.strato.de/
        http://bloedsinn@www.strato.de/

        Im zweiten Fall ist der Passwortteil weggelassen, was laut RFC zu-
        lässig ist, nur eben nicht bei http.
        Schon allein die Tatsache, dass bei allen mir bekannten @-Domain-
        Anbietern der User ('bloedsinn') clientseitig ausgewertet wird,
        zeigt die Fragwürdigkeit einer solchen Konstruktion.

        Ich finde es überaus besucherfeindlich, wenn irgendwo unfug@strato.de
        steht und der User ausprobieren muß, ob es sich dabei vielleicht ja
        um so eine "tolle" @-Domain handelt.
        Jede Art von Programm dürfte damit wesentlich mehr Schwierigkeiten
        haben.
        Alternativ kann man dann _immer_ das Protokoll angeben, wo da der
        Fortschritt liegt, konnte Strato bisher noch nicht erklären *g*

        Viele Grüße aus Dresden,
        Stefan Einspender

        1. Ich meinte übrigens, dass ein URL in der folgenden Form ungültig ist:
          http://bloedsinn:passwort@www.strato.de/
          http://bloedsinn@www.strato.de/

          Im zweiten Fall ist der Passwortteil weggelassen, was laut RFC zu-
          lässig ist, nur eben nicht bei http.

          Ja, Du hast uneingeschränkt recht. Ich habe mir nochmal den Abschnitt 3.3 genau angesehen. Und da steht "No user name or password is allowed", und das ist schon eindeutig. Man kommt nur etwas durcheinander weil das Common Internet Scheme Syntax aus 3.1 eben vorher da steht, wie auch jemand auf das ältere Posting von Dir geschrieben hat.

          Schon allein die Tatsache, dass bei allen mir bekannten @-Domain-
          Anbietern der User ('bloedsinn') clientseitig ausgewertet wird,
          zeigt die Fragwürdigkeit einer solchen Konstruktion.

          Ich finde es überaus besucherfeindlich, wenn irgendwo unfug@strato.de
          steht und der User ausprobieren muß, ob es sich dabei vielleicht ja
          um so eine "tolle" @-Domain handelt.

          :) "Rate mal mit... Strato" Die Chancen stehen 1:?... und wenn es nicht meine Domain ist, dann kannst du immer noch an diese Adresse eine e-mail schreiben und mir mitteilen, dass es keine ist ;-)

          Jede Art von Programm dürfte damit wesentlich mehr Schwierigkeiten
          haben.
          Alternativ kann man dann _immer_ das Protokoll angeben, wo da der
          Fortschritt liegt, konnte Strato bisher noch nicht erklären *g*

          Naja, das ist ja ein offenes Geheimnis, spätestens seit dem Super-Gau. Sonst hätten die ja schon lange Subdomains angeboten wie es jeder vernünftige Provider macht.

          Jetzt aber noch mal zu meinem ursprünglichen Problem: Auf Grund des besagtne RFC (Abschnitt 3.3) werde ich das wohl lassen mit HTTP und Login/PS und statt dessen mit Header usw. arbeiten wie in http://www.zend.com/zend/tut/authentication.php beschrieben. Den Vorschlag von Henryk muss ich noch ausprobieren. Da dürfte es ja keine Bedenken bzgl. RFCs geben. Müsste eine technisch "saubere" Lösung sein.

          Danke nochmal für den Hinweis!

          Gruß Clemens (übrigens aus Würzburg)

  2. Hi,

    zunächst einmal danke, dass du mich auf die Idee gebracht hast. So etwas ähnliches wollte ich auch immer schon benutzen, habe mich aber dann mit dem popup zufriedengegeben...

    1. Anklopfen mit apache_lookup_uri($name)
      Meine Idee war nun nicht direkt http://login:passwort@www.domain.de/geschuetzt/ die URL aufzurufen, sondern erst einmal "anzuklopfen" und bei entsprechendem "Alles richtig, herein!" die URL zu übergeben.

    Dieser Lösungsansatz ist schlichtweg genial! Diese Idee hatte mir bisher gefehlt.

    Also zum Problem: Wie Stefan schon schrieb wird dieser URL wohl nicht korrekt sein. Aber sein Verweis auf den RFC bringt auch die Lösung. Zwar nicht in rfc1738, sondern in rfc2616 und 2617:

    http://rfc.net/rfc2616.html
    http://rfc.net/rfc2617.html

    Was spricht denn dagegen, zumindest eine Minimalform von HTTP zu implementieren, womit du dann gleichzeitig exklusiven Zugriff auf die Fehlercodes hast.

    Also statt dem apache_lookup_uri($name) machst du etwas in der Art wie:

    $conn=fsockopen("www.domain.de",80);
    fwrite($conn,"GET /geschuetzt/ HTTP/1.1\nHost: www.domain.de\n".
    "Authorization: ".base64_encode($username.":".$password)."\n\n";
    $status=fgets($conn,4);
    fclose($conn);

    Und solltest anschließend den Statuscode, also 200 oder 401, in $status haben. (Der obige Code ist ein quick hack und natürlich ungetestet)

    Funktionen zum öffnen von Netzwerkverbindungen hat wohl auch Perl, so daß diese Lösung nicht auf PHP beschränkt bleiben dürfte...

    H2H

    --
    Henryk Plötz
    Grüße von der Ostsee

  3. Ich habe noch einen Tip bekommen:

    Man könnte per Script das eingegebene Login/PW mit den Angaben in der .htaccess vergleichen. Ein Code mit Pop-up via

    header( 'WWWW-Authenticate: Basic realm="Private"' );
      header( 'HTTP/1.0 401 Unauthorized' );
      echo 'Authorization Required.';
      exit;

    gibt es unter http://www.zend.com/zend/tut/authentication.php

    Wenn die Angaben dann korrekt sind, die URL übergeben.

    Gruß Clemens