home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume41 / mailagnt / part05 < prev    next >
Encoding:
Text File  |  1993-12-02  |  54.9 KB  |  1,540 lines

  1. Newsgroups: comp.sources.misc
  2. From: Raphael Manfredi <ram@acri.fr>
  3. Subject: v41i005:  mailagent - Flexible mail filtering and processing package, v3.0, Part05/26
  4. Message-ID: <1993Dec2.133626.18103@sparky.sterling.com>
  5. X-Md4-Signature: 16910566830c6f663d514e73a1d6f7a0
  6. Sender: kent@sparky.sterling.com (Kent Landfield)
  7. Organization: Advanced Computer Research Institute, Lyon, France.
  8. Date: Thu, 2 Dec 1993 13:36:26 GMT
  9. Approved: kent@sparky.sterling.com
  10.  
  11. Submitted-by: Raphael Manfredi <ram@acri.fr>
  12. Posting-number: Volume 41, Issue 5
  13. Archive-name: mailagent/part05
  14. Environment: UNIX, Perl
  15. Supersedes: mailagent: Volume 33, Issue 93-109
  16.  
  17. #! /bin/sh
  18. # This is a shell archive.  Remove anything before this line, then feed it
  19. # into a shell via "sh file" or similar.  To overwrite existing files,
  20. # type "sh file -c".
  21. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  22. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  23. # Contents:  agent/pl/actions.pl.01 agent/test/filter/list.t
  24. # Wrapped by ram@soft208 on Mon Nov 29 16:49:55 1993
  25. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  26. echo If this archive is complete, you will see the following message:
  27. echo '          "shar: End of archive 5 (of 26)."'
  28. if test -f 'agent/pl/actions.pl.01' -a "${1}" != "-c" ; then 
  29.   echo shar: Will not clobber existing file \"'agent/pl/actions.pl.01'\"
  30. else
  31.   echo shar: Extracting \"'agent/pl/actions.pl.01'\" \(49987 characters\)
  32.   sed "s/^X//" >'agent/pl/actions.pl.01' <<'END_OF_FILE'
  33. X;# $Id: actions.pl,v 3.0 1993/11/29 13:48:33 ram Exp ram $
  34. X;#
  35. X;#  Copyright (c) 1990-1993, Raphael Manfredi
  36. X;#  
  37. X;#  You may redistribute only under the terms of the Artistic License,
  38. X;#  as specified in the README file that comes with the distribution.
  39. X;#  You may reuse parts of this distribution only within the terms of
  40. X;#  that same Artistic License; a copy of which may be found at the root
  41. X;#  of the source tree for mailagent 3.0.
  42. X;#
  43. X;# $Log: actions.pl,v $
  44. X;# Revision 3.0  1993/11/29  13:48:33  ram
  45. X;# Baseline for mailagent 3.0 netwide release.
  46. X;#
  47. X;# 
  48. X#
  49. X# Implementation of filtering commands
  50. X#
  51. X
  52. X# The "LEAVE" command
  53. X# Leave a copy of the message in the mailbox. Returns (mbox, failed_status)
  54. Xsub leave {
  55. X    local($mailbox) = &mailbox_name;    # Incomming mailbox filename
  56. X    &add_log("starting LEAVE") if $loglvl > 15;
  57. X    &save($mailbox);                    # Propagate return status
  58. X}
  59. X
  60. X# The "SAVE" command
  61. X# Save a message in a folder. Returns (mbox, failed_status). If the folder
  62. X# already exists and has the 'x' bit set, then is is understood as an external
  63. X# hook and mailhook is invoked. If the folder name begins with '+', it is
  64. X# handled as an MH folder. If the folder is actually a directory, then message
  65. X# is saved in an individual file, much like an MH folder.
  66. Xsub save {
  67. X    local($mailbox) = @_;            # Where mail should be saved
  68. X    local($failed) = 0;                # Printing status
  69. X    unless ($mailbox) {                # Empty mailbox (e.g. SAVE %1 with no match)
  70. X        &add_log("WARNING empty folder name, using mailbox") if $loglvl > 5;
  71. X        $mailbox = &mailbox_name;
  72. X    }
  73. X    &add_log("starting SAVE $mailbox") if $loglvl > 15;
  74. X    if ($mailbox =~ s/^\+//) {        # MH folder?
  75. X        $failed = &mh'save($mailbox);
  76. X    } elsif (-d $mailbox) {            # A directory hook
  77. X        $failed = &mh'savedir($mailbox);
  78. X    } elsif (-x $mailbox) {            # Folder hook
  79. X        $failed = &save_hook;        # Deliver to program
  80. X    } else {                        # Saving to a normal folder
  81. X        # Uncompress folders if necessary. The restore routine will perform
  82. X        # the necessary checks and return immediately if no compression is
  83. X        # wanted for that particular folder. However, we can avoid the overhead
  84. X        # of calling this routine (and loading it when using dataloading) if
  85. X        # the 'compress' configuration parameter is missing.
  86. X        &compress'restore($mailbox) if $cf'compress;
  87. X        $failed = &save_folder($mailbox);
  88. X    }
  89. X    &add_log("ERROR could not save mail in $mailbox") if $failed && $loglvl;
  90. X    &emergency_save if $failed;
  91. X    ($mailbox, $failed);            # Where save was made and failure status
  92. X}
  93. X
  94. X# Called by &save when folder is a regular one (i.e. not a hook).
  95. Xsub save_folder {
  96. X    local($mailbox) = @_;            # Where mail should be saved
  97. X    local($amount);                    # Amount of bytes written
  98. X    local($failed);
  99. X    if (open(MBOX, ">>$mailbox")) {
  100. X
  101. X        &mbox_lock($mailbox);        # Lock mailbox, now have exclusive access
  102. X        local($size) = -s $mailbox;    # Initial mailbox size
  103. X
  104. X        # If MMDF-style mailboxes are allowed, then the saving routine will
  105. X        # try to determine what kind of folder it is delivering to and choose
  106. X        # the right format. Otherwise, standard Unix format is assumed.
  107. X        if ($cf'mmdf =~ /on/i) {    # MMDF-style allowed
  108. X            # Save to mailbox, selecting the right format (UNIX vs MMDF)
  109. X            ($failed, $amount) = &mmdf'save(*MBOX, $mailbox);
  110. X        } else {
  111. X            # Save to UNIX folder
  112. X            ($failed, $amount) = &mmdf'save_unix(*MBOX);
  113. X        }
  114. X
  115. X        # Because we might write over NFS, and because we might have had to
  116. X        # force fate to get a lock, it is wise to make sure the folder has the
  117. X        # right size, which would tend to indicate the mail made it to the
  118. X        # buffer cache, if not to the disk itself.
  119. X        local($should) = $size + $amount;    # Computed new size for mailbox
  120. X        local($new_size) = -s $mailbox;        # Last write was flushed to disk
  121. X        &add_log("ERROR $mailbox has $new_size bytes (should have $should)")
  122. X            if $new_size != $should && $loglvl;
  123. X        $failed = 1 if $new_size != $should;
  124. X
  125. X        # Finally, release the lock on the mailbox and close the file. If the
  126. X        # closing operation fails for whatever reason, the routine will return
  127. X        # a 1, so $failed will be set. Of course, "normally" it should not
  128. X        # fail at that point, since the mail was previously flushed.
  129. X        $failed |= &mbox_unlock($mailbox);    # Will close file
  130. X
  131. X    } else {
  132. X        &add_log("SYSERR open: $!") if $loglvl;
  133. X        if (-f "$mailbox") {
  134. X            &add_log("ERROR cannot append to $mailbox") if $loglvl;
  135. X        } else {
  136. X            &add_log("ERROR cannot create $mailbox") if $loglvl;
  137. X        }
  138. X        $failed = 1;
  139. X    }
  140. X    $failed;        # Propagate failure status
  141. X}
  142. X
  143. X# Called by &save when folder is a hook.
  144. X# Return command failure status.
  145. Xsub save_hook {
  146. X    local($failed) = &hook'process($mailbox);
  147. X    &add_log("HOOKED [$mfile]") if !$failed && $loglvl > 2;
  148. X    $failed;                # Propagate failure status
  149. X}
  150. X
  151. X# The "PROCESS" command
  152. X# The body of the message is expected to be in $Header{'Body'}
  153. Xsub process {
  154. X    local($subj) =            $Header{'Subject'};
  155. X    local($msg_id) =        $Header{'Message-Id'};
  156. X    local($sender) =        $Header{'Reply-To'};
  157. X    local($to) =            $Header{'To'};
  158. X    local($bad) = "";        # No bad commands
  159. X    local($pack) = "auto";    # Default packing mode for sending files
  160. X    local($ncmd) = 0;        # Number of valid commands we have found
  161. X    local($dest) = "";        # Destination (where to send answers)
  162. X    local(@cmd);            # Array of all commands
  163. X    local(%packmode);        # Records pack mode for each command
  164. X    local($error) = 0;        # Error report code
  165. X    local(@body);            # Body of message
  166. X
  167. X    &add_log("starting PROCESS") if $loglvl > 15;
  168. X
  169. X    # If no @PATH directive was found, use $sender as a return path
  170. X    $dest = $Userpath;                # Set by an @PATH
  171. X    $dest = $sender unless $dest;
  172. X    # Remove the <> if any (e.g. path derived from Return-Path)
  173. X    $dest =~ /<(.*)>/ && ($dest = $1);
  174. X
  175. X    # Debugging purposes
  176. X    &add_log("@PATH was '$Userpath' and sender was '$sender'") if $loglvl > 18;
  177. X    &add_log("computed destination: $dest") if $loglvl > 15;
  178. X
  179. X    # Copy body of message in an array, one line per entry
  180. X    @body = split(/\n/, $Header{'Body'});
  181. X
  182. X    # The command file contains the authorized commands
  183. X    if ($#command < 0) {            # Command file not processed yet
  184. X        open(COMMAND, "$cf'comfile") || &fatal("No command file!");
  185. X        while (<COMMAND>) {
  186. X            chop;
  187. X            $command{$_} = 1;
  188. X        }
  189. X        close(COMMAND);
  190. X    }
  191. X
  192. X    line: foreach (@body) {
  193. X        # Built-in commands
  194. X        if (/^@PACK\s*(.*)/) {        # Pack mode
  195. X            $pack = $1 if $1 ne '';
  196. X            $pack = "" if ($pack =~ /[=$^&*([{}`\\|;><?]/);
  197. X        }
  198. X        s/^[ \t]@SH/@SH/;    # allow one blank only
  199. X        if (/^@SH/) {
  200. X            s/\\!/!/g;        # if uucp address, un-escape `!'
  201. X            if (/[=\$^&*([{}`\\|;><?]/) {
  202. X                s/^@SH/bad command:/;    # space after ":" will be added
  203. X                $bad .= $_ . "\n";
  204. X                next line;
  205. X            }
  206. X            # Some useful substitutions
  207. X            s/@SH[ \t]*//;                # Allow leading blanks
  208. X            s/ PATH/ $dest/;             # PATH is a macro
  209. X            s/^mial(\w*)/mail\1/;        # Common mis-spellings
  210. X            s/^mailpath/mailpatch/;
  211. X            s/^mailist/maillist/;
  212. X            # Now fetch command's name (first symbol)
  213. X            if (/^([^ \t]+)[ \t]/) {
  214. X                $first = $1;
  215. X            } else {
  216. X                $first = $_;
  217. X            }
  218. X            if (!$command{$first}) {    # if un-authorized cmd
  219. X                s/^/unknown cmd: /;        # needs a space after ":"
  220. X                $bad .= $_ . "\n";
  221. X                next line;
  222. X            }
  223. X            $packmode{$_} = $pack;        # packing mode for this command
  224. X            push(@cmd, $_);                # record command
  225. X        }
  226. X    }
  227. X
  228. X    # ************* Check with authoritative file ****************
  229. X
  230. X    # Do not continue if an error occurred, in which case the mail will remain
  231. X    # in the queue and will be processed later on.
  232. X    return $error if $error || $dest eq '';
  233. X
  234. X    # Now we are sure the mail we proceed is for us
  235. X    $sender = "<someone>" if $sender eq '';
  236. X    $ncmd = $#cmd + 1;
  237. X    if ($ncmd > 1) {
  238. X        &add_log("$ncmd commands for $sender") if $loglvl > 11;
  239. X    } elsif ($ncmd == 1) {
  240. X        &add_log("1 command for $sender") if $loglvl > 11;
  241. X    } else {
  242. X        &add_log("no command for $sender") if $loglvl > 11;
  243. X    }
  244. X    foreach $fullcmd (@cmd) {
  245. X        $cmdfile = "/tmp/mess.cmd$$";
  246. X        open(CMD,">$cmdfile");
  247. X        # For our children
  248. X        print CMD "jobnum=$jobnum export jobnum\n";
  249. X        print CMD "fullcmd=\"$fullcmd\" export fullcmd\n";
  250. X        print CMD "pack=\"$packmode{$fullcmd}\" export pack\n";
  251. X        print CMD "path=\"$dest\" export path\n";
  252. X        print CMD "sender=\"$sender\" export sender\n";
  253. X        print CMD "set -x\n";
  254. X        print CMD "$fullcmd\n";
  255. X        close CMD;
  256. X        $fullcmd =~ /^[ \t]*(\w+)/;        # extract first word
  257. X        $cmdname = $1;        # this is the command name
  258. X        $trace = "$cf'tmpdir/trace.cmd$$";
  259. X        $pid = fork;                        # We fork here
  260. X        $pid = -1 unless defined $pid;
  261. X        if ($pid == 0) {
  262. X            open(STDOUT, ">$trace");        # Where output goes
  263. X            open(STDERR, ">&STDOUT");        # Make it follow pipe
  264. X            exec '/bin/sh', "$cmdfile";        # Don't use sh -c
  265. X        } elsif ($pid == -1) {
  266. X            # Set the error report code, and the mail will remain in queue
  267. X            # for later processing. Any @RR in the message will be re-executed
  268. X            # but it is not really important. In fact, this is going to be
  269. X            # a feature, not a bug--RAM.
  270. X            $error = 1;
  271. X            &add_log("ERROR cannot fork: $!") if $loglvl > 0;
  272. X            open(MAILER,"|$cf'sendmail $cf'mailopt $dest $cf'user");
  273. X            print MAILER <<EOM;
  274. XTo: $dest
  275. XSubject: $cmdname not executed
  276. X$MAILER
  277. X
  278. XYour command was: $fullcmd
  279. X
  280. XIt was not executed because I could not fork. Sigh !
  281. X(Kernel report: $!)
  282. X
  283. XThe command has been left in a queue and will be processed again
  284. Xas soon as possible, so it is useless to resend it.
  285. X
  286. X-- mailagent speaking for $cf'user
  287. XEOM
  288. X            close MAILER;
  289. X            if ($?) {
  290. X                &add_log("ERROR cannot report failure") if $loglvl;
  291. X            }
  292. X            return $error;        # Abort processing now--mail remains in queue
  293. X        } else {
  294. X            wait();
  295. X            if ($?) {
  296. X                open(MAILER,"|$cf'sendmail $cf'mailopt $dest $cf'user");
  297. X                print MAILER <<EOM;
  298. XTo: $dest
  299. XSubject: $cmdname returned a non-zero status
  300. X$MAILER
  301. X
  302. XYour command was: $fullcmd
  303. XIt produced the following output and failed:
  304. X
  305. XEOM
  306. X                if (open(TRACE, "$trace")) {
  307. X                    while (<TRACE>) {
  308. X                        print MAILER;
  309. X                    }
  310. X                    close TRACE;
  311. X                } else {
  312. X                    print MAILER "** SORRY - NOT AVAILABLE **\n";
  313. X                    &add_log("ERROR cannot dump trace") if $loglvl;
  314. X                }
  315. X                print MAILER "\n-- mailagent speaking for $cf'user\n";
  316. X                close MAILER;
  317. X                if ($?) {
  318. X                    &add_log("ERROR cannot report failure") if $loglvl;
  319. X                }
  320. X                &add_log("FAILED $fullcmd") if $loglvl > 1;
  321. X            } else {
  322. X                &add_log("OK $fullcmd") if $loglvl > 5;
  323. X            }
  324. X        }
  325. X        unlink $cmdfile, $trace;
  326. X    }
  327. X
  328. X    if ($bad) {
  329. X        open(MAILER,"|$cf'sendmail $cf'mailopt $dest $cf'user");
  330. X        chop($bad);            # Remove trailing new-line
  331. X        print MAILER <<EOM;
  332. XTo: $dest
  333. XSubject: the following commands were not executed
  334. X$MAILER
  335. X
  336. X$bad
  337. X
  338. XIf $cf'name can figure out what you wanted, he may do it anyway.
  339. X
  340. X-- mailagent speaking for $cf'user
  341. XEOM
  342. X        close MAILER;
  343. X        if ($?) {
  344. X            &add_log("ERROR unable to mail back bad commands from $sender")
  345. X                if $loglvl;
  346. X        }
  347. X        &add_log("bad commands from $sender") if $loglvl > 5;
  348. X    }
  349. X
  350. X    &add_log("all done for $sender") if $loglvl > 11;
  351. X    $error;        # Return error report (0 for ok)
  352. X}
  353. X
  354. X# The "MACRO" command
  355. Xsub macro {
  356. X    $_[0] =~ s/^\s*-([rdp]+)//;        # Remove options
  357. X    local($opt) = $1;
  358. X    local($replace) = $opt =~ /r/;    # Replace existing macro
  359. X    local($delete) = $opt =~ /d/;    # Delete macro
  360. X    local($pop) = $opt =~ /p/;        # Pop macro
  361. X    $_[0] =~ s/^\s+//;                # Trim leading spaces
  362. X    local($args) = $_[0];            # name = (value, type)
  363. X    local($name);                    # Macro's name
  364. X    if ($delete || $pop) {            # Macro is to be deleted or popped
  365. X        ($name) = $args =~ /(\S+)/;    # Get first "word"
  366. X        &usrmac'pop($name) if $pop;    # Pop last value, delete if last
  367. X        &usrmac'delete($name) if $delete;
  368. X        return ($name, $pop ? 'popped' : 'deleted');    # Propagate action
  369. X    }
  370. X    # There are two formats for the macro command. The first format uses the
  371. X    # 'name = (val, type)' template and can be used to specify any kind of
  372. X    # macro (see usrmac.pl). The other form is name ..., where ... is any
  373. X    # kind of string --including spaces-- which will be used as a SCALAR
  374. X    # value. Of course, that string cannot take the '= (val, type)' format.
  375. X    local($val);                    # Macro's value
  376. X    local($type) = 'SCALAR';        # Assume scalar type
  377. X    if ($args =~ /(\S+)\s*=\s*\(\s*(.*),\s*(\w+)\s*\)\s*/) {
  378. X        ($name, $val, $type) = ($1, $2, $3);
  379. X    } else {
  380. X        ($name, $val) = $args =~ /(\S+)\s+(.*)/;    # SCALAR type assumed
  381. X    }
  382. X    &usrmac'new($name, $val, $type) if $replace;
  383. X    &usrmac'push($name, $val, $type) unless $replace;
  384. X    ($name, $replace ? 'replaced' : 'pushed');        # Propagate action
  385. X}
  386. X
  387. X# The "MESSAGE" command
  388. Xsub message {
  389. X    local($msg) = @_;            # Vacation message to be sent back
  390. X    local(@head) = (
  391. X        "To: %r (%N)",
  392. X        "Subject: Re: %R"
  393. X    );
  394. X    local($to) = '%r';                # Recipient is macro %r
  395. X    ¯os_subst(*to);                # Evaluate it so we can give it to mailer
  396. X    &send_message($msg, *head, $to);
  397. X}
  398. X
  399. X# The "NOTIFY" command
  400. Xsub notify {
  401. X    local($msg, $address) = @_;
  402. X    # Any address included withing "" means addresses are stored in a file
  403. X    $address = &complete_list($address, 'address');
  404. X    $address =~ s/%/%%/g;    # Protect all '%' (subject to macro substitution)
  405. X    local($to) = $address;    # For the To: line...
  406. X    $to =~ s/\s+/, /g;        # Addresses separated by ',' on the To: line
  407. X    local(@head) = (
  408. X        "To: $to",
  409. X        "Subject: %s (notification)"
  410. X    );
  411. X    &send_message($msg, *head, $address);
  412. X}
  413. X
  414. X# Send a given message to somebody, as specified in the given header
  415. X# The message and the header are subject to macro substitution.
  416. X# Usually, when using sendmail, the -t option could be used to parse header
  417. X# and obtain the recipients. However, the mailer being configurable, we cannot
  418. X# assume it will understand -t. Therefore, the recipients must be specified.
  419. Xsub send_message {
  420. X    local($msg, *header, $recipients) = @_;    # Message to send, header, where
  421. X    unless (-f "$msg") {
  422. X        &add_log("ERROR cannot find message $msg") if $loglvl > 0;
  423. X        return 1;
  424. X    }
  425. X    unless (open(MSG, "$msg")) {
  426. X        &add_log("ERROR cannot open message $msg") if $loglvl > 0;
  427. X        return 1;
  428. X    }
  429. X    unless (open(MAILER,"|$cf'sendmail $cf'mailopt $recipients")) {
  430. X        &add_log("ERROR cannot run $cf'sendmail to send message: $!")
  431. X            if $loglvl;
  432. X        return 1;
  433. X    }
  434. X
  435. X    # Construction of value for the %T macro
  436. X    local($macro_T);            # Default value of macro %T is overwritten
  437. X    local($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime, $mtime,
  438. X        $ctime,$blksize,$blocks) = stat($msg);
  439. X    local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
  440. X            localtime($mtime);
  441. X    local($this_year) = (localtime(time))[5];
  442. X    # Do not put the year in %T if it is the same as the current one.
  443. X    ++$mon;                        # Month in the range 1-12
  444. X    if ($this_year != $year) {
  445. X        $macro_T = sprintf("%.2d/%.2d/%.2d", $year, $mon, $mday);
  446. X    } else {
  447. X        $macro_T = sprintf("%.2d/%.2d", $mon, $mday);
  448. X    }
  449. X
  450. X    # Header construction. If the file contains a header at the top, it is
  451. X    # added to the one we already have by default. Identical fields are
  452. X    # overwritten with the one found in the file.
  453. X    if (&header_found($msg)) {    # Top of message is a header
  454. X        local(@newhead);        # New header is constructed here
  455. X        local($field);
  456. X        while (<MSG>) {            # Read the header then
  457. X            last if /^$/;        # End of header
  458. X            chop;
  459. X            push(@newhead, $_);
  460. X            if (/^([\w\-]+):/) {
  461. X                $field = $1;
  462. X                @head = grep(!/^$field:/, @head);    # Field is overwritten
  463. X            }
  464. X        }
  465. X        foreach (@newhead) {
  466. X            push(@head, $_);
  467. X        }
  468. X    }
  469. X    push(@head, $FILTER);        # Avoid loops: replying to ourselves or whatever
  470. X    foreach $line (@head) {
  471. X        ¯os_subst(*line);    # In-place macro substitutions
  472. X        print MAILER "$line\n";    # Write header
  473. X    }
  474. X    print MAILER "\n";            # Header separated from body
  475. X    # Now write the body
  476. X    local($tmp);                # Because of a bug in perl 4.0 PL19
  477. X    while ($tmp = <MSG>) {
  478. X        next if $tmp =~ /^$/ && $. == 1;    # Escape sequence to protect header
  479. X        ¯os_subst(*tmp);        # In-place macro substitutions
  480. X        print MAILER $tmp;            # Write message line
  481. X    }
  482. X
  483. X    # Close pipe and check status
  484. X    close MSG;
  485. X    close MAILER;
  486. X    local($status) = $?;
  487. X    unless ($status) {
  488. X        if ($loglvl > 2) {
  489. X            local($dest) = $head[0];    # The To: header line
  490. X            ($dest) = $dest =~ m|^To:\s+(.*)|;
  491. X            &add_log("SENT message to $dest");
  492. X        }
  493. X    } else {
  494. X        &add_log("ERROR could not mail back $msg") if $loglvl > 1;
  495. X    }
  496. X    $status;        # 0 for success
  497. X}
  498. X
  499. X# The "FORWARD" command
  500. Xsub forward {
  501. X    local($addresses) = @_;            # Address(es) mail should be forwarded to
  502. X    local($address) = &email_addr;    # Address of user
  503. X    # Any address included withing "" is in fact a file name where actual
  504. X    # forwarding addresses are found.
  505. X    $addresses =
  506. X        &complete_list($addresses, 'address');    # Process "include-requests"
  507. X    unless (open(MAILER,"|$cf'sendmail $cf'mailopt $addresses")) {
  508. X        &add_log("ERROR cannot run $cf'sendmail to forward message: $!")
  509. X            if $loglvl;
  510. X        return 1;
  511. X    }
  512. X    local(@addr) = split(' ', $addresses);
  513. X    print MAILER &header'format("Resent-From: $address"), "\n";
  514. X    local($to) = "Resent-To: " . join(', ', @addr);
  515. X    print MAILER &header'format($to), "\n";
  516. X    # Protect Sender: and Resent-: lines in the original message
  517. X    foreach (split(/\n/, $Header{'Head'})) {
  518. X        next if /^From\s+(\S+)/;
  519. X        s/^Sender:\s*(.*)/Prev-Sender: $1/;
  520. X        s/^Resent-([\w\-]+):\s*(.*)/Prev-Resent-$1: $2/;
  521. X        print MAILER $_, "\n";
  522. X    }
  523. X    print MAILER $FILTER, "\n";
  524. X    print MAILER "\n";
  525. X    print MAILER $Header{'Body'};
  526. X    close MAILER;
  527. X    local($failed) = $?;        # Status of forwarding
  528. X    if ($failed) {
  529. X        &add_log("ERROR could not forward to $addresses") if $loglvl > 1;
  530. X    }
  531. X    $failed;        # 0 for success
  532. X}
  533. X
  534. X# The "BOUNCE" command
  535. Xsub bounce {
  536. X    local($addresses) = @_;            # Address(es) mail should be bounced to
  537. X    # Any address included withing "" is in fact a file name where actual
  538. X    # bouncing addresses are found.
  539. X    $addresses =
  540. X        &complete_list($addresses, 'address');    # Process "include-requests"
  541. X    unless (open(MAILER,"|$cf'sendmail $cf'mailopt $addresses")) {
  542. X        &add_log("ERROR cannot run $cf'sendmail to bounce message: $!")
  543. X            if $loglvl;
  544. X        return 1;
  545. X    }
  546. X    # Protect Sender: lines in the original message
  547. X    foreach (split(/\n/, $Header{'Head'})) {
  548. X        next if /^From\s+(\S+)/;
  549. X        s/^Sender:\s*(.*)/Prev-Sender: $1/;
  550. X        print MAILER $_, "\n";
  551. X    }
  552. X    print MAILER $FILTER, "\n";
  553. X    print MAILER "\n";
  554. X    print MAILER $Header{'Body'};
  555. X    close MAILER;
  556. X    local($failed) = $?;        # Status of forwarding
  557. X    if ($failed) {
  558. X        &add_log("ERROR could not bounce to $addresses") if $loglvl > 1;
  559. X    }
  560. X    $failed;        # 0 for success
  561. X}
  562. X
  563. X# The "POST" command
  564. Xsub post {
  565. X    # Option parsing: a -l restricts distribution to local
  566. X    local($localdist) = 0;
  567. X    $localdist = 1 if ($_[0] =~ s/^\s*-l\s+//);
  568. X    local($newsgroups) = @_;        # Newsgroup(s) mail should be posted to
  569. X    local($address) = &email_addr;    # Address of user
  570. X    unless (open(NEWS,"|$cf'sendnews $cf'newsopt -h")) {
  571. X        &add_log("ERROR cannot run $cf'sendnews to post message: $!")
  572. X            if $loglvl;
  573. X        return 1;
  574. X    }
  575. X    &add_log("distribution of posting is local")
  576. X        if $loglvl > 18 && $localdist;
  577. X    # Protect Sender: lines in the original message and clean-up header
  578. X    local($last_was_header);        # Set to true when header is skipped
  579. X    foreach (split(/\n/, $Header{'Head'})) {
  580. X        s/^Sender:\s*(.*)/Prev-Sender: $1/;
  581. X        next if /^From\s/;                    # First From line...
  582. X        if (
  583. X            /^To:/ ||
  584. X            /^Cc:/ ||
  585. X            /^Apparently-To:/ ||
  586. X            /^Distribution:/ ||                # No mix-up, please
  587. X            /^X-Mailer:/ ||                    # Mailer identification
  588. X            /^Newsgroups:/ ||                # Reply from news reader
  589. X            /^Return-Receipt-To:/ ||        # Sendmail's acknowledgment
  590. X            /^Received:/ ||                    # We want to remove received
  591. X            /^Errors-To:/ ||                # Error report redirection
  592. X            /^Resent-[\w-]*:/                # Resent tags
  593. X        ) {
  594. X            $last_was_header = 1;            # Mark we discarded the line
  595. X            next;                            # Line is skipped
  596. X        }
  597. X        next if /^\s/ && $last_was_header;    # Skip removed header continuations
  598. X        $last_was_header = 0;                # We decided to keep header line
  599. X        print NEWS $_, "\n";
  600. X    }
  601. X    # If no subject is present, fake one to make inews happy
  602. X    unless (defined($Header{'Subject'}) && $Header{'Subject'} ne '') {
  603. X        &add_log("WARNING no subject, faking one") if $loglvl > 5;
  604. X        print NEWS "Subject: <none>\n";
  605. X    }
  606. X    # Any address included withing "" means addresses are stored in a file
  607. X    $newsgroups = &complete_list($newsgroups, 'newsgroup');
  608. X    $newsgroups =~ s/\s/,/g;    # Cannot have spaces between them
  609. X    $newsgroups =~ tr/,/,/s;    # Squash down consecutive ','
  610. X    print NEWS "Newsgroups: $newsgroups\n";
  611. X    print NEWS "Distribution: local\n" if $localdist;
  612. X    print NEWS $FILTER, "\n";    # Avoid loops: inews may forward to sendmail
  613. X    print NEWS "\n";
  614. X    print NEWS $Header{'Body'};
  615. X    close NEWS;
  616. X    local($failed) = $?;        # Status of forwarding
  617. X    if ($failed) {
  618. X        &add_log("ERROR could not post to $newsgroups") if $loglvl > 1;
  619. X    }
  620. X    $failed;        # 0 for success
  621. X}
  622. X
  623. X# The "APPLY" command
  624. Xsub apply {
  625. X    local($rulefile) = @_;
  626. X    # Prepare new environment for apply_rules
  627. X    local($ever_saved) = 0;
  628. X    local($ever_matched) = 0;
  629. X    # Now call apply_rules, with no statistics recorded, propagating the
  630. X    # current mode we are in and using an alternate rule fule.
  631. X    local($saved, $matched) =
  632. X        &rules'alternate($rulefile, 'apply_rules', $wmode, 0);
  633. X    if (!defined($saved)) {
  634. X        &add_log("ERROR could not apply rule file $rulefile") if $loglvl > 1;
  635. X        return (1, 0);    # Notify failure
  636. X    }
  637. X    # Since APPLY will fail when no save, warn the user
  638. X    if (!$matched) {
  639. X        &add_log("NOTICE no match in $rulefile") if $loglvl > 6;
  640. X    } else {
  641. X        &add_log("NOTICE no save in $rulefile") if !$saved && $loglvl > 6;
  642. X    }
  643. X    (0, $saved);        # Mail was correctly filtered, but was it saved?
  644. X}
  645. X
  646. X# The "SPLIT" command
  647. X# This routine is RFC-934 compliant and will correctly burst digests produced
  648. X# with this RFC in mind. For instance, MH produces RFC-934 style digest.
  649. X# However, in order to reliably split non RFC-934 digest, some extra work is
  650. X# performed to ensure a meaningful output.
  651. Xsub split {
  652. X    # Option parsing: a -i splits "inplace", i.e. acts as a saving if the split
  653. X    # is fully successful. A -d discards the leading part. A -q queues messsages
  654. X    # instead of filling them into a folder.
  655. X    $_[0] =~ s/^\s*-([adeiw]+)//;    # Remove options
  656. X    local($opt) = $1;
  657. X    local($inplace) = $opt =~ /i/;    # Inplace (original marked saved)
  658. X    local($discard) = $opt =~ /d/;    # Discard digest leading part
  659. X    local($empty) = $opt =~ /e/;    # Discard leading digest only if empty
  660. X    local($watch) = $opt =~ /w/;    # Watch digest closely
  661. X    local($annotate) = $opt =~ /a/;    # Annotate items with X-Digest-To: field
  662. X    $_[0] =~ s/^\s+//;                # Trim leading spaces
  663. X    local($folder) = $_[0];        # Folder to save messages
  664. X    local(@leading);            # Leading part of the digest
  665. X    local(@header);                # Looked ahead header
  666. X    local($found_header) = 0;    # True when header digest was found
  667. X    local($look_header) = 0;    # True when we are looking for a mail header
  668. X    local($found_end) = 0;        # True when end of digest found
  669. X    local($valid);                # Return value from header checking package
  670. X    local($failed) = 0;            # Queuing status for each mail item
  671. X    local(@body);                # Body of extracted mail
  672. X    local($item) = 0;            # Count digest items found
  673. X    local($not_rfc934) = 0;        # Is digest RFC-934 compliant?
  674. X    local($digest_to);            # Value of the X-Digest-To: field
  675. X    local($_);
  676. X    # If item annotation is requested, then each item will have a X-Digest-To:
  677. X    # field added, which lists both the To: and Cc: fields of the original
  678. X    # digest message.
  679. X    if ($annotate) {            # Annotation requested
  680. X        $digest_to = $Header{'Cc'};
  681. X        $digest_to = ', ' . $digest_to if $digest_to;
  682. X        $digest_to = 'X-Digest-To: ' . $Header{'To'} . $digest_to;
  683. X        $digest_to = &header'format($digest_to);
  684. X    }
  685. X    # Start digest parsing. According to RFC-934, we could only look for a
  686. X    # single '-' as encapsulation boundary, but for safety we look for at least
  687. X    # three consecutive ones.
  688. X    foreach (split(/\n/, $Header{'All'})) {
  689. X        push(@leading, $_) unless $found_header;
  690. X        push(@body, $_) if $found_header;
  691. X        if (/^---/) {            # Start looking for mail header
  692. X            $look_header = 1;    # Focus on mail headers now
  693. X            # We are withing the body of a digest and we've just reached
  694. X            # what may be the end of a message, or the end of the leading part.
  695. X            @header = ();        # Reset look ahead buffer
  696. X            &header'reset;        # Reset header checking package
  697. X            next;
  698. X        }
  699. X        next unless $look_header;
  700. X        # Record lines we find, but skip possible blank lines after dash.
  701. X        # Note that RFC-934 does not make spaces compulsory after each
  702. X        # encapsulation boundary (EB) but they are allowed nonetheless.
  703. X        next if /^\s*$/ && 0 == @header;
  704. X        $found_end = 0;            # Maybe it's not garbage after all...
  705. X        $valid = &header'valid($_);
  706. X        if ($valid == 0) {        # Not a valid header
  707. X            $look_header = 0;    # False alert
  708. X            $found_end = 1;        # Garbage after last EB is to be ignored
  709. X            if ($watch) {
  710. X                # Strict RFC-934: if an EB is followed by something which does
  711. X                # not prove to be a valid header but looked like one, enough
  712. X                # to have some lines collected into @header, then signal it.
  713. X                ++$not_rfc934 unless 0 == @header;
  714. X            } else {
  715. X                # Don't be too scrict. If what we have found so far *may be* a
  716. X                # header, then yes, it's not RFC-934. Otherwise let it go.
  717. X                ++$not_rfc934 if $header'maybe;
  718. X            }
  719. X            next;
  720. X        } elsif ($valid == 1) {    # Still in header
  721. X            push(@header, $_);    # Record header lines
  722. X            next;
  723. X        }
  724. X        # Coming here means we reached the end of a valid header
  725. X        push(@header, $digest_to) if $annotate;
  726. X        push(@header, '');        # Blank header line
  727. X        if (!$found_header) {
  728. X            if ($empty) {
  729. X                $failed |= &save_mail(*leading, $folder)
  730. X                    unless &empty_body(*leading) || $discard;
  731. X            } else {
  732. X                $failed |= &save_mail(*leading, $folder) unless $discard;
  733. X            }
  734. X            undef @leading;        # Not needed any longer
  735. X            $item++;            # So that 'save_mail' starts logging items
  736. X        }
  737. X        # If there was already a mail being collected, save it now, because
  738. X        # we are sure it is followed by a valid mail.
  739. X        $failed |= &save_mail(*body, $folder) if $found_header;
  740. X        $found_header = 1;        # End of header -> this is truly a digest
  741. X        $look_header = 0;        # We found our header
  742. X        &header'clean(*header);    # Ensure minimal set of header
  743. X        @body = @header;        # Copy headers in mail body for next message
  744. X    }
  745. X
  746. X    return -1 unless $found_header;    # Message was not in digest format
  747. X
  748. X    # Save last message, making sure to add a final dash line if digest did
  749. X    # not have one: There was one if $look_header is true. There was also
  750. X    # one if $found_end is true.
  751. X    push(@body, '---') unless $look_header || $found_end;
  752. X
  753. X    # If the -w option was used, we look closely at the supposed trailing
  754. X    # garbage. If the length is greater than 100 characters, then maybe we
  755. X    # are missing something here...
  756. X    if ($watch) {
  757. X        local($idx) = $#body;
  758. X        $_ = $body[$idx];            # Get last line
  759. X        @header = ();                # Reset "garbage collector"
  760. X        unless (/^---/) {            # Do not go on if end of digest truly found
  761. X            for (; $idx >= 0; $idx--) {
  762. X                $_ = $body[$idx];
  763. X                last if /^---/;        # Reached end of presumed trailing garbage
  764. X                unshift(@header, $_);
  765. X            }
  766. X        }
  767. X    }
  768. X
  769. X    # Now save last message
  770. X    $failed |= &save_mail(*body, $folder);
  771. X
  772. X    # If we collected something into @header and if it is big enough, save it
  773. X    # as a trailing message.
  774. X    if ($watch && length(join('', @header)) > 100) {
  775. X        &add_log("NOTICE [$mfile] has trailing garbage...") if $loglvl > 6;
  776. X        @body = @header;            # Copy saved garbage
  777. X        @header = ();                # Now build final garbage headers
  778. X        $header[0] = 'Subject: ' . $Header{'Subject'} . ' (trailing garbage)';
  779. X        $header[1] = $digest_to if $annotate;
  780. X        &header'clean(*header);        # Build other headers
  781. X        unshift(@body, '') unless $body[0] =~ s/^\s*$//;    # Ensure EOH
  782. X        foreach (@body) {
  783. X            push(@header, $_);
  784. X        }
  785. X        push(@header, '---');
  786. X        $failed |= &save_mail(*header, $folder);
  787. X    }
  788. X
  789. X    $failed + 0x2 * $inplace + 0x4 * ($folder =~ /^\s*$/)
  790. X        + 0x8 * ($not_rfc934 > 0);
  791. X}
  792. X
  793. X# The "RUN" command and its friends
  794. X# Start a shell command and mail any output back to the user. The program is
  795. X# invoked from within the home directory.
  796. Xsub shell_command {
  797. X    local($program, $input, $feedback) = @_;
  798. X    unless (chdir $cf'home) {
  799. X        &add_log("WARNING cannot chdir to $cf'home: $!") if $loglvl > 5;
  800. X    }
  801. X    $program =~ s/^\s*~/$cf'home/;    # ~ substitution
  802. X    $program =~ s/\b~/$cf'home/g;    # ~ substitution as first letter in word
  803. X    $SIG{'PIPE'} = 'popen_failed';    # Protect against naughty program
  804. X    $SIG{'ALRM'} = 'alarm_clock';    # Protect against loops
  805. X    alarm 3600;                        # At most one hour of processing
  806. X    eval '&execute_command($program, $input, $feedback)';
  807. X    alarm 0;                        # Disable alarm timeout
  808. X    $SIG{'PIPE'} = 'emergency';        # Restore initial value
  809. X    $SIG{'ALRM'} = 'DEFAULT';        # Restore default behaviour
  810. X    if ($@ =~ /^failed/) {            # Something went wrong?
  811. X        &add_log("ERROR couldn't run '$program'") if $loglvl > 0;
  812. X        return 1;                    # Failed
  813. X    } elsif ($@ =~ /^aborted/) {    # Writing to program failed
  814. X        &add_log("WARNING pipe closed by '$program'") if $loglvl > 5;
  815. X        return 1;                    # Failed
  816. X    } elsif ($@ =~ /^feedback/) {    # Feedback failed
  817. X        &add_log("WARNING no feedback occurred") if $loglvl > 5;
  818. X        return 1;                    # Failed
  819. X    } elsif ($@ =~ /^alarm/) {        # Timeout
  820. X        &add_log("WARNING time out received") if $loglvl > 5;
  821. X        return 1;                    # Failed
  822. X    } elsif ($@ =~ /^non-zero/) {    # Program returned non-zero status
  823. X        &add_log("WARNING program returned non-zero status") if $loglvl > 5;
  824. X        return 1;
  825. X    } elsif ($@) {
  826. X        &add_log("ERROR $@") if $loglvl > 0;
  827. X        return 1;                    # Failed
  828. X    }
  829. X    0;            # Everything went fine
  830. X}
  831. X
  832. X# Abort execution of command when popen() fails or program dies abruptly
  833. Xsub popen_failed {
  834. X    unlink "$trace" if -f "$trace";
  835. X    die "$error\n";
  836. X}
  837. X
  838. X# When an alarm call is received, we should be in the 'execute_command'
  839. X# routine. The $pid variable holds the pid number of the process to be killed.
  840. Xsub alarm_clock {
  841. X    if ($trace ne '' && -f "$trace") {        # We come from execute_command
  842. X        local($status) = "terminated";        # Process was terminated
  843. X        if (kill "SIGTERM", $pid) {            # We could signal our child
  844. X            sleep 30;                        # Give child time to die
  845. X            unless (kill "SIGTERM", $pid) {    # Child did not die yet ?
  846. X                unless (kill "SIGKILL", $pid) {
  847. X                    &add_log("ERROR could not kill process $pid: $!")
  848. X                        if $loglvl > 1;
  849. X                } else {
  850. X                    $status = "killed";
  851. X                    &add_log("KILLED process $pid") if $loglvl > 4;
  852. X                }
  853. X            } else {
  854. X                &add_log("TERMINATED process $pid") if $loglvl > 4;
  855. X            }
  856. X        } else {
  857. X            $status = "unknwon";            # Process died ?
  858. X            &add_log("ERROR coud not signal process $pid: $!")
  859. X                if $loglvl > 1;
  860. X        }
  861. X        &mail_back;                    # Mail back any output we have so far
  862. X        unlink "$trace";            # Remove output of command
  863. X    }
  864. X    die "alarm call\n";                # Longjmp to shell_command
  865. X}
  866. X
  867. X# Execute the command, ran in an eval to protect against SIGPIPE signals
  868. Xsub execute_command {
  869. X    local($program, $input, $feedback) = @_;
  870. X    local($trace) = "$cf'tmpdir/trace.run$$";    # Where output goes
  871. X    local($error) = "failed";                # Error reported by popen_failed
  872. X    pipe(READ, WRITE);                        # Open a pipe
  873. X    local($ppid) = $$;                        # Pid of parent process
  874. X    local($pid) = fork;                        # We fork here
  875. X    $pid = -1 unless defined $pid;
  876. X    if ($pid == 0) {                        # Child process
  877. X        alarm 0;
  878. X        close WRITE;                        # The child reads from pipe
  879. X        open(STDIN, "<&READ");                # Redirect stdin to pipe
  880. X        close READ if $input == $NO_INPUT;    # Close stdin if needed
  881. X        unless (open(STDOUT, ">$trace")) {    # Where output goes
  882. X            &add_log("WARNING couldn't create $trace") if $loglvl > 5;
  883. X            if ($feedback == $FEEDBACK) {    # Need trace if feedback
  884. X                kill 'SIGPIPE', $ppid;        # Parent still waiting
  885. X                exit 1;
  886. X            }
  887. X        }
  888. X        open(STDERR, ">&STDOUT");            # Make it follow pipe
  889. X        exec "$program";                    # Run the program now
  890. X        &add_log("ERROR couldn't exec '$program': $!") if $loglvl > 1;
  891. X        kill 'SIGPIPE', $ppid;                # Parent still waiting
  892. X        exit 1;
  893. X    } elsif ($pid == -1) {
  894. X        &add_log("ERROR couldn't fork: $!") if $loglvl;
  895. X        return;
  896. X    }
  897. X    close READ;                                # The parent writes to its child
  898. X    # In case 'sleep' is inplemented using an alarm call, take precautions...
  899. X    local($remaining) = alarm 0;            # Stop alarm, save remaining time
  900. X    sleep 2;                                # Let the child initialize
  901. X    alarm $remaining;                        # Restore alarm clock
  902. X    $error = "aborted";                        # Error reported by popen_failed
  903. X    select(WRITE);
  904. X    $| = 1;                                    # Hot pipe wanted
  905. X    select(STDOUT);
  906. X    # Now feed the program with the mail
  907. X    if ($input == $BODY_INPUT) {            # Pipes body
  908. X        print WRITE $Header{'Body'};
  909. X    } elsif ($input == $MAIL_INPUT) {        # Pipes the whole mail
  910. X        print WRITE $Header{'All'};
  911. X    } elsif ($input == $HEADER_INPUT) {        # Pipes the header
  912. X        print WRITE $Header{'Head'};
  913. X    }
  914. X    close WRITE;                            # Close input, before waiting!
  915. X    wait();                                    # Wait for our child
  916. X    local($status) = $? ? "failed" : "ok";
  917. X    if ($?) {
  918. X        # Log execution failure and return to shell_command via die if some
  919. X        # feedback was to be done.
  920. X        &add_log("ERROR execution failed for '$program'") if $loglvl > 1;
  921. X        if ($feedback == $FEEDBACK) {        # We wanted feedback
  922. X            &mail_back;                        # Mail back any output
  923. X            unlink "$trace";                # Remove output of command
  924. X            die "feedback\n";                # Longjmp to shell_command
  925. X        }
  926. X    }
  927. X    &handle_output;            # Take appropriate action with command output
  928. X    unlink "$trace";        # Remove output of command
  929. X    die "non-zero status\n" unless $status eq 'ok';
  930. X}
  931. X
  932. X# If no feedback is wanted, simply mail the output of the commands to the
  933. X# user. However, in case of feedback, we have to update the values of
  934. X# %Header in the entries 'All', 'Body' and 'Head'. Note that the other
  935. X# header fields are left untouched. Only a RESYNC can synchronize them
  936. X# (this makes sense only for a FEED command, of course).
  937. X# Uses $feedback from execute_command
  938. Xsub handle_output {
  939. X    if ($feedback == $NO_FEEDBACK) {
  940. X        &mail_back;                        # Mail back any output
  941. X    } elsif ($feedback == $FEEDBACK) {
  942. X        &feed_back;                        # Feed result back into %Header
  943. X    }
  944. X}
  945. X
  946. X# Mail back the contents of the trace file (output of program), if not empty.
  947. X# Uses some local variables from execute_command
  948. Xsub mail_back {
  949. X    local($size) = -s "$trace";                # Size of output
  950. X    return unless $size;                    # Nothing to be done if no output
  951. X    local($std_input);                        # Standard input used
  952. X    $std_input = "none" if $input == $NO_INPUT;
  953. X    $std_input = "mail body" if $input == $BODY_INPUT;
  954. X    $std_input = "whole mail" if $input == $MAIL_INPUT;
  955. X    $std_input = "header" if $input == $HEADER_INPUT;
  956. X    local($program_name) = $program =~ m|^(\S+)|;
  957. X    open(MAILER,"|$cf'sendmail $cf'mailopt $cf'user");
  958. X    print MAILER <<EOM;
  959. XTo: $cf'user
  960. XSubject: Output of your '$program_name' command ($status)
  961. X$MAILER
  962. X
  963. XYour command was: $program
  964. XInput: $std_input
  965. XStatus: $status
  966. X
  967. XIt produced the following output:
  968. X
  969. XEOM
  970. X    unless (open(TRACE, "$trace")) {
  971. X        &add_log("ERROR couldn't reopen $trace") if $loglvl > 1;
  972. X        print MAILER "*** SORRY -- NOT AVAILABLE ***\n";
  973. X    } else {
  974. X        while (<TRACE>) {
  975. X            print MAILER;
  976. X        }
  977. X        close TRACE;
  978. X    }
  979. X    close MAILER;
  980. X    unless ($?) {
  981. X        &add_log("SENT output of '$program_name' to $cf'user ($size bytes)")
  982. X            if $loglvl > 2;
  983. X    } else {
  984. X        &add_log("ERROR couldn't send $size bytes to $cf'user") if $loglvl;
  985. X    }
  986. X}
  987. X
  988. X# Feed back output of a command in the %Header data structure.
  989. X# Uses some local variables from execute_command
  990. Xsub feed_back {
  991. X    unless (open(TRACE, "$trace")) {
  992. X        &add_log("ERROR couldn't feed back from $trace: $!") if $loglvl > 1;
  993. X        unlink "$trace";                # Maybe I should leave it around
  994. X        die "feedback\n";                # Return to shell_command
  995. X    }
  996. X    local($temp) = ' ' x 2000;            # Temporary storage (pre-extended)
  997. X    $temp = '';
  998. X    if ($input == $BODY_INPUT) {        # We have to feed back the body only
  999. X        while (<TRACE>) {
  1000. X            s/^From\s/>From$1/;            # Protect potentially dangerous lines
  1001. X            $temp .= $_;
  1002. X        }
  1003. X    } else {
  1004. X        local($head) = ' ' x 500;        # Pre-extend header
  1005. X        $head = '';
  1006. X        while (<TRACE>) {
  1007. X            if (1../^$/) {
  1008. X                $head .= $_ unless /^$/;
  1009. X            } else {
  1010. X                s/^From\s/>From$1/;        # Protect potentially dangerous lines
  1011. X                $temp .= $_;
  1012. X            }
  1013. X        }
  1014. X        $Header{'Head'} = $head;
  1015. X    }
  1016. X    close TRACE;
  1017. X    $Header{'Body'} = $temp unless $input == $HEADER_INPUT;
  1018. X    $Header{'All'} = $Header{'Head'} . "\n" . $Header{'Body'};
  1019. X}
  1020. X
  1021. X# Feed output back into $Back variable (used by BACK command). Typically, the
  1022. X# BACK command is used with RUN, though any other command is allowed (but does
  1023. X# not always make sense).
  1024. X# NB: This routine:
  1025. X#  - Is never called explicitely but via a type glob through *handle_output
  1026. X#  - Uses some local variables from execute_command
  1027. Xsub xeq_back {
  1028. X    unless (open(TRACE, "$trace")) {
  1029. X        &add_log("ERROR couldn't feed back from $trace: $!") if $loglvl > 1;
  1030. X        unlink "$trace";                # Maybe I should leave it around
  1031. X        die "feedback\n";                # Return to shell_command
  1032. X    }
  1033. X    while (<TRACE>) {
  1034. X        chop;
  1035. X        next if /^\s*$/;
  1036. X        $Back .= $_ . '; ';                # Replace \n by ';' separator
  1037. X    }
  1038. X    close TRACE;
  1039. X}
  1040. X
  1041. X# The "RESYNC" command
  1042. X# Resynchronizes the %Header entries by reparsing the 'All' entry
  1043. Xsub header_resync {
  1044. X    # Clean up all the non-special entries
  1045. X    foreach $key (keys %Header) {
  1046. X        next if $Pseudokey{$key};        # Skip pseudo-header entries
  1047. X        delete $Header{$key};
  1048. X    }
  1049. X    # There is some code duplication with parse_mail()
  1050. X    local($lines) = 0;
  1051. X    local($first_from);                        # First From line records sender
  1052. X    local($last_header);                    # Current normalized header field
  1053. X    local($in_header) = 1;                    # Bug in the range operator
  1054. X    local($value);                            # Value of current field
  1055. X    foreach (split(/\n/, $Header{'All'})) {
  1056. X        if ($in_header) {                    # Still in header of message
  1057. X            $in_header = 0 if /^$/;            # End of header
  1058. X            if (/^\s/) {                    # It is a continuation line
  1059. X                s/^\s+/ /;                    # Swallow multiple spaces
  1060. X                $Header{$last_header} .= "\n$_" if $last_header ne '';
  1061. X            } elsif (/^([\w-]+):\s*(.*)/) {    # We found a new header
  1062. X                $value = $2;                # Bug in perl 4.0 PL19
  1063. X                $last_header = &header'normalize($1);
  1064. X                # Multiple headers like 'Received' are separated by a new-
  1065. X                # line character. All headers end on a non new-line.
  1066. X                if ($Header{$last_header} ne '') {
  1067. X                    $Header{$last_header} .= "\n$value";
  1068. X                } else {
  1069. X                    $Header{$last_header} .= $value;
  1070. X                }
  1071. X            } elsif (/^From\s+(\S+)/) {        # The very first From line
  1072. X                $first_from = $1;
  1073. X            }
  1074. X        } else {
  1075. X            $lines++;                        # One more line in body
  1076. X        }
  1077. X    }
  1078. X    &header_check($first_from, $lines);    # Sanity checks
  1079. X}
  1080. X
  1081. X# The "STRIP" and "KEEP" commands (case insensitive)
  1082. X# Removes or keeps some headers and update the Header structure
  1083. Xsub alter_header {
  1084. X    local($headers, $action) = @_;
  1085. X    $headers =
  1086. X        &complete_list($headers, 'header');    # Process "file-inclusion"
  1087. X    local(@list) = split(/\s/, $headers);
  1088. X    local(@head) = split(/\n/, $Header{'Head'});
  1089. X    local(@newhead);                # The constructed header
  1090. X    local($last_was_altered) = 0;    # Set to true when header is altered
  1091. X    local($matched);                # Did any header matched ?
  1092. X    local($line);                    # Original header line
  1093. X
  1094. X    foreach $h (@list) {            # Prepare patterns
  1095. X        $h =~ s/:$//;                # Remove trailing ':' if any
  1096. X        $h = &perl_pattern($h);        # Headers specified by shell patterns
  1097. X    }
  1098. X
  1099. X    foreach (@head) {
  1100. X        if (/^From\s/) {            # First From line...
  1101. X            push(@newhead, $_);        # Keep it anyway
  1102. X            next;
  1103. X        }
  1104. X        $line = $_;                    # Save original
  1105. X        # Make sure header field name is normalized before attempting a match
  1106. X        s/^([\w-]+):/&header'normalize($1).':'/e;
  1107. X        unless (/^\s/) {            # If not a continuation line
  1108. X            $last_was_altered = 0;    # Reset header alteration flag
  1109. X            $matched = 0;            # Assume no match
  1110. X            foreach $h (@list) {    # Loop over to-be-altered lines
  1111. X                if (/^$h:/i) {        # We found a line to be removed/kept
  1112. X                    $matched = 1;
  1113. X                    last;
  1114. X                }
  1115. X            }
  1116. X            $last_was_altered = $matched;
  1117. X            next if $matched && $action == $HD_SKIP;
  1118. X            next if !$matched && $action == $HD_KEEP;
  1119. X        }
  1120. X        if ($action == $HD_SKIP) {
  1121. X            next if /^\s/ && $last_was_altered;        # Skip header continuations
  1122. X        } else {                                    # Action is $HD_KEEP
  1123. X            next if /^\s/ && !$last_was_altered;    # Header was not kept
  1124. X        }
  1125. X        push(@newhead, $line);        # Add line to the new header
  1126. X    }
  1127. X    $Header{'Head'} = join("\n", @newhead) . "\n";
  1128. X    $Header{'All'} = $Header{'Head'} . "\n" . $Header{'Body'};
  1129. X}
  1130. X
  1131. X# The "ANNOTATE" command
  1132. Xsub annotate_header {
  1133. X    local($field, $value, $date) = @_;    # Field, value and date flag.
  1134. X    if ($value eq '' && $date ne '') {    # No date and no value for field!
  1135. X        &add_log("WARNING no value for '$field' annotation") if $loglvl > 5;
  1136. X        return 1;
  1137. X    }
  1138. X    if ($field eq '') {                # No field specified!
  1139. X        &add_log("WARNING no field specified for annotation") if $loglvl > 5;
  1140. X        return 1;
  1141. X    }
  1142. X    local($annotation) = '';        # Annotation made
  1143. X    $annotation = "$field: " . &header'fake_date . "\n" unless $date;
  1144. X    $annotation .= &header'format("$field: $value") . "\n" if $value;
  1145. X    $Header{'Head'} .= $annotation;
  1146. X    $Header{'All'} = $Header{'Head'} . "\n" . $Header{'Body'};
  1147. X    0;
  1148. X}
  1149. X
  1150. X# The "TR" and "SUBST" commands
  1151. Xsub alter_value {
  1152. X    local($variable, $op) = @_;    # Variable and operation to performed
  1153. X    local($lvalue);                # Perl variable to be modified
  1154. X    local($extern);                # Lvalue used for persistent variables
  1155. X
  1156. X    # We may modify a variable or a backreference (not read-only as in perl)
  1157. X    if ($variable =~ s/^#://) {
  1158. X        $extern = &extern'val($variable);    # Fetch external value
  1159. X        $lvalue = '$extern';                # Modify this variable
  1160. X    } elsif ($variable =~ s/^#//) {
  1161. X        $lvalue = '$Variable{\''.$variable.'\'}';
  1162. X    } elsif ($variable =~ /^\d\d?$/) {
  1163. X        $variable = int($variable) - 1;
  1164. X        $lvalue = '$Backref[' . $variable . ']';
  1165. X    } else {
  1166. X        &add_log("ERROR incorrect variable name '$variable'") if $loglvl > 1;
  1167. X        return 1;
  1168. X    }
  1169. X
  1170. X    # Let perl do the work
  1171. X    &add_log("running $lvalue =~ $op") if $loglvl > 19;
  1172. X    eval $lvalue . " =~ $op";
  1173. X    &add_log("ERROR operation $op failed: $@") if $@ && $loglvl > 1;
  1174. X
  1175. X    # If an external (persistent) variable was used, update its value now,
  1176. X    # unless the operation failed, in which case the value is not modified.
  1177. X    &extern'set($variable, $extern) if $@ eq '' && $lvalue eq '$extern';
  1178. X
  1179. X    $@ eq '' ? 0 : 1;            # Failure status
  1180. X}
  1181. X
  1182. X# The "PERL" command
  1183. Xsub perl {
  1184. X    local($script) = @_;    # Location of perl script
  1185. X    local($failed) = '';    # Assume script did not fail
  1186. X    undef @_;                # No visible args for functions in script
  1187. X
  1188. X    unless (chdir $cf'home) {
  1189. X        &add_log("WARNING cannot chdir to $cf'home: $!") if $loglvl > 5;
  1190. X    }
  1191. X
  1192. X    # Set up the @ARGV array, by parsing the $script variable with &shellwords.
  1193. X    # Note that the @ARGV array is held in the main package, but since the
  1194. X    # mailagent makes no use of it at this point, there is no need to save its
  1195. X    # value before clobbering it.
  1196. X    require 'shellwords.pl';
  1197. X    eval '@ARGV = &shellwords($script)';
  1198. X    if (chop($@)) {                # There was an unmatched quote
  1199. X        $@ =~ s/^U/u/;
  1200. X        &add_log("ERROR $@") if $loglvl > 1;
  1201. X        &add_log("ERROR cannot run PERL $script") if $loglvl > 2;
  1202. X        return 1;
  1203. X    }
  1204. X
  1205. X    unless (open(PERL, $ARGV[0])) {
  1206. X        &add_log("ERROR cannot open perl script $ARGV[0]: $!") if $loglvl > 1;
  1207. X        return 1;
  1208. X    }
  1209. X
  1210. X    # Fetch the perl script in memory, within a block to really localize $/
  1211. X    local($body) = ' ' x (-s PERL);
  1212. X    {
  1213. X        local($/) = undef;
  1214. X        $body = <PERL>;        # Slurp whole file into pre-extended variable
  1215. X    }
  1216. X    close(PERL);
  1217. X    local(@saved) = @INC;    # Save INC array (perl library location path)
  1218. X    local(%saved) = %INC;    # Save already required files
  1219. X
  1220. X    # Run the perl script in special package
  1221. X    unshift(@INC, $privlib);    # Files first searched for in mailagent's lib
  1222. X    package mailhook;            # -- entering in mailhook --
  1223. X    &interface'new;                # Signal new script being loaded
  1224. X    &hook'initvar('mailhook');    # Initialize convenience variables
  1225. X    eval $'body;                # Load, compile and execute within mailhook
  1226. X    &interface'reset;            # Clear the mailhook package if no more pending
  1227. X    package main;                # -- reverting to main --
  1228. X    @INC = @saved;                # Restore INC array
  1229. X    %INC = %saved;                # In case script has required some other files
  1230. X
  1231. X    # If the script died with an 'OK' error message, then it meant 'exit 0'
  1232. X    # but also wanted the exit to be trapped. The &exit function is provided
  1233. X    # for that purpose.
  1234. X    if (chop($@)) {
  1235. X        if ($@ =~ /^OK/) {
  1236. X            $@ = '';
  1237. X            &add_log("script exited with status 0") if $loglvl > 18;
  1238. X        }
  1239. X        elsif ($@ =~ /^Exit (\d+)/) {
  1240. X            $@ = '';
  1241. X            $failed = "exited with status $1";
  1242. X        }
  1243. X        elsif ($@ =~ /^Status (\d+)/) {        # A REJECT, RESTART or ABORT
  1244. X            $@ = '';
  1245. X            $cont = $1;                        # This will modify control flow
  1246. X            &add_log("script ended with a control '$cont'") if $loglvl > 18;
  1247. X        }
  1248. X        else {
  1249. X            $@ =~ s/ in file \(eval\)//;
  1250. X            &add_log("ERROR $@") if $loglvl;
  1251. X            $failed = "execution aborted";
  1252. X        }
  1253. X        &add_log("ERROR perl failed ($failed)") if $loglvl > 1 && $failed;
  1254. X    }
  1255. X    $failed ? 1 : 0;
  1256. X}
  1257. X
  1258. X# The "REQUIRE" command
  1259. Xsub require {
  1260. X    local($file, $package) = @_;    # File to load, package to put it in
  1261. X    $package = 'newcmd' if $package eq '';    # Use newcmd if no package
  1262. X    $file =~ s/^\s*~/$cf'home/;        # ~ substitution
  1263. X    # Note that the dynload package records files being loaded into a H table,
  1264. X    # and "requiring" two times the same file in the *same* package will be
  1265. X    # a no-op, returning the same status as the first time.
  1266. X    local($ok) = &dynload'load($package, $file);
  1267. X    $file = &tilda($file);            # Replace home directory with a nice ~
  1268. X    unless (defined $ok) {
  1269. X        &add_log("ERROR cannot load $file in package $package");
  1270. X        return 1;        # Require failed
  1271. X    }
  1272. X    unless ($ok) {
  1273. X        &add_log("ERROR cannot parse $file into package $package");
  1274. X        return 1;        # Require failed
  1275. X    }
  1276. X    0;        # Success
  1277. X}
  1278. X
  1279. X
  1280. X# Modify control flow within automaton by calling a non-existant function
  1281. X# &perform, which has been dynamically bound to one of the do_* functions.
  1282. X# The REJECT, RESTART and ABORT actions share the following options and
  1283. X# arguments. If followed by -t (resp. -f), then the action only takes place
  1284. X# when the last recorded command status is true (resp. false, i.e. failure).
  1285. X# If a mode is present as an argument, the the state of the automaton is
  1286. X# changed to that mode prior alteration of the control flow.
  1287. Xsub alter_flow {
  1288. X    $_[0] =~ s/^\s*\w+//;            # Remove command name
  1289. X    $_[0] =~ s/^\s*-([tf]+)//;        # Remove options
  1290. X    local($opt) = $1;
  1291. X    local($true) = $opt =~ /t/;        # Perform only if $lastcmd is 0
  1292. X    local($false) = $opt =~ /f/;    # Perform only if $lastcmd recorded failure
  1293. X    $_[0] =~ s/^\s+//;                # Trim leading spaces
  1294. X    local($mode) = $_[0];            # New mode we eventually change to
  1295. X    # Variable $lastcmd comes from xeqte(), $wmode comes from analyze_mail().
  1296. X    return 0 if $true && $lastcmd != 0;
  1297. X    return 0 if $false && $lastcmd == 0;
  1298. X    if ($mode ne '') {
  1299. X        $wmode = $mode;
  1300. X        &add_log("entering new state $wmode") if $loglvl > 6;
  1301. X    }
  1302. X    &perform;                        # This was dynamically bound
  1303. X}
  1304. X
  1305. X# Perform a "REJECT"
  1306. Xsub do_reject {
  1307. X    $cont = $FT_REJECT;            # Reject ($cont defined in run_command)
  1308. X    &add_log("REJECTED [$mfile] in state $wmode") if $loglvl > 4;
  1309. X    0;
  1310. X}
  1311. X
  1312. X# Perform a "RESTART"
  1313. Xsub do_restart {
  1314. X    $cont = $FT_RESTART;        # Restart ($cont defined in run_command)
  1315. X    &add_log("RESTARTED [$mfile] in state $wmode") if $loglvl > 4;
  1316. X    0;
  1317. X}
  1318. X
  1319. X# Perform an "ABORT"
  1320. Xsub do_abort {
  1321. X    $cont = $FT_ABORT;            # Abort filtering ($cont defined in run_command)
  1322. X    &add_log("ABORTED [$mfile] in state $wmode") if $loglvl > 4;
  1323. X    0;
  1324. X}
  1325. X
  1326. X# Given a list of items separated by white spaces, return a new list of
  1327. X# items, but with "include-request" processed.
  1328. Xsub complete_list {
  1329. X    local(@addr) = split(' ', $_[0]);    # Original list
  1330. X    local($type) = $_[1];                # Type of item (header, address, ...)
  1331. X    local(@result);                        # Where result list is built
  1332. X    local($filename);                    # Name of include file
  1333. X    local($_);
  1334. X    foreach $addr (@addr) {
  1335. X        if ($addr !~ /^"/) {            # Item not enclosed within ""
  1336. X            push(@result, $addr);        # Kept as-is
  1337. X        } else {
  1338. X            # Load items from file whose name is given between "quotes"
  1339. X            push(@result, &include_file($addr, $type));
  1340. X        }
  1341. X    }
  1342. X    join(' ', @result);        # Return space separated items
  1343. X}
  1344. X
  1345. X# Save digest mail into a folder, or queue it if no folder is provided
  1346. X# Uses the variable '$item' from 'split' to log items.
  1347. Xsub save_mail {
  1348. X    local(*array, $folder) = @_;    # Where mail is and where to put it
  1349. X    local($length) = 0;                # Length of the digest item
  1350. X    local($mbox, $failed, $log_message);
  1351. X    local($_);
  1352. X    # Go back to the previous dash line, removing it from the body part
  1353. X    # (it's only a separator). In the process, we also remove any looked ahead
  1354. X    # header which belongs to the next digest item.
  1355. X    do {
  1356. X        $_ = pop(@array);            # Remove what belongs to next digest item
  1357. X    } while !/^---/;
  1358. X    # It is recommended in RFC-934 that all leading EB be escaped by a leading
  1359. X    # '- ' sequence, to allow nested forwarding. However, since the message
  1360. X    # we are dealing with might not be RFC-934 compliant, we are only removing
  1361. X    # the leading '- ' if it is followed by a '-'. We also use the loop to
  1362. X    # escape all potentially dangerous From lines.
  1363. X    local($last_was_space);
  1364. X    foreach (@array) {
  1365. X        s/^From\s+(\S+)/>From $1/ if $last_was_space;
  1366. X        s/^- -/-/;                    # This is the EB escape in RFC-934
  1367. X        $last_was_space = /^$/;        # From is dangerous after blank line
  1368. X    }
  1369. X    # Now @array holds the whole digest item
  1370. X    if ($folder =~ /^\s*$/) {        # No folder means we have to queue message
  1371. X        $failed = &qmail(*array);
  1372. X        $log_message = 'mailagent\'s queue';
  1373. X        foreach (@array) {
  1374. X            $length += length($_) + 1;    # No trailing new-lines
  1375. X        }
  1376. X    } else {
  1377. X        # Looks like we have to save the message in a folder. I cannot really
  1378. X        # ask for a local variable named %Header because emergency routines
  1379. X        # use it to save mail (they expect the whole mail in $Header{'All'}).
  1380. X        # However, if something goes wrong, we'll get back to the filter main
  1381. X        # loop and a LEAVE (default action) will be executed, taking the
  1382. X        # current values from 'Head' and 'Body'. Hence the following:
  1383. X
  1384. X        local(%NHeader);
  1385. X        $NHeader{'All'} = $Header{'All'};
  1386. X        local(*Header) = *NHeader;    # From now on, we really work on %NHeader
  1387. X        local($in_header) = 1;        # True while in message header
  1388. X        local($first_from);            # First From line
  1389. X
  1390. X        # Fill in %Header strcuture, which is expected by save(): header in
  1391. X        # entry 'Head' and body in entry 'Body'.
  1392. X        foreach (@array) {
  1393. X            if ($in_header) {
  1394. X                $in_header = 0 if /^$/;
  1395. X                next if /^$/;
  1396. X                $Header{'Head'} .= $_ . "\n";
  1397. X                $first_from = $_ if /^From\s+\S+/;
  1398. X                next;
  1399. X            }
  1400. X            $Header{'Body'} .= $_ . "\n";
  1401. X        }
  1402. X        $Header{'Head'} = "$FAKE_FROM\n" .  $Header{'Head'} unless $first_from;
  1403. X
  1404. X        # Now save into folder
  1405. X        ($mbox, $failed, $log_message) = &run_saving($folder, $FOLDER_APPEND);
  1406. X
  1407. X        # Keep track in the logfile of the length of the digest item.
  1408. X        $length = length($Header{'Head'}) + length($Header{'Body'}) + 1;
  1409. X    }
  1410. X    if ($failed) {
  1411. X        if ($loglvl > 2) {
  1412. X            local($s) = $length == 1 ? '' : 's';
  1413. X            &add_log("ERROR unable to save #$item ($length byte$s)") if $item;
  1414. X            &add_log("ERROR unable to save preamble ($length byte$s)")
  1415. X                unless $item;
  1416. X        }
  1417. X    } else {
  1418. X        if ($loglvl > 7) {
  1419. X            local($s) = $length == 1 ? '' : 's';
  1420. X            &add_log("SPLIT #$item in $log_message ($length byte$s)") if $item;
  1421. X            &add_log("SPLIT preamble in $log_message ($length byte$s)")
  1422. X                unless $item;
  1423. X        }
  1424. X    }
  1425. X    ++$item if $item;        # Count items, but not preamble (done by 'split')
  1426. X    $failed;                # Propagate failure status
  1427. X}
  1428. X
  1429. END_OF_FILE
  1430.   if test 49987 -ne `wc -c <'agent/pl/actions.pl.01'`; then
  1431.     echo shar: \"'agent/pl/actions.pl.01'\" unpacked with wrong size!
  1432.   fi
  1433.   # end of 'agent/pl/actions.pl.01'
  1434. fi
  1435. if test -f 'agent/test/filter/list.t' -a "${1}" != "-c" ; then 
  1436.   echo shar: Will not clobber existing file \"'agent/test/filter/list.t'\"
  1437. else
  1438.   echo shar: Extracting \"'agent/test/filter/list.t'\" \(2014 characters\)
  1439.   sed "s/^X//" >'agent/test/filter/list.t' <<'END_OF_FILE'
  1440. X# This tests mathching on list selectors like To or Newsgroups.
  1441. X
  1442. X# $Id: list.t,v 3.0 1993/11/29 13:50:01 ram Exp ram $
  1443. X#
  1444. X#  Copyright (c) 1990-1993, Raphael Manfredi
  1445. X#  
  1446. X#  You may redistribute only under the terms of the Artistic License,
  1447. X#  as specified in the README file that comes with the distribution.
  1448. X#  You may reuse parts of this distribution only within the terms of
  1449. X#  that same Artistic License; a copy of which may be found at the root
  1450. X#  of the source tree for mailagent 3.0.
  1451. X#
  1452. X# $Log: list.t,v $
  1453. X# Revision 3.0  1993/11/29  13:50:01  ram
  1454. X# Baseline for mailagent 3.0 netwide release.
  1455. X#
  1456. X
  1457. Xdo '../pl/filter.pl';
  1458. X
  1459. Xfor ($i = 1; $i <= 8; $i++) {
  1460. X    unlink "$user.$i";
  1461. X}
  1462. X
  1463. X&add_header('X-Tag: list');
  1464. X`$cmd`;
  1465. X$? == 0 || print "1\n";
  1466. X-f "$user.1" || print "2\n";
  1467. Xunlink "$user.1";
  1468. X
  1469. X&replace_header('To: uunet!eiffel.com!max, other@max.com');
  1470. X`$cmd`;
  1471. X$? == 0 || print "3\n";
  1472. X-f "$user.2" || print "4\n";
  1473. Xunlink "$user.2";
  1474. X
  1475. X&replace_header('To: root@eiffel.com (Super User), max <other@max.com>');
  1476. X`$cmd`;
  1477. X$? == 0 || print "5\n";
  1478. X-f "$user.3" || print "6\n";
  1479. Xunlink "$user.3";
  1480. X
  1481. X# Following is illeaal in RFC-822: should be "root@eiffel.com" <maxime>
  1482. X&replace_header('To: riot@eiffel.com (Riot Manager), root@eiffel.com <maxime>');
  1483. X`$cmd`;
  1484. X$? == 0 || print "7\n";
  1485. X-f "$user.4" || print "8\n";
  1486. Xunlink "$user.4";
  1487. X
  1488. X&replace_header('To: other, me, riotintin@eiffel.com, and, so, on');
  1489. X`$cmd`;
  1490. X$? == 0 || print "9\n";
  1491. X-f "$user.5" || print "10\n";
  1492. Xunlink "$user.5";
  1493. X
  1494. X&replace_header('To: other, me, chariot@eiffel.com, and, so, on');
  1495. X`$cmd`;
  1496. X$? == 0 || print "11\n";
  1497. X-f "$user.6" || print "12\n";
  1498. Xunlink "$user.6";
  1499. X
  1500. X&replace_header('To: other, me, abricot@eiffel.com, and, so, on');
  1501. X&add_header('Newsgroups: comp.lang.perl, news.groups, news.lists');
  1502. X`$cmd`;
  1503. X$? == 0 || print "13\n";
  1504. X-f "$user.7" || print "14\n";
  1505. Xunlink "$user.7";
  1506. X
  1507. X&replace_header('Newsgroups: comp.lang.perl, news.groups, news.answers');
  1508. X`$cmd`;
  1509. X$? == 0 || print "15\n";
  1510. X-f "$user.8" || print "16\n";
  1511. Xunlink "$user.8";
  1512. X
  1513. Xunlink 'mail';
  1514. Xprint "0\n";
  1515. END_OF_FILE
  1516.   if test 2014 -ne `wc -c <'agent/test/filter/list.t'`; then
  1517.     echo shar: \"'agent/test/filter/list.t'\" unpacked with wrong size!
  1518.   fi
  1519.   # end of 'agent/test/filter/list.t'
  1520. fi
  1521. echo shar: End of archive 5 \(of 26\).
  1522. cp /dev/null ark5isdone
  1523. MISSING=""
  1524. 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
  1525.     if test ! -f ark${I}isdone ; then
  1526.     MISSING="${MISSING} ${I}"
  1527.     fi
  1528. done
  1529. if test "${MISSING}" = "" ; then
  1530.     echo You have unpacked all 26 archives.
  1531.     echo "Now run 'sh PACKNOTES', then read README and type Configure.'"
  1532.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1533. else
  1534.     echo You still must unpack the following archives:
  1535.     echo "        " ${MISSING}
  1536. fi
  1537. exit 0
  1538.  
  1539. exit 0 # Just in case...
  1540.