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   846 use strict;
  2         3  
  2         45  
3              
4 2     2   781 use Devel::AssertOS::Unix;
  2         39483  
  2         57  
5              
6 2     2   892 use File::Find::Rule;
  2         11483  
  2         15  
7 2     2   86 use base qw(File::Find::Rule);
  2         3  
  2         191  
8 2         170 use vars qw(
9             $VERSION @EXPORT
10             %UIDsByUsername %UsernamesByUID %GIDsByGroupname
11             %GroupnamesByGID %UIDinGID
12 2     2   7 );
  2         2  
13             @EXPORT = @File::Find::Rule::EXPORT;
14             $VERSION = '2.02';
15              
16 2     2   7 use Fcntl qw(:mode);
  2         2  
  2         1531  
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 59467 sub stat { return CORE::stat(shift); }
100 0     0 0 0 sub geteuid { return $>; }
101             sub getusergroupdetails {
102 2     2 0 1265 while(my($name, undef, $uid, $gid) = getpwent()) {
103 40         69 $UIDsByUsername{$name} = $uid;
104 40         60 $UsernamesByUID{$uid} = $name;
105 40         1072 $UIDinGID{$gid}{$uid} = 1;
106             }
107 2         72 while(my($grname, $grpass, $gid, $members) = getgrent()) {
108 88         739 $GIDsByGroupname{$grname} = $gid;
109 88         101 $GroupnamesByGID{$gid} = $grname;
110            
111 88         276 foreach my $member (split(/\s+/, $members)) {
112 2 50       5 next unless(exists($UIDsByUsername{$member}));
113 2         12 $UIDinGID{$gid}{$UIDsByUsername{$member}} = 1;
114             }
115             }
116             }
117              
118             sub File::Find::Rule::permissions {
119 52     52 0 284991 my $self = shift()->_force_object;
120 52 50       624 my %criteria = UNIVERSAL::isa($_[0], "HASH") ? %{$_[0]} : @_;
  0         0  
121              
122             $self->exec(sub {
123 26624     26624   612315 my $file = shift;
124 26624         17918 my $userid;
125            
126             # first check that we've got the mandatory parameters
127 26624 50 66     57650 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       31530 $criteria{user} = geteuid() unless(exists($criteria{user}));
137 26624 100       45472 if($criteria{user} =~ /^\d+$/) { $userid = $criteria{user}; }
  10240         8818  
138 16384         19652 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         34047 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       165897 ($UIDinGID{$file_gid}{$userid} ? S_IRGRP : 0)
    100          
150             );
151             my $isWriteable = $mode & (
152             (($userid == $file_uid) ? S_IWUSR : 0) |
153 26624 100       39787 ($UIDinGID{$file_gid}{$userid} ? S_IWGRP : 0)
    100          
154             );
155             my $isExecutable = $mode & (
156             (($userid == $file_uid) ? S_IXUSR : 0) |
157 26624 100       36929 ($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     69021 if($userid != $file_uid && !$UIDinGID{$file_gid}{$userid}) {
162 10240         7099 $isReadable = $mode & S_IROTH;
163 10240         6718 $isWriteable = $mode & S_IWOTH;
164 10240         7922 $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       31678 $isReadable = $isWriteable = 1 if($userid == 0);
170 26624 100 100     42671 $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     69737 if(exists($criteria{isReadable}) && $criteria{isReadable}) { # must be readable
    100 66        
177 6144 100       47935 return 0 unless($isReadable);
178             } elsif(exists($criteria{isReadable}) && !$criteria{isReadable}) { # must not be ...
179 4096 100       42784 return 0 if($isReadable);
180             }
181 21504 100 66     57435 if(exists($criteria{isWriteable}) && $criteria{isWriteable}) { # must be writeable
    100 66        
182 4608 100       32131 return 0 unless($isWriteable);
183             } elsif(exists($criteria{isWriteable}) && !$criteria{isWriteable}) {
184 4608 100       47865 return 0 if($isWriteable);
185             }
186 16896 100 66     44168 if(exists($criteria{isExecutable}) && $criteria{isExecutable}) {# must be executable
    100 66        
187 4096 100       29599 return 0 unless($isExecutable);
188             } elsif(exists($criteria{isExecutable}) && !$criteria{isExecutable}) {
189 4096 100       41761 return 0 if($isExecutable);
190             }
191            
192 12800         200622 return 1;
193 52         355 } );
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;