From ba4f134ea4686469573c72c0099448ed7d50b579 Mon Sep 17 00:00:00 2001
From: Sergey Ivanov <seriv@cs.umd.edu>
Date: Wed, 23 May 2012 12:29:48 -0400
Subject: [PATCH] support for HOSTS {ALLOW|DENY} lists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Hosts allow-deny lists are the coma or blank separated lists of
a dotted decimal IPv4 addresses of the form a.b.c.d. to match incoming machine’s IP address exactly,
or an 'ipaddr/n' where ipaddr is the IP address and n is the number of one bits in the netmask.
The first to occur match gives the result, skipping other rules.
If nothing matches IP is allowed.
---
 lib/Resmon/Config.pm | 24 ++++++++++++++++++++++++
 lib/Resmon/Status.pm | 21 +++++++++++++++++++++
 resmon               |  2 +-
 resmon.conf.sample   |  9 +++++++++
 4 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/lib/Resmon/Config.pm b/lib/Resmon/Config.pm
index 7416038..0731d92 100755
--- a/lib/Resmon/Config.pm
+++ b/lib/Resmon/Config.pm
@@ -5,6 +5,22 @@ use warnings;
 
 use Sys::Hostname;
 
+
+sub split_ip_list {
+# this code is taken from  Marcel Gruenauer's <marcel@cpan.org> CPAN module Net::IP::Match
+    my $string = shift; 
+    my $allow = shift;
+    my (@result,$quad,$bits,$matchbits,$int,$mask);
+    for (split (/\s*[,\s]\s*/, $string)) {
+       ($quad, $bits) = m!^(\d+\.\d+\.\d+\.\d+)(?:/(\d+))?!g;
+       $bits = 32 if ($bits eq '');
+       $matchbits = 32 - $bits;
+       $int = unpack("N", pack("C4", split(/\./,$quad)));
+       $mask = $int >> $matchbits;
+       push @result => {mask => $mask, bits => $matchbits, allow => $allow};
+    }
+    return \@result;
+}
 sub new {
     my $class = shift;
     my $filename = shift;
@@ -130,6 +146,14 @@ sub new {
             elsif(/\s*AUTHPASS\s+(\S+)\s*;\s*/) {
                 $self->{authpass} = $1;
                 next;
+            }
+            elsif(/\s*HOSTS\s+ALLOW\s+([^;]+)\s*;\s*/) {
+                push (@{$self->{hostsallow}}, @{split_ip_list($1,1)});
+                next;
+            }
+            elsif(/\s*HOSTS\s+DENY\s+([^;]+)\s*;\s*/) {
+                push (@{$self->{hostsallow}}, @{split_ip_list($1,0)});
+                next;
             } elsif(/\s*INCLUDE\s+(\S+)\s*;\s*/) {
                 my $incglob = $1;
 
diff --git a/lib/Resmon/Status.pm b/lib/Resmon/Status.pm
index 7755c8f..19b6c20 100644
--- a/lib/Resmon/Status.pm
+++ b/lib/Resmon/Status.pm
@@ -441,6 +441,8 @@ sub serve_http_on {
     my $port = shift;
     $self->{authuser} = shift;
     $self->{authpass} = shift;
+    my $hostsallow = shift;
+
     if(!defined($ip) || $ip eq '' || $ip eq '*') {
         $ip = INADDR_ANY;
     } else {
@@ -469,6 +471,25 @@ sub serve_http_on {
             while(1) {
                 my $client = $handle->accept;
                 next unless $client;
+                my $hersockaddr    = getpeername($client);
+                my ($port, $iaddr) = sockaddr_in($hersockaddr);
+                my $denied;
+                 for my $el (@{$hostsallow}) {
+                  my $tmp = unpack("N",$iaddr);
+                  $tmp = $tmp >> $el->{bits} if $el->{bits};
+                  if ($tmp == $el->{mask}) {
+                    $denied = !$el->{allow};
+                    last;
+                  }
+                }
+                if ($denied) {
+                  my $response = "<html><head><title>IP denied</title></head>" .
+                  "<body><h1>IP denied</h1></body></html>";
+                  $client->print(http_header(401, length($response), 'text/html', $denied));
+                  $client->print($response . "\r\n");
+                  $client->close();
+                  next
+                };
                 my $req;
                 my $proto;
                 my $close_connection;
diff --git a/resmon b/resmon
index f3fd6fc..fd78b85 100755
--- a/resmon
+++ b/resmon
@@ -84,7 +84,7 @@ my $list = [];
 $status = Resmon::Status->new($config->{statusfile});
 $status->open();
 $status->serve_http_on($config->{interface}, $config->{port},
-        $config->{authuser}, $config->{authpass})
+        $config->{authuser}, $config->{authpass}, $config->{hostsallow})
     if($config->{port});
 
 while(1) {
diff --git a/resmon.conf.sample b/resmon.conf.sample
index f36b848..1319671 100755
--- a/resmon.conf.sample
+++ b/resmon.conf.sample
@@ -3,6 +3,15 @@ PORT 81;
 STATUSFILE /var/run/resmon-status.txt;
 TIMEOUT 10;
 
+HOSTS ALLOW 10.80.116.112, 127.0.0.1;
+# HOSTS {ALLOW/DENY} lists are the coma or blank separated lists of
+# a dotted decimal IPv4 addresses of the form a.b.c.d. to match incoming machine’s IP address exactly,
+# or an 'ipaddr/n' where ipaddr is the IP address and n is the number of one bits in the netmask.
+# the first match gives the result, if nothing matches IP is allowed.
+HOSTS DENY 10.80.117.128/25
+HOSTS ALLOW 10.80.116.0/23
+HOSTS DENY 0.0.0.0/0;
+
 # Resmon health check. Shows the hostname, svn revision and
 # any problems with modules or the configuration file.
 Core::Resmon {
-- 
GitLab