Tom: Anmerkungen zu einigen gravierenden Fehlern

Beitrag lesen

Hello,

Das ganze Script sieht so aus:

Das Script ist nicht sehr sauber geschrieben. Abgesehen davon, bedeutet eine IP nicht 'ein Client', denn manche Clients wechseln mit jedem Request ihre IP, aber das wusstest Du sicherlich schon.

<?php

// Konfiguration
$besucher_online_konfiguration = array (
  // in welcher Datei soll es gespeichert werden
  'datei' => 'txt/useron/user_ips.txt',

Diese Datei ist bei Dir auch direkt über HTTP erreichbar.
Das kannst Du bei Standardeinrichtungen von Apache verhindern, indem
Du einfach den Namen der Datei mit '.ht' beginnen lässt.

'datei' => 'txt/useron/.htuser_ips.txt',

// wie lange soll es dauern, bis ein Eintrag verfällt
  'zeitlimit' => 300 // 5 Minuten = 300
);

// zählt den aktuellen Besucher
function zaehle_besucher ($ident)

{

// hole die Liste
  $liste = hole_besucher_liste ();

// fuege das aktuelle Identifikationskriterium mit aktuellem Datum hinzu
  $liste[$ident] = time ();

// schreibe die Liste
  schreibe_besucher_liste ($liste);
}

Das Holen der Liste und das anschließende Schreiben muss gemeinsam durch ein LOCK_EX geklammert werden, wenn das Script ordentlich arbeiten soll. Sonst fängst Du Dir ein TOCTOU-Problem ein [link:http://en.wikipedia.org/wiki/Time-of-check-to-time-of-use], da ja im Normalfall auch mehrere verschiedenen Clients gleichzeitig Requests senden können, die dann nebenläufig abgehandelt werden.

Das Öffnen und Locken der Datei sollte daher außerhalb der Teilfunktionen stattfinden und diese sollten das Handle übergeben bekommen.

// holt die Liste aus einer Datei
function hole_besucher_liste () {
  // importiere die Konfiguration
  global $besucher_online_konfiguration;

Das Arbeiten mit globalen Variablen führt die Nutzung von Funktionen i.d.R. ad absurdum.
Übergebe den Funktionen die Werte als Argumente oder Referenzen auf die Werte.

// initialisiere Array
  $zeilen = array ();

// öffne datei
  $fh = fopen ($besucher_online_konfiguration['datei'], 'r');

// fehler?
  if (!is_resource ($fh)) {
    return array ();
  }

// sperre datei
  $res = flock ($fh, LOCK_SH);

// gehe Zeilen durch
  while (!feof ($fh)) {
    $zeilen[] = fgets ($fh);
  }

// entsperre Datei und schließe sie
  flock ($fh, LOCK_UN);

Das Entsperren ist überflüssig, wenn Du die Datei gleich anschließend schließt.
Es kann sogar zu Fehlern führen. Wenn Du explizit entsperren willst, musst Du vorher die Buffers der Datei leeren, damit auch wirklich alles Weggescchrieben wird. fflush($fh);
[link:http://de3.php.net/manual/en/function.fflush.php]

fclose ($fh);

$ergebnis = array ();
  // gehe die Zeilen durch
  foreach ($zeilen as $zeile)

{

// trenne Identifikationskriterium von Zeitstempel
    list ($ident, $stempel) = explode ('|', $zeile, 2);
    // wenn Identifikationskriterium leer ist, dann übergehen
    if (empty ($ident))

{

continue;
    }

// wenn stempel zu alt ist
    if ($stempel < time() - $besucher_online_konfiguration['zeitlimit']) {
      // übergehen
      continue;
    }

// füge zum Ergebnis hinzu
    $ergebnis[$ident] = (int)$stempel;
  }
  return $ergebnis;
}

// gibt die Anzahl der Besucher zurück
function anzahl_besucher ()

{

return count (hole_besucher_liste ());
}

// schreibe die Liste zurück
function schreibe_besucher_liste ($liste)

{

// importiere die Konfiguration
  global $besucher_online_konfiguration;

$ergebnis = '';
  // gehe die Liste durch
  foreach ($liste as $ident => $stempel) {
    $ergebnis .= "$ident|$stempel\n";
  }

// öffne datei
  $datei = fopen ($besucher_online_konfiguration['datei'], 'w');

// sperre datei
  flock ($datei, LOCK_EX);

// Fehler?
  if (!is_resource ($datei)) {
    return false;
  }

Diese beiden Statements sind zusätzlich zum TOCTOU-Problem in der falschen Reihenfolge

öffnen
    Öffnung prüfen
    sperren
    Sperre prüfen
    arbeiten
    schließen

// schreibe das ergebnis
  $cnt = fwrite ($datei, $ergebnis);

// fehler?
  if ($cnt === false) {
    // entsperren und schließen
    flock ($datei, LOCK_UN);
    fclose ($datei);
    return false;
  }

// schließe die Datei
  flock ($datei, LOCK_UN);
  fclose ($datei);

// wir sind fertig
  return true;
}

?>

  
Außerdem dauern die Aufgaben in diesem Script ein paar Millisekunden und die Liste der IPs könnte etwas länger werden. Was ist, wenn ein User mittendrin, kurz nachdem er geklickt hat, die Lust verliert, auf die Response zu warten und sein Browserfenster schließt.  
  
Dann gibt es einen User-Abort, der das Script sofort beendet. Es könnte passieren, dass die Datei dann noch nicht vollständig zurückgeschrieben worden ist. Damit wäre sie kaputt.  
<http://de3.php.net/manual/en/function.ignore-user-abort.php>  
  
HTTP ist zwar normalerweise verbindungslos, wenn aber die Verbindung zum Client mitten in der Abarbeitung des Besuches (genauer: während der Erzeugung der Response) gekappt wird, bekommt PHP das doch mit. Die Scripte liegen in der "Responsetime" des Dialoges.  
  
  
  
  
  
  
  
  
Tom vom Berg  
![](http://selfhtml.bitworks.de/Virencheck.gif)  
  

-- 
Nur selber lernen macht schlau  
<http://bergpost.annerschbarrich.de>