I have written a script which takes a "snapshot" of sorts of the permissions of a specified directory (or file) and spits it into a configuration file Apache-style. You can then use the script to test the permissions later on against the previous "snapshot." You can also use it to change the current permissions back to how they were in the snapshot.

My main drive for this was so that I would put something like /etc into Subversion and then do "svn update"s on other machines to maintain consistency across sytems' /etc's. But there are a number of files which have to have very specific permissions so that they work correctly, but Subversion does not keep permissions info, so I wrote this script "permmy."

This is one of my first large-ish scripts and would like any constructive criticism I could get on it regarding errors and streamlining, especially errors (screwing up permissons on /etc can be a BadThing(tm)...). If you are interested check out the readmore...

#!/usr/bin/perl -w ### LAME BUG ALERT - program will barf if there ### are repeated elements in the conf file ### so be careful you don't "add" the same directory ### to a conf file that already has it in it use strict; use diagnostics; use File::Find (); use Getopt::Std; #use Sys::Syslog; use Config::General; die "Usage: permmy -[itcad] [directory] -f config-file\n" unless @ARGV >= 3; use vars qw/ $opt_t $opt_c $opt_d $opt_i $opt_f $opt_a/; getopts('tcdi:f:a:') or die "Usable options are -tcifa: $!\n"; my $dir_tree = $opt_i; my $add_tree = $opt_a; my $perms_config_file = $opt_f; use vars qw/ $conf %config/; if ($opt_t || $opt_d || $opt_c) { $conf = new Config::General( -ConfigFile => "$perms_config_file", -UseApacheInclude => "yes", -InterPolateVars => "yes" ); %config = $conf->getall; } sub return_mode { sprintf "%04o", (shift) & 07777; } ## checks for validity. Takes a type argument (m for mode, u for user ## and g for group) and the test value sub validator ($$) { my($type, $value) = @_; if ($type =~ /.*mode/) { die "$value is not a valid mode: $!\n" unless ((oct($value)) & +& (oct($value) <= 4095)); } elsif ($type =~ /.*owner/) { die "$value is not a valid user: $!\n" unless ((getpwnam($valu +e)) || ($value eq "root")); } elsif ($type =~ /.*group/) { if (($value eq "root") && ($^O =~ /bsd|darwin/)) {print "There + is no $value group on $^O, fix your config file!\n";} if (($value eq "wheel") && ($^O =~ /linux/)) {print "There is +no $value group in $^O, fix your config file!\n";} if (($value eq "root") || ($value eq "wheel")) {$value = getgr +gid(0);} die "$value is not a valid group: $!\n" unless ((getgrnam($val +ue)) || (($value eq "root") || ($value eq "wheel"))); } else { die "Validator complaint: something wrong with type $type and/ +or value $value\n"; } } sub configurator ($$$$) { my($conf_type, $conf_file, $directory, $wanted_ref) = @_; if ($conf_type eq "i") { open CONFIG, ">$conf_file" or die "Could not open config file $conf_file for writing: + $!\n"; } elsif ($conf_type eq "a") { open CONFIG, ">>$conf_file" or die "Could not open config file $conf_file for writing: + $!\n"; } use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; *prune = *File::Find::prune; # Traverse desired filesystems File::Find::find({wanted => $wanted_ref}, $directory); exit; close CONFIG or die "Could not close config file $conf_file: $!\n" +; } sub configtest { foreach my $pri ( keys %config ) { die "$pri is not a valid descriptor: $!\n" unless (($pri eq "d +ir") || ($pri eq "file") || ($pri eq "link") || ($pri eq "area")); if ($pri eq "area") { foreach my $sec ( keys %{ $config{$pri} } ) { die "$sec does not exist: $!\n" unless (-e $sec); foreach my $ter (keys %{ $config{$pri}{$sec} } ) { die "$ter is not a valid type: $!\n" unless (($ter + eq "dir-mode") || ($ter eq "dir-owner") || ($ter eq "dir-group") || +($ter eq "file-mode") || ($ter eq "file-owner") || ($ter eq "file-gro +up") || ($ter eq "link-mode") || ($ter eq "link-owner") || ($ter eq " +link-group")); &validator($ter, $config{$pri}{$sec}{$ter}); } } } else { foreach my $sec (keys %{ $config{$pri} } ) { die "$sec does not exist: $!\n" unless (-e $sec); foreach my $ter (keys %{ $config{$pri}{$sec} } ) { die "$ter is not a valid type: $!\n" unless (($ter + eq "mode") || ($ter eq "owner") || ($ter eq "group")); &validator($ter, $config{$pri}{$sec}{$ter}); } } } } print "Syntax OK\n"; } sub wanted_ia { ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($_)); my $pwuid = getpwuid $uid; my $grgid = getgrgid $gid; my $nice_mode = &return_mode($mode); if (-d $_) { print CONFIG "<dir $name>\n\tmode $nice_mode\n\towner $pwuid\n +\tgroup $grgid\n</dir>\n"; } elsif (-f $_) { print CONFIG "<file $name>\n\tmode $nice_mode\n\towner $pwuid\ +n\tgroup $grgid\n</file>\n"; } elsif (-l $_) { print CONFIG "<link $name>\n\tmode $nice_mode\n\towner $pwuid\ +n\tgroup $grgid\n</link>\n"; } } sub change_mode ($$) { my($the_file, $change_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_nice_mode = &return_mode($mode); if (($change_value) && (oct($change_value) != oct($c_nice_mode))) { my $o_mode = oct($change_value); chmod($o_mode, $the_file) or warn "Could not change mode of $t +he_file: $!\n"; print "Changed mode of $the_file from $c_nice_mode to $change_ +value\n"; } } sub change_owner ($$) { my($the_file, $change_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_pwuid = getpwuid($uid); if (($change_value) && ($change_value ne $c_pwuid)) { my $num_uid = getpwnam($change_value); chown($num_uid, -1, $the_file) or warn "Could not change owner + of $the_file: $!\n"; print "Changed owner of $the_file from $c_pwuid to $change_val +ue\n"; } } sub change_group ($$) { my($the_file, $change_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_grgid = getgrgid($gid); if (($change_value) && ($change_value ne $c_grgid)) { if (($change_value eq "root") || ($change_value eq "wheel")) { +$change_value = getgrgid(0)} my $num_gid = getgrnam($change_value); chown(-1, $num_gid, $the_file) or warn "Could not change group + of $the_file: $!\n"; print "Changed group of $the_file from $c_grgid to $change_val +ue\n"; } } sub test_mode ($$) { my($the_file, $test_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_nice_mode = return_mode($mode); print "The file $the_file has a mode of $c_nice_mode but should ha +ve a mode of $test_value\n" unless ($c_nice_mode eq $test_value); } sub test_owner ($$) { my($the_file, $test_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_pwuid = getpwuid($uid); print "The file $the_file has an owner of $c_pwuid but should have + an owner of $test_value\n" unless ($c_pwuid eq $test_value); } sub test_group ($$) { my($the_file, $test_value) = @_; ((my $dev, my $ino, my $mode, my $nlink, my $uid, my $gid) = lstat +($the_file)); my $c_grgid = getgrgid($gid); print "The file $the_file has a group of $c_grgid but should have +a group of $test_value\n" unless ($c_grgid eq $test_value); } if ($opt_d) { configtest(); } elsif ($opt_i) { configurator("i", $perms_config_file, $dir_tree, \&wanted_ia); } elsif ($opt_a) { configurator("a", $perms_config_file, $add_tree, \&wanted_ia); } elsif ($opt_t) { configtest(); foreach my $test ( keys %config ) { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } foreach my $testy ( keys %{ $config{$test}{$tester} } ) { if ($testy eq "mode") { test_mode($tester, $config{$test}{$tester}{$testy} +); } elsif ($testy eq "owner") { test_owner($tester, $config{$test}{$tester}{$testy +}); } elsif ($testy eq "group") { test_group($tester, $config{$test}{$tester}{$testy +}); } } } } } elsif ($opt_c) { configtest(); foreach my $test ( keys %config ) { if ($test eq "area") { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } use vars qw/$d_mode $f_mode $l_mode $d_own $f_own $l_o +wn $d_grp $f_grp $l_grp/; $d_mode=$f_mode=$l_mode=$d_own=$f_own=$l_own=$d_grp=$f +_grp=$l_grp=0; foreach my $testy ( keys %{ $config{$test}{$tester} } +) { if ($testy eq "dir-mode") { $d_mode = $config{$tes +t}{$tester}{$testy} } elsif ($testy eq "file-mode") { $f_mode = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "link-mode") { $l_mode = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "dir-owner") { $d_own = $config{$ +test}{$tester}{$testy} } elsif ($testy eq "file-owner") { $f_own = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "link-owner") { $l_own = $config{ +$test}{$tester}{$testy} } elsif ($testy eq "dir-group") { $d_grp = $config{$ +test}{$tester}{$testy} } elsif ($testy eq "file-group") { $f_grp = $config{ +$test}{$te\ster}{$testy} } elsif ($testy eq "link-group") { $l_grp = $config{ +$test}{$tester}{$testy} } } use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; *prune = *File::Find::prune; # Traverse desired filesystems File::Find::find({wanted => \&wanted_c}, $tester); exit; sub wanted_c { if ((-d $_) && ($d_mode)) { change_mode($_, $d_mode); } elsif ((-d $_) && ($d_own)) { change_owner($_, $d_own); } elsif ((-d $_) && ($d_grp)) { change_group($_, $d_grp); } elsif ((-f $_) && ($f_mode)) { change_mode($_, $f_mode); } elsif ((-f $_) && ($f_own)) { change_owner($_, $f_own); } elsif ((-f $_) && ($f_grp)) { change_group($_, $f_grp); } elsif ((-l $_) && ($l_mode)) { change_mode($_, $l_mode); } elsif ((-l $_) && ($l_own)) { change_owner($_, $l_own); } elsif ((-l $_) && ($l_grp)) { change_group($_, $l_grp); } } } } else { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } foreach my $testy2 ( keys %{ $config{$test}{$tester} } + ) elsif ((-l $_) && ($l_mode)) { change_mode($_, $l_mode); } elsif ((-l $_) && ($l_own)) { change_owner($_, $l_own); } elsif ((-l $_) && ($l_grp)) { change_group($_, $l_grp); } } } } else { foreach my $tester ( keys %{ $config{$test} } ) { if (!-e $tester) { print "$tester does not seem to exist any more\n"; next; } foreach my $testy2 ( keys %{ $config{$test}{$tester} } + ) { if ($testy2 eq "mode") { change_mode($tester, $config{$test}{$tester}{$ +testy2}); } elsif ($testy2 eq "owner") { change_owner($tester, $config{$test}{$tester}{ +$testy2}); } elsif ($testy2 eq "group") { change_group($tester, $config{$test}{$tester}{ +$testy2}); } } } } } }

Below is a sample config file, showing all the options:

<area /etc> dir-perms 0755 file-perms 0644 link-perms 0777 dir-owner root file-owner root link-owner root dir-group root file-group root link-group root </area> <dir /etc/foo> perms 0700 owner shane group shane </dir> <file /etc/bar.cfg> perms 0744 owner joe group baz </file> <link /etc/baz> perms 0777 owner follow group me </link>

The one kinda "weird" part is the "area" tag - this basically just means to give default permissions (recursively) to the directory and its contents.

Thanks for any help.


In reply to Permissions Utility Script for sysadmins - seeking comments by BuddhaNature

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.