File Coverage

blib/lib/Net/Radius/Client.pm
Criterion Covered Total %
statement 28 100 28.0
branch 2 42 4.7
condition 0 9 0.0
subroutine 9 10 90.0
pod 2 3 66.6
total 41 164 25.0


line stmt bran cond sub pod time code
1             package Net::Radius::Client;
2              
3 1     1   5549 use 5.008;
  1         4  
  1         36  
4 1     1   1981 use IO::Socket::INET;
  1         27066  
  1         9  
5 1     1   1753 use Net::Radius::Dictionary;
  1         2823  
  1         41  
6 1     1   993 use Net::Radius::Packet;
  1         6092  
  1         92  
7 1     1   13 use strict;
  1         1  
  1         39  
8 1     1   6 use warnings;
  1         3  
  1         43  
9 1     1   5 use Carp;
  1         2  
  1         1216  
10              
11             require Exporter;
12              
13             our @ISA = qw(Exporter);
14              
15             # Items to export into callers namespace by default. Note: do not export
16             # names by default without a very good reason. Use EXPORT_OK instead.
17             # Do not simply export all your public functions/methods/constants.
18              
19             # This allows declaration use Net::Radius::Client ':all';
20             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
21             # will save memory.
22             our %EXPORT_TAGS = ( 'all' => [ qw(
23            
24             ) ] );
25              
26             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
27              
28             our @EXPORT = qw(
29             load
30             query
31             );
32              
33             our $VERSION = '0.03';
34             our $debug = 0;
35              
36             # Preloaded methods go here.
37              
38             my $ident = 1;
39              
40             # subroutine to make string of 16 random bytes
41             sub bigrand() {
42 0     0 0 0 pack "n8",
43             rand(65536), rand(65536), rand(65536), rand(65536),
44             rand(65536), rand(65536), rand(65536), rand(65536);
45             }
46              
47             my $dict = undef;
48              
49             sub load {
50 1     1 1 3 my ($d) = @_;
51             # Net::Radius::Dictionary pass silently if
52             # dictionary is not readable (seems like bug)
53 1 50       260 die "Couldn't read dictionary $d\n" unless (-r $d);
54 0 0       0 $dict = new Net::Radius::Dictionary $d
55             or die "Couldn't read dictionary: $!";
56             }
57              
58             sub query {
59 1     1 1 575 my ($servers, $code, $argref) = @_;
60 1         4 my $retref={};
61 1         2 my ($rec, $req, $rsp);
62 0         0 my $password;
63              
64 1 50       8 if (not defined($dict)) {
65 1         5 load("dictionary");
66             }
67              
68 0           $req = new Net::Radius::Packet $dict;
69 0           $req->set_code($code);
70              
71 0           foreach my $vs (keys %$argref) {
72 0           foreach my $a (keys %{$argref->{$vs}}) {
  0            
73 0 0         if ($vs) {
74 0           $req->set_vsattr($vs, $a, @{$argref->{$vs}->{$a}});
  0            
75             } else {
76 0 0         if ($a eq 'User-Password') {
77 0           $password = $argref->{$vs}->{$a}[0];
78             } else {
79 0           $req->set_attr($a, $argref->{$vs}->{$a}[0]);
80             }
81             }
82             }
83             }
84              
85 0           my ($retries, $timeout, $rc);
86              
87 0           foreach my $host (keys %$servers) {
88 0           foreach my $port (keys %{$servers->{$host}}) {
  0            
89 0 0         if (defined($servers->{$host}->{$port}->{'retries'})) {
90 0           $retries = $servers->{$host}->{$port}->{'retries'};
91             } else {
92 0           $retries = 3;
93             }
94 0 0         if (defined($servers->{$host}->{$port}->{'timeout'})) {
95 0           $timeout = $servers->{$host}->{$port}->{'timeout'};
96             } else {
97 0           $timeout = 1;
98             }
99            
100 0           $ident = ($ident + 1) & 255;
101 0           $req->set_identifier($ident);
102            
103 0 0         if ($code eq 'Access-Request') {
104 0           $req->set_authenticator(bigrand);
105             } else {
106 0           $req->set_authenticator("");
107             }
108              
109 0 0         if ($code eq 'Access-Request') {
110 0           $req->unset_attr('User-Password');
111 0           $req->set_password($password, $servers->{$host}->{$port}->{'secret'});
112             }
113              
114 0 0         $req->dump if ($debug);
115              
116 0           my $pack = $req->pack; # Can generate error 'Unknown RADIUS tuples'
117             # if dictionary has not been loaded (bug?)
118 0 0         if ($code ne 'Access-Request') {
119 0           $pack = auth_resp($pack,$servers->{$host}->{$port}->{'secret'});
120             }
121              
122 0           my $socket = new IO::Socket::INET->new(PeerAddr => $host,
123             PeerPort => $port,
124             Proto => 'udp',
125             Timeout => $timeout);
126              
127 0           while($retries) {
128 0           $retries--;
129              
130 0           $rc = $socket->send($pack);
131 0 0         next if ($rc != length($pack));
132              
133             # Timeout parametor has no effect to recv method;
134             # so we use select to detect timeout
135 0           my $rin = '';
136 0           vec($rin, fileno($socket), 1) = 1;
137 0           my $nfound = select($rin, undef, undef, $timeout);
138 0 0         next if ($nfound <= 0); # either timeout or end of file
139              
140 0           my $rec;
141 0           $rc = $socket->recv($rec, 4096); # RFC2866: 20<=size<=4095
142 0 0         next unless ($rc);
143              
144 0           $rsp = new Net::Radius::Packet $dict, $rec;
145              
146             # Make sure response is authentic
147             {
148 0           my $p = $rec;
  0            
149 0           substr($p, 4, 16) = $req->authenticator;
150 0           $p = auth_resp($p,$servers->{$host}->{$port}->{'secret'});
151 0 0         if ($rsp->authenticator ne substr($p, 4, 16)) {
152 0           next; # ignore non-authentic response
153             }
154             }
155            
156 0 0         $rsp->dump if ($debug);
157              
158 0 0         next if ($rsp->identifier != $ident);
159              
160 0 0 0       if ($code eq 'Access-Request' and
      0        
161             $rsp->code ne 'Access-Accept' and
162             $rsp->code ne 'Access-Reject') {
163 0           next;
164             }
165 0 0 0       if ($code eq 'Accounting-Request' and
166             $rsp->code ne 'Accounting-Response') {
167 0           next;
168             }
169              
170 0           foreach my $a ($rsp->attributes) {
171 0 0         if (not defined($retref->{0})) {
172 0           $retref->{0} = {};
173             }
174 0           $retref->{0}->{$a} = [ $rsp->attr($a) ];
175             }
176 0           foreach my $v ($rsp->vendors) {
177 0           foreach my $a ($rsp->vsattributes($v)) {
178 0 0         if (not defined($retref->{$v})) {
179 0           $retref->{$v} = {};
180             }
181 0           $retref->{$v}->{$a} = $rsp->vsattr($v, $a);
182             }
183             }
184              
185 0           return ($rsp->code, \%$retref);
186             }
187             }
188             }
189              
190 0           return ('', \%$retref);
191             }
192              
193             1;
194             __END__