#!/usr/local/bin/perl
# Script: mailmerge.cgi
# Copyright 1995, Lincoln D. Stein. You are free to use and
# redistribute this script so long as this copyright statement
# remains intact.
# Customized for Ithaca College:
# 4/18/96 CFK
# Added a REQUIRED option to the template file, in which you can specify
# required form fields, and a NEXTURL option, in which you can specify a
# URL to display after the form has been successfully submitted.
# 3/3/98 CFK
# Eliminate case-sensitivity in keywords (e.g. required and REQUIRED should
# be equivalent).
#
# Update to work with latest version of CGI.pm (i.e. make equivalent to
# standard mailmerge v1.2).
# 3/4/98 RHK
# Added a NEXT_HTML option that allows a user to specify HTML code for a
# "Thank You" page instead of a pre-written NEXTURL page.
# 3/11/98 RHK
# Added a new MULTIVALUE_FORMAT called LIST which outputs multiple values
# with
(in the case of NEXT_HTML) or \n (in the case of OUTPUT_FORM)
# between them.
# 2/10/99 RHK
# Modified &save_file subroutine so that it too could display the
# NEXT_HTML parameter
# 2/15/99 RHK
# Added "append", "inserta" (insert after), "insertb" (insert before) and
# "overwrite" options to the "File:" ACTION in the form of
# ACTION=File:/path/to/file.log,{append,inserta,insertb,overwrite}
# and in the logfile must be an identifier "" which stands for
# "Mailmerge Insertion Point"
# 2/25/99 RHK
# Added "FILE_OUTPUT_FORM" and "MAIL_OUTPUT_FORM" to allow separation of
# formatting between the file log and the email message when doing a
# multi-action'ed template; written as:
# ACTION=mailto:email@address.com;File:/path/to/file.log,{append,inserta,insertb,overwrite}
# 8/6/99 CFK
# Due to changes in the filesystem structure, if a template or a file
# specification is not found, look up the directory in the "directory
# move" database to see if it is in a different location.
# This is the CGI.pm library, which must be installed in
# /usr/local/lib/perl5 or equivalent.
use CGI;
# The path to the sendmail program may need to be set for
# your site.
$MAIL = '/usr/lib/sendmail';
$ENV{'PATH'}='/bin:/usr/bin:/usr/lib';
# For logfile pathname translation, change this if your site
# uses something different for its user's home web directories.
$PUBLIC_HTML = 'public_html';
# See the material following the __END__ for more user-configurable
# options.
#####################################################
# From here to __END__ should not need to be changed
#####################################################
%VERBOTEN = ('ACTION'=>1,'DATE'=>1,'SCRIPT'=>1,'REFERER'=>1,
'REMOTE_USER'=>1,'USER_AGENT'=>1,'REMOTE_HOST'=>1,
'SERVER'=>1,'SERVER_HOST'=>1,'ADMINISTRATOR'=>1,
'ESCAPE_HTML'=>1);
# Create a new CGI object
$query = new CGI;
# Read our configuration files and store the token
# lookup table in "dictionary"
($dictionary,$forms) = &read_configuration($query);
# Add information from the parsed form to the lookup table.
foreach ($query->param()) {
next if $VERBOTEN{$_}; # certain parameters can't be set by the remote user
$dictionary->{$_}=&process_user_input($dictionary,
$dictionary->{ESCAPE_HTML},
$query->param($_));
}
# Check the REQUIRED fields (if any) and make sure they were filled in
# before continuing to process the form. CFK
if ($dictionary->{REQUIRED} ne "NONE") {
# Parse the required fields
@required = split(/\,/, $dictionary->{REQUIRED});
foreach (@required) {
# Remove any white space from the field name
$_ =~ s/\s//g;
push (@missing, $_) if ($dictionary->{$_} =~ /^\s*$/);
}
if (@missing) {
# Print out the HTTP header
print $query->header;
print $query->start_html("Information Missing");
print <<"EOF";
{REFERER},"\">Back to the form.\n";
}
}
}
# ---------- Send out the form via E-mail ---------
# One copy gets e-mailed. The other gets displayed
# on the screen so that the remote user knows something happened.
sub send_mail {
my($header,$body,$html,$dictionary) = @_; # RHK - added parameter
$header = &do_substitutions($header,$dictionary);
$header = &remove_empty_fields($header);
$html = &do_substitutions($html,$dictionary,2); # RHK - added function call
$body = &do_substitutions($body,$dictionary,1);
# Check the TO address for shell meta-characters
if ($dictionary->{TO}=~/[;|\`\/]/) {
print < {REFERER},"\">Back to the form.\n";
}
}
# -------- Parse configuration form -------
# Pass the routine a filehandle open to the configuration
# file
sub read_configuration {
my($query) = @_;
my($dictionary) = {};
my($forms) = {};
my($user_config_file, $new_path);
&config(DATA,$dictionary,$forms); # read our built-in defaults
if (-e $query->path_info) {
$user_config_file = $query->path_info;
} elsif (-e $query->path_translated) {
$user_config_file = $query->path_translated;
##### CFK - the user config file may have moved...
} elsif (-e ($new_path = &site_moved($query->path_info))) {
$user_config_file = $new_path;
} else { # still can't find it, so try a rel. path
my($path) = $query->path_info=~m#^/(\S+)#;
$user_config_file = "./$path" if -e "./$path";
}
if ($user_config_file) {
open (USER_CONFIG,"$user_config_file") || &croak("$user_config_file: $!");
&config(USER_CONFIG,$dictionary,$forms);
close(USER_CONFIG);
}
return ($dictionary,$forms);
}
# Read an individual configuration file.
sub config {
my($filehandle,$dictionary,$forms)=@_;
my($tag,$value,$old);
&fill_in_predefines($dictionary);
while (<$filehandle>) {
chomp;
next if /^#/;
next unless ($tag,$value) = /(^\w+)\s*=\s*(.*)/;
# Eliminate case-sensitivity by converting to upper case
$tag =~ tr/a-z/A-Z/;
# RHK - added NEXT_HTML, FILE_OUTPUT_FORM, and MAIL_OUTPUT_FORM to tag list
if ($tag=~/MAIL_HEADER|NEXT_HTML|OUTPUT_FORM|FILE_OUTPUT_FORM|INPUT_FORM/) {
($old,$/)=($/,"\n.\n");
chomp($forms->{$tag}=<$filehandle>);
$forms->{$tag} .= "\n"; # add back a newline
$/=$old;
} else {
$dictionary->{$tag} = &do_substitutions($value,$dictionary);
}
}
}
# Fill in a few of the special predefined tags.
sub fill_in_predefines {
my $dictionary = shift;
$dictionary->{DATE}=localtime;
$dictionary->{SCRIPT}=$query->script_name;
$dictionary->{REFERER}=$query->referer;
$dictionary->{REMOTE_USER}=$ENV{HTTP_FROM} ||
$query->remote_ident || $query->remote_user;
$dictionary->{USER_AGENT}=$query->user_agent;
$dictionary->{REMOTE_HOST}=$query->remote_host;
my($name,$junk,$junk,$junk,$junk,$junk,$gcos) = getpwuid($<);
$dictionary->{SERVER} = "$name\@" . $query->server_name . " ($gcos)";
$dictionary->{SERVER_HOST} = $query->server_name;
$dictionary->{ADMINISTRATOR} = $ENV{SERVER_ADMIN};
}
# Do the variable substitutions.
sub do_substitutions {
my($scalar,$dictionary,$tag) = @_;
my($key);
my(@keys) = $scalar=~/\@(\w+)\@/g;
# Note that we use the unoptimized s/// form here for
# security reasons -- check the performance!
foreach $key (@keys) {
# RHK - if processing OUTPUT_FORM, use \n instead of An error occurred while processing this form:\n";
print " $msg\n
if ($dictionary->{MULTIVALUE_FORMAT} eq "LIST" && $tag == 1) {
$dictionary->{$key} =~ s/
/\n/g;
}
$scalar=~s/\@$key\@/$dictionary->{$key}/g;
}
return $scalar;
}
# A bit of aesthetic silliness -- remove empty
# fields in the e-mail header
sub remove_empty_fields {
my $header = shift;
do {} while $header=~s/\n[\w-]+:\s*\n/\n/mg;
return $header;
}
# Wordwrap paragraphs
sub wordwrap {
my ($text,$wordwraplength) = @_;
unless ($wordwraplength) {
print $text;
return;
}
my (@paragraphs) = split("\n\n",$text);
my $para;
foreach $para (@paragraphs) {
my (@lines) = split("\n",$para);
if (grep(length($_) > $wordwraplength,@lines)) {
&compile_format($wordwraplength);
($xText=$para)=~tr/\n//; # get rid of the newlines
$-=9999; # no page breaks
write;
} else {
print "$para\n\n";
}
}
}
# Compile the report format used to wordwrap lines
sub compile_format {
my $length = shift;
$~=OUT;
return if $FMT;
my ($linepat) = '<' x $length;
$FMT=<