File Coverage

lib/Provision/Unix/User/Linux.pm
Criterion Covered Total %
statement 105 169 62.1
branch 32 84 38.1
condition 5 15 33.3
subroutine 13 16 81.2
pod 1 10 10.0
total 156 294 53.0


line stmt bran cond sub pod time code
1             package Provision::Unix::User::Linux;
2             # ABSTRACT: provision user accounts on Linux systems
3              
4 2     2   25 use strict;
  2         6  
  2         169  
5 2     2   16 use warnings;
  2         4  
  2         281  
6              
7             our $VERSION = '0.20';
8              
9 2     2   16 use English qw( -no_match_vars );
  2         6  
  2         66  
10 2     2   2621 use Params::Validate qw( :all );
  2         10  
  2         736  
11              
12 2     2   17 use lib 'lib';
  2         5  
  2         62  
13 2     2   475 use Provision::Unix;
  2         5  
  2         8226  
14             my ( $prov, $user, $util );
15              
16             sub new {
17 2     2 1 8 my $class = shift;
18 2         83 my %p = validate(
19             @_,
20             { prov => { type => OBJECT },
21             user => { type => OBJECT },
22             debug => { type => BOOLEAN, optional => 1, default => 1 },
23             fatal => { type => BOOLEAN, optional => 1, default => 1 },
24             }
25             );
26              
27 2         16 $prov = $p{prov};
28 2         5 $user = $p{user};
29              
30 2         7 my $self = {
31             prov => $prov,
32             user => $user,
33             debug => $p{debug},
34             fatal => $p{fatal},
35             };
36 2         6 bless( $self, $class );
37              
38 2         13 $prov->audit("loaded User/Linux");
39 2         12 $util = $prov->get_util;
40 2         115 return $self;
41             }
42              
43             sub create {
44 6     6 0 17 my $self = shift;
45 6         1280 my %p = validate(
46             @_,
47             { 'username' => { type => SCALAR },
48             'uid' => { type => SCALAR, optional => 1 },
49             'gid' => { type => SCALAR, optional => 1 },
50             'shell' => { type => SCALAR | UNDEF, optional => 1 },
51             'password' => { type => SCALAR | UNDEF, optional => 1 },
52             'homedir' => { type => SCALAR | UNDEF, optional => 1 },
53             'gecos' => { type => SCALAR | UNDEF, optional => 1 },
54             'domain' => { type => SCALAR | UNDEF, optional => 1 },
55             'expire' => { type => SCALAR | UNDEF, optional => 1 },
56             'quota' => { type => SCALAR | UNDEF, optional => 1 },
57             'debug' => { type => SCALAR, optional => 1, default => 1 },
58             'test_mode' => { type => SCALAR, optional => 1 },
59             }
60             );
61              
62 5         89 my $debug = $p{debug};
63 5         26 my $username = $p{username};
64 5         15 my $password = $p{password};
65 5         90 $prov->audit("creating user '$username' on $OSNAME");
66              
67 5 100       48 $user->_is_valid_username( $username ) or return;
68 2   33     18 my $group = $p{gid} || $self->exists_group( $username );
69              
70 2         85 my $cmd = $util->find_bin( 'useradd', debug => 0 );
71 2 50       29 $cmd .= " -c '$p{gecos}'" if $p{gecos};
72 2 50       19 $cmd .= " -d $p{homedir}" if $p{homedir};
73 2 50       13 $cmd .= " -e $p{expire}" if $p{expire};
74 2 50       25 $cmd .= " -u $p{uid}" if $p{uid};
75 2 50       12 $cmd .= " -s $p{shell}" if $p{shell};
76 2 50       15 $cmd .= " -g $group" if $group;
77 2         8 $cmd .= " -m $username";
78              
79 2 100       19 return $prov->audit("\ttest mode early exit") if $p{test_mode};
80 1 50       35 $util->syscmd( $cmd, debug => 0, fatal => 0 ) or return;
81              
82 1 50       22 if ( $password ) {
83 0         0 my $passwd = $util->find_bin( 'passwd', debug => $p{debug} );
84             ## no critic
85 0         0 my $FH;
86 0 0       0 unless ( open $FH, "| $passwd --stdin $username" ) {
87 0         0 return $prov->error( "opening passwd failed for $username" );
88             }
89 0         0 print $FH "$password\n";
90 0         0 close $FH;
91             ## use critic
92             }
93              
94 1 50       25 $self->exists() or
95             return $prov->error( "failed to create user $username", fatal => 0 );
96            
97 1         23 $prov->audit( "created user $username successfully");
98 1         63 return 1;
99             }
100              
101             sub create_group {
102              
103 2     2 0 11 my $self = shift;
104              
105 2         772 my %p = validate(
106             @_,
107             { 'group' => { type => SCALAR },
108             'gid' => { type => SCALAR, optional => 1, },
109             'debug' => { type => SCALAR, optional => 1, default => 1 },
110             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
111             }
112             );
113              
114             # see if the group exists
115 2 50       43 if ( $self->exists_group( $p{group} ) ) {
116 0         0 $prov->audit("create_group: '$p{group}', already exists");
117 0         0 return 2;
118             }
119              
120 2         232 $prov->audit("create_group: installing $p{group} on $OSNAME");
121              
122 2         62 my $cmd = $util->find_bin( 'groupadd', debug => $p{debug} );
123 2 50       22 $cmd .= " -g $p{gid}" if $p{gid};
124 2         15 $cmd .= " $p{group}";
125              
126 2         48 return $util->syscmd( $cmd, debug => $p{debug} );
127             }
128              
129             sub destroy {
130 2     2 0 7 my $self = shift;
131 2         159 my %p = validate(
132             @_,
133             { 'username' => { type => SCALAR, },
134             'homedir' => { type => SCALAR, optional => 1, },
135             'archive' => { type => BOOLEAN, optional => 1, default => 0 },
136             'prompt' => { type => BOOLEAN, optional => 1, default => 0 },
137             'test_mode' => { type => BOOLEAN, optional => 1, default => 0 },
138             'fatal' => { type => SCALAR, optional => 1, default => 1 },
139             'debug' => { type => SCALAR, optional => 1, default => 1 },
140             },
141             );
142              
143 2         37 my $username = $p{username};
144 2         206 $prov->audit("removing user $username on $OSNAME");
145              
146 2 50       25 $user->_is_valid_username( $username ) or return;
147 2         1139 my $homedir = ( getpwnam( $username ) )[7];
148              
149 2 100       22 return $prov->audit("\ttest mode early exit") if $p{test_mode};
150              
151             # make sure user exists
152 1 50       6 if ( !$self->exists() ) {
153 0         0 return $prov->progress(
154             num => 10,
155             desc => 'error',
156             err => "\tno such user '$username'",
157             );
158             }
159              
160 1         44 my $userdel = $util->find_bin( 'userdel', debug => $p{debug} );
161              
162 1         21 my $opts = " -f";
163 1 50 33     66 $opts .= " -r" if -d $homedir && $homedir ne '/tmp';
164 1         9 $opts .= " $username";
165              
166 1         37 my $r = $util->syscmd( "$userdel $opts", debug => 0, fatal => $p{fatal} );
167              
168             # validate that the user was removed
169 1 50       51 if ( !$self->exists() ) {
170 1         93 return $prov->progress(
171             num => 10,
172             desc => "\tdeleted user $username"
173             );
174             }
175              
176 0         0 return $prov->progress(
177             num => 10,
178             desc => 'error',
179             'err' => "\tfailed to remove user '$username'",
180             );
181             }
182              
183             sub destroy_group {
184              
185 1     1 0 13 my $self = shift;
186              
187 1         89 my %p = validate(
188             @_,
189             { 'group' => { type => SCALAR, },
190             'gid' => { type => SCALAR, optional => 1 },
191             'test_mode' => { type => BOOLEAN, optional => 1, default => 0 },
192             'fatal' => { type => SCALAR, optional => 1, default => 1 },
193             'debug' => { type => SCALAR, optional => 1, default => 1 },
194             },
195             );
196              
197 1         13 my $group = $p{group};
198 1         3 my $fatal = $p{fatal};
199 1         6 my $debug = $p{debug};
200 1         25 $prov->audit("destroy group $group on $OSNAME");
201              
202 1         22 $prov->progress( num => 1, desc => 'validating' );
203              
204 1 50       10 if ( !$self->exists_group( $group ) ) {
205 0         0 $prov->progress( num => 10, desc => "group $group does not exist" );
206 0         0 return 1;
207             }
208              
209 1         45 my $cmd = $util->find_bin( 'groupdel', debug => 0 );
210 1         25 $cmd .= " $group";
211              
212 1 50       28 return 1 if $p{test_mode};
213 1         42 $prov->audit("destroy group cmd: $cmd");
214              
215 1 50       32 $util->syscmd( $cmd, debug => $debug, fatal => $fatal )
216             or return $prov->progress(
217             num => 10,
218             desc => 'error',
219             'err' => $prov->{errors}->[-1]->{errmsg},
220             );
221              
222             # validate that the group was removed
223 1 50       36 if ( !$self->exists_group( $group ) ) {
224 1         77 return $prov->progress( num => 10, desc => 'completed' );
225             }
226              
227 0         0 return;
228             }
229              
230             sub exists {
231 5     5 0 17 my $self = shift;
232 5   66     69 my $username = shift || $user->{username};
233              
234 5 50       43 $user->_is_valid_username($username)
235             or return $prov->error( "missing/invalid username param in request",
236             fatal => 0,
237             );
238              
239 5         29 $username = lc $username;
240              
241 5 50       133 if ( -f '/etc/passwd' ) {
242 5         61463 my $exists = `grep '^$username:' /etc/passwd`;
243 5 100       495 return if ! $exists;
244 3         26 chomp $exists;
245 3         183 $prov->audit("\t'$username' exists (passwd: $exists)");
246 3         85 return $exists;
247             }
248              
249 0         0 restart_nscd();
250              
251 0         0 my $uid = getpwnam $username;
252 0 0       0 return $prov->error("could not find user $user", fatal => 0 ) if ! defined $uid;
253              
254 0         0 $prov->audit("'$username' exists (uid: $uid)");
255 0         0 $self->{uid} = $uid;
256 0         0 return $uid;
257             }
258              
259             sub exists_group {
260 11     11 0 179 my $self = shift;
261 11   33     126 my $group = shift || $user->{group} || $prov->error( "missing group" );
262              
263 11 50       711 if ( -f '/etc/group' ) {
264 11         131565 my $exists = `grep '^$group:' /etc/group`;
265 11 100       1005 return if ! $exists;
266              
267 3         123 my (undef, undef, $gid) = split /:/, $exists;
268 3         199 $prov->audit("found group $group at gid $gid");
269 3         156 return $gid;
270             };
271              
272 0           restart_nscd();
273              
274 0           my $gid = getgrnam($group);
275 0 0         if ( defined $gid ) {
276 0           $prov->audit("found group $group at gid $gid");
277 0           return $gid;
278             };
279             }
280              
281             sub modify {
282 0     0 0   my $self = shift;
283 0           my %p = validate(
284             @_,
285             { 'username' => { type => SCALAR },
286             'shell' => { type => SCALAR, optional => 1 },
287             'password' => { type => SCALAR, optional => 1 },
288             'ssh_key' => { type => SCALAR, optional => 1 },
289             'gecos' => { type => SCALAR, optional => 1 },
290             'expire' => { type => SCALAR, optional => 1 },
291             'quota' => { type => SCALAR, optional => 1 },
292             'debug' => { type => SCALAR, optional => 1, default => 1 },
293             'test_mode' => { type => SCALAR, optional => 1 },
294             }
295             );
296              
297 0 0         if ( $p{password} ) {
298 0           $self->set_password(
299             username => $p{username},
300             password => $p{password},
301             ssh_key => $p{ssh_key},
302             );
303             };
304             };
305              
306             sub set_password {
307 0     0 0   my $self = shift;
308 0           my %p = validate(
309             @_,
310             { username => { type => SCALAR },
311             password => { type => SCALAR, optional => 1 },
312             ssh_key => { type => SCALAR, optional => 1 },
313             ssh_restricted => { type => SCALAR, optional => 1 },
314             debug => { type => SCALAR, optional => 1, default => 1 },
315             fatal => { type => SCALAR, optional => 1, default => 1 },
316             test_mode => { type => SCALAR, optional => 1 },
317             }
318             );
319              
320 0           my $fatal = $p{fatal};
321 0           my $username = $p{username};
322              
323 0 0         $prov->error( "user '$username' not found", fatal => $fatal )
324             if ! $self->exists( $username );
325              
326 0           my $pass_file = "/etc/shadow"; # SYS 5
327 0 0         if ( ! -f $pass_file ) {
328 0           $pass_file = "/etc/passwd";
329 0 0         -f $pass_file or return $prov->error( "could not find password file", fatal => $fatal );
330             };
331              
332 0           my @lines = $util->file_read( $pass_file, fatal => $fatal, debug => 0 );
333 0           my $entry = grep { /^$username:/ } @lines;
  0            
334 0 0         $entry or return $prov->error( "could not find user '$username' in $pass_file!", fatal => $fatal);
335              
336 0           my $crypted = $user->get_crypted_password( $p{password} );
337              
338 0           foreach ( @lines ) {
339 0 0         s/$username\:.*?\:/$username\:$crypted\:/ if m/^$username\:/;
340             };
341 0 0         $util->file_write( $pass_file, lines => \@lines, debug => 0, fatal => 0)
342             or $prov->error("failed to update password for $username", fatal => $fatal);
343              
344 0 0         if ( $p{ssh_key} ) {
345 0           @lines = $util->file_read( '/etc/passwd', debug => 1, fatal => $fatal );
346 0           ($entry) = grep { /^$username:/ } @lines;
  0            
347 0           my $homedir = (split(':', $entry))[5];
348 0 0 0       $homedir && -d $homedir or
349             return $prov->error("unable to determine home directory for $username", fatal => 0);
350 0           $user->install_ssh_key(
351             homedir => $homedir,
352             ssh_key => $p{ssh_key},
353             ssh_restricted => $p{ssh_restricted},
354             username => $username,
355             fatal => $fatal,
356             );
357             };
358 0           return 1;
359             };
360              
361             sub restart_nscd {
362              
363 0     0 0   my $nscd = '/var/run/nscd/nscd.pid';
364 0 0         return if ! -f $nscd;
365              
366 0           my $pid = `cat $nscd`; chomp $pid;
  0            
367 0 0         return if ! $pid;
368              
369 0           $nscd = $util->find_bin( 'nscd', debug => 0 );
370 0 0         return if ! -x $nscd;
371              
372 0           `killall -w nscd`;
373 0           `$nscd`;
374 0           sleep 1; # give the daemon a chance to get started
375              
376 0           $prov->audit("restarted nscd caching daemon");
377             };
378              
379             1;
380              
381              
382              
383             =pod
384              
385             =head1 NAME
386              
387             Provision::Unix::User::Linux - provision user accounts on Linux systems
388              
389             =head1 VERSION
390              
391             version 1.06
392              
393             =head1 SYNOPSIS
394              
395             Handles provisioning operations (create, modify, destroy) for system users on UNIX based operating systems.
396              
397             use Provision::Unix::User::Linux;
398              
399             my $provision_user = Provision::Unix::User::Linux->new();
400             ...
401              
402             =head1 FUNCTIONS
403              
404             =head2 new
405              
406             Creates and returns a new Provision::Unix::User::Linux object.
407              
408             =head1 BUGS
409              
410             Please report any bugs or feature requests to C<bug-unix-provision-user at rt.cpan.org>, or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Provision-Unix>. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
411              
412             =head1 SUPPORT
413              
414             You can find documentation for this module with the perldoc command.
415              
416             perldoc Provision::Unix
417              
418             You can also look for information at:
419              
420             =over 4
421              
422             =item * RT: CPAN's request tracker
423              
424             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Provision-Unix>
425              
426             =item * AnnoCPAN: Annotated CPAN documentation
427              
428             L<http://annocpan.org/dist/Provision-Unix>
429              
430             =item * CPAN Ratings
431              
432             L<http://cpanratings.perl.org/d/Provision-Unix>
433              
434             =item * Search CPAN
435              
436             L<http://search.cpan.org/dist/Provision-Unix>
437              
438             =back
439              
440             =head1 AUTHOR
441              
442             Matt Simerson <msimerson@cpan.org>
443              
444             =head1 COPYRIGHT AND LICENSE
445              
446             This software is copyright (c) 2013 by The Network People, Inc..
447              
448             This is free software; you can redistribute it and/or modify it under
449             the same terms as the Perl 5 programming language system itself.
450              
451             =cut
452              
453              
454             __END__
455