Writing CGI scripts

CGI scripts are a simple way of providing dynamic content to users. For example, you could write a program to allow users to run calculations over the network, or you could provide access to a centralized database. However, CGI scripts require a lot of processing. On a busy server, it would be more efficient to write a module for Apache.

  1. Find out which httpd.conf your system is using. Each installation puts this file in a different location.
    This command prints the location of the httpd binary that is actually used:
        cat /etc/rc.d/apache | grep httpd | grep bin  
    Run `strings' on the binary to find out where it looks for httpd.conf:
        strings /usr/local/apache/bin/httpd | grep httpd.conf 
    In this example, we will assume httpd.conf was in /etc/httpd.
  2. Edit /etc/httpd/httpd.conf and uncomment the following lines:
        AddType text/html .shtml
        AddHandler server-parsed .shtml
        AddType text/x-server-parsed-html .shtml
        Options  ExecCGI    
    You could also enable `includes' by commenting out any lines containing
        IncludesNOEXEC    
    and adding
        Options Includes 
    If any point in the path to the CGI files is a symlink, this must be changed to
        Options Includes FollowSymLinks
    or you will get "Access Forbidden". (This has to be done in several places.) Another possible cause of "Access Forbidden" are incorrectly set permissions. One way to distinguish between these two possibilities is to su to wwwrun (or whatever user Apache is running as) and try to run the CGI script manually. If it can be run manually, the problem is in the httpd configuration file; if not, the problem is probably with file permissions.

    The AddType option makes the server parse all files with the extension .shtml. It is possible to make the server parse ordinary .html files, but this is not a good idea, as it will degrade your system performance. Don't enable `includes' unless you absolutely have to.
  3. Restart the server.
  4. Before creating a CGI file, make sure the server is handling CGI correctly by typing
        http://localhost/cgi-bin/test-cgi
    in a browser. If test-cgi is in cgi-bin, it should print something like
        CGI/1.0 test script report:
    
        argc is 0. argv is .
    
        SERVER_SOFTWARE = Apache/1.3.26 (Unix) PHP/4.2.3
        SERVER_NAME = carbon.my_hostname.com
        GATEWAY_INTERFACE = CGI/1.1
        SERVER_PROTOCOL = HTTP/1.0
        SERVER_PORT = 80
        REQUEST_METHOD = GET
        HTTP_ACCEPT = image/gif, image/x-xbitmap, image/jpeg, 
           image/pjpeg, image/png, */*
        PATH_INFO = 
        PATH_TRANSLATED = 
        SCRIPT_NAME = /cgi-bin/test-cgi
        QUERY_STRING = 
        REMOTE_HOST =
        REMOTE_ADDR = 192.168.100.1
        REMOTE_USER =
        AUTH_TYPE =
        CONTENT_TYPE =
        CONTENT_LENGTH =
    Click here for a copy of the test-cgi script.
  5. Create an executable file in /usr/local/apache/cgi-bin. This can be a perl script, a shell script, or a binary, for example:
        #!/bin/sh
        set -f
        echo Content-type: text/plain
        echo
        echo Logging asdf
        logger asdf    
    Set its permissions to:
       -rwx------   1 wwwrun   nogroup  82 Oct 23 22:42 myscript    
    On some servers, the script won't run if the permissions are not set to 700 or if it is not in the cgi-bin directory. Compiled binaries are much safer than shell scripts from a security standpoint. In some configurations, the script must have the extension .cgi or .pl. The script will be run with the permissions of its owner. The script must write the line "Content-type: text/plain" followed by a blank line to stdout before anything else.
  6. In your shtml file, add the line:
      <!--#EXEC CGI="/cgi-bin/myscript"--> 
    or create a form with an "action" (see below). Note: the shtml file must be in /usr/local/apache/htdocs or it will not be executed.
  7. Note that there are severe limitations on what you can do. You can't, for example, execute an X-Window type application or an interactive program. The CGI interface can only handle text I/O. Graphics must be done with HTML. The script can, however, output HTML and create image files that are then loaded by the remote user's browser. Alternatively, to send a GIF file, first send
     Content-type: image/gif 
    followed by a blank line, then send the binary GIF file to stdout.
  8. Any commands in the script must be executable by wwwrun (or whatever user is running httpd). This may mean using sudo or making the binary suid (by typing "chmod a+s < program>"). This has obvious security implications. Be sure to restrict access to the file in /etc/httpd/httpd.conf.

Error messages

Internal Server Error The server encountered an internal error or misconfiguration and was unable to complete your request.
This can be caused by a line in your script that could not be executed. For example, if you had the line
 #!/usr/local/bin/csh 
you would receive this error if csh doesn't exist.

There should also be no spaces between <! and --#, and none at the end ..program-->.

Check to make sure the script is executable by "su"-ing to wwwrun and running the script manually.

When testing the script, you must exit and restart Netscape each time you make a change. Hitting 'refresh' doesn't always work.

Premature end of script headers: myprogram
This message in the error_log file means the server was unable to execute your script. Check to make sure that the AddType, AddHandler, and Options are set correctly in the httpd.conf file. Also check to make sure it is using the correct httpd.conf file. Apache may be using the httpd.conf file in /usr/local/apache/conf or the httpd.conf in /etc/httpd. Finally, check to make sure the script is executable by the server (usually wwwrun or nobody).

Symbolic link not allowed: /usr/local/apache2/cgi-bin/some-symlink

This error usually means one of two things: either your permissions are wrong, or your httpd.conf is missing an Option FollowSymLinks statement.

  1. The user that owns httpd (usually wwwrun) must have execute permission in all subdirectories leading to the cgi script. The cgi script itself must be executable by wwwrun.
  2. Sometimes, the cgi script calls some other program that is located somewhere else or has its permissions set wrong. Check the cgi script.
  3. In your httpd.conf file, you need to change three places to get cgi scripts to run:
    1. In your ScriptAlias line:
    ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"     
    2. In the cgi-bin options section. If this one is not set, cgi programs in the cgi-bin directory will work but programs elsewhere will not. Also, make sure allow from all is set, or cgi programs won't run.
    # this path must match the path in ScriptAlias
    <Directory "/usr/local/apache2/cgi-bin">  
    AllowOverride All
    Options FollowSymLinks
    Order allow,deny
    Allow from all
    </Directory>   
    3. In the main options section. If ExecCGI is not set here, your cgi programs won't run. If FollowSymLinks isn't set here, html files under a symlink won't be found.
    Options Indexes FollowSymLinks ExecCGI Includes SymLinksifOwnerMatch 
    4. Check for stupid errors, like editing the wrong copy of httpd.conf:
    ps -aux | grep httpd
    strings /usr/local/apache2/bin/httpd | grep httpd.conf

Example shtml file

Submit request

The shtml file below will display a Submit button and a selection box with 3 items visible. When the Submit button is clicked, the file 'printer.sh' is executed.

<HTML> 
<HEAD> <TITLE> Printers</TITLE> </HEAD> 
<BODY> 
<FORM ACTION="/cgi-bin/printer.sh" METHOD="GET"> 
<SELECT NAME="hpbw" SIZE=3> 
     <OPTION SELECTED> Check status
     <OPTION> Check queue
     <OPTION> Print test page
     <OPTION> Break Printer
     <OPTION> Remove job from queue 
</SELECT> 
<INPUT TYPE="submit" VALUE="Submit">  Submit request
</FORM> 
</BODY> 
</HTML> 
The file "printer.sh" might contain stuff like
#!/bin/sh
echo "Content-type: text/plain"
echo "" 
echo
case "$QUERY_STRING" in
    hpbw=Check+status)
        echo Printer status
        lpc stat hpbw
    ;;
    hpbw=Check+queue)
        echo Print queue
        lpq -Phpbw
    ;;
    hpbw=Print+test+page)
        echo Printing test page ...
        lpr -Phpbw testpage.ps
    ;;
    hpbw=Break+Printer)
        echo Segmentation fault
        echo Printer is now broken.
        echo Way to go.
    ;;
    *)
    echo Unrecognized command
esac
echo Press Back to continue ...
The form can use "GET" or "POST" to communicate with the script. If it uses "GET", the information is passed in the environment variable "QUERY_STRING". A number of other environment variables, including "REMOTE_HOST" and "REMOTE_ADDR", are also passed to the script. If the form uses "POST", the server reads the string from stdin. The exact number of bytes to read is automatically placed in the environment variable CONTENT_LENGTH. Unlike GET, POST can send large objects such as files.

Alternatively, you could use
   <!--#exec cmd=/usr/local/apache/cgi-bin/mycommand-->
   or
   <!--#exec cgi=/cgi-bin/mycommand-->
somewhere in your .shtml file. Note that "exec cmd" requires the full path of the command. The server may be configured to run server-side includes, or to allow only #exec and #include commands.

Example using Radio Buttons

Here is an example that uses radio buttons to select what to send:

Find in Books Music Videos

<FORM METHOD="GET" ACTION="http://search.somewhere.com/cgi-bin/QResults">
<B>Find</B> 
<INPUT TYPE="text" VALUE="" NAME="keyword" SIZE="25" MAXLENGTH="150">   in 
<INPUT TYPE="radio" NAME="mediaType" VALUE="Book"CHECKED>Books 
<INPUT TYPE="radio" NAME="mediaType" VALUE="Music">Music 
<INPUT TYPE="radio" NAME="mediaType" VALUE="Video">Videos
<INPUT TYPE="submit" NAME="submit" VALUE="Find!"> 
</FORM>

This script will execute the file "/cgi-bin/QResults" on search.somewhere.com and send its data through QUERY_STRING. For example, if the first radio button was clicked, it would send the string
keyword=&mediaType=Book&submit=Find%21
If the user entered "abcde" before clicking, it would send the string
keyword=abcde&mediaType=Book&submit=Find%21
in environment variable QUERY_STRING (0x21 = '!'). This would have to be parsed as assignments to three variables ("keyword", "mediaType" and "submit") which are separated by '&' or ';'. Spaces are converted to '+'. This might seem like a limitation, but in fact it does not pose any problem, because characters such as '+' in the string are converted hexadecimal (e.g., %2B for '+').

Example shtml file using POST

Here is one that uses POST. POST is typically used when there is no data to send, or when there is a large amount of data to send.


<FORM METHOD="POST" ACTION="http://www.somewhere.com/cgi-bin/Logoff">
<INPUT NAME="url" TYPE="hidden" VALUE="http://www.somewhere.com/">
<INPUT TYPE="SUBMIT" VALUE="Finished Shopping">
</FORM>

Example shtml file using POST to send data

This example will execute the Logoff script and will send data from a single-line text box and a multi-line text box. If TYPE="hidden" is present, the single-line text box will be invisible, but the text would still be sent. The ACTION tag can be a URL on a different server, or you can omit the address if the script is on the same server as the Web page.



<FORM METHOD="POST" ACTION="/cgi-bin/Logoff">
<INPUT NAME="url" VALUE="http://www.somewhere.com/"><BR>
<TEXTAREA NAME="address" ROWS=6 COLS=60>
The user can enter 
multiline text
in this area.
</TEXTAREA>
<INPUT TYPE="SUBMIT" VALUE="Finished Shopping">
</FORM>

An extra line would have to be added to the CGI script to acquire the data from stdin.
#!/bin/sh
echo Content-type: text/plain
echo
echo QUERY_STRING = "$QUERY_STRING"
echo REMOTE_ADDR = $REMOTE_ADDR
echo CONTENT_LENGTH = $CONTENT_LENGTH
echo Reading stuff from stdin
read STUFF
echo $STUFF
echo Done reading stuff from stdin
This script will print the following:
QUERY_STRING = 
REMOTE_ADDR = 192.168.100.1
CONTENT_LENGTH = 110
Reading stuff from stdin
url=http%3A%2F%2Fwww.somewhere.com%2F&address=The+user+ca
n+enter+%0D%0Amultiline+text%0D%0Ain+this+area.%0D%0A
Done reading stuff from stdin
Note that the environment variable QUERY_STRING is null.

WARNING: For a real CGI script, you must sanitize any data sent by the user to prevent your program or the shell from interpreting it as a command. Not doing so can be a major security risk. By using specially formatted text, a user might be able to open a command shell or xterm, mail themselves your password file, or change system files and gain remote root access to your computer. The following characters (and their hexadecimal equivalents) should always be removed before passing a user string to a shell: &   ;  `   '   \   "   |   *   ?   ~   <   >   ^   (   )   [ ]   {   }   $   \n   \r

Summary of input tags


Back