File Coverage

blib/lib/Apache/AuthenRadius.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Apache::AuthenRadius;
2              
3             # $Id: AuthenRadius.pm,v 1.2 1999/07/31 22:14:23 daniel Exp $
4             #
5             # Added digest authentication by Mike McCauley mikem@open.com.au
6             # especially so it could be used with RadKey token based
7             # authentication modules for IE5 and Radiator
8             # http://www.open.com.au/radiator
9             # http://www.open.com.au/radkey
10             #
11             # For Digest Requires Authen::Radius, at least version 0.06 which
12             # can handle passwords longer than 16 bytes
13              
14 1     1   779 use strict;
  1         1  
  1         33  
15 1     1   5 use warnings;
  1         1  
  1         24  
16 1     1   1191 use Authen::Radius;
  1         68477  
  1         103  
17 1     1   988 use Net::hostent;
  1         11751  
  1         6  
18 1     1   58 use Socket;
  1         2  
  1         634  
19 1     1   5 use vars qw($VERSION);
  1         2  
  1         48  
20              
21             $VERSION = '0.9';
22              
23 1     1   1617 use mod_perl;
  0            
  0            
24             use constant MP2 => $mod_perl::VERSION < 1.99 ? 0 : 1;
25              
26             =head1 NAME
27              
28             Apache::AuthenRadius - Authentication via a Radius server
29              
30             =head1 SYNOPSIS
31              
32             # Configuration in httpd.conf
33              
34             PerlModule Apache::AuthenRadius
35              
36             # Authentication in .htaccess
37              
38             AuthName Radius
39              
40             AuthType Digest or AuthType Basic
41              
42             # authenticate via Radius
43             PerlAuthenHandler Apache::AuthenRadius
44              
45             PerlSetVar Auth_Radius_host radius.foo.com
46             PerlSetVar Auth_Radius_port 1647
47             PerlSetVar Auth_Radius_secret MySharedSecret
48             PerlSetVar Auth_Radius_timeout 5
49              
50             # This allows you to append something to the user name that
51             # is sent to the RADIUS server
52             # usually a realm so the RADIUS server can use it to
53             # discriminate between users
54             PerlSetVar Auth_Radius_appendToUsername @some.realm.com
55              
56             require valid-user
57              
58             =head1 DESCRIPTION
59              
60             This module allows Basic and Digest authentication against a Radius server.
61              
62             =head1 PUBLIC METHODS
63              
64             =cut
65            
66             BEGIN {
67              
68             if (MP2) {
69              
70             require Apache::Access;
71             require Apache::RequestRec;
72             require Apache::RequestUtil;
73             require Apache::RequestIO;
74             require Apache::Const;
75             require Apache::Log;
76             Apache::Const->import(-compile => qw(OK AUTH_REQUIRED DECLINED SERVER_ERROR));
77              
78             } else {
79              
80             require Apache;
81             require Apache::Constants;
82             Apache::Constants->import(qw(OK AUTH_REQUIRED DECLINED SERVER_ERROR));
83             }
84             }
85              
86             =head2 handler( $r )
87              
88             The mod_perl handler.
89              
90             =cut
91            
92             sub handler {
93             my $r = shift;
94             my $type = (MP2 ? $r->ap_auth_type() : $r->auth_type()) || 'Basic';
95              
96             # Now choose a handler depending on the auth type
97             if ($type eq 'Basic') {
98              
99             return _handler_basic($r);
100              
101             } elsif ($type eq 'Digest') {
102              
103             return _handler_digest($r);
104              
105             } else {
106              
107             # Never heard of it
108             $r->log_error("Apache::AuthenRadius unknown AuthType", $type);
109             return MP2 ? Apache::DECLINED() : Apache::Constants::DECLINED();
110             }
111             }
112              
113             sub _handler_basic {
114             my $r = shift;
115            
116             # Continue only if the first request.
117             return OK() unless $r->is_initial_req();
118              
119             my $reqs_arr = $r->requires() || return OK();
120              
121             # Grab the password, or return if HTTP_UNAUTHORIZED
122             my($res,$pass) = $r->get_basic_auth_pw();
123             return $res if $res;
124              
125             # Get the user name.
126             my $user = MP2 ? $r->user() : $r->connection->user();
127              
128             # Sanity for usernames and passwords.
129             if (length $user > 64 or $user =~ /[^A-Za-z0-9@_-.]/) {
130              
131             $r->log_error("Apache::AuthenRadius username too long or contains illegal characters", $r->uri());
132             $r->note_basic_auth_failure();
133             return AUTH_REQUIRED();
134             }
135              
136             if (length $pass > 256) {
137              
138             $r->log_error("Apache::AuthenRadius password too long", $r->uri());
139             $r->note_basic_auth_failure();
140             return AUTH_REQUIRED();
141             }
142              
143             return _authen_radius($r, $user, $pass);
144              
145             }
146              
147             sub _handler_digest {
148             my $r = shift;
149              
150             # Continue only if the first request.
151             return OK() unless $r->is_initial_req();
152              
153             my $reqs_arr = $r->requires() || return OK();
154              
155             # Get the authorization header, if it exists
156             my %headers = $r->headers_in();
157             my $auth = $headers{$r->proxyreq()} ? 'Proxy-Authorization' : 'Authorization';
158             my $algorithm = $r->dir_config("Auth_Radius_algorithm") || 'MD5';
159             my $realm = $r->auth_name();
160              
161             unless ($auth) {
162              
163             # No authorization supplied, generate a challenge
164             my $nonce = time();
165              
166             # XXX
167             $r->err_header_out($r->proxyreq() ?
168             'Proxy-Authenticate' : 'WWW-Authenticate',
169             "Digest algorithm=\"$algorithm\", nonce=\"$nonce\", realm=\"$realm\""
170             );
171              
172             return AUTH_REQUIRED();
173             }
174              
175             # This is a response to a previous challenge
176             # extract some intersting data and send it to the Radius
177             # server
178              
179             # Get the user name.
180             my ($user) = ($auth =~ /username="([^"]*)"/);
181              
182             # REVISIT: check that the uri is correct
183             unless ($r->proxyreq()) {
184             my ($uri) = ($auth =~ /uri="([^"]*)"/);
185             return DECLINED() unless $r->uri() eq $uri;
186             }
187              
188             # check the nonce is not stale
189             my $nonce_lifetime = $r->dir_config('Auth_Radius_nonce_lifetime') || 300;
190             my ($nonce) = ($auth =~ /nonce="([^"]*)"/);
191              
192             if ($nonce < time() - $nonce_lifetime) {
193              
194             # Its stale. Send back another challenge
195             $nonce = time();
196              
197             # XXXX
198             $r->err_header_out($r->proxyreq() ?
199             'Proxy-Authenticate' : 'WWW-Authenticate',
200             "Digest algorithm=\"$algorithm\", nonce=\"$nonce\", realm=\"$realm\", stale=\"true\""
201             );
202              
203             return AUTH_REQUIRED();
204             }
205            
206             # Send the entire Authorization header as the password
207             # let the radius server figure it out
208             my $pass = $auth;
209              
210             # Sanity for usernames and passwords.
211             if (length $user > 64) {
212              
213             $r->log_error("Apache::AuthenRadius username too long or contains illegal characters", $r->uri());
214             return AUTH_REQUIRED();
215             }
216              
217             if (length $pass > 256) {
218              
219             $r->log_error("Apache::AuthenRadius password too long", $r->uri());
220             return AUTH_REQUIRED();
221             }
222              
223             return _authen_radius($r, $user, $pass);
224             }
225              
226             sub _authen_radius {
227             my ($r, $user, $pass) = @_;
228              
229             # Radius Server and port.
230             my $host = $r->dir_config("Auth_Radius_host") or return DECLINED();
231             my $port = $r->dir_config("Auth_Radius_port") || 1647;
232             my $ident = $r->dir_config("Auth_Radius_ident") || 'apache';
233             my $ip = inet_ntoa(gethost($r->hostname)->addr);
234              
235             # Shared secret for the host we are running on.
236             my $secret = $r->dir_config("Auth_Radius_secret") or return DECLINED();
237              
238             # Timeout to wait for a response from the radius server.
239             my $timeout = $r->dir_config("Auth_Radius_timeout") || 5;
240              
241             # Create the radius connection.
242             my $radius = Authen::Radius->new(
243             'Host' => "$host:$port",
244             'Secret' => $secret,
245             'TimeOut' => $timeout,
246             );
247              
248             # Error if we can't connect.
249             if (!$radius) {
250             $r->log_error("Apache::AuthenRadius failed to connect to $host: $port",$r->uri());
251             return SERVER_ERROR();
252             }
253              
254             # Possibly append somthing to the users name, so we can
255             # flag to the radius server where this request came from
256             # Clever radius servers like Radiator can then discriminate
257             # between web users and dialup users
258             $user .= $r->dir_config("Auth_Radius_appendToUsername");
259              
260             # Do the actual check by talking to the radius server
261             if ($radius->check_pwd($user,$pass)) {
262              
263             return OK();
264              
265             } else {
266              
267             $r->log_error("Apache::AuthenRadius rejected user $user", $r->uri());
268             return AUTH_REQUIRED();
269             }
270             }
271              
272             1;
273              
274             __END__