#!/usr/local/bin/perl
# CHANGE THE ABOVE PATH TO THE CORRECT PATH TO PERL VERSION 5.001
# ON YOUR SYSTEM!
# 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.
# This is the CGI.pm library, which must be installed in
# /usr/local/lib/perl5 or equivalent.
use CGI;
sub lock($$);
# 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 and print out the HTTP header
$query = new CGI;
print $query->header;
# Read our configuration files and store the token
# lookup table in "dictionary"
($dictionary,$forms) = &read_configuration($query);
# Print out the title and the top of the HTML document.
# Add information from the parsed form to the lookup table.
print $query->start_html($dictionary->{TITLE});
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($_));
}
# Try to post mail if we're being called by a POST request
# and the lookup table contains the TO field.
if (($ENV{REQUEST_METHOD} eq 'POST') && $dictionary->{TO}) {
if ($dictionary->{ACTION}=~/^mailto:\s*(.+)/i) {
$dictionary->{TO} = $1;
&send_mail($forms->{MAIL_HEADER},$forms->{OUTPUT_FORM},$dictionary);
} elsif ($dictionary->{ACTION}=~/^mail/i) {
&send_mail($forms->{MAIL_HEADER},$forms->{OUTPUT_FORM},$dictionary);
} elsif ($dictionary->{ACTION}=~/^file:\s*(\S+)/i) {
&save_file($1,$forms->{OUTPUT_FORM},$dictionary);
}
print "
self_url,"\">Back to the form.\n";
# Otherwise use the INPUT_FORM field to generate a new form
# for the use to fill out.
} else {
&generate_form($forms->{INPUT_FORM},$dictionary);
}
# The rest of this is advertising.
print <
This form generated by
mailmerge.
$dictionary->{AUTHOR}
$dictionary->{ADDRESS}
EOF
;
print $query->end_html;
################### SUBROUTINES ###############
# ------------ Create the form -----------
sub generate_form {
my($form,$dictionary) = @_;
$form = &do_substitutions($form,$dictionary);
print <
$form
EOF
}
# Join multivalued parameters according to the author's
# preference.
sub process_user_input {
my($config,$escape_html,@values) = @_;
@values = grep ($_,@values);
# This is a convenient place to escape any HTML characters
# in the user's input, if present.
if ($escape_html) {
foreach (@values) {
$_ = &CGI::escapeHTML({},$_);
}
}
return $values[0] unless @values > 1;
if ($config->{MULTIVALUE_FORMAT} eq 'BRACES') {
return "{" . join(",",@values) . "}";
} else {
my $last = pop @values;
return join(", ",@values) . " and $last";
}
}
# ----------------- Write message to a file ------------------
sub save_file {
my($file,$body,$dictionary) = @_;
$body = &do_substitutions($body,$dictionary);
my $logfile = &find_logfile($query,$file);
&croak("$0: Specified log file '$file' does not exist. It must already be created and writable.")
unless $logfile;
open(LOGFILE,">>$logfile") ||
&croak("Failed opening log file $file: $!\n");
lock(LOGFILE,1);
select(LOGFILE);
print '-'x50,"\n";
&wordwrap($body,$dictionary->{WORDWRAP});
lock(LOGFILE,undef);
close(LOGFILE);
# Print another copy to the screen so the user can see
# what's going out:
select STDOUT;
print <$dictionary->{TITLE}
The following is a copy of the log entry that was submitted.
EOF
;
&wordwrap($body,$dictionary->{WORDWRAP},$dictionary->{ESCAPE_HTML});
}
# ---------- 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,$dictionary) = @_;
$header = &do_substitutions($header,$dictionary);
$header = &remove_empty_fields($header);
$body = &do_substitutions($body,$dictionary);
# Check the TO address for shell meta-characters
if ($dictionary->{TO}=~/[;|\`\/]/) {
print <The To: address "$dictionary->{TO}" contains
illegal shell metacharacters. If this was unintentional,
please try again.
EOF
;
return;
}
# We fork ourselves in order to avoid passing characters
# through a shell.
open (MAIL, "|-") || exec($MAIL,'-t','-oi');
select MAIL;
print $header,"\n";
&wordwrap($body,$dictionary->{WORDWRAP});
close MAIL;
# Print another copy to the screen so the user can see
# what's going out:
select STDOUT;
print <$dictionary->{TITLE}
The following is a copy of the mail that was submitted.
$header
EOF
;
&wordwrap($body,$dictionary->{WORDWRAP});
print <
EOF
;
}
# -------- 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);
&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;
} 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*(.*)/;
if ($tag=~/MAIL_HEADER|OUTPUT_FORM|INPUT_FORM/) {
($old,$/)=($/,"\n.\n"); # look for a lonely dot
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) = @_;
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) {
$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=<An error occurred while processing this form:\n";
print "$msg