#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
##!~_~perlpath~_~
#
# MiniVend version 4.0xxx
#
# $Id: minivend,v 1.4 2000/02/06 01:53:29 mike Exp mike $
#
# Copyright 1996-2000 by Michael J. Heins <mikeh@minivend.com>
#
# This program was originally based on Vend 0.2
# Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
#
# Portions from Vend 0.3
# Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
#
# See the file 'Changes' for information.
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version. 
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA  02111-1307  USA.

use lib '/usr/local/minivend/lib';
#use lib '~_~INSTALLPRIVLIB~_~';
use lib '/usr/local/minivend';
#use lib '~_~INSTALLARCHLIB~_~';

BEGIN {

	$Vend::Foreground = 1;
	
($Global::VendRoot = $ENV{MINIVEND_ROOT})
	if defined $ENV{MINIVEND_ROOT};
	
$Global::VendRoot = $Global::VendRoot || '/usr/local/minivend';
#$Global::VendRoot = $Global::VendRoot || '~_~INSTALLARCHLIB~_~';
$Global::ConfigFile = 'minivend.cfg';
$Global::InitialErrorFile = $Global::ErrorFile = "$Global::VendRoot/error.log";

if($^O =~ /win32/i) {
	$Global::Windows = 1;
}

# Uncomment next line if you want to guarantee use of DB_File
#$ENV{MINIVEND_DBFILE} = 1;

# Uncomment next line if you want to guarantee use of GDBM and not DB_File
#$ENV{MINIVEND_GDBM} = 1;

# Uncomment next line if you want to use no DBM, sessions
# stored in files and databases in memory (or SQL)
#$ENV{MINIVEND_NODBM} = 1;

# Uncomment next line if you DON'T want to use DBI, can
# save a bit on code size
#$ENV{MINIVEND_NO_DBI} = 1;

# Uncomment next line if you want to use the Storable
# module for storing session data. It improves session performance
# to a good degree. We will also do a bit of auto-detect below.
#$ENV{MINIVEND_STORABLE} = 1;

# Uncomment next line if you want to use the Storable
# module for storing database data. It improves GBDM/DB_File performance
# to a good degree. We will also do a bit of auto-detect below.
#$ENV{MINIVEND_STORABLE_DB} = 1;

# Uncomment AND SET next line to set PGP path to somewhere besides
# the MiniVend user path
#$ENV{PGPPATH} = '/usr/local/pgp';

# Use the Storable module for storing data in DBM files.
if (-f "$Global::VendRoot/_session_storable") {
	$ENV{MINIVEND_STORABLE} = 1;
}

if (-f "$Global::VendRoot/_db_storable") {
	$ENV{MINIVEND_STORABLE_DB} = 1;
}

# MiniVend can use syslog via the "logger" command
# This prevents parsing of the value, default is syslog off
$Global::SysLog		= '';

}


### END CONFIGURABLE VARIABLES

use vars qw($VERSION);
require Exporter;

BEGIN {
	$VERSION = '4.01';
}


use strict;
use Fcntl;

# BSD, among others, defines sendmail to be in /usr/sbin, and
# we want to make sure the program is there. Insert the location
# of you sendmail binary (the configure script should do this)
$Global::SendMailLocation = ($Global::Windows and $Global::SendMailLocation) ||
	(-x $Global::SendMailLocation and $Global::SendMailLocation) ||
	(-x '/usr/lib/sendmail' and '/usr/lib/sendmail') ||
	(-x '/usr/sbin/sendmail' and '/usr/sbin/sendmail') ||
	'sendmail';
#	'~_~sendmail~_~';

#select a DBM

BEGIN {
	$Global::GDBM = $Global::DB_File =
# SQL
	$Global::DBI =
# END SQL
	0;

# SQL
	# This is for standard DBI
	eval {
			die if $ENV{MINIVEND_NODBI};
			require DBI and $Global::DBI = 1
	};
# END SQL

	# Now can use any type of database
	AUTO: {
		last AUTO if 
			(defined $ENV{MINIVEND_DBFILE} and $Global::DB_File = 1);
		last AUTO if 
			(defined $ENV{MINIVEND_NODBM});
		eval {require GDBM_File and $Global::GDBM = 1};
		last AUTO if 
			(defined $ENV{MINIVEND_GDBM} and $Global::GDBM = 1);
		eval {require DB_File and $Global::DB_File = 1};
	}

	if($Global::GDBM) {
		require Vend::Table::GDBM;
		import GDBM_File;
		$Global::GDBM = 1;
		$Global::Default_database = 'GDBM'
			unless defined $Global::Default_database;
	}
	if($Global::DB_File) {
		require Vend::Table::DB_File;
		import DB_File;
		$Global::DB_File = 1;
		$Global::Default_database = 'DB_FILE'
			unless defined $Global::Default_database;
	}
	$Global::Default_database = 'MEMORY'
			unless defined $Global::Default_database;
	require Vend::Table::InMemory;
}


eval {
	package Vend::Order;
	require CCLib;
	$Vend::CC2 = 1;
	my $ver = $CCLib::VERSION || '2.1';
	::logGlobal({}, "CyberCash module found (Version %s)", $ver )
		unless $Vend::Quiet;
};

$Vend::CyberCash = ! $@;

eval {
	package Vend::Order;
	require CCMckLib3_2 ;
	import CCMckLib3_2 qw/InitConfig %Config $MCKversion/;
	require CCMckDirectLib3_2;
	import CCMckDirectLib3_2 qw/SendCC2_1Server doDirectPayment/;
	require CCMckErrno3_2;
	import CCMckErrno3_2 qw/MCKGetErrorMessage/;
	$Vend::CC3 = 1;
	$Vend::CC3server = 0;
	my $ver = $CCMckLib3_2::VERSION || '3.x';
	::logGlobal({}, "CyberCash module found (Version %s)", $ver )
		unless $Vend::Quiet;
};

$Vend::CyberCash = $Vend::CyberCash || ! $@;


use Vend::Util;
use Vend::Server;
use Vend::Session;
use Vend::Config;

# You might try commenting out these lines and uncommenting the ones
# below to compact memory size
# NOAUTOUSE
#use Vend::Order;
#use Vend::Imagemap;
#use Vend::Error;
#use Vend::Control;
# END NOAUTOUSE


# You might try commenting out these lines and uncommenting the ones
# below to do development or test for strange problems
# AUTOUSE
use autouse 'Vend::Error' => qw/get_locale_message interaction_error do_lockout full_dump/;
use autouse 'Vend::Imagemap' => qw/action_map/;
use autouse 'Vend::Control' => qw/
											signal_reconfig
											signal_add
											signal_remove
											control_minivend
											change_catalog_directive
											change_global_directive
											remove_catalog
											add_catalog
											change_catalog_directive
											change_global_directive
									/;
use autouse 'Vend::Order' => qw/
											add_items
											check_order
											check_required
											cyber_charge
   										encrypt_standard_cc
   										mail_order
   										onfly
   										route_order
   										validate_whole_cc
   								/;

# END AUTOUSE

# GLIMPSE
use Vend::Glimpse;
# END GLIMPSE

use Vend::Scan;
use Vend::TextSearch;
use Vend::DbSearch;
use Vend::Data;
use Vend::UserDB;
use Vend::Interpolate;
use Vend::Page;
use File::CounterFile;

if( ! $Global::Windows and $> == -1 || scalar(getpwuid($>)) eq 'nobody' ) {
	warn errmsg("\aYou probably don't want to run as nobody!\n");
	sleep 1;
	warn errmsg("The security problems are on your head, though. Continuing...\n");
}

my $H;
sub http {
	return $H;
}

sub response {
	my ($output) = @_;
	return 1 if $Vend::BuildingPages;
	my $out = ref $output ? $output : \$output;
	if (defined $Vend::CheckHTML) {
		require Vend::External;
		Vend::External::check_html($out);
	}
	$H->respond($out);
}

## DO ORDER

# Order an item with product code CODE.

sub do_order {
    my($path) = @_;
	my $code        = $CGI::values{mv_arg};
#::logDebug("do_order: path=$path");
	my $cart;
	my $page;
# LEGACY
	if($path =~ s:/(.*)::) {
		$cart = $1;
		if($cart =~ s:/(.*)::) {
			$page = $1;
		}
	}
# END LEGACY
	if(defined $CGI::values{mv_pc} and $CGI::values{mv_pc} =~ /_(\d+)/) {
		$CGI::values{mv_order_quantity} = $1;
	}
	$CGI::values{mv_cartname} = $cart if $cart;
	$CGI::values{mv_nextpage} = $page if $page;
# LEGACY
	$CGI::values{mv_nextpage} = $CGI::values{mv_orderpage}
								|| find_special_page('order')
		if ! $CGI::values{mv_nextpage};
# END LEGACY
	add_items($code);
    return 1;
}

# Returns undef if interaction error
sub update_quantity {
    return 1 unless defined  $CGI::values{"quantity0"};
	my($h, $i, $quantity, $modifier, $cart);

	$cart = Vend::Cart::get_cart($CGI::values{mv_cartname});

	if(ref $Vend::Cfg->{UseModifier}) {
		foreach $h (@{$Vend::Cfg->{UseModifier}}) {
			delete @{$::Values}{grep /^$h\d+$/, keys %$::Values};
			foreach $i (0 .. $#$cart) {
#::logDebug("updating line $i modifiers: " . ::uneval($cart->[$i]));
				$modifier = $CGI::values{"$h$i"} || undef;
				if (defined($modifier)) {
					$modifier =~ s/\0+/\0/g;
					$modifier =~ s/\0$//;
					$modifier =~ s/^\0//;
					$modifier =~ s/\0/, /g;
					$cart->[$i]->{$h} = $modifier;
					$::Values->{"$h$i"} = $modifier;
					delete $CGI::values{"$h$i"};
				}
			}
		}
	}

	foreach $i (0 .. $#$cart) {
#::logDebug("updating line $i quantity: " . ::uneval($cart->[$i]));
    	$quantity = $CGI::values{"quantity$i"};
    	if (! defined $quantity) {
        	interaction_error("Variable '$quantity' not passed from form\n");
        	return undef;
		}
    	elsif ($quantity =~ m/^\d*$/) {
        	$cart->[$i]->{'quantity'} = $quantity || 0;
    	}
    	elsif ($quantity =~ m/^[\d.]+$/
				and $Vend::Cfg->{FractionalItems} ) {
        	$cart->[$i]->{'quantity'} = $quantity;
    	}
		# This allows a multiple input of item quantity to
		# pass -- FIRST ONE CONTROLS
		elsif ($quantity =~ s/\0.*//) {
			$CGI::values{"quantity$i"} = $quantity;
			redo;
		}
		else {
			my $item = $cart->[$i]->{'code'};
        	interaction_error("'$quantity' for item $item is not numeric\n");
        	return undef;
    	}
    	$::Values->{"quantity$i"} = delete $CGI::values{"quantity$i"};
    }
#::logDebug("after update, cart is: " . ::uneval($cart));

	# If the user has put in "0" for any quantity, delete that item
    # from the order list.
    Vend::Cart::toss_cart($cart);

#::logDebug("after toss, cart is: " . ::uneval($cart));

	1;

}

## Update the user-entered fields.
sub update_data {
	my($key,$value);
    # Update a database record

	# Check to see if this is allowed
	if(! $::Scratch->{mv_data_enable}) {
		logError(
			 "Attempted database update without permission, table=%s key=%s.",
			 $CGI::values{mv_data_table},
			 $CGI::values{$CGI::values{mv_data_key}},
		);
		return undef;
	}
	unless (defined $CGI::values{mv_data_table} and 
		    defined $CGI::values{mv_data_key}      ) {
		logError("Attempted database operation without table, fields, or key.\n" .
					 "Table: '%s'\n" .
					 "Fields:'%s'\n" .
					 "Key:   '%s'\n",
					 $CGI::values{mv_data_table},
					 $CGI::values{mv_data_fields},
					 $CGI::values{mv_data_key},
				 );

		return undef;
	}

	my $function	= lc (delete $CGI::values{mv_data_function});
	if($function eq 'delete' and ! delete $CGI::values{mv_data_verify}) {
		logError("update_data: DELETE without VERIFY, abort");
		return undef;
	}
	my $table		= $CGI::values{mv_data_table};
	my $prikey		= $CGI::values{mv_data_key};
	my $decode		= is_yes($CGI::values{mv_data_decode});
	my ($ref, $db, $database);

	$ref = $Vend::Cfg->{Database}->{$table} || '';

	if (! $ref) {
		logError("set: non-existent table %s", $table);
		return undef;
	}
	$Vend::WriteDatabase{$table} = 1;

    $db = database_exists_ref($table)
        or die "Not a defined database '$table': $!\n";
    $db = $db->ref();

	my @fields		= grep $_, split /[\s\0,]+/, $CGI::values{mv_data_fields};

	$function = 'update' unless $function;

	my (%data);
	for(@fields) {
		$data{$_} = [];
	}

    while (($key, $value) = each %CGI::values) {
        next unless defined $data{$key};
		@{$data{$key}} = split /\0/, $value;
	}

	unless ($data{$prikey}) {
		logError("No key '%s' found for function='%s' table='%s' key='%s'",
				$prikey, $function,
				 $CGI::values{mv_data_table},
				 $CGI::values{$prikey}   );
		return undef;
	}

	my ($query,$i);
	my (@k);
	my (@v);
	my (@c);
#::logDebug("update_data:db=$db key=$prikey VALUES=" . ::uneval(\%CGI::values));
	my $select_key;
	for($i = 0; $i < @{$data{$prikey}}; $i++) {
		@k = (); @v = ();
		for(keys %data) {
			next unless (length($value = $data{$_}->[$i]) || $CGI::values{mv_update_empty});
			push(@k, $_);
# LEGACY
			HTML::Entities::decode($value) if $decode;
# END LEGACY
			if(defined $CGI::values{"mv_data_filter_$_"}) {
				$value = Vend::Interpolate::filter_value(
							 $CGI::values{"mv_data_filter_$_"},
							 $value,
							 $i,
							 );
			}
			$select_key = $value if $_ eq $prikey;
			push(@v, $value);
		}
		if($function eq 'delete') {
			$db->delete_record($select_key);
		}
		else {
			my $field;
			$key = $data{$prikey}->[$i];
			while($field = shift @k) {
				$value = shift @v;
#::logDebug("update_data:db=$db key=$key field=$field value=$value");
				next if $field eq $prikey;
				$db->set_field($key, $field, $value);
			}
		}
	}

}

# Parse the mv_click and mv_check special variables
sub parse_click {
	my ($ref, $click, $extra) = @_;
    my($codere) = '[\w-_#/.]+';
	my $params;

#::logDebug("Looking for click $click");
	if($params = $::Scratch->{$click}) {
		# Do nothing, we found the click
#::logDebug("Found scratch click $click = |$params|");
	}
	elsif(defined ($params = $Vend::Cfg->{OrderProfileName}{$click}) ) {
		# Do nothing, we found the click
		$params = $Vend::Cfg->{OrderProfile}[$params];
#::logDebug("Found profile click $click = |$params|");
	}
	elsif($params = $::Scratch->{"mv_click $click"}) {
		$::Scratch->{mv_click_arg} = $click;
	}
	elsif($params = $::Scratch->{mv_click}) {
		$::Scratch->{mv_click_arg} = $click;
	}
	else {
#::logDebug("Found NO click $click");
		return 1;
	} # No click processor

	my($var,$val,$parameter);
	$params = interpolate_html($params);
	my(@param) = split /\n+/, $params;

	for(@param) {
		next unless /\S/;
		next if /^\s*#/;
		s/^[\r\s]+//;
		s/[\r\s]+$//;
		$parameter = $_;
		($var,$val) = split /[\s=]+/, $parameter, 2;
		$val =~ s/&#(\d+);/chr($1)/ge;
		$ref->{$var} = $val;
		$extra->{$var} = $val
			if defined $extra;
	}
}

# This is the set of CGI-passed variables to ignore, in other words
# never set in the user session.  If set in the mv_check pass, though,
# they will stick.
my %Ignore = qw(
	mv_todo  1
	mv_todo.submit.x  1
	mv_todo.submit.y  1
	mv_todo.return.x  1
	mv_todo.return.y  1
	mv_todo.checkout.x  1
	mv_todo.checkout.y  1
	mv_todo.todo.x  1
	mv_todo.todo.y  1
	mv_todo.map  1
	mv_doit  1
	mv_check  1
	mv_click  1
	mv_nextpage  1
	mv_credit_card_number  1
	);

sub update_values {

	if( $Vend::Cfg->{CreditCardAuto} and $CGI::values{mv_credit_card_number} ) {
		(
			@{$::Values}{
				qw/
						mv_credit_card_valid
						mv_credit_card_info
						mv_credit_card_exp_month
						mv_credit_card_exp_year
						mv_credit_card_exp_all
						mv_credit_card_type
						mv_credit_card_reference
						mv_credit_card_error
				/ }
		) = encrypt_standard_cc(\%CGI::values);
	}	

	my ($key, $value);
    while (($key, $value) = each %CGI::values) {
        next if defined $Ignore{$key};
        next if defined $Vend::Cfg->{FormIgnore}->{$key};
        next if ($key =~ m/^quantity\d+$/);
		# We add any checkbox ordered items, but don't update -- 
		# we don't want to order them twice
        $::Values->{$key} = $value;
    }
}

sub update_user {
	my($key,$value);
    # Update the user-entered fields.

	add_items() if defined $CGI::values{mv_order_item};
	update_values();

	if($CGI::values{mv_check}) {
		my(@checks) = split /\s*[,\0]+\s*/, delete $CGI::values{mv_check};
		my($check);
		foreach $check (@checks) {
				parse_click $::Values, $check, \%CGI::values;	
		}
	}

	check_save if defined $CGI::values{mv_save_session};

}

## DO PROCESS

sub do_click {
	my($click, @clicks);
	if(defined $CGI::values{mv_click}) {
		@clicks = split /\s*[\0]+\s*/, $CGI::values{mv_click};
	}

	if(defined $CGI::values{mv_click_map}) {
		my(@map) = split /\s*[\0]+\s*/, $CGI::values{mv_click_map};
		foreach $click (@map) {
			push (@clicks, $click)
				if defined $CGI::values{"mv_click.$click.x"}
				or defined $CGI::values{"$click.x"};
		}
	}

	foreach $click (@clicks) {
		parse_click \%CGI::values, $click;	
	}
	return 1;
}

my %form_action = (

	search	=> \&do_search,
	submit	=>
				sub {
					update_user();
					update_quantity()
						or return interaction_error("quantities");
					my $ok;
					my($missing,$next,$status,$final);

					# Set shopping cart if necessary
					# Vend::Items is tied, remember!
					$Vend::Items = $CGI::values{mv_cartname}
						if $CGI::values{mv_cartname};

				  CHECK_ORDER: {

					# If the user sets this later, will be used
					delete $Vend::Session->{mv_order_number};

					if (defined $CGI::values{mv_order_profile}) {
						($status,$final,$missing) =
							check_order($CGI::values{mv_order_profile});
					}
					else {
						$status = $final = 1;
					}

					my $provisional;
					if ($status and defined $CGI::values{mv_order_route}) {
						# This checks only route order profiles
#::logDebug("Routing order, pre-check");
						($status, $provisional, $missing) = route_order(
												$CGI::values{mv_order_route},
												$Vend::Items,
												'check',
											);
					} 

					$final = $provisional if ! $final;

#::logDebug("Routing status status=$status final=$final errors=$missing");
					if($status) {
						$CGI::values{mv_nextpage} = $::Values->{mv_successpage} 
							if $::Values->{mv_successpage};
						$CGI::values{mv_nextpage} = $::Values->{mv_orderpage} 
							if ! $CGI::values{mv_nextpage};
					}
					else {
						$CGI::values{mv_nextpage} = $::Values->{mv_failpage}
							if $::Values->{mv_failpage};
						$CGI::values{mv_nextpage} = find_special_page('needfield')
							if ! $CGI::values{mv_nextpage};
						undef $final;
					}

					return 1 unless $final;

					my $order_no;
					if (defined $CGI::values{mv_order_route}) {
						# $ok will not be defined unless Route "supplant" was set
						# $order_no will come back so we don't issue two of them
#::logDebug("Routing order");
						($ok, $order_no) = route_order(
											$CGI::values{mv_order_route},
											$Vend::Items
											);
					}
					my $mode = $CGI::values{mv_payment_mode};
					if (! $ok and defined $Vend::Cfg->{ActionMap}{$mode}) {
						($ok, $status) = $Vend::Cfg->{ActionMap}{$mode}->();
					}
					elsif(
							$Vend::Cfg->{CyberCash}
							and defined $CGI::values{mv_cyber_mode}
						)
					{
#::logDebug("Cyber charge");
						$status = cyber_charge();
						unless($status) {
							$CGI::values->{mv_nextpage} = find_special_page('failed')
								if ! $CGI::values->{mv_nextpage};
							return 1;
						}
					}

					# This function (followed down) now does the rudimentary
					# backend ordering with AsciiTrack and the order report.
					# If the "supplant" option was set in order routing it will
					# not be used ($ok would have been defined)
#::logDebug("Order number=$order_no\n");
					$ok = mail_order(undef, $order_no || undef) unless defined $ok;
#::logDebug("Order number=$order_no\n");

					# Display a receipt if configured

					if ($ok) {
						eval {
							display_special_page(
											$::Values->{mv_order_receipt}	||
											find_special_page('receipt')
											);
						};
						if($@) {
							my $msg = $@;
							::logError( 
								'Display of receipt on order number %s failed: %s',
								$::Values->{mv_order_number},
								$msg,
							);
						}
					}
					else {
						display_special_page(
								find_special_page('failed'),
								errmsg('Error transmitting order(%s): %s', $!, $@),
						);
					}

					# Remove the items
					@$Vend::Items = ();
					put_session();
					return 0;
				  }

			},
	refresh	=> sub {
					update_quantity()
						or return interaction_error("quantities");
# LEGACY
					$CGI::values{mv_nextpage} = $CGI::values{mv_orderpage}
						if $CGI::values{mv_orderpage};
# END LEGACY
					$CGI::values{mv_nextpage} = $CGI::values{mv_orderpage}
												|| find_special_page('order')
						if ! $CGI::values{mv_nextpage};
					update_user();
					return 1;
				},
	set		=> sub {
					update_user();
					update_data();
					return 1;
				},
	return	=> sub {
					update_user();
					update_quantity()
						or return interaction_error("quantities");
					return 1;
				},
	cancel	=> sub {
					put_session();
					get_session();
					init_session();
					$CGI::values{mv_nextpage} = find_special_page('canceled')
						if ! $CGI::values{mv_nextpage};
					return 1;
				},
);

$form_action{go} = $form_action{return};

# Process the completed order or search page.

sub do_process {

	do_click();

    my $todo = $CGI::values{mv_todo};

	# Maybe we have an imagemap input, if not, use $doit
    if ( ! defined $todo) {
		if (defined $CGI::values{'mv_todo.x'}) {
				my $x = $CGI::values{'mv_todo.x'};
				my $y = $CGI::values{'mv_todo.y'};
				my $map = $CGI::values{'mv_todo.map'};
				$todo = Vend::Imagemap::action_map($x,$y,$map);
		}
		elsif (defined $CGI::values{'mv_todo.submit.x'}) {
			$todo = 'submit';
		}
		elsif (defined $CGI::values{'mv_todo.checkout.x'}) {
			$todo = 'checkout';
		}
		elsif (defined $CGI::values{'mv_todo.return.x'}) {
			$todo = 'return';
		}
		else {
			$todo = $CGI::values{mv_doit} if defined $CGI::values{mv_doit};
		}
	}

	my ($sub, $status);
	#Now determine the action on the todo
    if (defined $Vend::Cfg->{FormAction}{$todo}) {
		$sub = $Vend::Cfg->{FormAction}{$todo};
	}
    elsif (not $sub = $form_action{$todo} ) {
		interaction_error("No action passed for processing\n");
		return;
    }
	eval {
		$status = $sub->($todo);
	};
	if($@) {
		undef $status;
		my $err = $@;
		my $template = <<EOF;
Sorry, there was an error in processing this form action. Please 
report the error or try again later.
EOF
		$template .= "\n\nError: %s\n"
				if $Global::DisplayErrors && $Vend::Cfg->{DisplayErrors}
			;
		$template = get_locale_message(500, $template, $err);
		$template .= "($err)";
		::response($template);
	}

	return $status;
}

sub config_named_catalog {
	my ($cat_name, $source, $build) = @_;
	my ($g,$c,$conf);

	$g = $Global::Catalog{$cat_name};
	unless (defined $g) {
		logGlobal( "Can't find catalog '%s'" , $cat_name );
		return undef;
	}

	$Vend::Log_suppress = 1;
    logGlobal( "Config '%s' %s", $g->{'name'}, $source )
		unless $Vend::Quiet;
	undef $Vend::Log_suppress;

    chdir $g->{'dir'}
            or die "Couldn't change to $g->{'dir'}: $!\n";
    $conf = $g->{'dir'} . '/etc';
    eval {
        $c = config($g->{'name'},
					$g->{'dir'},
					$conf,
					$g->{'base'} || undef,
# OPTION_EXTENSION
#					$Vend::CommandLine->{$g->{'name'}} || undef
# END OPTION_EXTENSION
					);
    };

    if($@) {
		my $msg = $@;
        logGlobal( "%s config error: %s" , $g->{'name'}, $msg );
     	return undef;
    }

	return $c if defined $g->{'base'};

	eval {
# STATICPAGE
	READSTATIC: {
			my $basedir = $c->{PageDir};
			last READSTATIC if ! -f "$basedir/.static";
			if ($c->{Static}) {
				print "loading static page names..." unless $Vend::Quiet;
				last READSTATIC if $c->{StaticDBM};
				open STATICPAGE, "$basedir/.static"
					or warn <<EOF;
Couldn't read static page status file $basedir/.static: $!
EOF
				while(<STATICPAGE>) {
					chomp;
					s/\t(.*)//;
					$c->{StaticPage}->{$_} = $1 || '';
				}
				close STATICPAGE;
			}
		}
# END STATICPAGE
		$Vend::Cfg = $c;	
		$::Variable = $Vend::Cfg->{Variable};
		Vend::Data::read_salestax();
		Vend::Data::read_shipping();
		open_database(1);
		my $db;
		DREAD: {
			last DREAD unless $db = $Vend::Cfg->{DbDatabase};
			$db = database_exists_ref($db)
				or last DREAD;
			$db = $db->ref();
			my ($k, @f);	# key and fields
			my @l;			# refs to locale repository
			my @n;			# names of locales

			@n = $db->columns();
			my $name;
			foreach $name (@n) {
				my $file = $db->field('_file', $name);
				my $type = $db->field('_type', $name);
				next unless $file and $type;
				Vend::Config::parse_database('', "$name $file $type");
			}
			my $i;
			while( ($k , @f ) = $db->each_record) {
				next if $k =~ /^_/;
				for ($i = 0; $i < @f; $i++) {
					next unless length $f[$i];
					Vend::Config::parse_database('', "$n[$i] $k $f[$i]");
				}
			}
			my $save = $^W;
			$^W = 0;
			close_database();
			$^W = $save;
			open_database(1);
		}

		LREAD: {
			last LREAD unless $db = $Vend::Cfg->{LocaleDatabase};
			$db = database_exists_ref($db)
				or last LREAD;
			$db = $db->ref();
			my ($k, @f);	# key and fields
			my @l;			# refs to locale repository
			my @n;			# names of locales

			@n = $db->columns();
			my $extra;
			for(@n) {
				$Vend::Cfg->{Locale_repository}{$_} = {}
					unless $Vend::Cfg->{Locale_repository}{$_};
				push @l, $Vend::Cfg->{Locale_repository}{$_};
			}
			my $i;
			while( ($k , @f ) = $db->each_record) {
				for ($i = 0; $i < @f; $i++) {
					next unless length($f[$i]);
					$l[$i]->{$k} = $f[$i];
				}
			}
			unless ($Vend::Cfg->{Locale}) {
				for(@n) {
					next unless $Vend::Cfg->{Locale_repository}{$_}{'default'};
					$Vend::Cfg->{DefaultLocale} = $_;
					$Vend::Cfg->{Locale} = $Vend::Cfg->{Locale_repository}{$_};
					last;
				}
				unless ($Vend::Cfg->{Locale}) {
					$Vend::Cfg->{Locale} = $Vend::Cfg->{Locale_repository}{$n[0]};
					$Vend::Cfg->{DefaultLocale} = $n[0];
				}
			}
		}

		SREAD: {
			last SREAD unless $db = ($Vend::Cfg->{DirectiveDatabase} || $Vend::Cfg->{VariableDatabase});
			$db = database_exists_ref($db)
				or last SREAD;
			$db = $db->ref();
			my ($k, @f);	# key and fields
			my @l;			# refs to locale repository
			my @n;			# names of locales

			@n = $db->columns();
			my $extra;
			for(@n) {
				if (! ref $Vend::Cfg->{$_} or $Vend::Cfg->{$_} !~ /HASH/) {
					# ignore non-existent directive
					push @l, {};
					next;
				}
				push @l, $Vend::Cfg->{$_};
			}
			my $i;
			while( ($k , @f ) = $db->each_record) {
				for ($i = 0; $i < @f; $i++) {
					next unless length($f[$i]);
					$l[$i]->{$k} = $f[$i];
				}
			}
		}

		close_database();
	};
	undef $Vend::Cfg;
	undef $Vend::BuildingPages;  # In case of eval error
    if($@) {
		my $msg = $@;
		$msg =~ s/\s+$//;
        logGlobal( "%s config error: %s" , $g->{'name'}, $msg );
     	return undef;
    }

	dump_structure($c, $g->{name}) if $Global::DumpStructure;
	undef $c->{Source};
	my $stime = scalar localtime();
	Vend::Util::writefile(">$Global::ConfDir/status.$g->{name}", "$stime\n");
	Vend::Util::writefile(">$c->{ConfDir}/status.$g->{name}", "$stime\n");

	return $c;

}

sub is_retired {
	my $id = shift;
	mkdir "$Vend::Cfg->{ScratchDir}/retired", 0777
		unless -d "$Vend::Cfg->{ScratchDir}/retired";
	my $fn = Vend::Util::get_filename($id, 2, 1, "$Vend::Cfg->{ScratchDir}/retired");
	return -f $fn ? 1 : 0;
}

sub retire_id {
	my $id = shift;
	mkdir "$Vend::Cfg->{ScratchDir}/retired", 0777
		unless -d "$Vend::Cfg->{ScratchDir}/retired";
	my $fn = Vend::Util::get_filename($id, 2, 1, "$Vend::Cfg->{ScratchDir}/retired");
	open(TMPRET, ">$fn")
		or die "retire id open: $!\n";
	close(TMPRET);
	return;
}

sub tie_static_dbm {
	my $rw = shift;
	untie(%Vend::StaticDBM) if $rw;
	if($Global::GDBM) {
        my $flags = $rw ? &GDBM_WRITER : &GDBM_READER;
        $flags = &GDBM_NEWDB
            if $rw && $Vend::BuildingPages;
        tie(%Vend::StaticDBM,
            'GDBM_File',
            "$Vend::Cfg->{StaticDBM}.gdbm",
            $flags,
            $Vend::Cfg->{'FileCreationMask'},
        )
        or undef $Vend::Cfg->{StaticDBM};
	}
	elsif ($Global::DB_File) {
		tie(%Vend::StaticDBM,
			'DB_File',
			"$Vend::Cfg->{StaticDBM}.db",
			($rw ? &O_RDWR | &O_CREAT : &O_RDONLY),
			$Vend::Cfg->{'FileCreationMask'},
			)
		or undef $Vend::Cfg->{StaticDBM};
	}
	else {
		undef $Vend::Cfg->{StaticDBM};
	}
	::logError("Failed to create StaticDBM %s", $Vend::Cfg->{StaticDBM})
		if $rw && ! $Vend::Cfg->{StaticDBM};
	return $Vend::Cfg->{StaticDBM} || undef;
}


sub adjust_cgi {

    my($host);

    die "REQUEST_METHOD is not defined" unless defined $CGI::request_method
		or @Global::argv;

	# The great and really final AOL fix
	#
    $host      = $CGI::remote_host;
    $CGI::ip   = $CGI::remote_addr;

	if($Global::DomainTail and $host) {
		$host =~ s/.*?([-A-Za-z0-9]+\.[A-Za-z]+)$/$1/;
	}
	elsif($Global::IpHead) {
		$host = $Global::IpQuad == 0 ? 'nobody' : '';
		my @ip;
		@ip = split /\./, $CGI::ip;
		$CGI::ip = '';
		$CGI::ip = join ".", @ip[0 .. ($Global::IpQuad - 1)] if $Global::IpQuad;
	}
	#
	# end AOL fix

    $CGI::host = $host || $CGI::ip;

    $CGI::user = $CGI::remote_user if $CGI::remote_user;
	undef $CGI::authorization if $CGI::remote_user;
	$Vend::Cookie = $CGI::cookie;

	unless ($Global::FullUrl) {
		$CGI::script_name = $CGI::script_path;
	}
	else {
		if($CGI::server_port eq '80') { $CGI::server_port = ''; }
		else 		{ $CGI::server_port = ":$CGI::server_port"; }
		$CGI::script_name = $CGI::server_name .
							$CGI::server_port .
							$CGI::script_path;
	}
}


sub url_history {
	$Vend::Session->{History} = []
		unless defined $Vend::Session->{History};
	pop @{$Vend::Session->{History}}
		if $#{$Vend::Session->{History}} >= $Vend::Cfg->{History};
	if($CGI::pragma =~ /\bno-cache\b/ || $CGI::values{mv_no_cache}) {
		push (@{$Vend::Session->{History}},  [ 'expired', {} ]);
	}
	else {
		push (@{$Vend::Session->{History}},  [ $CGI::path_info, \%CGI::values ]);
	}
	return;
}

## DISPATCH

# Parse the invoking URL and dispatch to the handling subroutine.

my %action = (
    process	=> \&do_process,
    minimate=> sub { 
					&MiniMate::CfgMgr::mm_acl_global;
					\&do_process(@_);
				   },
    scan	=> \&do_scan,
    search	=> \&do_search,
    order	=> \&do_order,
    obtain	=> \&do_order,
);

sub dispatch {
	my($http) = @_;
	$H = $http;
	if($Vend::Foreground) {
		Vend::Interpolate::reset_calc();
	}
#::logDebug ("begin dispatch: " . (join " ", times()) . "\n");
#::logDebug ("begin dispatch, locale LC_CTYPE: " . POSIX::setlocale(POSIX::LC_CTYPE()) . "\n");

	adjust_cgi();

    my($sessionid, $path);
	my(@path);
	my($g, $action);

	unless (defined $Global::Selector{$CGI::script_name}) {
		my $msg = get_locale_message(
						403,
						"Undefined catalog: %s",
						$CGI::script_name,
						);
		$Vend::StatusLine = <<EOF;
Status: 404 Not Found
Content-Type: text/plain
EOF
		::response($msg);
		logGlobal($msg);
		return;
	}
	$Vend::Cfg = $Global::Selector{$CGI::script_name};

## Uncomment this to get global directive setting on a per-catalog basis
## Probably only useful for:
##
##   DebugFile
##   DisplayErrors
##   DomainTail
##   ErrorLog
##   FullUrl
##   GlobalSub
##   HitCount
##   IpHead
##   IpQuad
##   Locale
##   LockoutCommand
##   NoAbsolute
##   SafeUntrap
##   UserTag
##   Variable

	my $catref = $Global::Catalog{$Vend::Cfg->{CatalogName}};
	if(! $Vend::Foreground and defined $catref->{directive}) {
		no strict 'refs';
		my ($key, $val);
		while ( ($key, $val) = each %{$catref->{directive}}) {
#::logDebug("directive key=$key val=" . ::uneval($val));
			${"Global::$key"} = $val;
		}
	}


	# See if it is a subcatalog
	if (defined $Vend::Cfg->{BaseCatalog}) {
		my $name = $Vend::Cfg->{BaseCatalog};
		my $ref = $Global::Catalog{$name};
		my $c = $Vend::Cfg;
		$Vend::Cfg = $Global::Selector{$ref->{'script'}};
		for(keys %{$c->{Replace}}) {
			undef $Vend::Cfg->{$_};
		}
		copyref $c, $Vend::Cfg;
		if($Vend::Cfg->{Variable}{MV_LANG}) {
			my $loc = $Vend::Cfg->{Variable}{MV_LANG};
			$Vend::Cfg->{Locale} = $Vend::Cfg->{Locale_repository}{$loc}
					if defined $Vend::Cfg->{Locale_repository}{$loc};
		}
		$Vend::Cfg->{StaticPage} = {}
			unless $Vend::Cfg->{Static};
	}
	$::Variable = $Vend::Cfg->{Variable};


	if (defined $Global::SelectorAlias{$CGI::script_name}
		and ! defined $Vend::InternalHTTP                 )
	{
		my $real = $Global::SelectorAlias{$CGI::script_name};
		if(defined $Vend::NoFork) {
			$Vend::Save = {} unless $Vend::Save;
			$Vend::Save->{VendURL}   = $Vend::Cfg->{VendURL};
			$Vend::Save->{SecureURL} = $Vend::Cfg->{SecureURL};
		}
		unless (	$CGI::secure                                        or
					$Vend::Cfg->{SecureURL} =~ m{$CGI::script_name$}     and
					$Vend::Cfg->{VendURL}   !~ m{$CGI::script_name$} 		)
		{
			$Vend::Cfg->{VendURL}   =~ s!$real!$CGI::script_name!;
			$Vend::Cfg->{SecureURL} =~ s!$real!$CGI::script_name!;
		}
	}
	elsif ($Vend::InternalHTTP) {
		$Vend::Cfg->{VendURL} = "http://" .
								$CGI::http_host .
								$CGI::script_path;
		$Vend::Cfg->{ImageDir} = $Vend::Cfg->{ImageDirInternal}
			if  $Vend::Cfg->{ImageDirInternal};
	}

	if($Global::HitCount) {
		my $ctr = new File::CounterFile
					"$Global::ConfDir/hits.$Vend::Cfg->{CatalogName}";
        $ctr->inc();
	}

	if ($Vend::Cfg->{SetGroup}) {
		eval {
			$) = "$Vend::Cfg->{SetGroup} $Vend::Cfg->{SetGroup}";
		};
		if ($@) {
			my $msg = $@;
			logGlobal( "Can't set group to GID %s: %s",
						$Vend::Cfg->{SetGroup}, $msg
					);
			logError("Can't set group to GID %s: %s",
						$Vend::Cfg->{SetGroup}, $msg
					);
		}
	}

	chdir $Vend::Cfg->{'VendRoot'} 
		or die "Couldn't change to $Vend::Cfg{'VendRoot'}: $!\n";
	set_file_permissions();
# STATICPAGE
	tie_static_dbm() if $Vend::Cfg->{StaticDBM};
# END STATICPAGE
	umask $Vend::Cfg->{'Umask'};
	open_database();

	$CGI::user = Vend::Util::check_authorization($CGI::authorization)
		if defined $CGI::authorization;

	my $from_cookie;
	$sessionid = $CGI::values{mv_session_id} || undef;
	$Vend::OnlyProducts = defined $Vend::Cfg->{ProductFiles}->[1]
						  ? undef
						  : $Vend::Cfg->{ProductFiles}->[0];

	if (defined $CGI::cookie and
		 $CGI::cookie =~ /\bMV_SESSION_ID=(\w{8,32})
								[:_] (
									(	\d{1,3}\.   # An IP ADDRESS
										\d{1,3}\.
										\d{1,3}\.
										\d{1,3})
									# A user name or domain
									|	([A-Za-z0-9][-\@A-Za-z.0-9]+) )?
									\b/x)
	{
		$sessionid = $1
			unless defined $CGI::values{mv_pc} and $CGI::values{mv_pc} eq 'RESET';
		$CGI::cookiehost = $3 || undef;
		$CGI::cookieuser = $4 || undef;
		$from_cookie = 1;
    }

	$CGI::host = 'nobody' if $Vend::Cfg->{WideOpen};

	if(! $sessionid) {
		my $id = $::Variable->{MV_SESSION_ID};
		$sessionid = $CGI::values{$id} if $CGI::values{$id};
		if (! $sessionid and $Vend::Cfg->{FallbackIP}) {
			$sessionid = generate_key($CGI::remote_addr . $CGI::useragent);
		}
	}

# DEBUG
#::logDebug ("session='$sessionid' cookie='$CGI::cookie' chost='$CGI::cookiehost'");
# END DEBUG

RESOLVEID: {
    if ($sessionid) {
		$Vend::SessionID = $sessionid;
    	$Vend::SessionName = session_name();
		# get_session will return a value if a session is read,
		# if not it will return false and a new session has been created.
		# The IP address will be counted for robot_resolution
		if(! get_session()) {
			retire_id($sessionid);
			last RESOLVEID;
		}
		my $now = time;
		if(! $from_cookie) {
			if( is_retired($sessionid) ) {
				new_session();
				last RESOLVEID;
			}
			my $compare_host	= $CGI::secure
								? ($Vend::Session->{shost})
								: ($Vend::Session->{ohost});

			if(! $compare_host) {
				new_session() unless $CGI::secure;
				$Vend::Session->{shost} = $CGI::secure;
			}
			elsif ($compare_host ne $CGI::remote_addr) {
				new_session();
			}
		}
		if ($now - $Vend::Session->{'time'} > $Vend::Cfg->{SessionExpire}) {
			retire_id($sessionid);
			new_session();
			last RESOLVEID;
		}
		elsif($Vend::Cfg->{RobotLimit}) {
			if ($now - $Vend::Session->{'time'} > 30) {
				$Vend::Session->{accesses} = 0;
			}
			else {
				$Vend::Session->{accesses}++;
				if($Vend::Session->{'accesses'} > $Vend::Cfg->{RobotLimit}) {
					my $msg = errmsg(
			"WARNING: POSSIBLE BAD ROBOT. %s accesses with no 30 second pause.",
			$Vend::Session->{accesses},
					);
					do_lockout($msg);
				}
			}
		}
    }
	else {
		if($Vend::Cfg->{RobotLimit}) {
			if (Vend::Session::count_ip() > $Vend::Cfg->{RobotLimit}) {
				my $msg;
				# Here they can get it back if they pass expiration time
				my $wait = $Global::Variable->{MV_ROBOT_EXPIRE} || 86400;
				$wait /= 3600;
				$msg = errmsg(<<EOF, $wait); 
Too many new ID assignments for this IP address. Please wait at least %d hours
before trying again. Only waiting that period will allow access. Terminating.
EOF
				$msg = Vend::Page::get_locale_message(403, $msg);
				do_lockout($msg);
				$Vend::StatusLine = <<EOF;
Status: 403 Forbidden
Content-Type: text/plain
EOF
					::response($msg);
					return;
			}
		}
		new_session();
    }
}

#::logDebug("session name='$Vend::SessionName'\n");

	$Vend::Interpolate::Calc_initialized = 0;
	$CGI::values{mv_session_id} = $Vend::Session->{id} = $Vend::SessionID;

	if($Vend::Cfg->{CookieLogin}) {
		COOKIELOGIN: {
			last COOKIELOGIN if $Vend::Session->{logged_in};
			last COOKIELOGIN if defined $CGI::values{mv_username};
			last COOKIELOGIN unless
				$CGI::values{mv_username} = Vend::Util::read_cookie('MV_USERNAME');
			my $password;
			last COOKIELOGIN unless
				$password = Vend::Util::read_cookie('MV_PASSWORD');
			$CGI::values{mv_password} = $password;
			local(%SIG);
			undef $SIG{__DIE__};
			eval {
				Vend::UserDB::userdb('login');
			};
			if($@) {
				$Vend::Session->{failure} .= $@;
			}
		}
	}

	$Vend::Session->{'arg'} = $Vend::Argument = ($CGI::values{mv_arg} || undef);
#::logDebug("arg is $Vend::Session->{arg}");
	if($CGI::values{mv_pc} and $CGI::values{mv_pc} =~ /[A-Za-z]/) {
		$Vend::Session->{'source'} =	$CGI::values{mv_pc} eq 'RESET'
										? ''
										: $CGI::values{mv_pc};
	}

	$Vend::Session->{'user'} = $CGI::user;

	undef $Vend::Cookie if 
		$Vend::Session->{logged_in} && ! $Vend::Cfg->{StaticLogged};

	$CGI::pragma = 'no-cache'
		if delete $Vend::Session->{scratch}{mv_no_cache};

	$path = $Vend::Session->{last_url} = $CGI::path_info;
	if(	defined $Vend::Session->{one_time_path_alias}{$path} ) {
		$CGI::path_info = $path = delete $Vend::Session->{one_time_path_alias}{$path};
	} 
	elsif( defined $Vend::Session->{path_alias}{$path}	) {
		$CGI::path_info = $path = $Vend::Session->{path_alias}{$path};
	}
    url_history($path) if $Vend::Cfg->{History};

	if($Vend::Cfg->{DisplayErrors} and $Global::DisplayErrors) {
		$SIG{"__DIE__"} = sub {
							my $msg = shift;
							response( <<EOF);
<HTML><HEAD><TITLE>Fatal MiniVend Error</TITLE></HEAD><BODY>
<H1>FATAL error</H1>
<PRE>$msg</PRE>
</BODY></HTML>
EOF
							exit 0;
						};
	}

# LEGACY
	ROUTINES: {
		last ROUTINES unless index($path, '/process/') == 0;
		while ($path =~ s:/process/(locale|language|currency)/([^/]*)/:/process/:) {
			$Vend::Session->{scratch}->{"mv_$1"} = $2;
		}
		$path =~ s:/process/page/:/:;
	}
	my $locale;
	if($locale = $Vend::Session->{scratch}->{mv_language}) {
		$Global::Variable->{LANG}
			= $::Variable->{LANG} = $locale;
	}

	if ($Vend::Cfg->{Locale}								and
		$locale = $Vend::Session->{scratch}->{mv_locale}	and
		defined $Vend::Cfg->{Locale_repository}->{$locale}
		)
	{ 
		$Global::Variable->{LANG}
				= $::Variable->{LANG}
				= $::Scratch->{mv_language}
				= $locale
			 if ! $::Scratch->{mv_language};
		Vend::Util::setlocale(	$locale,
								($Vend::Session->{scratch}{mv_currency} || undef)
							);
	}
# END LEGACY

	my $macro;
	if (
		defined $Vend::Cfg->{Autoload} and
		$macro = $Vend::Cfg->{Autoload}
		)
	{
		if($macro =~ /\[\w+/) {
			interpolate_html($macro);
		}
	}

	if (
		defined $Vend::Cfg->{Filter} and
		$macro = $Vend::Cfg->{Filter}
		)
	{
		for(keys %$macro) {
			Vend::Interpolate::input_filter_do($_, { 'op' => $macro->{$_} } );
		}
	}

	if (
		defined $Vend::Session->{Filter} and
		$macro = $Vend::Session->{Filter}
		)
	{
		for(keys %$macro) {
			Vend::Interpolate::input_filter_do($_, $macro->{$_});
		}
	}

	if (
		defined $Vend::Session->{Autoload} and
		$macro = $Vend::Session->{Autoload}
		)
	{
		if(ref $macro) {
			for (@$macro) {
				interpolate_html($_);
			}
		}
		else {
			interpolate_html($macro);
		}
	}

    # If the cgi-bin program was invoked with no extra path info,
    # just display the catalog page.
    if (! $path || $path =~ m:^/+$:) {
		$path = find_special_page('catalog');
    }

	$path =~ s:^/+::;
	$path =~ s/\.html?$//;
#::logDebug("path=$path");
	$Vend::FinalPath = $path;
    @path = split('/', $path, 2);
	if (defined $CGI::values{mv_action}) {
		$Vend::Action = 'process';
		$CGI::values{mv_todo} = $CGI::values{mv_action}
			if ! defined $CGI::values{mv_todo} and ! defined $CGI::values{mv_doit};
		$CGI::values{mv_nextpage} = $path
			if ! defined $CGI::values{mv_nextpage};
	}
	else {
		$Vend::Action = shift @path;
		$Vend::FinalPath = join "", @path;
	}

#::logDebug("action=$Vend::Action path=$path");
	my ($sub, $status);
	Vend::Interpolate::reset_calc();
	if(defined $Vend::Cfg->{ActionMap}{$Vend::Action}) {
		$sub = $Vend::Cfg->{ActionMap}{$Vend::Action};
		Vend::Interpolate::init_calc();
		$CGI::values{mv_nextpage} = $Vend::FinalPath
			if ! defined $CGI::values{mv_nextpage};
	}
	elsif ( ! defined ($sub = $action{$Vend::Action}) )  {
		$Vend::FinalPath = $path;
	}

	eval {
		if(defined $sub) {
#::logDebug("found sub");
				$status = $sub->($Vend::FinalPath);
		}
		else {
			$status = 1;
		}
	};

	if($@) {
		undef $status;
		my $err = $@;
		my $template = <<EOF;
Sorry, there was an error in processing this form action. Please 
report the error or try again later.
EOF
		$template .= "\n\nError: %s\n"
				if $Global::DisplayErrors && $Vend::Cfg->{DisplayErrors}
			;
		$template = get_locale_message(500, $template, $err);
		$template .= "($err)";
		::response($template);
	}

	$CGI::values{mv_nextpage} = $path
		if ! defined $CGI::values{mv_nextpage};

	do_page() if $status;

#::logDebug ("end dispatch: " . (join " ", times()) . "\n");

	put_session() if $Vend::HaveSession;
	close_database();

	undef $H;
	if($Vend::Save) {
		copyref ($Vend::Save, $Vend::Cfg);
		undef $Vend::Save;
	}
	undef $Vend::Cfg;

# DEBUG
#::logDebug ("closed all: " .  join " ", times() . "\n");
# END DEBUG

	return 1;
}

## DEBUG

sub dontwarn {

# STATICPAGE
	$File::Find::name +
	$File::Find::prune +
	<DATA> + 
# END STATICPAGE
	$FindBin::RealBin +
	$Global::AdminSub +
	$Global::DomainTail +
	$Global::FullUrl +
    $Global::HitCount +
    $Global::LockoutCommand +
	$Global::IpHead +
	$Vend::CheckHTML +
	$Vend::Action +
	$CGI::server_name +
	$CGI::content_type +
	$CGI::http_host +

	1;
}

sub dump_env {
    my($var, $value);

    open(Vend::E, ">$Vend::Cfg->{'VendRoot'}/env");
    while(($var, $value) = each %ENV) {
	print Vend::E "export $var='$value'\n";
    }
    close Vend::E;
}

sub version {
	print "MiniVend version $VERSION Copyright 1995 Andrew M. Wilcox\n";
	print "                      Copyright 1996-1998 Michael J. Heins\n";
}

=head1 NAME

minivend - an e-commerce and general HTTP database display system

=head1 SYNOPSIS

minivend [--options] [file]

=head1 VERSION

4.0

=head1 DESCRIPTION

MiniVend is a database access and retrieval system focused on e-commerce.
It allows customers to select items to buy from catalog pages. The program
tracks user information in sessions and interacts with an HTTP server
through sockets.

MiniVend has many, many, functions and features; they are too numerous
to describe in this venue. Complete information can be found at
its web site:

		http://www.minivend.com

MiniVend requires Perl 5.005 or higher; more information on Perl can
be seen at:

		http://www.perl.com

=head1 OPTIONS

MiniVend uses the Getopt::Long module, and most options will be recognized
if they uniquely identifiable. The canonical forms are:

=over 4 

=item C<-a, --add>

Add a catalog to the system. Information taken from the input file
(or standard input). Implies reconfig=catalog. Example:

  echo "Catalog simple /catalogs/simple /simple.cgi" | bin/minivend -a

The information is in the form of a standard MiniVend catalog line,
and must be in the single-line format.

=item C<-b catalog, --build=catalog>

Build static page tree for C<catalog>.

=item -d dir, --dir=dir

Directory for VendRoot. This is where the MiniVend configuration file
will be looked for (if not redefined with C<-f>), and where the log file
will go (if not redefined with the ErrorFile directive).

=item -e name, --exclude=name

Exclude catalog from this startup.

=item -f file, --config=file

Configuration file to use (default is minivend.cfg in VendRoot).

=item --files spec

File specification to build (perl regexp OK) for static page tree

=item -h, --help

Display help on command line options.

=item -i, --inetmode

Run with internet-domain socket only. Normally MiniVend runs with
both UNIX- and internet-domain sockets (except on Windows).

=item --kill [signal]

By default, kills the server ungracefully with signal KILL (9, usually).
The optional signal will be sent instead if supplied.

=item -q, --quiet

Suppress informational messages on startup. Only errors are shown.

=item --reconfig=name

Cause only catalog C<name> to re-read its configuration.

=item --remove=catalog

Remove a catalog from operation; any future requests will get a not-found
message.

=item -r, --restart

Stop and restart the server. This may take a long time if many catalogs
are in use, and will temporarily take the system offline. If you want to
change a UserTag, use the --add option instead.

=item --serve

This is the default if no mode options (--reconfig, --kill, --restart, etc.)
are supplied.

=item --stop

Stop server gracefully with a TERM signal.

=item -t, --test

Report problems with config files; causes a complete configuration of 
the Minivend server but no server start.

=item -u, --unix

Run with unix-domain socket only. Normally MiniVend runs with
both UNIX- and internet-domain sockets. This will not work on Windows.

=item -v, --version

Display program version.

=item -D, --DEBUG

Run foreground in debug mode. It is normal to receive warnings about
various things.

=cut

=item Directive=value

Set a MiniVend global directive upon start (or --restart). Example:

	minivend SocketPerms=0666

This will start the server and override the default of SocketPerms or the
value set in minivend.cfg for this instance only. Any --restarts must
re-specify the directive if it is still to have that value.

=item name:Directive=value

Set a MiniVend directive for catalog C<name> upon start (or --restart). Example:

	minivend simple:VendURL="http://localhost/cgi-bin/simple"

This will start the server and override the default of VendURL for the
value set in catalog.cfg for this instance only. Any --restarts must
re-specify the directive if it is still to have that value.

=back

=cut

sub usage {
	version();
	print <<'END';

MiniVend comes with ABSOLUTELY NO WARRANTY.  This is free software, and
you are welcome to redistribute and modify it under the terms of the
GNU General Public License.

Command line options (first letter will usually work):

     --add=catalog         remove a catalog from operation, parms taken
                           from the standard input
     -b catalog
        --build=catalog    build static page tree for catalog
     -d dir, --dir=dir     directory for VendRoot (minivend.cfg, error.log, etc.)
     -e name,
        --exclude=name     exclude catalog
     -f file,
        --config=file      configuration file (default minivend.cfg)
     --files spec          filespec (perl regexp OK) for static page tree
     -h, --help            display this message
     -i, --inetmode        run with Internet-domain socket (TCP)
     --kill [signal]       kill server ungracefully (9 or with optional signal)
     -q, --quiet           suppress informational messages on startup
     --reconfig=catalog    reconfig a particular catalog on the server
     --remove=catalog      remove a catalog from operation
     --restart             restart server
     --serve               start server (default) (-start is alias)
     --stop                stop server gracefully
     -t, --test            report problems with config files
     -u, --unix            run with UNIX-domain socket
     -v, --version         display program version
     -D, --DEBUG           run foreground in debug mode
END
}

## FILE PERMISSIONS

sub set_file_permissions {
	my($r, $w, $p, $u);

	$r = $Vend::Cfg->{'ReadPermission'};
	if    ($r eq 'user')  { $p = 0400;   $u = 0277; }
	elsif ($r eq 'group') { $p = 0440;   $u = 0227; }
	elsif ($r eq 'world') { $p = 0444;   $u = 0222; }
	else                  { die "Invalid value for ReadPermission\n"; }

	$w = $Vend::Cfg->{'WritePermission'};
	if    ($w eq 'user')  { $p += 0200;  $u &= 0577; }
	elsif ($w eq 'group') { $p += 0220;  $u &= 0557; }
	elsif ($w eq 'world') { $p += 0222;  $u &= 0555; }
	else                  { die "Invalid value for WritePermission\n"; }

	$Vend::Cfg->{'FileCreationMask'} = $p;
	$Vend::Cfg->{'Umask'} = $u;
}

## MAIN

sub catch_warnings {
	unless($_[0]) {
		$SIG{'__WARN__'} = '';
		return;
	}
	$SIG{'__WARN__'} = sub {
		return @_ unless $_[0] =~ /^Use of unitialized /;
		my $warn = $_[0];
		my $configline;
		if($warn =~ /CONFIG>\s+chunk\s+(\d+)/) {
			return <<EOF;
There is a possible problem in this catalog at line $configline
of the catalog.cfg file. Please check it out.
EOF
		}
		return @_;
	};
}

sub parse_options {

	use Getopt::Long;

	Getopt::Long::config(qw/permute/);

	#Getopt::Long::config(qw/debug/);
	my $rcfgsub = sub {
						my ($mode, $val) = @_;
						die "Can't set two modes -$mode and -$Vend::mode.\n"
								if $Vend::saw_mode;
						$Vend::Quiet = 1
							unless defined $Vend::Quiet;
						$Vend::saw_mode = 1;
						push @Vend::CatalogToReconfig, $val;
						$Vend::mode = $mode;
					};
	my $modesub = sub {
						my ($mode, $val) = @_;
						die "Can't set two modes -$mode and -$Vend::mode.\n"
								if $Vend::saw_mode;
						$Vend::saw_mode = 1;
						$Vend::CatalogToBuild{$val} = 1 if $mode eq 'build';
						$Vend::mode = $mode;
					};

	my ($c_direc, $g_direc);

	my @args = @ARGV;
	my $ignore = 0;

	my %optctl = (

		DEBUG 		    => \$Global::DEBUG,
		build           => $modesub,
		reconfig        => $rcfgsub,
		confdir         => \$Global::ConfDir,
		configfile      => \$Global::ConfigFile,
		dir          	=> \$Global::VendRoot,
		exclude         => \%Vend::CatalogToSkip,
		files	        => \$Vend::BuildSpec,
		help            => sub { usage(); exit 0 },
		inetmode        => \$Global::Inet_Mode,
		log             => \$Global::ErrorFile,
		quiet			=> \$Vend::Quiet,
		pidfile			=> \$Global::PIDfile,
		serve           => $modesub,

		test			=> $modesub,
		unixmode        => \$Global::Unix_Mode,
		version         => sub { version(); exit 0 },
		stop			=> \&control_minivend,
		add				=> \&signal_add,
		remove			=> \&signal_remove,
		kill			=> \&control_minivend,
		Ignore 			=> \$ignore,
		restart			=> sub {
								return if $ignore;
								$ignore = 1;
								control_minivend('stop', 'TERM', 1);
								sleep 3;
								exec $0, '--Ignore', @args;
							},
		'<>'			=> sub {
							my ($arg) = @_;
							return unless $arg =~ /=/;
							my ($opt, $val) = split /=/, $arg, 2;
							my $cat;
							if($opt =~ /:/) {
								($cat, $opt) = split /:/, $opt, 2;
							}

							my $direc;
							if($cat) {
								$c_direc = Vend::Config::catalog_directives()
									unless $c_direc;
								$direc = $c_direc;
							}
							else {
								$g_direc = Vend::Config::global_directives()
									unless $g_direc;
								$direc = $g_direc;
								$cat = 'mv_global';
							}
							my $found;

							for (@$direc) {
								next unless (lc $opt) eq (lc $_->[0]);
								$found = $_->[0];
								last;
							}
							unless ($found) {
								warn "Unrecognized directive '$arg', skipping.\n";
								return;
							}

							$MV::Default{$cat} = {},
							$MV::DefaultAry{$cat} = []
								unless $MV::Default{$cat};
							$MV::Default{$cat}{$found} = $val
								unless defined $MV::Default{$cat}{$found};
							push @{$MV::DefaultAry{$cat}}, "$found $val";
							return;
							},
	);

	my @options = ( qw/
		DEBUG|D:i
		Ignore
		add
		build|b=s
		confdir=s
		configfile|config|c|f=s
		dir|vendroot|d=s
		exclude|e=s
		files=s
		help|h
		inetmode|inet|i
		kill:s
		log|logfile|l=s
		quiet|q
		pidfile=s
		reconfig=s
		remove=s
		restart|r
		serve|start|s
		stop:s
		test|t
		unixmode|unix
		version|v
		<>
	/ );

	GetOptions(\%optctl, @options);

}

sub main_loop {
	# Setup
	unless ($Global::Windows) {
		$ENV{'PATH'} = '/bin:/usr/bin';
		$ENV{'SHELL'} = '/bin/sh';
		$ENV{'IFS'} = '';
	}
	srand;
	setup_escape_chars();
	my $status = 0;

	$Global::ConfDir = "$Global::VendRoot/etc";
	$Global::PIDfile = "$Global::ConfDir/minivend.pid";
	$Vend::mode = 'serve';      # mode will be reset by options if appropriate
	parse_options() or die usage() . "\n";
	if($> == 0) {
		die errmsg("The Minivend server must not be run as root.\n")
			unless $ENV{MINIVEND_ROOT} =~ m{/blib$};
	}
	delete $INC{'FindBin.pm'};
	delete $INC{'Getopt/Long.pm'};
	$Global::ErrorFile = "$Global::VendRoot/error.log"
		if $Global::ErrorFile eq $Global::InitialErrorFile;
	undef $Global::InitialErrorFile;
	chdir($Global::VendRoot) 
		or die "Couldn't change directory to $Global::VendRoot: $!\n";
	$Global::ConfigFile = "$Global::VendRoot/minivend.cfg"
		if ! $Global::ConfigFile;

	die "MiniVend not configured, no $Global::ConfigFile.\n"
		unless -f $Global::ConfigFile;

# OPTION_EXTENSION
#	parse_stdin() unless eof();
# END OPTION_EXTENSION

	if(! $Global::DEBUG) {
		$Global::DEBUG = $ENV{MINIVEND_DEBUG} || 0;
	}

print errmsg("\n##### DEBUG MODE, running in foreground #####\n") if $Global::DEBUG;

	umask 077;
	global_config();

#::logDebug(::uneval(\%Global::Catalog));

	if($Vend::mode eq 'reconfig') {
		eval {
			signal_reconfig(@Vend::CatalogToReconfig);
		};
		die "$@\n" if $@;
		exit;
	}

	$| = 1;
	logGlobal( "MiniVend V$VERSION");
	CATCONFIG: {
		my $i = 0;
		my ($g, $c, $name);
		foreach $name (sort keys %Global::Catalog) {
			$g =  $Global::Catalog{$name};
			next if defined $Vend::CatalogToSkip{$g->{'name'}};
			next if
				$Vend::mode eq 'build' and
				! defined $Vend::CatalogToBuild{$g->{'name'}};
			print "Configuring catalog " . $g->{'name'} . '...'
				unless $Vend::Quiet or $g->{name} eq '_mv_admin';
			if (exists $Global::Selector{$g->{'script'}}) {
				warn "Two catalogs with same script name $g->{'script'}.\n";
				warn "Skipping catalog $g->{'name'}....\n\n";
				next;
			}

			catch_warnings(1);
			eval {
				$c = config_named_catalog($name, "at server startup");
			};
			catch_warnings(0);

			if ($@ or ! defined $c) {
				my $msg = $@;
				print "\n$msg\n\a$g->{'name'}: error in configuration. Skipping.\n";
				$msg =~ s/\s+$//;
				$msg = " -- $msg" if $msg;
				logGlobal $g->{'name'} . ": config error$msg. Skipping.";
				undef $Global::Selector{$g->{'script'}};
				next;
			}

			$Global::Selector{$g->{script}} = $c;

			# Set up aliases
			if (defined $g->{alias}) {
				for(@{$g->{alias}}) {
					if (exists $Global::Selector{$_}) {
						warn "Alias $_ used a second time, skipping.\n";
						next;
					}
					elsif (m![^\w-_:~#/.]!) {
						warn "Bad alias $_, skipping.\n";
					}
					$Global::Selector{$_} = $c;
					$Global::SelectorAlias{$_} = $g->{'script'};
				}
			}
# STATICPAGE
			if ($Vend::CatalogToBuild{$g->{name}}) {
				require Vend::Misc::Static;
				eval {
					Vend::Misc::Static::build_all($g->{name});
				};
				if($@) {
					die "Error building pages: $@\n";
				}
				undef $Vend::BuildingPages;
			}
# END STATICPAGE
			print "done.\n"  unless $Vend::Quiet or $g->{name} =~ /^_/;
		}
	}

	#undef $Global::DumpStructure;

	if ($Vend::mode eq 'serve') {
		undef $Vend::Foreground;
		# This should never return unless killed or an error

		# Set the $0 to something not having 'perl' (won't
		# work on Solaris and IRIX)
		if(defined $Global::Variable->{MV_DOLLAR_ZERO}) {
			$0 = $Global::Variable->{MV_DOLLAR_ZERO};
			$0 = "minivend --> $Global::VendRoot"
					if length($0) < 2;
		}
		else {
			$0 = 'minivend';
		}


        select STDERR; 
        $| = 1;
        select STDOUT;
        $| = 1;

        Vend::Server::run_server();
	}
	elsif ($Vend::mode eq 'test' || $Vend::mode eq 'build') {
				# Blank by design, this option only tests config files
				# or builds catalogs
	}
	else {
		die "No mode!\n";
	}

}

eval { main_loop(); };
if ($@) {
	my($msg) = ($@);
	logGlobal( $msg );
	if ($Global::DisplayErrors) {
		print "$msg\n";
	}
	die "$msg\n" if $Vend::ForeGround;
}

=head1 SEE ALSO

mvdocs(8), compile_link(1), config_prog(1), configdump(1), dump(1), expire(1),
expireall(1), localize(1), makecat(1), offline(1), restart(1), update(1),
http://www.minivend.com

=head1 LICENSE

MiniVend comes with ABSOLUTELY NO WARRANTY.  This is free software, and
you are welcome to redistribute and modify it under the terms of the
GNU General Public License.

=head1 COPYRIGHT

Copyright 1995-2000, Mike Heins. All rights reserved except as in the
license.

=cut

=head1 AUTHOR

Mike Heins, <mikeh@minivend.com>. Please do not contact the author for
direct help with the system. Use the Minivend mail lists:

	minivend-users@minivend.com (English, subscribe at majordomo@Minivend.com)
	minivend-de@minivend.com    (Deutsch, subscribe at majordomo@Minivend.com)

General information and documentation for Minivend is at:

	http://www.minivend.com

=head1 ACKNOWLEDGEMENTS

Original author of the Vend was Andrew Wilcox. MiniVend was based
on Vend 0.2, with portions from Vend 0.3; both were produced in 1995.

Contributions to MiniVend have been made by:

    Andreas Koenig         
    Birgitt Funk           
    Bob Jordan             
    Brian Bullen           
    Bruce Albrecht         
    Christian Mueller
    Don Grodecki           
    Frank Bonita           
    Gunnar Hellekson       
    Heinz Wittenbecher     
    Keiko                    
    Jochen Wiedmann
    Larry Leszczynski
    Marc Austin
    Michael McCune
    Mike Frager
    Raj Goel
    Stefan Hornburg   
    Tim Baverstock
    William Dan Terry
    many others       

and, of course, the entire Perl team without whom MiniVend could not exist.

=cut

__END__

