File Coverage

blib/lib/Passwd/Linux.pm
Criterion Covered Total %
statement 133 182 73.0
branch 28 60 46.6
condition 2 12 16.6
subroutine 10 10 100.0
pod 0 4 0.0
total 173 268 64.5


line stmt bran cond sub pod time code
1             package Passwd::Linux;
2              
3 1     1   395 use strict;
  1         2  
  1         23  
4 1     1   2 use Carp;
  1         1  
  1         57  
5 1     1   3 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         1  
  1         1169  
6              
7             require Exporter;
8             require DynaLoader;
9              
10             @ISA = qw(Exporter DynaLoader);
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14             @EXPORT = qw();
15             @EXPORT_OK = qw(
16             modpwinfo
17             setpwinfo
18             rmpwnam
19             mgetpwnam
20             );
21              
22             $VERSION = '1.3';
23              
24             bootstrap Passwd::Linux $VERSION;
25              
26             our $have_lock = 0;
27              
28             sub _lock_and_read ($) {
29 6     6   8 my $hold_lock = shift;
30 6         4 my @pass;
31             my @shad;
32 0         0 my %entries;
33 0         0 my $lock;
34              
35 6         10 my $save_separator = $/; # just in case calling program change the separator
36 6         9 $/ = "\n";
37              
38 6 100       12 if ($hold_lock != 0) {
39 4         125 $lock = xs_getlock();
40 4 50       7 if ($lock != 0) {
41 0         0 croak "Couldn't get lock on password files (are you root?)";
42             }
43 4         5 $have_lock = 1;
44             }
45              
46 6 50       99 open(FILE, "
47 0 0       0 if ($hold_lock != 0) {
48 0         0 $lock = xs_releaselock();
49 0 0       0 if ($lock != 0) {
50 0         0 croak "Couldn't release lock after /etc/passwd read failure";
51             } else {
52 0         0 croak "Read of /etc/passwd failed";
53             }
54             }
55             };
56 6         148 chomp(@pass = );
57              
58 6         34 close(FILE);
59              
60 6 50       121 if (open(FILE, "
61 6         68 @shad = ;
62 6         24 close(FILE);
63             } else {
64 0 0       0 if ($hold_lock != 0) {
65 0         0 $lock = xs_releaselock();
66 0 0       0 if ($lock != 0) {
67 0         0 croak "Couldn't release lock after /etc/shadow read failure";
68             } else {
69 0         0 croak "Read of /etc/shadow failed";
70             }
71             } else { # non-shadow permission read
72 0         0 for (my $j = 0; $j <= $#pass; $j++) {
73 0         0 $shad[$j] = "unknown:x\n";
74             }
75             }
76             };
77              
78 6 50       15 if ($#pass != $#shad) {
79 0         0 croak "Mismatch in number of entries between /etc/passwd and /etc/shadow";
80             }
81            
82 6         7 my $info = [];
83 6         13 for (my $i=0; $i <= $#pass; $i++) {
84 123         208 my @pentry = split(/:/, $pass[$i]);
85 123         172 my @sentry = split(/:/, $shad[$i]);
86 123         78 my $entry = [];
87 123         106 chomp(@sentry);
88 123         78 $pentry[1] = $sentry[1];
89 123         66 push @{$entry}, @pentry, @sentry[2..$#sentry];
  123         366  
90 123         83 push @{$info}, $pentry[0]; #preserve the order
  123         110  
91 123         124 $entries{$pentry[0]} = $entry;
92 123         72 my @test = @{$entry};
  123         508  
93             #print "added $pentry[0] - @{$entry}\n";
94             }
95 6         7 $entries{':ORDER:'} = $info;
96 6         8 $/ = $save_separator; # restore original input separator
97 6         97 return %entries;
98             }
99              
100             sub _write_and_release (%) {
101 3     3   9 my %entries = @_;
102 3         3 my $info;
103             my $err;
104 0         0 my $err2;
105            
106             # verify we have the lock?
107 3 50       5 if ($have_lock == 0) {
108 0         0 croak "_write_and_release called and we didn't have the lock, how did that happen?"
109             }
110            
111 3 50       4 if (exists($entries{':ORDER:'})) {
112 3         4 $info = $entries{':ORDER:'};
113             } else {
114 0         0 xs_releaselock();
115 0         0 croak "_write_and_release called with no entry order info";
116             }
117            
118 3 50       341 rename "/etc/passwd", "/etc/opasswd" or do {
119 0         0 xs_releaselock();
120 0         0 croak "Couldn't rename /etc/passwd";
121             };
122            
123 3 50       175 rename "/etc/shadow", "/etc/oshadow" or do {
124 0         0 $err = rename "/etc/opasswd", "/etc/passwd";
125 0         0 xs_releaselock();
126 0 0       0 if ($err == 0) {
127 0         0 croak "/etc/passwd may not exist, /etc/opasswd contains the correct entries";
128             }
129 0         0 croak "Couldn't rename /etc/shadow";
130             };
131            
132 3 50       120 open(PASS, ">/etc/passwd") or do {
133 0         0 $err = rename "/etc/opasswd", "/etc/passwd";
134 0         0 $err2 = rename "/etc/oshadow", "/etc/shadow";
135 0         0 xs_releaselock();
136 0 0 0     0 if (($err == 0) || ($err2 == 0)) {
137 0         0 croak "/etc/passwd or /etc/shadow may not exist, /etc/opasswd and /etc/oshadow contain the correct entries";
138             }
139 0         0 croak "Couldn't open /etc/passwd for writing";
140             };
141 3         46 chmod 0644, "/etc/passwd";
142              
143 3 50       101 open(SHAD, ">/etc/shadow") or do {
144 0         0 $err = rename "/etc/opasswd", "/etc/passwd";
145 0         0 $err2 = rename "/etc/oshadow", "/etc/shadow";
146 0         0 xs_releaselock();
147 0 0 0     0 if (($err == 0) || ($err2 == 0)) {
148 0         0 croak "/etc/passwd or /etc/shadow may not exist, /etc/opasswd and /etc/oshadow contain the correct entries";
149             }
150 0         0 croak "Couldn't open /etc/passwd for writing";
151             };
152              
153             # if a shadow group exists give it read permissions
154 3         526 my @sgrp = getgrnam("shadow");
155 3 50       8 if (@sgrp > 1) {
156 3         47 chown 0, $sgrp[2], "/etc/shadow";
157 3         27 chmod 0640, "/etc/shadow";
158             } else {
159 0         0 chmod 0600, "/etc/shadow";
160             }
161              
162 3         7 my $save_separator = $/; # just in case the program using this has changed it
163 3         5 $/ = "\n";
164 3         4 foreach my $user (@{$info}) {
  3         5  
165 63 100       79 if (exists($entries{$user})) {
166 62         34 my @data = @{$entries{$user}};
  62         137  
167 62         88 my $pentry = join(":", $data[0], "x", @data[2..6]);
168 62         94 my $sentry = join(":", @data[0..1], @data[7..$#data]);
169 62         81 print PASS "$pentry\n";
170 62         35 chomp $sentry;
171 62         106 print SHAD "$sentry\n";
172             } # else skip, its a deleted entry
173             }
174 3         4 $/ = $save_separator;
175              
176 3         63 close(SHAD);
177 3         37 close(PASS);
178            
179 3         13 my $lock = xs_releaselock();
180 3 50       9 if ($lock != 0) {
181 0         0 croak "Couldn't release lock on password files";
182             }
183 3         3 $have_lock = 0;
184 3         14 return 0;
185             }
186              
187             sub _set_user ($$) {
188 2     2   4 my %entries = %{$_[0]};
  2         12  
189 2         2 my @info = @{$_[1]};
  2         5  
190 2         9 my $days = int(time()/86400);
191 2         2 my @data;
192              
193 2         2 $info[7] = $days;
194 2 100       4 if (exists($entries{$info[0]})) {
195 1         2 @data = @{$entries{$info[0]}};
  1         3  
196             } else {
197 1         1 push @{$entries{":ORDER:"}}, $info[0];
  1         2  
198             }
199 2         4 for (my $i = 0; $i <= $#info; $i++) {
200 28         35 $data[$i] = $info[$i];
201             }
202 2         4 $entries{$info[0]} = \@data;
203              
204 2 50       2 if (eval { _write_and_release(%entries); } ) {
  2         7  
205 0         0 print $@;
206 0         0 return 1;
207             }
208 2         12 return 0;
209             }
210              
211             sub modpwinfo {
212 2     2 0 55 my @info = @_;
213 2         2 my @user;
214              
215 2 50 33     9 if (($#info < 1) || ($#info > 13)) {
216 0         0 croak "modpwinfo: (name, crypted_pass, [uid, gid, gecos, home, shell, stuff from shadow file] )";
217             }
218 2         4 my %entries = _lock_and_read(1);
219 2 100       18 if (exists($entries{$info[0]})) {
220 1         2 @user = @{$entries{$info[0]}};
  1         3  
221 1         4 for (my $i = 0; $i <= $#info; $i++) {
222 13         17 $user[$i] = $info[$i];
223             }
224             } else {
225 1         7 xs_releaselock();
226 1         1 $have_lock = 0;
227 1         15 return 2;
228             }
229 1         2 return _set_user(\%entries, \@user);
230             }
231              
232             sub setpwinfo {
233 1     1 0 29 my @info = @_;
234 1         1 my @user;
235              
236 1 50 33     7 if (($#info < 1) || ($#info > 13)) {
237 0         0 print "setpwinfo croaking\n";
238 0         0 croak "setpwinfo: (name, crypted_pass, uid, gid, gecos, home, shell, [(man shadow for the rest of the fields)] )";
239             }
240 1         2 my %entries = _lock_and_read(1);
241 1 50       7 if (exists($entries{$info[0]})) {
242 0         0 @user = @{$entries{$info[0]}};
  0         0  
243             } else {
244 1         2 $user[8] = 0;
245 1         2 $user[9] = 99999;
246 1         2 $user[10] = 7;
247 1         3 $user[13] = "\n"; # fill in the rest as empty
248             }
249 1         4 for (my $i = 0; $i <= $#info; $i++) {
250 7         15 $user[$i] = $info[$i];
251             }
252 1         4 return _set_user(\%entries, \@user);
253             }
254              
255             sub mgetpwnam {
256 2     2 0 69 my ($login) = @_;
257            
258 2         4 my %entries = _lock_and_read(0);
259 2 100       8 if (exists($entries{$login})) {
260 1         1 my @info = @{$entries{$login}};
  1         4  
261 1         17 return @info;
262             }
263            
264 1         15 return;
265             }
266              
267             sub rmpwnam {
268 1     1 0 30 my %entries = _lock_and_read(1);
269 1         3 foreach my $login (@_) {
270 1 50       2 if (exists($entries{$login})) {
271 1         1 my @data = @{$entries{$login}};
  1         4  
272 1 50       6 if ($data[2] != 0) { # don't delete uid 0 accounts
273 1         4 delete $entries{$login};
274             } else {
275 0         0 return 1;
276             }
277             }
278             }
279 1         4 _write_and_release(%entries);
280 1         67 return 0;
281             }
282              
283             1;
284             __END__