/*
 * ----------------------------------------------------------------
 * Night Light IRC Proxy - Client Handle 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 (15.11.2006)
 *
 */

#define CLIENT_HANDLE_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 1		/* 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 1		/* Socket functions */
#define NEED_NETDB_H 1			/* 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"

#include "conf.h"

#include "access_conf.h"
#include "conn_conf.h"

#include "listen.h"

#include "client.h"
#include "client_connection.h"
#include "client_handle.h"
#include "client_auth.h"
#include "client_notice.h"

#include "conn.h"
#include "conn_sendq.h"

#include "chan.h"
#include "chan_user.h"

/* VARIABLES - JONAS (31.07.2001) */

#if ARES
  extern ares_channel Ares_Channel;
#endif

extern struct Conf_Struct ConfS;

extern struct Client_Struct *Client_Head;
extern struct Client_Struct *Client_Tail;
extern unsigned long int G_Clients;

extern struct AccessConf_Struct *Allow_Head;
extern struct AccessConf_Struct *Deny_Head;

extern unsigned short int Access_CheckFirst;
extern unsigned short int Access_Default;

extern struct Conn_Struct *Conn_Head;

/* CLIENT_ACCESSCHECK FUNCTION - JONAS (31.07.2001) */

unsigned short int client_accesscheck(struct Client_Struct *ClientS) {

  struct AccessConf_Struct *AllowConf = NULL;
  struct AccessConf_Struct *DenyConf = NULL;

  for (AllowConf = Allow_Head ; AllowConf != NULL ; AllowConf = AllowConf->Next) {
    if ((ClientS->HostName != NULL) && (strwm(AllowConf->Host, ClientS->HostName) == TRUE)) { break; }
    if (strwm(AllowConf->Host, ClientS->HostIPS) == TRUE) { break; }
  }

  for (DenyConf = Deny_Head ; DenyConf != NULL ; DenyConf = DenyConf->Next) {
    if ((ClientS->HostName != NULL) && (strwm(DenyConf->Host, ClientS->HostName) == TRUE)) { break; }
    if (strwm(DenyConf->Host, ClientS->HostIPS) == TRUE) { break; }
  }

  if (Access_CheckFirst == ACCESS_ALLOW) {
    if (AllowConf != NULL) {
      DEBUGPRINT(BITMASK_DEBUG_CLIENT, "Allowing connection from %s(%s): Matching ALLOW rule: \"%s\".", ClientS->HostName, ClientS->HostIPS, AllowConf->Host);
      return(TRUE);
    }
    if (DenyConf != NULL) {
      DEBUGPRINT(BITMASK_DEBUG_CLIENT, "Refusing connection from %s(%s): Matching DENY rule: \"%s\".", ClientS->HostName, ClientS->HostIPS, DenyConf->Host);
      if (ConfS.ClientShowDenyReason == TRUE) { client_close(ClientS, "Connection denied: Your host is matching DENY rule \"%s\": %s", DenyConf->Host, DenyConf->Reason); }
      else { client_close(ClientS, "Connection denied from your host: %s", DenyConf->Reason); }
      return(FALSE);
    }
  }
  else {
    if (DenyConf != NULL) {
      DEBUGPRINT(BITMASK_DEBUG_CLIENT, "Refusing connection from %s(%s): Matching DENY rule: \"%s\".", ClientS->HostName, ClientS->HostIPS, DenyConf->Host);
if (ConfS.ClientShowDenyReason == TRUE) { client_close(ClientS, "Connection denied: Your host is matching DENY rule \"%s\": %s", DenyConf->Host, DenyConf->Reason); }
else { client_close(ClientS, "Connection denied from your host: %s", DenyConf->Reason); }
      return(FALSE);
    }
    if (AllowConf != NULL) {
      DEBUGPRINT(BITMASK_DEBUG_CLIENT, "Allowing connection from %s(%s): Matching ALLOW rule: \"%s\".", ClientS->HostName, ClientS->HostIPS, AllowConf->Host);
      return(TRUE);
    }
  }

  if (Access_Default == ACCESS_ALLOW) {
    DEBUGPRINT(BITMASK_DEBUG_CLIENT, "Allowing connection from %s(%s): No matching rule, default = ALLOW.", ClientS->HostName, ClientS->HostIPS);
    return(TRUE);
  }
  else {
    DEBUGPRINT(BITMASK_DEBUG_CLIENT, "Refusing connection from %s(%s): No matching rule, default = DENY.", ClientS->HostName, ClientS->HostIPS);
if (ConfS.ClientShowDenyReason == TRUE) { client_close(ClientS, "Connection denied: No matching rule for your host, defaulting to DENY."); }
else { client_close(ClientS, "Connection denied from your host: %s", ACCESS_CONF_NOREASON); }

    return(FALSE);
  }

}

/* CLIENT_WELCOME FUNCTION - JONAS (01.07.2000) */

void client_welcome(struct Client_Struct *ClientS) {

  struct Conn_Struct *P_ConnS = NULL;
  struct Client_Struct *P_ClientS = NULL;
  char Line1[LINELEN+1] = "";
  char Line2[LINELEN+1] = "";
  char *TempPT = NULL;
  char *ConnsPT = NULL;
  char *ClientsPT = NULL;
  unsigned short int ConnsCount = 0;
  unsigned short int UsersCount = 0;
  unsigned short int Len = 0;

  client_notice(ClientS, "Authenticated as %s!", ClientS->User);
  client_notice(ClientS, "Commands are: PCONNLIST, PATTACH, PDETACH, PCONNECT, PQUIT, READLOG and ERASELOG.");

  for (P_ConnS = Conn_Head , memset(&Line1, 0, LINELEN+1) , memset(&Line2, 0, LINELEN+1) ; P_ConnS != NULL ; P_ConnS = P_ConnS->Next) {

    if (strcmp(P_ConnS->User, ClientS->User) != FALSE) { continue; }

    ++ConnsCount;

    if (Conn_IsWelcome(P_ConnS)) { snprintf(Line1, LINELEN+1, "%s(Connected %s:%ld)", P_ConnS->Name, P_ConnS->ServerHostName, P_ConnS->ServerPortH); }
    else { snprintf(Line1, LINELEN+1, "%s(Disconnected)", P_ConnS->Name); }
    snprintf(Line2, LINELEN+1, ", %s", Line1);

    if (ConnsPT != NULL) {
      Len = strlen(ConnsPT) + strlen(Line2);
      if (Len > IRCNOTICELEN) {
        client_notice(ClientS, "%s.", ConnsPT);
        FREE(ConnsPT);
      }
    }

    if (ConnsPT == NULL) {
      TempPT = straddbuffer(ConnsPT, "Your IRC connections: ", IRCNOTICELEN, 0);
      if (TempPT == NULL) { break; }
      ConnsPT = TempPT;
      TempPT = straddbuffer(ConnsPT, Line1, IRCNOTICELEN, 0);
      if (TempPT == NULL) { FREE(ConnsPT); break; }
      ConnsPT = TempPT;
      continue;
    }

    TempPT = straddbuffer(ConnsPT, Line2, IRCNOTICELEN, 0);
    if (TempPT == NULL) { FREE(ConnsPT); break; }
    ConnsPT = TempPT;

  }
  if (ConnsPT != NULL) { client_notice(ClientS, "%s.", ConnsPT); FREE(ConnsPT); }
  if (ConnsCount <= 0) { client_notice(ClientS, "You have no connections."); }


  for (P_ClientS = Client_Head , memset(&Line1, 0, LINELEN+1) , memset(&Line2, 0, LINELEN+1) ; P_ClientS != NULL ; P_ClientS = P_ClientS->Next) {

    if (!Client_IsVerified(P_ClientS)) { continue; }
    if (strcmp(P_ClientS->User, ClientS->User) != FALSE) { continue; }

    ++UsersCount;

    if (P_ClientS->ConnS == NULL) { snprintf(Line1, LINELEN+1, "%s(%s) Detached", P_ClientS->HostName, P_ClientS->HostIPS); }
    else { snprintf(Line1, LINELEN+1, "%s(%s) Attached=%s", P_ClientS->HostName, P_ClientS->HostIPS, P_ClientS->ConnS->Name); }
    snprintf(Line2, LINELEN+1, ", %s", Line1);

    if (ClientsPT != NULL) {
      Len = strlen(ClientsPT) + strlen(Line2);
      if (Len > IRCNOTICELEN) {
        client_notice(ClientS, "%s.", ClientsPT);
        FREE(ClientsPT);
      }
    }

    if (ClientsPT == NULL) {
      TempPT = straddbuffer(ClientsPT, "Clients from your host: ", IRCNOTICELEN, 0);
      if (TempPT == NULL) { break; }
      ClientsPT = TempPT;
      TempPT = straddbuffer(ClientsPT, Line1, IRCNOTICELEN, 0);
      if (TempPT == NULL) { break; }
      ClientsPT = TempPT;
      continue;
    }

    TempPT = straddbuffer(ClientsPT, Line2, IRCNOTICELEN, 0);
    if (TempPT == NULL) { FREE(ClientsPT); break; }
    ClientsPT = TempPT;

  }
  if (ClientsPT != NULL) {
    client_notice(ClientS, "%s.", ClientsPT);
    FREE(ClientsPT);
  }

  client_addsend(ClientS, ":%s 422 * :No MOTD here.", HOSTNAME);

}

/* CLIENT_ATTACH FUNCTION - JONAS (01.07.2000) */

void client_attach(struct Client_Struct *ClientS, struct Conn_Struct *ConnS) {

  unsigned short int Count = 0;
  unsigned short int Users = 0;
  unsigned short int Index = 0;
  struct Chan_Struct *ChanS = NULL;
  struct ChanUser_Struct *ChanUser = NULL;
  char *NamesPT = NULL;
  unsigned short int NameIndex = 0;
  unsigned short int NameLen = 0;
  unsigned short int DumbLen = 0;
  unsigned short int UserLen = 0;
  unsigned short int NewLen = 0;
  char *DumbPT = NULL;

  if (ConnS->NumClients >= ConnS->MaxClients) { client_notice(ClientS, "Connection %s has %d attached clients, only %d clients are allowed.", ConnS->Name, ConnS->NumClients, ConnS->MaxClients); return; }

  if (!Conn_IsWelcome(ConnS)) {
    ClientS->ConnS = ConnS;
    ++ConnS->NumClients;
    client_notice(ClientS, "Attached on connection %s: Not connected.", ConnS->Name);
    client_noticeallbutone(ClientS, "Client %s (%s) attached on connection %s.", ClientS->HostName, ClientS->HostIPS, ConnS->Name);
    return;
  }
  if (ConnS->ServerName == NULL) { client_notice(ClientS, "Internal error."); return; }

  for (ChanS = ConnS->Chan_Head ; ChanS != NULL ; ChanS = ChanS->Next) {
    if (!Chan_IsNames(ChanS)) { client_notice(ClientS, "Please wait for %s/NAMES to complete.", ChanS->Chan); return; }
  }

  if (ConnS->NumClients <= 0) {
    if ((ConnConf_IsAutoAway(ConnS)) && (Conn_IsAway(ConnS))) { conn_addsendq(ConnS, "AWAY"); }
    if (ConnConf_IsPublicAway(ConnS)) {
      for (ChanS = ConnS->Chan_Head ; ChanS != NULL ; ChanS = ChanS->Next) { conn_addsendq(ConnS, "PRIVMSG %s :\1ACTION %s\1", ChanS->Chan, ConnS->PublicAttachMsg); }
    }
    if ((!Conn_IsSentNick(ConnS)) && (!Conn_IsSentAwayNick(ConnS)) && (Conn_IsAwayNick(ConnS)) && (strcasecmp(ConnS->IRCNick, ConnS->Nick) != FALSE) && (strcasecmp(ConnS->Nick, ConnS->AwayNick) != FALSE)) {
      Conn_SetSentNick(ConnS);
      ConnS->NicksIndex = 0;
      conn_addsendq(ConnS, "NICK %s", ConnS->Nick);
    }
  }

  for (Count = 0 ; Count <= 4 ; ++Count) {
    if (ConnS->Welcome[Count] != NULL) { client_addsend(ClientS, ":%s 00%d %s %s", ConnS->ServerName, Count, ConnS->IRCNick, ConnS->Welcome[Count]); }
  }
  for (Index = 0 ; Index < ConnS->ISupportLines ; ++Index) {
    if (ConnS->ISupport_String[Index] != NULL) { client_addsend(ClientS, ":%s 00%d %s %s", ConnS->ServerName, IRC_GENERIC_RPL_ISUPPORT, ConnS->IRCNick, ConnS->ISupport_String[Index]); }
  }

  for (ChanS = ConnS->Chan_Head ; ChanS != NULL ; ChanS = ChanS->Next) {
    assert(ChanS->Me != NULL);
    client_addsend(ClientS, ":%s JOIN :%s", ChanS->Me->NUH, ChanS->Chan);
    FREE(NamesPT);
    NameLen = 12;
    NameLen += strlen(ConnS->ServerName);
    NameLen += strlen(ChanS->Me->Nick);
    NameLen += strlen(ChanS->Chan);
    DumbPT = realloc(NamesPT, NameLen);
    if (DumbPT == NULL) {
      client_close(ClientS, "Memory allocation failure: [%d] %s", errno, strerror(errno));
      free(NamesPT);
      return;
    }
    NamesPT = DumbPT;
    sprintf(NamesPT, ":%s 353 %s = %s :", ConnS->ServerName, ChanS->Me->Nick, ChanS->Chan);
    NameIndex = NameLen - 1;
    Count = 0;
    Users = 0;

    for (ChanUser = ChanS->User_Head ; ChanUser != NULL ; ChanUser = ChanUser->Next) {
      Count++;
      UserLen = strlen(ChanUser->Nick);
      if (ChanUser_IsOP(ChanUser)) { UserLen++; }
      else if (ChanUser_IsVoice(ChanUser)) { UserLen++; }
      if (Count > 1) { UserLen++; }
      DumbLen = NameLen + UserLen - 1;
      if (DumbLen > IRCMSGLEN) {
        client_addsend(ClientS, "%s", NamesPT);
        memset(NamesPT, 0, NameLen);
        FREE(NamesPT);
        NameLen = 12;
        NameLen += strlen(ConnS->ServerName);
        NameLen += strlen(ChanS->Me->Nick);
        NameLen += strlen(ChanS->Chan);
        DumbPT = realloc(NamesPT, NameLen);
        if (DumbPT == NULL) {
          client_close(ClientS, "Memory allocation failure: [%d] %s", errno, strerror(errno));
          free(NamesPT);
          return;
        }
        NamesPT = DumbPT;
        sprintf(NamesPT, ":%s 353 %s = %s :", ConnS->ServerName, ChanS->Me->Nick, ChanS->Chan);
        Count = 1;
        NameIndex = NameLen - 1;
        UserLen = strlen(ChanUser->Nick);
        if (ChanUser_IsOP(ChanUser)) { UserLen++; }
        else if (ChanUser_IsVoice(ChanUser)) { UserLen++; }
        DumbLen = NameLen + UserLen - 1;
        assert(DumbLen <= IRCMSGLEN);
      }
      NewLen = NameLen + UserLen;
      DumbPT = realloc(NamesPT, NewLen);
      if (DumbPT == NULL) {
        client_close(ClientS, "Memory allocation failure: [%d] %s", errno, strerror(errno));
        free(NamesPT);
        return;
      }
      NamesPT = DumbPT;
      DumbPT += NameIndex;
      if (Count > 1) {
        *DumbPT = ' ';
        DumbPT++;
      }
      if (ChanUser_IsOP(ChanUser)) {
        *DumbPT = '@';
        DumbPT++;
      }
      else if (ChanUser_IsVoice(ChanUser)) {
        *DumbPT = '+';
        DumbPT++;
      }
      Users++;
      strcpy(DumbPT, ChanUser->Nick);
      NameIndex += UserLen;
      NameLen += UserLen;
    }
    if (Count > 0) {
      client_addsend(ClientS, "%s", NamesPT);
      memset(NamesPT, 0, NameLen);
      FREE(NamesPT);
      NameLen = 0;
    }
    assert(ChanS->Users == Users);
    client_addsend(ClientS, ":%s 366 %s %s :End of NAMES from proxy", ConnS->ServerName, ChanS->Me->Nick, ChanS->Chan);
  }

  Conn_SetAttached(ConnS);
  ++ConnS->NumClients;
  ClientS->ConnS = ConnS;

  client_notice(ClientS, "Attached on connection %s %s(%s):%ld \"%s\"", ConnS->Name, ConnS->ServerHostName, ConnS->ServerHostIPS, ConnS->ServerPortH, ConnS->ServerName);
  client_noticeallbutone(ClientS, "Client %s (%s) attached on connection %s.", ClientS->HostName, ClientS->HostIPS, ConnS->Name);

}

/* CLIENT_DETACH FUNCTION - JONAS (01.07.2000) */

void client_detach(struct Client_Struct *ClientS) {

  assert(ClientS != NULL);
  assert(ClientS->ConnS != NULL);

  --ClientS->ConnS->NumClients;

  if (Conn_IsWelcome(ClientS->ConnS)) {
    if ((Client_IsSocket(ClientS)) && (Client_IsVerified(ClientS))) {
      client_close(ClientS, "Detached from connection %s %s(%s):%ld \"%s\"", ClientS->ConnS->Name, ClientS->ConnS->ServerHostName, ClientS->ConnS->ServerHostIPS, ClientS->ConnS->ServerPortH, ClientS->ConnS->ServerName);
    }
    if (ClientS->ConnS->NumClients <= 0) {
      if ((ConnConf_IsAutoAway(ClientS->ConnS)) && (!Conn_IsAway(ClientS->ConnS))) { conn_addsendq(ClientS->ConnS, "AWAY :%s", ClientS->ConnS->AwayMsg); }
      if (ConnConf_IsPublicAway(ClientS->ConnS)) {
        struct Chan_Struct *ChanS = NULL;
        for (ChanS = ClientS->ConnS->Chan_Head ; ChanS != NULL ; ChanS = ChanS->Next) { conn_addsendq(ClientS->ConnS, "PRIVMSG %s :\1ACTION %s\1", ChanS->Chan, ClientS->ConnS->PublicDetachMsg); }
      }
      if ((!Conn_IsSentNick(ClientS->ConnS)) && (!Conn_IsSentAwayNick(ClientS->ConnS)) && (!Conn_IsAwayNick(ClientS->ConnS)) && (strcasecmp(ClientS->ConnS->IRCNick, ClientS->ConnS->AwayNick) != FALSE) && (strcasecmp(ClientS->ConnS->Nick, ClientS->ConnS->AwayNick) != FALSE)) {
        Conn_SetSentAwayNick(ClientS->ConnS);
        ClientS->ConnS->NicksIndex = 0;
        conn_addsendq(ClientS->ConnS, "NICK %s", ClientS->ConnS->AwayNick);
      }
    }
  }
  else {
    if ((Client_IsSocket(ClientS)) && (Client_IsVerified(ClientS))) {
      client_notice(ClientS, "Detached from connection %s: Not connected.", ClientS->ConnS->Name);
    }
  }

  client_noticeallbutone(ClientS, "Client %s (%s) detached from connection %s.", ClientS->HostName, ClientS->HostIPS, ClientS->ConnS->Name);
  ClientS->ConnS = NULL;

}


syntax highlighted by Code2HTML, v. 0.9.1