File Coverage

blib/lib/File/Find/Rule/Permissions.pm
Criterion Covered Total %
statement 59 62 95.1
branch 48 52 92.3
condition 22 30 73.3
subroutine 10 11 90.9
pod 1 4 25.0
total 140 159 88.0


line stmt bran cond sub pod time code
1             package File::Find::Rule::Permissions;
2 2     2   922 use strict;
  2         3  
  2         54  
3              
4 2     2   863 use Devel::AssertOS::Unix;
  2         52083  
  2         62  
5              
6 2     2   899 use File::Find::Rule;
  2         11297  
  2         12  
7 2     2   79 use base qw(File::Find::Rule);
  2         3  
  2         182  
8 2         160 use vars qw(
9             $VERSION @EXPORT
10             %UIDsByUsername %UsernamesByUID %GIDsByGroupname
11             %GroupnamesByGID %UIDinGID
12 2     2   8 );
  2         2  
13             @EXPORT = @File::Find::Rule::EXPORT;
14             $VERSION = '2.03';
15              
16 2     2   7 use Fcntl qw(:mode);
  2         2  
  2         1413  
17              
18             =head1 NAME
19              
20             File::Find::Rule::Permissions - rule to match on file permissions and user
21             access
22              
23             =head1 SYNOPSIS
24              
25             use File::Find::Rule::Permissions;
26              
27             # Which files can the 'nobody' user read in the current directory?
28             @readable = File::Find::Rule::Permissions->file()
29             ->permissions(isReadable => 1, user => 'nobody')
30             ->in('.');
31            
32             # Which files can UID 42 *not* read in the current directory?
33             @notreadable = File::Find::Rule::Permissions->file()
34             ->permissions(isReadable => 0, user => 42)
35             ->in('.');
36            
37             # Find big insecurity badness!
38             @eek = File::Find::Rule::Permissions->permissions(
39             isWriteable => 1,
40             isExecutable => 1,
41             user => 'nobody'
42             )->in('/web');
43              
44             =head1 DESCRIPTION
45              
46             An extension for File::Find::Rule to work with file permission bits and
47             determine whether a given user can read, write or execute files.
48              
49             =head1 METHODS
50              
51             =head2 B
52              
53             Takes at least one parameter and up to four. The mandatory parameter
54             must be one of isReadable, isWriteable or isExecutable, which take
55             values of 1 or 0 (actually true or false). Any of those three that
56             are missing are ignored - ie, we match regardless of their truth or
57             falsehood. A value of 1 means that we must only match files where
58             the user can read/write/execute (as appropriate) the file, and a
59             value of 0 means we must only match if the user can NOT
60             read/write/execute the file. To supply none of these three is clearly
61             an error, as it is equivalent to not caring what the permissions are,
62             which is equivalent to seeing if the file exists, which
63             File::Find::Rule already does quite nicely thankyouverymuch.
64              
65             The 'user' parameter is optional. By default, we check access for the
66             current effective userid, which is normally the user running the
67             program. This can be changed using this parameter, which takes a
68             numeric uid or a username. Note, however, that if the user running
69             the program can't get at parts of the filesystem that the desired user
70             can, the results will be incomplete.
71              
72             The astute reader will have noticed that File::Find::Rule already
73             handles some of these rules (checking permissions for the effective
74             uid), but not for an arbitrary user. That this module can also check
75             for the effective uid is more of a lucky accident that just falls out
76             of the code when checking for any arbitrary user :-)
77              
78             =head1 BUGS
79              
80             I assume a Unix-a-like system, both when looking at file permissions,
81             and when divining users' membership of groups. Patches for other
82             systems are welcome.
83              
84             We divine which groups a user belongs to when the module is loaded. If
85             group membership changes underneath the program, incorrect results may
86             be returned. I consider this to be Just Fine, given that most shells
87             also have the same limitation.
88              
89             =cut
90              
91             # figure out who has what UID and which UIDs are in which group
92             (%UIDsByUsername, %UsernamesByUID, %GIDsByGroupname,
93             %GroupnamesByGID, %UIDinGID) = ();
94             getusergroupdetails();
95              
96             # we override these in the test suite to avoid having to be root.
97             # or we will do when that bit is written, anyway.
98              
99 13312     13312 1 73749 sub stat { return CORE::stat(shift); }
100 0     0 0 0 sub geteuid { return $>; }
101             sub getusergroupdetails {
102 2     2 0 1232 while(my($name, undef, $uid, $gid) = getpwent()) {
103 40         70 $UIDsByUsername{$name} = $uid;
104 40         70 $UsernamesByUID{$uid} = $name;
105 40         1102 $UIDinGID{$gid}{$uid} = 1;
106             }
107 2         73 while(my($grname, $grpass, $gid, $members) = getgrent()) {
108 88         827 $GIDsByGroupname{$grname} = $gid;
109 88         106 $GroupnamesByGID{$gid} = $grname;
110            
111 88         296 foreach my $member (split(/\s+/, $members)) {
112 2 50       7 next unless(exists($UIDsByUsername{$member}));
113 2         14 $UIDinGID{$gid}{$UIDsByUsername{$member}} = 1;
114             }
115             }
116             }
117              
118             sub File::Find::Rule::permissions {
119 52     52 0 316732 my $self = shift()->_force_object;
120 52 50       451 my %criteria = ref($_[0]) eq "HASH" ? %{$_[0]} : @_;
  0         0  
121              
122             $self->exec(sub {
123 26624     26624   661762 my $file = shift;
124 26624         18445 my $userid;
125            
126             # first check that we've got the mandatory parameters
127 26624 50 66     59313 if(
      66        
128             !exists($criteria{isReadable}) &&
129             !exists($criteria{isWriteable}) &&
130             !exists($criteria{isExecutable})
131 0         0 ) { die("File::Find::Rule::Permissions::permissions: no criteria\n"); }
132            
133             # if a user has been specified, first get their UID (from their
134             # username if necessary). If a user *hasn't* been specified,
135             # then we pretend one has anyway
136 26624 50       31571 $criteria{user} = geteuid() unless(exists($criteria{user}));
137 26624 100       50394 if($criteria{user} =~ /^\d+$/) { $userid = $criteria{user}; }
  10240         10660  
138 16384         20443 else { $userid = $UIDsByUsername{$criteria{user}}; }
139            
140             # now divine the user's permissions. first get the file's mode
141             # bits and ownership
142 26624         34767 my($mode, $file_uid, $file_gid) = (&stat($file))[2,4,5];
143            
144             # now check user/group perms. Set isReadable etc if the mode has
145             # the owner bit set and the user is the owner, or has the group bit
146             # set and the user is in the right group
147             my $isReadable = $mode & (
148             (($userid == $file_uid) ? S_IRUSR : 0) |
149 26624 100       164531 ($UIDinGID{$file_gid}{$userid} ? S_IRGRP : 0)
    100          
150             );
151             my $isWriteable = $mode & (
152             (($userid == $file_uid) ? S_IWUSR : 0) |
153 26624 100       40480 ($UIDinGID{$file_gid}{$userid} ? S_IWGRP : 0)
    100          
154             );
155             my $isExecutable = $mode & (
156             (($userid == $file_uid) ? S_IXUSR : 0) |
157 26624 100       39044 ($UIDinGID{$file_gid}{$userid} ? S_IXGRP : 0)
    100          
158             );
159             # now check "other" perms. Set isReadable etc if "other" bit is
160             # set and user is *not* owner and *not* in right group
161 26624 100 100     70821 if($userid != $file_uid && !$UIDinGID{$file_gid}{$userid}) {
162 10240         7232 $isReadable = $mode & S_IROTH;
163 10240         6386 $isWriteable = $mode & S_IWOTH;
164 10240         7605 $isExecutable = $mode & S_IXOTH;
165             }
166              
167             # root can read and write anything, can execute anything
168             # with any x bit set
169 26624 100       33272 $isReadable = $isWriteable = 1 if($userid == 0);
170 26624 100 100     45497 $isExecutable = 1 if($userid == 0 && $mode & 0111);
171            
172             # Why do all those constants look like incantations to the elder gods?
173             #
174             # S'IXOTH, S'IXOTH IRGRP!
175            
176 26624 100 66     73147 if(exists($criteria{isReadable}) && $criteria{isReadable}) { # must be readable
    100 66        
177 6144 100       46437 return 0 unless($isReadable);
178             } elsif(exists($criteria{isReadable}) && !$criteria{isReadable}) { # must not be ...
179 4096 100       47730 return 0 if($isReadable);
180             }
181 21504 100 66     59941 if(exists($criteria{isWriteable}) && $criteria{isWriteable}) { # must be writeable
    100 66        
182 4608 100       35434 return 0 unless($isWriteable);
183             } elsif(exists($criteria{isWriteable}) && !$criteria{isWriteable}) {
184 4608 100       52285 return 0 if($isWriteable);
185             }
186 16896 100 66     46425 if(exists($criteria{isExecutable}) && $criteria{isExecutable}) {# must be executable
    100 66        
187 4096 100       33509 return 0 unless($isExecutable);
188             } elsif(exists($criteria{isExecutable}) && !$criteria{isExecutable}) {
189 4096 100       41997 return 0 if($isExecutable);
190             }
191            
192 12800         220695 return 1;
193 52         341 } );
194             }
195              
196              
197             =head1 FEEDBACK
198              
199             I welcome constructive criticism. If you need to report a bug, it would
200             be most helpful - and it'll get fixed quicker - if you include sufficient
201             information for me to be able to replicate it consistently. Especially
202             useful are test scripts which fail with the current implementation but
203             should pass.
204              
205             Please report bugs either by email or using L.
206              
207             =head1 SOURCE CODE REPOSITORY
208              
209             L
210              
211             =head1 SEE ALSO
212              
213             File::Find::Rule
214              
215             =head1 AUTHOR, COPYRIGHT and LICENCE
216              
217             Copyright 2003-2009 David Cantrell Edavid@cantrell.org.ukE
218              
219             Based on code by Kate Pugh (File::Find::Rule::MP3Info) and Richard Clamp.
220              
221             This software is free-as-in-speech software, and may be used,
222             distributed, and modified under the terms of either the GNU
223             General Public Licence version 2 or the Artistic Licence. It's
224             up to you which one you use. The full text of the licences can
225             be found in the files GPL2.txt and ARTISTIC.txt, respectively.
226              
227             =head1 CONSPIRACY
228              
229             This module is also free-as-in-mason software.
230              
231             =cut
232              
233             1;