/* $Copyright: $
 * Copyright (c) 1995 - 2001 by Steve Baker (ice@mama.indstate.edu)
 * All Rights reserved
 *
 * This software is provided as is without any express or implied
 * warranties, including, without limitation, the implied warranties
 * of merchant-ability and fitness for a particular purpose.
 */

#define MAIN
#include "sac.h"

static char *version = "$Version: $ sac v1.9 (c) 1995 - 2001 by Steve Baker $";

/*
 * sac [-w wtmplist|-] [-acdDfFhilmoprtSUX[3|4]] [-b hour[:min[:sec]]]
 *     [-s start] [-e end] [[-u] userlist] [-x [userlist]] [-T [ttylist]]
 *     [-H [hostlist]] [-M hourrangespec[,hourrangspec[,...]]]
 *     [-R [portmasterlist]] [-I hours[:min[:sec]]] [--seconds] [--hms]
 *     [--hm] [--hours] [--round] [--longdate] [--help] [--cutoff H[:M[:S]]]
 *     [--discard H[:M[:S]]] [--verbose] [--version]
 */
int main(int argc, char **argv)
{
  struct exc *xp;
  struct usr *u, *cp, *pp;
  struct file *f, *files = NULL, *l, *p;
  char *start = NULL, *end = NULL;
  char listtype = USERLIST;
  int i, j, n, fd=0;
  time_t t;
#ifdef USER_PROCESS
  void (*gronk)() = gronk_sysv;
#else
  void (*gronk)() = gronk_bsd;
#endif
  char fastseek = FALSE;
  char ft= ACCT_WTMP;

  for(n=i=1;i<argc;i=n) {
    n++;
    if (argv[i][0] == '-' && argv[i][1]) {
      for(j=1;argv[i][j];j++) {
	switch (argv[i][j]) {
	  case 'w':
	    listtype = FILELIST;
	    break;
	  case 'p':
	    type = USER | (type & 0xFFF0);
	    break;
	  case 'd':
	    type = DAY | (type & 0xFFF0);
	    break;
	  case 't':
	    type = TTY | (type & 0xFFF0);
	    break;
	  case 'r':
	    type = RAW | (type & 0xFFF0) | HOUR;
	    break;
	  case 'U':
	    type = SIMUSE | (type & 0xFFF0);
/*
	    if (argv[n] == NULL) usage(10);
	    usespec = argv[n++];
*/
	    break;
	  case 'a':
	    type |= AVERAGE;
	    break;
	  case 'c':
	    type |= CLIP;
	    break;
	  case 'h':
	    type |= HOUR;
	    break;
	  case 'm':
	    type |= MINMAX;
	    break;
	  case 'l':
	    type |= LOGIN;
	    break;
	  case 'i':
	    hosttoo |= TRUE;
	    break;
	  case 'P':
	    type |= IPACCT;
	    break;
	  case 'C':
	    type |= CALLID;
	    break;
	  case 'I':
	    type |= IGNORE;
	    ignore = argv[n++];
	    break;
	  case 'F':
	    gronk = gronk_ftp;
	    ft = ACCT_WTMP;
	    break;
	  case 'f':
	    gronk = gronk_both;
	    ft = ACCT_WTMP;
	    break;
	  case 'R':
	    gronk = gronk_radius;
	    listtype = PORTMASTERLIST;
	    ft = ACCT_RADIUS;
	    break;
	  case 'L':
	    gronk = gronk_radiuslogfile;
	    listtype = LOGFILELIST;
	    ft = ACCT_RADIUS_LOGFILE;
	    break;
	  case 'o':
	    gronk = gronk_bsd;
	    ft = ACCT_ANCIENT;
	    break;
	  case 'X':
	    gronk = gronk_tacacs3;
	    ft = ACCT_TACACS3;
	    if (argv[i][j+1] == '4') {
	      gronk = gronk_tacacs4;
	      ft = ACCT_TACACS4;
	      j++;
	    } else if (argv[i][j+1] == '3') {
	      j++;
	    }
	    break;
	  case 'b':
	    if (argv[n] == NULL) usage(7);
	    back = argv[n++];
	    break;
	  case 's':
	    if (argv[n] == NULL) usage(7);
	    start = argv[n++];
	    break;
	  case 'e':
	    if (argv[n] == NULL) usage(7);
	    end = argv[n++];
	    break;
	  case 'S':
	    fastseek = TRUE;
	    break;
	  case 'D':
	    radhost = TRUE;
	    break;
	  case 'x':
	    listtype = EXCLUDELIST;
	    break;
	  case 'T':
	    listtype = TTYLIST;
	    break;
	  case 'u':
	    listtype = USERLIST;
	    break;
	  case 'H':
	    listtype = HOSTLIST;
	    break;
	  case 'M':
 	    if (argv[n] == NULL) usage(7);
	    hourmask |= hourrangespec(argv[n++]);
	    break;
	  case '-':
	    if (j == 1) {
	      if (!strcmp("--help",argv[i])) usage(4);
	      if (!strcmp("--version",argv[i])) usage(9);
	      if (!strcmp("--hms",argv[i])) {
		j = strlen(argv[i])-1;
		timefmt = HMS;
		break;
	      }
	      if (!strcmp("--hm",argv[i])) {
		j = strlen(argv[i])-1;
		timefmt = HM;
		break;
	      }
	      if (!strcmp("--hours",argv[i])) {
		j = strlen(argv[i])-1;
		timefmt = HOURS;
		break;
	      }
	      if (!strcmp("--seconds",argv[i])) {
		j = strlen(argv[i])-1;
		timefmt = SECONDS;
		break;
	      }
	      if (!strcmp("--round",argv[i])) {
		j = strlen(argv[i])-1;
		timefmt |= ROUND;
		break;
	      }
	      if (!strcmp("--verbose",argv[i])) {
		j = strlen(argv[i])-1;
		verbose = TRUE;
		break;
	      }
	      if (!strcmp("--longdate",argv[i])) {
		j = strlen(argv[i])-1;
		longdate = TRUE;
		break;
	      }
	      if (!strcmp("--cutoff",argv[i])) {
		j = strlen(argv[i])-1;
		if (argv[n] == NULL) usage(7);
		cutoff = churn_time(argv[n++]);
		break;
	      }
	      if (!strcmp("--discard",argv[i])) {
		j = strlen(argv[i])-1;
		if (argv[n] == NULL) usage(7);
		discard = churn_time(argv[n++]);
		break;
	      }
	    }
	  default:
	    usage(1);
	}
      }
    } else {
      switch(listtype) {
	case USERLIST:
	  adduser(&us,argv[i]);
	  fix = TRUE;
	  break;
	case EXCLUDELIST:
	  addexclude(argv[i]);
	  exclude = TRUE;
	  break;
	case TTYLIST:
	  addtty(argv[i]);
	  fixtty = TRUE;
	  break;
	case HOSTLIST:
	  addhost(argv[i]);
          fixhost = TRUE;
	  break;
	case PORTMASTERLIST:
	case FILELIST:
	  f = bmalloc(sizeof(struct file));
	  if (listtype == FILELIST) f->file = scopy(argv[i]);
	  else {
	    if (ispattern(argv[i])) {
	      free(f);
	      while ((f = parseradiusdir(RADIUS_DIR,argv[i],DETAIL_NAME))) {
		f->gronk = gronk;
		f->ftype = ft;
		f->nxt = files;
		files = f;
	      }
	      break;
	    }
	    sprintf(buf,"%s/%s/%s",RADIUS_DIR,argv[i],DETAIL_NAME);
	    f->file = scopy(buf);
	  }
	  f->gronk = gronk;
	  f->ftype = ft;
	  f->nxt = files;
	  files = f;
	  break;
	case LOGFILELIST:
	  f = bmalloc(sizeof(struct file));
	  f->file = scopy(argv[i]);
	  f->gronk = gronk;
	  f->ftype = ft;
	  if (files == NULL) files = f;
	  else {
	    for(l=p=files;p;p++) {
	      if (strncmp(p->file,"lastlog",7)) break;
	      if (strncmp(f->file,p->file,14) < 0) break;
	      l=p;
	    }
	    f->nxt = p;
	    if (p == files) files = f;
	    else l->nxt = f;
	  }
	  break;
      }
    }
  }

  for(xp=ex;xp;xp=xp->nxt) {
    while ((u = finduser(us,xp->user))) {
      for(pp=cp=us;cp && cp != u;cp=cp->nxt) pp = cp;
      if (!cp) break;
      if (us == cp) us = u->nxt;
      else pp->nxt = u->nxt;
      free(u);
    }
  }

  if (start) {
    if (start[0] == '-' || start[0] == '+') {
      for(i=1;start[i];i++) if (!isdigit(start[i])) usage(2);
      sm = atoi(start);
      if (sm == 0) {
	if (start[0] == '-') sm = MINUSZERO;
	else sm = PLUSZERO;
      }
    } else sd = getsacdate(start);
  }
  if (end) {
    if (end[0] == '-' || end[0] == '+') {
      t = time(0);
      for(i=1;end[i];i++) if (!isdigit(end[i])) usage(2);
      em = atoi(end);
      if (em == 0) {
	if (end[0] == '-') em = MINUSZERO;
	else em = PLUSZERO;
      }
    } else ed = getsacdate(end);
  }
  if (back) backtime = (curtime=time(0)) - churn_time(back);
  if (ignore) {
    ignoretime = churn_time(ignore);
    for(u=us;u;u=u->nxt)
      u->ignore = ignoretime;
  }

  if (files == NULL) {
    files = bmalloc(sizeof(struct file));
    files->file = scopy(_PATH_WTMP);
    files->gronk = gronk;
  }
  for(f=files;f;f=f->nxt) {
    if (!strcmp(f->file,"-")) fd = 0;
    else {
      if ((fd = open(f->file,O_RDONLY)) < 0) {
	fprintf(stderr,"sac: Error opening '%s' for reading. [%d]\n",f->file,errno);
	continue;
      }
    }
    if (fastseek) try_fastseek(fd,f);
    f->gronk(fd);
    cleanup();
    if (fd != 0) close(fd);
  }
  report();
  return 0;
}

void usage(int n)
{
  switch (n) {
    case 1:
      fprintf(stderr,"usage: sac [-w [wtmp-list|-]] [-acdDfFhilmoprtSUX[3|4]] [-s start] [-e end]\n       [-b H[:M[:S]]] [[-u] userlist] [-x [userlist]] [-T [ttylist]]\n       [-H [hostlist]] [-M hour-range-spec[,hour-range-spec[,...]]]\n       [-R [portmaster-list]] [--hms] [--hm] [--hours] [--round]\n       [--version] [--help]\n");
      break;
    case 2:
      fprintf(stderr,"sac: Invalid date.  Format: +days | -days | mm/dd/yy\n");
      break;
    case 3:
      fprintf(stderr,"sac: Invalid time.  Format: hours[:minutes[:seconds]]\n");
      break;
    case 4:
      fprintf(stderr,"usage: sac [-w [wtmp-list|-]] [-acdDfFhilmoprtSUX[3|4]] [-s start] [-e end]\n       [-b H[:M[:S]]] [[-u] userlist] [-x [userlist]] [-T [ttylist]]\n       [-H [hostlist]] [-M hour-range-spec[,hour-range-spec[,...]]]\n       [-R [portmaster-list]] [--hms] [--hm] [--hours] [--round]\n       [--version] [--help]\n");
      fprintf(stderr,"    -w [wtmp|-] Read alternate wtmp file(s).\n");
      fprintf(stderr,"    -d          Perform daily accounting.\n");
      fprintf(stderr,"    -a          Average usage / login.\n");
      fprintf(stderr,"    -t          Report usage by tty line.\n");
      fprintf(stderr,"    -U          List simultaneous usage levels.\n");
      fprintf(stderr,"    -r          Show raw statistics about accounting file(s).\n");
      fprintf(stderr,"    -h          Show hourly profile.\n");
      fprintf(stderr,"    -l          Show login listing.\n");
      fprintf(stderr,"    -p          Per user accounting.\n");
      fprintf(stderr,"    -f          Perform accounting for ftp logins too.\n");
      fprintf(stderr,"    -F          Perform accounting for ftp logins only.\n");
      fprintf(stderr,"    -m          Show min/max # of concurrent logins.\n");
      fprintf(stderr,"    -c          Perform login clipping.\n");
      fprintf(stderr,"    -o          Read old decrepit wtmp format.\n");
      fprintf(stderr,"    -X[3]       Read tacacs 3.4 - 3.5 wtmp format.\n");
      fprintf(stderr,"    -X4         Read tacacs 4.x wtmp format.\n");
      fprintf(stderr,"    -i          Include hostname in determining login/logouts.\n");
      fprintf(stderr,"    -R [pmstrs] Read Radius detail files for each portmaster listed.\n");
      fprintf(stderr,"    -D          Use @hostname in user@hostname instead of portmaster name.\n");
      fprintf(stderr,"    -P          Perform packet and octet accounting.\n");
      fprintf(stderr,"    -s start    Display accounting info from `start'.\n");
      fprintf(stderr,"    -e end      Display accounting info up to `end'.\n");
      fprintf(stderr,"    -S          Seek to start of day specified with -s (MM/DD/YY format).\n");
      fprintf(stderr,"    -M hourspec Select only specific hours to perform accounting on.\n");
      fprintf(stderr,"    -b H:M:S    Show accounting info from the last few hours:minutes:seconds.\n");
      fprintf(stderr,"    -I H:M:S    Ignore usage before starting accounting (per user only).\n");
      fprintf(stderr,"    -x [users]  Does not perform accounting for [users].\n");
      fprintf(stderr,"    -T [ttys]   Perform accounting on only those ttys listed.\n");
      fprintf(stderr,"    -H [hosts]  Perform accounting only for the hosts listed.\n");
      fprintf(stderr,"    -u [users]  Perform accounting only for the users listed.\n");
      fprintf(stderr,"    userlist    Perform accounting only for the users listed.\n");
      fprintf(stderr,"    --seconds   Display time in seconds.\n");
      fprintf(stderr,"    --hms       Display time in hours:minutes:seconds format.\n");
      fprintf(stderr,"    --hm        Display time in hours:minutes format.\n");
      fprintf(stderr,"    --hours     Display time in hours only format.\n");
      fprintf(stderr,"    --round     Round time to nearest minute or hour instead of always down.\n");
      fprintf(stderr,"    --longdate  Display dates in long format.\n");
      fprintf(stderr,"    --version   Display sac version.\n");
      fprintf(stderr,"    --help      Display this help message.\n");
      break;
    case 5:
      fprintf(stderr,"sac: Invalid hour range. Format: (0-23)[-(0-23)[,...]]\n");
      break;
    case 6:
      fprintf(stderr,"sac: Malformed wildcard.\n");
      break;
    case 7:
      fprintf(stderr,"sac: Missing argument.\n");
      break;
    case 8:
      fprintf(stderr,"sac: Must specify at least one portmaster with -R option \n     or specify stdin or detail file with -w.\n");
      break;
    case 9:
      {
	char *v = version+12;
	printf("%.*s\n",(int)strlen(v)-1,v);
	exit(0);
      }
    case 10:
      fprintf(stderr,"sac: Invalid usage range. Format: [+|-]N[-[+|-]M]\n");
      break;
  }
  exit(1);
}

struct file *parseradiusdir(char *base, char *pattern, char *file)
{
  static DIR *dir = NULL;
  struct dirent *ent;
  struct file *f;

  if (!dir) {
    dir = opendir(base);
    if (!dir) return NULL;
  }
  for(;;) {
    ent = readdir(dir);

    if (ent == NULL) {
      closedir(dir);
      dir = NULL;
      return NULL;
    }

    if (patmatch(ent->d_name,pattern) != 1) continue;

    sprintf(buf,"%s/%s/%s",RADIUS_DIR,ent->d_name,DETAIL_NAME);

    if (access(buf,F_OK)) continue;

    f = bmalloc(sizeof(struct file));
    f->file = scopy(buf);
    return f;
  }
}

/*
 * Turn "mm/dd/yy" into time in seconds.
 */
time_t getsacdate(char *s)
{
  struct tm tm;
  int y;
  time_t t;

/*
 * <puke>
 * Need a real date parser that can handle different formats and separators
 */
  if (!isdigit(s[0]) || !isdigit(s[1]) || isdigit(s[2])) usage(2);
  if (!isdigit(s[3]) || !isdigit(s[4]) || isdigit(s[5])) usage(2);
  if (!isdigit(s[6]) || !isdigit(s[7]) || s[8]) usage(2);

  tm.tm_mon = (((s[0]-'0')*10) + (s[1]-'0')) -1;
  tm.tm_mday = (((s[3]-'0')*10) + (s[4]-'0'));
  y = (((s[6]-'0')*10) + (s[7]-'0'));
  tm.tm_year = (y < 70? 100 + y : y);
  tm.tm_isdst = -1;
  tm.tm_hour = 0;
  tm.tm_min = tm.tm_sec = 0;

  t = mktime(&tm);
  if (t == (time_t)(-1)) {
    if (verbose) fprintf(stderr,"oops\n");
    usage(2);
  }
  return t;
}

time_t churn_time(char *s)
{
  time_t t = 0, mult=3600;
  u_char nbuf[20], p;

  for(p=0;*s;s++) {
    if (*s == ':') {
      nbuf[p] = (u_char)0;
      t += atoi(nbuf) * mult;
      p = 0;
      if (mult > 1) mult /= 60;
      else usage(3);
    } else if (isdigit(*s)) {
      if (p < 15) nbuf[p++] = (u_char)*s;
      else usage(3);
    } else usage(3);
  }
  nbuf[p] = (u_char)0;
  t += atoi(nbuf) * mult;

  return t;
}

/*
 * hourrangespec = [0-23][-[0-23]][,...]
 */
u_long hourrangespec(char *s)
{
  u_long mask = 0;
  int i= 0, j = 0, p = 0;

  while(1) {
    if (!*s) usage(5);
    for(i=0;isdigit(*s) && i < 24;s++) i = (i*10) + (*s-'0');
    if (i > 23) usage(5);

    if (*s == '-') {
      s++;
      if (!*s) usage(5);
      for(j=0;isdigit(*s) && j < 24;s++) j = (j*10) + (*s-'0');
      if (j > 23) usage(5);

      for(p=i;p<=j;p++) mask |= (1<<p);
    } else mask |= (1<<i);
    if (!*s) return mask;
    if (*s++ == ',') continue;
    usage(5);
  }
/* NOT REACHED */
  return mask;
}

/*
 * usagespec = [-] N [ + | - [-] M ]
 */
struct simuse *usagespec(char *t)
{
  struct simuse *s, **st;
  char ibuf[32];
  long bf=0,b=-1,ef=0, e=-1;
  int i, n;

  if (!*t) usage(10);
  if (*t == '-'){
    bf = -1;
    t++;
  }
  for(i=0; isdigit(*t) && i < 10; i++) ibuf[i] = *t++;
  if (isdigit(*t)) usage(10);
  ibuf[i] = 0;
  b = atoi(ibuf);

  if (*t == '+') {
    ef = 1;
  } else if (*t == '-') {
    t++;
    if (*t == '-') {
      ef = -1;
      t++;
    }
    for(i=0; isdigit(*t) && i < 10; i++) ibuf[i] = *t++;
    if (isdigit(*t)) usage(10);
    ibuf[i] = 0;
    e = atoi(ibuf);
  }
  if (!*t) usage(10);
  if (bf == -1 || ef == -1) {
    for(i=0,s=simuse; s; s=s->nxt) i++;
    st = malloc(sizeof(struct simuse *) * i);
    for(i=0,s=simuse; s; s=s->nxt) st[i++] = s;
    n = i-1;
  }

  return NULL;
}

/*
 * Adjust the time warp factor by as many days as you think are necessary.
 * If you have very infrequent logins, a WARP_FACTOR greater than 60 days
 * would probably be called for.  It's probably safe to have a huge warp
 * factor. Most errors in time are either small or very very large (i.e.
 * year 2038/1970).
 */
#define WARP_FACTOR	(60*24*60*60)

/*
 * Make sure the day that the wtmp entry corresponds to, exists in our
 * days list.  If day is less than the starting day by more than a day, or
 * greater than the last day by more than a day, allocate all the days in
 * between as well.
 */
int checkday(time_t t)
{
  struct day *d, *p;

  if (days == NULL) {
    end = days = mkday(t);
  } else {
    if (t < days->start) {
      if (t < days->start-WARP_FACTOR) return -1;
      p = d = mkday(t);
      while (p->stop+1 < days->start) {
        p->nxt = mkday(p->stop+1);
	p = p->nxt;
      }
      p->nxt = days;
      days = d;
    } else if (t > end->stop) {
      if (t > end->stop+WARP_FACTOR) return -1;
      p = d = mkday(end->stop+1);
      while (p->stop < t) {
        p->nxt = mkday(p->stop+1);
	p = p->nxt;
      }
      end->nxt = d;
      end = p;
    }
  }
  return 0;
}

/*
 * Makes a day entry.  We'll assume a day is exactly 24 hours or 86400
 * seconds long.  I don't know if this is the case or not.  I don't think
 * the time algorithm takes into account leap seconds.
 *
 * Oh boy!  Daylight savings time screws this all to hell.  We can't
 * assume anything.  Check the time at 12:00:00am and 11:59:59pm to get
 * the time bound for the day.
 */
struct day *mkday(time_t t)
{
  struct day *dp;
  struct tm tm, *xtm;
  int yday;

  xtm = localtime(&t);
  memcpy(&tm,xtm,sizeof(tm));
  tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
  yday = tm.tm_yday;

  dp = bmalloc(sizeof(struct day));
  dp->start = mktime(&tm);
  /* They may not have a 12am on the day they change to daylight savings time. */
  while (yday != tm.tm_yday) {
    tm.tm_hour++;
    dp->start = mktime(&tm);
  }

  tm.tm_isdst = -1;
  xtm = &tm;
  xtm->tm_hour = 23;
  xtm->tm_min = 59;
  xtm->tm_sec = 59;
  dp->stop = mktime(xtm);
  dp->day = xtm->tm_mday;
  dp->wday = xtm->tm_wday;
  dp->month = xtm->tm_mon;
  dp->year = 1900+xtm->tm_year;
  dp->minlogins = -1;
  ndays++;
/*
  if (d->stop > (d->start + (24*60*60))) fprintf(stderr,"Created long day: %s %d.\n",month[tm.tm_mon],tm.tm_mday);
  if (d->start > (d->stop - ((24*60*60)-1))) fprintf(stderr,"Created short day: %s %d.\n",month[tm.tm_mon],tm.tm_mday);
*/

  return dp;
}

void saclogin(struct sactmp su)
{
  struct user *q;
  struct day *d;
  struct usr *u = NULL, *up;
  int l;

  /*
   * If we still have a login on this line, it's logged out for sure now!
   * Wtmp could be corrupted.
   */
  saclogout(su,FALSE);

/*   printf ("Logging in  %s [%s] <%ld>...\n",u.ut_user,u.ut_line,u.ut_time); */
  q = bmalloc(sizeof(struct user));
  strncpy(q->line,su.u.ut_line,UT_LINESIZE);
  strncpy(q->user,su.u.ut_user,UT_NAMESIZE);
  strncpy(q->host,su.u.ut_host,UT_HOSTSIZE);
  q->user[UT_NAMESIZE] = q->line[UT_LINESIZE] = q->host[UT_HOSTSIZE] = 0;

  q->in = su.u.ut_time;
  q->nxt = usr;
  usr = q;

  if ((type & 0x000F) == SIMUSE) {
    release(q,su.u.ut_time,&su,0,1);
    return;
  }
  if ((type & MINMAX) == MINMAX && !(fix && (finduser(us,q->user)) == NULL) && !(exclude && isexcluded(q->user)) && !(fixtty && !istty(q->line)) && !(fixhost && !ishost(q->host))) {
    l = loggedin++;
    maxlogins = max(maxlogins,loggedin);
    if (minlogins > -1) minlogins = min(minlogins,l);
    else minlogins = l;

    if ((type & 0x000F) == USER) {
      if ((u = finduser(us,q->user)) == NULL) u = adduser(&us,q->user);
      u->loggedin++;
      u->maxlogins = max(u->maxlogins,u->loggedin);
      if (u->minlogins > -1) u->minlogins = min(u->minlogins,u->loggedin-1);
      else u->minlogins = u->loggedin-1;
    }

    if (su.u.ut_time < end->start) {
      for(d=days;d;d=d->nxt) {
	if (su.u.ut_time >= d->start && su.u.ut_time <= d->stop) {
	  d->maxlogins = max(d->maxlogins,loggedin);
	  if (d->minlogins > -1) d->minlogins = min(d->minlogins,l);
	  else d->minlogins = l;

	  if ((type & 0x000F) == USER) {
	    if ((up = finduser(d->us,q->user)) == NULL) up = adduser(&d->us,q->user);
	    up->maxlogins = max(up->maxlogins,u->loggedin);
	    if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin-1);
	    else up->minlogins = u->loggedin-1;
	  }
	  break;
	}
      }
    } else {
      end->maxlogins = max(end->maxlogins,loggedin);
      if (end->minlogins > -1) end->minlogins = min(end->minlogins,l);
      else end->minlogins = l;

      if ((type & 0x000F) == USER) {
	if ((up = finduser(end->us,q->user)) == NULL) up = adduser(&end->us,q->user);
	up->maxlogins = max(up->maxlogins,u->loggedin);
	if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin-1);
	else up->minlogins = u->loggedin-1;
      }
    }
  }
}

void saclogout(struct sactmp su, int usext)
{
  struct user *p, *q;
  struct usr *u = NULL, *up;
  struct day *d;

/*
  time_t t = su.u.ut_time - end->start,h,m,s;
  char uid[UT_NAMESIZE+2];
  int hack;

  hack = TRUE;
*/

  for(p=q=usr;p;) {
    if (!strncmp(su.u.ut_line,p->line,UT_LINESIZE) && (hosttoo? !strncmp(su.u.ut_host,p->host,UT_HOSTSIZE) : TRUE)) {
/*      printf ("Logging out %s [%s] <%ld>...\n",u.ut_user,u.ut_line,u.ut_time); */

      release(p,su.u.ut_time,&su,usext,-1);

      if ((type & MINMAX) == MINMAX && !(fix && (finduser(us,p->user)) == NULL) && !(exclude && isexcluded(p->user)) && !(fixtty && !istty(p->line)) && !(fixhost && !ishost(p->host))) {
	loggedin = max(0,loggedin-1);
	if (minlogins > -1) minlogins = min(minlogins,loggedin);
	else minlogins = loggedin;
    
	if ((type & 0x000F) == USER && ((u = finduser(us,p->user)) != NULL)) {
	  u->loggedin = max(0,u->loggedin-1);
	}

	if (su.u.ut_time < end->start) {
	  for(d=days;d;d=d->nxt) {
	    if (su.u.ut_time >= d->start && su.u.ut_time <= d->stop) {
	      d->maxlogins = max(d->maxlogins,loggedin+1);
	      if (d->minlogins > -1) d->minlogins = min(d->minlogins,loggedin);
	      else d->minlogins = loggedin;

	      if ((type & 0x000F) == USER) {
		if ((up = finduser(d->us,p->user)) == NULL) up = adduser(&d->us,p->user);
		up->maxlogins = max(up->maxlogins,u->loggedin+1);
		if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin);
		else up->minlogins = up->loggedin;
	      }
	      break;
	    }
	  }
	} else {
	  end->maxlogins = max(end->maxlogins,loggedin+1);
	  if (end->minlogins > -1) end->minlogins = min(end->minlogins,loggedin);
	  else end->minlogins = loggedin;

	  if ((type & 0x000F) == USER) {
	    if ((up = finduser(end->us,p->user)) == NULL) up = adduser(&end->us,p->user);
	    up->maxlogins = max(up->maxlogins,u->loggedin+1);
	    if (up->minlogins > -1) up->minlogins = min(up->minlogins,u->loggedin);
	    else up->minlogins = u->loggedin;
	  }
	}
      }

      if (p == usr) {
	usr = p->nxt;
	free(p);
	p = q = usr;
      } else {
	q->nxt = p->nxt;
	free(p);
	p = q->nxt;
      }
      return;
    }
    q = p;
    p = p->nxt;
  }

/*  Vain attempt to reasearch reaping logouts w/ missing logins.

  if (hack) {
    if (usext && su.u.ut_type != DEAD_PROCESS);

    if (!su.u.ut_name[0] && su.u.ut_name[1]) {
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);
      memcpy(uid,su.u.ut_name,UT_NAMESIZE);
      if (!uid[0]) uid[0] = '.';
      uid[UT_NAMESIZE] = 0;

      if (!strcmp("LOGIN", uid) || !strcmp(".OGIN", uid)) return;
      fprintf(stderr,"sac: no matching login for logout of [%s] at %s %d %2ld:%02ld:%02ld.\n",uid,month[end->month],end->day,h,m,s);
    }
  }
*/
}

/*
 * logout everyone on reboots or crashes.
 */
void do_reboot(time_t t)
{
  struct user *p;
  struct day *d;
  struct usr *u;

  for(p=usr;usr;p=usr){
    release(p,t,NULL,0,-1);
    usr=usr->nxt;
    free(p);
  }

  simdex = 0;
  simtime = 0;

  if ((type & MINMAX) == MINMAX) {
    loggedin = 0;
    minlogins = 0;

    if ((type & 0x000F) == USER) {
      for(u=us;u;u=u->nxt) {
	u->loggedin = 0;
	u->minlogins = 0;
      }
    }
    
    if (t < end->start) {
      for(d=days;d;d=d->nxt)
        if (t >= d->start && t <= d->stop) {
	  d->minlogins = 0;
	  break;
	}
    } else end->minlogins = 0;
  }
}

/*
 * A utmp entry denoted with a line of "|" denotes the "old" time before
 * netdate updated the time. An entry with line of "{" denotes the new time.
 * The difference in time is applied to the login time of every user still
 * logged in.
 */
void changetime(time_t t, char *line)
{
  static time_t old = 0;
  struct user *p;
  signed long dif;

  if (!strcmp("|",line)) {
    old = t;
    return;
  }
  if (old == 0) return;

  dif = (signed long)(t - old);
  for(p=usr;p;p=p->nxt) p->in -= dif;
  /* I *think* this is the right thing to do */
  simtime -= dif;
}

/*
 * Apply login time for users who haven't logged out yet (or that the wtmp file
 * in particular indicates haven't logged out by the EOF) to the days that
 * are in days list.  Login time is not applied to days not listed in the
 * wtmp file (therefor all the days between the first and last wtmp entries).
 * The reason for this is that if you're inspecting an old wtmp file, the
 * wtmp may not indicate all logouts for the last day.  It is not possible
 * to know when the wtmp file really truly ends however, so we apply the
 * minimum of the current time or the ending time for the last day.
 */
void cleanup(void)
{
  time_t t = time(0);
  struct day *lastday;
  struct user *p;
  struct usr *u;

/* Ooops, the -t option can't just ignore time that isn't in our days list
 * so we'll just force t to not go beyond the end of the last day in the
 * wtmp file.  An option should be made so you can opt to just use the
 * last wtmp entry for the end time.
 */
  if (lastent) {
    lastday = mkday(lastent);
    if (t > lastday->stop) t = lastday->stop;
  }
/*
 * Oops, if we're clipping, we have to remove the released time from the
 * list.  This is pretty easily done, by moving usr, instead of using a
 * temporary pointer.
 */
  for(p=usr;usr;p=usr) {
    release(usr,t,NULL,0,-1);
    usr=usr->nxt;
  }
  loggedin = 0;
  minlogins = 0;
  simdex = 0;

  if ((type & 0x000F) == USER) {
    for(u=us;u;u=u->nxt) {
      u->loggedin = 0;
      u->minlogins = 0;
    }
  }
}

/*
 * Release a login entry, applying the login time to the particular day
 * entries.
 * A user is logged in on a particular day when:
 *   in  >= start  &&  in  <= stop ||
 *   out >= start  &&  out <= stop ||
 *   in  <  start  &&  out >  stop
 */
void release(struct user *u, time_t t, struct sactmp *su, int usext, int siminc)
{
  struct day *p;
  struct usr *up;
  struct user *q;
  struct tty *tp;
  struct simuse *s;
  int i;

  if ((signed long)((t - u->in) + 1) < 0) return;

  if (cutoff && ((t - u->in)+1) > cutoff) t = u->in+cutoff;
  if (discard && ((t - u->in)+1) > discard) return;

/*  if ((type & 0x000F) == SIMUSE) return; */

/*
 * Clipping assumes that time moves forward. Only in computers is this
 * not necessarily a safe assumption.
 * Clipping rules:
 *   If (login time < all other login times)
 *      reduce logout time (t) to least login time (-1).
 *   else clip entirely.
 */
  if ((type & CLIP) == CLIP && (type & 0x000F) != SIMUSE) {
    for(q=usr;q;q=q->nxt) {
      if (q != u && !strncmp(u->user,q->user,UT_NAMESIZE)) {
	/* throw it out if he's already logged in earlier */
	if (q->in <= u->in) return;
	/* reduce the logout time to the lowest login time. */
	if (q->in < t) t = q->in-1;
      }
    }
  }

  if (backtime) {
    if (u->in > curtime || t < backtime) return;
    if (u->in < backtime && t > backtime) u->in = backtime;
    if (u->in < curtime && t > curtime) t = curtime;
  }

  /*  Check the user list if it's fixed.  */
  if (fix && (up = finduser(us,u->user)) == NULL) return;

  /*  Check the exclude user list.  */
  if (exclude && isexcluded(u->user)) return;

  /*  Check the tty list.  */
  if (fixtty && !istty(u->line)) return;

  /*  Check the host list.  */
  if (fixhost && !ishost(u->host)) return;


  if ((type & 0x000F) == SIMUSE) {
/*    if (t == simtime && siminc == -1) fprintf(stderr,"mark! [simtime=%ld:%d]\n",simtime,simdex); */
    if (siminc != -1) t--;
/*    if (t < simtime && siminc == 1) fprintf(stderr,"mark! [simtime=%ld:%d]\n",simtime,simdex); */
    if (simdex && simtime <= t) {
      if (simtime < end->start) {
	for(p=days;p;p=p->nxt) {
	  if (simtime >= p->start && simtime <= p->stop) {
	    s = findsim(&p->simuse,simdex);
	    s->time += (min(t,p->stop) - simtime) + 1;
	    s->count++;
	    if ((type & HOUR) == HOUR) apply_hours(simtime,t,p->start,s->h);
	  } else if (t >= p->start && t <= p->stop) {
	    s = findsim(&p->simuse,simdex);
	    s->time += (t - p->start) + 1;
	    s->count++;
	    if ((type & HOUR) == HOUR) apply_hours(simtime,t,p->start,s->h);
	  } else if (simtime < p->start && t > p->stop) {
	    s = findsim(&p->simuse,simdex);
	    s->time += (p->stop - p->start) + 1;
	    s->count++;
	    if ((type & HOUR) == HOUR) for(i=0;i<24;i++) s->h[i] += 3600;
	  }
	}
	if ((type & LOGIN) == LOGIN) {
	  for(p=days;p;p=p->nxt)
	    if (simtime <= p->stop) break;
	  s = findsim(&p->simuse,simdex);
	  addlogin(&s->login,&s->lastlogin,simtime,t,NULL,NULL,NULL);
	}
      } else {
	s = findsim(&end->simuse, simdex);
	s->time += (t - simtime) + 1;
	s->count++;
	if ((type & HOUR) == HOUR) apply_hours(simtime,t,end->start,s->h);
	if ((type & LOGIN) == LOGIN) addlogin(&s->login,&s->lastlogin,simtime,t,NULL,NULL,NULL);
      }
    }
    simdex = max(0,simdex+siminc);
    simtime = t+1;
    return;
  }

/*  Only count logins that we actually apply.  */
  logins++;

/*  if we're logging usage / tty then apply login time to tty entry.  */
  if ((type & 0x000F) == TTY || (type & 0x000F) == RAW) {
    tp=findtty(ttys,u->line);
    if (tp == NULL) {
      tp = bmalloc(sizeof(struct tty));
      strncpy(tp->line,u->line,UT_LINESIZE);
      if (ttys == NULL) ttys = tp;
      else {
	tp->nxt = ttys;
	ttys = tp;
      }
    }

    if ((type & LOGIN) == LOGIN) addlogin(&tp->login,&tp->lastlogin,u->in,t,u->user,NULL,u->host);

    tp->logins++;

    if (u->in < end->start) {
      for(p=days;p;p=p->nxt) {
	if (u->in >= p->start && u->in <= p->stop) tty_apply_hours(u->in,t,p,tp);
	else if (t >= p->start && t <= p->stop) tty_apply_hours(u->in,t,p,tp);
	else if (u->in < p->start && t > p->stop) tty_apply_hours(u->in,t,p,tp);
      }
    } else tty_apply_hours(u->in,t,end,tp);

    if (usext && (type & IPACCT) == IPACCT) {
      tp->inoct += su->inoct;
      tp->outoct += su->outoct;
      tp->inpkt += su->inpkt;
      tp->outpkt += su->outpkt;
      tp->datarate = tp->logins? ((tp->datarate*(tp->logins-1))+su->datarate)/tp->logins : su->datarate;
    }

    if ((type & 0x000F) != RAW) return;
  }

/*  if we're logging usage / user then apply login time to user entry.  */
  if (((type & 0x000F) == USER || (type&0x000F) == RAW) && (((up = finduser(us,u->user)) != NULL) || !fix)) {
    if (up == NULL) up = adduser(&us,u->user);

/*
    {
      time_t tt;
      u_long m,s;
      tt = (t-u->in)+1;
      m=tt/60;
      s=tt-(m*60);
      printf("Released %5ld:%ld\n",m,s);
    }
*/

    if ((type & LOGIN) == LOGIN) addlogin(&up->login,&up->lastlogin,u->in,t,NULL,u->line,u->host);
    up->logins++;

    if (u->in < end->start) {
      for(p=days;p;p=p->nxt) {
	if (u->in >= p->start && u->in <= p->stop) user_apply_hours(u,t,p);
	else if (t >= p->start && t <= p->stop) user_apply_hours(u,t,p);
	else if (u->in < p->start && t > p->stop) user_apply_hours(u,t,p);
      }
    } else user_apply_hours(u,t,end);
    if (usext && (type & IPACCT) == IPACCT) {
      struct usr *ud;
      double tt;
      if (u->in < end->start) {
	for(p=days;p;p=p->nxt) {
	  if ((u->in >= p->start && u->in <= p->stop) || (t >= p->start && t <= p->stop) ||
	      (u->in < p->start && t > p->stop)) {
	    if ((ud = finduser(p->us,u->user)) == NULL) ud = adduser(&p->us,u->user);
	    tt = (double)((min(p->stop,t) - max(p->start,u->in)) + 1) / (double)((t - u->in) + 1);
	    ud->inoct += (su->inoct * tt);
	    ud->outoct += (su->outoct * tt);
	    ud->inpkt += (su->inpkt * tt);
	    ud->outpkt += (su->outpkt * tt);
	    ud->datarate = up->logins? ((up->datarate*(up->logins-1))+(su->datarate*tt))/up->logins : (su->datarate*tt);
	  }
	}
      } else {
	if ((ud = finduser(end->us,u->user)) == NULL) ud = adduser(&end->us,u->user);
	ud->inoct += su->inoct;
	ud->outoct += su->outoct;
	ud->inpkt += su->inpkt;
	ud->outpkt += su->outpkt;
	ud->datarate = up->logins? ((up->datarate*(up->logins-1))+su->datarate)/up->logins : su->datarate;
      }
    }

    if ((type & 0x000F) != RAW) return;
  }

/*
 * We go through this for both daily and total. The total will be accumulated
 * at the end since we don't know the starting and ending dates until we're
 * done.
 */
  if (u->in < end->start) {
    /* Ugh, it's probably yesterday, but we've got to start all the way at the
     * beginning. I wonder if double-linking the list would speed things up.
     */
    for(p=days;p;p=p->nxt) {
      if (u->in >= p->start && u->in <= p->stop) {
	p->time += (min(p->stop,t) - u->in) + 1;
	p->logins++;
	if (hourmask || (type & HOUR) == HOUR) apply_hours(u->in,t,p->start,p->h);
      } else if (t >= p->start && t <= p->stop) {
	p->time += (t - max(p->start,u->in)) + 1;
	p->logins++;
	if (hourmask || (type & HOUR) == HOUR) apply_hours(u->in,t,p->start,p->h);
      } else if (u->in < p->start && t > p->stop) {
	p->time += 86400;
	p->logins++;
	if (hourmask || (type & HOUR) == HOUR)
	  for(i=0;i<24;i++) p->h[i] += 3600;
      }
    }
    if ((type & LOGIN) == LOGIN) {
      for(p=days;p;p=p->nxt)
	if (u->in <= p->stop) break;
      addlogin(&p->login,&p->lastlogin,u->in,t,u->user,u->line,u->host);
    }
/*
      if ((type & LOGIN) == LOGIN) addlogin(&p->login,&p->lastlogin,u->in,t,u->user,u->line,u->host);
      if (usext && (type & IPACCT) == IPACCT) {
	p->inoct += su->inoct;
	p->outoct += su->outoct;
	p->inpkt += su->inpkt;
	p->outpkt += su->outpkt;
	p->datarate = p->logins? ((p->datarate*(p->logins-1))+su->datarate)/p->logins : su->datarate;
      }
    }
*/
    if (usext && (type & IPACCT) == IPACCT) {
      struct usr *ud;
      double tt;
      for(p=days;p;p=p->nxt) {
	if ((u->in >= p->start && u->in <= p->stop) || (t >= p->start && t <= p->stop) ||
	    (u->in < p->start && t > p->stop)) {
	  tt = (double)((min(p->stop,t) - max(p->start,u->in)) + 1) / (double)((t - u->in) + 1);
	  p->inoct += (su->inoct * tt);
	  p->outoct += (su->outoct * tt);
	  p->inpkt += (su->inpkt * tt);
	  p->outpkt += (su->outpkt * tt);
	  p->datarate = p->logins? ((p->datarate*(p->logins-1))+(su->datarate*tt))/p->logins : (su->datarate*tt);
	}
      }
    }
  } else {
    end->time += (min(end->stop,t) - max(end->start,u->in)) + 1;
    end->logins++;
    if (hourmask || (type & HOUR) == HOUR) apply_hours(u->in,t,end->start,end->h);
    if ((type & LOGIN) == LOGIN) addlogin(&end->login,&end->lastlogin,u->in,t,u->user,u->line,u->host);
    if (usext && (type & IPACCT) == IPACCT) {
      end->inoct += su->inoct;
      end->outoct += su->outoct;
      end->inpkt += su->inpkt;
      end->outpkt += su->outpkt;
      end->datarate = end->logins? ((end->datarate*(end->logins-1))+su->datarate)/end->logins : su->datarate;
    }
  }
}

void addlogin(struct login **login, struct login **last, time_t in, time_t out, char *name, char *tty, char *host)
{
  struct login *l;

  l = bmalloc(sizeof(struct login));
  l->start = in;
  l->stop = out;
  l->name = name? scopy(name) : NULL;
  l->tty = tty? scopy(tty) : NULL;
  l->host = host? scopy(host) : NULL;
  l->nxt = NULL;
  if (!*last) *last = *login = l;
  else {
    (*last)->nxt = l;
    *last = l;
  }
}

void apply_hours(time_t in, time_t out,time_t start, time_t h[24])
{
  int i;
  time_t b, e;

  b = start;
  e = start + 3599;
  for(i=0;i<24;i++) {
    if (in >= b && in <= e) h[i] += (min(e,out) - in) + 1;
    else if (out >= b && out <= e) h[i] += (out - max(b,in)) + 1;
    else if (in < b && out > e) h[i] += 3600;

    b += 3600;
    e += 3600;
  }
}

void user_apply_hours(struct user *u, time_t out, struct day *d)
{
  int i;
  time_t b, e;
  struct usr *up;

  if ((up = finduser(d->us,u->user)) == NULL) up = adduser(&d->us,u->user);

  up->time += (min(out,d->stop) - max(u->in,d->start)) + 1;
  if (max(u->in,d->start) == u->in) up->logins++;
  else up->xlogins++;

  if (hourmask || ((type & HOUR) == HOUR)) {
    b = d->start;
    e = d->start + 3599;
    for(i=0;i<24;i++) {
      if (u->in >= b && u->in <= e) up->h[i] += (min(e,out) - u->in) + 1;
      else if (out >= b && out <= e) up->h[i] += (out - max(b,u->in)) + 1;
      else if (u->in < b && out > e) up->h[i] += 3600;

      b += 3600;
      e += 3600;
    }
  }
}

void tty_apply_hours(time_t in, time_t out, struct day *d, struct tty *t)
{
  int i;
  time_t b, e;
  struct tty *tp;

  if ((tp = findtty(d->tty,t->line)) == NULL) {
    tp = bmalloc(sizeof(struct tty));
    strncpy(tp->line,t->line,UT_LINESIZE);
    if (d->tty == NULL) d->tty = tp;
    else {
      tp->nxt = d->tty;
      d->tty = tp;
    }
  }
  
  tp->time += (min(out,d->stop) - max(in,d->start)) + 1;
  if (max(in,d->start) == in) tp->logins++;
  else tp->xlogins++;

  if (hourmask || ((type & HOUR) == HOUR)) {
    b = d->start;
    e = d->start + 3599;
    for(i=0;i<24;i++) {
      if (in >= b && in <= e) tp->h[i] += (min(e,out) - in) + 1;
      else if (out >= b && out <= e) tp->h[i] += (out - max(b,in)) + 1;
      else if (in < b && out > e) tp->h[i] += 3600;

      b += 3600;
      e += 3600;
    }
  }
}

char *getwrd(char *s)
{
  char *t;
  while(*s && isspace(*s)) s++;
  t = s;
  while(*s && !isspace(*s)) s++;
  *s = 0;
  return t;
}

struct usr *adduser(struct usr **up, char *s)
{
  struct usr *u;

  if (*s == '@') {
    char sbuf[BUFSIZE], *str;
    FILE *fd;

    sbuf[BUFSIZE-1] = 0;

    if ((fd = fopen(s+1,"r")) == NULL) {
      fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1);
      exit(1);
    }
    while (fgets(sbuf,BUFSIZE-1,fd) != NULL) {
      str = getwrd(sbuf);
      if (!str[0] || str[0] == '#') continue;
      adduser(up,str);
    }
    fclose(fd);
    return NULL;
  }

  u = bmalloc(sizeof(struct usr));
  strncpy(u->user,s,UT_NAMESIZE);
  u->minlogins = -1;

  u->nxt = *up;
  *up = u;
  return *up;
}

struct usr *finduser(struct usr *up, char *s)
{
  struct usr *u;

  for(u=up;u;u=u->nxt)
    if (!strcmp(s,u->user)) return u;
  return NULL;
}

void addexclude(char *s)
{
  struct exc *x;

  if (*s == '@') {
    char sbuf[BUFSIZE], *str;
    FILE *fd;

    sbuf[BUFSIZE-1] = 0;

    if ((fd = fopen(s+1,"r")) == NULL) {
      fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1);
      exit(1);
    }
    while (fgets(sbuf,BUFSIZE-1,fd) != NULL) {
      str = getwrd(sbuf);
      if (!str[0] || str[0] == '#') continue;
      addexclude(str);
    }
    fclose(fd);
    return;
  }

  x = bmalloc(sizeof(struct exc));
  strncpy(x->user,s,UT_NAMESIZE);
  x->user[UT_NAMESIZE] = 0;
  x->nxt = ex;
  ex = x;
  return;
}

int isexcluded(char *s)
{
  struct exc *u;

  for(u=ex;u;u=u->nxt)
    if (!strncmp(s,u->user,UT_NAMESIZE)) return TRUE;

  return FALSE;
}

struct tty *findtty(struct tty *tt, char *l)
{
  struct tty *t;

  for(t=tt;t;t=t->nxt)
    if (!strncmp(t->line,l,UT_LINESIZE)) return t;
  return NULL;
}

void addtty(char *s)
{
  struct ttys *t;
  int ispat;

  if (*s == '@') {
    char sbuf[BUFSIZE], *str;
    FILE *fd;

    sbuf[BUFSIZE-1] = 0;

    if ((fd = fopen(s+1,"r")) == NULL) {
      fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1);
      exit(1);
    }
    while (fgets(sbuf,BUFSIZE-1,fd) != NULL) {
      str = getwrd(sbuf);
      if (!str[0] || str[0] == '#') continue;
      addtty(str);
    }
    fclose(fd);
    return;
  }

  ispat = ispattern(s);
  if (ispat == -1 || (ispat && patmatch("ttyp0",s) == -1)) usage(6);

  t = bmalloc(sizeof(struct ttys));
  t->line = scopy(s);
  t->ispat = ispat;
  t->nxt = tty;
  tty = t;
  return;
}

int istty(char *s)
{
  struct ttys *t;

  for(t=tty;t;t=t->nxt) {
    if (t->ispat) {
      if (patmatch(s,t->line) == 1) return TRUE;
      continue;
    }
    if (!strncmp(s,t->line,UT_LINESIZE)) return TRUE;
  }

  return FALSE;
}

void addhost(char *s)
{
  struct hosts *h;
  int ispat;

  if (*s == '@') {
    char sbuf[BUFSIZE], *str;
    FILE *fd;

    sbuf[BUFSIZE-1] = 0;

    if ((fd = fopen(s+1,"r")) == NULL) {
      fprintf(stderr,"sac: error opening file '%s' for reading.\n",s+1);
      exit(1);
    }
    while (fgets(sbuf,BUFSIZE-1,fd) != NULL) {
      str = getwrd(sbuf);
      if (!str[0] || str[0] == '#') continue;
      addhost(str);
    }
    fclose(fd);
    return;
  }

  ispat = ispattern(s);
  if (ispat == -1 || (ispat && patmatch("tty",s) == -1)) usage(6);

  h = bmalloc(sizeof(struct hosts));
  h->host = scopy(s);
  h->ispat = ispat;
  h->len = strlen(h->host);
  if (!ispat && ((index(h->host,'.') == NULL) || (h->host[h->len-1] == '.'))) h->ss = TRUE;
  else h->ss = FALSE;
  h->nxt = hosts;
  hosts = h;
  return;
}

int ishost(char *s)
{
  struct hosts *h;

  for(h=hosts;h;h=h->nxt) {
    if (h->ispat) {
      if (patmatch(s,h->host) == 1) return TRUE;
      continue;
    }
    if (h->ss) {
      if (!strncmp(s,h->host,h->len)) return TRUE;
    } else {
      if (!strncmp(s,h->host,UT_HOSTSIZE)) return TRUE;
    }
  }

  return FALSE;
}

struct simuse *findsim(struct simuse **s, int i)
{
  struct simuse *sp, *pp, *n;

  for(pp=sp=*s;sp;sp=sp->nxt) {
    if (sp->index == i) return sp;
    if (sp->index > i) break;
    pp = sp;
  }
  n = bmalloc(sizeof(struct simuse));
  n->index = i;
  n->nxt = sp;
  if (sp == *s) *s = n;
  else pp->nxt = n;
  return n;
}

/* Tests for *, ?, and [] */
int ispattern(char *s)
{
  for(;*s;s++) {
    if (*s == '*') return 1;
    if (*s == '?') return 1;
    if (*s == '[') {
      for(;*s;s++)
	if (*s == ']') return 1;
      return -1;
    }
  }
  return 0;
}

/*
 * Patmatch() code courtesy of Thomas Moore (dark@mama.indstate.edu)
 * returns:
 *    1 on a match
 *    0 on a mismatch
 *   -1 on a syntax error in the pattern
 */
int patmatch(char *sbuf, char *pat)
{
  int match = 1,m,n;

  while(*pat && match) {
    switch(*pat) {
      case '[':
	pat++;
	if(*pat != '^') {
	  n = 1;
	  match = 0;
	} else {
	  pat++;
	  n = 0;
	}
	while(*pat != ']'){
	  if(*pat == '\\') pat++;
	  if(!*pat) return -1;
	  if(pat[1] == '-'){
	    m = *pat;
	    pat += 2;
	    if(*pat == '\\' && *pat)
		pat++;
	    if(*sbuf >= m && *sbuf <= *pat)
		match = n;
	    if(!*pat)
		pat--;
	  } else if(*sbuf == *pat) match = n;
	  pat++;
	}
	sbuf++;
	break;
      case '*':
	pat++;
	if(!*pat) return 1;
	while(*sbuf && !(match = patmatch(sbuf++,pat)));
	return match;
      case '?':
	if(!*sbuf) return 0;
	sbuf++;
	break;
      case '\\':
	if(*pat) pat++;
      default:
	match = (*sbuf++ == *pat);
	break;
    }
    pat++;
    if(match<1) return match;
  }
  if(!*sbuf) return match;
  return 0;
}

void *bmalloc(size_t s)
{
  void *m;

  m = malloc(s);
  if (m == NULL) {
    fprintf(stderr,"sac: memory exhausted.\n");
    exit(1);
  }
  memset(m,0,s);

  return m;
}

void report(void)
{
  struct day *d;
  struct usr *u, *up;
  struct tty *t, *tp;
  struct simuse *s, *sp;
  struct login *l;
  struct tm in, *out;
  time_t h[24], tt;
  double inoct, outoct, inpkt, outpkt;
  long datarate = 0;
  int i;

  /*
   * Narrow down the days list to the range of days specified by the -s and -e
   * options.  This seems kludged in to me, but I really can't see how else to
   * do it.
   */
  if (sd || ed || sm || em) {
    if (!sd) {
      if (sm == MINUSZERO) sd = end->start;
      else if (!sm || sm == PLUSZERO) sd = days->start;
      else if (sm < 0) sd = (end->start + (86400*sm)) + 7200;
      else if (sm > 0) sd = (days->start + (86400*sm)) + 7200;
    }

    if (!ed) {
      if (em == PLUSZERO) ed = days->start;
      else if (!em || em == MINUSZERO) ed = end->start;
      else if (em < 0) ed = (end->start + (86400*em)) + 7200;
      else if (em > 0) ed = (days->start + (86400*em)) + 7200;
    }

    if (sd > ed) {
      fprintf(stderr,"sac: Start time [%ld] is greater than the end time [%ld]\n",sd,ed);
      exit(0);
    }
    for(d=days;d;d=d->nxt) {
      if (d->start <= sd && d->stop >= sd) days = d;
      if (d->start <= ed && d->stop >= ed) {
	d->nxt = NULL;
	end = d;
      }
    }
  }

  /* Produce the reports... */
  switch(type & 0x000f) {
    case TOTAL:
      inoct = outoct = inpkt = outpkt = 0;
      datarate = 0;

      for(ndays=total=0,d=days;d;d=d->nxt) {
	if (hourmask) {
	  for(i=0;i<24;i++) if (hourmask&(1<<i)) total += d->h[i];
	} else total += d->time;
	ndays++;
	if ((type & IPACCT) == IPACCT) {
	  inoct += d->inoct;
	  outoct += d->outoct;
	  inpkt += d->inpkt;
	  outpkt += d->outpkt;
	  datarate = ndays? ((datarate*(ndays-1))+d->datarate)/ndays : d->datarate;
	}
      }
      if (backtime) printf("Total:   %s over %s hours.\n", dtime(total,0), back);
      else printf("Total: %12s over %d days.\n", dtime(total,0), ndays);
      if ((type & AVERAGE) == AVERAGE) {
	printf("Average: %10s / day, ",dtime(total,ndays));
	printf("%10s / login\n",dtime(total,logins));
      }
      if ((type & MINMAX) == MINMAX) printf("Logins: %11d   Min: %3d   Max: %3d\n",logins, max(0,minlogins), maxlogins);
      if ((type & IPACCT) == IPACCT) {
	printf("Octets: %12.0f in/out: %10.0f/%12.0f\n",inoct+outoct, inoct, outoct);
	printf("Packets:%12.0f in/out: %10.0f/%12.0f\n",inpkt+outpkt, inpkt, outpkt);
	if (datarate) printf("\tData rate: %9ld\n",datarate);
      }
      if ((type & LOGIN) == LOGIN) {
	for(d=days;d;d=d->nxt) {
	  for(l=d->login;l;l=l->nxt) {
	    out = localtime(&l->start);
	    memcpy(&in,out,sizeof(struct tm));
	    out = localtime(&l->stop);
	    tt = (l->stop - l->start) + 1;
	    if (l->stop < days->start || l->start > end->stop) continue;

	    if (longdate) printf("  %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year);
	    else printf("  %s %2d", month[in.tm_mon], in.tm_mday);
	    printf(" %02d:%02d:%02d - %02d:%02d:%02d %-8.8s %-6.12s %-16.16s %9s\n",
		   in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec,
		   l->name, l->tty, l->host, dtime(tt,0));
	  }
	}
      }
      if ((type & HOUR) == HOUR) {
	for(i=0;i<24;i++) h[i] = 0;
	for(d=days;d;d=d->nxt)
	  for(i=0;i<24;i++) h[i] += d->h[i];
	print_hours(h,total);
      }
      break;
    case DAY:
      for(d=days;d;d=d->nxt) {
	if (hourmask) {
	  for(total=i=0;i<24;i++) if (hourmask&(1<<i)) total += d->h[i];
	} else total = d->time;

	if (longdate) printf("%s %s %2d %4d",dayabbr[d->wday], month[d->month], d->day, d->year);
	else printf("%s %2d",month[d->month],d->day);
	printf("  total %10s",dtime(total,0));

	if ((type & AVERAGE) == AVERAGE) {
	  if (d->logins)
	    printf(" %5ld logins %8s %s/login",d->logins,dtime(total,d->logins),timefmt==SECONDS?"secs":"hrs");
	  else {
	    printf("    no logins");
	    if ((type & MINMAX) == MINMAX) printf("                   ");
	  }
	}
        if ((type & MINMAX) == MINMAX) {
	  if (d->minlogins == -1) d->maxlogins = d->minlogins = d->logins;
	  printf(" Min: %-3d Max: %3d",d->minlogins, d->maxlogins);
	}
	putchar('\n');
	if ((type & IPACCT) == IPACCT) {
	  printf("\tOctets: %12.0f in/out: %10.0f/%12.0f\n",d->inoct+d->outoct, d->inoct, d->outoct);
	  printf("\tPackets:%12.0f in/out: %10.0f/%12.0f\n",d->inpkt+d->outpkt, d->inpkt, d->outpkt);
	  if (d->datarate) printf("\tData rate: %9ld\n",d->datarate);
	}
	if ((type & LOGIN) == LOGIN) {
	  for(l=d->login;l;l=l->nxt) {
	    out = localtime(&l->start);
	    memcpy(&in,out,sizeof(struct tm));
	    out = localtime(&l->stop);
	    tt = (l->stop - l->start) + 1;
	    if (l->stop < days->start || l->start > end->stop) continue;
	    printf("  %-8.8s %02d:%02d:%02d - %02d:%02d:%02d port %-6.12s %-16.16s %10s\n",
		   l->name, in.tm_hour, in.tm_min, in.tm_sec,
		   out->tm_hour, out->tm_min, out->tm_sec,l->tty, l->host, dtime(tt,0));
	  }
	}
	if ((type & HOUR) == HOUR) print_hours(d->h,d->time);
      }
      break;
    case USER:
      for(u=us;u;u=u->nxt) {
	u->logins = 0;
	u->minlogins = -1;
	u->maxlogins = 0;
	u->ignore = ignoretime;
	u->inoct = u->outoct = u->inpkt = u->outpkt = 0;
	u->datarate = 0;

	for(ndays=total=0,d=days;d;d=d->nxt) {
	  if ((up = finduser(d->us,u->user)) != NULL) {
	    if (u->ignore) {
	      if (hourmask || (type & HOUR) == HOUR) {
		up->time -= (up->time <= u->ignore) ? up->time : u->ignore;
		for(i=0;i<24 && u->ignore; i++)
		  if (up->h[i] <= u->ignore) {
		    u->ignore -= up->h[i];
		    up->h[i] = 0;
		  } else {
		    up->h[i] -= u->ignore;
		    u->ignore = 0;
		  }
	      } else {
		if (up->time <= u->ignore) {
		  u->ignore -= up->time;
		  up->time = 0;
		} else {
		  up->time -= u->ignore;
		  u->ignore = 0;
		}
	      }
	    }

	    if (hourmask) {
	      for(i=0;i<24;i++) if (hourmask&(1<<i)) u->time += up->h[i];
	    } else u->time += up->time;

	    u->logins += up->logins;
	    if (d == days) u->logins += up->xlogins;
	    if ((type & HOUR) == HOUR) for(i=0;i<24;i++) u->h[i] += up->h[i];
	    if ((type & MINMAX) == MINMAX) {
	      if (up->minlogins == -1) up->maxlogins = up->minlogins = up->logins;
	      u->maxlogins = max(u->maxlogins,up->maxlogins);
	      if (u->minlogins > -1) u->minlogins = min(u->minlogins,up->minlogins);
	      else  u->minlogins = up->minlogins;
	    }
	    if ((type & IPACCT) == IPACCT) {
	      u->inoct += up->inoct;
	      u->outoct += up->outoct;
	      u->inpkt += up->inpkt;
	      u->outpkt += up->outpkt;
	      ndays++;
	      u->datarate = ndays? ((datarate*(ndays-1))+up->datarate)/ndays : up->datarate;
	    }
	  } else {
	    if ((type & MINMAX) == MINMAX) u->minlogins = min(0,u->minlogins);
	  }
	}
	printf("%-16s %10s",u->user,dtime(u->time,0));
	if ((type & AVERAGE) == AVERAGE) {
	  if (u->logins)
	    printf(" %4ld logins %9s %s/login",u->logins,dtime(u->time,u->logins),timefmt==SECONDS?"secs":"hrs");
	  else {
	    printf("   no logins");
	    if ((type & MINMAX) == MINMAX) printf("                    ");
	  }
	}
        if ((type & MINMAX) == MINMAX) {
	  if (u->minlogins == -1) u->maxlogins = u->minlogins = 0;
	  printf("  Min: %-3d Max: %3d",u->minlogins, u->maxlogins);
	}
	putchar('\n');
	if ((type & IPACCT) == IPACCT) {
	  printf("\tOctets: %12.0f in/out: %10.0f/%12.0f\n",u->inoct+u->outoct, u->inoct, u->outoct);
	  printf("\tPackets:%12.0f in/out: %10.0f/%12.0f\n",u->inpkt+u->outpkt, u->inpkt, u->outpkt);
	  if (u->datarate) printf("\tData rate: %9ld\n",u->datarate);
	}
	if ((type & LOGIN) == LOGIN) {
	  for(l=u->login;l;l=l->nxt) {
	    out = localtime(&l->start);
	    memcpy(&in,out,sizeof(struct tm));
	    out = localtime(&l->stop);
	    tt = (l->stop - l->start) + 1;
	    if (l->stop < days->start || l->start > end->stop) continue;
	    if (longdate) printf("  %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year);
	    else printf("  %s %2d", month[in.tm_mon], in.tm_mday);
	    printf(" %02d:%02d:%02d - %02d:%02d:%02d port %-6.12s %-16.16s %10s\n",
		   in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec,
		   l->tty, l->host, dtime(tt,0));
	  }
	}
	if ((type & HOUR) == HOUR) print_hours(u->h,u->time);
      }
      break;
    case TTY:
      for(t=ttys;t;t=t->nxt) {
	t->logins = 0;
	for (d=days;d;d=d->nxt) {
	  if ((tp = findtty(d->tty,t->line)) != NULL) {
	    if (hourmask) {
	      for(total=i=0;i<24;i++) if (hourmask&(1<<i)) t->time += tp->h[i];
	    } else t->time += tp->time;
	    t->logins += tp->logins;
	    if (d == days) t->logins += tp->xlogins;
	    if ((type & HOUR) == HOUR) for(i=0;i<24;i++) t->h[i] += tp->h[i];
	  }
	}

	if (t->time == 0) continue;
	printf("    %-8s %10s",t->line,dtime(t->time,0));
	if ((type & AVERAGE) == AVERAGE)
	  printf("\t%5ld logins %10s %s / login",t->logins,dtime(t->time,t->logins),timefmt==SECONDS?"seconds":"hours");
	putchar('\n');
	if ((type & IPACCT) == IPACCT) {
	  printf("\tOctets: %12.0f in/out: %10.0f/%12.0f\n",t->inoct+t->outoct, t->inoct, t->outoct);
	  printf("\tPackets:%12.0f in/out: %10.0f/%12.0f\n",t->inpkt+t->outpkt, t->inpkt, t->outpkt);
	  if (t->datarate) printf("\tData rate: %9ld\n",t->datarate);
	}
	if ((type & LOGIN) == LOGIN) {
	  for(l=t->login;l;l=l->nxt) {
	    out = localtime(&l->start);
	    memcpy(&in,out,sizeof(struct tm));
	    out = localtime(&l->stop);
	    tt = (l->stop - l->start) + 1;
	    if (l->stop < days->start || l->start > end->stop) continue;
	    if (longdate) printf("  %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year);
	    else printf("  %s %2d", month[in.tm_mon], in.tm_mday);
	    printf(" %02d:%02d:%02d - %02d:%02d:%02d %-8.8s %-16.16s %10s\n",
		   in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec,
		   l->name, l->host, dtime(tt,0));
	  }
	}
	if ((type & HOUR) == HOUR) print_hours(t->h,t->time);
      }
      break;
    case SIMUSE:
      for(d=days;d;d=d->nxt) {
	for(s=d->simuse;s;s=s->nxt) {
	  sp = findsim(&simuse,s->index);
	  sp->count += s->count;
	  sp->time += s->time;
	  for(i=0;i<24;i++) sp->h[i] += s->h[i];
	  if (s->login == NULL) continue;
	  if (sp->login == NULL) sp->login = s->login;
	  else sp->lastlogin->nxt = s->login;
	  sp->lastlogin = s->lastlogin;
	}
      }
      for (s=simuse;s;s=s->nxt) {
	printf("  %3d ttys %10s",s->index,dtime(s->time,0));
	printf("  %10s %s usage",dtime(s->time*s->index,0),timefmt==SECONDS?"secs": "hrs");
	if ((type & AVERAGE) == AVERAGE)
	  printf("  %5ld times %8s /instance", s->count, dtime(s->time,s->count));
	putchar('\n');
	if ((type & LOGIN) == LOGIN) {
	  for(l=s->login;l;l=l->nxt) {
	    out = localtime(&l->start);
	    memcpy(&in,out,sizeof(struct tm));
	    out = localtime(&l->stop);
	    tt = (l->stop - l->start) + 1;
	    if (l->stop < days->start || l->start > end->stop) continue;
	    if (longdate) printf("  %s %s %2d %4d",dayabbr[in.tm_wday], month[in.tm_mon], in.tm_mday, 1900+in.tm_year);
	    else printf("  %s %2d", month[in.tm_mon], in.tm_mday);
	    printf(" %02d:%02d:%02d - %02d:%02d:%02d %10s\n",
		   in.tm_hour, in.tm_min, in.tm_sec, out->tm_hour, out->tm_min, out->tm_sec, dtime(tt,0));
	  }
	}
	if ((type & HOUR) == HOUR) print_hours(s->h,s->time);
      }
      break;
    case RAW:
      for(i=0;i<24;i++) h[i] = 0;
      for(ndays=total=0,d=days;d;d=d->nxt) {
	if (hourmask) {
	  for(i=0;i<24;i++) {
	    if (hourmask&(1<<i)) {
	      total += d->h[i];
	      h[i] += d->h[i];
	    }
	  }
	} else {
	  total += d->time;
	  for(i=0;i<24;i++) h[i] += d->h[i];
	}
	ndays++;
	for(u=us;u;u=u->nxt) {
	  if ((up = finduser(d->us,u->user)) != NULL) {
	    if (hourmask) {
	      for(i=0;i<24;i++) if (hourmask&(1<<i)) u->time += up->h[i];
	    } else u->time += up->time;

	    u->logins += up->logins;
	    if (d == days) u->logins += up->xlogins;
	    for(i=0;i<24;i++) u->h[i] += up->h[i];
	  }
	}
	for(t=ttys;t;t=t->nxt) {
	  if ((tp = findtty(d->tty,t->line)) != NULL) {
	    if (hourmask) {
	      for(i=0;i<24;i++) if (hourmask&(1<<i)) t->time += tp->h[i];
	    } else t->time += tp->time;
	    t->logins += tp->logins;
	    if (d == days) t->logins += tp->xlogins;
	    for(i=0;i<24;i++) t->h[i] += tp->h[i];
	  }
	}
      }

      if (days) printf("Days in report: %d  Start: %02d/%02d/%02d  End: %02d/%02d/%02d  Hourmask: %06lX\n",ndays,days->month,days->day,days->year,end->month,end->day,end->year,hourmask&0x0FFF);
      else printf("Days in report: 0  Start: -/-/-  End: -/-/-  Hourmask: %06lX\n",hourmask&0x0FFF);

      printf("Total: %lu (%u logins) hours:",total,logins);
      for(i=0;i<24;i++) printf(" %lu",h[i]);
      printf("\n\n");
      for(i=0,u=us;u;u=u->nxt) i++;
      printf("Users in report: %d\n",i);
      for(u=us;u;u=u->nxt) {
	printf("  %8s total: %-10lu (%5ld logins) hours:",u->user,u->time,u->logins);
	for(i=0;i<24;i++) printf(" %lu",u->h[i]);
	printf("\n");
      }
      printf("\n");
      for(i=0,t=ttys;t;t=t->nxt) i++;
      printf("TTY's in report: %d\n",i);
      for(t=ttys;t;t=t->nxt) {
	printf("  %8s total: %-10lu (%5ld logins) hours:",t->line,t->time,t->logins);
	for(i=0;i<24;i++) printf(" %lu",t->h[i]);
	printf("\n");
      }
      printf("\n");
      for(d=days;d;d=d->nxt) {
	printf("%02d/%02d/%02d total: %-10lu (%5ld logins) hours:",d->month,d->day,d->year,d->time,d->logins);
	for(i=0;i<24;i++) printf(" %lu",d->h[i]);
	printf("\n");
	for(u=d->us;u;u=u->nxt) {
	  printf("  %8s total: %-10lu (%5ld logins) hours:",u->user,u->time,u->logins+(d==days?u->xlogins:0));
	  for(i=0;i<24;i++) printf(" %lu",u->h[i]);
	  printf("\n");
	}
	printf("\n");
	for(t=d->tty;t;t=t->nxt) {
	  printf("  %8s total: %-10lu (%5ld logins) hours:",t->line,t->time,t->logins+(d==days?t->xlogins:0));
	  for(i=0;i<24;i++) printf(" %lu",t->h[i]);
	  printf("\n");
	}
	printf("\n");
      }
  }
}

void print_hours(time_t h[24], time_t total)
{
  static char *bar = "########################################################################";
  int i, bl = strlen(bar)-9;
  float p[24], scale, maxp = 0;

  if (!total) {
    for(i=0;i<24;i++)
      printf("%02d-: %8s\n",i,dtime(0,0));
  } else {
    for(maxp=0,i=0;i<24;i++) {
      p[i] = (float)h[i] / (float)total;
      if (p[i] > maxp) maxp = p[i];
    }
    scale = (float)bl / maxp;

    for(i=0;i<24;i++) {
      if (hourmask && !(hourmask & (1<<i))) continue;
      printf("%02d-: %10s %.*s\n",i,dtime(h[i],0),(int)(scale*p[i]),bar);
    }
  }
}

char *dtime(time_t t, int d)
{
  static char tbuf[80];
  float ft;
  time_t h,m,s;
  char rup = FALSE;

  if ((timefmt&ROUND) == ROUND) rup = TRUE;

  switch(timefmt&0x0f) {
    case FRAC:
      if (d) ft=((float)t/3600)/d;
      else ft=(float)t/3600;
      sprintf(tbuf,"%.2f",ft);
      break;
    case SECONDS:
      if (d) h = t/d;
      else h = t;
      sprintf(tbuf,"%ld",h);
      break;
    case HMS:
      if (d) t=(time_t)((float)t/d);
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-((h*3600)+(m*60));
      sprintf(tbuf,"%ld:%02ld:%02ld",h,m,s);
      break;
    case HM:
      if (d) t=(time_t)((float)t/d);
      h=t/3600;
      m=(t-(h*3600))/60;
      if (rup) {
	s=t-((h*3600)+(m*60));
	if (s >= 30) m++;
	if (m >= 60) {h++; m=0;}
      }
      sprintf(tbuf,"%ld:%02ld",h,m);
      break;
    case HOURS:
      if (d) t=(time_t)((float)t/d);
      h=t/3600;
      if (rup) {
	m=(t-(h*3600))/60;
	s=t-((h*3600)+(m*60));
	if (s >= 30) m++;
	if (m >= 60) {h++; m=0;}
	if (m >= 30) h++;
      }
      sprintf(tbuf,"%ld",h);
      break;
  }
  return tbuf;
}
