#!/usr/bin/perl
#==============================================================================
#
# Name:		pd.cgi (Postcard Direct)
#
# Author:	Peter Sundstrom (Ginini Technologies Limited)
#
# Source:	http://postcard-direct.com/
#
# Copyright:	(c)1999-2002 Peter Sundstrom. 
#		All rights reserved.
#
#		See http://postcard-direct.com/index.html#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/perlerror
s.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 .= '/';
		}
	}

	require "${Bin}pdglobal.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;

#
# 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
	$AllowHtml
	$TemplateDir
	$RequireSenderName
	$RefererList
	$StrictEmailCheck
	$MidiList
	$CheckBadUsers
	$RequireMessage
	$Checkdomain
	$CacheExpiry
	$DesignList
	$SendButton
	$EnableLogging
	$Sendmail
	$SendmailQueue
	$MaxMessage
	$Mode
	$plain
	$Plaintext
	$CheckBadWords
	$obejctname
	$RequireReceiverName
	$ObjectList
	$LogDir
	$StyleDir
	$Title
	$Version
);

$Version='5.3.5';

$CGI::POST_MAX = $maxpost;	# Set maximum POST size
$CGI::DISABLE_UPLOADS = 1;	# Disable uploads

#
# Name of the default configuration filename if none is specified;
#
$default_config="default.txt";

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


# Unbuffer output
#
$|=1;

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

unless (%param) {
	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=$param{config};

if ($Config) {
	CheckBadPath($Config,"Configuration file");
	CheckConfigPath("$ConfigDir/$Config");

	$Lang=$param{lang} if $param{lang};
        require "$ConfigDir/$Config";
}
else {
	$default_config="$ConfigDir/$default_config";
	CheckConfigPath($default_config);

	$Lang=$param{lang} if $param{lang};
        require $default_config;
	$Config=basename($default_config);
}

#
# Check to see if the script is being called from a valid location
#
AntiLeech() if $AntiLeech;

#
# Set appropriate URL's
#
$Help = "$pdurl/help/$Lang/help.html";
$MidiURL = "$pdurl/" . basename($MidiDir);

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

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

#
# Check what action has been specified
#
if ($param{'send'} or $param{'send.x'}) {
	CheckBadData();
	SendPostcard();
}
elsif ($param{'preview'} or $param{'preview.x'}) {
	CheckBadData();
	PreviewPostcard();
}
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 ($param{image} or $param{object});

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

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

	#
	# Set the form name if it is specified as a parameter
	#
	if ($param{form}) {
		CheckBadPath($param{form},"Form name");
		$Form="$TemplateDir/$param{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 ($param{image}) {
        	if ($param{image} =~ /http:/i) {
			$ImagePath=GetRemoteObject($param{image});
		}
		else {
			$ImagePath=ObjectLocation($param{image});
			ObjectNotFound($param{image},$ImagePath) unless -f $ImagePath;
		}
	}

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

	my $form_output;

	while (<FORM>) {

		next if (/^#/);

		SizeTags();

		s!%CGI%|<PD_CGI>!$script!ig;
		s!%CONFIG%|<PD_CONFIG>!$Config!ig;
		s!%IMAGE%|<PD_IMAGE>!$param{image}!ig;
		s!%OBJECT%|<PD_OBJECT>!$param{object}!ig;
		s!%TITLE%|<PD_TITLE>!$param{title}!ig;
		s!%HELP%|<PD_HELP>!$Help!ig;
		s!%LANG%|<PD_LANG>!$Lang!ig;
		s!%FIELD1%|<PD_FIELD1>!$param{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$param{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$param{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$param{field4}!ig;
		s!%PDICON%|<PD_ICON>!$Images/pdicon.jpg!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($DesignList);
			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($MidiList);

			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($ObjectList);

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

		$form_output .= $_;
	}
	
	close(FORM);

	print $q->header;
	print $form_output;
}

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

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

	ValidateForm();


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

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

	my $text = ReadHTML();

	print $q->header;
	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 ($param{image}) {
		if ($param{image} !~ /http:/i) {
			$ImagePath=ObjectLocation($param{image});
		}
		else {
			RemoteSiteAllowed($param{image}) if ($param{image} =~ /http:/i);
			$ImagePath=GetRemoteObject("$param{image}");
		}
		$ImageType=ImageType($ImagePath);
	}


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


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

	$Receiver=qq("$param{r_name}"); 

	#
	# If there are multiple comma separated recipients, 
	# extract them, and set as a Bcc (if specified)
	#
	if ($param{r_email} =~ /,/) {
		my @addresses = split(',',$param{r_email});
		$Receiver = "<$addresses[0]>";

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

		}

		$Bcc =~ s/,$// if $Bcc;
		$Receiver =~ s/,$// if $Receiver;
	}
	else {
		$Receiver .= " <$param{r_email}>";
	}


	#
	# Wrap the text message if specified
	#
	#$param{message} = WrapText($param{message}) if $WrapText;

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

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

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

	if ($param{method} eq 'web') {
		SendWeb();
	}
	elsif ($param{method} eq 'traditional' or $Mode eq 'traditional') {
		SendTraditional();
		ExpireCards();
	}
	else {
		SendHTML();
	}

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

	if ($Sendmail) {
		@SendmailDir=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 (@SendmailDir);

		if ($SendmailQueue) {
			$Sendmailflags='-t -oi -oem -odq';
		}
		else {
			$Sendmailflags='-t -oi -oem';
		}

		MIME::Lite->send('sendmail',"$SendmailDir[0]/sendmail -f $param{s_email} $Sendmailflags") or Error("An error has occured trying to send the postcard.  Please try again later.",$!);
		$msg->send or Error("Sendmail error", $!);
	}
	else {
		Error("No SMTP mail server has been defined") unless $MailServer;

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


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

		
		$mail{smtp} = $MailServer;
		$mail{message} = $msg->body_as_string;

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

		my ($text,$suggestion);

		unless ($status == 1) {
			if ($status == -1) {
				$text = Gettext('Bad From address:',500);
				Error("$text $param{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 $MailServer",$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 $MailServer",$diag,$suggestion);
			}

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

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

			if ($status == -6) {
				$text = Gettext('Recipient error:',504);
				$suggest = Gettext('Check the diagnostic message',600);
				Error("$text $param{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);
		}

	}

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

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

	print $q->header;

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

		s/%IMAGE%|<PD_IMAGE>/$param{image}/ig;
		s/%OBJECT%|<PD_OBJECT>/$param{object}/ig;
		s/%TITLE%|<PD_TITLE>/$param{title}/ig;
		s/%SENDER%|<PD_SENDER>/$param{s_name}/ig;
		s/%SENDER_EMAIL%|<PD_SENDER_EMAIL>/$param{s_email}/ig;
		s/%RECIPIENT%|<PD_RECIPIENT>/$param{r_name}/ig;
		s/%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>/$param{r_email}/ig;
		s/%SUBJECT%|<PD_SUBJECT>/$Subject/ig;
		s/%MESSAGE%|<PD_MESSAGE>/$param{message}/ig;
		s/%IMAGES%|<PD_IMAGES>/$Images/ig;

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


		print;
	}

	close(SENT);

	PostcardLog() if $EnableLogging;
}

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

	InputError("You must include the email address of the person you are sending to",100) unless $param{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)
	#
	for $mailaddress (split(',',$param{r_email})) {
		InputError("Recipient email address:",102,"$mailaddress <I>$Result</I>") if ($Result=CheckAddress($mailaddress));
	}

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

	InputError("Message size too large",110) if (($RequireMessage) and length($param{message}) > $MaxMessage);

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

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

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

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

	if ($CheckBadUsers) {
		InputError("Email address is banned:",108,$param{s_email}) if BadUser($param{s_email},'sender');
		InputError("Email address is banned:",108,$param{r_email}) if BadUser($param{r_email},'recipient');
	}

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

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

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

	$param{message}=WrapText($param{message}) if $WrapText;
	$PlainMessage = $param{message};
	$PlainMessage =~ s/<[^>]*>//gs;

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


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

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

	
}

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

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

	$Subject=$param{subject} if $param{subject};

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

		s/%TITLE%|<PD_TITLE>/$param{title}/ig;
		s/%SENDER%|<PD_SENDER>/$param{s_name}/ig;
		s/%SENDER_EMAIL%|<PD_SENDER_EMAIL>/$param{s_email}/ig;
		s/%RECIPIENT%|<PD_RECIPIENT>/$param{r_name}/ig;
		s/%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>/$param{r_email}/ig;
		s/%SUBJECT%|<PD_SUBJECT>/$Subject/ig;
		s/%MESSAGE%|<PD_MESSAGE>/$PlainMessage/ig;
		s/%BACK%|<PD_BACK>//ig;
		s/%SEND%|<PD_SEND>//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,"$DesignDir/$param{design}" or Error("Can not open postcard design $DesignDir/$param{design}", $!,"Check pathnames and that the design file exists");

	$Subject=$param{subject} if ($param{subject});

	$CID=GenerateCID();

	#$param{message}=WrapText($param{message}) if $WrapText;

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

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

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

			s!%BACK%|<PD_BACK>!<form><input type="button" class="button" value="$ReturnButton" 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 (/%MIDI%|<PD_MIDI>/i) {
				if ($param{midi} ne 'none' and $param{midi}) {
					Error("Midi file not found: $MidiDir/$param{midi}") if (! -f "$MidiDir/$param{midi}");
					$text .= qq(<noembed><bgsound src="$MidiURL/$param{midi}" autostart="true"></bgsound> </noembed>\n);
					$text .= qq(<embed src="$MidiURL/$param{midi}" hidden="true" autostart="true"></embed>\n);
					next;
				}
			}

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

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

			SizeTags();

			if (/%MIDI%|<PD_MIDI>/i) {
				if ($param{midi} ne 'none' and $param{midi}) {
					Error("Midi file not found: $MidiDir/$param{midi}") if (! -f "$MidiDir/$param{midi}");
					$MidiCID=GenerateCID() unless $MidiCID;
					s!%MIDI%|<PD_MIDI>!<noembed><bgsound src="cid:$MidiCID" autostart="true"></bgsound></noembed> <embed src="cid:$MidiCID" 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 ($param{object} ne 'none' and $param{object}) {
					if ($param{object} =~ /http:/i) {
						s!%OBJECT%|<PD_OBJECT>!$param{object}!ig;
					}
					else {
						if (/img src/i) {
							s!%OBJECT%|<PD_OBJECT>!$param{object}!ig;
						}
						else {
							$ObjectCID=GenerateCID() unless $ObjectCID;
							s!%OBJECT%|<PD_OBJECT>!cid:$ObjectCID!ig;
						}
			
					}
				}
			}

			if (/img src/i and /PD_FIELD|%FIELD%/i) {
				chomp($param{field1});	
				s!%FIELD1%|<PD_FIELD1>!$param{field1}!ig;
				s!%FIELD2%|<PD_FIELD2>!$param{field2}!ig;
				s!%FIELD3%|<PD_FIELD3>!$param{field3}!ig;
				s!%FIELD4%|<PD_FIELD4>!$param{field4}!ig;
			}

			if (/img src/i and ! (/%IMAGE%|<PD_IMAGE>/i)) {
				$ImageCID[$ExtraImages]=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!$WebRoot!!;

				$ImageURL = $_;
				$ExtraImagePath[$ExtraImages]=ObjectLocation($ImageURL);
				$ExtraImageType[$ExtraImages]=ImageType("$ExtraImagePath[$ExtraImages]");
				$text .= "${Startline}\"cid:$ImageCID[$ExtraImages]\" $Attributes $Extra\n";
				$ExtraImages++;
				s/.*//;
			}
			elsif (/body.*background/i) {
				$ImageCID[$ExtraImages]=GenerateCID();
				s/(.*background=)\"*//i;
				$Startline = $1;

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

				$ImageURL = $_;
				$ExtraImagePath[$ExtraImages]=ObjectLocation($ImageURL);
				$ExtraImageType[$ExtraImages]=ImageType("$ExtraImagePath[$ExtraImages]");
				$text .= "${Startline}\"cid:$ImageCID[$ExtraImages]\" $Attributes\n";
				$ExtraImages++;
				s/.*//;
			}
			else {
				s/%IMAGE%|<PD_IMAGE>/cid:$CID/ig;
			}
		}

		s!%SEND%|<PD_SEND>!!ig;
		s!%MIDI%|<PD_MIDI>!!ig;
		s!%TITLE%|<PD_TITLE>!$param{title}!ig;
		s!%SENDER%|<PD_SENDER>!$param{s_name}!ig;
		s!%SENDER_EMAIL%|<PD_SENDER_EMAIL>!$param{s_email}!ig;
		s!%RECIPIENT%|<PD_RECIPIENT>!$param{r_name}!ig;
		s!%RECIPIENT_EMAIL|<PD_RECIPIENT_EMAIL>%!$param{r_email}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$Subject!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$param{message}!ig;
		s!%FIELD1%|<PD_FIELD1>!$param{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$param{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$param{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$param{field4}!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,$BadusersList  or Error("Can not open baduser list $BadusersList",$!);

	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,$BadwordsList or Error("Can not open badwords list $BadwordsList",$!);

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

	close(BADWORDS);

	return $found;
}

#-----------------------------------------------------------------------------
sub ObjectNotFound {
	my ($object,$objectpath) = @_;
	 
	my $message="Postcard image/object not found <P>URL: $object<BR>Directory path: $objectpath";
	my $suggestion=qq(Is <a href="$WebRoot/$object">$WebRoot/$object</a> actually viewable?);

	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,$RemoteSites  or Error("Can not open remote sites list $RemoteSites",$!);

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

	close(REMOTE);

	Error("$Sitename is not a 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($ImagePath) 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($ImagePath) 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
	#
	CleanCache() if ($CacheAge or $CacheAge == 0);

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


	#
	# 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,">$CacheDir/$objectname") or Error("Can not open $CacheDir/$objectname", $!,"Check the permissions on the directory $CacheDir");
		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 "$CacheDir/$objectname";
}
	
#-----------------------------------------------------------------------------
# Send postcard as an HTML file
#
sub SendHTML {
	my $i=0;
	my $Text;

	$ExtraImages=0;

	$Text = ReadHTML();
	$Plaintext = ReadPlain();

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

	$msg->add("Bcc" => $Bcc) if $Bcc;
	$msg->add("Reply-To" => $ReplyTo) if $ReplyTo;
	$msg->add("Errors-To" => $Sender);
	$msg->replace('X-Mailer' => "Postcard Direct ($Version)");
	$msg->add('X-Source' => "http://postcard-direct.com");

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

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

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

	#
	# Attach an image if one exists
	#
	if ($param{image}) {
		$html->attach(
			Type	=> "image/$ImageType",
			Path	=> $ImagePath,
			Id	=> "<$CID>"
			);
	}

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

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

	}

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

		$ObjectType=ObjectType($param{object});

		$html->attach(
			Type		=> "$ObjectType",
			Encoding	=> 'base64',
			Path		=> "$TopLevel$param{object}",
			Id		=> "<$ObjectCID>"
			);
	}


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

		foreach ($i=0; $i < $ExtraImages; $i++) {
			chomp $ExtraImagePath[$i];

			$html->attach(
			Type		=> "image/$ExtraImageType[$i]",
			Path		=> "$ExtraImagePath[$i]",
			Id		=> "<$ImageCID[$i]>"
			);
		}
	}
}

#-----------------------------------------------------------------------------
# Sends the postcard in HTML design, but doesn't include the image in the
# mail, just the URL to the image.  
#
sub SendWeb {
	open POSTCARD,"$DesignDir/$param{design}"  or Error("Can not open postcard design $DesignDir/$param{design}", $!);

	$Subject=$param{subject} if $param{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$WebRoot$2$3!i;
		}


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

		SizeTags();

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

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

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

		if ((/%MIDI%|<PD_MIDI>/i) and $param{midi} ne 'none' and $param{midi}) {
			Error("Midi file not found: $MidiDir/$param{midi}") if (! -f "$MidiDir/$param{midi}");
			s!%MIDI%|<PD_MIDI>!<noembed><bgsound src="$WebRoot$MidiURL/$param{midi}" autostart="true"></bgsound></noembed> <embed src="$WebRoot$MidiURL/$param{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>!$param{title}!ig;
		s!%SENDER%|<PD_SENDER>!$param{s_name}!ig;
		s!%SENDER_EMAIL%|<PD_SENDER_EMAIL>!$param{s_email}!ig;
		s!%RECIPIENT%|<PD_RECIPIENT>!$param{r_name}!ig;
		s!%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>!$param{r_email}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$Subject!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$param{message}!ig;
		s!%FIELD1%|<PD_FIELD1>!$param{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$param{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$param{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$param{field4}!ig;

		$text .= $_;
	}

	close(POSTCARD);


	# Now create the mail structure
	
	$msg = new MIME::Lite
		From	=> $Sender,
		To	=> $Receiver,
		Subject => $Subject,
		Type	=> 'text/html',
		Data	=> $text;

	$msg->add("Bcc" => $Bcc) if $Bcc;
	$msg->add("Reply-To" => $ReplyTo) if $ReplyTo;
	$msg->add("Errors-To" => $Sender);
	$msg->replace('X-Mailer' => "Postcard Direct ($Version)");
	$msg->add('X-Source' => "http://postcard-direct.com");
}

#-----------------------------------------------------------------------------
# Sends a "traditional" card, ie: send a link to the recipient to pick up
# their card.

sub SendTraditional {
	open POSTCARD,"$DesignDir/$param{design}"  or Error("Can not open postcard design $DesignDir/$param{design}", $!);

	$Subject=$param{subject} if $param{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$WebRoot$2$3!i;
		}


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

		SizeTags();

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

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

		if ((/%MIDI%|<PD_MIDI>/i) and $param{midi} ne 'none' and $param{midi}) {
			Error("Midi file not found: $MidiDir/$param{midi}") if (! -f "$MidiDir/$param{midi}");
			s!%MIDI%|<PD_MIDI>!<noembed><bgsound src="$MidiURL/$param{midi}" autostart="true"></bgsound></noembed> <embed src="$MidiURL/$param{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>!$param{title}!ig;
		s!%SENDER%|<PD_SENDER>!$param{s_name}!ig;
		s!%SENDER_EMAIL%|<PD_SENDER_EMAIL>!$param{s_email}!ig;
		s!%RECIPIENT%|<PD_RECIPIENT>!$param{r_name}!ig;
		s!%RECIPIENT_EMAIL%|<PD_RECIPIENT_EMAIL>!$param{r_email}!ig;
		s!%SUBJECT%|<PD_SUBJECT>!$Subject!ig;
		s!%MESSAGE%|<PD_MESSAGE>!$param{message}!ig;
		s!%FIELD1%|<PD_FIELD1>!$param{field1}!ig;
		s!%FIELD2%|<PD_FIELD2>!$param{field2}!ig;
		s!%FIELD3%|<PD_FIELD3>!$param{field3}!ig;
		s!%FIELD4%|<PD_FIELD4>!$param{field4}!ig;

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

		$text .= "$_";
	}

	close(POSTCARD);

	#
	# Store the postcard
	#
	Error("$PostcardDir does not exist") if (! -d $PostcardDir);
	Error("The webserver does not have write permission to $PostcardDir",undef,"Change permissions to the directory to 777.") if (! -w $PostcardDir);

	my $reference=int(time) . rand();
	open CARD,">$PostcardDir/$reference.html" or Error("Can not create $PostcardDir/$reference.html",$!);
	print CARD $text;
	close(CARD);
	

	#
	# 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;

	$PostcardURL = "$WebRoot/$pdurl/" . basename($PostcardDir);


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

		s!%URL%|<PD_URL>!$PostcardURL/$reference.html!ig;
		s/%SENDER%|<PD_SENDER>/$param{s_name}/ig;
		s/%RECIPIENT%|<PD_RECIPIENT>/$param{r_name}/ig;
		s/%AGE%|<PD_AGE>/$PostcardAge/ig;

		$text .= $_;
	}

	close(TRADITIONAL);
		
	$msg = new MIME::Lite
		From	=> $Sender,
		To	=> $Receiver,
		Subject => $Subject,
		Type	=> 'text/plain',
		Data	=> $text;

	$msg->add("Bcc" => $Bcc) if $Bcc;
	$msg->add("Reply-To" => $ReplyTo) if $ReplyTo;
	$msg->add("Errors-To" => $Sender);
	$msg->replace('X-Mailer' => "PD ($Version)");
	$msg->add('X-Source' => 'http://postcard-direct.com');
}

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

#-----------------------------------------------------------------------------
# Clean old images in the cache
#
sub CleanCache {
	opendir CACHE,$CacheDir  or Error("Can not search directory $CacheDir",$!);
	
	foreach my $object (grep {/\.\w+/} readdir(CACHE)) {
		if (-M "$CacheDir/$object" > $CacheAge) {
			unlink("$CacheDir/$object") or Error("Could not delete old cache object $CacheDir/$object",$!,"Check the permissions on $CacheDir are set to 777");
		}	
	}
	
}
#-----------------------------------------------------------------------------
# Logs the postcard details
#
# Order is Date (YYYY-MM-DD), Time, Method, Design, Image URL, Midi,
# Remote Host, Subject, Sender email, Sender Name, Receiver email, 
# Receiver Name and the Message. 
#
sub PostcardLog {
	my ($min,$hour,$mday,$month,$year)=(localtime)[1..5];

	LockFile(LOG);

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

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

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

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

	$param{message} =~ s/\r/ /g;
	#$param{message}=WrapText($param{message}) if $WrapText;

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

	print LOG "$param{midi}|$param{object}|$param{image}|$RemoteHost|$Subject|$param{s_email}|$param{s_name}|$param{r_email}|$param{r_name}|$param{message}\n";

	UnlockFile(LOG);
	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/$URLAlias// if $URLAlias;

	return "$TopLevel$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,$MimeTypes  or Error("Can not open mimetypes $MimeTypes",$!);

	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 $SendButton=Gettext('Send Postcard',301);

	$param{message} =~ s!"!'!g;
	$param{message} =~ s/<BR>/\n/g;

	return <<EOF
<form method="post" action="$script">
<input type="hidden" name="image" value="$param{image}">
<input type="hidden" name="object" value="$param{object}">
<input type="hidden" name="title" value="$param{title}">
<input type="hidden" name="config" value="$param{config}">
<input type="hidden" name="method" value="$param{method}">
<input type="hidden" name="design" value="$param{design}">
<input type="hidden" name="message" value="$param{message}">
<input type="hidden" name="field1" value="$param{field1}">
<input type="hidden" name="field2" value="$param{field2}">
<input type="hidden" name="field3" value="$param{field3}">
<input type="hidden" name="field4" value="$param{field4}">
<input type="hidden" name="bcc" value="$param{bcc}">
<input type="hidden" name="midi" value="$param{midi}">
<input type="hidden" name="lang" value="$Lang">
<input type="hidden" name="s_email" value="$param{s_email}">
<input type="hidden" name="r_email" value="$param{r_email}">
<input type="hidden" name="r_name" value="$param{r_name}">
<input type="hidden" name="s_name" value="$param{s_name}">
<input type="hidden" name="subject" value="$Subject">

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


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

sub LockFile {
	my $FH=shift;

	my $status=0;
	my $tries=0;

	while ($status != 0) {
		$status = flock($FH,2);
		last if ($tries == 4);
		$status && sleep(1);
		$tries++;
	}
}

#-----------------------------------------------------------------------------
# Unlock a file

sub UnlockFile {
	my $FH=shift;

	flock($FH,8);
}

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

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

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

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

	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>);

	print $q->header;

	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(/,/,$RefererList)) {
		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($param{design},"Design name");
	CheckBadPath($param{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 specified stylesheet
#
sub Stylesheet {
	my $style;

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

	open STYLE,$style  or Error("Can not open $style",$!);
	my @style=<STYLE>;
	close(STYLE);

	return "@style";
}

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

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

	return("Username: contains only a single character") if length($user) == 1;

	study $user;

	return("Username: duplicate letters") if $user =~ /(\w)\1{3,}/;

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

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

	return("Username: contains duplicate letters") if $user =~ /^(.)\1+$/;

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

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

	$letters = "qwertyuiopasdfghjklzxcvbnmmnbvcxzlkjhgfrdsapoiuytrewq";

	return("Username: contains consecutive letters") if 
		length($user) > 2 &&
		( index($letters, $user) != -1
		    ||
		  ($user =~ /^(\w+)\1$/ && length($1) > 2
		    && index($letters, $1) != -1)
		);
}

#-----------------------------------------------------------------------------
# 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")) {
		return Gettext('is an invalid email address',213);
	}

	if ($StrictEmailCheck) {
		return Gettext($Result) if ($Result=CheckUser($user));
		return Gettext($Result) if ($Result=CheckDomain($domain));
	}

	#
	# Check if the email domain is valid
	#
	if ($Checkdomain) {
		my ($domain,$found);
		open DOMAINS,$DomainsList or Error("Can not open $DomainsList",$!);
		($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 ($lang,$message);

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

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

	open MESSAGEDB,$MessageDB  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;
				($lang,$message) = split(/\|/,$line);
				last if ($line =~ /^\s+/ or $line =~ /^ID/);

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

			close(MESSAGEDB);

			return "** $text";
		}
	}

	close(MESSAGEDB);

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

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

	my $time=localtime();

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

	print $q->header;

	ErrorStandard($error,$diag,$suggestion) if (! -f $Error);

	open ERROR,$Error  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>/$diag/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,$diag,$suggestion)=@_;

	print <<HTML;
<html>
<head>
<title>$error</title>
</head>

<body bgcolor="#ffffff">

<blockquote>
<p>
<font face="Arial,Helvetica" size=+2>
<b>$error</b>
</font>

<p>

<font face="Arial,Helvetica">
<table border="0" cellpadding="5" bgcolor="yellow">
<tr>
<td valign="top"><b>Suggestion: </b></td>
<td>$suggestion</td>
</tr>
</table>
</font>
<p>

HTML

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


	print <<HTML;
<hr>

<p>This error might be covered in the <a href="http://postcard-direct.com/faq.html">Postcard Direct FAQ</a></p>

</font>
</blockquote>

</body>
</html>
HTML

	exit;
}

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

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

	print <<HTML;
<hr>
<font face="Arial,Helvetica">
<h3>Diagnostics</h3>
Error Message: <i>$diag</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

}
