#!/usr/local/bin/perl # # ngb-confirm.pl # The "Confirmation" half of the Nizkor Guest Book $version = "1.01"; # Requires perl 5; tested only with perl5.001m. # # Written by Jamie McCarthy (jamie@nizkor.almanac.bc.ca) # for the Nizkor Project (http://www.almanac.bc.ca/). # Copyright 1995 Jamie McCarthy. # Available from # # This source code may be publicly distributed by any means, # as long as the above authorship, copyright, and availability # notices are kept intact. If you distribute a modified version, # please explain what changes have been made. # # Changes in 1.01: # # Removed simple boolean check of $!, since $! returns boolean true on # some systems even when no error occurred. # Guest book file and special guest book file are properly created now. # Minor textual changes. # Time zone correction variable. You'd think someone would have put # this into a standard perl CGI library by now... # If you don't have cgi-lib.pl, check out # http://www.bio.cam.ac.uk/web/form.html require "cgi-lib.pl"; ################################################################### ################################################################### # CONSTANTS ################################################################### ################################################################### ################################################################### # constants you can change if you want ################################################################### # This value is only valid if you're using UnixWare 2.0 (and, # obviously, if you're in Canada's Pacific time zone!). This # format is more common: "PST8PDT". To find the appropriate # value for your site, 'echo $TZ' at your shell prompt. # $ENV{"TZ"} = ":Canada/Pacific"; # If you'd prefer not to have horizontal rules between entries, # simply make this variable empty. Or if you prefer double # rules, or blockquoted rules, or a special graphic that you # use for a rule, or whatever, just put its HTML tag here. $entrySeparator = "
"; # Feel free to localize to your own language. @monthFullName = ( "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ); ################################################################### # constants you should leave alone ################################################################### $todaysDate = $monthFullName[(localtime)[4]] . " " . (localtime)[3] . ", 19" . (localtime)[5]; # I'd appreciate it if you left this as is. Thanks. $ngbCredits = <Nizkor $version END_OF_NGB_CREDITS ################################################################### # constants you must modify for your web site ################################################################### $upToLink = <[ return to Alumni Home Page ] END_OF_UP_TO_LINK $addressInformation = <Webster University Alumni Office
webmaster\@webster2.websteruniv.edu END_OF_ADDRESS_INFORMATION # This directory must be readable and writable by the scripts, # which means the user/guest that is running your http daemon # must have read/write permission to it. Usually you'll want # to keep prying eyes out of this directory; for information # on NCSA httpd's authentication, for example, see # http://hoohoo.ncsa.uiuc.edu/docs/setup/admin/UserManagement.html $guestBookPrivateDir = "/usr/local/etc/httpd/htdocs/temp/guest-book/"; # This is the URL for that private directory. $guestBookPrivateDirURL = "http://www.websteruniv.edu/temp/guest-book/"; # This is the filename of the special guest book, which always # goes in that private directory. $specialGuestBookFilename = "alumni-guest-book-special.html"; # Permissions for the special guest book. $specialGuestBookPermissions = 0600; # This is the directory of the normal guest book. $guestBookDir = "/usr/local/etc/httpd/htdocs/alumni/"; # This is the URL for the directory of the normal guest book. $guestBookDirURL = "http://www.websteruniv.edu/alumni/"; # This is the filename of the guest book, which always goes in # that directory. $guestBookFilename = "alumni-guest-book.html"; # Permissions for the guest book. $guestBookPermissions = 0644; # Your email contact address. $emailContact = "webmaster\@webster2.websteruniv.edu"; # The text of the guest book file. $emptyGuestBookText = < Webster University Alumni Guest Book

Alumni Guest Book


These comments were left by alumni visiting this site. You are welcome to add your own.


$upToLink

$todaysDate $ngbCredits

END_OF_EMPTY_GUEST_BOOK_TEXT # The text of the special guest book file. $emptySpecialGuestBookText = < Webster Alumni Special Guest Book

Webster Alumni Special Guest Book


This is the "special" guest book, where we decide what to do with entries that we don't want to go to the main guest book.


October 28, 1995

END_OF_EMPTY_SPECIAL_GUEST_BOOK_TEXT ################################################################### ################################################################### # SUBROUTINES # # subroutines are: # # printHTMLTop - print the code for the top of the HTML page # printHTMLBot - print the code for the bottom of the HTML page # # readEntries - read in list of entries' filenames # loadEntries - read entry files and load associative arrays # getBlurb - given info about an entry, return an identifying comment # # printPotentialProblems - check for and print any potential problems # createGuestBook - create guest book file # createSpecialGuestBook - create special guest book file # # printForm - output HTML for form to choose what to do with entries # processForm - handle the choices made on that form ################################################################### ################################################################### ################################################################### # printHTMLTop # # Print the code for the top of the HTML page. # # You're welcome to change this to suit yourself. ################################################################### sub printHTMLTop { print &PrintHeader(); # from cgi-lib.pl print < Webster Alumni Guest Book Entry Confirmation

Alumni Guest Book Entry Confirmation


END_OF_HTML_TOP } ################################################################### # printHTMLBot # # Output the code for the bottom of the HTML page. # # You're welcome to change this to suit yourself. ################################################################### sub printHTMLBot { print <
$upToLink

$todaysDate $ngbCredits

END_OF_HTML_BOT } ################################################################### # readEntries # # Reads in the list of entries' filenames, from the # $guestBookPrivateDir directory. Stores that list in @entry. ################################################################### sub readEntries { local($entry); opendir(ENTRIES, "$guestBookPrivateDir"); do { $entry = readdir(ENTRIES); unshift(@entry, $entry) if $entry =~ /^entry/; } while ($entry); closedir(ENTRIES); } ################################################################### # loadEntries # # Must be called after readEntries, since it uses the @entry list # formed by readEntries. Walks through each entry file and # fills out associative arrays to reflect the data in those files. # # %timestamp is the array that holds the keys to the other arrays: # %name, %email, %url, %ipaddress, %browserEmail, and %comments. # In a %timestamp array member, the key is the timestamp, and the # value is the entry filename. For each of the other arrays, the # key is the entry filename, and the value is the data for that # entry. So, to iterate through all the entries, one iterates # through the keys of %timestamp, picks up the values, and uses # each value as the key of the remaining arrays. %timestamp was # of course chosen because it's pretty much guaranteed that each # timestamp will be unique. (Not totally guaranteed, so yes this # is technically a bug. But nothing bad will happen if several # entries should happen to have the same timestamp, and it's so # rare I'm not going to worry about it.) ################################################################### sub loadEntries { local($entryFullText, $entry); LOADENTRY: foreach $entry (@entry) { # Do some (probably over-paranoid) sanity checking. if (!-s "$guestBookPrivateDir$entry") { print "

Entry not found: $filename.\n"; print "This is a bug; email\n"; print "Jamie)\n\n"; next LOADENTRY; } if (!open(ENTRY, "$guestBookPrivateDir$entry")) { print "

Could not open $entry.\n"; print "This is a bug; email\n"; print "Jamie)\n\n"; next LOADENTRY; } # Now read in the entry file and assign it to the # associative arrays. $entryFullText = ""; while () { $entryFullText .= $_; } close(ENTRY); ($timestamp, $name{$entry}, $email{$entry}, $url{$entry}, $ipaddress{$entry}, $browserEmail{$entry}, $comments{$entry}) = split(/^/, $entryFullText, 7); chop($timestamp, $name{$entry}, $email{$entry}, $url{$entry}, $ipaddress{$entry}, $browserEmail{$entry}); $timestamp{$timestamp} = $entry; } } ################################################################### # getBlurb # # Given some information about a guest book entry -- specifically, # the name, email, url, and comments -- generates a one-liner # "blurb" that identifies that entry. ################################################################### sub getBlurb { local($blurb); local($name, $email, $url, $comments) = @_; if ($name) { $blurb = " (from " . $name . ")"; } elsif ($email) { $blurb .= " (from " . $email. ")"; } elsif ($url) { $blurb .= " (from " . $url. ")"; } elsif ($comments) { local($line1); ($line1) = split(/^/, $comments); chop($line1); $line1 =~ s/<[^>]*>//g; if (length($line1) > 50) { $line1 = substr($line1, 0, 50); $line1 =~ s/\s+\S+$//; } $blurb .= " (" . $line1. ")"; } return $blurb; } ################################################################### # printPotentialProblems # # Looks for potential problems with the script -- directories # that don't exist or don't have the right permissions, files that # don't have the right permissions, that kind of thing. Prints # HTML code describing any problems founds. Returns false if # everything looks okay to proceed, or true if serious problems # were found. ################################################################### sub printPotentialProblems { local($fatalErrorFound, $printUserGroupNames); local($pidName, $gidName) = ((getpwuid($>))[0], (getgrgid($)))[0]); if (!-s "$guestBookDir" || !-d "$guestBookDir") { $fatalErrorFound = 1; print <The specified guest book directory, $guestBookDir, does not exist! Have you edited the constants in ngb-confirm.pl? The variable \$guestBookDir is only one of many that must be set properly. END_OF_GUEST_BOOK_DIR_NOT_EXIST } if (!-s "$guestBookPrivateDir" || !-d "$guestBookPrivateDir") { $fatalErrorFound = 1; print <The specified special guest book directory, $guestBookPrivateDir, does not exist! Have you edited the constants in ngb-confirm.pl? The variable \$guestBookPrivateDir is only one of many that must be set properly. END_OF_SPECIAL_GUEST_BOOK_DIR_NOT_EXIST } if (-d "$guestBookDir" && !-s "$guestBookDir$guestBookFilename") { &createGuestBook(); if (-s "$guestBookDir$guestBookFilename") { print <The guest book file did not exist, so it was created. (This is normal if and only if this is the first time you've executed this cgi script. Reload this page and this message should not reappear.) END_OF_CREATED_GUEST_BOOK_FILE } else { $fatalErrorFound = 1; $printUserGroupNames = 1; print <The guest book file, $guestBookDir$guestBookFilename, did not exist, and it could not be created. The error message reported was "$!". Just in case that isn't informative enough for you: the most common error would be that this process user/group doesn't have write permission for the file's parent directory, namely $guestBookDir. END_OF_COULD_NOT_CREATE_GUEST_BOOK_FILE if (!-w "$guestBookDir") { print "\nYep, that seems to be the problem.\n"; } } } if (-d "$guestBookPrivateDir" && !-s "$guestBookPrivateDir$specialGuestBookFilename") { &createSpecialGuestBook(); if (-s "$guestBookPrivateDir$specialGuestBookFilename") { print <The special guest book file did not exist, so it was created. (This is normal if and only if this is the first time you've executed this cgi script. Reload this page and this message should not reappear.) END_OF_CREATED_SPECIAL_GUEST_BOOK_FILE } else { $fatalErrorFound = 1; $printUserGroupNames = 1; print <The special guest book file, $guestBookPrivateDir$specialGuestBookFilename, did not exist, and it could not be created. The error message reported was "$!". Just in case that isn't informative enough for you: the most common error would be that this process user/group doesn't have write permission for the file's parent directory, namely $guestBookPrivateDir. END_OF_COULD_NOT_CREATE_SPECIAL_GUEST_BOOK_FILE if (!-w "$guestBookPrivateDir") { print "\nYep, that seems to be the problem.\n"; } } } if (-e "$guestBookDir$guestBookFilename" && !-w "$guestBookDir$guestBookFilename") { $fatalErrorFound = 1; $printUserGroupNames = 1; print <This process user/group does not have write permission to the specified guest book file, $guestBookDir$guestBookFilename. END_OF_NO_WRITE_PERMS_GUEST_BOOK } if (-e "$guestBookPrivateDir$specialGuestBookFilename" && !-w "$guestBookPrivateDir$specialGuestBookFilename") { $fatalErrorFound = 1; $printUserGroupNames = 1; print <This process user/group does not have write permission to the specified special guest book file, $guestBookPrivateDir$specialGuestBookFilename. END_OF_NO_WRITE_PERMS_SPECIAL_GUEST_BOOK } if ($printUserGroupNames) { print <For your reference, this perl script is running under the effective user name of "$pidName" and the effective group name of "$gidName". END_OF_USER_GROUP_NAMES } return $fatalErrorFound; } ################################################################### # createGuestBook # # This subroutine is called when it's time to write to the guest # book and there isn't a file there! It simply creates the guest # book file itself. ################################################################### sub createGuestBook { if (open(GUESTBOOK, ">$guestBookDir$guestBookFilename")) { print GUESTBOOK $emptyGuestBookText; close(GUESTBOOK); chmod $guestBookPermissions, "$guestBookDir$guestBookFilename"; } } ################################################################### # createSpecialGuestBook # # This subroutine is called when it's time to write to the special # guest book and there isn't a file there! It simply creates the # special guest book file itself. ################################################################### sub createSpecialGuestBook { if (open(SPECIALGUESTBOOK, ">$guestBookPrivateDir$specialGuestBookFilename")) { print SPECIALGUESTBOOK $emptySpecialGuestBookText; close(SPECIALGUESTBOOK); chmod $specialGuestBookPermissions, "$guestBookPrivateDir$specialGuestBookFilename"; } } ################################################################### # printForm # # Print out the form for the webmaster to fill in. ################################################################### sub printForm { local($entry, $nEntries, $entryFullText); local($timestamp); local($addCheckText, $deleteCheckText); $nEntries = @entry; print <The quick usage guide:

When you click the "Submit Confirmations" button, you perform an action on each entry to the guest book. That action is determined by the radio button above that entry. END_OF_GUIDE_PART_1 if ($nEntries < 90) { print "

Usually, you'll want to Add an entry, so that is default.\n"; $addCheckText = "checked"; $deleteCheckText = ""; } else { print "

Note: Since there are over 90 entries, it's assumed\n"; print "that a hacker has tried to flood the system. Delete\n"; print "is the default.\n"; $addCheckText = ""; $deleteCheckText = "checked"; } print <If you Add to Special, the entry will not appear in the normal guest book, but rather in $guestBookPrivateDirURL$specialGuestBookFilename. The IP name or number, and the email address that the browser may or may not report, will only be logged for entries in the special guest book. Such entries are typically not visible to the public.

If you want to leave an entry alone for now and deal with it later, choose Leave. If you want to delete an entry outright, choose Delete. END_OF_GUIDE_PART_2 local($entrySingPl) = "entr" . ($nEntries == 1 ? "y" : "ies"); print "

There ", ($nEntries == 1) ? "is" : "are", " $nEntries\n"; print "$entrySingPl waiting to be added or deleted.\n\n"; if (&printPotentialProblems()) { print <These errors preclude the possibility of this script working properly, so the form to confirm the $entrySingPl will not even be presented. If you can't solve the problems on your own, you might try emailing the author, Jamie McCarthy, at jamie\@nizkor.almanac.bc.ca. END_OF_ERRORS_PRECLUDE_FORM } else { if ($nEntries > 0) { print "

\n\n"; &loadEntries(); foreach $timestamp (sort keys %timestamp) { $entry = $timestamp{$timestamp}; print <

Add Add to Special Leave Delete

Timestamp: $timestamp
Name: $name{$entry}
Submitted email: $email{$entry}
URL: $url{$entry}
IP: $ipaddress{$entry}
Browser email: $browserEmail{$entry} $comments{$entry} END_OF_ENTRY } print <


END_OF_SUBMIT_BUTTONS } } } ################################################################### # processForm # # Process the form, handling each entry appropriately. ################################################################### sub processForm { &loadEntries(); # # Walk through the keys in the %timestamp array. Since we # sort the keys before walking through them, we know we'll # be getting the entries in chronological order. # HANDLEENTRY: foreach $timestamp (sort keys %timestamp) { $entry = $timestamp{$timestamp}; $filename = "$guestBookPrivateDir$entry"; if (!open(ENTRY, $filename)) { print "

Could not open: $filename\n"; print "(this error shouldn't happen; email\n"; print "Jamie)\n\n"; next HANDLEENTRY; } if (!defined($in{$entry}) || !$in{$entry}) { print "

The entry\n"; print "$entry\n"; print "is unaccounted for. It was\n"; print "probably just entered -- you might want to go back to the\n"; print "confirmation page and reload.\n"; next HANDLEENTRY; } # # The entry seems to be OK. Let's handle it. # if ($in{$entry} eq "leave") { # # If "leave" was chosen, we don't do anything, and just print # a note saying that it was indeed left alone. # print "

Left alone \n"; print "$entry\n"; print &getBlurb($name{$entry}, $email{$entry}, $url{$entry}, $comments{$entry}); print "\n\n"; } elsif ($in{$entry} eq "delete") { # # If "delete" was chosen, we try to unlink the entry file # and, if successful, print a note to that effect. # if (!unlink($filename)) { print "

Could not delete: $filename\n"; print "(this error shouldn't happen; email\n"; print "Jamie)\n\n"; next HANDLEENTRY; } print "

Deleted $entry"; print &getBlurb($name{$entry}, $email{$entry}, $url{$entry}, $comments{$entry}); print "\n\n"; } elsif ($in{$entry} eq "add" || $in{$entry} eq "addspecial") { # # If "add" was chosen, things are tricky. We have to add # the comments in a certain form, into the _middle_ of the # guest book file. # # We do this by copying the existing guest book, up to # and including a special trigger line, into a temporary # file. Then we copy these comments into that file as # well. Then we continue copying the rest of the old # guest book. Then we replace the old guest book with # the temporary file. # # This could be done slightly more efficiently by # processing all guest-book entries in batch, but the # net savings would be small and the error-handling would # be trickier. # unlink("${guestBookPrivateDir}guest-temp.html"); open(GUESTTEMP, ">${guestBookPrivateDir}guest-temp.html"); select(GUESTTEMP); $success = 0; $tryingToCreate = 0; if ($in{$entry} eq "add") { if (!-s "$guestBookDir$guestBookFilename") { $tryingToCreate = 1; &createGuestBook(); } $success = open(GUESTBOOK, "$guestBookDir$guestBookFilename"); } else { if (!-s "$guestBookPrivateDir$specialGuestBookFilename") { $tryingToCreate = 1; &createSpecialGuestBook(); } $success = open(GUESTBOOK, "$guestBookPrivateDir$specialGuestBookFilename"); } # # Did we successfully open up the appropriate guest book? # if (!$success) { # # No -- output the reason why and continue handling entries. # select(STDOUT); close(GUESTTEMP); print "

The entry\n"; print "$entry\n"; print "could not be added, because the guest book could not\n"; print "be "; if ($tryingToCreate) { print "created. "; } else { print "opened. "; } print "The entry was left alone. The error reported was\n"; print ""$!."\n\n"; next HANDLEENTRY; } # # Walk through the existing guest book, and when the special # line is found, insert this new entry. # while () { tr#\015##d; # if guestbook file had CRs somehow inserted, trim them print $_; if (//) { # # There's that special line. Insert the entry. # print "

$entrySeparator\n\n"; # # Print the comments. # print "$comments{$entry}\n\n"; # # Now print the cite. This "firstLinePrinted" trickery is # so that the first line is preceded by a "

" and each # following line by a "
". # $firstLinePrinted = 0; print "

\n"; if ($name{$entry}) { print ($firstLinePrinted++ ? "
" : "

"); print "$name{$entry}\n"; } if ($email{$entry}) { print ($firstLinePrinted++ ? "
" : "

"); print ""; print "$email{$entry}\n"; } if ($url{$entry}) { print ($firstLinePrinted++ ? "
" : "

"); print "$url{$entry}\n"; } if ($in{$entry} eq "addspecial") { print ($firstLinePrinted++ ? "
" : "

"); print "$ipaddress{$entry}\n"; print "
$browserEmail{$entry}\n" if $browserEmail{$entry}; } print ($firstLinePrinted++ ? "
" : "

"); print "$todaysDate\n"; print "

\n\n"; # # After we insert the entry in the guest book, we continue # walking through the guest book, line by line, copying it # to the temp file. # } } close(GUESTBOOK); select(STDOUT); close(GUESTTEMP); # # Now rename that temp file to overwrite the guest book. # $success = 0; if ($in{$entry} eq "add") { $success = rename("${guestBookPrivateDir}guest-temp.html", "$guestBookDir$guestBookFilename"); chmod $guestBookPermissions, "$guestBookDir$guestBookFilename" if $success; } else { $success = rename("${guestBookPrivateDir}guest-temp.html", "$guestBookPrivateDir$specialGuestBookFilename"); chmod $specialGuestBookPermissions, "$guestBookPrivateDir$specialGuestBookFilename" if $success; } if (!$success) { # # This means that no further entries will be able to be # added to this guest book, since they won't be able # to overwrite it either. This error needs to be taken # care of immediately, so processing is aborted to # indicate its seriousness. # print "

Could not overwrite guest book.\n"; print "Aborting entry processing. The error reported was\n"; print ""$!."\n"; print "I suggest returning to the confirmation form (with\n"; print "your "go back" button) and seeing if there\n"; print "aren't any warnings that you should take care of.\n\n"; last HANDLEENTRY; } # # The entry was successfully added; report that fact. # print "

Added $entry to\n"; print "guest book" if ($in{$entry} eq "add"); print "special guest book" if ($in{$entry} eq "addspecial"); print &getBlurb($name{$entry}, $email{$entry}, $url{$entry}, $comments{$entry}); # # Delete the entry file. # if (!unlink($filename)) { print "\n

But could not delete: $filename\n"; print "(this error shouldn't happen; email\n"; print "Jamie)"; } print "\n\n"; } } } ################################################################### ################################################################### # main program # # Decide whether to invoke printForm() or processForm(). ################################################################### ################################################################### &readEntries(); &printHTMLTop(); if (&ReadParse()) { &processForm(); } else { &printForm(); } &printHTMLBot();