#include "innbbsconf.h"
#include "daemon.h"
#include "bbslib.h"
#include "config.h"

#define DEBUG
#undef DEBUG

#ifndef MAXCLIENT
#define MAXCLIENT 500
#endif

#ifndef ChannelSize
#define ChannelSize 4096
#endif

#ifndef ReadSize
#define ReadSize 1024
#endif

#ifndef DefaultINNBBSPort
# define DefaultINNBBSPort "7777"
#endif

#ifndef HIS_MAINT
# define HIS_MAINT
# define HIS_MAINT_HOUR 5
# define HIS_MAINT_MIN  30
#endif

int Maxclient=MAXCLIENT;
ClientType *Channel=NULL;
ClientType INNBBSD_STAT;

int Max_Art_Size = MAX_ART_SIZE;

int inetdstart = 0;

int Junkhistory = 0;

char *REMOTEUSERNAME, *REMOTEHOSTNAME;

static fd_set rfd,wfd,efd,orfd,owfd,oefd;

clearfdset(fd)
int fd;
{
   FD_CLR(fd,&rfd);
}

static
channelcreate(client)
ClientType *client;
{
  buffer_t *in, *out;
  in = &client->in;
  out = &client->out;
  if (in->data != NULL)
    free(in->data);
  in->data = (char*)mymalloc( ChannelSize );
  in->left = ChannelSize;
  in->used = 0;
  if (out->data != NULL)
    free(out->data);
  out->data = (char*)mymalloc( ChannelSize );
  out->used = 0;
  out->left = ChannelSize;
  client->ihavecount = 0;
  client->ihaveduplicate = 0;
  client->ihavefail = 0;
  client->ihavesize = 0;
  client->statcount = 0;
  client->statfail = 0;
  client->begin = time(NULL);
}

channeldestroy(client)
ClientType *client;
{
   if (client->in.data != NULL) {
     free(client->in.data);
     client->in.data = NULL;
   }
   if (client->out.data != NULL) {
     free(client->out.data);
     client->out.data = NULL;
   }
#if !defined(PowerBBS) && !defined(DBZSERVER)
   if (client->ihavecount >0 || client->statcount >0) {
     bbslog("%s@%s rec: %d dup: %d fail: %d size: %d, stat rec: %d fail: %d, time sec: %d\n",
    client->username, client->hostname, client->ihavecount,
    client->ihaveduplicate, client->ihavefail, client->ihavesize,
    client->statcount, client->statfail, time(NULL) - client->begin);
    INNBBSD_STAT.ihavecount += client->ihavecount;
    INNBBSD_STAT.ihaveduplicate += client->ihaveduplicate;
    INNBBSD_STAT.ihavefail += client->ihavefail;
    INNBBSD_STAT.ihavesize += client->ihavesize;
    INNBBSD_STAT.statcount += client->statcount;
    INNBBSD_STAT.statfail  += client->statfail;
   }
#endif
}

inndchannel(port, path)
char *port, *path;
{
     time_t tvec;
     int i;
     int bbsinnd ;
     int localbbsinnd;
     char obuf[4096];
     struct timeval tout;
     ClientType *client = (ClientType *)mymalloc( sizeof(ClientType) * Maxclient);
     int localdaemonready = 0;
     Channel = client;

     bbsinnd = pmain(port);
     if (bbsinnd < 0) {
        perror("pmain, existing");
        docompletehalt();
        return(-1);
     }

     FD_ZERO(&rfd); FD_ZERO(&wfd); FD_ZERO(&efd);

     localbbsinnd = p_unix_main(path);
     if (localbbsinnd < 0) {
        perror("local pmain, existing");
/* Kaede
        if (!inetdstart)
          fprintf(stderr, "if no other innbbsd running, try to remove %s\n",path);
*/
        close(bbsinnd);
        return(-1);
     } else {
       FD_SET(localbbsinnd,&rfd);
       localdaemonready = 1;
     }

     FD_SET(bbsinnd,&rfd);
     tvec = time((time_t *)0);
     for (i=0;i< Maxclient ;++i) {
         client[i].fd = -1;
         client[i].access=0;
         client[i].buffer[0] = '\0';
         client[i].mode = 0;
         client[i].in.left = 0;
         client[i].in.used = 0;
         client[i].in.data = NULL;
         client[i].out.left = 0;
         client[i].out.used = 0;
         client[i].out.data = NULL;
         client[i].midcheck = 1;
     }
     for (;;) {
        int nsel,i;

/*
          When to maintain history files.
*/
        time_t now;
        static int maint = 0;
        struct tm *local;

        if (INNBBSDshutdown()) {
           HISclose();
           bbslog(" Shutdown Complete \n");
           docompletehalt();
           exit(0);
        }

        time(&now);
        local = localtime(&now);
        if (local != NULL & local->tm_hour == His_Maint_Hour &&
            local->tm_min  >= His_Maint_Min ) {
           if (!maint) {
             bbslog(":Maint: start (%d:%d).\n",local->tm_hour,local->tm_min);
             HISmaint();
             time(&now);
             local = localtime(&now);
             if (local != NULL)
               bbslog(":Maint: end (%d:%d).\n",local->tm_hour,local->tm_min);
             maint = 1;
           }
        } else {
           maint = 0;
        }
/*
*/
/*
        in order to maintain history, timeout every 60 seconds in case
        no connections
*/
        tout.tv_sec = 60;
        tout.tv_usec = 0;
        orfd = rfd;
        if ((nsel=select(FD_SETSIZE,&orfd, NULL , NULL , &tout))<0){
                 continue;
        }
        if (localdaemonready && FD_ISSET(localbbsinnd,&orfd)) {
           int ns,length;
           int cc;
           ns=tryaccept(localbbsinnd);
           if (ns < 0) continue;
           for (i=0;i< Maxclient ;++i) {
               if (client[i].fd==-1) break;
           }
           if (i== Maxclient) {
              static char msg[]="502 no free descriptors\r\n";
              printf("%s",msg);
              write(ns, msg, sizeof(msg));
              close(ns);
              continue;
           }
           client[i].fd=ns;
           client[i].buffer[0] = '\0';
           client[i].mode = 0;
           client[i].midcheck = 1;
           channelcreate(&client[i]);
           FD_SET(ns,&rfd); /*FD_SET(ns,&wfd);*/
           {
                strncpy(client[i].username,"localuser",20);
                strncpy(client[i].hostname,"localhost",128);
                client[i].Argv.in  =  fdopen( ns,"r");
                client[i].Argv.out =  fdopen( ns,"w");
#if !defined(PowerBBS) && !defined(DBZSERVER)
                bbslog("connected from (%s@%s).\n",client[i].username, client[i].hostname);
#endif
#ifdef INNBBSDEBUG
                printf("connected from (%s@%s).\n",client[i].username, client[i].hostname);
#endif
#ifdef DBZSERVER
                fprintf(client[i].Argv.out,"200 %s InterNetNews DBZSERVER server %s (%s@%s).\r\n",MYBBSID, VERSION, client[i].username, client[i].hostname);
#else
                fprintf(client[i].Argv.out,"200 %s InterNetNews INNBBSD server %s (%s@%s).\r\n", MYBBSID, VERSION, client[i].username, client[i].hostname );
#endif
                fflush(client[i].Argv.out);
                verboselog("UNIX Connect from %s@%s\n",client[i].username, client[i].hostname);
           }
        }

        if (FD_ISSET(bbsinnd,&orfd)) {
           int ns=tryaccept(bbsinnd), length;
           struct sockaddr_in there;
           char *name;
           struct hostent *hp;
           int cc;
           if (ns < 0) continue;
           for (i=0;i< Maxclient ;++i) {
               if (client[i].fd==-1) break;
           }
           if (i== Maxclient) {
              static char msg[]="502 no free descriptors\r\n";
              printf("%s",msg);
              write(ns, msg, sizeof(msg));
              close(ns);
              continue;
           }
           client[i].fd=ns;
           client[i].buffer[0] = '\0';
           client[i].mode = 0;
           client[i].midcheck = 1;
           channelcreate(&client[i]);
           FD_SET(ns,&rfd); /*FD_SET(ns,&wfd);*/
           length = sizeof(there);
           if (getpeername(ns,(struct sockaddr *)&there,&length)>=0){
                time_t now=time((time_t *)0);
                name=(char*)my_rfc931_name(ns,&there);
                strncpy(client[i].username,name,20);
                hp = (struct hostent*)gethostbyaddr((char*)&there.sin_addr, sizeof (struct in_addr), there.sin_family);
                if (hp)
                   strncpy(client[i].hostname,hp->h_name,128);
                else
                   strncpy(client[i].hostname,(char*)inet_ntoa(there.sin_addr),128);

                client[i].Argv.in  =  fdopen( ns,"r");
                client[i].Argv.out =  fdopen( ns,"w");
                if ((char*)search_nodelist(client[i].hostname,client[i].username) == NULL) {
                  bbslog(":Err: invalid connection (%s@%s).\n",client[i].username, client[i].hostname);
                  fprintf(client[i].Argv.out,"502 You are not in my access file. (%s@%s)\r\n", client[i].username, client[i].hostname);
                  fflush(client[i].Argv.out);
                  fclose(client[i].Argv.in);
                  fclose(client[i].Argv.out);
                  close(client[i].fd);
                  FD_CLR(client[i].fd,&rfd);
                  client[i].fd = -1;
                  continue;
                }
                bbslog("connected from (%s@%s).\n",client[i].username, client[i].hostname);
#ifdef INNBBSDEBUG
                printf("connected from (%s@%s).\n",client[i].username, client[i].hostname);
#endif
#ifdef DBZSERVER
                fprintf(client[i].Argv.out,"200 %s InterNetNews DBZSERVER server %s (%s@%s).\r\n",MYBBSID, VERSION, client[i].username, client[i].hostname );
#else
                fprintf(client[i].Argv.out,"200 %s InterNetNews INNBBSD server %s (%s@%s).\r\n",MYBBSID, VERSION, client[i].username, client[i].hostname );
#endif
                fflush(client[i].Argv.out);
                verboselog("INET Connect from %s@%s\n",client[i].username, client[i].hostname);
           } else {
           }

        }
        for (i=0;i< Maxclient ;++i) {
            int fd=client[i].fd;
            if (fd < 0) {
                    continue;
            }
            if (FD_ISSET(fd,&orfd)) {
                int nr;
#ifdef DEBUG
                printf("before read i %d in.used %d in.left %d\n",i,client[i].in.used, client[i].in.left);
#endif
                nr=channelreader(client+i);
#ifdef DEBUG
                printf("after read i %d in.used %d in.left %d\n",i,client[i].in.used, client[i].in.left);
#endif
                /*int nr=read(fd,client[i].buffer,1024);*/
                if (nr <= 0) {
                    FD_CLR(fd,&rfd);
                    fclose(client[i].Argv.in);
                    fclose(client[i].Argv.out);
                    close(fd);
                    client[i].fd = -1;
                    channeldestroy(client+i);
                    continue;
                }
#ifdef DEBUG
                printf("nr %d %.*s", nr, nr, client[i].buffer);
#endif
                if (client[i].access==0) {
                   continue;
                }
            }
        }
     }
}

int channelreader(client)
ClientType *client;
{
   int len, clientlen;
   char buffer1[8192], buffer2[4096];
   char *ptr;
   buffer_t *in = &client->in;

   if (in->left < ReadSize+3) {
      int need = in->used + in->left + ReadSize + 3;
      need += need/5 ;
      in->data = (char*)myrealloc(in->data, need);
      in->left = need - in->used;
      verboselog("channelreader realloc %d\n",need);
   }
   len = read(client->fd, in->data+in->used, ReadSize);

   if (len <=0) return len;

   in->data[len+in->used] = '\0';
   in->lastread = len;
#ifdef DEBUG
   printf("after read lastread %d\n", in->lastread);
   printf("len %d client %d\n", len, strlen(in->data+in->used));
#endif

   REMOTEHOSTNAME = client->hostname;
   REMOTEUSERNAME = client->username;
   if (client->mode == 0) {
     if ( (ptr=(char*)strchr(in->data,'\n')) != NULL) {
      if (in->data[0] != '\r')
        commandparse(client);
     }
   } else {
     commandparse(client);
   }
   return len;
}

commandparse(client)
ClientType *client;
{
     char *ptr, *lastend;
     argv_t *Argv = &client->Argv;
     int (*Main)();
     char *buffer = client->in.data;
     int fd = client->fd;
     buffer_t *in = &client->in;
     int dataused;
     int dataleft;

#ifdef DEBUG
     printf("%s %s buffer %s",client->username, client->hostname, buffer);
#endif
     ptr= (char*) strchr(in->data+in->used,'\n');
     if (client->mode == 0) {
        if (ptr == NULL) {
             in->used += in->lastread;
             in->left -= in->lastread;
             return;
        } else {
          dataused = ptr - (in->data + in->used) + 1;
          dataleft = in->lastread - dataused;
          lastend = ptr + 1;
        }
     } else {
        if (in->used >= 5) {
          ptr = (char*) strstr(in->data+in->used-5,"\r\n.\r\n");
        } else if (strncmp(in->data,".\r\n",3)==0) {
          ptr = in->data;
        } else {
          ptr = (char*) strstr(in->data+in->used,"\r\n.\r\n");
        }
        if (ptr == NULL) {
             in->used += in->lastread;
             in->left -= in->lastread;
             return;
        } else {
          ptr[2]='\0';
          if ( strncmp(in->data,".\r\n",3)==0)
             dataused = 3;
          else
             dataused = ptr - (in->data + in->used) + 5;
          dataleft = in->lastread - dataused;
          lastend = ptr + 5;
          verboselog("Get: %s@%s end of data . size %d\n", client->username, client->hostname, in->used + dataused);
          client->ihavesize += in->used + dataused;
        }
     }
     if (client->mode == 0) {
        struct Daemoncmd * dp;
        Argv->argc = 0, Argv->argv = NULL,
        Argv->inputline= buffer;
        if ( ptr != NULL) *ptr = '\0';
        verboselog("Get: %s\n",Argv->inputline);
        Argv->argc = argify( in->data + in->used,&Argv->argv);
        if ( ptr != NULL) *ptr = '\n';
        dp = (struct Daemoncmd *) searchcmd(Argv->argv[0]);
        Argv->dc = dp;
        if (Argv->dc) {
#ifdef DEBUG
          printf("enter command %s\n",Argv->argv[0]);
#endif
          if (Argv->argc < dp->argc) {
           fprintf(Argv->out,"%d Usage: %s\r\n",dp->errorcode,dp->usage);
           fflush(Argv->out);
           verboselog("Put: %d Usage: %s\n",dp->errorcode,dp->usage);
          } else if (dp->argno != 0 && Argv->argc > dp->argno) {
           fprintf(Argv->out,"%d Usage: %s\r\n",dp->errorcode,dp->usage);
           fflush(Argv->out);
           verboselog("Put: %d Usage: %s\n",dp->errorcode,dp->usage);
          } else {
            Main=Argv->dc->main;
            if (Main) {
              fflush(stdout);
              (*Main)(client);
            }
          }
        } else {
          fprintf(Argv->out,"500 Syntax error or bad command\r\n");
          fflush(Argv->out);
          verboselog("Put: 500 Syntax error or bad command\r\n");
        }
        deargify(&Argv->argv);
     } else {
        if (Argv->dc) {
#ifdef DEBUG
           printf("enter data mode\n");
#endif
           Main=Argv->dc->main;
           if (Main) {
              fflush(stdout);
              (*Main)(client);
           }
        }
     }
     if (client->mode == 0) {
      if (dataleft > 0) {
          strncpy(in->data, lastend, dataleft);
#ifdef INNBBSDEBUG
          printf("***** try to copy %x %x %d bytes\n",in->data, lastend, dataleft);
#endif
      } else {
          dataleft = 0;
      }
      in->left += in->used - dataleft;
      in->used = dataleft;
     }
}

do_command()
{
}

void dopipesig(s)
int s;
{
    printf("catch sigpipe\n");
    signal(SIGPIPE, dopipesig);
}

int standaloneinit(port)
char *port ;
{
       int ndescriptors;
       FILE *pf;
       char pidfile[24];
       ndescriptors = getdtablesize();
/*#ifndef NOFORK*/
       if (!inetdstart)
          if (fork())
                exit(0);
/*#endif*/

       sprintf(pidfile,"/tmp/innbbsd-%s.pid",port);
/* Kaede
       if (!inetdstart)
         fprintf(stderr, "PID file is in %s\n", pidfile);
*/
       {  int s;
          for (s = 3; s < ndescriptors; s++)
              (void) close(s);
       }
       pf=fopen(pidfile,"w");
       if (pf != NULL) {
               fprintf(pf,"%d\n",getpid());
               fclose(pf);
       }
}

extern char *optarg;
extern int opterr, optind;

innbbsusage(name)
char *name;
{
   fprintf(stderr,"Usage: %s   [options] [port [path]]\n",name);
   fprintf(stderr,"   -v   (verbose log)\n");
   fprintf(stderr,"   -h|? (help)\n");
   fprintf(stderr,"   -n   (not to use in core dbz)\n");
   fprintf(stderr,"   -i   (start from inetd with wait option)\n");
   fprintf(stderr,"   -c   connections  (maximum number of connections accepted)\n");
   fprintf(stderr,"        default=%d\n",Maxclient);
   fprintf(stderr,"   -j   (keep history of junk article, default=none)\n");
}


#ifdef DEBUGNGSPLIT
main()
{
   char **ngptr ;
   char buf[1024];
   gets(buf);
   ngptr = (char**)BNGsplit(buf);
   printf("line %s\n",buf);
   while ( *ngptr != NULL) {
      printf("%s\n",*ngptr);
      ngptr++;
   }
}
#endif

static time_t INNBBSDstartup;
innbbsdstartup()
{
   return INNBBSDstartup;
}

main(argc,argv)
int argc;
char **argv;
{

    char *port, *path;
    int c, errflag=0;
    extern INNBBSDhalt();
/*
woju
*/
    setgid(BBSGID);
    setuid(BBSUID);
    chdir(BBSHOME);
    attach_SHM();
    resolve_boards();

    port = DefaultINNBBSPort;
    path = LOCALDAEMON;
    Junkhistory = 0;

    time(&INNBBSDstartup);
    openlog("innbbsd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
    while ((c = getopt(argc,argv,"c:f:s:vhidn?j"))!= -1)
      switch (c) {
      case 'j':
       Junkhistory = 1;
       break;
      case 'v':
       verboseon("innbbsd.log");
       break;
      case 'n':
       hisincore(0);
       break;
      case 'c':
       Maxclient = atoi(optarg);
       if (Maxclient < 0) Maxclient = 0;
       break;
      case 'i': {
       struct sockaddr_in there;
       int len = sizeof(there);
       int rel;
       if ((rel=getsockname(0,(struct sockaddr *)&there,&len))< 0){
          fprintf(stdout,"You must run -i from inetd with inetd.conf line: \n");
          fprintf(stdout,"service-port stream  tcp wait  bbs  /home/bbs/innbbsd innbbsd -i port\n");
          fflush(stdout);
          exit(5);
       }
       inetdstart  = 1;
       startfrominetd(1);
      }
       break;
      case 'd':
       dbzdebug(1);
       break;
      case 's':
       Max_Art_Size = atol(optarg);
       if (Max_Art_Size < 0) Max_Art_Size = 0;
       break;
      case 'h':
      case '?':
      default:
        errflag ++;
      }
    if (errflag > 0) {
       innbbsusage(argv[0]);
       return(1);
    }
    if (argc - optind >= 1) {
      port = argv[optind];
    }
    if (argc - optind >= 2) {
      path = argv[optind+1];
    }

    standaloneinit(port);

    initial_bbs("feed");
    
/* Kaede
    if (!inetdstart)
      fprintf(stderr, "Try to listen in port %s and path %s\n", port, path);
*/
    HISmaint();
    HISsetup();
    installinnbbsd();
    sethaltfunction(INNBBSDhalt);

    signal(SIGPIPE, dopipesig);
    inndchannel(port, path);
    HISclose();
}