File Coverage

blib/lib/Apache/AuthCookiePAM.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # Apache::AuthCookiePAM
4             #
5             # An AuthCookie module backed by a PAM.
6             #
7             # Copyright (C) 2002 SF Interactive.
8             #
9             # Author: Vandana Awasthi
10             #
11             # This library is free software; you can redistribute it and/or
12             # modify it under the terms of the GNU Lesser General Public
13             # License as published by the Free Software Foundation; either
14             # version 2.1 of the License, or (at your option) any later version.
15             #
16             # This library is distributed in the hope that it will be useful,
17             # but WITHOUT ANY WARRANTY; without even the implied warranty of
18             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19             # Lesser General Public License for more details.
20             #
21             # You should have received a copy of the GNU Lesser General Public
22             # License along with this library; if not, write to the Free Software
23             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24             #
25             #===============================================================================
26              
27             package Apache::AuthCookiePAM;
28              
29 1     1   6709 use strict;
  1         3  
  1         49  
30 1     1   32 use 5.004;
  1         4  
  1         45  
31 1     1   6 use vars qw( $VERSION );
  1         7  
  1         242  
32             ( $VERSION ) = '$Revision: 1.0 $' =~ /([\d.]+)/;
33              
34 1     1   1932 use Apache;
  0            
  0            
35             use Apache::Table;
36             use Apache::Constants qw(:common M_GET FORBIDDEN REDIRECT);
37             use Apache::AuthCookie::Util;
38             use Apache::Util qw(escape_uri);
39             use Apache::AuthCookie;
40             use Authen::PAM;
41             use vars qw( @ISA );
42             @ISA = qw( Apache::AuthCookie );
43              
44             use Apache::File;
45             use Digest::MD5 qw( md5_hex );
46             use Date::Calc qw( Today_and_Now Add_Delta_DHMS );
47             # Also uses Crypt::CBC if you're using encrypted cookies.
48              
49             #===============================================================================
50             # F U N C T I O N D E C L A R A T I O N S
51             #===============================================================================
52              
53             sub _log_not_set($$);
54             sub _dir_config_var($$);
55             sub _config_vars($);
56             sub _now_year_month_day_hour_minute_second();
57             sub _percent_encode($);
58             sub _percent_decode($);
59              
60             sub authen_cred($$\@);
61             sub authen_ses_key($$$);
62             sub group($$\@);
63              
64             #===============================================================================
65             # P A C K A G E G L O B A L S
66             #===============================================================================
67              
68             use vars qw( %CIPHERS );
69             # Stores Cipher::CBC objects in $CIPHERS{ idea:AuthName },
70             # $CIPHERS{ des:AuthName } etc.
71              
72             use vars qw( %SECRET_KEYS );
73             # Stores secret keys for MD5 checksums and encryption for each auth realm in
74             # $SECRET_KEYS{ AuthName }.
75              
76             #===============================================================================
77             # S E R V E R S T A R T I N I T I A L I Z A T I O N
78             #===============================================================================
79              
80             BEGIN {
81             my (@keyfile_vars, $keyfile_var);
82             @keyfile_vars = grep {
83             $_ =~ /PAM_SecretKeyFile$/
84             } keys %{ Apache->server->dir_config() };
85            
86             foreach $keyfile_var ( @keyfile_vars ) {
87             my $keyfile ;
88             $keyfile = Apache->server->dir_config( $keyfile_var );
89             my $auth_name ; $auth_name = $keyfile_var;
90            
91             $auth_name =~ s/PAM_SecretKeyFile$//;
92             unless ( open( KEY, "<$keyfile" ) ) {
93             Apache::log_error( "Could not open keyfile for $auth_name in file $keyfile" );
94             } else {
95             $SECRET_KEYS{ $auth_name } = ;
96             close KEY;
97             }
98             }
99             }
100              
101             #===============================================================================
102             # P E R L D O C
103             #===============================================================================
104              
105             =head1 NAME
106              
107             Apache::AuthCookiePAM - An AuthCookie module backed by a PAM .
108              
109             =head1 VERSION
110              
111             $Revision: 1.0 $
112              
113             =head1 SYNOPSIS
114              
115             # In httpd.conf or .htaccess
116             # This PerlSetVar MUST precede the PerlModule line because the
117             # key is read in a BEGIN block when the module is loaded.
118             PerlSetVar WhatEverPaM_SecretKeyFile /etc/httpd/acme.com.key
119             PerlSetVar WhatEverPAM_service login
120              
121             PerlModule Apache::AuthCookiePAM
122             PerlSetVar WhatEverPath /
123             PerlSetVar WhatEverLoginScript /login.pl
124              
125             # Optional, to share tickets between servers.
126             PerlSetVar WhatEverDomain .domain.com
127             PerlSetVar WhatEverChangePwdScript /changepwd.pl
128            
129             # These are optional, the module sets sensible defaults.
130             PerlSetVar WhatEverPAM_SessionLifetime 00-24-00-00
131              
132             # Protected by AuthCookiePAM.
133            
134             AuthType Apache::AuthCookiePAM
135             AuthName WhatEver
136             PerlAuthenHandler Apache::AuthCookiePAM->authenticate
137             PerlAuthzHandler Apache::AuthCookiePAM->authorize
138             require valid-user
139            
140              
141             # Login location. *** DEBUG *** I still think this is screwy
142            
143             AuthType Apache::AuthCookiePAM
144             AuthName WhatEver
145             SetHandler perl-script
146             PerlHandler Apache::AuthCookiePAM->login
147            
148              
149            
150             AuthType Apache::AuthCookiePAM
151             AuthName WhatEver
152             SetHandler perl-script
153             PerlHandler Apache::AuthCookiePAM->changepwd
154            
155              
156             =head1 DESCRIPTION
157              
158             This module is an authentication handler that uses the basic mechanism
159             provided by Apache::AuthCookie with PAM (based on DBI) . It is based on
160             two tokens being provided, a username and password, which can be any
161             strings (there are no illegal characters for either). The username is
162             used to set the remote user as if Basic Authentication was used.
163              
164             On an attempt to access a protected location without a valid cookie being
165             provided, the module prints an HTML login form (produced by a CGI or any
166             other handler; this can be a static file if you want to always send people
167             to the same entry page when they log in). This login form has fields for
168             username and password. On submitting it, the username and password are verfied
169             using PAM. If this succeeds, the user is issued a ticket. This ticket contains
170             the username, an issue time, an expire time, and an MD5 checksum of those and a
171             secret key for the server. It can optionally be encrypted before returning it
172             to the client in the cookie;
173             encryption is only useful for preventing the client from seeing the expire
174             time. If you wish to protect passwords in transport, use an SSL-encrypted
175             connection. The ticket is given in a cookie that the browser stores.
176              
177             After a login the user is redirected to the location they originally wished
178             to view (or to a fixed page if the login "script" was really a static file).
179              
180             On this access and any subsequent attempt to access a protected document, the
181             browser returns the ticket to the server. The server unencrypts it if
182             encrypted tickets are enabled, then extracts the username, issue time, expire
183             time and checksum. A new checksum is calculated of the username, issue time,
184             expire time and the secret key again; if it agrees with the checksum that
185             the client supplied, we know that the data has not been tampered with. We
186             next check that the expire time has not passed. If not, the ticket is still
187             good, so we set the username.
188              
189             Authorization checks then check that any "require valid-user" . If checks pass,
190             the document requested is displayed.
191              
192             If a ticket has expired or is otherwise invalid it is cleared in the browser
193             and the login form is shown again.
194              
195             =cut
196              
197             #===============================================================================
198             # P R I V A T E F U N C T I O N S
199             #===============================================================================
200              
201             #-------------------------------------------------------------------------------
202             # _log_not_set -- Log that a particular authentication variable was not set.
203              
204             sub _log_not_set($$)
205             {
206             my( $r, $variable ) = @_;
207             my $auth_name; $auth_name = $r->auth_name;
208             $r->log_error( "Apache::AuthCookiePAM: $variable not set for auth realm
209             $auth_name", $r->uri );
210             }
211              
212             #-------------------------------------------------------------------------------
213             # _dir_config_var -- Get a particular authentication variable.
214              
215             sub _dir_config_var($$)
216             {
217             my( $r, $variable ) = @_;
218             my $auth_name; $auth_name = $r->auth_name;
219             return $r->dir_config( "$auth_name$variable" );
220             }
221              
222             #-------------------------------------------------------------------------------
223             # _config_vars -- Gets the config variables from the dir_config and logs
224             # errors if required fields were not set, returns undef if any of the fields
225             # had errors or a hash of the values if they were all OK. Takes a request
226             # object.
227              
228             sub _config_vars($)
229             {
230             my( $r ) = @_;
231              
232             my %c; # config variables hash
233              
234             =head1 APACHE CONFIGURATION DIRECTIVES
235              
236             All configuration directives for this module are passed in PerlSetVars. These
237             PerlSetVars must begin with the AuthName that you are describing, so if your
238             AuthName is PrivateBankingSystem they will look like:
239              
240             PerlSetVar ProvateBankingSystemLoginScript /bvsm/login.pl
241              
242              
243             See also L for the directives required for any kind
244             of Apache::AuthCookie-based authentication system.
245              
246             In the following descriptions, replace "WhatEver" with your particular
247             AuthName. The available configuration directives are as follows:
248              
249             =over 4
250              
251             =item C
252              
253             The file that contains the secret key (on the first line of the file). This
254             is required and has no default value. This key should be owned and only
255             readable by root. It is read at server startup time. The key should be long
256             and fairly random. If you want, you can change it and restart the server,
257             (maybe daily), which will invalidate all prior-issued tickets.
258              
259             This directive MUST be set before the PerlModule line that loads this module,
260             because the secret key file is read immediately (at server start time). This
261             is so you can have it owned and only readable by root even though Apache
262             then changes to another user.
263              
264             =cut
265              
266             unless (
267             $c{ PAM_secretkeyfile } = _dir_config_var $r, 'PAM_SecretKeyFile'
268             ) {
269             _log_not_set $r, 'PAM_SecretKeyFile';
270             return undef;
271             }
272              
273             =item C
274              
275             How long tickets are good for after being issued. Note that presently
276             Apache::AuthCookie does not set a client-side expire time, which means that
277             most clients will only keep the cookie until the user quits the browser.
278             However, if you wish to force people to log in again sooner than that, set
279             this value. This can be 'forever' or a life time specified as:
280              
281             DD-hh-mm-ss -- Days, hours, minute and seconds to live.
282              
283             This is not required and defaults to '00-24-00-00' or 24 hours.
284              
285             =cut
286              
287             $c{ PAM_sessionlifetime }
288             = _dir_config_var( $r, 'PAM_SessionLifetime' ) || '00-24-00-00';
289              
290             =item C
291              
292             What kind of encryption to use to prevent the user from looking at the fields
293             in the ticket we give them. This is almost completely useless, so don't
294             switch it on unless you really know you need it. It does not provide any
295             protection of the password in transport; use SSL for that. It can be 'none',
296             'des', 'idea', 'blowfish', or 'blowfish_pp'.
297              
298             This is not required and defaults to 'none'.
299              
300             =cut
301              
302             $c{ PAM_encryptiontype } = _dir_config_var( $r, 'PAM_EncryptionType' )
303             || 'none';
304             # If we used encryption we need to pull in Crypt::CBC.
305             if ( $c{ PAM_encryptiontype } ne 'none' ) {
306             require Crypt::CBC;
307             }
308              
309             =item C
310              
311             The service that will be using PAM libraries for authentication.
312             These will be one of the services configured in /etc/pam.conf or /etc/pam.d/
313              
314             This directive defaults to "login"
315              
316             =cut
317              
318             $c{ PAM_service } = _dir_config_var ( $r, 'PAM_service' ) || 'login';
319              
320             return %c;
321             }
322              
323             #-------------------------------------------------------------------------------
324             # _now_year_month_day_hour_minute_second -- Return a string with the time in
325             # this order separated by dashes.
326              
327             sub _now_year_month_day_hour_minute_second()
328             {
329             return sprintf '%04d-%02d-%02d-%02d-%02d-%02d', Today_and_Now;
330             }
331              
332             #-------------------------------------------------------------------------------
333             # _percent_encode -- Percent-encode (like URI encoding) any non-alphanumberics
334             # in the supplied string.
335              
336             sub _percent_encode($)
337             {
338             my( $str ) = @_;
339             $str =~ s/([^\w])/ uc sprintf '%%%02x', ord $1 /eg;
340             return $str;
341             }
342              
343             #-------------------------------------------------------------------------------
344             # _percent_decode -- Percent-decode (like URI decoding) any %XX sequences in
345             # the supplied string.
346              
347             sub _percent_decode($)
348             {
349             my( $str ) = @_;
350             $str =~ s/%([0-9a-fA-F]{2})/ pack( "c",hex( $1 ) ) /ge;
351             return $str;
352             }
353              
354             #===============================================================================
355             # P U B L I C F U N C T I O N S
356             #===============================================================================
357              
358             #-------------------------------------------------------------------------------
359             # Take the credentials for a user and check that they match; if so, return
360             # a new session key for this user that can be stored in the cookie.
361             # If there is a problem, return a bogus session key.
362              
363             sub authen_cred($$\@)
364             {
365             my( $self, $r, @credentials ) ;
366             ( $self, $r, @credentials ) = @_;
367              
368             my $auth_name; $auth_name = $r->auth_name;
369             my %c ; %c = _config_vars $r;
370              
371             # Username goes in credential_0
372             my $user; $user = $credentials[ 0 ];
373             $user=~ tr/A-Z/a-z/;
374             unless ( $user =~ /^.+$/ ) {
375             $r->log_reason( "Apache::AuthCookiePAM: no username supplied for auth realm $auth_name", $r->uri );
376             $r->subprocess_env('AuthenReason', 'No username provided. Try again.');
377             return undef;
378             }
379             # Password goes in credential_1
380             my $password; $password = $credentials[ 1 ];
381             unless ( $password =~ /^.+$/ ) {
382             $r->log_reason( "Apache::AuthCookiePAM: no password supplied for auth realm $auth_name", $r->uri );
383             $r->subprocess_env('AuthenReason', 'No password provided. Try again.');
384             return undef;
385             }
386             # service to be used for authentication
387             my $service; $service = $c{PAM_service};
388             my ($pamh,$res,$funcref);
389             $funcref=create_conv_func($r,$user,$password);
390            
391             ref($pamh = new Authen::PAM($service, $user,$funcref)) || die "Error code $pamh during PAM init!";
392             # call auth module to authenticate user
393             $res = $pamh->pam_authenticate;
394             $funcref=0;
395             if ( $res != PAM_SUCCESS()) {
396             $r->log_error("ERROR: Authentication for $user Failed\n");
397             $r->subprocess_env('AuthenReason', 'Authentication failed. Username/Password provided incorrect.');
398             $pamh=0;
399             undef $pamh;
400             return undef;
401             }
402             else { # Now check if account is valid
403             $res = $pamh->pam_acct_mgmt();
404             if ( $res == PAM_ACCT_EXPIRED() ) {
405             $r->log_error("ERROR: Account for $user is locked. Contact your Administrator.\n");
406             $r->subprocess_env('AuthenReason', 'Account for $user is locked. Contact your Administrator.');
407             return 'bad';
408             }
409             if ( $res == PAM_NEW_AUTHTOK_REQD() ) {
410             $r->log_error("ERROR: PAssword for $user expired. Change Password\n");
411             $r->subprocess_env('AuthenReason', 'Password Expired. Please Change your password.');
412             return $r->auth_type->changepwd_form ($user);
413             }
414             if ( $res == PAM_SUCCESS() ) {
415             # Create the expire time for the ticket.
416             my $expire_time;
417             # expire time in a zillion years if it's forever.
418             if ( lc $c{ PAM_sessionlifetime } eq 'forever' ) {
419             $expire_time = '9999-01-01-01-01-01';
420             } else {
421             my( $deltaday, $deltahour, $deltaminute, $deltasecond ) = split /-/, $c{ PAM_sessionlifetime };
422             # Figure out the expire time.
423             $expire_time = sprintf( '%04d-%02d-%02d-%02d-%02d-%02d',
424             Add_Delta_DHMS( Today_and_Now,
425             $deltaday, $deltahour,
426             $deltaminute, $deltasecond ));
427             }
428              
429             # Now we need to %-encode non-alphanumberics in the username so we
430             # can stick it in the cookie safely. *** DEBUG *** check this
431             my $enc_user; $enc_user = _percent_encode $user;
432              
433             # OK, now we stick the username and the current time and the expire
434             # time together to make the public part of the session key:
435             my $current_time; $current_time = _now_year_month_day_hour_minute_second;
436             my $public_part; $public_part = "$enc_user:$current_time:$expire_time";
437              
438             # Now we calculate the hash of this and the secret key and then
439             # calculate the hash of *that* and the secret key again.
440             my $secret_key; $secret_key = $SECRET_KEYS{ $auth_name };
441             unless ( defined $secret_key ) {
442             $r->log_reason( "Apache::AuthCookiePAM: didn't have the secret key for auth realm $auth_name", $r->uri );
443             return 'bad';
444             }
445             my $hash ; $hash = md5_hex( join ':', $secret_key, md5_hex(
446             join ':', $public_part, $secret_key
447             ) );
448              
449             # Now we add this hash to the end of the public part.
450             my $session_key; $session_key = "$public_part:$hash";
451              
452             # Now we encrypt this and return it.
453             my $encrypted_session_key;
454             if ( $c{ PAM_encryptiontype } eq 'none' ) {
455             $encrypted_session_key = $session_key;
456             } elsif ( lc $c{ PAM_encryptiontype } eq 'des' ) {
457             $CIPHERS{ "des:$auth_name" }
458             ||= Crypt::CBC->new( $secret_key, 'DES' );
459             $encrypted_session_key = $CIPHERS{
460             "des:$auth_name"
461             }->encrypt_hex( $session_key );
462             } elsif ( lc $c{ PAM_encryptiontype } eq 'idea' ) {
463             $CIPHERS{ "idea:$auth_name" }
464             ||= Crypt::CBC->new( $secret_key, 'IDEA' );
465             $encrypted_session_key = $CIPHERS{
466             "idea:$auth_name"
467             }->encrypt_hex( $session_key );
468             } elsif ( lc $c{ PAM_encryptiontype } eq 'blowfish' ) {
469             $CIPHERS{ "blowfish:$auth_name" }
470             ||= Crypt::CBC->new( $secret_key, 'Blowfish' );
471             $encrypted_session_key = $CIPHERS{
472             "blowfish:$auth_name"
473             }->encrypt_hex( $session_key );
474             }
475             $pamh=0;
476             undef $pamh;
477             return $encrypted_session_key;
478             }
479             }
480             }
481              
482              
483             #-------------------------------------------------------------------------------
484             # Conversation function for PAM - authentication and change of password
485             sub create_conv_func
486             {
487             my ($r,$user,$pass,$newpass,$confpass);
488             ($r,$user,$pass,$newpass,$confpass) = @_;
489              
490             my $state; $state = 0;
491              
492             return sub {
493             my (@res);
494             while ( @_ )
495             {
496             my ($code, $msg, $ans);
497             $code = shift;
498             $msg = shift ;
499             $ans = "";
500              
501             $ans = $user if ($code == PAM_PROMPT_ECHO_ON() );
502             if ($code == PAM_PROMPT_ECHO_OFF() ) {
503             if ($state == 0) {
504             $ans = $pass ;
505             }
506             if ($state == 1) {
507             $ans = $newpass ;
508             }
509             if ($state == 2) {
510             $ans = $confpass ;
511             }
512             $r->log_error("VA: $msg $user $pass $newpass $confpass $state=$ans");
513             $state++;
514             }
515             push @res, (PAM_SUCCESS(),$ans);
516             }
517             push @res, PAM_SUCCESS();
518             return @res;
519             };
520             }
521              
522             #-------------------------------------------------------------------------------
523             # Take a session key and check that it is still valid; if so, return the user.
524              
525             sub authen_ses_key($$$)
526             {
527             my( $self, $r, $encrypted_session_key ) = @_;
528              
529             my $auth_name ; $auth_name = $r->auth_name;
530              
531             # Get the configuration information.
532             my %c; %c = _config_vars $r;
533              
534             # Get the secret key.
535             my $secret_key; $secret_key = $SECRET_KEYS{ $auth_name };
536             unless ( defined $secret_key ) {
537             $r->log_reason( "Apache::AuthCookiePAM: didn't the secret key from for auth realm $auth_name", $r->uri );
538             return undef;
539             }
540            
541             # Decrypt the session key.
542             my $session_key;
543             if ( $c{ PAM_encryptiontype } eq 'none' ) {
544             $session_key = $encrypted_session_key;
545             } else {
546             # Check that this looks like an encrypted hex-encoded string.
547             unless ( $encrypted_session_key =~ /^[0-9a-fA-F]+$/ ) {
548             $r->log_reason( "Apache::AuthCookiePAM: encrypted session key $encrypted_session_key doesn't look like it's properly hex-encoded for auth realm $auth_name", $r->uri );
549             return undef;
550             }
551              
552             # Get the cipher from the cache, or create a new one if the
553             # cached cipher hasn't been created, & decrypt the session key.
554             my $cipher;
555             if ( lc $c{ PAM_encryptiontype } eq 'des' ) {
556             $cipher = $CIPHERS{ "des:$auth_name" }
557             ||= Crypt::CBC->new( $secret_key, 'DES' );
558             } elsif ( lc $c{ PAM_encryptiontype } eq 'idea' ) {
559             $cipher = $CIPHERS{ "idea:$auth_name" }
560             ||= Crypt::CBC->new( $secret_key, 'IDEA' );
561             } elsif ( lc $c{ PAM_encryptiontype } eq 'blowfish' ) {
562             $cipher = $CIPHERS{ "blowfish:$auth_name" }
563             ||= Crypt::CBC->new( $secret_key, 'Blowfish' );
564             } elsif ( lc $c{ PAM_encryptiontype } eq 'blowfish_pp' ) {
565             $cipher = $CIPHERS{ "blowfish_pp:$auth_name" }
566             ||= Crypt::CBC->new( $secret_key, 'Blowfish_PP' );
567             } else {
568             $r->log_reason( "Apache::AuthCookiePAM: unknown encryption type $c{ PAM_encryptiontype } for auth realm $auth_name", $r->uri );
569             return undef;
570             }
571             $session_key = $cipher->decrypt_hex( $encrypted_session_key );
572             }
573            
574             # Break up the session key.
575             my( $enc_user, $issue_time, $expire_time, $supplied_hash )
576             = split /:/, $session_key;
577             # Let's check that we got passed sensible values in the cookie.
578             unless ( $enc_user =~ /^[a-zA-Z0-9_\%]+$/ ) {
579             $r->log_reason( "Apache::AuthCookiePAM: bad percent-encoded user $enc_user recovered from session ticket for auth_realm $auth_name", $r->uri );
580             return undef;
581             }
582             # decode the user
583             my $user; $user = _percent_decode $enc_user;
584             unless ( $issue_time =~ /^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}$/ ) {
585             $r->log_reason( "Apache::AuthCookiePAM: bad issue time $issue_time recovered from ticket for user $user for auth_realm $auth_name", $r->uri );
586             return undef;
587             }
588             unless ( $expire_time =~ /^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}$/ ) {
589             $r->log_reason( "Apache::AuthCookiePAM: bad expire time $expire_time recovered from ticket for user $user for auth_realm $auth_name", $r->uri );
590             return undef;
591             }
592             unless ( $supplied_hash =~ /^[0-9a-fA-F]{32}$/ ) {
593             $r->log_reason( "Apache::AuthCookiePAM: bad hash $supplied_hash recovered from ticket for user $user for auth_realm $auth_name", $r->uri );
594             return undef;
595             }
596              
597             # Calculate the hash of the user, issue time, expire_time and
598             # the secret key and then the hash of that and the secret key again.
599             my $hash; $hash = md5_hex( join ':', $secret_key, md5_hex(
600             join ':', $enc_user, $issue_time, $expire_time, $secret_key
601             ) );
602              
603             # Compare it to the hash they gave us.
604             unless ( $hash eq $supplied_hash ) {
605             $r->log_reason( "Apache::AuthCookiePAM: hash in cookie did not match calculated hash of contents for user $user for auth realm $auth_name", $r->uri );
606             return undef;
607             }
608              
609             # Check that their session hasn't timed out.
610             if ( _now_year_month_day_hour_minute_second gt $expire_time ) {
611             $r->log_reason( "Apache:AuthCookiePAM: expire time $expire_time has passed for user $user for auth realm $auth_name", $r->uri );
612             return undef;
613             }
614              
615             # If we're being paranoid about timing-out long-lived sessions,
616             # check that the issue time + the current (server-set) session lifetime
617             # hasn't passed too (in case we issued long-lived session tickets
618             # in the past that we want to get rid of). *** DEBUG ***
619             # if ( lc $c{ PAM_AlwaysUseCurrentSessionLifetime } eq 'on' )
620              
621             # They must be okay, so return the user.
622             return $user;
623             }
624              
625              
626             sub changepwd_form
627             {
628             my $self; $self = shift;
629             my $user; $user = shift;
630              
631             my $r; $r = Apache->request or die "no request";
632             $r->log_error(" $self ");
633             $r->subprocess_env("AuthenChangePwdUser","$user");
634             my $auth_name; $auth_name = $r->auth_name;
635              
636             my %args; %args = $r->method eq 'POST' ? $r->content : $r->args;
637              
638             $self->_convert_to_get($r, \%args) if $r->method eq 'POST';
639              
640             # There should be a PerlSetVar directive that gives us the URI of
641             # the script to execute for the login form.
642            
643             my $script;
644             unless ($script = $r->dir_config($auth_name . "ChangePwdScript")) {
645             $r->log_reason("PerlSetVar '${auth_name}ChangePwdScript' not set", $r->uri);
646             return SERVER_ERROR;
647             }
648             $r->log_error("Redirecting to $script");
649             $r->custom_response(REDIRECT, $script);
650            
651             return REDIRECT;
652             }
653              
654             sub _convert_to_get
655             {
656             my ($self, $r, $args) ;
657             ($self, $r, $args) = @_;
658              
659             return unless $r->method eq 'POST';
660              
661             my $debug ; $debug = $r->dir_config("AuthCookieDebug") || 0;
662              
663             $r->log_error("Converting POST -> GET") if $debug >= 2;
664              
665             my @pairs ; @pairs =();
666             my ($name, $value);
667            
668             while ( ($name, $value) = each %$args) {
669             # we dont want to copy login data, only extra data
670             next if $name eq 'destination'
671             or $name =~ /^credential_\d+$/;
672              
673             $value = '' unless defined $value;
674             push @pairs, escape_uri($name) . '=' . escape_uri($value);
675             }
676             $r->args(join '&', @pairs) if scalar(@pairs) > 0;
677              
678             $r->method('GET');
679             $r->method_number(M_GET);
680             $r->headers_in->unset('Content-Length');
681             }
682              
683             sub changepwd ($$)
684             {
685             my ($self, $r) ;
686             ($self, $r) = @_;
687            
688             my $debug; $debug = $r->dir_config("AuthCookieDebug") || 0;
689              
690             my ($auth_type, $auth_name);
691             ($auth_type, $auth_name) = ($r->auth_type, $r->auth_name);
692              
693             my %args; %args = $r->method eq 'POST' ? $r->content : $r->args;
694              
695             $self->_convert_to_get($r, \%args) if $r->method eq 'POST';
696              
697             unless (exists $args{'destination'}) {
698             $r->log_error("No key 'destination' found in form data");
699             $r->subprocess_env('AuthenReason', 'no_cookie');
700             return $auth_type->login_form;
701             }
702             $r->subprocess_env('AuthenReason', 'Password Change requested/required');
703            
704             # Get the credentials from the data posted by the client
705             my @credentials;
706             #user in credential_0
707             my $user; $user = $args{"credential_0"};
708             $user=~ tr/A-Z/a-z/;
709             unless ( $user =~ /^.+$/ ) {
710             $r->log_reason( "Apache::AuthCookiePAM: no username supplied for auth realm $auth_name", $r->uri );
711             }
712             # Old Password goes in credential_1
713             my $oldpassword; $oldpassword = $args{"credential_1"};
714             unless ( $oldpassword =~ /^.+$/ ) {
715             $r->log_reason( "Apache::AuthCookiePAM: no password supplied ", $r->uri );
716             }
717             # New Password goes in credential_2
718             my $newpassword ; $newpassword = $args{"credential_2"};
719             unless ( $newpassword =~ /^.+$/ ) {
720             $r->log_reason( "Apache::AuthCookiePAM: no password supplied ", $r->uri );
721             }
722             # Repeat Password goes in credential_3
723             my $confirmpassword; $confirmpassword = $args{"credential_3"};
724             unless ( $confirmpassword =~ /^.+$/ ) {
725             $r->log_reason( "Apache::AuthCookiePAM: passwords don't match", $r->uri );
726             }
727            
728             # Now do password change
729             #
730             my ($pamh,$res);
731             my $funcref;
732             $funcref=create_conv_func($r,$user,$oldpassword,$newpassword,$confirmpassword);
733            
734             my %c; %c = _config_vars $r;
735              
736             my $service; $service = $c{PAM_service};
737             ref($pamh = new Authen::PAM($service, $user,$funcref)) || die "Error code $pamh during PAM init!";
738             $res = $pamh->pam_chauthtok();
739             $pamh=0;
740             undef $pamh;
741              
742             if ( $res == PAM_SUCCESS()) {
743             $r->subprocess_env('AuthenReason', 'Password Updated. Please login with your new password');
744             $r->log_reason("AuthenCookiePAM:". $args{'destination'}."Password for $user Updated. Please login with your new password");
745             #
746             $auth_type->logout($r);
747             $r->err_header_out("Location" => $args{'destination'});
748             return REDIRECT;
749             }
750             else {
751             $r->subprocess_env('AuthenReason', "Password Not Updated. New password did not satisfy specified rules or failed authentication");
752             $r->log_reason("AuthenCookiePAM: Password for $user Not Updated. ");
753             return $auth_type->changepwd_form($user);
754             }
755             }
756              
757             #-------------------------------------------------------------------------------
758             # Take a list of groups and make sure that the current remote user is a member
759             # of one of them.
760              
761             __END__