/***************************************************************************
 *                                                                         *
 *   AUDAPI.C                                                              *
 *                                                                         *
 *   Copyright (c) 1987-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   Utilities for managing Galacticomm-style audit trail files.           *
 *                                                                         *
 *                                           - J. Alvrus    10/01/1997     *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#ifdef GCWINNT
#include "windows.h"
#endif // GCWINNT

#define FILREV "$Revision: 3 $"

jmp_buf findErrJump;               /* jump buffer for audfFindEntry()      */
#ifdef GCWINNT
GBOOL findErrInit=FALSE;           /* has critical section been init?      */
CRITICAL_SECTION csFindErr;        /* critical section for jump buffer     */

#ifdef GCMVC
#define try         __try
#define finally     __finally
#endif // GCMVC
#endif // GCWINNT

INT                                /*   returns result code                */
audfOpen(                          /* open an audit trail file             */
HAUDFILE *phaud,                   /*   buffer to receive opened handle    */
const CHAR *fileName,              /*   name of file to open               */
AUDMODE mode)                      /*   mode in which to open              */
{
     HAUDFILE haudTmp;
     INT rc;

     ASSERT(phaud != NULL);
     ASSERT(fileName != NULL);
     ASSERT(mode == AUDMODE_READONLY || mode == AUDMODE_READWRITE);
     if ((haudTmp=malloc(sizeof(struct tagAUDFILE))) == NULL) {
          return(AUDERR_MEMORY);
     }
     memset(haudTmp,0,sizeof(struct tagAUDFILE));
     haudTmp->mode=mode;
     stlcpy(haudTmp->name,fileName,GCMAXPTH);
     if ((rc=audfOpenLowLevel(haudTmp)) < AUDERR_OK) {
          free(haudTmp);
     }
     else {
          *phaud=haudTmp;
     }
     return(rc);
}

VOID
audfClose(                         /* close an audit trail file            */
HAUDFILE haud)                     /*   handle to close                    */
{
     ASSERT(haud != NULL);
     if (haud->fp != NULL) {
          fclose(haud->fp);
     }
     free(haud);
}

GBOOL
audfRecOK(                         /* check if raw record in correct format*/
const CHAR *rec)                   /*   raw record to check                */
{
     const CHAR *cp;
     size_t i;

     /* length must be < max, long enough to have channel */
     i=strlen(rec);
     if (i >= AUDRECLEN || i < fldoff(audEntry,channel)+1) {
          return(FALSE);
     }
     cp=rec;
     /* date stamp must be intact */
     for (i=0 ; i < CSTRLEN("YYYYMMDD") ; ++i,++cp) {
          if (!isdigit(*cp)) {
               return(FALSE);
          }
     }
     /* must be a space between date and time */
     if (*cp++ != ' ') {
          return(FALSE);
     }
     /* time stamp must be intact */
     for (i=0 ; i < CSTRLEN("HHMMSS") ; ++i,++cp) {
          if (!isdigit(*cp)) {
               return(FALSE);
          }
     }
     /* must be a space between time and brief */
     if (*cp++ != ' ') {
          return(FALSE);
     }
     /* brief must not contain non-printing characters */
     for (i=0 ; i < AUDBRIEFSIZ-1 ; ++i,++cp) {
          if (*cp < ' ') {
               return(FALSE);
          }
     }
     /* must be a space between brief and channel */
     if (*cp++ != ' ') {
          return(FALSE);
     }
     /* first character of channel must not be space or non-printing */
     if (*cp++ <= ' ') {
          return(FALSE);
     }
     /* channel must not contain non-printing characters */
     for (i=0 ; *cp != '\0' && *cp != '\n' && i < AUDCHANSIZ-2 ; ++i,++cp) {
          if (*cp < ' ') {
               return(FALSE);
          }
     }
     /* must be end of rec or a space between channel and detail */
     if (*cp != '\0' && *cp != '\n' && *cp++ != ' ') {
          return(FALSE);
     }
     /* detail must not contain non-printing characters */
     while (*cp != '\0' && *cp != '\n') {
          if (*cp++ < ' ') {
               return(FALSE);
          }
     }
     return(TRUE);
}

CHAR *
audfPadRec(                        /* make rec correct length & add \r\n   */
CHAR *buf)                         /*   buffer containing record to format */
{
     size_t n;

     n=strlen(buf);
     ASSERT(n != 0);
     if (buf[n-1] == '\n') {
          --n;
     }
     while (n < sizeof(struct audEntry)) {
          buf[n++]=' ';
     }
     strcpy(&buf[sizeof(struct audEntry)],"\r\n");
     ASSERT(strlen(buf) == AUDRECLEN);
     return(buf);
}

INT                                /*   returns result code                */
audfRecover(                       /* recover a damaged audit trail file   */
HAUDFILE haud)                     /*   handle to file to recover          */
{
     FILE *ifp,*ofp,*rfp;
     CHAR *cp;
     INT rc;
     CHAR dstFile[GCMAXPTH],rejFile[GCMAXPTH],recBuf[AUDRECLEN+1];

     ASSERT(haud != NULL);
     ASSERT(haud->fp != NULL);     /* should already have tried audfOpen   */
     ASSERT(isfile(haud->name));   /* should exist if it needs recovery    */
     if (!isfile(haud->name)) {
          return(AUDERR_OK);
     }
     if (haud->fp != NULL) {
          fclose(haud->fp);
          haud->fp=NULL;
     }
     stlcpy(dstFile,haud->name,GCMAXPTH);
     if ((cp=strrchr(dstFile,'.')) != NULL && cp > strrchr(dstFile,SL)) {
          ASSERT(!sameas(".new",cp));   /* don't name audit files *.new    */
          *cp='\0';
     }
     stlcpy(rejFile,dstFile,GCMAXPTH);
     stlcat(dstFile,".new",GCMAXPTH);
     stlcat(rejFile,".rej",GCMAXPTH);
     if ((ifp=fopen(haud->name,FOPRA)) == NULL) {
          return(AUDERR_FIO);
     }
     if ((ofp=fopen(dstFile,FOPWB)) == NULL) {
          fclose(ifp);
          return(AUDERR_FIO);
     }
     if ((rfp=fopen(rejFile,FOPAA)) == NULL) {
          fclose(ifp);
          fclose(ofp);
          return(AUDERR_FIO);
     }
     rc=AUDERR_OK;
     while (fgets(recBuf,sizeof(recBuf),ifp)) {
          if (audfRecOK(recBuf)) {
               audfPadRec(recBuf);
               if (fwrite(recBuf,1,AUDRECLEN,ofp) != AUDRECLEN) {
                    rc=AUDERR_FIO;
                    break;
               }
          }
          else {
               fputs(recBuf,rfp);
          }
     }
     fclose(ifp);
     fclose(ofp);
     fclose(rfp);
     if (rc == AUDERR_OK) {
          if (unlink(haud->name) == 0 && rename(dstFile,haud->name) == 0) {
               rc=audfOpenLowLevel(haud);
          }
          else {
               rc=AUDERR_FIO;
          }
     }
     return(rc);
}

ULONG
audfNumRecs(                       /* get number of records in file        */
HAUDFILE haud)                     /*   audit trail file to use            */
{
     ASSERT(haud != NULL);
     if (haud->fp == NULL) {
          return(0);
     }
     fseek(haud->fp,0,SEEK_END);
     return((ULONG)ftell(haud->fp)/AUDRECLEN);
}

INT                                /*   returns result code                */
audfReadEntry(                     /* read an entry by record number       */
HAUDFILE haud,                     /*   audit trail file to use            */
ULONG recNum,                      /*   record number to read              */
struct audEntry *buf,              /*   buffer to receive data             */
size_t bufSiz)                     /*   size of destination buffer         */
{
     INT rc;
     CHAR tmpBuf[AUDRECLEN];

     if ((rc=audfReadLowLevel(haud,recNum,tmpBuf,AUDRECLEN)) == AUDERR_OK) {
          audfCvtRawToEntry(tmpBuf,buf,bufSiz);
     }
     return(rc);
}

INT                                /*   > 0 = target > test, etc.          */
audfFindComp(                      /* binary search comparison function    */
const VOID *target,                /*   target object ("YYYYMMDD HHMMSS")  */
const VOID *array,                 /*   array object (audit trail file)    */
ULONG recNum)                      /*   index of entry to test             */
{
     INT rc;
     CHAR testBuf[AUDSTAMPSIZ];

     rc=audfReadLowLevel((HAUDFILE)array,recNum,testBuf,AUDSTAMPSIZ);
     if (rc != AUDERR_OK) {
          longjmp(findErrJump,rc);
     }
     testBuf[AUDSTAMPSIZ-1]='\0';
     return(strcmp(target,testBuf));
}

INT                                /*   returns result code                */
audfFindEntryDOS(                  /* find an entry by DOS date and time   */
HAUDFILE haud,                     /*   audit trail file to use            */
USHORT dosDate,                    /*   DOS-style date to search for       */
USHORT dosTime,                    /*   DOS-style time to search for       */
ULONG *pRecNum,                    /*   buf for rec # (NULL if don't want) */
struct audEntry *buf,              /*   buf for entry (NULL if don't want) */
size_t bufSiz)                     /*   size of destination buffer         */
{
     CHAR dateBuf[sizeof("YYYYMMDD")],timeBuf[sizeof("HHMMSS")];

     ASSERT(haud != NULL);
     return(audfFindEntryStr(haud,
                             sDateEncodeDOS(dosDate,dateBuf,sizeof(dateBuf)),
                             sTimeEncodeDOS(dosTime,timeBuf,sizeof(timeBuf)),
                             pRecNum,buf,bufSiz));
}

INT                                /*   returns result code                */
audfFindEntryStr(                  /* find an entry by string date & time  */
HAUDFILE haud,                     /*   audit trail file to use            */
const CHAR *strDate,               /*   YYYYMMDD date to search for        */
const CHAR *strTime,               /*   HHMMSS time to search for          */
ULONG *pRecNum,                    /*   buf for rec # (NULL if don't want) */
struct audEntry *buf,              /*   buf for entry (NULL if don't want) */
size_t bufSiz)                     /*   size of destination buffer         */
{
     ULONG recNum,nRecs;
     INT rc,comp;
     CHAR target[AUDSTAMPSIZ],tmpTarget[AUDSTAMPSIZ];

     ASSERT(haud != NULL);
     ASSERT(strDate != NULL);
     ASSERT(strTime != NULL);
     ASSERT(alldgs(strDate));
     ASSERT(alldgs(strTime));
     ASSERT(strlen(strDate) == CSTRLEN("YYYYMMDD"));
     ASSERT(strlen(strTime) == CSTRLEN("HHMMSS"));
     stlcpy(target,strDate,sizeof(target));
     stlcat(target," ",sizeof(target));
     stlcat(target,strTime,sizeof(target));
     nRecs=audfNumRecs(haud);
     if (nRecs == 0) {
          return(AUDERR_NOTFOUND);
     }
#ifdef GCWINNT
     if (!findErrInit) {
          findErrInit=TRUE;
          InitializeCriticalSection(&csFindErr);
     }
     try {
          EnterCriticalSection(&csFindErr);
#endif // GCWINNT
          if ((rc=setjmp(findErrJump)) == AUDERR_OK) {
               recNum=binFindNear(&comp,target,haud,nRecs,audfFindComp);
          }
#ifdef GCWINNT
     }
     finally {
          LeaveCriticalSection(&csFindErr);
     }
#endif // GCWINNT
     if (rc == AUDERR_OK) {
          if (recNum >= nRecs) {
               --recNum;
          }
          if (recNum > 0) {
               do {
                    rc=audfReadLowLevel(haud,recNum-1,tmpTarget,AUDSTAMPSIZ);
                    tmpTarget[AUDSTAMPSIZ-1]='\0';
                    if (!sameas(target,tmpTarget)) {
                         break;
                    }
                    --recNum;
               } while (rc == AUDERR_OK && recNum > 0);
          }
          if (rc == AUDERR_OK && buf != NULL) {
               rc=audfReadEntry(haud,recNum,buf,bufSiz);
          }
          if (rc == AUDERR_OK && pRecNum != NULL) {
               *pRecNum=recNum;
          }
     }
     return(rc);
}

INT                                /*   returns result code                */
audfReadOldStyle(                  /* read an old-style entry              */
HAUDFILE haud,                     /*   audit trail file to use            */
ULONG recNum,                      /*   record number to read              */
struct audOldStyle *buf,           /*   buffer to receive data             */
size_t bufSiz)                     /*   size of destination buffer         */
{
     INT rc;
     struct audEntry tmpBuf;

     rc=audfReadEntry(haud,recNum,&tmpBuf,sizeof(struct audEntry));
     if (rc == AUDERR_OK) {
          audfCvtEntryToOld(&tmpBuf,buf,bufSiz);
     }
     return(rc);
}

/* miscellaneous utility functions */

struct audEntry *                  /*   returns pointer to destination     */
audfFormatEntry(                   /* create properly-formatted entry      */
struct audEntry *buf,              /*   buffer to receive entry            */
size_t bufSiz,                     /*   size of destination buffer         */
const CHAR *brief,                 /*   brief description                  */
const CHAR *chanStr,               /*   channel description string         */
const CHAR *detail,                /*   detailed description/format string */
va_list argList)                   /*   argument list                      */
{
     struct audEntry tmpEntry;
     CHAR tmpBuf[AUDDETSIZ*2];

     ASSERT(brief != NULL);
     ASSERT(chanStr != NULL);
     ASSERT(detail != NULL);
     memset(&tmpEntry,0,sizeof(struct audEntry));
     ASSERT(sizeof(tmpBuf) >= AUDSTAMPSIZ);
     sDateEncodeDOS(today(),tmpBuf,sizeof("YYYYMMDD"));
     strcat(tmpBuf," ");
     sTimeEncodeDOS(now(),tmpBuf+CSTRLEN("YYYYMMDD "),sizeof("HHMMSS"));
     ASSERT(strlen(tmpBuf) == AUDSTAMPSIZ-1);
     stlcpy(tmpEntry.stamp,tmpBuf,AUDSTAMPSIZ);
     stlcpy(tmpEntry.brief,brief,AUDBRIEFSIZ);
     stlcpy(tmpEntry.channel,chanStr,AUDCHANSIZ);
     memset(tmpBuf,0,sizeof(tmpBuf));
     vsprintf(tmpBuf,detail,argList);
     if (tmpBuf[sizeof(tmpBuf)-1] != '\0') {
          catastro("AUDIT TRAIL MESSAGE TOO LONG!\n\"%s\"",tmpBuf);
     }
     if (tmpBuf[AUDDETSIZ-1] != '\0') {
          strcpy(&tmpBuf[AUDDETSIZ-2],"*");
     }
     ASSERT(strlen(tmpBuf) < AUDDETSIZ);
     strcpy(tmpEntry.detail,tmpBuf);
     memcpy(buf,&tmpEntry,min(sizeof(tmpEntry),bufSiz));
     ((CHAR *)buf)[bufSiz-1]='\0';
     return(buf);
}

struct audEntry *                  /*   returns pointer to buffer          */
audfCvtRawToEntry(                 /* convert raw file data to entry       */
const CHAR *src,                   /*   raw file data                      */
struct audEntry *buf,              /*   buffer to receive converted entry  */
size_t bufSiz)                     /*   size of destination buffer         */
{
     memcpy(buf,src,bufSiz);
     if (bufSiz >= fldoff(audEntry,stamp)+AUDSTAMPSIZ) {
          buf->stamp[AUDSTAMPSIZ-1]='\0';
     }
     if (bufSiz >= fldoff(audEntry,brief)+AUDBRIEFSIZ) {
          buf->brief[AUDBRIEFSIZ-1]='\0';
          unpad(buf->brief);
     }
     if (bufSiz >= fldoff(audEntry,channel)+AUDCHANSIZ) {
          buf->channel[AUDCHANSIZ-1]='\0';
          unpad(buf->channel);
     }
     if (bufSiz >= fldoff(audEntry,detail)+AUDDETSIZ) {
          buf->detail[AUDDETSIZ-1]='\0';
          unpad(buf->detail);
     }
     ((CHAR *)buf)[bufSiz-1]='\0';
     return(buf);
}

struct audOldStyle *               /*   returns pointer to buffer          */
audfCvtEntryToOld(                 /* convert an entry to old-style        */
const struct audEntry *src,        /*   new-style audit trail entry        */
struct audOldStyle *buf,           /*   buffer to receive converted entry  */
size_t bufSiz)                     /*   size of destination buffer         */
{
     USHORT dosDate,dosTime;

     memset(buf,0,bufSiz);
     if (bufSiz >= fldoff(audOldStyle,stamp)+AUDOLDSTAMPSIZ) {
          sDecodeDTDOS(src->stamp,&dosDate,&dosTime);
          if (dosDate == GCINVALIDDOT) {
               dosDate=0;
          }
          if (dosTime == GCINVALIDDOT) {
               dosTime=0;
          }
          sprintf(buf->stamp,"%-5.5s %s",nctime(dosTime),ncdate(dosDate));
     }
     if (bufSiz >= fldoff(audOldStyle,brief)+AUDOLDBRIEFSIZ) {
          stlcpy(buf->brief,src->brief,AUDOLDBRIEFSIZ-1);
          memrpl(buf->brief,'\0',' ',AUDOLDBRIEFSIZ);
     }
     if (bufSiz >= fldoff(audOldStyle,channel)+AUDOLDCHANSIZ) {
          stlcpy(buf->channel,src->channel,AUDOLDCHANSIZ);
     }
     if (bufSiz >= fldoff(audOldStyle,detail)+AUDOLDDETSIZ) {
          stlcpy(buf->detail,src->detail,AUDOLDDETSIZ);
          if (buf->detail[AUDOLDDETSIZ-2] != '\0') {
               strcpy(&buf->detail[AUDOLDDETSIZ-3],"*");
          }
     }
     ((CHAR *)buf)[bufSiz-1]='\0';
     return(buf);
}

/* low-level functions */

GBOOL                              /*   returns TRUE if file is damaged    */
audfDamaged(                       /* check file for damage                */
HAUDFILE haud)                     /*   audit trail file to use            */
{
     ULONG fileLen;
     CHAR buf[AUDRECLEN+1];

     /* check for partial records */
     fileLen=(ULONG)ftell(haud->fp);
     if (fileLen%((ULONG)AUDRECLEN) != 0) {
          return(TRUE);
     }
     /* do sanity check on first and last records */
     if (fileLen != 0) {
          if (audfReadLowLevel(haud,0,buf,AUDRECLEN) < AUDERR_OK) {
               return(TRUE);
          }
          buf[AUDRECLEN]='\0';
          if (!samend(buf,"\r\n") || !audfRecOK(unpad(buf))) {
               return(TRUE);
          }
          fileLen=fileLen/AUDRECLEN-1;
          if (audfReadLowLevel(haud,fileLen,buf,AUDRECLEN) < AUDERR_OK) {
               return(TRUE);
          }
          buf[AUDRECLEN]='\0';
          if (!samend(buf,"\r\n") || !audfRecOK(unpad(buf))) {
               return(TRUE);
          }
     }
     return(FALSE);
}

INT                                /*   returns result code                */
audfOpenLowLevel(                  /* low-level open file                  */
HAUDFILE haud)                     /*   audit trail file to use            */
{
     const CHAR *modeStr;
     USHORT shFlags;

     ASSERT(haud != NULL);
     ASSERT(haud->fp == NULL);
     ASSERT(haud->mode == AUDMODE_READONLY || haud->mode == AUDMODE_READWRITE);
     if (haud->mode == AUDMODE_READWRITE) {
          if (isfile(haud->name)) {
               modeStr=FOPRWB;
          }
          else {
               modeStr=FOPWRB;
          }
          shFlags=GSH_DENYWR;
     }
     else {
          modeStr=FOPRB;
          shFlags=GSH_DENYNO;
     }
     if ((haud->fp=gcfsopen(haud->name,modeStr,shFlags)) == NULL) {
          return(AUDERR_FIO);
     }
     fseek(haud->fp,0,SEEK_END);
     if (audfDamaged(haud)) {
          return(AUDERR_DAMAGED);
     }
     return(modeStr == FOPWRB ? AUDERR_NEW : AUDERR_OK);
}

INT                                /*   returns result code                */
audfAddLowLevel(                   /* low-level add entry                  */
HAUDFILE haud,                     /*   audit trail file to use            */
struct audEntry *src,              /*   formatted record to add            */
size_t srcSiz)                     /*   size of source buffer              */
{
     CHAR buf[AUDRECLEN+1];

     ASSERT(haud != NULL);
     if (haud->mode != AUDMODE_READWRITE) {
          return(AUDERR_MODE);
     }
     if (haud->fp == NULL) {
          return(AUDERR_NOTOPEN);
     }
     memcpy(buf,src,min(srcSiz,sizeof(buf)));
     memrpl(buf,'\0',' ',sizeof(struct audEntry));
     strcpy(&buf[sizeof(struct audEntry)],"\r\n");
     ASSERT(strlen(buf) == AUDRECLEN);
     if (fseek(haud->fp,0,SEEK_END) != 0
      || fwrite(buf,1,AUDRECLEN,haud->fp) != AUDRECLEN) {
          return(AUDERR_FIO);
     }
     fclose(haud->fp);
#ifdef DEBUG
     haud->fp=NULL;
#endif // DEBUG
     return(audfOpenLowLevel(haud));
}

INT                                /*   returns result code                */
audfReadLowLevel(                  /* low-level read an entry utility      */
HAUDFILE haud,                     /*   audit trail file to use            */
ULONG recNum,                      /*   record number to read              */
CHAR *buf,                         /*   buffer (must be AUDRECLEN+1 long)  */
size_t bufSiz)                     /*   size of buffer                     */
{
     CHAR tmpBuf[AUDRECLEN];

     ASSERT(haud != NULL);
     ASSERT(buf != NULL);
     if (haud->fp == NULL) {
          return(AUDERR_NOTOPEN);
     }
     if (recNum > audfNumRecs(haud)) {
          return(AUDERR_NOTFOUND);
     }
     if (fseek(haud->fp,recNum*AUDRECLEN,SEEK_SET) != 0
      || fread(tmpBuf,1,AUDRECLEN,haud->fp) != AUDRECLEN) {
          return(AUDERR_FIO);
     }
     memcpy(buf,tmpBuf,min(AUDRECLEN,bufSiz));
     return(AUDERR_OK);
}
