#!/usr/bin/perl
#==============================================================================
#
# Name:		pd.cgi (Postcard Direct)
#
# Author:	Peter Sundstrom (Ginini Technologies Limited)
#
# Source:	http://postcard-direct.com/
#
# Version:	6.1.0
#
# Copyright:	(c)1999-2007 Peter Sundstrom. 
#		All rights reserved.
#
#		See http://postcard-direct.com/#licence
#		for licence details.
#
#==============================================================================

BEGIN {
	require CGI::Carp;

        sub handle_errors {
                my $msg=shift;
                print qq(<html><head><title>Perl Error</title></head><body>\n);
                print qq(<font face="Verdana,Arial,Helvetica">);
                print qq(<h1>Perl Error</h1>\n);
                print qq(<h3>Error Message(s)</h3><pre>$msg</pre>\n);
                print qq(<br>Perl version: $]<br>\n);
                print qq(<br>Check <a href="http://postcard-direct.com/perlerrors.html">Perl Errors</a> for a list of common Perl errors and how to fix them.\n)
;
                print qq(<br>Also check <a href="http://postcard-direct.com/faq.html">Postcard Direct FAQ.\n);
                print qq(</body></html>\n);
        }

	#
	# Setting custom messages is only available in
	# CGI::CARP Version 1.09 and above.
	#
	if ($CGI::Carp::VERSION < 1.09) {
		CGI::Carp->import('carpout','fatalsToBrowser');
	}
	else {
		CGI::Carp->import('carpout','fatalsToBrowser','set_message');
        	set_message(\&handle_errors);
	}

	#
	# Determine path to modules
	#
	use FindBin qw($Bin);

	if ($Bin) {
		if ($Bin eq '/') {
			$Bin='';
		}
		else {
			$Bin .= '/';
		}
	}

	#
	# Determine website name and document root
	#
	if ($ENV{DOCUMENT_ROOT}) {
		$document_root=$ENV{DOCUMENT_ROOT};
	}
	elsif ($ENV{PATH_TRANSLATED}) {
		($document_root = $ENV{PATH_TRANSLATED}) =~ s/$ENV{PATH_INFO}//g;
	}	

	$site_url="http://$ENV{SERVER_NAME}";

	require "${Bin}configs/pdconfig.txt";

	$modules="${Bin}modules";

	if ($Bin) { chdir $Bin or die "Can not change directory to $Bin\n"; }


	#
	# Redefine flock if we're on a system that doesn't support it
	#
	unless (eval 'flock(STDIN, 0),1') {
		eval 'use subs "flock"; sub flock { }';
	}

}

use lib $modules;
use File::Basename;
use Fcntl qw(:DEFAULT :flock);
use charsets;
 
#
# On some Unix servers, the webservers runs with restricted
# perms, so created files won't have read permission.
#
umask 000;

#
# If we are running an old version of Perl, need to
# ensure we have CGI 2.53 as the minimum
#
if ($] < 5.006) {
	require 'CGI-2.53.pm';
}
else {
	use CGI;
}


use vars qw(
	$pdroot
	$maxpost
	$allow_html
	$require_sender_name
	$referers
	$strict_email_check
	$midi_list
	$check_bad_users
	$check_domain
	$cache_expiry
	$design_list
	$send_button
	$sendmail
	$sendmail_queue
	$max_message
	$mode
	$plain
	$plain_text
	$check_bad_words
	$objectname
	$object_list
	$require_message
	$require_receiver_name
	$enable_logging
	$log_dir
	$style_dir
	$template_dir
	$title
	$version
);

$version='6.1.0a';

#
# Set maximum POST size and enable/disable uploads
#
$CGI::POST_MAX = $maxpost;	
$CGI::DISABLE_UPLOADS = $disable_uploads; 


#
# Create a CGI object and set script path
#
my $q = new CGI;
my $script = $q->url(-absolute=>1);


#
# Check to see if the post size has been exceeded
#
Error("Maximum POST size exceeded.",undef,"Increase the size of \$maxpost in the configuration file") if ($q->cgi_error =~ /413/);

# Unbuffer output
#
$|=1;

#
# Convert all the parameters to a hash
#
my %p = $q->Vars;

unless (%p) {
	my $text='Script called without any parameters';
	my $suggestion='You need to specify a postcard image or object';
	Error($text,undef,"$suggestion<p>Example: <a href=$script?image=/pd/images/photo.jpg>$script?image=/pd/images/photo.jpg</a></p>");
}

#
# Check to see what configuration to use and set the language.
#
$config=$p{config};


if ($config) {
	CheckBadPath($config,"Configuration file");
	CheckConfigPath("$config_dir/$config");
}
else {
	$config='pdconfig.txt';
}



$lang=$p{lang} if ($p{lang});

require "$config_dir/$config";

Error("Document Root: $document_root can not be found",undef,"Manually set \$document_root in the configuration file") unless (-d $document_root);


#
# Check to see if the script is being called from a valid location
#
AntiLeech() if ($anti_leech and not $p{'showcard'});

#
# Set appropriate URL's
#
$help = "$pdurl/help/$lang/help.html";
$midi_url = "$pdurl/" . basename($midi_dir);

#
# Set default design if none is specified
#
$p{design}='default.txt' unless $p{design};

#
# Set default title if none is specified
#
$p{title}=$title unless $p{title};

#
# Check what action has been specified
#
if ($p{'send'} or $p{'send.x'}) {
	CheckBadData();
	SendPostcard();
}
elsif ($p{'preview'} or $p{'preview.x'}) {
	CheckBadData();
	PreviewPostcard();
}
elsif ($p{'imageupload'}) {
	ProcessUpload();
}
elsif ($p{'upload'}) {
	DisplayUploadForm();
}
elsif ($p{'showcard'}) {
	ShowCard();
}
elsif ($p{'sendcards'}) {
	if (-f "$modules/mimelite.pm") {
		require mimelite;
		MIME::Lite->import();
	}
	else {
		Error("Module $modules/mimelite.pm does not exist");
	}	

	CheckStoredCards();
}
else {
	DisplayForm();
}


#####################################################################
# SUB ROUTINES START HERE
#####################################################################

#--------------------------------------------------------------------
#
# Displays the postcard input form
#
sub DisplayForm {
	Error("No postcard image or object specified",undef,"Example: <a href=$script?image=/pd/images/photo.jpg>$script?image=/pd/images/photo.jpg</a>") unless ($p{image} or $p{object});

	if ($p{image}) {
		RemoteSiteAllowed($p{image}) if ($p{image} =~ /http:/i);
	}

	if ($p{object}) {
		RemoteSiteAllowed($p{object}) if ($p{object} =~ /http:/i);
	}

	#
	# Set the form name if it is specified as a parameter
	#
	if ($p{form}) {
		CheckBadPath($p{form},"Form name");
		$form="$template_dir/$p{form}";
	}

	open FORM,$form or Error("Can not open postcard form template $form", $!,"Check the pathname or form name is correct");

	#
	# Obtain image information (if any)
	#
	if ($p{image}) {
        	if ($p{image} =~ /http:/i) {
			$image_path=GetRemoteObject($p{image});
		}
		else {
			$image_path=ObjectLocation($p{image});
			ObjectNotFound($p{image},$image_path) unless -f $image_path;
		}
	}

	#
	# Obtain object information (if any)
	#
	if ($p{object}) {
        	if ($p{object} =~ /http:/i) {
			$object_path=GetRemoteObject($p{object});
		}
		else {
			$object_path=ObjectLocation($p{object});
			ObjectNotFound($p{object},$object_path) unless -f $object_path;
		}
	}

	my $form_output;

	$subject=$p{subject} if $p{subject};

	while (<FORM>) {

		next if (/^#/);

		SizeTags();

		s!%CGI%|<PD_CGI>!$script!ig;
		s!%CONFIG%|<PD_CONFIG>!$config!ig;
		s!%IMAGE%|<PD_IMAGE>!$p{image}!ig;
		s!%OBJECT%|<PD_OBJECT>!$p{object}!ig;
		s!%TITLE%|<PD_TITLE>!$p{title}!ig;
		s!%HELP%|<PD_HELP>!$help!ig;
		s!%LANG%|<PD_LANG>!$lang!ig;
		s!%S_EMAIL%|<PD_S_EMAIL>!$p{s_email}!ig;
		s!%S_NAME%|<PD_S_NAME>!$p{s_name}!ig;
		s!%R_EMAIL%|<PD_R_EMAIL>!$p{s_email}!ig;
		s!%R_NAME%|<PD_R_NAME>!$p{s_name}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$subject!ig;
		s!%FIELD1%|<PD_FIELD1>!$p{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$p{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$p{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$p{field4}!ig;
		s!%FIELD5%|<PD_FIELD5>!$p{field5}!ig;
		s!%FIELD6%|<PD_FIELD6>!$p{field6}!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$p{message}!ig;
		s!%PDICON%|<PD_ICON>!$images/pdicon.jpg!ig;

		if (/%INCLUDE%|<PD_INCLUDE>/i) {
			my $include=IncludeFile($p{include});
			s!%INCLUDE%|<PD_INCLUDE>!$include!ig;
		}

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		if (/%STYLENAME%|<PD_STYLENAME>/i) {
			my $stylename=Stylename();
			s!%STYLENAME%|<PD_STYLENAME>!$stylename!ig;
		}

		#
		# Design list dropdown
		#
		if (/%DESIGNS%|<PD_DESIGNS>/i) {
			$form_output .= DropDown($design_list);
			s/%DESIGNS%|<PD_DESIGNS>//ig;
		}

		# 
		# Midi List dropdown
		#
		if (/%MIDI%|<PD_MIDI>/i) {
			my $text=Gettext('No Music',300);
			$form_output .= qq(<option value="none">$text</option>\n);
			$form_output .= DropDown($midi_list);

			s/%MIDI%|<PD_MIDI>//ig;
		}

		#
		# Object list dropdown
		#
		if (/%OBJECTS%|<PD_OBJECTS>/i) {
			my $text=Gettext('None',303);
			$form_output .= qq(<option value="none">$text</option>\n);
			$form_output .= DropDown($object_list);

			s/%OBJECTS%|<PD_OBJECTS>//ig;
		}

		#
		# Include file list dropdown
		#
		if (/%INCLUDES%|<PD_INCLUDES>/i) {
			my $text=Gettext('None',303);
			$form_output .= qq(<option value="none">$text</option>\n);
			$form_output .= DropDown($include_list);

			s/%INCLUDES%|<PD_INCLUDES>//ig;
		}

		#
		# Sending date selection
		#
		if (/%DATE%|<PD_DATE>/i) {
			$form_output .= DateSelection();
			s/%DATE%|<PD_DATE>//ig;	
		}
		

		$form_output .= $_;
	}
	
	close FORM;

	my $charset = $charset{$lang} || $charset{'default'};
	print $q->header(-charset=>$charset);
	print $form_output;
}

#--------------------------------------------------------------------
#
# Displays the upload form
#
sub DisplayUploadForm {

	Error("Image uploads disabled",undef,"Enable uploads in the configuration file") if $disable_uploads;

	open FORM,$upload_form or Error("Can not open upload form template $upload_form", $!,"Check the pathname or form name is correct");

	my $form_output;

	while (<FORM>) {

		next if (/^#/);

		s!%CGI%|<PD_CGI>!$script!ig;
		s/%TITLE%|<PD_TITLE>/$p{title}/ig;

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		$form_output .= $_;
	}
	
	close FORM;

	my $charset = $charset{$lang} || $charset{'default'};
	print $q->header(-charset=>$charset);
	print $form_output;
}


#--------------------------------------------------------------------
#
# Process the uploaded image
#
sub ProcessUpload {

	my $file = $p{file};
	my $fh = $q->upload('file');

	Error("Image uploads disabled",undef,undef) if $disable_uploads;
	InputError("Image to upload was not specified",700) unless $file;

	#
	# Generate a semi random name for the upload image
	#
	$file =~ m/(\w+)(\.\w+$)/;
	my $ext = $2;
	my $filename = $1 . '-' . time() . rand() . $ext;
	my $upload_file="$upload_dir/$filename";

	#
	# Check the file has an allowed extention.
	#
	InputError("Upload file type is not allowed",701,$ext) unless ($ext =~ /$upload_types/i);

	open STORE, ">$upload_file" or Error("Can not write to $upload_file",undef,$!);
	binmode STORE ;
	print STORE <$fh>;
	close STORE;


	Error("The upload failed",undef,"Perhaps the image was empty or the wrong path was specified") if -z $upload_file;

	# 
	# If form and/or lang was specified, add it to the URL
	#
	my $parms;
	$parms  = ';form=' . $p{form} if $p{form};
	$parms .= ';lang=' . $p{lang} if $p{lang};
	$parms .= ';title=' . $p{title} if $p{title};

	my $uploadurl = "$pdurl/" . basename($upload_dir);

	if ($parms) {
		print "Location: $script?image=$uploadurl/$filename$parms\n\n";
	}
	else {
		print "Location: $script?image=$uploadurl/$filename\n\n";
	}

	#
	# Clean old uploaded images.
	#
	Cleanup($upload_dir,$upload_age) if ($upload_age > 0);

}

#--------------------------------------------------------------------
# Display postcard preview.
#
sub PreviewPostcard {

	RemoteSiteAllowed($p{image}) if ($p{image} =~ /http:/i);
	RemoteSiteAllowed($p{object}) if ($p{object} =~ /http:/i);

	ValidateForm();

	#
	# Work out path location if image or object is local
	#
	if ($p{image}) {
		if ($p{image} !~ /http:/i) {
			$image_path=ObjectLocation($p{image});
		}
		else {
			$image_path=GetRemoteObject($p{image});
		}
	}

	if ($p{object}) {
		if ($p{object} !~ /http:/i) {
			$object_path=ObjectLocation($p{Object});
		}
		else {
			$object_path=GetRemoteObject($p{object});
		}
	}

	my $text = ReadHTML();

	my $charset = $charset{$lang} || $charset{'default'};
	print $q->header(-charset=>$charset);
	print "$text\n";

}

#--------------------------------------------------------------------
# Mails the postcard to the receipient.
#
sub SendPostcard {

	ValidateForm();

	#
	# Import the MIME::Lite module
	#
	if (-f "$modules/mimelite.pm") {
		require mimelite;
		MIME::Lite->import();
	}
	else {
		Error("Module $modules/mimelite.pm does not exist");
	}	


	#
	# Work out path location if image or object is local
	#
	if ($p{image}) {
		if ($p{image} !~ /http:/i) {
			$image_path=ObjectLocation($p{image});
		}
		else {
			RemoteSiteAllowed($p{image}) if ($p{image} =~ /http:/i);
			$image_path=GetRemoteObject("$p{image}");
		}
		$image_type=ImageType($image_path);
	}


	if ($p{object}) {
		if ($p{object} !~ /http:/i) {
			$object_path=ObjectLocation($p{Object});
		}
		else {
			RemoteSiteAllowed($p{object}) if ($p{object} =~ /http:/i);
			$object_path=GetRemoteObject($p{object});
		}
	}


	#
	# Determine whether to use default sender or user supplied sender
	#
	if ($p{s_name}) {
		$sender=qq("$p{s_name}" <$p{s_email}>);
	}
	else {
		$sender="<$p{s_email}>";
	}

	$receiver=qq("$p{r_name}"); 

	#
	# If the sender has requested a copy, add them as a bcc address.
	#
	$bcc .= "$sender" if ($p{sendcopy} eq 'on');

	#
	# Multiple recipient addresses are either seperated by new lines
	# or by commas.
	#
	my @addresses = split(/,|\r*\n/,$p{r_email});

	if ($#addresses > 0) {
		InputError("Maximum number of recipients exceeded",111) if ( $#addresses > $max_recipients);

		$receiver .= " <$addresses[0]>,";

		for my $index (1..$#addresses) {
			if ($p{bcc} eq 'on') {
				$bcc .= " <$addresses[$index]>,";
			}
			else {
				$receiver .= " <$addresses[$index]>,";
			}

		}

		$bcc =~ s/,$// if $bcc;
		$receiver =~ s/,$// if $receiver;
	}
	else {
		$receiver .= " <$p{r_email}>";
	}


	#
	# Make sure we preserve any spaced indenting.
	#
	$p{message} =~ s/  /&nbsp;&nbsp;/g;

	#
	# If we are running in 'traditional' mode, store the postcard
	# on the server, otherwise send the postcard either as an 
	# HTML file with image embedded or HTML with just 
	# the URL of the image (web method).
	#

	if ($p{method} eq 'web') {
		CreateWebMail();
	}
	elsif ($p{method} eq 'traditional' or $mode eq 'traditional') {
		CreateTraditionalMail();
		ExpireCards();
	}
	else {
		CreateDirectMail();
	}

	#
	# Remove non essential MIME headers to help
	# broken MUA's that don't correctly support MIME
	#
	$msg->scrub;

	#
	# If a sending date has been specified, if it is in
	# the future, store the card to be sent on that date,
	# otherwise send it now.
	#
	my $now=1;
	my $sentmsg = Gettext('has been sent',304);

	unless ($p{sendnow}) {
		if ($p{day} or $p{senddate}) {
			require datesimple;
			Date::Simple->import('date','d8','today');
	
			my $fulldate;

			if ($p{senddate}) {
				$fulldate = $p{senddate};
			}
			else {
				$fulldate = "$p{year}$p{month}$p{day}";
			}

			my $today = today();
			$today = $today->as_d8;
	
			if ($fulldate > $today) {

				require pddates;
				StoreCard($msg,$fulldate);
				$now=0;
				$sentmsg = Gettext('will be sent',305);
				$fulldate =~ m/(\d{4})(\d\d)(\d\d)/;
				$sentmsg .= " $3 $pdmonth{$lang}[$2 - 1] $1";
			}
		}
	}

	#
	# If the postcard is to be sent now, check which method to
	# use for sending.
	#
	if ($now) {
		if ($sendmail) {
			SendUsingSendmail($msg);
		}
		else {
			SendUsingSMTP($msg);
		}
	}

	#
	# Display the final page notifying the user that
	# the postcard is successfully on its way.
	#
	$subject=$p{subject} if ($p{subject});

	open SENT,$sent or Error("Can not open $sent",$!);

	my $charset = $charset{$lang} || $charset{'default'};
	print $q->header(-charset=>$charset);

	while (<SENT>) {
		next if /^#/;

		s/%IMAGE%|<PD_IMAGE>/$p{image}/ig;
		s/%OBJECT%|<PD_OBJECT>/$p{object}/ig;
		s/%TITLE%|<PD_TITLE>/$p{title}/ig;
		s/%SENDER%|<PD_SENDER>/$p{s_name}/ig;
		s/%SENDER_EMAIL%|<PD_SENDER_EMAIL>/$p{s_email}/ig;
		s/%RECIPIENT%|<PD_RECIPIENT>/$p{r_name}/ig;
		s/%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>/$p{r_email}/ig;
		s/%SUBJECT%|<PD_SUBJECT>/$subject/ig;
		s/%MESSAGE%|<PD_MESSAGE>/$p{message}/ig;
		s/%IMAGES%|<PD_IMAGES>/$images/ig;
		s/%SENT%|<PD_SENT>/$sentmsg/ig;
		s/%FIELD1%|<PD_FIELD1>/$p{field1}/ig;
		s/%FIELD2%|<PD_FIELD2>/$p{field2}/ig;
		s/%FIELD3%|<PD_FIELD3>/$p{field3}/ig;
		s/%FIELD4%|<PD_FIELD4>/$p{field4}/ig;
		s/%FIELD5%|<PD_FIELD5>/$p{field5}/ig;
		s/%FIELD6%|<PD_FIELD6>/$p{field6}/ig;

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}


		print;
	}

	close SENT ;

	PostcardLog() if $enable_logging;

	#
	# See if there are any cards stored for a future date that are 
	# due to be sent now.
	#
	CheckStoredCards();	
}

#-----------------------------------------------------------------------------
# Send mail using sendmail
#
sub SendUsingSendmail {
	my $object = shift;

	if ($sendmail_path) { 
		Error("Sendmail Path: $sendmail_path not found") unless -x $sendmail_path;
	}
	else {
		@sendmail_dir=grep {-x "$_/sendmail"} split(/,/,'/usr/lib,/usr/sbin,/bin,/usr/bin,/usr/local/bin');
		Error("Can not locate sendmail in /usr/lib, /usr/sbin, /usr/bin, /bin or /usr/local/bin",undef,"Try setting the SMTP mail options.") unless (@sendmail_dir);
		$sendmail_path="$sendmail_dir[0]/sendmail";
	}

	if ($sendmail_queue) {
		$sendmail_flags='-t -oi -oem -odq';
	}
	else {
		$sendmail_flags='-t -oi -oem';
	}

	my $from = $object->get("from");

	if ($from =~ /(\S+\@\S+)/) {
		$from=$1;
	}


	MIME::Lite->send('sendmail',"$sendmail_path -f \"$from\" $sendmail_flags") or Error("An error has occured trying to send the postcard.  Please try again later.",$!);
	$object->send or Error("Sendmail error", $!);
}

#-----------------------------------------------------------------------------
# Send mail using SMTP
#
sub SendUsingSMTP {
	my $object = shift;

	Error("No SMTP mail server has been defined") unless $smtp_server;

	if (-f "$modules/smtp.pm") {
		require smtp;
		import Mail::SMTP();		
	}
	else {
		Error("SMTP module $modules/smtp.pm does not exist");
	}


	#
	# Initialise hash so that we don't store any previous values
	#
	my %mail;

	#
	# Convert mail headers to mail hash
	#
	foreach my $header (split(/\n/,$object->header_as_string)) {
		my ($type,$value) = split(/:/,$header);
		$mail{$type}=$value;
	}

		
	$mail{smtp} = $smtp_server;
	$mail{message} = $object->body_as_string;

	($status,$diag) = sendmail(%mail);

	my ($text,$suggestion);

	unless ($status == 1) {
		if ($status == -1) {
			$text = Gettext('Bad From address:',500);
			Error("$text $p{s_email}",$diag,undef);
		}

		if ($status == -2) {
			$text = Gettext('Failed to connect to SMTP server',501);
			$suggestion = Gettext('Check that you have specified the correct  SMTP server name',601);
			Error("$text $smtp_server",$diag,$suggestion);
		}

		if ($status == -3) {
			$text = Gettext('SMTP server not found:',502);
			$suggestion = Gettext('Check that you have specified the correct  SMTP server name',601);
			Error("$text $smtp_server",$diag,$suggestion);
		}

		if ($status == -4) {
			$text = Gettext('Failed to connect to SMTP server',501);
			$suggestion = Gettext('Check the diagnostic message',600);
			Error("$text $smtp_server",$diag,$suggestion);
		}

		if ($status == -5) {
			$text = Gettext('SMTP server error',503);
			$suggest = Gettext('Check the diagnostic message',600);
			Error("$text $smtp_server",$diag,$suggestion);
		}

		if ($status == -6) {
			$text = Gettext('Recipient error:',504);
			$suggest = Gettext('Check the diagnostic message',600);
			Error("$text $p{r_email}",$diag,$suggestion);
		}

		if ($status == -7) {
			$text = Gettext('Error sending message',505);
			$suggest = Gettext('Check the diagnostic message',600);
			Error($text,$diag,$suggestion);
		}

		Error($diag,undef,undef);
	}
}

#-----------------------------------------------------------------------------
# Validates and sanitises the form fields.
#
sub ValidateForm {
	#
	# Set default sender if specified in the configuration file
	#
	$p{s_email}=$sender_email if ($sender_email and ! $p{s_email});
	$p{s_name}=$sender_name if ($sender_name and ! $p{s_name});

	InputError("You must include the email address of the person you are sending to",100) unless $p{r_email};

	#
	# Do an RFC822 check on the address format.
	#
        if ( -f "$modules/emailvalid.pm") {
		require emailvalid;
		Email::Valid->import();
	}
	else {
		Error("Module $modules/emailvalid.pm does not exist");
	}	

	#
	# Process all mail addresses (comma separated)
	#
	#$p{r_email} =~ s/\r//g;
	#$p{r_email} =~ s/\n//g;

	for $mailaddress (split(/,|\r*\n/,$p{r_email})) {
		InputError("Recipient email address:",102,"$mailaddress <I>$result</I>") if ($result=CheckAddress($mailaddress));
	}

	$p{s_email} = $sender_email if ($sender_email and not $p{s_email});
	$p{s_name} = $sender_name if ($sender_name and not $p{s_name});

	InputError("You need to include a message",103) if (! $p{message} and $require_message);

	InputError("Message size too large",110) if (($require_message) and length($p{message}) > $max_message);

	InputError("You need to include the name of the person you are sending the postcard to",104) if (! $p{r_name} and $require_receiver_name);

	InputError("You need to include your email address as the sender",105) if (! $p{s_email});

 	InputError("You need to include your name as the sender",106) if (! $p{s_name} and $require_sender_name);

	InputError("Your email address:", 107,"$p{s_email}.<br> $result") if ($result=CheckAddress("$p{s_email}"));


	if ($check_bad_users) {
		for $mailaddress (split(/,|\r*\n/,$p{s_email})) {
			InputError("Sender Email address is banned:",108,$mailaddress) if BadUser($mailaddress,'sender');
		}

		for $mailaddress (split(/,|\r*\n/,$p{r_email})) {
			InputError("Recipient Email address is banned:",108,$mailaddress) if BadUser($mailaddress,'recipient');
		}
	}

	if ($check_bad_words) {
		InputError("Unacceptable words in the postcard message",109) if BadWords($p{message});
	}

	#
	# If a sending date has been selected, ensure the date is valid and in the future.
	#
	unless ($p{sendnow}) {
		if ($p{day}) {
			require datesimple;
			Date::Simple->import('date','d8','today');
	
			my $fulldate = "$p{year}$p{month}$p{day}";
			my $date = d8($fulldate);

			InputError("Invalid send date specified",800,undef) unless $date;

			my $today = today();
			$today = $today->as_d8;

			InputError("The send date can not be in the past",801,undef) if ($fulldate < $today);
	
			my $max_date = today() + $future_card_age;
			$max_date= $max_date->as_d8;
			InputError("The send date is too far in the future.  Maximum days allowed is ",802,$future_card_age) if ($fulldate > $max_date);

		}
	}

	#
 	# Store a non-escaped copy of the subject for the email subject line
	#
	if ($p{subject}) {
		$mail_subject = $p{subject};
	}
	else {
		$mail_subject = $subject;
	}

	#
	# Escape any potential HTML input to avoid XSS exploits
	#
	%op = %p;
	my @fields =  qw(subject title s_name r_name field1 field2 field3 field4 field5 field6); 

	foreach my $field (@fields) {
		$p{$field} = $q->escapeHTML($p{$field});
	}

	$plain_message = $p{message};
	$plain_message =~ s/<[^>]*>//gs;

	#
	# Escape and strip (simplistic) HTML from postcard message if HTML is disabled
	#
	unless ($allow_html) {
		$p{message} =~ s/<[^>]*>//gs;
		$p{message} = $q->escapeHTML($p{message});
	}


	#
	# Convert end of line markers to HTML <BR> tag
	#
	$p{message} =~ s/\r//g;
	$p{message} =~ s/\n/<br>/g;

	#
	# Make sure we preserve any indenting.
	#
	$p{message} =~ s/  /&nbsp;&nbsp;/g;

	# 
	# Ensure we don't have any stray nulls on values where 
	# they may have been defined more than once.
	#
	$p{object} =~ s/\0//g;
	$p{image} =~ s/\0//g;
	$p{include} =~ s/\0//g;
	
}

#-----------------------------------------------------------------------------
# Read plain design template
#
sub ReadPlain {
	my $text;

	open POSTCARD,"$design_dir/plain.txt" or Error("Can not open postcard design $design_dir/plain.txt", $!);

	$subject=$p{subject} if $p{subject};

	while (<POSTCARD>) {
		next if (/^#/);

		s/%TITLE%|<PD_TITLE>/$op{title}/ig;
		s/%SENDER%|<PD_SENDER>/$op{s_name}/ig;
		s/%SENDER_EMAIL%|<PD_SENDER_EMAIL>/$op{s_email}/ig;
		s/%RECIPIENT%|<PD_RECIPIENT>/$op{r_name}/ig;
		s/%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>/$op{r_email}/ig;
		s/%SUBJECT%|<PD_SUBJECT>/$mail_subject/ig;
		s/%MESSAGE%|<PD_MESSAGE>/$plain_message/ig;
		s/%BACK%|<PD_BACK>//ig;
		s/%SEND%|<PD_SEND>//ig;
		
		if (/%INCLUDE%|<PD_INCLUDE>/i) {
			my $include=IncludeFile($p{include});
			s!%INCLUDE%|<PD_INCLUDE>!$include!ig;
		}

		$text .= "$_";
	}

	close POSTCARD;

	return $text;
}

#-----------------------------------------------------------------------------
# Reads the appropriate html template and substitutes the appropriate values
# for the variables. 
# 
# If the message is being sent, then we must look for any additional images
# in the template and generate a CID for each one and keep track of the 
# names of each one.
#

sub ReadHTML {
	my $text;

	open POSTCARD,"$design_dir/$p{design}" or Error("Can not open postcard design $design_dir/$p{design}", $!,"Check pathnames and that the design file exists");

	$subject=$p{subject} if ($p{subject});

	$cid=GenerateCID();

	#
	# Convert end of line chars to BR tags.
	#
	$p{message} =~ s/\r*\n/<br>/g;

	while (<POSTCARD>) {
		next if (/^#/);

		if ($p{'preview'} or $p{'preview.x'}) {
			$return_button=Gettext('Return to Postcard Form',302);
			s/%IMAGE%|<PD_IMAGE>/$p{image}/ig;

			s!%BACK%|<PD_BACK>!<form><input type="button" class="button" value="$return_button" onClick="history.go(-1);return true"></form>!ig;

			SizeTags();

			if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
				my $stylesheet=Stylesheet();
				s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
			}

			if (/%STYLENAME%|<PD_STYLENAME>/i) {
				my $stylename=Stylename();
				s!%STYLENAME%|<PD_STYLENAME>!$stylename!ig;
			}

			if (/%SEND%|<PD_SEND>/i) {
				$SendText .= SendFromPreview();
				s!%SEND%|<PD_SEND>!$SendText!ig;
			}

			if (/%INCLUDE%|<PD_INCLUDE>/i) {
				if ($p{include} ne 'none' and $p{include}) {
					my $include=IncludeFile($p{include});
					s!%INCLUDE%|<PD_INCLUDE>!$include!ig;
				}
			}

			if (/%MIDI%|<PD_MIDI>/i) {
				if ($p{midi} ne 'none' and $p{midi}) {
					Error("Midi file not found: $midi_dir/$p{midi}") if (! -f "$midi_dir/$p{midi}");
					$text .= qq(<noembed><bgsound src="$midi_url/$p{midi}" autostart="true" loop="true"></bgsound> </noembed>\n);
					$text .= qq(<embed src="$midi_url/$p{midi}" hidden="true" autostart="true" loop="true"></embed>\n);
					next;
				}
			}

			if (/%OBJECT%|<PD_OBJECT>/i) {
				if ($p{object} ne 'none' and $p{object}) {
					Error("Object file not found: $document_root/$p{object}") if (! -f "$document_root/$p{object}" and $p{object} !~ /http:/i);
					s!%OBJECT%|<PD_OBJECT>!$p{object}!ig;
				}
			}

		}
		else {
			s/%BACK%|<PD_BACK>//ig;
			s/%SEND%|<PD_SEND>//ig;

			SizeTags();

			if (/%INCLUDE%|<PD_INCLUDE>/i) {
				if ($p{include} ne 'none' and $p{include}) {
					my $include=IncludeFile($p{include});
					s!%INCLUDE%|<PD_INCLUDE>!$include!ig;
				}
			}

			if (/%MIDI%|<PD_MIDI>/i) {
				if ($p{midi} ne 'none' and $p{midi}) {
					Error("Midi file not found: $midi_dir/$p{midi}") if (! -f "$midi_dir/$p{midi}");
					$midi_cid=GenerateCID() unless $midi_cid;
					s!%MIDI%|<PD_MIDI>!<noembed><bgsound src="cid:$midi_cid" autostart="true"></bgsound></noembed> <embed src="cid:$midi_cid" hidden="true" autostart="true"></embed>!ig;
				}
			}

			#
			# If an object is local, generate a CID for it
			# otherwise it is considered to be a remote object
			#
			if (/%OBJECT%|<PD_OBJECT>/i) {
				if ($p{object} ne 'none' and $p{object}) {
					if ($p{object} =~ /http:/i) {
						s!%OBJECT%|<PD_OBJECT>!$p{object}!ig;
					}
					else {
						if (/img src/i) {
							s!%OBJECT%|<PD_OBJECT>!$p{object}!ig;
						}
						else {
							$object_cid=GenerateCID() unless $object_cid;
							s!%OBJECT%|<PD_OBJECT>!cid:$object_cid!ig;
						}
			
					}
				}
			}

			if (/PD_FIELD|%FIELD%/i) {
				chomp($p{field1});	
				s!%FIELD1%|<PD_FIELD1>!$p{field1}!ig;
				s!%FIELD2%|<PD_FIELD2>!$p{field2}!ig;
				s!%FIELD3%|<PD_FIELD3>!$p{field3}!ig;
				s!%FIELD4%|<PD_FIELD4>!$p{field4}!ig;
				s!%FIELD5%|<PD_FIELD5>!$p{field5}!ig;
				s!%FIELD6%|<PD_FIELD6>!$p{field6}!ig;
			}

			if (/img src/i and ! (/%IMAGE%|<PD_IMAGE>/i)) {
				$image_cid[$extra_images]=GenerateCID();
				s/(.*<img src=)\"*//i;
				$Startline = $1;

				if (/\"*\s+.*?>/) {
					s/\"*(\s+.*?>)(.*$)// && ($Attributes=$1,$Extra=$2);
				}
				else {
					s/(\"*>)(.*$)// && ($Attributes=$1,$Extra=$2);
				}


				#
				# Strip off http component, if any
				#
				s!$site_url!!;

				$ImageURL = $_;
				$extra_image_path[$extra_images]=ObjectLocation($ImageURL);
				$extra_image_type[$extra_images]=ImageType("$extra_image_path[$extra_images]");
				$text .= "${Startline}\"cid:$image_cid[$extra_images]\" $Attributes $Extra\n";
				$extra_images++;
				s/.*//;
			}
			elsif (/body.*background=\"?\s+\"?/i) {
				$image_cid[$extra_images]=GenerateCID();
				s/(.*background=)\"*//i;
				$Startline = $1;

				if (/\"*\s+.*?>/) {
					s/\"*(\s+.*?>)(.*$)// && ($Attributes=$1,$Extra=$2);
				}
				else {
					s/(\"*>)(.*$)// && ($Attributes=$1,$Extra=$2);
				}

				$ImageURL = $_;
				$extra_image_path[$extra_images]=ObjectLocation($ImageURL);
				$extra_image_type[$extra_images]=ImageType("$extra_image_path[$extra_images]");
				$text .= "${Startline}\"cid:$image_cid[$extra_images]\" $Attributes\n";
				$extra_images++;
				s/.*//;
			}
			else {
				s/%IMAGE%|<PD_IMAGE>/cid:$cid/ig;
			}
		}

		s!%SEND%|<PD_SEND>!!ig;
		s!%MIDI%|<PD_MIDI>!!ig;
		s!%OBJECT%|<PD_OBJECT>!!ig;
		s!%INCLUDE%|<PD_INCLUDE>!!ig;
		s!%TITLE%|<PD_TITLE>!$p{title}!ig;
		s!%SENDER%|<PD_SENDER>!$p{s_name}!ig;
		s!%SENDER_EMAIL%|<PD_SENDER_EMAIL>!$p{s_email}!ig;
		s!%RECIPIENT%|<PD_RECIPIENT>!$p{r_name}!ig;
		s!%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>!$p{r_email}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$subject!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$p{message}!ig;
		s!%FIELD1%|<PD_FIELD1>!$p{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$p{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$p{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$p{field4}!ig;
		s!%FIELD5%|<PD_FIELD5>!$p{field5}!ig;
		s!%FIELD6%|<PD_FIELD6>!$p{field6}!ig;

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		if (/%STYLENAME%|<PD_STYLENAME>/i) {
			my $stylename=Stylename();
			s!%STYLENAME%|<PD_STYLENAME>!$stylename!ig;
		}

		$text .= $_;
	}

	close POSTCARD;
	
	return $text;
}

#-----------------------------------------------------------------------------
# Wraps the postcard message to the specified width.
#
sub WrapText {
	my $message=shift;

	require Text::Wrap;
	Text::Wrap->import('wrap');
	$text::Wrap::columns = $WrapText;
	$text::Wrap::columns = $WrapText;
	return wrap("","",$message);
}

#-----------------------------------------------------------------------------
# Checks to see if there are any banned user email addresses.
#
sub BadUser {
	my ($address,$type)=@_;

	my $found=0;

	open BADUSERS,$badusers_list  or Error("Can not open baduser list $badusers_list",$!);

	while (<BADUSERS>) {
		next if (/^#/ or ! /\w+/);
		chomp;
		my ($email,$type)=split(/\|/);

		if ($address =~ /$email/ and ($type eq 'all' or $type eq $type)) {
			$found=1;
			last;
		}
	}

	close BADUSERS;

	return $found;
}

#-----------------------------------------------------------------------------
# Checks if there are any banned words in the postcard message.
#
sub BadWords {
	my $message=shift;

	my $found=0;

	open BADWORDS,$badwords_list or Error("Can not open badwords list $badwords_list",$!);

	while (<BADWORDS>) {
		next if (/^#/ or ! /\w+/);
		chomp;
		s/\r//g;
		
		if ($message =~ /\b$_\b/i) {
			$found=1;
			last;
		}
	}

	close BADWORDS;

	return $found;
}

#-----------------------------------------------------------------------------
sub ObjectNotFound {
	my ($object,$objectpath) = @_;
	 
	my $message="<p>Postcard image/object not found</p>URL: $object<br>Directory path: $objectpath";

	my $suggestion;

	if ($object =~ /^\//) {
		$suggestion=qq(Is <a href="$site_url$object">$site_url$object</a> actually viewable?);
		$suggestion.=qq(If the image is viewable, then try manually setting \$document_root in the configuration file);
	}
	else {
		$suggestion=qq(You need to use an absolute URL path, ie:  /$object instead of $object);
	}

	Error($message,undef,$suggestion);
}

#-----------------------------------------------------------------------------
# Checks to see if a remote image/object is from an allowable site.
#
sub RemoteSiteAllowed {
	my $object=shift;
	my $found=0;

	#
	# Extract hostname from URL
 	#	
	$object =~ m!http://(.*?)/!i;
	my $sitename=$1;

	#
	# Check to see if the remote site is in the allowable list
	#
	open REMOTE,$remote_sites  or Error("Can not open remote sites list $remote_sites",$!);

	while (<REMOTE>) {
		next if (/^#/ or ! /\w+/);
		if (/$sitename/) {
			$found=1;
			last;
		}
	}

	close REMOTE;

	Error("$sitename is not an allowable remote site",undef,"Add $sitename to the remotesites.txt file") unless $found;
}

#-----------------------------------------------------------------------------
# Inserts image width and height attributes.
#
sub SizeTags {
	if (/%HEIGHT%|<PD_HEIGHT>/i or /%WIDTH%|<PD_WIDTH>/i) {
		if ( $] < 5.005 ) {
			if (-f "$modules/size-5.004.pm" ) {
				require 'size-5.004.pm';
			}
			else {
				Error("Module $modules/size-5.004.pm does not exist");
			}
			($width,$height) = imgsize($image_path) unless $width;
		}
		else {
			if (-f "$modules/size.pm") {
				require size;
				Image::Size->import();
			}
			else {
				Error("Module $modules/size.pm does not exist");
			}	

			($width,$height,$error) = imgsize($image_path) unless $width;
			Error("Image size error: $error") unless $width;
		}

		s!%WIDTH%|<PD_WIDTH>!$width!ig; 
		s!%HEIGHT%|<PD_HEIGHT>!$height!ig; 
	}
}

#-----------------------------------------------------------------------------
# Retrieves an object/image from a remote site if the object does exist in the
# local cache and has not expired.
#
sub GetRemoteObject {
	my $object=shift;

	my $objectname=basename($object);
	my ($file,$now,$mtime,$age);
		
	#
	# Clean out old files from the cache
	#
	Cleanup($cache_dir,$cache_age) if ($cache_age or $cache_age == 0);

	#
	# Check to see if the cached version is still current
	#
	if (-f "$cache_dir/$objectname") {
		$now=time();
		$mtime=(stat("$cache_dir/$objectname"))[9];
		$age=int(($now - $mtime) / 60 / 60 / 24);
		return "$cache_dir/$objectname" if ($age < $cache_expiry);
	}


	#
	# Import required modules from LWP
	#
	if (-f "$modules/simple.pm") {
        	require simple;
        	require status;
        	LWP::Simple->import();
		LWP::Status->import();
	}
	else {
		Error("Module $modules/simple.pm does not exist");
	}
	
	$file=get($object);


	if (defined($file)) {
		open CACHE,">$cache_dir/$objectname" or Error("Can not open $cache_dir/$objectname", $!,"Check the permissions on the directory $cache_dir");
		binmode(CACHE);
		print CACHE $file;
		close CACHE;
	}
	else {
		Error("Can not retrieve $file",$!,"Check that the URL is correct and that you are not behind a firewall");
	}


	return "$cache_dir/$objectname";
}
	
#-----------------------------------------------------------------------------
# Creates the mail format for sending "direct" postcards.  This means 
# embedding any images/objects in the mail body.
#
sub CreateDirectMail {
	my $i=0;
	my $text;

	$extra_images=0;

	$text = ReadHTML();
	$plain_text = ReadPlain();

	#
	# Build a multipart/alternative MIME object
	#
	$msg = new MIME::Lite(
		From	=> $sender,
		To	=> $receiver,
		Subject => $mail_subject,
		Type    => 'multipart/alternative',
		);

	$msg->add("Bcc" => $bcc) if $bcc;
	$msg->add("Reply-To" => $reply_to) if $reply_to;
	$msg->add("Errors-To" => $sender);
	$msg->delete('Date'); 
	$msg->add('X-Software' => "http://postcard-direct.com");
	$msg->replace('X-Mailer' => "Postcard Direct ($version)");
	$msg->add('X-Origin' => $ENV{'REMOTE_ADDR'});

	if ($p{'receipt'} eq 'on') {
		$msg->add('Read-Receipt-To' => $sender);
		$msg->add('Disposition-Notification-To' => $sender);
	}


	if ($add_message_id) {
		my $id = GenerateCID();
		$msg->add('Message-ID' => "<$id\@domain>");
	}

	$plain = $msg->attach(
		Type	=> 'text/plain',
		Data	=> "$plain_text"
		);

	$html = $msg->attach(Type  =>'multipart/related');

	my $charset = $charset{$lang} || $charset{'default'};

	$html->attach(
		Type	=> 'text/html; charset=$charset',
		Data	=> $text
		);

	#
	# Attach an image if one exists
	#
	if ($p{image}) {
		$html->attach(
			Type	=> "image/$image_type",
			Path	=> $image_path,
			Id	=> "<$cid>"
			);
	}

	#
	# Attach the midi file (if chosen)
	#
	if ($p{midi} ne 'none' and $p{midi}) {
		Error("Midi file not found: $midi_dir/$p{midi}") if (! -f "$midi_dir/$p{midi}");

		$html->attach(
			Type		=> "audio/mid",
			Encoding	=> 'base64',
			Path		=> "$midi_dir/$p{midi}",
			Id		=> "<$midi_cid>"
			);

	}

	#
	# Attach any object files if they are local
	#
	if ($p{object} ne 'none' and $p{object} !~ /http:/i and $p{object} ne '') {

		$object_type=ObjectType($p{object});

		$html->attach(
			Type		=> "$object_type",
			Encoding	=> 'base64',
			Path		=> "$document_root$p{object}",
			Id		=> "<$object_cid>"
			);
	}


	#
	# Attach any additional images
	#
	if ($extra_images > 0) {

		foreach ($i=0; $i < $extra_images; $i++) {
			chomp $extra_image_path[$i];

			$html->attach(
			Type		=> "image/$extra_image_type[$i]",
			Path		=> "$extra_image_path[$i]",
			Id		=> "<$image_cid[$i]>"
			);
		}
	}
}

#-----------------------------------------------------------------------------
# Creates the mail format for sending to web mail accounts.  This differs from
# the direct method as there are no embedded images.  All images/objects are
# referenced the from website the postcard is sent from.
#
sub CreateWebMail {
	open POSTCARD,"$design_dir/$p{design}"  or Error("Can not open postcard design $design_dir/$p{design}", $!);

	$subject=$p{subject} if $p{subject};


	my $text;

	while (<POSTCARD>) {
		next if (/^#/);

		#
		# Make sure additional images have a full URL
		#
		if (/img src/i and ! (/%IMAGE%|<PD_IMAGE>|%OBJECT%|<PD_OBJECT>/i) and ! /img src=\"?http:/i) {
			s!(<img src=\"?)(\S+)(.*)!$1$site_url$2$3!i;
		}


		if (/body.*background/i and ! /http:/i) {
			s!(background=\"?)(\S+)(.*)!$1$site_url$2$3!i;
		}

		SizeTags();

		#
		# Add host URL if not specified
		#
		if (/%IMAGE%|<PD_IMAGE>/i) {
			if ($p{image} !~ /http:/i) {
				s!%IMAGE%|<PD_IMAGE>!$site_url$p{image}!ig;
			}
			else {
				s!%IMAGE%|<PD_IMAGE>!$p{image}!ig;
			}
		}

		if (/%OBJECT%|<PD_OBJECT>/i) {
			if ($p{object} !~ /http:/i) {
				s!%OBJECT%|<PD_OBJECT>!$site_url$p{object}!ig;
			}
			else {
				s!%OBJECT%|<PD_OBJECT>!$p{object}!ig;
			}
		}

		if (/%INCLUDE%|<PD_INCLUDE>/i) {
			my $include=IncludeFile($p{include});
			s!%INCLUDE%|<PD_INCLUDE>!$include!ig;
		}

		if ($p{'preview'} or $p{'preview.x'}) {
			s!%BACK%|<PD_BACK>!<form><input type="button" class="button" value="$return_button" onClick="history.go(-1);return true"></form>!ig;
		}

		if ((/%MIDI%|<PD_MIDI>/i) and $p{midi} ne 'none' and $p{midi}) {
			Error("Midi file not found: $midi_dir/$p{midi}") if (! -f "$midi_dir/$p{midi}");
			s!%MIDI%|<PD_MIDI>!<noembed><bgsound src="$site_url$midi_url/$p{midi}" autostart="true"></bgsound></noembed> <embed src="$site_url$midi_url/$p{midi}" hidden="true" autostart="true"></embed>!ig;
		}
		else {
			s/%MIDI%|<PD_MIDI>//ig;
		}

		s!%BACK%|<PD_BACK>!!ig;
		s!%SEND%|<PD_SEND>!!ig;
		s!%TITLE%|<PD_TITLE>!$p{title}!ig;
		s!%SENDER%|<PD_SENDER>!$p{s_name}!ig;
		s!%SENDER_EMAIL%|<PD_SENDER_EMAIL>!$p{s_email}!ig;
		s!%RECIPIENT%|<PD_RECIPIENT>!$p{r_name}!ig;
		s!%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>!$p{r_email}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$subject!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$p{message}!ig;
		s!%FIELD1%|<PD_FIELD1>!$p{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$p{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$p{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$p{field4}!ig;
		s!%FIELD5%|<PD_FIELD5>!$p{field5}!ig;
		s!%FIELD6%|<PD_FIELD6>!$p{field6}!ig;

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		$text .= $_;
	}

	close POSTCARD ;


	# Now create the mail structure
	
	my $charset = $charset{$lang} || $charset{'default'};

	$msg = new MIME::Lite
		From	=> $sender,
		To	=> $receiver,
		Subject => $mail_subject,
		Type	=> 'text/html;charset=$charset',
		Data	=> $text;

	$msg->add("Bcc" => $bcc) if $bcc;
	$msg->add("Reply-To" => $reply_to) if $reply_to;
	$msg->add("Errors-To" => $sender);
	$msg->delete('Date');
	$msg->replace('X-Mailer' => "Postcard Direct ($version)");
	$msg->add('X-Software' => "http://postcard-direct.com");
	$msg->add('X-Origin' => $ENV{'REMOTE_ADDR'});

	if ($p{'receipt'} eq 'on') {
		$msg->add('Read-Receipt-To' => $sender);
		$msg->add('Disposition-Notification-To' => $sender);
	}

	if ($add_message_id) {
		my $id = GenerateCID();
		$msg->add('Message-ID' => "<$id\@domain>");
	}
}

#-----------------------------------------------------------------------------
# Creates the mail format for "traditional" cards (ie: pick up card) and writes
# the card to the directory so that it can be viewed by the recipient via their
# browser.

sub CreateTraditionalMail {
	open POSTCARD,"$design_dir/$p{design}"  or Error("Can not open postcard design $design_dir/$p{design}", $!);
	$subject=$p{subject} if $p{subject};

	my $text;

	while (<POSTCARD>) {
		next if (/^#/);

		#
		# Make sure additional images have a full URL
		#
		if (/img src/i and ! /%IMAGE%|<PD_IMAGE>/i and ! /img src=\"?http:/i) {
			s!(<img src=\"?)(\S+)(.*)!$1$site_url$2$3!i;
		}


		if (/body.*background/i and ! /http:/i) {
			s!(background=\"?)(\S+)(.*)!$1$site_url$2$3!i;
		}

		SizeTags();

		#
		# Add host URL if not specified
		#
		if (/%IMAGE%|<PD_IMAGE>/i) {
			if ($p{image} !~ /http:/i) {
				s!%IMAGE%|<PD_IMAGE>!$site_url$p{image}!ig;
			}
			else {
				s!%IMAGE%|<PD_IMAGE>!$p{image}!ig;
			}
		}

		if ((/%OBJECT%|<PD_OBJECT>/i) and $p{object} ne 'none' and $p{object}) {
			if ($p{object} !~ /http:/i) {
				s!%OBJECT%|<PD_OBJECT>!$site_url$p{object}!ig;
			}
			else {
			   	s!%OBJECT%|<PD_OBJECT!$p{object}!ig;
			}
		}

		if ((/%INCLUDE%|<PD_INCLUDE>/i) and $p{include} ne 'none' and $p{include}) {
			my $include=IncludeFile($p{include});
			s!%INCLUDE%|<PD_INCLUDE>!$include!ig;
		}

		if ((/%MIDI%|<PD_MIDI>/i) and $p{midi} ne 'none' and $p{midi}) {
			Error("Midi file not found: $midi_dir/$p{midi}") if (! -f "$midi_dir/$p{midi}");
			s!%MIDI%|<PD_MIDI>!<noembed><bgsound src="$midi_url/$p{midi}" autostart="true"></bgsound></noembed> <embed src="$midi_url/$p{midi}" hidden="true" autostart="true"></embed>!ig;
		}

		s!%MIDI%|<PD_MIDI>!!ig;
		s!%OBJECT%|<PD_OBJECT>!!ig;
		s!%INCLUDE%|<PD_INCLUDE>!!ig;
		s!%BACK%|<PD_BACK>!!ig;
		s!%SEND%|<PD_SEND>!!ig;
		s!%TITLE%|<PD_TITLE>!$p{title}!ig;
		s!%SENDER%|<PD_SENDER>!$p{s_name}!ig;
		s!%SENDER_EMAIL%|<PD_SENDER_EMAIL>!$p{s_email}!ig;
		s!%RECIPIENT%|<PD_RECIPIENT>!$p{r_name}!ig;
		s!%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>!$p{r_email}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$subject!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$p{message}!ig;
		s!%FIELD1%|<PD_FIELD1>!$p{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$p{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$p{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$p{field4}!ig;
		s!%FIELD5%|<PD_FIELD5>!$p{field5}!ig;
		s!%FIELD6%|<PD_FIELD6>!$p{field6}!ig;

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		$text .= "$_";
	}

	close POSTCARD;

	#
	# Store the postcard
	#
	Error("$postcard_dir does not exist") if (! -d $postcard_dir);
	Error("The webserver does not have write permission to $postcard_dir",undef,"Change permissions to the directory to 777 on Unix systems, or enable webserver write permissions on Windows.") if (! -w $postcard_dir);

	my $stamp=int(time) . rand();
	my $reference="$stamp.card";

	open CARD,">$postcard_dir/$reference" or Error("Can not create $postcard_dir/$reference",$!);
	print CARD $text;
	close CARD;
	
	#
	# If a read receipt has been requested, store the sender/recipient details.
	#
	if ($p{'receipt'} eq 'on') {
		my $receipt = "$postcard_dir/$stamp.receipt";
		open RECEIPT,">$receipt" or Error("Can not create $receipt",$!);
		print RECEIPT "$p{'s_email'}|$p{'s_name'}|$p{'r_email'}|$p{'r_name'}|$mail_subject";
		close RECEIPT;
	}

	#
	# Now create the mail structure with containing the text
	# from the traditional template.
	#
	open TRADITIONAL,$traditional  or Error("Can not open traditional template $traditional",$!);

	undef $text;

	$postcard_url = "$site_url$script?showcard=$reference";

	while (<TRADITIONAL>) {
		next if (/^#/ or /-->/ or /<!--/);

		s!%URL%|<PD_URL>!$postcard_url!ig;
		s/%TITLE%|<PD_TITLE>/$op{title}/ig;
		s/%SENDER%|<PD_SENDER>/$op{s_name}/ig;
		s/%SENDER_EMAIL%|<PD_SENDER_EMAIL>/$op{s_email}/ig;
		s/%RECIPIENT%|<PD_RECIPIENT>/$op{r_name}/ig;
		s/%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>/$op{r_email}/ig;
		s/%SUBJECT%|<PD_SUBJECT>/$mail_subject/ig;
		s/%MESSAGE%|<PD_MESSAGE>/$plain_message/ig;
		s/%AGE%|<PD_AGE>/$postcard_age/ig;

		$text .= $_;
	}

	close TRADITIONAL;
		
	$msg = new MIME::Lite
		From	=> $sender,
		To	=> $receiver,
		Subject => $mail_subject,
		Type	=> 'text/plain',
		Data	=> $text;

	$msg->add("Bcc" => $bcc) if $bcc;
	$msg->add("Reply-To" => $reply_to) if $reply_to;
	$msg->add("Errors-To" => $sender);
	$msg->replace('X-Mailer' => "PD ($version)");
	$msg->add('X-Software' => 'http://postcard-direct.com');
	$msg->delete('Date');
	$msg->add('X-Origin' => $ENV{'REMOTE_ADDR'});

	if ($p{'receipt'} eq 'on') {
		$msg->add('Read-Receipt-To' => $sender);
		$msg->add('Disposition-Notification-To' => $sender);
	}

	if ($add_message_id) {
		my $id = GenerateCID();
		$msg->add('Message-ID' => "<$id\@domain>");
	}
}

#-----------------------------------------------------------------------------
# Expires old cards (in traditional mode)
#
sub ExpireCards {
	opendir POSTCARDS,$postcard_dir or Error("Can not search directory $postcard_dir",$!);
	
	foreach my $card (grep {/\.card/} readdir(POSTCARDS)) {
		if (-M "$postcard_dir/$card" > $postcard_age) {
			unlink("$postcard_dir/$card") or Error("Could not delete old postcard $postcard_dir/$card",$!,"Check the permissions on $postcard_dir are set to 777 on Unix systems, or enable webserver write permission on Windows systems.");
		}	
	}
}

#-----------------------------------------------------------------------------
# Clean up old files in the cache/upload directory
#
sub Cleanup {
	my ($dir,$age) = @_;

	opendir DIR,$dir  or Error("Can not search directory $dir",$!);
	
	foreach my $object (grep {/$upload_types/i} readdir(DIR)) {
		if (-M "$dir/$object" > $age) {
			unlink("$dir/$object") or Error("Could not delete old object $dir/$object",$!,"Check the permissions on $dir are set to 777 on Unix systems, or enable webserver write permission on Windows systems.");
		}	
	}
	
}

#-----------------------------------------------------------------------------
# Stores a card for sending at a later date
#
sub StoreCard {
	my ($object,$senddate) = @_;

	require Data::Dumper;
	Data::Dumper->import();

	# Set the Data Dumper varname prefix
	$Data::Dumper::Varname='msg';

	# Set indent level to none for most compact storage
	$Data::Dumper::Indent=0;

	Error("$postcard_dir does not exist") if (! -d $postcard_dir);
	Error("The webserver does not have write permission to $postcard_dir",undef,"Change permissions to the directory to 777 on Unix systems, or enable webserver write permissions on Windows.") unless (-w $postcard_dir);

	my $reference=int(time) . rand();
	open CARD,">$postcard_dir/$reference.mail" or Error("Can not create $postcard_dir/$reference.mail",$!);
	print CARD Dumper($object);
	close CARD;


	open STOREDB,">>$store_db" or Error("Can not write to $store_db",$!);
	LockFile(STOREDB);
	print STOREDB "$senddate|$reference.mail\n";
	close STOREDB;
}

#-----------------------------------------------------------------------------
# Checks to see if there are any stored cards to send off.
#
sub CheckStoredCards {
	return unless (-f $store_db);

	require datesimple;
	Date::Simple->import('d8','today');

	my $today = today();
	$today = $today->as_d8;

	open STOREDB, $store_db or Error("Can not open $store_db", $!);
	LockFile(STOREDB);

	my (@lines,$sent);

	while (<STOREDB>) {
		chomp;

		my ($date,$file) = split(/\|/);

		if ($date <= $today) {

			if (-f "$postcard_dir/$file" ) {
				require "$postcard_dir/$file"; # Read MIME::Lite object back in
			}
			else {
				Error("Missing stored postcard $file",undef,"Check if the cache has been manually cleared");
			}

			if ($sendmail) {
				SendUsingSendmail($msg1);
			}
			else {
				SendUsingSMTP($msg1);
			}

			$sent=1;
			unlink "$postcard_dir/$file" or Error("Can not remove stored postcard $postcard_dir/$file",$!);
		}
		else {
			push @lines,"$_\n";
		}

	}

	close STOREDB;

	#
	# If any cards were sent, rewrite the database with them removed.
	#

	if ($sent) {
		open STOREDB, ">$store_db" or Error("Can not write to $store_db",$!);
		LockFile(STOREDB);
		print STOREDB @lines;
	}
		
}

#-----------------------------------------------------------------------------
# Displays the referenced card if sent in 'traditional' mode.
#
sub ShowCard {
	my $ref = $p{'showcard'};
	Error("No card reference specified",undef,"Check the URL has the card reference number") unless $ref;

	Error("Invalid card number") if ($ref =~ m#/|\.\.#);

	my $card = "$postcard_dir/$ref";

	ExpiredCard() unless -f $card;

	print $q->header;

	open CARD, $card or Error("Can not open $card",$!);
	print <CARD>;
	close CARD;

	#
	# Check if there is a read receipt associated with this card.
	#
	my $receipt;
	($receipt = $ref) =~ s/card/receipt/;
	$receipt="$postcard_dir/$receipt";

	if (-f $receipt) {
		open RECEIPT, $receipt or Error("Can not open $receipt",$!);
		my ($s_email,$s_name,$r_email,$r_name,$subject) = split(/\|/,<RECEIPT>);
		close RECEIPT;
		unlink $receipt or Error("Can not delete $receipt",$!);

		if (-f "$modules/mimelite.pm") {
			require mimelite;
			MIME::Lite->import();
		}
		else {
			Error("Module $modules/mimelite.pm does not exist");
		}	

		open RECEIPT_TEMPLATE, $read_receipt or Error("Can not open $read_receipt",$!);

		my $text;

		while (<RECEIPT_TEMPLATE>) {
			next if (/^#/ or /-->/ or /<!--/);

			s/%SENDER%|<PD_SENDER>/$s_name/ig;
			s/%SENDER_EMAIL%|<PD_SENDER_EMAIL>/$s_email/ig;
			s/%RECIPIENT%|<PD_RECIPIENT>/$r_name/ig;
			s/%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>/$r_email/ig;
			s/%SUBJECT%|<PD_SUBJECT>/$subject/ig;

			if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
				my $stylesheet=Stylesheet();
				s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
			}
			
			$text .= $_;

		}

		close RECEIPT_TEMPLATE;

		my $charset = $charset{$lang} || $charset{'default'};

		my $msg = new MIME::Lite
			From	=> $r_email,
			To	=> $s_email,
			Subject => $receipt_subject,
			Type	=> 'text/html;charset=$charset',
			Data	=> $text;

		$msg->replace('X-Mailer' => "Postcard Direct ($version)");
		$msg->add('X-Software' => "http://postcard-direct.com");
		$msg->add('X-Origin' => $ENV{'REMOTE_ADDR'});
		$msg->delete('Date');	

		if ($sendmail) {
			SendUsingSendmail($msg);
		}
		else {
			SendUsingSMTP($msg);
		}

	}

	ExpireCards();
}

#-----------------------------------------------------------------------------
sub ExpiredCard {
	open FILE, $expired or Error("Can not open $expired",$!);

  	print $q->header;

	while (<FILE>) {
		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		print;
	}

	close FILE;
	
	exit;
}

#-----------------------------------------------------------------------------
# Logs the postcard details
#
# Order is Date (YYYY-MM-DD), Time, Method, Design, Midi, Object, Image,
# Remote Host, Subject, Sender email, Sender Name, Receiver email, 
# Receiver Name, Send Date, BCC, Send copy, six freeform fields and Message.
#
sub PostcardLog {
	my ($min,$hour,$mday,$month,$year)=(localtime)[1..5];


	$year+=1900;
	$month = sprintf("%02d",$month+1);

	open LOG,">>$logfile"  or Error("Can not open or write to log $logfile", $!,"Check permissions for $log_dir are set to 777 on Unix systems, or the webserver has write permission on Windows systems");
	LockFile(LOG);

	if ($ENV{'REMOTE_HOST'}) {
		$remote_host=$ENV{'REMOTE_HOST'};
	}
	elsif ($ENV{'REMOTE_ADDR'}) {
		$remote_host=$ENV{'REMOTE_ADDR'};
	}
	else {
		$remote_host='Unknown';
	}

	printf LOG ("%04d-%02d-%02d|%02d:%02d|", $year, $month, $mday, $hour, $min);

	print LOG "$p{method}|$p{design}|";

	$p{message} =~ s/\r/ /g;

	#
	# Make sure we don't have a clashing field separator
	#
	$subject =~ s/\|/&#166;/g;
	$p{message} =~ s/\|/&#166;/g;
	$p{s_name} =~ s/\|/&#166;/g;
	$p{r_name} =~ s/\|/&#166;/g;

	for my $i (field1..field6) {
		$p{$i} =~ s/\|/&#166;/g;
	}

	#
	# Determine the date the postcard was sent.
	#
	if ($p{year}) {
		$senddate="$p{year}-$p{month}-$p{day}";
	}
	elsif ($p{senddate}) {
		($senddate = $p{senddate}) =~ s/(\d{4})(\d\d)(\d\d)/$1-$2-$3/;
	}
	else {
		$senddate="$year-$month-$mday";
	}

	print LOG "$p{midi}|$p{object}|$p{image}|$remote_host|$subject|$p{s_email}|$p{s_name}|$p{r_email}|$p{r_name}|$senddate|$p{field1}|$p{field2}|$p{field3}|$p{field4}|$p{field5}|$p{field6}|$p{bcc}|$p{receipt}|$p{sendcopy}|$p{message}\n";

	close LOG;
}
	
#-----------------------------------------------------------------------------
# Converts the URL of an image or object to corresponding full pathname.
#
sub ObjectLocation {
	my $object=shift;

	return $object if ($object =~ /http:/i);

	$object =~ s/$url_alias// if $url_alias;

	return "$document_root$object";
}


#-----------------------------------------------------------------------------
# Returns the MIME type for the specified object.
#
sub ObjectType {
	my $object=shift;
	my $found=0;
	my ($type,$ext);

	$object = basename($object);
	$object =~ s/\w+\.//;

	open MIME,$mime_types  or Error("Can not open mime types file: $mime_types",$!);

	while (<MIME>) {
		next if (/^#/ or ! /\w+/);
		chomp;
		($type,$ext)=split(/\|/);
		if ($ext =~ /$object/) {
			$found=1;
			last;
		}
	}

	close MIME;

	return $type if $found;

	Error("Object type <I>$object</I> not defined in mimetypes file");
}
		
#-----------------------------------------------------------------------------
# Generates a Content ID

sub GenerateCID {
	return(int(time).rand().rand());
}

#-----------------------------------------------------------------------------
# Returns a very primitive determination of the image type

sub ImageType {
	my $image=shift;
  
	if ($image =~ /\.jpg$/i or $image =~ /\.jpeg$/i) {
		return 'jpeg';
    	} 
	elsif ($image =~ /\.gif$/i) {
		return 'gif';	
	} 
	elsif ($image =~ /\.bmp$/i) {
		return 'bmp';
	}
	elsif ($image =~ /\.png$/i) {
		return 'png';
	}
	else {
		Error("Image: $image does not appear to be a gif, jpg, bmp or png",undef,"Check that you are using a standard image type.");
	}
}

#-----------------------------------------------------------------------------
sub SendFromPreview {
	my $send_button=Gettext('Send Postcard',301);

	$p{message} =~ s!"!'!g;
	$p{message} =~ s/<br>/\n/ig;

	return <<EOF
<form method="post" action="$script">
<input type="hidden" name="image" value="$p{image}">
<input type="hidden" name="object" value="$p{object}">
<input type="hidden" name="title" value="$p{title}">
<input type="hidden" name="config" value="$p{config}">
<input type="hidden" name="method" value="$p{method}">
<input type="hidden" name="design" value="$p{design}">
<input type="hidden" name="message" value="$p{message}">
<input type="hidden" name="include" value="$p{include}">
<input type="hidden" name="field1" value="$p{field1}">
<input type="hidden" name="field2" value="$p{field2}">
<input type="hidden" name="field3" value="$p{field3}">
<input type="hidden" name="field4" value="$p{field4}">
<input type="hidden" name="field5" value="$p{field5}">
<input type="hidden" name="field6" value="$p{field6}">
<input type="hidden" name="bcc" value="$p{bcc}">
<input type="hidden" name="sendcopy" value="$p{sendcopy}">
<input type="hidden" name="receipt" value="$p{receipt}">
<input type="hidden" name="midi" value="$p{midi}">
<input type="hidden" name="lang" value="$lang">
<input type="hidden" name="s_email" value="$p{s_email}">
<input type="hidden" name="r_email" value="$p{r_email}">
<input type="hidden" name="r_name" value="$p{r_name}">
<input type="hidden" name="s_name" value="$p{s_name}">
<input type="hidden" name="subject" value="$subject">
<input type="hidden" name="senddate" value="$p{year}$p{month}$p{day}">

<input type="submit" name="send" class="button" value="$send_button">
</form>
EOF
}


#-----------------------------------------------------------------------------
# Lock a file

sub LockFile {
	my $FH=shift;

	flock($FH,LOCK_EX) or Error("Could not obtain file lock",$!);
}

#-----------------------------------------------------------------------------
# Display any input errors from the form

sub InputError {
	my ($text,$id,$extra)=@_;

	$text=Gettext($text,$id);

	open INPUTERROR,$input_error or Error("Can not open $input_error", $!);

	my $button=Gettext('Return to Postcard Form',302);
	my $returnbutton = qq(<form><input type="button" class="button" value="$button" onClick="history.go(-1);return true"></form>);

	my $charset = $charset{$lang} || $charset{'default'};
	print $q->header(-charset=>$charset);

	while (<INPUTERROR>) {
		next if (/^#/);

		s/%MESSAGE%|<PD_MESSAGE>/$text $extra/ig;
		s!%BACK%|<PD_BACK>!$returnbutton!ig;

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}

		print;
	}

	close INPUTERROR;

	exit;
}

#-----------------------------------------------------------------------------
# Anti-leech check.  This check, along with all other anti-leech CGI methods
# that rely on the referer are flawed, but people request it, so hey here it is.

sub AntiLeech {
	my $referer=$ENV{'HTTP_REFERER'};
	
	unless ($referer) {
		my $text=Gettext('No referer set',604);
		my $suggestion=Gettext('Ensure you are calling the script from a web page',605);
		Error($text,$ENV{'REMOTE_ADDR'},$suggestion);
	}

	foreach my $host (split(/,/,$referers)) {
		return 1 if ($referer =~ /$host/i);
	}

	my $text=Gettext('This script can only be run from a valid site',606);
	my $suggestion=Gettext('Update the list of referers',607);
	Error($text,$referer,$suggestion);
}

#-----------------------------------------------------------------------------
# Check for possible security hacks
#
sub CheckBadData {
	CheckBadPath($p{design},"Design name");
	CheckBadPath($p{midi},"Midi name");
}

#-----------------------------------------------------------------------------
# Check for invalid characters in variables that get parsed in
# to make sure they are not doing it for mallicious purposes.
#
sub CheckBadPath {
	my ($string,$name) = @_;

	return 0 unless $string;

	if ($string !~ m#^([\w.-]+)$#) {
		Error("No pathnames or illegal characters allowed for $name: $string","No pathnames allowed for $name: $string","You should be using just the name.");
	}
}

#-----------------------------------------------------------------------------
# Check to see if a valid configuration path has been specified
#
sub CheckConfigPath {
	my $path = shift;

	Error("Path to configuration file is a URL not a directory path $path",undef,"Please read the definitions of the difference between a URL and directory path") if ($path =~ /http:/i);

        Error("Configuration file $path does not exist",undef,"Check to make sure you are using the full directory path") unless -f "$path";

}

#-----------------------------------------------------------------------------
# Opens a file with data for a dropdown list
#
sub DropDown {
	my $file = shift;

	my $output;
	my ($filename,$description);

	open LIST,$file or Error("Can not open list $file",$!);


	while (<LIST>) {
		next if (/^#/ or ! /\w+/);

		($filename,$description) = split(/\|/);
		$output .= qq(<option value="$filename">$description</option>\n);
	}

	close LIST;

	return $output;
}

#-----------------------------------------------------------------------------
# Returns content of an included file
#
sub IncludeFile {
	my $file = shift;
	
	return unless $file;

	$file="$include_dir/$file";
	
	Error("Include file: $file not found") unless -f $file;	

	open FILE, $file or Error("Can not open $file",$!);

  	{
	  local $/;	
	  my $contents=<FILE>;
	  close FILE;
	  return $contents;
	}

}


#-----------------------------------------------------------------------------
# Returns content of specified stylesheet
#
sub Stylesheet {
	my $style;

	if ($p{stylesheet}) {
		$style="$style_dir/$p{stylesheet}";
	}
	else {
		$style="$style_dir/$stylesheet";
	}
		
	Error("Stylesheet $style not found") unless -f $style;

	open STYLE,$style  or Error("Can not open $style",$!);

	{
	  local $/;
	  my $style=<STYLE>;
	  close STYLE;

	  return $style;
	}
}

#-----------------------------------------------------------------------------
# Returns name of stylesheet to used.
#
sub Stylename {
	if ($p{stylesheet}) {
		return $p{stylesheet};
	}
	else {
		return $stylesheet;
	}
}

#-----------------------------------------------------------------------------
# Check the username portion of the email address.
#
sub CheckUser {  
	my $user=shift;

	study $user;

	return("Username: contains whitespace") if $user =~ /\s/;

	return("Username: contains invalid characters") if $user =~ /[;,\/#^*]/;

	return("Username: contains no valid characters") unless $user =~ /[a-z0-9]/;

	return("Username: contains a backspace") if $user =~ /[\010\177]/;

	return 0;

}

#-----------------------------------------------------------------------------
# Check the domain name of an email address doesn't have bogus values.
#
sub CheckDomain {
	my $domain=shift;

	return("incomplete domain name") unless index($domain, '.') >= 0;

	study $domain;

	return("Domain name: contains whitespace") if $domain =~ /\s/;

	return("Domain name: contains invalid characters") if $domain =~ /[;,\/#^*]/;

	return("Domain name: must contain letters") unless $domain =~ /[a-z]/;

	return("Domain name: contains backspace") if $domain =~ /[\010\177]/;

}

#-----------------------------------------------------------------------------
# Check for a valid email address format.  Adapted from Tom Christianson
# ckaddr script
#
sub CheckAddress {
	my $address=shift;

	if ($address !~ /\@./) {
		return Gettext('Incomplete email address',211);
	}

	for ($address) {
		s/^-+//;
		tr/A-Z/a-z/;
	}

	($user, $domain) = split /\@/, $address;

	unless (Email::Valid->address(-address => $address, tldcheck => 0)) {
		return Gettext('is an invalid email address',213);
	}

	if ($strict_email_check) {
		return Gettext($result) if ($result=CheckUser($user));
		return Gettext($result) if ($result=CheckDomain($domain));
	}

	#
	# Check if the email domain is valid
	#
	if ($check_domain) {
		my ($domain,$found);
		open DOMAINS,$domains_list or Error("Can not open $domains_list",$!);
		($domain = $address) =~ s/.*\.//;

		while (<DOMAINS>) {
			next if (/^#/ or /^$/);
			chomp;

			if (/^$domain\b/i) {
				$found=1;
				last;
			}
		}
		
		close DOMAINS;

		return Gettext('Email address has an invalid domain:',212) . $domain unless $found;
	}

	return 0;
}

#-----------------------------------------------------------------------------
# Gets language text

sub Gettext {
	my ($text,$id) = @_;
	my ($language,$message);

	#
	# If no ID is specified, just return the text
	#
	return $text unless $id;

	Error("Messages database not found: $message_db<br>$text") unless -f $message_db;

	open MESSAGEDB,$message_db  or return "* $text";

	while (my $line=<MESSAGEDB>) {
		next if ($line =~ /^#/ or $line !~ /\w+/);
		chomp $line;

		if ($line =~ /^ID:$id/) {

			while (my $line=<MESSAGEDB>) {
				chomp $line;
				($language,$message) = split(/\|/,$line);
				last if ($line =~ /^\s+/ or $line =~ /^ID/);

				if ($language eq $lang) {
					close MESSAGEDB;
					return $message;
				}
			}

			close MESSAGEDB;

			return "** $text";
		}
	}

	close MESSAGEDB;

	return "*** $text";
}
				
#-----------------------------------------------------------------------------
# Returns a date selection

sub DateSelection {
	my ($text,$i);

	#
	# Default to English month list if we don't have one defined
	# for the particular language.
	#
	require pddates;
	my $lang=$lang;
	$lang='en' unless $pdmonth{$lang}[0];

	my ($day,$mon,$year) = (localtime)[3,4,5];
	$year += 1900;

	$text = qq(<select name="day" class="dropdown">\n);
	

	for $i (1..31) {
		if ($i == $day) {
			$text .= sprintf("<option selected value=\"%02d\">%d</option>\n",$i,$i);
		}
		else {
			$text .= sprintf("<option value=\"%02d\">%d</option>\n",$i,$i);
		}
	}

	$text .= qq(</select>);
	$text .= qq(<select name="month" class="dropdown">\n);

	for $i (1..12) {
		my $j = $i - 1;
		if ($j == $mon) {
			$text .= sprintf("<option selected value=\"%02d\">%s</option>\n",$i,$pdmonth{$lang}[$j]);
		}
		else {
			$text .= sprintf("<option value=\"%02d\">%s</option>\n",$i,$pdmonth{$lang}[$j]);
		}
	}

	$text .= qq(</select>);
	$text .= qq(<select name="year" class="dropdown">\n);

	#
	# Display years based on how far in the future a card can be sent.
	#
	require datesimple;
	Date::Simple->import('today');
	my $year_end = today() + $future_card_age;
	$year_end =~ s/-.*//;

	for $i ($year..$year_end) {	
		$text .= qq(<option value="$i">$i</option>\n);
	}

	$text .= qq(</select>);

	return $text;
}
	
#-----------------------------------------------------------------------------
# Display any errors using the template
#
sub Error {
	my ($error,$diagnostics,$suggestion)=@_;

	require Cwd;
	Cwd->import();
	my $dir=cwd();

	my $time=localtime();

	$suggestion='None' unless $suggestion;
	$diagnostics='None' unless $diag;

	my $charset = $charset{$lang} || $charset{'default'};
	print $q->header(-charset=>$charset);

	ErrorStandard($error,$diag,$suggestion) unless -f $error_template;

	open ERROR,$error_template  or ErrorStandard($error,$diag,$suggestion);

	while (<ERROR>) {	
		next if (/^#/);

		s/%ERROR%|<PD_ERROR>/$error/ig;
		s/%SUGGESTION%|<PD_SUGGESTION>/$suggestion/ig;

		if ($diag) {
			s/%DIAGNOSTIC%|<PD_DIAGNOSTIC>/$diagnostics/ig;
			s/%VERSION%|<PD_VERSION>/$version/ig;
			s/%PERLVER%|<PD_PERLVER>/$]/ig;
			s/%DIR%|<PD_DIR>/$dir/ig;
			s/%TIME%|<PD_TIME>/$time/ig;
			s/%SERVER%|<PD_SERVER>/$ENV{'SERVER_SOFTWARE'}/ig;
		}

		if (/%STYLESHEET%|<PD_STYLESHEET>/i) {
			my $stylesheet=Stylesheet();
			s!%STYLESHEET%|<PD_STYLESHEET>!$stylesheet!ig;
		}


		print;
	}

	close ERROR;
	exit;
}

#-----------------------------------------------------------------------------
# Foolproof way to display errors if the error template doesn't exist.
#
sub ErrorStandard {
	my ($error,$diagnostics,$suggestion)=@_;

	print <<HTML;
<html>
<head>
<title>PD Error</title>
</head>

<body>

<h2>$error</h2>

<p>Suggestion: $suggestion</p>

<hr>
HTML

	#
	# Display diagnostics if specified
	#
	DisplayDiag($diagnostics) if $diag;


	print <<HTML;
<hr>

<p>Check to see if this error is covered in the <a href="http://postcard-direct.com/faq.html">Postcard Direct FAQ</a> or the <a href="http://www.ginini.com/support/">Support Forums</a></p>

</body>
</html>
HTML

	exit;
}

#-----------------------------------------------------------------------------
# Displays diagnostic information, if specified
#
sub DisplayDiag {
	my $diagnostics = shift;

	my $time=localtime();
	require Cwd;
	Cwd->import();
	my $dir=cwd();

	print <<HTML;
<hr>
<h3>Diagnostics</h3>
Error Message: <i>$diagnostics</i><br>
Full Directory path to this script: <i>$dir</i><br>
Postcard Direct Version: <i>$version</i><br>
Perl Version: <i>$]</i><br>
Server Type: <i>$ENV{'SERVER_SOFTWARE'}</i><br>
Server Time: <i>$time</i><br>

<script language="javascript">
<!--
document.write("Your Time: <i>" +  Date() + "</i><br>")
-->
</script>

HTML

}
