line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RADIUS::UserFile; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
RADIUS::UserFile - Perl extension for manipulating a RADIUS users file. |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 SYNOPSIS |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
use RADIUS::UserFile; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
my $users = new RADIUS::UserFile |
13
|
|
|
|
|
|
|
File => '/etc/raddb/users', |
14
|
|
|
|
|
|
|
Check_Items => [ qw(Password Calling-Station-Id) ]; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
$users->load('/usr/local/etc/radius/users'); |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$users->add(Who => 'joeuser', |
19
|
|
|
|
|
|
|
Attributes => { key1 => 'val1', key2 => 'val2' }, |
20
|
|
|
|
|
|
|
Comment => 'Created on '. scalar localtime); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
$users->update(File => '/etc/raddb/users', |
23
|
|
|
|
|
|
|
Who => qw(joeuser janeuser)); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
print $users->format('joeuser'); |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 REQUIRES |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Perl5.004, Fcntl, File::Copy, Tie::IxHash |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 EXPORTS |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
Nothing |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 DESCRIPTION |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
This module provides methods for reading information from and modifying |
38
|
|
|
|
|
|
|
a RADIUS users text file. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=head2 PACKAGE METHODS |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=over 4 |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=item new RADIUS::UserFile |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=item new RADIUS::UserFile(File => I<$USERS_FILE>, Who => I<$USER>, |
47
|
|
|
|
|
|
|
Check_Items => [ I<@CHECK_ITEMS> ]) |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=item new RADIUS::UserFile(File => I<$USERS_FILE>, Who => [ I<@USERS> ], |
50
|
|
|
|
|
|
|
Check_Items => [ I<@CHECK_ITEMS> ]) |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
Creates and returns a new C object. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
C specifies the RADIUS users file to load (e.g. "/etc/raddb/users"). |
55
|
|
|
|
|
|
|
If no file is specified, one isn't loaded; in this case, the C |
56
|
|
|
|
|
|
|
method can be used to retrieve any user data. If an error occurred while |
57
|
|
|
|
|
|
|
reading C, 0 is returned instead. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
C limits the retrieval of user information to the list of users |
60
|
|
|
|
|
|
|
specified. A single user can be named using a string, or a set of users |
61
|
|
|
|
|
|
|
can be passed as a reference to an array. If Who is left undefined, all |
62
|
|
|
|
|
|
|
users will be loaded. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
C is a reference to a list of attributes that should be |
65
|
|
|
|
|
|
|
included in the first line of the record. By default, this list includes: |
66
|
|
|
|
|
|
|
"Password", "Auth-Type", "Called-Station-Id", "Calling-Station-Id", |
67
|
|
|
|
|
|
|
"Client-Port-DNIS", and "Expiration". |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=back |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head2 OBJECT METHODS |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=over 4 |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=item ->add(Who => I<$USER>, Attributes => I<\%ATTRS>, Comment => I<$TEXT>, Debug => I) |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Adds information about the named user. This information will henceforth |
78
|
|
|
|
|
|
|
be available through C, C, C, etc. Any |
79
|
|
|
|
|
|
|
comments are automatically prefixed with "# ". C should be |
80
|
|
|
|
|
|
|
specified as a reference to a hash; each value should either be an array |
81
|
|
|
|
|
|
|
ref or a string. On success, 1 is returned. On error, 0 is returned |
82
|
|
|
|
|
|
|
and STDERR gets an appropriate message. The debug level is used by the |
83
|
|
|
|
|
|
|
C function described below. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=item ->attributes(I<$USER>) |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Returns a list of defined attributes for the specified user. If the |
88
|
|
|
|
|
|
|
user doesn't exist, undef is returned. |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=item ->comment(I<$USER>) |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Returns a string representing the comments that would prefix the given |
93
|
|
|
|
|
|
|
user's entry in the users file. If the user doesn't exist, undef is |
94
|
|
|
|
|
|
|
returned. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=item ->debug(I, I<@messages>) |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
Prints out the list of strings in I<@messages> if the debug level is >= |
99
|
|
|
|
|
|
|
I. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=item ->dump(I<$USER>) |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Prints out the attributes of the named user, in alphabetical order. |
104
|
|
|
|
|
|
|
$self is returned. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=item ->files |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Returns a list of files from which we have read user attributes. The list |
109
|
|
|
|
|
|
|
is sorted according to the order in which the files were read. If no |
110
|
|
|
|
|
|
|
files have yet been read successfully, an empty array is returned. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=item ->format(I<$USER>) |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Returns a string containing the attributes of the named user, prefixed by |
115
|
|
|
|
|
|
|
any comments, according to the format required for the RADIUS users file. |
116
|
|
|
|
|
|
|
If the user doesn't exist, an empty string is returned. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=item ->load(File => I<$USERS_FILE>, Who => I<$USER>) |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=item ->load(File => I<$USERS_FILE>, Who => I<\@USERS>) |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
Loads the contents of the specified RADIUS users file. The name of the |
123
|
|
|
|
|
|
|
file is stored in a first-in, last-out stack enumerating which "databases" |
124
|
|
|
|
|
|
|
have been loaded (see C). The C object is |
125
|
|
|
|
|
|
|
returned. The options are the same as described in C. If a |
126
|
|
|
|
|
|
|
user already exists and further info is read about that user from the |
127
|
|
|
|
|
|
|
specified file, the new information is just added to what is already |
128
|
|
|
|
|
|
|
known. On success, 1 is returned; on failure, 0 is returned and an |
129
|
|
|
|
|
|
|
appropriate message is sent to STDERR. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=item ->read_users(I<$USERS_FILE>, I<$USER>) |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=item ->read_users(I<$USERS_FILE>, I<\@USERS>) |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Reads in the contents of the specified RADIUS users file, and returns |
136
|
|
|
|
|
|
|
a pair of hashes: one indexed by user name, with each element containing |
137
|
|
|
|
|
|
|
a hash of (attribute name => [ values ]) pairs; and another also indexed |
138
|
|
|
|
|
|
|
by user name, containing the comments that immediately preceded that |
139
|
|
|
|
|
|
|
user's file entry. The options are the same as in C. Each |
140
|
|
|
|
|
|
|
comment value is a string. Each user attribute value is a ref to an |
141
|
|
|
|
|
|
|
array of strings. This is mainly designed as a utility function to be |
142
|
|
|
|
|
|
|
used by C and C, and doesn't affect the calling object. |
143
|
|
|
|
|
|
|
On failure, 0 is returned. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=item ->remove(I<$USER> ...) |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
Deletes the specified users from the object. The list of users |
148
|
|
|
|
|
|
|
successfully deleted is returned. |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item ->removed() |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
Returns a list of users that have been removed from the object. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item ->update(File => I<$USERS_FILE>, Who => I<\@USERS>) |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Updates user attributes in a RADIUS users file. If the file is |
157
|
|
|
|
|
|
|
specified, its contents are updated; otherwise, the last file read is |
158
|
|
|
|
|
|
|
modified. If a list of users is provided, only their entries are |
159
|
|
|
|
|
|
|
updated; otherwise, all known users are. All users to be "updated" |
160
|
|
|
|
|
|
|
are printed using the results of C. Other users are printed |
161
|
|
|
|
|
|
|
as found. It should be noted that some extra newlines can be left |
162
|
|
|
|
|
|
|
in a file with this method: if an empty line follows a given record |
163
|
|
|
|
|
|
|
that has been Cd, then it will still be there in the file |
164
|
|
|
|
|
|
|
being updated. On success, non-zero is returned. On failure, 0 is |
165
|
|
|
|
|
|
|
returned and STDERR gets an appropriate message. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item ->user(I<$USER>) |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
Returns a ref to a hash representing the attributes of the named user. |
170
|
|
|
|
|
|
|
If the user doesn't exist, undef is returned. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=item ->usernames |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Returns a ref to an anonymous array of strings representing the users |
175
|
|
|
|
|
|
|
about which we have attributes defined. If no users are defined, a ref |
176
|
|
|
|
|
|
|
to an empty anonymous array is returned. |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=item ->users |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Returns a ref to a hash of user hashes, where each user hash is a set of |
181
|
|
|
|
|
|
|
(attribute name => value) pairs. This is the actual data stored in the |
182
|
|
|
|
|
|
|
object, so use with caution. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item ->values(I<$USER>, I<$ATTRIBUTE>) |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Returns an array of strings representing the values for the named |
187
|
|
|
|
|
|
|
attribute of the given user. If the user or attribute doesn't exist, |
188
|
|
|
|
|
|
|
undef is returned. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=back |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head1 AUTHOR |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Copyright (c) 2001 O'Shaughnessy Evans . |
195
|
|
|
|
|
|
|
All rights reserved. This version is distributed under the same |
196
|
|
|
|
|
|
|
terms as Perl itself (i.e. it's free), so enjoy. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
Thanks to Burkhard Weeber, James Golovich, Peter Bannis, and others |
199
|
|
|
|
|
|
|
for contributions and comments that have improved this software. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head1 SEE ALSO |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
L, L, L. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=cut |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
require 5.004; |
208
|
1
|
|
|
1
|
|
787
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
37
|
|
209
|
1
|
|
|
1
|
|
5
|
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
86
|
|
210
|
1
|
|
|
1
|
|
6
|
use Carp; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
101
|
|
211
|
1
|
|
|
1
|
|
849
|
use IO::File; |
|
1
|
|
|
|
|
16198
|
|
|
1
|
|
|
|
|
167
|
|
212
|
1
|
|
|
1
|
|
1037
|
use File::Copy; |
|
1
|
|
|
|
|
6035
|
|
|
1
|
|
|
|
|
76
|
|
213
|
1
|
|
|
1
|
|
7
|
use Fcntl qw(:flock); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
162
|
|
214
|
1
|
|
|
1
|
|
2548
|
use Tie::IxHash; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
require Exporter; |
217
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
218
|
|
|
|
|
|
|
@EXPORT_OK = qw(add attributes comment dump files format load new read_users |
219
|
|
|
|
|
|
|
update user usernames users values); |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
$VERSION = '1.01'; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
#my $RADIUS_USERS = '/etc/raddb/users'; # default users info file |
224
|
|
|
|
|
|
|
my $ATTR_MAX = 31; # max char len of any attribute name |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
my %fields = ( |
227
|
|
|
|
|
|
|
users => undef, |
228
|
|
|
|
|
|
|
removed => undef, # cheap hack for remove() |
229
|
|
|
|
|
|
|
files => undef, |
230
|
|
|
|
|
|
|
comments => undef, |
231
|
|
|
|
|
|
|
check_items => undef, |
232
|
|
|
|
|
|
|
ERROR => undef, |
233
|
|
|
|
|
|
|
DEBUG => undef |
234
|
|
|
|
|
|
|
); |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Create, initialize, and return a new RADIUS::UserFile object. |
237
|
|
|
|
|
|
|
# |
238
|
|
|
|
|
|
|
sub new |
239
|
|
|
|
|
|
|
{ |
240
|
|
|
|
|
|
|
my $me = shift; |
241
|
|
|
|
|
|
|
my $class = ref($me) || $me; |
242
|
|
|
|
|
|
|
my $self = { %fields }; |
243
|
|
|
|
|
|
|
bless $self, $class; |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
my %args = @_; |
246
|
|
|
|
|
|
|
return $self->_initialize(\%args); |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Do initial object-creation setup stuff. |
251
|
|
|
|
|
|
|
# |
252
|
|
|
|
|
|
|
sub _initialize |
253
|
|
|
|
|
|
|
{ |
254
|
|
|
|
|
|
|
my ($self, $args) = @_; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
if ($args->{Debug}) { |
257
|
|
|
|
|
|
|
$self->{DEBUG} = $args->{Debug}; |
258
|
|
|
|
|
|
|
$| = 1; |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
if ($args->{Check_Items}) { |
262
|
|
|
|
|
|
|
$self->{check_items} = [ @{$args->{Check_Items}} ]; |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
else { |
265
|
|
|
|
|
|
|
$self->{check_items} = [ "Password", "Auth-Type", |
266
|
|
|
|
|
|
|
"Called-Station-Id", "Calling-Station-Id", |
267
|
|
|
|
|
|
|
"Client-Port-DNIS", "Expiration" ]; |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
if ($args->{File}) { |
271
|
|
|
|
|
|
|
$self->debug(7, "init - loading $args->{File}"); |
272
|
|
|
|
|
|
|
my ($users, $comments) = $self->read_users($args->{File}, $args->{Who}); |
273
|
|
|
|
|
|
|
return 0 unless defined $comments and defined $users; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
if ($users) { |
276
|
|
|
|
|
|
|
@{$self->{users}}{keys %$users} = values %$users; |
277
|
|
|
|
|
|
|
@{$self->{comments}}{keys %$comments} = values %$comments; |
278
|
|
|
|
|
|
|
push @{$self->{files}}, $args->{File}; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
return $self; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# Adds the specified user to the collection. The arguments provided should |
287
|
|
|
|
|
|
|
# form a hash with the following structure: |
288
|
|
|
|
|
|
|
# 'Who' => "user_name" |
289
|
|
|
|
|
|
|
# 'Attributes' => { key1 => val1, key2 => [ val2 val3 val4 ], ... } |
290
|
|
|
|
|
|
|
# 'Comment' => "optional text to prefix the user's file entry" |
291
|
|
|
|
|
|
|
# |
292
|
|
|
|
|
|
|
# If there is some type of failure, 0 is returned. Otherwise, 1. |
293
|
|
|
|
|
|
|
sub add |
294
|
|
|
|
|
|
|
{ |
295
|
|
|
|
|
|
|
my ($self, %args) = @_; |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
unless ($args{Who} and ref $args{Attributes} eq 'HASH') { |
298
|
|
|
|
|
|
|
carp('Insufficient parameters: missing Who or hash of Attributes.'); |
299
|
|
|
|
|
|
|
return 0; |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
$self->debug(6, "add - adding $args{Who}"); |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Add quotes to each attrib value if it has whitespace and isn't already |
304
|
|
|
|
|
|
|
# quoted. |
305
|
|
|
|
|
|
|
foreach my $k (keys %{$args{Attributes}}) { |
306
|
|
|
|
|
|
|
if (ref $args{Attributes}->{$k} eq 'ARRAY') { |
307
|
|
|
|
|
|
|
for (my $i=0; $i <= $#{$args{Attributes}->{$k}}; $i++) { |
308
|
|
|
|
|
|
|
$args{Attributes}->{$k}[$i] =~ s/^([^"].*[\s,].*)$/"$1"/; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
else { |
312
|
|
|
|
|
|
|
$args{Attributes}->{$k} =~ s/^([^"].*\s.*)$/"$1"/; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
tie %{$self->{'users'}{$args{Who}}}, 'Tie::IxHash' |
317
|
|
|
|
|
|
|
unless tied %{$self->{'users'}{$args{Who}}}; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
foreach my $k (keys %{$args{Attributes}}) { |
320
|
|
|
|
|
|
|
push @{$self->{'users'}{$args{Who}}{$k}}, |
321
|
|
|
|
|
|
|
ref $args{Attributes}->{$k} eq 'ARRAY' |
322
|
|
|
|
|
|
|
? @{$args{Attributes}->{$k}} |
323
|
|
|
|
|
|
|
: $args{Attributes}->{$k} |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
if (exists $args{Comment}) { |
327
|
|
|
|
|
|
|
$args{Comment} =~ s/^/# /mg; |
328
|
|
|
|
|
|
|
$self->{comments}{$args{Who}} .= $args{Comment}. "\n"; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
return 1; |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# Return a list of defined RADIUS attributes for the specified user. |
336
|
|
|
|
|
|
|
# |
337
|
|
|
|
|
|
|
sub attributes |
338
|
|
|
|
|
|
|
{ |
339
|
|
|
|
|
|
|
my ($self, $who) = @_; |
340
|
|
|
|
|
|
|
my @a = eval { local $^W = undef; keys %{$self->{'users'}{$who}} }; |
341
|
|
|
|
|
|
|
return $@ ? undef : @a; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
# Return the comment text associated with a user. |
346
|
|
|
|
|
|
|
# |
347
|
|
|
|
|
|
|
sub comment |
348
|
|
|
|
|
|
|
{ |
349
|
|
|
|
|
|
|
my ($self, $who) = @_; |
350
|
|
|
|
|
|
|
my $text = eval { local $^W = undef; $self->{comments}{$who} }; |
351
|
|
|
|
|
|
|
return $@ ? undef : $text; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Print the attributes of the specified user. |
356
|
|
|
|
|
|
|
# |
357
|
|
|
|
|
|
|
sub dump |
358
|
|
|
|
|
|
|
{ |
359
|
|
|
|
|
|
|
my ($self, $who) = @_; |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
return $self unless defined $self->user($who); |
362
|
|
|
|
|
|
|
my @attribs = $self->attributes($who); |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
print "RADIUS user $who:\n"; |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
if (@attribs) { |
367
|
|
|
|
|
|
|
foreach my $a (@attribs) { |
368
|
|
|
|
|
|
|
foreach my $v ($self->values($who, $a)) { |
369
|
|
|
|
|
|
|
printf " %-${ATTR_MAX}s => %s\n", $a, $v; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
else { |
374
|
|
|
|
|
|
|
print " no attributes defined.\n"; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
return $self; |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# Return a ref to a list of files that we have read user info from. |
382
|
|
|
|
|
|
|
# |
383
|
|
|
|
|
|
|
sub files |
384
|
|
|
|
|
|
|
{ |
385
|
|
|
|
|
|
|
my $self = shift; |
386
|
|
|
|
|
|
|
my @files = eval { local $^W = undef; @{$self->{files}} }; |
387
|
|
|
|
|
|
|
return $@ ? () : @files; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Return a string containing the attributes for the given user, in the |
392
|
|
|
|
|
|
|
# format acceptable to a RADIUS users file. If the user doesn't exist, |
393
|
|
|
|
|
|
|
# an empty string is returned. |
394
|
|
|
|
|
|
|
sub format |
395
|
|
|
|
|
|
|
{ |
396
|
|
|
|
|
|
|
my ($self, $who) = @_; |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
return '' unless defined $self->user($who); |
399
|
|
|
|
|
|
|
my $str = $self->comment($who); |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
my @attribs = $self->attributes($who); |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# figure out a good way to indent each record |
404
|
|
|
|
|
|
|
my $indent = length($who) + 1; |
405
|
|
|
|
|
|
|
if ($indent < 24) { $indent = 24 } |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
if (@attribs) { |
408
|
|
|
|
|
|
|
my (@attrib_strs); |
409
|
|
|
|
|
|
|
my @checks = (); |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
foreach my $a (@attribs) { |
412
|
|
|
|
|
|
|
foreach my $v ($self->values($who, $a)) { |
413
|
|
|
|
|
|
|
if ($self->_is_check_item($a)) { |
414
|
|
|
|
|
|
|
$self->debug(8, "format - check item $a = $v"); |
415
|
|
|
|
|
|
|
push @checks, "$a = $v"; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
else { |
418
|
|
|
|
|
|
|
push @attrib_strs, |
419
|
|
|
|
|
|
|
sprintf("%s%s = %s", ' 'x$indent, $a, $v); |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
$str .= $who. (' 'x($indent - length $who)). join(', ', @checks). "\n"; |
424
|
|
|
|
|
|
|
$str .= join(",\n", @attrib_strs). "\n"; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
return $str; |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# Read user attributes from the specified file. If a set of users is |
432
|
|
|
|
|
|
|
# specified using "Who", the information is limited to those users. |
433
|
|
|
|
|
|
|
# |
434
|
|
|
|
|
|
|
sub load |
435
|
|
|
|
|
|
|
{ |
436
|
|
|
|
|
|
|
my ($self, %args) = @_; |
437
|
|
|
|
|
|
|
my $file = $args{File}; |
438
|
|
|
|
|
|
|
my $who = $args{Who}; |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
my ($users, $comments) = $self->read_users($file, $who); |
441
|
|
|
|
|
|
|
return 0 unless defined $comments and defined $users; |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
foreach my $u (keys %$users) { |
444
|
|
|
|
|
|
|
tie(%{$self->{'users'}{$u}}, 'Tie::IxHash'); |
445
|
|
|
|
|
|
|
foreach my $a (keys %{$users->{$u}}) { |
446
|
|
|
|
|
|
|
push @{$self->{'users'}{$u}{$a}}, @{$users->{$u}{$a}}; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
foreach my $user (keys %$comments) { |
450
|
|
|
|
|
|
|
$self->{comments}{$user} .= $comments->{$user}; |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
push @{$self->{'files'}}, $file; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
return 1; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# Read in a radius users file, according to the EBNF provided in |
459
|
|
|
|
|
|
|
# "users-file-syntax.1", distributed w/the Ascend radius server software. |
460
|
|
|
|
|
|
|
# Returns a ref to a hash of user names, where each user element is a hash |
461
|
|
|
|
|
|
|
# of (attribute_name => value) pairs. If a second argument is supplied |
462
|
|
|
|
|
|
|
# ($who), it specifies the set of users to read in... all others in the |
463
|
|
|
|
|
|
|
# file will be ignored. If $who is a string, it is interpreted as a single |
464
|
|
|
|
|
|
|
# user name; if it's a reference to an array, it's interpreted as a set |
465
|
|
|
|
|
|
|
# of user names. |
466
|
|
|
|
|
|
|
# |
467
|
|
|
|
|
|
|
sub read_users |
468
|
|
|
|
|
|
|
{ |
469
|
|
|
|
|
|
|
my ($self, $users_file, $who) = @_; |
470
|
|
|
|
|
|
|
my (@fields, $user, %users, $attrib_set, $attrib_input, @who_we_want, |
471
|
|
|
|
|
|
|
%comments, $comment, $attr, $val); |
472
|
|
|
|
|
|
|
local (*USERS); |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
$self->debug(2, "read_users - loading $users_file"); |
475
|
|
|
|
|
|
|
open(USERS, $users_file) |
476
|
|
|
|
|
|
|
or carp("Error opening $users_file: $!"), return 0; |
477
|
|
|
|
|
|
|
seek USERS, 0, 0; |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
@who_we_want = ref $who eq 'ARRAY' ? @$who : $who if defined $who; |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
while () { |
482
|
|
|
|
|
|
|
chomp; |
483
|
|
|
|
|
|
|
$self->debug(9, "read_users - in=``$_''"); |
484
|
|
|
|
|
|
|
($comment = '', next) unless $_; # Skip if there's nothing useful, |
485
|
|
|
|
|
|
|
($comment .= "$_\n", next) if /^#/; # or if it's just a comment. |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
if (/(^[^#,\s]+)\s+(.+)/) { # first line |
488
|
|
|
|
|
|
|
$user = $1; |
489
|
|
|
|
|
|
|
$attrib_input = $2; |
490
|
|
|
|
|
|
|
$comments{$user} = $comment if $comment; |
491
|
|
|
|
|
|
|
tie(%{$users{$user}}, 'Tie::IxHash'); |
492
|
|
|
|
|
|
|
$self->debug(5, "read_users - new record $user"); |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
else { # secondary line |
495
|
|
|
|
|
|
|
$attrib_input = $_; |
496
|
|
|
|
|
|
|
} |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
next if @who_we_want and !grep($_ eq $user, @who_we_want); |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
$attrib_set = _parse_attribs($attrib_input, $users_file); |
501
|
|
|
|
|
|
|
while (($attr, $val) = splice @$attrib_set, 0, 2) { |
502
|
|
|
|
|
|
|
push @{$users{$user}{$attr}}, $val; |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
close USERS; |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
return (\%users, \%comments); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
# Return a ref to a hash of RADIUS users attributes. We assume that |
512
|
|
|
|
|
|
|
# comments have already been stripped from the input string. |
513
|
|
|
|
|
|
|
# |
514
|
|
|
|
|
|
|
sub _parse_attribs |
515
|
|
|
|
|
|
|
{ |
516
|
|
|
|
|
|
|
my ($raw, $file) = @_; |
517
|
|
|
|
|
|
|
my @attribs; |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
$raw =~ s/^\s+//; # remove leading whitespace. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
while ($raw =~ s/^(\S+)\s*=\s*(("[^"]*")|[^",\s]+)\s*,?//) { |
522
|
|
|
|
|
|
|
if (defined $2) { |
523
|
|
|
|
|
|
|
push @attribs, $1, $2; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
else { |
526
|
|
|
|
|
|
|
carp("Couldn't understand line $. in `$file'."); |
527
|
|
|
|
|
|
|
last; |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
$raw =~ s/^\s+//; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
return \@attribs; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# Remove the specified users from $self. |
538
|
|
|
|
|
|
|
sub remove |
539
|
|
|
|
|
|
|
{ |
540
|
|
|
|
|
|
|
my ($self, @users) = @_; |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
foreach (@users) { |
543
|
|
|
|
|
|
|
delete $self->{'users'}{$_} and push @{$self->{removed}}, $_; |
544
|
|
|
|
|
|
|
delete $self->{comments}{$_}; |
545
|
|
|
|
|
|
|
} |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
my @removed = eval { local $^W = undef; @{$self->{removed}} }; |
548
|
|
|
|
|
|
|
return $@ ? () : @removed; |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
sub removed |
552
|
|
|
|
|
|
|
{ |
553
|
|
|
|
|
|
|
my $self = shift; |
554
|
|
|
|
|
|
|
my @removed = eval { local $^W = undef; @{$self->{removed}} }; |
555
|
|
|
|
|
|
|
return $@ ? () : @removed; |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# Update user attributes in a RADIUS users file. The arguments should be |
560
|
|
|
|
|
|
|
# specified as a hash. If the 'File' element is provided, that filename |
561
|
|
|
|
|
|
|
# is used; otherwise, the last file read is used. If the 'Who' element is |
562
|
|
|
|
|
|
|
# provided, only the specified users are updated; otherwise, all known |
563
|
|
|
|
|
|
|
# users are updated. |
564
|
|
|
|
|
|
|
sub update |
565
|
|
|
|
|
|
|
{ |
566
|
|
|
|
|
|
|
my ($self, %args) = @_; |
567
|
|
|
|
|
|
|
my $file = exists $args{File} ? $args{File} : $self->{'files'}->[-1]; |
568
|
|
|
|
|
|
|
my @who = exists $args{Who} |
569
|
|
|
|
|
|
|
? (ref $args{Who} eq 'ARRAY' ? @{$args{Who}} : $args{Who}) |
570
|
|
|
|
|
|
|
: eval { local $^W = undef; keys %{$self->{users}} }; |
571
|
|
|
|
|
|
|
my $temp = "$file.new"; |
572
|
|
|
|
|
|
|
local (*IN, *TMP); |
573
|
|
|
|
|
|
|
my $oldsep = $/; |
574
|
|
|
|
|
|
|
local ($/) = ''; # we'll lose multiple blank lines this way |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
carp('No users found'), return 0 unless (@who); |
577
|
|
|
|
|
|
|
_setup_files($file, \*IN, $temp, \*TMP) or return 0; |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
my (%who, @recs, $name, $in); |
580
|
|
|
|
|
|
|
@who{@who} = (0) x @who; |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
while () { |
583
|
|
|
|
|
|
|
undef @recs; |
584
|
|
|
|
|
|
|
$in = $_; |
585
|
|
|
|
|
|
|
while (/^( |
586
|
|
|
|
|
|
|
(?: \#.*\n)* # pre-record comment lines |
587
|
|
|
|
|
|
|
[^\#\s]+.*\n # start of record |
588
|
|
|
|
|
|
|
(?: # rest of record: |
589
|
|
|
|
|
|
|
(?: \s+\S.*\n)| # attribute settings, or |
590
|
|
|
|
|
|
|
((?: \#.*\n) # comments not followed by another |
591
|
|
|
|
|
|
|
(?! [^\#\s])) # start of record. |
592
|
|
|
|
|
|
|
)* |
593
|
|
|
|
|
|
|
)/goxm) { |
594
|
|
|
|
|
|
|
push @recs, $1; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
print(TMP $in), next unless @recs; |
598
|
|
|
|
|
|
|
foreach my $r (@recs) { |
599
|
|
|
|
|
|
|
($name) = $r =~ /^([^#\s]+)/m; |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
if (!$name) { |
602
|
|
|
|
|
|
|
print TMP $r; |
603
|
|
|
|
|
|
|
} |
604
|
|
|
|
|
|
|
elsif (exists $who{$name}) { |
605
|
|
|
|
|
|
|
$self->debug(6, "update - existing record $name"); |
606
|
|
|
|
|
|
|
print TMP $self->format($name) if $who{$name} == 0; |
607
|
|
|
|
|
|
|
$who{$name}++; |
608
|
|
|
|
|
|
|
} |
609
|
|
|
|
|
|
|
elsif (!grep($name eq $_, $self->removed)) { |
610
|
|
|
|
|
|
|
print TMP $r; |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
print TMP "\n"; # since the input sep is "\n\n" |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
# Print out records for anyone we didn't find in $file. |
617
|
|
|
|
|
|
|
foreach (grep($who{$_} == 0, keys %who)) { |
618
|
|
|
|
|
|
|
$self->debug(6, "update - new record $_"); |
619
|
|
|
|
|
|
|
print TMP $self->format($_), "\n"; |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
$/ = $oldsep; |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
# Close out input and output files (original and temporary, respectively) |
625
|
|
|
|
|
|
|
_cleanup_files($file, \*IN, $temp, \*TMP) or return 0; |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
return 1; |
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Organizational routine for update(). Sets up file handles for reading |
631
|
|
|
|
|
|
|
# from the RADIUS users file. The entire algorithm is like this: |
632
|
|
|
|
|
|
|
# open users file for read/write, creating if necessary |
633
|
|
|
|
|
|
|
# flock file exclusively |
634
|
|
|
|
|
|
|
# compare file opened to file locked, and re-open/lock while not equal |
635
|
|
|
|
|
|
|
# read from file, write to temp (handled in update()) |
636
|
|
|
|
|
|
|
# close temp (handled by _cleanup_files) |
637
|
|
|
|
|
|
|
# rename temp to file |
638
|
|
|
|
|
|
|
# close file |
639
|
|
|
|
|
|
|
sub _setup_files |
640
|
|
|
|
|
|
|
{ |
641
|
|
|
|
|
|
|
my ($file, $IN, $temp, $TMP) = @_; |
642
|
|
|
|
|
|
|
my $backup = "$file.bak"; |
643
|
|
|
|
|
|
|
my $existed = -f $file; |
644
|
|
|
|
|
|
|
my ($dev1, $ino1, $dev2, $ino2); |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
while (1) { |
647
|
|
|
|
|
|
|
open($IN, "+>>$file") |
648
|
|
|
|
|
|
|
or carp("Error opening $file: $!"), return 0; |
649
|
|
|
|
|
|
|
($dev1, $ino1) = (stat $IN)[0,1]; |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
flock($IN, LOCK_EX) |
652
|
|
|
|
|
|
|
or carp("Error locking $file: $!"), close $IN, return 0; |
653
|
|
|
|
|
|
|
($dev2, $ino2) = (stat $IN)[0,1]; |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
last if $dev1 == $dev2 and $ino1 == $ino2; |
656
|
|
|
|
|
|
|
close $IN; |
657
|
|
|
|
|
|
|
} |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
seek $IN, 0, 0; |
660
|
|
|
|
|
|
|
open($TMP, ">$temp") |
661
|
|
|
|
|
|
|
or carp("Error creating $temp: $!"), close $IN, return 0; |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
return 1; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
# We should have new content in $TMP, and old content in $IN. |
667
|
|
|
|
|
|
|
# So rename $TMP to $IN and close, releasing the flock on $IN established |
668
|
|
|
|
|
|
|
# in _setup_files(). |
669
|
|
|
|
|
|
|
sub _cleanup_files |
670
|
|
|
|
|
|
|
{ |
671
|
|
|
|
|
|
|
my ($file, $IN, $temp, $TMP) = @_; |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
close $TMP or carp("Error closing $temp: $!"), return 0; |
674
|
|
|
|
|
|
|
rename($temp, $file) or carp("Error renaming $file to $temp: $!"), return 0; |
675
|
|
|
|
|
|
|
close $IN or carp("Error closing $file: $!"), return 0; |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
return 1; |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# See if attribute is a checkable item (Lucent Radius fix -- Peter Bannis) |
682
|
|
|
|
|
|
|
sub _is_check_item |
683
|
|
|
|
|
|
|
{ |
684
|
|
|
|
|
|
|
my ($self, $attribute) = @_; |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
if ($attribute) { |
687
|
|
|
|
|
|
|
return grep(/^$attribute$/i, @{$self->{check_items}}); |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
else { |
690
|
|
|
|
|
|
|
return 0; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
# Return a ref to a hash representing the attributes of the specified user. |
696
|
|
|
|
|
|
|
# |
697
|
|
|
|
|
|
|
sub user |
698
|
|
|
|
|
|
|
{ |
699
|
|
|
|
|
|
|
my ($self, $who) = @_; |
700
|
|
|
|
|
|
|
my %hash = eval { local $^W = undef; %{$self->{'users'}{$who}} }; |
701
|
|
|
|
|
|
|
return $@ ? undef : \%hash; |
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
# Return a ref to a list of users we have RADIUS info for, or a ref to an |
706
|
|
|
|
|
|
|
# empty anonymous array if no users are defined. |
707
|
|
|
|
|
|
|
# |
708
|
|
|
|
|
|
|
sub usernames |
709
|
|
|
|
|
|
|
{ |
710
|
|
|
|
|
|
|
my $self = shift; |
711
|
|
|
|
|
|
|
my $users = eval { local $^W = undef; [ keys %{$self->{'users'}} ] }; |
712
|
|
|
|
|
|
|
return $@ ? [] : $users; |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
# Return a ref to a hash of RADIUS users, indexed by user name, each |
717
|
|
|
|
|
|
|
# containing a hash of attributes. This is a ref to the actual data |
718
|
|
|
|
|
|
|
# in the object, so the user information can be changed here. |
719
|
|
|
|
|
|
|
# |
720
|
|
|
|
|
|
|
sub users |
721
|
|
|
|
|
|
|
{ |
722
|
|
|
|
|
|
|
my $self = shift; return $self->{'users'}; |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
# Return an array with the values of the given attribute for the named user. |
727
|
|
|
|
|
|
|
# |
728
|
|
|
|
|
|
|
sub values |
729
|
|
|
|
|
|
|
{ |
730
|
|
|
|
|
|
|
my ($self, $who, $attr) = @_; |
731
|
|
|
|
|
|
|
my @vals = eval { local $^W = undef; @{$self->{'users'}{$who}{$attr}} }; |
732
|
|
|
|
|
|
|
return $@ ? undef : @vals; |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
sub debug |
736
|
|
|
|
|
|
|
{ |
737
|
|
|
|
|
|
|
my ($self, $level, @msg) = @_; |
738
|
|
|
|
|
|
|
if ($level <= $self->{DEBUG}) { |
739
|
|
|
|
|
|
|
print STDERR join("\n", @msg), "\n"; |
740
|
|
|
|
|
|
|
} |
741
|
|
|
|
|
|
|
} |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
1; |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
__END__ |