File Coverage

blib/lib/Unix/Mgt.pm
Criterion Covered Total %
statement 125 367 34.0
branch 14 120 11.6
condition 0 3 0.0
subroutine 33 64 51.5
pod 0 7 0.0
total 172 561 30.6


line stmt bran cond sub pod time code
1             ###############################################################################
2             # Unix::Mgt
3             #
4             package Unix::Mgt;
5 1     1   461 use strict;
  1         1  
  1         30  
6 1     1   551 use IPC::System::Simple 'runx';
  1         13013  
  1         69  
7 1     1   492 use Capture::Tiny 'capture_merged';
  1         24626  
  1         71  
8 1     1   563 use String::Util qw{define nocontent};
  1         2589  
  1         68  
9 1     1   505 use Unix::SearchPathGuess 'cmd_path_guess';
  1         1603  
  1         48  
10 1     1   5 use Carp 'croak';
  1         0  
  1         47  
11              
12             # debug tools
13             # use Debug::ShowStuff ':all';
14             # use Debug::ShowStuff::ShowVar;
15              
16             # version
17             our $VERSION = '0.13';
18              
19              
20             #------------------------------------------------------------------------------
21             # export
22             #
23 1     1   4 use base 'Exporter';
  1         1  
  1         50  
24 1     1   4 use vars qw[@EXPORT_OK %EXPORT_TAGS];
  1         1  
  1         522  
25             push @EXPORT_OK, 'unix_mgt_err';
26             %EXPORT_TAGS = ('all' => [@EXPORT_OK]);
27             #
28             # export
29             #------------------------------------------------------------------------------
30              
31              
32             #------------------------------------------------------------------------------
33             # opening POD
34             #
35              
36             =head1 NAME
37              
38             Unix::Mgt - lightweight Unix management tools
39              
40             =head1 SYNOPSIS
41              
42             # get user account
43             $user = Unix::Mgt::User->get('fred');
44              
45             # display some info
46             print 'uid: ', $user->uid, "\n";
47             print join(', ', $user->groups()), "\n";
48              
49             # set some properties
50             $user->gid('websters');
51             $user->shell('/bin/bash');
52             $user->add_to_group('postgres');
53              
54             # create user account
55             $user = Unix::Mgt::User->create('vera');
56              
57             # get user account, creating it if necessary
58             $user = Unix::Mgt::User->ensure('molly');
59              
60             # get group
61             $group = Unix::Mgt::Group->get('www-data');
62              
63             # display some info
64             print 'gid: ', $group->gid, "\n";
65             print join(', ', $group->members()), "\n";
66              
67             # add a member
68             $group->add_member('tucker');
69              
70             =head1 DESCRIPTION
71              
72             Unix::Mgt provides simple object-oriented tools for managing your Unixish
73             system. Currently this module provides tools for managing users and groups.
74             Other tools may follow as they evolve.
75              
76             Unix::Mgt does not directly manipulate any of the system files such as
77             C. This module uses Perl's built-in Unix functions such as
78             C to get information, and Unix's built-in programs such as
79             C.
80              
81             =head2 Early release
82              
83             In the spirit of "release early, release often", I'm releasing this version
84             of Unix::Mgt before it has all the features that might be expected. This
85             version does not include methods for removing users from groups, renaming
86             users or groups, or several other methods.
87              
88             =head2 Help with BSD development
89              
90             This version does not work well on BSDish systems. Although the methods for
91             retrieving information such as C and C work well, this module
92             currently cannot create, modify, or delete users or group. If you'd like to
93             help fill in these features, here's what needs to be done.
94              
95             Several places in the code is the comment "BSD code needed". In those places
96             what we need is to build an array consisting of an external command and the
97             arguments to be sent to the command. It would look something like this:
98              
99             # build command
100             @cmd = (
101             'pw',
102             'useradd',
103             $user->{'name'},
104             '-G',
105             $group->{'name'},
106             );
107              
108             The first element of the array is the program to be run. You don't need to put
109             the full path of the program, L should find it. (If it
110             doesn't please let me know, we may need to fix Unix::SearchPathGuess.) The
111             remaining elements are the params sent to the program using
112             L. The program is not run in a shell, but instead is
113             called directly, so it isn't necessary to do any shell escaping.
114              
115             The following methods currently need patching. In Unix::Mgt::User we need
116             create(), field_get_set(), group(), add_to_group(), and remove(). In
117             Unix::Mgt::Group we need create() and remove(). We will need more as we add
118             features to this module.
119              
120             Please email me at miko@idocs.com if you've made these mods.
121              
122             =cut
123              
124             #
125             # opening POD
126             #------------------------------------------------------------------------------
127              
128              
129             #------------------------------------------------------------------------------
130             # error id and message globals
131             #
132             our $err_id;
133             our $err_msg;
134             #
135             # error id and message globals
136             #------------------------------------------------------------------------------
137              
138              
139             #------------------------------------------------------------------------------
140             # set_err, reset_err
141             #
142             sub set_err {
143 4     4 0 5 my ($class, $id, $msg) = @_;
144 4         5 $err_id = $id;
145 4         4 $err_msg = $msg;
146 4         8 return undef;
147             }
148              
149             sub reset_err {
150 6     6 0 7 undef $err_id;
151 6         6 undef $err_msg;
152             }
153              
154             sub unix_mgt_err {
155 0 0   0 0 0 if ($err_id)
156 0         0 { return $err_id . ': ' . define($err_msg) . "\n" }
157             else
158 0         0 { return '' }
159             }
160             #
161             # reset_err
162             #------------------------------------------------------------------------------
163              
164              
165             #------------------------------------------------------------------------------
166             # called_sub
167             #
168             sub called_sub {
169 4     4 0 4 my (@caller, $sub_name);
170            
171             # TESTING
172             # println subname(class=>1); ##i
173            
174             # get caller info
175 4         23 @caller = caller(1);
176            
177             # get subroutine name and make it look like a method call
178 4         7 $sub_name = $caller[3];
179 4         32 $sub_name =~ s|^(.*)\:\:|$1\-\>|s;
180 4         5 $sub_name .= '()';
181            
182             # return
183 4         42 return $sub_name;
184             }
185             #
186             # called_sub
187             #------------------------------------------------------------------------------
188              
189              
190             #------------------------------------------------------------------------------
191             # even_odd_params
192             #
193             sub even_odd_params {
194 6     6 0 5 my $class = shift(@_);
195 6         6 my $id = shift(@_);
196 6         6 my ($name, %opts);
197            
198             # get params: even number means all params (except class) are options,
199             # odd number means first param is id
200 6 100       13 if (@_ % 2) {
201 4         7 ($name, %opts) = @_;
202             }
203             else {
204 2         3 %opts = @_;
205 2         4 $name = delete($opts{$id});
206             }
207            
208             # return
209 6         13 return ($name, %opts);
210             }
211             #
212             # even_odd_params
213             #------------------------------------------------------------------------------
214              
215              
216             #------------------------------------------------------------------------------
217             # run_cmd
218             #
219             sub run_cmd {
220 0     0 0 0 my ($class, $err_id_use, $cmd_id, @args) = @_;
221 0         0 my ($cmd, $out, $rv);
222            
223             # TESTING
224             # println subname(method=>1); ##i
225            
226             # get command
227 0         0 $cmd = cmd_path_guess($cmd_id);
228 0 0       0 $cmd or croak "do not find path for command $cmd_id";
229            
230             # run command
231             $out = capture_merged{
232 0     0   0 $rv = runx(IPC::System::Simple::EXIT_ANY, $cmd, @args);
233 0         0 };
234            
235             # if error
236 0 0       0 if ($rv) {
237 0         0 return $class->set_err(
238             $err_id_use,
239             "error running program $cmd: " . $out,
240             );
241             }
242            
243             # return success
244 0         0 return 1;
245             }
246             #
247             # run_cmd
248             #------------------------------------------------------------------------------
249              
250              
251             #------------------------------------------------------------------------------
252             # use_bsd
253             #
254             my ($use_bsd);
255              
256             sub use_bsd {
257 0     0 0 0 my ($ug) = @_;
258 0         0 my $use_bsd = \$Unix::Mgt::use_bsd;
259            
260             # TESTING
261             # println subname(method=>1); ##i
262            
263             # if cached, use that
264 0 0       0 if (defined $$use_bsd)
265 0         0 { return $$use_bsd }
266            
267             # else get the command, cache, and return
268 0         0 $$use_bsd = cmd_path_guess('pw');
269 0         0 return $$use_bsd;
270             }
271             #
272             # use_bsd
273             #------------------------------------------------------------------------------
274              
275              
276             #
277             # Unix::Mgt
278             ###############################################################################
279              
280              
281              
282             ###############################################################################
283             # Unix::Mgt::UGCommon
284             #
285             package Unix::Mgt::UGCommon;
286 1     1   5 use strict;
  1         1  
  1         24  
287 1     1   3 use String::Util ':all';
  1         1  
  1         258  
288 1     1   5 use Carp 'croak';
  1         1  
  1         44  
289 1     1   4 use Unix::SearchPathGuess 'cmd_path_guess';
  1         4  
  1         30  
290 1     1   3 use base 'Unix::Mgt';
  1         1  
  1         80  
291              
292             # debug tools
293             # use Debug::ShowStuff ':all';
294              
295              
296             #------------------------------------------------------------------------------
297             # object overloading
298             #
299             use overload
300 2     2   466 '""' => sub{$_[0]->{'name'}}, # stringification
301 1     1   13 fallback => 1; # operations not defined here
  1         1  
  1         7  
302             #
303             # object overloading
304             #------------------------------------------------------------------------------
305              
306              
307             #------------------------------------------------------------------------------
308             # normalize_name
309             #
310             sub normalize_name {
311 4     4   6 my ($class, $name) = @_;
312            
313             # if defined, remove spaces at beginning and end
314 4 50       6 if (defined $name) {
315 4         8 $name =~ s|^\s+||sg;
316 4         5 $name =~ s|\s+$||sg;
317             }
318            
319             # return
320 4         10 return $name;
321             }
322             #
323             # normalize_name
324             #------------------------------------------------------------------------------
325              
326              
327             #------------------------------------------------------------------------------
328             # name_check
329             #
330             sub name_check {
331 6     6   7 my ($class, $name, $id) = @_;
332            
333             # TESTING
334             # println subname(method=>1); ##i
335            
336             # if name does not have content, that's an error
337 6 100       15 if (nocontent $name) {
338 2         24 return $class->set_err(
339             $id,
340             $class->called_sub() . ' requires a user name parameter'
341             );
342             }
343            
344             # normalize
345 4         41 $name = $class->normalize_name($name);
346            
347             # return
348 4         5 return $name;
349             }
350             #
351             # name_check
352             #------------------------------------------------------------------------------
353              
354              
355             #------------------------------------------------------------------------------
356             # mod_only
357             #
358             sub mod_only {
359 0     0   0 my ($class, $name) = @_;
360 0         0 my ($only);
361            
362             # TESTING
363             # println subname(method=>1); ##i
364            
365             # get class
366 0 0       0 if (ref $class)
367 0         0 { $class = ref($class) }
368            
369             # get hash with destrictions
370             # KLUDGE: This is an awkward way to get the variable, but I didn't want
371             # to remember how to work through package hashes.
372 0 0       0 if ( $class eq 'Unix::Mgt::User' )
    0          
373 0         0 { $only = $Unix::Mgt::User::MOD_ONLY }
374             elsif ($class eq 'Unix::Mgt::Group')
375 0         0 { $only = $Unix::Mgt::Group::MOD_ONLY }
376             else
377 0         0 { croak qq|do not know package "$class" for mod restrictions | }
378            
379             # if $only is defined, name must be in the hash
380 0 0       0 if ($only) {
381             # deref
382 0 0       0 if (ref $name)
383 0         0 { $name = $name->{'name'} }
384            
385             # if no content in name, fail
386 0 0       0 if (nocontent $name)
387 0         0 { croak 'no content in $name' }
388            
389 0 0       0 if (! exists($only->{$name})) {
390 0         0 croak qq|cannot modify user "$name"|;
391             }
392             }
393            
394             # else it's ok to mod that user
395 0         0 return 1;
396             }
397             #
398             # mod_only
399             #------------------------------------------------------------------------------
400              
401              
402             #------------------------------------------------------------------------------
403             # ensure
404             #
405             sub ensure {
406 0     0   0 my $class = shift(@_);
407 0         0 my ($name, %opts) = $class->even_odd_params('name', @_);
408            
409             # TESTING
410             # println subname(method=>1); ##i
411            
412             # reset error globals
413 0         0 $class->reset_err();
414            
415             # check and normalize name
416 0         0 $name = $class->name_check($name, 'missing-user-name');
417 0 0       0 $name or return undef;
418            
419             # if user exists, return get method
420 0 0       0 if (my @fields = $class->fields($name)) {
421 0         0 return $class->get($name, fields=>\@fields)
422             }
423            
424             # else return create
425             else {
426 0         0 return $class->create($name);
427             }
428             }
429             #
430             # ensure
431             #------------------------------------------------------------------------------
432              
433              
434             #
435             # Unix::Mgt::UGCommon
436             ###############################################################################
437              
438              
439              
440             ###############################################################################
441             # Unix::Mgt::User
442             #
443             package Unix::Mgt::User;
444 1     1   493 use strict;
  1         2  
  1         39  
445 1     1   3 use Carp 'croak';
  1         1  
  1         41  
446 1     1   3 use String::Util ':all';
  1         1  
  1         142  
447 1     1   4 use base 'Unix::Mgt::UGCommon';
  1         2  
  1         1548  
448              
449             # debug tools
450             # use Debug::ShowStuff ':all';
451              
452             # safety mechanism for development
453             our $MOD_ONLY;
454              
455              
456             #------------------------------------------------------------------------------
457             # POD
458             #
459              
460             =head1 Unix::Mgt::User
461              
462             A Unix::Mgt::User object represents a user in the Unix system. The object
463             allows you to get and set information about the user account. A user object
464             is created in one of three ways: C, C, or C. Note that
465             there is no C method.
466              
467             Unix::Mgt::User objects stringify to the account's name. For example, the
468             following code would output C.
469              
470             $user = Unix::Mgt::User->get('miko');
471             print $user, "\n";
472              
473             =cut
474              
475             #
476             # POD
477             #------------------------------------------------------------------------------
478              
479              
480             #------------------------------------------------------------------------------
481             # field_names
482             #
483             our @field_names = qw{
484             name
485             passwd
486             uid
487             gid
488             quota
489             comment
490             gecos
491             dir
492             shell
493             expire
494             };
495             #
496             # field_names
497             #------------------------------------------------------------------------------
498              
499              
500              
501             #------------------------------------------------------------------------------
502             # get
503             #
504              
505             =head2 get
506              
507             Unix::Mgt::User->get() retrieves user account information using C or
508             C. The single param for this method is either the name or the uid of
509             the user.
510              
511             $user = Unix::Mgt::User->get('vera');
512             $user = Unix::Mgt::User->get('1010');
513              
514             If the user is not found then the C error id is set in
515             C<$Unix::Mgt::err_id> and undef is returned.
516              
517             =cut
518              
519             sub get {
520 4     4   6734 my $class = shift(@_);
521 4         14 my ($name, %opts) = $class->even_odd_params('name', @_);
522 4         3 my (@fields, $user);
523            
524             # TESTING
525             # println subname(method=>1); ##i
526            
527             # reset error globals
528 4         12 $class->reset_err();
529            
530             # check and normalize name
531 4         9 $name = $class->name_check($name, 'missing-user-name');
532 4 100       10 $name or return undef;
533            
534             # get fields
535 3         6 @fields = $class->fields($name);
536            
537             # if user exists, get name, else throw error
538 3 100       9 if (@fields) {
539 2         4 $name = $fields[0];
540             }
541             else {
542 1         3 return $class->set_err(
543             'do-not-have-user',
544             $class->called_sub() . qq|: do not find a user with name "$name"|,
545             );
546             }
547            
548             # create object
549 2         7 $user = bless({}, $class);
550            
551             # hold on to name
552 2         89 $user->{'name'} = $name;
553            
554             # return
555 2         7 return $user;
556             }
557             #
558             # get
559             #------------------------------------------------------------------------------
560              
561              
562             #------------------------------------------------------------------------------
563             # entry
564             #
565             sub entry {
566 0     0   0 my ($user) = @_;
567 0         0 my (@fields, %entry);
568            
569             # TESTING
570             # println subname(method=>1); ##i
571            
572             # get fields
573 0         0 @fields = $user->fields($user->{'name'});
574            
575             # if no fields, set error and return undef
576 0 0       0 if (! @fields) {
577 0         0 return $user->set_err(
578             'do-not-have-user-entry-anymore',
579             $user->called_sub() . ': do not have a user with name "' . $user->{'name'} . '"',
580             );
581             }
582            
583             # set hash
584 0         0 @entry{@field_names} = @fields;
585            
586             # return
587 0         0 return \%entry;
588             }
589             #
590             # entry
591             #------------------------------------------------------------------------------
592              
593              
594             #------------------------------------------------------------------------------
595             # fields
596             #
597             sub fields {
598 3     3   4 my ($class, $name) = @_;
599            
600             # TESTING
601             # println subname(method=>1); ##i
602            
603             # return
604 3 50       8 if ($name =~ m|^\d+$|s)
605 0         0 { return getpwuid($name) }
606             else
607 3         720 { return getpwnam($name) }
608             }
609             #
610             # fields
611             #------------------------------------------------------------------------------
612              
613              
614             #------------------------------------------------------------------------------
615             # create
616             #
617              
618             =head2 create
619              
620             Unix::Mgt::User->create() creates a user account. The required param for this
621             method is the name for the new account.
622              
623             $user = Unix::Mgt::User->create('vera');
624              
625             If the C param is true, then the account is created as a system user,
626             like this:
627              
628             $user = Unix::Mgt::User->create('lanny', system=>1);
629              
630             create() uses the Unix C program.
631              
632             =cut
633              
634             sub create {
635 0     0   0 my $class = shift(@_);
636 0         0 my ($name, %opts) = $class->even_odd_params('name', @_);
637 0         0 my ($user, @cmd);
638            
639             # TESTING
640             # println subname(method=>1); ##i
641            
642             # reset error globals
643 0         0 $class->reset_err();
644            
645             # check and normalize name
646 0         0 $name = $class->name_check($name, 'missing-user-name');
647 0 0       0 $name or return undef;
648            
649             # if user exists, throw error
650 0 0       0 if ($class->fields($name)) {
651 0         0 return $class->set_err(
652             'already-have-user',
653             $class->called_sub() . qq|: already have a user with name "$name"|,
654             );
655             }
656            
657             # safety check
658 0         0 $class->mod_only($name);
659            
660             # BSD style
661 0 0       0 if ($class->use_bsd) {
662             # BSD code needed
663            
664 0         0 croak
665             'We have not yet implemented for BSD. ' .
666             'Please see "Help with BSD development" in the Unix::Mgt ' .
667             'documents to help with this issue.';
668             }
669            
670             # else Linux style
671             else {
672             # build command
673 0         0 @cmd = (
674             'adduser',
675             '--disabled-password',
676             '--gecos', '',
677             );
678            
679             # if creating as system user
680 0 0       0 if ($opts{'system'})
681 0         0 { push @cmd, '--system' }
682            
683             # add name
684 0         0 push @cmd, $name;
685             }
686            
687             # run command
688 0 0       0 $class->run_cmd('error-creating-user', @cmd) or return undef;
689            
690             # create object
691 0         0 $user = bless({}, $class);
692            
693             # hold on to name
694 0         0 $user->{'name'} = $name;
695            
696             # return
697 0         0 return $user;
698             }
699             #
700             # create
701             #------------------------------------------------------------------------------
702              
703              
704             #------------------------------------------------------------------------------
705             # POD for ensure()
706             #
707              
708             =head2 ensure
709              
710             Unix::Mgt::User->ensure() gets a user account if it already exists, and
711             creates the account if it does not. For example, the following lines ensures
712             the C account:
713              
714             $user = Unix::Mgt::User->ensure('molly');
715              
716             =cut
717              
718             #
719             # POD for ensure()
720             #------------------------------------------------------------------------------
721              
722              
723             #------------------------------------------------------------------------------
724             # field gets
725             #
726              
727             =head2 name
728              
729             Returns the name of the user account. Currently this method cannot be used to
730             set the account name.
731              
732             print $user->name(), "\n";
733              
734             =head2 uid
735              
736             Returns the user's user id (uid).
737              
738             print $user->uid(), "\n";
739              
740             =head2 passwd
741              
742             Returns the password field from C. This method will not actually
743             return a password, it will probably just return C<*>.
744              
745             print $user->passwd(), "\n"; # probably outputs "*"
746              
747             =cut
748              
749             sub field_get {
750 0     0   0 my ($user, $key) = @_;
751 0         0 my ($entry);
752            
753             # TESTING
754             # println subname(method=>1); ##i
755            
756             # reset error
757 0         0 $user->reset_err();
758            
759             # get entry
760 0         0 $entry = $user->entry();
761 0 0       0 $entry or return undef;
762            
763             # return
764 0         0 return $entry->{$key};
765             }
766              
767 0     0   0 sub name { return shift->field_get('name') }
768 0     0   0 sub uid { return shift->field_get('uid') }
769 0     0   0 sub passwd { return shift->field_get('passwd') }
770              
771             #
772             # field gets
773             #------------------------------------------------------------------------------
774              
775              
776             #------------------------------------------------------------------------------
777             # field get|sets
778             #
779              
780             =head2 gid
781              
782             Sets/gets the gid of the user's primary group. Called without params, it
783             returns the user's gid:
784              
785             print $user->gid(), "\n";
786              
787             Called with a single param, gid() sets, then returns the user's primary
788             group id:
789              
790             print $user->gid('1010'), "\n";
791              
792             If you want to get a Unix::Mgt::Group object representing the user's primary
793             group, use $user->group().
794              
795             =head2 dir
796              
797             Sets/gets the user's home directory. Called without params, it returns the
798             directory name:
799              
800             print $user->dir(), "\n";
801              
802             Called with a single param, dir() sets, then returns the user's home directory:
803              
804             print $user->dir('/tmp'), "\n";
805              
806             =head2 shell
807              
808             Sets/gets the user's default command line shell. Called without params, it
809             returns the shell name:
810              
811             print $user->shell(), "\n";
812              
813             Called with a single param, shell() sets, then returns the user's shell:
814              
815             print $user->shell('/bin/sh'), "\n";
816              
817             =cut
818              
819             sub field_get_set {
820 0     0   0 my $user = shift(@_);
821 0         0 my $field = shift(@_);
822 0         0 my $option = shift(@_);
823            
824             # TESTING
825             # println subname(method=>1); ##i
826            
827             # if a value was sent, set the field to that value
828 0 0       0 if (@_) {
829 0         0 my ($value) = @_;
830 0         0 my (@cmd);
831            
832             # safety check
833 0         0 $user->mod_only($user->{'name'});
834            
835             # BSD style
836 0 0       0 if ($user->use_bsd) {
837             # BSD code needed
838            
839 0         0 croak
840             'We have not yet implemented for BSD. ' .
841             'Please see "Help with BSD development" in the Unix::Mgt ' .
842             'documents to help with this issue.';
843             }
844            
845             # else Linux style
846             else {
847             # build command
848 0         0 @cmd = (
849             'usermod',
850             "--$option",
851             $value,
852             $user->{'name'},
853             );
854             }
855            
856             # run command
857 0 0       0 $user->run_cmd("usermod-error-$field", @cmd) or return undef;
858             }
859            
860             # return field
861 0         0 return $user->field_get($field);
862             }
863              
864              
865 0     0   0 sub gid { return shift->field_get_set('gid', 'gid', @_) }
866 0     0   0 sub dir { return shift->field_get_set('dir', 'home', @_) }
867 0     0   0 sub shell { return shift->field_get_set('shell', 'shell', @_) }
868              
869             # sub quota { return shift->field_get_set('quota') }
870             # sub comment { return shift->field_get_set('comment', 'comment', @_) }
871             # sub expire { return shift->field_get_set('expire', 'expiredate', @_) }
872             # sub gecos { return shift->field_get_set('gecos') }
873              
874             #
875             # field get|sets
876             #------------------------------------------------------------------------------
877              
878              
879             #------------------------------------------------------------------------------
880             # group
881             #
882              
883             =head2 group
884              
885             Sets/gets the user's primary group. When called without any params, C
886             returns a Unix::Mgt::Group object representing the user's primary group:
887              
888             $group = $user->group();
889              
890             When called with a single param, C sets the user's primary group. The
891             param can be either the group's name or its gid:
892              
893             $user->group('video');
894             $user->group(44);
895              
896             =cut
897              
898             sub group {
899 0     0   0 my $user = shift(@_);
900 0         0 my ($new_group, %opts) = $user->even_odd_params('new', @_);
901 0         0 my ($entry, $gid, $group);
902            
903             # TESTING
904             # println subname(method=>1); ##i
905            
906             # default options
907 0         0 %opts = (object=>1, %opts);
908            
909             # set new group
910 0 0       0 if (defined $new_group) {
911 0         0 my (@args, $success);
912            
913             # reset error globals
914 0         0 $user->reset_err();
915            
916             # BSD style
917 0 0       0 if ($user->use_bsd) {
918             # BSD code needed
919            
920 0         0 croak
921             'We have not yet implemented for BSD. ' .
922             'Please see "Help with BSD development" in the Unix::Mgt ' .
923             'documents to help with this issue.';
924             }
925            
926             # else Linux style
927             else {
928             # build usermod arguments
929 0         0 @args = (
930             'usermod',
931             '-g',
932             "$new_group",
933             "$user"
934             );
935             }
936            
937             # change user's group
938 0         0 $success = $user->run_cmd('error-setting-user-group', @args);
939 0 0       0 $success or return 0;
940             }
941            
942             # get gid
943 0         0 $gid = $user->gid();
944 0 0       0 defined($gid) or return undef;
945            
946             # get group
947 0         0 $group = Unix::Mgt::Group->get($gid);
948            
949             # return
950 0 0       0 if ($opts{'object'})
951 0         0 { return $group }
952             else
953 0         0 { return $group->name }
954             }
955             #
956             # group
957             #------------------------------------------------------------------------------
958              
959              
960             #------------------------------------------------------------------------------
961             # secondary_groups
962             #
963              
964             =head2 secondary_groups
965              
966             C returns an array of the user's secondary groups. Each
967             element in the array is a Unix::Mgt::Group object.
968              
969             @groups = $user->secondary_groups();
970              
971             =cut
972              
973             sub secondary_groups {
974 0     0   0 my ($user, %opts) = @_;
975 0         0 my (%groups, @rv);
976            
977             # TESTING
978             # println subname(method=>1); ##i
979            
980             # default options
981 0         0 %opts = (object=>1, %opts);
982            
983             # loop through all groups
984 0         0 while (my @fields = getgrent()) {
985 0         0 my (%group);
986 0         0 @group{@Unix::Mgt::Group::field_names} = @fields;
987            
988             # if there are any members, of the group, see if this user is in it
989 0 0       0 if (my $member_str = $group{'members'}) {
990 0         0 my (%members);
991            
992             # parse out members
993 0         0 $member_str = crunch($member_str);
994 0         0 @members{split m|\s+|, $member_str} = ();
995            
996             # if this user is in the membership
997 0 0       0 if (exists $members{$user->{'name'}})
998 0         0 { $groups{$group{'name'}} = 1 }
999             }
1000             }
1001            
1002             # build return value
1003 0         0 foreach my $key (keys %groups) {
1004 0         0 my $group = Unix::Mgt::Group->get($key);
1005            
1006             # set as just string if options indicate to do so
1007 0 0       0 if (! $opts{'object'})
1008 0         0 { $group = $group->{'name'} }
1009            
1010             # add to return array
1011 0         0 push @rv, $group;
1012             }
1013            
1014             # return
1015 0         0 return @rv;
1016             }
1017             #
1018             # secondary_groups
1019             #------------------------------------------------------------------------------
1020              
1021              
1022             #------------------------------------------------------------------------------
1023             # groups
1024             #
1025              
1026             =head2 groups
1027              
1028             C returns an array of all of the groups the user is a member of. The
1029             first element in the array will be the user's primary group.
1030              
1031             @groups = $user->groups();
1032              
1033             =cut
1034              
1035             sub groups {
1036 0     0   0 my ($user, %opts) = @_;
1037 0         0 my (@rv);
1038            
1039             # TESTING
1040             # println subname(method=>1); ##i
1041            
1042             # get user's primary group
1043 0         0 push @rv, $user->group(%opts);
1044            
1045             # add user's secondary groups
1046 0         0 push @rv, $user->secondary_groups(%opts);
1047            
1048             # return
1049 0         0 return @rv;
1050             }
1051             #
1052             # groups
1053             #------------------------------------------------------------------------------
1054              
1055              
1056             #------------------------------------------------------------------------------
1057             # add_to_group
1058             #
1059              
1060             =head2 add_to_group
1061              
1062             C adds the user to a group. The group will be one of the user's
1063             secondary groups, not the primary group.
1064              
1065             $user->add_to_group('video');
1066              
1067             =cut
1068              
1069             sub add_to_group {
1070 0     0   0 my ($user, $group) = @_;
1071 0         0 my (@args, $success);
1072            
1073             # TESTING
1074             # println subname(method=>1); ##i
1075            
1076             # BSD style
1077 0 0       0 if ($user->use_bsd) {
1078             # BSD code needed
1079            
1080 0         0 croak
1081             'We have not yet implemented for BSD. ' .
1082             'Please see "Help with BSD development" in the Unix::Mgt ' .
1083             'documents to help with this issue.';
1084             }
1085            
1086             # else Linux style
1087             else {
1088             # build command arguments
1089 0         0 @args = (
1090             'usermod',
1091             '--append',
1092             '--groups',
1093             "$group",
1094             "$user"
1095             );
1096             }
1097            
1098             # run command
1099 0         0 $success = $user->run_cmd('error-adding-user-to-group', @args);
1100            
1101             # return success|failure
1102 0         0 return $success;
1103             }
1104             #
1105             # add_to_group
1106             #------------------------------------------------------------------------------
1107              
1108              
1109             #------------------------------------------------------------------------------
1110             # remove
1111             #
1112              
1113             =head2 remove
1114              
1115             C removes the user account from the system. C does not take any
1116             parameters.
1117              
1118             $user->remove();
1119              
1120             =cut
1121              
1122             sub remove {
1123 0     0   0 my ($user) = @_;
1124            
1125             # TESTING
1126             # println subname(method=>1); ##i
1127            
1128             # safety check
1129 0         0 $user->mod_only($user);
1130            
1131             # reset error
1132 0         0 $user->reset_err();
1133            
1134             # BSD style
1135 0 0       0 if ($user->use_bsd) {
1136             # BSD code needed
1137            
1138 0         0 croak
1139             'We have not yet implemented for BSD. ' .
1140             'Please see "Help with BSD development" in the Unix::Mgt ' .
1141             'documents to help with this issue.';
1142             }
1143            
1144             # else use userdel
1145             else {
1146 0 0       0 $user->run_cmd('error-deleting-user', 'userdel', $user->name) or return undef;
1147             }
1148            
1149             # return
1150 0         0 return 1;
1151             }
1152             #
1153             # remove
1154             #------------------------------------------------------------------------------
1155              
1156              
1157              
1158             #
1159             # Unix::Mgt::User
1160             ###############################################################################
1161              
1162              
1163              
1164             ###############################################################################
1165             # Unix::Mgt::Group
1166             #
1167             package Unix::Mgt::Group;
1168 1     1   5 use strict;
  1         1  
  1         30  
1169 1     1   3 use String::Util ':all';
  1         2  
  1         151  
1170 1     1   5 use Carp 'croak';
  1         1  
  1         46  
1171 1     1   3 use base 'Unix::Mgt::UGCommon';
  1         1  
  1         1251  
1172              
1173              
1174             # debug tools
1175             # use Debug::ShowStuff ':all';
1176              
1177             # safety mechanism for development
1178             our $MOD_ONLY;
1179              
1180             #------------------------------------------------------------------------------
1181             # POD
1182             #
1183              
1184             =head1 Unix::Mgt::Group
1185              
1186             A Unix::Mgt::Group object represents a group in the Unix system. The object
1187             allows you to get and set information about the group. A group object is
1188             created in one of three ways: C, C, or C. Note that
1189             there is no C method.
1190              
1191             Unix::Mgt::Group objects stringify to the groups's name. For example, the
1192             following code would output C
1193              
1194             $group = Unix::Mgt::Group->get('video');
1195             print $group, "\n";
1196              
1197             =cut
1198              
1199             #
1200             # POD
1201             #------------------------------------------------------------------------------
1202              
1203              
1204             #------------------------------------------------------------------------------
1205             # field_names
1206             #
1207             our @field_names = qw{
1208             name
1209             passwd
1210             gid
1211             members
1212             };
1213             #
1214             # field_names
1215             #------------------------------------------------------------------------------
1216              
1217              
1218             #------------------------------------------------------------------------------
1219             # fields
1220             #
1221             sub fields {
1222 1     1   1 my ($class, $name) = @_;
1223            
1224             # TESTING
1225             # println subname(method=>1); ##i
1226            
1227             # return
1228 1 50       3 if ($name =~ m|^\d+$|s)
1229 0         0 { return getgrgid($name) }
1230             else
1231 1         103 { return getgrnam($name) }
1232             }
1233             #
1234             # fields
1235             #------------------------------------------------------------------------------
1236              
1237              
1238             #------------------------------------------------------------------------------
1239             # get
1240             #
1241              
1242             =head2 get
1243              
1244             Unix::Mgt::Group->get() retrieves group information using C or
1245             C. The single param for this method is either the name or the gid of
1246             the group.
1247              
1248             $group = Unix::Mgt::Group->get('video');
1249             $group = Unix::Mgt::Group->get('44');
1250              
1251             If the group is not found then the C error id is set in
1252             C<$Unix::Mgt::err_id> and undef is returned.
1253              
1254             =cut
1255              
1256             sub get {
1257 2     2   679 my $class = shift(@_);
1258 2         7 my ($name, %opts) = $class->even_odd_params('name', @_);
1259 2         1 my (@fields, $group);
1260            
1261             # TESTING
1262             # println subname(method=>1); ##i
1263            
1264             # reset error globals
1265 2         8 $class->reset_err();
1266            
1267             # check and normalize name
1268 2         6 $name = $class->name_check($name, 'missing-group-name');
1269 2 100       7 $name or return undef;
1270            
1271             # get fields
1272 1         4 @fields = $class->fields($name);
1273            
1274             # if group exists, set name, else throw error
1275 1 50       4 if (@fields) {
1276 0         0 $name = $fields[0];
1277             }
1278             else {
1279 1         3 return $class->set_err(
1280             'do-not-have-group',
1281             $class->called_sub() . qq|: do not find a group with name "$name"|,
1282             );
1283             }
1284            
1285             # create object
1286 0           $group = bless({}, $class);
1287            
1288             # hold on to name
1289 0           $group->{'name'} = $name;
1290            
1291             # return
1292 0           return $group;
1293             }
1294             #
1295             # get
1296             #------------------------------------------------------------------------------
1297              
1298              
1299             #------------------------------------------------------------------------------
1300             # create
1301             #
1302              
1303             =head2 create
1304              
1305             Unix::Mgt::Group->create() creates a group. The required param for this method
1306             is the name for the new group.
1307              
1308             $group = Unix::Mgt::Group->create('websters');
1309              
1310             create() uses the Unix C program.
1311              
1312             =cut
1313              
1314             sub create {
1315 0     0     my $class = shift(@_);
1316 0           my ($name, %opts) = $class->even_odd_params('name', @_);
1317 0           my ($group, @cmd);
1318            
1319             # TESTING
1320             # println subname(method=>1); ##i
1321            
1322             # reset error globals
1323 0           $class->reset_err();
1324            
1325             # check and normalize name
1326 0           $name = $class->name_check($name, 'missing-group-name');
1327 0 0         $name or return undef;
1328            
1329             # if group exists, throw error
1330 0 0         if ($class->fields($name)) {
1331 0           return $class->set_err(
1332             'already-have-group',
1333             $class->called_sub() . qq|: already have a group with name "$name"|,
1334             );
1335             }
1336            
1337             # safety check
1338 0           $class->mod_only($name);
1339            
1340             # BSD style
1341 0 0         if ($class->use_bsd) {
1342             # BSD code needed
1343            
1344 0           croak
1345             'We have not yet implemented for BSD. ' .
1346             'Please see "Help with BSD development" in the Unix::Mgt ' .
1347             'documents to help with this issue.';
1348             }
1349            
1350             # else Linux style
1351             else {
1352             # command
1353 0           push @cmd, 'groupadd';
1354            
1355             # if creating as system group
1356 0 0         if ($opts{'system'})
1357 0           { push @cmd, '--system' }
1358            
1359             # add name
1360 0           push @cmd, $name;
1361             }
1362            
1363             # run command
1364 0 0         $class->run_cmd('error-creating-group', @cmd) or return undef;
1365            
1366             # create object
1367 0           $group = bless({}, $class);
1368            
1369             # hold on to name
1370 0           $group->{'name'} = $name;
1371            
1372             # return
1373 0           return $group;
1374             }
1375             #
1376             # create
1377             #------------------------------------------------------------------------------
1378              
1379              
1380             #------------------------------------------------------------------------------
1381             # POD for ensure()
1382             #
1383              
1384             =head2 ensure
1385              
1386             Unix::Mgt::Group->ensure() gets a group if it already exists, and creates the
1387             group if it does not. For example, the following lines ensures
1388             the C group:
1389              
1390             $group = Unix::Mgt::User->ensure('wbesters');
1391              
1392             =cut
1393              
1394             #
1395             # POD for ensure()
1396             #------------------------------------------------------------------------------
1397              
1398              
1399             #------------------------------------------------------------------------------
1400             # field gets
1401             #
1402              
1403             =head2 name
1404              
1405             Returns the name of the group. Currently this method cannot be used to set the
1406             group name.
1407              
1408             print $group->name(), "\n";
1409              
1410             =head2 gid
1411              
1412             Returns the groups's group id (gid).
1413              
1414             print $group->gid(), "\n";
1415              
1416             =cut
1417              
1418             sub field_get {
1419 0     0     my ($group, $key) = @_;
1420 0           my ($entry);
1421            
1422             # TESTING
1423             # println subname(method=>1); ##i
1424            
1425             # reset error
1426 0           $group->reset_err();
1427            
1428             # get entry
1429 0           $entry = $group->entry();
1430 0 0         $entry or return undef;
1431            
1432             # return
1433 0           return $entry->{$key};
1434             }
1435              
1436 0     0     sub name { return shift->field_get('name') }
1437 0     0     sub gid { return shift->field_get('gid') }
1438              
1439             #
1440             # field gets
1441             #------------------------------------------------------------------------------
1442              
1443              
1444             #------------------------------------------------------------------------------
1445             # entry
1446             #
1447             sub entry {
1448 0     0     my ($group) = @_;
1449 0           my (@fields, %entry);
1450            
1451             # TESTING
1452             # println subname(method=>1); ##i
1453            
1454             # get fields
1455 0           @fields = $group->fields($group->{'name'});
1456            
1457             # if no fields, set error and return undef
1458 0 0         if (! @fields) {
1459 0           return $group->set_err(
1460             'do-not-have-group-entry-anymore',
1461             $group->called_sub() . ': do not have a group with name "' . $group->{'name'} . '"',
1462             );
1463             }
1464            
1465             # set hash
1466 0           @entry{@field_names} = @fields;
1467            
1468             # return
1469 0           return \%entry;
1470             }
1471             #
1472             # entry
1473             #------------------------------------------------------------------------------
1474              
1475              
1476             #------------------------------------------------------------------------------
1477             # members
1478             #
1479              
1480             =head2 members
1481              
1482             C returns an array of all members of the group. Both users for whom
1483             this is the primary group, and users for whom this is a secondary group are
1484             returned.
1485              
1486             @members = $group->members();
1487              
1488             The elements in the array are Unix::Mgt::User objects.
1489              
1490             =cut
1491              
1492             sub members {
1493 0     0     my ($group, %opts) = @_;
1494 0           my (%members, @rv);
1495            
1496             # add users for whom this is their primary group
1497 0           foreach my $user ($group->primary_members(%opts)) {
1498 0           $members{"$user"} = $user;
1499             }
1500            
1501             # add users for whom this is a secondary group
1502 0           foreach my $user ($group->secondary_members(%opts)) {
1503 0           $members{"$user"} = $user;
1504             }
1505            
1506             # build return value
1507 0           @rv = values(%members);
1508            
1509             # return
1510 0           return @rv;
1511             }
1512             #
1513             # members
1514             #------------------------------------------------------------------------------
1515              
1516              
1517             #------------------------------------------------------------------------------
1518             # primary_members
1519             #
1520              
1521             =head2 primary_members
1522              
1523             C returns an array of users for whom this is the primary
1524             group.
1525              
1526             @members = $group->primary_members();
1527              
1528             The elements in the returned array are Unix::Mgt::User objects.
1529              
1530             =cut
1531              
1532             sub primary_members {
1533 0     0     my ($group, %opts) = @_;
1534 0           my ($gid, %members, @rv);
1535            
1536             # TESTING
1537             # println subname(method=>1); ##i
1538            
1539             # default options
1540 0           %opts = (object=>1, %opts);
1541            
1542             # get gid
1543 0           $gid = $group->gid();
1544            
1545             # get users for whom this i
1546 0           while (my @fields = getpwent()) {
1547 0           my (%user);
1548 0           @user{@Unix::Mgt::User::field_names} = @fields;
1549            
1550             # if the user is in the group, add to %members
1551 0 0 0       if ( defined($user{'gid'}) && ($user{'gid'} eq $gid) ) {
1552 0           $members{$user{'name'}} = 1;
1553             }
1554             }
1555            
1556             # build return array of objects
1557 0 0         if ($opts{'object'}) {
1558 0           foreach my $name (keys %members) {
1559 0           push @rv, Unix::Mgt::User->get($name);
1560             }
1561             }
1562            
1563             # else build return array of names
1564             else {
1565 0           @rv = keys(%members);
1566             }
1567            
1568             # return
1569 0           return @rv;
1570             }
1571             #
1572             # primary_members
1573             #------------------------------------------------------------------------------
1574              
1575              
1576             #------------------------------------------------------------------------------
1577             # secondary_members
1578             #
1579              
1580             =head2 secondary_members
1581              
1582             C returns an array of users for whom this is a secondary group.
1583              
1584             @members = $group->secondary_members();
1585              
1586             The elements in the returned array are Unix::Mgt::User objects.
1587              
1588             =cut
1589              
1590             sub secondary_members {
1591 0     0     my ($group, %opts) = @_;
1592 0           my ($gid, $members_str, %members, @rv);
1593            
1594             # TESTING
1595             # println subname(method=>1); ##i
1596            
1597             # default options
1598 0           %opts = (object=>1, %opts);
1599            
1600             # get users for whom this is a secondary group
1601 0           $members_str = $group->entry->{'members'};
1602 0 0         defined($members_str) or return ();
1603            
1604             # loop through members
1605             NAME_LOOP:
1606 0           foreach my $name (split m|\s+|s, $members_str) {
1607 0 0         if (hascontent $name) {
1608 0           my $user = Unix::Mgt::User->get($name);
1609 0           $members{$user->{'name'}} = 1;
1610             }
1611             }
1612            
1613             # build return array of objects
1614 0 0         if ($opts{'object'}) {
1615 0           foreach my $name (keys %members) {
1616 0           push @rv, Unix::Mgt::User->get($name);
1617             }
1618             }
1619            
1620             # else build return array of names
1621             else {
1622 0           @rv = keys(%members);
1623             }
1624            
1625             # return
1626 0           return @rv;
1627             }
1628             #
1629             # secondary_members
1630             #------------------------------------------------------------------------------
1631              
1632              
1633             #------------------------------------------------------------------------------
1634             # add_member
1635             #
1636              
1637             =head2 add_member
1638              
1639             C adds a user to the group as a secondary group. The single
1640             param can be a user name, uid, or Unix::Mgt::User object.
1641              
1642             $group->add_member('miko');
1643              
1644             If the user is already a member of the group then nothing is done and no error
1645             is set.
1646              
1647             =cut
1648              
1649             sub add_member {
1650 0     0     my ($group, $user) = @_;
1651            
1652             # TESTING
1653             # println subname(method=>1); ##i
1654            
1655             # get user object
1656 0 0         if (! ref $user)
1657 0           { $user = Unix::Mgt::User->get($user) }
1658            
1659             # add user to group
1660 0           return $user->add_to_group($group);
1661             }
1662             #
1663             # add_member
1664             #------------------------------------------------------------------------------
1665              
1666              
1667             #------------------------------------------------------------------------------
1668             # remove
1669             #
1670              
1671             =head2 remove
1672              
1673             C removes the group from the system. C does not take any
1674             parameters.
1675              
1676             $group->remove();
1677              
1678             If any users have the group as a primary group then this method will fail.
1679              
1680             =cut
1681              
1682             sub remove {
1683 0     0     my ($group) = @_;
1684            
1685             # TESTING
1686             # println subname(method=>1); ##i
1687            
1688             # safety check
1689 0           $group->mod_only($group);
1690            
1691             # reset error
1692 0           $group->reset_err();
1693            
1694             # cannot remove if any users have this group as primary group
1695 0 0         if (my @primaries = $group->primary_members) {
1696 0           my ($id, $msg);
1697            
1698             # ste id
1699 0           $id = 'cannot-remove-primary-group';
1700            
1701             # begin message
1702 0           $msg =
1703             'cannot remove the group "' . $group->name() .
1704             '" because it is the primary group for ';
1705            
1706             # plural
1707 0 0         if (@primaries > 1) {
1708 0           $msg .= 'the following users: ' . join(', ', @primaries);
1709             }
1710            
1711             # singular
1712             else {
1713 0           $msg .= 'the user "' . $primaries[0] . '"';
1714             }
1715            
1716             # return failure
1717 0           return $group->set_err($id, $msg);
1718             }
1719            
1720             # if using pw command
1721 0 0         if ($group->use_bsd) {
1722             # BSD code needed
1723            
1724 0           croak
1725             'We have not yet implemented for BSD. ' .
1726             'Please see "Help with BSD development" in the Unix::Mgt ' .
1727             'documents to help with this issue.';
1728             }
1729            
1730             # else use groupdel
1731             else {
1732 0 0         $group->run_cmd('error-deleting-group', 'groupdel', $group->name) or return undef;
1733             }
1734            
1735             # return
1736 0           return 1;
1737             }
1738             #
1739             # remove
1740             #------------------------------------------------------------------------------
1741              
1742              
1743             #
1744             # Unix::Mgt::Group
1745             ###############################################################################
1746              
1747              
1748              
1749             # return true
1750             1;
1751              
1752             __END__