Ein Perlskript erspart dem eiligen Nutzer das Herumklicken auf Splash-Pages von WiFi-Providern, nickt Geschäftsbedingungen automatisch ab und schaltet den Zugriff auch ohne Browser schnellstens frei.
Bevor Hotel-, Internet-Cafe- oder Flughafen-WiFis neue User ins Netz lassen, leiten sie Browser-Requests um (Abbildung 1) und der Surfdurstige muss sich erst durch eine Splash-Page (Abbildung 2) quälen. Dort erwarten ihn entweder ellenlange Nutzungsbedingungen, die eh niemand liest, Checkboxen, die zu aktivieren sind, oder Werbung, oder alle drei genannten Ärgernisse zugleich. WiFi-Provider stellen Splash-Pages als kleine Hürden auf, um die MAC-Addresse des Clients im WiFi-Netz zentral zu erfassen und den User an seine Pflichten als vorbildlichen Netzbürger zu erinnern. Absolviert dieser den Pflichttanz ordnungsgemäß, lässt der Access Point den Schutzschild fallen und nicht nur der Web-Verkehr fließt, sondern auch beliebige TCP-Verbindungen ins Internet stehen auf einmal offen.
Abbildung 1: Obwohl der User google.com wünscht, leitet das WiFi ihn zunächst zur Splash-Page des Providers um. |
Manchmal möchte der User aber gar keinen Web-Zugang, sondern nur einen SSH-Tunnel öffnen, und wundert sich bei offener Shell und geschlossenem Browser, warum er zwar eine IP-Addresse zugewiesen bekommt, aber offensichtlich eine Firewall den Zugriff aufs Internet blockiert.
Abbildung 2: Das Hotel-Wifi leitet den ersten Browser-Request zu einer Splash-Page um. |
Abbildung 3: Mit einem Klick akzeptiert der Benutzer die Bedingungen und darf fortan 24 Stunden unbehelligt surfen. |
Das Perl-Skript splash
hilft uns dabei heute aus der Patsche. Es wühlt sich
blind durch die zwischen den erwartungsfrohen User und den
ungetrübten Internetgenuss gestellten Web-Formulare, klickt auf alle Links,
checkt die Checkboxen, sammelt die Cookies ein und schickt sie brav an den
WiFi-Provider zurück, dem es so vorgaukelt, mit einem Browser samt
lebensechtem Bediener zu kommunizieren.
Doch wie kann ein einfaches Skript mit potentiell tausenden verschiedener Splash-Seiten zurechtkommen? WiFi-Provider könnten nicht nur beliebig komplexe Formulare erzeugen, sondern auch schwere Geschütze auffahren, wie mit JavaScript zu tricksen oder gar Flash-Animationen einzusetzen, um automatische Skripts abzuwehren.
Deshalb fährt das Skript eine Plugin-Strategie, mit der User oder auch freundliche Dritte es an neue Verfahren anpassen können. Jeder Plugin probiert nach seiner eigene Methode, die Splash-Seite zu überlisten: Der Plugin ClickAllLinks.pm klickt alle Links auf der Seite durch, während CheckBoxFill.pm alle dargestellten Checkboxen des ersten Webformulars aktiviert und anschließend einen eventuell vorhandenen Submit-Button drückt. Neu entwickelte Plugins fügt der User ins Plugin-Verzeichnis ein, von wo sie das Skript ohne weitere Konfiguration automatisch aufschnappt und als neue Taktik ausprobiert.
Nach jedem Plugin-Lauf prüft splash
, ob ein Request für http://google.com
auch wirklich die Webseite des Suchriesen einholt oder ob schon wieder die
verhasste Splash-Page erscheint. Falls sich noch kein Erfolg eingestellt
hat, kommt der nächste Plugin zum Zug, andernfalls bricht das
Skript mit einer Erfolgsmeldung ab.
Abbildung 4: Falls keine Netzwerkverbindung besteht, probiert das Skript in Abständen von 5 Sekunden, den Google-Server zu erreichen. |
Abbildung 4 zeigt, wie das Skript reagiert, falls einfach noch keine Netzwerkverbindung existiert, wohl weil der User noch keines der verfügbaren WiFi-Netzwerke selektiert oder noch nicht das erforderliche WPA-Passwort eingegeben hat. Solange die Google-Anfrage nur Fehler liefert oder länger als fünf Sekunden blockiert, probiert es das Skript nach kurzer Verschnaufpause einfach nochmal. Sobald der Client dann eine IP zugewiesen bekommt und sich bei Web-Anfragen wenigstens die Splash-Page einstellt, wirft es einen Plugin nach dem anderen ins Getümmel (Abbildung 5), bis einer schließlich die künstliche Mauer überwindet.
Abbildung 5: Trifft das Skript auf eine Splash-Page, versucht es zunächst nach dem Verfahren click-all-links alle Links durchzuklicken. |
Listing 1 zeigt die Basisklasse SplashJumper
aller Plugins, die lediglich
einen Konstruktor definiert. Sie zieht das CPAN-Modul Module::Pluggable
hinzu und übergibt ihm die Parameter require => 1
. Das Modul
durchforstet daraufhin das Unterverzeichnis SplashJumper/Plugin
nach
.pm-Dateien und lädt sie wegen der require
-Anweisung alle in das
gerade laufende Skript.
01 ########################################### 02 package SplashJumper; 03 ########################################### 04 # 2010, Mike Schilli <m@perlmeister.com> 05 use strict; 06 use warnings; 07 use Module::Pluggable require => 1; 08 09 sub new { 10 bless {}, shift; 11 } 12 13 1;
Einen typischen Plugin zeigt Listing 2 mit dem Modul ClickAllLinks
.
Wenn das Hauptskript den Plugin lädt, ruft es dessen register()
-Methode
auf, die den Namen der implementierten Taktik und eine Prioritätszahl
zurückliefert. Das Hauptskript sortiert die Plugins numerisch nach den
von ihnen mitgeteilten Prioritätszahlen, ruft also Plugins mit niedrigen
Werten zuerst auf. So können die Plugins untereinander auskarteln, wer
wann an die Reihe kommt. Üblicherweise sollte der mit den besten
Erfolgsaussichten den Anfang machen, das verkürzt den Skriptlauf.
Die beide heute vorgestellten Plugins definieren die Prioritäten 10
beziehungsweise 50,
so dass das Skript immer zuerst ClickAllLinks versucht, bevor das
weiter unten gezeigte CheckBoxFill-Verfahren zum Zug kommt.
01 ########################################### 02 package 03 SplashJumper::Plugin::ClickAllLinks; 04 ########################################### 05 # Mike Schilli, 2010 (m@perlmeister.com) 06 ########################################### 07 use Log::Log4perl qw(:easy); 08 09 ########################################### 10 sub register { 11 ########################################### 12 return "click-all-links", 10; 13 } 14 15 ########################################### 16 sub process { 17 ########################################### 18 my($self, $mech) = @_; 19 20 for my $link ( $mech->links() ) { 21 22 INFO "Clicking on ", $link->url(); 23 my $resp = $mech->get( $link ); 24 25 INFO "Got ", 26 length( $resp->content() ), 27 " bytes back"; 28 29 $mech->back(); 30 } 31 } 32 33 1;
Kommt ein Plugin zum Überwinden der Splash-Page
zum Einsatz, ruft das Skript dessen process()
-Methode
auf und übergibt ihr den Browser-Simulator $mech
, ein Objekt der
Klasse WWW::Mechanize. Dieses CPAN-Modul eignet sich hervorragend
dazu, Webseiten einzuholen, deren Inhalt zu analysieren und zu
tieferliegenden Links vorzudringen.
Es kommt häufig bei der Implementierung von Screen-Scrapern
zum Einsatz, da beinahe alle Browser-Funktionen beherrscht (abgesehen
von JavaScript-Code oder Flash-Plugins) und Cookies automatisch
empfängt und korrekt wieder an den Server zurückschickt. Im Plugin
ClickAllLinks
findet zunächst die Methode links()
alle auf der
eingeholten Splash-Seite vorhandenen Links in Form von
WWW::Mechanize::Link-Objekten und iteriert über die Liste
mittels einer for-Schleife. Die Funktion INFO aus dem Log4perl-Fundus
zeigt dann dem neugierigen User an, welchen Link der Plugin
gerade anklickt. Die Methode get()
des Browser-Simulators führt
den Web-Request anschließend durch.
Eine explizite Fehlerprüfung findet nicht statt, da WWW::Mechanize sich
standardmäßig im autocheck
-Modus befindet, in dem es bei jedem
auftretenden Fehler eine Exception wirft, die ein im Hauptskript um
den Plugin-Aufruf gewickelter eval {}
-Block abfängt. Anschließend behandelt
das Hauptprogramm den Fehler.
01 ########################################### 02 package SplashJumper::Plugin::CheckBoxFill; 03 ########################################### 04 # Mike Schilli, 2010 (m@perlmeister.com) 05 ########################################### 06 use Log::Log4perl qw(:easy); 07 08 ########################################### 09 sub register { 10 ########################################### 11 return "checkbox-fill", 50; 12 } 13 14 ########################################### 15 sub process { 16 ########################################### 17 my($self, $mech) = @_; 18 19 $mech->form_number(1); 20 21 my @inputs = $mech->current_form-> 22 find_input( undef, "checkbox" ); 23 24 for my $input ( @inputs ) { 25 $input->check(); 26 } 27 28 INFO "Submitting form 1"; 29 $mech->submit_form( form_number => 1 ); 30 $mech->back(); 31 } 32 33 1;
In Zeile 25 zeigt der Plugin dann mit einer weiteren INFO-Anweisung an,
wieviele Bytes über den geklickten Link zurückkamen und die Methode
back()
in Zeile 29 drückt den "Back"-Button des virtuellen Browsers,
damit dieser wieder zur Splash-Page zurückrudert.
Einen weitere Taktik zeigt der Plugin CheckBoxFill in Listing 3. Es sucht
im HTML der Splash-Seite das erste Webformular und setzt dieses mit
der Anweisung form_number(1)
als current_form
. Die Methode
find_input()
extrahiert daraus alle Eingabefelder vom Typ checkbox
und die for-Schleife ab Zeile 24 klickt sie alle an, indem sie deren
check()
-Methode aufruft. Zeile 29 schickt das Webformular dann
mit submit_form()
an den Server zurück, bevor Zeile 30 wieder auf
die Splash-Seite zurückkehrt, damit der eventuell gleich folgende
Plugin wieder normale Ausgangsbedingungen vorfindet. Mit dieser Methode
lassen sich Splash-Pages wie in den Abbildungen 6 und 7 knacken, die beide
dem User abverlangen, eine Checkbox zu aktivieren und dann den "Submit"-Button
anzuklicken.
Abbildung 6: Das kostenlose WiFi am Flughafen von San Diego verlangt, eine Checkbox zu anzuklicken und das Web-Form abzuschicken. |
Abbildung 7: Das kostenlose WiFi am Flughafen von San Francisco, ebenfalls mit einer Checkbox. |
Listing 3 zeigt schließlich das Hauptprogramm splash
. Es definiert
zunächst in Zeile 7 den Test-URL, den das Skript einholt, um festzustellen,
ob der Internetzugang bereits offen steht. Die Google-Seite eignet sich
dazu hervorragend, da sie leichtgewichtig und mit hoher Wahrscheinlichkeit
in einem funktionierenden Internet verfügbar ist. Zeile 9 initialisiert
das Log4perl-Framework mit dem Level DEBUG, damit der User detaillierte
Informationen darüber erhält, welcher Plugin gerade läuft und auf
welche Links dieser klickt.
Ein neu angelegtes Objekt der Basisklasse SplashJumper
verfügt dank
des dort eingebundenen Moduls Module::Pluggable über eine Methode
plugins()
, die eine Liste aller im Plugin-Unterverzeichnis eingestellten
Plugins zurückliefert. Zeile 17 prüft, ob jeder Plugin tatsächlich
vorschriftsgemäß eine Methode namens register()
anbietet und überspringt
mangelhaft implementierte Plugins mit einer Fehlermeldung.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use SplashJumper; 04 use WWW::Mechanize; 05 use Log::Log4perl qw(:easy); 06 07 my $url = "http://www.google.com"; 08 09 Log::Log4perl->easy_init($DEBUG); 10 11 my $sj = SplashJumper->new(); 12 13 my @ways = (); 14 15 for my $plugin ( $sj->plugins() ) { 16 17 if( ! $plugin->can("register") ) { 18 ERROR "$plugin can't do register()"; 19 next; 20 } 21 22 my($algo, $order) = $plugin->register(); 23 24 push @ways, [$algo, $plugin, $order]; 25 } 26 27 # sort by plugin priority 28 @ways = sort { $a->[2] <=> $b->[2] } @ways; 29 30 my $mech = WWW::Mechanize->new(); 31 $mech->timeout(5); 32 33 # wait until network is up 34 { 35 INFO "Trying $url"; 36 eval { $mech->get( $url ); }; 37 if($@) { 38 INFO "Connection down, retrying"; 39 sleep 5; 40 redo; 41 } 42 } 43 44 # try to get past splash page 45 for my $ways ( @ways ) { 46 eval { $mech->get( $url ); }; 47 48 my $current_url = 49 $mech->response->request->uri; 50 51 if( $current_url eq $url ) { 52 INFO "Link is up."; 53 last; 54 } else { 55 INFO "Link still down."; 56 } 57 58 my($algo, $plugin, $order) = @$ways; 59 60 eval { 61 INFO "Processing splash page ", 62 "$current_url with algo $algo"; 63 $plugin->process( $mech ); 64 }; 65 66 if($@) { 67 ERROR "Algo $algo failed ($@)"; 68 } else { 69 INFO "Plugin $algo succeeded"; 70 } 71 }
Ordnungsgemäße Plugins geben in Zeile 22 ihre Taktik in $algo
und
die gewünschte numerische Priorität in der Variablen $order
zurück.
Das Skript verpackt die gefundenen Daten in einen Array und schiebt eine
Referenz darauf ans Ende des Arrays @ways. Zeile 28 sortiert dessen
Elemente dann numerisch nach dem order
-Feld, so dass ein Plugin mit
Priorität 10 vor einem Plugin mit Priorität 50 läuft.
Der Browser-Simulator vom Typ WWW::Mechanize setzt seinen Timeout
in Zeile 31 auf 5 Sekunden, so dass die Schleife ab Zeile 33 jeweils nur
5 Sekunden in der get()-Methode hängt, bevor sie aufgibt und 5 Sekunden
schläft, bevor sie erneut probiert, den Google-Server zu kontaktieren.
Am Ende des Blocks, den Perl mit redo
wiederholt,
funktioniert zumindest das lokale WiFi-Netz und der Client bekam eine
gültige IP-Adresse zugewiesen, doch der
WiFi-Provider leitet Requests an www.google.com
eventuell noch zu
einem internen Server weiter, der die Splash-Page produziert.
Diese versucht die for-Schleife ab Zeile 45 mit den verschiedenen Plugins zu überwinden und sobald die URL des letzten Requests gleich der Test-URL ist (also kein Redirect zur Splash-Page mehr stattfand) befindet Zeile 51, dass die Splash-Page überwunden ist und die Internetverbindung offen steht.
Ist dies noch nicht der Fall, ruft Zeile 62 die Methode process()
des
nächsten Plugins auf, gemäß der vorher definerierten Priortiäten. Der
eval{}-Block ab Zeile 59 fängt im Plugin auftretende Fehler ab und
Zeile 65 prüft die Variable $@
, um festzustellen, ob etwas vorgefallen
ist.
Damit das Skript das Modul SplashJumper findet, muss letzteres im Suchpfad
%INC des Skripts installiert sein, am einfachsten geht das, indem beide
im gleichen Verzeichnis landen. Die Plugins wandern ins neu angelegte
Unterverzeichnis SplashJumper/Plugin
, so dass das Datei-Layout wie
folgt aussieht:
splash SplashJumper.pm SplashJumper/Plugin/ClickAllLinks.pm SplashJumper/Plugin/CheckBoxFill.pm
Weitere Plugins, die eventuell mehrere Webformulare verarbeiten oder gar
Javascript-Tricks überlisten legt der User ebenfalls ins Verzeichnis
Plugin
ab und weist ihnen in der Methode register()
einen Taktiknamen
und eine Priorität zu, um die Reihenfolge ihres Einsatzes festzulegen.
Nach dem Aufklappen des Laptops am Flughafen ist dann einfach splash
zu
starten. Die Ausgabe des Skripts zeigt anschließend, wie sich der kleine
Krieger in der neuen Umgebung schlägt und ob er dort ebenfalls die
in den Weg gestellten Hürden umschubsen kann.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2010/11/Perl
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |