| 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; |