#!/usr/bin/perl use strict; use warnings; my %oberkonfig = ( cron_errorlog => 'cron_error.txt', ); print "Rueckgabe: '".fritz_reconnect(\%oberkonfig)."'\n"; # Rückgaben: # ========== # '' -> jetzt keine Ausführung, da die aktuelle Stunde nicht $job_konfig{ausfuehrung_um} entspricht # oder die aktuelle IP von heute ist (es wird nur ein Reconnect pro Tag ausgeführt) # String -> Fehler aufgetreten, im String steht eine sprechende Fehlermeldung # String mit Präfix | -> Auftrag ausgeführt, der String enthält alte und neue Verbindungsdaten # # hier im Beispiel soll der Reconnect immer erst durchgeführt werden, # wenn die aktuelle Stunde auf der Uhr $job_konfig{ausfuehrung_um} entspricht, # es wird von einem Aufruf per Cronjob in jeder vollen Stunde des Tages ausgegangen # -------------------------------------------------------------------------------------------------------------- sub systemlog_eintrag { # Möglichkeit, in einer Art Protokoll die Verbindungsdaten aufzuzeichnen return; } # -------------------------------------------------------------------------------------------------------------- sub error { print STDERR '['.localtime().'] [error] [MELDUNG JOB '.$_[1].'] '.$_[0]."\n"; } # -------------------------------------------------------------------------------------------------------------- sub fritz_reconnect { # # DSL Reconnect der FRITZ!Box per Screenscraping mit Perl nebst WWW::Mechanize und Digest::MD5 # # Getestet mit der FRITZ!Box 7490 # mit FRITZ!OS 06.30 # Firmware-Version: 113.06.30 # im Oktober 2015 # my %job_konfig = ( ausfuehrung_um => 4, # Stunde des Tages, wann der Reconnect durchgeführt werden soll url => 'http://fritz.box', pass => '***', # debug => 'test_%s.html', # Dateiname und ggf. Pfad für die Dateien in die die einzelnen Seiten gespeichert werden, %s ist die laufende Nummer, z.B. '/meinpfad/debug_%2.html', nicht definieren, wenn kein Debug erwünscht response_reconnect => 'done:0', # was die FRITZ!Box auf "Neu verbinden" zurück liefert wait_nach_reconnect => 20, # max. Sekunden Wartezeit nach dem Reconnect zum Abruf der neuen Verbindungsdaten verbindungsstatus => sub { # hier den Regex für den Verbindungsstatus pflegen; stattdessen im HTML Baum mit HTML::TreeBuilder rum zu suchen bietet sich hier nicht an my ($content,$data) = @_; # für FRITZ!OS 6.30: if ($$content =~ m~
Internet, IPv4
 
verbunden seit (\d+).(\d+).(\d+), (\d+):(\d+) Uhr, ([^<]+),
IP-Adresse: (\d+).(\d+).(\d+).(\d+)
~) { $data->{datum} = "$1.$2.$3"; $data->{zeit} = "$4:$5"; $data->{provider} = $6; $data->{ip4} = "$7.$8.$9.$10"; $data->{found} = defined; } }, # ----- ab hier nichts mehr ändern ----- fritzbox_hex_md5 => sub { # Dank an Jan! https://www.perl-community.de/bat/poard/thread/19845#ms_182588 my ($string) = @_; my @chars = split(//,$$string); require Digest::MD5; return Digest::MD5::md5_hex(join("\0",@chars)."\0"); }, makeDots => sub { my ($str) = @_; my $newStr = ''; my $len = length($str); for (my $i = 0; $i < $len; $i ++) { my $char = substr($str,$i,1); $newStr .= (ord($char) > 255 ? '.' : $char); } return $newStr; }, write_debug_file => sub { my ($k,$mech,$nr) = @_; if (defined $k->{debug}) { $$nr ++; $$mech->save_content( sprintf($k->{debug},$$nr), binmode => ':raw', decoded_by_headers => 1, ); } }, ); my ($env) = @_; my ($back,$content,$g_challenge); my @jetzt = localtime(time()); return '' if $jetzt[2] != $job_konfig{ausfuehrung_um}; require WWW::Mechanize; my $mech = WWW::Mechanize->new( agent => 'Automat', # ist der FRITZ!Box egal quiet => 1, # prophylaktisch onwarn => \&{ # sollte definiert werden sonst schreibt es bei einer Warnung trotz quiet=>1 auf STDERR sub { $back = "Warnung '".join('',@_)."'"; goto FEHLER_UND_RUECKSPRUNG_ZUM_HAUPTPROGRAMM; } }, onerror => \&{ # sollte definiert werden sonst schreibt es bei einem Fehler trotz quiet=>1 auf STDERR sub { $back = "Fehler '".join('',@_)."'"; goto FEHLER_UND_RUECKSPRUNG_ZUM_HAUPTPROGRAMM; } }, stack_depth => 0, # die history Tabelle wird nicht gebraucht, Speicher schonen ); my $debug_nr = 0; do {{ # sonst werden die Einrückungen im Code zu breit my $dest_url = "$job_konfig{url}/login.lua"; # Login Formular aufrufen $mech->get($dest_url); $content = $mech->content(); if (!defined $content || $content eq '') { $back = "Abruf der Ressource '$dest_url' gescheitert"; last; } $job_konfig{write_debug_file}->(\%job_konfig,\$mech,\$debug_nr); if ($content =~ /g_challenge( ?)=( ?)"([^"]{8})"/) { $g_challenge = $3; } else { $back = "Finde die Variablendefinition zu >g_challenge< nicht"; last; } my $form_id = 'uiMainForm'; my $formular = $mech->form_id($form_id); # selektiert auch das default Formular obwohl es in der Doku anders steht if (!defined $formular) { $back = "Finde das Formular mit der ID '$form_id' nicht"; last; } my $temp = $g_challenge.'-'.$job_konfig{makeDots}->($job_konfig{pass}); my $var_response = $g_challenge.'-'.$job_konfig{fritzbox_hex_md5}->(\$temp); my $feldname = 'response'; $mech->field($feldname,$var_response,1); # field() liefert leider kein def/undef zurück # Login Formular absenden if (!$mech->submit()) { $back = "Das Formular mit der ID '$form_id' konnte nicht abgesendet werden"; last; } $content = $mech->content(); if (!defined $content || $content eq '') { $back = "Nach Absenden des Formulars mit der ID '$form_id' wird nichts mehr geliefert"; last; } $job_konfig{write_debug_file}->(\%job_konfig,\$mech,\$debug_nr); my $link_zu_dsl; if ($content =~ /\/internet\/inetstat_monitor\.lua\?sid\=([0-9a-z]+)/) { # nutze kein $mech->follow_link(), weil der Link unten nochmal gebraucht wird $link_zu_dsl = "$job_konfig{url}/internet/inetstat_monitor.lua?sid=$1"; } else { $back = "Finde den Menüpunkt 'Internet' nicht"; last; } # Menüpunkt 'Internet' anklicken $mech->get($link_zu_dsl); $content = $mech->content(); if (!defined $content || $content eq '') { $back = "FRITZ!Box liefert im Menüpunkt 'Internet' nichts zurück"; last; } $job_konfig{write_debug_file}->(\%job_konfig,\$mech,\$debug_nr); my %data; $job_konfig{verbindungsstatus}->(\$content,\%{$data{alt}}); if (!defined $data{alt}{found}) { $back = "Im Menüpunkt 'Online-Monitor' konnte der Verbindungsstatus für 'Internet, IPv4' nicht gefunden werden"; last; } if ($data{alt}{datum} eq sprintf('%02d.%02d.%04d',$jetzt[3],$jetzt[4]+1,$jetzt[5]+1900)) { return ''; } if ($content =~ /internet\/inetstat_monitor\.lua\?sid=([0-9a-z]+)&useajax=1&action=disconnect/) { $dest_url = "$job_konfig{url}/internet/inetstat_monitor.lua?sid=$1&useajax=1&action=disconnect"; } else { $back = "Im Menüpunkt 'Online-Monitor' konnte der Schalter 'Neu verbinden' nicht gefunden werden"; last; } # Schalter 'Neu verbinden anklicken' my $time_reconnect = time(); $mech->get($link_zu_dsl.'&useajax=1&action=disconnect'); $content = $mech->content(); if (!defined $content || $content eq '') { $back = "Die FRTIZ!Box liefert auf 'Neu verbinden' nichts zurück"; last; } $job_konfig{write_debug_file}->(\%job_konfig,\$mech,\$debug_nr); if ($content ne $job_konfig{response_reconnect}) { $back = "Der Aufruf 'Neu verbinden' liefert nach disconnect nicht '$job_konfig{response_reconnect}' zurück sondern '$content'"; last; } # wieder Menüpunkt 'Internet' aufrufen my $start = time(); while( # als Schleife, weil das Connect unterschiedlich lange dauert ( !exists $data{neu} || "$data{alt}{datum}$data{alt}{zeit}" eq "$data{neu}{datum}$data{neu}{zeit}" ) && time() - $start < $job_konfig{wait_nach_reconnect} ) { sleep(2); $mech->get($link_zu_dsl); $content = $mech->content(); if (!defined $content || $content eq '') { $back = "FRITZ!Box liefert im Menüpunkt 'Internet' beim zweiten Aufruf nichts zurück"; last; } $job_konfig{write_debug_file}->(\%job_konfig,\$mech,\$debug_nr); delete $data{neu} if exists $data{neu}; $job_konfig{verbindungsstatus}->(\$content,\%{$data{neu}}); if (!defined $data{neu}{found}) { $back = "Nach dem Reconnect konnte im Menüpunkt 'Online-Monitor' der Verbindungsstatus für 'Internet, IPv4' nicht gefunden werden"; last; } } last if defined $back; if ("$data{alt}{datum}$data{alt}{zeit}" eq "$data{neu}{datum}$data{neu}{zeit}") { my @t = localtime($time_reconnect); $back = "Etwas muss schief gelaufen sein, trotz Reconnect am " .sprintf("%02d.%02d.%04d um %02d:%02d Uhr",@t[3..5],$t[2],$t[1]) ." und einer Wartezeit von " .(time() - $start) ." Sekunde(n) werden keine neuen Verbindungsdaten ausgegeben, nur die alte IP Adresse $data{alt}{ip4} von $data{alt}{datum} um $data{alt}{zeit} Uhr konnte ermittelt werden" ; last; } my $msg = "|Job erledigt, bisherige IP Adresse seit $data{alt}{datum} um $data{alt}{zeit} Uhr lautete" ." $data{alt}{ip4}, neue IP Adresse seit $data{neu}{datum} um $data{neu}{zeit} Uhr lautet" ." $data{neu}{ip4}" ; main::systemlog_eintrag(0,1,20,$msg); return $msg; }} while(0); FEHLER_UND_RUECKSPRUNG_ZUM_HAUPTPROGRAMM: error($back,87); return "Fehler aufgetreten siehe '$env->{cron_errorlog}'"; }