| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sys::Export::Unix::UserDB; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Abstractions for Unix passwd/group/shadow files |
|
4
|
|
|
|
|
|
|
our $VERSION = '0.003'; # VERSION |
|
5
|
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
295445
|
use v5.26; |
|
|
2
|
|
|
|
|
6
|
|
|
7
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
140
|
|
|
8
|
2
|
|
|
2
|
|
12
|
use experimental qw( signatures ); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
13
|
|
|
9
|
2
|
|
|
2
|
|
289
|
use Carp (); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
61
|
|
|
10
|
2
|
|
|
2
|
|
1030
|
use File::Spec::Functions qw( catfile ); |
|
|
2
|
|
|
|
|
1727
|
|
|
|
2
|
|
|
|
|
167
|
|
|
11
|
2
|
|
|
2
|
|
749
|
use Storable qw( dclone ); |
|
|
2
|
|
|
|
|
5350
|
|
|
|
2
|
|
|
|
|
147
|
|
|
12
|
2
|
|
|
2
|
|
13
|
use Scalar::Util (); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
52
|
|
|
13
|
2
|
|
|
2
|
|
1174
|
use User::pwent qw( getpwnam pw_has ); |
|
|
2
|
|
|
|
|
13654
|
|
|
|
2
|
|
|
|
|
194
|
|
|
14
|
2
|
|
|
2
|
|
693
|
use Sys::Export qw( :isa ); |
|
|
2
|
|
|
|
|
7
|
|
|
|
2
|
|
|
|
|
12
|
|
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# making lexical subs allows these to be seen by inner packages as well |
|
17
|
|
|
|
|
|
|
# and removes need for namespace::clean |
|
18
|
2
|
|
|
2
|
|
357
|
my sub carp { goto \&Carp::carp } |
|
19
|
9
|
|
|
9
|
|
1694
|
my sub croak { goto \&Carp::croak } |
|
20
|
51
|
|
|
51
|
|
266
|
my sub isa_hash :prototype($) { ref $_[0] eq 'HASH' } |
|
21
|
24
|
|
|
24
|
|
84
|
my sub isa_array :prototype($) { ref $_[0] eq 'ARRAY' } |
|
22
|
119
|
100
|
|
119
|
|
789
|
my sub isa_int :prototype($) { Scalar::Util::looks_like_number($_[0]) && int($_[0]) == $_[0] } |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
|
25
|
13
|
|
|
13
|
1
|
293023
|
sub new($class, @args) { |
|
|
13
|
|
|
|
|
30
|
|
|
|
13
|
|
|
|
|
55
|
|
|
|
13
|
|
|
|
|
19
|
|
|
26
|
13
|
100
|
66
|
|
|
79
|
my %args= @args == 1 && isa_hash($args[0])? $args[0]->%* : @args; |
|
27
|
13
|
|
|
|
|
85
|
my $self = bless { |
|
28
|
|
|
|
|
|
|
users => {}, |
|
29
|
|
|
|
|
|
|
uids => {}, |
|
30
|
|
|
|
|
|
|
groups => {}, |
|
31
|
|
|
|
|
|
|
gids => {}, |
|
32
|
|
|
|
|
|
|
}, $class; |
|
33
|
|
|
|
|
|
|
# I don't want to declare this as an official attribute because a subclass might decide |
|
34
|
|
|
|
|
|
|
# to implement is_valid_name using something other than a regex, and then the attribute |
|
35
|
|
|
|
|
|
|
# would be inconsistent. |
|
36
|
13
|
50
|
|
|
|
47
|
$self->{valid_name_regex}= $args{valid_name_regex} if defined $args{valid_name_regex}; |
|
37
|
|
|
|
|
|
|
# Extract initial user and group lists before initializing attributes, then apply afterward |
|
38
|
13
|
|
|
|
|
31
|
my $g= delete $args{groups}; |
|
39
|
13
|
|
|
|
|
30
|
my $u= delete $args{users}; |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# Apply writable attributes and/or initial methods |
|
42
|
13
|
|
|
|
|
42
|
for (keys %args) { |
|
43
|
5
|
100
|
|
|
|
49
|
croak "Unknown option '$_'" unless $self->can($_); |
|
44
|
4
|
|
|
|
|
18
|
$self->$_($args{$_}); |
|
45
|
|
|
|
|
|
|
} |
|
46
|
12
|
100
|
|
|
|
36
|
if ($g) { |
|
47
|
2
|
50
|
|
|
|
7
|
if (isa_array $g) { |
|
|
|
50
|
|
|
|
|
|
|
48
|
0
|
|
|
|
|
0
|
$self->add_group($_) for $g->@*; |
|
49
|
|
|
|
|
|
|
} elsif (isa_hash $g) { |
|
50
|
2
|
|
|
|
|
13
|
$self->add_group($_ => $g->{$_}->%*) for keys %$g; |
|
51
|
|
|
|
|
|
|
} else { |
|
52
|
0
|
|
|
|
|
0
|
croak "Option 'groups' must be arrayref or hashref of group objects"; |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
} |
|
55
|
12
|
100
|
|
|
|
47
|
if ($u) { |
|
56
|
2
|
50
|
|
|
|
5
|
if (isa_array $u) { |
|
|
|
50
|
|
|
|
|
|
|
57
|
0
|
|
|
|
|
0
|
$self->add_user($_) for $u->@*; |
|
58
|
|
|
|
|
|
|
} elsif (isa_hash $u) { |
|
59
|
2
|
|
|
|
|
15
|
$self->add_user($_ => $u->{$_}->%*) for keys %$u; |
|
60
|
|
|
|
|
|
|
} else { |
|
61
|
0
|
|
|
|
|
0
|
croak "Option 'users' must be arrayref or hashref of user objects"; |
|
62
|
|
|
|
|
|
|
} |
|
63
|
|
|
|
|
|
|
} |
|
64
|
12
|
|
|
|
|
49
|
return $self; |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
|
|
68
|
13
|
|
|
13
|
1
|
705
|
sub users($self) { $self->{users} } |
|
|
13
|
|
|
|
|
23
|
|
|
|
13
|
|
|
|
|
22
|
|
|
|
13
|
|
|
|
|
139
|
|
|
69
|
|
|
|
|
|
|
|
|
70
|
1
|
|
|
1
|
1
|
5
|
sub uids($self) { $self->{uids} } |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
46
|
|
|
71
|
|
|
|
|
|
|
|
|
72
|
5
|
|
|
5
|
1
|
967
|
sub groups($self) { $self->{groups} } |
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
7
|
|
|
|
5
|
|
|
|
|
37
|
|
|
73
|
|
|
|
|
|
|
|
|
74
|
1
|
|
|
1
|
1
|
2
|
sub gids($self) { $self->{gids} } |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
8
|
|
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
|
|
77
|
66
|
|
|
66
|
1
|
80
|
sub auto_import($self, @val) { |
|
|
66
|
|
|
|
|
85
|
|
|
|
66
|
|
|
|
|
78
|
|
|
|
66
|
|
|
|
|
77
|
|
|
78
|
|
|
|
|
|
|
# coerce anything other than a userdb into a boolean |
|
79
|
|
|
|
|
|
|
@val? ($self->{auto_import}= isa_userdb($val[0])? $val[0] : !!$val[0]) : $self->{auto_import} |
|
80
|
66
|
100
|
|
|
|
220
|
} |
|
|
|
100
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
|
|
83
|
1
|
|
|
1
|
1
|
2
|
sub clone($self) { |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
3
|
|
|
84
|
1
|
|
|
|
|
185
|
return dclone($self); |
|
85
|
|
|
|
|
|
|
} |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
|
|
88
|
91
|
|
|
91
|
1
|
147
|
sub is_valid_name($self, $name) { |
|
|
91
|
|
|
|
|
132
|
|
|
|
91
|
|
|
|
|
129
|
|
|
|
91
|
|
|
|
|
121
|
|
|
89
|
|
|
|
|
|
|
defined $self->{valid_name_regex}? scalar( $name =~ $self->{valid_name_regex} ) |
|
90
|
91
|
50
|
|
|
|
518
|
: scalar( $name =~ /^[A-Za-z_][-A-Za-z0-9_.]{0,30}[-A-Za-z0-9_.\$]?\z/ ) |
|
91
|
|
|
|
|
|
|
} |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
|
|
94
|
3
|
|
|
3
|
1
|
504
|
sub load($self, $path, %options) { |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
6
|
|
|
95
|
3
|
50
|
|
|
|
12
|
croak "Path is required" unless defined $path; |
|
96
|
3
|
|
|
|
|
48
|
my $passwd_file = catfile($path, 'passwd'); |
|
97
|
3
|
|
|
|
|
16
|
my $group_file = catfile($path, 'group'); |
|
98
|
3
|
|
|
|
|
14
|
my $shadow_file = catfile($path, 'shadow'); |
|
99
|
|
|
|
|
|
|
|
|
100
|
3
|
50
|
|
|
|
21
|
unless ($options{format}) { |
|
101
|
|
|
|
|
|
|
# If we have passwd, group, and shadow, assume Linux. |
|
102
|
3
|
50
|
66
|
|
|
265
|
$options{format}= (-f $passwd_file && -f $group_file && -f $shadow_file)? 'Linux' |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
: croak "Unable to detect format: passwd ".(-f $passwd_file? "[found]":"[not found]") |
|
104
|
|
|
|
|
|
|
." group ".(-f $group_file? "[found]" : "[not found]") |
|
105
|
|
|
|
|
|
|
." shadow ".(-f $shadow_file? "[found]" : "[not found]"); |
|
106
|
|
|
|
|
|
|
} |
|
107
|
2
|
|
|
|
|
6
|
my $records; |
|
108
|
2
|
50
|
|
|
|
9
|
if ($options{format} eq 'Linux') { |
|
109
|
2
|
50
|
|
|
|
10
|
$records= $self->_parse_linux_passwd_format({ |
|
110
|
|
|
|
|
|
|
passwd => _slurp($passwd_file), |
|
111
|
|
|
|
|
|
|
group => _slurp($group_file), |
|
112
|
|
|
|
|
|
|
(-r $shadow_file? ( shadow => _slurp($shadow_file) ) : ()), |
|
113
|
|
|
|
|
|
|
}); |
|
114
|
|
|
|
|
|
|
} else { |
|
115
|
0
|
|
|
|
|
0
|
croak "Unsupported format '$options{format}'"; |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# convert user primary gid to group name |
|
119
|
2
|
|
|
|
|
10
|
my (%group_by_name, %group_by_gid, %user_by_name); |
|
120
|
2
|
|
|
|
|
8
|
for ($records->{groups}->@*) { |
|
121
|
9
|
|
33
|
|
|
45
|
$group_by_name{$_->{name}} //= $_; |
|
122
|
9
|
|
33
|
|
|
40
|
$group_by_gid{$_->{gid}} //= $_; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
2
|
|
|
|
|
7
|
for ($records->{users}->@*) { |
|
125
|
7
|
|
33
|
|
|
34
|
$user_by_name{$_->{name}} //= $_; |
|
126
|
7
|
|
|
|
|
15
|
my $gid= delete $_->{gid}; |
|
127
|
7
|
50
|
|
|
|
19
|
if (my $primary_group= $group_by_gid{$gid}) { |
|
128
|
7
|
|
|
|
|
27
|
$_->{group}= $primary_group->{name}; |
|
129
|
|
|
|
|
|
|
} else { |
|
130
|
0
|
|
|
|
|
0
|
carp "User '$_->{name}' references non-existent gid $gid"; |
|
131
|
|
|
|
|
|
|
# This allows the gid to pass through for a round trip, but will generate |
|
132
|
|
|
|
|
|
|
# warning in various places. |
|
133
|
0
|
|
|
|
|
0
|
$_->{group}= $gid; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# move group membership to user records |
|
138
|
2
|
|
|
|
|
7
|
for my $g ($records->{groups}->@*) { |
|
139
|
9
|
100
|
|
|
|
28
|
if (my $members= delete $g->{members}) { |
|
140
|
4
|
|
|
|
|
14
|
for (split ',', $members) { |
|
141
|
|
|
|
|
|
|
my $u= $user_by_name{$_} |
|
142
|
6
|
50
|
|
|
|
49
|
or do { carp "Group '$g->{name}' references non-existent user '$_'"; next; }; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
143
|
6
|
|
|
|
|
10
|
push @{$u->{groups}}, $g->{name}; |
|
|
6
|
|
|
|
|
24
|
|
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
$self->_add_group_object(Sys::Export::Unix::UserDB::Group->new(%$_)) |
|
149
|
2
|
|
|
|
|
28
|
for $records->{groups}->@*; |
|
150
|
|
|
|
|
|
|
$self->_add_user_object(Sys::Export::Unix::UserDB::User->new(%$_)) |
|
151
|
2
|
|
|
|
|
30
|
for $records->{users}->@*; |
|
152
|
|
|
|
|
|
|
|
|
153
|
2
|
|
|
|
|
41
|
return $self; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
|
|
157
|
4
|
|
|
4
|
1
|
462
|
sub save($self, $target, %options) { |
|
|
4
|
|
|
|
|
15
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
8
|
|
|
158
|
4
|
|
33
|
|
|
40
|
my $format= $options{format} // $self->{default_format} // 'Linux'; |
|
|
|
|
50
|
|
|
|
|
|
159
|
4
|
|
|
|
|
8
|
my $data; |
|
160
|
4
|
50
|
|
|
|
17
|
if ($format eq 'Linux') { |
|
161
|
4
|
|
|
|
|
15
|
$data= $self->_generate_linux_passwd_format(%options); |
|
162
|
|
|
|
|
|
|
} else { |
|
163
|
0
|
|
|
|
|
0
|
croak "Unsupported format $options{format}"; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
4
|
100
|
|
|
|
15
|
if (isa_hash $target) { |
|
166
|
2
|
|
|
|
|
43
|
%$target= %$data; |
|
167
|
|
|
|
|
|
|
} else { |
|
168
|
2
|
50
|
|
|
|
62
|
croak "path does not exist: '$target'" unless -e $target; |
|
169
|
2
|
|
|
|
|
12
|
for (keys %$data) { |
|
170
|
6
|
|
100
|
|
|
33
|
my $public= $_ eq 'passwd' || $_ eq 'group'; |
|
171
|
6
|
100
|
|
|
|
69
|
_mkfile(catfile($target, $_), $data->{$_}, $public? 0755 : 0700); |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
} |
|
174
|
4
|
|
|
|
|
31
|
return $self; |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
|
|
177
|
6
|
|
|
6
|
|
12
|
sub _mkfile($name, $data, $mode=undef) { |
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
10
|
|
|
|
6
|
|
|
|
|
16
|
|
|
|
6
|
|
|
|
|
11
|
|
|
178
|
6
|
50
|
|
|
|
1261
|
open my $fh, '>:raw', $name or croak "open(>$name): $!"; |
|
179
|
6
|
50
|
|
|
|
58
|
$fh->print($data) or croak "write($name): $!"; |
|
180
|
6
|
50
|
|
|
|
95
|
$fh->close or croak "close($name): $!"; |
|
181
|
6
|
50
|
33
|
|
|
658
|
chmod $mode, $name or croak "chmod($name, $mode): $!" |
|
182
|
|
|
|
|
|
|
if defined $mode; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
6
|
|
|
6
|
|
14
|
sub _slurp($name) { |
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
12
|
|
|
185
|
6
|
50
|
|
|
|
224
|
open my $fh, '<:raw', $name or die "open(<$name): $!"; |
|
186
|
6
|
|
|
|
|
31
|
local $/; |
|
187
|
6
|
|
|
|
|
230
|
my $ret= scalar <$fh>; |
|
188
|
6
|
50
|
|
|
|
71
|
close $fh or die "close($name): $!"; |
|
189
|
6
|
|
|
|
|
121
|
$ret; |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
our @_linux_shadow_fields= qw( passwd pw_change_time pw_min_days pw_max_days pw_warn_days pw_inactive_days expire_time ); |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# Unix time ignores leap seconds, so the conversion from seconds to days is simple division |
|
195
|
10
|
100
|
|
10
|
|
15
|
sub _time_to_days_since_1970($t) { !length $t? undef : int($t / 86400) } |
|
|
10
|
|
|
|
|
19
|
|
|
|
10
|
|
|
|
|
16
|
|
|
|
10
|
|
|
|
|
219
|
|
|
196
|
8
|
100
|
|
8
|
|
12
|
sub _days_since_1970_to_time($d) { !length $d? undef : $d * 86400; } |
|
|
8
|
|
|
|
|
16
|
|
|
|
8
|
|
|
|
|
12
|
|
|
|
8
|
|
|
|
|
37
|
|
|
197
|
|
|
|
|
|
|
|
|
198
|
2
|
|
|
2
|
|
5
|
sub _parse_linux_passwd_format($self, $files) { |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
4
|
|
|
199
|
2
|
|
|
|
|
7
|
my @users; |
|
200
|
|
|
|
|
|
|
my %users; |
|
201
|
2
|
|
|
|
|
14
|
for (split "\n", $files->{passwd}) { |
|
202
|
7
|
50
|
|
|
|
31
|
next if /^\s*(#|\z)/; |
|
203
|
7
|
|
|
|
|
14
|
my %r; |
|
204
|
7
|
|
|
|
|
59
|
@r{qw( name pw_flag uid gid gecos dir shell )}= split ':'; |
|
205
|
7
|
|
|
|
|
17
|
delete $r{pw_flag}; |
|
206
|
7
|
|
|
|
|
33
|
push @users, \%r; |
|
207
|
7
|
|
50
|
|
|
45
|
$users{$r{name}} //= \%r; |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
|
|
210
|
2
|
|
50
|
|
|
14
|
for (split "\n", ($files->{shadow}//'')) { |
|
211
|
4
|
50
|
|
|
|
20
|
next if /^\s*(#|\z)/; |
|
212
|
4
|
|
|
|
|
21
|
my ($name,@vals)= split ':'; |
|
213
|
|
|
|
|
|
|
my $r= $users{$name} |
|
214
|
4
|
50
|
|
|
|
17
|
or do { carp "Found shadow entry for non-existent user '$name'"; next; }; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
215
|
4
|
|
|
|
|
10
|
@{$r}{@_linux_shadow_fields}= @vals; |
|
|
4
|
|
|
|
|
30
|
|
|
216
|
4
|
|
|
|
|
15
|
$r->{pw_change_time}= _days_since_1970_to_time($r->{pw_change_time}); |
|
217
|
4
|
|
|
|
|
11
|
$r->{expire_time}= _days_since_1970_to_time($r->{expire_time}); |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
|
|
220
|
2
|
|
|
|
|
5
|
my @groups; |
|
221
|
2
|
|
|
|
|
10
|
for (split "\n", $files->{group}) { |
|
222
|
9
|
50
|
|
|
|
31
|
next if /^\s*(#|\z)/; |
|
223
|
9
|
|
|
|
|
17
|
my %r; |
|
224
|
9
|
|
|
|
|
38
|
@r{qw( name passwd gid members )}= split ":"; |
|
225
|
9
|
|
|
|
|
24
|
push @groups, \%r; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
2
|
|
|
|
|
15
|
return { users => \@users, groups => \@groups }; |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
|
|
230
|
4
|
|
|
4
|
|
9
|
sub _generate_linux_passwd_format($self, %options) { |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
7
|
|
|
231
|
4
|
|
|
|
|
15
|
my ($passwd, $group, $shadow)= ('','',''); |
|
232
|
4
|
|
|
|
|
49
|
my @users= sort { $a->uid <=> $b->uid } values $self->{users}->%*; |
|
|
5
|
|
|
|
|
19
|
|
|
233
|
4
|
|
|
|
|
17
|
my @groups= sort { $a->gid <=> $b->gid } values $self->{groups}->%*; |
|
|
11
|
|
|
|
|
27
|
|
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Generate passwd content |
|
236
|
4
|
|
|
|
|
15
|
for my $user (@users) { |
|
237
|
9
|
|
|
|
|
15
|
my $gid; |
|
238
|
9
|
50
|
|
|
|
24
|
if ($user->group) { |
|
239
|
9
|
50
|
|
|
|
26
|
my $group= $self->group($user->group) |
|
240
|
|
|
|
|
|
|
or croak "User '".$user->name."' has invalid group '".$user->group."'"; |
|
241
|
9
|
|
|
|
|
54
|
$gid= $group->gid; |
|
242
|
|
|
|
|
|
|
} else { |
|
243
|
0
|
0
|
|
|
|
0
|
$gid= $user->gid |
|
244
|
|
|
|
|
|
|
or croak "User '".$user->name."' lacks 'group' or 'gid' attribute"; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
# If shadow fields exist, write a 'x' in passwd, else '*'. |
|
247
|
9
|
|
|
|
|
21
|
my $pw_flag= '*'; |
|
248
|
9
|
100
|
|
|
|
20
|
if (grep defined, @{$user}{@_linux_shadow_fields}) { |
|
|
9
|
|
|
|
|
69
|
|
|
249
|
5
|
|
|
|
|
8
|
$pw_flag= 'x'; |
|
250
|
5
|
|
50
|
|
|
16
|
$shadow .= sprintf "%s:%s:%s:%s:%s:%s:%s:%s:%s\n", |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
251
|
|
|
|
|
|
|
$user->name, |
|
252
|
|
|
|
|
|
|
$user->passwd // '*', |
|
253
|
|
|
|
|
|
|
_time_to_days_since_1970($user->pw_change_time) // '', |
|
254
|
|
|
|
|
|
|
$user->pw_min_days // '', |
|
255
|
|
|
|
|
|
|
$user->pw_max_days // '', |
|
256
|
|
|
|
|
|
|
$user->pw_warn_days // '', |
|
257
|
|
|
|
|
|
|
$user->pw_inactive_days // '', |
|
258
|
|
|
|
|
|
|
_time_to_days_since_1970($user->expire_time) // '', |
|
259
|
|
|
|
|
|
|
''; # reserved field, no idea what to name it in user object... |
|
260
|
|
|
|
|
|
|
} |
|
261
|
9
|
|
100
|
|
|
31
|
$passwd .= sprintf "%s:%s:%d:%d:%s:%s:%s\n", |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
262
|
|
|
|
|
|
|
$user->name, $pw_flag, $user->uid, $gid, |
|
263
|
|
|
|
|
|
|
$user->gecos//'', $user->dir//'', $user->shell//''; |
|
264
|
|
|
|
|
|
|
} |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Generate group content |
|
267
|
4
|
|
|
|
|
11
|
for my $g (@groups) { |
|
268
|
12
|
|
|
|
|
32
|
my $grnam= $g->name; |
|
269
|
|
|
|
|
|
|
# Collect members from users who have this group, excluding users whose primary |
|
270
|
|
|
|
|
|
|
# group is already the group. |
|
271
|
12
|
|
100
|
|
|
37
|
my @members= map $_->name, grep $_->groups->{$grnam} && ($_->group//'') ne $grnam, @users; |
|
272
|
12
|
|
100
|
|
|
91
|
$group .= sprintf "%s:%s:%d:%s\n", |
|
273
|
|
|
|
|
|
|
$grnam, $g->passwd // '*', $g->gid, join ',', sort @members; |
|
274
|
|
|
|
|
|
|
} |
|
275
|
|
|
|
|
|
|
|
|
276
|
4
|
|
|
|
|
34
|
return { passwd => $passwd, group => $group, shadow => $shadow }; |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
|
|
280
|
3
|
|
|
3
|
1
|
5
|
sub import_user($self, $name_or_obj, %attrs) { |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
5
|
|
|
281
|
3
|
50
|
66
|
|
|
17
|
ref $name_or_obj or length $name_or_obj or croak "Attempt to import empty username"; |
|
282
|
3
|
100
|
66
|
|
|
8
|
if (isa_hash($name_or_obj) || isa_user($name_or_obj)) { |
|
|
|
50
|
|
|
|
|
|
|
283
|
2
|
|
|
|
|
8
|
%attrs= ( %$name_or_obj, %attrs ); |
|
284
|
|
|
|
|
|
|
} elsif (keys %attrs) { |
|
285
|
0
|
|
|
|
|
0
|
$attrs{name}= "$name_or_obj"; |
|
286
|
|
|
|
|
|
|
} else { |
|
287
|
1
|
50
|
|
|
|
10
|
my $pw= getpwnam($name_or_obj) |
|
288
|
|
|
|
|
|
|
or croak "User '$name_or_obj' not found in system"; |
|
289
|
1
|
|
|
|
|
1050
|
$attrs{name}= $pw->name; |
|
290
|
1
|
|
|
|
|
30
|
$attrs{passwd}= $pw->passwd; |
|
291
|
1
|
|
|
|
|
31
|
$attrs{uid}= $pw->uid; |
|
292
|
1
|
50
|
|
|
|
12
|
$attrs{quota}= $pw->quota if pw_has('quota'); # BSD |
|
293
|
1
|
50
|
|
|
|
58
|
$attrs{class}= $pw->class if pw_has('class'); # BSD |
|
294
|
1
|
50
|
|
|
|
25
|
$attrs{comment}= $pw->comment if pw_has('comment'); # always empty on Linux and BSD... |
|
295
|
1
|
50
|
|
|
|
21
|
$attrs{gecos}= $pw->gecos if pw_has('gecos'); |
|
296
|
1
|
|
|
|
|
72
|
$attrs{dir}= $pw->dir; |
|
297
|
1
|
|
|
|
|
30
|
$attrs{shell}= $pw->shell; |
|
298
|
1
|
50
|
|
|
|
12
|
if (pw_has('expire')) { |
|
299
|
|
|
|
|
|
|
# Normalize this field to epoch seconds, based on the current platform. |
|
300
|
|
|
|
|
|
|
# FreeBSD has expire in seconds. Linux has an expire field of days, in /etc/shadow, but |
|
301
|
|
|
|
|
|
|
# all tests on Linux so far have had pw_has('expire') = false. |
|
302
|
|
|
|
|
|
|
# getpwnam dies with 'not implemented' on Strawberry perl for Windows. |
|
303
|
0
|
0
|
|
|
|
0
|
$attrs{expire_time}= $^O eq 'FreeBSD'? $pw->expire |
|
|
|
0
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
: $^O eq 'linux'? _days_since_1970_to_time($pw->expire) |
|
305
|
|
|
|
|
|
|
: undef; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
# convert gid to group name |
|
308
|
1
|
|
|
|
|
42
|
my $gid= $pw->gid; |
|
309
|
1
|
50
|
0
|
|
|
164
|
if (my $grnam= getgrgid($gid)) { |
|
|
|
0
|
|
|
|
|
|
|
310
|
1
|
|
|
|
|
19
|
$attrs{group}= $grnam; |
|
311
|
|
|
|
|
|
|
} elsif ($gid == $pw->uid || $pw->uid < 1000) { |
|
312
|
|
|
|
|
|
|
# If the group wasn't found, but the uid and gid are identical and look like system |
|
313
|
|
|
|
|
|
|
# accounts then assume the group will be the same name. |
|
314
|
0
|
|
|
|
|
0
|
$attrs{group}= $pw->name; |
|
315
|
|
|
|
|
|
|
} else { |
|
316
|
0
|
|
|
|
|
0
|
carp "User '$name_or_obj' primary group $gid doesn't exist"; |
|
317
|
0
|
|
|
|
|
0
|
$attrs{group}= 'nogroup'; # it has to be something. Could croak instead of this... |
|
318
|
|
|
|
|
|
|
} |
|
319
|
|
|
|
|
|
|
} |
|
320
|
3
|
|
50
|
|
|
10
|
$attrs{group} //= 'nogroup'; |
|
321
|
3
|
|
100
|
|
|
14
|
$attrs{groups} //= {}; |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# Check for UID collision |
|
324
|
|
|
|
|
|
|
defined $attrs{uid} |
|
325
|
3
|
50
|
|
|
|
9
|
or croak "Can't import user $attrs{name} without a 'uid'"; |
|
326
|
3
|
50
|
|
|
|
16
|
if ($self->{uids}{$attrs{uid}}) { |
|
327
|
0
|
0
|
|
|
|
0
|
if ($attrs{uid} >= 1000) { |
|
328
|
0
|
|
|
|
|
0
|
++$attrs{uid} while exists $self->{uids}{$attrs{uid}}; |
|
329
|
|
|
|
|
|
|
} else { |
|
330
|
0
|
|
|
|
|
0
|
for (101..999) { |
|
331
|
0
|
0
|
|
|
|
0
|
if (!exists $self->{uids}{$_}) { |
|
332
|
0
|
|
|
|
|
0
|
$attrs{uid}= $_; |
|
333
|
0
|
|
|
|
|
0
|
last; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
croak "No available UIDs below 1000 for $attrs{name}" |
|
337
|
0
|
0
|
|
|
|
0
|
if $self->{uids}{$attrs{uid}}; |
|
338
|
|
|
|
|
|
|
} |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# do the groups exist? Calling ->group will trigger auto_import if enabled. |
|
342
|
3
|
|
|
|
|
16
|
for my $gname (grep !$self->group($_), $attrs{group}, keys $attrs{groups}->%*) { |
|
343
|
|
|
|
|
|
|
# Is the group name the same as the user name? Try creating with GID = UID |
|
344
|
0
|
0
|
|
|
|
0
|
if ($gname eq $attrs{name}) { |
|
|
|
0
|
|
|
|
|
|
|
345
|
0
|
|
|
|
|
0
|
$self->import_group($gname, gid => $attrs{uid}); |
|
346
|
|
|
|
|
|
|
} elsif ($gname =~ /^(nobody|nogroup)/) { |
|
347
|
0
|
|
|
|
|
0
|
$self->import_group($gname, gid => 65534); |
|
348
|
|
|
|
|
|
|
} else { |
|
349
|
0
|
|
|
|
|
0
|
croak "User '$attrs{name}' references non-existent group '$gname'"; |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
|
|
353
|
3
|
|
|
|
|
18
|
my $u= Sys::Export::Unix::UserDB::User->new(%attrs); |
|
354
|
3
|
|
|
|
|
12
|
$self->_add_user_object($u); |
|
355
|
3
|
|
|
|
|
16
|
return $u; |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
|
|
359
|
4
|
|
|
4
|
1
|
6
|
sub import_group($self, $name_or_obj, %attrs) { |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
5
|
|
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
4
|
|
|
360
|
4
|
50
|
66
|
|
|
13
|
ref $name_or_obj or length $name_or_obj or croak "Attempt to import empty username"; |
|
361
|
4
|
100
|
66
|
|
|
9
|
if (isa_hash($name_or_obj) || isa_group($name_or_obj)) { |
|
|
|
50
|
|
|
|
|
|
|
362
|
3
|
|
|
|
|
8
|
%attrs= ( %$name_or_obj, %attrs ); |
|
363
|
|
|
|
|
|
|
} elsif (keys %attrs) { |
|
364
|
0
|
|
|
|
|
0
|
$attrs{name}= "$name_or_obj"; |
|
365
|
|
|
|
|
|
|
} else { |
|
366
|
1
|
50
|
|
|
|
74
|
my ($grnam, $passwd, $gid, $members) = getgrnam($name_or_obj) |
|
367
|
|
|
|
|
|
|
or croak "Group '$name_or_obj' not found in system"; |
|
368
|
1
|
|
|
|
|
7
|
$attrs{name}= $grnam; |
|
369
|
1
|
|
|
|
|
5
|
$attrs{passwd}= $passwd; |
|
370
|
1
|
|
|
|
|
3
|
$attrs{gid}= $gid; |
|
371
|
1
|
|
|
|
|
5
|
$attrs{members}= $members; |
|
372
|
|
|
|
|
|
|
} |
|
373
|
4
|
|
|
|
|
8
|
my $members= delete $attrs{members}; |
|
374
|
4
|
|
|
|
|
20
|
my $g= Sys::Export::Unix::UserDB::Group->new(%attrs); |
|
375
|
4
|
|
|
|
|
14
|
$self->_add_group_object($g); |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# Can't store member list in group, so store these for later when a user gets added |
|
378
|
4
|
100
|
|
|
|
11
|
if (defined $members) { |
|
379
|
|
|
|
|
|
|
$self->_lazy_add_user_to_group($_, $attrs{name}) |
|
380
|
1
|
50
|
|
|
|
4
|
for (isa_array $members? @$members |
|
|
|
50
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
: isa_hash $members? keys %$members |
|
382
|
|
|
|
|
|
|
: split / /, $members); |
|
383
|
|
|
|
|
|
|
} |
|
384
|
4
|
|
|
|
|
16
|
return $g; |
|
385
|
|
|
|
|
|
|
} |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
|
|
388
|
15
|
|
|
15
|
1
|
749
|
sub add_user($self, $name_or_obj, %attrs) { |
|
|
15
|
|
|
|
|
38
|
|
|
|
15
|
|
|
|
|
23
|
|
|
|
15
|
|
|
|
|
56
|
|
|
|
15
|
|
|
|
|
22
|
|
|
389
|
15
|
100
|
66
|
|
|
37
|
if (isa_hash($name_or_obj) || isa_user($name_or_obj)) { |
|
390
|
1
|
|
|
|
|
9
|
%attrs= ( %$name_or_obj, %attrs ); |
|
391
|
|
|
|
|
|
|
} else { |
|
392
|
14
|
|
|
|
|
26
|
my $name= "$name_or_obj"; |
|
393
|
14
|
50
|
0
|
|
|
32
|
if (keys %attrs) { |
|
|
|
0
|
|
|
|
|
|
|
394
|
14
|
|
|
|
|
37
|
$attrs{name}= $name; |
|
395
|
|
|
|
|
|
|
} |
|
396
|
|
|
|
|
|
|
# trigger an import if just a name, and auto_import enabled |
|
397
|
|
|
|
|
|
|
elsif ($self->auto_import && !$self->{users}{$name}) { |
|
398
|
0
|
0
|
|
|
|
0
|
$self->user($name) |
|
399
|
|
|
|
|
|
|
or croak "Failed to import user $name"; |
|
400
|
0
|
|
|
|
|
0
|
return $self; |
|
401
|
|
|
|
|
|
|
} |
|
402
|
|
|
|
|
|
|
} |
|
403
|
15
|
|
|
|
|
67
|
$self->_add_user_object(Sys::Export::Unix::UserDB::User->new(%attrs)); |
|
404
|
|
|
|
|
|
|
} |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
|
|
407
|
14
|
|
|
14
|
1
|
1415
|
sub add_group($self, $name_or_obj, %attrs) { |
|
|
14
|
|
|
|
|
22
|
|
|
|
14
|
|
|
|
|
43
|
|
|
|
14
|
|
|
|
|
48
|
|
|
|
14
|
|
|
|
|
24
|
|
|
408
|
14
|
100
|
66
|
|
|
32
|
if (isa_hash($name_or_obj) || isa_group($name_or_obj)) { |
|
409
|
1
|
|
|
|
|
6
|
%attrs= ( %$name_or_obj, %attrs ); |
|
410
|
|
|
|
|
|
|
} else { |
|
411
|
13
|
|
|
|
|
26
|
my $name= "$name_or_obj"; |
|
412
|
13
|
50
|
0
|
|
|
30
|
if (keys %attrs) { |
|
|
|
0
|
|
|
|
|
|
|
413
|
13
|
|
|
|
|
35
|
$attrs{name}= $name; |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
# trigger an import if just a name, and auto_import enabled |
|
416
|
|
|
|
|
|
|
elsif ($self->auto_import && !$self->{groups}{$name}) { |
|
417
|
0
|
0
|
|
|
|
0
|
$self->group($name) |
|
418
|
|
|
|
|
|
|
or croak "Failed to import group $name"; |
|
419
|
0
|
|
|
|
|
0
|
return $self; |
|
420
|
|
|
|
|
|
|
} |
|
421
|
|
|
|
|
|
|
} |
|
422
|
14
|
|
|
|
|
59
|
$self->_add_group_object(Sys::Export::Unix::UserDB::Group->new(%attrs)); |
|
423
|
|
|
|
|
|
|
} |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
|
|
426
|
16
|
|
|
16
|
1
|
697
|
sub user($self, $spec) { |
|
|
16
|
|
|
|
|
26
|
|
|
|
16
|
|
|
|
|
29
|
|
|
|
16
|
|
|
|
|
19
|
|
|
427
|
16
|
50
|
|
|
|
46
|
length $spec or return undef; |
|
428
|
16
|
100
|
|
|
|
35
|
my $u= isa_int $spec? $self->{uids}{$spec} : $self->{users}{$spec}; |
|
429
|
16
|
100
|
100
|
|
|
54
|
if (!$u && $self->auto_import) { |
|
430
|
3
|
100
|
|
|
|
8
|
if (isa_userdb $self->auto_import) { |
|
431
|
2
|
|
50
|
|
|
3
|
my $peer_u= $self->auto_import->user($spec) // return undef; |
|
432
|
2
|
50
|
|
|
|
3
|
$u= eval { $self->import_user($peer_u) } or warn $@; |
|
|
2
|
|
|
|
|
3
|
|
|
433
|
|
|
|
|
|
|
} else { |
|
434
|
1
|
50
|
0
|
|
|
5
|
my $name= isa_int $spec? (getpwuid($spec) // return undef) : $spec; |
|
435
|
1
|
50
|
|
|
|
5
|
$u= eval { $self->import_user($name) } or warn $@; |
|
|
1
|
|
|
|
|
6
|
|
|
436
|
|
|
|
|
|
|
} |
|
437
|
|
|
|
|
|
|
} |
|
438
|
16
|
|
|
|
|
73
|
$u; |
|
439
|
|
|
|
|
|
|
} |
|
440
|
|
|
|
|
|
|
|
|
441
|
4
|
|
|
4
|
1
|
10
|
sub has_user($self, $spec) { |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
9
|
|
|
442
|
4
|
100
|
|
|
|
13
|
defined(isa_int $spec? $self->{uids}{$spec} : $self->{users}{$spec}); |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
|
|
445
|
69
|
|
|
69
|
1
|
640
|
sub group($self, $spec) { |
|
|
69
|
|
|
|
|
102
|
|
|
|
69
|
|
|
|
|
157
|
|
|
|
69
|
|
|
|
|
87
|
|
|
446
|
69
|
50
|
|
|
|
135
|
length $spec or return undef; |
|
447
|
69
|
100
|
|
|
|
126
|
my $g= isa_int $spec? $self->{gids}{$spec} : $self->{groups}{$spec}; |
|
448
|
69
|
100
|
100
|
|
|
205
|
if (!$g && $self->auto_import) { |
|
449
|
4
|
100
|
|
|
|
10
|
if (isa_userdb $self->auto_import) { |
|
450
|
3
|
|
50
|
|
|
5
|
my $peer_g= $self->auto_import->group($spec) // return undef; |
|
451
|
3
|
50
|
|
|
|
4
|
$g= eval { $self->import_group($peer_g) } or warn $@; |
|
|
3
|
|
|
|
|
6
|
|
|
452
|
|
|
|
|
|
|
} else { |
|
453
|
1
|
50
|
0
|
|
|
5
|
my $name= isa_int $spec? (getgrgid($spec) // return undef) : $spec; |
|
454
|
1
|
50
|
|
|
|
4
|
$g= eval { $self->import_group($name) } or warn $@; |
|
|
1
|
|
|
|
|
6
|
|
|
455
|
|
|
|
|
|
|
} |
|
456
|
|
|
|
|
|
|
} |
|
457
|
69
|
|
|
|
|
226
|
$g; |
|
458
|
|
|
|
|
|
|
} |
|
459
|
|
|
|
|
|
|
|
|
460
|
4
|
|
|
4
|
1
|
12
|
sub has_group($self, $spec) { |
|
|
4
|
|
|
|
|
39
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
6
|
|
|
461
|
4
|
100
|
|
|
|
14
|
defined(isa_int $spec? $self->{gids}{$spec} : $self->{groups}{$spec}); |
|
462
|
|
|
|
|
|
|
} |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
# Private methods |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# Allows adding user to group before user is defined |
|
467
|
0
|
|
|
0
|
|
0
|
sub _lazy_add_user_to_group($self, $unam, $grnam) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
468
|
0
|
0
|
|
|
|
0
|
if (my $u= $self->{users}{$unam}) { |
|
469
|
0
|
|
|
|
|
0
|
$u->add_group($grnam); |
|
470
|
|
|
|
|
|
|
} else { |
|
471
|
0
|
|
|
|
|
0
|
push $self->{_lazy_add_user_to_group}{$unam}->@*, $grnam; |
|
472
|
|
|
|
|
|
|
} |
|
473
|
|
|
|
|
|
|
} |
|
474
|
|
|
|
|
|
|
|
|
475
|
25
|
|
|
25
|
|
45
|
sub _add_user_object($self, $user) { |
|
|
25
|
|
|
|
|
39
|
|
|
|
25
|
|
|
|
|
40
|
|
|
|
25
|
|
|
|
|
35
|
|
|
476
|
25
|
|
|
|
|
56
|
my $name = $user->name; |
|
477
|
25
|
|
|
|
|
76
|
my $uid = $user->uid; |
|
478
|
25
|
50
|
|
|
|
55
|
$self->is_valid_name($name) or croak "Invalid user name '$name'"; |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# Check for name conflicts |
|
481
|
|
|
|
|
|
|
croak "Username '$name' already exists" |
|
482
|
25
|
100
|
|
|
|
110
|
if defined $self->{users}{$name}; |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
# Warn about UID conflicts |
|
485
|
|
|
|
|
|
|
carp "UID $uid already exists for user '".$self->{uids}{$uid}->name."', now also used by '$name'" |
|
486
|
24
|
100
|
|
|
|
74
|
if defined $self->{uids}{$uid}; |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# Check for references to non-existent groups |
|
489
|
|
|
|
|
|
|
# If auto_import is enabled, accessing ->group will trigger their creation. |
|
490
|
24
|
50
|
|
|
|
112
|
for ((isa_int $user->group? () : ($user->group)), keys $user->groups->%*) { |
|
491
|
39
|
50
|
|
|
|
79
|
$self->is_valid_name($_) |
|
492
|
|
|
|
|
|
|
or croak "Invalid group name '$_' for user '$name'"; |
|
493
|
|
|
|
|
|
|
# add the user temporarily so auto_import feature can see it |
|
494
|
39
|
100
|
|
|
|
113
|
local $self->{users}{$name} = $user |
|
495
|
|
|
|
|
|
|
if $self->auto_import; |
|
496
|
39
|
50
|
|
|
|
84
|
croak "User '$name' references non-existent group '$_'" |
|
497
|
|
|
|
|
|
|
unless $self->group($_); |
|
498
|
|
|
|
|
|
|
} |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# Add lazy group membership from earlier |
|
501
|
24
|
50
|
|
|
|
84
|
if (my $lazy= delete $self->{_lazy_add_user_to_group}{$name}) { |
|
502
|
|
|
|
|
|
|
$self->{groups}{$_} && $user->add_group($_) |
|
503
|
0
|
|
0
|
|
|
0
|
for @$lazy; |
|
504
|
|
|
|
|
|
|
} |
|
505
|
|
|
|
|
|
|
|
|
506
|
24
|
|
66
|
|
|
127
|
$self->{uids}{$uid} //= $user; |
|
507
|
24
|
|
|
|
|
149
|
$self->{users}{$name} = $user; |
|
508
|
|
|
|
|
|
|
} |
|
509
|
|
|
|
|
|
|
|
|
510
|
27
|
|
|
27
|
|
42
|
sub _add_group_object($self, $group) { |
|
|
27
|
|
|
|
|
42
|
|
|
|
27
|
|
|
|
|
54
|
|
|
|
27
|
|
|
|
|
39
|
|
|
511
|
27
|
|
|
|
|
60
|
my $name = $group->name; |
|
512
|
27
|
|
|
|
|
82
|
my $gid = $group->gid; |
|
513
|
27
|
50
|
|
|
|
61
|
$self->is_valid_name($name) or croak "Invalid group name '$name'"; |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
# Check for name conflicts |
|
516
|
|
|
|
|
|
|
croak "Group name '$name' already exists" |
|
517
|
27
|
100
|
|
|
|
83
|
if defined $self->{groups}{$name}; |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# Warn about GID conflicts |
|
520
|
|
|
|
|
|
|
carp "GID $gid already exists for group '".$self->{gids}{$gid}->name."', now also used by '$name'" |
|
521
|
26
|
100
|
|
|
|
73
|
if defined $self->{gids}{$gid}; |
|
522
|
|
|
|
|
|
|
|
|
523
|
26
|
|
66
|
|
|
136
|
$self->{gids}{$gid} //= $group; |
|
524
|
26
|
|
|
|
|
115
|
$self->{groups}{$name} = $group; |
|
525
|
|
|
|
|
|
|
} |
|
526
|
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
package Sys::Export::Unix::UserDB::User { |
|
529
|
2
|
|
|
2
|
|
29
|
use v5.26; |
|
|
2
|
|
|
|
|
8
|
|
|
530
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
193
|
|
|
531
|
2
|
|
|
2
|
|
13
|
use experimental qw( signatures ); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
17
|
|
|
532
|
|
|
|
|
|
|
our @CARP_NOT= qw( Sys::Export::Unix::UserDB ); |
|
533
|
|
|
|
|
|
|
our %known_attrs= map +($_ => 1), qw( name uid passwd group groups comment gecos dir shell |
|
534
|
|
|
|
|
|
|
quota class pw_change_time pw_min_days pw_max_days pw_warn_days pw_inactive_days expire_time ); |
|
535
|
30
|
|
|
30
|
|
4151
|
sub new($class, %attrs) { |
|
|
30
|
|
|
|
|
60
|
|
|
|
30
|
|
|
|
|
152
|
|
|
|
30
|
|
|
|
|
43
|
|
|
536
|
|
|
|
|
|
|
my $self= bless { |
|
537
|
|
|
|
|
|
|
name => delete $attrs{name}, |
|
538
|
|
|
|
|
|
|
uid => delete $attrs{uid}, |
|
539
|
|
|
|
|
|
|
group => delete $attrs{group}, |
|
540
|
30
|
|
|
|
|
146
|
groups => {}, |
|
541
|
|
|
|
|
|
|
}, $class; |
|
542
|
30
|
100
|
|
|
|
95
|
croak "User 'name' is required" unless defined $self->{name}; |
|
543
|
29
|
100
|
|
|
|
69
|
croak "User 'uid' is required" unless defined $self->{uid}; |
|
544
|
28
|
100
|
|
|
|
93
|
unless (defined $self->{group}) { |
|
545
|
|
|
|
|
|
|
# pull primary group from first element of list if not provided |
|
546
|
5
|
100
|
|
|
|
12
|
if (isa_array $attrs{groups}) { |
|
547
|
4
|
|
|
|
|
7
|
$self->{group}= $attrs{groups}[0]; |
|
548
|
|
|
|
|
|
|
} |
|
549
|
5
|
100
|
|
|
|
15
|
croak "User primary 'group' is required" unless length $self->{group}; |
|
550
|
|
|
|
|
|
|
} |
|
551
|
27
|
|
|
|
|
73
|
for my $key (keys %attrs) { |
|
552
|
83
|
50
|
|
|
|
231
|
carp "Unknown user attribute '$key'" unless $known_attrs{$key}; |
|
553
|
83
|
|
|
|
|
2223
|
$self->$key($attrs{$key}); |
|
554
|
|
|
|
|
|
|
} |
|
555
|
27
|
|
|
|
|
133
|
return $self; |
|
556
|
|
|
|
|
|
|
} |
|
557
|
1
|
|
|
1
|
|
3
|
sub clone($self, %attrs) { |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
19
|
|
|
558
|
1
|
|
|
|
|
9
|
return ref($self)->new( %$self, %attrs ); |
|
559
|
|
|
|
|
|
|
} |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
# Read-only attributes |
|
562
|
48
|
|
|
48
|
|
1418
|
sub name($self) { $self->{name} } |
|
|
48
|
|
|
|
|
67
|
|
|
|
48
|
|
|
|
|
67
|
|
|
|
48
|
|
|
|
|
306
|
|
|
563
|
54
|
|
|
54
|
|
5297
|
sub uid($self) { $self->{uid} } |
|
|
54
|
|
|
|
|
79
|
|
|
|
54
|
|
|
|
|
84
|
|
|
|
54
|
|
|
|
|
468
|
|
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
# Writable attributes |
|
566
|
84
|
|
|
84
|
|
1408
|
sub group($self, @val) { |
|
|
84
|
|
|
|
|
113
|
|
|
|
84
|
|
|
|
|
121
|
|
|
|
84
|
|
|
|
|
108
|
|
|
567
|
84
|
50
|
|
|
|
330
|
@val? ($self->{group}= $val[0]) : $self->{group}; |
|
568
|
|
|
|
|
|
|
} |
|
569
|
|
|
|
|
|
|
|
|
570
|
78
|
|
|
78
|
|
693
|
sub groups($self, @val) { |
|
|
78
|
|
|
|
|
106
|
|
|
|
78
|
|
|
|
|
116
|
|
|
|
78
|
|
|
|
|
106
|
|
|
571
|
78
|
100
|
|
|
|
176
|
if (@val) { |
|
572
|
14
|
50
|
33
|
|
|
83
|
if (@val > 1 || !ref $val[0]) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
573
|
0
|
|
|
|
|
0
|
$self->{groups}= { map +($_ => 1), @val }; |
|
574
|
|
|
|
|
|
|
} elsif (isa_array $val[0]) { |
|
575
|
9
|
|
|
|
|
31
|
$self->{groups}= { map +($_ => 1), @{$val[0]} }; |
|
|
9
|
|
|
|
|
55
|
|
|
576
|
|
|
|
|
|
|
} elsif (isa_hash $val[0]) { |
|
577
|
5
|
|
|
|
|
11
|
$self->{groups}= { %{$val[0]} }; |
|
|
5
|
|
|
|
|
27
|
|
|
578
|
|
|
|
|
|
|
} else { |
|
579
|
0
|
|
|
|
|
0
|
$self->{groups}= { $val[0] => 1 }; # just stringify it |
|
580
|
|
|
|
|
|
|
} |
|
581
|
|
|
|
|
|
|
} |
|
582
|
|
|
|
|
|
|
$self->{groups} |
|
583
|
78
|
|
|
|
|
285
|
} |
|
584
|
|
|
|
|
|
|
|
|
585
|
7
|
|
|
7
|
|
16
|
sub add_group($self, $group_name) { |
|
|
7
|
|
|
|
|
12
|
|
|
|
7
|
|
|
|
|
62
|
|
|
|
7
|
|
|
|
|
12
|
|
|
586
|
7
|
|
|
|
|
22
|
$self->{groups}{$group_name}= 1; |
|
587
|
7
|
|
|
|
|
14
|
return $self; |
|
588
|
|
|
|
|
|
|
} |
|
589
|
|
|
|
|
|
|
|
|
590
|
1
|
|
|
1
|
|
3
|
sub remove_group($self, $group_name) { |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
2
|
|
|
591
|
1
|
|
|
|
|
4
|
delete $self->{groups}{$group_name}; |
|
592
|
1
|
|
|
|
|
2
|
return $self; |
|
593
|
|
|
|
|
|
|
} |
|
594
|
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
{ |
|
596
|
|
|
|
|
|
|
# Generate generic read/write accessors for all other known attributes |
|
597
|
|
|
|
|
|
|
my $pl= join "\n", |
|
598
|
|
|
|
|
|
|
map <<~PL, grep !__PACKAGE__->can($_), keys %known_attrs; |
|
599
|
|
|
|
|
|
|
sub $_(\$self, \@val) { |
|
600
|
|
|
|
|
|
|
\@val ? (\$self->{$_} = \$val[0]) : \$self->{$_}; |
|
601
|
|
|
|
|
|
|
} |
|
602
|
|
|
|
|
|
|
PL |
|
603
|
0
|
0
|
|
0
|
|
0
|
eval $pl." 1" or croak $@; |
|
|
0
|
0
|
|
0
|
|
0
|
|
|
|
0
|
100
|
|
23
|
|
0
|
|
|
|
0
|
100
|
|
9
|
|
0
|
|
|
|
0
|
100
|
|
24
|
|
0
|
|
|
|
0
|
100
|
|
22
|
|
0
|
|
|
|
0
|
100
|
|
11
|
|
0
|
|
|
|
0
|
100
|
|
9
|
|
0
|
|
|
|
0
|
100
|
|
9
|
|
0
|
|
|
|
0
|
100
|
|
9
|
|
0
|
|
|
|
23
|
100
|
|
9
|
|
555
|
|
|
|
23
|
0
|
|
0
|
|
44
|
|
|
|
23
|
100
|
|
23
|
|
66
|
|
|
|
23
|
|
|
|
|
37
|
|
|
|
23
|
|
|
|
|
321
|
|
|
|
9
|
|
|
|
|
22
|
|
|
|
9
|
|
|
|
|
17
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
12
|
|
|
|
9
|
|
|
|
|
56
|
|
|
|
24
|
|
|
|
|
91
|
|
|
|
24
|
|
|
|
|
51
|
|
|
|
24
|
|
|
|
|
48
|
|
|
|
24
|
|
|
|
|
42
|
|
|
|
24
|
|
|
|
|
318
|
|
|
|
22
|
|
|
|
|
1669
|
|
|
|
22
|
|
|
|
|
40
|
|
|
|
22
|
|
|
|
|
42
|
|
|
|
22
|
|
|
|
|
38
|
|
|
|
22
|
|
|
|
|
244
|
|
|
|
11
|
|
|
|
|
323
|
|
|
|
11
|
|
|
|
|
17
|
|
|
|
11
|
|
|
|
|
24
|
|
|
|
11
|
|
|
|
|
18
|
|
|
|
11
|
|
|
|
|
57
|
|
|
|
9
|
|
|
|
|
23
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
176
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
19
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
151
|
|
|
|
9
|
|
|
|
|
22
|
|
|
|
9
|
|
|
|
|
17
|
|
|
|
9
|
|
|
|
|
17
|
|
|
|
9
|
|
|
|
|
17
|
|
|
|
9
|
|
|
|
|
182
|
|
|
|
9
|
|
|
|
|
18
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
19
|
|
|
|
9
|
|
|
|
|
16
|
|
|
|
9
|
|
|
|
|
169
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
23
|
|
|
|
|
329
|
|
|
|
23
|
|
|
|
|
43
|
|
|
|
23
|
|
|
|
|
41
|
|
|
|
23
|
|
|
|
|
39
|
|
|
|
23
|
|
|
|
|
148
|
|
|
604
|
|
|
|
|
|
|
} |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# Other generic read/write accessors |
|
607
|
|
|
|
|
|
|
our $AUTOLOAD; |
|
608
|
|
|
|
|
|
|
sub AUTOLOAD { |
|
609
|
0
|
|
|
0
|
|
0
|
my $attr= substr($AUTOLOAD, rindex($AUTOLOAD, ':')+1); |
|
610
|
0
|
|
|
|
|
0
|
my $self= shift; |
|
611
|
|
|
|
|
|
|
carp "Unknown user attribute '$attr'" |
|
612
|
0
|
0
|
0
|
|
|
0
|
unless exists $known_attrs{$attr} || exists $self->{$attr}; |
|
613
|
0
|
0
|
|
|
|
0
|
@_? ( $self->{$attr}= shift ) : $self->{$attr}; |
|
614
|
|
|
|
|
|
|
} |
|
615
|
|
|
|
|
|
|
|
|
616
|
|
|
|
0
|
|
|
sub import {} |
|
617
|
|
|
|
0
|
|
|
sub DESTROY {} |
|
618
|
|
|
|
|
|
|
} |
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
package Sys::Export::Unix::UserDB::Group { |
|
622
|
2
|
|
|
2
|
|
2933
|
use v5.26; |
|
|
2
|
|
|
|
|
8
|
|
|
623
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
|
2
|
|
|
|
|
25
|
|
|
|
2
|
|
|
|
|
112
|
|
|
624
|
2
|
|
|
2
|
|
10
|
use experimental qw( signatures ); |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
9
|
|
|
625
|
|
|
|
|
|
|
our @CARP_NOT= qw( Sys::Export::Unix::UserDB ); |
|
626
|
|
|
|
|
|
|
our %known_attrs= map +($_ => 1), qw( name gid passwd ); |
|
627
|
|
|
|
|
|
|
|
|
628
|
31
|
|
|
31
|
|
4834
|
sub new($class, %attrs) { |
|
|
31
|
|
|
|
|
50
|
|
|
|
31
|
|
|
|
|
121
|
|
|
|
31
|
|
|
|
|
45
|
|
|
629
|
|
|
|
|
|
|
my $self= bless { |
|
630
|
|
|
|
|
|
|
name => delete $attrs{name}, |
|
631
|
|
|
|
|
|
|
gid => delete $attrs{gid}, |
|
632
|
|
|
|
|
|
|
passwd => delete $attrs{passwd}, |
|
633
|
31
|
|
|
|
|
167
|
}, $class; |
|
634
|
31
|
100
|
|
|
|
99
|
croak "Group 'name' is required" unless defined $self->{name}; |
|
635
|
30
|
100
|
|
|
|
80
|
croak "Group 'gid' is required" unless defined $self->{gid}; |
|
636
|
29
|
|
|
|
|
70
|
for my $key (keys %attrs) { |
|
637
|
0
|
0
|
|
|
|
0
|
carp "Unknown group attribute: '$key'" unless $known_attrs{$key}; |
|
638
|
0
|
|
|
|
|
0
|
$self->$key($attrs{$key}); |
|
639
|
|
|
|
|
|
|
} |
|
640
|
29
|
|
|
|
|
97
|
return $self; |
|
641
|
|
|
|
|
|
|
} |
|
642
|
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
# Read-only attributes |
|
644
|
42
|
|
|
42
|
|
1364
|
sub name($self) { $self->{name} } |
|
|
42
|
|
|
|
|
68
|
|
|
|
42
|
|
|
|
|
54
|
|
|
|
42
|
|
|
|
|
125
|
|
|
645
|
81
|
|
|
81
|
|
4097
|
sub gid($self) { $self->{gid} } |
|
|
81
|
|
|
|
|
119
|
|
|
|
81
|
|
|
|
|
124
|
|
|
|
81
|
|
|
|
|
250
|
|
|
646
|
|
|
|
|
|
|
|
|
647
|
1
|
|
|
1
|
|
3
|
sub clone($self, %attrs) { |
|
|
1
|
|
|
|
|
56
|
|
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
2
|
|
|
648
|
1
|
|
|
|
|
8
|
return ref($self)->new( %$self, %attrs ); |
|
649
|
|
|
|
|
|
|
} |
|
650
|
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
# Other generic read/write accessors |
|
652
|
|
|
|
|
|
|
our $AUTOLOAD; |
|
653
|
|
|
|
|
|
|
sub AUTOLOAD { |
|
654
|
15
|
|
|
15
|
|
50
|
my $attr= substr($AUTOLOAD, rindex($AUTOLOAD, ':')+1); |
|
655
|
15
|
|
|
|
|
28
|
my $self= shift; |
|
656
|
|
|
|
|
|
|
carp "Unknown group attribute '$attr'" |
|
657
|
15
|
0
|
33
|
|
|
48
|
unless exists $known_attrs{$attr} || exists $self->{$attr}; |
|
658
|
15
|
100
|
|
|
|
82
|
@_? ( $self->{$attr}= shift ) : $self->{$attr}; |
|
659
|
|
|
|
|
|
|
} |
|
660
|
|
|
|
|
|
|
|
|
661
|
|
|
|
0
|
|
|
sub import {} |
|
662
|
|
|
|
0
|
|
|
sub DESTROY {} |
|
663
|
|
|
|
|
|
|
} |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
# Avoiding dependency on namespace::clean |
|
666
|
2
|
|
|
2
|
|
1461
|
{ no strict 'refs'; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
278
|
|
|
667
|
|
|
|
|
|
|
delete @{"Sys::Export::Unix::"}{qw( |
|
668
|
|
|
|
|
|
|
croak carp catfile dclone getpwnam pw_has |
|
669
|
|
|
|
|
|
|
isa_export_dst isa_exporter isa_group isa_user isa_userdb |
|
670
|
|
|
|
|
|
|
)}; |
|
671
|
|
|
|
|
|
|
} |
|
672
|
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
1; |
|
674
|
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
__END__ |