#define HBBSNODELIB
#define MAIN

#include <ctype.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <exec/exec.h>
#include <exec/types.h>

#include <dos/dos.h>
#include <dos/dostags.h>

#include <libraries/reqtools.h>

#include <intuition/intuition.h>

#include <devices/console.h>
#include <devices/conunit.h>

#include <devices/serial.h>
#include <hardware/cia.h>

#include <graphics/gfxbase.h>
#include <graphics/scale.h>


#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/alib_protos.h>
#include <clib/reqtools_protos.h>
#include <clib/graphics_protos.h>

#include <pragmas/exec_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/reqtools.h>


#include "//common/types.h"
#include "//common/errors.h"
#include "//common/defines.h"
#include "//common/structures.h"
#include "//common/strings.h"
#include "//common/files.h"
#include "//common/access.h"
#include "//common/release.h"

#include "//library/hbbscommon_protos.h"
#include "//library/hbbscommon_pragmas.h"

#define ClrSignal(s)  SetSignal(0,s)

char *versionstr="$VER: HBBSNode.library "RELEASE_STR;


// needed libs..

struct DosLibrary *DOSBase=NULL;
struct GfxBase *GfxBase=NULL;
struct ExecBase *SysBase=NULL;
struct ReqToolsBase *ReqToolsBase=NULL;
struct Library *HBBSCommonBase=NULL;

struct BBSGlobalData *BBSGlobal=NULL;
struct NodeData *N_ND=NULL;

struct DoorData C_DOOR; // current door (if active of course!)

V_BOOL DoorOK=FALSE;

// * SOME FORWARD DEFINES..

LONG __asm __saveds LIBHBBS_TimeOnline( void );
LONG __asm __saveds LIBHBBS_TimeLeft( void );

void DoorStatus(V_BIGNUM status)
{
  struct DoorActivityMsg *DMsg;

  // memory will be free'd by NODE program (if node is alive of course!)
  if (DMsg=(struct DoorActivityMsg *)AllocVec(sizeof(struct DoorActivityMsg),MEMF_PUBLIC))
  {
    DMsg->message.mn_Node.ln_Type = NT_MESSAGE;
    DMsg->message.mn_ReplyPort=NULL;  // must set to null!
    DMsg->message.mn_Length=sizeof(struct DoorActivityMsg);
    DMsg->MsgType=mtype_DOORACTIVITY;
    DMsg->Status=status;
    SendMessage((struct Message*)DMsg,N_ND->PortName);
  }
}

BOOL SendStartDoorMsg( void )
{
  struct DoorActivityMsg *DMsg;
  BOOL retval=FALSE;

  if (DMsg=AllocVec(sizeof(struct DoorActivityMsg ),MEMF_PUBLIC))
  {
    DMsg->message.mn_Node.ln_Type = NT_MESSAGE;
    DMsg->message.mn_ReplyPort=C_DOOR.ReplyPort;
    DMsg->message.mn_Length=sizeof(struct DoorActivityMsg);
    DMsg->MsgType=mtype_DOORACTIVITY;
    DMsg->Status=1;
    if (SendMessage((struct Message*)DMsg,N_ND->DoorStartPortName)) retval=TRUE;
    FreeVec(DMsg);
  }
  return(retval);
}


BOOL __asm __saveds LIBHBBS_InitNode(register __d0 int N_NodeNum)
{
  SysBase = *(struct ExecBase **) 4L;
  if (DOSBase = (struct DosLibrary *) OpenLibrary (DOSNAME, 0))
  {
    if (GfxBase = (struct GfxBase *) OpenLibrary (GRAPHICSNAME, 0))
    {
      if (ReqToolsBase = (struct ReqToolsBase *) OpenLibrary (REQTOOLSNAME, REQTOOLSVERSION))
      {
        if (HBBSCommonBase = OpenLibrary("HBBSCommon.library",0))
        {
          if (BBSGlobal=HBBS_GimmeBBS())
          {
            if (N_ND=HBBS_NodeDataPtr(N_NodeNum))
            {
              return(TRUE);
            }
          }
        }
      }
    }
  }
  return(FALSE);
}

void __asm __saveds LIBHBBS_CleanUpNode( void )
{
  if (HBBSCommonBase)
  {
//    if (N_ND) HBBS_ResetNodeData(N_ND); // reset node variables
    CloseLibrary (HBBSCommonBase);
  }
  if (ReqToolsBase) CloseLibrary ((struct Library *)ReqToolsBase);
  if (GfxBase) CloseLibrary ((struct Library *) GfxBase);
  if (DOSBase) CloseLibrary ((struct Library *) DOSBase);
}

BOOL __asm __saveds LIBHBBS_InitDoor( register __d0 int N_NodeNum, register __a0 char *name)
{
  if (LIBHBBS_InitNode(N_NodeNum))
  {
    C_DOOR.node.ln_Name=name;

    sprintf(C_DOOR.DoorPortName,"HBBS_Node%dDoorPort_%d",N_NodeNum,N_ND->DoorsRunning+1); // we increment door number below..

    if (C_DOOR.DoorPort=CreatePort(C_DOOR.DoorPortName,0)) // named port
    {
      if (C_DOOR.ReplyPort=CreateMsgPort()) // unnamed port
      {
        // these must be updated at the same time as we don't want another program referencing
        // ActiveDoor AND the door list and it not being correct now do we ? :-)

        Forbid();
        N_ND->ActiveDoor=&C_DOOR;
        AddHead(N_ND->DoorList,(struct Node*)&C_DOOR);
        N_ND->DoorsRunning++;
        Permit();

        // tell the NODE the door is running OK (if the node does not get a door OK message)
        // within 15 seconds then it timesout! (unlike /X which locks up!!)

        if (SendStartDoorMsg())
        {
          DoorStatus(DMSG_DOORSTARTED);
          // set the internal library's flag (used when we close the door down to free mem!
          DoorOK=TRUE;
          return(TRUE);
        }

      }
      else DeletePort(C_DOOR.DoorPort);
    }
  }
  return(FALSE);
}

void __asm __saveds LIBHBBS_CleanUpDoor( void )
{
  if (DoorOK)
  {
    DeleteMsgPort(C_DOOR.ReplyPort); // unnnamed port
    DeletePort(C_DOOR.DoorPort);     // named port
    Forbid();
    // remove ourself from the doorlist

    RemHead(N_ND->DoorList);

    N_ND->DoorsRunning--;

    // set the active door to the door that called us (if we were spawned by another door)
    // if not, set activedoor to null

    if (N_ND->DoorList->lh_Head->ln_Succ) // more doors ?
    {
      N_ND->ActiveDoor=(struct DoorData *)N_ND->DoorList->lh_Head;
    }
    else N_ND->ActiveDoor=NULL;

    Permit();


    // tell the node that we've finished!
    DoorStatus(DMSG_DOORFINISHED);

  }
  LIBHBBS_CleanUpNode();
}
void  __asm __saveds LIBConWriteData(register __a0 UBYTE *data,register __d0 ULONG length)
{
  if (N_ND->ConOK && data && length>=0)
  {
    N_ND->ConWrite->io_Command  = CMD_WRITE;
    N_ND->ConWrite->io_Data     = data;
    N_ND->ConWrite->io_Length   = length;

    DoIO((struct IORequest *)N_ND->ConWrite);
  }
}

void  __asm __saveds LIBConWriteStr(register __a0 UBYTE *data)
{
  if (N_ND->ConOK && data)
  {
    N_ND->ConWrite->io_Command  = CMD_WRITE;
    N_ND->ConWrite->io_Data     = data;
    N_ND->ConWrite->io_Length   = -1;

    DoIO((struct IORequest *)N_ND->ConWrite);
  }
}

void  __asm __saveds LIBAbortConRead( void )
{
  if (N_ND->ConWaiting)
  {
    if (!CheckIO((struct IORequest*)N_ND->ConRead))
    {
      AbortIO((struct IORequest*)N_ND->ConRead);
      WaitIO((struct IORequest*)N_ND->ConRead);
    }
    N_ND->ConWaiting=FALSE;
  }
}

void  __asm __saveds LIBSendConReadData( void )
{
  if (N_ND->ConWaiting) LIBAbortConRead();
  N_ND->ConRead->io_Command  = CMD_READ;
  N_ND->ConRead->io_Data     = (APTR)N_ND->ConBuffer;
  N_ND->ConRead->io_Length   = N_ND->ConBufferLen;

  ClrSignal(1L << N_ND->ConRPort->mp_SigBit);
  SendIO((struct IORequest*)N_ND->ConRead);
  N_ND->ConWaiting=TRUE;
}

void  __asm __saveds LIBConReadData(register __d0 ULONG Length ) // make DAMN sure that length is NEVER more that the buffer size!
{
  if (N_ND->ConWaiting) LIBAbortConRead();
  N_ND->ConRead->io_Command  = CMD_READ;
  N_ND->ConRead->io_Data     = (APTR)N_ND->ConBuffer;
  N_ND->ConRead->io_Length   = Length;

  ClrSignal(1L << N_ND->ConRPort->mp_SigBit);
  SendIO((struct IORequest*)N_ND->ConRead);
  N_ND->ConWaiting=TRUE;
}

void  __asm __saveds LIBConWaitData( void )
{
  if (!N_ND->ConWaiting) LIBSendConReadData();
  WaitIO((struct IORequest*)N_ND->ConRead);
  N_ND->ConWaiting=FALSE;
  N_ND->ConBytes=N_ND->ConRead->io_Actual;
}

// aborts an iorequest that has been sent

void __asm __saveds LIBAbortSerRead( void )
{
  if (N_ND->SerWaiting)
  {
    if (!CheckIO((struct IORequest*)N_ND->SerRead))
    {
      AbortIO((struct IORequest*)N_ND->SerRead);
    }
    WaitIO((struct IORequest*)N_ND->SerRead); // to tidy up..
    N_ND->SerWaiting=FALSE;
  }
}

// SendIO()'s a request to read 1 bye

void __asm __saveds LIBSendSerReadData( void )
{
  N_ND->SerRead->IOSer.io_Command  = CMD_READ;
  N_ND->SerRead->IOSer.io_Data     = (APTR)N_ND->SerBuffer;
  N_ND->SerRead->IOSer.io_Length   = 1;

  ClrSignal(1L << N_ND->SerPort->mp_SigBit);
  SendIO((struct IORequest*)N_ND->SerRead);
  N_ND->SerWaiting=TRUE;
}

void __asm __saveds LIBSerWaitData( void )
{
  if (!N_ND->SerWaiting) LIBSendSerReadData();
  WaitIO((struct IORequest*)N_ND->SerRead);
  N_ND->SerBytes=N_ND->SerRead->IOSer.io_Actual;
  N_ND->SerWaiting=FALSE;
}

// SendIO()'s a request to read a block of data

void __asm __saveds LIBSendSerReadBlock(register __a0 UBYTE *data,register __d0 ULONG length )
{
  LIBAbortSerRead(); // *C* not sure about this one..  might slowdown throughput..

  N_ND->SerRead->IOSer.io_Command  = CMD_READ;
  N_ND->SerRead->IOSer.io_Data     = (APTR)data;
  N_ND->SerRead->IOSer.io_Length   = length;

  ClrSignal(1L << N_ND->SerPort->mp_SigBit);
  SendIO((struct IORequest*)N_ND->SerRead);
  N_ND->SerWaiting=TRUE;
}

// DioIO()'s a request to read a block of data

void __asm __saveds LIBWaitSerReadBlock(register __a0 UBYTE *data,register __d0 ULONG length )
{
  LIBSendSerReadBlock(data,length);
  LIBSerWaitData();
}

// DoIO()'s a Query!

ULONG __asm __saveds LIBSerQueryData( void )
{
  N_ND->SerWrite->IOSer.io_Command  = SDCMD_QUERY;
  DoIO((struct IORequest*)N_ND->SerWrite);
  return(N_ND->SerWrite->IOSer.io_Actual);
}

V_BOOL __asm __saveds LIBCarrierLost( void )
{
  if (N_ND->LoginType==LOGIN_REMOTE && !N_ND->NodeDevice.NullModemCable)
  {
    LIBSerQueryData();
    return((N_ND->SerWrite->io_Status & 1L << CIAB_COMCD) ? (V_BOOL)TRUE : (V_BOOL)FALSE);
  }
  else return((V_BOOL)FALSE);
}

// writes a block of data to the serial port, checking for a timeout
// (specified in seconds,micros) and returns FALSE if a timeout occured before all
// the data has been sent, or there was a memory error.

BOOL __asm __saveds LIBSerWriteDataWithTimeout(register __a0 UBYTE *data,register __d0 ULONG length,register __d1 ULONG Seconds,register __d2 ULONG Micros)
{
  BOOL retval=FALSE;
  struct TimerData *TD;

  if (TD=SubmitTimer(N_ND->NodeTimer,Seconds,Micros))
  {
    if (data && length>0)
    {
      LIBAbortSerRead();
      N_ND->SerWrite->IOSer.io_Command  = CMD_WRITE;
      N_ND->SerWrite->IOSer.io_Data     = data;
      N_ND->SerWrite->IOSer.io_Length   = length;

      SendIO((struct IORequest *)N_ND->SerWrite);

      // ok, wait for the timer or serial device to tell us sommat..
      Wait (DEF_TIMERSIG | DEF_SERSIG);

      // ok sommat hapenned. lets check the timer..
      if (CheckTimer(N_ND->NodeTimer,TD))
      {
        // yup, timeout occured, so abort the serial write request..
        AbortIO((struct IORequest *)N_ND->SerWrite);
        TD=NULL;
      }
      else
      {
        // timer not done yet, so it must be the serial port that finished..
        retval=TRUE;
      }
      // and then tidy up..
      WaitIO((struct IORequest *)N_ND->SerWrite);
    }
    if (TD) AbortTimer(N_ND->NodeTimer,TD);
  }
  return(retval);
}

// writes a block of data to the serial port without checking for a timeout..

void __asm __saveds LIBSerWriteData(register __a0 UBYTE *data,register __d0 ULONG length)
{
  if (data && length>0)
  {
    LIBAbortSerRead();
    N_ND->SerWrite->IOSer.io_Command  = CMD_WRITE;
    N_ND->SerWrite->IOSer.io_Data     = data;
    N_ND->SerWrite->IOSer.io_Length   = length;

    DoIO((struct IORequest *)N_ND->SerWrite);
  }
}

BOOL __asm __saveds LIBSerWriteStrWithTimeout(register __a0 UBYTE *str,register __d0 ULONG Seconds,register __d1 ULONG Micros)
{
  BOOL retval=FALSE;
  struct TimerData *TD;

  if (TD=SubmitTimer(N_ND->NodeTimer,Seconds,Micros))
  {
    if (str && str[0])  // str valid pointer ? and does it contain data ??
    {
      LIBAbortSerRead();
      N_ND->SerWrite->IOSer.io_Command  = CMD_WRITE;
      N_ND->SerWrite->IOSer.io_Data     = str;
      N_ND->SerWrite->IOSer.io_Length   = -1;

      ClrSignal(DEF_TIMERSIG | DEF_SERSIG);

      SendIO((struct IORequest *)N_ND->SerWrite);

      // ok, wait for the timer or serial device to tell us sommat..
      Wait (DEF_TIMERSIG | DEF_SERSIG);

      // ok sommat hapenned. lets check the timer..
      if (CheckTimer(N_ND->NodeTimer,TD))
      {
        TD=NULL;
        // yup, timeout occured, so abort the serial write request..
        AbortIO((struct IORequest *)N_ND->SerWrite);
      }
      else
      {
        // timer not done yet, so it must be the serial port that finished..
        retval=TRUE;
        // and then tidy up..
      }
      WaitIO((struct IORequest *)N_ND->SerWrite);
    }
    if (TD) AbortTimer(N_ND->NodeTimer,TD);
  }
  return(retval);
}

// writes a string to the serial device..

void __asm __saveds LIBSerWriteStr(register __a0 UBYTE *str)
{
  if (str && str[0])   // str valid pointer ? and does it contain data ??
  {
    LIBAbortSerRead();
    N_ND->SerWrite->IOSer.io_Command  = CMD_WRITE;
    N_ND->SerWrite->IOSer.io_Data     = str;
    N_ND->SerWrite->IOSer.io_Length   = -1;
    // hehe LamiExpress sets io_Length to strlen(str).. LAME! ;-)
    // if they read the docs they might see that setting io_Length to -1
    // makes the device output the contents of io_Data until it encounters
    // a null terminator! :-)

    DoIO((struct IORequest *)N_ND->SerWrite);
  }
}

// writes a char to the serial device..

void __asm __saveds LIBSerWriteChar(register __d0 UBYTE c)
{
  char ch=c;

  LIBAbortSerRead();
  N_ND->SerWrite->IOSer.io_Command  = CMD_WRITE;
  N_ND->SerWrite->IOSer.io_Data     = &ch;
  N_ND->SerWrite->IOSer.io_Length   = 1;
  DoIO((struct IORequest *)N_ND->SerWrite);
}

void  __asm __saveds LIBPutText(register __a0 UBYTE *str)
{
  // generic routine for all string output, this determines wether or not
  // to write to the console or serial ports..

  if (str && str[0])
  {
    if (N_ND->ConOK)
    {
      N_ND->ConWrite->io_Command  = CMD_WRITE;
      N_ND->ConWrite->io_Data     = str;
      N_ND->ConWrite->io_Length   = -1;
      DoIO((struct IORequest *)N_ND->ConWrite);
    }
    if ((N_ND->LoginType==LOGIN_REMOTE) && (!(N_ND->NodeFlags & NFLG_BLOCKSERIAL)))
    {
      LIBAbortSerRead();
      N_ND->SerWrite->IOSer.io_Command  = CMD_WRITE;
      N_ND->SerWrite->IOSer.io_Data     = str;
      N_ND->SerWrite->IOSer.io_Length   = -1;
      DoIO((struct IORequest *)N_ND->SerWrite);
    }
  }
}

void  __asm __saveds LIBPutData(register __a0 UBYTE *data,register __d0 ULONG Length)
{
  // generic routine for all tring output, this determines wether or not
  // to write to the console or serial ports..

  if (N_ND->ConOK) LIBConWriteData(data,Length);
  if ((N_ND->LoginType==LOGIN_REMOTE)  && (!(N_ND->NodeFlags & NFLG_BLOCKSERIAL))) LIBSerWriteData(data,Length);
}

void  __asm __saveds LIBPutChar(register __d0 UBYTE ch)
{
  char c=ch;
  // generic routine for all tring output, this determines wether or not
  // to write to the console or serial ports..
  if (N_ND->ConOK) LIBConWriteData(&c,1);
  if ((N_ND->LoginType==LOGIN_REMOTE) && (!(N_ND->NodeFlags & NFLG_BLOCKSERIAL))) LIBSerWriteData(&c,1);
}

void  __asm __saveds LIBPutConText(register __a0 UBYTE *str)
{
  // generic routine for all tring output, this determines wether or not
  // to write to the console or serial ports..

  if (N_ND->ConOK) LIBConWriteStr(str);
}

void  __asm __saveds LIBPutConData(register __a0 UBYTE *data,register __d0 ULONG Length)
{
  // generic routine for all tring output, this determines wether or not
  // to write to the console or serial ports..

  if (N_ND->ConOK) LIBConWriteData(data,Length);
}

void  __asm __saveds LIBPutConChar(register __d0 UBYTE ch)
{
  char c=ch;
  // generic routine for all tring output, this determines wether or not
  // to write to the console or serial ports..
  if (N_ND->ConOK) LIBConWriteData(&c,1);
}

ULONG __asm __saveds LIBSetupConSerSigs( void )
{
  N_ND->ConSig=0L;
  N_ND->ConWinSig=0L;
  if (N_ND->ConOK)
  {
    if (N_ND->ConWaiting) N_ND->ConSig=DEF_CONSIG;
    N_ND->ConWinSig=DEF_CONWINSIG;
  }

  // only check serial if remote login.. OR if no-one's connected yet..

  if (N_ND->LoginType !=LOGIN_LOCAL && N_ND->SerOK && N_ND->SerWaiting)
    N_ND->SerSig=DEF_SERSIG;
  else
    N_ND->SerSig=0;

  return(N_ND->SerSig | N_ND->ConSig);
}

ULONG __asm __saveds LIBHandleConSigs(register __d0 ULONG ReturnedSigs)
{
  // a calling routine should call this until it returns false
  // this is cos you might get two signals at the same time
  // and you don't want to miss anything!

  if (ReturnedSigs & N_ND->ConSig && N_ND->ConOK && N_ND->ConWaiting)
  {
    LIBConWaitData();
    N_ND->IBuffer=N_ND->ConBuffer;
    N_ND->IBytes=N_ND->ConBytes;
    return(TRUE);
  }
  return(FALSE);
}

ULONG __asm __saveds LIBHandleSerSigs(register __d0 ULONG ReturnedSigs)
{
  // a calling routine should call this until it returns false
  // this is cos you might get two signals at the same time
  // and you don't want to miss anything!


  if (ReturnedSigs & N_ND->SerSig && N_ND->SerOK && N_ND->SerWaiting)
  {
    LIBSerWaitData();
    N_ND->IBuffer=N_ND->SerBuffer;
    N_ND->IBytes=N_ND->SerBytes;
    return(TRUE);
  }
  return(FALSE);
}

ULONG __asm __saveds LIBHandleConSerSigs(register __d0 ULONG ReturnedSigs)
{
  // a calling routine should call this until it returns false
  // this is cos you might get two signals at the same time
  // and you don't want to miss anything!

  if (LIBHandleConSigs(ReturnedSigs) || LIBHandleSerSigs(ReturnedSigs))
  {
    return(TRUE);
  }
  else return(FALSE);

/*  if (ReturnedSigs & N_ND->ConSig && N_ND->ConOK && N_ND->ConWaiting)
  {
    LIBConWaitData();
    N_ND->IBuffer=N_ND->ConBuffer;
    N_ND->IBytes=N_ND->ConBytes;
    return(TRUE);
  }

  if (ReturnedSigs & N_ND->SerSig && N_ND->SerOK && N_ND->SerWaiting)
  {
    LIBSerWaitData();
    N_ND->IBuffer=N_ND->SerBuffer;
    N_ND->IBytes=N_ND->SerBytes;
    return(TRUE);
  }
  return(FALSE);
*/
}

ULONG SendDoorIOMessage(ULONG Status,UBYTE *Data,ULONG DataLength,ULONG Flags,UBYTE *OptionStr,ULONG Num1,ULONG Num2)
{
  ULONG retval=0;
  struct DoorIOMsg *NewIOMsg=NULL;
  if (NewIOMsg=(struct DoorIOMsg*)AllocVec(sizeof(struct DoorIOMsg),MEMF_PUBLIC))
  {
    NewIOMsg->message.mn_Node.ln_Type = NT_MESSAGE;
    NewIOMsg->message.mn_ReplyPort=C_DOOR.ReplyPort;
    NewIOMsg->message.mn_Length=sizeof(struct DoorIOMsg);
    NewIOMsg->MsgType=mtype_DOORIO;
    NewIOMsg->Status=Status;
    NewIOMsg->Data=Data;
    NewIOMsg->DataLength=DataLength;
    NewIOMsg->ReturnVal=0;
    NewIOMsg->Flags=Flags;
    NewIOMsg->OptionStr=OptionStr;
    NewIOMsg->Num1=Num1;
    NewIOMsg->Num2=Num2;
    SendMessage((struct Message*)NewIOMsg,N_ND->PortName);
    retval=NewIOMsg->ReturnVal;
    FreeVec(NewIOMsg);
  }
  return(retval);
}

ULONG QuickDoorIOMessage(ULONG Status,UBYTE *Data)
{
  // optimized send message for stuff that only uses Data and Datalen

//  return(SendDoorIOMessage(Status,Data,0,0,NULL,0,0));

  ULONG retval=0;
  struct DoorIOMsg *NewIOMsg;
  if (NewIOMsg=(struct DoorIOMsg*)AllocVec(sizeof(struct DoorIOMsg),MEMF_PUBLIC|MEMF_CLEAR))
  {
    NewIOMsg->message.mn_Node.ln_Type = NT_MESSAGE;
    NewIOMsg->message.mn_ReplyPort=C_DOOR.ReplyPort;
    NewIOMsg->message.mn_Length=sizeof(struct DoorIOMsg);
    NewIOMsg->MsgType=mtype_DOORIO;
    NewIOMsg->Status=Status;
    NewIOMsg->Data=Data;

    PutMsg(N_ND->NodePort,(struct Message*)NewIOMsg);

    if (1L << C_DOOR.ReplyPort->mp_SigBit==Wait(1L << C_DOOR.ReplyPort->mp_SigBit))
    {
      GetMsg(NewIOMsg->message.mn_ReplyPort);
      retval=NewIOMsg->ReturnVal;
    }
    else retval=1L;

    FreeVec(NewIOMsg);
    return(retval);

  }
  else return(0);
}

void __asm __saveds LIBDOOR_SysopText(register __a0 UBYTE *str)
{
  QuickDoorIOMessage(DOORIO_WRITECONSTR,str);
}

void __asm __saveds LIBDOOR_WriteText(register __a0 UBYTE *str)
{
  QuickDoorIOMessage(DOORIO_WRITESTR,str);
}

void __asm __saveds LIBDOOR_WriteSerText(register __a0 UBYTE *str)
{
  QuickDoorIOMessage(DOORIO_WRITESERSTR,str);
}

ULONG __asm __saveds LIBDOOR_GetLine(register __d0 ULONG Flags, register __d1 char PasswordChar,register __d2 ULONG MaxLen,register __d3 ULONG Timeout,register __a0 UBYTE *PromptStr)
{
  char c=PasswordChar;

  return(SendDoorIOMessage(DOORIO_GETLINE,&c,1,Flags,PromptStr,MaxLen,Timeout));
}

void __asm __saveds LIBDOOR_UpdateNodeStatus(register __d0 ULONG What)
{
  UBYTE *str=NULL;
  UWORD X=0,MaxLen;
  switch(What)
  {
    case UPD_NAME:
      if (N_ND->User.Valid) str=N_ND->User.NormalData.Handle;
      X=4;
      MaxLen=MAX_NAME_LEN;
      break;
    case UPD_GROUP:
      if (N_ND->User.Valid) str=N_ND->User.NormalData.Group;
      X=164;
      MaxLen=MAX_GROUP_LEN;
      break;
    case UPD_ACTION:
      str=N_ND->Action;
      X=324;
      MaxLen=MAX_ACTION_LEN;
      break;
    case UPD_CPSBAUD:
      if (!N_ND->TransferringFile) str=N_ND->ConnectBaud; // *C* add cps here
      X=556;
      MaxLen=MAX_CPSBAUD_LEN;
      break;
    default:
      return;
  }
  // ok, have we got an option ?
  // and a string to print ?
  // AND if the window open ?
  if ((X) && (str) && (!N_ND->NodeSettings.Iconified))
  {
    SetAPen(N_ND->NodeWnd->RPort,0L); // 0=grey
    SetBPen(N_ND->NodeWnd->RPort,0); // grey!
    RectFill(N_ND->NodeWnd->RPort, X+N_ND->NodeWnd->BorderLeft,15+N_ND->NodeWnd->BorderTop,X+N_ND->NodeWnd->BorderLeft+(8*MaxLen),15+N_ND->NodeWnd->BorderTop+9);

    if ((What == UPD_NAME) && (N_ND->NodeFlags & NFLG_PAGED))
    {
      SetAPen(N_ND->NodeWnd->RPort,2L); // white!
    }
    else
    {
      SetAPen(N_ND->NodeWnd->RPort,1L); // black!
    }
    Move(N_ND->NodeWnd->RPort,1+X+N_ND->NodeWnd->BorderLeft,8+14+N_ND->NodeWnd->BorderTop);  // 8 for text height, 13 for placement
    Text(N_ND->NodeWnd->RPort,str,(strlen(str)>MaxLen) ? MaxLen : strlen(str));
  }
}

// spawn another system door, e.g. "FRONTEND"

void __asm __saveds LIBDOOR_SystemDoor(register __a0 UBYTE *doorname,register __a1 UBYTE *options)
{
  SendDoorIOMessage(DOORIO_SYSTEMDOOR,doorname,0,0,options,0,0);
}

// spawn another user door, e.g. "FR W"

V_BOOL __asm __saveds LIBDOOR_UserDoor(register __a0 UBYTE *doorname,register __a1 UBYTE *options)
{
  return(SendDoorIOMessage(DOORIO_USERDOOR,doorname,0,0,options,0,0));
}

V_BOOL __asm __saveds LIBDOOR_HangUp( void )
{
  return(SendDoorIOMessage(DOORIO_HANGUP,NULL,0,0,NULL,0,0));
}

void __asm __saveds LIBDOOR_Return( register __a0 UBYTE *returnstring )
{
  strNcpy(N_ND->DoorReturn,returnstring,LEN_MAXDOORRETURN);
}

V_BOOL __asm __saveds LIBDOOR_DisplayScreen( register __a0 UBYTE *screenname )
{
  return(SendDoorIOMessage(DOORIO_DISPLAYSCREEN,screenname,0,0,NULL,0,0));
}

V_BOOL __asm __saveds LIBDOOR_DisplaySpecialScreen( register __a0 UBYTE *screenname )
{
  return(SendDoorIOMessage(DOORIO_DISPLAYSPECIALSCREEN,screenname,0,0,NULL,0,0));
}


V_BOOL __asm __saveds LIBDOOR_PausePrompt( register __a0 UBYTE *prompt )
{
  return(QuickDoorIOMessage(DOORIO_PAUSEPROMPT,prompt));
}

void __asm __saveds LIBDOOR_Add_Last_Upload( register __a0 UBYTE *details )
{
  QuickDoorIOMessage(DOORIO_ADDLASTUPLOAD,details);
}



V_BOOL __asm __saveds LIBDOOR_ContinuePrompt( register __a0 UBYTE *prompt,register __d0 V_BIGNUM Flags )
{
  return(SendDoorIOMessage(DOORIO_CONTINUEPROMPT,prompt,0,Flags,NULL,0,0));
}


void __asm __saveds LIBDOOR_MenuPrompt(register __a0 char *promptstr,register __d0 char promptdefault)
{
  char options[2];
  options[0]=promptdefault;
  options[1]=0;

  SendDoorIOMessage(DOORIO_MENUPROMPT,promptstr,0,0,options,0,0);
}

void __asm __saveds LIBDOOR_Continue( register __d0 V_BOOL Continue )
{
  N_ND->DoorContinue=Continue;
}

V_BOOL __asm __saveds LIBValidConfNum(register __d0 V_BIGNUM ConfNum)
{
  // index from 1 (i.e. first conf = 1
  return((V_BOOL)((ConfNum>0 && ConfNum <=BBSGlobal->Conferences) ? TRUE : FALSE));
}

struct ConfData __asm __saveds *LIBFindConf( void )
{
  struct ConfData *Conf=NULL;
  V_BIGNUM ConfNum;


  if (N_ND->CurrentConf)
  {
    Conf=N_ND->CurrentConf;
  }
  else
  {
    if (N_ND->User.Valid)
    {
      if (ConfNum=N_ND->User.CallData.LastConf)
      {
        if (LIBValidConfNum(ConfNum))
        {
          Conf=(struct ConfData *)GetNode(BBSGlobal->ConfList,ConfNum-1);
        }
      }
    }
  }
  return(Conf);

}


void  __asm __saveds LIBLoadAccess( register __a0 char *filename, register __a1 struct AccessData *AD)
{
  struct CfgFileData *CfgFile;
  V_BOOL acsdata;

  if (CfgFile=HBBS_LoadConfig(filename,LCFG_NONE))
  {

    /* Main Options */

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_DLFILES,OPT_SINGLE))
      AD->Data[ACS_DLFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ULFILES,OPT_SINGLE))
      AD->Data[ACS_ULFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_SYSOPCOMMENT,OPT_SINGLE))
      AD->Data[ACS_SYSOPCOMMENT]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_READMAIL,OPT_SINGLE))
      AD->Data[ACS_READMAIL]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_WRITEMAIL,OPT_SINGLE))
      AD->Data[ACS_WRITEMAIL]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_SCANMAIL,OPT_SINGLE))
      AD->Data[ACS_SCANMAIL]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWSTATUS,OPT_SINGLE))
      AD->Data[ACS_ALLOWSTATUS]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWEDIT,OPT_SINGLE))
      AD->Data[ACS_ALLOWEDIT]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_SCANFILES,OPT_SINGLE))
      AD->Data[ACS_SCANFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWVIEW,OPT_SINGLE))
      AD->Data[ACS_ALLOWVIEW]=acsdata ? 'Y' : 'N';


    /* Mail Options */

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_DELETEMAIL,OPT_SINGLE))
      AD->Data[ACS_DELETEMAIL]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_DELETEGROUP,OPT_SINGLE))
      AD->Data[ACS_DELETEGROUP]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_DELETEANYONE,OPT_SINGLE))
      AD->Data[ACS_DELETEANYONE]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWALL,OPT_SINGLE))
      AD->Data[ACS_ALLOWALL]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWEVERYBODY,OPT_SINGLE))
      AD->Data[ACS_ALLOWEVERYBODY]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWMULTIPLE,OPT_SINGLE))
      AD->Data[ACS_ALLOWMULTIPLE]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ATTACHFILES,OPT_SINGLE))
      AD->Data[ACS_ATTACHFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWMULTIFILES,OPT_SINGLE))
      AD->Data[ACS_ALLOWMULTIFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWPRIVATEFILES,OPT_SINGLE))
      AD->Data[ACS_ALLOWPRIVATEFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWALLFILES,OPT_SINGLE))
      AD->Data[ACS_ALLOWALLFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_ALLOWEVERYONEFILES,OPT_SINGLE))
      AD->Data[ACS_ALLOWEVERYONEFILES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_AUTOEXPIREMSG,OPT_SINGLE))
      AD->Data[ACS_AUTOEXPIREMSG]=acsdata ? 'Y' : 'N';

    /* W command settings */

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITHANDLE,OPT_SINGLE))
      AD->Data[ACS_EDITHANDLE]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITREALNAME,OPT_SINGLE))
      AD->Data[ACS_EDITREALNAME]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITPASSWORD,OPT_SINGLE))
      AD->Data[ACS_EDITPASSWORD]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITGROUP,OPT_SINGLE))
      AD->Data[ACS_EDITGROUP]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITLOCATION,OPT_SINGLE))
      AD->Data[ACS_EDITLOCATION]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITCOUNTRY,OPT_SINGLE))
      AD->Data[ACS_EDITCOUNTRY]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITCOMPUTER,OPT_SINGLE))
      AD->Data[ACS_EDITCOMPUTER]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITPHONENUMBER,OPT_SINGLE))
      AD->Data[ACS_EDITPHONENUMBER]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITSCREENTYPE,OPT_SINGLE))
      AD->Data[ACS_EDITSCREENTYPE]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITLINES,OPT_SINGLE))
      AD->Data[ACS_EDITLINES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITEDITOR,OPT_SINGLE))
      AD->Data[ACS_EDITEDITOR]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_EDITPROTOCOL,OPT_SINGLE))
      AD->Data[ACS_EDITPROTOCOL]=acsdata ? 'Y' : 'N';

    /* ULIMITEDxxxx Settings */

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_UNLIMTIME,OPT_SINGLE))
      AD->Data[ACS_UNLIMTIME]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_UNLIMCALLS,OPT_SINGLE))
      AD->Data[ACS_UNLIMCALLS]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_UNLIMBYTES,OPT_SINGLE))
      AD->Data[ACS_UNLIMBYTES]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_UNLIMCHAT,OPT_SINGLE))
      AD->Data[ACS_UNLIMCHAT]=acsdata ? 'Y' : 'N';



/*  Spare ones


    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_,OPT_SINGLE))
      AD->Data[ACS_]=acsdata ? 'Y' : 'N';

    if (HBBS_GetSetting(CfgFile,(void *)&acsdata,VTYPE_BOOL,ACSSTR_,OPT_SINGLE))
      AD->Data[ACS_]=acsdata ? 'Y' : 'N';
*/
    HBBS_FlushConfig(CfgFile);
  }

}

char __asm __saveds *LIBHBBS_ModifyString(register __a0 char *str)
{
  char tmpstr[20];  // *D* Document! the {x} codes

  // Time Left

  sprintf(tmpstr,"%d",LIBHBBS_TimeLeft());
  replace(str,str,"{L}",tmpstr);

  // Time Online

  sprintf(tmpstr,"%d",LIBHBBS_TimeOnline());
  replace(str,str,"{O}",tmpstr);

  // System Name

  replace(str,str,"{S}",BBSGlobal->BBSName);

  // Conference Name

  if (N_ND->CurrentConf) replace(str,str,"{C}",N_ND->CurrentConf->node.ln_Name);

  // Actual Time Without Seconds

  HBBS_GetTime(tmpstr);
  tmpstr[5]=0;
  replace(str,str,"{T}",tmpstr);

  // Actual Time WITH seconds

  HBBS_GetTime(tmpstr);
  replace(str,str,"{TS}",tmpstr);

  // Actual Date

  HBBS_GetDate(tmpstr);
  replace(str,str,"{D}",tmpstr);

  // Credits Left (In Current Conference)

  replace(str,str,"{B}","<Creds>");

  // Ansi CSI

  replace(str,str,"{E}","\033[");


  if (N_ND->User.Valid)
  {

    // Users Handle

    replace(str,str,"{H}",N_ND->User.CallData.Handle);

    // Users Group

    replace(str,str,"{G}",N_ND->User.CallData.Group);
  }

  return(str);
}

V_BOOL __asm __saveds LIBHBBS_LoadConfAcs( register __a0 struct ConfAcsData *ConfAcs, register __a1 char *filename)
{
  V_BOOL retval=FALSE,error=FALSE;

  struct CfgFileData *CfgFile;
  int loop;
  struct BoolNode *tmpnode;
  V_BOOL TmpVal;
  char tmpstr[BIG_STR];

  if (CfgFile=HBBS_LoadConfig(filename,LCFG_NONE))
  {
    if (HBBS_GetSetting(CfgFile,(void *)&ConfAcs->Name,VTYPE_STRING,"Name",OPT_SINGLE))
    {
      TmpVal=FALSE;
      HBBS_GetSetting(CfgFile,(void *)&TmpVal,VTYPE_BOOL,"Default",OPT_SINGLE);

      // set allow/disallow conf access to whatever "default" is set to for all conferences
      for (loop=0;!error && loop<BBSGlobal->Conferences;loop++)
      {
        tmpnode=(struct BoolNode*)GetNode(ConfAcs->Access,loop);
        tmpnode->Boolean=TmpVal;
      }

      // then make this value the opposite for all "Override_<confnum>=YES"'s
      for (loop=0;!error && loop<BBSGlobal->Conferences;loop++)
      {
        sprintf(tmpstr,"Override_%d",loop+1);
        if (HBBS_GetSetting(CfgFile,(void *)&TmpVal,VTYPE_BOOL,tmpstr,OPT_SINGLE) && TmpVal==TRUE)
        {
          tmpnode=(struct BoolNode*)GetNode(ConfAcs->Access,loop);
          tmpnode->Boolean=!tmpnode->Boolean;
        }
      }

      // then do the same thing for SeeDefault and all See_<confnum>'s
      TmpVal=FALSE;
      HBBS_GetSetting(CfgFile,(void *)&TmpVal,VTYPE_BOOL,"SeeDefault",OPT_SINGLE);

      for (loop=0;!error && loop<BBSGlobal->Conferences;loop++)
      {
        tmpnode=(struct BoolNode*)GetNode(ConfAcs->See,loop);
        tmpnode->Boolean=TmpVal;
      }

      for (loop=0;!error && loop<BBSGlobal->Conferences;loop++)
      {
        sprintf(tmpstr,"SeeOverride_%d",loop+1);
        if (HBBS_GetSetting(CfgFile,(void *)&TmpVal,VTYPE_BOOL,tmpstr,OPT_SINGLE) && TmpVal==TRUE)
        {
          tmpnode=(struct BoolNode*)GetNode(ConfAcs->See,loop);
          tmpnode->Boolean=!tmpnode->Boolean;
        }
      }
      retval=TRUE;
    }
    HBBS_FlushConfig(CfgFile);
  }
  return(retval);
}


void __asm __saveds LIBHBBS_SetAccess( void )
{
  char tmpfilename[BIG_STR];
  V_BIGNUM loop;

  // Clear out the access

  for (loop=0;loop<MAX_ACCESSSETTINGS;loop++)
  {
    N_ND->User.Acs.Data[loop]='N';
  }

  if (N_ND->User.Valid)
  {
    N_ND->User.Acs.AccessLevel=N_ND->User.CallData.Access;

    LIBLoadAccess("HBBS:Access/Levels/Level_Global",&N_ND->User.Acs);

    sprintf(tmpfilename,"HBBS:Access/Levels/Level_%d",N_ND->User.Acs.AccessLevel);
    LIBLoadAccess(tmpfilename,&N_ND->User.Acs);

    sprintf(tmpfilename,"%sAccess/Level_%d",N_ND->NodeLocation,N_ND->User.Acs.AccessLevel);
    LIBLoadAccess(tmpfilename,&N_ND->User.Acs);

    if (N_ND->CurrentConf)
    {
      sprintf(tmpfilename,"%sAccess/Level_%d",N_ND->CurrentConf->ConfPath,N_ND->User.Acs.AccessLevel);
      LIBLoadAccess(tmpfilename,&N_ND->User.Acs);
    }

    sprintf(tmpfilename,"HBBS:Access/Users/%s",N_ND->User.CallData.Handle);
    LIBLoadAccess(tmpfilename,&N_ND->User.Acs);

    sprintf(tmpfilename,"HBBS:System/Data/ConfAcs/%s.CFG",N_ND->User.CallData.ConfAcsDataFile);
    if (!LIBHBBS_LoadConfAcs(&N_ND->User.ConfAcs,tmpfilename))
    {
      HBBS_LogError(BBSGlobal->ErrorLogFile,ERR_ERROR_OPENING,tmpfilename,TYPE_WARNING);
    }
  }
}

V_BOOL __asm __saveds LIBHBBS_CheckAccess(register __d0 ULONG AccessOption)
{
  if ((N_ND->User.Valid) && (N_ND->User.Acs.Data[AccessOption]=='Y')) return(TRUE); else return(FALSE);
}

void __asm __saveds LIBHBBS_AddToCallersLog(register __a0 UBYTE *String)
{
  UBYTE tmpstr[BIG_STR];

  if (N_ND->NodeSettings.CallersLogFile)
  {

    HBBS_GetDate(tmpstr);
    strcat(tmpstr," ");
    HBBS_GetTime(tmpstr+strlen(tmpstr));
    strcat(tmpstr," ");
    strcat(tmpstr,String);
    strcat(tmpstr,"\n");

    HBBS_AppendStrToFile(N_ND->NodeSettings.CallersLogFile,tmpstr);
  }
}


struct TaggedFile __asm __saveds *LIBHBBS_FindTag(register __a0 UBYTE *FileName,register __d0 BOOL MatchALL)
{
  struct TaggedFile *Tag=NULL,*retval=NULL;

  if (N_ND->TaggedFiles)
  {
    for (Tag=(struct TaggedFile*)N_ND->TaggedFileList->lh_Head;!retval && Tag->node.ln_Succ;Tag=(struct TaggedFile *)Tag->node.ln_Succ)
    {
      if (MatchALL)
      {
        if (stricmp(FileName,Tag->node.ln_Name)==0)
        {
          retval=Tag;
        }
      }
      else
      {
        if (stricmp(FilePart(FileName),FilePart(Tag->node.ln_Name))==0)
        {
          retval=Tag;
        }
      }
    }
  }
  return(retval);
}


void __asm __saveds LIBHBBS_SetBBSCols( void )
{
  struct CfgFileData *CfgFile;
  char filename[1024],*bbscolsfile="BBSColours.CFG";

  BOOL foundfile=FALSE;
  BYTE triedall=3;

  FreeAndSet(&N_ND->BBSCols->MenuTextANSI,"[0;37m");
  FreeAndSet(&N_ND->BBSCols->MenuOpenBracket,"[0;34m[");
  FreeAndSet(&N_ND->BBSCols->MenuCloseBracket,"[0;34m]");
  FreeAndSet(&N_ND->BBSCols->MenuHighlightANSI,"[0;33m");
  FreeAndSet(&N_ND->BBSCols->MenuDefaultOptANSI,"[1;37m");
  FreeAndSet(&N_ND->BBSCols->MenuPromptANSI,"[0;34m:");

  do
  {
    triedall--;
    switch (triedall)
    {
      case 2:
        if (N_ND->CurrentConf)
        {
          strcpy(filename,N_ND->CurrentConf->ConfPath);
          strcat(filename,bbscolsfile);
        } else filename[0]=0;
        break;
      case 1:
        strcpy(filename,N_ND->NodeLocation);
        strcat(filename,bbscolsfile);
        break;
      case 0:
        strcpy(filename,"HBBS:System/Data/");
        strcat(filename,bbscolsfile);
        break;
    }
    if (filename[0])
    {
      if (CfgFile=HBBS_LoadConfig(filename,LCFG_NOSTRIPCOMMENTS))
      {
        foundfile=TRUE;
        HBBS_GetSetting(CfgFile,(void *)&N_ND->BBSCols->MenuTextANSI,VTYPE_STRING,"MenuTextANSI",OPT_SINGLE)             ;
        HBBS_GetSetting(CfgFile,(void *)&N_ND->BBSCols->MenuOpenBracket,VTYPE_STRING,"MenuOpenBracket",OPT_SINGLE)       ;
        HBBS_GetSetting(CfgFile,(void *)&N_ND->BBSCols->MenuCloseBracket,VTYPE_STRING,"MenuCloseBracket",OPT_SINGLE)     ;
        HBBS_GetSetting(CfgFile,(void *)&N_ND->BBSCols->MenuHighlightANSI,VTYPE_STRING,"MenuHighlightANSI",OPT_SINGLE)   ;
        HBBS_GetSetting(CfgFile,(void *)&N_ND->BBSCols->MenuDefaultOptANSI,VTYPE_STRING,"MenuDefaultOptANSI",OPT_SINGLE) ;
        HBBS_GetSetting(CfgFile,(void *)&N_ND->BBSCols->MenuPromptANSI,VTYPE_STRING,"MenuPromptANSI",OPT_SINGLE)         ;

        HBBS_FlushConfig(CfgFile);
      }
    }
  } while ((!foundfile) && (triedall>0));
}


V_BOOL __asm __saveds LIBDOOR_CheckRaw(register __d0 ULONG Flags)
{
  return(SendDoorIOMessage(DOORIO_CHECKRAW,NULL,0,Flags,NULL,0,0));
}


LONG __asm __saveds LIBHBBS_TimeLeft( void )
{
  // return 0 if time limit is disabled, or the amount of time left..
  // therefore: only log people off if this returns <0, but ideally check for ACS_UNLIMTIME too...
  return((N_ND->User.Acs.Data[ACS_UNLIMTIME]=='Y') ? 0 : ((LONG)((N_ND->User.CallData.TimeAllowed+N_ND->User.CallData.ExtraTimeLimit)-N_ND->User.CallData.TimeUsed-LIBHBBS_TimeOnline())));

}

LONG __asm __saveds LIBHBBS_TimeOnline( void )
{
  LONG tdiff;
    // current time, - time logged on (secs) * 60 = mins on-line
  time(&tdiff);

  return((tdiff-N_ND->User.CallData.LastCalledDate) / 60);
}

void __asm __saveds LIBDOOR_Goodbye( void )
{
  // Perform all actions required for a sucessful logoff..
  // (see Logout door for an example..)

  N_ND->OnlineStatus=OS_OFFLINE;

  // lostcarrier flag is set by default when a user has a successful login,
  // so we must UNset it!
  N_ND->Actions[ACTN_CARRIERLOST]=ACTC_NONE;
}

BOOL __asm __saveds LIBHBBS_AllowConfAccess(register __d0 V_BIGNUM confnum,register __a0 struct UserData *User)
{
  // confnum starts at 1.

  struct ConfData *tmpconf;
  struct BoolNode *boolnode;

  char tmpfilename[BIG_STR];
  struct UserData *CheckUser;
  struct ConfAcsData *CheckConfAcs;
  struct ConfAcsData ConfAcsDat;

  if (User==NULL)
  {
    CheckUser=&N_ND->User.CallData;
    CheckConfAcs=&N_ND->User.ConfAcs;
  }
  else
  {
    CheckUser=User;
    sprintf(tmpfilename,"HBBS:System/Data/ConfAcs/%s.CFG",CheckUser->ConfAcsDataFile);
    if (LIBHBBS_LoadConfAcs(&ConfAcsDat,tmpfilename))
    {
      CheckConfAcs=&ConfAcsDat;
    }
    else
    {
      confnum=0; //to force a FALSE return...
    }
  }


  if (confnum>0 && confnum<=BBSGlobal->Conferences)
  {
    tmpconf=(struct ConfData *)GetNode(BBSGlobal->ConfList,confnum-1);

    // access level required to enter conference greater than users access level ?
    if (tmpconf->ConfAccess > User->Access)
    {
      return(FALSE);
    }
    else
    {
      boolnode=(struct BoolNode *)GetNode(CheckConfAcs->Access,confnum-1);
      return((BOOL)boolnode->Boolean);
    }
  }
  return(FALSE);
}
