/* -*- c -*-
 *
 * Author:      James Brister <brister@vix.com> -- berkeley-unix --
 * Start Date:  Thu Dec 28 13:15:04 1995
 * Project:     INN (innfeed)
 * File:        innlistener.c
 * RCSId:       $Id: innlistener.c,v 1.18 1996/08/20 14:07:47 brister Exp $
 *
 * Copyright:   Copyright (c) 1996 by Internet Software Consortium
 *
 *              Permission to use, copy, modify, and distribute this
 *              software for any purpose with or without fee is hereby
 *              granted, provided that the above copyright notice and this
 *              permission notice appear in all copies.
 *
 *              THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE
 *              CONSORTIUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 *              SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *              MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET
 *              SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 *              INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *              WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 *              WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 *              TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 *              USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Description: Implementation of the InnListener class.
 * 
 */

#if ! defined (lint)
static const char *rcsid = "$Id: innlistener.c,v 1.18 1996/08/20 14:07:47 brister Exp $" ;
static void use_rcsid (const char *rid) {   /* Never called */
  use_rcsid (rcsid) ; use_rcsid (rid) ;
}
#endif

#include "config.h"


#if defined (DO_HAVE_UNISTD)
#include <unistd.h>
#endif

#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>

#include "innlistener.h"
#include "endpoint.h"
#include "host.h"
#include "buffer.h"
#include "article.h"
#include "msgs.h"

#define LISTENER_INPUT_BUFFER 2048 /* size of the input buffer */


struct innlistener_s 
{
    EndPoint myep ;

    Host *myHosts ;
    size_t hostLen ;
    Buffer inputBuffer ;
    bool dummyListener ;
    bool dynamicPeers ;

    InnListener next ;
};

static u_int listenerCount = 0 ;
static InnListener listenerList = NULL ;

InnListener mainListener ;



static void giveArticleToPeer (InnListener lis,
                               Article article, const char *peerName) ;
static void newArticleCommand (EndPoint ep, IoStatus i,
                               Buffer *buffs, void *data) ;
static void logBadPeer (const char *peer) ;
static bool isBadPeer (const char *peer) ;
static void rememberBadPeer (const char *peer) ;



InnListener newListener (EndPoint endp, bool isDummy, bool dynamicPeers)
{
  InnListener l = CALLOC (struct innlistener_s, 1) ;
  Buffer *readArray ;

  ASSERT (l != NULL) ;

  l->myep = endp ;

  l->hostLen = MAX_HOSTS ;
  l->myHosts = CALLOC (Host, l->hostLen) ;
  ASSERT (l->myHosts != NULL) ;

  l->inputBuffer = newBuffer (LISTENER_INPUT_BUFFER) ;
  l->dummyListener = isDummy ;
  l->dynamicPeers = dynamicPeers ;

  readArray = makeBufferArray (bufferTakeRef (l->inputBuffer), NULL) ;
  prepareRead (endp,readArray,newArticleCommand,l,1) ;

  l->next = listenerList ;
  listenerList = l ;

  listenerCount++ ;
  
  return l ;
}

void gPrintListenerInfo (FILE *fp, u_int indentAmt)
{
  InnListener p ;
  char indent [INDENT_BUFFER_SIZE] ;
  u_int i ;
  
  for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
    indent [i] = ' ' ;
  indent [i] = '\0' ;

  fprintf (fp,"%sGlobal InnListener list : %p (count %d) {\n",
           indent,listenerList,listenerCount) ;
  for (p = listenerList ; p != NULL ; p = p->next)
    printListenerInfo (p,fp,indentAmt + INDENT_INCR) ;
  fprintf (fp,"%s}\n",indent) ;
}



void printListenerInfo (InnListener listener, FILE *fp, u_int indentAmt)
{
  char indent [INDENT_BUFFER_SIZE] ;
  u_int i ;
  
  for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
    indent [i] = ' ' ;
  indent [i] = '\0' ;

  fprintf (fp,"%sInnListener : %p {\n",indent,listener) ;
  fprintf (fp,"%s    endpoint : %p\n", indent, listener->myep) ;
  fprintf (fp,"%s    dummy-listener : %s\n",indent,
           boolToString (listener->dummyListener)) ;
  fprintf (fp,"%s    dynamicPeers : %s\n",indent,
           boolToString (listener->dynamicPeers)) ;

  fprintf (fp,"%s    input-buffer {\n",indent) ;
  printBufferInfo (listener->inputBuffer,fp,indentAmt + INDENT_INCR) ;
  fprintf (fp,"%s    }\n",indent) ;

  fprintf (fp,"%s    hosts {\n",indent) ;
  for (i = 0 ; i < listener->hostLen ; i++)
    {
#if 0
      if (listener->myHosts [i] != NULL)
        printHostInfo (listener->myHosts [i],fp,indentAmt + INDENT_INCR) ;
#else
      fprintf (fp,"%s        %p\n",indent,listener->myHosts[i]) ;
#endif
    }
  
  fprintf (fp,"%s    }\n",indent) ;
  
  fprintf (fp,"%s}\n",indent) ;
}


  /* Close down all hosts on this listener. When they're all gone the
     Listener will be deleted. */
void shutDown (InnListener l)
{
  u_int i ;

  dprintf (1,"Shutting down the listener\n") ;
  
  if (l->myep != NULL)
    delEndPoint (l->myep) ;
  l->myep = NULL ;
  
  for (i = 0 ; i < l->hostLen ; i++)
    if (l->myHosts [i] != NULL)
      hostClose (l->myHosts[i]) ;
}


bool listenerAddPeer (InnListener listener, Host hostObj)
{
  u_int i ;

  dprintf (1,"Adding peer: %s\n", hostPeerName (hostObj)) ;
  
  for (i = 0 ; i < listener->hostLen ; i++)
    {
      if (listener->myHosts [i] == NULL)
        {
          listener->myHosts [i] = hostObj ;

          return true ;
        }
    }

  return false ;
}


/* return true if this listener doesn't ever generate articles. */
bool listenerIsDummy (InnListener listener)
{
  return listener->dummyListener ;
}

  /* Called by the Host when it (the Host) is about to delete itself. */
void listenerHostGone (InnListener listener, Host host)
{
  u_int i ;
  bool someThere = false ;

  dprintf (1,"Host is gone: %s\n", hostPeerName (host)) ;
  
  for (i = 0 ; i < listener->hostLen ; i++)
    if (listener->myHosts [i] == host)
      listener->myHosts [i] = NULL ;
    else if (listener->myHosts [i] != NULL)
      someThere = true ;

  if (!someThere)
    {
      time_t now = theTime () ;
      char dateString [30] ;

      strcpy (dateString,ctime (&now)) ;
      dateString [24] = '\0' ;
      
      syslog (LOG_NOTICE,STOPPING_PROGRAM,dateString) ;
      exit (0) ;
    }
}


/* called by the Host when it has nothing to do. */
void listenerHostIsIdle (InnListener listener, Host host)
{
  ASSERT (listener != NULL) ;
  ASSERT (host != NULL) ;

  dprintf (1,"Host is idle: %s\n", hostPeerName (host)) ;
  
  if (!listener->dummyListener)
    return ;

  /* if this listener is a dummy (i.e. not generating articles cause we're
     just dealing with backlog files) then forget about the host and when
     last one is gone we exit. */

  hostClose (host) ;
}


/**********************************************************************/
/**                     STATIC PRIVATE FUNCTIONS                     **/
/**********************************************************************/


/* EndPoint callback function for when the InnListener's fd is ready for
   reading. */
static void newArticleCommand (EndPoint ep, IoStatus i,
                               Buffer *buffs, void *data)
{
  InnListener lis = (InnListener) data ;
  char *msgid, *msgidEnd ;
  char *fileName, *fileNameEnd ;
  char *peer, *peerEnd ;
  char *cmd, *endc ;
  char *bbase = bufferBase (buffs [0]) ;
  size_t blen = bufferDataSize (buffs [0]) ;
  Buffer *readArray ;

  ASSERT (ep = lis->myep) ;

  bufferAddNullByte  (buffs [0]) ;
  
  if (i == IoEOF || i == IoFailed)
    {
      if (i == IoEOF)
        {
          dprintf (1,"Got EOF on listener\n") ;
          syslog (LOG_NOTICE,INN_GONE) ;
        }
      else
        {
          errno = endPointErrno (ep) ;
          syslog (LOG_ERR,INN_IO_ERROR) ;
          dprintf (1,"Got IO Error on listener\n") ;
        }

      shutDown (lis) ;
    }
  else if (strchr (bbase, '\n') == NULL) /* partial read */
    {
      expandBuffer (buffs [0], BUFFER_EXPAND_AMOUNT) ;
      readArray = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
      prepareRead (ep, readArray, newArticleCommand, data, 1) ;
    }
  else
    {
      /* now iterate over each full command we got on the input. */
      cmd = bbase ;
      while ((cmd < (bbase + blen)) && ((endc = strchr (cmd,'\n')) != NULL))
        {
          Article article ;
          char *next = endc + 1;

          if (*next == '\r')
            next++ ;

          endc-- ;
          if (*endc != '\r')
            endc++ ;

          *endc = '\0' ;
          
          dprintf (2,"INN Command: %s\n", cmd) ;

          /* pick out the leading string (the filename) */
          if ((fileName = findNonBlankString (cmd,&fileNameEnd)) == NULL)
            {
              syslog (LOG_ERR,INN_BAD_CMD,cmd) ;
              shutDown (lis) ;

              return ;
            }
          
          *fileNameEnd = '\0' ; /* for the benefit of newArticle() */

          /* now pick out the next string (the message id) */
          if ((msgid = findNonBlankString (fileNameEnd + 1,&msgidEnd)) == NULL)
            {
              *fileNameEnd = ' ' ; /* to make syslog work properly */
              syslog (LOG_ERR,INN_BAD_CMD,cmd) ;
              shutDown (lis) ;

              return ;
            }

          *msgidEnd = '\0' ;    /* for the benefit of newArticle() */
          
          /* now create an article object and give it all the peers on the
             rest of the command line. Will return null if file is missing. */
          article = newArticle (fileName, msgid) ;
          *fileNameEnd = ' ' ;
          *msgidEnd = ' ' ;

          /* now get all the peernames off the rest of the command lines */
          peerEnd = msgidEnd ;
          do 
            {
              *peerEnd = ' ' ;

              /* pick out the next peer name */
              if ((peer = findNonBlankString (peerEnd + 1,&peerEnd))==NULL)
                break ;     /* even no peer names is OK. */ /* XXX REALLY? */

              *peerEnd = '\0' ;
              
              if (article != NULL)
                giveArticleToPeer (lis,article,peer) ;
            }
          while (peerEnd < endc) ;

          delArticle (article) ;
          
          cmd = next ;
        }


      if (*cmd != '\0')         /* partial command left in buffer */
        {
          Buffer *bArr ;
          u_int leftAmt = blen - (cmd - bbase) ;

          /* first we shift whats left in the buffer down to the bottom */
          if (cmd != bbase)
            {
              memcpy (bbase,cmd,leftAmt) ;
              bufferSetDataSize (buffs [0],leftAmt) ;
            }
          else if ( !expandBuffer (buffs[0],BUFFER_EXPAND_AMOUNT) )
            {
              syslog (LOG_ERR,L_BUFFER_EXPAND_ERROR);

              shutDown (lis) ;

              return ;
            }
      
          bArr = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
      
          if ( !prepareRead (lis->myep, bArr, newArticleCommand, lis, 1) )
            {
              syslog (LOG_ERR,L_PREPARE_READ_FAILED) ;

              freeBufferArray (bArr) ;
              
              shutDown (lis) ;

              return ;
            }
        }
      else if ( !readIsPending (lis->myep) ) 
        {                       /* XXX read should never be pending here */
          Buffer *bArr = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
      
          bufferSetDataSize (buffs [0],0) ;
      
          if ( !prepareRead (lis->myep, bArr, newArticleCommand, lis, 1) )
            {
              syslog (LOG_ERR,L_PREPARE_READ_FAILED) ;

              shutDown (lis) ;

              return ;
            }
        }
    }

  freeBufferArray (buffs) ;
}


/*
 * Log the name of the bad peer. One time only.
 */
static char **peers ;
static u_int peerLen ;
static u_int peerIdx ;

static void rememberBadPeer (const char *peer)
{
  u_int i ;

  if (isBadPeer (peer))         /* check if we already logged it. */
    return ;
  
  if (i == peerIdx && peerIdx == peerLen)
    {
      peerLen += 10 ;
      if (peers != NULL)
        peers = REALLOC (peers, char *, peerLen) ;
      else
        peers = ALLOC (char *, peerLen) ;
    }

  if (i == peerIdx)
    peers [peerIdx++] = strdup (peer) ;
}

static void logBadPeer (const char *peer)
{
  if (isBadPeer (peer))
    return ;
  
  rememberBadPeer (peer) ;
  syslog (LOG_ERR,UNKNOWN_PEER,peer) ;
}


static bool isBadPeer (const char *peer)
{
  bool rval = true ;
  int i ;
  
  for (i = 0 ; i < peerIdx ; i++)
    if (strcmp (peer,peers [i]) == 0)
      rval = false ;

  return rval ;
}


/* Find the Host object for the peer and hand off a reference to the
   article for it to transmit. */
static void giveArticleToPeer (InnListener lis,
                               Article article, const char *peerName)
{
  u_int i ;

  for (i = 0 ; i < lis->hostLen ; i++)
    if (lis->myHosts[i] != NULL)
      if (strcmp (peerName,hostPeerName (lis->myHosts [i])) == 0)
        {
          dprintf (1,"Giving article to peer: %s\n", peerName) ;
          hostSendArticle (lis->myHosts [i],artTakeRef (article)) ;
          break ;
        }

  if (i == lis->hostLen)
    {
      dprintf (1,"Failed to give article to peer: -%s-\n", peerName) ;
      
      if (lis->dynamicPeers && !isBadPeer (peerName))
        {
          u_int articleTout ;
          u_int respTout ;
          u_int initialCxns ;
          u_int maxCxns ;
          u_int maxChecks ;
          bool streaming ;
          double lowFilter ;
          double highFilter ;
          u_short portNum ;
          Host newHostObj ;

          dprintf (1, "Adding peer dynamically\n") ;
          
          syslog (LOG_NOTICE,DYNAMIC_PEER,peerName) ;

          getHostDefaults (&articleTout, &respTout, &initialCxns,
                           &maxCxns, &maxChecks, &streaming,
                           &lowFilter, &highFilter, &portNum) ;

          newHostObj = newHost (lis, peerName, peerName,
                                articleTout, respTout, initialCxns,
                                maxCxns, maxChecks, portNum, CLOSE_PERIOD,
                                streaming, lowFilter, highFilter);

          if (newHostObj == NULL)
            {
              /* XXX I need to handle this better. */
              rememberBadPeer (peerName) ;
              syslog (LOG_ERR,NO_HOST,peerName) ;
            }
          else if ( !listenerAddPeer (lis, newHostObj) )
            {
              /* XXX need to remember we've gone over the limit and not try
                 to add any more. */
              rememberBadPeer (peerName) ;
              syslog (LOG_ERR, TOO_MANY_HOSTS, lis->hostLen) ;
            }
          else
            hostSendArticle (newHostObj,artTakeRef (article)) ;
        }
      else if ( !isBadPeer (peerName) )
        logBadPeer (peerName) ;
    }
}

