#!/usr/bin/perl 
#!c:\perl\bin\perl.exe 
#!/usr/local/bin/perl 
#
#       ----------------------
#               DBMan
# 		----------------------
#		Database Administrator
#
#         File: db.cgi
#  Description: This is the main program file and contains all the functionality
#               of the database manager.
#       Author: Alex Krohn
#        Email: alex@gossamer-threads.com
#          Web: http://www.gossamer-threads.com/
#	   Version: 2.04
#
# COPYRIGHT NOTICE:
#
# Copyright 1997 Gossamer Threads Inc.  All Rights Reserved.
#
# This program is being distributed as shareware.  It may be used and
# modified free of charge for personal, academic, government or non-profit
# use, so long as this copyright notice and the header above remain intact.
# Any commercial use should be registered.  Please also send me an email,
# and let me know where you are using this script. By using this program 
# you agree to indemnify Gossamer Threads Inc. from any liability.
#
# Selling the code for this program without prior written consent is
# expressly forbidden.  Obtain permission before redistributing this
# program over the Internet or in any other medium.  In all cases
# copyright and header must remain intact.
#
# Please check the README file for full details on registration.
# =====================================================================

# If you run into problems, set $db_script_path to the full path
# to your directory.
$db_script_path = ".";

# Load the form information and set the config file and userid.
local(%in) = &parse_form;
$in{'db'}  ? ($db_setup = $in{'db'}) : ($db_setup = 'default');
$in{'uid'} ? ($db_uid   = $in{'uid'}): ($db_uid   = '');

# Required Librariers
# --------------------------------------------------------
# Make sure we are using perl 5.003, load the config file, and load the auth file.
eval {
	unshift (@INC, $db_script_path);
	require 5.003;				# We need at least Perl 5.003
	unless ($db_setup =~ /^[A-Za-z0-9]+$/) { die "Illegal navn på config-fil: $db_setup"; }
	require "$db_setup.cfg";	# Database Definition File
	require "auth.pl";			# Authorization Routines
};
if ($@) { &cgierr ("Error loading required libraries.\nCheck that they exist, permissions are set correctly and that they compile.\nReason: $@"); }

# If we are using benchmarking, then we start a timer and stop it around &main. Then we print the difference.
if ($db_benchmark) { $t0 = new Benchmark; }

eval { &main; };							# Trap any fatal errors so the program hopefully 
if ($@) { &cgierr("fatal error: $@"); }		# never produces that nasty 500 server error page.

# Stop the timer and print.
if ($db_benchmark) { $t1 = new Benchmark; print "<h6>Processing Time: " . timestr(timediff($t1, $t0)) . "</h6>"; }

# Display debugging information if requested.
&cgierr("Debug Information") if ($db_debug);

exit; 	# There are only two exit calls in the script, here and in in &cgierr. 

sub main {
# --------------------------------------------------------
	my ($status, $uid);
	local($per_add, $per_view, $per_mod, $per_del, $per_admin);	

	$|++;		# Flush Output Right Away

	&auth_cleanup unless ($auth_no_authentication);	 # Remove old session files.

	($status, $uid, $per_view, $per_add, $per_del, $per_mod, $per_admin) 
			= &auth_check_password;	     # Authenticate User, get permissions and userid.

	if ($status eq "ok") {
# Set the script link URL with db and user info for links. Use $db_script_url for forms.
		$db_script_link_url = "$db_script_url?db=$db_setup&uid=$db_uid";
		if ($uid eq "default") { $db_userid = $uid; }
		else { ($db_userid) = $db_uid =~ /([A-Za-z0-9]+)\.\d+/; }		
					
# Main Menu. Check to see what the user requested, then, if he has permission for that
# request, do it. Otherwise send the user off to an unauthorized request page.
		if    ($in{'add_form'})				{ if ($per_add) { &html_add_form; } 		  else { &html_unauth; } }

#### Following two lines added for secure_password_lookup mod
 elsif ($in{'change_password_form'}) { unless ($db_userid eq "default") { &html_change_password_form; } else { &html_unauth; } }
 elsif ($in{'change_password'})      { unless ($db_userid eq "default") { &change_password; }           else { &html_unauth; } }
#### Following two lines added for secure_password_lookup mod
 elsif ($in{'change_email_form'}) { unless ($db_userid eq "default") { &html_change_email_form; } else { &html_unauth; } }
 elsif ($in{'change_email'})      { unless ($db_userid eq "default") { &change_email; }           else { &html_unauth; } }

		elsif ($in{'add_record'})			{ if ($per_add) { &add_record; } 			  else { &html_unauth; } }
		elsif ($in{'view_search'})			{ if ($per_view) { &html_view_search; } 	  else { &html_unauth; } }
		elsif ($in{'view_records'})			{ if ($per_view) { &view_records; } 		  else { &html_unauth; } } 
		elsif ($in{'delete_search'})		{ if ($per_del) { &html_delete_search; } 	  else { &html_unauth; } }  
		elsif ($in{'delete_form'})			{ if ($per_del) { &html_delete_form; }		  else { &html_unauth; } }  
		elsif ($in{'delete_records'})		{ if ($per_del) { &delete_records; } 		  else { &html_unauth; } }  
		elsif ($in{'modify_search'})		{ if ($per_mod) { &html_modify_search; } 	  else { &html_unauth; } }  
		elsif ($in{'modify_form'})			{ if ($per_mod) { &html_modify_form; }		  else { &html_unauth; } }  
		elsif ($in{'modify_form_record'})	{ if ($per_mod) { &html_modify_form_record; } else { &html_unauth; } }  
		elsif ($in{'modify_record'})		{ if ($per_mod) { &modify_record;  } 		  else { &html_unauth; } }
		elsif ($in{'admin_display'})		{ if ($per_admin) { &admin_display; }		  else { &html_unauth; } }
		elsif ($in{'logoff'})				{ &auth_logging('logged off') if ($auth_logging);
											  $auth_logoff ? (print "Location: $auth_logoff\n\n") : (print "Location: $db_script_url\n\n"); }
		elsif ((keys(%in) <= 2) || 
				($in{'login'}))				{ &html_home; }
		else 								{ &html_unkown_action; }		
	}
# If we allow users to signup, and they want to, go to the signup form.	
	elsif ($auth_signup and $in{'signup_form'}) {
		&html_signup_form; 
	}
	elsif ($auth_signup and $in{'signup'}) {
		&signup; 
	}
      elsif ($in{'lookup_form'}) { &html_lookup_form; }
      elsif ($in{'lookup'})      { &lookup;           }

# Auth Check Password has determined that the user has not logged in, so let's send
# him to the login screen.
	elsif ($status eq "no login") {
		&html_login_form;
	}
# Auth Check Password had an error trying to authenticate the user. Probably there was
# an invalid user/password or the user file has expired. Let's go to an error page and
# ask the user to re log on.
	else {
		&html_login_failure($status);
	}
}

sub add_record {
# --------------------------------------------------------
# Adds a record to the database. First, validate_record is called
# to make sure the record is ok to add. If it is, then the record is
# encoded and added to the database and the user is sent to 
# html_add_success, otherwise the user is sent to html_add_failure with
# an error message explaining why. The counter file is also updated to the
# next number.

	my ($output, $status, $counter);
# Set the userid to the logged in user.
	($auth_user_field >= 0) and ($in{$db_cols[$auth_user_field]} = $db_userid);
	
# First we validate the record to make sure the addition is ok.	
	$status = &validate_record;

# We keep checking for the next available key, or until we've tried 50 times
# after which we give up.
	while ($status eq "kun een række må oprettes med denne nøgle" and $db_key_track) {
		return "kun een række må oprettes med denne nøgle" if ($counter++ > 50);
		$in{$db_key}++;
		$status = &validate_record;
	}

	if ($status eq "ok") {
		open (DB, ">>$db_file_name") or &cgierr("error in add_record. unable to open database: $db_file_name.\nReason: $!");
			if ($db_use_flock) {
				flock(DB, 2)  or &cgierr("kan ikke få eksklusiv adgang til $db_file_name.\nÅrsag: $!");
			}
			print DB &join_encode(%in);	
		close DB;		# automatically removes file lock
		if ($db_key_track) {
			open (ID, ">$db_id_file_name") or &cgierr("error in get_defaults. unable to open id file: $db_id_file_name.\nReason: $!");
				if ($db_use_flock) {
					flock(ID, 2)  or &cgierr("kan ikke få eksklusiv adgang til $db_id_file_name.\nÅrsag: $!");
				}
				print ID $in{$db_key};		# update counter.
			close ID;		# automatically removes file lock
		}
		&auth_logging("added record: $in{$db_key}") if ($auth_logging);
		&html_add_success;
	}
	else {
		&html_add_failure($status);
	}
}

sub delete_records {
# --------------------------------------------------------
# Deletes a single or multiple records. First the routine goes thrrough
# the form input and makes sure there are some records to delete. It then goes
# through the database deleting each entry and marking it deleted. If there
# are any keys not deleted, an error message will be returned saying which keys
# were not found and not deleted, otherwise the user will go to the success page.

	my ($key, %delete_list, $rec_to_delete, @lines, $line, @data, $errstr, $succstr, $output, $restricted);
	$rec_to_delete = 0;
	foreach $key (keys %in) {				# Build a hash of keys to delete.
		if ($in{$key} eq "delete") {
			$delete_list{$key} = 1;
			$rec_to_delete = 1;
		}
	}
	if (!$rec_to_delete) {
		&html_delete_failure("ingen rækker udpeget.");
		return;
	}

	open (DB, "<$db_file_name") or &cgierr("error in delete_records. unable to open db file: $db_file_name.\nReason: $!");
		if ($db_use_flock) { flock(DB, 1); }
		@lines = <DB>;
	close DB;

	($restricted = 1) if ($auth_modify_own and !$per_admin);
	
	LINE: foreach $line (@lines) {
		if ($line =~ /^$/) { next LINE; }
		if ($line =~ /^#/) { $output .= $line; next LINE; }
		chomp ($line);			
		@data     = &split_decode($line);
		($output .= "$line\n" and next LINE) if ($restricted and ($db_userid ne $data[$auth_user_field]));
		
		$delete_list{$data[$db_key_pos]} ? 			    # if this id is one we want to delete
			($delete_list{$data[$db_key_pos]} = 0) : 	# then mark it deleted and don't print it to the new database.
			($output .= $line . "\n");					# otherwise print it.
	}
	
	foreach $key (keys %delete_list) {
		$delete_list{$key} ?				# Check to see if any items weren't deleted
			($errstr .= "$key,") :			# that should have been.
			($succstr .= "$key,"); 			# For logging, we'll remember the one's we deleted.
	}
	chop($succstr);		# Remove trailing delimeter
	chop($errstr);		# Remove trailing delimeter

	open (DB, ">$db_file_name") or &cgierr("error in delete_records. unable to open db file: $db_file_name.\nReason: $!");
		if ($db_use_flock) {
			flock(DB, 2) or &cgierr("unable to get exclusive lock on $db_file_name.\nReason: $!");
		}
		print DB $output;
	close DB;		# automatically removes file lock			

	&auth_logging("deleted records: $succstr") if ($auth_logging);
	$errstr ?								# Do we have an error?
		&html_delete_failure($errstr) :		# If so, then let's report go to the failure page,
		&html_delete_success($succstr);		# else, everything went fine.
}	

sub modify_record {
# --------------------------------------------------------
# This routine does the actual modification of a record. It expects
# to find in %in a record that is already in the database, and will
# rewrite the database with the new entry. First it checks to make
# sure that the modified record is ok with validate record.
# It then goes through the database looking for the right record to
# modify, if found, it prints out the modified record, and returns
# the user to a success page. Otherwise the user is returned to an error
# page with a reason why.

	my ($status, $line, @lines, @data, $output, $found, $restricted);
	
	$status = &validate_record;		# Check to make sure the modifications are ok!

	if ($status eq "ok") {
		open (DB, "<$db_file_name") or &cgierr("error in modify_records. unable to open db file: $db_file_name.\nReason: $!");
			if ($db_use_flock) { flock(DB, 1); }
			@lines = <DB>;	# Slurp the database into @lines..
		close DB;

		($restricted = 1) if ($auth_modify_own and !$per_admin);

		$found = 0;		# Make sure the record is in here!
		LINE: foreach $line (@lines) {
			if ($line =~ /^$/) { next LINE; }					# Skip and Remove blank lines
			if ($line =~ /^#/) { $output .= $line; next LINE; }	# Comment Line
			chomp ($line);			
			@data     = &split_decode($line);
			($output .= "$line\n" and next LINE) if ($restricted and ($db_userid ne $data[$auth_user_field]));
			
			if ($data[$db_key_pos] eq $in{$db_key}) {
# If we have userid's and this is not an admin, then we force the record to keep it's own
# userid.
				if ($auth_user_field >= 0 and (!$per_admin or !$in{$db_cols[$auth_user_field]})) {
					$in{$db_cols[$auth_user_field]} = $data[$auth_user_field];  
				}
				$output .= &join_encode(%in);			
				$found = 1;								
			}
			else {
				$output .= $line . "\n";				# else print regular line.
			}
		}
		if ($found) {
			open (DB, ">$db_file_name") or &cgierr("error in modify_records. unable to open db file: $db_file_name.\nReason: $!");
				if ($db_use_flock) {
					flock(DB, 2)  or &cgierr("unable to get exclusive lock on $db_file_name.\nReason: $!");
				}
				print DB $output;				
			close DB;			# automatically removes file lock

			&auth_logging("modified record: $in{$db_key}") if ($auth_logging);
			&html_modify_success;
		}
		else {
			&html_modify_failure("$in{$db_key} (can't find requested record)");
		}
	}
	else {
		&html_modify_failure($status);		# Validation Error
	}
}

sub view_records {
# --------------------------------------------------------
# This is called when a user is searching the database for 
# viewing. All the work is done in query() and the routines just
# checks to see if the search was successful or not and returns
# the user to the appropriate page.

	my ($status, @hits) = &query("view");
	if ($status eq "ok") {
		&html_view_success(@hits);
	}
	else {
		&html_view_failure($status);
	}
}

sub query {
# --------------------------------------------------------
# First let's get a list of database fields we want to search on and 
# store it in @search_fields

	my ($i, $column, @search_fields, @search_gt_fields, @search_lt_fields, $maxhits, $numhits, $nh,
	    $field, @regexp, $line, @values, $key_match, @hits, @sortedhits, $next_url, $next_hit, $prev_hit,
		$first, $last, $upper, $lower, $left, $right, $restricted);	
	local (%sortby);
	
# First thing we do is find out what we are searching for. We build a list of fields
# we want to search on in @search_fields.
	if ($in{'keyword'}) {		# If this is a keyword search, we are searching the same
		$i = 0; 				# thing in all fields. Make sure "match any" option is 
		$in{'ma'} = "on";		# on, otherwise this will almost always fail.
		foreach $column (@db_cols) {		
			if (($db_sort{$column} eq 'date') or &date_to_unix($in{'keyword'})) { $i++; next; }
			if ($i == $auth_user_field) { $i++; next; }
			push (@search_fields, $i);		# Search every column			
			$in{$column} = $in{'keyword'};	# Fill %in with keyword we are looking for.
			$i++;
		}
	}
	else {						# Otherwise this is a regular search, and we only want records
		$i = 0;					# that match everything the user specified for.		
		foreach $column (@db_cols) {
			if ($in{$column}   =~ /^\>(.+)$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($1) or return "Illegalt dato-format: '$1'");
												push (@search_gt_fields, $i); $in{"$column-gt"} = $1; $i++; next; }
			if ($in{$column}   =~ /^\<(.+)$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($1) or return "Illegalt dato-format: '$1'");
												push (@search_lt_fields, $i); $in{"$column-lt"} = $1; $i++; next; }
			if ($in{$column}      !~ /^\s*$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($in{$column}) or return "Illegalt dato-format: '$in{$column}'");
												push(@search_fields, $i); $i++; next; }
			if ($in{"$column-gt"} !~ /^\s*$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($in{"$column-gt"}) or return "Illegalt dato-format: '$in{$column}'");
												push(@search_gt_fields, $i); }
			if ($in{"$column-lt"} !~ /^\s*$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($in{"$column-lt"}) or return "Illegalt dato-format: '$in{$column}'");
												push(@search_lt_fields, $i); }
			$i++;
		}
	}
# If we don't have anything to search on, let's complain.
	if (!@search_fields and !@search_gt_fields and !@search_lt_fields) {
		return "ingen søgeord angivet";
	}
	
# Define the maximum number of hits we will allow, and the next hit counter.	
	$in{'mh'} ?	($maxhits = $in{'mh'}) : ($maxhits = $db_max_hits);
	$in{'nh'} ? ($nh      = $in{'nh'}) : ($nh      = 1);
	$numhits = 0;

# Let's set restricted to 1 if the user can only view/mod their own and
# this isn't an admin.
	($restricted = 1) if ($_[0] eq "view" and $auth_view_own and !$per_admin);
	($restricted = 1) if ($_[0] eq "mod"  and $auth_modify_own and !$per_admin);

# Now let's build up all the regexpressions we will use. This saves the program
# from having to recompile the same regular expression every time.
	foreach $field (@search_fields) {
		my $tmpreg = "$in{$db_cols[$field]}";
		(!$in{'re'}) and ($tmpreg = "\Q$tmpreg\E");
		($in{'ww'})  and ($tmpreg = "\\b$tmpreg\\b");
		(!$in{'cs'}) and ($tmpreg = "(?i)$tmpreg");
		($in{$db_cols[$field]} eq "*") and ($tmpreg = ".*");	# A "*" matches anything.
		
		$regexp_func[$field] = eval "sub { m/$tmpreg/o }";
		$regexp_bold[$field] = $tmpreg;
	}

# Now we go through the database and do the actual searching.	
# First figure out which records we want:
	$first = ($maxhits * ($nh - 1));
	$last  =  $first + $maxhits - 1;
	
	open (DB, "<$db_file_name") or &cgierr("error in search. unable to open database: $db_file_name.\nReason: $!");
	if ($db_use_flock) { flock(DB, 1); }	
	LINE: while (<DB>) {
		(/^#/)      and next LINE;		# Skip comment Lines.
		(/^\s*$/)   and next LINE;		# Skip blank lines.
		$line = $_;	chomp ($line);		# Remove trailing new line.
		@values = &split_decode($line);

# If we are only allowed to view/mod our own record, then let's check here.
		next LINE if ($restricted and ($db_userid ne $values[$auth_user_field]));
		
# Normal searches.		
		$key_match = 0;
		foreach $field (@search_fields) {
			$_ = $values[$field];	# Reg function works on $_.
			$in{'ma'} ?
				($key_match = ($key_match or &{$regexp_func[$field]})) :
			    (&{$regexp_func[$field]} or next LINE);
		}
# Greater then searches.
		foreach $field (@search_gt_fields) {		
			$term = $in{"$db_cols[$field]-gt"};						
			if ($db_sort{$db_cols[$field]} eq "date") {
				$in{'ma'} ?
					($key_match = ($key_match or (&date_to_unix($values[$field])) > &date_to_unix($term))) :
					(&date_to_unix($values[$field]) > (&date_to_unix($term)) or next LINE);
			}
			elsif ($db_sort{$db_cols[$field]} eq 'alpha') {
				$in{'ma'} ?
					($key_match = ($key_match or ($values[$field] > $term))) :
					((lc($values[$field]) gt lc($term)) or next LINE);			
			}
			else {;			
				$in{'ma'} ?
					($key_match = ($key_match or ($values[$field] > $term))) :
					(($values[$field] > $term) or next LINE);
			}
		}
# Less then searches.
		foreach $field (@search_lt_fields) {
			$term = $in{"$db_cols[$field]-lt"};
			if ($db_sort{$db_cols[$field]} eq "date") {
				$in{'ma'} ?
					($key_match = ($key_match or (&date_to_unix($values[$field]) < &date_to_unix($term)))) :
					(&date_to_unix($values[$field]) < (&date_to_unix($term)) or next LINE);
			}
			elsif ($db_sort{$db_cols[$field]} eq 'alpha') {
				$in{'ma'} ?
					($key_match = ($key_match or ($values[$field] < $term))) :
					((lc($values[$field]) lt lc($term)) or next LINE);			
			}
			else {
				$in{'ma'} ?
					($key_match = ($key_match or ($values[$field] < $term))) :
					(($values[$field] < $term) or next LINE);
			}
		}
# Did we find a match? We only add the hit to the @hits array if we need it. We can
# skip it if we are not sorting and it's not in our first < > last range.
		if ($key_match || (!($in{'keyword'}) && !($in{'ma'}))) {			
			if (exists $in{'sb'}) {
				$sortby{(($#hits+1) / ($#db_cols+1))} = $values[$in{'sb'}];				
				push (@hits, @values); 
			}
			else {
				(($numhits >= $first) and ($numhits <= $last)) and push (@hits, @values);
			}
			$numhits++;		# But we always count it!
		}
	}
	close DB;
	
# Now we've stored all our hits in @hits, and we've got a sorting values stored
# in %sortby indexed by their position in @hits.
	$numhits ? ($db_total_hits = $numhits) : ($db_total_hits = 0);
	($db_total_hits == 0) and return ("Ingen rækker matchede.");

# Sort the array @hits in order if we are meant to sort.
	if (exists $in{'sb'}) {	# Sort hits on $in{'sb'} field.
		my ($sort_order, $sort_func);
		$in{'so'} ? ($sort_order = $in{'so'}) : ($sort_order = "ascend");
		$sort_func = "$db_sort{$db_cols[$in{'sb'}]}_$sort_order";		
		
		foreach $hit (sort $sort_func (keys %sortby)) {
			$first = ($hit * $#db_cols) + $hit; $last = ($hit * $#db_cols) + $#db_cols + $hit;			
			push (@sortedhits, @hits[$first .. $last]);
		}
		@hits = @sortedhits;
	}	

# If we have to many hits, let's build the next toolbar, and return only the hits we want.
	if ($numhits > $maxhits) {	
# Remove the nh= from the query string.		
		$next_url = $ENV{'QUERY_STRING'};
		$next_url =~ s/\&nh=\d+//;
		$next_hit = $nh + 1; $prev_hit = $nh - 1;

# Build the next hits toolbar. It seems really complicated as we have to do
# some number crunching to keep track of where we are on the toolbar, and so
# that the toolbar stays centred.		

# First, set how many pages we have on the left and the right.
		$left  = $nh; $right = int($numhits/$maxhits) - $nh;		
# Then work out what page number we can go above and below.		
		($left > 7)  ? ($lower = $left - 7) : ($lower = 1);
		($right > 7) ? ($upper = $nh + 7)   : ($upper = int($numhits/$maxhits) + 1);
# Finally, adjust those page numbers if we are near an endpoint.		
		(7 - $nh >= 0) and ($upper = $upper + (8 - $nh));
		($nh > ($numhits/$maxhits - 7)) and ($lower = $lower - ($nh - int($numhits/$maxhits - 7) - 1));
		$db_next_hits = "";

# Then let's go through the pages and build the HTML.		
		($nh > 1) and ($db_next_hits .= qq~<a href="$db_script_url?$next_url&nh=$prev_hit">[<<]</a> ~);
		for ($i = 1; $i <= int($numhits/$maxhits) + 1; $i++) {
			if ($i < $lower) { $db_next_hits .= " ... "; $i = ($lower-1); next; }			
			if ($i > $upper) { $db_next_hits .= " ... "; last; }
			($i == $nh) ?
				($db_next_hits .= qq~$i ~) :
				($db_next_hits .= qq~<a href="$db_script_url?$next_url&nh=$i">$i</a> ~);
			if (($i * $maxhits) >= $numhits) { last; }  # Special case if we hit exact.
		}
		$db_next_hits .= qq~<a href="$db_script_url?$next_url&nh=$next_hit">[>>]</a> ~ unless ($nh == $i);
		
# Slice the @hits to only return the ones we want, only have to do this if the results are sorted.
		if (exists $in{'sb'}) {			
			$first = ($maxhits * ($nh - 1)) * ($#db_cols+1);
			$last  =  $first + (($#db_cols+1) * $maxhits) - 1;		
			$last = $#hits if ($last > $#hits);
			@hits = @hits[$first .. $last];
		}
	}
	
# Bold the results 
	if ($db_bold and $in{'view_records'}) {
		for $i (0 .. (($#hits+1) / ($#db_cols+1)) - 1) {
			$offset = $i * ($#db_cols+1);
			foreach $field (@search_fields) {				
				$hits[$field + $offset] =~ s,(<[^>]+>)|($regexp_bold[$field]),defined($1) ? $1 : "<B>$2</B>",ge;
			}
		}
	}
	return ("ok", @hits);
}

sub admin_display {
# --------------------------------------------------------
# Let's an admin add/update/remove users from the authorization file.
#	
    my ($message, @lines, $line);
	
# Do we have anything to do?
    CASE: {
# If we've been passed in new_username, then we are adding a new user. Do
# some basic error checking and then add him into the password file.
        $in{'new_username'} and do {
            unless ((length($in{'new_username'}) >= 3) and (length($in{'new_username'}) <= 12) and ($in{'new_username'} =~ /^[a-zA-Z0-9]+$/)) {
                $message = "Illegal bruger-id: $in{'new_username'}. Må kun indeholde bogstaver (a-z) og tal og være mellem 3 og 12 tegn.";
                last CASE;
            }
            unless ((length($in{'password'}) >= 3) and (length($in{'password'}) <= 12)) {
                $message = "Illegal adgangskode: '$in{'password'}'. Skal være mellem 3 og 12 tegn.";
                last CASE;
            }
#### Following four lines added for secure_password_lookup mod
            unless ($in{'email'} =~ /.+\@.+\..+/) {
                $message = "Illegal email-adresse: '$in{'password'}'.";
                last CASE;
            }
 
            open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne password fil. Årsag: $!\n");
                @passwds = <PASSWD>;
            close PASSWD;

            foreach $pass (@passwds) {	# Go through each pass and see if we match..
                next if ($pass =~ /^$/);	# Skip blank lines.
                next if ($pass =~ /^#/);	# Skip Comment lines.
                chomp ($pass);

#### Following line change for secure_password_lookup mod
                ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass);

                if ($in{'new_username'} eq $userid) {
                    $message = "Bruger-id findes allerede. Prøv en anden.";
                    last CASE;
                }

#### Following four lines added for secure_password_lookup mod
                if ($in{'email'} eq $email) {
                    $message .= "email address already exists.";
                    last CASE;
                }

            }

            open (PASS, ">>$auth_pw_file") or &cgierr ("unable to open: $auth_pw_file.\nReason: $!");
                if ($db_use_flock) {
                    flock(PASS, 2)  or &cgierr("unable to get exclusive lock on $auth_pw_file.\nReason: $!");
                }
                my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/');
                my $salt = join '', @salt_chars[rand 64, rand 64];
                my $encrypted = crypt($in{'password'}, $salt);	
                
#### Following line changed for secure_password_lookup mod
                print PASS "$in{'new_username'}:$encrypted:$in{'per_view'}:$in{'per_add'}:$in{'per_del'}:$in{'per_mod'}:$in{'per_admin'}:$in{'email'}\n";

            close PASS;
            $message = "Bruger: $in{'new_username'} oprettet.";
            last CASE;
        };
# If we've been passed in delete, then we are removing a user. Check
# to make sure a user was selected then try and remove him.
        $in{'delete'} and do {
            unless ($in{'username'}) {
                $message = "Ingen bruger valgt til sletning.";
                last CASE;
            }
            open (PASS, "<$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
                if ($db_use_flock) { flock(PASS, 1)	}				
                @lines = <PASS>;
            close PASS;
				
            open (PASS, ">$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
                if ($db_use_flock) {
                    flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
                }
                my $found = 0;
                foreach $line (@lines) {
                    ($line =~ /^$in{'username'}:/) ?
                        ($found = 1) :
                        print PASS $line;
                }
            close PASS;
            $found ?
                ($message = "Bruger: $in{'username'} slattet.") :
                ($message = "Kan ikke finde bruger-id: $in{'username'} i password-filen.");
            last CASE;
        };
# If we have a username, and the admin didn't press inquire, then
# we are updating a user. 
        ($in{'username'} && !$in{'inquire'}) and do {
            open (PASS, "<$auth_pw_file") or &cgierr ("kan ikke åbnr: $auth_pw_file.\nÅrsag: $!");
                if ($db_use_flock) { flock(PASS, 1); }				
                @lines = <PASS>;
            close PASS;

            foreach $pass (@passwds) {	# Go through each pass and see if we match..
                next if ($pass =~ /^$/);	# Skip blank lines.
                next if ($pass =~ /^#/);	# Skip Comment lines.
                chomp ($pass);

#### Following line changed for secure_password_lookup mod
                ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass);

#### Following four lines added for secure_password_lookup mod
                if (($in{'email'} eq $email) && ($in{'username'} ne $userid)) {
                    $message .= "email-adresse er allerede benyttet.";
                    last CASE;
                }
            }

#### Following three lines added for secure_password_lookup mod
#### !!!!!!! IMPORTANT !!!!!!!
#### If you are not using the new subroutine -- sub change_email_in_records
#### delete the following three lines
            if ($auth_email_field >= 0) {
                &change_email_in_records($in{'username'});
            }

            open (PASS, ">$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
                if ($db_use_flock) {
                    flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
                }
                my $found = 0;
                foreach $line (@lines) {
                    if ($line =~ /^$in{'username'}:/) {
                        my $password = (split (/:/, $line))[1];
                        unless ($password eq $in{'password'}) {
                            my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/');
                            my $salt = join '', @salt_chars[rand 64, rand 64];
                            $password = crypt($in{'password'}, $salt);
                        }

#### Following line changed for secure_password_lookup mod
                        print PASS "$in{'username'}:$password:$in{'per_view'}:$in{'per_add'}:$in{'per_del'}:$in{'per_mod'}:$in{'per_admin'}:$in{'email'}\n";

                        $found = 1;
                    }
                    else {
                        print PASS $line;
                    }
                }
            close PASS;
            $in{'inquire'} = $in{'username'};
            $found ?
                ($message = "Bruger: $in{'username'} opdateret.") :
                ($message = "Kan ikke finde bruger: '$in{'username'}' i password-filen.");
            last CASE;
        };
    };

# Now let's load the list of users.	
    open (PASS, "<$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
        if ($db_use_flock) { flock(PASS, 1); }	
        @lines = <PASS>;
    close PASS;

# If we are inquiring, let's look for the specified user.
    my (@data, $user_list, $perm, $password, $email);
    $user_list = qq~<select name="username"><option> </option>~;
    LINE: foreach $line (@lines) {
        $line =~ /^#/    and next LINE;
        $line =~ /^\s*$/ and next LINE;
        chomp $line;
        @data = split (/:/, $line);
        push (@users,$data[0]);
        if ($in{'inquire'} and ($in{'username'} eq $data[0])) {
            $password = $data[1];

#### Following line added for secure_password_lookup mod
            $email = $data[7];

            $perm = qq|
            View <input type=checkbox name="per_view" value="1"   |; ($data[2] and $perm .= "CHECKED"); $perm .= qq|> 
            Add <input type=checkbox name="per_add" value="1"     |; ($data[3] and $perm .= "CHECKED"); $perm .= qq|> 
            Delete <input type=checkbox name="per_del" value="1"  |; ($data[4] and $perm .= "CHECKED"); $perm .= qq|> 
            Modify <input type=checkbox name="per_mod" value="1"  |; ($data[5] and $perm .= "CHECKED"); $perm .= qq|>
            Admin <input type=checkbox name="per_admin" value="1" |; ($data[6] and $perm .= "CHECKED"); $perm .= qq|>|;
        }
    }
    foreach $user (sort @users) {
        if ($in{'inquire'} and ($in{'username'} eq $user)) {
            $user_list .= qq~<option value="$user" SELECTED>$user</option>\n~;
        }
        else {
            $user_list .= qq~<option value="$user">$user</option>\n~; 
        }
    }
    $user_list .= "</select>";

# Build the permissions list if we haven't inquired in someone.
    if (!$perm) {
        $perm = qq|
        View <input type=checkbox name="per_view" value="1"   |; ($auth_default_perm[0] and $perm .= "CHECKED"); $perm .= qq|> 
        Add <input type=checkbox name="per_add" value="1"     |; ($auth_default_perm[1] and $perm .= "CHECKED"); $perm .= qq|> 
        Delete <input type=checkbox name="per_del" value="1"  |; ($auth_default_perm[2] and $perm .= "CHECKED"); $perm .= qq|> 
        Modify <input type=checkbox name="per_mod" value="1"  |; ($auth_default_perm[3] and $perm .= "CHECKED"); $perm .= qq|>
        Admin <input type=checkbox name="per_admin" value="1" |; ($auth_default_perm[4] and $perm .= "CHECKED"); $perm .= qq|>|;
    }	
    
#### Following line changed for secure_password_lookup mod
    &html_admin_display ($message, $user_list, $password, $perm, $email);
}

sub signup {
# --------------------------------------------------------
# Allows a user to sign up without admin approval. Must have $auth_signup = 1
# set. The user gets @default_permissions.
#
    my ($message,$userid, $pw, $view, $add, $del, $mod, $admin, $email, $password);

# Check to make sure userid is ok, pw ok, and userid is unique.	
    unless ((length($in{'userid'}) >= 3) and (length($in{'userid'}) <= 12) and ($in{'userid'} =~ /^[a-zA-Z0-9]+$/)) {
        $message = "Illegal bruger-id: $in{'userid'}. Må kun indeholde bogstaver (a-z) og tal og være mellem 3 og 12 tegn.";
    }

#### Following lines deleted for secure_password_lookup mod
#	unless ((length($in{'pw'}) >= 3) and (length($in{'pw'}) <= 12)) {
#		$message = "illegal adgangskode: '$in{'pw'}'. Skal være mellem 3 og 12 tegn.";
#	}


#### Following three lines added for secure_password_lookup mod
    unless ($in{'email'} =~ /.+\@.+\..+/) {
        $message = "Forkert opbygget email-addresse: '$in{'email'}'.";
    }

#### Following lines deleted for secure_password_lookup mod
#	open (PASS, "<$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
#	if ($db_use_flock) { flock(PASS, 1); }
#	while (<PASS>) {
#		/^\Q$in{'userid'}\E:/ and ($message = "userid already exists. Please try another.");
#	}
#	close PASS;	

#### Following fifteen lines added for secure_password_lookup mod
    open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne password fil. Årsag: $!\n");
        @passwds = <PASSWD>;
    close PASSWD;
    foreach $pass (@passwds) {	# Go through each pass and see if we match..
        next if ($pass =~ /^$/);	# Skip blank lines.
        next if ($pass =~ /^#/);	# Skip Comment lines.
        chomp ($pass);
        ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass);
        if ($in{'userid'} eq $userid) {
            $message = "bruger-id er allerede optaget. Prøv en anden.<BR>";
        }
        if (lc($in{'email'}) eq lc($email)) {
            $message .= "email-adresse er allerede benyttet.";
        }
    }


	if ($message) {
		&html_signup_form ($message);
		return;
	}

#### Following line added for secure_password_lookup mod
    $in{'pw'} = &generate_password;
    $in{'email'} = lc($in{'email'});
# Add the userid into the file with signup permissions.	
	open (PASS, ">>$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
	if ($db_use_flock) {
		flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
	}
	srand( time() ^ ($$ + ($$ << 15)) );	# Seed Random Number
    my @salt_chars  = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/');
	my $salt        = join '', @salt_chars[rand 64, rand 64];
	my $encrypted   = crypt($in{'pw'}, $salt);			
	my $permissions = join (":", @auth_signup_permissions);
	
#### Following line changed for secure_password_lookup mod
    print PASS "$in{'userid'}:$encrypted:$permissions:$in{'email'}\n";

    close PASS;

#### Following fifteen lines added for secure_password_lookup mod
    open (MAIL, "$mailprog") || &cgierr("Kan ikke starte mail-program");
        print MAIL "To: $in{'email'}\n";
        print MAIL "From: $admin_email\n";
        print MAIL "Subject: $html_title Konto oprettet\n\n";
        print MAIL "-" x 75 . "\n\n";
        print MAIL "Din konto i $html_title er blevet oprettet.\n\n";
        print MAIL "Din $html_title bruger-id er: $in{'userid'}\n";
        print MAIL "Din $html_title adgangskode er: $in{'pw'}\n\n";
        print MAIL "Gem denne email til senere brug.\n\n";
        print MAIL "For at logge ind, skal du gå til\n\n";
        print MAIL "$db_script_url?db=$db_setup\n";
        print MAIL "og skrive bruger-id og adgangskode.\n";
        print MAIL "Hvis du har lyst, kan du der rette din adgangskode.\n\n";
        print MAIL "Du kan kontakte $html_title på: $admin_email\n";
        print MAIL "hvis du har spørgsmål.\n\n";
    close (MAIL);

	&html_signup_success;
}

###############################################################################
#                               script:  db.cgi                               #
#                               sub get_email                                 #
#                                                                             #
#                               new subroutine                                #
#                                                                             #
# What it does--                                                              #
#   Pulls the email address from the password file when a user adds a new     #
#   record.                                                                   #
# How to use it--                                                             #
#   In sub html_record_form, after                                            #
#              my (%rec) = @_;                                                #
#   add                                                                       #
#              if ($in{'add_form'}) {                                         #
#                $rec{'Email'} = &get_email;                                  #
#              }                                                              #
#                                                                             #
#  Be sure to change the word 'Email' above to match exactly the name of the  #
#  field that holds the user's email address.                                 #
#                                                                             #
# Important!!                                                                 #
#   This only gets the email address for the currently logged in user.        #
###############################################################################

sub get_email {
# --------------------------------------------------------
# Pulls the email address from the password file.
#### Following subroutine added for secure_password_lookup mod

    my ($userid, $pw, $view, $add, $del, $mod, $admin, $email);

    open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne password fil. Årsag: $!\n");
        @passwds = <PASSWD>;
    close PASSWD;

    foreach $pass (@passwds) {	# Go through each pass and see if we match..
        next if ($pass =~ /^$/);	# Skip blank lines.
        next if ($pass =~ /^#/);	# Skip Comment lines.
        chomp ($pass);
        ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass);
        if ($db_userid eq $userid) {
            return $email;
        }
    }
    return "$db_userid ikke fundet i password fil";
}

###############################################################################
#                               script:  db.cgi                               #
#                            sub change_password                              #
#                                                                             #
#                               new subroutine                                #
#                                                                             #
###############################################################################

sub change_password {
# --------------------------------------------------------
#### Following subroutine added for secure_password_lookup mod

    my ($message, $userid, $pw, $view, $add, $del, $mod, $admin, $email, $password, $found, $output);

# Check to make sure password is ok	
    unless ($in{'pw1'} eq $in{'pw2'}) {
        $message = "Skriv den samme adgangskode to gange.";
    }
    unless ((length($in{'pw1'}) >= 3) and (length($in{'pw1'}) <= 12)) {
		$message = "Illegal adgangskode: '$in{'pw'}'. Skal være mellem 3 og 12 tegn.";
	}
    if ($message) {
        &html_change_password_form($message);
        return;
    }
    open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne passwor file. Årsag: $!\n");
        @passwds = <PASSWD>;
    close PASSWD;
    $found = '';
    PASS: foreach $pass (@passwds) {	# Go through each pass and see if we match..
        next PASS if ($pass =~ /^$/);	# Skip blank lines.
        chomp ($pass);
        ($userid, $pw, @rest) = split (/:/, $pass);
        if ($userid eq $db_userid) {
          $found = $pass;
            unless (crypt($in{'old'}, $pw) eq $pw) {
              $message = "gammel adgangskode er ikke korrekt";
            }
        }
        else {
          $output .= $pass . "\n";
        }
    }
	if (!$found) {
		&html_change_password_form ("Din bruger-id fandtes ikke i password filen.");
		return;
	}
    elsif ($message) {
      &html_change_password_form($message);
      return;
    }
    else {
        open (PASS, ">$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
            if ($db_use_flock) {
                flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
            }
            print PASS $output;
        close PASS;

        ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $found);
	    srand( time() ^ ($$ + ($$ << 15)) );	# Seed Random Number
        my @salt_chars  = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/');
	    my $salt        = join '', @salt_chars[rand 64, rand 64];
	    my $encrypted   = crypt($in{'pw1'}, $salt);			
	
	    open (PASS, ">>$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
	        if ($db_use_flock) {
		        flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
	        }
	        print PASS "$userid:$encrypted:$view:$add:$del:$mod:$admin:$email\n";
	    close PASS;
	
	    &html_change_password_success;
    }
}

###############################################################################
#                               script:  db.cgi                               #
#                                 sub lookup                                  #
#                                                                             #
#                               new subroutine                                #
#                                                                             #
###############################################################################

sub lookup {
# --------------------------------------------------------
# This subroutine added for the secure_password_lookup mod

    my ($message, $userid, $pw, $view, $add, $del, $mod, $admin, $email, $password, $found, $output);

# Check to make sure email is ok	
    unless ($in{'email'} =~ /.+\@.+\..+/) {
        &html_lookup_form ("Illegalt format for email-adresse: '$in{'email'}'.");
        return;
    }
    open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne password fil. Årsag: $!\n");
        @passwds = <PASSWD>;
    close PASSWD;
    $found = '';
    foreach $pass (@passwds) {	# Go through each pass and see if we match.
        chomp ($pass);
        if ($pass =~ /$in{'email'}/) {
            $found = $pass;
        }
        else {
          $output .= $pass . "\n";
        }
    }
	if (!$found) {
		&html_lookup_form ("Email-adressen du skrev kan ikke findes.");
		return;
	}
    else {
        open (PASS, ">$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
            if ($db_use_flock) {
                flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
            }
            print PASS $output;
        close PASS;

        ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $found);
        $password = &generate_password;
	    srand( time() ^ ($$ + ($$ << 15)) );	# Seed Random Number
        my @salt_chars  = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/');
	    my $salt        = join '', @salt_chars[rand 64, rand 64];
	    my $encrypted   = crypt($password, $salt);			

# Add the userid into the file with default permissions.	
	    open (PASSWD, ">>$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
	        if ($db_use_flock) {
		        flock(PASSWD, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
	        }
	        print PASSWD "$userid:$encrypted:$view:$add:$del:$mod:$admin:$email\n";
	    close PASSWD;
	
        open (MAIL, "$mailprog") || &cgierr("Kan ikke starte mail program");
    
            print MAIL "To: $in{'email'}\n";
            print MAIL "From: $admin_email\n";
            print MAIL "Subject: $html_title Ny adgangskode\n\n";
    
            print MAIL "-" x 75 . "\n\n";

            print MAIL "Her er din nye adgangskode til $html_title.\n\n";

            print MAIL "Din $html_title bruger-id er: $userid\n";
            print MAIL "Din $html_title adgangskode er: $password\n\n";

            print MAIL "Gem denne email til senere brug.\n\n";

            print MAIL "For at logge ind, skal du gå til\n\n";
            print MAIL "$db_script_url?db=$db_setup\n";
            print MAIL "og skrive bruger-id og adgangskode.\n";
            print MAIL "Hvis du har lyst, kan du der rette din adgangskode.\n\n";

            print MAIL "Kontakt $html_title på: $admin_email\n";
            print MAIL "hvis du har spørgsmål.\n\n";

        close (MAIL);

	    &html_lookup_success;
    }
}

###############################################################################
#                               script:  db.cgi                               #
#                              sub change_email                               #
#                                                                             #
#                               new subroutine                                #
#                                                                             #
###############################################################################

sub change_email {
# --------------------------------------------------------
#### Following subroutine added for secure_password_lookup mod

    my ($message, $userid, $pw, $view, $add, $del, $mod, $admin, $email, $password, $found, $output, $pass);

# Check to make sure email is ok	
    unless ($in{'email'} =~ /.+\@.+\..+/) {
        $message = "Illegalt format for email-adresse.";
    }

    open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne password fil. Årsag: $!\n");
        @passwds = <PASSWD>;
    close PASSWD;

    foreach $pass (@passwds) {	# Go through each pass and see if we match..
        chomp ($pass);
        ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $pass);
        if (($in{'email'} eq $email) && ($db_userid ne $userid)) {
            $message = "Email-adresse er allerede benyttet.";
        }
        elsif (($in{'email'} eq $email) && ($db_userid eq $userid)) {
            $message = "Du skrev den samme email-adresse igen";
        }
    }
    if ($message) {
        &html_change_email_form($message);
        return;
    }

    open (PASSWD, "<$auth_pw_file") || &cgierr("kan ikke åbne password fil. Årsag: $!\n");
        @passwds = <PASSWD>;
    close PASSWD;
    $found = '';
    PASS: foreach $pass (@passwds) {	# Go through each pass and see if we match..
        next PASS if ($pass =~ /^$/);	# Skip blank lines.
        chomp ($pass);
        if ($pass =~ /^$db_userid:/) {
            $found = $pass;
        }
        else {
          $output .= $pass . "\n";
        }
    }
	if (!$found) {
		&html_change_email_form ("Din bruger-id fandtes ikke i password filen.");
		return;
	}
    else {
        open (PASS, ">$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
            if ($db_use_flock) {
                flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
            }
            print PASS $output;
        close PASS;

        ($userid, $pw, $view, $add, $del, $mod, $admin, $email) = split (/:/, $found);

        $password = &generate_password;
	    srand( time() ^ ($$ + ($$ << 15)) );	# Seed Random Number
        my @salt_chars  = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/');
	    my $salt        = join '', @salt_chars[rand 64, rand 64];
	    my $encrypted   = crypt($password, $salt);			
	
	    open (PASS, ">>$auth_pw_file") or &cgierr ("kan ikke åbne: $auth_pw_file.\nÅrsag: $!");
	        if ($db_use_flock) {
		        flock(PASS, 2)  or &cgierr("kan ikke få eksklusiv adgang til $auth_pw_file.\nÅrsag: $!");
	        }
	        print PASS "$userid:$encrypted:$view:$add:$del:$mod:$admin:$in{'email'}\n";
	    close PASS;

        open (MAIL, "$mailprog") || &cgierr("Kan ikke starte mail program");
    
            print MAIL "To: $in{'email'}\n";
            print MAIL "From: $admin_email\n";
            print MAIL "Subject: $html_title Ny adgangskode\n\n";
    
            print MAIL "-" x 75 . "\n\n";

            print MAIL "-" x 75 . "\n\n";

            print MAIL "Her er din nye adgangskode til $html_title.\n\n";

            print MAIL "Din $html_title bruger-id er: $userid\n";
            print MAIL "Din $html_title adgangskode er: $password\n\n";

            print MAIL "Gem denne email til senere brug.\n\n";

            print MAIL "For at logge ind, skal du gå til\n\n";
            print MAIL "$db_script_url?db=$db_setup\n";
            print MAIL "og skrive bruger-id og adgangskode.\n";
            print MAIL "Hvis du har lyst, kan du der rette din adgangskode.\n\n";

            print MAIL "Kontakt $html_title på: $admin_email\n";
            print MAIL "hvis du har spørgsmål.\n\n";

        close (MAIL);
#### !!!!!!! IMPORTANT !!!!!!!
#### If you are not using the new subroutine -- sub change_email_in_records
#### delete the following three lines
        if ($auth_email_field >= 0) {
            &change_email_in_records($db_userid);
        }

	    &html_change_email_success;
    }
}

###############################################################################
#                               script:  db.cgi                               #
#                        sub change_email_in_records                          #
#                                                                             #
#                               new subroutine                                #
#                                                                             #
###############################################################################

sub change_email_in_records {
# --------------------------------------------------------
# The code below will change the email address in record(s) belonging to the user. In order to use this,
# you must have an "auth_user_field" set in your .cfg file. It is best if you use the &get_email modification
# above as well. 
#### Following subroutine added for secure_password_lookup mod

    my ($userid) = $_[0];
    my (@lines, $output, $line, @data);

    open (DB, "<$db_file_name") or &cgierr("error in change_email. unable to open db file: $db_file_name.\nReason: $!");
        if ($db_use_flock) { flock(DB, 1); }
        @lines = <DB>;	# Slurp the database into @lines..
    close DB;

    LINE: foreach $line (@lines) {
        if ($line =~ /^$/) { next LINE; }					# Skip and Remove blank lines
        chomp ($line);			
        @data     = &split_decode($line);
        if ($userid eq $data[$auth_user_field]) {
            $data[$auth_email_field] = $in{'email'};
            %rec = &array_to_hash(0,@data);
            $line = &join_encode(%rec);
        }
        $output .= $line . "\n";
    }
    open (DB, ">$db_file_name") or &cgierr("error in change_email. unable to open db file: $db_file_name.\nReason: $!");
	    if ($db_use_flock) {
            flock(DB, 2)  or &cgierr("unable to get exclusive lock on $db_file_name.\nReason: $!");
        }
        print DB $output;				
    close DB;    # automatically removes file lock
}

###############################################################################
#                               script:  db.cgi                               #
#                            sub generate_password                            #
#                                                                             #
#                               new subroutine                                #
#                                                                             #
# What it does --                                                             #
# Generates an 8-character password, consisting of 4 two-letter "syllables"   #
###############################################################################

sub generate_password {
# --------------------------------------------------------
#### Following subroutine added for secure_password_lookup mod

    my (@c, @v, $password);

    srand( time() ^ ($$ + ($$ << 15)) );    # Seed Random Number

    @c = split(/ */, "bcdfghjklmnprstvwxyz");
    @v = split(/ */, "aeiou");

    for ($i=1; $i<=4; ++$i) {
        $password .= $c[int(rand(20))] . $v[int(rand(5))];
    }

    return $password;
}

sub get_record {
# --------------------------------------------------------
# Given an ID as input, get_record returns a hash of the 
# requested record or undefined if not found.

	my ($key, $found, $line, @data, $field, $restricted);
	$key = $_[0];	
	$found = 0;
	($restricted = 1) if ($auth_modify_own and !$per_admin);

	open (DB, "<$db_file_name") or &cgierr("error in get_records. unable to open db file: $db_file_name.\nReason: $!");
	if ($db_use_flock) { flock(DB, 1); }	
	LINE: while (<DB>) {
		(/^#/)      and next LINE;
		(/^\s*$/)   and next LINE;
		$line = $_;	chomp ($line);		
		@data = &split_decode($line);
		next LINE if ($restricted and ($db_userid ne $data[$auth_user_field]));
		if ($data[$db_key_pos] eq $key) {
			$found = 1;
			for ($i = 0; $i <= $#db_cols; $i++) {  # Map the array columns to a hash.
				$rec{$db_cols[$i]} = $data[$i];
			}
			last LINE;
		}
	}
	close DB;	
	$found ?
		(return %rec) :
		(return undef);
}

sub get_defaults {
# --------------------------------------------------------
# Returns a hash of the defaults used for a new record.

	my (%default);

	foreach $field (keys %db_defaults) {
		$default{$field} =  $db_defaults{$field};
	}
	
	if ($db_key_track) {
		open (ID, "<$db_id_file_name") or &cgierr("error in get_defaults. unable to open id file: $db_id_file_name.\nReason: $!");
		if ($db_use_flock) { flock(ID, 1);	}	
		$default{$db_key} = <ID> + 1;	# Get next ID number
		close ID;
	}
	return %default;
}


sub validate_record {
# --------------------------------------------------------
# Verifies that the information passed through the form and stored
# in %in matches a valid record. It checks first to see that if 
# we are adding, that a duplicate ID key does not exist. It then
# checks to see that fields specified as not null are indeed not null,
# finally it checks against the reg expression given in the database
# definition.

	my ($col, @input_err, $errstr, $err, $line, @lines, @data);	

	if ($in{'add_record'}) 	{		# don't need to worry about duplicate key if modifying	
		open (DB, "<$db_file_name") or &cgierr("error in validate_records. unable to open db file: $db_file_name.\nReason: $!");		
		if ($db_use_flock) { flock(DB, 1); }		
		LINE: while (<DB>) {
			(/^#/)      and next LINE;
			(/^\s*$/)   and next LINE;
			$line = $_;	chomp ($line);
			@data = &split_decode($line);	
			if ($data[$db_key_pos] eq $in{$db_key}) {
				return "kun een række må oprettes med denne nøgle";
			}
		}
		close DB;
	}	
	foreach $col (@db_cols) {
		if ($in{$col} =~ /^\s*$/) {				# entry is null or only whitespace
			($db_not_null{$col}) and			# entry is not allowed to be null.
				push(@input_err, "$col (Må ikke være blank)");  # so let's add it as an error
		}
		else {									# else entry is not null.
			($db_valid_types{$col} && !($in{$col} =~ /$db_valid_types{$col}/)) and
				push(@input_err, "$col (Ulovligt format)");	# but has failed validation.
		}
		(length($in{$col}) > $db_lengths{$col}) and
			push (@input_err, "$col (For lang. Max længde: $db_lengths{$col})");
		if ($db_sort{$col} eq "date") {		
			push (@input_err, "$col (Ulovligt datoformat)") unless &date_to_unix($in{$col});
		}
	}
	
	if ($#input_err+1 > 0) {					# since there are errors, let's build
		foreach $err (@input_err) {				# a string listing the errors
			$errstr .= "<li>$err";				# and return it.
		}
		return "<ul>$errstr</ul>";
	}
	else {
		return "ok";							# no errors, return ok.
	}
}

sub join_encode {
# --------------------------------------------------------
# Takes a hash (ususally from the form input) and builds one 
# line to output into the database. It changes all occurrences
# of the database delimeter to '~~' and all newline chars to '``'.

	my (%hash) = @_;
	my ($tmp, $col, $output);	

	foreach $col (@db_cols) {				
		$tmp = $hash{$col};
		$tmp =~ s/^\s+//g;				# Trim leading blanks...
		$tmp =~ s/\s+$//g;				# Trim trailing blanks...
		$tmp =~ s/\Q$db_delim\E/~~/og;	# Change delimeter to ~~ symbol.
		$tmp =~ s/\n/``/g;				# Change newline to `` symbol.
		$tmp =~ s/\r//g;				# Remove Windows linefeed character.
		$output .= $tmp . $db_delim;	# Build Output.
	}
	chop $output;		# remove extra delimeter.
	$output .= "\n";	# add linefeed char.
	return $output;
}

sub split_decode {
# --------------------------------------------------------
# Takes one line of the database as input and returns an
# array of all the values. It replaces special mark up that 
# join_encode makes such as replacing the '``' symbol with a 
# newline and the '~~' symbol with a database delimeter.

	my ($input) = shift;
	$input =~ s/\Q$db_delim\E$/$db_delim /o; # Add a space if we have delimiter new line.
	my (@array) = split (/\Q$db_delim\E/o, $input);
	for ($i = 0; $i <= $#array; $i++) {
		$array[$i] =~ s/~~/$db_delim/og;	# Retrieve Delimiter..
		$array[$i] =~ s/``/\n/g;			# Change '' back to newlines..
	}	
	return @array;
}

sub build_select_field {
# --------------------------------------------------------
# Builds a SELECT field based on information found
# in the database definition. Parameters are the column to build
# and a default value (optional).

	my ($column, $value) = @_;	
	my (@fields, $ouptut);

	@fields = split (/\,/, $db_select_fields{$column});
	if ($#fields == -1) {
		$output = "error building select field: no select fields specified in config for field '$column'!";
	}
	else {
		$output = qq|<SELECT NAME="$column"><OPTION>---|;
		foreach $field (@fields) {
			$field eq $value ?
				($output .= "<OPTION SELECTED>$field\n") :
				($output .= "<OPTION>$field");
		}
		$output .= "</SELECT>";
	}
	return $output;
}

sub build_select_field_from_db {
# --------------------------------------------------------
# Builds a SELECT field from the database. 

	my ($column, $value, $name) = @_;
	my (@fields, $field, @selectfields, @lines, $line, $ouptut);
	my ($fieldnum, $found, $i) = 0;
	
	$name || ($name = $column);
	
	for ($i = 0; $i <= $#db_cols; $i++) {
		if ($column eq $db_cols[$i]) {
			$fieldnum = $i; $found = 1;
			last;
		}
	}
	if (!$found) {
		return "error building select field: no fields specified!";
	}

	open (DB, "<$db_file_name") or &cgierr("unable to open $db_file_name. Reason: $!");
	if ($db_use_flock) { flock(DB, 1); }
	LINE: while (<DB>) {		
		next if /^#/;
		next if /^\s*$/;
		$line = $_;
		chomp ($line);		
		@fields = &split_decode ($line);
		if (!(grep $_ eq $fields[$fieldnum], @selectfields)) {
			push (@selectfields, $fields[$fieldnum]);
		}
	}
	close DB;
	
	$output = qq|<SELECT NAME="$name"><OPTION>---|;
	foreach $field (sort @selectfields) {
		($field eq $value) ?
			($output .= "<OPTION SELECTED>$field") :
			($output .= "<OPTION>$field");
	}
	$output .= "</SELECT>";

	return $output;
}

sub build_checkbox_field {
# --------------------------------------------------------
# Builds a CHECKBOX field based on information found
# in the database definition. Parameters are the column to build
# whether it should be checked or not and a default value (optional).

	my ($column, $values) = @_;

	if (!$db_checkbox_fields{$column}) {
		return "error building checkboxes: no checkboxes specified in config for field '$column'";
	}	
	
	my @names  = split (/,/, $db_checkbox_fields{$column});
	my @values = split (/\Q$db_delim\E/, $values);
	my ($name, $output);	

	foreach $name (@names) {
		(grep $_ eq $name, @values) ?
			($output .= qq!<INPUT TYPE="CHECKBOX" NAME="$column" VALUE="$name" CHECKED> $name\n!) :
			($output .= qq!<INPUT TYPE="CHECKBOX" NAME="$column" VALUE="$name"> $name\n!);
	}
	return $output;
}

sub build_radio_field {
# --------------------------------------------------------
# Builds a RADIO Button field based on information found
# in the database definition. Parameters are the column to build
# and a default value (optional).

	my ($column, $value) = @_;
	my (@buttons, $button, $output);

	@buttons = split (/,/, $db_radio_fields{$column});
	
	if ($#buttons == -1) {
		$output = "error building radio buttons: no radio fields specified in config for field '$column'!";
	}
	else {
		foreach $button (@buttons) {
			$value =~ /^\Q$button\E$/ ?
				($output .= qq|<INPUT TYPE="RADIO" NAME="$column" VALUE="$button" CHECKED> $button \n|) :
				($output .= qq|<INPUT TYPE="RADIO" NAME="$column" VALUE="$button"> $button \n|);
		}
	}	
	return $output;
}

sub array_to_hash {
# --------------------------------------------------------
# Converts an array to a hash using db_cols as the field names.

	my($hit, @array) = @_;
	my(%hash);
	 
	for ($j = 0; $j <= $#db_cols; $j++) {
		$hash{$db_cols[$j]} = $array[$hit * ($#db_cols+1) + $j];
	}	
	return %hash;
}

sub build_html_record {
# --------------------------------------------------------
# Builds a record based on the config information.
#
	my (%rec) = @_;
	my ($output, $field);
	
	$output = "<p><table border=0 width=450>";
	foreach $field (@db_cols) {
		next if ($db_form_len{$field} == -1);
		$output .= qq~<tr><td align=right valign=top width=20%><$font>$field:</font></td>
		                <td width=80%><$font>$rec{$field}</font></td></tr>
					~;
	}
	$output .= "</table></p>\n";
	return $output;
}

sub build_html_record_form {
# --------------------------------------------------------
# Builds a record form based on the config information.
#
	my (%rec) = @_;
	my ($output, $field);
	
	$output = "<p><table border=0>";
	foreach $field (@db_cols) {
		if ($db_select_fields{$field}) {      $output .= "<tr><td align=right valign=top width=20%><$font>$field:</font></td><td width=80%>" . &build_select_field($field, $rec{$field}) . "</td></tr>"; }
		elsif ($db_radio_fields{$field}) {    $output .= "<tr><td align=right valign=top width=20%><$font>$field:</font></td><td width=80%>" . &build_radio_field($field, $rec{$field}) . "</td></tr>"; }
		elsif ($db_checkbox_fields{$field}) { $output .= "<tr><td align=right valign=top width=20%><$font>$field:</font></td><td width=80%>" . &build_checkbox_field ($field, $rec{$field}) . "</td></tr>"; }
		elsif ($db_form_len{$field} =~ /(\d+)x(\d+)/) { 
											  $output .= qq~<tr><td align=right valign=top width=20%><$font>$field:</font></td><td width=80%><textarea name="$field" cols="$1" rows="$2">$rec{$field}</textarea></td></tr>~; }
		elsif ($db_form_len{$field} == -1) {  $output  = qq~<input type=hidden name="$field" value="$rec{$field}">$output~; }
		elsif ($db_form_len{$field} == -2) {  $per_admin ? ($output .= qq~<tr><td align=right valign=top width=20%><$font>$field:</font></td><td width=80%><input type=text name="$field" value="$rec{$field}" maxlength="$db_lengths{$field}"></td></tr>~) :
														   ($output  = qq~<input type=hidden name="$field" value="$rec{$field}">$output~); }
		else  { 							  $output .= qq~<tr><td align=right valign=top width=20%><$font>$field:</font></td><td width=80%><input type=text name="$field" value="$rec{$field}" size="$db_form_len{$field}" maxlength="$db_lengths{$field}"></td></tr>~; }
	}
	$output .= "</table></p>\n";
	return $output;
}		
		
sub get_time {
# --------------------------------------------------------
# Returns the time in the format "hh-mm-ss".
#
	my ($sec, $min, $hour, $day, $mon, $year, $dweek, $dyear, $daylight) = localtime(time());
	($sec < 10)  and ($sec = "0$sec");
	($min < 10)  and ($min = "0$min");
	($hour < 10) and ($hour = "0$hour");
	
	return "$hour:$min:$sec";
}

sub get_date {
# --------------------------------------------------------
# Returns the date in the format "dd-mmm-yy".
# Warning: If you change the default format, you must also modify the &date_to_unix
# subroutine below which converts your date format into a unix time in seconds for sorting
# purposes.

    my ($sec, $min, $hour, $day, $mon, $year, $dweek, $dyear, $daylight) = localtime(time());
    my (@months) = qw!Jan Feb Mar Apr Maj Jun Jul Aug Sep Okt Nov Dec!;
	($day < 10) and ($day = "0$day");
	$year = $year + 1900;
    
    return "$day-$months[$mon]-$year";
}

sub date_to_unix {
# --------------------------------------------------------
# This routine must take your date format and return the time a la UNIX time().
# Some things to be careful about.. 
#     int your values just in case to remove spaces, etc.
#     catch the fatal error timelocal will generate if you have a bad date..
#     don't forget that the month is indexed from 0!
#
    my ($date)   = $_[0]; 
    my (%months) = ("Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "Maj" => 4, "Jun" => 5, 
                    "Jul" => 6, "Aug" => 7, "Sep" => 8, "Okt" => 9, "Nov" => 10,"Dec" => 11);
	my ($time);
    my ($day, $mon, $year) = split(/-/, $_[0]);
    unless ($day and $mon and $year)  { return undef; }
    unless (defined($months{$mon}))   { return undef; }    

	use Time::Local;
    eval {		
		$day = int($day); $year = int($year) - 1900; 
        $time = timelocal(0,0,0,$day, $months{$mon}, $year);
    };
    if ($@) { return undef; } # Could return 0 if you want.
    return ($time); 
}

# These are the sorting functions used in &query.
# --------------------------------------------------------
sub alpha_ascend  { lc($sortby{$a}) cmp lc ($sortby{$b}) }
sub alpha_descend { lc($sortby{$b}) cmp lc ($sortby{$a}) }
sub numer_ascend  { $sortby{$a} <=> $sortby{$b} }
sub numer_descend { $sortby{$b} <=> $sortby{$a} }
sub date_ascend   { &date_to_unix($sortby{$a}) <=> &date_to_unix($sortby{$b}) }
sub date_descend  { &date_to_unix($sortby{$b}) <=> &date_to_unix($sortby{$a}) }

sub parse_form {
# --------------------------------------------------------
# Parses the form input and returns a hash with all the name
# value pairs. Removes SSI and any field with "---" as a value 
# (as this denotes an empty SELECT field.

	my (@pairs, %in);
	my ($buffer, $pair, $name, $value);	

	if ($ENV{'REQUEST_METHOD'} eq 'GET') {
		@pairs = split(/&/, $ENV{'QUERY_STRING'});
	}
	elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
		read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
 		@pairs = split(/&/, $buffer);
	}
	else {
		&cgierr ("This script must be called from the Web\nusing either GET or POST requests\n\n");
	}
	PAIR: foreach $pair (@pairs) {
		($name, $value) = split(/=/, $pair);
		 
		$name =~ tr/+/ /;
		$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

		$value =~ tr/+/ /;
		$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

		$value =~ s/<!--(.|\n)*-->//g;			  # Remove SSI.
		if ($value eq "---") { next PAIR; }		  # This is used as a default choice for select lists and is ignored.
		(exists $in{$name}) ?
			($in{$name} .= "~~$value") :	      # If we have multiple select, then we tack on
			($in{$name}  = $value);				  # using the ~~ as a seperator.
	}
	return %in;
}

sub cgierr {
# --------------------------------------------------------
# Displays any errors and prints out FORM and ENVIRONMENT 
# information. Useful for debugging.

	if (!$html_headers_printed) {
		print "Content-type: text/html\n\n";
		$html_headers_printed = 1;
	}
	print "<PRE>\n\nCGI ERROR\n==========================================\n";
	$_[0]      and print "Error Message       : $_[0]\n";	
	$0         and print "Script Location     : $0\n";
	$]         and print "Perl Version        : $]\n";	
	$db_setup  and print "Setup File          : $db_setup.cfg\n";
	$db_userid and print "User ID             : $db_userid\n";
	$db_uid    and print "Session ID          : $db_uid\n";
	
	print "\nForm Variables\n-------------------------------------------\n";
	foreach $key (sort keys %in) {
		my $space = " " x (20 - length($key));
		print "$key$space: $in{$key}\n";
	}
	print "\nEnvironment Variables\n-------------------------------------------\n";
	foreach $env (sort keys %ENV) {
		my $space = " " x (20 - length($env));
		print "$env$space: $ENV{$env}\n";
	}
	print "\n</PRE>";
	exit -1;
}