/***************************************************************************
 *                                                                         *
 *   WEBD.C                                                                *
 *                                                                         *
 *   Copyright (c) 1994-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   World-Wide Web (WWW) server for access to HTML pages.                 *
 *                                                                         *
 *   Ref HTTP/1.0 draft of 3/8/95:                                         *
 *        ftp://ds.internic.net/internet-drafts/                           *
 *        draft-ietf-http-v10-spec-00.txt                                  *
 *                                                                         *
 *   Ref HTML - 2.0 draft of 6/16/95                                       *
 *        ftp://ds.internic.net/internet-drafts/                           *
 *        draft-ietf-html-spec-04.txt                                      *
 *                                                                         *
 *                                              - R. Stein  8/5/94         *
 *   Clickable image maps, statistics logging   - C. Yap    6/95           *
 *   HTTP 1.0, interceptable vectors, forms     - R. Stein  7/95           *
 *                                                                         *
 *   DoS and Hack Fixes                                                    *
 *              - Arash Aghlara:                                           *
 *                Arash_Aghlara@Dialsoft.Com,Arash_Aghlara@yahoo.com       *
 ***************************************************************************/

#define WEBD_VERSION ""            /* WEBD_VERSION text variable           */
                                   /* (obsolete, use BBS_VERSION instead)  */
#include "gcomm.h"
#include "majorbbs.h"
#include "remote.h"
#include "ftscope.h"
#if defined(UNIX) && defined(SVR4)
#define BAKOPTLEN OPTLEN
#undef OPTLEN
#endif
#include "tcpip.h"
#if defined(UNIX) && defined(SVR4)
#undef OPTLEN
#define OPTLEN BAKOPTLEN
#endif
#include "chandir.h"
#include "inpoly.h"
#include "webd.h"
#include "galwebd.h"
#if defined(UNIX)
#include <fcntl.h>
#include <unistd.h>
#endif
#define byendl(mn) byetcp(mn)
#include "mimectyp.h"

#define FILREV "$Revision: 42 $"

static GBOOL CreateHeader(CHAR* pth);
static VOID webincall(INT gotchn);
static GBOOL webdinp(VOID);
static VOID webdsts(VOID);
static SHORT hdrdone(VOID);
static SHORT dftwebhdr(VOID);
static SHORT dfthdrlin(CHAR *hdrlin);
static VOID dftwebreq(CHAR *uri);
static SHORT dftwebget(CHAR *uri);
static SHORT dftwebsnd(CHAR *pth);
static SHORT chnwebsnd(CHAR *pth);
static INT dftimgmap(CHAR *mapnam,UINT xt,UINT yt,CHAR **newurl);
static INT dftrspbdy(CHAR *buffer,INT nbytes);
static INT req2mem(VOID);
static INT req2fil(VOID);
VOID closereq(VOID);
static VOID webdrst(VOID);
static CHAR *tvar_now_http(VOID);
static CHAR *tvar_webd_version(VOID);
GBOOL fsSendName(char *fname,int skt);
GBOOL fsSendHandle(FILE *fp,CHAR* fname,int skt);
VOID fsSending(struct fsinfo *fsi);
static USHORT webmit(struct datstm *dsp,CHAR *srcloc,USHORT nwant);

INT webdstt;                       /* Web server module state number       */
struct module webdmodule={         /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     webdinp,                      /*    input routine if selected         */
     webdsts,                      /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     NULL,                         /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL                          /*    finish-up (sys shutdown) routine  */
};

static CHAR *months[]={"Jan","Feb","Mar","Apr","May","Jun",
                       "Jul","Aug","Sep","Oct","Nov","Dec"};
static CHAR *dayofwk[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static CHAR tznames[][4]={
     "PST","PDT","MST","MDT","CST","CDT","EST","EDT","GMT"};
static UINT tzmins[]={
      480 , 420 , 420 , 360 , 360 , 300 , 300 , 240 ,  0  };
#define TZNUM (sizeof(tzmins)/sizeof(tzmins[0]))

static VOID (*oldrst)(VOID);       /* saved hdlrst() vector                */
static FILE *axslog=NULL;          /* file pointer to access log           */
static FILE *agtlog=NULL;          /* file pointer to agent log            */
static FILE *eralog=NULL;          /* file pointer to error log            */
static FILE *reflog=NULL;          /* file pointer to referrer log         */
static INT maxwebd;                /* max number of connections to Web svr */
static CHAR webonl;                /* 1=Web server online, 0=offline       */
static CHAR webrej;                /* 1=Reject when offline, 0=ignore      */
static CHAR audweb;                /* audit web retrievals?                */
static CHAR audwebe;               /* audit erroneous web requests?        */
static UINT webpatc;               /* patience for activity, 1/16 sec units*/
static HMCVFILE webdmb;            /* GALWEBD.MCV file                     */
CHAR *webroot;                     /* path prefix to all Web HTML pages    */
CHAR *imgroot;                     /* path prefix for image maps           */
CHAR *dftfil;                      /* default file for "GET /" response    */
INT webtzone;                      /* minutes behind GMT (240=EDT)         */
CHAR *imgpfix;                     /* prefix for (*hdlimgmap)() i'ceptors  */
                                   /* always begins and ends with "/"      */
CHAR *noclick="noclick";           /* pseudo-URI for image maps: ignore clk*/
CHAR *webd_version=WEBD_VERSION;   /* Web server version string (for tvar) */
INT reqque;                        /* Web request queue length             */
GBOOL chustat;                     /* channel taken up for unsecured file? */
INT nonchu;                        /* number of simultaneous bkground xfers*/

#define FSCHUNK 4096

struct fsinfo {                    /* file-to-socket information           */
     int socket;                   /*   socket                             */
     FILE *fp;                     /*   file handle                        */
     short flags;                  /*   see below                          */
     CHAR* header;                 /*   HTTP Header to return              */
} *fsinfo=NULL;

#define FSIUSE 0x0001              /* info entry in use                    */

                                   /*   returns 1=ok, 0=byeweb() called    */
SHORT (*prswebhdr)(VOID)           /* parse web request header             */
     =dftwebhdr;                   /*   see webdptr->reqsiz and hdrsiz     */

                                   /*   returns 1=handled, 0=not recognized*/
SHORT (*prshdrlin)(                /* parse header line of incoming req    */
     CHAR *hdrlin)=dfthdrlin;      /*   for each header line (not req line)*/

VOID (*hdlwebreq)(                 /* request interceptor                  */
     CHAR *uri)=dftwebreq;         /*   URI following method in req line   */

                                   /*   ret 1=handled, 0=404 err (no found)*/
SHORT (*hdlwebget)(                /* GET request interceptor              */
     CHAR *uri)=dftwebget;         /*   URI following 'GET' in request line*/

                                   /*   ret 1=handled, 0=404 err (no found)*/
SHORT (*filwebsnd)(                /* File send interceptor                */
     CHAR *pth)=dftwebsnd;         /*   full path of file                  */

                                   /*   ret 1=mapped, 0=no action, -1=error*/
INT (*hdlimgmap)(                  /* Image map mouse-click interceptor    */
     CHAR *mapnam,                 /*   Image map file name (after imgpfix)*/
     UINT xt,                      /*   pixel X of mouse click             */
     UINT yt,                      /*   pixel Y of mouse click             */
     CHAR **newurl)=dftimgmap;     /*   where to put pointer to mapped URL */
                                   /*   (when returning 1 only)            */

                                   /*   returns no. bytes sent as rsp body */
INT (*hdlrspbdy)(                  /* response body bytes en route client  */
     CHAR *buffer,                 /*   bytes to send in response body     */
     INT nbytes)=dftrspbdy;        /*   number of bytes                    */

CHAR *bdyfil="GALWEBD.BDY";        /* temporary file for incom. Entity Body*/
ULONG bdymax=BDYMAX;               /* maximum size of body (in bdyfil)     */
#define HTTPVLEN (4+1+3+1+2)       /* room for "HTTP/NNN.NN"               */

VOID EXPORT
init__galwebd(VOID)                /* Initialization the WG 3.0 way        */
{
     init__webd();
}

VOID EXPORT
init__webd(VOID)                   /* Initialization (for compatbility)    */
{
     CHAR *cp;
     INT i;
     static GBOOL webinit=FALSE;

     if (webinit) {
          return;
     }
     webinit=TRUE;
     init__tcpip();
     webdmb=opnmsg("galwebd.mcv");
     webonl=ynopt(WEBONL);
     webrej=ynopt(WEBREJ);
     if (webonl || webrej) {
          maxwebd=numopt(MAXWEBD,0,250);
          if (samend(webroot=stgopt(WEBROOT),"\\") || samend(webroot,"/")) {
               webroot[strlen(webroot)-1]='\0';
          }
          if (samend(imgroot=stgopt(IMGROOT),"\\") || samend(imgroot,"/")) {
               imgroot[strlen(imgroot)-1]='\0';
          }
          imgpfix=getmsg(IMGPFIX);
          if (*imgpfix != '/') {
               movmem(imgpfix,imgpfix+1,strlen(imgpfix)+1);
               *imgpfix='/';
          }
          if (!samend(imgpfix,"/")) {
               strcat(imgpfix,"/");
          }
          imgpfix=strdup(imgpfix);
          cp=getmsg(WEBDZONE);
          if (alldgs(cp) || *cp == '-' && alldgs(cp+1)) {
               webtzone=atoi(cp);
          }
          else {
               for (i=0 ; i < TZNUM ; i++) {
                    if (sameas(cp,tznames[i])) {
                         webtzone=tzmins[i];
                         break;
                    }
               }
               if (i == TZNUM) {
                    catastro("Invalid WEBDZONE setting: \"%s\"",cp);
               }
          }
          reqque=numopt(REQQUE,0,256);
          chustat=ynopt(CHUSTAT);
          if (!chustat) {
               nonchu=numopt(NONCHU,1,256);
               fsinfo=(struct fsinfo *)alczer(nonchu*sizeof(struct fsinfo));
          }
          else {
               nonchu=0;
          }
          dftfil=stgopt(DFTFIL);
          audweb=ynopt(AUDWEB);
          audwebe=ynopt(AUDWEBE);
          webpatc=numopt(WEBPATC,0,2047)<<4;
          if (ynopt(AXSFIL)
           && (axslog=fopen(cp=getmsg(AXSFILN),FOPAA)) == NULL) {
               catastro("Cannot write to access log: %s",cp);
          }
          if (ynopt(AGTFIL)
           && (agtlog=fopen(cp=getmsg(AGTFILN),FOPAA)) == NULL) {
               catastro("Cannot write to agent log: %s",cp);
          }
          if (ynopt(ERAFIL)
           && (eralog=fopen(cp=getmsg(ERAFILN),FOPAA)) == NULL) {
               catastro("Cannot write to error log: %s",cp);
          }
          if (ynopt(REFFIL)
           && (reflog=fopen(cp=getmsg(REFFILN),FOPAA)) == NULL) {
               catastro("Cannot write to referrer log: %s",cp);
          }
          stzcpy(webdmodule.descrp,gmdnam("galwebd.mdf"),MNMSIZ);
          webdstt=register_module(&webdmodule);
          if (regtcpsvr(WEBNAME,(UINT)lngopt(WEBPORT,1L,65535L),
                        WEBBACKLOG,webincall)) {
               svrMaxque(reqque);
               svrMaxstt(maxwebd);
          }
          else
          {
               //   DEBUG
               shocst( "init__webd", "regtcpsvr() failed" );
          }
          oldrst=hdlrst;
          hdlrst=webdrst;
          dclvda(sizeof(struct webdusr)+MINREQBUF);
          dclvda(FSCHUNK);
          register_textvar("NOW_HTTP",tvar_now_http);
          register_textvar("WEBD_VERSION",tvar_webd_version);
     }
     else {
          clsmsg(webdmb);
     }
}

VOID EXPORT
initwc__galwebd(VOID)
{
     init__webd();
}

static VOID
webincall(                         /* begin incoming Web session           */
INT gotchn)                        /* 1=chan (curusr) assigned, 0=not avail*/
{                                  /* implicit:  clskt=socket to client    */
     setmbk(webdmb);
     clrprf();
     if (!gotchn) {
          switch (ijreas) {
          case IJRSHUT:
               sprintf(prfbuf,xlttxv(stpans(getasc(SYSSHUT)),mxmssz));
               break;
          case IJRFULL:
          default:
               sprintf(prfbuf,xlttxv(stpans(getasc(SYSFULL)),mxmssz),
                       numcdi("TCP/IP"));
               break;
          case IJRALIM:
               sprintf(prfbuf,xlttxv(stpans(getasc(WEBFULL)),mxmssz),maxwebd);
               break;
          }
          send(clskt,prfbuf,strlen(prfbuf),0);
     }
     else {
          setmem(webdptr,vdasiz,0);
          usrptr->usrcls=BBSPRV;
          usrptr->state=webdstt;
          usrptr->substt=WEBHDR;
          usrptr->flags|=NOGLOB+NOINJO;
          sprintf(usaptr->userid,"(%s) web browser",inet_ntoa(tcpipinf[usrnum].inaddr));
          btubsz(usrnum,OUTSIZ/2,OUTSIZ/2);
          btutrg(usrnum,OUTSIZ);
          btuech(usrnum,0);
          tcpipinf[usrnum].outsnk.datstm.moveit=webmit;
          if (!webonl) {
               byeweb(OFFLINE);
               rejinc(WEBNAME,"Web disabled");
          }
          else {
               shochl("Incoming Web contact",'w',0x1F);
               sktnfy(TNFRECV,clskt,tcpinc,&tcpipinf[usrnum],usrnum);
               if (whomon == usrnum) {
                    (*ftscope)(FTSCMT,spr("***** (%s) session begins",
                               inet_ntoa(tcpipinf[usrnum].inaddr)));
               }
               btuinj(usrnum,CYCLE);
               webdptr->sttime=(USHORT)(hrtval()>>12);
          }
     }
}

static GBOOL
webdinp(VOID)                      /* text line fm client (never happens)  */
{

     return(TRUE);
}

static VOID
webdsts(VOID)                      /* status handling                      */
{
     INT nbytes;
     INT nread;
     INT nsent;
     INT nroom;
     INT more=1;
     USHORT stnow;
     UINT pct;
	 
     if (status == RING) {
          if (webdptr->abwhy[0] == '\0') {
               strcpy(webdptr->abwhy,"kill channel");
          }
          rstchn();
          return;
     }
     if ((usrptr->flags&BYEBYE)) {
          return;
     }

  // Fix "..." hack. Arash Aghlara
 	  if(webdptr->uri!=NULL) {
		 if (samein("...",webdptr->uri)) {
			 shocst(spr("IGNORED URL [%s]",inet_ntoa(tcpipinf[usrnum].inaddr)),"%s",webdptr->uri);
			 rstchn();
			 return;
		 }
	  }

     setmbk(webdmb);
     stnow=(USHORT)(hrtval()>>12);
     if (stnow-webdptr->sttime > webpatc && webpatc > 0)  {
          strcpy(webdptr->abwhy,"timeout");
          btuclo(usrnum);
          byeweb(TIMEOUT,webpatc>>4);
          return;
     }
	 
     switch (usrptr->substt) {
     case WEBHDR:
		 
          if ((nbytes=req2mem()) < 0) {
               more=0;
          }
          else if (nbytes == 0) {
               actdet=0;
          }
          else {
               webdptr->sttime=stnow;
               if (hdrdone()) {
                    more=(*prswebhdr)();
               }
               else if (webdptr->reqcnt == maxreqbuf) {
                    byeweb(HDRBUST,maxreqbuf,maxreqbuf);
                    more=0;
               }
          }
          break;
     case WEBBDY:
		  
          if (webdptr->concnt < webdptr->conlen) {
               nbytes=webdptr->reqfp == NULL ? req2mem() : req2fil();
               if (nbytes < 0) {
                    more=0;
               }
               else if (nbytes == 0) {
                    actdet=0;
               }
               else {
                    webdptr->sttime=stnow;
                    webdptr->concnt+=nbytes;
               }
          }
          else {
               if (webdptr->reqfp != NULL
                && (fflush(webdptr->reqfp) != 0
                 || fseek(webdptr->reqfp,0L,SEEK_SET) != 0)) {
                    byeweb(FRFAIL);
                    more=0;
               }
               else {
                    usrptr->substt=WEBHDL;
               }
          }
          break;
     case WEBHDL:
		 
          btucli(usrnum);
          if (agtlog != NULL && webdptr->userag != NULL) {
               fprintf(agtlog,"%s\n",webdptr->userag);
          }
          stlcpy(input,webdptr->method,INPSIZ);
          stlcat(input," ",INPSIZ);
          stlcat(input,webdptr->uri,INPSIZ);
          stlcat(input," ",INPSIZ);
          stlcat(input,webdptr->vertxt,INPSIZ);
          status=CRSTG;


		  (*shomal_hook)();
       // Fix DoS of HTTP! Arash Aghlara
          if (strlen(webdptr->uri)<=4096) (*hdlwebreq)(webdptr->uri);
          else byeweb(0);

          more=0;
          break;
     case WEBFIL:
          btucli(usrnum);
          if ((nroom=btuoba(usrnum)) <= 0) {
               actdet=0;
          }
          else if ((nread=fread(vdatmp,1,min(nroom,vdasiz),
                                webdptr->rspfp)) > 0) {
               nsent=(*hdlrspbdy)(vdatmp,nread);
               webdptr->bytcnt+=nsent;
               webdptr->sttime=stnow;
          }
          else if (nroom == OUTSIZ/2-1) {
               endwebd(1);
               byendl(NO_MESSAGE);
               more=0;
          }
          else {
               actdet=0;
          }
          if (webdptr->tfsupd < lngtck) {
               if (webdptr->filsiz > 0UL) {
                    pct=(unsigned)((webdptr->bytcnt*100+(webdptr->filsiz>>1))
                                                        /webdptr->filsiz);
                    shochl(spr("Web %u%% %0.60s (%s cps)",pct,webdptr->goturi,
                               commas(ul2as(webdptr->bytcnt-webdptr->lstcnt))),
                           'w',0x1F);
               }
               webdptr->tfsupd=lngtck;
               webdptr->lstcnt=webdptr->bytcnt;
          }
          break;
     default:
          more=0;
          break;
     }
     if (more) {
          btuinj(usrnum,CYCLE);
     }
}

static SHORT                       /*   1=yes (reqsiz,httpver,hdrsiz set)  */
hdrdone(VOID)                      /* all of request header received yet?  */
{                                  /*   (inspects httpreqbuf w/o modifying)*/
     INT reqlen;                   /*   req line length (w/o terminators)  */
     CHAR *lfptr;                  /*   pointer to each '\n' of header     */
     INT vpos;
     CHAR vbuff[HTTPVLEN+1];
     UINT mjr,mnr;
     INT beglin;                   /*   index of begin of each header line */

     if ((lfptr=memchr(httpreqbuf,'\n',webdptr->reqcnt)) == NULL) {
          return(0);
     }
     reqlen=(INT)(lfptr-httpreqbuf);
     webdptr->reqsiz=reqlen+1;
     if (reqlen > 0 && httpreqbuf[reqlen-1] == '\r') {
          reqlen--;
     }
     webdptr->httpver=0;
     for (vpos=reqlen ; vpos > 0 ; vpos--) {
          if (isspace(httpreqbuf[vpos-1])) {
               if (reqlen-vpos <= HTTPVLEN) {
                    stzcpy(vbuff,httpreqbuf+vpos,reqlen-vpos+1);
                    if (sscanf(vbuff,"HTTP/%u.%u",&mjr,&mnr) == 2) {
                         webdptr->httpver=mjr*100+mnr;
                    }
               }
               break;
          }
     }
     if (webdptr->httpver < 100) {
          webdptr->hdrsiz=webdptr->reqsiz;
          return(1);
     }
     while ((beglin=(INT)(lfptr-httpreqbuf)+1) < webdptr->reqcnt) {
          if (httpreqbuf[beglin] == '\r') {       /* CR of hdr terminator? */
               if (++beglin >= webdptr->reqcnt) {
                    return(0);
               }
          }
          if (httpreqbuf[beglin] == '\n') {       /* LF of hdr terminator? */
               webdptr->hdrsiz=beglin+1;
               return(1);
          }
          if ((lfptr=memchr(httpreqbuf+beglin,'\n',
                            webdptr->reqcnt-beglin)) == NULL) {
               return(0);
          }
     }
     return(0);
}

SHORT                              /*   return 1=ok, 0=byeweb() called     */
prsreqlin(VOID)                    /* parse request line (meth,uri,ver)    */
{
     INT retval=1;

     if (webdptr->method == NULL) {
          httpreqbuf[webdptr->reqsiz-1]='\0';     /* terminate request line*/
          if ((webdptr->method=strtok(httpreqbuf," \t\r\n")) == NULL
           || (webdptr->uri=strtok(NULL," \t\r\n")) == NULL) {
               setmbk(webdmb);
               byeweb(REQFMT);
               retval=0;
          }
          else if ((webdptr->vertxt=strtok(NULL," \t\r\n")) == NULL) {
               webdptr->vertxt="";
          }
     }
     return(retval);
}

static SHORT                       /*   returns 1=ok, 0=byeweb() called    */
dftwebhdr(VOID)                    /* default (*prswebhdr)() vector        */
                                   /* Parse web request header and decide  */
                                   /* if a body is to be received (conlen).*/
                                   /* Expects reqsiz & hdrsiz already set. */
                                   /* If ret true, computes & terminates   */
                                   /* these fields:  method,uri,vertxt.    */
                                   /* If ret true, also sets new substt.   */
{                                  /* First vector called for each new req.*/
     CHAR *hdrlin;
     CHAR *remhdr;

     prsreqlin();
     usrptr->substt=WEBHDL;

     setmbk(webdmb);
     if (webdptr->hdrsiz == webdptr->reqsiz) {
          return(1);
     }
	 
     httpreqbuf[webdptr->hdrsiz-1]='\0';     /* terminate header           */
     for (hdrlin=strtok(httpreqbuf+webdptr->reqsiz,"\r\n")
        ; hdrlin != NULL
        ; hdrlin=strtok(remhdr,"\r\n")) {
          remhdr=strtok(NULL,"");
          (*prshdrlin)(hdrlin);
          if (remhdr == NULL) {
               break;
          }
     }
     setmbk(webdmb);
     if (webdptr->conlen > 0) {
          if (webdptr->conlen > bdymax) {
               byeweb(BDYBUST,ul2as(bdymax),ul2as(bdymax));
               return(0);
          }
          webdptr->concnt=webdptr->reqcnt-webdptr->hdrsiz;
          if (webdptr->conlen > bodyroom) {
               if (!bdy2fil()) {
                    return(0);
               }
          }
          usrptr->substt=WEBBDY;
     }
     return(1);
}

static SHORT                       /*   returns 1=handled, 0=not recognized*/
dfthdrlin(                         /* default (*prshdrlin)() vector        */
CHAR *hdrlin)                      /*   line from the incoming req header  */
{
     CHAR *httpdat;
     CHAR *clstg;

     setmbk(webdmb);
     if (isspace(hdrlin[0])) {
     }
     else if (hdrstg(hdrlin,"Content-Length:",&clstg)
           && sscanf(clstg," %lu",&webdptr->conlen) == 1) {
     }
     else if (hdrstg(hdrlin,"Content-Type:",&webdptr->contyp)) {
     }
     else if (hdrstg(hdrlin,"If-Modified-Since:",&httpdat)) {
          if (dechttpdate(httpdat,&webdptr->imsdat,&webdptr->imstim)) {
               webdptr->flags|=WFLIMS;
          }
     }
     else if (hdrstg(hdrlin,"Referer:",&webdptr->refer)) {
     }
     else if (hdrstg(hdrlin,"User-Agent:",&webdptr->userag)) {
     }
     else {
          return(0);
     }
     return(1);
}

static VOID
dftwebreq(                         /* default (*hdlwebreq)() vector        */
CHAR *uri)                         /*   URI from request line              */
{

     UINT date;

	 setmbk(webdmb);
     if (sameas(webdptr->method,"GET")) {

		 if (!(*hdlwebget)(uri)) {
               setmbk(webdmb);
               byeweb(NOTFND,uri,uri);

			   if (audwebe) {
                    shocst("WEB SERVER PAGE DOES NOT EXIST",
                           "page \"%0.60s\" requested by %s",
                           uri,inet_ntoa(tcpipinf[usrnum].inaddr));
               }

			   if (eralog != NULL) {
                    date=today();
                    fprintf(eralog,"[%s %s %d %s %d] "
                            "httpd: access to %s failed for %s reason: "
                            "file does not exist%s\n",
                            dayofwk[daytoday()],months[ddmon(date)-1],
                            ddday(date),nctime(now()),ddyear(date),
                            uri,inet_ntoa(tcpipinf[usrnum].inaddr),
                            webdptr->refer == NULL ? ""
                            : spr(" from %-0.100s",webdptr->refer));
               }


          }
          setmbk(webdmb);

     }
     else {
          byeweb(BADMETH,webdptr->method,webdptr->method);
     }
}

static SHORT                       /*   ret 1=handled, 0=404 err (no found)*/
dftwebget(                         /* default (*hdlwebget)() routine       */
CHAR *uri)                         /*   resource identifier                */
{
     return(mapwebget(uri) || filwebget(uri));
}

SHORT                              /*   ret 1=handled, 0=not recognized    */
mapwebget(                         /* NCSA-style image map file interpreter*/
CHAR *uri)                         /* URI from client                      */
{
     CHAR *qpos;
     UINT xt,yt;
     CHAR *mapuri;
     CHAR *newurl;
     INT imgrc;

     setmbk(webdmb);
     if (!sameto((CHAR *)imgpfix,(CHAR *)uri)) {
          return(0);
     }
     mapuri=uri+strlen(imgpfix)-1;      /* mapuri should start with a "/"  */
     if ((qpos=strrchr(mapuri,'?')) == NULL
      || sscanf(qpos+1,"%u,%u",&xt,&yt) != 2) {
          byeweb(BADMAP,imgpfix);
          return(1);
     }
     *qpos='\0';                        /* mapuri is now just map file name*/
     imgrc=(*hdlimgmap)(mapuri,xt,yt,&newurl);
     setmbk(webdmb);
     switch (imgrc) {
     case -1:
          break;
     case 0:
          byeweb(MAPNOCLK);
          break;
     case 1:
          if (strchr(newurl,':') == NULL) {
               if (!filwebget(newurl)) {
                    byeweb(BADMAPRF,newurl,mapuri,newurl,mapuri);
               }
          }
          else {
               byeweb(MAPREDIR,newurl,newurl,newurl);
          }
          break;
     }
     return(1);
}

SHORT                              /*   ret 1=handled, 0=404 err (no found)*/
filwebget(                         /* file-retrieval form of GET request   */
CHAR *uri)                         /*   URI (file path rel to WEBROOT{})   */
{                                  /*   (corrupts vdatmp)                  */
     struct ffblk fb;
     CHAR *pth;
     const CHAR *cp;

     setmbk(webdmb);
     if (sameas(uri,"/")) {
          uri=dftfil;
     }
     if (badfspec(uri)) {
          return(1);
     }
     pth=webpth(uri);
     if (strlen(pth) == 0) {            /*  Filename was > GCMAXPTH        */
          return(0);
     }
     if (!fndfile(&fb,pth,FAMDIR)) {
          if (samend(pth,"/")) {        /* If uri ends in a slash,         */
               fb.ff_attrib|=FAMDIR;    /* pretend we did find a directory.*/
          }
          else if (samend(pth,".htm")) {     /* if .htm don't work         */
               stlcat(pth,"l",GCMAXPTH);     /* try .html                  */
               if (!fndfile(&fb,pth,FAMDIR)) {
                    return(0);          /* 404 - no such file or directory */
               }
          }
          else if (samend(pth,".html")) {    /* if .html don't work        */
               pth[strlen(pth)-1]='\0';      /* try .htm                   */
               if (!fndfile(&fb,pth,FAMDIR)) {
                    return(0);          /* 404 - no such file or directory */
               }
          }
          else {                             /* if doesn't work as it is   */
               stlcat(pth,".htm",GCMAXPTH);  /* try adding .htm            */
               if (!fndfile(&fb,pth,0)) {
                    stlcat(pth,"l",GCMAXPTH);/* or try adding .html        */
                    if (!fndfile(&fb,pth,FAMDIR)) {
                         return(0);     /* 404 - no such file or directory */
                    }
               }
          }
     }
     if (fb.ff_attrib&FAMDIR) {
          if (samend(pth,"/")) {
               if (sameto("/",dftfil) || sameto("\\",dftfil)) {
                    cp=dftfil+1;
               }
               else {
                    cp=dftfil;
               }
               stlcat(pth,cp,GCMAXPTH);
               if (!fndfile(&fb,pth,0)) {
                    return(0);          /* 404 - directory has no index.htm*/
               }                        /* otherwise, default file found!  */
          }
          else {
               byeweb(DIRREDIR,uri,uri); /* tell browser to end in a slash */
               return(1);
          }
     }
     if (webdptr->flags&WFLIMS
      && (fb.ff_fdate < webdptr->imsdat
       || (fb.ff_fdate == webdptr->imsdat && fb.ff_ftime < webdptr->imstim))) {
          byeweb(NOTMOD);
          return(1);
     }
     stzcpy(webdptr->goturi,uri,MAXPATH);
     return((*filwebsnd)(pth));
}

static
SHORT                              /*   ret 0=404 not found, 1=handled     */
dftwebsnd(                         /* default (*filwebsnd)() vector        */
CHAR *pth)                         /*   full path of file to send          */
{                                  /*   expects webdptr to be set          */
     if (!chustat && fsSendName(pth,tcpipinf[usrnum].socket)) {
          shochl(spr("Web get %0.80s",webdptr->goturi),'w',0x1F);
          closereq();
          tcpipinf[usrnum].server=NULL;
          tcpipinf[usrnum].socket=-1;
          btucdi(usrnum,NULL);
          bbsdsc(usrnum);
          return(1);
     }
     else {
          return(chnwebsnd(pth));
     }
}

static
SHORT                              /*   ret 0=404 not found, 1=handled     */
chnwebsnd(                         /* channel-using (*filwebsnd)() vector  */
CHAR *pth)                         /*   full path of file to send          */
{                                  /*   expects webdptr to be set          */
     struct ffblk fb;
     if (!fndfile(&fb,pth,0)) {
          return(0);
     }
     if ((webdptr->rspfp=fopen(pth,FOPRB)) == NULL) {
          return(0);
     }
     if (CreateHeader(pth)) {
          powprf();
     }
     usrptr->substt=WEBFIL;
     webdptr->tfstart=lngtck;
     webdptr->tfsupd=lngtck-1;
     webdptr->filsiz=fb.ff_fsize;
     btuinj(usrnum,CYCLE);
     shochl(spr("Web get %0.80s",webdptr->goturi),'w',0x1F);
     return(1);
}

static INT                         /*   ret 1=mapped, 0=no action, -1=error*/
dftimgmap(                         /* default (*hdlimgmap)() handler       */
CHAR *mapnam,                      /*   file name for image map            */
UINT xt,                           /*   pixel X of mouse click             */
UINT yt,                           /*   pixel Y of mouse click             */
CHAR **newurl)                     /*   where to put pointer to mapped URL */
                                   /*   (useful when returning 1 only)     */
{                                  /*   (corrupts vdatmp)                  */
     CHAR *type;
     CHAR *url;
     static UINT poly[MAXVERT][2];
     #define x1 (poly[0][0])
     #define y1 (poly[0][1])
     #define x2 (poly[1][0])
     #define y2 (poly[1][1])
     INT npoints;
     INT n;
     ULONG mindist;
     ULONG pntdist;
     FILE *mapfp;
     CHAR *wordn;
     INT bingo;                    /* definite hit                         */
     INT pbingo;                   /* potential hit, see if better one come*/
     CHAR *pth;

     setmbk(webdmb);
     if (badfspec(mapnam)) {
          return(-1);
     }
     pth=imgpth(mapnam);
     if ((mapfp=fopen(pth,FOPRA)) == NULL) {
          byeweb(MAPNOT,pth,pth);  /* never use pth again (from spr() ret) */
          return(-1);
     }
     pbingo=0;
     while (fgets(vdatmp,vdasiz-URISIZ,mapfp) != NULL) {
          if ((n=strlen(vdatmp)) > 0 && vdatmp[n-1] == '\n') {
               vdatmp[n-1]='\0';
          }
          if (vdatmp[0] != '#'
           && vdatmp[0] != '\0'
           && (type=strtok(vdatmp," \t")) != NULL
           && (url=strtok(NULL," \t")) != NULL) {
               for (npoints=0 ; npoints < MAXVERT ; npoints++) {
                    if ((wordn=strtok(NULL," \t")) == NULL
                     || sscanf(wordn,"%u,%u",&poly[npoints][0],
                                             &poly[npoints][1]) != 2) {
                         break;
                    }
               }
               bingo=0;
               if (sameas((CHAR *)type,(CHAR *)"default") && !pbingo) {
                    *newurl=stzcpy(vdatmp+vdasiz-URISIZ,url,URISIZ);
                    mindist=(ULONG)(-1L);
                    pbingo=1;
               }
               else if (sameto((CHAR *)"rect",(CHAR *)type)
                     && npoints == 2) {
                    bingo=(x1 <= xt) == (xt <= x2)
                       && (y1 <= yt) == (yt <= y2);
               }
               else if (sameto((CHAR *)"circ",(CHAR *)type)
                     && npoints == 2) {
                    bingo=udif2(x1,xt)+udif2(y1,yt)
                        < udif2(x1,x2)+udif2(y1,y2);
               }
               else if (sameto((CHAR *)"poly",(CHAR *)type)
                     && npoints >= 3) {
                    bingo=inpoly(poly,npoints,xt,yt);
               }
               else if (sameto((CHAR *)"point",(CHAR *)type)
                     && npoints == 1) {
                    pntdist=udif2(xt,x1)+udif2(yt,y1);
                    if (!pbingo) {
                         mindist=pntdist;
                         *newurl=stzcpy(vdatmp+vdasiz-URISIZ,url,URISIZ);
                         pbingo=1;
                    }
                    else if (pntdist < mindist) {
                         mindist=pntdist;
                         *newurl=stzcpy(vdatmp+vdasiz-URISIZ,url,URISIZ);
                    }
               }
               if (bingo) {
                    *newurl=url;
                    fclose(mapfp);
                    return(!sameas((CHAR *)(*newurl),noclick));
               }
          }
     }
     fclose(mapfp);
     return(pbingo && !sameas((CHAR *)(*newurl),noclick));
     #undef x1
     #undef y1
     #undef x2
     #undef y2
}

static INT                         /*   returns no. bytes sent in response */
dftrspbdy(                         /* default (*hdlrspbdy)() vector        */
CHAR *buffer,                      /*   bytes to send in response body     */
INT nbytes)                        /*   number of bytes                    */
{
     INT nroom,nsent;

     setmbk(webdmb);
     nroom=btuoba(usrnum);
     nsent=min(nroom,nbytes);      /* recompute in case intercepted        */
     if (nsent > 0) {
          btuxct(usrnum,nsent,buffer);
          if (whomon == usrnum) {
               (*ftscope)(FTSOUS,buffer,nsent);
          }
     }
     return(nsent);
}

static INT                         /*   0=no data, -1=byeweb(), else=nbytes*/
req2mem(VOID)                      /* receive request bytes to httpreqbuf  */
{                                  /*   must not call if httpreqbuf is full*/
     INT ibw,nroom,ica;

     if ((ibw=btuibw(usrnum)) <= 0) {
          return(0);
     }
     nroom=maxreqbuf-webdptr->reqcnt;
     ASSERT(nroom > 0);
     if ((ica=btuica(usrnum,httpreqbuf+webdptr->reqcnt,min(ibw,nroom))) < 0) {
          setmbk(webdmb);
          byeweb(ICAGOOF);
          rstmbk();
          return(-1);
     }
     if (ica > 0 && whomon == usrnum) {
          (*ftscope)(FTSINS,httpreqbuf+webdptr->reqcnt,ica);
     }
     webdptr->reqcnt+=ica;
     ASSERT(webdptr->reqcnt <= maxreqbuf);
     return(ica);
}

SHORT                              /*   returns 1=success, 0=byeweb called */
bdy2fil(VOID)                      /* write request body (so far) to file  */
{                                  /*   leaves file open for read & write  */
     UINT nwrite;

     if (webdptr->reqfp != NULL) {
          return(1);
     }
     chdmak();
     unlink(chdpath(bdyfil));
     if ((webdptr->reqfp=fopen(chdpath(bdyfil),FOPWRB)) == NULL) {
          setmbk(webdmb);
          byeweb(FOPFAIL,bdyfil,bdyfil);
          rstmbk();
          return(0);
     }
     if (webdptr->reqcnt > webdptr->hdrsiz) {
          nwrite=(UINT)webdptr->concnt;
          ASSERT(nwrite <= bodyroom);
          if (fwrite(bodyarea,1,nwrite,webdptr->reqfp) != nwrite) {
               setmbk(webdmb);
               byeweb(FWFAIL);
               rstmbk();
               return(0);
          }
     }
     return(1);
}

static INT                         /*   0=no data, -1=byeweb(), else=nbytes*/
req2fil(VOID)                      /* receive request bytes to body file   */
{                                  /*   (corrupts vdatmp)                  */
     INT ica;

     ASSERT(webdptr->reqfp != NULL);
     if ((ica=btuica(usrnum,vdatmp,vdasiz)) < 0) {
          setmbk(webdmb);
          byeweb(ICAGOOF);
          rstmbk();
          return(-1);
     }
     if (ica == 0) {
          return(0);
     }
     if (whomon == usrnum) {
          (*ftscope)(FTSINS,vdatmp,ica);
     }
     if (fwrite(vdatmp,1,ica,webdptr->reqfp) != ica) {
          setmbk(webdmb);
          byeweb(FWFAIL);
          rstmbk();
          return(-1);
     }
     return(ica);
}

/*--- Web Server utilities ---*/

SHORT                              /*   ret 1=bad (byeweb() called), 0=ok  */
badfspec(                          /* Is URI-based filespec dangerous?     */
CHAR *uri)                         /*   URI or filespec based on a URI     */
{
     if (strstr(uri,"..") != NULL
      || strchr(uri,':') != NULL
      || rsvnam(uri)) {
          setmbk(webdmb);
          byeweb(BADFSPEC,uri);
          rstmbk();
          return(1);
     }
     return(0);
}

SHORT                              /*   returns 1=match, 0=not             */
hdrstg(                            /* parse a named token                  */
CHAR *source,                      /*   line, eg 'Content-Type: text/html' */
CHAR *token,                       /*   token name, eg 'Content-Type:'     */
CHAR **var)                        /*   where to store pointer to value    */
{
     CHAR *retval;

     if (sameto(token,source)) {
          retval=source+strlen(token);
          while (isspace(*retval)) {
               retval++;
          }
          *var=retval;
          return(1);
     }
     return(0);
}

ULONG
udif2(                             /* square of difference of two u-ints   */
UINT a,
UINT b)
{
     ULONG dif;

     dif=(a < b) ? b-a : a-b;
     return(dif*dif);
}

CHAR *
webpth(                            /* format path name of web page         */
CHAR *pagnam)                      /* page name (can start with "/" or "\")*/
{                                  /* returns spr() ret or "" if too long  */
     static CHAR FullPath[GCMAXPTH];
     CHAR* ptr;

     if (strlen(webroot)+1+strlen(pagnam)+1 > GCMAXPTH) {
          strcpy(FullPath,"");
     }
     else {
          stlcpy(FullPath,webroot,GCMAXPTH);
          if (!(pagnam[0] == '\\' || pagnam[0] == '/')) {
               stlcat(FullPath,"/",GCMAXPTH);
          }
          stlcat(FullPath,pagnam,GCMAXPTH);
     }
     if ((ptr=strchr(FullPath,'?')) != NULL) {
          *ptr='\0';
     }
     return(FullPath);
}

CHAR *
imgpth(                            /* format path name of web page         */
CHAR *pagnam)                      /* page name (can start with "/" or "\")*/
{                                  /* returns spr() ret or "" if too long  */
     static CHAR FullPath[GCMAXPTH];

     if (strlen(imgroot)+1+strlen(pagnam)+1 > GCMAXPTH) {
          strcpy(FullPath,"");
     }
     else {
          stlcpy(FullPath,imgroot,GCMAXPTH);
          if (!(pagnam[0] == '\\' || pagnam[0] == '/')) {
               stlcat(FullPath,"/",GCMAXPTH);
          }
          stlcat(FullPath,pagnam,GCMAXPTH);
     }
     return(FullPath);
}

/*--- Web Server end of session ---*/

VOID
byeweb(                            /* say goodbye to a Web user w/msg      */
INT msgno,                         /*   message number to use              */
...)                               /*   usrnum is an implicit input        */
{                                  /*   (do not use msgnum=NO_MESSAGE)     */
     va_list vlst;

     ASSERTM(btuoba(usrnum) == OUTSIZ/2-1,
             spr("msgno=%d,btuoba=%d",msgno,btuoba(usrnum)));
     endwebd(0);
     btulok(usrnum,1);
     btutrg(usrnum,0);   /* (so btulok() doesn't make snkwin() return 0)   */
     btucli(usrnum);
     btuclo(usrnum);
     btuoes(usrnum,1);
     va_start(vlst,msgno);
     vsprintf(prfbuf,xlttxv(stpans(rawmsg(msgno)),mxmssz),vlst);
     va_end(vlst);
     outprf(usrnum);
     clrprf();
     (usroff(usrnum))->flags|=BYEBYE;
}

VOID
closereq(VOID)
{
     if (webdptr->reqfp != NULL) {
          fclose(webdptr->reqfp);
          unlink(chdpath(bdyfil));
          webdptr->reqfp=NULL;
     }
}

VOID
endwebd(                           /* terminate the WWW session            */
INT complt)                        /* 1=finished, 0=aborted                */
{
     SHORT date;
     ULONG secs=0UL;
     ULONG cps=0UL;

     btubsz(usrnum,INPSIZ,OUTSIZ);
     closereq();
     if (webdptr->rspfp != NULL) {
          fclose(webdptr->rspfp);
          webdptr->rspfp=NULL;
          if (audweb) {
               shocst(complt ? "WEB SERVER PAGE RETRIEVED"
                             : "WEB SERVER RETRIEVAL ABORTED",
                      "page \"%0.60s\", %lu bytes, sent to %s",
                      webdptr->uri,webdptr->bytcnt,
                      inet_ntoa(tcpipinf[usrnum].inaddr));
          }
          if (whomon == usrnum) {
               (*ftscope)(FTSCMT,spr("***** (%s) session ends",
                          inet_ntoa(tcpipinf[usrnum].inaddr)));
          }
          if (webdptr->abwhy[0] == '\0' && (kilipg || errcod != 1)) {
               strcpy(webdptr->abwhy,"system shutdown");
          }
          secs=lngtck-webdptr->tfstart;
          if (secs > 0UL) {
               cps=(webdptr->bytcnt+(secs>>1))/secs;
          }
          if (!complt && eralog != NULL) {
               date=today();
               fprintf(eralog,"[%s %s %d %s %d] "
                       "httpd: access to %s failed for %s reason: %s",
                       dayofwk[daytoday()],months[ddmon(date)-1],
                       ddday(date),nctime(now()),ddyear(date),
                       webdptr->uri,inet_ntoa(tcpipinf[usrnum].inaddr),
                       webdptr->abwhy);
               fprintf(eralog," bytes=%lu file=%lu",
                              webdptr->bytcnt,webdptr->filsiz);
               if (secs > 0UL) {
                    fprintf(eralog," seconds=%lu cps=%lu",secs,cps);
               }
               fprintf(eralog,"\n");
          }
     }
     if (axslog != NULL && complt) {
          date=today();
          fprintf(axslog,"%s - - [%d/%s/%d:%s -0000] \"%s %s %s\" 200 %lu",
               inet_ntoa(tcpipinf[usrnum].inaddr),ddday(date),
               months[ddmon(date)-1],ddyear(date),nctime(now()),
               webdptr->method,webdptr->uri,webdptr->vertxt,webdptr->bytcnt);
          if (secs > 0UL) {
               fprintf(axslog," seconds=%lu cps=%lu",secs,cps);
          }
          fprintf(axslog,"\n");
     }
     if (reflog != NULL) {  
          date=today();
          fprintf(reflog,"%s -> %s",webdptr->refer,webdptr->uri);
          fprintf(reflog,"\n");
     }
}

static VOID
webdrst(VOID)                      /* Web server reset interception        */
{
     if (usrptr->usrcls == BBSPRV && usrptr->state == webdstt) {
          endwebd(0);
     }
     (*oldrst)();
}

/*--- Web Server text variables ---*/

static CHAR *
tvar_now_http(VOID)                /* text variable:  RFC1123 fmt date&time*/
{
     return(enchttpdate(today(),now()));
}

static CHAR *
tvar_webd_version(VOID)            /* text variable:  Web server version   */
{
     return(webd_version);
}

/*--- File-to-Socket utilities ---*/

GBOOL                              /*   TRUE=on its way, FALSE=can't       */
fsSendName(                        /* send a file to a socket              */
char *fname,                       /*   name (path) of file                */
int skt)                           /*   socket handle                      */
{
     FILE *fp;

     if ((fp=fopen(fname,FOPRB)) == NULL) {
          return(FALSE);           /* can't send, file not found           */
     }
     if (!fsSendHandle(fp,fname,skt)) {
          fclose(fp);
          return(FALSE);           /* can't send, too many already sending */
     }
     return(TRUE);
}

GBOOL                              /*   TRUE=on its way, FALSE=can't       */
fsSendHandle(                      /* send a file to a socket              */
FILE *fp,                          /*   open file handle                   */
CHAR* fname,                       /*   File name we are sending           */
int skt)                           /*   socket handle                      */
{
     INT i;

     for (i=0 ; i < nonchu ; i++) {
          if (!(fsinfo[i].flags&FSIUSE)) {
               fsinfo[i].flags|=FSIUSE;
               fsinfo[i].socket=skt;
               fsinfo[i].fp=fp;
               fsinfo[i].header=NULL;
               sktcnc(TNFRECV,skt);
               sktnfy(TNFSEND,skt,(VOID (*)(VOID *))fsSending,&fsinfo[i],-1);
               if (CreateHeader(fname)) {
                    fsinfo[i].header=strdup(prfbuf);
               }
               return(TRUE);
          }
     }
     return(FALSE);
}

VOID
fsSending(                         /* continue sending file (via sktnfy()) */
struct fsinfo *fsi)                /*   file-socket info                   */
{
     INT nread;
     INT nsent;
     GBOOL bClose=FALSE;

     ASSERT(nfyskt == fsi->socket);
     ASSERT(usrnum == -1);
     if (fsi->header != NULL) { // Still must send the header
          if ((nsent=send(fsi->socket,fsi->header,strlen(fsi->header),0)) < 0
           && tcpip_errno != EWOULDBLOCK) {
               bClose=TRUE;
          }
          else if (nsent == strlen(fsi->header)) { // All was sent
               free(fsi->header);
               fsi->header=NULL;
          }
          else {
               strmove(fsi->header,&fsi->header[nsent]);
          }
     }
     else {
          if ((nread=fread(vdatmp,1,FSCHUNK,fsi->fp)) <= 0
           || ((nsent=send(fsi->socket,vdatmp,nread,0)) < 0
           && tcpip_errno != EWOULDBLOCK)) {
               bClose=TRUE;
          }
          else {
               if (nsent < 0) {
                    nsent=0;
               }
               if (nsent < nread) {
                    fseek(fsi->fp,nsent-nread,SEEK_CUR);
               }
          }
     }

     if (bClose) {
          if (fsi->header != NULL) {
               free(fsi->header);
               fsi->header=NULL;
          }
          fclose(fsi->fp);
          clsskt(fsi->socket);
          fsi->flags&=~FSIUSE;
     }
}                                  /* should we keep trying (N*FSCHUNK)?   */

/*--- The following routine, tcpinc(), will be moved to TCPIP.C in ICO  ---*/
/*--- version 2.0.  It's included here for binary compatibility w/1.0.  ---*/
/*--- But now it includes special diagnostics, so it may stay here.     ---*/

VOID
tcpinc(                            /* read socket data into GCDI channel   */
struct tcpipinf *tip)              /* user's TCP/IP channel info           */
                                   /* application:  sktnfy(,,tcpinc,tip,)  */
{
     struct datstm *dsp;
     UINT nroom;
     INT nactual;
     INT attempt;
     INT oldicx;

     dsp=tip->cdidst;
     ASSERT(tip->socket == nfyskt);
     for (attempt=0 ; attempt < 10 ; attempt++) {
          oldicx=iskopen();
          if ((nroom=dsp->snkwin(dsp)) == 0) {
               if (attempt == 0) {
                    actdet=0;
               }
               iskclose(oldicx);
               break;
          }
          if ((nactual=recv(tip->socket,dsp->snkloc,nroom,0)) < 0) {
               switch (tcpip_errno) {
               case ECONNRESET:         /* benign disconnect (abort)       */
                    strcpy(webdptru(tip->unum)->abwhy,"connection reset");
                    sktcnc(TNFRECV,tip->socket);
                    sktcnc(TNFSEND,tip->socket);
                    bbsdsc(usrnum);
					
                    break;
               case EWOULDBLOCK:        /* no more polling necessary       */
                    break;
               default:                 /* error condition                 */
                    sprintf(webdptru(tip->unum)->abwhy,"recv error %d: %0.60s",
                            tcpip_errno,tcpErrStg(tcpip_errno));
                    svrfail(usrnum,"RECEIVE",tip->server->port);
                    break;
               }
               iskclose(oldicx);
               break;
          }
          if (nactual == 0) {
               if (attempt == 0) {
                    strcpy(webdptru(tip->unum)->abwhy,"disconnected");
                    sktcnc(TNFRECV,tip->socket);
                    sktcnc(TNFSEND,tip->socket);
                    bbsdsc(usrnum);
               }
               iskclose(oldicx);
               break;
          }
          dsp->didmov(dsp,nactual);
          iskclose(oldicx);
          if (tip->outsnk.bufcnt > 0) {
               tcpsnd(tip);
          }
          if (nactual < nroom) {   /* recv() didn't exhaust sink's window, */
               break;              /* no point in trying to recv() again   */
          }
     }
}

/*--- The following routine is a clone of tcpmit() in TCPIP.C           ---*/
/*--- This version has specialized diagnostics.                         ---*/

USHORT
webmit(                            /* TCP/IP moveit(), channel output sink */
struct datstm *dsp,                /* (pointer to sink's datstm structure) */
CHAR *srcloc,                      /* source specifies the location        */
USHORT nwant)                      /* source wants sink to move this many  */
{                                  /* returns actual number of bytes moved */
     INT nactual;
     struct tcpipinf *tip;
     dealwith_cdi_interrupts;

     allow_cdi_interrupts;

     tip=(struct tcpipinf *)dsp;
     ASSERT(tip->server != NULL);
     if (nwant > 0 && !(tip->flags&TCPDSC)) {
          if ((nactual=send(tip->socket,srcloc,nwant,0)) == SOCKET_ERROR) {
               if (tcpip_errno == EWOULDBLOCK) {
                    nactual=0;
               }
               else {
                    sprintf(webdptru(tip->unum)->abwhy,"send error %d: %0.60s",
                            tcpip_errno,tcpErrStg(tcpip_errno));
                    svrfail(tip->unum,"SEND",tip->server->port);
                    restore_cdi_interrupts;
                    return(0);
               }
          }
          ASSERTM(nactual >= 0 && nactual <= nwant,
                  spr("nwant=%u nactual=%d",nwant,nactual));
          if (tip->outsnk.bufcnt > 0 && nactual < nwant) {
               sktnfy(TNFSEND,tip->socket,tcpsnd,tip,tip->unum);
          }
          restore_cdi_interrupts;
          return((UINT)nactual);
     }
     restore_cdi_interrupts;
     return(0);
}


/*--- The following code may one day be moved to a more general purpose ---*/
/*--- place, such as DOSFACE.C or DATUTILS.C or DSKUTL.C.               ---*/

#define DAYSECS (60UL*60UL*24UL)   /* seconds in a day                     */
#define DECADESECS (DAYSECS*(365UL*10UL+2UL))
                                   /* seconds from 1/1/70 to 1/1/80        */

VOID
datu2d(                            /* convert Unix date/time to DOS format */
ULONG uxtime,                      /*   Unix seconds since 1/1/70          */
USHORT *dat,                       /*   pointer to DOS packed date output  */
USHORT *tim)                       /*   pointer to DOS packed time output  */
{
     USHORT day,hour,minsec;

     if (uxtime >= DECADESECS) {
          uxtime-=DECADESECS;      /* conv to secs since 00:00 1/1/80      */
     }
     else {
          uxtime=0UL;
     }
     day=(USHORT)(uxtime/DAYSECS);            /* days since 1/1/80 */
     hour=(USHORT)((uxtime/3600UL)%24UL);     /* hour since midnite*/
     minsec=(USHORT)(uxtime%3600UL);          /* seconds since hour*/
     *tim=dttime(hour,minsec/60,minsec%60);
     *dat=datofc(day);
}

VOID
datd2u(                            /* convert DOS date/time to Unix format */
USHORT dat,                        /*   DOS packed date                    */
USHORT tim,                        /*   DOS packed time                    */
ULONG *uxtime)                     /*   ptr, Unix secs since 1/1/70 output */
{
     *uxtime=DECADESECS+           /* 00:00 1/1/70 to 00:00 1/1/80         */
             cofdat(dat)*DAYSECS+  /* 00:00 1/1/80 to 00:00 fileday        */
             dthour(tim)*3600UL+   /*  \                                   */
             dtmin(tim)*60UL+      /*   > compute filetime since           */
             dtsec(tim);           /*  /  00:00 on fileday                 */
}

CHAR *                             /*   returns pointer to formatted date  */
enchttpdate(                       /* Encode an HTTP date                  */
USHORT dat,                        /*   DOS packed date format             */
USHORT tim)                        /*   DOS packed time format             */
{
     static CHAR buff[40];
     ULONG uxnow;

     datd2u(dat,tim,&uxnow);
     datu2d(uxnow+webtzone*60,&dat,&tim);
     sprintf(buff,"%-3.3s, %02u %s %u %02u:%02u:%02u GMT",
                  dayofwk[(cofdat(dat)+2)%7],
                  ddday(dat),moname[ddmon(dat)],ddyear(dat),
                  dthour(tim),dtmin(tim),dtsec(tim));
     buff[9]=tolower(buff[9]);
     buff[10]=tolower(buff[10]);
     return(buff);
}

INT                                /*   returns 1=ok, 0=bad format         */
dechttpdate(                       /* Decode an HTTP date                  */
CHAR *dnt,                         /*   RFC 1123, RFC 850, or ANSI C format*/
USHORT *dat,                       /*   DOS packed date format             */
USHORT *tim)                       /*   DOS packed time format             */
                                   /*   CAUTION: destroys dnt date & time  */
                                   /*   See HTTP 1.0 section 3.3.1, 3/8/95 */
                                   /*   1123: WWW, DD MMM YYYY HH:MM:SS GMT*/
                                   /*   850: WWWWWW, DD-MMM-YY HH:MM:SS GMT*/
                                   /*   ANSI: WWW MMM DD HH:MM:SS YYYY     */
{                                  /*   Good for dates from 1980 to 2069   */
     USHORT num[6];                /* decoded numbers                      */
     static CHAR dlims[]=" -:";    /* delimiters between numbers & words   */
     INT numstg;                   /* qty of non-numbers (exc day of week) */
     INT idxstg;                   /* index, 0 to 5, of last non-number    */
     CHAR *stg;                    /* last non-number                      */
     INT hri;                      /* index, 0-5, of hour (min&sec follow) */
     USHORT mon,day,year;          /* values extracted from numbers & words*/
     CHAR *cp;
     INT i;
     ULONG uxtim;

     cp=strtok(dnt,dlims);
     if (cp == NULL) {
          return(0);
     }
     numstg=0;
     for (i=0 ; i < 6 ; i++) {
          if ((cp=strtok(NULL,dlims)) == NULL) {
               return(0);
          }
          if (alldgs(cp)) {
               num[i]=(USHORT)atoi(cp);
          }
          else {
               numstg++;
               idxstg=i;
               stg=cp;
          }
     }
     if (numstg != 1 || idxstg >= 2) {
          return(0);
     }
     for (i=1 ; i <= 12 ; i++) {
          if (sameto((CHAR *)moname[i],stg)) {
               mon=i;
               break;
          }
     }
     if (i > 12) {
          return(0);
     }
     if (idxstg == 1) {            /* RFC 1123 or 850 format date & time   */
          year=num[2];
          day=num[0];
          hri=3;
     }
     else {                        /* ANSI C's asctime() format            */
          year=num[5];
          day=num[1];
          hri=2;
     }
     if (year < 70) {                             /* 00-69 => 2000-2069    */
          year+=2000;                             /* 70-79 => 1980         */
     }                                            /* 80-99 => 1980-1999    */
     if (year < 100) {                            /* 1970-1979 => 1980     */
          year+=1900;                             /* 1980-2069 => 1980-2069*/
     }
     if (year < 1980) {
          year=1980;
     }
     *dat=dddate(mon,day,year);
     *tim=dttime(num[hri],num[hri+1],num[hri+2]);
     datd2u(*dat,*tim,&uxtim);
     datu2d(uxtim-webtzone*60L,dat,tim);
     return(1);
}

static GBOOL
CreateHeader(                 /*  Create an HTTP header               */
CHAR* pth)                    /*  Path of file to create header       */
{
     const CHAR *cp;
     CHAR fnext[MAXEXT];
     struct ffblk fb;

     if (fndfile(&fb,pth,0) && webdptr->httpver >= 100) {
          if ((cp=gmimctyp(fileparts(GCPART_EXTN,fb.ff_name,fnext,MAXEXT)))
           != NULL) {
               prf(xlttxv(stpans(getasc(FILEGET2)),mxmssz),
                   ul2as(fb.ff_fsize),enchttpdate(fb.ff_fdate,fb.ff_ftime),
                   cp);
          }
          else {
               prf(xlttxv(stpans(getasc(FILEGET)),mxmssz),
                   ul2as(fb.ff_fsize),enchttpdate(fb.ff_fdate,fb.ff_ftime));
          }
          return(TRUE);
     }
     return(FALSE);
}
