#!/usr/bin/perl -T -w use strict; # Contact Form is a Perl script that you can run on your website that will # allow others to send you email through a web interface. # See: http://ostermiller.org/contactform/ # Copyright (C) 2002-2007 Stephen Ostermiller # http://ostermiller.org/contact.pl?regarding=Contact+Form # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # See copying.txt for details. my ($NO_DESCRIPTION, $version, $LETTER, $DIGIT, $DOSEOL, $EOL, $LETTER_DIGIT, $LETTER_DIGIT_HYPHEN, $HEX_DIGIT, $QUOTEDSTRING, $ATOM, $SUBDOMAIN, $WORD, $DOMAIN, $LOCALPART, $EMAIL, $PHONE_DIGIT, $PHONE, $ZIPCODE, $PRICE, $FLOAT, $INTEGER, $SOMETHING, $ANYTHING, $ONE_LINE_REQUIRED, $ONE_LINE_OPTIONAL, $ZIPCODE_REQUIRED, $ZIPCODE_OPTIONAL, $PHONE_REQUIRED, $PHONE_OPTIONAL, $EMAIL_REQUIRED, $EMAIL_OPTIONAL, $PRICE_REQUIRED, $PRICE_OPTIONAL, $FLOAT_REQUIRED, $FLOAT_OPTIONAL, $INTEGER_REQUIRED, $INTEGER_OPTIONAL, $BLANK); &initConstants(); # List of email address to which mail can be sent. # Mail cannot be sent to any email address which is not on this list. # If a single address is listed, it will be a hidden value on the # form, otherwise, the user will be presented with a pulldown menu # of aliases to which email can be sent. # The addresses listed here are never visible via served web pages. my @Aliases = ( 'InsightCentral.net Editors','insightcentral@insightcentral.net', 'Community Support','team@insightcentral.net', 'Site Administrator','benjamin@insightcentral.net', #'administrator',&safeHeader($ENV{'SERVER_ADMIN'}), # The following aliases are commented out examples, remove the leading # sign to use them #'webmaster','webmaster@yoursite.tld', #'postmaster','postmaster@yoursite.tld', #'two people','webmaster@yoursite.tld,postmaster@yoursite.tld', ); # Modify the following to control how the HTML pages look # Page titles for the input and thank your pages my $input_page_title = 'InsightCentral.net - Contact Us'; my $sent_page_title = 'InsightCentral.net - Message Sent'; # Extra text (may be html formatted) that will be placed on # the page before the the form my $input_page_text = '
'; my $sent_page_text = ''; my $preview_page_text = ''; # Page structure -- the look and feel of the page # This variable may be either changed directly, or if the # page_template_file variable is set, this variable is # ignored and the template file is used instead. # Contact form can place content into any of four places: # $title -- the title of the page # $css -- style rules that control how the form looks # $javascript -- client side validation rules. # $content -- the form itself. # The css and title variables are optional and can easily # be omitted and replaced with your own elements to better # suit your taste. The form will not work properly if either # the javascript or content variables are removed or duplicated. my $page_template = '$required_marker denotes a required field.
"; # Style rules for the input page. # to put question and answer on the same line use: # .cf_userentry, .cf_radioselection { display:inline; margin-left:0.25cm; } my $input_page_css = ''; # Style rules for thank you page my $sent_page_css = ''; # Link to favicon, placed in the head after the JavaScript my $icon_link=''; # Form copyright header placed in the head after the JavaScript my $copyright_link = ''; # Redirect to this url after the message has been sent # By default there is no redirect. The url must # be fully qualified (must start with http://) # Example: my $redirect_url_sent = "http://example.com/"; my $redirect_url_sent = "http://www.insightcentral.net/thankyou.html"; # Show the sent confirmation for this many seconds before redirecting # after the message has been sent if the redirect_url_sent has been defined. # If zero is specified, the message sent confirmation will not # be shown at all (you can redirect to your own thank you) my $redirect_delay_sent = 0; #===================================================================== # You need to know Perl to and have a strong stomach to # modify much of anything below this line my(%SubmittedData, $mail_message, %AliasesMap, @AliasesOrdered, %FieldMap, @Field_Order, $template_error, $field_name_to, $field_name_from_email, $field_name_from_name, $field_name_subject, $field_name_regarding, $field_name_referrer); &loadTemplate(); &parseInput(); &createMaps(); &sanityCheck(); &composeEmail(); &previewMessage(); &sendEmail(); &sentPage(); sub initConstants { # initialize the path to something safe so we can later call sendmail $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; if (&safeHeader($ENV{'PATH_INFO'}) eq '/contactformicon.png'){ &contactformicon(); } # denotes that no description is desired. $NO_DESCRIPTION = "-"; # Version number of this software. $version = "2.01.01"; # Reqular expression building blocks $LETTER = "[a-zA-Z]"; $DIGIT = "[0-9]"; $DOSEOL = "(?:[\r][\n])"; $EOL = "(?:[\r\n]|$DOSEOL)"; $LETTER_DIGIT = "[0-9a-zA-Z]"; $LETTER_DIGIT_HYPHEN = "(?:[0-9a-zA-Z-])"; $HEX_DIGIT = "(?:[0-9a-fA-F])"; $QUOTEDSTRING = "(?:[\\\"](?:[^\\\"]|(?:[\\][\\\"]))*[\\\"])"; $ATOM = "(?:[\\!\\#-\\\\\\'\\*\\+\\-\\/-9\\=\\?A-Z\\^-\\~]+)"; $SUBDOMAIN = "(?:" . $LETTER_DIGIT . "(?:" . $LETTER_DIGIT_HYPHEN . "*" . $LETTER_DIGIT . ")?)"; $WORD = "(?:" . $ATOM . "|" . $QUOTEDSTRING . ")"; $DOMAIN = "(?:" . $SUBDOMAIN . "(?:[\\.]" . $SUBDOMAIN . ")+)"; $LOCALPART = "(?:" . $WORD . "(?:[\\.]" . $WORD . ")*)"; $EMAIL = "(?:" . $LOCALPART . "[\\@]" . $DOMAIN . ")"; $PHONE_DIGIT = "[\\.\\-\\(\\)\\+\\ Xx]*"; $PHONE = "(?:(?:" . $PHONE_DIGIT . $DIGIT . "){10,20})"; $ZIPCODE = "(?:" . $DIGIT . "{5}(?:[\\-]" . $DIGIT . "{4})?)"; $PRICE = "(?:" . $DIGIT . "+(?:[\\.]" . $DIGIT . "{2})?)|(?:[\\.]" . $DIGIT . "{2})"; $FLOAT = "(?:" . $DIGIT . "+(?:[\\.]" . $DIGIT . "*)?)|(?:[\\.]" . $DIGIT . "+)"; $INTEGER = "(?:" . $DIGIT . "+)"; # Some recommended regular expressions $SOMETHING = ".+"; $ANYTHING = ".*"; $ONE_LINE_REQUIRED = "^(?:(?:[^\\n\\r])+)\$"; $ONE_LINE_OPTIONAL = "^(?:(?:[^\\n\\r])*)\$"; $ZIPCODE_REQUIRED = "^" . $ZIPCODE . "\$"; $ZIPCODE_OPTIONAL = "^(?:" . $ZIPCODE . "?)\$"; $PHONE_REQUIRED = "^" . $PHONE . "\$"; $PHONE_OPTIONAL = "^(?:" . $PHONE . "?)\$"; $EMAIL_REQUIRED = "^" . $EMAIL . "\$"; $EMAIL_OPTIONAL = "^(?:" . $EMAIL . "?)\$"; $PRICE_REQUIRED = "^" . $PRICE . "\$"; $PRICE_OPTIONAL = "^(?:" . $PRICE . "?)\$"; $FLOAT_REQUIRED = "^" . $FLOAT . "\$"; $FLOAT_OPTIONAL = "^(?:" . $FLOAT . "?)\$"; $INTEGER_REQUIRED = "^" . $INTEGER . "\$"; $INTEGER_OPTIONAL = "^(?:" . $INTEGER . "?)\$"; $BLANK = "^\$"; } sub loadTemplate(){ $template_error = ""; if ($page_template_file ne ""){ if (!open(TEMPLATE, "<$page_template_file")){ $template_error = '\n'; return; } $page_template = join("", ); close(TEMPLATE); } } sub createMaps { # Since hash maps are not ordered, Aliases and Form_Fields # Are declared as arrays. We need to create two data structures # from each. The first is an undered map, the second is an # ordered key set. %AliasesMap = @Aliases; my $useAlias = 1; foreach my $alias (@Aliases){ if ($useAlias){ push(@AliasesOrdered, $alias); $useAlias = 0; } else { $useAlias = 1; } } $field_name_to = ''; $field_name_from_email = ''; $field_name_from_name = ''; $field_name_subject = ''; $field_name_regarding = ''; $field_name_referrer = ''; %FieldMap = @FormFields; foreach my $key (@FormFields){ if ($FieldMap{$key} and (! defined ${$FieldMap{$key}}{"enabled"} or ${$FieldMap{$key}}{"enabled"} != 0)){ push(@Field_Order, $key); if (defined ${$FieldMap{$key}}{"special"}){ my $special = ${$FieldMap{$key}}{"special"}; if ($special eq "to"){ $field_name_to = $key; } elsif ($special eq "from"){ $field_name_from_email = $key; } elsif ($special eq "name"){ $field_name_from_name = $key; } elsif ($special eq "subject"){ $field_name_subject = $key; } elsif ($special eq "regarding"){ $field_name_regarding = $key; } elsif ($special eq "referrer"){ $field_name_referrer = $key; } } if (defined ${$FieldMap{$key}}{"default"}){ ${$FieldMap{$key}}{"selected"} = ${$FieldMap{$key}}{"default"}; } } } # Put the referrer header into the map if it is not there already if ($field_name_referrer ne "" and $FieldMap{$field_name_referrer} and ! exists $SubmittedData{$field_name_referrer}){ if (defined $ENV{'HTTP_REFERER'}){ $SubmittedData{$field_name_referrer} = $ENV{'HTTP_REFERER'}; } else { $SubmittedData{$field_name_referrer} = "-"; } } } sub parseInput { my ($pair, @pairs, $buffer); if (&safeHeader($ENV{'REQUEST_METHOD'}) eq 'GET'){ @pairs = split(/&/, $ENV{'QUERY_STRING'}); } elsif (&safeHeader($ENV{'REQUEST_METHOD'}) eq 'POST'){ read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); @pairs = split(/&/, $buffer); } elsif ($ENV{'SERVER_NAME'}){ &inputPage("This form must be submitted via either 'GET' or 'POST'."); } foreach $pair (@pairs){ if ($pair =~ /([^\=]+)(?:\=(.*))?/){ my $name = $1; $name =~ tr/+/ /; $name =~ s/%($HEX_DIGIT{2})/pack("C", hex($1))/eg; $name =~ tr/\0//d; my $value = ""; if ($2){ $value = $2; $value =~ tr/+/ /; $value =~ s/%($HEX_DIGIT{2})/pack("C", hex($1))/eg; $value =~ tr/\0//d; } $SubmittedData{$name} = $value; } } } sub getRequired { my ($key) = @_; if (defined ${$FieldMap{$key}}{"required"}){ return ${$FieldMap{$key}}{"required"}; } elsif (&getType($key) eq "select" || &getType($key) eq "radio"){ return ""; } else { return $ANYTHING; } } sub getError { my ($key) = @_; if (defined ${$FieldMap{$key}}{"error"}){ return ${$FieldMap{$key}}{"error"}; } else { return "The field '$key' does not appear to be valid."; } } sub getType { my ($key) = @_; if ($key eq $field_name_to){ if ($#AliasesOrdered > 0){ return "select"; } else { return "hidden"; } } if (defined ${$FieldMap{$key}}{"type"}){ return ${$FieldMap{$key}}{"type"}; } else { return "text"; } } sub getSelected { my ($key) = @_; if (defined ${$FieldMap{$key}}{"selected"}){ return ${$FieldMap{$key}}{"selected"}; } else { return undef; } } sub getDescription { my ($key) = @_; if (defined ${$FieldMap{$key}}{"description"}){ return ${$FieldMap{$key}}{"description"}; } else { "$key:"; } } sub isDefined(){ my ($key) = @_; return defined($SubmittedData{$key}); } sub getSelection { my ($key) = @_; if (defined($SubmittedData{$key})){ return $SubmittedData{$key}; } if (&getSelected($key)){ return &getSelected($key); } return ""; } sub sanityCheck { # Check the referrer if (&safeHeader($ENV{'HTTP_REFERER'}) && &safeHeader($ENV{'HTTP_REFERER'}) !~ /$allowedReferers/g){ &inputPage("This form cannot be submitted from".&escapeHTML($ENV{'HTTP_REFERER'})."."); } my $some_required_field_present = 0; my $errorCount = 0; my %errorHash = (); my @field_keys = &getOrderedFields(); foreach my $key (@field_keys){ my $required_value = &getRequired($key); my $data = &getSelection($key); my $form_type = &getType($key); $errorHash{$key} = ""; if ($data !~ /$required_value/g){ if($data ne "" or !defined($SubmittedData{"prefill"})){ $errorCount++; $errorHash{$key} = &getError($key); } } elsif (&isDefined($key) && $form_type ne 'hidden'){ $some_required_field_present = 1; } foreach my $disallow_check (keys(%disallowed_text)){ if ($data =~ /$disallow_check/){ $errorCount++; $errorHash{$key} .= $disallowed_text{$disallow_check}; } } } if (!($some_required_field_present)){ &inputPage('', $input_page_text); } foreach my $key (@field_keys){ if (&getType($key) eq "trap" and !&isDefined($key)){ $errorCount++; $errorHash{$key} .= &getError($key); } } if ($errorCount > 0){ my $errorMessage = ""; if ($errorCount == 1){ $errorMessage = "Please correct the error to continue.
"; } else { $errorMessage = "Please correct all errors to continue.
"; } &inputPage($errorMessage, "", \%errorHash); } if ((!$SubmittedData{$field_name_to}) || $SubmittedData{$field_name_to} eq ""){ &inputPage('', $input_page_text); } else { my $recipent = $SubmittedData{$field_name_to}; if ((!$AliasesMap{$recipent})){ &inputPage("Your message cannot be sent to the specified recipient.") } } } sub composeEmail { my ($subject, $from, @field_keys, $key); if ($field_name_from_email ne '' and $FieldMap{$field_name_from_email}){ $from = &safeHeader(&getSelection($field_name_from_email)); } else { $from = ''; } if ($from !~ /$EMAIL_REQUIRED/g){ $from = 'nobody'; } if ($field_name_from_name ne "" and $FieldMap{$field_name_from_name} and &getSelection($field_name_from_name) ne ''){ $from = &safeHeaderName(&getSelection($field_name_from_name))." <$from>"; } if ($field_name_subject ne "" and $FieldMap{$field_name_subject} and &getSelection($field_name_subject)){ $subject = &safeHeader(&getSelection($field_name_subject)); } else { $subject = "Website Form Submission"; } if ($field_name_regarding ne "" and $FieldMap{$field_name_regarding} and &getSelection($field_name_regarding) ne ''){ $subject .= " (".&safeHeader(&getSelection($field_name_regarding)).")"; } $mail_message = "From: $from\n"; $mail_message .= "Subject: $subject\n"; $mail_message .= "\n"; @field_keys = &getOrderedFields(); foreach $key (@field_keys){ if ($key ne $field_name_to && $key ne $field_name_subject && $key ne $field_name_from_name && $key ne $field_name_from_email && $key ne $field_name_regarding && $key ne $field_name_referrer && &getType($key) ne "trap"){ my $mail_description = &getDescription($key); if ($mail_description eq $NO_DESCRIPTION){ $mail_description = ""; } else { $mail_description .= "\n"; } $mail_message .= $mail_description; $mail_message .= &getSelection($key)."\n"; $mail_message .= "\n"; } } } sub sendEmail { my @to_address_list = split(/,/,$AliasesMap{&getSelection($field_name_to)}); foreach my $to_address (@to_address_list){ open(MAIL,"|$sendmail"); print MAIL "To: ".&safeHeader($to_address)."\n"; print MAIL "Content-Type: text/plain; charset=".&safeHeader($charset)."\n"; print MAIL "X-Mailer: ContactForm/".&safeHeader($version)." (http://ostermiller.org/contactform/)\n"; print MAIL "X-Server-Name: ".&safeHeader($ENV{'SERVER_NAME'})."\n"; print MAIL "X-Server-Admin: ".&safeHeader($ENV{'SERVER_ADMIN'})."\n"; print MAIL "X-Script-Name: ".&safeHeader($ENV{'SCRIPT_NAME'})."\n"; print MAIL "X-Path-Info: ".&safeHeader($ENV{'PATH_INFO'})."\n"; print MAIL "X-Remote-Host: ".&safeHeader($ENV{'REMOTE_HOST'})."\n"; print MAIL "X-Remote-Addr: ".&safeHeader($ENV{'REMOTE_ADDR'})."\n"; print MAIL "X-Remote-User: ".&safeHeader($ENV{'REMOTE_USER'})."\n"; print MAIL "X-HTTP-User-Agent: ".&safeHeader($ENV{'HTTP_USER_AGENT'})."\n"; print MAIL "X-HTTP-Referer: ".&safeHeader($ENV{'HTTP_REFERER'})."\n"; if ($field_name_referrer ne ""){ print MAIL "X-First-HTTP-Referer: ".&safeHeader(&getSelection($field_name_referrer))."\n"; } print MAIL $mail_message; close (MAIL); } } sub previewMessage() { if ($require_preview == 0){ return; } if (defined $SubmittedData{$field_name_submit} and $SubmittedData{$field_name_submit} eq "Send"){ return; } my ($message); $message = &textToHTML("To: ".&getSelection($field_name_to)."\n".$mail_message); &inputPage( '', "$preview_page_text/g;
$value =~ s/[\r\n]+/
/g;
$value =~ s/\
/\n
\n/g;
$value =~ s/\
/
\n/g;
return $value;
}
# put in javascript escape sequences.
sub escapeJavaScript {
my ($value) = @_;
if (!defined($value)){
$value="";
}
$value =~ s/\\/\\\\/g;
$value =~ s/\'/\\'/g;
$value =~ s/\n/\\n'/g;
$value =~ s/\r/\\r'/g;
$value =~ s/\t/\\t'/g;
return $value;
}
sub getOrderedFields {
return @Field_Order;
}
sub contactformicon {
print "Content-type: image/png\n";
print "\n";
print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52";
print "\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff";
print "\x61\x00\x00\x01\xbc\x49\x44\x41\x54\x78\x9c\xa5\x92\x4d\x6e\x13";
print "\x41\x10\x85\xbf\xea\xee\xb1\x31\x72\xe2\xbf\x90\xe4\x0a\x88\x25";
print "\x87\x60\x83\x60\xc9\x6d\xb8\x0b\x6b\x96\x88\x15\x6b\x8e\x00\x6c";
print "\xed\x28\x12\xc1\x7f\x33\x4e\x6c\xc7\xe3\xe9\xae\x62\xe1\x38\x8e";
print "\x09\x28\x20\x6a\xd7\xaa\xf7\x5e\xd7\x7b\x55\xf0\x9f\x25\xef\xde";
print "\x5f\xbc\xed\x1c\xda\x4b\xef\x39\xaf\xa2\x9c\x95\x6b\x06\x55\x64";
print "\x20\x22\x83\x5a\x26\x67\xb5\x4c\xa6\xaf\x5f\x3c\x49\x7f\x14\xf8";
print "\xf8\xe9\xfb\x57\xef\x79\x7a\xd2\x53\xbc\x03\x03\x52\x82\xaa\x12";
print "\xd6\x11\x4b\x89\xcb\xa4\x72\x9e\x94\x41\x15\xe9\x97\x6b\x19\xac";
print "\x4a\xfa\x49\xa5\x1f\xbc\x7c\x0b\xe2\x48\xa7\x3d\xe5\xc7\xd4\x71";
print "\xda\x53\x44\x20\x78\x08\xde\x68\x80\x00\x2d\xb0\x16\xf0\x6c\xfb";
print "\xab\x1a\xcc\x97\xc2\x70\x22\x9f\x1d\x46\x74\x0e\x8e\x3b\x1b\x11";
print "\xb3\x87\x7d\xa7\x04\x31\x42\xaf\x6d\x3d\x67\x50\x01\x78\x0f\xbd";
print "\x96\x32\xca\xe5\x41\x72\x71\x25\x74\x5b\x06\x20\xce\x8c\xb8\x6d";
print "\x66\x01\xda\x07\xc6\xb8\xf8\xbd\x88\x19\x4c\x66\xc2\x51\xfb\x66";
print "\x4c\xc1\x39\x90\x78\x17\x54\xcb\xa0\xd9\x30\x86\xd3\xfb\x22\xa3";
print "\x7c\x43\x96\x5d\xcb\x39\x33\xab\x7e\x05\x2e\x4b\xc1\x7b\x98\xce";
print "\x76\xc8\x49\x21\xa8\x71\x97\x0c\x86\x06\x33\xf6\x04\xae\x96\x42";
print "\x70\x70\xd8\x34\xae\x4b\x61\x5c\x08\xde\x41\xe3\x11\x74\x6a\xb6";
print "\x6f\x01\x2c\x98\xc9\x7a\xfb\x5a\xad\xa1\xaa\xd8\x06\x44\xa3\x6e";
print "\x64\x1e\x92\x41\x3d\xdb\x60\x0e\x1e\x1b\xb3\xb9\xd0\x6a\xda\x26";
print "\x03\x35\x16\x00\x55\x84\xd9\x2e\xdd\xdb\x0a\x61\x47\x06\xa8\xd7";
print "\xc0\x39\x58\x95\xb7\x19\x10\x55\x61\x9c\x3b\x8e\xbb\x7f\x71\x04";
print "\x37\x53\x5c\x97\x42\x4a\x48\x50\x65\x7d\x31\x71\x9c\x74\x75\x3f";
print "\xa0\xad\x49\xdb\x5c\x9e\x2a\xa4\x24\x24\x85\xa4\x98\x08\xe5\x28";
print "\x77\x5f\xc2\xe2\x5a\x3e\x64\xc1\x9e\x0f\x73\x37\x57\x25\x57\xa5";
print "\x48\x4a\x1e\x93\xe4\x31\x51\xc4\x48\x9e\x94\xdc\x8c\x42\x44\x72";
print "\xef\x28\x42\x90\x4b\xef\xdd\xe2\xcd\xab\xa3\x7b\x1b\xfc\xe7\xfa";
print "\x09\xfd\xc8\xe6\x8e\x15\xeb\x74\x06\x00\x00\x00\x00\x49\x45\x4e";
print "\x44\xae\x42\x60\x82\x00";
exit;
}