home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume33 / mailagnt / part03 < prev    next >
Encoding:
Text File  |  1992-11-19  |  54.5 KB  |  1,657 lines

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