/* $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.
 */

#undef MAIN
#include "sac.h"


/*
extern char **month;

extern char radhost, hosttoo, verbose;
extern int sd, ed;
extern time_t lastent;
extern signed int sm;
extern char *buf;
*/

#define FUDGE_FACTOR		3600	/* 1 hour */

void try_fastseek(int fd, struct file *f)
{
  struct utmp start, end, cur;
  struct stat st;
  u_long s, e, m, t;

  if (f->ftype == ACCT_RADIUS || fd == 0) return;

  if (fstat(fd,&st) < 0) return;
  if ((st.st_size % sizeof(struct utmp)) != 0) return;

  if (lseek(fd,0,SEEK_SET) < 0) return;
  if (read(fd,&start,sizeof(struct utmp)) != sizeof(struct utmp)) return restore(fd);
  if ((t = lseek(fd,-sizeof(struct utmp),SEEK_END)) < 0) return restore(fd);
  if (read(fd,&end,sizeof(struct utmp)) != sizeof(struct utmp)) return restore(fd);

  s = 0;
  e = t/sizeof(struct utmp);
  m = e/2;

  if (!sd) {
    if (!sm) return restore(fd);
  }

  if (start.ut_time >= sd) return restore(fd);

  while(s < e) {
    if (lseek(fd,m*sizeof(struct utmp),SEEK_SET) < 0) return restore(fd);
    if (read(fd,&cur,sizeof(struct utmp)) < 0) return restore(fd);
    if (cur.ut_time < sd) {
      if (cur.ut_time+FUDGE_FACTOR >= sd) return;
      s = m;
      m += (e-s)/2;
    } else {
      e = m;
      m -= (e-s)/2;
    }
  }
}

void restore(int fd)
{
  if (verbose) fprintf(stderr,"sac: fastseek failed, rewinding input.\n");
  lseek(fd,0,SEEK_SET);
  return;
}

#ifdef USER_PROCESS
void gronk_sysv(int fd)
{
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &su.u;
  int n, m;

  lastent = 0;

  while (1) {
    for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    /* Do we have this day allocated? */
    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    /* Q: Why does the following bother me? */
    /* A: It may not handle all possible cases. Wtmp documentation sucks.
     *    Programs are also pretty free to put whatever the hell they want
     *    in wtmp.
     */
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time);
	else if (u->ut_type == USER_PROCESS && strncmp("ftp",u->ut_line,3)) saclogin(su);
	else if (u->ut_type == DEAD_PROCESS && strncmp("ftp",u->ut_line,3)) saclogout(su,FALSE);
	else if (u->ut_type == LOGIN_PROCESS) saclogout(su,FALSE);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	else saclogout(su,FALSE);
      }
    }
  }
}
#endif

/*
 * Do it the bsd way, where u.ut_type is invalid or doesn't get used or
 * doesn't even exist.
 * This is also for non-standard wtmp's kept by programs such as tacacs.
 * Note: Versions of Xterm may write a wtmp entry on exit that looks like
 *	 a login entry if we ignore the ut_type field (which is "dead
 *	 process" in this case).  This will obviously screw things up.
 *
 * This may not be 100%.
 */
void gronk_bsd(int fd)
{
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &su.u;
  int n, m;

  lastent = 0;
  while (1) {
    for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time);
	else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line) && strncmp("ftp",u->ut_line,3)) saclogin(su);
	else saclogout(su,FALSE);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	else saclogout(su,FALSE);
      }
    }
  }
}

/*
 * Do it the tacacs 3.4-3.5 way, with tacacs ancient utmp format, which may work
 * for other ancient (such as old BSD) programs.
 * This too may not be 100%.
 */
void gronk_tacacs3(int fd)
{
  struct tacacs3_utmp t;
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &t;
  int n, m;

  lastent = 0;
  while (1) {
    for(n=m=read(fd,ut,sizeof(struct tacacs3_utmp));n < (int)sizeof(struct tacacs3_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs3_utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    /* put tacacs_utmp into a normal utmp for the rest of this */
    u->ut_time = t.ut_tactime;
    strncpy(u->ut_line,t.ut_line,8);
    strncpy(u->ut_user,t.ut_user,8);
    strncpy(u->ut_host,t.ut_host,16);
/*    u->ut_line[8] = u->ut_user[8] = 0; */

    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time);
	else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line) && strncmp("ftp",u->ut_line,3)) saclogin(su);
	else saclogout(su,FALSE);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	else saclogout(su,FALSE);
      }
    }
  }
}

/*
 * Do it the new 4.x xtacacs way...  Added the comment field -- 16 bytes to store
 * the PID?
 * This too may not be 100%.  Should be merged with above.
 */
void gronk_tacacs4(int fd)
{
  struct tacacs4_utmp t;
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &t;
  int n, m;

  lastent = 0;
  while (1) {
    for(n=m=read(fd,ut,sizeof(struct tacacs4_utmp));n < (int)sizeof(struct tacacs4_utmp);n+=m=read(fd,ut+n,sizeof(struct tacacs4_utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    /* put tacacs_utmp into a normal utmp for the rest of this */
    u->ut_time = t.ut_tactime;
    strncpy(u->ut_line,t.ut_line,8);
    strncpy(u->ut_user,t.ut_user,8);
    strncpy(u->ut_host,t.ut_host,16);
/*    u->ut_line[8] = u->ut_user[8] = 0; */

    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (u->ut_user[0] == '?') saclogout(su,FALSE);
	else if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time);
	else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line) && strncmp("ftp",u->ut_line,3)) saclogin(su);
	else saclogout(su,FALSE);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	saclogout(su,FALSE);
      }
    }
  }
}

/*
 * Perform accounting on ftp logins only.  This handles ftp entries generated
 * from wu-ftpd at least.
 */
void gronk_ftp(int fd)
{
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &su.u;
  int n, m;

  lastent = 0;
  while (1) {
    for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strcmp("shutdown",u->ut_user))) do_reboot(u->ut_time);
	else if (!strncmp("ftp",u->ut_line,3)) saclogin(su);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	else if (!strncmp("ftp",u->ut_line,3)) saclogout(su,FALSE);
      }
    }
  }
}

/*
 * Perform accounting on both normal logins and ftp simultaneously.
 */
#ifdef USER_PROCESS
void gronk_both(int fd)
{
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &su.u;
  int n, m;

  lastent = 0;
  while (1) {
    for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time);
	else if (u->ut_type == USER_PROCESS || !strncmp("ftp",u->ut_line,3)) saclogin(su);
	else if (u->ut_type == DEAD_PROCESS) saclogout(su,FALSE);
	else if (u->ut_type == LOGIN_PROCESS) saclogout(su,FALSE);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	else saclogout(su,FALSE);
      }
    }
  }
}
#else
void gronk_both(int fd)
{
  struct sactmp su;
  struct utmp *u = &su.u;
  void *ut = &su.u;
  int n, m;

  lastent = 0;
  while (1) {
    for(n=m=read(fd,ut,sizeof(struct utmp));n < (int)sizeof(struct utmp);n+=m=read(fd,ut+n,sizeof(struct utmp)-n)) {
      if (m < 0) {
	perror("sac");
	return;
      }
      if (!m) return;
    }

    /* corrupted (zapped) wtmp entry! Could be an 3l33t3 h4kk0r. */
    if (u->ut_time == 0) {
      time_t t,h,m,s;

      if (!end) {
	if (verbose) fprintf(stderr,"sac: corrupted entry at beginning of wtmp.\n");
	continue;
      }

      t = lastent - end->start;
      h=t/3600;
      m=(t-(h*3600))/60;
      s=t-(h*3600)-(m*60);

      if (verbose) fprintf(stderr,"sac: corrupted (zapped) entry after %s %d %2ld:%02ld:%02ld.\n",month[end->month],end->day,h,m,s);
      continue;
    }
    lastent = u->ut_time;

    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time,FALSE);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry, possible wtmp corruption.\n");
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) {
	if (!strncmp("~",u->ut_line,1) && (!strcmp("reboot",u->ut_user) || !strncmp("shutdown",u->ut_user,8))) do_reboot(u->ut_time,FALSE);
	else if (strcmp("LOGIN",u->ut_user) && strcmp("~",u->ut_line)) saclogin(su,FALSE);
	else saclogout(su,FALSE);
      } else {
	if (!strcmp("|",u->ut_line) || !strcmp("{",u->ut_line)) changetime(u->ut_time,u->ut_line);
	else saclogout(su,FALSE);
      }
    }
  }
}
#endif


/* Modify this to be as large as you like. */
/* The linux kernel may give a bias towards 32k */
#define RADIUS_BUFFER_SIZE	32768

#define RADIUS_HASH_SIZE	256
#define radhash(x)		(x%RADIUS_HASH_SIZE)
struct ltable {
  int port;
  struct utmp last;
  struct ltable *nxt;
} *ltable[RADIUS_HASH_SIZE];

/*
 * Perform accounting for radius log files for livingston portmasters.
 */
void gronk_radius(int fd)
{
  struct sactmp su;
  struct utmp *u = &su.u;
  struct ltable *lp, *cp;
  FILE *rfd;
  int r;
  static char buffer[RADIUS_BUFFER_SIZE];

  /* I don't want to re-invent the wheel, until I know it works, cause no one
     may want to use this anyway. */

  rfd = fdopen(fd,"r");

  /* This seems to make things fast enough for most people.  Faster by far than perl */
  setvbuf(rfd,buffer,_IOFBF,RADIUS_BUFFER_SIZE);
  /* Comment out above to reduce sac memory usage. */

  for(r=0;r<RADIUS_HASH_SIZE;r++) ltable[r] = NULL;

  do {
    r = read_radius(rfd,&su);
    if (r < 0) break;
    if (checkday(u->ut_time) < 0) {
      u->ut_time = lastent;
      do_reboot(u->ut_time);
      if (verbose) fprintf(stderr,"sac: processing stopped due to bad time entry (before %.24s), possible detail corruption.\n",ctime(&lastent));
      return;
    }
    if (u->ut_line[0]) {
      if (u->ut_user[0]) saclogin(su);
      else saclogout(su,TRUE);
    }
    lastent = u->ut_time;
  } while(r >= 0);

  for(r=0;r<RADIUS_HASH_SIZE;r++) {
    for(lp=cp=ltable[r];cp;lp=cp) {
      cp=cp->nxt;
      free(lp);
    }
  }
  fclose(rfd);
}

/*
 * Reads a radius entry.
 * <rant mode=on>
 * Radius logs suck suck suck... It would be nice if you had the option of
 * storing the logs in a nice machine readable format since it would probably
 * make cutting through the log 100 times faster at least.
 *
 * There are at least 5 different radius detail log formats, all slightly
 * different.  Sac tries to support them all.
 * Using the Acct-Session-Time field is problematic, although not impossible,
 * because at least some radius entries will sometimes write multiple stop
 * records for a single logout.  The only way to detect this is to check the
 * current record against the last (for that particular port) and if it
 * matches, ignore it.
 * </rant>
 * In this function we read forward until we get to a date entry, then parse
 * the date, and read the info block.
 */
int read_radius(FILE *rfd, struct sactmp *su)
{
  char **s, name[UT_NAMESIZE];
  int n, i, r;
  struct tm t;
  time_t st;
  struct sactmp tu;
  struct ltable *lp;
  int port;

  memset(su,0,sizeof(struct sactmp));

  while(fgets(buf,1024,rfd) != NULL) {
    if (buf[0] == '\n' || buf[0] == ' ') continue;
    s = split(buf, " \f\n\r\t\v:\"", &n);
    if (n < 4) continue;
    if (n == 7) {
      for(i=0;i<12;i++)
	if (!strcmp(s[1],month[i])) t.tm_mon = i;
      t.tm_mday = stoi(s[2]);
      t.tm_hour = stoi(s[3]);
      t.tm_min = stoi(s[4]);
      t.tm_sec = stoi(s[5]);
      t.tm_year = stoi(s[6]) - 1900;
      t.tm_isdst = -1;
    } else if (n == 4){
      s[0][2] = 0;
      t.tm_mday = stoi(s[0]);
      s[0][5] = 0;
      t.tm_mon = stoi(s[0]+3)-1;
      t.tm_year = stoi(s[0]+6) - 1900;
      t.tm_hour = stoi(s[1]);
      t.tm_min = stoi(s[2]);
      t.tm_sec = stoi(s[3]);
      t.tm_isdst = -1;
    }
    su->u.ut_time = mktime(&t);
    r = read_block(rfd,su,&port,&st,name);
/*  printf("%ld: [%-8.8s] %-8.8s %.16s\n",su->u.ut_time,su->u.ut_user,su->u.ut_line,su->u.ut_host); */

    for(lp=ltable[radhash(port)];lp;lp=lp->nxt)
      if (lp->port == port) break;

    if (st) {
      struct user *p;

      for(p=usr;p;p=p->nxt)
	if (!strncmp(su->u.ut_line,p->line,UT_LINESIZE) && (hosttoo? !strncmp(su->u.ut_host,p->host,UT_HOSTSIZE) : TRUE)) goto found_one;

      if (lp && !strncmp(lp->last.ut_user,su->u.ut_user,UT_NAMESIZE) && (hosttoo? !strncmp(lp->last.ut_host,su->u.ut_host,UT_HOSTSIZE) : TRUE)) {
/*	printf("Extraneous stop ignored.\n"); */
	goto found_one;
      }

/*
      printf("Stop entry with no start [%ld seconds]:\n",st);
      printf("%ld: [%-8.8s] %-8.8s %.16s\n",su->u.ut_time,name,su->u.ut_line,su->u.ut_host);
*/

      tu.u.ut_time = su->u.ut_time - st;
      strncpy(tu.u.ut_user,name,UT_NAMESIZE);
      strncpy(tu.u.ut_host,su->u.ut_host,UT_HOSTSIZE);
      strncpy(tu.u.ut_line,su->u.ut_line,UT_LINESIZE);

      if (!checkday(tu.u.ut_time))
	saclogin(tu);
    }
found_one:

    if (!lp) {
      lp = bmalloc(sizeof(struct ltable));
      lp->port = port;
      lp->nxt = ltable[radhash(port)];
      ltable[radhash(port)] = lp;
    }
    memcpy(&lp->last,&su->u,sizeof(struct utmp));
    if (r < 0) return -1;
    if (r == 0) continue;

    return 0;
  }
  return -1;
}

enum {
  USER_NAME, CLIENT_ID, CLIENT_PORT_ID, ACCT_STATUS_TYPE, ACCT_SESSION_TIME,
  ACCT_INPUT_OCTETS, ACCT_OUTPUT_OCTETS, ACCT_INPUT_PACKETS, ACCT_OUTPUT_PACKETS,
  DATA_RATE, FRAMED_IP_ADDRESS, CALLED_STATION_ID, USELESS
};
/* This ought to be initialized into a tree or hash table for speed. */
struct radius_str {
  char *id;
  char use;
} radius_strs[] = {
  {"User-Name",			USER_NAME},
  {"Client-Id",			CLIENT_ID},
  {"NAS-IP-Address",		CLIENT_ID},
  {"NAS-Identifier",		CLIENT_ID},
  {"Client-Port-Id",		CLIENT_PORT_ID},
  {"NAS-Port",			CLIENT_PORT_ID},
  {"NAS-Port-Id",		CLIENT_PORT_ID},
  {"Acct-Status-Type",		ACCT_STATUS_TYPE},
  {"Acct-Session-Time",		ACCT_SESSION_TIME},
  {"Acct-Input-Octets",		ACCT_INPUT_OCTETS},
  {"Acct-Output-Octets",	ACCT_OUTPUT_OCTETS},
  {"Acct-Input-Packets",	ACCT_INPUT_PACKETS},
  {"Acct-Output-Packets",	ACCT_OUTPUT_PACKETS},
  {"Ascend-Data-Rate",		DATA_RATE},
  {"Framed-IP-Address",		FRAMED_IP_ADDRESS},
  {"Called-Station-Id",		CALLED_STATION_ID},
  {NULL, USELESS}
};

/*
 * Check the ID strings in the block against those above, if it's one of them,
 * then process it.  Exit when we hit a blank line.
 */
int read_block(FILE *rfd, struct sactmp *su, int *port, time_t *st, char *name)
{
  char **s, *p, stopflg;
  int i, n, flg = FALSE;
  int framed = FALSE, callid = FALSE;

  *st = 0;

  for(stopflg = FALSE;fgets(buf,1024,rfd) != NULL;) {
    if (buf[0] == '\n') {
      if (stopflg) {
	strncpy(name,su->u.ut_user,UT_NAMESIZE);
	su->u.ut_user[0] = 0;
      }
      return 1;
    }
    s = split(buf, " \f\n\r\t\v:\"", &n);
    for(i=0;radius_strs[i].id;i++)
      if (!strcmp(radius_strs[i].id,s[0]))
	switch(radius_strs[i].use) {
	  case USER_NAME:
	    if (s[2] == NULL) {
	      strncpy(su->u.ut_user,"UNKNOWN",UT_NAMESIZE);
	      break;
	    }
	    if (s[2][0] == '!') s[2]++;
	    else if ((p=index(s[2],'@'))) {
	      *p++ = 0;
	      if (radhost) strncpy(su->u.ut_host,p,UT_HOSTSIZE);
	    }
	    strncpy(su->u.ut_user,s[2],UT_NAMESIZE);
	    flg = TRUE;
	    break;
#if USE_CALLED_STATION_ID != 0
	  case CALLED_STATION_ID:
	    strncpy(su->u.ut_host,s[2],UT_HOSTSIZE);
	    callid = TRUE;
	    break;
#endif
#if USE_FRAMED_IP_ADDR != 0
	  case FRAMED_IP_ADDRESS:
	    if (!radhost && !callid) strncpy(su->u.ut_host,s[2],UT_HOSTSIZE);
	    framed = TRUE;
	    break;
#endif
	  case CLIENT_ID:
	    if (!framed && !radhost && !callid) strncpy(su->u.ut_host,s[2],UT_HOSTSIZE);
	    break;
	  case CLIENT_PORT_ID:
	    *port = stoi(s[2]);
	    strncpy(su->u.ut_line,s[2],UT_LINESIZE);
	    break;
	  case ACCT_STATUS_TYPE:
	    if (!strcmp("Stop",s[2])) stopflg = TRUE;
	    break;
	  case ACCT_SESSION_TIME:
	    *st = stoi(s[2]);
	    break;
	  case ACCT_INPUT_OCTETS:
	    su->inoct = (double)stoi(s[2]);
	    break;
	  case ACCT_OUTPUT_OCTETS:
	    su->outoct = (double)stoi(s[2]);
	    break;
	  case ACCT_INPUT_PACKETS:
	    su->inpkt = (double)stoi(s[2]);
	    break;
	  case ACCT_OUTPUT_PACKETS:
	    su->outpkt = (double)stoi(s[2]);
	    break;
	  case DATA_RATE:
	    su->datarate = stoi(s[2]);
	    break;
	}
  }
  if (stopflg) {
    strncpy(name,su->u.ut_user,UT_NAMESIZE);
    su->u.ut_user[0] = 0;
  }
  if (flg) return 1;
  return -1;
}

/*
 * split the line into words broken by whitespace, colons, and quotes
 */
/*
char **split(s, n)
char *s;
int *n;
{
  static char *w[256];
  int i;

  for(i=0;*s;) {
    while (*s && (isspace(*s) || *s == ':' || *s == '"')) s++;
    if (!*s) break;
    w[i++] = s;
    while (*s && !isspace(*s) && *s != ':' && *s != '"') s++;
    if (!*s) break;
    *s = 0;
    s++;
  }
  w[*n = i] = NULL;
  return w;
}
*/

char **split(char *str, char *delim, int *nwrds)
{
  static char *w[256];

  *nwrds = 0;
  w[*nwrds = 0] = strtok(str,delim);

  while (w[*nwrds] && *nwrds < 99) w[++(*nwrds)] = strtok(NULL,delim);

  w[*nwrds] = NULL;
  return w;
}

/*
 * This will probably not be supported unless there is some demand for it.
 */
void gronk_radiuslogfile(int fd)
{
}

/*
 * A simple atoi, for speed.
 * Atoi() might actually be faster. Ought to test that.
 */
u_long stoi(char *s)
{
  u_long n,i;

  if (s[1] == 0) {
    return s[0] - '0';
  } else if (s[2] == 0) {
    return ((s[0]-'0')*10) + (s[1]-'0');
  } else {
    for(i=n=0;s[i];i++)
      n = (n*10) + (s[i]-'0');
    return n;
  }
}
