File Coverage

blib/lib/Privileges/Drop.pm
Criterion Covered Total %
statement 15 55 27.2
branch 0 12 0.0
condition 0 15 0.0
subroutine 5 7 71.4
pod 2 2 100.0
total 22 91 24.1


line stmt bran cond sub pod time code
1             package Privileges::Drop;
2 1     1   21495 use strict;
  1         2  
  1         34  
3 1     1   4 use warnings;
  1         1  
  1         27  
4 1     1   763 use English qw( -no_match_vars );
  1         4653  
  1         6  
5 1     1   408 use Carp;
  1         1  
  1         84  
6              
7             our $VERSION = '1.03';
8              
9             =head1 NAME
10              
11             Privileges::Drop - A module to make it simple to drop all privileges, even
12             POSIX groups.
13              
14             =head1 DESCRIPTION
15              
16             This module tries to simplify the process of dropping privileges. This can be
17             useful when your Perl program needs to bind to privileged ports, etc. This
18             module is much like Proc::UID, except that it's implemented in pure Perl.
19             Special care has been taken to also drop saved uid on platforms that support
20             this, currently only test on on Linux.
21              
22             =head1 SYNOPSIS
23            
24             use Privileges::Drop;
25              
26             # Do privileged stuff
27              
28             # Drops privileges and sets euid/uid to 1000 and egid/gid to 1000.
29             drop_uidgid(1000, 1000);
30              
31             # Drop privileges to user nobody looking up gid and uid with getpwname
32             # This also set the enviroment variables USER, LOGNAME, HOME and SHELL.
33             drop_privileges('nobody');
34              
35             =head1 METHODS
36              
37             =over
38              
39             =cut
40              
41 1     1   4 use base "Exporter";
  1         1  
  1         699  
42              
43             our @EXPORT = qw(drop_privileges drop_uidgid);
44              
45             =item drop_uidgid($uid, $gid, @groups)
46              
47             Drops privileges and sets euid/uid to $uid and egid/gid to $gid.
48              
49             Supplementary groups can be set in @groups.
50              
51             =cut
52              
53             sub drop_uidgid {
54 0     0 1   my ($uid, $gid, @reqPosixGroups) = @_;
55            
56             # Sort the groups and make sure they are uniq
57 0           my %groupHash = map { $_ => 1 } ($gid, @reqPosixGroups);
  0            
58 0           my $newgid ="$gid ".join(" ", sort { $a <=> $b } (keys %groupHash));
  0            
59              
60             # Description from:
61             # http://www.mail-archive.com/perl5-changes@perl.org/msg02683.html
62             #
63             # According to Stevens' APUE and various
64             # (BSD, Solaris, HP-UX) man pages setting
65             # the real uid first and effective uid second
66             # is the way to go if one wants to drop privileges,
67             # because if one changes into an effective uid of
68             # non-zero, one cannot change the real uid any more.
69             #
70             # Actually, it gets even messier. There is
71             # a third uid, called the saved uid, and as
72             # long as that is zero, one can get back to
73             # uid of zero. Setting the real-effective *twice*
74             # helps in *most* systems (FreeBSD and Solaris)
75             # but apparently in HP-UX even this doesn't help:
76             # the saved uid stays zero (apparently the only way
77             # in HP-UX to change saved uid is to call setuid()
78             # when the effective uid is zero).
79              
80             # Drop privileges to $uid and $gid for both effective and saved uid/gid
81 0           ($GID) = split /\s/, $newgid;
82 0           $EGID = $newgid;
83 0           $EUID = $UID = $uid;
84              
85             # To overwrite the saved UID on all platforms we need to do it twice
86 0           ($GID) = split /\s/, $newgid;
87 0           $EGID = $newgid;
88 0           $EUID = $UID = $uid;
89              
90             # Sort the output so we can compare it
91 0           my %GIDHash = map { $_ => 1 } ($gid, split(/\s/, $GID));
  0            
92 0           my $cgid = int($GID)." ".join(" ", sort { $a <=> $b } (keys %GIDHash));
  0            
93 0           my %EGIDHash = map { $_ => 1 } ($gid, split(/\s/, $EGID));
  0            
94 0           my $cegid = int($EGID)." ".join(" ", sort { $a <=> $b } (keys %EGIDHash));
  0            
95            
96             # Check that we did actually drop the privileges
97 0 0 0       if($UID ne $uid or $EUID ne $uid or $cgid ne $newgid or $cegid ne $newgid) {
      0        
      0        
98 0           croak("Could not drop privileges to uid:$uid, gid:$newgid\n"
99             ."Currently is: UID:$UID, EUID=$EUID, GID=$cgid, EGID=$cegid\n");
100             }
101             }
102              
103             =item drop_privileges($user)
104              
105             Drops privileges to the $user, looking up gid and uid with getpwname and
106             calling drop_uidgid() with these arguments.
107              
108             The environment variables USER, LOGNAME, HOME and SHELL are also set to the
109             values returned by getpwname.
110              
111             Returns the $uid and $gid on success and dies on error.
112              
113             NOTE: If drop_privileges() is called when you don't have root privileges
114             it will just return undef;
115              
116             =cut
117              
118             sub drop_privileges {
119 0     0 1   my ($user) = @_;
120            
121 0 0         croak "No user give" if !defined $user;
122              
123             # Check if we are root and stop if we are not.
124 0 0 0       if($UID != 0 and $EUID != 0) {
125 0           return;
126             }
127            
128             # Find user in passwd file
129 0           my ($uid, $gid, $home, $shell) = (getpwnam($user))[2,3,7,8];
130 0 0 0       if(!defined $uid or !defined $gid) {
131 0           croak("Could not find uid and gid user $user");
132             }
133              
134             # Find all the groups the user is a member of
135 0           my @groups;
136 0           while (my ($name, $comment, $ggid, $mstr) = getgrent()) {
137 0           my %membership = map { $_ => 1 } split(/\s/, $mstr);
  0            
138 0 0         if(exists $membership{$user}) {
139 0 0         push(@groups, $ggid) if $ggid ne 0;
140             }
141             }
142              
143             # Cleanup $ENV{}
144 0           $ENV{USER} = $user;
145 0           $ENV{LOGNAME} = $user;
146 0           $ENV{HOME} = $home;
147 0           $ENV{SHELL} = $shell;
148              
149 0           drop_uidgid($uid, $gid, @groups);
150              
151 0           return ($uid, $gid, @groups);
152             }
153              
154             =back
155              
156             =head1 NOTES
157              
158             As this module only uses Perl's build in function, it relies on them to work
159             correctly. That means setting $GID and $EGID should also call setgroups(),
160             something that might not have been the case before Perl 5.004. So if you are
161             running an older version, Proc::UID might be a better choice.
162              
163             =head1 AUTHOR
164              
165             Troels Liebe Bentsen
166              
167             =head1 COPYRIGHT
168              
169             Copyright(C) 2007-2009 Troels Liebe Bentsen
170              
171             This library is free software; you can redistribute it and/or modify
172             it under the same terms as Perl itself.
173              
174             =cut
175              
176             1;