# # Monitorix - A lightweight system monitoring tool. # # Copyright (C) 2005-2014 by Jordi Sanfeliu # # This program is 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 # (at your option) any later version. # # This program 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. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # package HTTPServer; use strict; use warnings; use Monitorix qw(trim); use POSIX qw(strftime); use HTTP::Server::Simple::CGI; use base qw(HTTP::Server::Simple::CGI); use MIME::Base64 qw(decode_base64); use Socket; sub logger { my ($url, $type) = @_; if(open(OUT, ">> $main::config{httpd_builtin}->{log_file}")) { if($type eq "OK") { print OUT localtime() . " - $type - [$ENV{REMOTE_ADDR}] \"$ENV{REQUEST_METHOD} $url - $ENV{HTTP_USER_AGENT}\"\n"; } elsif($type eq "NOTEXIST") { print OUT localtime() . " - $type - [$ENV{REMOTE_ADDR}] File does not exist: $url\n"; } elsif($type eq "AUTHERR") { print OUT localtime() . " - $type - [$ENV{REMOTE_ADDR}] Authentication error: $url\n"; } elsif($type eq "NOTALLOWED") { print OUT localtime() . " - $type - [$ENV{REMOTE_ADDR}] Access not allowed: $url\n"; } else { print OUT localtime() . " - $type - [$ENV{REMOTE_ADDR}] $url\n"; } close(OUT); } else { print STDERR localtime() . " - ERROR: unable to open logfile '$main::config{httpd_builtin}->{log_file}'.\n"; } } sub check_passwd { my ($user, $pass) = @_; if(open(IN, $main::config{httpd_builtin}->{auth}->{htpasswd})) { while() { my %pair = split(':', $_); if($pair{$user || ""}) { chomp($pair{$user}); if(crypt($pass, $pair{$user}) ne $pair{$user}) { next; } return 0; } } close(IN); } else { print STDERR localtime() . " - ERROR: can't open file '$main::config{httpd_builtin}->{auth}->{htpasswd}'.\n"; } return 1; } sub ip_validity { my ($myip, $hosts) = @_; my $valid = 0; foreach my $address (split(',', $hosts)) { my $myip_bin = inet_aton($myip); $address = "0.0.0.0/0" if $address eq "all"; my ($ip, $netmask) = split('/', trim($address) . "/"); my $ip_bin = inet_aton($ip); $netmask = "255.255.255.255" if $netmask eq ""; $netmask = unpack("%32b*", inet_aton($netmask)) if length($netmask) > 2; my $netmask_bin = ~pack("N", (2**(32-$netmask))-1); my $first_valid = unpack("N", $ip_bin & $netmask_bin) + ($netmask eq "32" ? 0 : 1); my $last_valid = unpack("N", $ip_bin | ~$netmask_bin) - ($netmask eq "32" ? 0 : 1); $myip_bin = unpack("N", $myip_bin); if($myip_bin >= $first_valid && $myip_bin <= $last_valid) { $valid++; } } return $valid; } sub http_header { my ($code, $mimetype) = @_; my $msg = $main::config{httpd_builtin}->{auth}->{msg} || ""; if($code eq "200") { print "HTTP/1.0 200 OK\r\n"; } elsif($code eq "401") { my (undef, $encoded_str) = split(' ', $ENV{HTTP_AUTHORIZATION} || ""); my ($user, $pass) = split(':', decode_base64($encoded_str || ":")); if(check_passwd($user, $pass)) { print "HTTP/1.0 401 Access Denied\r\n"; print "WWW-Authenticate: Basic realm=\"$msg\"\r\n"; print "Content-Length: 0\r\n"; print "\r\n"; return 1; } return 0; } elsif($code eq "404") { print "HTTP/1.0 404 Not found\r\n"; } else { print "HTTP/1.0 403 Forbidden\r\n"; } print "Date: " . strftime("%a, %d %b %Y %H:%M:%S %z", localtime) . "\r\n"; print "Server: Monitorix HTTP Server\r\n"; print "Connection: close\r\n"; if($mimetype =~ m/(html|cgi)/) { print "Content-Type: text/html; charset=UTF-8\r\n"; } else { print "Content-Type: image/$mimetype;\r\n"; } print "\r\n"; return 0; } sub handle_request { my ($self, $cgi) = @_; my $base_url = $main::config{base_url}; my $base_cgi = $main::config{base_cgi}; my $host = $main::config{httpd_builtin}->{host} || "localhost"; my $port = $main::config{httpd_builtin}->{port} || "8080"; my $hosts_deny = $main::config{httpd_builtin}->{hosts_deny} || ""; my $hosts_allow = $main::config{httpd_builtin}->{hosts_allow} || ""; my $auth = lc($main::config{httpd_builtin}->{auth}->{enabled}); my $mimetype; my $target; my $target_cgi; my @data; my $OK_CHARS='-a-zA-Z0-9_./'; # a restrictive list of valid chars return if fork(); # parent returns my $url = $cgi->path_info(); my $url_disarmed = $url; # this should disarm all XSS and Cookie Injection attempts $url_disarmed =~ s/\&/&/g; $url_disarmed =~ s/\/>/g; $url_disarmed =~ s/\"/"/g; $url_disarmed =~ s/\'/'/g; $url_disarmed =~ s/\(/(/g; $url_disarmed =~ s/\)/)/g; $url_disarmed =~ s/\////g; $0 = "monitorix-httpd"; # change process' name # check if the IP address is allowed to connect my $denied; my $allowed = ip_validity($ENV{REMOTE_ADDR}, $hosts_allow); $denied = ip_validity($ENV{REMOTE_ADDR}, $hosts_deny) if !$allowed; if(!$allowed && $denied) { http_header("403", "html"); print "\r\n"; print "\r\n"; print "403 Forbidden\r\n"; print "\r\n"; print "

Forbidden

\r\n"; print "

You don't have permission to access $url_disarmed\r\n"; print "on this server.

\r\n"; print "
\r\n"; print "
Monitorix HTTP Server listening at $host Port $port
\r\n"; print "\r\n"; logger($url, "NOTALLOWED"); exit(0); } # sanitizes the $target $target = $url; while() { my $cur = length($target); $target =~ s/\.\.\///; $target =~ s/^\///; $target =~ s/\/$//; last unless $cur ne length($target); } $target = $target_cgi = "/$target"; $target =~ s/^$base_url//; # removes the 'base_url' part $target_cgi =~ s/^$base_cgi//; # removes the 'base_cgi' part if(!$target || $target eq $base_url) { $target = "index.html" unless $target; } ($mimetype) = ($target =~ m/.*\.(html|cgi|png)$/); $target =~ s/^\/*//; # removes leading slashes $target_cgi =~ s/^\/*//; # removes leading slashes $target =~ s/[^$OK_CHARS]/_/go; # only $OK_CHARS are allowed $target_cgi =~ s/[^$OK_CHARS]/_/go; # only $OK_CHARS are allowed if($target_cgi eq "monitorix.cgi") { chdir("cgi"); open(EXEC, "./$target_cgi |"); @data = ; close(EXEC); } elsif($target) { if(open(IN, $target)) { @data = ; close(IN); } } if(scalar(@data)) { if($auth eq "y") { if(http_header("401", $mimetype)) { print "\r\n"; print "\r\n"; print "401 Authorization Required\r\n"; print "\r\n"; print "

Authorization Required

\r\n"; print "

This server could not verify that you\r\n"; print "are authorized to access the document\r\n"; print "requested. Either you supplied the wrong\r\n"; print "credentials (e.g., bad password), or your\r\n"; print "browser doesn't understand how to supply\r\n"; print "the credentials required.

\r\n"; print "\r\n"; logger($url, "AUTHERR"); exit(0); } } http_header("200", $mimetype); foreach(@data) { print $_; } logger($url, "OK"); } else { http_header("404", "html"); print "\r\n"; print "\r\n"; print "404 Not Found\r\n"; print "\r\n"; print "

Not Found

\r\n"; print "The requested URL $url_disarmed was not found on this server.

\r\n"; print "


\r\n"; print "
Monitorix HTTP Server listening at $host Port $port
\r\n"; print "\r\n"; logger($url, "NOTEXIST"); } exit(0); } 1;