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   654 use strict;
  1         1  
  1         28  
4 1     1   4 use Carp;
  1         1  
  1         98  
5 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         1  
  1         1609  
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.2';
23              
24             bootstrap Passwd::Linux $VERSION;
25              
26             our $have_lock = 0;
27              
28             sub _lock_and_read ($) {
29 6     6   12 my $hold_lock = shift;
30 6         10 my @pass;
31             my @shad;
32 0         0 my %entries;
33 0         0 my $lock;
34              
35 6         12 my $save_separator = $/; # just in case calling program change the separator
36 6         37 $/ = "\n";
37              
38 6 100       17 if ($hold_lock != 0) {
39 4         191 $lock = xs_getlock();
40 4 50       13 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       231 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         247 chomp(@pass = );
57              
58 6         66 close(FILE);
59              
60 6 50       187 if (open(FILE, "
61 6         133 @shad = ;
62 6         63 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       21 if ($#pass != $#shad) {
79 0         0 croak "Mismatch in number of entries between /etc/passwd and /etc/shadow";
80             }
81            
82 6         11 my $info = [];
83 6         19 for (my $i=0; $i <= $#pass; $i++) {
84 123         438 my @pentry = split(/:/, $pass[$i]);
85 123         428 my @sentry = split(/:/, $shad[$i]);
86 123         1003 my $entry = [];
87 123         207 chomp(@sentry);
88 123         130 $pentry[1] = $sentry[1];
89 123         105 push @{$entry}, @pentry, @sentry[2..$#sentry];
  123         738  
90 123         155 push @{$info}, $pentry[0]; #preserve the order
  123         189  
91 123         223 $entries{$pentry[0]} = $entry;
92 123         111 my @test = @{$entry};
  123         1044  
93             #print "added $pentry[0] - @{$entry}\n";
94             }
95 6         14 $entries{':ORDER:'} = $info;
96 6         13 $/ = $save_separator; # restore original input separator
97 6         103 return %entries;
98             }
99              
100             sub _write_and_release (%) {
101 3     3   25 my %entries = @_;
102 3         4 my $info;
103             my $err;
104 0         0 my $err2;
105            
106             # verify we have the lock?
107 3 50       10 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       9 if (exists($entries{':ORDER:'})) {
112 3         6 $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       235274 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       328 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       328 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         105 chmod 0644, "/etc/passwd";
142              
143 3 50       209 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         3109 my @sgrp = getgrnam("shadow");
155 3 50       17 if (@sgrp > 1) {
156 3         113 chown 0, $sgrp[2], "/etc/shadow";
157 3         70 chmod 0640, "/etc/shadow";
158             } else {
159 0         0 chmod 0600, "/etc/shadow";
160             }
161              
162 3         11 my $save_separator = $/; # just in case the program using this has changed it
163 3         10 $/ = "\n";
164 3         4 foreach my $user (@{$info}) {
  3         11  
165 63 100       137 if (exists($entries{$user})) {
166 62         55 my @data = @{$entries{$user}};
  62         294  
167 62         170 my $pentry = join(":", $data[0], "x", @data[2..6]);
168 62         180 my $sentry = join(":", @data[0..1], @data[7..$#data]);
169 62         146 print PASS "$pentry\n";
170 62         66 chomp $sentry;
171 62         214 print SHAD "$sentry\n";
172             } # else skip, its a deleted entry
173             }
174 3         7 $/ = $save_separator;
175              
176 3         172 close(SHAD);
177 3         91 close(PASS);
178            
179 3         50 my $lock = xs_releaselock();
180 3 50       25 if ($lock != 0) {
181 0         0 croak "Couldn't release lock on password files";
182             }
183 3         5 $have_lock = 0;
184 3         25 return 0;
185             }
186              
187             sub _set_user ($$) {
188 2     2   2 my %entries = %{$_[0]};
  2         20  
189 2         6 my @info = @{$_[1]};
  2         9  
190 2         13 my $days = int(time()/86400);
191 2         2 my @data;
192              
193 2         4 $info[7] = $days;
194 2 100       6 if (exists($entries{$info[0]})) {
195 1         2 @data = @{$entries{$info[0]}};
  1         6  
196             } else {
197 1         1 push @{$entries{":ORDER:"}}, $info[0];
  1         3  
198             }
199 2         7 for (my $i = 0; $i <= $#info; $i++) {
200 28         55 $data[$i] = $info[$i];
201             }
202 2         4 $entries{$info[0]} = \@data;
203              
204 2 50       4 if (eval { _write_and_release(%entries); } ) {
  2         13  
205 0         0 print $@;
206 0         0 return 1;
207             }
208 2         30 return 0;
209             }
210              
211             sub modpwinfo {
212 2     2 0 1656 my @info = @_;
213 2         5 my @user;
214              
215 2 50 33     17 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         7 my %entries = _lock_and_read(1);
219 2 100       25 if (exists($entries{$info[0]})) {
220 1         3 @user = @{$entries{$info[0]}};
  1         6  
221 1         6 for (my $i = 0; $i <= $#info; $i++) {
222 13         29 $user[$i] = $info[$i];
223             }
224             } else {
225 1         15 xs_releaselock();
226 1         2 $have_lock = 0;
227 1         24 return 2;
228             }
229 1         7 return _set_user(\%entries, \@user);
230             }
231              
232             sub setpwinfo {
233 1     1 0 33 my @info = @_;
234 1         2 my @user;
235              
236 1 50 33     9 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         3 my %entries = _lock_and_read(1);
241 1 50       6 if (exists($entries{$info[0]})) {
242 0         0 @user = @{$entries{$info[0]}};
  0         0  
243             } else {
244 1         1 $user[8] = 0;
245 1         2 $user[9] = 99999;
246 1         2 $user[10] = 7;
247 1         1 $user[13] = "\n"; # fill in the rest as empty
248             }
249 1         8 for (my $i = 0; $i <= $#info; $i++) {
250 7         23 $user[$i] = $info[$i];
251             }
252 1         4 return _set_user(\%entries, \@user);
253             }
254              
255             sub mgetpwnam {
256 2     2 0 95 my ($login) = @_;
257            
258 2         9 my %entries = _lock_and_read(0);
259 2 100       11 if (exists($entries{$login})) {
260 1         1 my @info = @{$entries{$login}};
  1         6  
261 1         34 return @info;
262             }
263            
264 1         23 return;
265             }
266              
267             sub rmpwnam {
268 1     1 0 49 my %entries = _lock_and_read(1);
269 1         5 foreach my $login (@_) {
270 1 50       4 if (exists($entries{$login})) {
271 1         3 my @data = @{$entries{$login}};
  1         5  
272 1 50       7 if ($data[2] != 0) { # don't delete uid 0 accounts
273 1         8 delete $entries{$login};
274             } else {
275 0         0 return 1;
276             }
277             }
278             }
279 1         8 _write_and_release(%entries);
280 1         31 return 0;
281             }
282              
283             1;
284             __END__