Sudoers probe to read /etc/sudoers files
Robert 'phaylon' Sedlacek [Mon, 7 May 2012 19:34:08 +0000 (19:34 +0000)]
lib/System/Introspector/Sudoers.pm [new file with mode: 0644]
t/sudoers.t [new file with mode: 0644]

diff --git a/lib/System/Introspector/Sudoers.pm b/lib/System/Introspector/Sudoers.pm
new file mode 100644 (file)
index 0000000..d6bfca6
--- /dev/null
@@ -0,0 +1,60 @@
+package System::Introspector::Sudoers;
+use Moo;
+
+has sudoers_file => (
+    is      => 'ro',
+    default => sub { '/etc/sudoers' },
+);
+
+has hostname => (
+    is      => 'ro',
+    default => sub { scalar `hostname` },
+);
+
+sub gather {
+    my ($self) = @_;
+    my %file = $self->_gather_files($self->sudoers_file);
+    return \%file;
+}
+
+sub _gather_files {
+    my ($self, $file) = @_;
+    open my $fh, '<', $file
+        or return $file => { error => "Unable to read: $!" };
+    my @lines = <$fh>;
+    my %file = ($file => { body => join '', @lines });
+    for my $line (@lines) {
+        chomp $line;
+        if ($line =~ m{^#include\s+(.+)$}) {
+            my $inc_file = $self->_insert_hostname($1);
+            %file = (%file, $self->_gather_files($inc_file));
+        }
+        elsif ($line =~ m{^#includedir\s+(.+)$}) {
+            my $inc_dir = $self->_insert_hostname($1);
+            %file = (%file, $self->_gather_from_dir($inc_dir));
+        }
+    }
+    return %file;
+}
+
+sub _gather_from_dir {
+    my ($self, $dir) = @_;
+    opendir(my $dh, $dir);
+    return $dir => { error => "Unable to read dir $dir: $!" }
+        unless $dh;
+    my %file;
+    while (my $file = readdir $dh) {
+        next if $file =~ m{\.} or $file =~ m{~$};
+        %file = (%file, $self->_gather_files("$dir/$file"));
+    }
+    return %file;
+}
+
+sub _insert_hostname {
+    my ($self, $value) = @_;
+    my $hostname = $self->hostname;
+    $value =~ s{\%h}{$hostname}g;
+    return $value;
+}
+
+1;
diff --git a/t/sudoers.t b/t/sudoers.t
new file mode 100644 (file)
index 0000000..8420e07
--- /dev/null
@@ -0,0 +1,51 @@
+use strictures 1;
+use Test::More;
+use FindBin;
+
+use System::Introspector::Sudoers;
+
+my $dir = "$FindBin::Bin/sudoers-data";
+
+system("mkdir $dir");
+system("mkdir $dir/bar_host");
+
+my $start = write_file('sudoers',
+    'foo bar',
+    'baz qux',
+    "#include $dir/foo_%h",
+    "#includedir $dir/bar_%h",
+);
+
+my $foo_file = write_file('foo_host',
+    'in foo',
+);
+my $bar_file = write_file("bar_host/baz",
+    'in bar file',
+);
+
+my $probe = System::Introspector::Sudoers->new(
+    sudoers_file => $start,
+    hostname     => 'host',
+);
+
+ok((my $data = $probe->gather), 'received data');
+
+my $inc = "#include $dir/foo_\%h\n#includedir $dir/bar_\%h\n";
+is_deeply $data, {
+    $start      => { body => "foo bar\nbaz qux\n$inc" },
+    $foo_file   => { body => "in foo\n" },
+    $bar_file   => { body => "in bar file\n" },
+}, 'found files';
+
+system("rm $_") for $start, $foo_file, $bar_file;
+system("rmdir $dir/bar_host");
+system("rmdir $dir");
+done_testing;
+
+sub write_file {
+    my ($file, @lines) = @_;
+    my $path = "$FindBin::Bin/sudoers-data/$file";
+    open my $fh, '>', $path or die "Unable to write $path: $!\n";
+    print $fh map "$_\n", @lines;
+    return $path;
+}