/*
 *	$Source: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/runtime.c,v $
 *	$Author: ckclark $
 *	$Locker:  $
 *	$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/runtime.c,v 1.6 91/07/27 21:24:09 ckclark Exp $
 */

#ifdef DTEST
#define DPRINT(a) fprintf(stderr,a)
#else
#define DPRINT(a)
#endif

#ifndef lint
static char *rcsid_runtime_c = "$Header: /afs/.athena.mit.edu/contrib/watchmaker/src/pmdc/RCS/runtime.c,v 1.6 91/07/27 21:24:09 ckclark Exp $";
#endif	lint

#include "pmd.h"
#include <stdio.h>
#include <sys/file.h>
#include <sysexits.h>
#include <strings.h>
#ifdef mips
#define dbm_fetch(a,b) fetch(b)
#define dbm_firstkey(a) firstkey()
#define dbm_nextkey(a) nextkey()
#define dbm_store(a,b,c,d) store(b,c)
#include <dbm.h>
#else
#include <ndbm.h>
#endif
#include <sys/time.h>

#define LOCKTIME 15		/* Time to wait while grabbing a lock */
#define MAXTIME	10		/* Number of locks to wait through */
#define TIMEFIELD "_pptime"	/* Time of first database entry */

#define LSIZE 512		/* Size of various lines */

char *pptemp;			/* Temporary file */
FILE *ppin;			/* Temporary file filepointer */
char ppline[MAXLINELEN];	/* Input line buffer */
int pperr;			/* Error flag */
datum ppkey, ppcount;		/* For statistical routines */


/*
 * Sets up pptemp and pperr, then calls ppact to do the real work.
 * Cleans up on error.
 */
main()
{
    char *mktemp();

    /* If we can't chdir to home, we're scrod */
    if(chdir(pphome) != 0) {
	fprintf(stderr, "Pmd: mail directory %s does not exist!\n", pphome);
	exit(EX_TEMPFAIL);
    }
    pperr = 0;
    pptemp = mktemp("/tmp/pmdXXXXXX");
    if(ppfcat(pptemp) == 0) {
	fprintf(stderr, "Can't write to temp file %s\n", pptemp);
	exit(EX_TEMPFAIL);
    }
    if((ppin = fopen(pptemp, "r")) == (FILE *) NULL) {
	fprintf(stderr, "Can't read temp file %s\n", pptemp);
	exit(EX_TEMPFAIL);
    }
    ppinc("Total messages");
    ppact();
    fclose(ppin);
    if(pperr) {
	ppinc("Dead letters");
	ppputact(pperrf);	/* If this fails, we can do nothing */
    }
    unlink(pptemp);
    exit(0);
}

/* Do a system call, with input from pptemp */
/* Sets pperr if there is an error */
pprunact(action)
char *action;
{
  char line[LSIZE];

  strcpy(line, action);
  strcat(line, " < ");
  strcat(line, pptemp);
  if(system(line) != 0) pperr++;	/* Check to see that the pipe works */
}

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

static void
dobiff(offset, name, host)
int offset;
char *name, *host;
{
    char buf[64];
    int sock;
    struct hostent *hp;
    struct servent *sp;
    struct sockaddr_in sin;
    extern char *index();
    register char *cp = 0;

    sprintf(buf, "%s@%d", name, offset);

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	return;

    for (;;) {
	if (cp = index(host, ' ')) {
	    *cp = '\0';
	}
	if (!(hp = gethostbyname(host)))
	    break;
	if (!(sp = getservbyname("biff", "udp")))
	    break;

	bcopy(hp->h_addr, &sin.sin_addr, 4); /* XXX we know IP is 4 */
	sin.sin_port = sp->s_port;
	sin.sin_family = PF_INET;

	sendto(sock, buf, sizeof(buf), 0, &sin, sizeof(sin));
	if (cp)
	    host = cp+1;
	else
	    break;
    }
    close(sock);
    return;
}

ppbiffputact(file, user, host)
char *file;
char *user;
char *host;
{
    ppputact1(file, user, host);
}

ppputact(file)
char *file;
{
    ppputact1(file, 0, 0);
}

/* Appends pptemp to file, flocking file to prevent lossage */
/* Sets pperr on error */
ppputact1(file, user, host)
char *file;
char *user;
char *host;
{
  FILE *f1, *f2;
  int fd, rmail, mmdf;
  char line[LSIZE], *fgets();
  long end, ftell();

  if((f2=fopen(pptemp, "r")) == (FILE *) NULL) {
    pperr++;
    return;
  }

  if((fd=open(file, (O_CREAT | O_APPEND | O_WRONLY), ppmode|0200)) < 0) {
    pperr++;
    return;
  }

  flock(fd, LOCK_EX);		/* Lock the file */
  
  if((f1=fdopen(fd, "a")) == (FILE *) NULL) {
    close(fd);
    pperr++;
    return;
  }

  fseek(f1, 0L, 2);
  end=ftell(f1);
  
  /* Everything ok, copy file */
  /* Do this line-at-a-time so we can test for from lines */
  /* Note: this is somewhat specialized for mailbox format */
  /* We need to put a from line on if none exists */
  if(fgets(line, LSIZE, f2) == NULL) goto wrerr;
  rmail = !strcmp (ppformat, "rmail");
  mmdf = !strcmp (ppformat, "mmdf");

  if(rmail) fprintf (f1, "\014\n0, unseen,,\n*** EOOH ***\n");
  else if (mmdf) fprintf (f1, "\001\001\001\001\n");
  else if(!isin("from ", line)) {
    struct timeval tp;
    struct timezone tzp;
    
    gettimeofday(&tp, &tzp);
    /* ctime includes the \n */
    fprintf(f1, "From Personal_Mail_Daemon  %s", ctime(&(tp.tv_sec)));
  }
  else fputs(line, f1);

  /* After the first line, all postmark lines get prefixed */
  while(fgets(line, LSIZE, f2) != NULL) {
    if(isin("from ", line) && !rmail && !mmdf) {
      if('>' != putc('>', f1)) goto wrerr;
    }
    if (isin ("\037", line) && rmail)
      fprintf (f1, "^_%s", line + 1);
    else
      fputs(line, f1);
  }

  if (rmail) fprintf (f1, "\037");
  else if (mmdf) fprintf (f1, "\001\001\001\001\n");

  if(fflush(f1) == EOF || ferror(f1)) {
  wrerr:
    ftruncate(fd, (int) end);	/* Cast to int to get lint to shut up */
    close(fd);
    pperr++;
    return;
  }

  close(fd);
  if (user)
      dobiff(end, user, host);
  return;
}

/* Barf out this message with argument as error string */
/* NEVER RETURNS */
ppbarfact(error)
char *error;
{
    ppinc("Bounced messages");
    ppinc("Total messages");
    fprintf(stderr, "PMD error: %s\n", error);
    unlink(pptemp);
    exit(EX_UNAVAILABLE);
}

/* Cat standard input to file */
ppfcat(file)
char *file;
{
  FILE *f;
  int fd;
  int c;

  DPRINT("Creating temp file...\n");
  if((fd=open(file,  (O_CREAT | O_EXCL | O_WRONLY), ppmode|0600)) < 0) {
    return(0);
  }

  DPRINT("Opening temp stream for write\n");
  if((f=fdopen(fd, "w")) == (FILE *) NULL) {
    close(fd);
    return(0);
  }

  /* Everything ok, cat to file */
  while((c=getchar()) != EOF)
    if(c != putc(c, f)) goto wrerr;
  if('\n' != putc('\n', f)) goto wrerr; /* Add a newline */

  if(fflush(f) == EOF) {
  wrerr:
    DPRINT("oops... error on write/flush\n");
    close(fd);
    unlink(file);		/* This is a temporary, so clean up */
    return(0);
  }
  
  DPRINT("done with temp file.\n");
  close(fd);
  return(1);
}

/* Returns 1 iff s1 matches s2 (ignoring case) for the length of s1 */
isin(s1, s2)
char *s1, *s2;
{
#ifdef DEBUG
    printf("isin(%s, %s)\n", s1, s2);
#endif DEBUG
    for(; *s1; s1++, s2++) {
	if(((*s1 | 0x20) != (*s2 | 0x20)) /* Ctype's tolower loses big */
	   || *s2 == '\0'
	   || *s2 == '\n') return(0);
    }
#ifdef DEBUG
    puts("Returning true");
#endif    
    return(1);
}

/* Returns 1 iff ppline is a header line of type field which contains val */
pphead(field, val)
char *field, *val;
{
    register char *p;

#ifdef DEBUG
    printf("pphead(%s, %s) applied to %s\n", field, val, ppline);
#endif    
    if(*field != '\0') {	/* If "", don't match field */
	if(!isin(field, ppline)) return(0);	/* Not of type field */
	p = ppline + strlen(field);
	if(*p++ != ':') return(0);	/* Header field too long */
    } else {			/* We don't care about field */
	p = ppline;
    }
    for(; *p; p++) {
	if(isin(val, p)) return(1); /* Bingo */
    }
    return(0);			/* You lose */
}

/*
 * Returns true iff ppline contains val
 */
ppbody(val)
char *val;
{
    register char *p;

#ifdef DEBUG
    printf("ppbody(%s) applied to %s\n", val, ppline);
#endif
    for(p = ppline; *p; p++) {
	if(isin(val, p)) return(1);
    }
    return(0);
}

/*
 * Increment counter in ppdbfile associated with key
 * Returns 0 if successful, <0 otherwise.
 */
ppinc(key)
char *key;
{
#ifndef mips
    DBM *db;
#endif
    int result, lock, lockcount;
    unsigned long count;

    /* We use ppdbfile itself for locking */
    for(lockcount = 0;
	(lock = open(ppdbfile, O_CREAT|O_EXCL, 0)) < 0;
	lockcount++) {
	    sleep(LOCKTIME);
	    if(lockcount > MAXTIME) { /* Give up */
		unlink(ppdbfile); /* File is garbage */
		lock = open(ppdbfile, O_CREAT, 0);
		break;
	    }
	}
    /* No need to waste file descriptors */
    if(lock > 0) close(lock);

    /* We have the lock, update the database */
#ifndef mips
    if((db = dbm_open(ppdbfile, O_CREAT|O_RDWR, ppmode|0600)) == NULL) {
	return(-1);
    }
#else
    if (dbminit(ppdbfile) < 0) {
	 return(-1);
    }
#endif
    
    /* Check the time settings */
    ppkey.dptr = TIMEFIELD;
    ppkey.dsize = strlen(ppkey.dptr)+1;
    ppcount = dbm_fetch(db, ppkey);
    if(ppcount.dptr == NULL) {
	struct timeval tp;
	struct timezone tzp;

	gettimeofday(&tp, &tzp);
	ppcount.dptr = ctime(&(tp.tv_sec));
	ppcount.dsize = strlen(ppcount.dptr)+1;
	dbm_store(db, ppkey, ppcount, DBM_INSERT);
    }

    ppkey.dptr = key;
    ppkey.dsize = strlen(key)+1;
    ppcount = dbm_fetch(db, ppkey);
    if(ppcount.dptr == NULL) {
	count = 0;
    } else {
	bcopy((char *)ppcount.dptr, (char *)&count, sizeof(count));
    }
    count++;
    ppcount.dptr = (char *) &count;
    ppcount.dsize = sizeof(count);
    result = dbm_store(db, ppkey, ppcount, DBM_REPLACE);
#ifndef mips
    dbm_close(db);
#endif
    
    /* Relase the lock */
    unlink(ppdbfile);

    return(result);
}

/* Fgethdr; like fgets, but eats newlines that are internal to a header line */
char *fgethdr(line, size, stream)
char *line;
int size;
FILE *stream;
{
    char *cur;
    int c,d;

    if(feof(stream)) return(NULL);

    for(cur=line; size>2; cur++, size--) {
	if ((d=getc(stream)) == EOF) goto done;
	switch(*cur = (char)d) {
          case('\n'):
	    if(cur != line && isspace(c=getc(stream)) && c != '\n') {
	    	*cur=c;
		break;
	    } else {
		if (cur != line) {
		    ungetc(c, stream);
		    goto done;
		} else {
		    /* blank line; end of headers */
		    ungetc(d,stream);
		    *cur = '\0';
		    return(line);
		}
	    }
    	  default:
	    break;
      }
    }
  done:
    *cur++='\n';		/* For fgets compatibility */
    *cur= '\0';
    return(line);
}
