#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  MANIFEST Makefile install_pmd.sh man man/install_pmd.l
#   man/pmd_stats.l man/pmdc.l pmd.c pmd.h pmd_stats.c pmdc.h pmdc.l
#   pmdc.y runtime.c
# Wrapped by jik@pit-manager on Fri Dec 15 10:16:13 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
echo shar: Extracting \"'MANIFEST'\" \(548 characters\)
sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                   1	This shipping list
X Makefile                   1	
X install_pmd.sh             1	
X man                        1	
X man/install_pmd.l          1	
X man/pmd_stats.l            1	
X man/pmdc.l                 1	
X pmd.c                      1	
X pmd.h                      1	
X pmd_stats.c                1	
X pmdc.h                     1	
X pmdc.l                     1	
X pmdc.y                     1	
X runtime.c                  1	
END_OF_FILE
if test 548 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
fi
# end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(1927 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#
X# 	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/Makefile,v $
X#	$Author: jik $
X#	$Locker:  $
X#	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/Makefile,v 2.8 89/10/26 20:26:38 jik Exp $
X#
X
XDESTDIR=
XBINDIR=		/mit/watchmaker/${MACHINE}bin
XMANDIR=		/mit/watchmaker/man
XLIBDIR=		/mit/watchmaker/$(MACHINE)lib/pmdc
XMANSECT=	l
XINCS=
XDEFINES=
XLDFLAGS=
XPMDCLIBS=	-ll -ly
XSTATSLIBS=	-ldbm
XCFLAGS= 	-O ${INCS} ${DEFINES}
XCC=		cc
XLEX=		lex
XYACC=		yacc
XRM=		rm -rf
XINSTALL=	install
XBININSTFLAGS=	-c -s
XSHINSTFLAGS=	-c
XLIBINSTFLAGS=	-c
XMKDIR=		mkdir
XTARGETS=	pmdc runtime.o pmd.h install_pmd.sh pmd_stats
XMANS=		install_pmd pmd_stats pmdc
X
Xall: $(TARGETS)
X
Xpmdc: pmdc.y.o pmdc.l.o
X	$(CC) -o pmdc $(LDFLAGS) $(CFLAGS) pmdc.y.o pmdc.l.o $(PMDCLIBS)
X
Xpmdc.l.c: pmdc.l
X	$(LEX) -v pmdc.l; mv -f lex.yy.c pmdc.l.c
X
Xpmdc.y.c pmdc.y.h:    pmdc.y
X	$(YACC) -d pmdc.y; mv -f y.tab.c pmdc.y.c; mv -f y.tab.h pmdc.y.h
X
Xpmd_stats: pmd_stats.c
X	$(CC) -o pmd_stats $(CFLAGS) $(LDFLAGS) pmd_stats.c $(STATSLIBS)
X
Xpmdc.l.o: pmdc.l.c pmdc.y.h pmdc.h
Xpmdc.y.o: pmdc.y.c pmdc.h
Xruntime.o: runtime.c pmd.h
X
Xinstall: bin_install lib_install man_install
X
Xbin_install: pmdc pmd_stats install_pmd.sh
X	-$(RM) $(DESTDIR)$(BINDIR)/pmdc $(DESTDIR)$(BINDIR)/pmd_stats\
X		$(DESTDIR)$(BINDIR)/install_pmd
X	$(INSTALL) $(BININSTFLAGS) pmdc $(DESTDIR)$(BINDIR)
X	$(INSTALL) $(BININSTFLAGS) pmd_stats $(DESTDIR)$(BINDIR)
X	$(INSTALL) $(SHINSTFLAGS) install_pmd.sh\
X		$(DESTDIR)$(BINDIR)/install_pmd
X
Xlib_install: pmd.h runtime.o
X	-$(RM) $(DESTDIR)$(LIBDIR)
X	$(MKDIR) $(DESTDIR)$(LIBDIR)
X	$(INSTALL) $(LIBINSTFLAGS) runtime.o $(DESTDIR)$(LIBDIR)
X	$(INSTALL) $(SHINSTFLAGS) pmd.h $(DESTDIR)$(LIBDIR)
X
Xman_install:
X	cd man;\
X	for i in $(MANS); do\
X		$(INSTALL) $(SHINSTFLAGS) $$i.l\
X			 $(DESTDIR)$(MANDIR)/man$(MANSECT)/$$i.$(MANSECT);\
X	done
X
Xclean:	FORCE
X	rm -f pmdc.y.c pmdc.y.h pmdc pmdc.l.c pmdc.y.o pmdc.l.o \
X		runtime.o pmd_stats *~
X
XFORCE:
END_OF_FILE
if test 1927 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'install_pmd.sh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'install_pmd.sh'\"
else
echo shar: Extracting \"'install_pmd.sh'\" \(1703 characters\)
sed "s/^X//" >'install_pmd.sh' <<'END_OF_FILE'
X#!/bin/csh -f
X# User interface for pmdc.  Compiles and installs a Personal Mail Daemon.
X#
X# Usage: install_pmd [-A] rules-file [pmd-file-name]
X#
X
X# this is the home directory for the pmdc system.
X# it will need to be changed depending on where the system is installed
Xset pmdcbin=/mit/watchmaker/`/bin/athena/machtype`bin
Xset pmdc=/mit/watchmaker/`/bin/athena/machtype`lib/pmdc
X
Xif($#argv > 1 && $1 == '-A') then
X    set athena
X    shift
Xendif
X
Xif($#argv == 0 || $#argv > 2) then
X    echo "Usage: install_pmd [-A] rules-file [pmd-file-name]"
X    exit 1
Xelse if($#argv == 1) then
X    set pmd=$HOME/.pmd
Xelse if($2 !~ /*) then
X    echo "Pmd name must be absolute."
X    exit 1
Xelse
X    set pmd=$2
Xendif
X
Xecho "Parsing rules file..."
X${pmdcbin}/pmdc < $1 > /tmp/pmdc$$.c
Xif($status) then
X    echo "Cleaning up..."
X    rm -f /tmp/pmdc$$.c
X    exit 1
Xendif
X
Xecho "Compiling pmd..."
Xchdir /tmp
Xcc -o pmdc$$ -O -s -I${pmdc} pmdc$$.c ${pmdc}/runtime.o
X
Xif($status) then
X    echo "Cleaning up..."
X    exit 1
Xendif
X
Xrm -f pmdc$$.c
Xrm -f pmdc$$.o
X
Xif(-e $HOME/.forward) then
Xecho "Saving old .forward file..."
Xmv -f $HOME/.forward $HOME/.forward.old
Xendif
X
Xecho "Installing new pmd..."
X
X# Here's where we build the .forward file.  Notice difference for athena.
Xif($?athena) then
X    echo \"\|$pmd\"@`hostname`.LOCAL >/tmp/pmdcf$$
Xelse
X    echo \"\|$pmd\" >/tmp/pmdcf$$
Xendif
X
X# Now do the actual install
Xmv -f /tmp/pmdc$$ $pmd
Xchmod 4755 $pmd
Xmv -f /tmp/pmdcf$$ $HOME/.forward
X
Xexit
X#
X# 	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/install_pmd.sh,v $
X#	$Author: jik $
X#	$Locker:  $
X#	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/install_pmd.sh,v 1.1 89/10/26 20:42:06 jik Exp $
X#
END_OF_FILE
if test 1703 -ne `wc -c <'install_pmd.sh'`; then
    echo shar: \"'install_pmd.sh'\" unpacked with wrong size!
fi
chmod +x 'install_pmd.sh'
# end of 'install_pmd.sh'
fi
if test ! -d 'man' ; then
    echo shar: Creating directory \"'man'\"
    mkdir 'man'
fi
if test -f 'man/install_pmd.l' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'man/install_pmd.l'\"
else
echo shar: Extracting \"'man/install_pmd.l'\" \(1321 characters\)
sed "s/^X//" >'man/install_pmd.l' <<'END_OF_FILE'
X.TH INSTALL_PMD l "22 Jan 1986"
X.FM quote "MIT Watchmakers"
X.SH NAME
Xinstall_pmd \- user interface for pmdc
X.SH SYNOPSIS
X.B install_pmd
X[-A] rules-file [pmd-file]
X.SH DESCRIPTION
XWith one argument,
X.I install_pmd
Xcompiles a pmdc rules file into a runnable program which is installed
Xas .pmd in the user's home directory.  The user's .forward file is modified
Xto pipe to this program, which will then filter mail as described in the
Xdocumentation for pmd(l)).
X.PP
XWith two arguments,
X.I install_pmd
Xinstalls the compiled Personal Mail Daemon in the location specified
Xby the second argument.  This location must be specified absolutely.
XThe user's .forward file will be set to pipe to this location.
X.PP
XIf the -A option is given, the .forward file will be in a format
Xdesigned for use with the sendmail configuration for MIT Project Athena
Xmail clients.
XThis option should not be used on machines with normal sendmail
Xconfigurations.
X.SH FILES
X/usr/unsupported/watchmkr/pmdc/pmdc
X.br
X/usr/unsupported/watchmkr/pmdc/runtime.o
X.br
X$HOME/.forward to set pmd to process incoming mail
X.br
X$HOME/.pmd
X.SH SEE ALSO
Xpmdc(l), pmd_stats(l), sendmail(8)
X.SH AUTHOR
XJim Aspnes, MIT Project Athena.
X.SH BUGS
XThe location of the pmdc program and runtime library is built into
Xthe script, and will need to be respecified if it changes.
END_OF_FILE
if test 1321 -ne `wc -c <'man/install_pmd.l'`; then
    echo shar: \"'man/install_pmd.l'\" unpacked with wrong size!
fi
# end of 'man/install_pmd.l'
fi
if test -f 'man/pmd_stats.l' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'man/pmd_stats.l'\"
else
echo shar: Extracting \"'man/pmd_stats.l'\" \(592 characters\)
sed "s/^X//" >'man/pmd_stats.l' <<'END_OF_FILE'
X.TH PMD_STATS l "7 Jan 1986"
X.FM quote "MIT Watchmakers"
X.SH NAME
Xpmd_stats \- read a Personal Mail Daemon statistics file
X.SH SYNOPSIS
X.B pmd_stats
X[stats-file]
X.SH DESCRIPTION
X.I Pmd_stats
Xreads a PMD statistics file.  By default, it attempts to read
Xthe files .pmd.stats.dir and .pmd.stats.pag in the current directory;
Xif an argument is given it looks there instead.
X.SH FILES
X$HOME/.pmd.stats.dir
X.br
X$HOME/.pmd.stats.pag
X.SH SEE ALSO
Xpmdc(l)
X.SH AUTHOR
XJim Aspnes, MIT Project Athena.
X.SH BUGS
XOutput is not sorted; it depends only on the order of the hashing
Xfunction of the database.
END_OF_FILE
if test 592 -ne `wc -c <'man/pmd_stats.l'`; then
    echo shar: \"'man/pmd_stats.l'\" unpacked with wrong size!
fi
# end of 'man/pmd_stats.l'
fi
if test -f 'man/pmdc.l' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'man/pmdc.l'\"
else
echo shar: Extracting \"'man/pmdc.l'\" \(6055 characters\)
sed "s/^X//" >'man/pmdc.l' <<'END_OF_FILE'
X.TH PMDC l "17 Oct 1986"
X.FM quote "MIT Watchmakers"
X.SH NAME
Xpmdc \- personal mail daemon compiler
X.SH SYNOPSIS
X.B pmdc
X< rules-file > pmd.c
X.PP
Xcc -o pmd pmd.c runtime.o -ldbm
X.SH DESCRIPTION
X.I Pmdc
Xtakes a Personal Mail Daemon rules file and translates it into
Xa C source file, which must then by compiled and linked to produce a
XPMD program capable of filtering incoming mail.	 This provides, in
Xaddition to the ability to sort mail by type and content, the ability
Xto run other programs on certain messages, and to maintain a count of
Xmessages falling into various categories.
X.PP
XPmdc itself is not intended to be used directly; most users
Xwill want to use install_pmd(l)
Xinstead.  The remainder of this file describes
Xthe structure of the pmdc rules file.
X.SH RULES FILE FORMAT
XA rules file consists of zero or more declarations, followed by zero
Xor more if-then rules and a required default action.  The minimum
Xpossible rules file consists only of a default action.
X.SH DECLARATIONS
XDeclarations have the form
X.PP
X<variable> is <value>
X.PP
Xwhere <variable> can be any of
Xhome,
Xmode,
Xformat,
Xerrorfile,
Xor statfile.
X.PP
XThe value of the 
X.I home
Xvariable specifies which directory the PMD will
Xwork in while running; it defaults to the user's home directory at the
Xtime of compilation.
X.PP
X.I Mode
Xspecifies the default mode for all files created by the PMD; its value
Xmust be a three-digit octal number if specified, and defaults to 644.
XThe specified restrictions are relaxed if they prevent the PMD from
Xoperating -- in particular, no temporary or statistics 
Xfile will be created with a
Xmode more restrictive than 600, and no mailbox will be created with a
Xmode more restrictive than 200.
X.PP
X.I Format
Xspecifies the format to be used for messages appended to files created
Xby the PMD.  If specified, it must be either rmail or mmdf.  If
Xnothing is specified, the messages will be appended in the format used
Xby /bin/mail(1). 
X.PP
X.I Errorfile
Xand
X.I statfile
Xspecify the names of the files used for logging errors and message
Xstatistics, respectively.  They default to "pmd.dead.letter" and
X".pmd.stats".
X.SH RULES
XRules are tried in order, and the first rule which matches a message
Xdetermines the actions to be taken on that message.  If no rule
Xmatches a message, the default action is taken.
XRules take the form
X.PP
Xif <expression> then <actions> end
X.PP
XExpressions consist of zero or more predicates which may be combined
Xusing the binary operators
X.I and
Xand
X.I or,
Xthe unary operator
X.I not
Xand parentheses for grouping.  The order of precedence is
X.I not, and, or.
X.SH PREDICATES
XPredicates can be of the form
X.PP 
X<field> 
Xis
X<string>
X.PP
Xin which case they match any message whose header field <field>
Xcontains the string to match.
XIn addition, the following special predicates may be used:
X.PP
Xis from
X<string>
X.PP
Xmatches all messages which contain the given string in their from,
Xsender, resent-from, apparently-from, or resent-by fields.
X.PP
Xis to
X<string>
X.PP
Xmatches all messages which contain the given string in their
Xto, cc, bcc, resent-to, forwarded-to, or apparently-to fields.
X.PP
Xheader contains
X<string>
X.PP
Xmatches all messages which contain the given string in any header field.
X.PP
Xbody contains
X<string>
X.PP
Xmatches all messages which contain the given string in their body.
X.PP
Xcontains
X<string>
X.PP
Xis equivalent to
X.PP
X(header contains
X<string>
Xor body contains
X<string>)
X.SH ACTIONS
XThere are three types of actions which may be taken when a rule
Xmatches:
X.I is
Xactions,
X.I put
Xactions, and
X.I run
Xactions.  Each is specified by the form <keyword> <argument(s)>, where
X<keyword> determines the type of the action.  
XFor
X.I is
Xactions, the counter named by the argument is incremented in the
Xstatistics file.  For
X.I put
Xactions, the message is appended to the mailbox specified by the
Xargument.
XFor
X.I run
Xactions, the argument is an arbitrary shell command line; this is run
Xwith the message as standard input.
X.PP
XMore than one action may be specified in a rule; multiple actions must
Xbe separated by semicolons.
XThe PMD will attempt to carry out all actions specified, in the order
Xlisted.	 If one or more of the actions fails, this will not affect any
Xremaining actions but will cause the message to be written to the
Xerror file, if possible.
X.SH DEFAULT ACTIONS
XThe default actions are specifed by the 
X.PP
Xotherwise
X<actions>
Xend
X.PP
Xform, which must be placed at the end of the rules file.  A default
Xaction is required.
X.SH EXAMPLES
XThis minimal rules file duplicates the normal action of the mailer:
X.PP
X# bare-bones pmdc rules file
X.br
Xotherwise put /usr/spool/mail/your-name-here end
X.PP
XThis is a more complicated file:
X.PP
X# declarations
X.br
Xhome is /mit/a/s/asp/mail
X.br
Xmode is 644
X.br
X# note that I use the default error and stat files.
X.br
X
X.br
X# Junk mail gets categorized and placed in junk
X.br
Xif is from sf-lovers then is sf-lovers; put junk end
X.br
Xif is from human-nets then is human-nets; put junk end
X.br
X
X.br
X# this entry saves fortunes mailed using another program
X.br
X# note that "from is" != "is from"
X.br
Xif from is asp
X.br
X   and to is asp
X.br
X   and subject is "save this fortune"
X.br
X   then
X.br
X	is fortune;
X.br
X	run "sed -n '/^$/,$p' >> fortunes"
X.br
X   end
X.br
X
X.br
X# a default action is required
X.br
Xotherwise put mbox end
X.SH FILES
X/usr/unsupported/watchmkr/pmdc/pmdc
X.br
X/usr/unsupported/watchmkr/pmdc/runtime.o
X.br
X$HOME/.forward to set pmd to process incoming mail
X.br
X$HOME/.pmd.stats* (default statistics files)
X.br
X$HOME/pmd.dead.letter (default error file)
X.SH SEE ALSO
Xinstall_pmd(l), pmd_stats(l), sendmail(8), binmail(1)
X.SH AUTHOR
XJim Aspnes, MIT Project Athena.  Format variable added by Robert
XKrawitz, MIT Project Athena.
X.SH BUGS
XReserved words such as "and", "then", or "is" must be quoted when used in
Xarguments to predicates or actions.  Also, all whitespace in unquoted
Xarguments is converted to spaces; this may create problems when
Xspecifying some run actions.
X.PP
XThe format for pmdc rules files is incompatible with that of older versions of
Xpmd.
END_OF_FILE
if test 6055 -ne `wc -c <'man/pmdc.l'`; then
    echo shar: \"'man/pmdc.l'\" unpacked with wrong size!
fi
# end of 'man/pmdc.l'
fi
if test -f 'pmd.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pmd.c'\"
else
echo shar: Extracting \"'pmd.c'\" \(9198 characters\)
sed "s/^X//" >'pmd.c' <<'END_OF_FILE'
X/*
X *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd.c,v $
X *	$Author: jik $
X *	$Locker:  $
X *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd.c,v 1.1 89/10/26 20:45:17 jik Exp $
X */
X
X#ifndef lint
Xstatic char *rcsid_pmd_c = "$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd.c,v 1.1 89/10/26 20:45:17 jik Exp $";
X#endif	lint
X
X/* Copyright 1985 by the Massachussetts Institute of Technology */
X#include <stdio.h>
X#include <sys/file.h>
X#include <sysexits.h>
X
X#define MAXPREDS 20		/* Maximum number of predicates per rule */
X#define MAXLEN 200		/* Maximum input line length */
X#define MAXFIELD 80		/* Maximum predicate field length */
X#define MAXRULES 100		/* Maximum number of rules in the system */
X
X#define ACT_DELIM ':'		/* Action delimiter */
X#define ACT_SEP ';'		/* Inter-action delimiter */
X#define FIELD_DELIM '='		/* Field delimiter */
X#define PRED_DELIM ';'		/* Delimiter between fieldspec and match */
X
X#define COMMENT_CHAR '#'	/* Put this at the start of comment lines */
X#define PIPE_CHAR '|'   	/* Indicates action is a pipe */
X
Xstruct pred {
X  int header_only;		/* If nonzero, this predicate fails in body */
X  char field[MAXFIELD];
X  char match[MAXFIELD];
X};
X
Xstruct rule {
X  int pred_count;
X  struct pred preds[MAXPREDS];
X  char action[MAXLEN];
X} rules[MAXRULES];
X
Xint rule_count;			/* Total number of rules in the system */
X
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X  char *action, *apply_rules();
X  char *tempfile = "/tmp/pmdXXXXXX";
X  
X  if(argc != 3) {
X    fprintf(stderr, "Usage: pmd rule-file default-mailbox\n");
X    exit(EX_TEMPFAIL);
X  }
X
X  mktemp(tempfile);
X  if(!fcat(tempfile)) {		/* Get the input message */
X    unlink(tempfile);
X    exit(EX_TEMPFAIL);
X  }
X  rule_count=read_rules(rules, argv[1]); /* Read the rules  */
X  if((action=apply_rules(rules, rule_count, tempfile)) == NULL) {
X    if(fappend(argv[2], tempfile)) {
X      unlink(tempfile);
X      exit(0);
X    } else {
X      unlink(tempfile);
X      exit(EX_TEMPFAIL);
X    }
X  } else {
X    do_action(action, tempfile, argv[2]);
X  }
X}
X
X
X/* Parse a sequence of actions and execute them */
Xdo_action(action, tempfile, deffile)
Xchar *action, *tempfile, *deffile;
X{
X  char *act, *index();
X
X  while(action) {
X    act = action;
X    if(action = index(act, ACT_SEP))
X      *action++ = '\0';		/* Terminate the input act */
X    if(*act == PIPE_CHAR) {
X      if(do_syscall(++act, tempfile) != 0) {
X	if(!fappend(deffile, tempfile)) {
X	  unlink(tempfile);
X	  exit(EX_TEMPFAIL);
X	} else {
X	  unlink(tempfile);
X	  exit(0);
X	}
X      }
X    } else {
X      if(!fappend(act, tempfile)) {
X	if(!fappend(deffile, tempfile)) {
X	  unlink(tempfile);
X	  exit(EX_TEMPFAIL);
X	} else {
X	  unlink(tempfile);
X	  exit(0);
X	}
X      }
X    }
X  }
X  /* All done, clean up */
X  unlink(tempfile);
X  exit(0);
X}
X
X
X/* Do a system call, with input from tempfile */
X/* Returns status of the system call */
Xint do_syscall(action, tempfile)
Xchar *action, *tempfile;
X{
X  char line[400];
X
X  sprintf(line, "%s < %s\n", action, tempfile);
X  return(system(line));		/* Check to see if the pipe works */
X}
X
X
X/* Returns 1 iff s1 matches s2 (ignoring case) for the length of s1 */
Xisin(s1, s2)
Xchar *s1, *s2;
X{
X  for(; *s1; s1++, s2++) {
X    if(((*s1 | 0x20) != (*s2 | 0x20)) /* Ctype's tolower loses big */
X       || *s2 == '\0'
X       || *s2 == '\n') return(0);
X  }
X  return(1);
X}
X
X/* Returns 1 if predicate applies to line */
Xapply_pred(p, s)
Xstruct pred *p;
Xchar *s;
X{
X  if(!isin(p->field, s)) return(0);
X  while(*s) {
X    if(isin(p->match, s++)) {
X#ifdef DEBUG
X      printf("'%s=%s' (%d) applies to '%s'\n", p->field, p->match,
X	     p->header_only, --s);
X#endif DEBUG
X      return(1);
X    }
X  }
X  return(0);
X}
X  
X/* Applies rule to line.  Returns true if rule fires */
Xapply_rule_to_line(r, s, f)
Xstruct rule *r;
Xchar *s;
Xint f;				/* Flag to allow header predicates */
X{
X  int i;
X
X  for(i=0; i < r->pred_count; i++) {
X    /* If a predicate applies, remove it */
X    if((f || !r->preds[i].header_only) && apply_pred(&r->preds[i], s)) {
X      r->preds[i--] = r->preds[--(r->pred_count)]; /* Note we decrement i */
X    }
X  }
X  return(!r->pred_count);
X}
X  
X/* Parses a new rule from a line */
Xstruct rule *parse_rule(r, s)
Xstruct rule *r;
Xchar *s;
X{
X  char *index(), *rindex();
X  char *cp;
X  int p;
X
X#ifdef DEBUG
X  printf("parse_rule: parsing %s\n", s);
X#endif
X
X  /* First get the action field */
X  if(!(cp = index(s, ACT_DELIM))) return(NULL);
X  strncpy(r->action, cp+1, MAXLEN);
X  *cp='\0';			/* We don't care about the action any more */
X  
X  /* Now the predicates */
X  for(p=0; p < MAXPREDS && strlen(s) > 0; p++) {
X    if(!(cp = rindex(s, PRED_DELIM))) {
X      parse_pred(r->preds + p++, s);
X      break;
X    } else {
X	*cp++ = '\0';		/* Separate this predicate from the rest */
X      parse_pred(r->preds + p, cp);
X    }
X  }
X
X  r->pred_count = p;		/* Set the predicate count */
X#ifdef DEBUG
X  printf("pred_count = %d  action = '%s'\n", p, r->action);
X#endif
X  return(r);			/* Return r */
X}
X
X/* Parses predicate */
Xparse_pred(p, s)
Xstruct pred *p;
Xchar *s;
X{
X  char *cp, *index();
X
X#ifdef DEBUG
X  printf("parse_pred parsing %s\n", s);
X#endif
X
X  if(!(cp = index(s, FIELD_DELIM))) {
X    p->header_only = 0;		/* Body match */
X    *p->field = '\0';		/* No field */
X    strncpy(p->match, s, MAXFIELD);
X  } else {
X    p->header_only = 1;		/* Header match */
X    *cp++ = '\0';		/* Separate match and field */
X    strncpy(p->field, s, MAXFIELD);
X    strncpy(p->match, cp, MAXFIELD);
X  }
X#ifdef DEBUG
X  printf("field='%s' match='%s' header_only=%d\n", p->field, p->match,
X	 p->header_only);
X#endif
X}  
X
X/* Read in a rule table from filename */
X/* Returns number of rules parsed */
Xread_rules(rules, file)
Xstruct rule rules[];
Xchar *file;
X{
X  char line[MAXLEN];
X  FILE *f;
X  int r;
X  char *g;
X
X  if((f=fopen(file, "r")) == NULL) {
X    fprintf(stderr, "read_rules: cannot open file %s\n", file);
X    return(0);
X  }
X
X  for(r=0; r < MAXRULES; r++) {
X    /* Get the next non-empty non-comment line */
X    do {
X      g=fgets(line, MAXLEN, f);
X    } while(g != NULL && *line == COMMENT_CHAR && strlen(line) != 0);
X
X    if(g == NULL) break; /* End of file here */
X
X    line[strlen(line)-1] = '\0';
X    if(parse_rule(&rules[r], line) == NULL) {
X      r--;			/* Bag this rule */
X#ifdef DEBUG
X      puts("Parse failure -- tossing rule");
X#endif
X      continue;
X    }
X  }
X  fclose(f);
X  return(r);
X}
X
X/* Apply a rule table to filename */
X/* Return action of first firing rule in table */
X/* And NULL on error or if no rules fire */
X/* Note that this destroys the rule table */
Xchar *apply_rules(rules, rule_count, file)
Xstruct rule rules[];
Xint rule_count;
Xchar *file;
X{
X  char line[MAXLEN];
X  FILE *f;
X  int r, header;
X  
X  if((f=fopen(file, "r")) == NULL) {
X    fprintf(stderr, "apply_rules: cannot open file %s\n", file);
X    return(NULL);
X  }
X
X  header=1;				/* Start with header predicates on */
X
X  while(fgets(line, MAXLEN, f) != NULL) {
X    if(*line == '\n') header=0;	/* We're out of the header now */
X    for(r=0; r < rule_count; r++) {
X      if(rules[r].pred_count != 0) {	/* Don't bother with fired rules */
X	apply_rule_to_line(&rules[r], line, header);
X      }
X    }
X  }
X
X  fclose(f);
X
X  for(r=0; r < rule_count; r++) {
X    if(rules[r].pred_count == 0) {
X      return(rules[r].action);
X    }
X  }
X
X  return(NULL);			/* No rules fired */
X}
X
X/* Appends file2 to file1, flocking file1 to prevent lossage */
X/* Returns 0 if files cannot be opened */
X/* Returns 1 if the append was done successfully */
X/* Dies the True Death on a write error */
Xfappend(file1, file2)
Xchar *file1, *file2;
X{
X  FILE *f1, *f2;
X  int fd;
X  char c;
X  long end, ftell();
X
X  if((f2=fopen(file2, "r")) == NULL) {
X    fprintf(stderr, "fappend: cannot open file %s\n", file2);
X    return(0);
X  }
X
X  if((fd=open(file1, (O_CREAT | O_APPEND | O_WRONLY), 0600)) < 0) {
X    fprintf(stderr, "fappend: cannot open file %s\n", file1);
X    return(0);
X  }
X
X  flock(fd, LOCK_EX);		/* Lock the file */
X  
X  if((f1=fdopen(fd, "a")) == NULL) {
X    fprintf(stderr, "fappend: cannot open file %s\n", file1);
X    flock(fd, LOCK_UN);
X    close(fd);
X    return(0);
X  }
X
X  fseek(f1, 0L, 2);
X  end=ftell(f1);
X  
X  /* Everything ok, copy file */
X  while((c=getc(f2)) != EOF)
X    if(c != putc(c, f1)) goto wrerr;
X
X  if(fflush(f1) == EOF) {
X  wrerr:
X    perror("fappend:");
X    ftruncate(fd, (int) end);	/* Cast to int to get lint to shut up */
X    close(fd);
X    exit(EX_TEMPFAIL);
X  }
X
X  close(fd);
X  return(1);
X}
X
X
X/* Cat standard input to file */
Xfcat(file)
Xchar *file;
X{
X  FILE *f;
X  int fd;
X  char c;
X
X  if((fd=open(file,  (O_CREAT | O_EXCL | O_WRONLY), 0600)) < 0) {
X    fprintf(stderr, "fcat: cannot open file %s\n", file);
X    return(0);
X  }
X
X  flock(fd, LOCK_EX);		/* Lock the file */
X  
X  if((f=fdopen(fd, "w")) == NULL) {
X    fprintf(stderr, "fappend: cannot open file %s\n", file);
X    close(fd);
X    return(0);
X  }
X
X  /* Everything ok, cat to file */
X  while((c=getchar()) != EOF)
X    if(c != putc(c, f)) goto wrerr;
X  if('\n' != putc('\n', f)) goto wrerr; /* Add a newline */
X
X  if(fflush(f) == EOF) {
X  wrerr:
X    perror("fcat:");
X    close(fd);
X    unlink(file);		/* This is a temporary, so clean up */
X    exit(EX_TEMPFAIL);
X  }
X
X  close(fd);
X  return(1);
X}
X
END_OF_FILE
if test 9198 -ne `wc -c <'pmd.c'`; then
    echo shar: \"'pmd.c'\" unpacked with wrong size!
fi
# end of 'pmd.c'
fi
if test -f 'pmd.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pmd.h'\"
else
echo shar: Extracting \"'pmd.h'\" \(701 characters\)
sed "s/^X//" >'pmd.h' <<'END_OF_FILE'
X/*
X *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd.h,v $
X *	$Author: jik $
X *	$Locker:  $
X *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd.h,v 1.1 89/10/26 20:45:59 jik Exp $
X */
X
X#define MAXLINELEN 2000		/* Longest line that can be read */
X
X/* Externals for the pmd runtime package */
X#include <stdio.h>
X#include <ctype.h>
X
X/* These should appear in lex.yy.c */
Xextern int  ppmode;
Xextern char *pphome;
Xextern char *pperrf;
Xextern char *ppdbfile;
Xextern char *ppformat;
Xextern ppact();
Xextern FILE *ppin;
Xextern char ppline[MAXLINELEN];
X
X/* These are in runtime.c */
Xextern ppputact();
Xextern pprunact();
Xextern ppbarfact();
Xextern pphead();
Xextern ppbody();
END_OF_FILE
if test 701 -ne `wc -c <'pmd.h'`; then
    echo shar: \"'pmd.h'\" unpacked with wrong size!
fi
# end of 'pmd.h'
fi
if test -f 'pmd_stats.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pmd_stats.c'\"
else
echo shar: Extracting \"'pmd_stats.c'\" \(1753 characters\)
sed "s/^X//" >'pmd_stats.c' <<'END_OF_FILE'
X/*
X *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd_stats.c,v $
X *	$Author: jik $
X *	$Locker:  $
X *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd_stats.c,v 1.3 89/10/26 21:05:12 jik Exp $
X */
X
X#ifndef lint
Xstatic char *rcsid_pmd_stats_c = "$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmd_stats.c,v 1.3 89/10/26 21:05:12 jik Exp $";
X#endif	lint
X
X#include <stdio.h>
X#ifdef mips
X#include <dbm.h>
X#define dbm_fetch(a,b) fetch(b)
X#define dbm_firstkey(a) firstkey()
X#define dbm_nextkey(a) nextkey()
X#else
X#include <ndbm.h>
X#endif
X#include <sys/file.h>
X
X#define TIMEFIELD "_pptime"	/* Field holding start time */
X
Xdatum key, contents;
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X#ifndef mips
X    DBM *db;
X#endif
X    char *statfile;
X    unsigned long count;
X
X    switch(argc) {
X	case 1:
X	statfile = ".pmd.stats";
X	break;
X	case 2:
X	statfile = argv[1];
X	break;
X      default:
X	fputs("Usage: pmd_stats [statfile]\n", stderr);
X	exit(1);
X    }
X
X#ifndef mips
X    if((db = dbm_open(statfile, O_RDONLY, 0644)) == NULL) {
X	fputs("Could not open database\n", stderr);
X	exit(2);
X    }
X#else
X    if (dbminit(statfile) < 0) {
X	 fputs("Could not open database\n", stderr);
X	 exit(2);
X    }
X#endif
X    key.dptr = TIMEFIELD;
X    key.dsize = strlen(key.dptr)+1;
X    contents = dbm_fetch(db, key);
X    if(contents.dptr == NULL) {
X	puts("Couldn't find start time.\n");
X    } else {
X	printf("Database started on %s\n", contents.dptr);
X    }
X
X    for(key = dbm_firstkey(db); key.dptr != NULL; key = dbm_nextkey(db)) {
X	contents = dbm_fetch(db, key);
X	if(strcmp(key.dptr, TIMEFIELD)) {
X	    bcopy((char *)contents.dptr, (char *)&count, sizeof(count));
X	    printf("%30s %lu\n", key.dptr, count);
X	}
X    }
X    
X    exit(0);
X}
END_OF_FILE
if test 1753 -ne `wc -c <'pmd_stats.c'`; then
    echo shar: \"'pmd_stats.c'\" unpacked with wrong size!
fi
# end of 'pmd_stats.c'
fi
if test -f 'pmdc.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pmdc.h'\"
else
echo shar: Extracting \"'pmdc.h'\" \(369 characters\)
sed "s/^X//" >'pmdc.h' <<'END_OF_FILE'
X/*
X *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmdc.h,v $
X *	$Author: jik $
X *	$Locker:  $
X *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmdc.h,v 1.1 89/10/26 20:46:15 jik Exp $
X */
X
X#include <stdio.h>
X
Xextern char *ds();		/* Creates a dynstr from its argument */
Xextern char *cat();		/* Destructively concatenates 3 dynstr's */
END_OF_FILE
if test 369 -ne `wc -c <'pmdc.h'`; then
    echo shar: \"'pmdc.h'\" unpacked with wrong size!
fi
# end of 'pmdc.h'
fi
if test -f 'pmdc.l' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pmdc.l'\"
else
echo shar: Extracting \"'pmdc.l'\" \(1088 characters\)
sed "s/^X//" >'pmdc.l' <<'END_OF_FILE'
X%C
X%{
X#include    "pmdc.h"
X#include    "pmdc.y.h"
X#include    <ctype.h>
X
X/* Nuke the output routine */
X#undef output
X#define output(X) 0
X%}
X
XS [ \t\n;\(\)]
XC [^ \t\n\";\(\)]
XD [0-9]
X%%
X^#.*$	    	    	;   /* Delete comments */
Xhome/{S}+		return(HOME);
Xmode/{S}+		return(MODE);
Xerrorfile/{S}+		return(ERR);
Xstatfile/{S}+		return(DB);
Xformat/{S}+		return(FORMAT);
X{D}{3,4}/{S}+		{ yylval.dynstr = ds(yytext);
X			  return(NUM); 
X			  }
Xif/{S}+			return(IF);
Xthen/{S}+			return(THEN);
Xotherwise/{S}+		return(OTHERWISE);
Xend/{S}+			return(END);
Xnot/{S}+			return(NOT);
Xand/{S}+			return(AND);
Xor/{S}+			return(OR);
Xis/{S}+			return(IS);
Xheader/{S}+		return(HEADER);
Xbody/{S}+			return(BODY);
Xcontains/{S}+		return(CONTAINS);
Xfrom/{S}+			return(FROM);
Xto/{S}+			return(TO);
X\(			return(LEFTP);
X\)			return(RIGHTP);
X;			return(SEMICOLON);
Xrun/{S}+			return(RUN);
Xput/{S}+			return(PUT);
Xbounce-with-error/{S}+		return(BARF);
X\"[^\"\n]*\"/{S}+	{ yytext[yyleng-1] = '\0';
X			  yylval.dynstr = ds(yytext+1);
X			  return(STRING); }
X{C}+/{S}+		{ yylval.dynstr = ds(yytext);
X			  return(STRING); }
X%%
END_OF_FILE
if test 1088 -ne `wc -c <'pmdc.l'`; then
    echo shar: \"'pmdc.l'\" unpacked with wrong size!
fi
# end of 'pmdc.l'
fi
if test -f 'pmdc.y' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pmdc.y'\"
else
echo shar: Extracting \"'pmdc.y'\" \(8818 characters\)
sed "s/^X//" >'pmdc.y' <<'END_OF_FILE'
X/*
X *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmdc.y,v $
X *	$Author: jik $
X *	$Locker:  $
X *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/pmdc.y,v 1.1 89/10/26 20:46:24 jik Exp $
X */
X
X%{
X#include <stdio.h>
X
X#define DSLP	ds("(")
X#define	DSRP	ds(")")
X#define DSOR	ds(" || ")
X#define DSAND	ds(" && ")
X#define DSNOT	ds("!")
X
X#define P(x)		cat(DSLP, x, DSRP)
X#define POR(x,y)	P(cat(x, DSOR, y))
X#define PAND(x,y)	P(cat(x, DSAND, y))
X#define PNOT(x)		P(cat(DSNOT, x, NULL))
X
Xchar *cat(), *ds(), *headrule(), *bodyrule(), *def_rule(), *def_global();
Xchar *malloc();
X
Xchar *defs = NULL;		/* Header definitions */
Xchar *hrules = NULL;		/* Header rules */
Xchar *brules = NULL;		/* Body rules */
X
Xchar *home, *mode;	/* Set using declarator commands */
Xchar *errorfile, *dbfile, *format;
X
Xint errorflag;			/* Global error flag.  Determines exit code */
X
X
X%}
X
X%union {
X	char *dynstr;
X}
X
X%token	HOME MODE ERR DB FORMAT	/* Option declarators */
X%token	IF THEN OTHERWISE END
X%token	NOT AND OR LEFTP RIGHTP
X%token	IS HEADER BODY CONTAINS FROM TO
X%token	<dynstr> STRING
X%token	<dynstr> NUM
X%token	RUN PUT BARF SEMICOLON
X
X%type	<dynstr>    rules rule default expr pred phrase word actions action
X
X%left OR
X%left AND
X%left NOT
X
X%start prog
X
X%%
Xprog	:   decls rules default
X		{ make_pmd(cat($2, $3, NULL)); }
X	|   error
X	;
X
Xrules	:   rules rule
X	    { $$ = cat($1, $2, NULL); }
X	|   /* empty */
X	    { $$ = NULL; }
X	;
X
Xrule	:   IF expr THEN actions END
X	    {
X	      $$ = cat(ds("\nif("),
X		       cat($2,
X			   ds(") {\n"),
X			   $4),
X		       ds("\nreturn;\n}\n"));
X	    }
X	|   error
X	    { $$ = NULL; }
X	;
Xexpr	:   NOT expr
X		{ $$ = PNOT($2); }
X	|   expr AND expr
X		{ $$ = PAND($1, $3); }
X	|   expr OR expr
X		{ $$ = POR($1, $3); }
X	|   LEFTP expr RIGHTP
X		{ $$ = P($2); }
X	|   pred
X	;
X
Xpred	:   IS FROM phrase
X		{
X		    $$ = POR(POR(headrule(ds($3), ds("from")),
X				 headrule(ds($3), ds("sender"))),
X			     POR(POR(headrule(ds($3), ds("resent-from")),
X				     headrule(ds($3), ds("apparently-from"))),
X				 headrule(ds($3), ds("resent-by"))));
X		    free($3);	/* Must be freed explicitly */
X		}
X	|   IS TO phrase
X		{
X		    $$ = POR(POR(headrule(ds($3), ds("to")),
X				 headrule(ds($3), ds("cc"))),
X			     POR(POR(headrule(ds($3), ds("bcc")),
X				     headrule(ds($3), ds("resent-to"))),
X				 POR(headrule(ds($3), ds("forwarded-to")),
X				     headrule(ds($3), ds("apparently-to")))));
X		    free($3);	/* Must be freed explicitly */
X		}
X
X	|   word IS phrase
X		{ $$ = headrule($3, $1); }
X	|   TO IS phrase
X	    	{ $$ = headrule($3, ds("to")); }
X	|   FROM IS phrase
X	    	{ $$ = headrule($3, ds("from")); }
X	|   HEADER CONTAINS phrase
X		{ $$ = headrule($3, NULL); }
X	|   BODY CONTAINS phrase
X		{ $$ = bodyrule($3); }
X	|   CONTAINS phrase
X		{
X		    $$ = POR(headrule(ds($2), NULL),
X			     bodyrule(ds($2)));
X		    free($2);
X		}
X	;
X
Xactions	:   actions SEMICOLON action
X		{ $$ = cat($1, $3, NULL); }
X	|   actions action
X		{ $$ = cat($1, $2, NULL); }
X	|   action
X	;
X
Xaction	:   RUN phrase
X		{
X		  $$ = cat(ds("pprunact(\""),
X			   $2,
X			   ds("\");\n"));
X		}
X	|   PUT STRING
X		{
X		  $$ = cat(ds("ppputact(\""),
X			   $2,
X			   ds("\");\n"));
X		}
X	|   IS phrase
X	    	{
X		    $$ = cat(ds("ppinc(\""),
X			     $2,
X			     ds("\");\n"));
X		}
X	|   BARF phrase
X		{
X		    $$ = cat(ds("ppbarfact(\""),
X			     $2,
X			     ds("\");\n"));
X		}
X	|   /* empty */
X		{ $$ = ds(";"); }
X	|   error
X	    { $$ = NULL; }
X	;
X
Xdecls	:   decls decl
X	|   ;
X
Xdecl	:   HOME IS STRING
X		{ free(home); home = $3; }
X	|   MODE IS NUM
X		{ free(mode); mode = $3; }
X	|   ERR IS STRING
X		{ free(errorfile); errorfile = $3; }
X	|   DB IS STRING
X		{ free(dbfile); dbfile = $3; }
X	|   FORMAT IS STRING
X                { free(format); format = $3; }
X	|   error   ;
X
Xdefault	:   OTHERWISE actions END
X		{ $$ = cat(ds("{\n"), $2, ds("\nreturn;\n}")); }
X	;
X
Xphrase	:   phrase STRING
X		{
X		    $$ = cat($1, ds(" "), $2);
X		}
X	|   STRING
X	;
X
Xword	:   STRING
X    	    	{
X		    if(index($1, ' ') || index($1, '\t') ||
X		       index($1, ':')) {
X			yyerror("illegal character in header field");
X			YYERROR;
X		    }
X		    $$ = $1;
X		}
X	;
X
X%%
X
X
Xmain()
X{
X#ifdef YYDEBUG
X    extern int yydebug;
X    yydebug = 1;
X#endif
X    errorflag = 0;
X    hrules = brules = defs = NULL;
X    mode = ds("0664");
X    home = ds(getenv("HOME"));
X    errorfile = ds("pmd.dead.letter");
X    dbfile = ds(".pmd.stats");
X    format = ds("mail");
X    yyparse();
X    exit(errorflag);
X}
X
Xyyerror(s)
Xchar *s;
X{
X    extern int yylineno;
X
X    fprintf(stderr, "%d: %s\n", yylineno, s);
X    errorflag |= 1;		/* Set the error flag */
X}
X
X/*
X * Support for dynamic strings:
X * cat creates a string from three input strings.
X * The input strings are freed by cat (so they better have been malloced).
X * ds makes a malloced string from one that's not.
X * (Stolen from cdecl source)
X */
X
Xchar *cat(s1,s2,s3)
Xchar *s1,*s2,*s3;
X{
X    register char *newstr;
X    register unsigned len = 1;
X
X    if (s1 != NULL) len += strlen(s1);
X    if (s2 != NULL) len += strlen(s2);
X    if (s3 != NULL) len += strlen(s3);
X    newstr = malloc(len);
X    *newstr = NULL;
X    if (s1 != NULL) {
X	strcat(newstr,s1);
X	free(s1);
X    }
X    if (s2 != NULL) {
X	strcat(newstr,s2);
X	free(s2);
X    }
X    if (s3 != NULL) {
X	strcat(newstr,s3);
X	free(s3);
X    }
X    return(newstr);
X}
X
Xchar *ds(s)
Xchar *s;
X{
X    register char *p;
X
X    p = malloc((unsigned)(strlen(s) + 1));
X    strcpy(p,s);
X    return p;
X}
X
X/*
X * Rule constructors
X */
X
X/*
X * Headrule creates a new rule in defs and rules, and returns the name of
X * the flag associated with the rule.  It destroys its arguments.
X */
Xchar *headrule(val, field)
Xchar *val;
Xchar *field;
X{
X    char *name;
X
X    name = def_rule();
X
X    /* If field is NULL, rule field should be "" */
X    if(field == NULL) field = ds("");
X    hrules = cat(hrules,
X		 cat(cat(ds("if(pphead(\""),
X			 field,
X			 ds("\", \"")),
X		     cat(val,
X			 ds("\")) "),
X			 ds(name)),
X		     ds("++;\n")),
X		 NULL);
X    return(name);
X}
X
X/*
X * Bodyrule defines a rule which fires if val is in the message body,
X * and returns the name of that rule.  It destroys its argument.
X */
Xchar *bodyrule(val)
Xchar *val;
X{
X    char *name;
X
X    name = def_rule();
X
X    brules = cat(brules,
X		 cat(ds("if(ppbody(\""),
X		     cat(val,
X			 ds("\")) "),
X			 ds(name)),
X		     ds("++;\n")),
X		 NULL);
X    return(name);
X}
X
X/*
X * Defines a new rule name in the global variable defs.
X * Returns the new name.  All names generated are unique.
X */
Xchar *def_rule()
X{
X    char rulename[20];
X    static long lastname = 0;
X    extern char *defs;
X
X    strcpy(rulename, "ppr");
X    sprintf(rulename + strlen(rulename), "%ld", lastname++);
X
X    /* Define the rule flag */
X    defs = cat(defs,
X	       ds("\nint "),
X	       cat(ds(rulename),
X		   ds(" = 0;\n"),
X		   NULL));
X    return(ds(rulename));
X}
X
X/*
X * Puts a null-terminated array of strings to stdout, one to a line.
X */
Xput_block(block)
Xchar *block[];
X{
X    char **ptr;
X
X    for(ptr = block; *ptr; ptr++) puts(*ptr);
X}
X
X/* Top of definitions */
Xchar *def_head[] = {
X    "#include \"pmd.h\"",
X    (char *) NULL };
X
X/* Top of ppact */
Xchar *ppacthead[] = {
X    "ppact()",
X    "{",
X    "char *fgets(), *fgethdr();",
X    "while(fgethdr(ppline, MAXLINELEN, ppin) != NULL && *ppline != '\\0') {",
X    (char *) NULL };
X
X/* In ppact, between header rules and body rules */
Xchar *ppactbody[] = {
X    "}",			/* End of while */
X    "while(fgets(ppline, MAXLINELEN, ppin) != NULL) {",
X    (char *) NULL };
X
X/* In ppact, after brules but before actions */
Xchar *ppactact[] = {
X    "}",				/* End of while in ppactbody */
X    (char *) NULL };
X
X/* End of ppact */
Xchar *ppacttail[] = {
X    "}",			/* End of ppact */
X    (char *) NULL};
X
X/*
X * Creates the pmd source file.
X *
X * Make_pmd prints this file to stdout, destroying [hb]rules, defs, and its
X * argument.
X */
Xmake_pmd(acts)
Xchar *acts;
X{
X
X    /* Definitions section */
X    put_block(def_head);
X    free_puts(cat(cat(def_global("pphome", home),
X		      cat(ds("\nint ppmode=0"), mode, ds(";\n")),
X		      cat(def_global("pperrf", errorfile), ds("\n"),
X			  cat(def_global("ppformat", format), ds("\n"),
X			      def_global("ppdbfile", dbfile)))),
X		  defs,
X		  NULL));
X	   
X	      /* Rules section */
X	      put_block(ppacthead);
X	      free_puts(hrules);
X	      put_block(ppactbody);
X	      free_puts(brules);
X	      put_block(ppactact);
X	      free_puts(acts);
X	      put_block(ppacttail);
X	  }
X
X/*
X * Like puts, only destroys its argument.
X */
Xfree_puts(ds)
Xchar *ds;
X{
X    puts(ds);
X    free(ds);
X}
X
X/*
X * First argument is a normal string, second is a dynstr which is destroyed.
X * Generates a char * variable definition of var as val.
X */
Xchar *def_global(var, val)
Xchar *var;
Xchar *val;
X{
X    return(cat(cat(ds("\nchar *"),
X		   ds(var),
X		   ds("=")),
X	       cat(ds("\""),
X		   val,
X		   ds("\";\n")),
X	       NULL));
X}
END_OF_FILE
if test 8818 -ne `wc -c <'pmdc.y'`; then
    echo shar: \"'pmdc.y'\" unpacked with wrong size!
fi
# end of 'pmdc.y'
fi
if test -f 'runtime.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'runtime.c'\"
else
echo shar: Extracting \"'runtime.c'\" \(8710 characters\)
sed "s/^X//" >'runtime.c' <<'END_OF_FILE'
X/*
X *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/runtime.c,v $
X *	$Author: jik $
X *	$Locker:  $
X *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/runtime.c,v 1.3 89/10/26 21:05:36 jik Exp $
X */
X
X#ifdef DTEST
X#define DPRINT(a) fprintf(stderr,a)
X#else
X#define DPRINT(a)
X#endif
X
X#ifndef lint
Xstatic char *rcsid_runtime_c = "$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/runtime.c,v 1.3 89/10/26 21:05:36 jik Exp $";
X#endif	lint
X
X#include "pmd.h"
X#include <stdio.h>
X#include <sys/file.h>
X#include <sysexits.h>
X#include <strings.h>
X#ifdef mips
X#define dbm_fetch(a,b) fetch(b)
X#define dbm_firstkey(a) firstkey()
X#define dbm_nextkey(a) nextkey()
X#define dbm_store(a,b,c,d) store(b,c)
X#include <dbm.h>
X#else
X#include <ndbm.h>
X#endif
X#include <sys/time.h>
X
X#define LOCKTIME 15		/* Time to wait while grabbing a lock */
X#define MAXTIME	10		/* Number of locks to wait through */
X#define TIMEFIELD "_pptime"	/* Time of first database entry */
X
X#define LSIZE 512		/* Size of various lines */
X
Xchar *pptemp;			/* Temporary file */
XFILE *ppin;			/* Temporary file filepointer */
Xchar ppline[MAXLINELEN];	/* Input line buffer */
Xint pperr;			/* Error flag */
Xdatum ppkey, ppcount;		/* For statistical routines */
X
X
X/*
X * Sets up pptemp and pperr, then calls ppact to do the real work.
X * Cleans up on error.
X */
Xmain()
X{
X    char *mktemp();
X
X    /* If we can't chdir to home, we're scrod */
X    if(chdir(pphome) != 0) {
X	fprintf(stderr, "Pmd: mail directory %s does not exist!\n", pphome);
X	exit(EX_TEMPFAIL);
X    }
X    pperr = 0;
X    pptemp = mktemp("/tmp/pmdXXXXXX");
X    if(ppfcat(pptemp) == 0) {
X	fprintf(stderr, "Can't write to temp file %s\n", pptemp);
X	exit(EX_TEMPFAIL);
X    }
X    if((ppin = fopen(pptemp, "r")) == (FILE *) NULL) {
X	fprintf(stderr, "Can't read temp file %s\n", pptemp);
X	exit(EX_TEMPFAIL);
X    }
X    ppinc("Total messages");
X    ppact();
X    fclose(ppin);
X    if(pperr) {
X	ppinc("Dead letters");
X	ppputact(pperrf);	/* If this fails, we can do nothing */
X    }
X    unlink(pptemp);
X    exit(0);
X}
X
X/* Do a system call, with input from pptemp */
X/* Sets pperr if there is an error */
Xpprunact(action)
Xchar *action;
X{
X  char line[LSIZE];
X
X  strcpy(line, action);
X  strcat(line, " < ");
X  strcat(line, pptemp);
X  if(system(line) != 0) pperr++;	/* Check to see that the pipe works */
X}
X
X/* Appends pptemp to file, flocking file to prevent lossage */
X/* Sets pperr on error */
Xppputact(file)
Xchar *file;
X{
X  FILE *f1, *f2;
X  int fd, rmail, mmdf;
X  char line[LSIZE], *fgets();
X  long end, ftell();
X
X  if((f2=fopen(pptemp, "r")) == (FILE *) NULL) {
X    pperr++;
X    return;
X  }
X
X  if((fd=open(file, (O_CREAT | O_APPEND | O_WRONLY), ppmode|0200)) < 0) {
X    pperr++;
X    return;
X  }
X
X  flock(fd, LOCK_EX);		/* Lock the file */
X  
X  if((f1=fdopen(fd, "a")) == (FILE *) NULL) {
X    close(fd);
X    pperr++;
X    return;
X  }
X
X  fseek(f1, 0L, 2);
X  end=ftell(f1);
X  
X  /* Everything ok, copy file */
X  /* Do this line-at-a-time so we can test for from lines */
X  /* Note: this is somewhat specialized for mailbox format */
X  /* We need to put a from line on if none exists */
X  if(fgets(line, LSIZE, f2) == NULL) goto wrerr;
X  rmail = !strcmp (ppformat, "rmail");
X  mmdf = !strcmp (ppformat, "mmdf");
X
X  if(rmail) fprintf (f1, "\014\n0,unseen,,\n*** EOOH ***\n");
X  else if (mmdf) fprintf (f1, "\001\001\001\001\n");
X  else if(!isin("from ", line)) {
X    struct timeval tp;
X    struct timezone tzp;
X    
X    gettimeofday(&tp, &tzp);
X    fprintf(f1, "From Personal_Mail_Daemon  %s\n", ctime(&(tp.tv_sec)));
X  }
X  else fputs(line, f1);
X
X  /* After the first line, all postmark lines get prefixed */
X  while(fgets(line, LSIZE, f2) != NULL) {
X    if(isin("from ", line) && !rmail && !mmdf) {
X      if('>' != putc('>', f1)) goto wrerr;
X    }
X    if (isin ("\037", line) && rmail)
X      fprintf (f1, "^_%s", line + 1);
X    else
X      fputs(line, f1);
X  }
X
X  if (rmail) fprintf (f1, "\037");
X  else if (mmdf) fprintf (f1, "\001\001\001\001\n");
X
X  if(fflush(f1) == EOF || ferror(f1)) {
X  wrerr:
X    ftruncate(fd, (int) end);	/* Cast to int to get lint to shut up */
X    close(fd);
X    pperr++;
X    return;
X  }
X
X  close(fd);
X  return;
X}
X
X/* Barf out this message with argument as error string */
X/* NEVER RETURNS */
Xppbarfact(error)
Xchar *error;
X{
X    ppinc("Bounced messages");
X    ppinc("Total messages");
X    fprintf(stderr, "PMD error: %s\n", error);
X    unlink(pptemp);
X    exit(EX_UNAVAILABLE);
X}
X
X/* Cat standard input to file */
Xppfcat(file)
Xchar *file;
X{
X  FILE *f;
X  int fd;
X  int c;
X
X  DPRINT("Creating temp file...\n");
X  if((fd=open(file,  (O_CREAT | O_EXCL | O_WRONLY), ppmode|0600)) < 0) {
X    return(0);
X  }
X
X  DPRINT("Opening temp stream for write\n");
X  if((f=fdopen(fd, "w")) == (FILE *) NULL) {
X    close(fd);
X    return(0);
X  }
X
X  /* Everything ok, cat to file */
X  while((c=getchar()) != EOF)
X    if(c != putc(c, f)) goto wrerr;
X  if('\n' != putc('\n', f)) goto wrerr; /* Add a newline */
X
X  if(fflush(f) == EOF) {
X  wrerr:
X    DPRINT("oops... error on write/flush\n");
X    close(fd);
X    unlink(file);		/* This is a temporary, so clean up */
X    return(0);
X  }
X  
X  DPRINT("done with temp file.\n");
X  close(fd);
X  return(1);
X}
X
X/* Returns 1 iff s1 matches s2 (ignoring case) for the length of s1 */
Xisin(s1, s2)
Xchar *s1, *s2;
X{
X#ifdef DEBUG
X    printf("isin(%s, %s)\n", s1, s2);
X#endif DEBUG
X    for(; *s1; s1++, s2++) {
X	if(((*s1 | 0x20) != (*s2 | 0x20)) /* Ctype's tolower loses big */
X	   || *s2 == '\0'
X	   || *s2 == '\n') return(0);
X    }
X#ifdef DEBUG
X    puts("Returning true");
X#endif    
X    return(1);
X}
X
X/* Returns 1 iff ppline is a header line of type field which contains val */
Xpphead(field, val)
Xchar *field, *val;
X{
X    register char *p;
X
X#ifdef DEBUG
X    printf("pphead(%s, %s) applied to %s\n", field, val, ppline);
X#endif    
X    if(*field != '\0') {	/* If "", don't match field */
X	if(!isin(field, ppline)) return(0);	/* Not of type field */
X	p = ppline + strlen(field);
X	if(*p++ != ':') return(0);	/* Header field too long */
X    } else {			/* We don't care about field */
X	p = ppline;
X    }
X    for(; *p; p++) {
X	if(isin(val, p)) return(1); /* Bingo */
X    }
X    return(0);			/* You lose */
X}
X
X/*
X * Returns true iff ppline contains val
X */
Xppbody(val)
Xchar *val;
X{
X    register char *p;
X
X#ifdef DEBUG
X    printf("ppbody(%s) applied to %s\n", val, ppline);
X#endif
X    for(p = ppline; *p; p++) {
X	if(isin(val, p)) return(1);
X    }
X    return(0);
X}
X
X/*
X * Increment counter in ppdbfile associated with key
X * Returns 0 if successful, <0 otherwise.
X */
Xppinc(key)
Xchar *key;
X{
X#ifndef mips
X    DBM *db;
X#endif
X    int result, lock, lockcount;
X    unsigned long count;
X
X    /* We use ppdbfile itself for locking */
X    for(lockcount = 0;
X	(lock = open(ppdbfile, O_CREAT|O_EXCL, 0)) < 0;
X	lockcount++) {
X	    sleep(LOCKTIME);
X	    if(lockcount > MAXTIME) { /* Give up */
X		unlink(ppdbfile); /* File is garbage */
X		lock = open(ppdbfile, O_CREAT, 0);
X		break;
X	    }
X	}
X    /* No need to waste file descriptors */
X    if(lock > 0) close(lock);
X
X    /* We have the lock, update the database */
X#ifndef mips
X    if((db = dbm_open(ppdbfile, O_CREAT|O_RDWR, ppmode|0600)) == NULL) {
X	return(-1);
X    }
X#else
X    if (dbminit(ppdbfile) < 0) {
X	 return(-1);
X    }
X#endif
X    
X    /* Check the time settings */
X    ppkey.dptr = TIMEFIELD;
X    ppkey.dsize = strlen(ppkey.dptr)+1;
X    ppcount = dbm_fetch(db, ppkey);
X    if(ppcount.dptr == NULL) {
X	struct timeval tp;
X	struct timezone tzp;
X
X	gettimeofday(&tp, &tzp);
X	ppcount.dptr = ctime(&(tp.tv_sec));
X	ppcount.dsize = strlen(ppcount.dptr)+1;
X	dbm_store(db, ppkey, ppcount, DBM_INSERT);
X    }
X
X    ppkey.dptr = key;
X    ppkey.dsize = strlen(key)+1;
X    ppcount = dbm_fetch(db, ppkey);
X    if(ppcount.dptr == NULL) {
X	count = 0;
X    } else {
X	bcopy((char *)ppcount.dptr, (char *)&count, sizeof(count));
X    }
X    count++;
X    ppcount.dptr = (char *) &count;
X    ppcount.dsize = sizeof(count);
X    result = dbm_store(db, ppkey, ppcount, DBM_REPLACE);
X#ifndef mips
X    dbm_close(db);
X#endif
X    
X    /* Relase the lock */
X    unlink(ppdbfile);
X
X    return(result);
X}
X
X/* Fgethdr; like fgets, but eats newlines that are internal to a header line */
Xchar *fgethdr(line, size, stream)
Xchar *line;
Xint size;
XFILE *stream;
X{
X    char *cur;
X    int c,d;
X
X    if(feof(stream)) return(NULL);
X
X    for(cur=line; size>2; cur++, size--) {
X	if ((d=getc(stream)) == EOF) goto done;
X	switch(*cur = (char)d) {
X          case('\n'):
X	    if(cur != line && isspace(c=getc(stream)) && c != '\n') {
X	    	*cur=c;
X		break;
X	    } else {
X		ungetc(c, stream);
X	    	goto done;
X	    }
X    	  default:
X	    break;
X      }
X    }
X  done:
X    *cur++='\n';		/* For fgets compatibility */
X    *cur= '\0';
X    return(line);
X}
END_OF_FILE
if test 8710 -ne `wc -c <'runtime.c'`; then
    echo shar: \"'runtime.c'\" unpacked with wrong size!
fi
# end of 'runtime.c'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
