/***************************************************************************
 *                                                                         *
 *   DNS.C                                                                 *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   Domain Name Service for Worldgroup.                                   *
 *                                                                         *
 *                                        - RNStein  7/13/94               *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "ftscope.h"
#include "tcpip.h"
#include "dns.h"
#include "galdns.h"
#undef LEVEL4
#include "galtcpip.h"
#include "phasedbg.h"

#define FILREV "$Revision: 30 $"


#ifndef ICO2
#define tcpip_errno ips_errno
#define TNFACCP TNFRECV
#define TNFCONN TNFSEND
#define INET_ADDR_ERR 0xFFFFFFFFL
#endif

static INT globaldns(VOID);
static GBOOL dnsinp(VOID);
static VOID dnshup(VOID);
static VOID mdlcbk(struct dns *dnsptr);
static VOID adrrpt(struct dns *dnsptr,INT msgnum);
static INT numlit(struct dns *dnsptr);
static INT lclhst(struct dns *dnsptr);
static VOID askem(struct dns *dnsptr);
static VOID dnssend(VOID *ptr);
static INT fmtqry(CHAR *buffer);
static VOID dnstim(VOID);
static VOID dnsrdt(VOID *ptr);
static VOID hdlans(CHAR *datgrm,INT size,CHAR *nambuf);
static INT othnam(struct dns *dnsptr);
static GBOOL dnsslap(struct dns *dnsptr);
static VOID dnserr(VOID);
static VOID dnseor(VOID);
static VOID dnscbk(INT status);
static INT dnsenc(CHAR *name,CHAR *buffer);
static INT dnsdec(CHAR *buffer,INT size,INT offset,CHAR *name);
static CHAR *tvar_hostnam(VOID);
static CHAR *tvar_domain(VOID);
static CHAR *tvar_ip_namesvr(VOID);
static VOID setusr(INT unum);
static VOID sortmxrc(struct dns *dnsptr,struct dnshstex *dnsexptr);
static VOID readDNSSet(VOID);

struct dnsusr {                    /* per-channel DNS management info      */
     struct dns *dnsptr;           /* info on active request (app-supplied)*/
     struct dnshstex *dnsexptr;    /* extended info (app-supplied)         */
     SHORT service;                /* service type (TYPECNAME,TYPEA,TYPEMX)*/
     USHORT id;                    /* DNS header ID no. (seq*256 + usrnum) */
} *dnsusr;

INT dnsstt;                        /* DNS module state number              */
struct module dnsmodule={          /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     dnsinp,                       /*    input routine if selected         */
     dfsthn,                       /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     dnshup,                       /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL,                         /*    finish-up (sys shutdown) routine  */
};

#define MAXLUP 15                  /* max IP addresses in /HOST list       */

/*--- DNS header ID masks etc. ---*/
#define DNSUSM 0x0FFF              /* User-ID mask (limited to 4k users)   */
#define DNSSQM 0xF000              /* sequence number mask                 */
#define DNSSQI 0x1000              /* sequence number increment            */

/*--- public variables ---*/
CHAR dnsemg[100];                  /* Error msg for error callbk (status<0)*/
CHAR svronl;                       /* replies accepted from DNS server only*/
INT isdns;                         /* Name server being used?              */
CHAR *domain;                      /* this Worldgroup's domain name        */
CHAR *hostnam;                     /* this WG's host name within domain    */
CHAR *hdsufx;                      /* domain suffix: ".domain"             */
CHAR *hstdom;                      /* host and domain comb:  "host.domain" */
struct sockaddr_in dnsaddr;        /* DNS server address, for sendto()     */

/*--- local variables ---*/
static HMCVFILE dnsmg;             /* for GALDNS.MCV                       */
static CHAR hostgo;                /* /HOST becomes /GO HOST?              */
static INT dnssock=-1;             /* socket for UDP conversation w/server */
static INT dnspend=0;              /* no. active DNS requests, WGS-wide    */
static struct sockaddr_in svaddr;  /* source of UDP packet (presumably DNS)*/
static UINT wtping;                /* dnsdec(): why this packet is no good */
static CHAR *atmsec;               /* retransmit schedule, seconds/attempt */
static INT numatm;                 /* maximum number of retransmit attempts*/
static INT totatm;                 /* total seconds of all retries         */
static CHAR *ipnsvr;               /* IP address of DNS name server        */
static struct in_addr *lastad;     /* user's last addresses tried          */
static FILE *hostfp;               /* for \piper\etc\hosts (always open)   */
static INT xterms=16;              /* extra DNS structures                 */
static GBOOL *inuse;               /* in-use flag for extra DNS structures */

VOID EXPORT
init__galdns(VOID)                 /* Initialize                           */
{
     CHAR *cp;
     INT unum;
     INT n;

     init__tcpip();
     dnsmg=opnmsg("galdns.mcv");
     readDNSSet();
     sprintf(prfbuf,".%s",domain);
     hdsufx=alcdup(prfbuf);
     sprintf(prfbuf,"%s.%s",hostnam,domain);
     hstdom=alcdup(prfbuf);
     if (!dnsValidDomain(hstdom)) {
          catastro("GALDNS: %s is not a valid host.domain combination",hstdom);
     }
     dnsaddr.sin_family=AF_INET;
     dnsaddr.sin_port=htons(DNSPORT);
     isdns=*ipnsvr != '\0' && !sameas(ipnsvr,"NONE");
     if (isdns
      && (strlen(ipnsvr) > 15
       || (dnsaddr.sin_addr.s_addr=inet_addr(ipnsvr)) == INET_ADDR_ERR)) {
          catastro("Offline Hardware Setup option IPNSVR is not a valid\n"
                   "IP address: \"%s\".",ipnsvr);
     }
     ipnsvr=strdup(inet_ntoa(dnsaddr.sin_addr));
     setmem(dnsaddr.sin_zero,8,0);
     hostgo=ynopt(HOSTGO);
     svronl=ynopt(SVRONL);
     totatm=0;
     numatm=0;
     for (cp=strtok(getmsg(RSCHED)," ,") ; cp != NULL ; cp=strtok(NULL," ,")) {
          if (!alldgs(cp) || (n=atoi(cp)) < 1) {
               catastro("Offline Configuration option RSCHED is invalid:\n"
                        "\"%s\"",getmsg(RSCHED));
          }
          prfbuf[numatm]=n;
          totatm+=n;
          numatm++;
     }
     prfbuf[numatm]=0;
     atmsec=alcdup(prfbuf);
     dnsusr=(struct dnsusr *)alczer((nterms+xterms)*sizeof(struct dnsusr));
     for (unum=0 ; unum < nterms+xterms ; unum++) {
          dnsusr[unum].id=unum;
     }
     inuse=(GBOOL *)alczer(xterms*sizeof(GBOOL));
     rtkick(1,dnstim);
     dclvda(512);
     globalcmd(globaldns);
     stzcpy(dnsmodule.descrp,gmdnam("galdns.mdf"),MNMSIZ);
     dnsstt=register_module(&dnsmodule);
     lastad=(struct in_addr *)alczer((nterms+xterms)*sizeof(struct in_addr));
     hostfp=fopen(getmsg(ETCHOST),FOPRA);
     register_textvar("HOSTNAME",tvar_hostnam);
     register_textvar("DOMAIN",tvar_domain);
     register_textvar("IP_NAMESVR",tvar_ip_namesvr);
}

VOID EXPORT
initwc__galdns(VOID)
{
     init__galdns();
}

static INT
globaldns(VOID)                    /* global "/host <hostname>" command    */
{
     CHAR *cp;
     INT rc;

     if (margc >= 1 && sameas(margv[0],"/host") && hostgo) {
          cp=margv[0];
          rstrin();
          movmem(cp,cp+3,(UINT)(INPSIZ-(cp-input)-3));
          input[INPSIZ-1]='\0';
          movmem("/go ",cp,4);
          parsin();
          rc=globalgo();
          if (usrptr->state != dnsstt && usrptr->flags&MASTER) {
               setmbk(dnsmg);
               prfmsg(HSTNOPAG);
               outprf(usrnum);
               rstmbk();
          }
          return(rc);
     }
     return(0);
}

static GBOOL
dnsinp(VOID)                       /* <CR> while processing /HOST command  */
{
     struct dns *dnsptr;
     CHAR *cp;
     GBOOL rc=TRUE;

     setmbk(dnsmg);
     dnsptr=(struct dns*)vdaptr;
     if (margc == 1 && sameas(margv[0],"X")) {
          return(FALSE);
     }
     do {
          bgncnc();
          switch (usrptr->substt) {
          case 0:
               cncchr();
               prfmsg(usrptr->substt=LOOKUP);
               break;
          case LOOKUP:
               if (margc == 0) {
                    prfmsg(LOOKUP);
               }
               else if (sameas(margv[0],"?")) {
                    dnshelp();
                    prfmsg(LOOKUP);
               }
               else {
                    dnsptr->callbk=mdlcbk;
                    stzcpy(dnsptr->name,cp=cncall(),DNSNSZ);
                    dnsptr->numaddr=MAXLUP;
                    if (!dnsn2at(dnsptr,TYPEA)) {
                         prfmsg(usrptr->substt=LOOKING,cp);
                    }
               }
               break;
          case LOOKING:
               cncall();
               if ((usrptr->flags&(INJOIP+ABOIP)) == INJOIP) {
                    break;
               }
               dnsabt();
               break;
          case LUPDNS:
               cncall();
               rc=FALSE;
               break;
          }
     } while (!endcnc());
     outprf(usrnum);
     return(rc);
}

VOID
dnshelp(VOID)                      /* display help host name help message  */
{                                  /* (callable by other modules)          */
     setmbk(dnsmg);
     prfmsg(!isdns && hostfp == NULL ? DNSIPHLP : DNSHELP);
     rstmbk();
}

static VOID
dnshup(VOID)                       /* DNS channel hangup vector            */
{
     lastad[usrnum].s_addr=0L;
}

static VOID
mdlcbk(                            /* "/host" command callback vector      */
struct dns *dnsptr)
{
     setmbk(dnsmg);
     switch(dnsptr->status) {
     case DNSLOK:
     case DNSNOK:
          prfmsg(LUPNUM,inet_ntoa(dnsptr->inaddr[0]));
          break;
     case DNSHOK:
          adrrpt(dnsptr,LUPHOSTS);
          break;
     case DNSSOK:
          adrrpt(dnsptr,LUPDNS);
          break;
     default:
          prfmsg(LUPERR,dnsemg);
          break;
     }
     usrptr->substt=LUPDNS;
     btuinj(usrnum,CRSTG);
     outprf(usrnum);
     clrprf();
}

static VOID
adrrpt(                            /* report on address(es)                */
struct dns *dnsptr,
INT msgnum)
{
     INT i;

     prfmsg(msgnum,dnsptr->name,inet_ntoa(dnsptr->inaddr[0]));
     for (i=1 ; i < dnsptr->numaddr ; i++) {
          prfmsg(LUPADDL,inet_ntoa(dnsptr->inaddr[i]));
     }
     if (dnsptr->numaddr == MAXLUP) {
          prfmsg(LUPBUST);
     }
}

INT
dnsn2ex(                           /* extended DNS lookup (svc type + hosts)*/
struct dns *dnsptr,
SHORT type,                        /* service type (TYPECNAME,TYPEA,TYPEMX)*/
struct dnshstex *dnsexptr)         /* host names                           */
                                   /* expects caller to set 'dnshstex.buf' */
                                   /* and 'dnshstex.bufsiz'                */
{
     if (dnsusr[usrnum].dnsptr == NULL) {
          dnsusr[usrnum].dnsexptr=dnsexptr;
          dnsexptr->numofhst=0;
          if (dnsexptr->maxofhst > MAXHSTS) {
               dnsexptr->maxofhst=MAXHSTS;
          }
     }
     return(dnsn2at(dnsptr,type));
}

INT
dnsn2at(                           /* DNS: convert name to address w/type  */
struct dns *dnsptr,
SHORT type)
{
     if (dnsusr[usrnum].dnsptr == NULL) {
          dnsusr[usrnum].service=type;
     }
     return(dnsn2a(dnsptr));
}

INT
dnsn2a(                            /* DNS:  convert name to address        */
struct dns *dnsptr)                /* app fills in name, callbk, & numaddr */
                                   /* returns 1=immediate callbk 0=deferred*/
                                   /* expects curusr() to be in effect     */
{                                  /* uses vdatmp                          */
     INT uc;

     dnsptr->dmnsfx=dnsptr->name+strlen(dnsptr->name);
     if (dnsusr[usrnum].dnsptr != NULL) {
          dnsusr[usrnum].dnsptr=dnsptr;
          sprintf(dnsemg,"Overlapped by another dnsn2a() call "
                         "on the same channel.");
          dnscbk(DNSOVL);
          if (dnsusr[usrnum].dnsptr != NULL) {
               catastro("Runaway DNS calls.");
          }
          return(1);
     }
     dnsusr[usrnum].dnsptr=dnsptr;
     dnsusr[usrnum].id+=DNSSQI;
     if (dnsusr[usrnum].service == TYPENONE) {
          dnsusr[usrnum].service=TYPEA;
     }
     dnspend++;
     if (sameas(dnsptr->name,"LAST") && lastad[usrnum].s_addr != 0L) {
          dnsptr->inaddr[0].s_addr=lastad[usrnum].s_addr;
          strcpy(dnsptr->name,inet_ntoa(lastad[usrnum]));
          dnsptr->numaddr=1;
          dnscbk(DNSLOK);
          return(1);
     }
     if (numlit(dnsptr)) {
          return(1);
     }
     if (dnsptr->name[0] == '\0'
      || dnsptr->name[0] == '.'
      || dnsptr->name[strlen(dnsptr->name)-1] == '.'
      || strstr(dnsptr->name,"..") != NULL) {
          sprintf(dnsemg,"\"%0.31s\" is not a valid host name.",dnsptr->name);

          dnscbk(DNSSTX);
          return(1);
     }
     if (isdns) {
          if (strchr(dnsptr->name,'.') == NULL) {
               dnsslap(dnsptr);
          }
     }
     if (lclhst(dnsptr)) {
          return(1);
     }
     if (!isdns) {
          sprintf(dnsemg,
                  hostfp != NULL ? "No host is available named \"%0.29s\"."
                                 : "\"%0.30s\" is not a valid IP address.",
                  dnsptr->name);
          dnscbk(DNSUNK);
          return(1);
     }
     if (dnspend == 1) {
          if ((uc=udpdial(0,&dnssock)) < 0) {
               setmbk(tcpmg);
               if (tcpip_errno == EADDRNOTAVAIL) {
                    sprintf(dnsemg,"Configuration option IPADDR "
                                   "should not be set to %s.",getmsg(IPADDR));
               }
               else {
                    sprintf(dnsemg,"UDP dialing error %d/%d:  %-0.30s",
                                   uc,tcpip_errno,tcpErrStg(tcpip_errno));
               }
               rstmbk();
               dnserr();
               return(1);
          }
          sktnfy(TNFRECV,dnssock,dnsrdt,NULL,-1);
     }
     dnsptr->atm=0;
     askem(dnsptr);
     return(0);
}

static INT
numlit(                            /* Is name a numeric literal address?   */
struct dns *dnsptr)
{
     ULONG laddr;

     if ((laddr=inet_addr(dnsptr->name)) != INET_ADDR_ERR) {
          dnsptr->inaddr[0].s_addr=laddr;
          dnsptr->numaddr=1;
          dnscbk(DNSNOK);
          return(1);
     }
     return(0);
}

static INT
lclhst(                            /* Attempt resolution with hosts file   */
struct dns *dnsptr)                /* DNS info                             */
{                                  /* ret 1=resolved, 0=no local knowledge */
     CHAR buffer[132];
     CHAR *num,*name;
     CHAR numbuf[16];
     CHAR c;

     if (hostfp == NULL) {
          return(0);
     }
     rewind(hostfp);
     while (fgets(buffer,132,hostfp) != NULL) {
          if ((c=*(num=firstwd(buffer))) != '\0' && c != '#') {
               stzcpy(numbuf,num,16);
               while ((c=*(name=nextwd())) != '\0' && c != '#') {
                    if (sameas(name,dnsptr->name)
                     && (dnsptr->inaddr[0].s_addr=inet_addr(numbuf))
                      != INET_ADDR_ERR) {
                         dnsptr->numaddr=1;
                         dnscbk(DNSHOK);
                         return(1);
                    }
               }
          }
     }
     return(0);
}

static VOID
askem(                             /* need to ask server, wait on select() */
struct dns *dnsptr)                /* DNS info                             */
{
     dnsptr->status=DNSQRY;
     sktnfy(TNFSEND,dnssock,dnssend,NULL,-1);
}

static VOID
dnssend(                           /* send query to DNS server             */
VOID *ptr)                         /* (dummy pointer)                      */
{
     INT unum;
     INT nbyt;
     INT sc;
     struct dns *dnsptr;

     (VOID)ptr;
     if (dnspend == 0) {
          sktcnc(TNFSEND,nfyskt);
          return;
     }
     for (unum=0 ; unum < nterms+xterms ; unum++) {
          if ((dnsptr=dnsusr[unum].dnsptr) != NULL
           && dnsptr->status == DNSQRY) {
               setusr(unum);
               nbyt=fmtqry(vdatmp);
               if (unum == whomon) {
                    ftscope(FTSOUS,vdatmp,nbyt);
               }
               if ((sc=sendto(dnssock,vdatmp,nbyt,0,
                              (struct sockaddr *)&dnsaddr,
                              sizeof(dnsaddr))) != nbyt) {
                    if (sc == -1) {
                         if (tcpip_errno == ENETUNREACH
                          || tcpip_errno == EHOSTUNREACH) {
                              sprintf(dnsemg,"Error, IPNSVR is probably "
                                             "misconfigured (%s).",ipnsvr);
                         }
                         else {
                              sprintf(dnsemg,"Send error %d:  %-0.42s",
                                             tcpip_errno,tcpErrStg(tcpip_errno));
                         }
                    }
                    else {
                         sprintf(dnsemg,"Sent only %d bytes "
                                        "out of a %d-byte datagram.",sc,nbyt);
                    }
                    dnscbk(DNSERS);
               }
               else {
                    dnsptr->status=DNSANS;
                    dnsptr->atmsec=atmsec[dnsptr->atm];
               }
               return;
          }
     }
     if (dnspend > 0) {            /* there are still some active requests */
          sktcnc(TNFSEND,dnssock); /* but none need sending at the moment  */
     }
}

static INT
fmtqry(                            /* format an outgoing DNS query         */
CHAR *buffer)                      /* output formatted datagram            */
                                   /* dnsptr->name is Internet domain name */
                                   /* returns number of bytes in datagram  */
{                                  /* expects curusr() to be in effect     */
     CHAR *cp;
     USHORT *ip;
     struct dns *dnsptr;

     ip=(USHORT *)buffer;
     dnsptr=dnsusr[usrnum].dnsptr;
     shtset(ip++,htons(dnsusr[usrnum].id));            /* DNS header: ID */
     shtset(ip++,htons(DNSFRD)); /* DNS header: flags (recursion desired)*/
     shtset(ip++,htons(1));      /* DNS header: # questions              */
     shtset(ip++,0);             /* DNS header: # answers                */
     shtset(ip++,0);             /* DNS header: # authority RR's         */
     shtset(ip++,0);             /* DNS header: # additional RR's        */
     cp=(CHAR *)ip;
     cp+=dnsenc(dnsptr->name,cp);  /* DNS query: coded domain name         */
     ip=(USHORT *)cp;
     shtset(ip++,htons(dnsusr[usrnum].service)); /* DNS query type       */
     shtset(ip++,htons(INCLASS));/* DNS query: class (Internet)          */
     return((INT)(((CHAR *)ip)-buffer));
}

static VOID
dnstim(VOID)                       /* DNS 1 Hz processing                  */
{
     INT unum;
     struct dnsusr *dnu;
     struct dns *dnsptr;

     if (dnspend > 0) {
          for (unum=0,dnu=dnsusr ; unum < nterms+xterms ; unum++,dnu++) {
               if (dnu->dnsptr != NULL) {
                    dnsptr=dnu->dnsptr;
                    if (dnsptr->status == DNSANS
                     && dnsptr->atmsec > 0
                     && --dnsptr->atmsec == 0) {
                         if (++dnsptr->atm == numatm) {
                              sprintf(dnsemg,"Server didn't respond after %d "
                                             "attempts over %d seconds",
                                             numatm,totatm);
                              setusr(unum);
                              dnscbk(DNSTMO);
                         }
                         else {
                              askem(dnsptr);
                         }
                    }
               }
          }
     }
     rtkick(1,dnstim);
}

static VOID
dnsrdt(                            /* receive answers from DNS server      */
VOID *ptr)                         /* (dummy pointer)                      */
{
     INT ngram;
     INT fromlen;

     (VOID)ptr;
     if (dnspend == 0) {
          sktcnc(TNFRECV,nfyskt);
          return;
     }
     fromlen=sizeof(svaddr);
     if ((ngram=recvfrom(dnssock,vdatmp,vdasiz,0,
                         (struct sockaddr *)(&svaddr),&fromlen)) < 0) {
#ifdef GCWINNT
          /*  We should ignore this error  */
          if (tcpip_errno == WSAEWOULDBLOCK) {
               return;
          }
#endif
          sprintf(dnsemg,"Receiver error %d:  %-0.38s",tcpip_errno,
                                              tcpErrStg(tcpip_errno));
          dnserr();
     }
     else {
          hdlans(vdatmp,ngram,prfbuf);
          clrprf();
     }
}

static VOID
hdlans(                            /* handle an answer from the DNS server */
CHAR *datgrm,                      /* DNS answer datagram (entire UDP data)*/
INT size,                          /* number of bytes in datagram          */
CHAR *nambuf)                      /* buffer for forming domain names      */
                                   /* svaddr = info on source of datagram  */
                                   /* puts address(es) in dnsptr->inaddr[] */
{                                  /* uses ftscope() to report data, errors*/
     CHAR *ip;
     USHORT id,flags,nans,nquest;
     INT ansunm;
     INT offset;
     struct dns *dnsptr;
     struct dnshstex *dnsexptr;
     USHORT rdl;
     INT nstor;
     INT i;
     USHORT minpref=0xFFFF,newpref;
     INT minoffs;
     GBOOL hasansw;
     INT numofhst,roomleft;
     CHAR *bufoffs;

     if (size < 12) {
          return;
     }
     ip=(CHAR *)datgrm;
     id=ntohs(shtval(ip));
     flags=ntohs(shtval(ip+2));
     nquest=ntohs(shtval(ip+4));
     nans=ntohs(shtval(ip+6));
     if ((ansunm=id&DNSUSM) >= nterms+xterms) {
          return;
     }
     setusr(ansunm);
     if (usrnum == whomon) {
          ftscope(FTSINS,datgrm,size);
     }
     if (svronl && svaddr.sin_addr.s_addr != dnsaddr.sin_addr.s_addr) {
          if (usrnum == whomon) {
               ftscope(FTSCMT,spr("RERR  0 Packet from %s, not name server",
                                  inet_ntoa(svaddr.sin_addr)));
          }
          return;
     }
     if ((dnsptr=dnsusr[usrnum].dnsptr) == NULL || id != dnsusr[usrnum].id) {
          if (usrnum == whomon) {
               ftscope(FTSCMT,spr("RERR  1 (%04X != %04X)",
                                  id,dnsusr[usrnum].id));
          }
          return;
     }
     dnsexptr=dnsusr[usrnum].dnsexptr;
     if (flags&DNSFTC) {
          sprintf(dnsemg,"Truncated DNS server message.");
          dnscbk(DNSTNC);
          return;
     }
     if (!(flags&DNSFRA)) {
          sprintf(dnsemg,"DNS server doesn't support recursion.");
          dnscbk(DNSRNA);
          return;
     }
     if ((flags&DNSFQR) == 0 || nquest != 1) {
          if (usrnum == whomon) {
               ftscope(FTSCMT,"RERR  2");
          }
          return;
     }
     if ((offset=dnsdec(datgrm,size,12,nambuf)) == -1
      || offset+4 > size) {
          if (usrnum == whomon) {
               ftscope(FTSCMT,spr("RERR  3 (%u)",wtping));
          }
          return;
     }
     ip=(CHAR *)(datgrm+offset);
     if (!sameas(nambuf,dnsptr->name)
      || ntohs(shtval(ip)) != dnsusr[usrnum].service
      || ntohs(shtval(ip+2)) != INCLASS) {
          if (usrnum == whomon) {
               ftscope(FTSCMT,spr("RERR  4 \"%s\" != \"%s\" at offset %d",
                                  nambuf,dnsptr->name,offset));
          }
          return;
     }
     offset+=4;
     if ((flags&DNSFRC) == DNSFNE) {
          if (!othnam(dnsptr)) {
               sprintf(dnsemg,"That domain name does not exist.");
               dnscbk(DNSNOX);
          }
          return;
     }
     nstor=0;
     for (i=0,hasansw=FALSE ; i < nans ; i++) {
          if ((offset=dnsdec(datgrm,size,offset,NULL)) == -1
           || offset+10 > size) {
               if (usrnum == whomon) {
                    ftscope(FTSCMT,spr("RERR  5 (%u)",wtping));
               }
               return;
          }
          ip=(CHAR *)(datgrm+offset);
          rdl=ntohs(shtval(ip+8));
          if (offset+10+rdl > size) {
               if (usrnum == whomon) {
                    ftscope(FTSCMT,spr("RERR  7 (answer %d, offset %d,"
                                       " rdl=%d)",i,offset,rdl));
               }
               return;
          }
          if (ntohs(shtval(ip)) == dnsusr[usrnum].service
           && ntohs(shtval(ip+2)) == INCLASS) {
               if (dnsusr[usrnum].service == TYPEA) {
                    if (rdl == 4) {
                         movmem(ip+10,&dnsptr->inaddr[nstor++],4);
                         if (nstor >= dnsptr->numaddr) {
                              break;
                         }
                    }
               }
               else if (dnsusr[usrnum].service == TYPEMX) {
                    newpref=((UINT)datgrm[offset+10])*0x100+
                            ((UINT)datgrm[offset+11]);
                    if (newpref <= minpref) {
                         hasansw=TRUE;
                         minpref=newpref;
                         minoffs=offset;
                    }
                    if (dnsexptr != NULL && dnsexptr->buf != NULL ) {
                         numofhst=dnsexptr->numofhst;
                         if (numofhst < dnsexptr->maxofhst) {
                              dnsdec(datgrm,size,offset+12,dnsptr->name);
                              if (numofhst == 0) {
                                   bufoffs=dnsexptr->buf;
                              }
                              else {
                                   bufoffs=&dnsexptr->host[numofhst-1].hostname
                                    [strlen(dnsexptr->host[numofhst-1].hostname)
                                    +1];
                              }
                              roomleft=(INT)(&dnsexptr->buf[dnsexptr->bufsiz-1]
                                             -bufoffs);
                              if (strlen(dnsptr->name) < roomleft) {
                                   dnsexptr->host[numofhst].hostname=bufoffs;
                                   dnsexptr->host[numofhst].prefval=newpref;
                                   strcpy(bufoffs,dnsptr->name);
                                   dnsexptr->numofhst++;
                              }
                              else {
                                   if (usrnum == whomon) {
                                        ftscope(FTSCMT,spr("RERR  8 (name %s)",
                                                            dnsptr->name));
                                   }
                              }
                         }
                         else {
                              if (usrnum == whomon) {
                                   ftscope(FTSCMT,spr("RERR  9 (name %s)",
                                                       dnsptr->name));
                              }
                         }
                    }
               }
               else if (dnsusr[usrnum].service == TYPECNAME) {
                    dnsdec(datgrm,size,offset+10,dnsptr->name);
                    hasansw=TRUE;
                    break;
               }
          }
          offset+=10+rdl;
     }
     if (dnsusr[usrnum].service == TYPEMX) {
          if (hasansw) {
               dnsdec(datgrm,size,minoffs+12,dnsptr->name);
               sortmxrc(dnsptr,dnsexptr);
               dnseor();
               dnsn2at(dnsptr,TYPEA);
          }
          else if (!othnam(dnsptr)) {
               sprintf(dnsemg,"No mail exchange records for that domain.");
               dnscbk(DNSUNK);
          }
     }
     else if (dnsusr[usrnum].service == TYPEA){
          if ((dnsptr->numaddr=nstor) > 0) {
               dnscbk(DNSSOK);
          }
          else if (!othnam(dnsptr)) {
               sprintf(dnsemg,"No host is available by that name.");
               dnscbk(DNSUNK);
          }
     }
     else if (dnsusr[usrnum].service == TYPECNAME) {
          if (hasansw) {
               dnscbk(DNSSOK);
          }
          else if (!othnam(dnsptr)) {
               sprintf(dnsemg,"No canonical name for that domain.");
               dnscbk(DNSUNK);
          }
     }
}

static VOID
sortmxrc(                          /* sort MX rec. by preference value     */
struct dns *dnsptr,                /*   DNS info                           */
struct dnshstex *dnsexptr)         /*   extended DNS info                  */
{                                  /*   also skip record we lookup now     */
                                   /*   dnsptr->name - implicit input      */
     INT i,j;
     CHAR *hostname;
     UINT prefval;

     for (i=0 ; i < dnsexptr->numofhst ; i++) {
          if (sameas(dnsexptr->host[i].hostname,dnsptr->name)) {
               if (i < --dnsexptr->numofhst) {
                    movmem(&(dnsexptr->host[i+1]),&(dnsexptr->host[i]),
                     sizeof(dnsexptr->host[0])*(dnsexptr->numofhst-i));
               }
               break;
          }
     }
     for (i=0 ; i < dnsexptr->numofhst ; i++) {
          for (j=dnsexptr->numofhst-1 ; j > i ; j--) {
               if (dnsexptr->host[j-1].prefval > dnsexptr->host[j].prefval) {
                    hostname=dnsexptr->host[j].hostname;
                    prefval=dnsexptr->host[j].prefval;
                    dnsexptr->host[j].hostname=dnsexptr->host[j-1].hostname;
                    dnsexptr->host[j].prefval=dnsexptr->host[j-1].prefval;
                    dnsexptr->host[j-1].hostname=hostname;
                    dnsexptr->host[j-1].prefval=prefval;
               }
          }
     }
}

static INT                         /*   1=yes, query initiated, 0=no more  */
othnam(                            /* can we try another name?             */
struct dns *dnsptr)                /*   DNS info                           */
{
     CHAR *adot;

     if (*dnsptr->dmnsfx == '\0') {
          if (!dnsslap(dnsptr)) {
               return(0);
          }
     }
     else if ((adot=strchr(dnsptr->dmnsfx+1,'.')) != NULL
         && (strchr(adot+1,'.')) != NULL) {
          strcpy(dnsptr->dmnsfx+1,adot+1);
     }
     else {
          *dnsptr->dmnsfx='\0';
          return(0);
     }
     if (!lclhst(dnsptr)) {
          dnsptr->atm=0;
          askem(dnsptr);
     }
     return(1);
}

static GBOOL                       /*   return TRUE=done, FALSE=no room    */
dnsslap(                           /* slap the domain suffix onto name     */
struct dns *dnsp)                  /*   DNS session in question            */
{
     if ((INT)(dnsp->dmnsfx-dnsp->name)+strlen(hdsufx) < DNSNSZ-1) {
          strcpy(dnsp->dmnsfx,hdsufx);
          return(TRUE);
     }
     return(FALSE);
}

static VOID
dnserr(VOID)                       /* WGS-wide error, kill all requests    */
{
     INT unum,unmsav;

     unmsav=usrnum;
     for (unum=0 ; unum < nterms+xterms ; unum++) {
          setusr(unum);
          dnscbk(DNSERR);
     }
     setusr(unmsav);
}

VOID
dnsabt(VOID)                       /* Abort DNS lookup                     */
{                                  /* expects curusr() to be in effect     */
     sprintf(dnsemg,"Lookup aborted.");
     dnscbk(DNSABT);
}

static VOID
dnseor(VOID)                       /* end of a DNS request                 */
{                                  /* usrnum identifies DNS request        */
     dnsusr[usrnum].dnsptr=NULL;
     dnsusr[usrnum].dnsexptr=NULL;
     dnsusr[usrnum].service=TYPENONE;
     dnspend--;
     if (dnspend == 0 && dnssock != -1) {
          clsskt(dnssock);
          dnssock=-1;
     }
}

static VOID
dnscbk(                            /* DNS call back with final status      */
INT status)                        /* status code to report                */
{                                  /* expects curusr() to be in effect     */
     struct dns *dnsptr;

     if ((dnsptr=dnsusr[usrnum].dnsptr) != NULL) {
          dnseor();
          if (status >= 0) {
               lastad[usrnum].s_addr=dnsptr->inaddr[0].s_addr;
          }
          dnsptr->status=status;
          if (dnsptr->callbk != NULL) {
               clrprf();
               BEG_PHASE("DNS callback",dnsptr->callbk);
               dnsptr->callbk(dnsptr);
               END_PHASE("DNS callback",dnsptr->callbk);
          }
     }
}

static INT
dnsenc(                            /* encode an Internet domain name       */
CHAR *name,                        /* name (e.g. "ftp.gcomm.com")          */
CHAR *buffer)                      /* code (e.g. "\3ftp\5gcomm\3com")      */
                                   /* returns number of bytes of coded name*/
{                                  /* (does not do name compression)       */
     CHAR *cp;

     buffer[0]=0;
     strcpy(buffer+1,name);
     for (cp=strtok(buffer+1,".") ; cp != NULL ; cp=strtok(NULL,".")) {
          cp[-1]=strlen(cp);
     }
     return(strlen(buffer)+1);
}

static INT
dnsdec(                            /* DNS address decode                   */
CHAR *datgrm,                      /* Entire DNS datagram (UDP data field) */
INT size,                          /* size of entire datagram              */
INT offset,                        /* offset in datagram of RR or question */
CHAR *name)                        /* output name (DNSNSZ max) or NULL=not */
                                   /* returns offset after name or -1=error*/
{                                  /* sets wtping according to error reason*/
     CHAR c;
     CHAR *np;
     INT namlim=DNSNSZ;
     INT rc;
     INT jumped=0;

     wtping=0;
     if (offset >= size) {
          wtping=10000U+offset;
          return(-1);
     }
     for (np=name ; (c=datgrm[offset]) != 0 ; offset+=1+c) {
          if ((c&0xC0) == 0xC0) {
               if (!jumped) {
                    rc=offset+2;
               }
               jumped=1;
               offset=((c&0x3F)<<8)+datgrm[offset+1];
               if (offset >= size) {
                    wtping=30000U+offset;
                    return(-1);
               }
               c=datgrm[offset];
          }
          if ((c&0xC0) != 0
           || offset+1+c >= size
           || (namlim-=1+c) < 0) {
               wtping=50000U+offset;
               return(-1);
          }
          if (name != NULL) {
               movmem(datgrm+offset+1,np,c);
               np+=c;
               *np++='.';
          }
     }
     if (name != NULL) {
          np[0]='\0';
          if (name[0] != '\0') {
               np[-1]='\0';
          }
     }
     if (!jumped) {
          rc=offset+1;
     }
     return(rc);
}

/*--- text variables ---*/

static CHAR *
tvar_hostnam(VOID)                 /* text variable:  host name            */
{
     return(hostnam);
}

static CHAR *
tvar_domain(VOID)                  /* text variable:  domain name          */
{
     return(domain);
}

static CHAR *
tvar_ip_namesvr(VOID)              /* IP_NAMESVR Name Server IP address    */
{
     return(ipnsvr);
}

/*
 *  The following support routines enable a non-channel based function
 *  (such as a task) to call the DNS routines. Before calling the DNS
 *  routines, you first call dnsfvc() to get a 'pseudo-channel' and
 *  assign usrnum to that value. Call backs from DNS will have usrnum
 *  set to that value (NOTE: usrptr, vdaptr, etc will NOT be valid since
 *  this is a pseudo channel). The call back function should call dnsfre()
 *  to release the pseudo-channel.
 */

INT
dnsfvc(VOID)                       /* Find a vacant DNS structure          */
{
     INT i;

     for (i=0 ; i < xterms ; i++) {
          if (!inuse[i]) {
               inuse[i]=TRUE;
               return(nterms+i);
          }
     }
     return(-1);
}

VOID
dnsfre(                            /* Free a vacant DNS structure          */
INT unum)
{
     if (unum >= nterms && unum < nterms+xterms) {
          inuse[unum-nterms]=FALSE;
     }
}

static VOID
setusr(                            /* DNS version of curusr()              */
INT unum)
{
     if (unum < nterms) {
          curusr(unum);
     }
     else {
          usrnum=unum;
     }
}

static VOID
readDNSSet(VOID)                   /* reads host, domain, DNS server IP    */
{
     hostnam=skpwht(unpad(stgopt(HOSTNAM)));
     domain=skpwht(unpad(stgopt(DOMAIN)));
     if (!dnsValidDomain(domain)) {
          catastro("GALDNS: %s is not a valid domain name.",domain);
     }
     ipnsvr=stgopt(IPNSVR);
}

GBOOL                              /*  TRUE if valid domain name           */
dnsValidDomain(                    /* Check for valid domain name          */
const CHAR* Domain)                /*  Domain name to check                */
{                                  /*  As per RFC STD13.TXT                */
     const CHAR* ptr;

     for (ptr=Domain; *ptr != 0; ptr++) {
          if (!isalpha(*ptr) && !isdigit(*ptr) && *ptr != '-' && *ptr != '.') {
               return(FALSE);
          }
     }
     return(TRUE);
}
