home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #30 / NN_1992_30.iso / spool / comp / sources / misc / 4165 < prev    next >
Encoding:
Text File  |  1992-12-12  |  46.4 KB  |  1,380 lines

  1. Newsgroups: comp.sources.misc
  2. Path: sparky!kent
  3. From: ram@eiffel.com (Raphael Manfredi)
  4. Subject:  v34i025:  mailagent - Rule Based Mail Filtering, Patch13
  5. Message-ID: <1992Dec13.022249.29882@sparky.imd.sterling.com>
  6. Followup-To: comp.sources.d
  7. X-Md4-Signature: a3c760916049925ca96b1720928a195c
  8. Sender: kent@sparky.imd.sterling.com (Kent Landfield)
  9. Organization: Interactive Software Engineering, Santa Barbara CA
  10. Date: Sun, 13 Dec 1992 02:22:49 GMT
  11. Approved: kent@sparky.imd.sterling.com
  12. Lines: 1366
  13.  
  14. Submitted-by: ram@eiffel.com (Raphael Manfredi)
  15. Posting-number: Volume 34, Issue 25
  16. Archive-name: mailagent/patch13
  17. Environment: Perl, Sendmail, UNIX
  18. Patch-To: mailagent: Volume 33, Issue 93-109
  19.  
  20. [Please note that mailagent was initially posted to comp.sources.misc]
  21. [at patchlevel 12. This is the first of two patches (13 and 14) being]
  22. [posted, bringing mailagent to version 2.9 patchlevel 14.    -Kent+  ]
  23.  
  24. System: mailagent version 2.9
  25. Patch #: 13
  26. Priority: MEDIUM
  27. Subject: changed Configure test for 'union wait'
  28. Subject: chkagent could report errors due to spurious matches
  29. Subject: added extra checking for writes to soft NFS-mounted disks
  30. Subject: filter now also complains when using -t in setgid mode
  31. Subject: removed spurious inclusion of <sys/types.h>
  32. Subject: hostname is now computed once and cached
  33. Subject: fixed various typos on the word "Precedence"
  34. Subject: new paragraph about file inclusion
  35. Subject: allowed file inclusion for KEEP and STRIP
  36. Subject: new macros %A, %C, %I and %O
  37. Subject: remove context file lock when excessively old
  38. Subject: action parsing rewritten to handle nested braces
  39. Subject: forgot to handle the %H macro
  40. Subject: (reported by David Giddy <d.giddy@trl.oz.au>)
  41. Subject: now also understands multiple To and Cc lines in headers
  42. Subject: added internet info extraction out of e-mail address
  43. Subject: now takes care of escaped ';' for layout purposes
  44. Subject: read statistics lines one at a time to limit memory usage
  45. Subject: added new tests for file inclusion with KEEP and STRIP
  46. Date: Tue Dec  1 09:48:46 PST 1992
  47. From: Raphael Manfredi <ram@eiffel.com>
  48.  
  49. Description:
  50.     Changed Configure test for 'union wait'. A lot of platforms had
  51.     problems with that and had to manually undefine UNION_WAIT from
  52.     config.h. Configure now looks for 'union.*wait.*{' in <sys/wait.h>
  53.     to see whether your system wants a plain int pointer or a union
  54.     wait pointer.
  55.  
  56.     chkagent could report errors due to spurious matches. This script
  57.     (intended to be run through cron) gave false alarms when a message
  58.     subject contained the word 'ERROR' for instance, and was logged.
  59.     The script now makes sure such a word is preceded by ': ' in the
  60.     logfile. This should reduce the chance of getting an error report
  61.     whereas nothing went wrong.
  62.  
  63.     Added extra checking for writes to soft NFS-mounted disks. The filter
  64.     program makes all the necessary system call status checks when queuing
  65.     a message. However, when writing on a soft NFS partition, I once got
  66.     an empty message with no error report from write. So the filter now
  67.     stats the queued file to make sure its size matches the size of the
  68.     mail read from sendmail.
  69.  
  70.     The filter now also complains when using -t in setgid mode. It already
  71.     complained when used in setuid mode, but I discovered a way to breach
  72.     through security by using only the setgid bit, so...
  73.  
  74.     Removed spurious inclusion of <sys/types.h> in parser.c. This could
  75.     prevent the parser from actually compiling.
  76.  
  77.     Fixed various typos on the word "Precedence" throughout the manual page.
  78.  
  79.     There is a new paragraph about file inclusion in the manual page,
  80.     explaining what it is and how it works.
  81.  
  82.     Allowed file inclusion for KEEP and STRIP. I've also made sure that
  83.     those worked even when mail headers are not normalized. For instance,
  84.     'STRIP Cc' should strip a 'cc:' line in the message header.
  85.  
  86.     New macros %A, %C, %I and %O. Refer to the manual page for details.
  87.  
  88.     Action parsing was rewritten to handle nested braces, in anticipation
  89.     for other features I'd like to add.
  90.  
  91.     Forgot to handle the %H macro (reported by David Giddy
  92.     <d.giddy@trl.oz.au>).
  93.  
  94.     Now understands multiple To and Cc lines in headers. The fields
  95.     are correctly concatenated, for filtering purposes, into a long list
  96.     of comma separated addresses.
  97.  
  98.     Now takes care of escaped ';' for layout purposes (when dumping rules).
  99.  
  100.     Read statistics lines one at a time to limit memory usage. If you are
  101.     collecting statistics and have changed your rule file so often that
  102.     your statistics file is huge (say 400 Kb), then you may have noticed
  103.     excessive memory consumptions, since the mailagent was trying to load
  104.     that file into memory without any pre-extension, thus causing the
  105.     process to grow rapidly as numerous realloc() occured.
  106.  
  107.     Added new tests for file inclusion with KEEP and STRIP and make sure
  108.     they behave in a case insensitive manner.
  109.  
  110.     Three new files were added in agent/pl/.
  111.  
  112.  
  113. Fix:    From rn, say "| patch -p -N -d DIR", where DIR is your mailagent source
  114.     directory.  Outside of rn, say "cd DIR; patch -p -N <thisarticle".
  115.     If you don't have the patch program, apply the following by hand,
  116.     or get patch (version 2.0, latest patchlevel).
  117.  
  118.     After patching:
  119.         *** DO NOTHING--INSTALL ALL PATCHES UP THROUGH #14 FIRST ***
  120.  
  121.     If patch indicates that patchlevel is the wrong version, you may need
  122.     to apply one or more previous patches, or the patch may already
  123.     have been applied.  See the patchlevel.h file to find out what has or
  124.     has not been applied.  In any event, don't continue with the patch.
  125.  
  126.     If you are missing previous patches they can be obtained from me:
  127.  
  128.         Raphael Manfredi <ram@eiffel.com>
  129.  
  130.     If you send a mail message of the following form it will greatly speed
  131.     processing:
  132.  
  133.         Subject: Command
  134.         @SH mailpatch PATH mailagent 2.9 LIST
  135.                ^ note the c
  136.  
  137.     where PATH is a return path FROM ME TO YOU either in Internet notation,
  138.     or in bang notation from some well-known host, and LIST is the number
  139.     of one or more patches you need, separated by spaces, commas, and/or
  140.     hyphens.  Saying 35- says everything from 35 to the end.
  141.  
  142.     To get some more detailed instructions, send me the following mail:
  143.  
  144.         Subject: Command
  145.         @SH mailhelp PATH
  146.  
  147.  
  148. Index: patchlevel.h
  149. Prereq: 12
  150. 4c4
  151. < #define PATCHLEVEL 12
  152. ---
  153. > #define PATCHLEVEL 13
  154.  
  155. Index: agent/man/mailagent.SH
  156. Prereq: 2.9.1.6
  157. *** agent/man/mailagent.SH.old    Tue Dec  1 09:47:54 1992
  158. --- agent/man/mailagent.SH    Tue Dec  1 09:47:56 1992
  159. ***************
  160. *** 18,24 ****
  161.   .TH MAILAGENT $manext "Version $VERSION PL$PATCHLEVEL"
  162.   ''' @(#) Manual page for mailagent's filter -- (c) ram February 1991
  163.   '''
  164. ! ''' $Id: mailagent.SH,v 2.9.1.6 92/11/10 10:12:13 ram Exp $
  165.   '''
  166.   '''  Copyright (c) 1991, 1992, Raphael Manfredi
  167.   '''
  168. --- 18,24 ----
  169.   .TH MAILAGENT $manext "Version $VERSION PL$PATCHLEVEL"
  170.   ''' @(#) Manual page for mailagent's filter -- (c) ram February 1991
  171.   '''
  172. ! ''' $Id: mailagent.SH,v 2.9.1.7 92/12/01 09:16:23 ram Exp $
  173.   '''
  174.   '''  Copyright (c) 1991, 1992, Raphael Manfredi
  175.   '''
  176. ***************
  177. *** 26,31 ****
  178. --- 26,37 ----
  179.   '''  License as specified in the README file that comes with dist.
  180.   '''
  181.   ''' $Log:    mailagent.SH,v $
  182. + ''' Revision 2.9.1.7  92/12/01  09:16:23  ram
  183. + ''' patch13: fixed various typos on the word "Precedence"
  184. + ''' patch13: new paragraph about file inclusion
  185. + ''' patch13: allowed file inclusion for KEEP and STRIP
  186. + ''' patch13: new macros %A, %C, %I and %O
  187. + ''' 
  188.   ''' Revision 2.9.1.6  92/11/10  10:12:13  ram
  189.   ''' patch12: perl interface functions now return 1 for success
  190.   ''' 
  191. ***************
  192. *** 54,60 ****
  193.   ''' 
  194.   ''' Revision 2.9.1.1  92/07/25  12:35:51  ram
  195.   ''' patch1: now respects English uppercased title conventions
  196. ! ''' patch1: a bulk or junk Precendence header voids vacation message
  197.   ''' patch1: documents the minimal set of header selectors available
  198.   ''' patch1: host name in p_host config variable cannot have domain name
  199.   ''' 
  200. --- 60,66 ----
  201.   ''' 
  202.   ''' Revision 2.9.1.1  92/07/25  12:35:51  ram
  203.   ''' patch1: now respects English uppercased title conventions
  204. ! ''' patch1: a bulk or junk Precedence header voids vacation message
  205.   ''' patch1: documents the minimal set of header selectors available
  206.   ''' patch1: host name in p_host config variable cannot have domain name
  207.   ''' 
  208. ***************
  209. *** 1267,1273 ****
  210.   a "KEEP From To Cc Subject" will keep only the principal fields from the
  211.   mail message. This is suitable for archving mailing lists messages.
  212.   You may add a ':' after each header field name if you wish, but that is not
  213. ! strictly necessary.
  214.   (Does not modify existing status)
  215.   .TP
  216.   LEAVE
  217. --- 1273,1280 ----
  218.   a "KEEP From To Cc Subject" will keep only the principal fields from the
  219.   mail message. This is suitable for archving mailing lists messages.
  220.   You may add a ':' after each header field name if you wish, but that is not
  221. ! strictly necessary. Headers may be specified using shell-style regular
  222. ! expressions, and file inclusion is allowed to get headers from a file.
  223.   (Does not modify existing status)
  224.   .TP
  225.   LEAVE
  226. ***************
  227. *** 1431,1437 ****
  228.   Remove the corresponding lines in the header of the mail. For instance,
  229.   a "STRIP Newsgroups Apparently-To" will remove the appropriate lines to wipe
  230.   out any Newsgroups: or Apparently-To: header. You may add a ':' after each
  231. ! header field name if you wish, but that is not strictly necessary.
  232.   (Does not alter execution status)
  233.   .TP
  234.   SUBST \fIvar expression\fR
  235. --- 1438,1445 ----
  236.   Remove the corresponding lines in the header of the mail. For instance,
  237.   a "STRIP Newsgroups Apparently-To" will remove the appropriate lines to wipe
  238.   out any Newsgroups: or Apparently-To: header. You may add a ':' after each
  239. ! header field name if you wish, but that is not strictly necessary. Headers
  240. ! may be specified via shell-style regular expressions or via "file" inclusion.
  241.   (Does not alter execution status)
  242.   .TP
  243.   SUBST \fIvar expression\fR
  244. ***************
  245. *** 1616,1622 ****
  246.   The login name of the address on the From: line.
  247.   .TP
  248.   .I \$precedence
  249. ! The content of the Precendence: line, if any at all.
  250.   .TP
  251.   .I \$sender
  252.   The sender of the message (may have a comment), derived in the same way the
  253. --- 1624,1630 ----
  254.   The login name of the address on the From: line.
  255.   .TP
  256.   .I \$precedence
  257. ! The content of the Precedence: line, if any at all.
  258.   .TP
  259.   .I \$sender
  260.   The sender of the message (may have a comment), derived in the same way the
  261. ***************
  262. *** 1666,1671 ****
  263. --- 1674,1693 ----
  264.   includes scripts started via the PERL command and mail hooks. The latter will
  265.   be described in detail further down.
  266.   '''
  267. + .SS "File inclusion"
  268. + .PP
  269. + Some commands like FORWARD or KEEP allow you to specify a file name between
  270. + double quotes to actually load parameters from this file. Unless a full path
  271. + is given, the following method is used to locate the file: first in the location
  272. + pointed to by the \fImailfilter\fR variable if set, otherwise in \fImaildir\fR
  273. + and finally in the home directory. Note that this is not a search path in the
  274. + sense that if \fImailfilter\fR is defined and the file is not there, an error
  275. + will be reported.
  276. + .PP
  277. + The file should list each parameter (be it an address, a header or a pattern)
  278. + on a line by itself. Shell-style comments (#) are allowed within that file and
  279. + leading white spaces are trimmed (but not trailing spaces).
  280. + '''
  281.   .SS "Macros Substitutions"
  282.   .PP
  283.   All the commands go through a macro substitution mechanism before being
  284. ***************
  285. *** 1676,1686 ****
  286.   %%
  287.   A real percent sign
  288.   .TP
  289.   %D
  290.   Day of the week (0-6)
  291.   .TP
  292.   %H
  293. ! Host name (name of the machine on which the \fImailagent\fR runs)
  294.   .TP
  295.   %L
  296.   Length of the body part, in bytes
  297. --- 1698,1721 ----
  298.   %%
  299.   A real percent sign
  300.   .TP
  301. + %A
  302. + The internet address extracted out of the \fIFrom:\fR field (\fIa.b.c\fR
  303. + in \fIu@a.b.c\fR), converted to lower-case.
  304. + .TP
  305. + %C
  306. + CPU name on which the mailagent runs. That is a fully qualified hostname
  307. + with the domain name, e.g. \fIlyon.eiffel.com\fR.
  308. + .TP
  309.   %D
  310.   Day of the week (0-6)
  311.   .TP
  312.   %H
  313. ! Host name (name of the machine on which the \fImailagent\fR runs), without
  314. ! any domain name. Always in lower-case, regardless of the machine name.
  315. ! .TP
  316. ! %I
  317. ! The internet domain name extracted out of the \fIFrom:\fR field (\fIb.c\fR
  318. ! in \fIu@a.b.c\fR), converted to lower-case.
  319.   .TP
  320.   %L
  321.   Length of the body part, in bytes
  322. ***************
  323. *** 1688,1693 ****
  324. --- 1723,1732 ----
  325.   %N
  326.   Full name of the sender (login name if none)
  327.   .TP
  328. + %O
  329. + The organization name extracted out of the \fIFrom:\fR field (\fIb\fR in
  330. + \fIu@a.b.c\fR), converted to lower-case.
  331. + .TP
  332.   %R
  333.   Subject of the original message with leading Re: suppressed
  334.   .TP
  335. ***************
  336. *** 1891,1897 ****
  337.   
  338.   Sincerely,
  339.   --
  340. ! %U <%u@%H>
  341.   .fi
  342.   .in -5
  343.   .sp
  344. --- 1930,1936 ----
  345.   
  346.   Sincerely,
  347.   --
  348. ! %U <%u@%C>
  349.   .fi
  350.   .in -5
  351.   .sp
  352. ***************
  353. *** 1905,1911 ****
  354.   \fInewsmaster\fR, \fIusenet\fR, \fIMAILER-DAEMON\fR or \fInobody\fR).
  355.   Matches are done in a case insentive manner, so \fIMailer-Daemon\fR will also
  356.   be recognized as a special user.
  357. ! Furthermore, any message tagged with a \fIPrecendence:\fR field set to
  358.   \fIbulk\fR or \fIjunk\fR will not trigger a vacation message. This built-in
  359.   behaviour can of course be overloaded by suitable rules (by testing and
  360.   issuing the vacation message yourself via MESSAGE).
  361. --- 1944,1950 ----
  362.   \fInewsmaster\fR, \fIusenet\fR, \fIMAILER-DAEMON\fR or \fInobody\fR).
  363.   Matches are done in a case insentive manner, so \fIMailer-Daemon\fR will also
  364.   be recognized as a special user.
  365. ! Furthermore, any message tagged with a \fIPrecedence:\fR field set to
  366.   \fIbulk\fR or \fIjunk\fR will not trigger a vacation message. This built-in
  367.   behaviour can of course be overloaded by suitable rules (by testing and
  368.   issuing the vacation message yourself via MESSAGE).
  369.  
  370. Index: agent/pl/actions.pl
  371. Prereq: 2.9.1.3
  372. *** agent/pl/actions.pl.old    Tue Dec  1 09:48:00 1992
  373. --- agent/pl/actions.pl    Tue Dec  1 09:48:01 1992
  374. ***************
  375. *** 1,4 ****
  376. ! ;# $Id: actions.pl,v 2.9.1.3 92/11/01 15:44:28 ram Exp $
  377.   ;#
  378.   ;#  Copyright (c) 1992, Raphael Manfredi
  379.   ;#
  380. --- 1,4 ----
  381. ! ;# $Id: actions.pl,v 2.9.1.4 92/12/01 09:18:05 ram Exp $
  382.   ;#
  383.   ;#  Copyright (c) 1992, Raphael Manfredi
  384.   ;#
  385. ***************
  386. *** 6,11 ****
  387. --- 6,15 ----
  388.   ;#  Licence as specified in the README file that comes with dist.
  389.   ;#
  390.   ;# $Log:    actions.pl,v $
  391. + ;# Revision 2.9.1.4  92/12/01  09:18:05  ram
  392. + ;# patch13: allowed file inclusion for KEEP and STRIP
  393. + ;# patch13: file inclusion processing now handled by &include_file
  394. + ;# 
  395.   ;# Revision 2.9.1.3  92/11/01  15:44:28  ram
  396.   ;# patch11: the PERL command now sets up @ARGV as if invoked from shell
  397.   ;# patch11: fixed message substitution bug (for MESSAGE and NOTIFY)
  398. ***************
  399. *** 407,413 ****
  400.       local($address) = &email_addr;    # Address of user
  401.       # Any address included withing "" is in fact a file name where actual
  402.       # forwarding addresses are found.
  403. !     $addresses = &complete_addr($addresses);    # Process "include-requests"
  404.       unless (open(MAILER,"|/usr/lib/sendmail -odq $addresses")) {
  405.           do add_log("cannot run sendmail to forward message") if $loglvl > 0;
  406.           return 1;
  407. --- 411,418 ----
  408.       local($address) = &email_addr;    # Address of user
  409.       # Any address included withing "" is in fact a file name where actual
  410.       # forwarding addresses are found.
  411. !     $addresses =
  412. !         &complete_list($addresses, 'address');    # Process "include-requests"
  413.       unless (open(MAILER,"|/usr/lib/sendmail -odq $addresses")) {
  414.           do add_log("cannot run sendmail to forward message") if $loglvl > 0;
  415.           return 1;
  416. ***************
  417. *** 439,445 ****
  418.       local($addresses) = @_;            # Address(es) mail should be bounced to
  419.       # Any address included withing "" is in fact a file name where actual
  420.       # bouncing addresses are found.
  421. !     $addresses = &complete_addr($addresses);    # Process "include-requests"
  422.       unless (open(MAILER,"|/usr/lib/sendmail -odq $addresses")) {
  423.           do add_log("cannot run sendmail to bounce message") if $loglvl > 0;
  424.           return 1;
  425. --- 444,451 ----
  426.       local($addresses) = @_;            # Address(es) mail should be bounced to
  427.       # Any address included withing "" is in fact a file name where actual
  428.       # bouncing addresses are found.
  429. !     $addresses =
  430. !         &complete_list($addresses, 'address');    # Process "include-requests"
  431.       unless (open(MAILER,"|/usr/lib/sendmail -odq $addresses")) {
  432.           do add_log("cannot run sendmail to bounce message") if $loglvl > 0;
  433.           return 1;
  434. ***************
  435. *** 955,975 ****
  436.   # Removes or keeps some headers and update the Header structure
  437.   sub alter_header {
  438.       local($headers, $action) = @_;
  439.       local(@list) = split(/\s/, $headers);
  440.       local(@head) = split(/\n/, $Header{'Head'});
  441.       local(@newhead);                # The constructed header
  442.       local($last_was_altered) = 0;    # Set to true when header is altered
  443.       local($matched);                # Did any header matched ?
  444.       foreach (@head) {
  445.           if (/^From\s/) {            # First From line...
  446.               push(@newhead, $_);        # Keep it anyway
  447.               next;
  448.           }
  449.           unless (/^\s/) {            # If not a continuation line
  450.               $last_was_altered = 0;    # Reset header alteration flag
  451.               $matched = 0;            # Assume no match
  452.               foreach $h (@list) {    # Loop over to-be-altered lines
  453. -                 $h =~ s/:$//;        # Remove trailing ':' if any
  454.                   if (/^$h:/i) {        # We found a line to be removed/kept
  455.                       $matched = 1;
  456.                       last;
  457. --- 961,992 ----
  458.   # Removes or keeps some headers and update the Header structure
  459.   sub alter_header {
  460.       local($headers, $action) = @_;
  461. +     $headers =
  462. +         &complete_list($headers, 'header');    # Process "file-inclusion"
  463.       local(@list) = split(/\s/, $headers);
  464.       local(@head) = split(/\n/, $Header{'Head'});
  465.       local(@newhead);                # The constructed header
  466.       local($last_was_altered) = 0;    # Set to true when header is altered
  467.       local($matched);                # Did any header matched ?
  468. +     local($line);                    # Original header line
  469. +     foreach $h (@list) {            # Prepare patterns
  470. +         $h =~ s/:$//;                # Remove trailing ':' if any
  471. +         $h = &perl_pattern($h);        # Headers specified by shell patterns
  472. +     }
  473.       foreach (@head) {
  474.           if (/^From\s/) {            # First From line...
  475.               push(@newhead, $_);        # Keep it anyway
  476.               next;
  477.           }
  478. +         $line = $_;                    # Save original
  479. +         # Make sure header field name is normalized before attempting a match
  480. +         s/^([\w-]+):/&header'normalize($1).':'/e;
  481.           unless (/^\s/) {            # If not a continuation line
  482.               $last_was_altered = 0;    # Reset header alteration flag
  483.               $matched = 0;            # Assume no match
  484.               foreach $h (@list) {    # Loop over to-be-altered lines
  485.                   if (/^$h:/i) {        # We found a line to be removed/kept
  486.                       $matched = 1;
  487.                       last;
  488. ***************
  489. *** 984,990 ****
  490.           } else {                                    # Action is $HD_KEEP
  491.               next if /^\s/ && !$last_was_altered;    # Header was not kept
  492.           }
  493. !         push(@newhead, $_);                        # Add line to the new header
  494.       }
  495.       $Header{'Head'} = join("\n", @newhead) . "\n";
  496.   }
  497. --- 1001,1007 ----
  498.           } else {                                    # Action is $HD_KEEP
  499.               next if /^\s/ && !$last_was_altered;    # Header was not kept
  500.           }
  501. !         push(@newhead, $line);        # Add line to the new header
  502.       }
  503.       $Header{'Head'} = join("\n", @newhead) . "\n";
  504.   }
  505. ***************
  506. *** 1158,1194 ****
  507.       0;
  508.   }
  509.   
  510. ! # Given a list of addresses separated by white spaces, return a new list of
  511. ! # addresses, but with "include-request" processed.
  512. ! sub complete_addr {
  513.       local(@addr) = split(' ', $_[0]);    # Original list
  514.       local(@result);                        # Where result list is built
  515.       local($filename);                    # Name of include file
  516.       local($_);
  517.       foreach $addr (@addr) {
  518. !         if ($addr !~ /^"/) {            # Address not enclosed within ""
  519.               push(@result, $addr);        # Kept as-is
  520.           } else {
  521. !             ($filename) = $addr =~ /^"(.*)"$/;
  522. !             $filename = &locate_file($filename);
  523. !             if ($filename && open(ADDRESSES, "$filename")) {
  524. !                 while (<ADDRESSES>) {
  525. !                     next if /^\s*#/;    # Skip shell comments
  526. !                     chop;
  527. !                     s/^\s+//;            # Remove leading spaces
  528. !                     push(@result, $_);
  529. !                 }
  530. !                 close ADDRESSES;
  531. !             } elsif ($filename) {        # Could not open file
  532. !                 &add_log("WARNING couldn't open $filename for addresses: $!")
  533. !                     if $loglvl > 4;
  534. !             } else {
  535. !                 &add_log("WARNING incorrect file inclusion request")
  536. !                     if $loglvl > 4;
  537. !             }
  538.           }
  539.       }
  540. !     join(' ', @result);        # Return space separated addresses
  541.   }
  542.   
  543.   # Save digest mail into a folder, or queue it if no folder is provided
  544. --- 1175,1197 ----
  545.       0;
  546.   }
  547.   
  548. ! # Given a list of items separated by white spaces, return a new list of
  549. ! # items, but with "include-request" processed.
  550. ! sub complete_list {
  551.       local(@addr) = split(' ', $_[0]);    # Original list
  552. +     local($type) = $_[1];                # Type of item (header, address, ...)
  553.       local(@result);                        # Where result list is built
  554.       local($filename);                    # Name of include file
  555.       local($_);
  556.       foreach $addr (@addr) {
  557. !         if ($addr !~ /^"/) {            # Item not enclosed within ""
  558.               push(@result, $addr);        # Kept as-is
  559.           } else {
  560. !             # Load items from file whose name is given between "quotes"
  561. !             push(@result, &include_file($addr, $type));
  562.           }
  563.       }
  564. !     join(' ', @result);        # Return space separated items
  565.   }
  566.   
  567.   # Save digest mail into a folder, or queue it if no folder is provided
  568.  
  569. Index: agent/pl/lexical.pl
  570. Prereq: 2.9.1.2
  571. *** agent/pl/lexical.pl.old    Tue Dec  1 09:48:13 1992
  572. --- agent/pl/lexical.pl    Tue Dec  1 09:48:13 1992
  573. ***************
  574. *** 1,4 ****
  575. ! ;# $Id: lexical.pl,v 2.9.1.2 92/11/01 15:50:52 ram Exp $
  576.   ;#
  577.   ;#  Copyright (c) 1992, Raphael Manfredi
  578.   ;#
  579. --- 1,4 ----
  580. ! ;# $Id: lexical.pl,v 2.9.1.3 92/12/01 09:22:16 ram Exp $
  581.   ;#
  582.   ;#  Copyright (c) 1992, Raphael Manfredi
  583.   ;#
  584. ***************
  585. *** 6,11 ****
  586. --- 6,15 ----
  587.   ;#  Licence as specified in the README file that comes with dist.
  588.   ;#
  589.   ;# $Log:    lexical.pl,v $
  590. + ;# Revision 2.9.1.3  92/12/01  09:22:16  ram
  591. + ;# patch13: now counts lines even when reading rules from memory
  592. + ;# patch13: action parsing rewritten to handle nested braces
  593. + ;# 
  594.   ;# Revision 2.9.1.2  92/11/01  15:50:52  ram
  595.   ;# patch11: fixed English typo
  596.   ;# 
  597. ***************
  598. *** 29,34 ****
  599. --- 33,39 ----
  600.   # The following subroutine is called in place of read_rule when rules are
  601.   # coming from the command line via @Linerules.
  602.   sub read_linerule {
  603. +     $.++;                        # One more line
  604.       shift(@Linerules);            # Read a new line from array
  605.   }
  606.   
  607. ***************
  608. *** 116,135 ****
  609.       $pattern;
  610.   }
  611.   
  612.   sub get_action {
  613.       local(*line) = shift(@_);    # edited in place
  614.       local($_) = $line;            # make a copy of original
  615. !     local($action) = "";
  616. !     if (s/^\s*{([^}]*)}//) {
  617. !         $action = $1;
  618. !     } else {
  619. !         unless (/\{.*\}/) {        # trash line if no { action } is present
  620. !             &add_log("ERROR expected action, discarded '$_'") if $loglvl;
  621. !             $_ = '';
  622.           }
  623.       }
  624. -     $line = $_;                    # eventually updates the line
  625. -     $action =~ s/\s+$//;        # remove trailing spaces
  626. -     $action;
  627.   }
  628.   
  629. --- 121,182 ----
  630.       $pattern;
  631.   }
  632.   
  633. + # Extract the action part from the line (by editing it in place) and return
  634. + # the first action encountered. Nesting of {...} blocks may occur.
  635.   sub get_action {
  636.       local(*line) = shift(@_);    # edited in place
  637.       local($_) = $line;            # make a copy of original
  638. !     return '' unless s/^\s*\{/{/;
  639. !     local($action) = &action_parse(*_, 0);
  640. !     &add_log("ERROR no action, discarding '$_'") if $loglvl && $action eq '';
  641. !     $line = $_;                    # eventually update the line
  642. !     $action =~ s/^\{\s*//;        # remove leading and trailing braces
  643. !     $action =~ s/\s*\}$//;
  644. !     $action;                    # return new action block
  645. ! }
  646. ! # Recursively parse the action string and return the parsed portion of the text
  647. ! # with proper nesting wherever necessary. The string given as parameter is
  648. ! # edited in place and the remaining is the unparsed part.
  649. ! sub action_parse {
  650. !     local(*_) = shift(@_);        # edited in place
  651. !     local($level) = shift(@_);    # recursion level
  652. !     local($parsed) = '';        # the part we parsed so far
  653. !     local($block);                # block recognized
  654. !     local($follow);                # recursion string returned
  655. !     for (;;) {
  656. !         # Go to first un-escaped '{', if possible and save leading string
  657. !         # up-to first '{'. Note that any '}' immediately stops scanning.
  658. !         s/^(([^\\{}]|\\.)*{)// && ($parsed .= $1);
  659. !         # Go to first un-escaped '}', with any '{' stopping scan.
  660. !         $block = '';
  661. !         s/^(([^\\{}]|\\.)*\})// && ($block = $1);
  662. !         $parsed .= $block;        # block may be empty, or has trailing '}'
  663. !         if ($parsed =~ s/\{$//) {    # recursion if '{' found
  664. !             $follow = &action_parse(*_, $level + 1);
  665. !             # If a null string is returned, then no matching '}' was found
  666. !             &add_log("WARNING no closing brace (added for you)")
  667. !                 if $follow eq '' && $loglvl > 5;
  668. !             $parsed .= '{' . $follow . '}';
  669. !         } elsif (s/^\}//) {            # reached end of a block
  670. !             &add_log("WARNING extra closing brace ignored")
  671. !                 if $level == 0 && $loglvl > 5;
  672. !             return $parsed;
  673. !         } else {
  674. !             # Get the whole string until the next '}' and return. If a '{'
  675. !             # interposes, the first match will return an empty string. In that
  676. !             # case, we continue if we are not at level #0. Otherwise we got the
  677. !             # whole action and may return now.
  678. !             $block = '';
  679. !             s/^(([^\\{}]|\\.)*\})// && ($block = $1);
  680. !             if ($block eq '' && $level) {        # Advance until '{'
  681. !                 s/^(([^\\}]|\\.)*\{)// && ($block = $1);
  682. !                 $parsed .= $block;
  683. !                 next;
  684. !             }
  685. !             $block =~ s/\}//;
  686. !             return $parsed . $block;
  687.           }
  688.       }
  689.   }
  690.   
  691.  
  692. Index: agent/pl/macros.pl
  693. Prereq: 2.9.1.2
  694. *** agent/pl/macros.pl.old    Tue Dec  1 09:48:16 1992
  695. --- agent/pl/macros.pl    Tue Dec  1 09:48:16 1992
  696. ***************
  697. *** 1,4 ****
  698. ! ;# $Id: macros.pl,v 2.9.1.2 92/08/26 13:16:14 ram Exp $
  699.   ;#
  700.   ;#  Copyright (c) 1992, Raphael Manfredi
  701.   ;#
  702. --- 1,4 ----
  703. ! ;# $Id: macros.pl,v 2.9.1.3 92/12/01 09:24:09 ram Exp $
  704.   ;#
  705.   ;#  Copyright (c) 1992, Raphael Manfredi
  706.   ;#
  707. ***************
  708. *** 6,11 ****
  709. --- 6,16 ----
  710.   ;#  Licence as specified in the README file that comes with dist.
  711.   ;#
  712.   ;# $Log:    macros.pl,v $
  713. + ;# Revision 2.9.1.3  92/12/01  09:24:09  ram
  714. + ;# patch13: new macros %A, %C, %I and %O
  715. + ;# patch13: forgot to handle the %H macro
  716. + ;# patch13: (reported by David Giddy <d.giddy@trl.oz.au>)
  717. + ;# 
  718.   ;# Revision 2.9.1.2  92/08/26  13:16:14  ram
  719.   ;# patch8: added support for external variables (persistent)
  720.   ;# 
  721. ***************
  722. *** 18,27 ****
  723. --- 23,36 ----
  724.   ;# 
  725.   # Macros:
  726.   # %%     A real percent sign
  727. + # %A     Sender's main address (host.domain.ct in user@loc.host.domain.ct)
  728. + # %C     CPU name, fully qualified with domain name
  729.   # %D     Day of the week (0-6)
  730.   # %H     Host name (name of the machine on which the mailagent runs)
  731. + # %I     Internet domain from sender (domain.ct in user@host.domain.ct)
  732.   # %L     Length of the message in bytes (without header)
  733.   # %N     Full name of sender (login name if none)
  734. + # %O     Organization name from sender address (domain in user@host.domain.ct)
  735.   # %R     Subject of orginal message with leading Re: suppressed
  736.   # %S     Re: subject of original message
  737.   # %T     Time of last modification on mailed file (value taken from $macro_T)
  738. ***************
  739. *** 83,93 ****
  740. --- 92,106 ----
  741.   
  742.       s/%%/##pr##/g;                        # Protect double percent signs
  743.       s/%/#%%!/g;                            # Make sure substitutions do not add %
  744. +     s/#%%!A/¯o'internet/eg;            # Main internet address of sender
  745.       s/#%%!d/$mday/g;                    # Day of the month (01-31)
  746. +     s/#%%!C/&domain_addr/eg;            # CPU name, fully qualified with domain
  747.       s/#%%!D/$wday/g;                    # Day of the week (0-6)
  748.       s/#%%!f/$Header{'From'}/g;            # The "From:" line
  749.       s/#%%!h/$hour/g;                    # Hour of the day (00-23)
  750. +     s/#%%!H/&myhostname/eg;                # Hostname on which mailagent runs
  751.       s/#%%!i/$Header{'Message-Id'}/g;    # Message-Id (null string if none)
  752. +     s/#%%!I/¯o'domain/eg;            # Internet domain name of sender
  753.       s/#%%!l/$Header{'Lines'}/g;            # Number if lines in message
  754.       s/#%%!L/$Header{'Length'}/g;        # Length of message, in bytes
  755.       s/#%%!m/$mon/g;                        # Month of the year
  756. ***************
  757. *** 94,99 ****
  758. --- 107,113 ----
  759.       s/#%%!n/$login/g;                    # Lower-cased login name of sender
  760.       s/#%%!N/$fullname/g;                # Full name of sender (login if none)
  761.       s/#%%!o/$orgname/g;                    # Organization name
  762. +     s/#%%!O/¯o'org/eg;                # Organization part of sender's address
  763.       s/#%%!r/$reply_to/g;                # Return path of message
  764.       s/#%%!R/$subject/g;                    # Subject with leading Re: suppressed
  765.       s/#%%!s/$Header{'Subject'}/g;        # Subject of message
  766. ***************
  767. *** 114,117 ****
  768. --- 128,159 ----
  769.       s/##pr##/%/g;                        # A double percent expands to %
  770.       $str = $_;                            # Update string in-place
  771.   }
  772. + package macro;
  773. + # Return the internet information of the From address
  774. + sub info {
  775. +     local($addr) = (&'parse_address($'Header{'From'}))[0];
  776. +     &'internet_info($addr);
  777. + }
  778. + # Return the organization name
  779. + sub org {
  780. +     local($host, $domain, $country) = &info;
  781. +     $domain;
  782. + }
  783. + # Return the domain name
  784. + sub domain {
  785. +     local($host, $domain, $country) = &info;
  786. +     $domain .'.'. $country;
  787. + }
  788. + # Return the qualified internet address
  789. + sub internet {
  790. +     local($host, $domain, $country) = &info;
  791. +     $host ne '' ? $host .'.'. $domain .'.'. $country : $domain .'.'. $country;
  792. + }
  793. + package main;
  794.   
  795.  
  796. Index: agent/pl/matching.pl
  797. Prereq: 2.9.1.1
  798. *** agent/pl/matching.pl.old    Tue Dec  1 09:48:18 1992
  799. --- agent/pl/matching.pl    Tue Dec  1 09:48:19 1992
  800. ***************
  801. *** 1,4 ****
  802. ! ;# $Id: matching.pl,v 2.9.1.1 92/08/02 16:11:54 ram Exp $
  803.   ;#
  804.   ;#  Copyright (c) 1992, Raphael Manfredi
  805.   ;#
  806. --- 1,4 ----
  807. ! ;# $Id: matching.pl,v 2.9.1.2 92/12/01 09:25:48 ram Exp $
  808.   ;#
  809.   ;#  Copyright (c) 1992, Raphael Manfredi
  810.   ;#
  811. ***************
  812. *** 6,11 ****
  813. --- 6,15 ----
  814.   ;#  Licence as specified in the README file that comes with dist.
  815.   ;#
  816.   ;# $Log:    matching.pl,v $
  817. + ;# Revision 2.9.1.2  92/12/01  09:25:48  ram
  818. + ;# patch13: new perl_pattern function to transform shell-style patterns
  819. + ;# patch13: file inclusion now handled by include_file
  820. + ;# 
  821.   ;# Revision 2.9.1.1  92/08/02  16:11:54  ram
  822.   ;# patch2: added support for negated selectors
  823.   ;# 
  824. ***************
  825. *** 39,44 ****
  826. --- 43,57 ----
  827.       );
  828.   }
  829.   
  830. + # Transform a shell-style pattern into a perl pattern
  831. + sub perl_pattern {
  832. +     local($_) = @_;        # The shell pattern
  833. +     s/\./\\./g;            # Escape .
  834. +     s/\*/.*/g;            # Transform * into .*
  835. +     s/\?/./g;            # Transform ? into .
  836. +     $_;                    # Perl pattern
  837. + }
  838.   # Take a pattern as written in the rule file and make it suitable for
  839.   # pattern matching as understood by perl. If the pattern starts with a
  840.   # leading /, nothing is done. Otherwise, a set of / are added.
  841. ***************
  842. *** 46,56 ****
  843.   sub make_pattern {
  844.       local($_) = shift(@_);
  845.       unless (m|^/|) {                # Pattern does not start with a /
  846. !         # With simple words, patterns have the same form as shell ones
  847. !         s/\./\\./g;            # Escape .
  848. !         s/\*/.*/g;            # Transform * into .*
  849. !         s/\?/./g;            # Transform ? into .
  850. !         $_ = "/^$_\$/";        # Anchor pattern
  851.       }
  852.       # The whole pattern is inserted within () to make at least one
  853.       # backreference. Otherwise, the following could happen:
  854. --- 59,66 ----
  855.   sub make_pattern {
  856.       local($_) = shift(@_);
  857.       unless (m|^/|) {                # Pattern does not start with a /
  858. !         $_ = &perl_pattern($_);        # Simple words specified via shell patterns
  859. !         $_ = "/^$_\$/";                # Anchor pattern
  860.       }
  861.       # The whole pattern is inserted within () to make at least one
  862.       # backreference. Otherwise, the following could happen:
  863. ***************
  864. *** 76,106 ****
  865.       if ($pattern !~ /^"/) {
  866.           $matched = do apply_match($selector, $pattern);
  867.       } else {
  868. !         local(@filepat) = ();            # File pattern
  869. !         local($filename);                # Where pattern should be read from
  870. !         ($filename) =
  871. !             $pattern =~ /^"(.*)"$/;        # The filename is held within ""
  872. !         $filename =
  873. !             &locate_file($filename);    # Path may not be absolute
  874. !         if ($filename) {
  875. !             if (open(PATTERN, "$filename")) {
  876. !                 while (<PATTERN>) {
  877. !                     next if /^\s*#/;    # Skip shell comments
  878. !                     chop;
  879. !                     s/^\s*//;            # Remove leading spaces
  880. !                     push(@filepat, $_);
  881. !                     do add_log ("loading pattern $_") if $loglvl > 19;
  882. !                 }
  883. !                 close PATTERN;
  884. !             } else {
  885. !                 do add_log("WARNING couldn't open $filename for patterns")
  886. !                     if $loglvl > 4;
  887. !                 push(@filepat, "*");    # Ensure anything matches
  888. !             }
  889. !         } else {
  890. !             do add_log("WARNING incorrect file name $pattern") if $loglvl > 4;
  891. !             push(@filepat, "*");    # Ensure anything matches
  892. !         }
  893.           # Now do the match for all the patterns. Stop as soon as one matches.
  894.           foreach (@filepat) {
  895.               $matched = do apply_match($selector, $_);
  896. --- 86,93 ----
  897.       if ($pattern !~ /^"/) {
  898.           $matched = do apply_match($selector, $pattern);
  899.       } else {
  900. !         # Load patterns from file whose name is given between "quotes"
  901. !         local(@filepat) = &include_file($pattern, 'pattern');
  902.           # Now do the match for all the patterns. Stop as soon as one matches.
  903.           foreach (@filepat) {
  904.               $matched = do apply_match($selector, $_);
  905.  
  906. Index: agent/filter/io.c
  907. Prereq: 2.9
  908. *** agent/filter/io.c.old    Tue Dec  1 09:47:40 1992
  909. --- agent/filter/io.c    Tue Dec  1 09:47:41 1992
  910. ***************
  911. *** 11,17 ****
  912.   */
  913.   
  914.   /*
  915. !  * $Id: io.c,v 2.9 92/07/14 16:48:13 ram Exp $
  916.    *
  917.    *  Copyright (c) 1992, Raphael Manfredi
  918.    *
  919. --- 11,17 ----
  920.   */
  921.   
  922.   /*
  923. !  * $Id: io.c,v 2.9.1.1 92/12/01 09:11:51 ram Exp $
  924.    *
  925.    *  Copyright (c) 1992, Raphael Manfredi
  926.    *
  927. ***************
  928. *** 19,24 ****
  929. --- 19,27 ----
  930.    *  Licence as specified in the README file that comes with dist.
  931.    *
  932.    * $Log:    io.c,v $
  933. +  * Revision 2.9.1.1  92/12/01  09:11:51  ram
  934. +  * patch13: added extra checking for writes to soft NFS-mounted disks
  935. +  * 
  936.    * Revision 2.9  92/07/14  16:48:13  ram
  937.    * 3.0 beta baseline.
  938.    * 
  939. ***************
  940. *** 497,502 ****
  941. --- 500,506 ----
  942.       register1 char *mailptr;        /* Pointer into mail buffer */
  943.       register2 int length;            /* Number of bytes already written */
  944.       register3 int amount;            /* Amount of bytes written by last call */
  945. +     struct stat buf;                /* Stat buffer */
  946.   
  947.       sprintf(path, "%s/%s.%d", dir, template, progpid);
  948.   
  949. ***************
  950. *** 506,512 ****
  951.           return (char *) 0;
  952.       }
  953.   
  954. !     /* Write the mail on disc. We do not call a single write on the mail buffer
  955.        * as in "write(fd, mail, len)" in case the mail length exceeds the maximum
  956.        * amount of bytes the system can atomically write.
  957.        */
  958. --- 510,516 ----
  959.           return (char *) 0;
  960.       }
  961.   
  962. !     /* Write the mail on disk. We do not call a single write on the mail buffer
  963.        * as in "write(fd, mail, len)" in case the mail length exceeds the maximum
  964.        * amount of bytes the system can atomically write.
  965.        */
  966. ***************
  967. *** 524,535 ****
  968.               if (n == -1)
  969.                   add_log(1, "SYSERR write: %m (%e)");
  970.               add_log(2, "ERROR cannot write to file %s", path);
  971. -             if (-1 == unlink(path)) {
  972. -                 add_log(1, "SYSERR unlink: %m (%e)");
  973. -                 add_log(4, "WARNING leaving %s around", path);
  974. -             }
  975.               close(fd);
  976. !             return (char *) 0;
  977.           }
  978.       }
  979.   
  980. --- 528,535 ----
  981.               if (n == -1)
  982.                   add_log(1, "SYSERR write: %m (%e)");
  983.               add_log(2, "ERROR cannot write to file %s", path);
  984.               close(fd);
  985. !             goto error;                /* Remove file and report error */
  986.           }
  987.       }
  988.   
  989. ***************
  990. *** 536,542 ****
  991. --- 536,568 ----
  992.       close(fd);
  993.       add_log(19, "mail in %s", path);
  994.   
  995. +     /* I don't really trust writes through NFS soft-mounted partitions, and I
  996. +      * am also suspicious about hard-mounted ones. I could have opened the file
  997. +      * with the O_SYNC flag, but the effect on NFS is not well defined either.
  998. +      * So, let's just make sure the mail has been correctly written on the disk
  999. +      * by comparing the file size and the orginal message size. If they differ,
  1000. +      * complain and return an error.
  1001. +      */
  1002. +     if (-1 == stat(path, &buf))        /* No entry in file system, probably */
  1003. +         return (char *) 0;            /* Saving failed */
  1004. +     if (buf.st_size != len) {        /* Not written entirely */
  1005. +         add_log(2, "ERROR mail truncated to %d bytes (had %d)",
  1006. +             buf.st_size, len);
  1007. +         goto error;                    /* Remove file and report error */
  1008. +     }
  1009.       return path;            /* Where mail was writen (static data) */
  1010. + error:        /* Come here when a write error has been detected */
  1011. +     if (-1 == unlink(path)) {
  1012. +         add_log(1, "SYSERR unlink: %m (%e)");
  1013. +         add_log(4, "WARNING leaving %s around", path);
  1014. +     }
  1015. +     return (char *) 0;
  1016.   }
  1017.   
  1018.   #ifndef RENAME
  1019.  
  1020. Index: agent/pl/rules.pl
  1021. Prereq: 2.9.1.2
  1022. *** agent/pl/rules.pl.old    Tue Dec  1 09:48:28 1992
  1023. --- agent/pl/rules.pl    Tue Dec  1 09:48:29 1992
  1024. ***************
  1025. *** 1,4 ****
  1026. ! ;# $Id: rules.pl,v 2.9.1.2 92/11/01 15:52:24 ram Exp $
  1027.   ;#
  1028.   ;#  Copyright (c) 1992, Raphael Manfredi
  1029.   ;#
  1030. --- 1,4 ----
  1031. ! ;# $Id: rules.pl,v 2.9.1.3 92/12/01 09:30:01 ram Exp $
  1032.   ;#
  1033.   ;#  Copyright (c) 1992, Raphael Manfredi
  1034.   ;#
  1035. ***************
  1036. *** 6,11 ****
  1037. --- 6,15 ----
  1038.   ;#  Licence as specified in the README file that comes with dist.
  1039.   ;#
  1040.   ;# $Log:    rules.pl,v $
  1041. + ;# Revision 2.9.1.3  92/12/01  09:30:01  ram
  1042. + ;# patch13: fixed mode selection pattern (no brace allowed)
  1043. + ;# patch13: now takes care of escaped ';' for layout purposes
  1044. + ;# 
  1045.   ;# Revision 2.9.1.2  92/11/01  15:52:24  ram
  1046.   ;# patch11: fixed English typo
  1047.   ;# patch11: makes sure default rules apply if no valid rules are present
  1048. ***************
  1049. *** 180,186 ****
  1050.           next unless &before($rulenum);                # Call 'before' hook
  1051.           $selnum = 0;
  1052.           $rules = $_;        # Work on a copy
  1053. !         $rules =~ s/^(.*){// && ($mode = $1);        # First "word" is the mode
  1054.           $rules =~ s/\s*(.*)}// && ($action = $1);    # Then action within {}
  1055.           $mode =~ s/\s*$//;                            # Remove trailing spaces
  1056.           print "<$mode> ";                            # Mode in which it applies
  1057. --- 184,190 ----
  1058.           next unless &before($rulenum);                # Call 'before' hook
  1059.           $selnum = 0;
  1060.           $rules = $_;        # Work on a copy
  1061. !         $rules =~ s/^([^{]*){// && ($mode = $1);    # First "word" is the mode
  1062.           $rules =~ s/\s*(.*)}// && ($action = $1);    # Then action within {}
  1063.           $mode =~ s/\s*$//;                            # Remove trailing spaces
  1064.           print "<$mode> ";                            # Mode in which it applies
  1065. ***************
  1066. *** 213,219 ****
  1067. --- 217,232 ----
  1068.               }
  1069.           }
  1070.           print "  " if $lines == 1;
  1071. +         # Split actions, but take care of escaped \; (layout purposes)
  1072. +         $action =~ s/\\\\/\02/g;            # \\ -> ^B
  1073. +         $action =~ s/\\;/\01/g;                # \; -> ^A
  1074.           @action = split(/;/, $action);
  1075. +         foreach (@action) {                    # Restore escapes by in-place edit
  1076. +             s/\01/\\;/g;                    # ^A -> \;
  1077. +             s/\02/\\\\/g;                    # ^B -> \\
  1078. +         }
  1079.           # If action is large enough, format differently (one action/line)
  1080.           if (length($action) > 60 && @action > 1) {
  1081.               print "\n\t{\n";
  1082.  
  1083. Index: agent/pl/rfc822.pl
  1084. Prereq: 2.9.1.1
  1085. *** agent/pl/rfc822.pl.old    Tue Dec  1 09:48:26 1992
  1086. --- agent/pl/rfc822.pl    Tue Dec  1 09:48:26 1992
  1087. ***************
  1088. *** 1,4 ****
  1089. ! ;# $Id: rfc822.pl,v 2.9.1.1 92/11/01 15:51:46 ram Exp $
  1090.   ;#
  1091.   ;#  Copyright (c) 1992, Raphael Manfredi
  1092.   ;#
  1093. --- 1,4 ----
  1094. ! ;# $Id: rfc822.pl,v 2.9.1.2 92/12/01 09:27:19 ram Exp $
  1095.   ;#
  1096.   ;#  Copyright (c) 1992, Raphael Manfredi
  1097.   ;#
  1098. ***************
  1099. *** 6,11 ****
  1100. --- 6,14 ----
  1101.   ;#  Licence as specified in the README file that comes with dist.
  1102.   ;#
  1103.   ;# $Log:    rfc822.pl,v $
  1104. + ;# Revision 2.9.1.2  92/12/01  09:27:19  ram
  1105. + ;# patch13: added internet info extraction out of e-mail address
  1106. + ;# 
  1107.   ;# Revision 2.9.1.1  92/11/01  15:51:46  ram
  1108.   ;# patch11: allows _ as separator in names (as in First_Last)
  1109.   ;# 
  1110. ***************
  1111. *** 63,67 ****
  1112. --- 66,93 ----
  1113.       s/.*_(\w+)/$1/;                    # Same as above (_ separation)
  1114.       tr/A-Z/a-z/;                    # And lowercase it
  1115.       $_;
  1116. + }
  1117. + # Parse an e-mail address and return a three element array:
  1118. + #   ($host, $domain, $country)
  1119. + sub internet_info {
  1120. +     local($_) = shift(@_);                # The internet address
  1121. +     local($login) = &login_name($_);    # Get the address login name
  1122. +     local($internet);                    # The internet part of the address
  1123. +     # Try with uucp form first, to detect things like eiffel!ram@inria.fr
  1124. +     # We use the login name to anchor the last '!' or the first '@' or '%'
  1125. +     ($internet) = /([^!]*)!$login/i;
  1126. +     ($internet) = /$login[@%]([\w.-]*)/i unless $internet;
  1127. +     $internet =~ tr/A-Z/a-z/;                # Always lower-cased
  1128. +     local(@parts) = split(/\./, $internet);    # Break on dots
  1129. +     if (@parts == 1) {                        # Only a host name
  1130. +         # Maybe this is a local address, maybe this is a uucp name. Assume that
  1131. +         # it is local if there is an '@' sign, as in 'ram@lyon'. Otherwise, it
  1132. +         # is a uucp name, as in 'eiffel!ram'.
  1133. +         push(@parts, 'uucp') if /!$login/;    # UUCP name
  1134. +         push(@parts, split(/\./, $mydomain)) if @parts == 1;
  1135. +     }
  1136. +     unshift(@parts, '') if @parts == 2;        # No host name
  1137. +     @parts[($#parts - 2) .. $#parts];        # ($host, $domain, $country)
  1138.   }
  1139.   
  1140.  
  1141. Index: agent/magent.SH
  1142. Prereq: 2.9.1.2
  1143. *** agent/magent.SH.old    Tue Dec  1 09:47:48 1992
  1144. --- agent/magent.SH    Tue Dec  1 09:47:49 1992
  1145. ***************
  1146. *** 22,28 ****
  1147.   # via the filter. Mine looks like this:
  1148.   #   "|exec /users/ram/mail/filter >>/users/ram/.bak 2>&1"
  1149.   
  1150. ! # $Id: magent.SH,v 2.9.1.2 92/08/26 12:41:27 ram Exp $
  1151.   #
  1152.   #  Copyright (c) 1991, 1992, Raphael Manfredi
  1153.   #
  1154. --- 22,28 ----
  1155.   # via the filter. Mine looks like this:
  1156.   #   "|exec /users/ram/mail/filter >>/users/ram/.bak 2>&1"
  1157.   
  1158. ! # $Id: magent.SH,v 2.9.1.3 92/12/01 09:14:07 ram Exp $
  1159.   #
  1160.   #  Copyright (c) 1991, 1992, Raphael Manfredi
  1161.   #
  1162. ***************
  1163. *** 30,35 ****
  1164. --- 30,39 ----
  1165.   #  Licence as specified in the README file that comes with dist.
  1166.   #
  1167.   # $Log:    magent.SH,v $
  1168. + # Revision 2.9.1.3  92/12/01  09:14:07  ram
  1169. + # patch13: hostname is now computed once and cached
  1170. + # patch13: three new .pl files are now appended
  1171. + # 
  1172.   # Revision 2.9.1.2  92/08/26  12:41:27  ram
  1173.   # patch8: better no-lock handling
  1174.   # patch8: now maintains the notion of private library directory
  1175. ***************
  1176. *** 459,465 ****
  1177.       local($_);                            # Our host name
  1178.       $_ = $hiddennet if $hiddennet ne '';
  1179.       if ($_ eq '') {
  1180. !         chop($_ = `$phostname`);        # Must fork to get hostname, grr...
  1181.           $_ .= $mydomain unless /\./;    # We want something fully qualified
  1182.       }
  1183.       $_;
  1184. --- 463,469 ----
  1185.       local($_);                            # Our host name
  1186.       $_ = $hiddennet if $hiddennet ne '';
  1187.       if ($_ eq '') {
  1188. !         $_ = &hostname;                    # Must fork to get hostname, grr...
  1189.           $_ .= $mydomain unless /\./;    # We want something fully qualified
  1190.       }
  1191.       $_;
  1192. ***************
  1193. *** 568,572 ****
  1194. --- 572,579 ----
  1195.   $grep -v '^;#' pl/mailhook.pl >>magent
  1196.   $grep -v '^;#' pl/interface.pl >>magent
  1197.   $grep -v '^;#' pl/getdate.pl >>magent
  1198. + $grep -v '^;#' pl/include.pl >>magent
  1199. + $grep -v '^;#' pl/plural.pl >>magent
  1200. + $grep -v '^;#' pl/hostname.pl >>magent
  1201.   chmod 755 magent
  1202.   $eunicefix magent
  1203.  
  1204. Index: agent/pl/parse.pl
  1205. Prereq: 2.9.1.1
  1206. *** agent/pl/parse.pl.old    Tue Dec  1 09:48:21 1992
  1207. --- agent/pl/parse.pl    Tue Dec  1 09:48:21 1992
  1208. ***************
  1209. *** 1,4 ****
  1210. ! ;# $Id: parse.pl,v 2.9.1.1 92/08/26 13:17:47 ram Exp $
  1211.   ;#
  1212.   ;#  Copyright (c) 1992, Raphael Manfredi
  1213.   ;#
  1214. --- 1,4 ----
  1215. ! ;# $Id: parse.pl,v 2.9.1.2 92/12/01 09:26:19 ram Exp $
  1216.   ;#
  1217.   ;#  Copyright (c) 1992, Raphael Manfredi
  1218.   ;#
  1219. ***************
  1220. *** 6,11 ****
  1221. --- 6,14 ----
  1222.   ;#  Licence as specified in the README file that comes with dist.
  1223.   ;#
  1224.   ;# $Log:    parse.pl,v $
  1225. + ;# Revision 2.9.1.2  92/12/01  09:26:19  ram
  1226. + ;# patch13: now also understands multiple To and Cc lines in headers
  1227. + ;# 
  1228.   ;# Revision 2.9.1.1  92/08/26  13:17:47  ram
  1229.   ;# patch8: created by extraction from analyze.pl
  1230.   ;# patch8: parsing can now be done on header only
  1231. ***************
  1232. *** 122,132 ****
  1233.       }
  1234.   
  1235.       # There is usually one Apparently-To line per address. Remove all new lines
  1236. !     # in the header line and replace them with ','.
  1237. !     $* = 1;
  1238. !     $Header{'Apparently-To'} =~ s/\n/,/g;    # Remove new-lines
  1239.       $* = 0;
  1240. -     $Header{'Apparently-To'} =~ s/,$/\n/;    # Restore last new-line
  1241.   
  1242.       # If no To: field, then maybe there is an Apparently-To: instead. If so,
  1243.       # make them identical. Otherwise, assume the mail was directed to the user.
  1244. --- 125,138 ----
  1245.       }
  1246.   
  1247.       # There is usually one Apparently-To line per address. Remove all new lines
  1248. !     # in the header line and replace them with ','. Likewise for To: and Cc:.
  1249. !     # although it is far less likely to occur.
  1250. !     local($*) = 1;
  1251. !     foreach $field ('Apparently-To', 'To', 'Cc') {
  1252. !         $Header{$field} =~ s/\n/,/g;    # Remove new-lines
  1253. !         $Header{$field} =~ s/,$/\n/;    # Restore last new-line
  1254. !     }
  1255.       $* = 0;
  1256.   
  1257.       # If no To: field, then maybe there is an Apparently-To: instead. If so,
  1258.       # make them identical. Otherwise, assume the mail was directed to the user.
  1259.  
  1260. Index: agent/pl/include.pl
  1261. *** agent/pl/include.pl.old    Tue Dec  1 09:48:11 1992
  1262. --- agent/pl/include.pl    Tue Dec  1 09:48:11 1992
  1263. ***************
  1264. *** 0 ****
  1265. --- 1,45 ----
  1266. + ;# $Id: include.pl,v 2.9.1.1 92/12/01 09:21:10 ram Exp $
  1267. + ;#
  1268. + ;#  Copyright (c) 1992, Raphael Manfredi
  1269. + ;#
  1270. + ;#  You may redistribute only under the terms of the GNU General Public
  1271. + ;#  Licence as specified in the README file that comes with dist.
  1272. + ;#
  1273. + ;# $Log:    include.pl,v $
  1274. + ;# Revision 2.9.1.1  92/12/01  09:21:10  ram
  1275. + ;# patch13: created
  1276. + ;# 
  1277. + ;# 
  1278. + # Process "include-file" requests. The file is allowed to have shell comments
  1279. + # and leading spaces are trimmed. The function returns an array, each item
  1280. + # being one of the non-comment lines found in the file.
  1281. + sub include_file {
  1282. +     local($inc) = shift(@_);    # Include request "file-name"
  1283. +     local($what) = shift(@_);    # What we are looking for (singular)
  1284. +     local(*INCLUDE);            # Local file handle
  1285. +     local($filename) = $inc =~ /^"(.*)"$/;
  1286. +     local(@result);
  1287. +     local($_);
  1288. +     # Find file using mailfilter, maildir variables if not specified with an
  1289. +     # absolute pathname (starting iwht a '/').
  1290. +     $filename = &locate_file($filename);
  1291. +     &add_log("loading ".&plural($what)." from $filename") if $loglvl > 18;
  1292. +     if ($filename ne '' && open(INCLUDE, "$filename")) {
  1293. +         while (<INCLUDE>) {
  1294. +             next if /^\s*#/;    # Skip shell comments
  1295. +             chop;
  1296. +             s/^\s+//;            # Remove leading spaces
  1297. +             push(@result, $_);
  1298. +             &add_log("loaded $what '$_'") if $loglvl > 19;
  1299. +         }
  1300. +         close INCLUDE;
  1301. +     } elsif ($filename ne '') {        # Could not open file
  1302. +         &add_log("WARNING couldn't open $filename for ".&plural($what).": $!")
  1303. +             if $loglvl > 4;
  1304. +     } else {
  1305. +         &add_log("WARNING incorrect file inclusion request: $inc")
  1306. +             if $loglvl > 4;
  1307. +     }
  1308. +     @result;        # List of non-comment lines held in file
  1309. + }
  1310.  
  1311. Index: agent/files/chkagent.sh
  1312. Prereq: 2.9
  1313. *** agent/files/chkagent.sh.old    Tue Dec  1 09:47:38 1992
  1314. --- agent/files/chkagent.sh    Tue Dec  1 09:47:38 1992
  1315. ***************
  1316. *** 5,13 ****
  1317.   #  You may redistribute only under the terms of the GNU General Public
  1318.   #  Licence as specified in the README file that comes with dist.
  1319.   #
  1320. ! # $Id: chkagent.sh,v 2.9 92/07/14 16:47:41 ram Exp $
  1321.   #
  1322.   # $Log:    chkagent.sh,v $
  1323.   # Revision 2.9  92/07/14  16:47:41  ram
  1324.   # 3.0 beta baseline.
  1325.   # 
  1326. --- 5,16 ----
  1327.   #  You may redistribute only under the terms of the GNU General Public
  1328.   #  Licence as specified in the README file that comes with dist.
  1329.   #
  1330. ! # $Id: chkagent.sh,v 2.9.1.1 92/12/01 09:10:33 ram Exp $
  1331.   #
  1332.   # $Log:    chkagent.sh,v $
  1333. + # Revision 2.9.1.1  92/12/01  09:10:33  ram
  1334. + # patch13: chkagent could report errors due to spurious matches
  1335. + # 
  1336.   # Revision 2.9  92/07/14  16:47:41  ram
  1337.   # 3.0 beta baseline.
  1338.   # 
  1339. ***************
  1340. *** 42,48 ****
  1341.   
  1342.   if test -f "$logfile"; then
  1343.       grep "$today" $logfile > $todaylog
  1344. !     egrep "$lookat" $todaylog > $output
  1345.       if test -s "$output"; then
  1346.           echo "*** Errors from logfile ($logfile):" > $report
  1347.           echo " " >> $report
  1348. --- 45,51 ----
  1349.   
  1350.   if test -f "$logfile"; then
  1351.       grep "$today" $logfile > $todaylog
  1352. !     egrep ": ($lookat)" $todaylog > $output
  1353.       if test -s "$output"; then
  1354.           echo "*** Errors from logfile ($logfile):" > $report
  1355.           echo " " >> $report
  1356.  
  1357. *** End of Patch 13 ***
  1358.  
  1359. exit 0 # Just in case...
  1360.