| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- #!/usr/bin/perl -w
- ################################################################
- # $Id:$
- # vim: ts=2:et
- #
- # (c) 2012 Copyright: Martin Fischer (m_fischer at gmx dot de)
- # All rights reserved
- #
- # This script free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # any later version.
- #
- # The GNU General Public License can be found at
- # http://www.gnu.org/copyleft/gpl.html.
- # A copy is found in the textfile GPL.txt and important notices to the license
- # from the author is found in LICENSE.txt distributed with these scripts.
- #
- # This script is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- ################################################################
- use CGI qw(:standard :html3 :header Vars);
- use CGI::Carp qw(warningsToBrowser fatalsToBrowser carpout);
- use CGI::Session;
- use DBI; #requires libdbd-sqlite3-perl
- use File::Copy;
- use LWP::Simple;
- use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
- use lib "./lib";
- use Geo::IP;
- use strict;
- use warnings;
- no warnings 'uninitialized';
- # directory cointains databases
- my $datadir = "./data";
- # geo ip database file from http://www.maxmind.com/download/geoip/database/
- # should be updated once per month
- my $geoIPDat = "$datadir/GeoLiteCity.dat";
- # database
- my $dbf = "$datadir/fhem_statistics_db.sqlite";
- my $dsn = "dbi:SQLite:dbname=$dbf";
- my $sth;
- # requirements for housekeeping;
- my $controlFileURL = "http://fhem.de/fhemupdate4/svn/controls_fhem.txt";
- # fhem node
- my $ua = $ENV{HTTP_USER_AGENT};
- my $ip = $ENV{REMOTE_ADDR};
- # cascading style sheets
- my $css = "http://fhem.de/../css/style.css";
- my $myStyle=<<END;
- ul.menu {
- margin: 0;
- padding: 0;
- }
- ul.menu li {
- list-style: none;
- display: inline;
- margin: 0;
- padding-right: 2px;
- }
- END
- my $dbh = DBI->connect($dsn,"","", { RaiseError => 1, ShowErrorStatement => 1 }) ||
- die "Cannot connect: $DBI::errstr";
- my $cgi = new CGI;
- my $session = new CGI::Session(undef, $cgi, {Directory=>'/tmp'});
- my $cookie = $cgi->cookie(CGISESSID => $session->id );
- &init($cgi,$session);
- if($session->param("~login-trials") >= 3) {
- print $cgi->header(),
- $cgi->start_html(
- -title => 'fhem.de - Statistics Maintainance mode',
- -author => 'm_fischer@gmx.de',
- -base => 'true',
- -style => {-src => $css,-code => $myStyle},
- ),
- $cgi->p("You failed 3 times in a row.<br>" .
- "Your session is blocked. Please contact us with the details of your action"
- ),
- $cgi->end_html;
- exit(0);
- }
- unless($session->param("~logged-in")) {
- print login_page($cgi,$session);
- exit(0);
- }
- &maintainance($cgi,$session);
- exit(0);
- ########################################
- sub login_page {
- my ($cgi,$session) = @_;
- print $cgi->header(-cookie=>$cookie),
- $cgi->start_html(
- -title => 'fhem.de - Statistics Maintainance mode',
- -author => 'm_fischer@gmx.de',
- -base => 'true',
- -style => {-src => $css,-code => $myStyle},
- ),
- $cgi->h3("fhem.de - Statistics Maintainance mode"),
- $cgi->start_form,
- $cgi->hidden(-name=>'_cmd',-value=>$cgi->param('_cmd')),
- $cgi->hidden(-name=>'_act',-value=>$cgi->param('_act')),
- $cgi->strong("<code>Username: </code>"),
- $cgi->textfield(-name=>'username'),br,
- $cgi->strong("<code>Password: </code>"),
- $cgi->password_field(-name=>'password'),br,
- $cgi->submit(-value=>'Login'),
- $cgi->end_form,
- $cgi->end_html;
- }
- ########################################
- sub init($$) {
- my ($cgi,$session) = @_;
- if($session->param("~logged-in")) {
- return 1;
- }
- my $username = $cgi->param("username") or return;
- my $password = $cgi->param("password") or return;
- if(my $profile = authUser($username,$password)) {
- $session->param("~profile", $profile);
- $session->param("~logged-in", 1);
- $session->clear(["~login-trials"]);
- return 1;
- }
- my $trials = $session->param("~login-trials") || 0;
- return $session->param("~login-trials", ++$trials);
- }
- ########################################
- sub authUser($$) {
- my ($username,$password) = @_;
- my %credentials;
- my $fh;
- if(open($fh,"<$datadir/.maintainance.pwd")) {
- while (my $line = <$fh>) {
- chomp $line;
- my ($user,$pass) = split(":",$line);
- $credentials{$user} = $pass;
- }
- close $fh;
- }
- if(exists $credentials{$username} &&
- crypt($password,"Fhem") eq $credentials{$username}) {
- my $p_mask = "x" . length($credentials{$username});
- return {username=>$username, password=>$p_mask};
- }
- return undef;
- }
- ########################################
- sub maintainance($$) {
- my ($cgi,$session) = @_;
- my $url = url(-path_info=>1);
- my $profile = $session->param("~profile");
- my @geo = getLocation($ip);
-
- if($cgi->param("_file")) {
- &cmdDownload($cgi,$session,param("_file"));
- }
- print $cgi->header(),
- $cgi->start_html(
- -title => 'fhem.de - Statistics Maintainance mode',
- -author => 'm_fischer@gmx.de',
- -base => 'true',
- -style => {-src => $css,-code => $myStyle},
- ),
- $cgi->h3("fhem.de - Statistics Maintainance mode");
- print $cgi->p("Welcome $profile->{username} ..."),
- $cgi->p("IP: $ip, countryname:$geo[2] city:$geo[5] lat:$geo[6] lon:$geo[7]"),
- $cgi->ul({-class=>'menu'},
- $cgi->li([
- "<span>[</span>",
- $cgi->a({href=>$url},"home"),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=backup"},"backup"),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=dir"},"dir"),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=housekeeping"},"housekeeping"),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=update"},"update"),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=help"},"help"),
- "<span>|</span>",
- $cgi->a({href=>"http://fhem.de/stats/statistics.cgi",-target=>'_blank'},"view statistics"),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=logout"},"logout"),
- "<span>]</span>",
- ])
- ),
- $cgi->hr;
- my $cmd = $cgi->param("_cmd");
- my $act = $cgi->param("_act");
- if($cmd) {
- my $error;
- my @t = localtime;
- my $timeNow = sprintf("%04d%02d%02d-%02d%02d%02d",$t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
- my $ret;
- if($cmd eq "help") {
- &cmdHelp($cgi,$session);
- } elsif($cmd eq "backup") {
- &cmdBackup($cgi,$session,$act);
- } elsif($cmd eq "dir") {
- &cmdDir($cgi,$session);
- } elsif($cmd eq "housekeeping") {
- &cmdHousekeeping($cgi,$session,$act);
- } elsif($cmd eq "update") {
- &cmdUpdate($cgi,$session,$act);
- } elsif($cmd eq "logout") {
- $session->clear(["~logged-in"]);
- print "<META HTTP-EQUIV=refresh CONTENT=\"1;URL=$url\">\n";
- }
- if($error) {
- print $cgi->p("Error: $error");
- }
- }
- print end_html;
- }
- ########################################
- sub cmdHelp($$) {
- my ($cgi,$session) = @_;
- print $cgi->h4("Help"),
- $cgi->table({-border=>0,-cellpadding=>'5'},
- $cgi->Tr({-align=>'left',-valign=>'top'},
- [
- $cgi->th([
- "command",
- "action",
- "short description"
- ]),
- $cgi->td([
- "<code>help</code>",
- "",
- "<code>show this info.</code>"
- ]),
- $cgi->td([
- "<code>backup</code>",
- "<code>statistics</code>",
- "<code>backup statisitc database with timestamp extension</code>"
- ]),
- $cgi->td([
- "<code>backup</code>",
- "<code>geoip</code>",
- "<code>backup geoip databae with timestamp extension</code>"
- ]),
- $cgi->td([
- "<code>dir</code>",
- "",
- "<code>show content of datadir '$datadir'</code>"
- ]),
- $cgi->td([
- "<code>housekeeping</code>",
- "<code>modules</code>",
- "<code>get controlfile from '$controlFileURL' and remove inofficial modules from table 'modules'</code>"
- ]),
- $cgi->td([
- "<code>update</code>",
- "<code>geoip</code>",
- "<code>get new version of geoip database 'GeoLiteCity.dat', unzip and install it.</code>"
- ]),
- ]
- )
- );
- return undef;
- }
- ########################################
- sub cmdBackup($$$) {
- my ($cgi,$session,$act) = @_;
- my $url = url(-path_info=>1);
- my $timeNow = TimeNow();
- my $error;
- print $cgi->h4("Backup"),
- $cgi->ul({-class=>'menu'},
- $cgi->li([
- "<span>[</span>",
- $cgi->a({href=>$url."?_cmd=backup;_act=statistics"},"statistics database "),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=backup;_act=geoip"},"geoip database "),
- "<span>|</span>",
- $cgi->a({href=>$url."?_cmd=backup;_act=download;_file=$dbf"},"download statistics database "),
- "<span>]</span>",
- ])
- );
- if($act eq "statistics") {
- print $cgi->h5("backup $dbf");
- copy($dbf,$dbf."-".$timeNow) or $error = "Copy failed: $!";
- print $cgi->p("<code>copy $dbf to $dbf-$timeNow done.</code>");
- }
- if($act eq "geoip") {
- print $cgi->h5("backup $geoIPDat");
- copy($geoIPDat,$geoIPDat."-".$timeNow) or $error = "Copy failed: $!";
- print $cgi->p("<code>copy $geoIPDat to $geoIPDat-$timeNow done.</code>");
- }
- if($error) {
- print $cgi->p("Error: $error");
- }
- return undef;
- }
- ########################################
- sub cmdDownload($$$) {
- my ($cgi,$session,$file) = @_;
- my $error;
- my $filename = substr $file,rindex($file,'/')+1;
- open(my $DLFILE,"<$file") or $error = "Open failed: $!";
- print $cgi->header(-type => 'application/x-download',
- -attachment => $filename,
- -Content_length => -s "$file",
- );
-
- binmode $DLFILE;
- print while <$DLFILE>;
- undef ($DLFILE);
- if($error) {
- print $cgi->p("Error: $error");
- }
-
- }
- ########################################
- sub cmdDir($$$) {
- my ($cgi,$session,$act) = @_;
- my $error;
- print $cgi->h4("Content of directory $datadir");
- opendir(my $dh, $datadir) or $error = "Can't opendir $datadir: $!";
- my @dir = grep { !/^\./ && -f "$datadir/$_" } readdir($dh);
- closedir $dh;
- for my $file (sort @dir) {
- print $cgi->code($file),$cgi->br;
- }
- if($error) {
- print $cgi->p("Error: $error");
- }
- return undef;
- }
- ########################################
- sub cmdUpdate($$$) {
- my ($cgi,$session,$act) = @_;
- my $url = url(-path_info=>1);
- my $timeNow = TimeNow();
- my $error;
- print $cgi->h4("Update"),
- $cgi->ul({-class=>'menu'},
- $cgi->li([
- "<span>[</span>",
- $cgi->a({href=>$url."?_cmd=update;_act=geoip"},"GeoLiteCity.dat"),
- "<span>]</span>",
- ])
- );
- if($act eq "geoip") {
- print $cgi->h5("update GeoLiteCity.dat");
- my $url = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz";
- my $infile = "$datadir/GeoLiteCity.dat.gz";
- my $outfile = "$datadir/GeoLiteCity.dat";
- my $data = getstore($url,$infile);
- if($data == "200") {
- copy($geoIPDat,$geoIPDat."-".$timeNow) or $error = "Copy failed: $!";
- print $cgi->p("<code>copy $geoIPDat to $geoIPDat-$timeNow done.</code>");
- gunzip $infile => $outfile or $error = "gunzip failed: $GunzipError";
- print $cgi->p("<code>New $outfile installed.</code>");
- } else {
- $error = "response for $infile: $data";
- }
- }
- if($error) {
- print $cgi->p("Error: $error");
- }
- return undef;
- }
- ########################################
- sub cmdHousekeeping($$$) {
- my ($cgi,$session,$act) = @_;
- my $url = url(-path_info=>1);
- my $error;
- print $cgi->h4("Housekeeping"),
- $cgi->ul({-class=>'menu'},
- $cgi->li([
- "<span>[</span>",
- $cgi->a({href=>$url."?_cmd=housekeeping;_act=modules"},"remove inofficial modules"),
- "<span>]</span>",
- ])
- );
- if($act eq "modules") {
- my $control = get($controlFileURL);
- my $control_ref = {};
- ($error,$control_ref) = parseControlFile("fhem",$control,$control_ref,0);
- print $cgi->h5("Housekeeping for table 'modules'");
- my @ignoreColumns = qw(Global uniqueID);
- my %columnOld = %{ $dbh->column_info(undef, undef, 'modules', undef)->fetchall_hashref('COLUMN_NAME') };
- my %columnNew = %columnOld;
- my $removeColumns;
- foreach my $col (sort keys %columnOld) {
- if(!exists $control_ref->{$col} && !grep {/$col/} @ignoreColumns) {
- delete $columnNew{$col};
- $removeColumns .= "$col ";
- }
- }
- if(!$removeColumns) {
- print $cgi->p("<p><code>inofficial modules found:<br />none</code>");
- } else {
- print $cgi->p("<p><code>inofficial modules found:<br />$removeColumns</code>");
- copy($dbf,$dbf."-".TimeNow()) or $error = "Copy of $dbf failed: $!";
- if(!$error) {
- delete $columnNew{uniqueID};
- my $createTable = "CREATE TABLE modules (uniqueID VARCHAR(32) PRIMARY KEY UNIQUE";
- my $selectColumns = "uniqueID";
- foreach my $col (sort keys %columnNew) {
- $createTable .= ", $col INTEGER DEFAULT 0";
- $selectColumns .= ", $col";
- }
- $createTable .= ");";
- my $sql;
- $sql = "ALTER TABLE 'modules' RENAME TO 'modules_old';";
- print $cgi->p("<code>sql:<br />$sql</code>");
- $dbh->do($sql);
- $sql = $createTable;
- print $cgi->p("<code>sql:<br />$sql</code>");
- $dbh->do($sql);
- $sql = "INSERT INTO 'modules' ($selectColumns) SELECT $selectColumns FROM 'modules_old';";
- print $cgi->p("<code>sql:<br />$sql</code>");
- $dbh->do($sql);
- $sql = "DROP TABLE 'modules_old';";
- print $cgi->p("<code>sql:<br />$sql</code>");
- $dbh->do($sql);
- }
- }
- }
- if($error) {
- print $cgi->p("Error: $error");
- }
- return undef;
- }
- ########################################
- sub parseControlFile($$$$) {
- my ($pack,$controlFile,$control_ref,$local) = @_;
- my %control = %$control_ref if ($control_ref && ref($control_ref) eq "HASH");
- my $from = ($local ? "local" : "remote");
- my $ret;
- if ($local) {
- my $str = "";
- # read local controlfile in string
- if (open FH, "$controlFile") {
- $str = do { local $/; <FH> };
- }
- close(FH);
- $controlFile = $str
- }
- # parse file
- if ($controlFile) {
- foreach my $l (split("[\r\n]", $controlFile)) {
- chomp($l);
- my ($ctrl,$date,$size,$file,$move) = "";
- if ($l =~ m/^(UPD) (20\d\d-\d\d-\d\d_\d\d:\d\d:\d\d) (\d+) (\S+)$/) {
- $ctrl = $1;
- $date = $2;
- $size = $3;
- $file = $4;
- } elsif ($l =~ m/^(DIR) (\S+)$/) {
- $ctrl = $1;
- $file = $2;
- } elsif ($l =~ m/^(MOV) (\S+) (\S+)$/) {
- $ctrl = $1;
- $file = $2;
- $move = $3;
- } elsif ($l =~ m/^(DEL) (\S+)$/) {
- $ctrl = $1;
- $file = $2;
- } else {
- $ctrl = "ESC"
- }
- if ($ctrl eq "ESC") {
- $ret = "File 'controls_".lc($pack).".txt' ($from) is corrupt";
- }
- last if ($ret);
- if ($l =~ m/^UPD/ && $file =~ m/^FHEM/) {
- if ($file =~ m/^.*(\d\d_)(.*).pm$/) {
- my $modName = $2;
- $control{$modName} = $file;
- }
- }
- }
- }
- return ($ret, \%control);
- }
- ########################################
- sub getLocation($) {
- my ($ip) = shift;
- my $gi = Geo::IP->open($geoIPDat, GEOIP_STANDARD);
- my $rec = $gi->record_by_addr($ip);
- if(!$rec) {
- return;
- } else {
- return (
- $rec->country_code,$rec->country_code3,$rec->country_name,$rec->region,$rec->region_name,$rec->city,
- $rec->latitude,$rec->longitude,$rec->time_zone,$rec->continent_code
- );
- }
- }
- ########################################
- sub TimeNow() {
- my @t = localtime;
- return sprintf("%04d%02d%02d-%02d%02d%02d",$t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]);
- }
- 1;
|