File Coverage

blib/lib/Authen/Radius.pm
Criterion Covered Total %
statement 376 666 56.4
branch 110 300 36.6
condition 30 130 23.0
subroutine 72 86 83.7
pod 13 22 59.0
total 601 1204 49.9


line stmt bran cond sub pod time code
1             #############################################################################
2             # #
3             # Radius Client module for Perl 5 #
4             # #
5             # Written by Carl Declerck , (c)1997 #
6             # All Rights Reserved. See the Perl Artistic License 2.0 #
7             # for copying & usage policy. #
8             # #
9             # Modified by Olexander Kapitanenko, Andrew Zhilenko #
10             # and the rest of PortaOne team (c) 2002-2013 #
11             # Current maintainer's contact: perl-radius@portaone.com #
12             # #
13             # See the file 'Changes' in the distribution archive. #
14             # #
15             #############################################################################
16              
17             package Authen::Radius;
18              
19 8     8   468260 use strict;
  8         69  
  8         186  
20 8     8   34 use warnings;
  8         15  
  8         145  
21 8     8   77 use v5.10;
  8         22  
22 8     8   2893 use FileHandle;
  8         63671  
  8         37  
23 8     8   5564 use IO::Socket;
  8         96032  
  8         28  
24 8     8   5721 use IO::Select;
  8         10623  
  8         287  
25 8     8   50 use Digest::MD5;
  8         14  
  8         206  
26 8     8   3971 use Data::Dumper;
  8         45023  
  8         427  
27 8     8   2735 use Data::HexDump;
  8         9750  
  8         340  
28 8     8   4380 use Net::IP qw(ip_bintoip ip_compress_address ip_expand_address ip_iptobin);
  8         395429  
  8         1038  
29 8     8   4072 use Time::HiRes qw(time);
  8         9414  
  8         32  
30              
31 8     8   1400 use vars qw($VERSION @ISA @EXPORT);
  8         15  
  8         1053  
32              
33             require Exporter;
34              
35             @ISA = qw(Exporter);
36             @EXPORT = qw(ACCESS_REQUEST ACCESS_ACCEPT ACCESS_REJECT ACCESS_CHALLENGE
37             ACCOUNTING_REQUEST ACCOUNTING_RESPONSE ACCOUNTING_STATUS
38             DISCONNECT_REQUEST DISCONNECT_ACCEPT DISCONNECT_REJECT
39             STATUS_SERVER
40             COA_REQUEST COA_ACCEPT COA_REJECT COA_ACK COA_NAK);
41              
42             $VERSION = '0.30';
43              
44             my (%dict_id, %dict_name, %dict_val, %dict_vendor_id, %dict_vendor_name );
45             my ($request_id) = $$ & 0xff; # probably better than starting from 0
46             my ($radius_error, $error_comment) = ('ENONE', '');
47             my $debug = 0;
48              
49 8     8   54 use constant WIMAX_VENDOR => '24757';
  8         19  
  8         490  
50 8     8   44 use constant WIMAX_CONTINUATION_BIT => 0b10000000;
  8         15  
  8         375  
51              
52 8     8   42 use constant NO_VENDOR => 'not defined';
  8         17  
  8         382  
53              
54 8     8   42 use constant DEFAULT_DICTIONARY => '/etc/raddb/dictionary';
  8         13  
  8         634  
55              
56             #
57             # we'll need to predefine these attr types so we can do simple password
58             # verification without having to load a dictionary
59             #
60              
61             # ATTRIBUTE User-Name 1 string
62             # ATTRIBUTE User-Password 2 string
63             # ATTRIBUTE NAS-IP-Address 4 ipaddr
64             $dict_id{ NO_VENDOR() }{1}{type} = 'string';
65             $dict_id{ NO_VENDOR() }{2}{type} = 'string';
66             $dict_id{ NO_VENDOR() }{4}{type} = 'ipaddr';
67              
68             # ATTRIBUTE Vendor-Specific 26 octets
69 8     8   56 use constant ATTR_VENDOR => 26;
  8         15  
  8         509  
70              
71 8     8   46 use constant ACCESS_REQUEST => 1;
  8         18  
  8         404  
72 8     8   42 use constant ACCESS_ACCEPT => 2;
  8         16  
  8         318  
73 8     8   41 use constant ACCESS_REJECT => 3;
  8         19  
  8         344  
74 8     8   39 use constant ACCOUNTING_REQUEST => 4;
  8         14  
  8         502  
75 8     8   42 use constant ACCOUNTING_RESPONSE => 5;
  8         12  
  8         386  
76 8     8   93 use constant ACCOUNTING_STATUS => 6;
  8         11  
  8         362  
77 8     8   40 use constant ACCESS_CHALLENGE => 11;
  8         10  
  8         375  
78 8     8   40 use constant STATUS_SERVER => 12;
  8         30  
  8         338  
79 8     8   39 use constant DISCONNECT_REQUEST => 40;
  8         13  
  8         365  
80 8     8   40 use constant DISCONNECT_ACCEPT => 41;
  8         13  
  8         322  
81 8     8   39 use constant DISCONNECT_REJECT => 42;
  8         12  
  8         308  
82 8     8   38 use constant COA_REQUEST => 43;
  8         17  
  8         282  
83 8     8   37 use constant COA_ACCEPT => 44;
  8         25  
  8         290  
84 8     8   37 use constant COA_ACK => 44;
  8         15  
  8         301  
85 8     8   38 use constant COA_REJECT => 45;
  8         13  
  8         342  
86 8     8   44 use constant COA_NAK => 45;
  8         11  
  8         41456  
87              
88             my $HMAC_MD5_BLCKSZ = 64;
89             my $RFC3579_MSG_AUTH_ATTR_ID = 80;
90             my $RFC3579_MSG_AUTH_ATTR_LEN = 18;
91             my %SERVICES = (
92             'radius' => 1812,
93             'radacct' => 1813,
94             'radius-acct' => 1813,
95             );
96              
97             sub new {
98 7     7 1 324 my $class = shift;
99 7         69 my %h = @_;
100 7         14 my ($host, $port, $service);
101 7         14 my $self = {};
102              
103 7         15 bless $self, $class;
104              
105 7         22 $self->set_error;
106 7         14 $debug = $h{'Debug'};
107              
108 7 50 66     36 if (!$h{'Host'} && !$h{'NodeList'}) {
109 0         0 return $self->set_error('ENOHOST');
110             }
111              
112 7 50       20 $service = $h{'Service'} ? $h{'Service'} : 'radius';
113 7         2136 my $serv_port = getservbyname($service, 'udp');
114 7 50 33     49 if (!$serv_port && !exists($SERVICES{$service})) {
    50          
115 0         0 return $self->set_error('EBADSERV');
116             } elsif (!$serv_port) {
117 0         0 $serv_port = $SERVICES{$service};
118             }
119              
120 7 50       24 $self->{'timeout'} = $h{'TimeOut'} ? $h{'TimeOut'} : 5;
121 7         15 $self->{'localaddr'} = $h{'LocalAddr'};
122 7         13 $self->{'secret'} = $h{'Secret'};
123 7         13 $self->{'message_auth'} = $h{'Rfc3579MessageAuth'};
124              
125 7 100       17 if ($h{'NodeList'}) {
126             # contains resolved node list in text representation
127 4         6 $self->{'node_list_a'} = {};
128 4         6 foreach my $node_a (@{$h{'NodeList'}}) {
  4         11  
129 8         22 my ($n_host, $n_port) = split(/:/, $node_a);
130 8   33     16 $n_port ||= $serv_port;
131 8         124 my @hostinfo = gethostbyname($n_host);
132 8 50       23 if (!scalar(@hostinfo)) {
133 0 0       0 print STDERR "Can't resolve node hostname '$n_host': $! - skipping it!\n" if $debug;
134 0         0 next;
135             }
136              
137 8         36 my $ip = inet_ntoa($hostinfo[4]);
138 8 50       17 print STDERR "Adding ".$ip.':'.$n_port." to node list.\n" if $debug;
139             # store split address to avoid additional parsing later
140 8         31 $self->{'node_list_a'}->{$ip.':'.$n_port} = [$ip, $n_port];
141             }
142              
143 4 50       5 if (!scalar(keys %{$self->{'node_list_a'}})) {
  4         12  
144 0         0 return $self->set_error('ESOCKETFAIL', 'Empty node list.');
145             }
146             }
147              
148 7 100       17 if ($h{'Host'}) {
149 6         21 ($host, $port) = split(/:/, $h{'Host'});
150 6   66     27 $port ||= $serv_port;
151 6 50       18 print STDERR "Using Radius server $host:$port\n" if $debug;
152              
153 6         144933 my @hostinfo = gethostbyname($host);
154 6 100       44 if (!scalar(@hostinfo)) {
155 1 50       13 if ($self->{'node_list_a'}) {
156 1 50       7 print STDERR "Can't resolve hostname '$host'\n" if $debug;
157 1         56 return $self;
158             }
159              
160 0         0 return $self->set_error('ESOCKETFAIL', "Can't resolve hostname '".$host."'.");
161             }
162              
163 5         35 my $ip = inet_ntoa($hostinfo[4]);
164              
165             # if Host used with NodeList - it must be from the list
166 5 100 100     26 if ($self->{'node_list_a'} && !exists($self->{'node_list_a'}->{$ip.':'.$port})) {
167 1 50       16 print STDERR "'$host' doesn't exist in node list - ignoring it!\n" if $debug;
168 1         13 return $self;
169             }
170              
171             # set as active node
172 4         15 $self->{'node_addr_a'} = $ip.':'.$port;
173              
174             my %io_sock_args = (
175             Type => SOCK_DGRAM,
176             Proto => 'udp',
177             Timeout => $self->{'timeout'},
178 4         26 LocalAddr => $self->{'localaddr'},
179             PeerAddr => $host,
180             PeerPort => $port,
181             );
182 4 50       44 $self->{'sock'} = IO::Socket::INET->new(%io_sock_args)
183             or return $self->set_error('ESOCKETFAIL', $@);
184             }
185              
186 5         1504 return $self;
187             }
188              
189             sub send_packet {
190 0     0 1 0 my ($self, $type, $retransmit) = @_;
191 0         0 my $data;
192 0         0 my $length = 20 + length($self->{attributes});
193              
194 0 0       0 if (!$retransmit) {
195 0         0 $request_id = ($request_id + 1) & 0xff;
196             }
197              
198 0         0 $self->set_error;
199 0 0 0     0 if ($type == ACCOUNTING_REQUEST || $type == DISCONNECT_REQUEST || $type == COA_REQUEST) {
      0        
200 0         0 $self->{authenticator} = "\0" x 16;
201 0         0 $self->{authenticator} = $self->calc_authenticator($type, $request_id, $length);
202             } else {
203 0 0       0 $self->gen_authenticator unless defined $self->{authenticator};
204             }
205              
206 0 0 0     0 if (($self->{message_auth} && ($type == ACCESS_REQUEST)) || ($type == STATUS_SERVER)) {
      0        
207 0         0 $length += $RFC3579_MSG_AUTH_ATTR_LEN;
208             $data = pack('C C n', $type, $request_id, $length)
209             . $self->{authenticator}
210             . $self->{attributes}
211 0         0 . pack('C C', $RFC3579_MSG_AUTH_ATTR_ID, $RFC3579_MSG_AUTH_ATTR_LEN)
212             . "\0" x ($RFC3579_MSG_AUTH_ATTR_LEN - 2);
213              
214 0         0 my $msg_authenticator = $self->hmac_md5($data, $self->{secret});
215             $data = pack('C C n', $type, $request_id, $length)
216             . $self->{authenticator}
217             . $self->{attributes}
218 0         0 . pack('C C', $RFC3579_MSG_AUTH_ATTR_ID, $RFC3579_MSG_AUTH_ATTR_LEN)
219             . $msg_authenticator;
220 0 0       0 if ($debug) {
221 0         0 print STDERR "RFC3579 Message-Authenticator: "._ascii_to_hex($msg_authenticator)." was added to request.\n";
222             }
223             } else {
224             $data = pack('C C n', $type, $request_id, $length)
225             . $self->{authenticator}
226 0         0 . $self->{attributes};
227             }
228              
229 0 0       0 if ($debug) {
230 0         0 print STDERR "Sending request:\n";
231 0         0 print STDERR HexDump($data);
232             }
233 0         0 my $res;
234 0 0       0 if (!defined($self->{'node_list_a'})) {
235 0 0       0 if ($debug) { print STDERR 'Sending request to: '.$self->{'node_addr_a'}."\n"; }
  0         0  
236 0   0     0 $res = $self->{'sock'}->send($data) || $self->set_error('ESENDFAIL', $!);
237             } else {
238 0 0 0     0 if (!$retransmit && defined($self->{'sock'})) {
239 0 0       0 if ($debug) { print STDERR 'Sending request to active node: '.$self->{'node_addr_a'}."\n"; }
  0         0  
240 0   0     0 $res = $self->{'sock'}->send($data) || $self->set_error('ESENDFAIL', $!);
241             } else {
242 0 0       0 if ($debug) { print STDERR "ReSending request to all cluster nodes.\n"; }
  0         0  
243 0         0 $self->{'sock'} = undef;
244 0         0 $self->{'sock_list'} = [];
245             my %io_sock_args = (
246             Type => SOCK_DGRAM,
247             Proto => 'udp',
248             Timeout => $self->{'timeout'},
249 0         0 LocalAddr => $self->{'localaddr'},
250             );
251 0         0 foreach my $node (keys %{$self->{'node_list_a'}}) {
  0         0  
252 0 0       0 if ($debug) { print STDERR 'Sending request to: '.$node."\n"; }
  0         0  
253 0         0 $io_sock_args{'PeerAddr'} = $self->{'node_list_a'}->{$node}->[0];
254 0         0 $io_sock_args{'PeerPort'} = $self->{'node_list_a'}->{$node}->[1];
255 0 0       0 my $new_sock = IO::Socket::INET->new(%io_sock_args)
256             or return $self->set_error('ESOCKETFAIL', $@);
257 0   0     0 $res = $new_sock->send($data) || $self->set_error('ESENDFAIL', $!);
258 0 0       0 if ($res) {
259 0         0 push @{$self->{'sock_list'}}, $new_sock;
  0         0  
260             }
261 0   0     0 $res ||= $res;
262             }
263             }
264             }
265 0         0 return $res;
266             }
267              
268             sub recv_packet {
269 0     0 1 0 my ($self, $detect_bad_id) = @_;
270 0         0 my ($data, $type, $id, $length, $auth, $sh, $resp_attributes);
271              
272 0         0 $self->set_error;
273              
274 0 0 0     0 if (defined($self->{'sock_list'}) && scalar(@{$self->{'sock_list'}})) {
  0 0       0  
275 0 0       0 $sh = IO::Select->new(@{$self->{'sock_list'}}) or return $self->set_error('ESELECTFAIL');
  0         0  
276             } elsif (defined($self->{'sock'})) {
277 0 0       0 $sh = IO::Select->new($self->{'sock'}) or return $self->set_error('ESELECTFAIL');
278             } else {
279 0         0 return $self->set_error('ESELECTFAIL');
280             }
281 0         0 my $timeout = $self->{'timeout'};
282 0         0 my @ready;
283             my $from_addr_n;
284 0         0 my ($start_time, $end_time);
285 0         0 while ($timeout > 0){
286 0         0 $start_time = time();
287 0 0       0 @ready = $sh->can_read($timeout) or return $self->set_error('ETIMEOUT', $!);
288 0         0 $end_time = time();
289 0         0 $timeout -= $end_time - $start_time;
290 0         0 $from_addr_n = $ready[0]->recv($data, 65536);
291 0 0       0 if (defined($from_addr_n)) {
292 0         0 last;
293             }
294 0 0 0     0 if (!defined($from_addr_n) && !defined($self->{'sock_list'})) {
    0          
295 0         0 return $self->set_error('ERECVFAIL', $!);
296             }elsif ($debug) {
297 0         0 print STDERR "Received error/event from one peer:".$!."\n";
298             }
299             }
300              
301 0 0       0 if ($debug) {
302 0         0 print STDERR "Received response:\n";
303 0         0 print STDERR HexDump($data);
304             }
305              
306 0 0       0 if (defined($self->{'sock_list'})) {
307             # the sending attempt was 'broadcast' to all cluster nodes
308             # switching to single active node
309 0         0 $self->{'sock'} = $ready[0];
310 0         0 $self->{'sock_list'} = undef;
311 0         0 my ($node_port, $node_iaddr) = sockaddr_in($from_addr_n);
312 0         0 $self->{'node_addr_a'} = inet_ntoa($node_iaddr).':'.$node_port;
313 0 0       0 if ($debug) { print STDERR "Registering new active peeer:".$self->{'node_addr_a'}."\n"; }
  0         0  
314             }
315              
316 0         0 ($type, $id, $length, $auth, $resp_attributes ) = unpack('C C n a16 a*', $data);
317 0 0 0     0 if ($detect_bad_id && defined($id) && ($id != $request_id) ) {
      0        
318 0         0 return $self->set_error('EBADID');
319             }
320              
321 0 0       0 if ($auth ne $self->calc_authenticator($type, $id, $length, $resp_attributes)) {
322 0         0 return $self->set_error('EBADAUTH');
323             }
324             # rewrite attributes only in case of a valid response
325 0         0 $self->{'attributes'} = $resp_attributes;
326 0         0 my $rfc3579_msg_auth;
327 0         0 foreach my $a ($self->get_attributes()) {
328 0 0       0 if ($a->{Code} == $RFC3579_MSG_AUTH_ATTR_ID) {
329 0         0 $rfc3579_msg_auth = $a->{Value};
330 0         0 last;
331             }
332             }
333 0 0       0 if (defined($rfc3579_msg_auth)) {
334 0         0 $self->replace_attr_value($RFC3579_MSG_AUTH_ATTR_ID,
335             "\0" x ($RFC3579_MSG_AUTH_ATTR_LEN - 2));
336             my $hmac_data = pack('C C n', $type, $id, $length)
337             . $self->{'authenticator'}
338 0         0 . $self->{'attributes'};
339 0         0 my $calc_hmac = $self->hmac_md5($hmac_data, $self->{'secret'});
340 0 0       0 if ($calc_hmac ne $rfc3579_msg_auth) {
    0          
341 0 0       0 if ($debug) {
342 0         0 print STDERR "Received response with INVALID RFC3579 Message-Authenticator.\n";
343 0         0 print STDERR 'Received '._ascii_to_hex($rfc3579_msg_auth)."\n";
344 0         0 print STDERR 'Calculated '._ascii_to_hex($calc_hmac)."\n";
345             }
346 0         0 return $self->set_error('EBADAUTH');
347             } elsif ($debug) {
348 0         0 print STDERR "Received response with VALID RFC3579 Message-Authenticator.\n";
349             }
350             }
351              
352 0         0 return $type;
353             }
354              
355             sub check_pwd {
356 0     0 1 0 my ($self, $name, $pwd, $nas) = @_;
357              
358 0 0       0 $nas = eval { $self->{'sock'}->sockhost() } unless defined($nas);
  0         0  
359 0         0 $self->clear_attributes;
360 0   0     0 $self->add_attributes (
361             { Name => 1, Value => $name, Type => 'string' },
362             { Name => 2, Value => $pwd, Type => 'string' },
363             { Name => 4, Value => $nas || '127.0.0.1', Type => 'ipaddr' }
364             );
365              
366 0         0 $self->send_packet(ACCESS_REQUEST);
367 0         0 my $rcv = $self->recv_packet();
368 0   0     0 return (defined($rcv) and $rcv == ACCESS_ACCEPT);
369             }
370              
371             sub clear_attributes {
372 1     1 1 3 my ($self) = @_;
373              
374 1         4 $self->set_error;
375              
376 1         2 delete $self->{'attributes'};
377 1         2 delete $self->{'authenticator'};
378              
379 1         4 1;
380             }
381              
382             sub _decode_enum {
383 3     3   7 my ( $name, $value) = @_;
384              
385 3 50 33     23 if ( defined $value && defined( $dict_val{$name}{$value} ) ) {
386 0         0 $value = $dict_val{$name}{$value}{name};
387             }
388              
389 3         9 return $value;
390             }
391              
392             sub _decode_string {
393 1     1   5 my ( $self, $vendor, $id, $name, $value, $has_tag ) = @_;
394              
395 1 50 33     4 if ( $id == 2 && $vendor eq NO_VENDOR ) {
396 0         0 return '';
397             }
398              
399 1 50       3 if ($has_tag) {
400 0         0 my $tag = unpack('C', substr($value, 0, 1));
401             # rfc2868 section-3.3
402             # If the Tag field is greater than 0x1F, it SHOULD be
403             # interpreted as the first byte of the following String field.
404 0 0       0 if ($tag > 31) {
405 0 0       0 print STDERR "Attribute $name has tag value $tag bigger than 31 - ignoring it!\n" if $debug;
406 0         0 $tag = undef;
407             }
408             else {
409             # cut extracted tag
410 0         0 substr($value, 0, 1, '');
411             }
412 0         0 return ($value, $tag);
413             }
414              
415 1         4 return ($value);
416             }
417              
418             sub _decode_integer {
419 3     3   7 my ( $self, $vendor, $id, $name, $value, $has_tag ) = @_;
420              
421 3         5 my $tag;
422 3 50       7 if ($has_tag) {
423 0         0 $tag = unpack('C', substr($value, 0, 1));
424 0 0       0 if ($tag > 31) {
425 0 0       0 print STDERR "Attribute $name has tag value $tag bigger than 31 - ignoring it!\n" if $debug;
426 0         0 $tag = undef;
427             }
428             else {
429 0         0 substr($value, 0, 1, "\x00");
430             }
431             }
432              
433 3         11 $value = unpack('N', $value);
434 3         10 return (_decode_enum( $name, $value), $tag);
435             }
436              
437             sub _decode_ipaddr {
438 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
439 1         9 return inet_ntoa($value);
440             }
441              
442             sub _decode_ipv6addr {
443 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
444              
445 1         4 my $binary_val = unpack( 'B*', $value );
446 1 50       4 if ($binary_val) {
447 1         6 my $ip_val = ip_bintoip( $binary_val, 6 );
448 1 50       30 if ($ip_val) {
449 1         5 return ip_compress_address( $ip_val, 6 );
450             }
451             }
452              
453 0         0 return undef;
454             }
455              
456             sub _decode_ipv6prefix {
457 1     1   4 my ( $self, $vendor, $id, $name, $value ) = @_;
458              
459 1         5 my ( $skip, $prefix_len, $prefix_val ) = unpack( 'CCB*', $value );
460 1 50 33     9 if ( defined($prefix_len) && $prefix_len < 128 ) {
461 1         3 my $ip_val = ip_bintoip( $prefix_val, 6 );
462 1 50       19 if ($ip_val) {
463 1         3 $value = ip_compress_address( $ip_val, 6 );
464 1 50       86 if ( defined $value ) {
465 1         5 return "$value/$prefix_len";
466             }
467             }
468             }
469              
470 0         0 return undef;
471             }
472              
473             sub _decode_ifid {
474 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
475              
476 1         5 my @shorts = unpack( 'S>S>S>S>', $value );
477 1 50       4 if ( @shorts == 4 ) {
478 1         7 return sprintf( '%x:%x:%x:%x', @shorts );
479             }
480              
481 0         0 return undef;
482             }
483              
484             sub _decode_integer64 {
485 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
486 1         4 return unpack( 'Q>', $value );
487             }
488              
489             sub _decode_avpair {
490 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
491              
492 1         6 $value =~ s/^.*=//;
493 1         3 return $value;
494             }
495              
496             sub _decode_sublist {
497 1     1   4 my ( $self, $vendor, $id, $name, $value ) = @_;
498              
499             # never got a chance to test it, since it seems that Digest attributes only come from clients
500              
501 1         2 my ( $subid, $subvalue, $sublength, @values );
502 1         5 while ( length($value) ) {
503 1         5 ( $subid, $sublength, $value ) = unpack( 'CCa*', $value );
504 1         5 ( $subvalue, $value ) = unpack( 'a' . ( $sublength - 2 ) . ' a*', $value );
505 1         8 push @values, "$dict_val{$name}{$subid}{name} = \"$subvalue\"";
506             }
507              
508 1         4 return join( '; ', @values );
509             }
510              
511             sub _decode_octets {
512 0     0   0 my ( $self, $vendor, $id, $name, $value ) = @_;
513 0         0 return '0x'.unpack("H*", $value);
514             }
515              
516             my %decoder = (
517             # RFC2865
518             string => \&_decode_string,
519             integer => \&_decode_integer,
520             ipaddr => \&_decode_ipaddr,
521             date => \&_decode_integer,
522             time => \&_decode_integer,
523             octets => \&_decode_octets,
524             # RFC3162
525             ipv6addr => \&_decode_ipv6addr,
526             ipv6prefix => \&_decode_ipv6prefix,
527             ifid => \&_decode_ifid,
528             # RFC6929
529             integer64 => \&_decode_integer64,
530             # internal
531             avpair => \&_decode_avpair,
532             sublist => \&_decode_sublist,
533             );
534              
535             sub _decode_value {
536 12     12   478 my ( $self, $vendor, $id, $type, $name, $value, $has_tag ) = @_;
537              
538 12 50       44 if ( defined $type ) {
539 12 100       28 if ( exists $decoder{$type} ) {
540 11         29 my ($decoded, $tag) = $decoder{$type}->( $self, $vendor, $id, $name, $value, $has_tag );
541 11 50       167 return wantarray ? ($decoded, $tag) : $decoded;
542             }
543             else {
544 1 50       4 if ($debug) {
545 0         0 print {*STDERR} "Unsupported type '$type' for attribute with id: '$id'.\n";
  0         0  
546             }
547             }
548             }
549             else {
550 0 0       0 if ($debug) {
551 0         0 print {*STDERR} "Unknown type for attribute with id: '$id'. Check RADIUS dictionaries!\n";
  0         0  
552             }
553             }
554              
555 1         4 return undef;
556             } ## end sub _decode_value
557              
558             sub get_attributes {
559 0     0 1 0 my $self = shift;
560 0         0 my ( $vendor, $vendor_id, $name, $id, $length, $value, $type, $rawvalue, $tag, @a );
561 0         0 my ($attrs) = $self->{attributes};
562              
563 0         0 $self->set_error;
564              
565 0         0 while ( length($attrs) ) {
566 0         0 ( $id, $length, $attrs ) = unpack( 'CCa*', $attrs );
567 0         0 ( $rawvalue, $attrs ) = unpack( 'a' . ( $length - 2 ) . 'a*', $attrs );
568              
569 0 0       0 if ( $id == ATTR_VENDOR ) {
570 0         0 ( $vendor_id, $id, $length, $rawvalue ) = unpack( 'NCCa*', $rawvalue );
571 0   0     0 $vendor = $dict_vendor_id{$vendor_id}{name} // $vendor_id;
572             }
573             else {
574 0         0 $vendor = NO_VENDOR;
575             }
576              
577 0   0     0 my $r = $dict_id{ $vendor }{ $id } // {};
578              
579 0   0     0 $name = $r->{name} // $id;
580 0         0 $type = $r->{type};
581              
582 0         0 ($value, $tag) = $self->_decode_value( $vendor, $id, $type, $name, $rawvalue, $r->{has_tag} );
583              
584 0 0       0 push(
585             @a, {
586             Name => $tag ? $name . ':' . $tag : $name,
587             AttrName => $name,
588             Code => $id,
589             Value => $value,
590             RawValue => $rawvalue,
591             Vendor => $vendor,
592             Tag => $tag,
593             }
594             );
595             } ## end while ( length($attrs) )
596              
597 0         0 return @a;
598             } ## end sub get_attributes
599              
600             # returns vendor's ID or 'not defined' string for the attribute
601             sub vendorID ($) {
602 5     5 0 8 my ($attr) = @_;
603 5 100       12 if (defined $attr->{'Vendor'}) {
604 2   66     17 return ($dict_vendor_name{ $attr->{'Vendor'} }{'id'} // int($attr->{'Vendor'}));
605             } else {
606             # look up vendor by attribute name
607 3 100       14 my $vendor_name = $dict_name{$attr->{'Name'}}{'vendor'} or return NO_VENDOR;
608 2 100       10 my $vendor_id = $dict_vendor_name{$vendor_name}{'id'} or return NO_VENDOR;
609 1         6 return $vendor_id;
610             }
611             }
612              
613             sub _encode_enum {
614 6     6   10 my ( $name, $value, $format ) = @_;
615              
616 6 100       20 if ( defined( $dict_val{$name}{$value} ) ) {
617 1         2 $value = $dict_val{$name}{$value}{id};
618             }
619              
620 6         24 return pack( $format, int($value) );
621             }
622              
623             sub _encode_string {
624 2     2   5 my ( $self, $vendor, $id, $name, $value, $tag ) = @_;
625              
626 2 50 33     7 if ( $id == 2 && $vendor eq NO_VENDOR ) {
627 0         0 $self->gen_authenticator();
628 0         0 return $self->encrypt_pwd($value);
629             }
630              
631             # if ($vendor eq WIMAX_VENDOR) {
632             # # add the "continuation" byte
633             # # but no support for attribute splitting for now
634             # return pack('C', 0) . substr($_[0], 0, 246);
635             # }
636              
637 2 50       4 if (defined $tag) {
638 0         0 $value = pack('C', $tag) . $value;
639             }
640              
641 2         11 return $value;
642             }
643              
644             sub _encode_integer {
645 3     3   6 my ( $self, $vendor, $id, $name, $value, $tag ) = @_;
646 3         8 $value = _encode_enum( $name, $value, 'N' );
647 3 50       8 if (defined $tag) {
648             # tag added to 1st byte, not extending the value length
649 0         0 substr($value, 0, 1, pack('C', $tag) );
650             }
651 3         11 return $value;
652             }
653              
654             sub _encode_ipaddr {
655 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
656 1         9 return inet_aton($value);
657             }
658              
659             sub _encode_ipv6addr {
660 1     1   4 my ( $self, $vendor, $id, $name, $value ) = @_;
661              
662 1         6 my $expanded_val = ip_expand_address( $value, 6 );
663 1 50       69 if ($expanded_val) {
664 1         4 $value = ip_iptobin( $expanded_val, 6 );
665 1 50       18 if ( defined $value ) {
666 1         7 return pack( 'B*', $value );
667             }
668             }
669              
670 0         0 return undef;
671             }
672              
673             sub _encode_ipv6prefix {
674 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
675              
676 1         4 my ( $prefix_val, $prefix_len ) = split( /\//, $value, 2 );
677 1 50       3 if ( defined $prefix_len ) {
678 1         3 my $expanded_val = ip_expand_address( $prefix_val, 6 );
679 1 50       52 if ($expanded_val) {
680 1         3 $value = ip_iptobin( $expanded_val, 6 );
681 1 50       14 if ( defined $value ) {
682 1         7 return pack( 'CCB*', 0, $prefix_len, $value );
683             }
684             }
685             }
686              
687 0         0 return undef;
688             }
689              
690             sub _encode_ifid {
691 1     1   2 my ( $self, $vendor, $id, $name, $value ) = @_;
692              
693 1         5 my @shorts = map { hex() } split( /:/, $value, 4 );
  4         21  
694 1 50       5 if ( @shorts == 4 ) {
695 1         7 return pack( 'S>S>S>S>', @shorts );
696             }
697              
698 0         0 return undef;
699             }
700              
701             sub _encode_integer64 {
702 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
703 1         6 return pack( 'Q>', $value );
704             }
705              
706             sub _encode_avpair {
707 1     1   2 my ( $self, $vendor, $id, $name, $value ) = @_;
708              
709 1         4 $value = "$name=$value";
710 1         4 return substr( $value, 0, 253 );
711             }
712              
713             sub _encode_sublist {
714 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
715              
716             # Digest attributes look like:
717             # Digest-Attributes = 'Method = "REGISTER"'
718              
719 1         2 my @pairs;
720 1 50       4 if ( ref($value) ) {
721             # hashref
722 1 50       4 return undef if ( ref($value) ne 'HASH' );
723 1         1 foreach my $key ( keys %{$value} ) {
  1         4  
724 1         3 push @pairs, [ $key => $value->{$key} ];
725             }
726             }
727             else {
728             # string
729 0         0 foreach my $z ( split( /\"\; /, $value ) ) {
730 0         0 my ( $subname, $subvalue ) = split( /\s+=\s+\"/, $z, 2 );
731 0         0 $subvalue =~ s/\"$//;
732 0         0 push @pairs, [ $subname => $subvalue ];
733             }
734             }
735              
736 1         2 $value = '';
737 1         3 foreach my $da (@pairs) {
738 1         1 my ( $subname, $subvalue ) = @{$da};
  1         2  
739 1         2 my $subid = $dict_val{$name}->{$subname}->{id};
740 1 50       3 next if ( !defined($subid) );
741 1         5 $value .= pack( 'CC', $subid, length($subvalue) + 2 ) . $subvalue;
742             }
743              
744 1         5 return $value;
745             } ## end sub _encode_sublist
746              
747             sub _encode_octets {
748 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
749              
750 1         2 my $new_value = '';
751 1         4 foreach my $c ( split( //, $value ) ) {
752 16         22 $new_value .= pack( 'C', ord($c) );
753             }
754              
755 1         5 return $new_value;
756             }
757              
758             sub _encode_byte {
759 2     2   5 my ( $self, $vendor, $id, $name, $value ) = @_;
760 2         3 return _encode_enum( $name, $value, 'C' );
761             }
762              
763             sub _encode_short {
764 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
765 1         2 return _encode_enum( $name, $value, 'n' );
766             }
767              
768             sub _encode_signed {
769 2     2   3 my ( $self, $vendor, $id, $name, $value ) = @_;
770 2         11 return pack( 'l>', $value );
771             }
772              
773             sub _encode_comboip {
774 2     2   4 my ( $self, $vendor, $id, $name, $value ) = @_;
775              
776 2 100       11 if ( $value =~ m/^\d+\.\d+\.\d+.\d+/ ) {
777             # IPv4 address
778 1         7 return inet_aton($value);
779             }
780              
781             # currently unsupported, use IPv4
782 1         5 return undef;
783             }
784              
785             sub _encode_tlv {
786 1     1   3 my ( $self, $vendor, $id, $name, $value ) = @_;
787              
788 1 50       4 return undef if ( ref($value) ne 'ARRAY' );
789              
790 1         2 my $new_value = '';
791 1         1 foreach my $sattr ( sort { $a->{TLV_ID} <=> $b->{TLV_ID} } @{$value} ) {
  1         5  
  1         6  
792 2         3 my $sattr_name = $sattr->{Name};
793 2   33     10 my $sattr_type = $sattr->{Type} // $dict_name{$sattr_name}{type};
794 2   33     7 my $sattr_id = $dict_name{$sattr_name}{id} // int($sattr_name);
795              
796 2         7 my $svalue = $self->_encode_value( $vendor, $sattr_id, $sattr_type, $sattr_name, $sattr->{Value} );
797 2 50       4 if ( defined $svalue ) {
798 2         9 $new_value .= pack( 'CC', $sattr_id, length($svalue) + 2 ) . $svalue;
799             }
800             }
801              
802 1         5 return $new_value;
803             }
804              
805             my %encoder = (
806             # RFC2865
807             string => \&_encode_string,
808             integer => \&_encode_integer,
809             ipaddr => \&_encode_ipaddr,
810             date => \&_encode_integer,
811             time => \&_encode_integer,
812             # RFC3162
813             ipv6addr => \&_encode_ipv6addr,
814             ipv6prefix => \&_encode_ipv6prefix,
815             ifid => \&_encode_ifid,
816             # RFC6929
817             integer64 => \&_encode_integer64,
818             # internal
819             avpair => \&_encode_avpair,
820             sublist => \&_encode_sublist,
821             octets => \&_encode_octets,
822             # WiMAX
823             byte => \&_encode_byte,
824             short => \&_encode_short,
825             signed => \&_encode_signed,
826             'combo-ip' => \&_encode_comboip,
827             tlv => \&_encode_tlv,
828             );
829              
830             sub _encode_value {
831 22     22   490 my ( $self, $vendor, $id, $type, $name, $value, $tag ) = @_;
832              
833 22 50       43 if ( defined $type ) {
834 22 100       39 if ( exists $encoder{$type} ) {
835 21         54 return $encoder{$type}->( $self, $vendor, $id, $name, $value, $tag );
836             }
837             else {
838 1 50       5 if ($debug) {
839 0         0 print {*STDERR} "Unsupported type '$type' for attribute with name: '$name'.\n";
  0         0  
840             }
841             }
842             }
843             else {
844 0 0       0 if ($debug) {
845 0         0 print {*STDERR} "Unknown type for attribute with name: '$name'. Check RADIUS dictionaries!\n";
  0         0  
846             }
847             }
848              
849 1         4 return undef;
850             } ## end sub _encode_value
851              
852             sub add_attributes {
853 0     0 1 0 my ($self, @attr) = @_;
854 0         0 my ($a, $vendor, $id, $type, $value);
855 0         0 my @a = ();
856 0         0 $self->set_error;
857              
858             # scan for WiMAX TLV
859 0         0 my %request_tlvs;
860 0         0 for my $attr (@attr) {
861 0         0 my $attr_name = $attr->{Name};
862             # tagged attribute in 'name:tag' form
863 0 0       0 if ($attr_name =~ /^([\w-]+):(\d+)$/) {
864 0         0 $attr->{Name} = $1;
865 0         0 $attr->{Tag} = $2;
866 0         0 $attr_name = $1;
867             }
868              
869 0 0       0 die 'unknown attr name '.$attr_name if (! exists $dict_name{$attr_name});
870              
871 0   0     0 $id = $dict_name{$attr_name}{id} // int($attr_name);
872 0         0 $vendor = vendorID($attr);
873 0 0       0 if (exists($dict_name{$attr_name}{'tlv'})) {
874             # this is a TLV attribute
875 0         0 my $tlv = $dict_name{$attr_name}{'tlv'};
876             # insert TLV type so we can order them by type inside of the container attribute
877 0         0 $attr->{'TLV_ID'} = $id;
878              
879 0 0       0 unless (exists($request_tlvs{$tlv})) {
880             # this is a first attribute of this TLV in the request
881 0         0 my $new_attr = {
882             Name => $tlv, Type => 'tlv',
883             Value => [ $attr ]
884             };
885 0         0 $request_tlvs{$tlv} = $new_attr;
886 0         0 push @a, $new_attr;
887             } else {
888 0         0 my $tlv_list = $request_tlvs{$tlv}->{'Value'};
889 0 0       0 next unless ref($tlv_list); # should not happen
890 0         0 push @{$tlv_list}, $attr;
  0         0  
891             }
892             } else {
893             # normal attribute, just copy over
894 0         0 push @a, $attr;
895             }
896             }
897              
898 0         0 for $a (@a) {
899 0   0     0 $id = $dict_name{ $a->{Name} }{id} // int($a->{Name});
900 0   0     0 $type = $a->{Type} // $dict_name{ $a->{Name} }{type};
901 0         0 $vendor = vendorID($a);
902 0   0     0 my $need_tag = (defined $a->{Tag}) || $dict_name{ $a->{Name} }{has_tag};
903 0 0       0 if ($need_tag) {
904 0   0     0 $a->{Tag} //= 0;
905 0 0 0     0 if ($a->{Tag} < 1 || $a->{Tag} > 31) {
906 0 0       0 print STDERR "Tag value is out of range [1..31] for attribute ".$a->{Name} if $debug;
907 0         0 next;
908             }
909             }
910              
911 0 0       0 if ($vendor eq WIMAX_VENDOR) {
912             # WiMAX uses non-standard VSAs - include the continuation byte
913             }
914              
915 0 0       0 unless (defined($value = $self->_encode_value($vendor, $id, $type, $a->{Name}, $a->{Value}, $a->{Tag}))) {
916 0 0       0 print STDERR "Unable to encode attribute $a->{Name} ($id, $type, $vendor) with value '$a->{Value}'\n" if $debug;
917 0         0 next;
918             }
919              
920 0 0       0 if ($debug) {
921             printf STDERR "Adding attribute %s (%s, %s, %s) with value '%s'%s\n",
922             $a->{Name}, $id, $type, $vendor,
923             $a->{Value},
924 0 0       0 ($a->{Tag} ? sprintf(' (tag:%d)', $a->{Tag}) : '');
925             }
926              
927 0 0       0 if ( $vendor eq NO_VENDOR ) {
928             # tag already included in $value, if any
929 0         0 $self->{'attributes'} .= pack('C C', $id, length($value) + 2) . $value;
930             } else {
931             # VSA
932             # pack vendor-ID + vendor-type + vendor-length
933 0 0       0 if ($vendor eq WIMAX_VENDOR) {
934             # add continuation byte
935 0         0 $value = pack('N C C C', $vendor, $id, length($value) + 3, 0) . $value;
936             } else {
937             # tag already included in $value, if any
938 0         0 $value = pack('N C C', $vendor, $id, length($value) + 2) . $value;
939             }
940              
941             # add the normal RADIUS attribute header: type + length
942 0         0 $self->{'attributes'} .= pack('C C', ATTR_VENDOR, length($value) + 2) . $value;
943             }
944             }
945              
946 0         0 return 1;
947             }
948              
949             sub replace_attr_value {
950 0     0 0 0 my ($self, $id, $value) = @_;
951 0         0 my $length = length($self->{'attributes'});
952 0         0 my $done = 0;
953 0         0 my $cur_pos = 0;
954 0         0 while ($cur_pos < $length) {
955 0         0 my ($cur_id, $cur_len) = unpack('C C', substr($self->{'attributes'}, $cur_pos, 2));
956 0 0       0 if ($cur_id == $id) {
957 0 0       0 if (length($value) != ($cur_len - 2)) {
958 0 0       0 if ($debug) {
959 0         0 print STDERR "Trying to replace attribute ($id) with value which has different length\n";
960             }
961 0         0 last;
962             }
963 0         0 substr($self->{'attributes'}, $cur_pos + 2, $cur_len - 2, $value);
964 0         0 $done = 1;
965 0         0 last;
966             }
967 0         0 $cur_pos += $cur_len;
968             }
969 0         0 return $done;
970             }
971              
972             sub calc_authenticator {
973 0     0 0 0 my ($self, $type, $id, $length, $attributes) = @_;
974 0         0 my ($hdr, $ct);
975              
976 0         0 $self->set_error;
977              
978 0         0 $hdr = pack('C C n', $type, $id, $length);
979 0         0 $ct = Digest::MD5->new;
980             $ct->add ($hdr, $self->{'authenticator'},
981             (defined($attributes)) ? $attributes : $self->{'attributes'},
982 0 0       0 $self->{'secret'});
983 0         0 $ct->digest();
984             }
985              
986             sub gen_authenticator {
987 0     0 0 0 my ($self) = @_;
988 0         0 my ($ct);
989              
990 0         0 $self->set_error;
991 0     0 0 0 sub rint { int rand(2 ** 32 - 1) };
992 0         0 $self->{'authenticator'} =
993             pack "L4", rint(), rint(), rint(), rint();
994             }
995              
996             sub encrypt_pwd {
997 0     0 0 0 my ($self, $pwd) = @_;
998 0         0 my ($i, $ct, @pwdp, @encrypted);
999              
1000 0         0 $self->set_error;
1001 0         0 $ct = Digest::MD5->new();
1002              
1003 0         0 my $non_16 = length($pwd) % 16;
1004 0 0       0 $pwd .= "\0" x (16 - $non_16) if $non_16;
1005 0         0 @pwdp = unpack('a16' x (length($pwd) / 16), $pwd);
1006 0         0 for $i (0..$#pwdp) {
1007 0 0       0 my $authent = $i == 0 ? $self->{'authenticator'} : $encrypted[$i - 1];
1008 0         0 $ct->add($self->{'secret'}, $authent);
1009 0         0 $encrypted[$i] = $pwdp[$i] ^ $ct->digest();
1010             }
1011 0         0 return join('',@encrypted);
1012             }
1013 8     8   67 use vars qw(%included_files);
  8         15  
  8         9701  
1014              
1015             sub load_dictionary {
1016 42     42 1 273 shift;
1017 42         57 my $file = shift;
1018             # options, format => {freeradius|gnuradius|default}
1019 42         74 my %opt = @_;
1020 42 50 50     150 my $freeradius_dict = (($opt{format} // '') eq 'freeradius') ? 1 : 0;
1021 42 50 50     118 my $gnuradius_dict = (($opt{format} // '') eq 'gnuradius') ? 1 : 0;
1022              
1023 42         67 my ($cmd, $name, $id, $type, $vendor, $tlv, $extra, $has_tag);
1024 42         54 my $dict_def_vendor = NO_VENDOR;
1025              
1026 42   50     75 $file ||= DEFAULT_DICTIONARY;
1027              
1028             # prevent infinite loop in the include files
1029 42 50       79 return undef if exists($included_files{$file});
1030 42         90 $included_files{$file} = 1;
1031 42 50       210 my $fh = FileHandle->new($file) or die "Can't open dictionary '$file' ($!)\n";
1032 42 0       2979 printf STDERR "Loading dictionary %s using %s format\n", $file, ($freeradius_dict ? 'FreeRADIUS' : 'default') if $debug;
    50          
1033              
1034 42         785 while (my $line = <$fh>) {
1035 8106         9671 chomp $line;
1036 8106 100 100     24220 next if ($line =~ /^\s*$/ || $line =~ /^#/);
1037              
1038 6546 100       10748 if ($freeradius_dict) {
    50          
1039             # ATTRIBUTE name number type [options]
1040 654         2609 ($cmd, $name, $id, $type, $extra) = split(/\s+/, $line);
1041 654         921 $vendor = undef;
1042             }
1043             elsif ($gnuradius_dict) {
1044             # ATTRIBUTE name number type [vendor] [flags]
1045 0         0 ($cmd, $name, $id, $type, $vendor, undef) = split(/\s+/, $line);
1046             # flags looks like '[LR-R-R]=P'
1047 0 0 0     0 $vendor = NO_VENDOR if ($vendor && ($vendor eq '-' || $vendor =~ /^\[/));
      0        
1048             }
1049             else {
1050             # our default format (Livingston radius)
1051 5892         20408 ($cmd, $name, $id, $type, $vendor) = split(/\s+/, $line);
1052             }
1053              
1054 6546         9301 $cmd = lc($cmd);
1055 6546 100       11580 if ($cmd eq 'attribute') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1056             # Vendor was previously defined via BEGIN-VENDOR
1057 1473   50     3772 $vendor ||= $dict_def_vendor // NO_VENDOR;
      66        
1058              
1059 1473         1500 $has_tag = 0;
1060 1473 100 100     2229 if ($extra && $extra !~ /^#/) {
1061 33         61 my(@p) = split(/,/, $extra);
1062 33         97 $has_tag = grep /has_tag/, @p;
1063             }
1064              
1065 1473         4982 $dict_name{ $name } = {
1066             id => $id,
1067             type => $type,
1068             vendor => $vendor,
1069             has_tag => $has_tag,
1070             };
1071              
1072 1473 100       2179 if (defined($tlv)) {
1073             # inside of a TLV definition
1074 159         261 $dict_id{$vendor}{$id}{'tlv'} = $tlv;
1075 159         232 $dict_name{$name}{'tlv'} = $tlv;
1076             # IDs of TLVs are only unique within the master attribute, not in the dictionary
1077             # so we have to use a composite key
1078 159         476 $dict_id{$vendor}{$tlv.'/'.$id}{'name'} = $name;
1079 159         507 $dict_id{$vendor}{$tlv.'/'.$id}{'type'} = $type;
1080             } else {
1081 1314         5118 $dict_id{$vendor}{$id} = {
1082             name => $name,
1083             type => $type,
1084             has_tag => $has_tag,
1085             };
1086             }
1087             } elsif ($cmd eq 'value') {
1088 4203 100       9035 next unless exists($dict_name{$name});
1089 1113         2371 $dict_val{$name}->{$type}->{'name'} = $id;
1090 1113         4065 $dict_val{$name}->{$id}->{'id'} = $type;
1091             } elsif ($cmd eq 'vendor') {
1092 12         43 $dict_vendor_name{$name}{'id'} = $id;
1093 12         49 $dict_vendor_id{$id}{'name'} = $name;
1094             } elsif ($cmd eq 'begin-vendor') {
1095 3         6 $dict_def_vendor = $name;
1096 3 50       11 if (! $freeradius_dict) {
1097             # force format
1098 3         5 $freeradius_dict = 1;
1099 3 50       11 print STDERR "Detected BEGIN-VENDOR, switch to FreeRADIUS dictionary format\n" if $debug;
1100             }
1101             } elsif ($cmd eq 'end-vendor') {
1102 3         65 $dict_def_vendor = NO_VENDOR;
1103             } elsif ($cmd eq 'begin-tlv') {
1104             # FreeRADIUS dictionary syntax for defining WiMAX TLV
1105 18 50 33     126 if (exists($dict_name{$name}) and $dict_name{$name}{'type'} eq 'tlv') {
1106             # This name was previously defined as an attribute with TLV type
1107 18         44 $tlv = $name;
1108             }
1109             } elsif ($cmd eq 'end-tlv') {
1110 18         38 undef($tlv);
1111             } elsif ($cmd eq '$include') {
1112 39         101 my @path = split("/", $file);
1113 39         53 pop @path; # remove the filename at the end
1114 39 50       126 my $path = ( $name =~ /^\// ) ? $name : join("/", @path, $name);
1115 39         111 load_dictionary('', $path, %opt);
1116             }
1117             }
1118 42         238 $fh->close;
1119             # print Dumper(\%dict_name);
1120 42         849 1;
1121             }
1122              
1123             sub clear_dictionary {
1124 0     0 0 0 shift;
1125 0         0 %dict_id = ();
1126 0         0 %dict_name = ();
1127 0         0 %dict_val = ();
1128 0         0 %dict_vendor_id = ();
1129 0         0 %dict_vendor_name = ();
1130 0         0 %included_files = ();
1131             }
1132              
1133             sub set_timeout {
1134 0     0 1 0 my ($self, $timeout) = @_;
1135              
1136 0         0 $self->{'timeout'} = $timeout;
1137 0 0       0 $self->{'sock'}->timeout($timeout) if (defined $self->{'sock'});
1138 0 0       0 if (defined $self->{'sock_list'}) {
1139 0         0 foreach my $sock (@{$self->{'sock_list'}}) {
  0         0  
1140 0         0 $sock->timeout($timeout);
1141             }
1142             }
1143              
1144 0         0 1;
1145             }
1146              
1147             sub set_error {
1148 9     9 0 24 my ($self, $error, $comment) = @_;
1149 9         12 $@ = undef;
1150 9 100       46 $radius_error = $self->{'error'} = (defined($error) ? $error : 'ENONE');
1151 9 100       23 $error_comment = $self->{'error_comment'} = (defined($comment) ? $comment : '');
1152 9         15 undef;
1153             }
1154              
1155             sub get_error {
1156 4     4 1 11 my ($self) = @_;
1157              
1158 4 100       9 if (!ref($self)) {
1159 2         9 return $radius_error;
1160             } else {
1161 2         9 return $self->{'error'};
1162             }
1163             }
1164              
1165             sub strerror {
1166 1     1 1 3 my ($self, $error) = @_;
1167              
1168 1         11 my %errors = (
1169             'ENONE', 'none',
1170             'ESELECTFAIL', 'select creation failed',
1171             'ETIMEOUT', 'timed out waiting for packet',
1172             'ESOCKETFAIL', 'socket creation failed',
1173             'ENOHOST', 'no host specified',
1174             'EBADAUTH', 'bad response authenticator',
1175             'ESENDFAIL', 'send failed',
1176             'ERECVFAIL', 'receive failed',
1177             'EBADSERV', 'unrecognized service',
1178             'EBADID', 'response to unknown request'
1179             );
1180              
1181 1 50       18 if (!ref($self)) {
1182 0         0 return $errors{$radius_error};
1183             }
1184 1 50       7 return $errors{ (defined($error) ? $error : $self->{'error'} ) };
1185             }
1186              
1187             sub error_comment {
1188 1     1 1 3 my ($self) = @_;
1189              
1190 1 50       16 if (!ref($self)) {
1191 0         0 return $error_comment;
1192             } else {
1193 1         4 return $self->{'error_comment'};
1194             }
1195             }
1196              
1197             sub get_active_node {
1198 4     4 1 1370 my ($self) = @_;
1199 4         18 return $self->{'node_addr_a'};
1200             }
1201              
1202             sub hmac_md5 {
1203 1     1 0 119 my ($self, $data, $key) = @_;
1204 1         6 my $ct = Digest::MD5->new;
1205              
1206 1 50       5 if (length($key) > $HMAC_MD5_BLCKSZ) {
1207 0         0 $ct->add($key);
1208 0         0 $key = $ct->digest();
1209             }
1210 1         5 my $ipad = $key ^ ("\x36" x $HMAC_MD5_BLCKSZ);
1211 1         3 my $opad = $key ^ ("\x5c" x $HMAC_MD5_BLCKSZ);
1212 1         4 $ct->reset();
1213 1         4 $ct->add($ipad, $data);
1214 1         4 my $digest1 = $ct->digest();
1215 1         3 $ct->reset();
1216 1         3 $ct->add($opad, $digest1);
1217 1         11 return $ct->digest();
1218             }
1219              
1220             sub _ascii_to_hex {
1221 0     0     my ($string) = @_;
1222 0           my $hex_res = '';
1223 0           foreach my $cur_chr (unpack('C*',$string)) {
1224 0           $hex_res .= sprintf("%02X ", $cur_chr);
1225             }
1226 0           return $hex_res;
1227             }
1228              
1229              
1230             1;
1231             __END__