File Coverage

blib/lib/File/Find/Rule/Permissions.pm
Criterion Covered Total %
statement 63 66 95.4
branch 50 52 96.1
condition 27 30 90.0
subroutine 12 13 92.3
pod 1 1 100.0
total 153 162 94.4


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