diff -u Apache-AuthenRadius-0.3.orig/AuthenRadius.pm Apache-AuthenRadius-0.3/AuthenRadius.pm --- Apache-AuthenRadius-0.3.orig/AuthenRadius.pm Sun Aug 1 08:14:23 1999 +++ Apache-AuthenRadius-0.3/AuthenRadius.pm Fri Apr 5 20:15:51 2002 @@ -1,16 +1,46 @@ package Apache::AuthenRadius; # $Id: AuthenRadius.pm,v 1.2 1999/07/31 22:14:23 daniel Exp $ - +# Added digest authentication by Mike McCauley mikem@open.com.au +# especially so it could be used with RadKey token based +# authentication modules for IE5 and Radiator +# http://www.open.com.au/radiator +# http://www.open.com.au/radkey +# For Digest Requires Authen::Radius, at least version 0.06 which +# can handle passwords longer than 16 bytes use strict; use Apache (); use Apache::Constants qw(OK AUTH_REQUIRED DECLINED SERVER_ERROR); use Authen::Radius; use vars qw($VERSION); -$VERSION = '0.3'; +$VERSION = '0.4'; + + +sub handler +{ + my $r = shift; + my $type = $r->auth_type; + $type = 'Basic' unless $type ne ''; + + # Now choose a handler depending on the auth type + if ($type eq 'Basic') + { + return &handler_basic($r); + } + elsif ($type eq 'Digest') + { + return &handler_digest($r); + } + else + { + # Never heard of it + $r->log_reason("Apache::AuthenRadius unknown AuthType", $type); + return DECLINED; + } +} -sub handler { +sub handler_basic { my $r = shift; # Continue only if the first request. @@ -26,16 +56,6 @@ # Get the user name. my $user = $r->connection->user; - # Radius Server and port. - my $host = $r->dir_config("Auth_Radius_host") or return DECLINED; - my $port = $r->dir_config("Auth_Radius_port") || 1647; - - # Shared secret for the host we are running on. - my $secret = $r->dir_config("Auth_Radius_secret") or return DECLINED; - - # Timeout to wait for a response from the radius server. - my $timeout = $r->dir_config("Auth_Radius_timeout") || 5; - # Sanity for usernames and passwords. if (length $user > 64 or $user =~ /[^A-Za-z0-9]/) { $r->log_reason("Apache::AuthenRadius username too long or" @@ -50,29 +70,141 @@ return AUTH_REQUIRED; } - # Create the radius connection. - my $radius = Authen::Radius->new( - Host => "$host:$port", - Secret => $secret, - TimeOut => $timeout - ); - - # Error if we can't connect. - if (!defined $radius) { - $r->log_reason("Apache::AuthenRadius failed to" - ."connect to $host: $port",$r->uri); - return SERVER_ERROR; - } - - # Do the actual check. - if ($radius->check_pwd($user,$pass)) { - return OK; - } else { - $r->log_reason("Apache::AuthenRadius failed for user $user", - $r->uri); - $r->note_basic_auth_failure; - return AUTH_REQUIRED; + return authen_radius($r, $user, $pass); + +} +sub handler_digest +{ + my $r = shift; + + # Continue only if the first request. + return OK unless $r->is_initial_req; + + my $reqs_arr = $r->requires; + return OK unless $reqs_arr; + + # Get the authorization header, if it exists + my $auth = $r->header_in($r->proxyreq ? + 'Proxy-Authorization' : 'Authorization'); + + my $algorithm = $r->dir_config("Auth_Radius_algorithm") || 'MD5'; + my $qop = $r->dir_config("Auth_Radius_qop") || 'none'; + + my $realm = $r->auth_name; + + if ($auth eq '') + { + # No authorisation supplied, generate a challenge + my $nonce = time; + my $a = "Digest realm=\"$realm\", algorithm=\"$algorithm\", nonce=\"$nonce\""; + $a .= ", qop=\"$qop\"" unless $qop eq 'none'; + $r->err_header_out($r->proxyreq ? + 'Proxy-Authenticate' : 'WWW-Authenticate', $a); + return AUTH_REQUIRED; + } + else + { + # This is a response to a previous challenge + # extract some intersting data and send it to the Radius + # server + + # Get the user name. + my $user; + $user = $1 if $auth =~ /username="([^"]*)"/; + + # REVISIT: check that the uri is correct + if (!$r->proxyreq) + { + my $uri; + $uri = $1 if $auth =~ /uri="([^"]*)"/; + return DECLINED unless $r->uri eq $uri; + } + + # check the nonce is not stale + my $nonce; + my $nonce_lifetime = $r->dir_config("Auth_Radius_nonce_lifetime") + || 300; + $nonce = $1 if $auth =~ /nonce="([^"]*)"/; + if ($nonce < time - $nonce_lifetime) + { + # Its stale. Send back another challenge + $nonce = time; + my $a = "Digest realm=\"$realm\", algorithm=\"$algorithm\", nonce=\"$nonce\", stale=\"true\""; + $a .= ", qop=\"$qop\"" unless $qop eq 'none'; + $r->err_header_out($r->proxyreq ? + 'Proxy-Authenticate' : 'WWW-Authenticate', $a); + return AUTH_REQUIRED; + } + + # Send the entire Authorization header as the password + # let the radius server figure it out. Append the method + # since some algorithms (MD5) need it + my $pass = $auth . ', method="' . $r->method . '"'; + + # Sanity for usernames and passwords. + if (length $user > 64) + { + $r->log_reason("Apache::AuthenRadius username too long or" + ."contains illegal characters", $r->uri); + return AUTH_REQUIRED; } + + if (length $pass > 256) + { + $r->log_reason("Apache::AuthenRadius password too long", $r->uri); + return AUTH_REQUIRED; + } + + return authen_radius($r, $user, $pass); + } +} + +sub authen_radius +{ + my ($r, $user, $pass) = @_; + + # Radius Server and port. + my $host = $r->dir_config("Auth_Radius_host") or return DECLINED; + my $port = $r->dir_config("Auth_Radius_port") || 1647; + + # Shared secret for the host we are running on. + my $secret = $r->dir_config("Auth_Radius_secret") or return DECLINED; + + # Timeout to wait for a response from the radius server. + my $timeout = $r->dir_config("Auth_Radius_timeout") || 5; + + # Create the radius connection. + my $radius = Authen::Radius->new + ( + Host => "$host:$port", + Secret => $secret, + TimeOut => $timeout + ); + + # Error if we can't connect. + if (!defined $radius) + { + $r->log_reason("Apache::AuthenRadius failed to connect to $host: $port",$r->uri); + return SERVER_ERROR; + } + + # Possibly append somthing to the users name, so we can + # flag to the radius server where this request came from + # Clever radius servers like Radiator can then discriminate + # between web users and dialup users + $user .= $r->dir_config("Auth_Radius_appendToUsername"); + + # Do the actual check by talking to the radius server + if ($radius->check_pwd($user,$pass)) + { + return OK; + } + else + { + $r->log_reason("Apache::AuthenRadius rejected user $user", + $r->uri); + return AUTH_REQUIRED; + } } 1; @@ -92,6 +224,7 @@ # Authentication in .htaccess AuthName Radius + # AuthType Digest is also supported. AuthType Basic # authenticate via Radius @@ -102,11 +235,17 @@ PerlSetVar Auth_Radius_secret MySharedSecret PerlSetVar Auth_Radius_timeout 5 + # This allows you to append something to the user name that + # is sent to the RADIUS server + # usually a realm so the RADIUS server can use it to + # discriminate between users + #PerlSetVar Auth_Radius_appendToUsername @some.realm.com + require valid-user =head1 DESCRIPTION -This module allows authentication against a Radius server. +This module allows Basic and Digest authentication against a Radius server. =head1 LIST OF TOKENS @@ -134,6 +273,41 @@ The timeout in seconds to wait for a response from the Radius server. +=item * +Auth_Radius_algorithm + +For Digest authentication, this is the algorithm to use. Defaults to 'MD5'. +For Basic authentication, it is ignored. If Digest authentication is +set, unauthenticated requests will be sent a Digest challenge, +including a nonce. Authenticated requests will have the +nonce checked against Auth_Radius_nonce_lifetime, then the whole +Authentication header sent as the password to RADIUS. + +=item * +Auth_Radius_appendToUsername + +Appends a string to the end of the user name that is sent to RADIUS. +This would normally be in the form of a realm (i.e. @some.realm.com) +This is useful where you might want to discriminate between the +same user in several contexts. Clever RADIUS servers such as Radiator can +use the realm to let the user in or no depending on which protected +Apache directory they are trying to access. + +=item * +Auth_Radius_nonce_lifetime + +Specifies the maximum nonce lifetime in seconds for Digest authentication. +This parameter allows you to change the nonce lifetime for Digest +authentication. Digest authentications whose nonce exceeds the +maximum lifetime are declined. Defaults to 300 seconds. + +=item * +Auth_Radius_qop + +Specifies the "Quality of Protection" required. See RFC2617. +Defaults to 'auth'. A value of 'none' disables qop. The default works +with INternet Explorer, Konqueror and others. + =head1 CONFIGURATION The module should be loaded upon startup of the Apache daemon. @@ -147,6 +321,9 @@ when making mod_perl: perl Makefile.PL PERL_AUTHEN=1 + +For Digest authentication, you will need Authen::Radius version +0.06 or better. Version 0.05 only permits 16 byte passwords =head1 SEE ALSO diff -u Apache-AuthenRadius-0.3.orig/Changes Apache-AuthenRadius-0.3/Changes --- Apache-AuthenRadius-0.3.orig/Changes Thu Dec 17 10:05:29 1998 +++ Apache-AuthenRadius-0.3/Changes Thu Apr 4 22:32:11 2002 @@ -2,6 +2,14 @@ Revision history for Perl extension Apache::AuthenRadius. +0.04 Tue Jan 18 10:21:38 2000 + - Added support for Digest with customised algorithms. + Automatically generates a nonce, and checks for nonce + staleness. The entire authentication header is sent + to the radius server as the password. Requires Authen::Radius + version 0.06 or better to handle long passwords + Mike McCauley mikem@open.com.au + 0.02 Mon Jun 8 15:10:31 1998 - Added documentation, packaged up. diff -u Apache-AuthenRadius-0.3.orig/Makefile.PL Apache-AuthenRadius-0.3/Makefile.PL --- Apache-AuthenRadius-0.3.orig/Makefile.PL Thu Dec 17 10:05:29 1998 +++ Apache-AuthenRadius-0.3/Makefile.PL Fri Apr 5 10:04:39 2002 @@ -11,6 +11,6 @@ WriteMakefile( 'NAME' => 'Apache::AuthenRadius', 'VERSION_FROM' => 'AuthenRadius.pm', - 'PREREQ_PM' => { Authen::Radius => 0.05 }, + 'PREREQ_PM' => { Authen::Radius => 0.06 }, 'dist' => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, ); Only in Apache-AuthenRadius-0.3: Makefile.old