#!/usr/bin/perl -T # # email_form_data # # AUTHOR: # Dan Harkless # # COPYRIGHT: # This file is Copyright (C) 2016 by Dan Harkless, and is released under the # GNU General Public License . # # DESCRIPTION: # Yet another web form data emailer along the lines of Reuven M. Lerner's 1994 # script form-mail.pl. Just recently (as of this writing), spammers seem to # have upped their abuse of web form mailers, so email_form_data contains some # countermeasures to help prevent this. In order for this to be possible, it # needs to control the generation of the email form, rather than being able to # be called from any HTML form page, like many form mailers (including # previous versions of this script). # # To use email_form_data, you must create a configuration file in the same # directory as the script. The config file, at a minimum, must set a value # for the "email_to" variable (more to come on this in a moment). # # The config file must be named "email_form_data.confSOMERANDOMSTRING". The # random string is up to you. For instance, you could choose # "email_form_data.conf.Ha_ha_ha_spammers,_you_can't_harvest_my_address!". As # you might gather from that, the purpose of the required random string is so # that spammers that know you're using this script can't fetch the # config file (which of course needs to be readable by the webserver user, # unless you play setuid tricks) to find out your email address. Of course, # in order for this protection to be effective, you need to configure your # webserver to disallow directory indexes ("Options -Indexes" for Apache) in # the directory where this script lives (or have a default index file there). # And if you were previously using UNIX filesystem protections to prevent # directory listings there, you'll need to run 'chmod o+r' so the script can # read the directory and find the config file. # # Here's an example config file: # # captcha = addition # # email_to = webmaster@yourdomain.tld # # email_from = apache@www.yourdomain.tld # # stylesheet_url = ../common/stylesheet.css # # title_prefix = Email Dan Harkless # # footer = < # Validated HTML 4.01 # Transitional # EOF # # The '=' signs are actually optional. The "<>" (you must type the double angle brackets). # For more information, see the documentation for the Config::General Perl # module (which is required for this script to function, and is available from # CPAN). # # The config file has one required setting: # # email_to # This is the only non-optional setting in the config file, and specifies # your email address -- the form data will be emailed here. Be sure to # take the precautions described above so that spammers can't fetch the # config file to harvest your address. # # The rest of the settings in the config file (listed here alphabetically, but # they can appear in any order in the file) are optional: # # captcha # If this setting is specified, the form will include a CAPTCHA field to # attempt to prevent spammer abuse. Currently, the only valid value for # this variable is "addition", which will cause an easy addition problem # to be asked. Support for other types of CAPTCHA will be added in the # future if needed. # # charset # The charset to specify for the generated HTML page. If this parameter # is not present, we default to ISO-8859-1. To prevent a character set # from being specified in the , set this to a blank # value. # # dtd # The DTD to use for the generated HTML page. By default, we use # "-//W3C//DTD HTML 4.01 Transitional//EN". # # email_from # When we send email, we don't actually forge it as being From: the email # address specified by the web form user. Instead, we put the sender's # name and email address in quotes, and use the address specified by this # variable as the actual email address part on the From: line. If not # specified, this will default to your own email address (email_to value). # # As an example of how the email_from value is used, if you set it to # 'apache@www.yourdomain.tld', and the visitor to your web form typed # 'Jane Winecooler' as their name and 'jane@winecooler.tld' as their email # address, the email would be sent from '"Jane Winecooler # " '. We also include a # Reply-To: header in the mail with an unmangled version of the visitor's # name and address, so that replying to one of these emails works as # expected. # # email_preview_message # By default, on the page displayed after the user hits the 'Preview' # button, after the header we display the message "

The following is a # preview of your message. It will not be sent until you hit the # Send button below.

". If you would like a different message to # appear in this case, set this variable to its HTML contents. # # email_sent_no_redirect_message # By default, on the page displayed after the user hits the 'Send' button, # after the header, if we don't know what page the user was browsing # before they clicked over to the email form page (e.g. because they have # web privacy software installed that removes the Referer: [sic] HTTP # header), we'll display the message "

The below email has been sent. # Please use your browser's Back functionality to resume browsing the # site.

". If you would like a different message to display in this # case, set this variable to its HTML contents. # # email_sent_plus_redirect_message # By default, on the page displayed after the user hits the 'Send' button, # after the header, if we *do* know what page the user was browsing before # they clicked over to the email form page, we'll display the message # "

The below email has been sent. You can use your browser's Back # functionality or click on the following URL to resume browsing from # where you were:

", followed by the hyperlinked text of the URL, in a #
section. If you would like a different message to precede # the URL, set this variable to its HTML contents. # # footer # If you'd like some HTML to always appear at the bottom of the page, set # this variable. # # header # By default, at the top of the page we'll generate an

heading with # the contents of the title_prefix variable. If you would like to have # other HTML at the top of the page instead, include its contents here. # # initial_message # By default, on the initial page displayed when the user first visits the # email form, prior to hitting Preview or Send, after the header we'll # display the message "

You can use the form below to email me.

". # If you'd like a different message to appear here, set this variable to # its HTML contents. # # input_x # By default, the text entry fields in the form will be 72 characters # wide. If you'd like to use a different value, set this variable. # # input_y # By default, the "Message" textarea box will be 30 lines high. If you'd # like to use a different value, set this variable. # # lang # The language to be specified on the tag. If the parameter is not # present, we default to "en". No "lang" attribute will be specified if # this parameter is set to a blank value or if an HTML 2 or 3 DTD is used. # # stylesheet_url # The URL of the stylesheet to have the generated page point to. This URL # can be absolute, or relative to the directory email_form_data lives in. # # title_prefix # By default, the initial part of the page will be the name (as # installed) of this script. If you'd like to use your own custom title, # set this variable. # # On the initial visit to the form, the prefix (default or custom) will be # the entire title. For the Preview page, it'll have ": Preview" appended # to it. For the Send page, it'll have ": Email Sent" appended. # # To enable you to distinguish emails sent by email_form_data, and to help # track any abuse, in addition to the From: mangling described above, we # include a comment at the beginning of the email giving information on the # machine that submitted the form. For example: # # [The following email was sent via a POST to http://www.domain.tld/cgi-bin/ # email_form_data, called from http://www.domain.tld/cgi-bin/email_form_data # , linked to from http://www.domain.tld/faq.html, submitted by ppp-77-128-6 # 9-184.dsl.irvnca.pacbell.net (77.128.69.184:3487), running Mozilla/4.0 (co # mpatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322; InfoPath .1; .NET C # LR 2.0.50727).] # # email_form_data requires you to have a 'sendmail' executable in /usr/sbin or # /usr/lib. Perhaps in the future I'll modify it to optionally be able to # send via Net::SMTP (or to flat-out require it and always send that way). As # mentioned previously, it also requires Config::General, available from CPAN. # # TODO: # Add settings to the config file to (re-)allow webmaster-defined input fields # in the generated form, rather than just the canned "Your name", "Your email # address", "Subject", and "Message". # # DATE MODIFICATION # ========== ================================================================== # 2016-07-04 Assume Perl 5.6.0+; 'use warnings' rather than -w on shebang line # so we only output warnings in this script, not in modules we use. # 2016-07-03 Although 'charset', if specified, comes from a user-controlled # file, html_escape() it just in case. # 2016-04-01 Added 'charset' parameter to override default; use <META> tag in # addition to the HTTP header to silence validator.w3.org warning. # 2016-03-15 'use Config::General' now requires 'qw(ParseConfig)' for that # routine to be avail. Not sure if this breaks compat. w/ old vers. # 2010-01-12 Added 'captcha' config file directive and its handling. # 2010-01-12 Removed the -no_xhtml from the 'use CGI' since CGI.pm 3.46 fixes # the bug that required that to avoid HTML validation errors. # 2008-09-02 "use English qw(-no_match_vars)": avoid regex performance penalty. # 2007-09-04 Well, that experiment didn't work. Spammers still found the # script with its new name (and the new input names) on my site and # are sending more web form spam than ever. Overhauled the script # again, this time making it generate its own email form, so that we # can have a Preview button (that results in a page that includes # the previewed email *and* the reiterated form with the entered # values) in addition to Send. Reportedly, spammers' scripts # generally only try hitting the first submit button found. Due to # needing a lot more webmaster configurability so we can generate # the HTML form page per their liking, added handling of the new # email_form_data.confSOMERANDOMSTRING config file, which requires # the CPAN module Config::General. Now it's no longer necessary for # users of this script to hardcode their email address in the script # (which makes upgrading more of a nuisance) to avoid spammer # harvesting -- the random string in the config file name (plus # disabling of indexes for the directory) now takes care of that # protection. # 2007-04-22 Due to recent spammer abuse / probing of the email_form_data # installations on my site, added documentation suggesting people # rename the script when installing it on their sites (in case # spammers are keying off of "mail" and "form" in the script name), # and renamed the _email_form_data_* parameters to _efd_*. # 2007-01-21 Modified the result page to include a copy of the email that was # sent, so people can easily use copy & paste to save a record of it # (as they would have in their Sent folder if they were sending the # email with an actual email client). Emailing them a copy is not # an option since otherwise this script could be used as a spam # reflector. The echo of the email can be suppressed with the new # _email_form_data_suppress_email_echo parameter. # 2006-11-24 Require _email_form_data_subject, _email_form_data_body, or some # user-defined input to be non-empty before we'll send an email. # This could still allow content-free emails to be submitted in the # case of user-defined radio buttons and the like, though. # 2006-11-23 Did some betterment of the documentation. # 2006-11-22 Completely revamped. The email recipient can no longer be # specified as an HTTP parameter -- it now must be hardcoded in the # script (for anti-spam reasons). Made the email format cleaner, to # aid in the use of this script replacing mailto: links. Now output # the sender's name and address as a "comment" in the From: line as # well as on the Reply-To: line. Selectable redirect behavior: # immediate, timeout-based, or none. CGI::Carp's die() message # format sucks (get ultra-wide pages due to the <pre>) -- use our # own die() function as in my other CGIs. No longer save sendmail # output to temp file -- just send its stdout to stderr (error_log). # Special reserved parameters now all begin with "_email_form_data". # 2003-03-15 Use CGI.pm's DISABLE_UPLOADS and POST_MAX variables to protect # against DoS attacks. POSTs of more than 1 MiB will error. # 2002-08-17 Apparently the copies of Reuven Lerner's form-mail.pl that I was # replacing with this script had been modified without comment by # someone to take the recipient as a hidden form input, inducing # the security hole (since the now untrusted recipient string was # still being passed to sendmail on the commandline) and spammer # vulnerability. Reuven's original (at least version 1.3) # apparently did not contain these holes, so I've fixed the comments # that said it did. # 2002-02-25 Finally followed up on the TBD mentioned in the previous entry. # 2000-06-23 CGI::Carp won't stop errors from external programs from coming out # at the wrong time. Therefore we need to send sendmail's output to # a temp file and then output it later on if we detect failure. # TBD: Need to name temp file properly and delete it upon exit. # 2000-06-09 Properly format any TEXTAREA input as a coherent block. # 2000-06-09 Include web client's host IP and name (if available). # 2000-06-09 If email_subject parameter is given, use that for Subject: line. # 2000-06-07 Original. ## Modules used ################################################################ use CGI; use Config::General qw(ParseConfig); # for config file parsing functions use English qw(-no_match_vars); # allow use of names like @ARG rather than @_ use warnings; # output warnings for this script but not for modules used # Use only while debugging (due to major performance hit): #use diagnostics; # output verbose versions of warnings; slow ## Subroutines ################################################################# sub die_pre_html_start { print $cgi->header(-type => $content_type); print $cgi->start_html(@start_html_params, -title => "ERROR from $program_name_no_path"); print $cgi->h1("ERROR from $program_name_no_path:"); print $cgi->p(@ARG); print $cgi->end_html, "\n"; exit 1; } sub html_escape { # This is a copy & paste of CGI.pm's internal escapeHTML() routine except # that I've changed '"' to escape as '"' instead of '"' since the # latter was accidentally left out of the HTML 3.2 DTD, making # http://validator.w3.org/ complain about pages that have transformed # strings like 'Vinyl 12" Single'. Also removed the here-meaningless # 'dontescape' checking. my $toencode = shift; return undef unless defined($toencode); $toencode=~s/&/&/g; $toencode=~s/\"/&\#34;/g; $toencode=~s/>/>/g; $toencode=~s/</</g; return $toencode; } sub sanitize_addr { my $address_part = shift; $address_part =~ s/[\n\r]//g; $address_part =~ s/\\/\\\\/g; $address_part =~ s/"/\\"/g; return $address_part; } ## Initialization ############################################################## $CGI::DISABLE_UPLOADS = 1; $CGI::POST_MAX = 1048576; # 1 MiB $charset = "ISO-8859-1"; $content_type = "text/html; charset=$charset"; $lang = "en"; $OUTPUT_AUTOFLUSH = 1; # let the browser render as dynamically as possible ($program_name_no_path = $PROGRAM_NAME) =~ s<.*/><>; $static_meta_tags = {Generator => "efd, by" . ' Dan Harkless -- http://harkless.org/dan/software/'}; @start_html_params = (-meta => $static_meta_tags); $cgi = new CGI; if ($cgi->cgi_error()) { # Surprisingly, current browsers (as of 2003) don't respond properly to HTTP # status code 413, so we'll generate an HTML error page rather than calling # $cgi->header($cgi->cgi_error()). die_pre_html_start($cgi->cgi_error()); } ## Main ######################################################################## opendir(CWD, "."); @potential_config_files = grep(/^email_form_data\.conf.+/, readdir CWD); closedir CWD; if (scalar(@potential_config_files) == 0) { die_pre_html_start("No config file found."); } elsif (scalar(@potential_config_files) > 1) { die_pre_html_start("Multiple potential config files found. Please rename" . " or delete all but one of them."); } # I would use -IncludeDirectories and -IncludeGlob here, but those options # aren't supported in the 2.27 version of Config::General included in the Fedora # Extras repository. %config = ParseConfig(-ConfigFile => $potential_config_files[0], -AllowMultiOptions => "no"); if ($config{email_to}) { $email_to = $config{email_to}; } else { die_pre_html_start("email_to parameter not defined in config file."); } if ($config{email_from}) { $email_from = $config{email_from}; } else { $email_from = $email_to; } if ($config{dtd}) { $dtd = $config{dtd}; } else { $dtd = "-//W3C//DTD HTML 4.01 Transitional//EN"; } if ($dtd =~ /[^X]HTML (2\.0|3\.2|4\.01?)/i) { # Need to do this before calling $cgi->meta() below, as we haven't yet told # CGI we don't want XHTML/HTML5-style self-closing tags like <META ... /> # for prior DTDs. $CGI::XHTML = 0; } push @start_html_params, (-dtd => $dtd); # This block needs to come after $dtd setting -- see above. if (defined($config{charset})) { $charset = html_escape($config{charset}); if ($charset) { $content_type = "text/html; charset=$charset"; } else { # Even though it's allowed per HTTP 1.1 to specify the Content-Type # without an explicit charset and have it default to ISO-8859-1, the # version of CGI.pm I'm using changes the below into an explicitly # defaulted "text/html; charset=ISO-8859-1" in the HTTP header (though # it comes through as-is in the <META HTTP-EQUIV>). $content_type = "text/html"; } } push @start_html_params, (-head => $cgi->meta({-http_equiv => "Content-Type", -content => $content_type})); if ($dtd !~ /[^X]HTML [23]/i) { if ($config{lang}) { $lang = $config{lang}; } push @start_html_params, (-lang => $lang); } if ($config{stylesheet_url}) { push @start_html_params, (-style => {-src => $config{stylesheet_url}}); } # TBD: Support title suffixes too? if ($config{title_prefix}) { $title_prefix = $config{title_prefix}; } else { $title_prefix = $program_name_no_path; } $title = $title_prefix; if ($cgi->param("_efd_preview")) { $title .= ": Preview"; } elsif ($cgi->param("_efd_send")) { $title .= ": Email Sent"; } push @start_html_params, (-title => $title); # Start HTML: print $cgi->header(-type => $content_type); print $cgi->start_html(@start_html_params), "\n"; if ($config{header}) { print $config{header}; } else { print $cgi->h1($title_prefix); } print "\n"; if (($cgi->param("_efd_preview") and $cgi->param("_efd_preview") eq "Preview") or ($cgi->param("_efd_send") and $cgi->param("_efd_send") eq "Send")) { $user_entered_message_data = 0; foreach $param_name ($cgi->param()) { if (($param_name eq "_efd_body" or $param_name eq "_efd_subject" or $param_name !~ /^_efd/) and $cgi->param($param_name) ne "") { $user_entered_message_data = 1; last; } } if (not $user_entered_message_data) { print "<p>ERROR: No message data entered. Please try again.</p>\n"; } else { # Subject: if ($cgi->param("_efd_subject")) { ($subject = $cgi->param("_efd_subject")) =~ s/[\n\r]//g; $email = "Subject: $subject"; } else { $email = "Subject: Form data from " . $cgi->url(); } # Start of the mail body. $email .= "\n\n"; # What machine sent data to which form, and which script processed it. $email .= "[The following email was sent via "; if ($cgi->request_method()) { $email .= "a " . $cgi->request_method() . " to "; } $email .= $cgi->url(); if ($cgi->referer()) { $email .= ", called from " . $cgi->referer(); } if ($cgi->param("_efd_redirect_url")) { $email .= ", linked to from " . $cgi->param("_efd_redirect_url"); } if (defined $ENV{"REMOTE_ADDR"}) { # no $cgi->remote_addr() function $email .= ", submitted by "; $remote_addr = $ENV{"REMOTE_ADDR"}; if (defined $ENV{"REMOTE_PORT"}) { $remote_addr .= ":" . $ENV{"REMOTE_PORT"}; } if (defined $ENV{"REMOTE_HOST"} and $ENV{"REMOTE_ADDR"} ne $ENV{"REMOTE_HOST"} ) { $email .= $ENV{"REMOTE_HOST"} . " (" . $remote_addr . ")"; } else { $email .= $remote_addr; } } if (defined $cgi->user_agent()) { $email .= ", running " . $cgi->user_agent(); } $email .= ".]\n\n"; # Parameters from the form. foreach $param_name ($cgi->param()) { if ($param_name eq "_efd_body") { $email .= $cgi->param($param_name) . "\n\n"; } elsif ($param_name !~ /^_efd_/) { $email .= "$param_name:\n"; $email .= $cgi->param($param_name) . "\n\n"; } } $email =~ s/\s+\z//; # remove any trailing whitespace # Figure out From (email version and display version) and Reply-To. $from_comment = ""; $reply_to = ""; if ($cgi->param("_efd_sender_email")) { $sender_email = sanitize_addr($cgi->param("_efd_sender_email")); $from_comment = "<" . $sender_email . ">"; $reply_to = "<" . $sender_email . ">"; } if ($cgi->param("_efd_sender_name")) { $sender_name = sanitize_addr($cgi->param("_efd_sender_name")); $from_comment = " " . $from_comment if $from_comment; $from_comment = $sender_name . $from_comment; $reply_to = '"' . $sender_name . '" ' . $reply_to if $reply_to; } $captcha_answered_incorrectly = 0; if ($cgi->param("_efd_preview")) { if ($config{preview_message}) { print $config{preview_message}; } else { print "<p>The following is a <em>preview</em> of your message.", " It will not be sent until you hit the Send button ", " below.</p>"; } } elsif ($config{captcha} and $cgi->param("_efd_send") and ((not defined $cgi->param("_efd_captcha_expected")) or (not defined $cgi->param("_efd_captcha_actual")) or ($cgi->param("_efd_captcha_expected") != $cgi->param("_efd_captcha_actual")))) { if (not defined $cgi->param("_efd_captcha_actual")) { print "<p>ERROR: Required parameter _efd_captcha_actual is", " missing.</p>\n"; } elsif (not defined $cgi->param("_efd_captcha_expected")) { print "<p>ERROR: Required parameter _efd_captcha_expected is", " missing.</p>\n"; } else { # TBD: Would be more user-friendly to check during Preview too. print "<p>ERROR: The math question must be answered correctly", " to send the email. Please try again.</p>\n"; $captcha_answered_incorrectly = 1; } } else { # Send # Un-taint the $PATH before running programs. sendmail is always in # one of these two directories, right? $ENV{PATH} = "/usr/lib:/usr/sbin"; # Do *not* put the recipient on the commandline (as many form # mailing scripts do). Doing so would allow hackers to use # recipient names like "nobody; rm -rf /". Putting the recipient on # a To: line in the email and using sendmail -t saves us from this, # although users of very old versions of sendmail still need to # watch out, as those allowed sending mail directly to a program if # the first character of the recipient was '|'. If these people # haven't upgraded, though, this script is the least of their # security concerns. The -i option prevents lines containing # periods by themselves from terminating the email. We redirect # stdout to the stderr stream so any unexpected sendmail output will # go to the error_log. if (not open SENDMAIL, "| sendmail -i -t 1>&2") { print "<p>ERROR: Can't fork(): $OS_ERROR. Aborting. The", " below email was <em>not</em> sent.</p>\n"; } else { # To: (Don't output this to the page!) print SENDMAIL "To: $email_to\n"; # From: and Reply-To: print SENDMAIL "From: "; print SENDMAIL '"' . $from_comment . '" ' if $from_comment; print SENDMAIL "<$email_from>\n"; print SENDMAIL "Reply-To: $reply_to\n" if $reply_to; # Send the mail. print SENDMAIL $email; if (not close SENDMAIL) { print "<p>sendmail failed with "; if ($OS_ERROR) { print '$OS_ERROR = ' . $OS_ERROR . "; "; } print "exit code = " . ($CHILD_ERROR >> 8) . ". Aborting. The below email was <em>not</em>" . " sent.</p>\n"; } else { if ($cgi->param("_efd_redirect_url")) { # We know where the user came to the email form from. if ($config{email_sent_plus_redirect_message}) { print $config{email_sent_plus_redirect_message}, "\n"; } else { print "<p>The below email has been sent. ", " You can use your browser's Back", " functionality or click on the following URL to", " resume browsing from where you were:</p>\n"; } print "<blockquote>", $cgi->a({href => $cgi->param("_efd_redirect_url")}, $cgi->escapeHTML($cgi->param( "_efd_redirect_url"))), "</blockquote><br>\n"; } else { # We don't know where the user came to the email form # from. if ($config{email_sent_no_redirect_message}) { print $config{email_sent_no_redirect_message}, "\n"; } else { print "<p>The below email has been sent. ", " Please use your browser's Back", " functionality to resume browsing the", " site.</p>\n"; } } } } } if ($reply_to) { # No need to let the web visitor know about the From: munging. $email = "From: $reply_to\n" . $email; } $email = $cgi->escapeHTML($email); $email =~ s{\n}{<br />\n}g; if (not $captcha_answered_incorrectly) { print "<table border=1><tr><td><tt>$email</tt></td></tr></table><br>\n"; } } } else { if ($config{initial_message}) { print $config{initial_message}; } else { print "<p>You can use the form below to email me.</p>"; } } print "\n"; if ((not $cgi->param("_efd_send")) or $captcha_answered_incorrectly) { if ($config{input_x}) { $input_x = $config{input_x}; } else { $input_x = 72; } if ($config{input_y}) { $input_y = $config{input_y}; } else { $input_y = 30; } print $cgi->start_form(), "\n"; print "<table>\n"; print " <tr valign='middle'>\n"; print " <th align='right'>Your name:</th>\n"; print " <td>", $cgi->textfield(-name => "_efd_sender_name", -size => $input_x, -style => "font-family: monospace"), "</td>\n"; print " </tr>\n"; print " <tr valign='middle'>\n"; print " <th align='right'>Your email address:</th>\n"; print " <td>", $cgi->textfield(-name => "_efd_sender_email", -size => $input_x, -style => "font-family: monospace"), "</td>\n"; print " </tr>\n"; print " <tr valign='middle'>\n"; print " <th align='right'>Subject:</th>\n"; print " <td>", $cgi->textfield(-name => "_efd_subject", -size => $input_x, -style => "font-family: monospace"), "</td>\n"; print " </tr>\n"; print " <tr valign='top'>\n"; print " <th align='right'>Message:</th>\n"; print " <td>", $cgi->textarea(-name => "_efd_body", -columns => $input_x, -rows => $input_y, -style => "font-family: monospace"), "</td>\n"; print " </tr>\n"; if ($config{captcha}) { print " <tr valign='middle'>\n"; if ($config{captcha} eq "addition") { if ($cgi->param("_efd_captcha_expected")) { $random_int = $cgi->param("_efd_captcha_expected") - 10; } else { srand(time() ^ ($PROCESS_ID + ($PROCESS_ID << 15))); $random_int = int(rand(11)); } print " <th align='right'>"; if ($captcha_answered_incorrectly) { print "<font color=red>"; } print "What is 10 + ", $random_int, "?"; if ($captcha_answered_incorrectly) { print "</font>"; } print "</th>\n"; print "<td>", $cgi->textfield(-name => "_efd_captcha_actual", -size => 5, -style => "font-family: monospace"); # TBD: Make this less easy to subvert? print $cgi->hidden(-name => "_efd_captcha_expected", -value => 10 + $random_int); print "  (NOTE: Must be answered correctly to prevent", " spam-bot abuse of this form.)</td>\n"; } else { print " <th></th>\n"; print " <td>ERROR: Invalid value for 'captcha' specified in", " config file: '", $cgi->escapeHTML($config{captcha}), "'.</td>\n"; } print " </tr>\n"; } print " <tr>\n"; print " <td></td>\n"; print ' <td style="padding-top: 7px">'; print $cgi->submit(-name => "_efd_preview", -value => "Preview"); print "    "; print $cgi->submit(-name => "_efd_send", -value => "Send"), "</td>\n"; print " </tr>\n"; print "</table>\n"; if ($cgi->param("_efd_redirect_url")) { print $cgi->hidden(-name => "_efd_redirect_url", -default => $cgi->param("_efd_redirect_url")), "\n"; } elsif ($cgi->referer()) { print $cgi->hidden(-name => "_efd_redirect_url", -default => $cgi->referer), "\n"; } print $cgi->end_form(), "\n"; } if ($config{footer}) { print $config{footer}, "\n"; } print $cgi->end_html, "\n";