berg@messua.informatik.rwth-aachen.de (Stephen R. van den Berg) (06/17/91)
Submitted-by: Stephen R. van den Berg <berg@messua.informatik.rwth-aachen.de> Posting-number: Volume 20, Issue 51 Archive-name: procmail/part03 Environment: UNIX, sendmail Supersedes: procmail: Volume 17, Issue 31-32 ---- Cut Here and feed the following to sh ---- #!/bin/sh # This is part 03 of procmail # ============= procmail/retint.c ============== if test ! -d 'procmail'; then echo 'x - creating directory procmail' mkdir 'procmail' fi if test -f 'procmail/retint.c' -a X"$1" != X"-c"; then echo 'x - skipping procmail/retint.c (File already exists)' else echo 'x - extracting procmail/retint.c (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/retint.c' && X/************************************************************************ X * Collection of routines that return an int (sort of anyway :-) * X * * X * Copyright (c) 1990-1991, S.R.van den Berg, The Netherlands * X * The sources can be freely copied for non-commercial use. * X * #include "README" * X * * X * #include "STYLE" * X * * X ************************************************************************/ X#ifdef RCS Xstatic char rcsid[]="$Id: retint.c,v 2.0 1991/06/10 14:35:35 berg Rel $"; X#endif X#include "config.h" X#include "procmail.h" X#include "shell.h" X Xsetdef(name,contents)const char*const name,*const contents;{ X strcat(strcat(strcpy(sgetcp=buf2,name),"="),contents); X readparse(buf,sgetc,0);sputenv(buf);} X Xchar*backblock; /* what is to be recovered in case of filter failure */ Xlong backlen; /* its length */ Xpid_t pidfilt,pidchild; Xint pbackfd[2]; /* the emergency backpipe :-) */ X Xpipthrough(line,source,len)char*line,*source;const long len;{ X int pinfd[2],poutfd[2]; X rpipe(pbackfd);rpipe(pinfd);flaggerd=0; /* main pipes setup */ X if(!(pidchild=fork())){ /* create a sending procmail */ X backblock=source;backlen=len;signal(SIGTERM,stermchild); X signal(SIGINT,stermchild);signal(SIGHUP,stermchild); X signal(SIGQUIT,stermchild);rclose(rc);rclose(PRDI);rclose(PRDB); X rpipe(poutfd);rclose(STDOUT); X if(!(pidfilt=fork())){ /* create the filter */ X rclose(PWRO);rclose(PWRB);rdup(PWRI);rclose(PWRI);getstdin(PRDO); X callnewprog(line);} X rclose(PWRI);rclose(PRDO); X if(forkerr(pidfilt,line)){ X rclose(PWRO);stermchild();} X if(dump(PWRO,source,len)){ /* send in the text to be filtered */ X writeerr(line);stermchild();} X if(pwait&&waitfor(pidfilt)!=EX_OK){ /* check the exitcode of the filter */ X progerr(line);stermchild();} X rclose(PWRB);kill(thepid,SIGQUIT);exit(EX_OK);} /* tell parent to proceed */ X rclose(PWRI);rclose(PWRB);getstdin(PRDI); X if(forkerr(pidchild,"procmail")) X return 1; X return 0;} /* we stay behind to read back the filtered text */ X Xwaitflagger(){ /* wait for SIGQUIT from child */ X while(!flaggerd) X suspend();} /* to prevent polling */ X Xgrepin(expr,source,len,casesens)const char*const expr,*const source;long len;{ X pid_t pid;int poutfd[2];static const char*newargv[5]={0,"-e"}; X newargv[3]=casesens?(char*)0:"-i";*newargv=tgetenv(grep);newargv[2]=expr; X rpipe(poutfd); X if(!(pid=sfork())){ /* start grep */ X rclose(PWRO);rclose(rc);getstdin(PRDO);shexec(newargv);} X rclose(PRDO);len=dump(PWRO,source,len); X if(!forkerr(pid,*newargv)){ X if(len) X writeerr(*newargv); X else X return waitfor(pid)!=EX_OK;} /* did it grep? */ X return EX_UNAVAILABLE;} X Xwaitfor(pid)const pid_t pid;{int i; /* wait for a specific process */ X while(pid!=wait(&i)||(i&127)==127); X return i>>8&255;} X Xgetstdin(pip)const int pip;{ X rclose(STDIN);rdup(pip);rclose(pip);} X Xcallnewprog(newname)const char*const newname;{ X if(sh){const char*newargv[4]; /* should we start a shell? */ X yell(executing,newname);newargv[3]=0;newargv[2]=newname; X newargv[1]=tgetenv(shellflags);*newargv=tgetenv(shell);shexec(newargv);} X {register const char*p;int argc;const char**newargv; X argc=1;p=newname; /* If no shell, chop up the arguments ourselves */ X if(verbose){ X log(executing);log(oquote);goto no_1st_comma;} X do{ /* show chopped up command line */ X if(verbose){ X log(","); Xno_1st_comma: X log(p);} X while(*p++);} X while(argc++,*p!=TMNATE); X if(verbose) X log(cquote); X newargv=malloc(argc*sizeof*newargv);p=newname;argc=0; /* alloc argv array */ X do{ X newargv[argc++]=p; X while(*p++);} X while(*p!=TMNATE); X newargv[argc]=0;shexec(newargv);}} X Xwriteerr(line)const char*const line;{ X log("Error while writing to");logqnl(line);} X Xforkerr(pid,a)const pid_t pid;const char*const a;{ X if(pid==-1){ X log("Failed forking");logqnl(a);return 1;} X return 0;} X Xprogerr(line)const char*const line;{ X log("Program failure of");logqnl(line);} X Xopena(a)const char*const a;{ X lastfolder=cstr(lastfolder,a);yell("Opening",a); X return ropen(a,O_WRONLY|O_APPEND|O_CREAT,0666);} X Xyell(a,b)const char*const a,*const b;{ /* log if -d option set */ X if(verbose){ X log(a);logqnl(b);}} X Xunlock(lockp)const char**const lockp;{ X lcking=1; X if(*lockp){ X yell("Unlocking",*lockp); X if(unlink(*lockp)){ X log("Couldn't unlock");logqnl(*lockp);} X free(*lockp);*lockp=0;} X if(nextexit==1){ /* make sure we are not inside terminate already */ X log(newline);terminate();} X lcking=0;} X Xnomemerr(){ X log("Out of memory\nbuffer 0: \"");buf[linebuf-1]=buf2[linebuf-1]='\0'; X log(buf);log("\"\nbuffer 1:");logqnl(buf2);retval=EX_OSERR;terminate();} X Xlogqnl(a)const char*const a;{ X log(oquote);log(a);log(cquote);} X Xnextrcfile(){const char*p; /* next rcfile specified on the command line */ X while(p= *gargv){ X gargv++; X if(!strchr(p,'=')){ X rcfile=p;return 1;}} X return 0;} X Xrclose(fd)const int fd;{int i; /* a sysV secure close (signal immune) */ X while((i=close(fd))&&errno==EINTR); X return i;} X Xrwrite(fd,a,len)const int fd,len;void*const a;{int i; /* a sysV secure write */ X while(0>(i=write(fd,a,(size_t)len))&&errno==EINTR); X return i;} X Xrread(fd,a,len)const int fd,len;void*const a;{int i; /* a sysV secure read */ X while(0>(i=read(fd,a,(size_t)len))&&errno==EINTR); X return i;} X Xropen(name,mode,mask)const char*const name;const mode_t mask;{int i,r; X for(r=noresretry;0>(i=open(name,mode,mask));) /* a sysV secure open */ X if(errno!=EINTR) X if(!((errno==EMFILE||errno==ENFILE)&&(r<0||r--))) X break; /* survives a temporary "file table full" condition */ X return i;} X Xrdup(p)const int p;{int i,r; X for(r=noresretry;0>(i=dup(p));) /* catch "file table full" */ X if(!((errno==EMFILE||errno==ENFILE)&&(r<0||r--))) X break; X return i;} X Xrpipe(fd)int fd[2];{int i,r; X for(r=noresretry;0>(i=pipe(fd));) /* catch "file table full" */ X if(!((errno==EMFILE||errno==ENFILE)&&(r<0||r--))){ X *fd=fd[1]= -1;break;} X return i;} X Xlockit(name,lockp)char*name;const char**const lockp;{int i;struct stat stbuf; X unlock(lockp); /* unlock any previous lockfile FIRST */ X if(!*name) X return; X for(lcking=1;;){ /* to prevent deadlocks (I hate deadlocks) */ X yell("Locking",name); X if(!NFSxopen(name)){ X *lockp=tstrdup(name); /* lock acquired, hurray! */ Xterm: if(nextexit){ X log(whilstwfor);log("lockfile");logqnl(name);terminate();} X lcking=0;return;} /* check if it's time for a lock override */ X if(errno==EEXIST&&!stat(name,&stbuf)&&!stbuf.st_size){time_t t; X if(locktimeout&&(t=time((time_t*)0),!stat(name,&stbuf))) /* from stat */ X if(locktimeout<t-stbuf.st_mtime){ /* till unlink should be atomic */ X if(unlink(name)){ /* but can't guarantee that */ X log("Forced unlock denied on");logqnl(name);} X else{ X log("Forcing lock on");logqnl(name);suspend();}}} X else{ /* maybe filename too long, shorten and retry */ X if(0<(i=strlen(name)-1)&&!strchr(dirsep,(name[i-1]))){ X name[i]='\0';continue;} X log("Lockfailure on");logqnl(name);return;} X sleep(DEFlocksleep,locksleep); X if(nextexit) X goto term;}} X Xlcllock(){ /* lock a local file (if need be) */ X if(locknext) X if(tolock) X lockit(tolock,&loclock); X else X lockit(strcat(buf2,tgetenv(lockext)),&loclock);} X Xterminate(){ X nextexit=2; /* prevent multiple invocations of terminate */ X if(retval!=EX_OK) X log("Mail bounced\n"); X unlock(&loclock);unlock(&globlock);exit(retval);} X Xsuspend(){ X sleep((unsigned)suspendv);} X Xskipspace(){ X while(testb(' ')||testb('\t'));} X Xsgetc(){ /* a fake fgetc for a string */ X return *sgetcp?*(uchar*)sgetcp++:EOF;} X Xskipped(x)const char*const x;{ X log("Skipped");logqnl(x);} X Xstatic uchar rcbuf[STDBUF],*rcbufp,*rcbufend; /* buffers for custom stdio */ Xstatic ungetb; /* pushed back char */ X Xbopen(name)const char*const name;{ /* my fopen */ X rcbufp=rcbufend=0;ungetb= -1;yell("Rcfile:",name); X return rc=ropen(name,O_RDONLY);} X Xgetbl(p)char*p;{int i;char*q; /* my gets */ X for(q=p;;){ X switch(i=getb()){ X case '\n':case EOF: X *p='\0';return p!=q;} /* did we read anything at all? */ X *p++=i;}} X Xgetb(){ /* my fgetc */ X if(ungetb>=0){int i; /* anything pushed back? */ X i=ungetb;ungetb= -1;return i;} X if(rcbufp==rcbufend){ X rcbufend=rcbuf+rread(rc,rcbufp=rcbuf,STDBUF);} /* refill */ X return rcbufp<rcbufend?*rcbufp++:EOF;} X Xtestb(x)const int x;{int i; /* fgetc that only succeeds if it matches */ X if((i=getb())==x) X return 1; X ungetb=i;return 0;} X Xalphanum(c)const int c;{ X return c>='0'&&c<='9'||c>='a'&&c<='z'||c>='A'&&c<='Z'||c=='_';} X /* open file or new file in directory */ Xdeliver(boxname)char*const boxname;{struct stat stbuf; X strcpy(buf,boxname); /* boxname can be found back in buf */ X return stat(buf,&stbuf)||!S_ISDIR(stbuf.st_mode)?opena(buf):dirmail();} X X#include "exopen.h" X /* an NFS secure exclusive file open */ XNFSxopen(name)char*name;{char*p,*q;int j= -1,i; X for(q=name;p=strpbrk(q,dirsep);q=p+1); /* find last DIRSEP */ X i=q-name;strncpy(p=malloc(i+UNIQnamelen),name,i); X if(unique(p,p+i,0)) X j=myrename(p,name); /* try and rename it, fails if nonexclusive */ X free(p);return j;} SHAR_EOF chmod 0644 procmail/retint.c || echo 'restore of procmail/retint.c failed' Wc_c="`wc -c < 'procmail/retint.c'`" test 9348 -eq "$Wc_c" || echo 'procmail/retint.c: original size 9348, current size' "$Wc_c" fi # ============= procmail/shell.h ============== if test -f 'procmail/shell.h' -a X"$1" != X"-c"; then echo 'x - skipping procmail/shell.h (File already exists)' else echo 'x - extracting procmail/shell.h (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/shell.h' && X/*$Id: shell.h,v 2.0 1991/06/10 14:39:08 berg Rel $*/ X X#define malloc(n) tmalloc((size_t)(n)) X#define realloc(p,n) trealloc(p,(size_t)(n)) X#define tmemmove(t,f,n) memmove(t,f,(size_t)(n)) SHAR_EOF chmod 0644 procmail/shell.h || echo 'restore of procmail/shell.h failed' Wc_c="`wc -c < 'procmail/shell.h'`" test 188 -eq "$Wc_c" || echo 'procmail/shell.h: original size 188, current size' "$Wc_c" fi # ============= procmail/INSTALL ============== if test -f 'procmail/INSTALL' -a X"$1" != X"-c"; then echo 'x - skipping procmail/INSTALL (File already exists)' else echo 'x - extracting procmail/INSTALL (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/INSTALL' && XTo install procmail, lockfile and formail: edit Makefile accordingly (if need Xbe, config.h as well) and type 'make install'. X X'make install' will: X - execute autoconf (a shell script that repeatedly calls the C compiler X to determine if certain features/symbols are supported), which will X create a file named autoconf.h X - compile the *.c files, create the three binaries: procmail, lockfile, X formail X - copy these binaries to $(BINDIR) X - copy the man pages to $(MANDIR) X X XMinimal requirements: X Xprocmail must be installed. Xlockfile needs only to be installed if you plan to read several mailboxes X with one of the standard mailers. Xformail needs only to be installed if mail sometimes arrives in nonstandard X mailbox format (or if you want to generate auto replies, split up X mailboxes/digests etc., see the man page of formail for more info). X X XIf things don't compile automagically, I suggest you take a look at: Xautoconf, autoconf.h, config.h, includes.h X XThe program is supposed to be fully ANSI and K&R compliant. X XIf you run procmail by hand and pipe in some sample mail, then make Xsure that if you kill procmail, you use "kill pid" and NOT "kill -9 pid". XShould procmail seem to hang, check if the $LOCKFILE is still present. XIf you kill procmail with "kill pid" it will clean up the $LOCKFILE Xitself. X XSuggested command line for a test run: "procmail -d LOGFILE=/dev/tty" X(now type in a mail message). X XEvery user that wants to use procmail should have a .forward and a X.procmailrc file in his HOME directory. For starters, you can look Xat the supplied example files in "examples". X(BTW, be sure to make .forward *world* readable). X XFor more info about the program, see the man page. X X------------------------------------------------------------------------------ X-----------------------Frequently Asked Questions----------------------------- X------------------------------------------------------------------------------ X X1. Why does the logfile have lines containing "file -i not found"? X X Your egrep doesn't understand the -i option (ignore case). Either X use a different grep (e.g. set GREP to /bin/grep), or be sure X to specify the 'D' option on every recipe. X X2. I installed procmail (i.e. typed 'make install'), but how am I supposed to X use it? When I type procmail on the command line it simply does nothing. X X You're not supposed to start procmail from the command line. X Be sure to have a .forward and a .procmailrc file in your home X directory (see the examples subdirectory or the man page). SHAR_EOF chmod 0644 procmail/INSTALL || echo 'restore of procmail/INSTALL failed' Wc_c="`wc -c < 'procmail/INSTALL'`" test 2542 -eq "$Wc_c" || echo 'procmail/INSTALL: original size 2542, current size' "$Wc_c" fi # ============= procmail/Makefile ============== if test -f 'procmail/Makefile' -a X"$1" != X"-c"; then echo 'x - skipping procmail/Makefile (File already exists)' else echo 'x - extracting procmail/Makefile (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/Makefile' && X#$Id: Makefile,v 2.0 1991/06/10 14:39:08 berg Rel $ X X# change BASENAME to your home directory if need be XBASENAME = /usr/local X XBINDIR = $(BASENAME)/bin XMANDIR = $(BASENAME)/man/man1 X X######################################################################## X# Only edit below this line if you *think* you know what you are doing # X######################################################################## X X# Directory for the standard include files XUSRINCLUDE = /usr/include X XOCFLAGS = -O XOLDFLAGS = -s X XCFLAGS = $(OCFLAGS) -I$(USRINCLUDE) -I./include XLDFLAGS = $(OLDFLAGS) X XCC = cc XO = o XRM= rm -f X XBINS=procmail lockfile formail X XOBJ=procmail.$(O) nonint.$(O) goodies.$(O) X XDEP=shell.h procmail.h config.h X Xall: autoconf.h $(BINS) X Xprocmail: $(OBJ) exopen.$(O) common.$(O) retint.$(O) X $(CC) $(CFLAGS) -o procmail $(OBJ) exopen.$(O) common.$(O) \ Xretint.$(O) $(LDFLAGS) X Xlockfile: lockfile.$(O) exopen.$(O) X $(CC) $(CFLAGS) -o lockfile lockfile.$(O) exopen.$(O) ${LDFLAGS} X Xformail: formail.$(O) common.$(O) X $(CC) $(CFLAGS) -o formail formail.$(O) common.$(O) ${LDFLAGS} X X_autotst: _autotst.c X $(CC) $(CFLAGS) -o _autotst _autotst.c $(LDFLAGS) X Xautoconf.h: autoconf Makefile X /bin/sh autoconf X X$(OBJ): $(DEP) X Xretint.$(O): $(DEP) exopen.h X Xexopen.$(O): config.h includes.h exopen.h X Xformail.$(O): config.h includes.h shell.h X Xlockfile.$(O): config.h includes.h X Xcommon.$(O): includes.h shell.h X Xprocmail.h: includes.h X touch procmail.h X Xincludes.h: autoconf.h X touch includes.h X X.c.$(O): X $(CC) $(CFLAGS) -c $*.c X Xinstall: all X chmod 755 $(BINS) X cp $(BINS) $(BINDIR) X chmod 644 man/procmail.1 man/lockfile.1 man/formail.1 X cp man/procmail.1 man/lockfile.1 man/formail.1 $(MANDIR) X Xclean: X $(RM) $(OBJ) common.$(O) lockfile.$(O) exopen.$(O) retint.$(O) \ Xformail.$(O) $(BINS) autoconf.h _autotst* SHAR_EOF chmod 0644 procmail/Makefile || echo 'restore of procmail/Makefile failed' Wc_c="`wc -c < 'procmail/Makefile'`" test 1801 -eq "$Wc_c" || echo 'procmail/Makefile: original size 1801, current size' "$Wc_c" fi # ============= procmail/autoconf ============== if test -f 'procmail/autoconf' -a X"$1" != X"-c"; then echo 'x - skipping procmail/autoconf (File already exists)' else echo 'x - extracting procmail/autoconf (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/autoconf' && X#!/bin/sh X#$Id: autoconf,v 2.1 1991/06/11 12:59:16 berg Rel $ X XSHELL=/bin/sh || exec /bin/sh <autoconf # we're in a csh, feed myself to sh X XACONF=autoconf.h Xtrap "rm $ACONF;exit 1" 1 2 3 15 Xcat >$ACONF <<HERE X/* This file was automagically generated by autoconf */ X XHERE X X# WARNING: in ./include/stdlib.h the const keyword is already used! X# hence the const test has to precede all others. X Xcat >_autotst.c <<HERE Xmain(){const int i;return 0;} XHERE X Xecho 'Testing for const' Xif make _autotst >/dev/null 2>&1 Xthen X: Xelse X echo '#define const' >>$ACONF Xfi Xrm -f _autotst _autotst.o X Xcat >_autotst.c <<HERE Xmain(){volatile int i;return 0;} XHERE X Xecho 'Testing for volatile' Xif make _autotst >/dev/null 2>&1 Xthen X: Xelse X echo '#define volatile' >>$ACONF Xfi Xrm -f _autotst _autotst.o X Xcat >_autotst.c <<HERE Xmain(){int i;i= -1;return i=-i;} XHERE X Xecho 'Testing for compiler age' Xmake _autotst >_autotst.rrr 2>&1 X Xif _autotst Xthen X echo 'Aha, this one is genuine antique!' X echo '#define void char' >>$ACONF Xfi Xrm -f _autotst _autotst.o X X Xcat >_autotst.c <<HERE X#include "includes.h" Xmain(){int i; X void*p; X i=(size_t)1; X i+=(pid_t)1; X i+=(time_t)1; X i+=(mode_t)1; X return 0;} XHERE X Xecho 'Testing for void*,size_t,pid_t,time_t,mode_t' Xmake _autotst >_autotst.rrr 2>&1 Xrm -f _autotst _autotst.o X Xgrepfor(){ Xif fgrep "$1" _autotst.rrr >/dev/null Xthen X echo "$2" >>$ACONF Xfi X} X Xgrepfor void '#define void char' Xgrepfor size_t 'typedef unsigned size_t;' Xgrepfor pid_t 'typedef int pid_t;' Xgrepfor time_t 'typedef long time_t;' Xgrepfor mode_t 'typedef int mode_t;' X Xcat >_autotst.c <<HERE X#include "includes.h" Xmain(){char a[1]; X setpwent();endpwent();memmove(a,"t",1);bcopy("t",a,1);strstr(a,"t");return 0;} XHERE X Xecho 'Testing for memmove & strstr' Xmake _autotst >_autotst.rrr 2>&1 Xrm -f _autotst _autotst.o X Xif fgrep memmove _autotst.rrr >/dev/null Xthen X echo '#define NOmemmove' >>$ACONF X grepfor bcopy '#define NObcopy' Xfi Xgrepfor strstr '#define NOstrstr' Xgrepfor setpwent '#define setpwent()' Xgrepfor endpwent '#define endpwent()' X Xrm -f _autotst* X Xecho -----------------------------autoconf.h----------------------------------- Xcat autoconf.h Xecho -------------------------------------------------------------------------- SHAR_EOF chmod 0644 procmail/autoconf || echo 'restore of procmail/autoconf failed' Wc_c="`wc -c < 'procmail/autoconf'`" test 2224 -eq "$Wc_c" || echo 'procmail/autoconf: original size 2224, current size' "$Wc_c" fi # ============= procmail/exopen.h ============== if test -f 'procmail/exopen.h' -a X"$1" != X"-c"; then echo 'x - skipping procmail/exopen.h (File already exists)' else echo 'x - extracting procmail/exopen.h (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/exopen.h' && X/*$Id: exopen.h,v 2.0 1991/06/10 14:39:08 berg Rel $*/ X#define SERIALchars 3 X#define UNIQnamelen (1+SERIALchars+HOSTNAMElen+1) X#define SERIALmask ((1L<<6*SERIALchars)-1) SHAR_EOF chmod 0644 procmail/exopen.h || echo 'restore of procmail/exopen.h failed' Wc_c="`wc -c < 'procmail/exopen.h'`" test 170 -eq "$Wc_c" || echo 'procmail/exopen.h: original size 170, current size' "$Wc_c" fi # ============= procmail/formail.c ============== if test -f 'procmail/formail.c' -a X"$1" != X"-c"; then echo 'x - skipping procmail/formail.c (File already exists)' else echo 'x - extracting procmail/formail.c (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/formail.c' && X/************************************************************************ X * formail.c a mail (re)formatter * X * * X * Seems to be relatively bug free. * X * * X * Copyright (c) 1990-1991, S.R.van den Berg, The Netherlands * X * The sources can be freely copied for non-commercial use. * X * #include "README" * X * * X * #include "STYLE" * X * * X ************************************************************************/ X#ifdef RCS Xstatic char rcsid[]="$Id: formail.c,v 2.3 1991/06/12 10:50:21 berg Rel $"; X#endif Xstatic char rcsdate[]="$Date: 1991/06/12 10:50:21 $"; X#include "config.h" /* I know, overkill, only need BinSh */ X#include "includes.h" X X#define BSIZE 4096 X X#define FROM "From " X#define UNKNOWN "foo@bar" X X#define Re (re+1) X#define Nextchar(x) do{x=getchar();if(feof(stdin))goto foundeof;}while(0) X#define putssn(a,l) tputssn(a,(size_t)(l)) X#define putcs(a) (errout=putc(a,mystdout)) X#define PRDO poutfd[0] X#define PWRO poutfd[1] X Xstatic const char From[]=FROM,replyto[]="Reply-To:",Fromm[]="From:", X returnpath[]="Return-Path",sender[]="Sender:",outofmem[]="Out of memory\n", X subject[]="Subject:",re[]=" Re:",couldntw[]="Couldn't write to stdout", X references[]="References:",messageid[]="Message-ID:",Date[]="Date:"; Xconst char binsh[]=BinSh; Xstatic struct {const char*const head;const int len,wrepl;}sest[]={ X {sender,STRLEN(sender),0},{replyto,STRLEN(replyto),4}, X {Fromm,STRLEN(Fromm),2},{returnpath,STRLEN(returnpath),1}}; Xstatic struct {const char*const headr;const int lenr;size_t offset;}rex[]={ X {subject,STRLEN(subject)},{references,STRLEN(references)}, X {messageid,STRLEN(messageid)}}; X#define subj rex[0] X#define refr rex[1] X#define msid rex[2] Xstatic struct {const char*const hedr;const int lnr;}cdigest[]={ X {Fromm,STRLEN(Fromm)},{Date,STRLEN(Date)},{subject,STRLEN(subject)}}; X#define mxl(a,b) mx(STRLEN(a),STRLEN(b)) X#define dig_HDR_LEN mx(mxl(From,Fromm),mxl(Date,subject)) Xstatic errout,oldstdout; Xstatic pid_t child= -1; Xstatic FILE*mystdout; Xstatic size_t nrskip,nrtotal= -1; X X#ifdef NOstrstr Xchar*strstr(whole,part)const char*whole,*const part;{size_t len; X if(!(len=strlen(part))) X return(char*)whole; X while(*whole&&strncmp(whole,part,len)) X ++whole; X return *whole?(char*)whole:(char*)0;} X#endif X Xvoid*tmalloc(len)const size_t len;{void*p; X if(p=malloc(len)) X return p; X log(outofmem);exit(EX_OSERR);} X Xvoid*trealloc(old,len)void*old;const size_t len;{ X if(old=realloc(old,len)) X return old; X log(outofmem);exit(EX_OSERR);} X X#include "shell.h" X Xmain(argc,argv)const char*const argv[];{time_t t; X int i,lastm,nowm,thelen=0,split=0,force=0,bogus=1,every=0,areply=0, X trust=0,digest=0,nowait=0; X size_t buflen,p=0,lnl=0,thename,ll; X char*buf,*chp; X while(*++argv){ X if((lastm= **argv)=='+') X goto number; X else if(lastm!='-') X goto usg; X for(i=1;;){ X switch((*argv)[i++]){ X case 't':trust=1;continue; /* trust the sender for valid headers */ X case 'r':areply=1;continue; /* generate a reply */ X case 'f':force=1;continue; /* accept arbitrary format */ X case 'e':every=1;continue; /* split on every From */ X case 'd':digest=1;continue; /* split up digests */ X case 'n':nowait=1;continue; /* don't wait for the programs */ X case 's':split=1;bogus=0; X if(!(*argv++)[i]) X goto parsedoptions; X goto usg; Xnumber: default: X if((*argv)[1]-'0'>(unsigned)9){ Xusg: log("Usage: formail [+nnn] [-nnn] [-bfrtned] \ X[-s command argument ...]\n");return EX_USAGE;} X ll=strtol((*argv)+1,(char**)0,10); X if(lastm=='+') X nrskip=ll; X else X nrtotal=ll; X break; X case 'b':bogus=0;continue; /* leave bogus Froms intact */ X case '\0':;} X break;}} Xparsedoptions: X mystdout=stdout; X if(split){ X oldstdout=dup(STDOUT);fclose(stdout);startprog(argv);} X while('\n'==(i=getchar())); X buf=malloc(buflen=BSIZE);t=time((time_t*)0); X for(;;){ /* start parsing the header */ X if((buf[p++]=i)=='\n'){ X chp=buf+lnl;i=maxindex(rex); X while(strnicmp(rex[i].headr,chp,rex[i].lenr)&&i--); X if(i>=0) /* found anything already? */ X rex[i].offset=lnl+rex[i].lenr; X else if(!strncmp(From,chp,STRLEN(From))){ X if(!lnl){ /* was the real "From " line */ X if(!areply) X goto endofheader; X nowm=trust?1:3/*wreply*/;ll=lnl+STRLEN(From);goto foundfrom;} X if(bogus){ X tmemmove(chp+1,chp,p++-lnl);*chp='>';}} /* disarm */ X else{ X i=maxindex(sest); X do X if(!strnicmp(sest[i].head,chp,sest[i].len)){ X nowm=areply?sest[i].wrepl:i;ll=lnl+sest[i].len; Xfoundfrom: buf[p]='\0'; X if(chp=strchr(buf+ll,'<')) /* extract the address */ X ll=chp-buf+1; X chp=buf+(ll+=strspn(buf+ll," \t")); X if((i=strcspn(chp,">(\n \t"))&&(!thelen||nowm>lastm)){ X thename=ll;thelen=i; X lastm=strstr(chp,".UUCP")?nowm-maxindex(sest)-1:nowm;} X break;} X while(i--); X if(lnl==p-1) X break;} /* end of header reached */ X lnl=p;} X if(p>=buflen-2) X buf=realloc(buf,buflen+=BSIZE); Xredigest: X i=getchar(); X if(feof(stdin)) X i='\n';} /* make sure the header ends with 2 newlines */ X if(areply||!force){ X putss(areply?"To: ":From); X if(thelen) /* found any sender address? */ X putssn(buf+thename,thelen); X else X putss(UNKNOWN); X if(areply){ X putcs('\n'); X if(subj.offset){ /* any Subject: ? */ X putss(subject);chp=buf+subj.offset; X if(strnicmp(chp+strspn(chp," "),Re,STRLEN(Re))) X putss(re); /* no Re: , add one ourselves */ X nlputss(chp);} X if(refr.offset||msid.offset){ /* any Message-ID: or References: ? */ X putss(references); X if(refr.offset){ X chp=buf+refr.offset; X putssn(chp,strchr(chp,'\n')-chp+!msid.offset);} X if(msid.offset){ X nlputss(buf+msid.offset);putss("In-Reply-To:"); X nlputss(buf+msid.offset);}} X putcs('\n'); X while(getchar(),!feof(stdin));return EX_OK;} X putcs(' ');putss(ctime(&t));} Xendofheader: X putssn(buf,p);p=0;lnl=1; X if(!bogus&&!split) X for(;;putcs(i)) X Nextchar(i); X for(;;){ /* continue the quest */ X do{ /* read line until not From */ X if(p==buflen-1) X buf=realloc(buf,++buflen); X Nextchar(i=buf[p]); X if(++p==STRLEN(From)) X if(!strncmp(From,buf,STRLEN(From))){ X if(bogus){ X putcs('>');break;} /* disarm */ X else if(every) X goto splitit; X else if(split&&lnl) X lnl=2;} /* mark line as possible postmark */ X if(lnl==1&&digest){ X thelen=maxindex(cdigest); X do /* check for new digest header */ X if(p==cdigest[thelen].lnr&&!strncmp(buf,cdigest[thelen].hedr,p)){ X lnl=thelen=0;goto splitit;} /* pretend we started over */ X while(thelen--);}} X while(i!='\n'&&(lnl==2||p<dig_HDR_LEN)); X if(lnl==2){ X buf[p]='\0'; /* perform more thorough check for postmark */ X for(i=STRLEN(From)-1;buf[++i]==' ';); X if(ll=strcspn(buf+i,"\n \t")){ X i+=ll; X if(ll=strspn(buf+i," ")&&(ll=buf[i+ll])!='\t'&&ll!='\n'){ Xsplitit: if(fclose(mystdout)==EOF||errout==EOF){ X log(couldntw);log(", continuing...\n");split= -1;} X if(!nowait) X waitforit(); X startprog(argv); X if(!lnl) /* digest split? */ X goto redigest; X i='\n';}}} X lnl=p==1;putssn(buf,p);p=0; X if(i!='\n') X do Nextchar(i); X while(putcs(i),i!='\n');} Xfoundeof: X putssn(buf,p); X if(fclose(mystdout)==EOF||errout==EOF){ X log(couldntw);return EX_IOERR;} X child= -1;waitforit();return split<0?EX_IOERR:EX_OK;} /* wait for everyone */ X Xstrnicmp(a,b,l)register const char*a,*b;register unsigned l;{int i,j; X if(l) /* case insensitive strncmp */ X do{ X while(*a&&*a==*b&&--l) X ++a,++b; X if(!l) X break; X if((i= *a++)>='A'&&i<='Z') X i+='a'-'A'; X if((j= *b++)>='A'&&j<='Z') X j+='a'-'A'; X if(j!=i) X return i>j?1:-1;} X while(i&&j&&--l); X return 0;} X Xlog(a)const char*const a;{ X fputs(a,stderr);} X Xlogqnl(a)const char*a;{ X log(" \"");log(a);log("\"\n");} X Xnlputss(a)const char*const a;{ X putssn(a,strchr(a,'\n')+1-a);} X Xputss(a)const char*a;{ X while(*a) X putcs(*a++);} X Xtputssn(a,l)const char*a;size_t l;{ X while(l--) X putcs(*a++);} X Xstartprog(argv)const char*const*const argv;{int poutfd[2]; X if(!nrtotal) X goto squelch; X if(nrskip){ X --nrskip; Xsquelch: X if(!(mystdout=fopen(DevNull,"a"))) X goto nofild; X return;} X if(nrtotal>0) X --nrtotal; X dup(oldstdout);pipe(poutfd); X if(!(child=fork())){ X close(oldstdout);close(PWRO);fclose(stdin);dup(PRDO);close(PRDO); X shexec(argv);} X close(STDOUT);close(PRDO); X if(STDOUT!=dup(PWRO)||!(mystdout=fdopen(STDOUT,"a"))){ Xnofild: X log("File table full\n");exit(EX_OSERR);} X close(PWRO); X if(0>child){ X log("Can't fork\n");exit(EX_OSERR);}} X Xwaitforit(){int i; X while(child!=wait(&i)||(i&127)==127);} SHAR_EOF chmod 0644 procmail/formail.c || echo 'restore of procmail/formail.c failed' Wc_c="`wc -c < 'procmail/formail.c'`" test 8833 -eq "$Wc_c" || echo 'procmail/formail.c: original size 8833, current size' "$Wc_c" fi # ============= procmail/config.h ============== if test -f 'procmail/config.h' -a X"$1" != X"-c"; then echo 'x - skipping procmail/config.h (File already exists)' else echo 'x - extracting procmail/config.h (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/config.h' && X/*$Id: config.h,v 2.0 1991/06/10 14:35:35 berg Rel $*/ X X/*#define console "/dev/console" /* uncomment if you want procmail to X use the console (or any other X terminal) to print any error messages that could not be dumped in the X "logfile". (Only recommended for debugging purposes, if you have X trouble creating a "logfile") */ X X/************************************************************************ X * Only edit below this line if you *think* you know what you are doing * X ************************************************************************/ X X#define DEFlinebuf 2048 /* default max expanded line length */ X#define BLKSIZ 16384 /* blocksize while reading/writing */ X#define STDBUF 1024 /* blocksize for emulated stdio */ X#define HOSTNAMElen 8 /* nr of significant chararacters for HOST */ X#define PROCMAILRC ".procmailrc" X#define DEFsuspend 16 /* multi-purpose 'idle loop' period */ X#define DEFlocksleep 8 X#define TOkey "^TO" X#define TOsubstitute "^(To|Cc|Apparently-To):.*" X#define DEFshellmetas "&()[]*?|<>~;:" /* never put '$' in here */ X#define DEFmaildir "$HOME" X#define DEFdefault "$MAILDIR/.mailbox" X#define DEFmsgprefix "msg." X#define DEForgmail "/usr/spool/mail/$USER" X#define DEFgrep "/usr/bin/egrep" X#define DEFsendmail "/usr/lib/sendmail" X#define DEFlockext ".lock" X#define DEFshellflags "-c" X#define DEFlocktimeout 3600 /* defaults to one hour */ X#define DEFnoresretry 2 /* default nr of retries if no resources left */ X X#define NRRECFLAGS (9+1) X#define RECFLAGS " %9[HBDIhbfcws] %[^\n]" /* 'I' and 's' are obsolete */ X#define BinSh "/bin/sh" X#define Tmp "/tmp" X#define DevNull "/dev/null" X#define DIRSEP "/" /* directory separator symbols, the */ X /* last one should be the most common one */ X X/* the regular expression we use to look for bogus headers X (which I took from /usr/ucb/mail) is: X "\n\nFrom +[^\t\n ]+ +[^\n\t]" */ X X#define FromSCAN "From%*[ ]%*[^\t\n ]%*[ ]%1[^\n\t]" X#define EOFName "%[^ \t\n#'\")};]" X X#define VERSIONOPT 'v' /* option to display version */ X#define PRESERVOPT 'p' X#define DEBUGOPT 'd' X X#define MINlinebuf 128 /* minimal LINEBUF length (don't change this) */ X#define SFROM "From " X#define SFROM_S "From%*[ ]%74[^\n]" X#define SSUBJECT " Subject:" X#define SSUBJECT_S \ X "%*1[Ss]%*1[Uu]%*1[Bb]%*1[Jj]%*1[Ee]%*1[Cc]%*1[Tt]:%70[^\n]" X#define FOLDER " Folder: " X#define LENtSTOP 9 /* tab stop at which message length will be logged */ X X#define TABCHAR "\t" X#define TABWIDTH 8 SHAR_EOF chmod 0644 procmail/config.h || echo 'restore of procmail/config.h failed' Wc_c="`wc -c < 'procmail/config.h'`" test 2501 -eq "$Wc_c" || echo 'procmail/config.h: original size 2501, current size' "$Wc_c" fi # ============= procmail/procmail.h ============== if test -f 'procmail/procmail.h' -a X"$1" != X"-c"; then echo 'x - skipping procmail/procmail.h (File already exists)' else echo 'x - extracting procmail/procmail.h (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/procmail.h' && X/*$Id: procmail.h,v 2.0 1991/06/10 14:35:35 berg Rel $*/ X X#include "includes.h" X Xtypedef unsigned char uchar; X X#ifndef console X#define console devnull X#endif X X#define XTRAlinebuf 2 /* surplus of LINEBUF (see readparse()) */ X#define TMNATE '\377' /* terminator (see readoarse()) */ X X#define PRDO poutfd[0] X#define PWRO poutfd[1] X#define PRDI pinfd[0] X#define PWRI pinfd[1] X#define PRDB pbackfd[0] X#define PWRB pbackfd[1] X#define LENoffset (TABWIDTH*LENtSTOP) X#define MAXfoldlen (LENoffset-STRLEN(sfolder)-1) X#define MCDIRSEP (dirsep+STRLEN(dirsep)-1) /* most common DIRSEP */ X Xstruct varval{const char*const name;long val;}; X#define locksleep (strenvvar[0].val) X#define locktimeout (strenvvar[1].val) X#define suspendv (strenvvar[2].val) X#define noresretry (strenvvar[3].val) X#define MAXvarvals maxindex(strenvvar) X X#ifndef MAIN Xextern char*buf,*buf2,*globlock,*loclock,*tolock,*lastfolder; Xextern const char grep[],shellflags[],shell[],lockext[],newline[],binsh[], X unexpeof[],shellmetas[],*const*gargv,*sgetcp,*rcfile,dirsep[],msgprefix[], X devnull[],executing[],oquote[],cquote[],whilstwfor[]; Xextern struct varval strenvvar[]; Xextern long lastdump; Xextern sh,pwait,retval,lcking,locknext,verbose,linebuf,rc; Xextern volatile flaggerd,nextexit; Xextern pid_t thepid; X#endif X X#ifdef NOmemmove Xvoid*memmove(); X#endif X Xvoid*tmalloc(),*trealloc(); Xpid_t sfork(); Xvoid sterminate(),stermchild(),flagger(),errgrandchild(); Xlong dump(),pipin(),renvint(); Xchar*readdyn(),*fromprog(),*cat(),*findnl(),*tstrdup(),*cstr(); Xconst char*tgetenv(),*hostname(); Xint sgetc(),getb(); SHAR_EOF chmod 0644 procmail/procmail.h || echo 'restore of procmail/procmail.h failed' Wc_c="`wc -c < 'procmail/procmail.h'`" test 1582 -eq "$Wc_c" || echo 'procmail/procmail.h: original size 1582, current size' "$Wc_c" fi # ============= procmail/HISTORY ============== if test -f 'procmail/HISTORY' -a X"$1" != X"-c"; then echo 'x - skipping procmail/HISTORY (File already exists)' else echo 'x - extracting procmail/HISTORY (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/HISTORY' && X1990/12/07: v1.00 X First release (after 2 weeks of coding and testing) X1990/12/12: v1.01 X Added fsync to procmail.c X Removed longjmp in lockfile.c out of the signal handler X (not portable, so I'm told) X1991/02/04: v1.02 X Changes to procmail.c: X Added physical-write-error check X Altered egrep invocation (left out the -s flag, not supported X on all machines; this didn't change functionality though) X Avoided the 'dirty' allocation of one more byte than needed by X rewriting the bogus_header_replace routine X Made the search for bogus headers more robust X Added sysV lines in Makefile X1991/02/13: v1.10 X Changes to procmail.c: X Fixed slight error in parsing the recipe when 'h' and 'b' where X specified X Started using the official exit codes X Made sure that the procmail is not influenced by falsely X set environment variables like LOCKSLEEP X1991/02/21: v1.20 X Changes to procmail.c: X Removed library conflict on some machines for 'locking' X Added uname call as alternative for gethostname X Changed name of SHELLMETA to SHELLMETAS (for conformance X with dmake) X Added LOCKTIMEOUT to learn procmail to decide wether or not X a certain lockfile is still 'valid' X Added the function sputenv (smart-putenv) to avoid library X problems and to finally have a putenv that works the X way it was supposed to work all along (you wouldn't believe X how brain damaged this library function is :-) X Changed environment variable assignment to skip trailing blanks X only, and allow intermediate blanks (parsing like make) X1991/02/22: v1.21 X Split up procmail.c in procmail.c, nonint.c, retint.c, procmail.h, X config.h, sysexits.h, shell.h X Changes to lockfile.c: X Added -l option (locktimeout) X Moved virtually all configuration stuff from Makefile into X config.h X Added -v option to procmail X1991/03/01: v1.30 X Added out of memory/swap space immunity (NOMEMRETRY) X Made forced terminations of procmail more reliable and verbose X (previously, procmail would display erroneous error messages X if an attempt was made to kill procmail more than once in X rapid succession) X Fixed up man pages and comments X Included more example files X Made sure that variable substitution works in locallockfile- X specifications too X1991/03/15: v1.35 X Fixed the problem when no rcfile was found (rc!=NULL) X Made my memmove replacement ANSI compatible X Fixed up the Makefile (include file dependencies were incorrect) X Started using RCS to manage the source X1991/06/04: v1.99 X Changed NOMEMRETRY into NORESRETRY, now all machine limitations X are caught X Beefed up the parsing routine, now supports virtually complete X /bin/sh syntax (*all* quotes and escapes are understood) X Cleaned up the code, arranged for all remotely configuration- X dependent looking values to be in config.h X Created an include directory to catch missing include files X Created an autoconf script that determines all installation X specifics (all the user has to do now is make) X Threw out (the last?) BSD specific library routines X ANSI'fied the code, it's now as close as you can get to 'native' X ANSI while maintaining compatibility with K&R X Stopped using the %i identifier from sscanf X Improved the diagnostic messages when procmail is killed (tells X you what it was waiting for) X Made the file locking mechanism RELIABLE over NFS X Procmail now is able to deliver to directories too (AMS -- X Andrew Mail System), it creates a uniquely named new file for X every new mail X Threw out the 's' and 'I' options in rcfiles, introduced 'D' X Completely rewrote the filter code, made sure that the pid number X off procmail does not change anymore while filtering X Threw out the stdio library (out of procmail) to avoid the hidden X malloc on fopen (I want to catch all mallocs), reliability X was improved, binary size did not shrink because the getpwent X library links in the stdio library (aargh!) X Procmail ditches the environment completely now upon startup X Provided a -p flag to preserve the environment X Provided a -d flag for debugging purposes (debugging rc scripts) X Improved logfile format, now logs a third entry on mail arrival: X which folder the mail finally went to and how long it was X Made logfile output line buffered to get a cleaner logfile if X several programs write it concurrently X Changes to lockfile.c: X Added -s flag to maintain step with procmail X Created formail.c: X A new (the third) program X Rearranged the routines a bit, so that two object files could be X shared between the three programs X1991/06/10: v2.00 X Changed all "ambiguous assignments" to have a space inserted X (preserves compatibility with older compilers) X Made some more entries in the config.h file X Changes to formail.c: X Added strstr library replacement X Added the option to split digests X Updated the manual files (I hate doing this) X Updated the example files X1991/06/11: v2.01 X Fixed up includes.h and autoconf to be more POSIX compliant X Fixed up small bug in readparse() (goodies.c) that caused trouble X with recursively nested backquotes (nobody will probably ever X use this, but I was testing worst case behaviour) X Documented the $$ environment variable in procmail.1 X1991/06/12: v2.02 X Fixed typo in strstr replacement X Fixed runaway line while logging long folder names SHAR_EOF chmod 0644 procmail/HISTORY || echo 'restore of procmail/HISTORY failed' Wc_c="`wc -c < 'procmail/HISTORY'`" test 5739 -eq "$Wc_c" || echo 'procmail/HISTORY: original size 5739, current size' "$Wc_c" fi # ============= procmail/FEATURES ============== if test -f 'procmail/FEATURES' -a X"$1" != X"-c"; then echo 'x - skipping procmail/FEATURES (File already exists)' else echo 'x - extracting procmail/FEATURES (Text)' sed 's/^X//' << 'SHAR_EOF' > 'procmail/FEATURES' && XFeature summary for procmail: X + Easy to install X + Simple to maintain and configure because X all you need is actually only ONE executable (procmail) X and ONE configuration file (.procmailrc) X + Uses *your* (i.e. easily configurable) favourite regular expression X syntax X + Allows for very-easy-to-use yes-no decisions on where the mail X should go X + Filters, delivers and forwards mail *reliably* X + Provides a stable and guaranteed environment for any programs or X shell scripts you may wish to start upon mail arrival X + Is designed for reliability, once procmail gets hold of your mail X you can consider it delivered X + Is event driven (i.e. gets invoked automagically when mail arrives) X + Performs heroically under even the worst conditions X (file system full, out of swap space, process table full, X file table full, missing support files, unavailable executables, X denied permissions) and tries to deliver the mail somehow anyway X (it usually succeeds were other programs would have given up) X + procmail is the closest you can get to a program that outlives X the swapper :-) X + Absolutely undeliverable mail (after trying every trick in the book) X will bounce back to the sender X + Does not use *any* temporary files X + Is explicitly designed to work under NFS as well X + Performs more reliable mailbox locking than most other mailers X (especially across NFS, DON'T use NFS mounted mailboxes WITHOUT X installing procmail, you may use valuable mail one day) X + Supports both mailfolder standards (single file folders (standard X *NIX format) as well as directory folders (AMS -- Andrew Mail System) X that contain one file per message) X + Variable assignment and substitution is a subset of the standard X /bin/sh syntax X + Provides a mail log file, which logs all mail arrival, shows X in summary whence it came from, what it was about, where it went X (what folder) and how long (in bytes) it was X + Uses this log file to display a wide range of diagnostic and error X messages (if something went wrong) X + Processed mail can contain arbitrary 8-bit characters (including X '\0'); i.e. binary mailings can be processed if the rest of the X mailing system knew how to handle them too X + It has a man page (boy, does *it* have a man page) X + It can be used as a local delivery agent (substitute for /bin/mail) X XFeature summary for formail: X + Can generate auto-reply headers X + Can force mail into mailbox format (so that you can process it with X standard mail programs) X + Can split up mailboxes into the individual messages X + Can split up digests into the individual messages X XFeature summary for lockfile: X + Provides NFS-secure lockfiles to shell script programmers SHAR_EOF chmod 0644 procmail/FEATURES || echo 'restore of procmail/FEATURES failed' Wc_c="`wc -c < 'procmail/FEATURES'`" test 2717 -eq "$Wc_c" || echo 'procmail/FEATURES: original size 2717, current size' "$Wc_c" fi exit 0 exit 0 # Just in case... -- Kent Landfield INTERNET: kent@sparky.IMD.Sterling.COM Sterling Software, IMD UUCP: uunet!sparky!kent Phone: (402) 291-8300 FAX: (402) 291-4362 Please send comp.sources.misc-related mail to kent@uunet.uu.net.