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   832708 use strict;
  12         120  
  12         349  
20 12     12   60 use warnings;
  12         24  
  12         289  
21 12     12   148 use v5.10;
  12         38  
22 12     12   5240 use FileHandle;
  12         116732  
  12         62  
23 12     12   9709 use IO::Socket;
  12         173061  
  12         52  
24 12     12   10455 use IO::Select;
  12         19401  
  12         647  
25 12     12   94 use Digest::MD5;
  12         28  
  12         370  
26 12     12   7257 use Data::Dumper;
  12         81680  
  12         748  
27 12     12   4998 use Data::HexDump;
  12         18041  
  12         631  
28 12     12   8169 use Net::IP qw(ip_bintoip ip_compress_address ip_expand_address ip_iptobin);
  12         721045  
  12         1777  
29 12     12   6889 use Time::HiRes qw(time);
  12         16963  
  12         68  
30              
31 12     12   2543 use vars qw($VERSION @ISA @EXPORT);
  12         33  
  12         1869  
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.31';
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   114 use constant WIMAX_VENDOR => '24757';
  12         30  
  12         899  
50 12     12   84 use constant WIMAX_CONTINUATION_BIT => 0b10000000;
  12         28  
  12         685  
51              
52 12     12   81 use constant NO_VENDOR => 'not defined';
  12         25  
  12         616  
53              
54 12     12   72 use constant DEFAULT_DICTIONARY => '/etc/raddb/dictionary';
  12         28  
  12         1230  
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   87 use constant ATTR_VENDOR => 26;
  12         40  
  12         946  
70              
71 12     12   99 use constant ACCESS_REQUEST => 1;
  12         24  
  12         659  
72 12     12   75 use constant ACCESS_ACCEPT => 2;
  12         25  
  12         629  
73 12     12   94 use constant ACCESS_REJECT => 3;
  12         25  
  12         687  
74 12     12   135 use constant ACCOUNTING_REQUEST => 4;
  12         25  
  12         911  
75 12     12   80 use constant ACCOUNTING_RESPONSE => 5;
  12         24  
  12         700  
76 12     12   83 use constant ACCOUNTING_STATUS => 6;
  12         29  
  12         620  
77 12     12   74 use constant ACCESS_CHALLENGE => 11;
  12         34  
  12         633  
78 12     12   81 use constant STATUS_SERVER => 12;
  12         37  
  12         563  
79 12     12   80 use constant DISCONNECT_REQUEST => 40;
  12         21  
  12         631  
80 12     12   111 use constant DISCONNECT_ACCEPT => 41;
  12         23  
  12         558  
81 12     12   70 use constant DISCONNECT_REJECT => 42;
  12         30  
  12         611  
82 12     12   76 use constant COA_REQUEST => 43;
  12         27  
  12         574  
83 12     12   73 use constant COA_ACCEPT => 44;
  12         34  
  12         560  
84 12     12   74 use constant COA_ACK => 44;
  12         38  
  12         604  
85 12     12   72 use constant COA_REJECT => 45;
  12         23  
  12         622  
86 12     12   75 use constant COA_NAK => 45;
  12         25  
  12         77442  
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 722 my $class = shift;
99 11         61 my %h = @_;
100 11         28 my ($host, $port, $service);
101 11         30 my $self = {};
102              
103 11         26 bless $self, $class;
104              
105 11         50 $self->set_error;
106 11         23 $debug = $h{'Debug'};
107              
108 11 50 66     48 if (!$h{'Host'} && !$h{'NodeList'}) {
109 0         0 return $self->set_error('ENOHOST');
110             }
111              
112 11 50       34 $service = $h{'Service'} ? $h{'Service'} : 'radius';
113 11         4881 my $serv_port = getservbyname($service, 'udp');
114 11 50 33     107 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       49 $self->{'timeout'} = $h{'TimeOut'} ? $h{'TimeOut'} : 5;
121 11         29 $self->{'localaddr'} = $h{'LocalAddr'};
122 11         30 $self->{'secret'} = $h{'Secret'};
123 11         31 $self->{'message_auth'} = $h{'Rfc3579MessageAuth'};
124              
125 11 100       35 if ($h{'NodeList'}) {
126             # contains resolved node list in text representation
127 4         9 $self->{'node_list_a'} = {};
128 4         6 foreach my $node_a (@{$h{'NodeList'}}) {
  4         12  
129 8         29 my ($n_host, $n_port) = split(/:/, $node_a);
130 8   33     17 $n_port ||= $serv_port;
131 8         155 my @hostinfo = gethostbyname($n_host);
132 8 50       26 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         47 my $ip = inet_ntoa($hostinfo[4]);
138 8 50       21 print STDERR "Adding ".$ip.':'.$n_port." to node list.\n" if $debug;
139             # store split address to avoid additional parsing later
140 8         41 $self->{'node_list_a'}->{$ip.':'.$n_port} = [$ip, $n_port];
141             }
142              
143 4 50       7 if (!scalar(keys %{$self->{'node_list_a'}})) {
  4         16  
144 0         0 return $self->set_error('ESOCKETFAIL', 'Empty node list.');
145             }
146             }
147              
148 11 100       35 if ($h{'Host'}) {
149 10         48 ($host, $port) = split(/:/, $h{'Host'});
150 10   66     67 $port ||= $serv_port;
151 10 50       41 print STDERR "Using Radius server $host:$port\n" if $debug;
152              
153 10         12948 my @hostinfo = gethostbyname($host);
154 10 100       67 if (!scalar(@hostinfo)) {
155 1 50       7 if ($self->{'node_list_a'}) {
156 1 50       4 print STDERR "Can't resolve hostname '$host'\n" if $debug;
157 1         29 return $self;
158             }
159              
160 0         0 return $self->set_error('ESOCKETFAIL', "Can't resolve hostname '".$host."'.");
161             }
162              
163 9         82 my $ip = inet_ntoa($hostinfo[4]);
164              
165             # if Host used with NodeList - it must be from the list
166 9 100 100     54 if ($self->{'node_list_a'} && !exists($self->{'node_list_a'}->{$ip.':'.$port})) {
167 1 50       20 print STDERR "'$host' doesn't exist in node list - ignoring it!\n" if $debug;
168 1         15 return $self;
169             }
170              
171             # set as active node
172 8         40 $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         70 LocalAddr => $self->{'localaddr'},
179             PeerAddr => $host,
180             PeerPort => $port,
181             );
182 8 50       105 $self->{'sock'} = IO::Socket::INET->new(%io_sock_args)
183             or return $self->set_error('ESOCKETFAIL', $@);
184             }
185              
186 9         3793 return $self;
187             }
188              
189             sub send_packet {
190 3     3 1 537 my ($self, $type, $retransmit) = @_;
191              
192 3   100     21 $self->{attributes} //= '';
193              
194 3         7 my $data;
195 3         9 my $length = 20 + length($self->{attributes});
196              
197 3 50       12 if (!$retransmit) {
198 3         13 $request_id = ($request_id + 1) & 0xff;
199             }
200              
201 3         11 $self->set_error;
202 3 50 33     29 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     25 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         23 . $self->{attributes};
230             }
231              
232 3 50       26 if ($debug) {
233 0         0 print STDERR "Sending request:\n";
234 0         0 print STDERR HexDump($data);
235             }
236 3         8 my $res;
237 3 50       38 if (!defined($self->{'node_list_a'})) {
238 3 50       11 if ($debug) { print STDERR 'Sending request to: '.$self->{'node_addr_a'}."\n"; }
  0         0  
239 3   33     33 $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         524 return $res;
269             }
270              
271             sub recv_packet {
272 1     1 1 4 my ($self, $detect_bad_id) = @_;
273 1         10 my ($data, $type, $id, $length, $auth, $sh, $resp_attributes);
274              
275 1         4 $self->set_error;
276              
277 1 50 50     23 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       11 $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         4 my @ready;
286             my $from_addr_n;
287 1         0 my ($start_time, $end_time);
288 1         4 while ($timeout > 0){
289 1         13 $start_time = time();
290 1 50       5 @ready = $sh->can_read($timeout) or return $self->set_error('ETIMEOUT', $!);
291 1         52 $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       44 if (defined($from_addr_n)) {
295 0         0 last;
296             }
297 1 50 33     8 if (!defined($from_addr_n) && !defined($self->{'sock_list'})) {
    0          
298 1         4 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 569 my ($self, $name, $pwd, $nas) = @_;
360              
361 1 50       5 $nas = eval { $self->{'sock'}->sockhost() } unless defined($nas);
  1         7  
362 1         63 $self->clear_attributes;
363 1   50     48 $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         8 $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 1212 my ($self) = @_;
376              
377 3         12 $self->set_error;
378              
379 3         7 delete $self->{'attributes'};
380 3         9 delete $self->{'authenticator'};
381              
382 3         13 1;
383             }
384              
385             sub _decode_enum {
386 4     4   11 my ( $name, $value) = @_;
387              
388 4 50 33     29 if ( defined $value && defined( $dict_val{$name}{$value} ) ) {
389 0         0 $value = $dict_val{$name}{$value}{name};
390             }
391              
392 4         14 return $value;
393             }
394              
395             sub _decode_string {
396 1     1   3 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       3 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         4 return ($value);
419             }
420              
421             sub _decode_integer {
422 4     4   24 my ( $self, $vendor, $id, $name, $value, $has_tag ) = @_;
423              
424 4         7 my $tag;
425 4 50       17 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         18 $value = unpack('N', $value);
437 4         15 return (_decode_enum( $name, $value), $tag);
438             }
439              
440             sub _decode_ipaddr {
441 1     1   4 my ( $self, $vendor, $id, $name, $value ) = @_;
442 1         10 return inet_ntoa($value);
443             }
444              
445             sub _decode_ipv6addr {
446 1     1   5 my ( $self, $vendor, $id, $name, $value ) = @_;
447              
448 1         6 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       38 if ($ip_val) {
452 1         7 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     9 if ( defined($prefix_len) && $prefix_len < 128 ) {
464 1         4 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       107 if ( defined $value ) {
468 1         7 return "$value/$prefix_len";
469             }
470             }
471             }
472              
473 0         0 return undef;
474             }
475              
476             sub _decode_ifid {
477 1     1   5 my ( $self, $vendor, $id, $name, $value ) = @_;
478              
479 1         7 my @shorts = unpack( 'S>S>S>S>', $value );
480 1 50       5 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         6 return unpack( 'Q>', $value );
490             }
491              
492             sub _decode_avpair {
493 1     1   4 my ( $self, $vendor, $id, $name, $value ) = @_;
494              
495 1         9 $value =~ s/^.*=//;
496 1         4 return $value;
497             }
498              
499             sub _decode_sublist {
500 1     1   4 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         2 my ( $subid, $subvalue, $sublength, @values );
505 1         5 while ( length($value) ) {
506 1         7 ( $subid, $sublength, $value ) = unpack( 'CCa*', $value );
507 1         7 ( $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   4 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   576 my ( $self, $vendor, $id, $type, $name, $value, $has_tag ) = @_;
540              
541 14 50       35 if ( defined $type ) {
542 14 100       35 if ( exists $decoder{$type} ) {
543 13         49 my ($decoded, $tag) = $decoder{$type}->( $self, $vendor, $id, $name, $value, $has_tag );
544 13 100       231 return wantarray ? ($decoded, $tag) : $decoded;
545             }
546             else {
547 1 50       6 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         5 return undef;
559             } ## end sub _decode_value
560              
561             sub get_attributes {
562 3     3 1 17 my $self = shift;
563 3         6 my ( $vendor, $vendor_id, $name, $id, $length, $value, $type, $rawvalue, $tag, @a );
564 3   100     14 my $attrs = $self->{attributes} // '';
565              
566 3         13 $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       6 if ( $id == ATTR_VENDOR ) {
573 2         8 ( $vendor_id, $id, $length, $rawvalue ) = unpack( 'NCCa*', $rawvalue );
574 2   33     8 $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     12 $name = $r->{name} // $id;
583 2         5 $type = $r->{type};
584              
585 2         7 ($value, $tag) = $self->_decode_value( $vendor, $id, $type, $name, $rawvalue, $r->{has_tag} );
586              
587 2 50       15 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         13 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 30 my ($attr) = @_;
606 14 100       53 if (defined $attr->{'Vendor'}) {
    100          
607 2   66     25 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       20 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         16 return NO_VENDOR;
615             }
616              
617             sub _encode_enum {
618 7     7   16 my ( $name, $value, $format ) = @_;
619              
620 7 100       43 if ( defined( $dict_val{$name}{$value} ) ) {
621 1         4 $value = $dict_val{$name}{$value}{id};
622             }
623              
624 7         40 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     17 if ( $id == 2 && $vendor eq NO_VENDOR ) {
631 1         5 $self->gen_authenticator();
632 1         4 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       9 if (defined $tag) {
642 0         0 $value = pack('C', $tag) . $value;
643             }
644              
645 3         20 return $value;
646             }
647              
648             sub _encode_integer {
649 4     4   14 my ( $self, $vendor, $id, $name, $value, $tag ) = @_;
650 4         13 $value = _encode_enum( $name, $value, 'N' );
651 4 50       11 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         26 return $value;
656             }
657              
658             sub _encode_ipaddr {
659 2     2   7 my ( $self, $vendor, $id, $name, $value ) = @_;
660 2         21 return inet_aton($value);
661             }
662              
663             sub _encode_ipv6addr {
664 1     1   5 my ( $self, $vendor, $id, $name, $value ) = @_;
665              
666 1         8 my $expanded_val = ip_expand_address( $value, 6 );
667 1 50       128 if ($expanded_val) {
668 1         5 $value = ip_iptobin( $expanded_val, 6 );
669 1 50       29 if ( defined $value ) {
670 1         9 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         5 my $expanded_val = ip_expand_address( $prefix_val, 6 );
683 1 50       64 if ($expanded_val) {
684 1         5 $value = ip_iptobin( $expanded_val, 6 );
685 1 50       16 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   3 my ( $self, $vendor, $id, $name, $value ) = @_;
696              
697 1         6 my @shorts = map { hex() } split( /:/, $value, 4 );
  4         12  
698 1 50       7 if ( @shorts == 4 ) {
699 1         13 return pack( 'S>S>S>S>', @shorts );
700             }
701              
702 0         0 return undef;
703             }
704              
705             sub _encode_integer64 {
706 1     1   5 my ( $self, $vendor, $id, $name, $value ) = @_;
707 1         9 return pack( 'Q>', $value );
708             }
709              
710             sub _encode_avpair {
711 1     1   5 my ( $self, $vendor, $id, $name, $value ) = @_;
712              
713 1         3 $value = "$name=$value";
714 1         6 return substr( $value, 0, 253 );
715             }
716              
717             sub _encode_sublist {
718 1     1   5 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       7 if ( ref($value) ) {
725             # hashref
726 1 50       5 return undef if ( ref($value) ne 'HASH' );
727 1         3 foreach my $key ( keys %{$value} ) {
  1         4  
728 1         6 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         3 my ( $subname, $subvalue ) = @{$da};
  1         3  
743 1         5 my $subid = $dict_val{$name}->{$subname}->{id};
744 1 50       4 next if ( !defined($subid) );
745 1         7 $value .= pack( 'CC', $subid, length($subvalue) + 2 ) . $subvalue;
746             }
747              
748 1         7 return $value;
749             } ## end sub _encode_sublist
750              
751             sub _encode_octets {
752 2     2   10 my ( $self, $vendor, $id, $name, $value ) = @_;
753              
754 2         5 my $new_value = '';
755 2         11 foreach my $c ( split( //, $value ) ) {
756 18         38 $new_value .= pack( 'C', ord($c) );
757             }
758              
759 2         11 return $new_value;
760             }
761              
762             sub _encode_byte {
763 2     2   7 my ( $self, $vendor, $id, $name, $value ) = @_;
764 2         6 return _encode_enum( $name, $value, 'C' );
765             }
766              
767             sub _encode_short {
768 1     1   5 my ( $self, $vendor, $id, $name, $value ) = @_;
769 1         3 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   5 my ( $self, $vendor, $id, $name, $value ) = @_;
779              
780 2 100       13 if ( $value =~ m/^\d+\.\d+\.\d+.\d+/ ) {
781             # IPv4 address
782 1         9 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   3 my ( $self, $vendor, $id, $name, $value ) = @_;
791              
792 1 50       6 return undef if ( ref($value) ne 'ARRAY' );
793              
794 1         3 my $new_value = '';
795 1         3 foreach my $sattr ( sort { $a->{TLV_ID} <=> $b->{TLV_ID} } @{$value} ) {
  1         8  
  1         7  
796 2         5 my $sattr_name = $sattr->{Name};
797 2   33     12 my $sattr_type = $sattr->{Type} // $dict_name{$sattr_name}{type};
798 2   33     7 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       21 if ( defined $svalue ) {
802 2         10 $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   802 my ( $self, $vendor, $id, $type, $name, $value, $tag ) = @_;
836              
837 29 100       63 if ( defined $type ) {
838 27 100       68 if ( exists $encoder{$type} ) {
839 26         111 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       6 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 503 my ($self, @attr) = @_;
858 4         10 my ($a, $vendor, $id, $type, $value, $need_tag);
859 4         26 my @a = ();
860 4         16 $self->set_error;
861              
862             # scan for WiMAX TLV
863 4         7 my %request_tlvs;
864 4         11 for my $attr (@attr) {
865 7         14 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       20 if (! exists $dict_name{$attr_name}) {
874             # no dictionaries loaded, $attr_name must be attribute ID
875 5         37 push @a, $attr;
876 5         12 next;
877             }
878              
879 2   33     6 $id = $dict_name{$attr_name}{id} // int($attr_name);
880 2         7 $vendor = vendorID($attr);
881 2 50       6 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         5 push @a, $attr;
903             }
904             }
905              
906 4         10 for $a (@a) {
907 7 100       36 if (exists $dict_name{ $a->{Name} }) {
908 2         5 my $def = $dict_name{ $a->{Name} };
909 2         5 $id = $def->{id};
910             # allow to override Type (why?)
911 2   33     8 $type = $a->{Type} // $def->{type};
912 2   33     6 $need_tag = $a->{Tag} // $def->{has_tag};
913             }
914             else {
915             # ID must be a value for Name
916 5         13 $id = int($a->{Name});
917 5         8 $type = $a->{Type};
918 5         11 $need_tag = $a->{Tag};
919             }
920              
921             # we do not support 0 value for Tag
922 7 50       17 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         20 $vendor = vendorID($a);
931 7 100       28 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         4 next;
940             }
941              
942 5 50       25 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       13 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         8 $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         7 $value = pack('N C C', $vendor, $id, length($value) + 2) . $value;
961             }
962              
963             # add the normal RADIUS attribute header: type + length
964 2         10 $self->{'attributes'} .= pack('C C', ATTR_VENDOR, length($value) + 2) . $value;
965             }
966             }
967              
968 4         14 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         7 my ($ct);
1011              
1012 3         9 $self->set_error;
1013 12     12 0 183 sub rint { int rand(2 ** 32 - 1) };
1014 3         14 $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         3 my ($i, $ct, @pwdp, @encrypted);
1021              
1022 1         3 $self->set_error;
1023 1         8 $ct = Digest::MD5->new();
1024              
1025 1         3 my $non_16 = length($pwd) % 16;
1026 1 50       7 $pwd .= "\0" x (16 - $non_16) if $non_16;
1027 1         9 @pwdp = unpack('a16' x (length($pwd) / 16), $pwd);
1028 1         5 for $i (0..$#pwdp) {
1029 1 50       5 my $authent = $i == 0 ? $self->{'authenticator'} : $encrypted[$i - 1];
1030 1         15 $ct->add($self->{'secret'}, $authent);
1031 1         8 $encrypted[$i] = $pwdp[$i] ^ $ct->digest();
1032             }
1033 1         10 return join('',@encrypted);
1034             }
1035 12     12   120 use vars qw(%included_files);
  12         26  
  12         18377  
1036              
1037             sub load_dictionary {
1038 56     56 1 913 shift;
1039 56         139 my $file = shift;
1040             # options, format => {freeradius|gnuradius|default}
1041 56         104 my %opt = @_;
1042 56 50 50     235 my $freeradius_dict = (($opt{format} // '') eq 'freeradius') ? 1 : 0;
1043 56 50 50     189 my $gnuradius_dict = (($opt{format} // '') eq 'gnuradius') ? 1 : 0;
1044              
1045 56         109 my ($cmd, $name, $id, $type, $vendor, $tlv, $extra, $has_tag);
1046 56         84 my $dict_def_vendor = NO_VENDOR;
1047              
1048 56   50     113 $file ||= DEFAULT_DICTIONARY;
1049              
1050             # prevent infinite loop in the include files
1051 56 50       128 return undef if exists($included_files{$file});
1052 56         173 $included_files{$file} = 1;
1053 56 50       292 my $fh = FileHandle->new($file) or die "Can't open dictionary '$file' ($!)\n";
1054 56 0       4517 printf STDERR "Loading dictionary %s using %s format\n", $file, ($freeradius_dict ? 'FreeRADIUS' : 'default') if $debug;
    50          
1055              
1056 56         1350 while (my $line = <$fh>) {
1057 10808         15280 chomp $line;
1058 10808 100 100     38641 next if ($line =~ /^\s*$/ || $line =~ /^#/);
1059              
1060 8728 100       15999 if ($freeradius_dict) {
    50          
1061             # ATTRIBUTE name number type [options]
1062 872         4074 ($cmd, $name, $id, $type, $extra) = split(/\s+/, $line);
1063 872         1520 $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         32088 ($cmd, $name, $id, $type, $vendor) = split(/\s+/, $line);
1074             }
1075              
1076 8728         15546 $cmd = lc($cmd);
1077 8728 100       18608 if ($cmd eq 'attribute') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1078             # Vendor was previously defined via BEGIN-VENDOR
1079 1964   50     6051 $vendor ||= $dict_def_vendor // NO_VENDOR;
      66        
1080              
1081 1964         2440 $has_tag = 0;
1082 1964 100 100     3539 if ($extra && $extra !~ /^#/) {
1083 44         96 my(@p) = split(/,/, $extra);
1084 44         147 $has_tag = grep /has_tag/, @p;
1085             }
1086              
1087 1964         8418 $dict_name{ $name } = {
1088             id => $id,
1089             type => $type,
1090             vendor => $vendor,
1091             has_tag => $has_tag,
1092             };
1093              
1094 1964 100       3539 if (defined($tlv)) {
1095             # inside of a TLV definition
1096 212         416 $dict_id{$vendor}{$id}{'tlv'} = $tlv;
1097 212         404 $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         829 $dict_id{$vendor}{$tlv.'/'.$id}{'name'} = $name;
1101 212         885 $dict_id{$vendor}{$tlv.'/'.$id}{'type'} = $type;
1102             } else {
1103 1752         8521 $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       14477 next unless exists($dict_name{$name});
1111 1484         3928 $dict_val{$name}->{$type}->{'name'} = $id;
1112 1484         6666 $dict_val{$name}->{$id}->{'id'} = $type;
1113             } elsif ($cmd eq 'vendor') {
1114 16         66 $dict_vendor_name{$name}{'id'} = $id;
1115 16         69 $dict_vendor_id{$id}{'name'} = $name;
1116             } elsif ($cmd eq 'begin-vendor') {
1117 4         10 $dict_def_vendor = $name;
1118 4 50       24 if (! $freeradius_dict) {
1119             # force format
1120 4         10 $freeradius_dict = 1;
1121 4 50       19 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         59 undef($tlv);
1133             } elsif ($cmd eq '$include') {
1134 52         150 my @path = split("/", $file);
1135 52         95 pop @path; # remove the filename at the end
1136 52 50       177 my $path = ( $name =~ /^\// ) ? $name : join("/", @path, $name);
1137 52         166 load_dictionary('', $path, %opt);
1138             }
1139             }
1140 56         332 $fh->close;
1141             # print Dumper(\%dict_name);
1142 56         1204 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         52 $@ = undef;
1172 31 100       149 $radius_error = $self->{'error'} = (defined($error) ? $error : 'ENONE');
1173 31 100       78 $error_comment = $self->{'error_comment'} = (defined($comment) ? $comment : '');
1174 31         62 undef;
1175             }
1176              
1177             sub get_error {
1178 4     4 1 15 my ($self) = @_;
1179              
1180 4 100       11 if (!ref($self)) {
1181 2         10 return $radius_error;
1182             } else {
1183 2         12 return $self->{'error'};
1184             }
1185             }
1186              
1187             sub strerror {
1188 1     1 1 3 my ($self, $error) = @_;
1189              
1190 1         10 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       8 return $errors{ (defined($error) ? $error : $self->{'error'} ) };
1207             }
1208              
1209             sub error_comment {
1210 1     1 1 3 my ($self) = @_;
1211              
1212 1 50       18 if (!ref($self)) {
1213 0         0 return $error_comment;
1214             } else {
1215 1         42 return $self->{'error_comment'};
1216             }
1217             }
1218              
1219             sub get_active_node {
1220 4     4 1 2080 my ($self) = @_;
1221 4         19 return $self->{'node_addr_a'};
1222             }
1223              
1224             sub hmac_md5 {
1225 1     1 0 163 my ($self, $data, $key) = @_;
1226 1         8 my $ct = Digest::MD5->new;
1227              
1228 1 50       7 if (length($key) > $HMAC_MD5_BLCKSZ) {
1229 0         0 $ct->add($key);
1230 0         0 $key = $ct->digest();
1231             }
1232 1         7 my $ipad = $key ^ ("\x36" x $HMAC_MD5_BLCKSZ);
1233 1         4 my $opad = $key ^ ("\x5c" x $HMAC_MD5_BLCKSZ);
1234 1         5 $ct->reset();
1235 1         5 $ct->add($ipad, $data);
1236 1         5 my $digest1 = $ct->digest();
1237 1         4 $ct->reset();
1238 1         4 $ct->add($opad, $digest1);
1239 1         13 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__