/*
 * ----------------------------------------------------------------
 * Night Light IRC Proxy - Main Functions
 * ----------------------------------------------------------------
 * Copyright (C) 1997-2007 Jonas Kvinge <jonas@night-light.net>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Last modified by:
 * Jonas Kvinge (27.11.2007)
 *
 */

#define MAIN_C

#define NEED_SYS_TYPES_H 1		/* Extra types */
#define NEED_SYS_PARAM_H 1		/* Some systems need this */
#define NEED_LIMITS_H 0			/* Kernel limits */
#define NEED_STDARG_H 1			/* va_list, etc */
#define NEED_ERRNO_H 1			/* errno */
#define NEED_CTYPE_H 0			/* isdigit(), etc */
#define NEED_NETINET_IN_H 1		/* in_addr, sockaddr_in, etc */
#define NEED_ARPA_INET_H 0		/* inet_ntoa(), inet_aton(), etc */
#define NEED_STDIO_H 1			/* Standard C UNIX functions */
#define NEED_STDLIB_H 1			/* malloc(), exit(), atoi(), etc */
#define NEED_TIME_H 1			/* time(), etc */
#define NEED_SYSCTL_H 0			/* sysctl(), etc */
#define NEED_SYS_STAT_H 0		/* chmod(), mkdir(), etc */
#define NEED_SYS_UIO_H 0		/* iovec, etc */
#define NEED_FCNTL_H 1			/* open(), creat(), fcntl(), etc */
#define NEED_SYS_IOCTL_H 0		/* ioctl(), etc */
#define NEED_SYS_FILIO_H 0		/* Solaris need this for ioctl(), etc */
#define NEED_UNISTD_H 1			/* Unix standard functions */
#define NEED_STRING_H 1			/* C string functions */
#define NEED_SIGNAL_H 0			/* Signal functions */
#define NEED_SYS_SOCKET_H 0		/* Socket functions */
#define NEED_NETDB_H 0			/* Network database functions */
#define NEED_ARPA_NAMESER_H 0		/* Nameserver definitions */
#define NEED_GETUSERPW_HEADERS 0 	/* Functions to retrive system passwords */
#define NEED_ARES 1			/* Functions needed for ares */
#define NEED_SSL 1			/* Needed for SSL support */

#include "includes.h"
#include "irc.h"

#if MEMDEBUG
  #include "memlist.h"
#endif
#include "main.h"

#include "conf.h"
#if SSL_SUPPORT
  #include "ssl.h"
#endif

#include "access_conf.h"
#include "listen_conf.h"
#include "user_conf.h"
#include "conn_conf.h"
#include "autoop_conf.h"
#include "listen.h"
#include "listen_io.h"
#include "client.h"
#include "client_io.h"
#include "client_connection.h"
#include "client_handle.h"
#include "client_notice.h"
#include "conn.h"
#include "conn_io.h"
#include "conn_connection.h"

#if IDENTD
  #include "ident_conf.h"
  #include "ident_listen.h"
  #include "ident_conn.h"
#endif

/* VARIABLES - JONAS (29.07.2001) */

unsigned long int ScreenBitmask =
  (
  BITMASK_MAIN
  |
  BITMASK_ERROR
  |
  BITMASK_EXTRA
#if DEBUG
  |
  BITMASK_DEBUG
#define SCREENMODE 1
#endif
);

char *STDIN_RecvBufferPT = NULL;

#if ARES
  ares_channel Ares_Channel = { 0 };
#endif

extern unsigned short int ScreenMode;
extern unsigned short int ScreenPrint;
extern unsigned short int Run;
extern unsigned short int Exit;

extern time_t ExitTime;
extern time_t FlushTime;

extern struct Conf_Struct ConfS;
extern struct Listen_Struct *Listen_Head;
extern struct Client_Struct *Client_Head;
extern struct Conn_Struct *Conn_Head;

#if IDENTD
  struct IdentListen_Struct *IdentListen_Head;
#endif

struct PrintFile_Struct PrintFileS[] = {

  {
    "/dev/null",
    0,
    NULL
  },
  {
    MAINLOGFILE,
    (BITMASK_MAIN|BITMASK_ERROR
#if EXTRAINFO
|BITMASK_EXTRA
#endif
),
    NULL
  },
  {
    ERRORLOGFILE,
    BITMASK_ERROR,
    NULL
  },
#if DEBUG
  {
    DEBUGLOGFILE,
    (BITMASK_MAIN|BITMASK_ERROR|BITMASK_DEBUG),
    NULL
  }
#endif

};
unsigned short int PrintFileS_Len = (sizeof(PrintFileS) / sizeof(*PrintFileS));

/* MAIN FUNCTION - JONAS (30.07.2001) */

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

  signed char Char = 0;
#if SCREENMODE
  unsigned short int Fork = FALSE;
#else
  unsigned short int Fork = TRUE;
#endif
 unsigned short int Term = FALSE;
 unsigned short int Rehash = FALSE;

#if FDDEBUG
  socktable_add(0, __FILE__, __LINE__, __FUNCTION__);
  socktable_add(1, __FILE__, __LINE__, __FUNCTION__);
  socktable_add(2, __FILE__, __LINE__, __FUNCTION__);
  fdtable_add(stdin, __FILE__, __LINE__, __FUNCTION__, "0", "0");
  fdtable_add(stdout, __FILE__, __LINE__, __FUNCTION__, "0", "0");
  fdtable_add(stderr, __FILE__, __LINE__, __FUNCTION__, "0", "0");
#endif

  sysinit();

  while (Char != ERROR) {
    Char = getopt(argc, argv, "vfrtc:");
    switch(Char) {
      case 'v':
        printf("%s v%s\n", PACKAGE, VERSION);
        printf("%s\n", COPYRIGHT);
        exit(0);
      case 'c':
        if (optarg != NULL) ConfS.File = strdup(optarg);
        printf("Using configuration file: %s\n", ConfS.File);
        break;
      case 'f':
        Fork = FALSE;
        break;
      case 'r':
        Rehash = TRUE;
        break;
      case 't':
        Term = TRUE;
        break;
      case '?':
        printf("%s v%s\n", PACKAGE, VERSION);
        printf("Syntax: %s [-vfcrt] <configuration file>\n", argv[0]);
        printf("v = Print version\n");
        printf("f = Fullscreen mode\n");
        printf("c = Specify configuration file\n");
        printf("r = Rehash program\n");
        printf("t = Terminate program\n");
        exit(0);
      default:
        break;
    }
  }

  conf_read();

  if (Rehash == TRUE) { sysrehash(); exit(0); }
  if (Term == TRUE) { systerm(); exit(0); }

  PrintFileS[0].Name = strdup("/dev/null");
  PrintFileS[1].Name = strdup(ConfS.MainLogFile);
  PrintFileS[2].Name = strdup(ConfS.ErrorLogFile);
#if DEBUG
  PrintFileS[3].Name = strdup(ConfS.DebugLogFile);
#endif

  sysinitfiles();
  sysopenfiles_fg();

#if !WIN32
  sysinitid();
#endif

#if !WIN32
  syscheckpid();
  syswritepid();
  if (Fork == TRUE) {
    sysfork();
  }
#endif

  syssetsignals();

  sysprint(BITMASK_MAIN, STARTUPLN1);
  sysprint(BITMASK_MAIN, STARTUPLN2);

#if ARES
  ares_init(&Ares_Channel);
#endif

#if SSL_SUPPORT
  if (ConfS.SSLSupport == TRUE) { ssl_init(); }
#endif

  main_read();
  main_loop();

  assert(FALSE);

  return(SUCCESS);

}

/* MAIN_READ FUNCTION - JONAS (25.06.2000) */

void main_read(void) {

  access_conf_read();
  listen_conf_read();
  user_conf_read();
  conn_conf_read();
#if IDENTD
  ident_conf_read();
#endif
  autoop_conf_read();

}

/* MAIN_WRITE FUNCTION - JONAS (25.06.2000) */

void main_write(void) {
}

/* MAIN_REHASH FUNCTION - JONAS (25.06.2000) */

void main_rehash(void) {

  struct Listen_Struct *ListenS = NULL;
  struct Client_Struct *ClientS = NULL;
  struct Conn_Struct *ConnS = NULL;
  struct ConnServer_Struct *ConnServer = NULL;
  struct ConnServer_Struct *ConnServer_DEL = NULL;

  struct ListenConf_Struct *ListenConf = NULL;
  struct UserConf_Struct *UserConf = NULL;
  struct ConnConf_Struct *ConnConf = NULL;
  struct ConnConfServer_Struct *ConnConfServer = NULL;
#if IDENTD
  struct IdentConf_Struct *IdentConfS = NULL;
  struct IdentListen_Struct *IdentListenS = NULL;
#endif

  main_read();

  for (ListenS = Listen_Head ; ListenS != NULL ; ListenS = ListenS->Next) {
    ListenS->Time = 0;
    ListenConf = listen_conf_get(ListenS->Host, ListenS->PortH, ((Listen_IsIPv6(ListenS)) ? TRUE : FALSE), ((Listen_IsSSL(ListenS)) ? TRUE : FALSE));
    if (ListenConf == NULL) {
      listen_stop(ListenS);
      Listen_SetRemove(ListenS);
      continue;
    }
  }

  for (ClientS = Client_Head ; ClientS != NULL ; ClientS = ClientS->Next) {
    UserConf = user_conf_get(ClientS->User);
    if (UserConf == NULL) { client_close(ClientS, "User no longer exist after rehashing configuration."); }
  }

  for (ConnS = Conn_Head ; ConnS != NULL ; ConnS = ConnS->Next) {
    ConnConf = conn_conf_get(ConnS->Name);
    if (ConnConf == NULL) {
      if (Conn_IsSocket(ConnS)) {
        if (Conn_IsWelcome(ConnS)) { conn_quit(ConnS, "Connection %s removed on configuration rehash.", ConnS->Name); }
        else { conn_disconnect(ConnS, "Connection %s removed on configuration rehash.", ConnS->Name); }
      }
      Conn_SetRemove(ConnS);
    }
    else {
      if (strcmp(ConnS->User, ConnConf->User) != FALSE) {
        for (ClientS = Client_Head ; ClientS != NULL ; ClientS = ClientS->Next) {
          if (ClientS->ConnS == ConnS) {
            client_notice(ClientS, "You are automatically detached from %s: New owner of connection after rehashing configuration.", ConnS->Name);
            client_detach(ClientS);
          }
        }
        ConnS->User = strrealloc(ConnS->User, ConnConf->User);
      }
      ConnS->Host = strrealloc(ConnS->Host, ConnConf->Host);
      ConnS->Nick = strrealloc(ConnS->Nick, ConnConf->Nick);
      ConnS->AwayNick = strrealloc(ConnS->AwayNick, ConnConf->AwayNick);
      ConnS->Mode = strrealloc(ConnS->Mode, ConnConf->Mode);
      ConnS->Info = strrealloc(ConnS->Info, ConnConf->Info);
      ConnS->Chans = strrealloc(ConnS->Chans, ConnConf->Chans);
      ConnS->ConfFlags = ConnConf->ConfFlags;
      ConnS->MaxClients = ConnConf->MaxClients;
      ConnS->SendMaxLines = ConnConf->SendMaxLines;
      ConnS->SendLineTime = ConnConf->SendLineTime;
      ConnS->SendMaxBuffer = ConnConf->SendMaxBuffer;
      ConnS->SendBufferTime = ConnConf->SendBufferTime;
      ConnS->AwayMsg = strrealloc(ConnS->AwayMsg, ConnConf->AwayMsg);
      ConnS->PublicDetachMsg = strrealloc(ConnS->PublicDetachMsg, ConnConf->PublicDetachMsg);
      ConnS->PublicAttachMsg = strrealloc(ConnS->PublicAttachMsg, ConnConf->PublicAttachMsg);
      ConnS->NickServNUH = strrealloc(ConnS->NickServNUH, ConnConf->NickServNUH);
      ConnS->NickServPass = strrealloc(ConnS->NickServPass, ConnConf->NickServPass);

      for (ConnServer = ConnS->Server_Head ; ConnServer != NULL ;) {
        ConnConfServer = conn_conf_getserver(ConnConf, ConnServer->Host);
        if (ConnConfServer == NULL) {
          ConnServer_DEL = ConnServer;
          ConnServer = ConnServer->Next;
          conn_remserver(ConnS, ConnServer_DEL);
          continue;
        }
        else {
          ConnServer->Host = strrealloc(ConnServer->Host, ConnConfServer->Host);
          ConnServer->Port = ConnConfServer->Port;
          ConnServer = ConnServer->Next;
          continue;
        }
      }

    }
  }
#if IDENTD
  for (IdentListenS = IdentListen_Head ; IdentListenS != NULL ; IdentListenS = IdentListenS->Next) {
    IdentConfS = ident_conf_get(IdentListenS->Host, IdentListenS->PortH);
    if (IdentConfS == NULL) {
      ident_listen_stop(IdentListenS);
      IdentListen_SetRemove(IdentListenS);
      continue;
    }
  }
#endif

}

/* MAIN_LOOP FUNCTION - JONAS (29.07.2001) */

void main_loop(void) {

  signed long int Result = 0;
  unsigned long int FDS = 0;
  time_t Duration = 0;
  fd_set ReadFDS;
  fd_set WriteFDS;
  struct timeval TV = { 0 };

  FOREVERLOOP {

    NOW = time(NULL);
    Duration = (NOW - FlushTime);
    if (Duration >= FLUSHTIME) { sysflushfiles(); }

    assert(geteuid() != 0);

    if (Exit == TRUE) { TV.tv_sec = 1; }
    else { TV.tv_sec = NLPRGSEC; }
    TV.tv_usec = NLPRGUSEC;

    FD_ZERO(&ReadFDS);
    FD_ZERO(&WriteFDS);

    if (ScreenMode == TRUE) { FD_SET(STDIN_FILENO, &ReadFDS); }
    if (Run == TRUE) {
      #if ARES
        ares_fds(Ares_Channel, &ReadFDS, &WriteFDS);
      #endif
      listen_fds(&ReadFDS, &WriteFDS, &FDS);
      client_fds(&ReadFDS, &WriteFDS, &FDS);
      conn_fds(&ReadFDS, &WriteFDS, &FDS);
      #if IDENTD
        ident_listen_fds(&ReadFDS, &WriteFDS, &FDS);
        ident_conn_fds(&ReadFDS, &WriteFDS, &FDS);
      #endif
    }

    Result = select(FD_SETSIZE, &ReadFDS, &WriteFDS, NULL, &TV);
    if (Result <= ERROR) {
      if (errno == EINTR) { continue; }
      sysprint(BITMASK_ERROR, "select() I/O error: [%d]: %s", errno, strerror(errno));
      exit(1);
    }
    FDS = Result;
    NOW = time(NULL);

    if (ScreenMode == TRUE) { main_io(&ReadFDS, &WriteFDS, &FDS); }
    if (Run == TRUE) {
      #if ARES
        ares_process(Ares_Channel, &ReadFDS, &WriteFDS);
      #endif
      listen_io(&ReadFDS, &WriteFDS, &FDS);
      client_io(&ReadFDS, &WriteFDS, &FDS);
      conn_io(&ReadFDS, &WriteFDS, &FDS);
      #if IDENTD
        ident_listen_io(&ReadFDS, &WriteFDS, &FDS);
        ident_conn_io(&ReadFDS, &WriteFDS, &FDS);
      #endif
    }
#if 0 /* Doesn't work with C-Ares */
    assert(FDS == 0);
#endif

    if (Exit == TRUE) { main_exit("Exit."); }

  }

}

/* MAIN_IO FUNCTION - JONAS (01.03.2000) */

static void main_io(fd_set *ReadFDS, fd_set *WriteFDS, unsigned long int *FDS) {

  signed long int Result = 0;
  unsigned long int Len = 0;
  unsigned long int OldLen = 0;
  unsigned long int NewLen = 0;
  char RecvBuffer[RECVBUFFERLEN+1] = "";
  char *RecvBufferPT = NULL;
  char *TempPT = NULL;

  if (!FD_ISSET(STDIN_FILENO, ReadFDS)) { return; }
  *FDS = *FDS - 1;

  do {
    memset(&RecvBuffer, 0, RECVBUFFERLEN+1);
    Result = read(STDIN_FILENO, RecvBuffer, RECVBUFFERLEN);
    if (Result <= ERROR) {
      if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR)) { break; }
      sysprint(BITMASK_ERROR, "Read error to stdin: [%d]: %s", errno, strerror(errno));
      exit(1);
    }
    Len = Result;
    if (Len == 0) {
      sysprint(BITMASK_ERROR, "EOF to stdin.");
      exit(1);
    }
    if (Len <= 1) { return; }
    assert(Len == strlen(RecvBuffer));
    if (RecvBufferPT == NULL) { OldLen = 0; }
    else { OldLen = strlen(RecvBufferPT); }
    NewLen = OldLen + Len + 1;
    TempPT = realloc(RecvBufferPT, NewLen);
    if (TempPT == NULL) { exit(1); }
    RecvBufferPT = TempPT;
    TempPT += OldLen;
    strcpy(TempPT, RecvBuffer);
  }
  while (Len >= RECVBUFFERLEN);

  STDIN_RecvBufferPT = RecvBufferPT;
  main_parse();

}

/* MAIN_PARSE FUNCTION - JONAS (01.07.2000) */

static void main_parse(void) {

  char *BufferPT = NULL;
  char *LinePT = NULL;
  char *DumbPT = NULL;
  unsigned long int Count = 0;
  unsigned long int Len = 0;

  assert(STDIN_RecvBufferPT != NULL);

  BufferPT = STDIN_RecvBufferPT;

  for (Count = 1 ; BufferPT != NULL ; Count++) {
    DumbPT = strchr(BufferPT, '\n');
    if (DumbPT == NULL) {
      if (Count == 1) { return; }
      BufferPT = strtok(BufferPT, "\0");
      if (BufferPT != NULL) {
        Len = strlen(BufferPT) + 1;
        strcpy(STDIN_RecvBufferPT, BufferPT);
        DumbPT = realloc(STDIN_RecvBufferPT, Len);
        if (DumbPT == NULL) { exit(1); }
        STDIN_RecvBufferPT = DumbPT;
        return;
      }
      FREE(STDIN_RecvBufferPT);
      return;
    }
    LinePT = strtok(BufferPT, "\n");
    BufferPT = strtok(NULL, "\0");
    if (LinePT == NULL) { continue; }
    Len = strlen(LinePT);
    if (LinePT[Len-1] == '\r') { LinePT[Len-1] = 0; }

    if (strcasecmp(LinePT, "START") == FALSE) {
      if (Run == TRUE) {
        printf("I/O loop already started.\n");
        continue;
      }
      Run = TRUE;
      printf("I/O loop started.\n");
    }
    else if (strcasecmp(LinePT, "STOP") == FALSE) {
      if (Run == FALSE) {
        printf("I/O loop already stopped.\n");
        continue;
      }
      Run = FALSE;
      printf("I/O loop stopped.\n");
    }
#if !WIN32
    else if (strcasecmp(LinePT, "FORK") == FALSE) {
      printf("Going into the background...\n");
      sysfork();
      continue;
    }
#endif
    else if (strcasecmp(LinePT, "REHASH") == FALSE) {
      printf("Rehashing configuration...\n");
      main_rehash();
      continue;
    }
    else if (strcasecmp(LinePT, "EXIT") == FALSE) {
      main_exit("EXIT from console.");
      continue;
    }
    else if (strcasecmp(LinePT, "EXITNOW") == FALSE) {
      exit(0);
    }
#if MEMDEBUG
    else if (strcasecmp(LinePT, "MEMLIST") == FALSE) {
      mem_list();
      continue;
    }
    else if (strcasecmp(LinePT, "MEMLEAKS") == FALSE) {
      mem_leaks();
      continue;
    }
#endif
#if FDDEBUG
    else if (strcasecmp(LinePT, "FDPRINT") == FALSE) {
      fd_print();
      continue;
    }
#endif
    else if (strcasecmp(LinePT, "PRINT1") == FALSE) {
      sysprint(BITMASK_MAIN, "This is a test of MAIN print.");
      continue;
    }
    else if (strcasecmp(LinePT, "PRINT2") == FALSE) {
      sysprint(BITMASK_ERROR, "This is a test of ERROR print.");
      continue;
    }
    else if (strcasecmp(LinePT, "PRINT3") == FALSE) {
      continue;
    }
    else {
      printf("Unknown command.\n");
      continue;
    }
    if (STDIN_RecvBufferPT == NULL) { break; }
  }

  FREE(STDIN_RecvBufferPT);

}

/* MAIN_EXIT FUNCTION - JONAS (01.07.2000) */

void main_exit(const char *const MessagePT, ...) {

  char Message[LINELEN+1] = "";
  va_list Args = { 0 };
  unsigned short int Count = 0;
  time_t Duration = 0;

  va_start(Args, MessagePT);
  vsnprintf(Message, LINELEN+1, MessagePT, Args);
  va_end(Args);

  if (Exit == FALSE) {
    DEBUGPRINT(BITMASK_DEBUG_MAIN, "Shutting down: %s", Message);
    client_noticeall("Shutting down: %s", Message);
    Exit = TRUE;
    ExitTime = NOW;
  }

  Count += client_closeall("%s", Message);
  Count += conn_closeall("%s", Message);
  Count += listen_closeall("%s", Message);
#if IDENTD
  Count += ident_listen_closeall("%s", Message);
  Count += ident_conn_closeall();
#endif

#if ARES && !HAVE_ARES_CANCELQUERY && HAVE_ARES_CANCEL
  ares_cancel(Ares_Channel);
#endif

  Duration = (NOW - ExitTime);
  if ((Count == 0) || (Exit >= EXITTTL)) {
    access_conf_destroy();
    listen_conf_destroy();
    user_conf_destroy();
    conn_conf_destroy();
#if IDENTD
    ident_conf_destroy();
#endif
    autoop_conf_destroy();
    conf_destroy();
#if ARES
    ares_destroy(Ares_Channel);
#endif
#if SSL_SUPPORT
    ssl_cleanup();
#endif
    irc_cleanup();
    strcleanup();
    syscleanup();

#if FDDEBUG
    fd_print();
    fdtable_clear();
    socktable_clear();
#endif

#if MEMDEBUG
    mem_list();
    mem_leaks();
#endif
    exit(0);
  }

}



syntax highlighted by Code2HTML, v. 0.9.1