Web Feedback with Sentience Test

by John Walker

According to tradition, the door of Plato's Academy in Athens bore the legend AGEOMETRETOS MEDEIS EISITO “Let no one ignorant of geometry enter”. Well, times change and intellectual standards erode—one of the biggest challenges in developing computer aided design software was finding competent programmers who knew anything at all about geometry. But even in this demotic age, is it too much to ask strangers who wish to comment on pages of a Web site primarily devoted to science and technology to be able to solve a linear equation?

FeedbackForm is a server-side CGI (Common Gateway Interface) program written in the Perl language which permits you to add Feedback buttons to pages on your Web site. When pressed, the user receives a feedback form which permits entering an E-mail style message, which can be either anonymous or include the submitter's name and E-mail address for eventual replies. In order to submit the form, the user is asked to solve a linear equation with integer coefficients and unknown. If the correct solution is entered, the feedback message is forwarded to an E-mail address defined in the CGI program on the server and hence completely invisible to the submitter or to a junk mailer crawling the site seeking E-mail addresses to abuse. The feedback E-mail includes the URL on which the user pressed the feedback button, avoiding the mystery when a user makes some generic comment about a page such as “The back link is broken” and neglects to cite the URL in which they noted the flaw.

Try It!

The following button is “live”—it will launch the FeedbackForm application in “test mode”. You can play around with it, going as far as requesting feedback to be sent, but no feedback E-mail will actually be sent, so you needn't worry about cluttering up my mailbox. To actually send feedback, use the button at the bottom of this document.

Downloading and Installation

Note: FeedbackForm is a Common Gateway Interface (CGI) program written in the Perl language. Installing a CGI program requires detailed knowledge of the Web server configuration of the system on which it is to be installed, and may require administrative (super-user) privilege to install. Programs and data directories must be installed with the correct ownership attributes and permissions, and program and library paths may need to be set to permit the CGI program to find the utilities it requires. Since Web server configurations differ widely from system to system, there's no cookbook approach to installing a program such as this—you need to understand what you're doing, and know how to track down and fix problems based on error messages in the HTTP server error log.
To install FeedbackForm on your Web server, perform the following steps.

  1. Download the FeedbackForm distribution archive:
  2. Uncompress and extract the archive into a new empty directory.
  3. Determine where the following directories and programs are located on your Web server. In the Example column, I give the directory used by the system-wide Apache Web server installed with Red Hat Enterprise Linux 3. These directories vary from system to system; it is essential you determine the correct locations for your installation! If you're installing FeedbackForm as an individual user on a multi-user server, the directories will usually be within your own home directory tree—consult your grumpy, overworked system administrator for information and admonishment. I've provided blank items in the following table to write in the directory paths for your system. Each variable is linked to its description in the detailed configuration section.

Directory/File Variable Example Location on your system
CGI binaries $CGI_Directory /var/www/cgi-bin                    
Mailer protocol $mailer mail                    
Mailer $mailerpath /bin/mail                    
Logresolve $logresolve /usr/bin/logresolve                    
Spell checker $spellCmd /usr/bin/spell                    
Perl interpreter #! /usr/bin/perl                    
  1. Choose settings for the following variables and enter them into the script. Each variable is linked to its description in the detailed configuration section.

Description Variable Your Setting
Feedback E-mail $email  
Master key $masterKey  
Working directory $workingDirectory  
Log file $logFile  
  1. Edit the program, locate the configuration section at the top, and replace the values assigned to each of the variables in the Variable column of the tables above with the correct values for your system. The “#!” item specifies the location of the Perl interpreter on your system; on Unix-like systems you can determine this with the shell command “which perl”. This location should be entered on the first line of, following the “#! ” characters.
  2. Create the directory you defined as $workingDirectory in This directory must be owned by the user ID under which CGI programs are run on your Web server. Note that to change the directory ownership, you must be logged in as the super-user (root). One trick you can use to determine the user ID of your HTTP server on Unix systems is to enter the command “ps -ef | grep httpd” which will list the processes belonging to the server, giving the user name. On Red Hat Linux systems, the user and group IDs are both “apache”.
  3. Save the modified file.
  4. Copy the into your CGI binaries directory; this is the directory you specified as $CGI_Directory. Make sure the program has execute permission for all users (on Unix-like systems, you can use the command “chmod 755” set global execute permission).
  5. Test it with the command:
    ./ test
    If you get a “bad interpreter” or “not executable” error, the location of Perl in the first line of the program is probably incorrect or the process of editing the program has caused it to lose execute permission (or Perl is missing or improperly installed on your system). If you get one or more missing directory or file messages or other misconfiguration errors, correct the configuration accordingly until you get the message “FeedbackForm configuration tests passed.”. Note that you must run this test on your Web server machine to verify that the programs and directories are properly configured there.

Adding Feedback Buttons to Your Pages

To add a feedback button to a page on your Web site, simply add the following HTML code where you wish the button to appear.

    <form name="feedback" method="post"
    <input type="submit" value=" Feedback "  />

If your server uses a different $CGI_Directory, adjust the “action” attribute in the form tag accordingly. You're free to change the label (“value”) on the button to whatever you wish, or use a graphical button instead of a text button. You can put as many feedback buttons on a given page as you like (for example, one at the top and bottom of a long document), and you can do so either by considering the entire document a single form with multiple submit buttons, or declaring each button within its own form (which allows you to include other forms between feedback buttons).

Customising Form Requests

You can customise the behaviour of FeedbackForm by including the following hidden input fields within the form which invokes the script. These may be used in any combination, but only one field with a given name may appear in an individual form.

<input type="hidden" name="mode" value="modes" />
The specified modes are used when processing the request. If more than one mode is required, they may be separated by a comma. Available modes are:
<input type="hidden" name="pagetitle" value="text" />
The specified text will appear as the title of the page from which feedback is sent in the feedback form. If not specified, the URL of the referring page will be used.
<input type="hidden" name="return" value="URL">
The specified URL specifies the URL to which the user will be returned after sending feedback, overriding the default of returning to the page which contained the feedback button.
<input type="hidden" name="backlink" value="text" />
The specified text will be used for the link back to the document from which feedback was sent (or the URL given by a return field, if specified) instead of the default “Back to URL”.
<input type="hidden" name="j" value="Encrypted_address" />
The feedback will be E-mailed to the specified Encrypted_address instead of the $email address configured in the script. This request will be honoured only if FeedbackForm is configured with $overrideEmail nonzero and if the specified address, once decrypted, matches the configured $restrictEmail pattern. E-mail addresses are encrypted for use in this item by invoking the script (with the same $masterKey the CGI program uses), an argument of mailcode and the E-mail address you wish to encrypt. For example:
      ./ mailcode
The complete input tag, ready to paste into your HTML, will be printed.

Adding and Removing White List Entries

Whenever a feedback message is forwarded to the designated E-mail address, if the user specified an E-mail address, a URL is included which will add that E-mail address to the white list (if it is not on the list), or remove it (if it is).

Once an E-mail address is on the white list, a user who enters that address in a feedback form's E-mail field need not solve the problem in order to send feedback. Although the problem will be presented (since there's no way to know the requester's E-mail address when initially sending the form), a white listed user may simply leave the solution blank. The confirmation a white listed user receives when sending feedback contains a reminder that they're on the white list and need not solve future problems to send feedback.

Detailed Configuration

You can customise the configuration of FeedbackForm in many ways by changing variable definitions at the start of the script. The following sections document each of these variables, grouped by function. The variables do not necessarily appear in the script in the same order they're listed here.

Execution Environment

$CGI_Directory Absolute path of the directory from which your Web server runs Common Gateway Interface (CGI) scripts.
$actionscript URL with which the is invoked. This is usually “/cgi-bin/”, but you may change it as required if your server uses a different naming convention or you wish to invoke a different program while testing.
$mailer Name of program used to send feedback E-mail messages. This is just the program name, for example “mail”, and is used solely to identify how the message is passed to the mailer program.
$mailerpath Path name used to invoke the program used to send feedback E-mail. It's best to specify an absolute path name, but you can use a relative name as long as you're sure a CGI program invoked by your Web server will be able to find it. The program name needn't have anything to do with $mailer; it's fine, for example, to set $mailer to “mailx” and $mailerpath to, say, “/usr/ucb/mail”.
$email Feedback will be sent to this E-mail address unless the $overrideEmail mechanism is enabled and an encrypted E-mail address is specified in the request form. If you filter your incoming mail, you may want to create an alias for messages sent by FeedbackForm, and/or use the little-known “+” gimmick (for example with an existing address to identify feedback mail.
$logresolve Absolute path name to execute the logresolve program, included with the Apache Web server, which looks up IP addresses (for example and returns their fully qualified domain names (such as If this program is not available on your server, set $logresolve to the null string; in this case domain lookups will not be performed and numeric IP addresses will be used in messages and log entries.
$spellCmd Absolute path name to execute the spelling checker program. This must be a program which reads the text to be checked from standard input and writes the possibly misspelled words to standard output. If you set $spellCmd to the null string, spelling checking will be disabled and no “Spell Check” button will appear in the feedback form. If you're worried about the burden spelling checking may impose on your server (or you don't have a suitable spelling checker program), set $spellCmd to the null string. This will remove the “Spell Check” button from the form and disable spelling checking.
$workingDirectory Directory in which FeedbackForm will keep its databases. This directory must be readable and writable by the user ID under which CGI programs run, and should not be accessible through your Web site.

User Interface

$wrapMax If nonzero, the message input text box will be this number of characters in width and lines longer than this will be wrapped to be less than or equal to this length. If zero, the text input box will be 70 characters wide and lines longer than this will be left unchanged.
$headerText The HTML text assigned to this variable replaces the default heading on the feedback form. This can be any HTML code. The value assigned to this string is evaluated at run-time before being included in the document, and hence may interpolate string variables such as $referer (the URL of the referring page) and $pagetitle (the title of the referring page). Because the string is evaluated, all double quote marks must be preceded by a backslash. It is usually most convenient to define $headerText as a single-quoted “here” document. If $headerText is the null string, the default heading is generated. Here is the rather baroque $headerText declaration used by the Fourmilab feedback page.
    $headerText = << 'EOF';
<table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">
<td width=\"160\" align=\"left\">
<a href=\"../\" target=\"_top\">
<img src=\"/images/logo/swlogo.png\" width=\"82\" height=\"74\"
    align=\"left\" border=\"0\" style=\"padding-left: 6px\"
    alt=\"Fourmilab home\" />
<td align=\"center\">
<font size=\"+2\"><em>Index Librorum Liberorum</em></font><br>
<font size=\"+1\">Send feedback on
    page:<br><a href=\"$referer\">$qpagetitle</a></font><br>
<td width=\"160\" align=\"right\" style=\"padding-right: 6px\">
<p />
<hr />
$footerText The HTML text assigned to this variable will be appended to the bottom of the feedback form. If set to the null string, nothing will be appended. As with $headerText, this string is evaluated at run-time, and double quote marks must be escaped with backslashes.
$returnTime After a user successfully submits feedback, they'll receive a confirmation page which, after $returnTime seconds, will return them automatically to the page on which they pressed the Feedback button. If you set $returnTime to 0, no automatic return will occur. The default is 10*$seconds.
$sentience If nonzero (the default), the user will be required to solve a problem before feedback will be forwarded. It set to zero, feedback will be sent immediately the user presses the “Send” button.
$maxtries Number of incorrect solutions a user may enter before being placed on the black list. The default is 3.
  Note: When setting the following parameters which govern generation of the equation the user is asked to solve, be careful that in the worst case (greatest order, coefficients, and answer), the right hand side of the equation does not exceed the maximum value of a 32 bit signed integer (2,147,483,648). That'd be a bit much to ask of the Gentle User in any case.
$minorder Minimum order (highest power of the unknown) of the equation sent to the user. The default value of 1 denotes a linear equation.
$maxorder Maximum order of the equation sent to the user. The default value of 1 denotes a linear equation. If you set $maxorder to a greater value (2 for quadratic, 3 for cubic, 4 for quartic, etc.), the order will be randomly chosen between $minorder and $maxorder inclusive.
$mincoeff Smallest coefficient of terms in the equation. The default value is 1. Note that you are free to set $mincoeff to a negative value; if you do, randomly chosen coefficients which come out zero are changed to 1.
$maxcoeff Largest coefficient on terms in the equation. This may be set to any value greater than $mincoeff; values are randomly selected within this inclusive range.
$minanswer Smallest value for the solution to the equation. The default value is 4. You may set this value negative.
$maxanswer Largest value for the solution to the equation, default 100.
$leastanswer If the range from $minanswer to $maxanswer includes zero, randomly chosen answers with absolute value less than $leastanswer will be set to $leastanswer, preserving the sign of the randomly generated value.


$masterKey Values sent to the user and returned when the form is submitted are encrypted using the $masterKey, which should be 32 bytes (64 digits) or more of hexadecimal data. You must generate a unique master key when you install the program. You can create a $masterKey declaration ready to paste into the program by running:
./ masterkey
$restrictReferer If $restrictReferer is non-null, it is used as a regular expression to test the HTTP_REFERER field received from the Web server. This can (and should) be used to restrict access to pages originating at the site where it's hosted. If your site has several aliases (,, etc.) or permits access by IP address or IP address range, you'll have to craft a regular expression which matches everything you may see as a referer. Here, for example, is the declaration used for the Fourmilab server:
$restrictReferer = qr{^http://(((www\.)?fourmilab\.(ch|com|net|org|to))|193\.8\.230\.\d+)/};
$cribIP If $cribIP is non-null, IP addresses which match its regular expression pattern will have the solution to the equation already filled into the text box in the feedback page. This is handy (only) for testing new versions of the program as you don't need to actually solve the equation every time in a long series of tests, and since you can still edit the value filled in the box, it permits testing both right and wrong answers, which white listing does not. The default is null.
$overrideEmail If set nonzero (the default is 0), $overrideEmail permits feedback request forms to specify the E-mail address to which feedback is sent, overriding the default address specified by $email. The E-mail address is specified by a hidden data field in the request form named “j” whose value is the E-mail address encrypted with the $masterKey you set for the program by invoking it with a command like:
./ mailcode
  which will print the complete hidden field declaration to paste into the request form in your HTML document. Do not use this unless you absolutely must! Keeping the E-mail address hidden in the CGI program is immeasurably more secure than embedding it, however protected, in a document a user can examine. If you do permit E-mail addresses to be overridden, be sure to specify a $restrictEmail pattern so that, in the worst case, overrides are confined to your own site.
$restrictEmail If you must set $overrideEmail to 1, you should restrict E-mail overrides to your own domain by forcing them to match the regular expression $restrictEmail. Otherwise, should your $masterKey be compromised or somebody figure out how to break the encryption in some other manner, your feedback page could be hijacked for use by other sites and, even worse, used to send junk mail to third parties.


FeebackForm maintains several databases, all kept as Comma Separated Value (CSV) text files, to store permissions and the current state of transactions it's processing. All of these database files are kept within the $workingDirectory you define.

White List

The White List is a list of E-mail addresses which are permitted to send feedback without entering a solution to the sentience test problem. Links included in feedback messages permit you to add and remove White List items.

$whiteList White List database file name, “whitelist.csv” by default.
$restrictIP If $restrictIP is non-null, it is used as a regular expression to test the IP address from which white list requests are sent. If the IP address does not match, the request will be rejected. Use this if you always manage your mail from a known IP address or range. If you process such requests from different IP addresses, leave this null (the default).

Black List

The Black List keeps track of IP addresses from which more than the maximum permitted number of consecutive wrong answers have been received. Additional feedback requests from an IP address on the black list will receive an immediate message indicating they've already struck out. Black list entries expire after a specified time—this both gives the user another chance, and avoids permanently banning an IP address which may, in fact, be assigned to different users in succession as they connect to and disconnect from the Internet. A reasonable black list timeout interval is an hour or two.

$blackList Black List database file name, “blacklist.csv” by default.
$blacklistTime Interval, in seconds, between an IP address's being placed on the black list and when it expires and is removed. Constants are defined for $seconds, $minutes, $hours, $days to make this definition more readable. The default value is 2*$hours. If set to zero, the black list mechanism is entirely disabled.

Green List: Problem Expiration and Re-use

Problems sent to a user for solution are time stamped and normally must be solved with a given time interval. This prevents, for example, some cunning urchin's taking the problem to school the next day and inveigling teacher into solving it. Once a correct solution is entered, the time stamp of the last successfully solved problem is saved in the Green List. This defends against a “replay attack”, where the user saves a feedback page with the valid solution and then submits it over and over with additional messages. If FeedbackForm receives a purported solution which has either expired or has a time stamp equal to or earlier than the last problem solved, it chides the user and provides a fresh new problem. Problem expiration permits entries in the Green List to be removed after the expiration time is reached.

$greenList Green List database file name, “greenlist.csv” by default.
$expirationTime Interval, in seconds, between the time a problem is generated and sent to a user for solution and when it expires, after which a solution will no longer be accepted. This is also how long an entry for the time stamp of the last problem solved by a user remains in the green list. The default value is 1*$hours; if you set the value to 0, problems will never expire and no green list will be kept.

Log File

$logFile Log file name, “$workingDirectory/log.csv” by default. Note that while the default setting keeps the log file in the $workingDirectory, you're free to put it anywhere you like that CGI programs have write access. If you specify the null string, log file generation will be completely disabled. You can transfer logging to a new file at any time simply by renaming the existing log file to something else.

Log File Format

If you specify a non-null value for $logFile, FeedbackForm appends records to the specified file in Comma Separated Value (CSV) format for each transaction it processes. Each record begins with the fields:


followed by field specific to the item Type. Fields which appear in log items have the following format and meaning. Fields which contain a comma or double quote are enclosed in double quotes; double quotes within a field are denoted by two consecutive double quotes. For example, the text:

"Shut up", he explained.
would appear as the field:
"""Shut up"", he explained."
within the CSV log file.

Answer Solution to equation entered by the user.
Date_time Date and time the item was logged in the server's local time in the ISO-8601-like format: “YYYY-MM-DD:hh:mm:ss”.
E-mail E-mail address entered in the feedback form or being added to or removed from the white list.
Equation Equation the user was asked to solve.
IP_address The IP address in dotted quad notation (for example, from which the logged transaction originated.
Name Name entered in the feedback form.
Referer URL of the page from which the feedback form was invoked. This is as given by the HTTP_REFERER passed by the Web server.
Rhs Right hand side value of equation sent to the user.
Rhs_Wrong Right hand side value with user's incorrect solution.
Sent_to E-mail address to which feedback was sent (or would have been sent were test mode not specified).
Subject Subject entered in the feedback form.
Type Text string identifying the event this item represents (and hence its format).

The fields in each type of log item are listed below, along with a discussion of the event that item represents and the interpretation of type-specific fields.

The E-mail address has been added to the white list.
The E-mail address has been deleted from the white list.
The user submitted feedback with a problem which has expired. The fields have the same meaning as those of the Sent item.
The user entered the incorrect solution Answer to the Equation. The actual right hand side of the equation is Rhs; with the user's Answer the right hand side would be Rhs_Wrong. The balance of the fields have the same meaning as those of the Sent item.
The user entered an Answer which is non-numeric. The balance of the fields have the same meaning as those of the Sent item.
The user attempted to send additional feedback re-using a correct solution to an equation previously accepted (but not expired, and hence present in the green list). The fields have the same meaning as those of the Sent item.
A correct solution to the Equation has been entered, and feedback E-mailed to Sent_to. The E-mail, Name, and Subject are as entered in the feedback form; note that the E-mail address and Name may be null. The Referer is the page from which the feedback form was originally invoked, as given by the HTTP_REFERER passed by the Web server. If $restrictReferer is null, note that the Referer can be null if a user enters the URL of the FeedbackForm CGI script directly in a browser's URL window.
A user requested a feedback form after having failed $maxtries times to solve the problem, and before the $blacklistTime has expired. The fields have the same meaning as those of the Sent item.
A user submitted a feedback form in which the equation embedded in the form has been tampered with or otherwise corrupted (detected by checksum failure). The fields have the same meaning as those of the Sent item.
A correct solution was entered, but feedback not sent because the “mode” item in the request form specified “test” mode. The fields have the same meaning as those of the Sent item.

Questions and Answers

Does the feedback page set a cookie in the visitor's browser?
No, it is entirely cookie-free. The state of the session is remembered in hidden input fields in the form, which are encrypted with a secret key invisible to the user ($masterKey) and checksummed against tampering. The black list and white list files are databases kept on the server.
Is Java or JavaScript required?
Nope—everything is done on the server by the CGI program. No fancy browser gizmos of any kind are required.
Whoa! I'm a math professor, and those equations are way too simple for my audience. Can I make them tougher?
You bet. By default, coefficients are limited to values between 1 and 100 and solutions to values from 4 to 100. You may increase these by modifying the $mincoeff, $maxcoeff, $minanswer, and $maxanswer variables in the script. Note that you can set the minima to negative integers; if you do, coefficients of zero and solutions with absolute value less than $leastanswer will be automatically excluded to avoid trivial problems.

If that's too easy, you can increase the maximum order of the equation to quadratic or cubic by setting $maxorder to 2 or 3 respectively. The order of the equation is chosen randomly between $minorder and $maxorder. It's unkind to ask folks to solve quartic and higher order equations, but you can do it if you must. If you do, you may have to limit $maxcoeff and $maxanswer, as the right hand side of an equation with maximum order, coefficients, and solution must fit in a 32 bit integer (2147483648).

If that still doesn't float your boat, I'd entertain a code contribution which provides the option of requiring the user to solve a Diophantine Equation.

Why not make the equation an image? If you generated it with TeXtoGIF it would look better and wouldn't be vulnerable to automatic parsing by programs which scan the HTML.
That wouldn't be accessible to blind users with screen reader programs. If the equation were embedded in the ALT text of the image, it would still be vulnerable to robot solvers. At this point, I'm going to consider anybody willing to write a program to avoid solving a linear equation a curious creature from whom I'm willing to entertain feedback.
Why go to all this trouble to generate problems which are so easily solved by computer?
Well, first of all, one of the first things I learned in my ten years on the Web is how few people can solve anything on a computer. Those who can tend to be among the interesting ones. I decided to initially fix the bar very low and see what happens; if a linear equation does the job, I've learned something. (I have no preconceived notions—this is an experiment.) Second, I always find it easier to build all the infrastructure for a project at the outset, when I have all the details of the code loaded into my short-term memory. Trying to add something like a black list or protection against various ways to game the system with browser caching and the like can consume more time and inflict more anguish when digging back into code written a year or more before. Since I anticipate the user test part of this program to evolve as the experiment progresses (for example, to include word games, algebra, sequence completion, word problems from elementary arithmetic, etc.), I decided to create a framework which would support such problems without a lot of changes to the gory guts of the program.
Isn't the encryption rather primitive?
Sure, but let's be realistic about what it's protecting. The worst thing somebody could do if they managed to break the encryption is decode the equation embedded in the form, and they don't need to, since it can be parsed just as easily from the human-readable text. As long as the E-mail address override (if enabled at all) is protected by a $restrictEmail pattern, there's little damage one can do there, and if somebody makes bogus entries in the white list, the log file will point right back to them. Since everything is encrypted with a cipher block chaining algorithm with a four byte random initial vector, it's pretty difficult to dig the secret key out of a bunch of traffic. Adding AES or similar encryption to something this simple just doesn't seem justified to me unless the $masterKey proves inadequate in the real world.

This software is in the public domain. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, without any conditions or restrictions. This software is provided “as is” without express or implied warranty.
Valid CSS   Valid XHTML 1.0
by John Walker
3rd December 2006

Fourmilab Home Page