home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / info-service / gopher / Unix / GopherTools / gmail-1.01.shar / gmail < prev    next >
Encoding:
Text File  |  1993-03-29  |  25.9 KB  |  808 lines

  1. #!/usr/local/bin/perl
  2. #
  3. #  gmail: mail-to-Gopherspace interface
  4. #
  5. #  usage:  gmail [-c]
  6. #
  7. #  PASR 12/28/92  Rough draft.
  8. #  PASR 12/31/92  Initial test installation.
  9. #  PASR 01/03/93  Added DELETE function.
  10. #  PASR 01/05/93  Reworded authorization rejection message.
  11. #  PASR 01/05/93  Forced sender's address to lowercase for easy matching.
  12. #  PASR 01/08/93  Declared this release 0.1 for beta testing.
  13. #  PASR 01/15/93  Fixed bug caused by perl's inability to write to long
  14. #                 paths; removed $caldir_by_date; made year in output
  15. #                 format be yyyy instead of yy to facilitate sorting.
  16. #  PASR 02/02/93  Added the title line to the body of each file saved
  17. #                 to facilitate WAIS indexing.
  18. #  PASR 02/03/93  Changed "From" lines to ">From" in header in order
  19. #                 to avoid confusing mailers when we send out feedback.
  20. #                 Reversed the logic of the test in &check_target (it was
  21. #                 bogus).
  22. #                 Declared this release 0.2 for further beta testing.
  23. #  PASR 02/04/93  Force addresses in $gmailers file to lower case since
  24. #                 the gmail administrator can't be counted on to remember
  25. #                 to do so. :-)
  26. #  PASR 02/05/93  Added "$publiccal" mode: if it is turned on, submissions
  27. #                 in calendar mode require no authorization in the $gmailers
  28. #                 file (although deletions still do).
  29. #                 Added an optional "$disclaimer".
  30. #  PASR 02/22/93  Declared this release 1.0 for general use.
  31. #  PASR 03/07/93  Fixed security problem by quoting $sender in mail pipe.
  32. #                 Attempt to prevent mail loops when error messages bounce
  33. #                 back to gmail.
  34. #                 Fixed whitespace cleanup in &normalize.
  35. #                 Don't allow tabs in Name= fields in .cap files
  36. #                 (reportedly gopherd can be confused by them).
  37. #                 Unlink data file if corresponding .cap file can't be made.
  38. #  PASR 03/09/93  Create .cap files only when necessary.
  39. #                 Moved misplaced unlink() calls to take place before &abends.
  40. #  PASR 03/12/93  Removed unnecessary &initialize call; just use &parse_message.
  41. #  PASR 03/29/93  Declared this version 1.01.
  42. #
  43. #  TODO: Think about cleaning up &delete_it() (should move aside old
  44. #        data rather than just gobbling it into memory).
  45. #  TODO: Think about screening for binhex and other ASCIIfied binary formats.
  46. #  TODO: Think about extending the "public calendar" mode to cover
  47. #        non-calendar items as well (a "public" mode which puts data into
  48. #        a "$publicdir" directory).
  49. #  TODO: Think about including a command-line option which causes header
  50. #        info to be read from a file, allowing a single installation to
  51. #        be used in multiple configurations (in particular, to allow
  52. #        multiple events calendars).
  53. #  TODO: Think about a reasonable way to specify multiple target directories
  54. #        per sender.  (Hairball!)
  55. #  TODO: Think about notifying the original submitter if someone else
  56. #        deletes or overwrites her data.
  57. #  TODO: Think about using $address instead of/in addition to $sender
  58. #        for address matching.
  59. #  TODO: Think about a way to handle directory names in the gmailers file
  60. #     containing whitespace or # signs.
  61. #  TODO: Add better date checking (eliminate "Feb 30", etc.).
  62.  
  63. #--------------------------------------------------------------------------
  64. # CONFIGURATION: modify these to suit your site!
  65.  
  66. # All targets must live under a directory in this list.
  67. # (EXAMPLE: @safedir = ("/foo/bar/dir1", "/foo/bar/dir2");
  68. @safedir = ("/foo/cwis/gopher/world", "/foo/cwis/gopher/rice",
  69.             "/foo/cwis/gopher/test");
  70.  
  71. # Calendar data live under here (only needed if calendar mode is turned on)
  72. $caldir = "/foo/cwis/gopher/world/Calendars/Events/Upcoming";
  73.  
  74. # File which lists authorized users and their target directories:
  75. $gmailers = "/foo/cwis/gopher/etc/gmailers";
  76.  
  77. # Public calendar switch: if this is set to 1, items submitted in calendar
  78. # mode (the "-c" flag) will not require authorization in the $gmailers file.
  79. # Deletions and non-calendar items will still require authorization.
  80. # This option is irrelevant if you are not running in calendar mode.
  81. $publiccal = 0;
  82.  
  83. # Administrator to notify in case of errors or unauthorized submissions:
  84. $prob_admin = "jdoe@foobar.edu";
  85.  
  86. # Administrator to notify of *every* submission (leave this undefined if
  87. # you only want to hear about problems):
  88. $debug_admin = "jdoe@foobar.edu";
  89.  
  90. # Preferred mailer program.  Must accept recipients' addresses on command
  91. # line and "Subject:" line on standard input.
  92. $MAIL = "/bin/mail";
  93.  
  94. # A short descriptive name of your Gopher server for use in feedback and
  95. # error messages.  It should be under 40 characters for best results.
  96. $server_name = "the FooInfo Gopher server";
  97.  
  98. # Gopher administrator contact info and signature for use in feedback and
  99. # error messages.
  100. $sig =
  101. "To contact the FooInfo Gopher administrator with questions, problems
  102. or suggestions, send mail to fooinfo@foobar.edu or call the Consulting
  103. Center at 527-4983.
  104.  
  105. -- FooInfo Gopher Administrator, Information Systems, fooinfo@foobar.edu
  106. -- This message was automatically generated.
  107. ";
  108.  
  109. # Optional disclaimer to be appended to each item saved in Gopherspace.
  110. # Leave it blank if you don't need a ubiquitous disclaimer.
  111. #$disclaimer = "[Foobar University is not responsible for this stuff.]";
  112. $disclaimer = "";
  113.  
  114. # Umask: the Unix file permissions mask.  Use 002 if you wish to have
  115. # files created by gmail be group-writeable, 022 otherwise.
  116. $UMASK = 022;
  117.  
  118. #--------------------------------------------------------------------------
  119. # Feedback and error messages.  You can tinker with these if you like,
  120. # but it shouldn't be necessary.
  121.  
  122. # Acceptance message.
  123. $accept_msg =
  124. "Your submission was posted to $server_name.
  125. Please use Gopher to check it and make sure it looks as you
  126. intended.  If it does not, please submit it again.
  127.  
  128. If you did *not* submit this request, it may mean that someone has been
  129. forging electronic mail in your name.  Please contact the Gopher
  130. administrator immediately.
  131. ";
  132.  
  133. # Authorization message.
  134. $auth_msg =
  135. "Your submission was not accepted for $server_name
  136. because you have not been authorized to submit data by mail from this
  137. e-mail address.  If you would like to sign up to do so, please contact
  138. the Gopher administrator.
  139. ";
  140.  
  141. # Date message.
  142. $date_msg =
  143. "Your announcement was not accepted for $server_name
  144. because the subject line did not contain an appropriately formatted
  145. date.  The subject line must begin with a date in the format
  146. \"yy-mm-dd\" or \"yyyy-mm-dd\".  For example:
  147.  
  148.      Subject: 92-10-31: Institutional Halloween Party
  149.  
  150.      Subject: 2001-01-10: Twenty-first Century Lecture
  151.  
  152. Please reformat the date in your subject line and resubmit your
  153. announcement.
  154. ";
  155.  
  156. # Deletion acknowledgement.
  157. $del_ack_msg =
  158. "Your request to delete data in $server_name
  159. was accepted.  Please check the data below and make sure that it was
  160. what you intended to delete.
  161.  
  162. If you did *not* request that this item be deleted, it may mean that
  163. someone has been forging electronic mail in your name.  Please save
  164. this message and contact the Gopher administrator immediately.
  165. ";
  166.  
  167. # Deletion error message.
  168. $del_err_msg =
  169. "Your deletion request for data in $server_name
  170. was not processed because of the following error:
  171. ";
  172.  
  173. # Error message.
  174. $error_msg =
  175. "Your submission was not accepted for $server_name
  176. because of the following error:
  177. ";
  178.  
  179. # Style message.
  180. $style_msg =
  181. "Your submission was not accepted for $server_name
  182. because either (1) it is not strictly printable ASCII text or (2) it
  183. contains lines which are greater than 80 columns in length.  Please
  184. reformat your data appropriately and resubmit it.
  185. ";
  186.  
  187. # Separators for displaying the submitted item.
  188. $subsep = "-------------------- Submitted request follows --------------------";
  189. $subend = "-------------------- End of submitted request ---------------------";
  190. $delsep = "---------------------- Deleted item follows -----------------------";
  191. $delend = "---------------------- End of deleted item ------------------------";
  192.  
  193. #--------------------------------------------------------------------------
  194.  
  195. # Further initialization.  Don't mess with this.
  196. require("ctime.pl");
  197. require("timelocal.pl");
  198. $auth = 0;            # Is the sender authorized?
  199. $calendar = 0;            # Are we in calendar mode? ("-c" option)
  200. $delete = 0;            # Are we in delete mode? ("DELETE" keyword)
  201. $usage = "usage: gmail [-c]";
  202. umask $UMASK;
  203. $loopsenders = '^(root|mailer-daemon|postmaster)\b';
  204.                 # senders who may mean we're in a mail loop
  205.  
  206. # Now we're rolling...
  207.  
  208. # Refuse to run as root.
  209. &abend("Data not accepted for Gopher due to gmail error",
  210.     $error_msg . "gmail should not be run as root.\n")
  211.     if ($> == 0);
  212.  
  213. # Process command-line options.
  214. ARGS:
  215. while ($#ARGV >= 0) {
  216.     $arg = $ARGV[0];
  217.     shift;
  218.     if ($arg eq "-c") {
  219.         $calendar = 1;
  220.         next ARGS;
  221.     }
  222.     &abend("Data not accepted for Gopher due to gmail error",
  223.         $error_msg . "Unrecognized command-line option: $arg\n\n"
  224.         . $usage);
  225. }
  226.  
  227. # Parse the incoming message.  Global variables returned which we will use:
  228. #   $body  $friendly  @headers  $header  $sender  $subject
  229. &parse_message(STDIN);
  230. $header =~ s/^From/>From/;    # Don't want to confuse mailer in feedback
  231. $subject =~ tr/\t/ /;        # Tabs could confuse gopherd.
  232. $sender =~ s/'//g;        # We'll want to enclose $sender in 's later.
  233. &abend("Data not accepted for Gopher due to gmail error",
  234.     $error_msg . "Sender not defined in mail header.\n")
  235.     unless ($sender);
  236. if ($sender =~ /$loopsenders/io || 
  237.     $subject =~ /(returned mail|user unknown)/i) {
  238.     # We may be in a mail loop.  We can't abend the normal way because
  239.     # that could perpetuate the loop.  We try to signal our distress
  240.     # via other methods, then exit without acknowledging the message.
  241.     system("/usr/ucb/logger -i -p mail.error gmail in possible mail loop with '$sender'")
  242.          if (-x "/usr/ucb/logger");
  243.     exit(0);
  244. }
  245.  
  246. # Try to match the sender in the list of authorized users.
  247. $target = &check_auth();
  248.  
  249. # See if this is a delete request.
  250. # If so, this will modify the $subject accordingly.
  251. $delete = &parse_delete();
  252.  
  253. # If we're in calendar mode, parse the subject line for a date.
  254. # This will modify the $subject and $target accordingly.
  255. &parse_date() if ($calendar);
  256.  
  257. # Check $target to make sure it safely falls within @safedir.
  258. &check_target($target);
  259.  
  260. # Carry out the deletion and exit if we're in delete mode.
  261. $filename = &normalize($subject);
  262. &delete_it() if $delete;
  263.  
  264. # Check style of title and data.
  265. &check_style($subject, $body);
  266.  
  267. # Write the file and the associated .cap file.
  268. $byline = "\n[Submitted by: $friendly ($sender)\n               $headers{'date'}]\n";
  269. &write_it();
  270.  
  271. # Give the user some positive feedback.
  272. &feedback();
  273.  
  274. # Normal end.
  275. exit 0;
  276.  
  277. #--------------------------------------------------------------------------
  278. # abend -- mail an error message to the sender and administrator and exit
  279. #
  280. # This will alert the administrator even if $sender has not yet been
  281. # defined...
  282. #
  283. # usage:  &abend($shortmsg, $longmsg);
  284. # Global variables used:
  285. #      $body $header $sender $MAIL $subend $subsep $prob_admin
  286.  
  287. sub abend {
  288.     local($shortmsg, $longmsg) = @_;
  289.     if ($sender && sender !~ /$loopsenders/io) {
  290.         open (MAIL, "| $MAIL $prob_admin '$sender'");
  291.     } else {
  292.         open (MAIL, "| $MAIL $prob_admin");
  293.     }
  294.     print MAIL "Subject: $shortmsg\n\n";
  295.     print MAIL "$longmsg\n$sig\n\n";
  296.     print MAIL "$subsep\n";
  297.     print MAIL "$header";
  298.     print MAIL "$body";
  299.     print MAIL "$subend\n";
  300.     close(MAIL);
  301.     # We'd like to exit here with an error but it confuses sendmail...
  302.     exit 0;
  303. }
  304. #--------------------------------------------------------------------------
  305. # check_auth -- look up user in list of authorized gmailers
  306. #
  307. # usage:    $target = &check_auth()
  308. # returns:  target directory ($caldir if we are in calendar mode)
  309. #
  310. # side effect: forces $sender to lower case
  311. # global variables used:
  312. #      $calendar $caldir $error_msg $gmailers $auth_msg $sender $target
  313. #         $delete $publiccal
  314.  
  315. sub check_auth {
  316.     local($auth, $targ, $matchaddr);
  317.  
  318.     # If we are in calendar mode and *not* in delete mode and the
  319.     # "$publiccal" switch is turned on, no further authorization is
  320.     # necessary.
  321.     return ($caldir) if ($calendar && !$delete && $publiccal);
  322.  
  323.     $sender =~ tr/A-Z/a-z/;        # ignore case for easy matching
  324.     open (GMAIL, $gmailers) ||
  325.         &abend("Data not accepted for Gopher due to gmail error",
  326.             $error_msg . "Can't open gmailers file $gmailers\n$@");
  327.     while (<GMAIL>) {
  328.         ($matchaddr) = /^(\S*)/;
  329.         $matchaddr =~ tr/A-Z/a-z/;    # ignore case
  330.         if ($matchaddr eq $sender) {
  331.             # carve what we want out of the current line
  332.             s/\s*#.*//;    # remove comments;
  333.             s/\s+$//;    # remove final whitespace;
  334.             s/^\s*\S*\s+//;    # remove initial whitespace
  335.                     # and sender's address
  336.             if ($calendar) {
  337.                 # Look for the "calendar" keyword
  338.                 $auth = (/\bcalendar\b/);
  339.                 $targ = $caldir;
  340.             } else {
  341.                 # Remove the "calendar" keyword
  342.                 s/\s*calendar\s*//;
  343.                 $auth = $targ = $_;
  344.             }
  345.             last;
  346.         }
  347.     }
  348.     close(GMAIL);
  349.     unless ($auth) {
  350.         if ($calendar) {
  351.             &abend("Not authorized to submit data to Gopher events calendar", $auth_msg);
  352.         } else {
  353.             &abend("Not authorized to submit data to Gopher", $auth_msg);
  354.         }
  355.     }
  356.     return($targ);
  357. }
  358. #--------------------------------------------------------------------------
  359. # check_style -- check to make sure that the style is acceptable 
  360. #                (i.e., that the subject and body consist of printable ASCII
  361. #                characters and the lines in the body are all <80 chars wide).
  362. #                Exit with an error message if it is not.
  363. #
  364. # usage:   &check_style($subject, $body)
  365.  
  366. sub check_style {
  367.     local($subject, $body) = @_;
  368.     local($line, $okay, $unprintables);
  369.     $unprintables = "[\000-\010\012-\037]";
  370.  
  371.     $okay = 1;
  372.     $okay = 0 if ($subject =~ /$unprintables/o);
  373.     foreach $line (split(/\n/, $body)) {
  374.         if ((length($line) >= 80) || ($line =~ /$unprintables/o)) {
  375.             $okay = 0;
  376.             last;
  377.         }
  378.     }
  379.     &abend("Data not accepted for Gopher: style problems", $style_msg)
  380.         unless $okay;
  381. }
  382. #--------------------------------------------------------------------------
  383. # check_target -- check to make sure target directory is legitimate,
  384. #                 exit with an error message if it is not.
  385. #
  386. # usage:   &check_target($target)
  387. # Global variables used:  @safedir
  388.  
  389. sub check_target {
  390.     local($target) = @_;
  391.     local($okay, $safe);
  392.  
  393.     $okay = 0;
  394.     # Disallow ".." to keep from climbing up out of @safedir.
  395.     $okay = 0 if ($target =~ m#(^|/)\.\.(/|$)#);
  396.     # Require that $target falls within @safedir.
  397.     $target .= "/";
  398.     SAFELOOP:
  399.     foreach $safe (@safedir) {
  400.         if ($target =~ m#^$safe/#) {
  401.             $okay = 1;
  402.             last SAFELOOP;
  403.         }
  404.     }
  405.     &abend("Data not accepted for Gopher: bad target directory",
  406.              $error_msg . "Bad target directory: $target\n")
  407.         unless $okay;
  408. }
  409. #--------------------------------------------------------------------------
  410. # delete_it -- perform requested deletion of an item in Gopherspace
  411. #
  412. # usage:  &delete_it()
  413. # Global variables used:
  414. #      $filename $subject $target
  415.  
  416. sub delete_it {
  417.     local(@deldata);
  418.  
  419.     # Does the file even exist?
  420.     &abend("Data not deleted from Gopher due to error",
  421.         $del_err_msg . "File does not exist: $target/$filename\n")
  422.         unless ( -f "$target/$filename" );
  423.  
  424.     # Slurp up a copy of data to be deleted.  Yes, this is potentially
  425.     # a big waste of memory.  We should probably copy it or move it
  426.     # instead (maybe even keep a backup), but we're feeling simple-minded
  427.     # today.
  428.     open (DATA, "< $target/$filename");
  429.     @deldata = <DATA>;
  430.     close(DATA);
  431.  
  432.     # Nuke it, and its .cap file too.
  433.     unlink("$target/.cap/$filename");
  434.     &abend("Data not deleted from Gopher due to error",
  435.         $del_err_msg . "Could not delete file $target/$filename\n$@")
  436.         unless (unlink("$target/$filename"));
  437.  
  438.     # Success -- give positive feedback and exit.
  439.     open (MAIL, "| $MAIL $debug_admin '$sender'");
  440.     print MAIL "Subject: Data deleted from Gopher: \"$subject\"\n\n";
  441.     print MAIL "$del_ack_msg\n$sig\n";
  442.     print MAIL "$delsep\n";
  443.     print MAIL "Title:  $subject\n";
  444.     print MAIL "File:   $target/$filename\n\n";
  445.     print MAIL "@deldata";
  446.     print MAIL "$delend\n\n";
  447.     print MAIL "$subsep\n";
  448.     print MAIL "$header";
  449.     print MAIL "$body";
  450.     print MAIL "$subend\n";
  451.     close(MAIL);
  452.     exit(0);
  453. }
  454. #--------------------------------------------------------------------------
  455. # Subroutine expand
  456. #     expand a line (To, Cc, etc.) into a list of addressees.
  457. #
  458. # [Borrowed with thanks from the "audit.pl" package by Martin Streicher
  459. #  (strike@convex.com), revision 1.9, 92/05/01.]
  460. #
  461. sub expand {
  462.     local($_) = @_;
  463.     local(@fccs) = ( );
  464.  
  465.     return(@fccs) if /^$/;
  466.  
  467.     for (split(/\s*,\s*/)) {
  468.     s/.*<([^>]+)>.*/$1/;
  469.     s/@.*//;
  470.     s/.*!//;
  471.     s/\(.*\)//;
  472.     s/\s//g;
  473.     push(@fccs,$_) unless $seen{$_}++;
  474.     } 
  475.  
  476.     return(@fccs);
  477. #--------------------------------------------------------------------------
  478. # feedback -- mail the accepted item back to the user
  479. #             (and to the $debug_admin, if defined)
  480. #
  481. # usage:  &feedback()
  482. # Global variables used:
  483. #      $accept_msg $body $byline $diclaimer $debug_admin $filename
  484. #         $header $MAIL $sender $subend $subsep $target
  485.  
  486. sub feedback {
  487.     open (MAIL, "| $MAIL $debug_admin '$sender'");
  488.     print MAIL "Subject: Data submitted to Gopher: \"$subject\"\n\n";
  489.     print MAIL "$accept_msg\n$sig\n";
  490.     print MAIL "$subsep\n";
  491.     print MAIL "Title:  $subject\n";
  492.     print MAIL "File:   $target/$filename\n\n";
  493.     print MAIL "$header";
  494.     print MAIL "$body";
  495.     print MAIL "$byline";
  496.     print MAIL "$disclaimer\n" if ($disclaimer);
  497.     print MAIL "$subend\n";
  498.     close(MAIL);
  499. }
  500. #--------------------------------------------------------------------------
  501. # normalize -- convert a title to something good for a filename
  502.  
  503. sub normalize {
  504.         local($str) = @_;
  505.         $str =~ s/\s/_/g;             # change white space to _
  506.         $str =~ tr/\/&\\/+/;             # change ands and slashes to +
  507.         $str =~ tr/#%+,\-.0-9:;=@A-Z[]_a-z~/#/c; # change trouble to #
  508.         $str;
  509. }
  510. #--------------------------------------------------------------------------
  511. # parse_delete -- see if the subject line specifies a deletion request
  512. #
  513. # Check whether the subject line begins with "delete" or "DELETE".
  514. # If not, return 0; if so, remove the "delete" keyword from the $subject
  515. # variable and return 1.
  516. #
  517. # usage:  $delete = &parse_delete()
  518. # Global variables used: $subject
  519.  
  520. sub parse_delete {
  521.     local($firstword, $rest);
  522.     ($firstword, $rest) = split(/\s+/, $subject, 2);
  523.     $firstword =~ tr/A-Z/a-z/;
  524.     if ($firstword eq "delete") {
  525.         $subject = $rest;
  526.         return 1;
  527.     }
  528.     return 0;
  529. }
  530. #--------------------------------------------------------------------------
  531. # parse_date -- parse a date on the subject line
  532. #
  533. # This has a side effect: it inserts a weekday into $subject.
  534. # It also abends with a message in case of error.
  535. #
  536. # We try to be flexible in parsing dates.  Here are some formats accepted:
  537. #    92-12-30: This is the preferred format
  538. #    92/12/30 : (Wed) : But we can handle other separators and a weekday
  539. #       1992-12-30: We accept the year with or without the century
  540. #    1-1-1 This is a minimalist January 1, 2001
  541. #
  542. # These will all result in a new subject like this:
  543. #    1992-12-30 (Wed): The way things will look in Gopher
  544. #
  545. # usage:  &parse_date()
  546. # Global variables used: $caldir $caldir_by_date $date_msg $subject $target
  547.  
  548. sub parse_date {
  549.     local($date, $gooddate, $yy, $mm, $dd, $title);
  550.     $gooddate = 0;
  551.     if ($subject =~ /^\s*(\d?\d?\d?\d)\D(\d?\d)\D(\d?\d)\s*:?\s*(.*)/) {
  552.         $year = $1;
  553.         $mm = $2;
  554.         $dd = $3;
  555.         $title = $4;
  556.     }
  557.  
  558.     # Force the portions of the date into (at least) two-digit form.
  559.     $year = "0" . $year if (length($year) == 1);
  560.     $mm = "0" . $mm if (length($mm) == 1);
  561.     $dd = "0" . $dd if (length($dd) == 1);
  562.  
  563.     # Add a century if it is missing.
  564.     if ($year < 100) {
  565.         if ($year > 70) {
  566.             # We're in the waning years of the 20th century
  567.             $year += 1900;
  568.         } else {
  569.             # You mean somebody's still using this program?
  570.             $year += 2000;
  571.         }
  572.     } 
  573.  
  574.     # Don't accept ancient history or Martian calendars
  575.     &abend("Data not accepted for Gopher: bad date", $date_msg)
  576.         unless ($year >= 1970 && $mm >= 1 && $mm <= 12 && 
  577.             $dd >= 1 && $dd <=31);
  578.  
  579.     # Discard a weekday if one followed the date on the subject line.
  580.     # Note the final whitespace in the pattern: we don't want to mess up
  581.     # a subject like "93-06-01 Wedding bells for Sarah and Jim"!
  582.     $title =~ s/^\(?(Mon|mon|Tue|tue|Wed|wed|Thu|thu|Fri|fri|Sat|sat|Sun|sun)\)?\s*:?\s+//;
  583.  
  584.     # Set the global $subject and $target.
  585.     $date = "$year-$mm-$dd";
  586.     $weekday = &weekday($year, $mm, $dd);
  587.     $subject = "$date ($weekday): $title";
  588. }
  589. #--------------------------------------------------------------------------
  590. # Subroutine parse_email_address
  591. #    Parse an email address into address, from, organization
  592. #    address is full Internet address, from is just the login
  593. #    name and organization is Internet hostname (without final domain)
  594. #
  595. # [Borrowed with thanks from the "audit.pl" package by Martin Streicher
  596. #  (strike@convex.com), revision 1.9, 92/05/01.]
  597. #
  598. sub parse_email_address {
  599.     local($_) = @_;
  600.     local($friendly, $address, $from, $organization);
  601.  
  602.     $organization = "local";
  603.     $friendly = "unknown";
  604.  
  605. # From: Disk Monitor Daemon (/usr/adm/bin/dfbitch) <daemon@hydra.convex.com>?
  606.  
  607.     s/^\s*//;
  608.     s/\s*$//;
  609.     if (/(.*)\s*<[^>]+>$|<[^>]+>\s*(.*)$/) {
  610.     $friendly = $+;
  611.     $friendly =~ s/\"//g;
  612.     } elsif (/\(([^\)]+)\)/) {
  613.     $friendly = $1;
  614.     };
  615.  
  616.     s/.*<([^>]+)>.*/$1/;
  617.     s/\(.*\)//;
  618.     s/\s*$//;
  619.     $address = $_;
  620.  
  621.     s/@.*//;
  622.     s/%.*//;
  623.     s/.*!//;
  624.     s/\s//g;
  625.     $from = $_;
  626.  
  627.     $_ = $address;
  628.     tr/A-Z/a-z/;
  629.     if (/!/ && /@/) {
  630.         s/\s//g;
  631.         s/!.*//;
  632.         $organization = $_;
  633.     } elsif (/!/) {
  634.         s/\s//g;
  635.         s/![A-Za-z0-9_@]*$//;
  636.         s/.*!//;
  637.         s/\..*//;
  638.         $organization = $_;
  639.     } elsif (/@/) {
  640.         s/.*@//;
  641.         s/\s//g;
  642.         if (! /\./) {
  643.             $organization = "unknown";
  644.         } else {
  645.             if (/\.(com|edu)$/) {
  646.                 s/\.[A-Za-z0-9_]*$//;
  647.                 s/.*\.//;
  648.             } else {
  649.                 s/\.[A-Za-z0-9_]*$//;
  650.                 s/\.[A-Za-z0-9_]*$//;
  651.                 s/.*\.//;
  652.             };
  653.             $organization = $_;
  654.         };
  655.     };
  656.  
  657.     return ($friendly, $address, $from, $organization);
  658. };
  659. #--------------------------------------------------------------------------
  660. # Subroutine parse_message
  661. #    Parse a message into headers, body and special variables
  662. #
  663. # [Borrowed with thanks from the "audit.pl" package by Martin Streicher
  664. #  (strike@convex.com), revision 1.9, 92/05/01.]
  665. #
  666. sub parse_message {
  667.     local(*INFILE) = @_;
  668.  
  669.     $/ = '';        # read input in paragraph mode
  670.     %headers = ( );
  671.     @received = ( );
  672.  
  673.     $header = <INFILE>;
  674.  
  675.     $* = 1;
  676.     while (<INFILE>) { 
  677.     s/^From />From /g;
  678.     $body = "" if !defined($body);
  679.     $body .= $_; 
  680.     };
  681.     $/ = "\n";        
  682.     $* = 0;
  683.  
  684.  
  685.     ;# -----
  686.     ;# $sender comes from the UNIX-style From line (From strike...)
  687.     ;#
  688.     ($sender) = ($header =~ /^From\s+(\S+)/); 
  689.  
  690.  
  691.     ;# -----
  692.     ;# fill out the headers associative array with fields from the mail
  693.     ;# header.
  694.     ;#
  695.     $_ = $header;
  696.     s/\n\s+//g;
  697.     @lines = split('\n');
  698.     for ( @lines ) {
  699.     /^(\w*):\s*(.*)/ && do {
  700.         $mheader = $1;
  701.         $mheader =~ tr/A-Z/a-z/;
  702.         if (($mheader eq "cc" || $mheader eq "to") && $headers{$mheader}) {
  703.         $headers{$mheader} .= ", $2";
  704.         } elsif ($mheader eq "received") {
  705.         push(@received, $2);
  706.         } else {
  707.         $headers{$mheader} = $2;
  708.         };
  709.     };
  710.     }
  711.     @received = reverse(@received);
  712.  
  713.  
  714.     ;# -----
  715.     ;# for convenience, $subject is $headers{'subject'} and $precedence is
  716.     ;# $headers{'precedence'}
  717.     ;#
  718.     $subject = $headers{'subject'};
  719.     $subject = "(No subject)" unless $subject;
  720.     $subject =~ s/\s+$//;
  721.     $precedence = $headers{'precedence'};
  722.  
  723.  
  724.     ;# -----
  725.     ;# $from comes from From: line. $address is their email address.
  726.     ;# $organization is their site. for example, strike@pixel.convex.com 
  727.     ;# yields an organization of convex.
  728.     ;#
  729.     $_ = $headers{'from'} ||
  730.          $headers{'resent-from'} ||
  731.          $headers{'sender'} ||
  732.          $headers{'resent-sender'} ||
  733.          $headers{'return-path'} ||
  734.          $headers{'reply-to'};
  735.  
  736.     if ($_ eq "") {
  737.        $from = $address = $organization = "unknown";
  738.        return;
  739.     };
  740.  
  741.     ($friendly, $address, $from, $organization) = &parse_email_address($_);
  742.  
  743.     ;# -----
  744.     ;# create arrays for who was on the To, Cc lines
  745.     ;#
  746.     @to = &expand($headers{'to'}); 
  747.     push(@to, &expand($headers{'apparently-to'}));
  748.     @cc = &expand($headers{'cc'});
  749. }
  750. #--------------------------------------------------------------------------
  751. # weekday -- given a date, return the three-letter name of a weekday
  752. #
  753. # usage:  &weekday($year, $mm, $dd)
  754.  
  755. sub weekday {
  756.     local($year, $mm, $dd) = @_;
  757.     local($datestr);
  758.     $year -= 1900;            # tz structure expects years - 1900
  759.     $mm -= 1;            # tz struct expects months to be 0-11;
  760.     $datestr = &ctime(&timelocal(1, 1, 1, $dd, $mm, $year, "", "", 0));
  761.     return(substr($datestr, 0, 3));
  762. }
  763. #--------------------------------------------------------------------------
  764. # write_it -- write the data to Gopherspace (along with a .cap file)
  765. #
  766. # usage:  &write_it()
  767. # Global variables used:
  768. #      $body $byline $disclaimer $error_msg $filename $subject $target
  769. #
  770. # SIDE EFFECT: We chdir() to the $target directory.
  771. # Thanks to Fred Barrie (barrie@futique.scs.unr.edu) for supplying the fix
  772. # for a peculiar perl bug: it can't open a file with a fully-defined path
  773. # longer than 64 characters.
  774.  
  775. sub write_it {
  776.  
  777.     chdir($target) ||
  778.         &abend("Data not accepted for Gopher due to gmail error",
  779.             $error_msg . "Can't chdir to directory $target\n$@");
  780.     open (FILE, "> $filename") ||
  781.         &abend("Data not accepted for Gopher due to gmail error",
  782.             $error_msg . "Can't open file $target/$filename\n$@");
  783.     print FILE "$subject\n\n";
  784.     print FILE $body;
  785.     print FILE "$byline";
  786.     print FILE "$disclaimer\n" if ($disclaimer);
  787.     close(FILE);
  788.  
  789.     # Make a .cap file only if one is needed.
  790.     if ($subject ne $filename) {
  791.         unless (-d ".cap") {
  792.             mkdir(".cap", 0755);
  793.         }
  794.         unless (open (FILE, "> .cap/$filename")) {
  795.             # Can't create .cap file -- complain and clean up
  796.             unlink("$filename");
  797.             &abend("Data not accepted for Gopher due to gmail error",
  798.                 $error_msg . "Can't open file $target/.cap/$filename\n$@");
  799.         }
  800.         print FILE "Name=$subject\n";
  801.         close(FILE);
  802.     }
  803. }
  804. #--------------------------------------------------------------------------
  805.  
  806. # end of gmail script
  807.