File Coverage

blib/lib/Apache2/AuthEnv.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             package Apache2::AuthEnv;
2              
3             $VERSION = 'v1.3.8';
4              
5             =head1 NAME
6              
7             Apache2::AuthEnv - Perl Authentication and Authorisation via Environment Variables.
8              
9             =head1 SYNOPSIS
10              
11             ### In httpd.conf file (required to load the directives).
12             PerlLoadModule Apache2::AuthEnv
13              
14             ### In httpd.conf or .htaccess: ################
15             # Set the remote user and trigger the auth* stages
16             AuthEnvUser %{REMOTE_ADDR}@%{SOME_ENV_VAR}
17              
18             # turn on logging
19             AuthEnvLogInfo On
20              
21             # Also possible is setting the remote user from a list
22             # of alternative environment variables or a default value.
23             AuthEnvUser %{HTTP_XX_USER|HTTP_YY_USER:anon}
24              
25             # Set extra environment variables.
26             AuthEnvSet HTTP_AE_SERVER %{SERVER_ADDR:unknown}:%{SERVER_PORT:unknown}
27             AuthEnvChange HTTP_AE_SERVER s/:/!/g
28             AuthEnvChange HTTP_AE_SERVER tr/a-z/A-Z/
29              
30             # Load environment settings from a DBM database.
31             AuthEnvDbImport HTTP_EXTRA_ /etc/dbfile Key
32              
33             # Allow and Deny access based on environment.
34             # The default is to deny access.
35             # Allow and deny rules are evaluated based on their order in this file.
36             AuthEnvAllowUser fred@here.org
37             AuthEnvDenyUser george@here.org
38             AuthEnvAllowMatch %{HTTP_USER_AGENT} ^Mozilla
39             AuthEnvDeny %{REMOTE_ADDR} 192.168.2.3
40             AuthEnvDenyMatch %{HTTP_USER_AGENT} Fedora
41             AuthEnvAllow %{SERVER_PORT} 80
42             AuthEnvAllowSplit %{HTTP_MEMBEROF} '\^' 'CN=....'
43              
44             AuthEnvAllowAll
45             AuthEnvDenyAll
46              
47             AuthEnvDenial UNAUTHORISED|UNAUTHORIZED|NOT_FOUND|FORBIDDEN
48              
49             =head1 DESCRIPTION
50              
51             B allows you to promote a string composed of CGI
52             environment variables to act as an authenticated user. The format is
53             set via the AuthEnvUser command and the result is placed in the
54             environment variable B.
55              
56             This module is for use only when another Apache module pre-authenticates
57             and pre-authorises a user but does not provide authentication nor
58             authorisation controls within Apache.
59              
60             This module, once loaded, is triggered by the Apache directive
61             I setting a format from the environment for the remote
62             user name. Authorisation is controlled by I and
63             I directives. The default is to deny authorisation
64             to everyone.
65              
66             AuthEnvUser %{HTTP_SSO_USER}@%{HTTP_SSO_ORG}
67             AuthEnvAllowUser fred@ORG
68              
69             Such a system is Computer Asscoiates' SiteMinder (c) Single Sign On
70             solution. Only pre-authenticated and pre-authorised users are allowed
71             through to protected URLs. However there is no local control by the
72             local web server. SiteMinder sets various environment variables
73             including HTTP_SM_USER and HTTP_SM_AUTHDIRNAME. So a reasonable
74             setting would be
75              
76             AuthEnvUser %{HTTP_SM_USER}@%{HTTP_SM_AUTHDIRNAME}
77             AuthEnvAllowUser fred@ORG
78              
79             Another example is
80             AuthEnvUser %{HTTP_UI_PRINCIPAL_NAME}
81             AuthEnvAllowUser fred@ORG.org
82             AuthEnvAllow %{HTTP_UI_DEPARTMENT} sales
83              
84             Some systems may take authentication information from various sources
85             and provide different environment variables for each source. So you can
86             list alternative variables to use.
87             AuthEnvUser %{HTTP_SOURCE1_NAME|HTTP_SOURCE2_NAME|HTTP_SOURCE3_NAME}
88              
89             If nothing matches then you can set a default value (say 'anon') via
90             AuthEnvUser %{HTTP_SOURCE_NAME|HTTP_SOURCE2_NAME:anon}
91              
92             For nested directives, configurations are inherited from one
93             configuration file to the next. I directives overwrite each
94             other as do collections of I rules. Each individual
95             AuthEnvSet and AuthEnvChange directive, unless overwriten, is inherited.
96              
97             The default denial code returned to the browser is FORBIDDEN.
98             The directive I can be used to change the return code.
99             For example,
100              
101             AuthEnvDenial NOT_FOUND
102              
103             =head1 FORMAT
104              
105             The substitution format is composed of strings of characters and
106             variable substitutions starting with '%{' and ending in '}'.
107             Substitutions are of the following formats:
108              
109             =over 2
110              
111             =item * %{ENVIRONMENT_VARIABLE_NAME},
112              
113             =item * %{ENVIRONMENT_VARIABLE_NAME1|ENVIRONMENT_VARIABLE_NAME2|....}
114              
115             =item * %{ENVIRONMENT_VARIABLE_NAME:default}.
116              
117             =back
118              
119             In the first case, the value of the environment variable is simply substituted. If a
120             '|' separated list of variables is specified then each variable is
121             checked in order, substituting the value of the first that is not empty.
122             If no substitution succeeds and there is a default specified then that
123             value is used instead.
124              
125             To use formats with spaces in the .htaccess file, enclose the format in
126             double quotes.
127              
128             =head1 METHODS
129              
130             =over 4
131              
132             =item * handler()
133              
134             This is the method used as augument to the I or the
135             I directives in .htaccess and httpd.conf files.
136              
137             =item * authenticate()
138              
139             This is the method used as augument to the the PerlAuthenHandler
140             directive in .htaccess and httpd.conf files.
141              
142             =item * authorise()
143              
144             This is the method used as augument to the the PerlAuthzHandler
145             directive in .htaccess and httpd.conf files.
146              
147             =back
148              
149             =head1 APACHE DIRECTIVES
150              
151             In the Apache configuration file httpd.conf, the module must be loaded
152              
153             =over 2
154              
155             PerlLoadModule Apache2::AuthEnv
156              
157             =back
158              
159             PerlLoadModule, rather than PerlModule, is required to load this module
160             as it implements new Apache directives.
161              
162             =over 4
163              
164             =item * AuthEnvUser
165              
166             This turns on the authentication and authorisation stages and sets the
167             format for the remote user name, which is filled in during
168             authentication. Any prior authorisation lists are cleared.
169              
170             This directive is allowed in exactly the same contexts as the
171             Require directive.
172              
173             =item * AuthEnvDbImport
174              
175             This imports extra environment variables from a database for that
176             particular value of the key-format. The database is created via the
177             MLDBM and BerkeleyDB::Btree packages.
178              
179             =item * AuthEnvSet
180              
181             This sets the specified environment variable using the sepcified format.
182              
183             =item * AuthEnvSet
184              
185             This changes the specified environment variable according to the following
186             Perl substitution. Modifications to REMOTE_USER are allowed.
187              
188             =item * AuthEnvAllowUser
189              
190             =item * AuthEnvDenyUser
191              
192             These allow or deny the specified user.
193              
194             =item * AuthEnvAllow
195              
196             =item * AuthEnvAllowMatch
197              
198             =item * AuthEnvDeny
199              
200             =item * AuthEnvDenyMatch
201              
202             These directives allow or deny depending on the environment variables.
203             Those that end in I match the environment against a Perl regular
204             repression and the others require exact matches.
205              
206             These allow or deny the specified user.
207              
208             =item * AuthEnvAllowSplit
209              
210             =item * AuthEnvAllowSplitMatch
211              
212             =item * AuthEnvDenySplit
213              
214             =item * AuthEnvDenySplitMatch
215              
216             These directives allow or deny depending on the environment variables.
217             The formatted string is first split according to the regular expression
218             I and then each component is considered separately.
219             Those that end in I match the environment against a Perl regular
220             repression and the others require exact matches.
221              
222             This is useful for environment variables that are really lists
223             of values delimited with a specific value.
224              
225             Note that the string is a regular expression and needs to be
226             escaped appropiately; e.g. split on '\^' not on '^' as the latter just
227             splits on the beginning of the string and is probably not what you want.
228              
229             =item * AuthEnvAllowFile
230              
231             =item * AuthEnvDenyFile
232              
233             These directives allow or deny, respectively,
234             any users from the specified file.
235              
236             =item * AuthEnvAllowAll
237              
238             This directive allows any connection that hasn't been denied up to now.
239             This is useful to allow all users to access the controlled area.
240              
241             =item * AuthEnvDenyAll
242              
243             This directive denies any connection that hasn't been allowed up to now.
244             This is really the default action but included for completeness.
245             It is useful when an area needs to be temporarily denied but the rest of the configuration needs to stay intact.
246              
247             =item * AuthEnvDenial UNAUTHORISED|UNAUTHORIZED|NOT_FOUND|FORBIDDEN
248              
249             This directive sets the HTTP denial code returned to the
250             browser if authorisation fails. The default is FORBIDDEN.
251              
252             =item * AuthEnvLogInfo On|Off
253              
254             Turn on or off extra logging about which users are getting allowed or
255             denied by various rules. The default is no logging to reduce log sizes.
256              
257             =back
258              
259             =head1 AUTHOR
260              
261             Anthony R Fletcher arif@cpan.org
262              
263             =head1 COPYRIGHT
264              
265             Copyright (c) 2008 Anthony R Fletcher. All rights reserved.
266              
267             This program is free software; you can redistribute it and/or modify it under
268             the same terms as Perl itself. It is supplied on an-is basis and there
269             is no warrenty of any kind.
270              
271             SiteMinder (c) is owned by Computer Asscoiates. This module does not
272             rely on or use any part of SiteMinder and works purely via the
273             environemnt within mod_perl.
274              
275             =head1 SEE ALSO
276              
277             L, L, L.
278              
279             =cut
280              
281             ############################################################
282 1     1   1084 use 5;
  1         5  
  1         51  
283 1     1   6 use strict;
  1         3  
  1         41  
284              
285             # allow redefinitions so we can use the reload module.
286 1     1   21 use warnings FATAL => 'all', NONFATAL => 'redefine';
  1         2  
  1         62  
287              
288 1     1   5 use vars qw($VERSION);
  1         2  
  1         45  
289              
290 1     1   4 use Carp;
  1         2  
  1         78  
291 1     1   1074 use Data::Dumper;
  1         7547  
  1         93  
292              
293 1     1   2123 use Safe;
  1         41884  
  1         59  
294 1     1   1177 use Memoize;
  1         2663  
  1         61  
295 1     1   1960 use Memoize::Expire;
  1         4279  
  1         38  
296 1     1   1229 use Storable qw(freeze thaw dclone);
  1         3833  
  1         91  
297              
298 1     1   495 use BerkeleyDB;
  0            
  0            
299             use MLDBM qw(BerkeleyDB::Btree);
300              
301             use ModPerl::Util;
302             use Apache2::Module;
303             use Apache2::Access ();
304             use Apache2::Log;
305             use Apache2::CmdParms ();
306             use Apache2::ServerUtil;
307             use Apache2::ServerRec qw(warn);
308             use Apache2::RequestUtil ();
309             use Apache2::RequestRec;
310             use Apache2::Directive ();
311             use Apache2::Const -compile => qw(OK DECLINED NO_ARGS TAKE1 TAKE2 TAKE3 FLAG
312             NOT_FOUND HTTP_FORBIDDEN HTTP_UNAUTHORIZED
313             :override
314             );
315              
316             die "The module mod_perl 2.0 is required!" unless
317             ( exists $ENV{MOD_PERL_API_VERSION} and
318             $ENV{MOD_PERL_API_VERSION} >= 2 );
319              
320              
321             ###########################################################
322             my @directives = (
323             {
324             name => 'AuthEnvUser',
325             errmsg => 'AuthEnvUser EnvVarFrormat',
326             req_override => Apache2::Const::OR_AUTHCFG, # only allow where Require is allowed.
327             },
328             {
329             name => 'AuthEnvVar',
330             errmsg => 'AuthEnvVar EnvVarFrormat',
331             req_override => Apache2::Const::OR_AUTHCFG, # only allow where Require is allowed.
332             },
333             {
334             name => 'AuthEnvAllowUser',
335             args_how => Apache2::Const::TAKE1,
336             errmsg => 'AuthEnvAllowUser User',
337             },
338             {
339             name => 'AuthEnvDenyUser',
340             args_how => Apache2::Const::TAKE1,
341             errmsg => 'AuthEnvDenyUser User',
342             },
343             {
344             name => 'AuthEnvAllow',
345             args_how => Apache2::Const::TAKE2,
346             errmsg => 'AuthEnvAllow EnvVarFormat Value',
347             },
348             {
349             name => 'AuthEnvAllowMatch',
350             args_how => Apache2::Const::TAKE2,
351             errmsg => 'AuthEnvAllow EnvVarFormat RegEx',
352             },
353             {
354             name => 'AuthEnvDeny',
355             args_how => Apache2::Const::TAKE2,
356             errmsg => 'AuthEnvDeny EnvVarFormat Value',
357             },
358             {
359             name => 'AuthEnvDenyMatch',
360             args_how => Apache2::Const::TAKE2,
361             errmsg => 'AuthEnvDeny EnvVarFormat RegEx',
362             },
363             {
364             name => 'AuthEnvAllowSplit',
365             args_how => Apache2::Const::TAKE3,
366             errmsg => 'AuthEnvAllowSplit EnvVarFormat SplitRegEx Value',
367             },
368             {
369             name => 'AuthEnvAllowSplitMatch',
370             args_how => Apache2::Const::TAKE3,
371             errmsg => 'AuthEnvAllowSplitMatch EnvVarFormat SplitRegEx RegEx',
372             },
373             {
374             name => 'AuthEnvDenySplit',
375             args_how => Apache2::Const::TAKE3,
376             errmsg => 'AuthEnvDenySplit EnvVarFormat SplitRegEx Value',
377             },
378             {
379             name => 'AuthEnvDenySplitMatch',
380             args_how => Apache2::Const::TAKE3,
381             errmsg => 'AuthEnvDenySplitMatch EnvVarFormat SplitRegEx RegEx',
382             },
383             {
384             name => 'AuthEnvAllowAll',
385             args_how => Apache2::Const::NO_ARGS,
386             errmsg => 'AuthEnvAllowAll',
387             },
388             {
389             name => 'AuthEnvDenyAll',
390             args_how => Apache2::Const::NO_ARGS,
391             errmsg => 'AuthEnvDenyAll',
392             },
393             {
394             name => 'AuthEnvAllowFile',
395             args_how => Apache2::Const::TAKE1,
396             errmsg => 'AuthEnvAllowFile ',
397             },
398             {
399             name => 'AuthEnvDenyFile',
400             args_how => Apache2::Const::TAKE1,
401             errmsg => 'AuthEnvDenyFile ',
402             },
403              
404             {
405             name => 'AuthEnvDbImport',
406             args_how => Apache2::Const::TAKE3,
407             errmsg => 'AuthEnvDbImport EnvPrefix DB Key',
408             },
409             {
410             name => 'AuthEnvSet',
411             args_how => Apache2::Const::TAKE2,
412             errmsg => 'AuthEnvSet EnvVar Format',
413             },
414             {
415             name => 'AuthEnvChange',
416             args_how => Apache2::Const::TAKE2,
417             errmsg => 'AuthEnvChange EnvVar '
418             },
419             {
420             name => 'AuthEnvDenial',
421             args_how => Apache2::Const::TAKE1,
422             errmsg => 'AuthEnvDenial '
423             },
424             {
425             name => 'AuthEnvLogInfo',
426             args_how => Apache2::Const::FLAG,
427             errmsg => 'AuthEnvLogInfo On/Off',
428             },
429             {
430             name => 'AuthEnvLogDebug',
431             args_how => Apache2::Const::FLAG,
432             errmsg => 'AuthEnvLogInfo On/Off',
433             },
434             );
435              
436             # Register the directives.
437             Apache2::Module::add(__PACKAGE__, \@directives);
438              
439             # Debugging only.
440             sub debug { 1; }
441              
442             # errors.
443             sub err { warn @_; }
444              
445             # Log information
446             sub info { 1; }
447              
448             # Create an object; not used by mod_perl2
449             sub new
450             {
451             # Create an object.
452             my $this = shift;
453             my $class = ref($this) || $this;
454             my $self = { };
455             bless $self, $class;
456              
457             $self;
458             }
459              
460             ###################### Directives ###########################################
461              
462             # Set the environment variable to use for authentication
463             # and set the system to authenticate and authorise.
464             sub AuthEnvUser
465             {
466             my ($cfg, $parms, $fmt, @args) = @_;
467              
468             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
469              
470             # Check that the format contains something to expand.
471             # Warn if it's fixed.
472             unless ($fmt =~ /%\{.*\}/)
473             {
474             # NB the request object is not available when called in
475             # global config files (eg httpd.conf).
476             err("AuthEnvUser format '$fmt' has no expansion at $line");
477              
478             #return Apache2::Const::HTTP_FORBIDDEN;
479             }
480              
481             # Loading the configuration handles for auth*.
482             # This can be done anywhere so there shouldnever be a problem.
483             eval {
484             $parms->add_config([
485             'PerlAuthenHandler Apache2::AuthEnv::authenticate',
486             'PerlAuthzHandler Apache2::AuthEnv::authorise',
487             ]);
488             };
489             warn "$line: $@" if ($@);
490              
491             # Force auth* stages to be done by loading the configuration.
492             # May not be allowed in this part of the httpd conf files.
493             # So trap!
494             eval {
495             $parms->add_config([
496             'AuthType AuthEnv',
497             'Require valid-user',
498             ]);
499             };
500              
501             # Should never be a problem because the directive is
502             # restricted to location, directory and .htaccess only.
503             # Trap the error.
504             if ($@) {
505             if ($@ =~ /not allowed/i)
506             {
507             # Directive not allowed in this part of httpd configuration.
508             warn "AuthEnvUser not allowed here at $line";
509             }
510             else
511             {
512             # Unknown failure.
513             warn "AuthEnvUser: $@ at $line";
514             }
515              
516             exit 2;
517             }
518              
519             # Save value for user name format.
520             $cfg->{AuthEnvUser} = $fmt;
521              
522             # Make sure the the user gets set later.
523             push @{$cfg->{set}}, ['set', 'REMOTE_USER', $fmt];
524              
525             # Initialise the authorise rule list.
526             $cfg->{authorise} = ();
527              
528             1;
529             }
530              
531             sub AuthEnvVar { AuthEnvUser(@_); }
532              
533             # The @authorise array contains arrays of four elements:
534             # the environment format string,
535             # if it's an allow rule (1) or deny (0).
536             # if it's an exact (1) or a match rule (0).
537             # the string to compare/match it against.
538              
539             sub AuthEnvAllowAll
540             {
541             my ($cfg, $parms) = @_;
542             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
543             push @{$cfg->{authorise}}, ['', 1, 1, undef, '', $line];
544             }
545              
546             sub AuthEnvDenyAll
547             {
548             my ($cfg, $parms) = @_;
549             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
550             push @{$cfg->{authorise}}, ['', 0, 1, undef, '', $line];
551             }
552              
553             sub AuthEnvAllowUser
554             {
555             my ($cfg, $parms, $user) = @_;
556             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
557             push @{$cfg->{authorise}}, ['%{REMOTE_USER}', 1, 1, undef, $user, $line];
558             }
559              
560             sub AuthEnvDenyUser
561             {
562             my ($cfg, $parms, $user) = @_;
563             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
564             push @{$cfg->{authorise}}, ['%{REMOTE_USER}', 0, 1, undef, $user, $line];
565             }
566              
567             sub AuthEnvAllow
568             {
569             my ($cfg, $parms, $var, $regex) = @_;
570             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
571             push @{$cfg->{authorise}}, [$var, 1, 1, undef, $regex, $line];
572             }
573              
574             sub AuthEnvAllowMatch
575             {
576             my ($cfg, $parms, $var, $regex) = @_;
577             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
578             push @{$cfg->{authorise}}, [$var, 1, 0, undef, $regex, $line];
579             }
580              
581             sub AuthEnvDeny
582             {
583             my ($cfg, $parms, $var, $regex) = @_;
584             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
585             push @{$cfg->{authorise}}, [$var, 0, 1, undef, $regex, $line];
586             }
587              
588             sub AuthEnvDenyMatch
589             {
590             my ($cfg, $parms, $var, $regex) = @_;
591             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
592             push @{$cfg->{authorise}}, [$var, 0, 0, undef, $regex, $line];
593             }
594              
595             sub AuthEnvAllowSplit
596             {
597             my ($cfg, $parms, $var, $split, $regex) = @_;
598             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
599             push @{$cfg->{authorise}}, [$var, 1, 1, $split, $regex, $line];
600             }
601              
602             sub AuthEnvAllowSplitMatch
603             {
604             my ($cfg, $parms, $var, $split, $regex) = @_;
605             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
606             push @{$cfg->{authorise}}, [$var, 1, 0, $split, $regex, $line];
607             }
608              
609             sub AuthEnvDenySplit
610             {
611             my ($cfg, $parms, $var, $split, $regex) = @_;
612             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
613             push @{$cfg->{authorise}}, [$var, 0, 1, $split, $regex, $line];
614             }
615              
616             sub AuthEnvDenySplitMatch
617             {
618             my ($cfg, $parms, $var, $split, $regex) = @_;
619             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
620             push @{$cfg->{authorise}}, [$var, 0, 0, $split, $regex, $line];
621             }
622              
623             sub AuthEnvAllowFile
624             {
625             my ($cfg, $parms, $file) = @_;
626             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
627              
628             local *FILE;
629             unless (open (FILE, '<', $file))
630             {
631             err "AuthEnvAllowFile: Cannot read access allow file '$file' ($!) at $line.\n";
632             return;
633             }
634              
635             local ($/) = undef; # slurp.
636             my $users = ;
637             $users =~ s/#.*$//gm;
638            
639             for my $user (split/\s+/, $users)
640             {
641             next unless ($user ne '');
642             push @{$cfg->{authorise}}, ['%{REMOTE_USER}', 1, 1, undef, $user, $line];
643             }
644              
645             close FILE;
646             }
647              
648             sub AuthEnvDenyFile
649             {
650             my ($cfg, $parms, $file) = @_;
651             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
652              
653             local *FILE;
654             unless (open (FILE, '<', $file))
655             {
656             err "AuthEnvDenyFile: Cannot read access deny file '$file' ($!) at $line.\n";
657             err "AuthEnv: Denying all!\n";
658              
659             # deny all from this point; just in case.
660             &AuthEnvDenyAll($cfg, $parms);
661              
662             return;
663             }
664              
665             local ($/) = undef; # slurp.
666             my $users = ;
667             $users =~ s/#.*$//gm;
668            
669             for my $user (split /\s+/s, $users)
670             {
671             next unless ($user ne '');
672             push @{$cfg->{authorise}}, ['%{REMOTE_USER}', 0, 1, undef, $user, $line];
673             }
674              
675             close FILE;
676             }
677              
678             sub AuthEnvDbImport
679             {
680             my ($cfg, $parms, $var, $db, $fmt) = @_;
681             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
682              
683             # Check file is valid - i.e. exists and readable.
684             unless ( -r $db )
685             {
686             #warn "DB file is '$db'.\n";
687             warn "Cannot read database file at $line.\n";
688             return 0;
689             }
690              
691             # Untaint as file exists.
692             $db = $1 if ($db =~ /^(.*)$/);
693              
694             push @{$cfg->{set}}, ['dbimport', $var, $db, $fmt, $line];
695             }
696              
697             sub AuthEnvSet
698             {
699             my ($cfg, $parms, $var, $fmt) = @_;
700             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
701             push @{$cfg->{set}}, ['set', $var, $fmt, $line];
702             }
703              
704             sub AuthEnvChange
705             {
706             my ($cfg, $parms, $var, $change) = @_;
707             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
708             push @{$cfg->{set}}, ['change', $var, $change, $line];
709             }
710              
711             sub AuthEnvDenial
712             {
713             my ($cfg, $parms, $code) = @_;
714              
715             if ($code =~ /FORBIDDEN/i)
716             {
717             $cfg->{Denial} = Apache2::Const::HTTP_FORBIDDEN;
718             }
719             elsif ($code =~ /UNAUTHORI[SZ]ED/i)
720             {
721             $cfg->{Denial} = Apache2::Const::HTTP_UNAUTHORIZED;
722             }
723             elsif ($code =~ /NOT.FOUND/i)
724             {
725             $cfg->{Denial} = Apache2::Const::NOT_FOUND;
726             }
727             else
728             {
729             # warning of bad denial code.
730             my $line = join(':', $parms->directive->filename, $parms->directive->line_num);
731             warn "Invalid argument to AuthEnvDenial at $line";
732              
733             # Set a default.
734             $cfg->{Denial} = Apache2::Const::HTTP_FORBIDDEN;
735              
736             return 0;
737             }
738              
739             1;
740             }
741              
742             # Turn on information logging to the log files.
743             sub AuthEnvLogInfo
744             {
745             my ($cfg, $parms, $onoff) = @_;
746              
747             $cfg->{LogInfo} = $onoff;
748              
749             1;
750             }
751              
752             # Turn on or off debugging; unpublished.
753             sub AuthEnvLogDebug
754             {
755             my ($cfg, $parms, $onoff) = @_;
756              
757             $cfg->{LogDebug} = $onoff;
758              
759             1;
760             }
761              
762             ###################### End of directives #####################################
763              
764             # Merge configuration objects together so the the various
765             # Apache config files override each other.
766             sub merge
767             {
768             my ($base, $add) = @_;
769              
770             my $merged = new Apache2::AuthEnv;
771              
772             # Merge environment variables to set.
773             $merged->{set} = $base->{set};
774             push @{$merged->{set}}, @{$add->{set}};
775             delete $base->{set};
776             delete $add->{set};
777              
778             for my $k (keys %$base) { $merged->{$k} = $base->{$k}; }
779             for my $k (keys %$add) { $merged->{$k} = $add->{$k}; }
780              
781             $merged;
782             }
783              
784             # Turn on custom merging.
785             sub DIR_MERGE { merge(@_) }
786             sub SERVER_MERGE { merge(@_) }
787              
788              
789             # Fill out a sub-format with the correct values.
790             # Take a context ($r), a format of environment variables (with optional default) and
791             # a fail reference.
792             # Return the value of the first environment variable that exists, or the default if specified
793             # or '' and increament the failure variable reference.
794             sub fillout
795             {
796             my ($r, $fmt, $fail) = @_;
797              
798             debug("Expanding '$fmt' for URL ", $r->uri);
799              
800             # Isolate the default value.
801             my $default = ($fmt =~ s/:(\w*)$//) ? $1 : undef;
802              
803             # Run though each environment valriable in turn.
804             for my $e (split(/\|/, $fmt))
805             {
806             # return value if it exists.
807             return $r->subprocess_env($e) if defined($r->subprocess_env($e));
808             }
809              
810             # Otherwise return the default value.
811             return $default if defined $default;
812              
813             info "Failed to expand '$fmt' for URL ", $r->uri;
814              
815             # Failed.
816             $$fail++;
817              
818             '';
819             }
820              
821             # Look a key up in the MLDBM database, with a function that can be cached.
822             sub dblookup2
823             {
824             my ($file, $var) = @_;
825             ##warn("db key '$var' in file '$file'");
826              
827             my $null = freeze {};
828              
829             return $null unless defined $file;
830              
831             my $db = tie my %data, 'MLDBM',
832             -Filename => $file,
833             -Flags => DB_RDONLY,
834             ;
835              
836             unless ($db)
837             {
838             err("Cannot read database '$file' failed ($!) ");
839             return $null;
840             }
841              
842             # Side step any taint issues.
843             # The datbase is a valid file.
844             $db->RemoveTaint(1);
845              
846             # Return nothing if there is no entry.
847             return $null unless exists $data{$var};
848              
849             # Return frozen data.
850             freeze $data{$var};
851             }
852              
853             # Wrap the lookup function.
854             tie my %mcache => 'Memoize::Expire',
855             LIFETIME => 5, # In seconds
856             ;
857             memoize 'dblookup2', SCALAR_CACHE => [HASH => \%mcache ], LIST_CACHE => 'FAULT', ;
858              
859             # This is a wrapper to manage the unthawing process correctly.
860             sub dblookup
861             {
862             my $user = dblookup2(@_);
863             $user = thaw $user;
864             }
865              
866             ###########################################################
867              
868             # NB There is almost no environment to speak of at this time!
869              
870             # Authenticate a user based on the presence of environemnt variables.
871             # Fail to authenticate if a environment variable doesn't exist.
872             # Promote environment variables in format to REMOTE_USER.
873             sub authenticate
874             {
875             my ($r) = @_;
876              
877             # recover configuration.
878             my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
879              
880             # Check that we are using the right AuthType directive.
881             my $type = __PACKAGE__; $type =~ s/^.*:://;
882             if ($r->auth_type ne $type)
883             {
884             err("Wrong authentication Type ", $r->auth_type);
885             return Apache2::Const::HTTP_UNAUTHORIZED;
886             }
887             unless (defined $cfg->{AuthEnvUser})
888             {
889             err("AuthEnvUser not used! ", $r->auth_type);
890             return Apache2::Const::HTTP_UNAUTHORIZED;
891             }
892              
893             # set logging on or off.
894             if (exists $cfg->{LogInfo} && $cfg->{LogInfo})
895             {
896             # info on
897             no warnings;
898             eval 'sub info { warn @_; };';
899             }
900             else
901             {
902             # info off
903             no warnings;
904             eval 'sub info { 1; };';
905             }
906            
907             # set debugging on or off.
908             if (exists $cfg->{LogDebug} && $cfg->{LogDebug})
909             {
910             # debug on
911             no warnings;
912             eval 'sub debug { warn @_; };';
913             }
914             else
915             {
916             # info off
917             no warnings;
918             eval 'sub debug { 1; };';
919             }
920              
921             # Import CGI environment.
922             $r->subprocess_env unless $r->is_perl_option_enabled('SetupEnv');
923              
924             # expand $AuthEnvUser format; fail if a variable doesn't
925             # not exist.
926              
927             # Check that AuthEnvUser is set.
928             return Apache2::Const::HTTP_UNAUTHORIZED unless exists $cfg->{AuthEnvUser};
929              
930             # Set the AE version environment.
931             $r->subprocess_env('HTTP_AE_VERSION', $VERSION);
932              
933             # Set the environment and the REMOTE_USER along the way.
934             for my $s (@{$cfg->{set}})
935             {
936             my ($act, $v, $f) = @$s;
937              
938             # Set an environment variable.
939            
940             if ($act eq 'dbimport')
941             {
942             my ($act, $prefix, $file, $var) = @$s;
943             my $fail = 0; # count non-existant variables.
944             $var =~ s/%\{([^\}]+)\}/&fillout($r, $1, \$fail)/gxe;
945             next if $fail;
946              
947             # Load user data.
948             my $user = dblookup($file, $var);
949              
950             # Load the environment.
951             for my $k (keys %$user)
952             {
953             debug("db env key '$k' for URL ", $r->uri);
954             $r->subprocess_env($prefix . uc($k), $user->{$k});
955             }
956             }
957             elsif ($act eq 'set')
958             {
959             my $fail = 0; # count non-existant variables.
960              
961             #debug($r->uri, ": change '$f'");
962              
963             $f =~ s/%\{([^\}]+)\}/&fillout($r, $1, \$fail)/gxe;
964              
965             # something wasn't defined.
966             return Apache2::Const::HTTP_UNAUTHORIZED if $fail;
967              
968             $r->subprocess_env($v, $f);
969             }
970             # Change an environment variable.
971             elsif ($act eq 'change')
972             {
973             my $val = $r->subprocess_env($v);
974              
975             # Run the modification in a safe environment.
976             my $cpt = new Safe;
977             ${$cpt->varglob('val')} = $val;
978             $cpt->reval("\$val =~ $f");
979              
980             if ($@)
981             {
982             # failure to run.
983             err("change '$f' failed ($@) ", $r->uri);
984             return Apache2::Const::HTTP_UNAUTHORIZED;
985             }
986             else
987             {
988             # success.
989             $r->subprocess_env($v,${$cpt->varglob('val')});
990             }
991             }
992              
993             # Set the authenticated user as we go.
994             $r->user($r->subprocess_env('REMOTE_USER'))
995             if ($v eq 'REMOTE_USER');
996             }
997              
998             # Check that the user is real.
999             my $user = $r->user();
1000             return Apache2::Const::HTTP_UNAUTHORIZED unless defined $user;
1001             return Apache2::Const::HTTP_UNAUTHORIZED if ($user eq '');
1002              
1003             # Success.
1004             return Apache2::Const::OK;
1005             }
1006              
1007             # Match the various allow or deny rules.
1008             sub allowed
1009             {
1010             my ($r, @list) = @_;
1011              
1012             #info 1+$#list, " authorise rules\n";
1013              
1014             my $user = $r->user;
1015              
1016             for my $a (@list)
1017             {
1018             # Each rule consists of 3 parts.
1019             my ($value, $allow, $exact, $split, $regex, $line) = @{$a};
1020              
1021             my $fail = 0; # count non-existant variables.
1022              
1023             # Substitute.
1024             my $val = $value;
1025             $val =~ s/%\{([^\}]+)\}/&fillout($r, $1, \$fail)/gxe;
1026              
1027             # Substitute the regex as well.
1028             $regex =~ s/%\{([^\}]+)\}/&fillout($r, $1, \$fail)/gxe;
1029              
1030             # CHANGE IN BEHAVIOUR!
1031             # Fail if this contains a non-existant environment variable.
1032             #return 0 if $fail;
1033              
1034             #debug "$val $exact $regex\n";
1035              
1036             # Split the value up if required.
1037             my @parts = (defined $split) ? split(/$split/, $val) : $val;
1038              
1039             #warn "parts = ", join('-', @parts);
1040              
1041             # Check each part.
1042             for my $v (@parts)
1043             {
1044             #warn "checking '$v' with '$regex' (exact=$exact)\n";
1045             my $match = $exact
1046             ? ($v eq $regex)
1047             : ($v =~ m/$regex/);
1048              
1049              
1050             #return $allow if $match;
1051             if ($match)
1052             {
1053             #debug "match '$v' against '$regex' returns '$allow'\n";
1054             #info "Rule: match '$val' against '$regex' returns '$allow'\n";
1055             info "User $user ", ($allow ? 'allowed' : 'denied'), " by $line for URI ", $r->uri;
1056              
1057             return $allow;
1058             }
1059             }
1060             }
1061              
1062             info "User $user denied by default for URI ", $r->uri;
1063              
1064             0;
1065             }
1066              
1067             # Look through the deny and allow rules; fail by default.
1068             sub authorise
1069             {
1070             my ($r) = @_;
1071              
1072             # recover configuration.
1073             my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
1074              
1075             #debug "$#authorise authorise rules\n";
1076              
1077             # default denial code.
1078             $cfg->{Denial} ||= Apache2::Const::HTTP_FORBIDDEN;
1079              
1080             # Import CGI environment.
1081             $r->subprocess_env unless $r->is_perl_option_enabled('SetupEnv');
1082              
1083             # Sanity check that there is a authenticated user.
1084             my $user = $r->user;
1085             unless ($user)
1086             {
1087             err("No authenticated user ", $r->uri);
1088             return $cfg->{Denial};
1089             }
1090              
1091             # Check allow rules.
1092             allowed($r, @{$cfg->{authorise}}) &&
1093             return Apache2::Const::OK;
1094              
1095             # Fail by default.
1096              
1097             #err("User $user denied by default", $r->uri);
1098              
1099             return $cfg->{Denial};
1100              
1101             return Apache2::Const::NOT_FOUND;
1102             return Apache2::Const::HTTP_FORBIDDEN;
1103             return Apache2::Const::HTTP_UNAUTHORIZED;
1104             }
1105              
1106             # Default handler
1107             sub handler
1108             {
1109             my ($r) = @_;
1110              
1111             # What phase are we in?
1112             my $phase = ModPerl::Util::current_callback();
1113              
1114             # Handle the right phase in the right way.
1115             if ($phase eq 'PerlAuthenHandler') { return authenticate(@_); }
1116             if ($phase eq 'PerlAuthzHandler') { return authorise(@_); }
1117              
1118             # This phase is not handled by this module.
1119             err("Handler called in wrong phase ($phase)!");
1120              
1121             return Apache2::Const::HTTP_FORBIDDEN;
1122             }
1123              
1124             # Alternative spelling.
1125             sub authorize { authorise(@_); }
1126              
1127             1;
1128              
1129