home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Writing Apache Modules with Perl and C
By:   Lincoln Stein and Doug MacEachern
Published:   O'Reilly & Associates, Inc.  - March 1999

Copyright © 1999 by O'Reilly & Associates, Inc.


 


   Show Contents   Previous Page   Next Page

Chapter 6 - Authentication and Authorization
Access Control with mod_perl

In this section...

Introduction
A Simple Access Control Module
Time-Based Access Control
Browser-Based Access Control
Blocking Greedy Clients

Introduction

   Show Contents   Go to Top   Previous Page   Next Page

This section shows you how to write a simple access control handler in mod_perl.

A Simple Access Control Module

   Show Contents   Go to Top   Previous Page   Next Page

To create an access control module, you'll install a handler for the access control phase by adding a PerlAccessHandler directive to one of Apache's configuration files or to a per-directory .htaccess file. The access control handler has the job of giving thumbs up or down for each attempted access to the URI. The handler indicates its decision in the result code it returns to the server. OK will allow the user in, FORBIDDEN will forbid access by issuing a 403 status code, and DECLINED will defer the decision to any other access control handlers that may be installed.

We begin with the simplest type of access control, a stern module called Apache::GateKeeper (Example 6-1). Apache::GateKeeper recognizes a single configuration variable named Gate. If the value of Gate is open, the module allows access to the URI under its control. If the value of Gate is closed, the module forbids access. Any other value results in an "internal server error" message.

The code is straightforward. It begins in the usual way by importing the common Apache and HTTP constants from Apache::Constants:

package Apache::GateKeeper;
# file: Apache/GateKeeper.pm
use strict;
use Apache::Constants qw(:common);
sub handler {
   my $r = shift;
   my $gate = $r->dir_config("Gate");
   return DECLINED unless defined $gate;
   return OK if lc($gate) eq 'open';

When the handler is executed, it fetches the value of the Gate configuration variable. If the variable is absent, the handler declines to handle the transaction, deferring the decision to other handlers that may be installed. If the variable is present, the handler checks its value, and returns a value of OK if Gate is open.

    if (lc $gate eq 'closed') {
       $r->log_reason("Access forbidden unless the gate is open",
       $r->filename);
       return FORBIDDEN;
   }
    $r->log_error($r->uri, ": Invalid value for Gate ($gate)");
   return SERVER_ERROR;
}

On the other hand, if the value of Gate is "closed" the handler returns a FORBIDDEN error code. In the latter case, the subroutine also writes a message to the log file using the log_reason() logging method (see "Error Logging," in Chapter 4, Content Handlers). Any other value for Gate is a configuration error, which we check for, log, and handle by returning SERVER_ERROR.

Example 6.1 Simple Access Control

package Apache::GateKeeper;
# file: Apache/GateKeeper.pm
use strict;
use Apache::Constants qw(:common);
sub handler {
   my $r = shift;
   my $gate = $r->dir_config("Gate");
   return DECLINED unless defined $gate;
   return OK if lc $gate eq 'open';
    if (lc $gate eq 'closed') {
       $r->log_reason("Access forbidden unless the gate is open", $r->filename);
       return FORBIDDEN;
   }
    $r->log_error($r->uri, ": Invalid value for Gate ($gate)");
   return SERVER_ERROR;
}
1;
__END__
# .htaccess file entry
PerlAccessHandler Apache::GateKeeper
PerlSetVar Gate closed

The bottom of the listing shows the two-line .htaccess entry required to turn on Apache::GateKeeper for a particular directory (you could also use a <Location> or <Directory> entry for this purpose). It uses the PerlAccessHandler directive to install Apache::GateKeeper as the access handler for this directory, then calls PerlSetVar to set the Perl configuration variable Gate to closed.

How does the GateKeeper access control handler interact with other aspects of Apache access control, authentication, and authorization? If an authentication handler is also installed--for example, by including a require valid-user directive in the .htaccess file--then Apache::GateKeeper is called as only the first step in the process. If Apache::GateKeeper returns OK, then Apache will go on to the authentication phase and the user will be asked to provide his name and password.

However, this behavior can be modified by placing the line Satisfy any in the .htaccess file or directory configuration section. When this directive is in effect, Apache will try access control first and then try authentication/authorization. If either returns OK, then the request will be satisfied. This lets certain privileged users get into the directory even when Gate is closed. (The bouncer steps aside when he recognizes his boss!)

Now consider a .htaccess file like this one:

PerlAccessHandler Apache::GateKeeper
PerlSetVar Gate open
order deny,allow
deny from all
allow from 192.168.2

This configuration installs two access control handlers: one implemented by the standard mod_access module (which defines the order, allow, and deny directives) and Apache::GateKeeper. The two handlers are potentially in conflict. The IP-based restrictions implemented by mod_access forbid access from any address but those in a privileged 192.168.2 subnet. Apache::GateKeeper, in contrast, is set to allow access to the subdirectory from anyone. Who wins?

The Apache server's method for resolving these situations is to call each handler in turn in the reverse order of installation. If the handler returns FORBIDDEN, then Apache immediately refuses access. If the handler returns OK or DECLINED, however, Apache passes the request to the next handler in the chain. In the example given above, Apache::GateKeeper gets first shot at approving the request because it was installed last (mod_access is usually installed at compile time). If Apache::GateKeeper approves or declines the request, then the request will be passed on to mod_access. However, if Apache::GateKeeper returns FORBIDDEN, then the request is immediately refused and mod_access isn't even invoked at all. The system is not unlike the UN Security Council: for a resolution to pass, all members must either vote "yes" or abstain. Any single "no" (or "nyet") acts as a veto.

The Satisfy any directive has no effect on this situation.

Time-Based Access Control

   Show Contents   Go to Top   Previous Page   Next Page

For a slightly more interesting access handler, consider Example 6-2, which implements access control based on the day of the week. URIs protected by this handler will only be accessible on the days listed in a variable named ReqDay. This could be useful for a web site that observes the Sabbath, or, more plausibly, it might form the basis for a generic module that implements time-based access control. Many sites perform routine maintenance at scheduled times of the day, and it's often helpful to keep visitors out of directories while they're being updated.

The handler, Apache::DayLimit, begins by fetching the ReqDay configuration variable. If not present, it declines the transaction and gives some other handler a chance to consider it. Otherwise, the handler splits out the day names, which are assumed to be contained in a space- or comma-delimited list, and compares them to the current day obtained from the localtime() function. If there's a match, the handler allows the access by returning OK. Otherwise, it returns the FORBIDDEN HTTP error code as before, and access is denied.

Example 6.2 Access Control by the Day of Week

package Apache::DayLimit;
use strict;
use Apache::Constants qw(:common);
use Time::localtime;
my @wday = qw(sunday monday tuesday wednesday thursday friday saturday);
sub handler {
   my $r = shift;
   my $requires = $r->dir_config("ReqDay");
   return DECLINED unless $requires;
    my $day = $wday[localtime->wday];
   return OK if $requires =~ /$day([,\s]+|$)/i;
    $r->log_reason(qq{Access forbidden on weekday "$day"}, $r->uri);
   return FORBIDDEN;
}
1;
__END__

A <Location> section to go with Apache::DayLimit:

<Location /weekends_only>
  PerlSetVar ReqDay saturday,sunday
  PerlAccessHandler Apache::DayLimit
</Location>
   Show Contents   Go to Top   Previous Page   Next Page
Copyright © 1999 by O'Reilly & Associates, Inc.