File Coverage

lib/Rex/User/Linux.pm
Criterion Covered Total %
statement 46 260 17.6
branch 0 108 0.0
condition 1 39 2.5
subroutine 15 28 53.5
pod 0 13 0.0
total 62 448 13.8


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             package Rex::User::Linux;
6              
7 2     2   57 use v5.12.5;
  2         18  
8 2     2   29 use warnings;
  2         6  
  2         229  
9              
10             our $VERSION = '1.14.2.2'; # TRIAL VERSION
11              
12 2     2   25 use Rex::Logger;
  2         6  
  2         31  
13             require Rex::Commands;
14 2     2   154 use Rex::Commands::MD5;
  2         22  
  2         75  
15 2     2   19 use Rex::Helper::Run;
  2         12  
  2         300  
16 2     2   36 use Rex::Helper::Encode;
  2         15  
  2         138  
17 2     2   20 use Rex::Commands::Fs;
  2         5  
  2         22  
18 2     2   14 use Rex::Interface::File;
  2         6  
  2         21  
19 2     2   60 use Rex::Interface::Fs;
  2         5  
  2         11  
20 2     2   68 use Rex::Interface::Exec;
  2         10  
  2         11  
21 2     2   63 use Rex::Helper::Path;
  2         5  
  2         195  
22 2     2   24 use JSON::MaybeXS;
  2         2050  
  2         204  
23              
24 2     2   34 use Rex::User::Base;
  2         7  
  2         23  
25 2     2   70 use base qw(Rex::User::Base);
  2         8  
  2         5384  
26              
27             sub new {
28 2     2 0 17 my $that = shift;
29 2   33     23 my $proto = ref($that) || $that;
30 2         15 my $self = $proto->SUPER::new(@_);
31              
32 2         5 bless( $self, $proto );
33              
34 2         23 return $self;
35             }
36              
37             sub create_user {
38 0     0 0   my ( $self, $user, $data ) = @_;
39              
40 0           my $cmd;
41              
42 0           my $uid = $self->get_uid($user);
43              
44 0           my $run_cmd = 0;
45 0           my $should_create_home;
46              
47             # if any home creation intent has been defined,
48             # don't follow the default home creation policy
49             my $use_default_home_policy =
50             ( defined $data->{'create_home'}
51             || defined $data->{'create-home'}
52             || defined $data->{'no_create_home'}
53 0 0 0       || defined $data->{'no-create-home'} ) ? 0 : 1;
54              
55 0 0         if ( !$use_default_home_policy ) {
56 0 0 0       if ( $data->{'create_home'} || $data->{'create-home'} ) {
    0 0        
    0 0        
      0        
      0        
57 0           $should_create_home = 1;
58             }
59             elsif ( $data->{'no_create_home'} || $data->{'no-create-home'} ) {
60 0           $should_create_home = 0;
61             }
62             elsif (
63             ( exists $data->{'no_create_home'} && $data->{'no_create_home'} == 0 )
64             || ( exists $data->{'no-create-home'} && $data->{'no-create-home'} == 0 )
65             )
66             {
67 0           $should_create_home = 1;
68             }
69             }
70              
71 0 0         if ( !defined $uid ) {
72 0           Rex::Logger::debug("User $user does not exists. Creating it now.");
73 0           $cmd = "/usr/sbin/useradd ";
74              
75 0 0         if ( exists $data->{system} ) {
76 0           $cmd .= " -r";
77             }
78              
79 0           $run_cmd = 1;
80             }
81             else {
82             # only the user should be there, no modifications.
83             # so just return
84 0 0         if ( !defined $data ) {
85             return {
86 0           changed => 0,
87             uid => $uid,
88             };
89             }
90              
91 0           Rex::Logger::debug("User $user already exists. Updating...");
92              
93 0 0 0       if ( exists $data->{uid} && $data->{uid} == $uid ) {
94 0           delete $data->{uid};
95             }
96              
97 0           $cmd = "/usr/sbin/usermod ";
98             }
99              
100 0 0         if ( exists $data->{non_uniq} ) {
101 0           $cmd .= " -o ";
102 0           $run_cmd = 1;
103             }
104              
105 0 0         if ( exists $data->{uid} ) {
106 0           $cmd .= " --uid " . $data->{uid};
107 0           $run_cmd = 1;
108             }
109              
110 0 0         if ( exists $data->{home} ) {
111 0           $run_cmd = 1;
112 0           $cmd .= " -d " . $data->{home};
113              
114             # don't create home directory in useradd mode if it already exists
115 0 0 0       $should_create_home = 0 if ( !defined $uid && is_dir( $data->{home} ) );
116             }
117              
118 0 0         if ( !$use_default_home_policy ) {
119 0 0         if ( !defined $uid ) { #useradd mode
120 0 0         if ($should_create_home) {
121 0           $cmd .= " -m ";
122             }
123             else {
124 0           $cmd .= " -M ";
125             }
126             }
127             else { #usermod mode
128 0 0         $cmd .= " -m " if ( exists $data->{home} );
129             }
130             }
131              
132 0 0         if ( exists $data->{shell} ) {
133 0           $run_cmd = 1;
134 0           $cmd .= " --shell " . $data->{shell};
135             }
136              
137 0 0         if ( exists $data->{comment} ) {
138 0           $run_cmd = 1;
139 0           $cmd .= " --comment '" . $data->{comment} . "'";
140             }
141              
142 0 0         if ( exists $data->{expire} ) {
143 0           $run_cmd = 1;
144 0           $cmd .= " --expiredate '" . $data->{expire} . "'";
145             }
146              
147 0 0         if ( exists $data->{groups} ) {
148 0           $run_cmd = 1;
149 0           my @groups = @{ $data->{groups} };
  0            
150 0           my $pri_group = shift @groups;
151              
152 0           $cmd .= " --gid $pri_group";
153              
154 0 0         if (@groups) {
155 0           $cmd .= " --groups " . join( ",", @groups );
156             }
157             }
158              
159 0           my $old_pw_md5 = md5("/etc/passwd");
160 0           my $old_sh_md5 = "";
161 0           eval { $old_sh_md5 = md5("/etc/shadow"); };
  0            
162              
163             # only run the cmd if needed
164 0 0         if ($run_cmd) {
165 0           my $rnd_file = get_tmp_file;
166 0           my $fh = Rex::Interface::File->create;
167 0           $fh->open( ">", $rnd_file );
168 0           $fh->write("rm \$0\n$cmd $user\nexit \$?\n");
169 0           $fh->close;
170              
171 0           i_run "/bin/sh $rnd_file", fail_ok => 1;
172 0 0         if ( $? == 0 ) {
173 0           Rex::Logger::debug("User $user created/updated.");
174             }
175             else {
176 0           Rex::Logger::info( "Error creating/updating user $user", "warn" );
177 0           die("Error creating/updating user $user");
178             }
179              
180             }
181              
182 0 0         if ( exists $data->{password} ) {
183 0           my $rnd_file = get_tmp_file;
184 0           my $fh = Rex::Interface::File->create;
185 0           $fh->open( ">", $rnd_file );
186             $fh->write( "rm \$0\n/bin/echo -e '"
187             . $data->{password} . "\\n"
188             . $data->{password}
189 0           . "' | /usr/bin/passwd $user\nexit \$?\n" );
190 0           $fh->close;
191              
192 0           Rex::Logger::debug("Changing password of $user.");
193 0           i_run "/bin/sh $rnd_file", fail_ok => 1;
194 0 0         if ( $? != 0 ) {
195 0           die("Error setting password for $user");
196             }
197              
198             }
199              
200 0 0 0       if ( exists $data->{crypt_password} && $data->{crypt_password} ) {
201 0           my $rnd_file = get_tmp_file;
202 0           my $fh = Rex::Interface::File->create;
203 0           $fh->open( ">", $rnd_file );
204             $fh->write( "rm \$0\nusermod -p '"
205             . $data->{crypt_password}
206 0           . "' $user\nexit \$?\n" );
207 0           $fh->close;
208              
209 0           Rex::Logger::debug("Setting encrypted password of $user");
210 0           i_run "/bin/sh $rnd_file", fail_ok => 1;
211 0 0         if ( $? != 0 ) {
212 0           die("Error setting password for $user");
213             }
214             }
215              
216 0           my $new_pw_md5 = md5("/etc/passwd");
217 0           my $new_sh_md5 = "";
218 0           eval { $new_sh_md5 = md5("/etc/shadow"); };
  0            
219              
220 0 0 0       if ( $new_pw_md5 eq $old_pw_md5 && $new_sh_md5 eq $old_sh_md5 ) {
221             return {
222 0           changed => 0,
223             ret => $self->get_uid($user),
224             };
225             }
226             else {
227             return {
228 0           changed => 1,
229             ret => $self->get_uid($user),
230             },
231             ;
232             }
233              
234             }
235              
236             sub rm_user {
237 0     0 0   my ( $self, $user, $data ) = @_;
238              
239 0           Rex::Logger::debug("Removing user $user");
240              
241 0           my $cmd = "/usr/sbin/userdel";
242              
243 0 0         if ( exists $data->{delete_home} ) {
244 0           $cmd .= " --remove";
245             }
246              
247 0 0         if ( exists $data->{force} ) {
248 0           $cmd .= " --force";
249             }
250              
251 0           my $output = i_run $cmd . " " . $user, fail_ok => 1;
252 0 0         if ( $? == 6 ) {
    0          
253 0           Rex::Logger::info( "Cannot delete user $user (no such user)", "warn" );
254             }
255             elsif ( $? != 0 ) {
256 0           die("Error deleting user $user ($output)");
257             }
258              
259             }
260              
261             sub get_uid {
262 0     0 0   my ( $self, $user ) = @_;
263              
264 0           my %data = $self->get_user($user);
265 0           return $data{uid};
266             }
267              
268             sub user_groups {
269 0     0 0   my ( $self, $user ) = @_;
270              
271 0           Rex::Logger::debug("Getting group membership of $user");
272 0           my $rnd_file = get_tmp_file;
273 0           my $fh = Rex::Interface::File->create;
274 0           my $script = q|
275             unlink $0;
276             $exe = "/usr/bin/groups";
277             if(! -x $exe) {
278             $exe = "/bin/groups";
279             } print to_json([ map {chomp; $_ =~ s/^[^:]*:\s*(.*)\s*$/$1/; split / /, $_} qx{$exe $ARGV[0]} ]);
280              
281             |;
282              
283 0           $fh->open( ">", $rnd_file );
284 0           $fh->write($script);
285 0           $fh->write( func_to_json() );
286 0           $fh->close;
287              
288 0           my $data_str = i_run "perl $rnd_file $user", fail_ok => 1;
289 0 0         if ( $? != 0 ) {
290 0           die("Error getting group list");
291             }
292              
293 0           my $data = decode_json($data_str);
294              
295 0           my $wantarray = wantarray();
296              
297 0 0 0       if ( defined $wantarray && !$wantarray ) {
298              
299             # arrayref
300 0           return $data;
301             }
302              
303 0           return @{$data};
  0            
304             }
305              
306             sub user_list {
307 0     0 0   my $self = shift;
308              
309 0           Rex::Logger::debug("Getting user list");
310 0           my $rnd_file = get_tmp_file;
311 0           my $script = q|
312             unlink $0;
313             print to_json([ map {chomp; $_ =~ s/^([^:]*):.*$/$1/; $_} qx{/usr/bin/getent passwd} ]);
314             |;
315 0           my $fh = Rex::Interface::File->create;
316 0           $fh->open( ">", $rnd_file );
317 0           $fh->write($script);
318 0           $fh->write( func_to_json() );
319 0           $fh->close;
320              
321 0           my $data_str = i_run "perl $rnd_file", fail_ok => 1;
322 0 0         if ( $? != 0 ) {
323 0           die("Error getting user list");
324             }
325              
326 0           my $data = decode_json($data_str);
327              
328 0           return @$data;
329             }
330              
331             sub get_user {
332 0     0 0   my ( $self, $user ) = @_;
333              
334 0           Rex::Logger::debug("Getting information for $user");
335 0           my $rnd_file = get_tmp_file;
336 0           my $fh = Rex::Interface::File->create;
337 0           my $script = q|
338             unlink $0;
339             print to_json([ getpwnam($ARGV[0]) ]);
340             |;
341 0           $fh->open( ">", $rnd_file );
342 0           $fh->write($script);
343 0           $fh->write( func_to_json() );
344 0           $fh->close;
345              
346 0           my $data_str = i_run "perl $rnd_file $user", fail_ok => 1;
347 0 0         if ( $? != 0 ) {
348 0           die("Error getting user information for $user");
349             }
350              
351 0           my $data = decode_json($data_str);
352              
353             return (
354 0 0         name => $data->[0],
355             password => $data->[1],
356             uid => $data->[2],
357             gid => $data->[3],
358             comment => $data->[5],
359             home => $data->[7],
360             shell => $data->[8],
361             expire => exists $data->[9] ? $data->[9] : 0,
362             );
363             }
364              
365             sub lock_password {
366 0     0 0   my ( $self, $user ) = @_;
367              
368             # Is the password already locked?
369 0           my $result = i_run "passwd --status $user", fail_ok => 1;
370              
371 0 0         die "Unexpected result from passwd: $result"
372             unless $result =~ /^$user\s+(L|NP|P)\s+/;
373              
374 0 0         if ( $1 eq 'L' ) {
375              
376             # Already locked
377 0           return { changed => 0 };
378             }
379             else {
380 0           my $ret = i_run "passwd --lock $user", fail_ok => 1;
381 0 0         if ( $? != 0 ) {
382 0           die("Error locking account $user: $ret");
383             }
384             return {
385 0           changed => 1,
386             ret => $ret,
387             };
388             }
389             }
390              
391             sub unlock_password {
392 0     0 0   my ( $self, $user ) = @_;
393              
394             # Is the password already unlocked?
395 0           my $result = i_run "passwd --status $user", fail_ok => 1;
396              
397 0 0         die "Unexpected result from passwd: $result"
398             unless $result =~ /^$user\s+(L|NP|P)\s+/;
399              
400 0 0         if ( $1 eq 'P' ) {
401              
402             # Already unlocked
403 0           return { changed => 0 };
404             }
405             else {
406             # Capture error string on failure (eg. account has no password)
407 0     0     my ( $ret, $err ) = i_run "passwd --unlock $user", sub { @_ }, fail_ok => 1;
  0            
408 0 0         if ( $? != 0 ) {
409 0           die("Error unlocking account $user: $err");
410             }
411             return {
412 0           changed => 1,
413             ret => $ret,
414             };
415             }
416             }
417              
418             sub create_group {
419 0     0 0   my ( $self, $group, $data ) = @_;
420              
421 0           my $cmd;
422              
423 0           my $gid = $self->get_gid($group);
424              
425 0 0 0       if ( !defined $gid ) {
    0          
426 0           Rex::Logger::debug("Creating new group $group");
427              
428 0           $cmd = "/usr/sbin/groupadd ";
429             }
430             elsif ( exists $data->{gid} && $data->{gid} == $gid ) {
431 0 0         if ( Rex::Config->get_do_reporting ) {
432             return {
433 0           changed => 0,
434             ret => $gid,
435             };
436             }
437 0           return $gid;
438             }
439             else {
440 0 0         if ( !defined $data ) {
441 0 0         if ( Rex::Config->get_do_reporting ) {
442             return {
443 0           changed => 0,
444             ret => $gid,
445             };
446             }
447              
448 0           return $gid;
449             }
450 0           Rex::Logger::debug("Group $group already exists. Updating...");
451 0           $cmd = "/usr/sbin/groupmod ";
452             }
453              
454 0 0         if ( exists $data->{gid} ) {
455 0           $cmd .= " -g " . $data->{gid};
456 0           $gid = undef;
457             }
458              
459 0           i_run $cmd . " " . $group, fail_ok => 1;
460 0 0         if ( $? != 0 ) {
461 0           die("Error creating/modifying group $group");
462             }
463              
464 0 0         if ( defined $gid ) {
465 0           return $gid;
466             }
467              
468 0           return $self->get_gid($group);
469             }
470              
471             sub get_gid {
472 0     0 0   my ( $self, $group ) = @_;
473              
474 0           my %data = $self->get_group($group);
475 0           return $data{gid};
476             }
477              
478             sub get_group {
479 0     0 0   my ( $self, $group ) = @_;
480              
481 0           Rex::Logger::debug("Getting information for $group");
482 0           my @data =
483             split(
484             " ",
485             ""
486             . i_run(
487             "perl -le 'print join(\" \", getgrnam(\$ARGV[0]));' '$group'",
488             fail_ok => 1
489             ),
490             4
491             );
492 0 0         if ( $? != 0 ) {
493 0           die("Error getting group information");
494             }
495              
496             return (
497 0           name => $data[0],
498             password => $data[1],
499             gid => $data[2],
500             members => $data[3],
501             );
502             }
503              
504             sub rm_group {
505 0     0 0   my ( $self, $group ) = @_;
506              
507 0           i_run "/usr/sbin/groupdel $group", fail_ok => 1;
508 0 0         if ( $? != 0 ) {
509 0           die("Error deleting group $group");
510             }
511             }
512              
513             1;