File Coverage

blib/lib/Authen/Radius.pm
Criterion Covered Total %
statement 495 675 73.3
branch 151 304 49.6
condition 50 135 37.0
subroutine 81 86 94.1
pod 13 22 59.0
total 790 1222 64.6


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