home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-12-02 | 54.7 KB | 1,342 lines |
- Newsgroups: comp.sources.misc
- From: Raphael Manfredi <ram@acri.fr>
- Subject: v41i012: mailagent - Flexible mail filtering and processing package, v3.0, Part12/26
- Message-ID: <1993Dec2.133940.18644@sparky.sterling.com>
- X-Md4-Signature: 431e377a5f4975ccb81b9530b42f80e1
- Sender: kent@sparky.sterling.com (Kent Landfield)
- Organization: Advanced Computer Research Institute, Lyon, France.
- Date: Thu, 2 Dec 1993 13:39:40 GMT
- Approved: kent@sparky.sterling.com
-
- Submitted-by: Raphael Manfredi <ram@acri.fr>
- Posting-number: Volume 41, Issue 12
- Archive-name: mailagent/part12
- Environment: UNIX, Perl
- Supersedes: mailagent: Volume 33, Issue 93-109
-
- #! /bin/sh
- # This is a shell archive. Remove anything before this line, then feed it
- # into a shell via "sh file" or similar. To overwrite existing files,
- # type "sh file -c".
- # The tool that generated this appeared in the comp.sources.unix newsgroup;
- # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
- # Contents: MANIFEST agent/pl/analyze.pl agent/pl/interface.pl
- # agent/pl/rules.pl
- # Wrapped by ram@soft208 on Mon Nov 29 16:49:56 1993
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- echo If this archive is complete, you will see the following message:
- echo ' "shar: End of archive 12 (of 26)."'
- if test -f 'MANIFEST' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'MANIFEST'\"
- else
- echo shar: Extracting \"'MANIFEST'\" \(15748 characters\)
- sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
- XArtistic The Artistic Licence
- XChanges User-visible changes between 2.9 and 3.0
- XConfigure Portability tool
- XCredits Traditional "thank you" list
- XJmakefile Description of the main Makefile
- XMANIFEST This list of files
- XMakefile.SH A makefile to run subsidiary makefiles
- XREADME Basic instructions
- Xagent/ Where mailagent support files are located
- Xagent/Jmakefile High level description of Makefile
- Xagent/Makefile.SH Makefile which builds and installs mailagent
- Xagent/README Welcome to mailagent
- Xagent/examples/ A set of files from my own environment
- Xagent/examples/README Explains what the examples are
- Xagent/examples/daemon Rules for "vacation" emulation
- Xagent/examples/mailfolders A copy of my ~/.mailfolders
- Xagent/examples/mchk Checks for new mail
- Xagent/examples/mhinc Call the MH inc command to incorporate new mail
- Xagent/examples/nocmds Message you currently get if you send me a command
- Xagent/examples/profile What I added to my onw ~/.profile
- Xagent/examples/rules The rules I am currently using
- Xagent/examples/vacation A sample vacation message
- Xagent/files/ Mailagent's configuration files
- Xagent/files/Jmakefile High level description for Makefile
- Xagent/files/Makefile.SH Makefile for subsidiary files
- Xagent/files/README Notes about files found in this directory
- Xagent/files/agenthelp Help file used by mailhelp
- Xagent/files/chkagent.sh Cron script to spot problems in the mailagent system
- Xagent/files/commands Allowed commands for mailagent
- Xagent/files/distribs Example of distribution list
- Xagent/files/filter.sh Shell script version of the mail filter
- Xagent/files/help Directory holding SERVER help files
- Xagent/files/help/Jmakefile Generic makefile for help directory
- Xagent/files/help/Makefile.SH Generated makefile
- Xagent/files/help/README States what this directory is about
- Xagent/files/help/addauth.SH Help file for addauth
- Xagent/files/help/approve.SH Help file for approve
- Xagent/files/help/delpower.SH Help file for delpower
- Xagent/files/help/end.SH Help file for end
- Xagent/files/help/getauth.SH Help file for getauth
- Xagent/files/help/help.SH Help file for help
- Xagent/files/help/newpower.SH Help file for newpower
- Xagent/files/help/passwd.SH Help file for passwd
- Xagent/files/help/password.SH Help file for password
- Xagent/files/help/power.SH Help file for power
- Xagent/files/help/release.SH Help file for release
- Xagent/files/help/remauth.SH Help file for remauth
- Xagent/files/help/set.SH Help file for set
- Xagent/files/help/setauth.SH Help file for setauth
- Xagent/files/help/user.SH Help file for user
- Xagent/files/mailagent.cf Example of configuration file
- Xagent/files/passwd An example for power password file
- Xagent/files/proglist Example of description file
- Xagent/files/server An example for server command file
- Xagent/filter/ The C version of the mail filter
- Xagent/filter/Jmakefile Generic makefile template
- Xagent/filter/Makefile.SH Makefile for C filter
- Xagent/filter/README Introduction to filter
- Xagent/filter/environ.c Environment management routines
- Xagent/filter/environ.h Declarations for environment management routines
- Xagent/filter/hash.c Symbol table handling
- Xagent/filter/hash.h Declarations for symbol table
- Xagent/filter/io.c I/O routines
- Xagent/filter/io.h Header for I/O routines
- Xagent/filter/lock.c File locking
- Xagent/filter/lock.h Declarations for file locking routines
- Xagent/filter/logfile.c Logging facilities
- Xagent/filter/logfile.h Header for logging routines
- Xagent/filter/main.c The main entry point for filter
- Xagent/filter/misc.c Miscellaneous routines
- Xagent/filter/msg.c Handles fatal messages
- Xagent/filter/msg.h Declarations for user messages
- Xagent/filter/parser.c Parse the config file with variable substitutions
- Xagent/filter/parser.h About config file parsing
- Xagent/filter/portable.h Portable declarations
- Xagent/filter/sysexits.h Standard exit codes
- Xagent/filter/user.c To get login name from user
- Xagent/magent.SH The main processor
- Xagent/maildist.SH Mails a whole distribution
- Xagent/mailhelp.SH Mails some help
- Xagent/maillist.SH Mails a list of available distributions
- Xagent/mailpatch.SH Mails patches for a given distribution
- Xagent/man/ Manual pages for mailagent
- Xagent/man/Jmakefile Makefile description for jmake
- Xagent/man/Makefile.SH Makefile for manual pages extraction
- Xagent/man/mailagent.SH Produces a manual page for mailagent
- Xagent/man/maildist.SH Produces a manual page for maildist
- Xagent/man/mailhelp.SH Produces a manual page for mailhelp
- Xagent/man/maillist.SH Produces a manual page for maillist
- Xagent/man/mailpatch.SH Produces a manual page for mailpatch
- Xagent/man/package.SH Produces a manual page for package
- Xagent/package.SH Records users of a PD package (cf dist-3.0)
- Xagent/pl/ Perl files used by mailagent scripts
- Xagent/pl/acs_rqst.pl Perl library to ask for private file access
- Xagent/pl/actions.pl Implementation of mailagent's actions
- Xagent/pl/add_log.pl Perl library to add logs to logfile
- Xagent/pl/analyze.pl Perl library analyzing the incoming mail
- Xagent/pl/builtins.pl Perl library dealing with builtins
- Xagent/pl/checklock.pl Perl library to check for long lasting locks
- Xagent/pl/cmdserv.pl Implements generic mail server
- Xagent/pl/compress.pl Folder compression library
- Xagent/pl/context.pl Mailagent context file handling
- Xagent/pl/dbr.pl Internal database management
- Xagent/pl/distribs.pl Perl library to scan the distribs file
- Xagent/pl/dynload.pl Dynamically loads perl code into mailagent
- Xagent/pl/emergency.pl Perl library dealing with emergencies
- Xagent/pl/eval.pl A little expression interpreter
- Xagent/pl/extern.pl Perl library to handle persistent variables
- Xagent/pl/fatal.pl Perl library to deal with fatal errors
- Xagent/pl/file_edit.pl File edition with extensive error checking
- Xagent/pl/filter.pl Running the filtering commands
- Xagent/pl/free_file.pl Perl library to free file access
- Xagent/pl/gensym.pl Dynamic symbol generator
- Xagent/pl/getdate.pl Richard Ohnemus's getdate package
- Xagent/pl/header.pl Header-related routines
- Xagent/pl/history.pl Perl library to implement history mechanism
- Xagent/pl/hook.pl Mail hook wrapping functions
- Xagent/pl/hostname.pl Perl library to compute hostname
- Xagent/pl/include.pl Processing of "include file" requests
- Xagent/pl/interface.pl Perl interface with filter commands
- Xagent/pl/jobnum.pl Perl library to compute a job number
- Xagent/pl/lexical.pl Perl library for lexical analysis
- Xagent/pl/listqueue.pl Perl library to list the queue
- Xagent/pl/locate.pl Perl library to locate loaded patterns/addresses
- Xagent/pl/macros.pl Perl library for macros expansion
- Xagent/pl/mailhook.pl Initializing and running hooks
- Xagent/pl/makedir.pl Perl library for making a directory
- Xagent/pl/matching.pl Matching routines used by filter
- Xagent/pl/mbox.pl Getting mails from a mailbox file
- Xagent/pl/mh.pl Handles MH-style folder delivery
- Xagent/pl/mmdf.pl MMDF-style mailbox handling
- Xagent/pl/newcmd.pl Filter command extension driver
- Xagent/pl/once.pl Dealing with once commands
- Xagent/pl/parse.pl Perl library to parse a mail message
- Xagent/pl/period.pl Perl library to compute periods
- Xagent/pl/plsave.pl Perl library to handle the plsave cache file
- Xagent/pl/plural.pl Perl library to pluralize words
- Xagent/pl/power.pl Power management for mail server
- Xagent/pl/pqueue.pl Processing the queued mails
- Xagent/pl/q.pl Quote removal function
- Xagent/pl/queue_mail.pl Queuing mails
- Xagent/pl/rangeargs.pl Perl library to expand a list of patches
- Xagent/pl/read_conf.pl Perl library to read configuration file
- Xagent/pl/rfc822.pl Perl library to parse RFC822 addresses
- Xagent/pl/rules.pl Compiles the filtering rules
- Xagent/pl/runcmd.pl Filter commands ran from here
- Xagent/pl/secure.pl Make sure a file is "secure" and can be trusted
- Xagent/pl/sendfile.pl Perl library to send files in shar / kit mode
- Xagent/pl/stats.pl Mailagent's statistics recording and printing
- Xagent/pl/tilde.pl Perl library to perform ~name expansion
- Xagent/pl/unpack.pl Perl library to unpack archive files
- Xagent/pl/usrmac.pl User-defined macros
- Xagent/test/ Regression test suite
- Xagent/test/Jmakefile Generic makefile for test suite
- Xagent/test/Makefile.SH Makefile for test suite
- Xagent/test/README About the regression tests
- Xagent/test/TEST Runs the full test suite
- Xagent/test/actions Rule file for cmd tests
- Xagent/test/basic/ Basic tests
- Xagent/test/basic/config.t Main test initialization and sanity checks
- Xagent/test/basic/filter.t Make sure C filter works
- Xagent/test/basic/mailagent.t Make sure mailagent basically works
- Xagent/test/cmd/ Tests of mailagent's filtering commands
- Xagent/test/cmd/abort.t Test ABORT command
- Xagent/test/cmd/annotate.t Test ANNOTATE command
- Xagent/test/cmd/apply.t Test APPLY command
- Xagent/test/cmd/assign.t Test ASSIGN command
- Xagent/test/cmd/back.t Test BACK command
- Xagent/test/cmd/begin.t Test BEGIN command
- Xagent/test/cmd/bounce.t Test BOUNCE command
- Xagent/test/cmd/delete.t Test DELETE command
- Xagent/test/cmd/feed.t Test FEED command
- Xagent/test/cmd/forward.t Test FORWARD command
- Xagent/test/cmd/give.t Test GIVE command
- Xagent/test/cmd/keep.t Test KEEP command
- Xagent/test/cmd/leave.t Test LEAVE command
- Xagent/test/cmd/macro.t Test MACRO command
- Xagent/test/cmd/message.t Test MESSAGE command
- Xagent/test/cmd/nop.t Test NOP command
- Xagent/test/cmd/notify.t Test NOTIFY command
- Xagent/test/cmd/once.t Test ONCE command
- Xagent/test/cmd/pass.t Test PASS command
- Xagent/test/cmd/perl.t Test PERL command
- Xagent/test/cmd/pipe.t Test PIPE command
- Xagent/test/cmd/post.t Test POST command
- Xagent/test/cmd/process.t Test PROCESS command
- Xagent/test/cmd/purify.t Test PURIFY command
- Xagent/test/cmd/queue.t Test QUEUE command
- Xagent/test/cmd/record.t Test RECORD command
- Xagent/test/cmd/reject.t Test REJECT command
- Xagent/test/cmd/require.t Test REQUIRE command
- Xagent/test/cmd/restart.t Test RESTART command
- Xagent/test/cmd/resync.t Test RESYNC command
- Xagent/test/cmd/run.t Test RUN command
- Xagent/test/cmd/save.t Test SAVE command
- Xagent/test/cmd/select.t Test SELECT command
- Xagent/test/cmd/server.t Test SERVER command
- Xagent/test/cmd/split.t Test SPLIT command
- Xagent/test/cmd/store.t Test STORE command
- Xagent/test/cmd/strip.t Test STRIP command
- Xagent/test/cmd/subst.t Test SUBST command
- Xagent/test/cmd/tr.t Test TR command
- Xagent/test/cmd/unique.t Test UNIQUE command
- Xagent/test/cmd/unknown.t Make sure unknown command defaults correctly
- Xagent/test/cmd/vacation.t Test VACATION command
- Xagent/test/cmd/write.t Test WRITE command
- Xagent/test/filter/ Testing the filtering capabilities
- Xagent/test/filter/backref.t Check backreferences
- Xagent/test/filter/case.t Normalized header case tests
- Xagent/test/filter/default.t Check default behaviour when mail not saved
- Xagent/test/filter/escape.t Escape sequences within actions
- Xagent/test/filter/group.t Selector combination tests
- Xagent/test/filter/hook.t Ensure hooks are correctly invoked
- Xagent/test/filter/list.t Check matching on lists like To and Newsgroups
- Xagent/test/filter/loop.t Check loop detection
- Xagent/test/filter/mode.t Make sure mode selection logic works
- Xagent/test/filter/multiple.t Check multiple selectors
- Xagent/test/filter/not.t Negated pattern tests
- Xagent/test/filter/pattern.t Check patterns specification and loading
- Xagent/test/filter/range.t Selector range tests
- Xagent/test/filter/status.t Action status updating tests
- Xagent/test/level Default logging level for tests
- Xagent/test/mail The mail used by testing routines
- Xagent/test/misc/ Directory for miscellaneous tests
- Xagent/test/misc/compress.t Folder compression checks
- Xagent/test/misc/mh.t MH-style folder checks
- Xagent/test/misc/mmdf.t MMDF-style mailbox checks
- Xagent/test/misc/newcmd.t Filter command extension tests
- Xagent/test/misc/usrmac.t User-defined macros checks
- Xagent/test/option/ Tests the options to the mailagent program
- Xagent/test/option/L.t Test -L option
- Xagent/test/option/V.t Test -V option
- Xagent/test/option/c.t Test -c option
- Xagent/test/option/d.t Test -d option
- Xagent/test/option/e.t Test -e option
- Xagent/test/option/f.t Test -f option
- Xagent/test/option/h.t Test -h option
- Xagent/test/option/i.t Test -i option
- Xagent/test/option/l.t Test -l option
- Xagent/test/option/o.t Test -o option
- Xagent/test/option/q.t Test -q option
- Xagent/test/option/r.t Test -r option
- Xagent/test/option/s.t Test -s option
- Xagent/test/option/t.t Test -t option
- Xagent/test/option/what.t Ensure good behaviour with unknown option
- Xagent/test/pl/ Perl libraries for the regression test suite
- Xagent/test/pl/cmd.pl Initializes command paths
- Xagent/test/pl/filter.pl Set up environment for filter tests
- Xagent/test/pl/init.pl Variable initializations
- Xagent/test/pl/logfile.pl Logging file checking
- Xagent/test/pl/mail.pl Modifies mail components
- Xagent/test/pl/misc.pl Set up for miscellaneous tests
- Xagent/test/pl/mta.pl Trivial MTA and NTA for tests
- Xagent/test/rules Rules used by filtering tests
- Xbin/ Directory for uninstalled binaries
- Xbin/perload The dataloading/autoloading perl translator
- Xconfig_h.SH Produces config.h
- Xconfmagic.h Magic symbol remapping
- Xinstall.SH Installation script
- Xmisc/ Miscellaneous server commands
- Xmisc/README Introduction to the misc directory
- Xmisc/shell/ Command to run arbitrary shell commands
- Xmisc/shell/README Warning, should be read carefully
- Xmisc/shell/server.cf Configuration of this server command
- Xmisc/shell/shell The shell command itself
- Xmisc/unkit/ Command to automatically unkit messages
- Xmisc/unkit/README Some notes about the UNKIT command
- Xmisc/unkit/kitok.msg An example of message to be sent when kit received
- Xmisc/unkit/mailagent.cf Template for inclusion into your ~/.mailagent
- Xmisc/unkit/newcmd.cf Configuration of the new command
- Xmisc/unkit/rules Rules to be added to handle kit messages
- Xmisc/unkit/unkit.pl Implementation of the user-defined UNKIT command
- Xpatchlevel.h Current version number and patch level
- END_OF_FILE
- if test 15748 -ne `wc -c <'MANIFEST'`; then
- echo shar: \"'MANIFEST'\" unpacked with wrong size!
- fi
- # end of 'MANIFEST'
- fi
- if test -f 'agent/pl/analyze.pl' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'agent/pl/analyze.pl'\"
- else
- echo shar: Extracting \"'agent/pl/analyze.pl'\" \(14857 characters\)
- sed "s/^X//" >'agent/pl/analyze.pl' <<'END_OF_FILE'
- X;# $Id: analyze.pl,v 3.0 1993/11/29 13:48:35 ram Exp ram $
- X;#
- X;# Copyright (c) 1990-1993, Raphael Manfredi
- X;#
- X;# You may redistribute only under the terms of the Artistic License,
- X;# as specified in the README file that comes with the distribution.
- X;# You may reuse parts of this distribution only within the terms of
- X;# that same Artistic License; a copy of which may be found at the root
- X;# of the source tree for mailagent 3.0.
- X;#
- X;# $Log: analyze.pl,v $
- X;# Revision 3.0 1993/11/29 13:48:35 ram
- X;# Baseline for mailagent 3.0 netwide release.
- X;#
- X;#
- X#
- X# Analyzing mail
- X#
- X
- X# Special users. Note that as login name matches are done in a case-insensitive
- X# manner, there is no need to upper-case any of the followings.
- Xsub init_special {
- X %Special = (
- X 'root', 1, # Super-user
- X 'uucp', 1, # Unix to Unix copy
- X 'daemon', 1, # Not a real user, hopefully
- X 'news', 1, # News daemon
- X 'postmaster', 1, # X-400 mailer-daemon name
- X 'newsmaster', 1, # My convention for news administrator--RAM
- X 'usenet', 1, # Aka newsmaster
- X 'mailer-daemon', 1, # Sendmail
- X 'mailer-agent', 1, # NeXT mailer
- X 'nobody', 1 # Nobody we've heard of
- X );
- X}
- X
- X# Parse mail message and apply the filtering rules on it
- Xsub analyze_mail {
- X local($file) = shift(@_); # Mail file to be parsed
- X local($mode) = 'INITIAL'; # Initial working mode
- X local($wmode) = $mode; # Needed for statistics routines
- X
- X # Set-up proper environment. Dynamic scoping is used on those variables
- X # for the APPLY command (see the &apply function). Note that the $wmode
- X # variable is passed to &apply_rules but is local to that function,
- X # meaning there is no feedback of the working mode when using APPLY.
- X # However, the variables listed below may be probed upon return since they
- X # are external to &apply_rules.
- X local($ever_matched) = 0; # Did we ever matched a single saving rule ?
- X local($ever_saved) = 0; # Did we ever saved a message ?
- X
- X # Other local variables used only in this function
- X local($ever_seen) = 0; # Did we ever enter seen mode ?
- X local($vacation) = 1; # Vacation message allowed a priori
- X local($header); # Header entry name to look for in Header table
- X
- X # Parse the mail message in file
- X &parse_mail($file); # Parse the mail and fill-in H tables
- X return 0 unless defined $Header{'All'}; # Mail not parsed correctly
- X &reception if $loglvl > 8; # Log mail reception
- X &run_builtins; # Execute builtins, if any were found
- X
- X # Now analyze the mail. If there is already a X-Filter header, then the
- X # mail has already been processed. In that case, the default action is
- X # performed: leave it in the incomming mailbox with no further action.
- X # This should prevent nasty loops.
- X
- X &add_log ("analyzing mail") if $loglvl > 18;
- X $header = $Header{'X-Filter'}; # Mulitple occurences possible
- X if ($header ne '') { # Hmm... already filtered...
- X local(@filter) = split(/\n/, $header); # Look for each X-Filter
- X local($address) = &email_addr; # Our e-mail address
- X local($done) = 0; # Already processed ?
- X local($*) = 0;
- X local($_);
- X foreach (@filter) { # Maybe we'll find ourselves
- X if (/mailagent.*for (\S+)/) { # Mark left by us ?
- X $done = 1 if $1 eq $address; # Yes, we did that
- X $* = 1;
- X # Remove that X-Filter line, LEAVE will add one anyway
- X $Header{'Head'} =~ s/^X-Filter:\s*mailagent.*for $address\n//;
- X $* = 0;
- X last;
- X }
- X }
- X if ($done) { # We already processed that message
- X &add_log("NOTICE already filtered, entering seen mode")
- X if $loglvl > 5;
- X $mode = '_SEEN_'; # This is a special mode
- X $ever_seen = 1; # This will prevent vacation messages
- X &s_seen; # Update statistics
- X }
- X }
- X
- X &apply_rules($mode, 1); # Now apply the filtering rules on it.
- X
- X # Deal with vacation mode. It applies only on mail not previously seen.
- X # The vacation mode must be turned on in the configuration file. The
- X # conditions for a vacation message to be sent are:
- X # - Message was directly sent to the user.
- X # - Message does not come from a special user like root.
- X # - Vacation message was not disabled via a VACATION command
- X
- X if (!$ever_seen && $cf'vacation =~ /on/i && $vacation) {
- X unless (&special_user) { # Not from special user and sent to me
- X # Send vacation message only once per address per period
- X &xeqte("ONCE (%r,vacation,$cf'vacperiod) MESSAGE $cf'vacfile");
- X &s_vacation; # Message received while in vacation
- X }
- X }
- X
- X # Default action if no rule ever matched. Statistics routines will use
- X # our own local $wmode variable.
- X
- X unless ($ever_matched) {
- X &add_log("NOTICE no match, leaving in mailbox") if $loglvl > 5;
- X &xeqte("LEAVE"); # Default action anyway
- X &s_default; # One more application of default rule
- X } else {
- X unless ($ever_saved) {
- X &add_log("NOTICE not saved, leaving in mailbox") if $loglvl > 5;
- X &xeqte("LEAVE"); # Leave if message not saved
- X &s_saved; # Message saved by default rule
- X }
- X }
- X &s_filtered($Header{'Length'}); # Update statistics
- X
- X 0; # Ok status
- X}
- X
- X# This is the heart of the mail agent -- Apply the filtering rules
- Xsub apply_rules {
- X local($wmode, $stats)= @_; # Working mode (the mode we start in)
- X local($mode); # Mode (optional)
- X local($selector); # Selector (mandatory)
- X local($range); # Range for selection (optional)
- X local($rulentry); # Entry in rule H table
- X local($pattern); # Pattern for selection, as written in rules
- X local($action); # Related action
- X local($last_selector); # Last used selector
- X local($rules); # A copy of the rules
- X local($matched); # Flag set to true if a rule is matched
- X local(%Matched); # Records the selectors which have been matched
- X local($status); # Status returned by xeqte
- X local(@Executed); # Records already executed rules
- X local($selist); # Key used to detect identical selector lists
- X local(%Inverted); # Records inverted '!' selectors which matched
- X local(%Variable); # User-defined variables
- X
- X # The @Executed array records whether a specified action for a rule was
- X # executed. Loops are possible via the RESTART action, and as there is
- X # almost no way to exit from such a loop (there is one with FEED and RESYNC)
- X # I decided to prohibit them. Hence a given action is allowed to be executed
- X # only once during a mail analysis (modulo each possible working mode).
- X # For a rule number n, $Executed[n] is a collection of modes in which the
- X # rule was executed, comma separated.
- X
- X $Executed[$#Rules] = ''; # Pre-extend array
- X
- X # Order wrt the one in the rule file is guaranteed. I use a for construct
- X # with indexed access to be able to restart from the beginning upon
- X # execution of RESTART. This also helps filling in the @Executed array.
- X
- X local($i, $j); # Indices within rule array
- X
- X rule: for ($i = 0; $i <= $#Rules; $i++) {
- X $j = $i + 1;
- X $_ = $Rules[$i];
- X
- X # The %Matched array records the boolean value associated with each
- X # possible selector. If two identical selector are found, the values
- X # are OR'ed (and we stop evaluating as soon as one is true). Otherwise,
- X # the values are AND'ed (for different selectors, but all are evaluated
- X # in case we later find another identical selectors -- no sort is done).
- X # The %Inverted which records '!' selector matches has all the above
- X # rules inverted according to De Morgan's Law.
- X
- X undef %Matched; # Reset matching patterns
- X undef %Inverted; # Reset negated patterns
- X $rules = $_; # Work on a copy
- X $rules =~ s/^([^{]*){// && ($mode = $1); # First word is the mode
- X $rules =~ s/\s*(.*)}// && ($action = $1); # Followed by action
- X $mode =~ s/\s*$//; # Remove trailing spaces
- X $rules =~ s/^\s+//; # Remove leading spaces
- X $last_selector = ""; # Last selector used
- X
- X # Make sure we are in the correct mode. The $mode variable holds a
- X # list of comma-separated modes. If the working mode is found in it
- X # then the rules apply. Otherwise, skip them.
- X
- X next rule unless &right_mode; # Skip rule if not in right mode
- X
- X # Now loop over all the keys and apply the patterns in turn
- X
- X &reset_backref; # Reset backreferences
- X foreach $key (split(/ /, $rules)) {
- X $rulentry = $Rule{$key};
- X $rulentry =~ s/^\s*([^\/]*:)// && ($selector = $1);
- X $rulentry =~ s/^\s*//;
- X $pattern = $rulentry;
- X if ($last_selector ne $selector) { # Update last selector
- X $last_selector = $selector;
- X }
- X $selector =~ s/:$//; # Remove final ':' on selector
- X $range = '<1,->'; # Default range
- X $selector =~ s/\s*(<[\d\s,-]+>)$// && ($range = $1);
- X
- X &add_log ("selector '$selector' on '$range', pattern '$pattern'")
- X if $loglvl > 19;
- X
- X # Identical (lists of) selectors are logically OR'ed. To make sure
- X # 'To Cc:' and 'Cc To:' are correctly OR'ed, the selector list is
- X # alphabetically sorted.
- X
- X $selist = join(',', sort split(' ', $selector));
- X
- X # Direct selectors and negated selectors (starting with a !) are
- X # kept separately, because the rules are dual:
- X # For normal selectors (kept in %Matched):
- X # - Identical are OR'ed
- X # - Different are AND'ed
- X # For inverted selectors (kept in %Inverted):
- X # - Identical are AND'ed
- X # - Different are OR'ed
- X # Multiple selectors like 'To Cc' are sorted according to the first
- X # selector on the list, i.e. 'To !Cc' is normal but '!To Cc' is
- X # inverted.
- X
- X if ($selector =~ /^!/) { # Inverted selector
- X # In order to guarantee an optimized AND, we first check that
- X # no previous failure has been reported for the current set of
- X # selectors.
- X unless (defined $Inverted{$selist} && !$Inverted{$selist}) {
- X $Inverted{$selist} = &match($selector, $pattern, $range);
- X }
- X } else { # Normal selector
- X # Here it is the OR which is guaranteed to be optimized. Do
- X # not attempt the match if an identical selector already
- X # matched sucessfully.
- X unless ($Matched{$selist}) {
- X $Matched{$selist} = &match($selector, $pattern, $range);
- X }
- X }
- X }
- X
- X # Both groups recorded in %Matched and %Inverted are globally AND'ed
- X # However, only one match is necessary within %Inverted whilst all
- X # must have matched within %Matched...
- X
- X $matched = 1; # Assume everything matched
- X foreach $key (keys %Matched) { # All entries must have matched
- X $matched = 0 unless $Matched{$key};
- X }
- X if ($matched) { # If %Matched failed, all failed!
- X foreach $key (keys %Inverted) { # Only one entry needs to match
- X $matched = 0 unless $Inverted{$key};
- X last if $matched;
- X }
- X }
- X
- X if ($matched) { # Execute action if pattern matched
- X # Make sure the rule has not already been executed in that mode
- X if ($Executed[$i] =~ /,$wmode,/) {
- X &add_log("NOTICE loop detected, rule $j, state $wmode")
- X if $loglvl > 5;
- X last rule; # Processing ends here
- X } else { # Rule was never executed
- X $Executed[$i] = ',' unless $Executed[$i];
- X $Executed[$i] .= "$wmode,";
- X }
- X $ever_matched = 1; # At least one match
- X &add_log("MATCH on rule #$j in mode $wmode") if $loglvl > 8;
- X &track_rule($j, $wmode) if $track_all;
- X &s_match($j, $wmode) if $stats; # Record match for statistics
- X $status = &xeqte($action);
- X last rule if $status == $FT_CONT;
- X $ever_matched = 0; # No match if REJECT or RESTART
- X next rule if $status == $FT_REJECT;
- X $i = -1; # Restart analysis from the beginning ($FT_RESTART)
- X }
- X }
- X ($ever_saved, $ever_matched);
- X}
- X
- X# Return true if the modes currently specified by the rule (held in $mode)
- X# are selected by the current mode (in $wmode), meaning the rule has to
- X# be applied.
- Xsub right_mode {
- X local($list) = "," . $mode . ",";
- X &add_log("in mode '$wmode' for $mode") if $loglvl > 19;
- X
- X # If mode is negated, skip the rule, whatever other selectors may
- X # indicate. Thus <ALL, !INITIAL> will not be taken into account if
- X # mode is INITIAL, despite the leading ALL. They can be seen as further
- X # requirements or restrictions applied to the mode list (like in the
- X # sentence "all the listed modes *but* the one negated").
- X
- X return 0 if $list =~ /!ALL/; # !ALL cannot match, ever
- X return 0 if $list =~ /,!$wmode,/; # Negated modes logically and'ed
- X
- X # Now strip out all negated modes, and if the resulting string is
- X # empty, force a match...
- X
- X 1 while $list =~ s/,![^,]*,/,/; # Strip out negated modes
- X $list = ',ALL,' if $list eq ','; # Emtpy list, force a match
- X
- X # The special ALL mode matches anything but the other sepcial mode for
- X # already filtered messages. Otherwise, direct mode (i.e. non-negated)
- X # are logically or'ed.
- X
- X if ($list =~ /,ALL,/) {
- X return 0 if $wmode eq '_SEEN_' && $list !~ /,_SEEN_,/;
- X } else {
- X return 0 unless $list =~ /,$wmode,/;
- X }
- X
- X 1; # Ok, rule can be applied
- X}
- X
- X# Return true if the mail was from a special user (root, uucp...) or if the
- X# mail was not directly mailed to the user (i.e. it comes from a distribution
- X# list or has bounced somewhere).
- Xsub special_user {
- X # Before sending the vacation message, we have to make sure the mail
- X # was sent to the user directly, through a 'To:' or a 'Cc:'. Otherwise,
- X # it must be from a mailing list or a 'Bcc:' and we don't want to
- X # send something back in that case.
- X local($matched) = &match_list("To", $cf'user);
- X $matched = &match_list("Cc", $cf'user) unless $matched;
- X unless ($matched) {
- X &add_log("mail was not directly sent to $cf'user") if $loglvl > 8;
- X return 1;
- X }
- X # If there is a Precedence: header set to either 'bulk', 'list' or 'junk',
- X # then we do not reply either.
- X local($prec) = $Header{'Precedence'};
- X if ($prec =~ /^bulk|junk|list/i) {
- X &add_log("mail was tagged with a '$prec' precedence") if $loglvl > 8;
- X return 1;
- X }
- X # Make sure the mail does not come from a "special" user, as listed in
- X # the %Special array (root, uucp...)
- X $matched = 0;
- X local($matched_login);
- X foreach $login (keys %Special) {
- X $matched = &match_single("From", $login);
- X $matched_login = $login if $matched;
- X last if $matched;
- X }
- X if ($matched) {
- X &add_log("mail was from special user $matched_login")
- X if $loglvl > 8;
- X return 1;
- X }
- X}
- X
- X# Log reception of mail (sender and subject fields). This is mainly intended
- X# for people like me who parse the logfile once in a while to do more
- X# statistics about mail reception. Hence the another distinction between
- X# original mails and answers.
- Xsub reception {
- X local($subject) = $Header{'Subject'};
- X local($sender) = $Header{'Sender'};
- X local($from) = $Header{'From'};
- X &add_log("FROM $from");
- X &add_log("VIA $sender") if $sender ne '' &&
- X (&parse_address($sender))[0] ne (&parse_address($from))[0];
- X if ($subject ne '') {
- X if ($subject =~ s/^Re:\s*//) {
- X &add_log("REPLY $subject");
- X } else {
- X &add_log("ABOUT $subject");
- X }
- X }
- X print "-------- From $from\n" if $track_all;
- X}
- X
- X# Print match on STDOUT when -t option is used
- Xsub track_rule {
- X local($number, $mode) = @_;
- X print "*** Match on rule $number in mode $mode ***\n";
- X &print_rule($number);
- X}
- X
- END_OF_FILE
- if test 14857 -ne `wc -c <'agent/pl/analyze.pl'`; then
- echo shar: \"'agent/pl/analyze.pl'\" unpacked with wrong size!
- fi
- # end of 'agent/pl/analyze.pl'
- fi
- if test -f 'agent/pl/interface.pl' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'agent/pl/interface.pl'\"
- else
- echo shar: Extracting \"'agent/pl/interface.pl'\" \(5893 characters\)
- sed "s/^X//" >'agent/pl/interface.pl' <<'END_OF_FILE'
- X;# $Id: interface.pl,v 3.0 1993/11/29 13:48:53 ram Exp ram $
- X;#
- X;# Copyright (c) 1990-1993, Raphael Manfredi
- X;#
- X;# You may redistribute only under the terms of the Artistic License,
- X;# as specified in the README file that comes with the distribution.
- X;# You may reuse parts of this distribution only within the terms of
- X;# that same Artistic License; a copy of which may be found at the root
- X;# of the source tree for mailagent 3.0.
- X;#
- X;# $Log: interface.pl,v $
- X;# Revision 3.0 1993/11/29 13:48:53 ram
- X;# Baseline for mailagent 3.0 netwide release.
- X;#
- X;#
- X;# This is for people who, like me, are perl die-hards :-). It simply provides
- X;# a simple perl interface for hook scripts and PERL commands. Instead of
- X;# writing 'COMMAND with some arguments;' in the filter rule file, you may say
- X;# &command('with some arguments') in the perl script. Big deal! Well, at least
- X;# that brings you some other nice features from perl itself ;-).
- X;#
- X#
- X# Perl interface with the filter actions
- X#
- X
- Xpackage mailhook;
- X
- Xsub abort { &interface'dispatch; }
- Xsub annotate { &interface'dispatch; }
- Xsub apply { &interface'dispatch; }
- Xsub assign { &interface'dispatch; }
- Xsub back { &interface'dispatch; }
- Xsub begin { &interface'dispatch; }
- Xsub bounce { &interface'dispatch; }
- Xsub delete { &interface'dispatch; }
- Xsub feed { &interface'dispatch; }
- Xsub forward { &interface'dispatch; }
- Xsub give { &interface'dispatch; }
- Xsub keep { &interface'dispatch; }
- Xsub leave { &interface'dispatch; }
- Xsub macro { &interface'dispatch; }
- Xsub message { &interface'dispatch; }
- Xsub nop { &interface'dispatch; }
- Xsub notify { &interface'dispatch; }
- Xsub once { &interface'dispatch; }
- Xsub pass { &interface'dispatch; }
- Xsub perl { &interface'dispatch; }
- Xsub pipe { &interface'dispatch; }
- Xsub post { &interface'dispatch; }
- Xsub process { &interface'dispatch; }
- Xsub purify { &interface'dispatch; }
- Xsub queue { &interface'dispatch; }
- Xsub record { &interface'dispatch; }
- Xsub reject { &interface'dispatch; }
- Xsub require { &interface'dispatch; }
- Xsub restart { &interface'dispatch; }
- Xsub resync { &interface'dispatch; }
- Xsub run { &interface'dispatch; }
- Xsub save { &interface'dispatch; }
- Xsub select { &interface'dispatch; }
- Xsub server { &interface'dispatch; }
- Xsub split { &interface'dispatch; }
- Xsub store { &interface'dispatch; }
- Xsub strip { &interface'dispatch; }
- Xsub subst { &interface'dispatch; }
- Xsub tr { &interface'dispatch; }
- Xsub unique { &interface'dispatch; }
- Xsub vacation { &interface'dispatch; }
- Xsub write { &interface'dispatch; }
- X
- X# A perl filtering script should call &exit and not exit directly.
- Xsub exit {
- X local($code) = @_;
- X die "OK\n" unless $code;
- X die "Exit $code\n";
- X}
- X
- Xpackage interface;
- X
- X# Perload OFF
- X# (Cannot be dynamically loaded as it uses the caller() function)
- X
- X# The dispatch routine is really simple. We compute the name of our caller,
- X# prepend it to the argument and call run_command to actually run the command.
- X# Upon return, if we get anything but a continue status, we simply die with
- X# an 'OK' string, which will be a signal to the routine monitoring the execution
- X# that nothing wrong happened.
- Xsub dispatch {
- X local($args) = join(' ', @_); # Arguments for the command
- X local($name) = (caller(1))[3]; # Function which called us
- X local($status); # Continuation status
- X $name =~ s/^\w+'//; # Strip leading package name
- X &'add_log("calling '$name $args'") if $'loglvl > 18;
- X $status = &'run_command("$name $args"); # Case does not matter
- X
- X # The status propagation is the only thing we have to deal with, as this
- X # is handled within run_command. All other variables which are meaningful
- X # for the filter are dynamically bound to function called before in the
- X # stack, hence they are modified directly from within the perl script.
- X
- X die "Status $status\n" unless $status == $'FT_CONT;
- X
- X # Return the status held in $lastcmd, unless the command does not alter
- X # the status significantly, in which case we return success. Note that
- X # this is in fact a boolean success status, so 1 means success, whereas
- X # $lastcmd records a failure status.
- X
- X $name =~ tr/a-z/A-Z/; # Stored upper-cased
- X $'Nostatus{$name} ? 1 : !$lastcmd; # Propagate status
- X}
- X
- X# Perload ON
- X
- X$in_perl = 0; # Number of nested perl evaluations
- X
- X# Record entry in new perl evaluation
- Xsub new {
- X ++$in_perl; # Add one evalution level
- X}
- X
- X# Reset an empty mailhook package by undefining all its symbols.
- X# (Warning: heavy wizardry used here -- look at perl's manpage for recipe.)
- Xsub reset {
- X return if --$in_perl > 0; # Do nothing if pending evals remain
- X &'add_log("undefining variables from mailhook") if $'loglvl > 11;
- X local($key, $val); # Key/value from perl's symbol table
- X # Loop over perl's symbol table for the mailhook package
- X while (($key, $val) = each(%_mailhook)) {
- X local(*entry) = $val; # Get definitions of current slot
- X undef $entry unless length($key) == 1 && $key !~ /^\w/;
- X undef @entry;
- X undef %entry unless $key =~ /^_/ || $key eq 'header';
- X undef &entry if &valid($key);
- X $_mailhook{$key} = *entry; # Commit our changes
- X }
- X}
- X
- X# Return true if the function may safely be undefined
- Xsub valid {
- X local($fun) = @_; # Function name
- X return 0 if $fun eq 'exit'; # This function is a convenience
- X # We cannot undefine a filter function, which are listed (upper-cased) in
- X # the %main'Filter table.
- X return 1 unless length($fun) == ($fun =~ tr/a-z/A-Z/);
- X return 1 unless $'Filter{$fun};
- X 0;
- X}
- X
- X# Add a new interface function for user-defined commands
- Xsub add {
- X local($cmd) = @_; # Command name
- X $cmd =~ tr/A-Z/a-z/; # Cannonicalize to lower case
- X eval &'q(<<EOP); # Compile new mailhook perl interface function
- X: sub mailhook'$cmd { &interface'dispatch; }
- XEOP
- X if (chop($@)) {
- X &'add_log("ERROR while adding 'sub $cmd': $@") if $'loglvl;
- X &'add_log("WARNING cannot use '&$cmd' in perl hooks")
- X if $'loglvl > 5;
- X }
- X}
- X
- Xpackage main;
- X
- END_OF_FILE
- if test 5893 -ne `wc -c <'agent/pl/interface.pl'`; then
- echo shar: \"'agent/pl/interface.pl'\" unpacked with wrong size!
- fi
- # end of 'agent/pl/interface.pl'
- fi
- if test -f 'agent/pl/rules.pl' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'agent/pl/rules.pl'\"
- else
- echo shar: Extracting \"'agent/pl/rules.pl'\" \(14814 characters\)
- sed "s/^X//" >'agent/pl/rules.pl' <<'END_OF_FILE'
- X;# $Id: rules.pl,v 3.0 1993/11/29 13:49:14 ram Exp ram $
- X;#
- X;# Copyright (c) 1990-1993, Raphael Manfredi
- X;#
- X;# You may redistribute only under the terms of the Artistic License,
- X;# as specified in the README file that comes with the distribution.
- X;# You may reuse parts of this distribution only within the terms of
- X;# that same Artistic License; a copy of which may be found at the root
- X;# of the source tree for mailagent 3.0.
- X;#
- X;# $Log: rules.pl,v $
- X;# Revision 3.0 1993/11/29 13:49:14 ram
- X;# Baseline for mailagent 3.0 netwide release.
- X;#
- X;#
- X# Here are the data structures we use to store the compiled form of the rules:
- X# @Rules has entries looking like "<$mode> {$action} $rulekeys..."
- X# %Rule has entries looking like "$selector: $pattern"
- X# Each rule was saved in @Rules. The ruleskeys have the form H<num> where <num>
- X# is an increasing integer. They index the rules in %Rule.
- X
- X# Compile the rules held in file $cf'rules (usually ~/.rules) or in memory
- Xsub compile_rules {
- X local($mode); # mode (optional)
- X local($first_selector); # selector (mandatory first time)
- X local($selector); # selector (optional)
- X local($pattern); # pattern to be matched
- X local($action); # associated action
- X local($rulekeys); # keys to rules in hash table
- X local($rulenum) = 0; # to compute unique keys for the hash table
- X local($line); # buffer for next rule
- X local($env); # environment variable recognized
- X
- X # This function is called whenever a new line rule has to be read. By
- X # default, rules are read from a file, but if @Linerules is set, they
- X # are read from there.
- X local(*read_rule) = *read_filerule if @Linerules == 0;
- X local(*read_rule) = *read_linerule if @Linerules > 0;
- X
- X unless ($edited_rules) { # If no rules from command line
- X unless (-s "$cf'rules") { # No rule file or empty
- X &default_rules; # Build default rules
- X return;
- X }
- X unless (open(RULES, "$cf'rules")) {
- X &add_log("ERROR cannot open $cf'rules: $!") if $loglvl;
- X &default_rules; # Default rules will apply then
- X return;
- X }
- X if (&rules'read_cache) { # Rules already compiled and cached
- X close RULES; # No parsing needs to be done
- X return;
- X }
- X } else { # Rules in @Linerules array
- X &rule_cleanup if @Linerules == 1;
- X }
- X
- X while ($line = &get_line) {
- X # Detect environment settings as soon as possible
- X if ($line =~ s/^\s*(\w+)\s*=\s*//) {
- X # All the variables referenced in the line have to be environment
- X # variables. So replace them with the values we already computed as
- X # perl variables. This enables us to do variable substitution in
- X # perl with minimum trouble.
- X $env = $1; # Variable being changed
- X $line =~ s/\$(\w+)/\$XENV{'$1'}/g; # $VAR -> $XENV{'VAR'}
- X $line =~ s/;$//; # Remove trailing ;
- X eval "\$XENV{'$env'} = \"$line\""; # Perl does the evaluations
- X &eval_error; # Report any eval error
- X next;
- X }
- X $rulekeys = ''; # Reset keys for each line
- X $mode = &get_mode(*line); # Get operational mode
- X &add_log("mode: <$mode>") if $loglvl > 19;
- X $first_selector = &get_selector(*line); # Fetch a selector
- X $first_selector = "Subject:" unless $first_selector;
- X $selector = $first_selector;
- X for (;;) {
- X if ($line =~ /^\s*;/) { # Selector alone on the line
- X &add_log("ERROR no pattern nor action, line $.") if $loglvl > 1;
- X last; # Ignore the whole line
- X }
- X &add_log("selector: $selector") if $loglvl > 19;
- X # Get a pattern. If none is found, it is assumed to be '*', which
- X # will match anything.
- X $pattern = &get_pattern(*line);
- X $pattern = '*' if $pattern =~ /^\s*$/;
- X &add_log("pattern: $pattern") if $loglvl > 19;
- X # Record entry in H table and update the set of used keys
- X $Rule{"H$rulenum"} = "$selector $pattern";
- X $rulekeys .= "H$rulenum ";
- X $rulenum++;
- X # Now look for an action. No action at the end means LEAVE.
- X $action = &get_action(*line);
- X $action = "LEAVE" if $action =~ /^\s*$/ && $line =~/^\s*;/;
- X if ($action !~ /^\s*$/) {
- X &add_log("action: $action") if $loglvl > 19;
- X push(@Rules, "$mode {$action} $rulekeys");
- X $rulekeys = ''; # Reset rule keys once used
- X }
- X last if $line =~ /^\s*;/; # Finished if end of line reached
- X last if $line =~ /^\s*$/; # Also finished if end of file
- X # Get a new selector, defaults to last one seen if none is found
- X $selector = &get_selector(*line);
- X $selector = $first_selector if $selector eq '';
- X $first_selector = $selector;
- X }
- X }
- X close RULES; # This may not have been opened
- X
- X &default_rules unless @Rules; # Use defaults if no valid rules
- X
- X # If rules have been compiled from a file and not entered on the command
- X # line via -e switch(es), then $edited_rules is false and it makes sense
- X # to cache the lattest compiled rules. Note that the 'rulecache' parameter
- X # is optional, and rules are actually cached only if it is defined.
- X
- X &rules'write_cache unless $edited_rules;
- X}
- X
- X# Build default rules:
- X# - Anything with 'Subject: Command' in it is processed.
- X# - All the mails are left in the mailbox.
- Xsub default_rules {
- X &add_log("building default rules") if $loglvl > 18;
- X @Rules = ("ALL {LEAVE; PROCESS} H0");
- X $Rule{'H0'} = "All: /^Subject: [Cc]ommand/";
- X}
- X
- X# Rule cleanup: If there is only one rule specified within the @Linerules
- X# array, it might not have {} braces.
- Xsub rule_cleanup {
- X return if $Linerules[0] =~ /[{}]/; # Braces found
- X $Linerules[0] = '{' . $Linerules[0] . '}';
- X}
- X
- X# Hook functions for dumping rules
- Xsub print_rule_number {
- X local($rulenum) = @_;
- X print "# Rule $rulenum\n"; # For easier reference
- X 1; # Continue
- X}
- X
- X# Void function
- Xsub void_func {
- X print "\n";
- X}
- X
- X# Print only rule whose number is held in variable $number
- Xsub exact_rule {
- X $_[0] eq $number;
- X}
- X
- Xsub nothing { } # Do nothing, really nothing
- X
- X# Dump the rules we've compiled -- for debug purposes
- Xsub dump_rules {
- X # The 'before' hook is called before each rule is called. It returns a
- X # boolean stating wether we should continue or skip the rule. The 'after'
- X # hook is called after the rule has been printed. Both hooks are given the
- X # rule number as argument.
- X local(*before, *after) = @_; # Hook functions to be called
- X local($mode); # mode (optional)
- X local($selector); # selector (mandatory)
- X local($rulentry); # entry in rule H table
- X local($pattern); # pattern for selection
- X local($action); # related action
- X local($last_selector); # last used selector
- X local($rules); # a copy of the rules
- X local($rulenum) = 0; # each rule is numbered
- X local($lines); # number of pattern lines printed
- X local(@action); # split actions (split on ;)
- X local($printed) = 0; # characters printed on line so far
- X local($indent); # next item indentation
- X local($linelen) = 78; # maximum line length
- X # Print the environement variable which differ from the original
- X # environment, i.e. those variable which were set by the user.
- X $lines = 0;
- X foreach (keys(%XENV)) {
- X unless ("$XENV{$_}" eq "$ENV{$_}") {
- X print "$_ = ", $XENV{$_}, ";\n";
- X $lines++;
- X }
- X }
- X print "\n" if $lines;
- X # Order wrt the one in the rule file is guaranteed
- X foreach (@Rules) {
- X $rulenum++;
- X next unless &before($rulenum); # Call 'before' hook
- X $rules = $_; # Work on a copy
- X $rules =~ s/^([^{]*){// && ($mode = $1); # First "word" is the mode
- X $rules =~ s/\s*(.*)}// && ($action = $1); # Then action within {}
- X $mode =~ s/\s*$//; # Remove trailing spaces
- X print "<$mode> "; # Mode in which it applies
- X $printed = length($mode) + 3;
- X $rules =~ s/^\s+//; # The rule keys remain
- X $last_selector = ""; # Last selector in use
- X $lines = 0;
- X foreach $key (split(/ /, $rules)) { # Loop over the keys
- X $rulentry = $Rule{$key};
- X $rulentry =~ s/^\s*([^\/]*:)// && ($selector = $1);
- X $rulentry =~ s/^\s*//;
- X $pattern = $rulentry;
- X if ($last_selector eq $selector) { # Try to stay on same line
- X # Go to next line if current pattern won't fit nicely
- X if ($printed + length($pattern) > $linelen) {
- X $indent = length($mode) + length($selector) + 4;
- X print ",\n", ' ' x $indent;
- X $lines++;
- X $printed = $indent;
- X } else {
- X print ", ";
- X $printed += 2;
- X }
- X } else { # Selector has changed
- X if ($lines++) {
- X $indent = length($mode) + 3;
- X print ",\n", ' ' x $indent;
- X $printed = $indent;
- X }
- X }
- X if ($last_selector ne $selector) { # Update last selector
- X $last_selector = $selector;
- X if ($selector ne 'script:') { # Pseudo not printed
- X print "$selector ";
- X $printed += length($selector) + 1;
- X }
- X }
- X if ($selector ne 'script:') {
- X print "$pattern"; # Normal pattern
- X $printed += length($pattern);
- X } else {
- X print "[[ $pattern ]] "; # An interpreted script
- X $printed += length($pattern) + 7;
- X }
- X }
- X print " " if $lines == 1 && $printed += 2;
- X
- X # Split actions, but take care of escaped \; (layout purposes)
- X $action =~ s/\\\\/\02/g; # \\ -> ^B
- X $action =~ s/\\;/\01/g; # \; -> ^A
- X @action = split(/;/, $action);
- X foreach (@action) { # Restore escapes by in-place edit
- X s/\01/\\;/g; # ^A -> \;
- X s/\02/\\\\/g; # ^B -> \\
- X }
- X
- X # If action is large enough, format differently (one action/line)
- X $lines++ if length($action) + 5 + $printed > $linelen;
- X $indent = $lines > 1 ? length($mode) + 3 + 4 : 0;
- X $printed = $indent == 0 ? $printed : $indent;
- X if ((length($action) + $printed) > $linelen && @action > 1) {
- X print "\n\t{\n";
- X foreach $act (@action) {
- X $act =~ s/^\s+//;
- X print "\t\t$act;\n";
- X }
- X print "\t};\n";
- X } else {
- X print "\n", ' ' x $indent if $lines > 1;
- X print "{ $action };\n";
- X }
- X $printed = 0;
- X
- X # Call the hook function after having printed the rule
- X &after($rulenum);
- X }
- X}
- X
- X# Print only a specific rule on stdout
- Xsub print_rule {
- X local($number) = @_;
- X local(%XENV); # Suppress printing of leading variables
- X &dump_rules(*exact_rule, *nothing);
- X}
- X
- X#
- X# The following package added to hold all the new rule-specific functions
- X# added at version 3.0.
- X#
- X
- Xpackage rules;
- X
- X# Cache rules to the 'rulecache' file. The first line is the full pathname
- X# of the rule file, followed by the modification time stamp. The rulecache
- X# file will be recreated each time a different rule file is provided or when
- X# it is out of date. Note that this function is only called when actually
- X# compiling from the 'rules' file defined in the config file.
- X# The function returns 1 if success, 0 on failure.
- Xsub write_cache {
- X return 0 unless defined $cf'rulecache;
- X local(*CACHE); # File handle used to write the cache
- X unless (open(CACHE, ">$cf'rulecache")) {
- X &'add_log("ERROR cannot create rule cache $cf'rulecache: $!")
- X if $'loglvl;
- X unlink $cf'rulecache;
- X return 0;
- X }
- X local($error) = 0;
- X local($ST_MTIME) = 9 + $[;
- X local($mtime) = (stat($cf'rules))[$ST_MTIME];
- X (print CACHE "$cf'rules $mtime\n") || $error++;
- X &write_fd(CACHE) || $error++; # Write rules
- X &writevar_fd(CACHE) || $error++; # And XENV variables
- X close(CACHE) || $error++;
- X if ($error) {
- X &'add_log("WARNING could not cache rules") if $'loglvl > 5;
- X unlink $cf'rulecache;
- X return 0;
- X }
- X 1; # Success
- X}
- X
- X# Read cached rules into @Rules and %Rules and returns 1 if done, 0 when
- X# the cache may not be read for whatever reason (e.g. out of date).
- Xsub read_cache {
- X return 0 unless &cache_ok;
- X local(*CACHE); # File handle used to read the cache
- X local($_);
- X open(CACHE, $cf'rulecache) || return 0; # Cannot open, assume out of date
- X $_ = <CACHE>; # Disregard top line
- X while (<CACHE>) { # First read the @Rules
- X chop;
- X last if /^$/; # Reached end of @Rules table
- X push(@'Rules, $_);
- X }
- X local($rulenum) = 0;
- X while (<CACHE>) { # Next read sorted values, assigned to H...
- X chop;
- X last if /^\+\+\+\+\+\+/; # End of dumped rules
- X $'Rule{"H$rulenum"} = $_;
- X $rulenum++;
- X }
- X while (<CACHE>) { # Read XENV variables
- X chop;
- X s/^\s*(\w+)\s*=\s*// && ($'XENV{$1} = $_);
- X }
- X close CACHE;
- X 1; # Success
- X}
- X
- X# Is cache up-to-date with respect to the rule file? Returns true if cache ok.
- Xsub cache_ok {
- X return 0 unless defined $cf'rulecache;
- X local(*CACHE); # File handle used to read the cache
- X local($top); # Top line recording file name and timestamp
- X open(CACHE, $cf'rulecache) || return 0; # Cannot open, assume out of date
- X $top = <CACHE>; # Get that first line
- X close CACHE;
- X local($name, $stamp) = split(' ', $top);
- X return 0 if $name ne $cf'rules; # File changed, cache out of date
- X local($ST_MTIME) = 9 + $[;
- X local($mtime) = (stat($cf'rules))[$ST_MTIME];
- X $mtime != $stamp ? 0 : 1; # Cache up-to-date only if $stamp == $mtime
- X}
- X
- X# Dump the internal form of the rules, returning 1 for success.
- Xsub write_fd {
- X local($file) = @_; # Filehandle in which rules are to be dumped
- X local($_);
- X local($error) = 0;
- X foreach (@'Rules) {
- X (print $file $_, "\n") || $error++;
- X }
- X (print $file "\n") || $error++; # A blank line separates tables
- X foreach (sort hashkey keys %'Rule) {
- X (print $file $'Rule{$_}, "\n") || $error++;
- X }
- X (print $file "++++++\n") || $error++; # Marks end of dumped rules
- X $error ? 0 : 1; # Success when no error reported
- X}
- X
- X# Dump the internal form of environment variables, returning 1 for success.
- Xsub writevar_fd {
- X local($file) = @_; # Filehandle in which variables are printed
- X local($error) = 0;
- X local($_);
- X foreach (keys(%'XENV)) {
- X unless ("$'XENV{$_}" eq "$'ENV{$_}") {
- X (print $file "$_ = ", $'XENV{$_}, "\n") || $error++;
- X }
- X }
- X $error ? 0 : 1; # Success when no error reported
- X}
- X
- X# Sorting for hash keys used by %Rule
- Xsub hashkey {
- X local($c) = $a =~ /^H(\d+)/;
- X local($d) = $b =~ /^H(\d+)/;
- X $c <=> $d;
- X}
- X
- X# The following sets-up a new rule environment and then transfers the control
- X# to some other function, giving it the remaining parameters. That enables the
- X# other function to work transparently with a different set of rules. Merely
- X# done for the APPLY function. Returns undef for errors, or propagates the
- X# result of the function.
- Xsub alternate {
- X local($rules, $fn, @rest) = @_;
- X local($'edited_rules) = 1; # Signals that rules do not come from main file
- X local(@'Rules); # Set up a new dynamic environment...
- X local(%'Rule);
- X local(@'Linerules); # We're stuffing our new rules there
- X
- X unless (open(RULES, $rules)) {
- X &'add_log("ERROR cannot open alternate rule file $rules: $!")
- X if $'loglvl;
- X return undef;
- X }
- X local($_);
- X while (<RULES>) {
- X chop; # Not really needed, but it'll save space :-)
- X push(@'Linerules, $_);
- X }
- X close RULES;
- X
- X # Need at list two line rules or we'll try to apply some default fixes
- X # used by the -e 'rules' switch...
- X push(@'Linerules, '', '') if @'Linerules <= 1;
- X
- X # Make sure transfer function is package-qualified
- X $fn = "main'$fn" unless $fn =~ /'/;
- X
- X &'compile_rules; # Compile new rules held in the @'Linerules array
- X &$fn(@rest); # Transfer control in new environment
- X}
- X
- Xpackage main;
- X
- END_OF_FILE
- if test 14814 -ne `wc -c <'agent/pl/rules.pl'`; then
- echo shar: \"'agent/pl/rules.pl'\" unpacked with wrong size!
- fi
- # end of 'agent/pl/rules.pl'
- fi
- echo shar: End of archive 12 \(of 26\).
- cp /dev/null ark12isdone
- MISSING=""
- for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ; do
- if test ! -f ark${I}isdone ; then
- MISSING="${MISSING} ${I}"
- fi
- done
- if test "${MISSING}" = "" ; then
- echo You have unpacked all 26 archives.
- echo "Now run 'sh PACKNOTES', then read README and type Configure.'"
- rm -f ark[1-9]isdone ark[1-9][0-9]isdone
- else
- echo You still must unpack the following archives:
- echo " " ${MISSING}
- fi
- exit 0
-
- exit 0 # Just in case...
-