/*
 *  MBTNC.C - 12/20/92 - Deal with the tnc.
 */

#include "mb.h"

#ifdef MCH_AMIGA
extern PORTS *devtnc;   /* Set by inittnc() in amiga.c */
extern char conn_direction;
char *tcmds[] =
#else
static char *tcmds[] =
#endif
{
  "cono on\n",
  "cono of\n",
  "m on\n",
  "m of\n"
};

#ifdef MCH_AMIGA
extern char tmpstr[];
extern short debug;
extern unsigned char vhfstream,hfstream;
word save_mode;

/* FIX 12
   The KAM does not just say "*** DISCO" if you send a disco command and
   it is already disconnected. No! That would be too easy. It says:
   'Can't DISCONNECT, A/V Link state is: DISCONNECTED'
   This can tie distnc() up in loop.
   So look for this as well.
*/
isother(cp)
char *cp;
{
   if(search(cp,"Can't DISCO",11))return true;
   return false;
}
#endif

isdis(cp)
char *cp;
{
  if (search(cp, "*** DISC", 8)) return true;
  return false;
}

isreq(cp)
char *cp;
{
  if (matchn(cp, "*** conn", 8)) return true;
  if (matchn(cp, "*** Conn", 8)) return true;
  return false;
}

islink(cp)
char *cp;
{
  return matchn(cp, "*** LINK", 8);
}

iscon(cp)
char *cp;
{
  return search(cp, "*** CONN", 8);
}

isretry(cp)
char *cp;
{
  if (matchn(cp, "*** Retry count exceeded", 24)) return true;
  if (matchn(cp, "*** retry count exceeded", 24)) return true;
  return false;
}
alloff()
{
#ifdef MCH_AMIGA
   PORTS *p;
  if(devtnc->dev == p_serial) {
      if(port->mode & ops) {
         p = port;
         ioport(devtnc);
         outstr("ATS0=0\n");
         waitcmd(2);
         ioport(p);
      }
      return;
  }
#endif
  allcmd(t_coff);  /* Turn off connects */
  allcmd(t_moff);  /* Turn off monitoring */
}

allon()
{
#ifdef MCH_AMIGA
   PORTS *p;
  if(devtnc->dev == p_serial) {
      if(port->mode & ops) {
         p = port;
         ioport(devtnc);
         outstr("ATS0=2\n");
         waitcmd(2);
         ioport(p);
      }
      return;
  }
#endif
  allcmd(t_mon);  /* Turn on monitoring */
  allcmd(t_con);  /* Turn on connects  */
}

login()
{
  register PORTS *p;
  register char *i;

  readmsg();
  readusr();

  p = port;

  p->lport = cport;
  cport->lport = p;
  p->errors = 0;
  p->msg    = NULL;
  p->cmdcnt = 0;
  p->flags clrbit p_opreq;
  p->ec = p->ecuser;

/*
 *  Prepare to check if this user can indeed connect.
 */

  switch (p->dev)
  {
    case p_console:
      p->mode = local;
      alloff();
      rduser(p->user->call, p->user);
      unbl(p->line, p->user->call, ln_call);
      log('C', 'S', ' ', p->line);
      break;

    case p_tnc:
      p->mode = remote;
      tncstate();   /* Pick up the "... connected to ..." line */
#ifdef MCH_AMIGA
/* FIX 8 */
      /* Was this a valid connect? If the BBS saw "*** CONN" but the TNC
         is disconnected (both hf and vhf), then it was monitoring someone's
         data and it is not a valid connection, so log a bad one and get
         out. Need to do same thing when connected to a user who is stupid
         enough to send data that looks like a disconnect.
      */
      if(p->mode == discon) {
         /* force the mode to idle ... the user was never there.
            discon means user was there and then disconnected
         */
         p->mode = idle;
         /* type 'E' log is for errors .... prtlog ignores them */
         /* EF is for the false connected message */
         log('E','F',' ',p->line);
         return;
      }
#endif
      alloff();
      if (p->tmode) trantnc(); else convtnc();
/*
 *  Remove the "; 1 unacked" that pk-232 puts on status line.
 */
      if ((i = strchr(p->line, ';')) isnt NULL) *i = '\0';
      if ((i = strchr(p->line, '*')) isnt NULL) *i = '\0';
      strcpy(p->line, p->line + 28);

      logina(false);
      log('C', p->id, ' ', p->line);
      break;

#ifdef MCH_AMIGA
    case p_nulmdm:
#endif
    case p_serial:
      p->mode = remote;
      p->flags setbit p_trans;
      alloff();
      strcpy(p->line, p->line + 17);
      logina(false);
      log('C', p->id, ' ', p->line);
      break;

    default: ;
  }
}

/*
 *  The login checking process.
 */

logina(link)
short link;
{
  register char *p, *q;
  register int ssid;
  register short nd;

  char bullfile[11];

  remnl(port->line);

/*
 *  Clear any pending connect request.
 */

  port->flags clrbit p_req;

/*
 *  Get the users call and ssid.
 */

  parse();
  ssid = pcall(tcall, port->fld[0]);

/*
 *  Add the call to J list for port, and for connect.
 */

  addcall(port->fld[0], cport);
  addcall(port->fld[0], port);

/*
 *  Save call of connected gateway, if user linked here.
 */

  if (link) strncpy(tmp->scr, port->user->call, ln_call);

/*
 *  Get (or make) this users user record.
 */

  rduser(tcall, port->user);

/*
 *  Update the data in the user record.
 */

  port->user->ssid = ssid;
  if (link) port->user->port = 'L'; else port->user->port = port->id;

/*
 *  Get path. Count how many digi in path.
 *  First, fix digi path in case is a PK-232.
 */

  nd = 3;
  while (port->flds > 3)
  {
    strcat(port->fld[2], port->fld[nd]);
    port->flds--;
    nd++;
  }

  nd = 0;

  if (link) unbl(port->user->path, tmp->scr, ln_call);
  else if (port->flds > 2)
  {
    nd++;
    p = port->user->path;
    for (q = port->fld[2]; *q; q++)
      if (*q isnt ' ') if ((*p++ = *q) is ',') nd++;
    *p = '\0';
  }
  else *port->user->path = '\0';

/*
 *  If this user is excluded, exclude him.
 */

  if (port->user->options & u_exclude) { port->mode = exclude; return; }

/*
 *  At last, decide if this user CAN connect.
 *  If sysop or bbs, can always connect.
 */

  if (port->user->options & (u_sysop | u_bbs)) {
#ifdef MCH_AMIGA
   conn_direction = '<';
   user_title(port->user->call);
#endif
    outstr(vers);
    return;
  }

/*
 *  If this turkey has illegal call, kick him right off.
 */

  if (port->priv & p_ilcal) if (!iscall(tcall)) { port->mode = exclude; return; }

/*
 *  This user isn't a bbs. If port is bbs only, bye bye.
 */

  if (port->priv & p_bbs) { port->mode = exclude; return; }

/*
 *  If user connected through too many digi, bye bye.
 */

  if (nd > port->ndigi) { port->mode = exclude; return; }

/*
 *  Well, this user can indeed connect on this port.
 *  Send our "I am a machine message"
 */

  outstr(vers);

#ifdef MCH_AMIGA
/* Put the call in the title bar */
   conn_direction = '<';
   user_title(port->user->call);
#endif

  sprintf(bullfile, "BULLETIN.%c",port->id);
  if ((port->fl = fopen(bullfile, "r")) isnt NULL)
  {
    while( fgets(tmp->scr, scrmax, port->fl) isnt NULL)
    {
       if (chkdis()) break;
       prtx(tmp->scr);
    }
    fclose(port->fl);
  }

/*
 *  If not an expert user, give login message and
 *  tell about unread messages.
 */

  if (!(port->user->options & u_expert))
    prtm(motd);
    newmsg();
}

/*
 *  Execute a command to each port:
 *  Connects ok, monitor on, etc.
 */

allcmd(index)
int index;
{
  register PORTS *p, *was;
  byte ec;

  was = port;
  for (p = porthd; p isnt NULL; p = p->next)
  {
    ioport(p);
    if(p->dev is p_tnc)
    {
   p->flags clrbit p_give;
   ec = p->ec;
   p->ec = p->eccmds;
   outstr(tcmds[index]);
   waitcmd(0);
   p->flags setbit p_give;
   p->ec = ec;
    }
  }
  ioport(was);
}

alltnc(cp)
char *cp;
{
  register PORTS *p, *was;
  byte ec;

  was = port;
  for (p = porthd; p isnt NULL; p = p->next) switch(p->dev)
  {
    case p_tnc:
      ioport(p);
      ec = p->ec;
      p->ec = p->eccmds;
      p->flags clrbit p_give;
      outstr(cp);
      waitcmd(0);
      p->flags setbit p_give;
      p->ec = ec;
      break;

    default: ;
  }
  ioport(was);
}

/*
 *  Execute a command to the current port:
 *  Connects ok, monitor on, etc.
 */

onecmd(index)
int index;
{
  register byte ec;

  if(port->dev is p_tnc)
  {
      ec = port->ec;
      port->ec = port->eccmds;
      port->flags clrbit p_give;
      outstr(tcmds[index]);
      waitcmd(0);
      port->ec = ec;
      port->flags setbit p_give;
  }
}

onetnc(cp)
char *cp;
{
  register byte ec;

  if (port->dev is p_tnc)
  {
    ec = port->ec;
    port->ec = port->eccmds;
    port->flags clrbit p_give;
#ifdef MCH_AMIGA
/* Interpret any special commands - at the moment only the one that sets
   the date/time in the TNC. See amiga.c
*/
    tnc_string(cp);
    outstr(tmpstr);
#else
    outstr(cp);
#endif
    waitcmd(0);
    port->ec = ec;
    port->flags setbit p_give;
  }
}

/*
 *  Need to distinguish between "disconect in progress", "disconnected",
 *  "connected", "connect in progress".
 */

issta(cp)
char *cp;
{
  register char *p;

  p = cp;

#ifndef MCH_AMIGA
  if (matchn(cp, "cmd:", 4)) p += 4;
#else
/* There could be more than one cmd: lurking on the line */
   while (matchn(cp, "cmd:", 4)) strcpy(p,p+4);
   /*PH Check for beginning of line from a KAM
     of the form 'A/V' or 'A/H' */
   if(p[1] == '/') {
      strcpy(p,p+4);
   }
#endif

  if (!matchn(p, "Link sta", 8)) return false;

  if (matchn(p + 15, "DISC", 4) and (strlen(p) < 30)) port->mode = discon;
  return true;
}

/*
 *  Put tnc in converse mode.
 */

convtnc()
{
  register byte ec;

  if(port->dev is p_tnc)
  {
      ec = port->ec;
      port->ec = port->eccmds;
      outstr("conv\n");
      port->ec = ec;
      wait(2);
  }
}


trantnc()
{
  register byte ec;

  if(port->dev is p_tnc)
  {
      ec = port->ec;
      port->ec = port->eccmds;
      outstr("trans\n");
      port->flags setbit p_trans;
      port->ec = ec;
      wait(1);
  }
}

/*
 *  Put tnc in command mode.
 */

cmdtnc()
{
  register byte ec;

  if(port->dev is p_tnc)
  {
      ec = port->ec;
      port->ec = port->eccmds;
      port->flags clrbit p_give;
      if (port->flags & p_trans) breakport(); else outchar(ctl_c);
#ifndef MCH_AMIGA
      waitcmd(1);
#else
      waitcmd(3);
#endif
/*
#else
      {
         int i;
         for(i=0;i<3;i++) {
            if(port->flags & p_trans)
               breakport();
            else
               outchar(ctl_c);
            if(waitcmd(3))break;
         }
      }
#endif
*/
      port->ec = ec;
      port->flags setbit p_give;
      port->flags clrbit p_trans;
  }
}

/*
 *  Discover the state of the device on the current port.
 */

tncstate()
{
   register byte ec;

   if(port->mode & idle) return;

   if(port->dev is p_tnc) {
      ec = port->ec;
      port->ec = port->eccmds;
      cmdtnc();
      port->flags clrbit p_give;
#ifdef MCH_AMIGA
      /* If this is the amiga version but it doesn't look like a
         KAM then just use the usual code
      */
      if(!hfstream) {
#endif
         outstr("C\n");
         while(true) {
            getdat();
            if(issta(port->line)) {
               waitcmd(1);
               port->ec = ec;
               port->flags setbit p_give;
               return;
            }
            else {
               cmdtnc();
               port->flags clrbit p_give;
               outstr("C\n");
            }
         }
#ifdef MCH_AMIGA
      }
      while(true) {
         /* Try to get an HF status report. */
         /* Ask for HF status first. The reason is that if both streams
            are disconnected, then this procedure will leave the TNC on
            the VHF stream during idle periods. So if you use the TAlk
            command to talk to the TNC and just give it a Connect command
            it will by default send it out the VHF port.
         */
Delay(10L);
         outchar(hfstream);
         outstr("AC\n");
         getdat();
         /* save the port mode before calling issta. If the HF port is
            DISConnected it will force the mode to discon. This must be
            restored to its original value as it was after the getdat
            when the VHF port is tried because otherwise distnc() and
            other routines will think the VHF side is disconnected even
            if it is not, and they will not force a disconnect when one
            is needed.
            Also, saving the mode here immediately AFTER the first getdat
            will preserve the 'forced' state if the SYSOP had used the
            ^F to force the port to disconnect because we call getdat
            again and the second call won't know the ^F has happened.
         */
         save_mode = port->mode;
         if(issta(port->line)) {
            /* It's a valid status line */
            waitcmd(1);
            /* If it returns not disconnected then it's the HF channel */
            if(port->mode != discon) {
               port->ec = ec;
               port->flags setbit p_give;
               return;
            }
            /* The HF channel is not connected so either the VHF port
               is disconnected, in which case return OR it is connected
               or connecting in which case we still return but force the
               TNC onto the VHF channel
            */
            outchar(vhfstream);
            outstr("AC\n");
            /* now restore the port mode again - getdat will only override
               the existing mode if something goes wrong so if the last
               mode was forced, it will remain forced unless, for example,
               it now sees a *** DISC.
            */
            port->mode = save_mode;
            getdat();
            if(issta(port->line)) {
               waitcmd(1);
               port->ec = ec;
               port->flags setbit p_give;
               return;
            }
         }
         /* If can't get a valid status line from either then fire another
            control C at the port and try again
         */
         cmdtnc();
         port->flags clrbit p_give;
      }
#endif
   }
}

/*
 *  Disconnect tnc.
 */
#ifdef MCH_AMIGA
/* This is the last remaining busy-wait routine in cbbs. If it can be
   removed then the settmr and chktmr routines can also be removed from
   funcs.c
*/
#endif

distnc()
{
   register int tsave;
   register short done;
   byte ec;

   tncstate();
  
   if(port->mode & idle) return;

   ec = port->ec;
   port->ec = port->eccmds;
   switch(port->dev) {
#ifdef MCH_AMIGA
   case p_nulmdm:
#endif
   case p_serial:
      if(!(port->mode & discon)) outstr("*** DISCONNECTED\n");
      break;
   case p_tnc:
      if (port->mode & discon) break;
      port->flags clrbit p_give;
      outstr("D\n");  /* This does not eat cmd:, thus dtime not used */
      settmr(&port->expire, port->dtime);
      while(true) {
         if(instat()) {
            tsave = port->ctime;
            port->ctime = port->dtime;
            getdat();
            port->ctime = tsave;
#ifndef MCH_AMIGA
            if(isdis(port->line))
#else
/* FIX 12 */
            if(isdis(port->line) || isother(port->line))
#endif
            {
               waitcmd(1);
               port->ec = ec;
               port->flags setbit p_give;
               return;
            }
         }

         if(!chktmr(port->expire)) {
            outstr("D\n");
#ifndef MCH_AMIGA
            while(!isdis(port->line)) getdat();
#else
/* FIX 12 */
            while(!isdis(port->line) && !isother(port->line)) getdat();
#endif
            waitcmd(1);
            port->ec = ec;
            port->flags setbit p_give;
            return;
         }
      }
      break;
   }
   port->ec = ec;
   port->mode = discon;
}

/*
 *  Send connect string to tnc.
 */

contnc(p)
char *p;
{
  register byte ec;
#ifdef MCH_AMIGA
   register char *q;
#endif

  ec = port->ec;
  port->ec = true;
  switch (port->dev)
  {
    case p_tnc:
      outstr(p);
      port->ec = port->eccmds;
#ifndef MCH_AMIGA
/*
   DON'T DO THIS. MFJ (and some other TNCs) which, for proper BBS operation,
   should have BBSMSGS ON and NEWMODE ON, does NOT send a cmd: prompt after
   a Connect command. With any other combination of BBSMSGS and NEWMODE, the
   stupid thing sends cmd: but the one combination that we need to work,
   doesn't!
   So the waitcmd(0) here will not see a cmd: prompt but it will see, AND
   THROW AWAY, the *** CONNECTED message. This means that the loop which
   will try to find the CONNECTED message will fail.
*/
      waitcmd(0);
#endif
      break;

    case p_serial:
#ifdef MCH_AMIGA
      outstr(p);
      while(1) {
         getdat();
         q = port->line;
         /* Ignore the error returns from getdat on a serial line. It looks
            for carrier and dies if it isn't there. But we know it can't be
            there yet.
         */
         if(*q == 0)continue;
         if((*q == '\n') || (*q == '\r'))continue;
         if((*q == 'A') && (*(q+1) == 'T'))continue;
         if(matchn(q,"RING",4))continue;
         if(matchn(q,"CONNECT",7))break;
         port->mode = idle;
printf("PORT IDLE\n");
         return;
      }
      /* The other end, notably the ZyXEL, may not have seen its CONNECT
         message yet ... so we must Delay here before sending the connect
      */
      Delay(250L);
      port->mode = remote;
#endif
#ifdef MCH_AMIGA
    case p_nulmdm:
/* The initial \n will flush any garbage generated in the modem during
   the connect procedure.
*/
      prtx("\n*** CONNECTED to $O\n");
#else
      prtx("*** CONNECTED to $O\n");
#endif
  }
  port->ec = ec;
}

/*
 *  Wait for "cmd:" prompt.
 */

#ifndef MCH_AMIGA
waitcmd(x)
int x;
{
  static char *wanted = "cmd:";
  register short i;
  long l;
  int t;
  if (s_flag & s_dv) t = 5; else t = 2;
  if (x) t = x;
  if(port->dev is p_tnc)
  {
      settmr(&l, t);
      for (i = 0; i < 4;)
      {
   if (instat()) if (inchar() is *(wanted + i)) i++; else i = 0;
   if (!chktmr(l))
   {
       if (!(port->eccmds))
       bdos(2,tolower(port->id),0); return;
   }
      }
      if (!(port->eccmds)) bdos(2,port->id,0);
  }
}
#endif
