Приглашаем посетить
Пастернак (pasternak.niv.ru)

Authenticate

#!/usr/local/bin/perl

$NO = 0;
$YES = 1;

############################################################################
#                                                                          #
# Authenticate                      Version 1.8                            #
# Written by Craig A. Patchett      craig@patchett.com                     #
# Created 9/15/96                   Last Modified 7/15/97                  #
#                                                                          #
# Copyright 1997 Craig Patchett & Matthew Wright.  All Rights Reserved.    #
# This program is part of The CGI/Perl Cookbook from John Wiley & Sons.    #
# License to use this program or install it on a server (in original or    #
# modified form) is granted only to those who have purchased a copy of The #
# CGI/Perl Cookbook. (This notice must remain as part of the source code.) #
#                                                                          #
############################################################################


############################################################################
# Define configuration constants                                           #
############################################################################

# $REG_HOLD should be set to $YES or $NO to indicate whether or not you 
# want to implement the registration hold feature of the program. This 
# feature requires newly registered visitors to enter a registration code 
# along with their User ID and Password the first time they log in. 

$REG_HOLD = $YES;

# $REG_NOTICE should be set to $YES or $NO to indicate whether or not you 
# want to send a notice of each registration to a site administrator.

$REG_NOTICE = $YES;

# $REG_ADMIN should be set to $YES or $NO to indicate whether or not you 
# want visitors to receive automatic access to the system after registering.
# If $REG_ADMIN is set to $YES then new registrations will be added to
# $REG_HOLD_FILE and must be processed manually by the site adminstrator
# before the newly registered visitors will be allowed access to the site.

$REG_ADMIN = $NO;

# $ALLOW_FORCED is set to $YES or $NO depending on whether or not the program
# should check the first line in a requested page for a forced 
# authentication flag (AUTHENTICATE="YES"). If set to $YES, such a flag 
# exists, and the visitor's browser does not support cookies, the program 
# will force the visitor to re-enter their user name and password.

$ALLOW_FORCED = $YES;

# $REG_ID_START is the starting value to use for Registration IDs

$REG_ID_START = '10000';

# $TRACK_USERS should be set to $YES or $NO depending on whether or not you
# want to keep authentication files in $AUTH_DIR after they've expired. 
# (Since these files contain information about a visitor's journey through
# your pages, keeping them around gives you information that can be used
# to track users and site usage. It will also eventually use up a lot of
# disk space, however.)

$TRACK_USERS = $YES;

# $MAX_AGE is the maximum age in minutes since it was last accessed that a 
# file in $AUTH_DIR should still be considered valid.

$MAX_AGE = 10;

# $MAX_RETRY is the maximum number of times a visitor may retry their
# User_ID/Password combination in either $REG_HOLD_URL or $AUTH_URL.
# A value of 0 means that no retries are allowed.

$MAX_RETRY = 2;

# $AUTH_DIR is the full path to the directory where your authentication
# files will be kept. (Note that it should include a directory delimiter at 
# the end.)

$AUTH_DIR = '/home/user/authenticate/tmp/';

# $PAGE_DIR is the full path to the top directory where the HTML pages that 
# are being protected by this program are kept. (Note that it should include 
# a directory delimiter at the end.)

$PAGE_DIR = '/home/user/authenticate/pages/';

# $AUTH_PAGE_DIR is the full path to the directory where the HTML pages used 
# by this program will be kept. (Note that it should include a directory 
# delimiter at the end.)

$AUTH_PAGE_DIR = '/home/user/authenticate/';

# $PASSWD_DIR is the full path to the directory where your password 
# information will be kept. (Note that it should include a directory
# delimiter at the end.)

$PASSWD_DIR = '/home/user/authenticate/';

# $LOCK_DIR is the full path to the directory where all lock files will be placed
# (Note that it should include a directory delimiter at the end.) If you're using
# a UNIX web server with flock it must be a directory outside the web space, i.e.
# "/tmp/"

$LOCK_DIR = '/tmp/';

# $AUTH_PAGE is the name of the HTML form that should be used for general
# authentication. This form should include the fields "USER_ID" and
# "PASSWORD" along with a hidden field named "TYPE" with a value of
# "auth_basic" and should point to this program. (This file should be
# stored in $AUTH_PAGE_DIR.)

$AUTH_PAGE = 'auth_basic.html';

# $AUTH_RETRY_PAGE is the name of the HTML form that should be used when a
# visitor has entered an incorrect User ID or Password in $AUTH_URL. 
# This form should include the fields "USER_ID" and "PASSWORD" along with 
# a hidden field named "TYPE" with a value of "auth_retry", and should 
# point to this program. (This file should be stored in $AUTH_PAGE_DIR.)

$AUTH_RETRY_PAGE = 'auth_retry.html';

# $REG_HOLD_PAGE is the name of the HTML form that should be used to 
# authenticate newly registered visitors who are logging in for the first 
# time (only needed if $REG_HOLD is set to $YES. This form should include 
# the fields "USER_ID", "PASSWORD", and "REG_CODE" along with a hidden 
# field named "TYPE" with a value of "reg_hold", and should point to 
# this program. (This file should be stored in $AUTH_PAGE_DIR.)

$REG_HOLD_PAGE = 'reg_hold.html';

# $HOLD_RETRY_PAGE is the name of the HTML form that should be used when
# a visitor has entered an incorrect User ID or Password in 
# $REG_HOLD_URL. This form should include the fields "USER_ID" and
# "PASSWORD" along with a hidden field named "TYPE" with a value of
# "hold_retry", and should point to this program. (This file should be
# stored in $AUTH_PAGE_DIR.)

$HOLD_RETRY_PAGE = 'hold_retry.html';

# $REJECT_PAGE is the name of the HTML page that should be used for a
# visitor who does not enter a valid User_ID, Password, or Registration
# Code within the number of retries specified by $RETRY_URL. (This file
# should be stored in $AUTH_PAGE_DIR or may be a complete URL.)

$REJECT_PAGE = 'reject.html';

# $LOST_ID_PAGE is the name of the HTML form that should be used for a
# visitor who has lost their User ID. This form should include the fields 
# "USER_ID", "PASSWORD", and "VERIFY" along with a hidden field named 
# "TYPE" with a value of "lost_id", and should point to this program.
# (This file should be stored in $AUTH_PAGE_DIR.)

$LOST_ID_PAGE = 'lost_id.html';

# $LOST_INFO_PAGE is the name of the HTML page that should be used for a
# visitor who filled in the lost ID page and whose missing information
# has been found. (This file should be stored in $AUTH_PAGE_DIR.)

$LOST_INFO_PAGE = 'lost_info.html';

# $LOST_REJECT_PAGE is the name of the HTML page that should be used for
# a visitor who cannot be found in the password file after filling in the 
# lost ID page. (This file should be stored in $AUTH_PAGE_DIR.)

$LOST_REJECT_PAGE = 'lost_reject.html';

# $REGISTER_PAGE is the name of the HTML form that should be used for
# registering a new user. This form should include the fields "USER_ID"
# and "PASSWORD" along with a hidden field named "TYPE" with a value of
# "register". It should also include the fields "NAME", "EMAIL",
# and "VERIFY" that will contain the full name of the visitor, their 
# email address, and a word used to verify their identity in case they
# forget their User ID or Password (i.e. mother's maiden name). Other
# fields may also be included and can be processed by the ®ister()
# subroutine that should be in the required register.pl file. (This file
# should be stored in $AUTH_PAGE_DIR.)

$REGISTER_PAGE = 'register.html';

# $REG_INFO_PAGE is the name of the HTML page that should be used for
# general messages generated by the program during registration validation.
# The page should contain '<<TITLE>>' (without quotes) where the title of
# the page is to appear and '<<MESSAGE>>' (without quotes) where the message
# is to appear. (This file should be stored in $AUTH_PAGE_DIR.)

$REG_INFO_PAGE = 'reg_info.html';

# $DEFAULT_PAGE is the name of the default HTML page that should be displayed
# after a visitor has been successfully authenticated. This page will only 
# be used if the program was initially called without a requested page.

$DEFAULT_PAGE = 'index.html';

# $PASSWD_FILE is the name of the file where your password information
# will be kept. If using UNIX, you may want to begin the filename with 
# a period to add one extra (albeit relatively negligible) layer of 
# protection.

$PASSWD_FILE = '.password';

# $REG_HOLD_FILE is the name of the file where information about visitors
# who have registered but not yet logged in for the first time will be
# stored if $REG_HOLD is set to $YES. This file should be in $PASSWD_DIR

$REG_HOLD_FILE = '.reg_hold';

# $REG_ID_FILE is the name of the file where the current registration id
# will be kept. The registration id is incremented for each registered
# user and allows each user to be assigned a unique identifictation number.
# If the registration hold feature is turned on, this number is sent to the
# visitor's email address and must be entered along with the User ID and
# Password the first time they authenticate. (This file will be stored in 
# $PASSWD_DIR.) $REG_ID_START is the starting value to use for Registration 
# IDs

$REG_ID_FILE = '.reg_id';

# If $REG_NOTICE is set to $YES, $NOTICE_SUBJECT will be used as the 
# subject for the registration notice.

$NOTICE_SUBJECT = 'Registration Application';

# If $REG_NOTICE is set to $YES than a notice of each registration will
# be sent to the addresses specified by $NOTICE_TO, $NOTICE_CC, and 
# $NOTICE_BCC. ($NOTICE_CC and $NOTICE_BCC are optional.)

$NOTICE_TO = 'admin@domain.com';
$NOTICE_CC = '';
$NOTICE_BCC = '';

# If $REG_NOTICE is set to $YES, $NOTICE_BODY should specify the full path 
# to the file that contains the registration notification message to send to 
# the administrator. This file can include any of the form variables 
# surrounded by double brackets (i.e. <<PASSWORD>>) and the values of such 
# variables will be substituted before the template is sent). 

$NOTICE_BODY = '/home/user/authenticate/admin.txt';

# $REG_SUBJECT is the subject for the email message sent to a visitor with 
# their registration ID

$REG_SUBJECT = 'Your Registration ID';

# $REG_FROM, $REG_CC, $REG_BCC are the email addresses that should accompany
# the registration id email send to a visitor after registering. $REG_CC and 
# $REG_BCC are optional and will probably only be used if you want to 
# receive a copy of each piece of email.

$REG_FROM = '(Your Site Name) webmaster@domain.com';
$REG_CC = '';
$REG_BCC = 'webmaster@domain.com';

# $REG_BODY is the full path to the text file that contains the body copy 
# for the registration id email message. This message should contain 
# '<<REG_ID>>' (without quotes) wherever you want the registration id to 
# appear, and '<<PROGRAM>>' (without quotes) wherever you want the URL to the 
# registration validation form to appear.

$REG_BODY = '/home/user/authenticate/reg_body.txt';

# $LOST_SUBJECT is the subject for the email message sent to a visitor with 
# their authentication information if they forgot their User ID or Password

$LOST_SUBJECT = 'Your Authentication Information';

# $LOST_FROM, $LOST_CC, $LOST_BCC are the email addresses that should 
# accompany the authentication information email send to a visitor after 
# filling out the lost id form. $LOST_CC and $LOST_BCC are optional and will 
# probably only be used if you want to receive a copy of each piece of email.

$LOST_FROM = '(Your Site Name) webmaster@domain.com';
$LOST_CC = '';
$LOST_BCC = 'webmaster@domain.com';

# $LOST_BODY is full path to the text file that contains the body copy for 
# the authentication informatioin email message. This message should contain 
# '<<USER_ID>>' and '<<PASSWORD>>' (without quotes) wherever you 
# want the User ID and password to appear.

$LOST_BODY = '/home/user/authenticate/lost_body.txt';

# $SMTP_SERVER is the domain name of the SMTP server you will be using 
# to send email. If you're not sure about this, ask your service provider.

$SMTP_SERVER = 'smtp.domain.com';

# $DUP_TITLE is the title that will appear in the registration information page
# if the visitor enters a duplicate user ID. $DUP_MESSAGE is the message that
# will be shown.

$DUP_TITLE = 'DUPLICATE USER ID';
$DUP_MESSAGE = 'The User ID you entered has already been taken.<P>';
$DUP_MESSAGE .= 'Please press the Back button on your browser to return to the ';
$DUP_MESSAGE .= 'registration page and enter a different User ID.';

# $SUBMIT_TITLE is the title that will appear in the registration information
# page after the visitor submits valid registration information if $REG_ADMIN 
# is set to $YES. $SUBMIT_MESSAGE is the message that will be shown.

$SUBMIT_TITLE = 'REGISTRATION SUBMITTED';
$SUBMIT_MESSAGE = 'Your registration information has been submitted.<P>';
$SUBMIT_MESSAGE .= 'You will be notified by email as soon as your ';
$SUBMIT_MESSAGE .= 'registration has been processed and given instructions ';
$SUBMIT_MESSAGE .= 'on how to activate your account.';

# $MAX_WAIT is the maximum number of seconds the lock program will wait for a
# locked file to become unlocked

$MAX_WAIT = 5;

# $NAME_LEN is the maximum filename length in characters (not including 
# extensions)

$NAME_LEN = 32;

# $DATE_FORMAT holds the &format_date() format for the current date and time

$DATE_FORMAT = "<0m>/<0d>/<yr> <mh>.<0n>.<0s>";

# $ERROR_PAGE is the full path to the HTML file that contains the template 
# for an error message. This message should contain 
# '<<ERROR_TITLE>>', '<<ERROR_NAME>>', and '<<ERROR_MSG>>' (without quotes) 
# wherever you want the page title, error name, and error message to appear.

$ERROR_PAGE = '/home/user/error_page.html';

# $PERMISSIONS is the type of permissions you want to give to the files
# the program creates. They are expressed in standard UNIX octal format 
# (i.e. 0666). If your server is running on a system that doesn't support 
# file permissions then you should set $PERMISSIONS to 0.

$PERMISSIONS = 0666;

# $REQUIRE_DIR is the directory in which all of your required files are
# placed.  On most systems, if you leave the required files in the same
# directory as the CGI program, you can leave this variable blank.  
# Otherwise, if you move the required files to another directory, specify
# the full or relative path here.

$REQUIRE_DIR = 'require';

############################################################################
# Get required subroutines which need to be included.                      #
############################################################################

# Push $REQUIRE_DIR onto the @INC array for include file directories
# and list required files.

push(@INC, $REQUIRE_DIR) if $REQUIRE_DIR;

require 'locksubs.pl';
require 'formdate.pl';
require 'parsform.pl';
require 'register.pl';
require 'ipconvrt.pl';
require 'chkemail.pl';
require 'sendmail.pl';
require 'uuencode.pl';
require 'base64.pl';
require 'template.pl';
require 'error.pl';
require 'scramble.pl';

############################################################################
# Initialize other constants                                               #
############################################################################

$WEB_SERVER = $ENV{'SERVER_NAME'};
$PROGRAM_URL = "http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}$ENV{'PATH_INFO'}";
@PASSWORD = ($PASSWD_FILE, $REG_HOLD_FILE);
$REG_HOLD = ($REG_HOLD || $REG_ADMIN);

############################################################################
# Parse form data and check for cookies                                    #
############################################################################

if (!&parse_form) { &error($Error_Message) }
if (!$FORM{'REQUEST'}) { 
    $FORM{'REQUEST'} = substr($ENV{'PATH_INFO'}, 1);
    $FORM{'REQUEST'} =~ s|:/([^/])|://\1|;
    if (!$FORM{'REQUEST'} && ($ENV{'HTTP_REFERER'} ne $PROGRAM_URL)) { 
        $FORM{'REQUEST'} = $ENV{'HTTP_REFERER'};
    }
}
if ($ENV{'HTTP_COOKIE'}) { $COOKIES = $YES }

############################################################################
# Process request for registration hold authentication page                #
############################################################################

if ($REG_HOLD && ($ENV{QUERY_STRING} eq 'HOLD')) {
    $page_name = $REG_HOLD_PAGE;
    $form_type = 'auth-hold-basic';
}

############################################################################
# Process initial request                                                  #
############################################################################

elsif (!$FORM{'TYPE'}) {

    # Make sure the requested page is a valid one
    
    $page_name = $FORM{'REQUEST'};
    if ($page_name && !($page_name =~ m|://|)) {
        $page_path = "$PAGE_DIR$page_name";
    
        if (!(-f $page_path)) { 
            &error("$page_name is an invalid file name ($!).");
        }
        elsif (!(-r $page_path)) { 
            &error("$page_name is not readable ($!).");
        }
        elsif (!open(PAGE, $page_path)) {
            &error("$page_name could not be opened ($!).");
        }
    
        # If the forced authentication option is on, check the first line 
        # of the file to see if it includes a forced authentication flag
        
        if ($ALLOW_FORCED) {
            $_ = <PAGE>;
            $force_auth = /AUTHENTICATE="YES"/i;
        }
        close(PAGE);
    }

    # Check to see if they're already authenticated
    
    if ($ENV{'HTTP_COOKIE'} =~ /$ENV{'SCRIPT_NAME'}=(\d+)/) { $auth_id = $1 }
    elsif (!$COOKIES && !$force_auth) { 
        $auth_id = &ip_convert($ENV{'REMOTE_ADDR'});
    }
    
    # If an authentication id exists, make sure it's valid
    
    $auth_path = "$AUTH_DIR$auth_id";
    $date = time;
    if ($auth_id && (-f $auth_path) && (-r $auth_path)) {
        if (-M $auth_path < ($MAX_AGE / 1440)) {
    
            # File exists and is valid so set appropriate flag and update 
            # file
            
            open(AUTH, ">>$auth_path") 
              || &error("Could not open authentication file ($!).");
            print AUTH "$FORM{'REQUEST'}||$date\n";
            close(AUTH);        
            $valid = $YES;
        }
        elsif (!$TRACK_USERS) { unlink($auth_path) }
    }
    if (!$valid) {
    
        # No id or an invalid one, so prepare to give them the 
        # authentication form and attempt to set a cookie if needed
        
        $page_name = $AUTH_PAGE;
        $form_type = 'auth-basic';
        if (!$COOKIES) {
            print "Set-Cookie: $ENV{'SCRIPT_NAME'}_LAST=$date; path=/; expires=Tue, 04-Oct-2061 12:00:00 GMT;\n";
        }
    }
}
        
############################################################################
# Process request for lost id page                                         #
############################################################################

elsif ($FORM{'HELP'}) {
    $page_name = $LOST_ID_PAGE;
    $form_type = "lost_id";
}

############################################################################
# Process request for registration page                                    #
############################################################################

elsif ($FORM{'REGISTER'}) {
    $page_name = $REGISTER_PAGE;
    $form_type = "register";
}

############################################################################
# Process response from authentication page (plain, register, or retry)    #
############################################################################

elsif ($FORM{'TYPE'} =~ /^auth/) {

    # Check first to see if this is a registration hold
    
    if ($reg_hold = ($REG_HOLD && ($FORM{'TYPE'} =~ '-hold-'))) {
        $check_id = $FORM{'REG_ID'};
        $passwd_file = $REG_HOLD_FILE;
    }
    else { $passwd_file = $PASSWD_FILE }
    $passwd_path = "$PASSWD_DIR$passwd_file";

    # Make sure they actually entered the necessary information
    
    if ($FORM{'USER_ID'} && $FORM{'PASSWORD'} 
      && (!$reg_hold || $check_id)) {
    
        # Check password file to see if the entered information is valid
        
        if (&lock($passwd_file, $LOCK_DIR, $MAX_WAIT)) { 
            &error($Error_Message); 
        }
        if (-e $passwd_path) {
            open(PASS, $passwd_path) || &error("Could not open $passwd_file ($!).");
            $user_id = $FORM{'USER_ID'};
            while ($record = <PASS>) {
                if ($record =~ /^$user_id\|\|/) {
                    
                    # User ID matches so check the password (and 
                    # registration id if appropriate)
                    
                    ($file_passwd, $reg_id, $name, $email) = (split(/\|\|/, $record, 9))[1,4,7,8];
                    chop($email);
                    if ((crypt($FORM{'PASSWORD'}, $file_passwd) eq $file_passwd)
                      && (!$reg_hold || ($reg_id == $check_id))) {
                    
                        $valid = $YES;
                        
                        # If this is a registration hold, move the user's 
                        # info to the password file
                        
                        if ($reg_hold) {
                            
                            # Copy to the password file
                            
                            if (&lock($PASSWD_FILE, $LOCK_DIR, $MAX_WAIT)) { 
                                &error($Error_Message);
                            }
                            open(PASSWD, ">>$PASSWD_DIR$PASSWD_FILE") 
                              || &error("Could not open $PASSWD_FILE ($!).");
                            print PASSWD $record;
                            close(PASSWD);                      
                            if (&unlock($PASSWD_FILE, $LOCK_DIR)) { 
                                &error($Error_Message); 
                            }
                            
                            # Remove from the registration hold file
                            
                            seek(PASS, 0, 0);
                            $backup = "$passwd_path.bak";
                            open(BAK, ">$backup") 
                              || &error("Could not create $backup ($!).");
                            if ($PERMISSIONS) { chmod $PERMISSIONS, $backup }
                            while (<PASS>) {
                                if (!/^$user_id\|\|/) { print BAK }
                            }
                            close(PASS);
                            unlink($passwd_path);
                            rename($backup, $passwd_path)
                              || &error("Could not rename $backup ($!).");
                        }
                        
                        # Call the local authentication subroutine
                        
                        &set_authenticate;
                        
                        # Get out of the loop
                        
                        last;
                    }
                }
            }
            close(PASS);
        }
        if (&unlock($passwd_file, $LOCK_DIR)) { &error($Error_Message) }
    }
        
    # If not, send to retry page as long as maximum tries not exceeded

    if (!$valid && $FORM{'RETRIES'} < $MAX_RETRY) { 
        ++$FORM{'RETRIES'};
        if ($reg_hold) { $page_name = $HOLD_RETRY_PAGE }
        else { $page_name = $AUTH_RETRY_PAGE }
        $form_type = $FORM{'TYPE'};
        $form_type =~ s/-.*$/-retry/i;
    }
    elsif (!$valid) { 
        $page_name = $REJECT_PAGE;
        $form_type = 'auth_reject';
    }
}

############################################################################
# Process response from registration page                                  #
############################################################################

elsif ($FORM{'TYPE'} =~ /^register/) {
    
    # Make sure the User ID is unique (check password file and reg hold 
    # file)
    
    if ($user_id = $FORM{'USER_ID'}) {
        foreach $passwd_file (@PASSWORD) {
            $passwd_path = "$PASSWD_DIR$passwd_file";
            if (&lock($passwd_file, $LOCK_DIR, $MAX_WAIT)) { 
                &error($Error_Message);
            }
            if (!(-e $passwd_path)) {
                open(PASS, ">>$passwd_path") 
                  || &error("Could not create $passwd_file ($!).");
                if ($PERMISSIONS) { chmod $PERMISSIONS, $passwd_path }
            }
            else {
                open(PASS, $passwd_path) 
                  || &error("Could not open $passwd_file ($!).");
                while ($unique && ($record = <PASS>)) {
                    if ($record =~ /^$user_id\|\|/) { $unique = $NO }
                }
            }
            close(PASS);
            if (&unlock($passwd_file, $LOCK_DIR)) { &error($Error_Message) }  
        }
    }
    
    # Tell them if not
    
    if (!$unique) {
        $VAR{'TITLE'} = $DUP_TITLE;
        $VAR{'MESSAGE'} = $DUP_MESSAGE;
    }

    # Otherwise, if the registration information hasn't already been 
    # processed, do so now with the user-provided routine
    
    elsif (®ister) {
        
        # Get the next registration id
    
        $reg_id_path = "$PASSWD_DIR$REG_ID_FILE";
        if (&lock($REG_ID_FILE, $LOCK_DIR, $MAX_WAIT)) { 
            &error($Error_Message);
        }
        if (!(-e $reg_id_path) || -z $reg_id_path) {
            open(REGID, ">>$reg_id_path") 
              || &error("Could not create $REG_ID_FILE ($!).");
            if ($PERMISSIONS) { chmod $PERMISSIONS, $reg_id_path }
            $reg_id = $REG_ID_START;
        }
        else {
            open(REGID, "+<$reg_id_path") 
              || &error("Could not open $REG_ID_FILE ($!).");
            $reg_id = <REGID>;
            seek(REGID, 0, 0);
        }
        print REGID $reg_id + 1;
        close(REGID);
        if (&unlock($REG_ID_FILE, $LOCK_DIR)) { &error($Error_Message) }  
        
        # Encrypt the password and verify fields and scramble the password
        # using the verify field as the key (the scrambled password will
        # only be used when searching for a lost password)
    
        srand(time^$$);
        $encrypt_pass = crypt($FORM{'PASSWORD'}, substr(rand(time), -2);
        $encrypt_verify = crypt($FORM{'VERIFY'}, substr(rand(time), -2);
        $scramble_pass = &scramble($FORM{'PASSWORD'}, $FORM{'VERIFY'});
        
        # Create the password file record
        
        $date = &format_date(time, $DATE_FORMAT);
        $record = "$FORM{'USER_ID'}||$encrypt_pass||$encrypt_verify||";
        $record .= "$scramble_pass||$reg_id||$ENV{'REMOTE_ADDR'}||$date||";
        $record .= "$FORM{'NAME'}||$FORM{'EMAIL'}\n";
        
        # Save it to the appropriate file
        
        $passwd_file = $REG_HOLD ? $REG_HOLD_FILE : $PASSWD_FILE;
        if (&lock($passwd_file, $LOCK_DIR, $MAX_WAIT)) { 
            &error($Error_Message);
        }
        open(PASSWD, ">>$PASSWD_DIR$passwd_file") 
          || &error("Could not open $passwd_file ($!).");
        print PASSWD $record;
        close(PASSWD);                      
        if (&unlock($passwd_file, $LOCK_DIR)) { &error($Error_Message) }

        # If registration notification is on, send the administrator
        # the registration information
            
        if ($REG_NOTICE) {                
            $VAR{'REG_ID'} = $reg_id;
            if (&send_email($NOTICE_SUBJECT, $FORM{'EMAIL'}, $NOTICE_TO, 
                            $NOTICE_CC, $NOTICE_BCC, $NOTICE_BODY)) {
                &error($Error_Message);
            }
        }
        
        # Send the visitor the appropriate message
    
        if ($REG_ADMIN) {
            
            # Give the visitor a message also
            
            $VAR{'TITLE'} = $SUBMIT_TITLE;
            $VAR{'MESSAGE'} = $SUBMIT_MESSAGE;
        }
        elsif ($REG_HOLD) {

            # Email them their registration id if registration hold is 
            # active
        
            $VAR{'PROGRAM'} = "$PROGRAM_URL?HOLD";
            $VAR{'REG_ID'} = $reg_id;
            if (&send_email($REG_SUBJECT, $REG_FROM, $FORM{'EMAIL'}, $REG_CC,
                            $REG_BCC, $REG_BODY)) {
                &error($Error_Message);
            }
            
            $page_name = $REG_HOLD_PAGE;
            $form_type = 'auth-hold-basic';
        }
        else { 
            
            # They're in!
                        
            &set_authenticate;
            $valid = $YES;
        }
    }
    if ($VAR{'TITLE'}) {
        
        # Print the MIME header
        
        print "Content-type: text/html\n\n";
        
        # Read and parse the file

        &parse_template("$AUTH_PAGE_DIR$REG_INFO_PAGE", *STDOUT);
        
        # We're done
        
        exit;
    }
}

############################################################################
# Process response from lost ID page                                       #
############################################################################

elsif ($FORM{'TYPE'} eq 'lost_id') {

    # Make sure they gave us at least two pieces of requested information
    
    if (!$FORM{'VERIFY'} || (!$FORM{'USER_ID'} && !$FORM{'PASSWORD'})) {
        
        # Send them back to the form again
        
        $page_name = $LOST_ID_PAGE;
        $form_type = 'lost_id';
    }

    # Otherwise, search for a matching record in the password files
        
    else {  
        
        # Pull the fields out of the array for speed
        
        $user_id = $FORM{'USER_ID'};
        $password = $FORM{'PASSWORD'};
        $verify = $FORM{'VERIFY'};
        
        # Check each password file
        
        CHECK: foreach $passwd_file (@PASSWORD) {
            $passwd_path = "$PASSWD_DIR$passwd_file";
            if (&lock($passwd_file, $LOCK_DIR, $MAX_WAIT)) {
                &error($Error_Message);
            }
            if (!(-e $passwd_path)) { next }
            open(PASS, $passwd_path) 
              || &error("Could not open $passwd_file ($!).");
            while ($record = <PASS>) {
                
                # Check first for a match on User ID if given
                
                if ($user_id && ($record =~ /^$user_id\|\|/)) {
                    ($file_passwd, $file_verify, $pass_scramble, $email) = 
                      (split(/\|\|/, $record, 9))[1,2,3,8];
                    if (crypt($verify, $file_verify) eq $file_verify) {
                        $password = &unscramble($pass_scramble, $verify);
                        if (crypt($password, $file_passwd) eq $file_passwd) {
                            $VAR{'PASSWORD'} = $password;
                            $FORM{'USER_ID'} = '';
                            $match = $YES;
                        }
                    }
                    
                    # We're done here one way or another, so get out of the 
                    # loops
                    
                    last CHECK;
                }
                
                # If no match on User ID, check for match on password and 
                # verify
                
                elsif (!$user_id) {
                    ($file_id, $file_passwd, $file_verify, $email) = 
                      (split(/\|\|/, $record, 9))[0,1,2,8];
                    if ((crypt($password, $file_passwd) eq $file_passwd)
                      && (crypt($verify, $file_verify) eq $file_verify)) {
                        $VAR{'USER_ID'} = $file_id;
                        $FORM{'PASSWORD'} = '';
                        $match = $YES;
                        last CHECK;
                    }
                }
            }
            close(PASS);
            if (&unlock($passwd_file, $LOCK_DIR)) { &error($Error_Message) }  
        }
        
        # If there's a match then email them the information they're 
        # missing, otherwise send them to the reject page
        
        if ($match) {
            chop($email);
            if (&send_email($LOST_SUBJECT, $LOST_FROM, $email, $LOST_CC,
                            $LOST_BCC, $LOST_BODY)) {
                &error($Error_Message);
            }
            $page_name = $LOST_INFO_PAGE;
        }
        else { 
            $page_name = $LOST_REJECT_PAGE;
            $form_type = 'lost_reject'; 
        }
    }
}

############################################################################
# Send them to or give them the appropriate page                           #
############################################################################

# If they're a valid user then give them the page they requested

if ($valid) {
    if (!$FORM{'REQUEST'}) { $FORM{'REQUEST'} = $DEFAULT_PAGE }
    if ($FORM{'REQUEST'} =~ m|://|) {
        print "Location: $FORM{'REQUEST'}\n\n";
        exit;
    }
    $page_name = $FORM{'REQUEST'};
    $page_path = "$PAGE_DIR$page_name";
}
else { $page_path = "$AUTH_PAGE_DIR$page_name" }

# Check to see if the requested page is a valid, readable file

if (!(-f $page_path)) { 
    &error("$page_name is an invalid file name ($!).");
}
elsif (!(-r $page_path)) { 
    &error("$page_name is not readable ($!).");
}
elsif (!open(PAGE, $page_path)) {
    &error("$page_name could not be opened ($!).");
}

# Set up the hidden fields if we're sending them to a form

if ($form_type) {
    $type = '<INPUT TYPE="HIDDEN"';
    $hidden_fields = "$type NAME=\"RETRIES\" VALUE=\"$FORM{'RETRIES'}\">\n";
    $hidden_fields .= "$type NAME=\"TYPE\" VALUE=\"$form_type\">\n";
    $hidden_fields .= "$type NAME=\"REQUEST\" VALUE=\"$FORM{'REQUEST'}\">\n";
}

# Print the MIME header

if ($page_path =~ /\.html?$/i) { 
    print "Content-type: text/html\n\n"; 
    $html = $YES;
}
elsif ($page_path =~ /\.txt$/i) { print "Content-type: text/plain\n\n" }
elsif ($page_path =~ /\.gif$/i) { print "Content-type: image/gif\n\n" }
elsif ($page_path =~ /\.jpe?g$/i) { print "Content-type: image/jpeg\n\n" }
elsif ($page_path =~ /\.pdf$/i) { print "Content-type: application/pdf\n\n" }
else { print "Content-type: application/octet-stream\n\n" }

# Read and parse the file

while (<PAGE>) {

    # If this is a form check for the ACTION attribute if we haven't seen it
    
    if ($html && $form_type && !$action_line 
      && s/ACTION="[^"]*"/ACTION="$PROGRAM_URL"/i) {
        $action_line = $YES;
        print $_;
        print $hidden_fields;
    }
    else { print $_ }
}

############################################################################
# Local subroutine to assign authentication ID and create auth file.       #
############################################################################

sub set_authenticate {

    # Set the authentication id
    
    $date = time;
    if ($COOKIES) { 
        $auth_id = $reg_id;
        $ENV{'SCRIPT_NAME'} =~ m|^(/?.*)/|;
        print "Content-type: text/html\n";
        print "Set-Cookie: $ENV{'SCRIPT_NAME'}=$auth_id; path=$1\n";
        print "Set-Cookie: $ENV{'SCRIPT_NAME'}_LAST=$date; path=/; expires=Tue, 04-Oct-2061 12:00:00 GMT;\n";
    }
    else { $auth_id = &ip_convert($ENV{'REMOTE_ADDR'}) }
    
    # Create the authentication file
    
    $auth_path = "$AUTH_DIR$auth_id";
    if (!$COOKIES && !$ALLOW_FORCED && (-e $auth_path)
       && (-M $auth_path < ($MAX_AGE / 1440))) {
        &error("Someone is already logged in from $ENV{'REMOTE_ADDR'}");
    }
    open(AUTH, ">>$auth_path") 
      || &error("Could not modify $auth_id ($!).");
    if ($PERMISSIONS) { chmod $PERMISSIONS, "AUTH_DIR$auth_id" }
    if (-s $auth_path) { print AUTH "\n" }
    print AUTH "$user_id\n$FORM{'REQUEST'}||$date\n";
    close(AUTH);
   
    # Delete any old authentication files if requested
    
    if (!$TRACK_USERS) {
        opendir(DIR, $AUTH_DIR);
        @files = readdir(DIR);
        closedir(DIR);
        foreach $file (@files) {
            if (-M "$AUTH_DIR$file" >= ($MAX_AGE / 1440)) {
                push(@expired, "$AUTH_DIR$file");
            }
        }
        unlink(@expired);
    }
}