/************************************************************************
 *   IRC - Internet Relay Chat, iauth/conf.c
 *
 *   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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   $Id: conf.c,v 1.1.1.1 2002/09/12 01:08:28 cry0gen Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "auth.h"
#include "class.h"
#include "conf.h"
#include "iauth.h"
#include "log.h"
#include "match.h"
#include "misc.h"
#include "mtree.h"
#include "setup.h"

/*
 * Configuration file
 */
char                    *ConfigFile = CONFFILE;

/*
 * List of banned users (K-lines)
 */
struct ServerBan        *ServerBanList = NULL;

/*
 * List of allowed users
 */
struct Iline            *IlineList = NULL;

/*
 * List of listen ports
 */
struct Port             *PortList = NULL;

/*
 * List of nickname quarantines
 */
struct Quarantine       *QuarantineList = NULL;

static int ParseConfig(char *filename);
static char *getfield (char *newline);
static void GetUserAndHost(char **user, char **host);

static struct ServerBan *AddServerBan(char *user, char *host,
                                      char *reason, time_t expires);
static void DeleteServerBan(struct ServerBan *kptr);

static char *ParseIlineFlags(struct Iline *iptr, char *user);
static struct Iline *AddIline(char *spoofhost, char *password,
                              char *user, char *host, int class);

static void AddPort(int portnum);

static void AddQuarantine(char *nick, char *reason, char *user, char *host);

/*
ParseConfig()
  Parse configuration file 'filename'

Return: 1 if successful
        0 if unsuccessful
*/

static int
ParseConfig(char *filename)

{
  FILE *fp;
  char buffer[BUFSIZE];
  char *key;
  int linenum;

  if (!(fp = fopen(filename, "r")))
  {
    log(L_ERROR,
      "ParseConfig(): Unable to open configuration file: %s",
      filename);
    return 0;
  }

  linenum = 0;
  while (fgets(buffer, sizeof(buffer) - 1, fp) && ++linenum)
  {
    if (*buffer == '#')
      continue; /* comment */

    if (!(key = getfield(buffer)))
      continue;

    switch (*key)
    {
      /*
       * K-lines (bans)
       */
      case 'k':
      case 'K':
      {
        char *user,
             *host,
             *reason;

        user = getfield(NULL);
        reason = getfield(NULL);

        if (!user || !reason)
        {
          log(L_ERROR,
            "%s:%d Invalid configuration line",
            filename,
            linenum);
          continue;
        }

        GetUserAndHost(&user, &host);

        AddServerBan(user, host, reason, 0);

        break;
      } /* case 'K' */

      /*
       * I-lines (authorization)
       */
      case 'i':
      case 'I':
      {
        char *spoofhost,
             *password,
             *user,
             *host,
             *class;

        spoofhost = getfield(NULL);
        password = getfield(NULL);
        user = getfield(NULL);
        (void) getfield(NULL); /* unused field */
        class = getfield(NULL);

        if (!user || !class)
        {
          log(L_ERROR,
            "%s:%d Invalid configuration line",
            filename,
            linenum);
          continue;
        }

        GetUserAndHost(&user, &host);

        AddIline(spoofhost, password, user, host, atoi(class));

        break;
      } /* case 'I' */

      /*
       * Y-lines (class lines)
       */
      case 'y':
      case 'Y':
      {
        break;
      } /* case 'Y' */

      /*
       * P-lines (listen ports)
       */
      case 'p':
      case 'P':
      {
        char *port;

        port = getfield(NULL);
        if (!port)
        {
          log(L_ERROR,
            "%s:%d Invalid configuration line",
            filename,
            linenum);
          continue;
        }

        AddPort(atoi(port));

        break;
      } /* case 'P' */

      /*
       * Nickname quarantine lines
       */
      case 'q':
      case 'Q':
      {
        char *nick,
             *reason,
             *user,
             *host;

        nick = getfield(NULL);
        reason = getfield(NULL);
        user = getfield(NULL);

        if (!nick)
        {
          log(L_ERROR,
            "%s:%d Invalid configuration line",
            filename,
            linenum);
          continue;
        }

        if (user)
          GetUserAndHost(&user, &host);

        AddQuarantine(nick, reason, user, host);
      } /* case 'Q' */
    } /* switch (*key) */
  }

  fclose(fp);

  return 1;
} /* ParseConfig() */

/*
LoadConfig()
  Call ParseConfig() to load configuration file
*/

void
LoadConfig()

{
  if (!ParseConfig(ConfigFile))
  {
    fprintf(stderr,
      "Unable to parse configuration file: %s\n",
      ConfigFile);
    exit (-1);
  }
} /* LoadConfig() */

/*
getfield()
 Get next field separated by a : in the line
*/

static char *
getfield (char *newline)

{
  static char *line = NULL;
  char *end, *field;
  
  if (newline)
    line = newline;

  if (!line)
    return (NULL);

  field = line;
  if ((end = strchr(line, ':')) == NULL)
  {
    line = NULL;
    if ((end = strchr(field, '\n')) == NULL)
      end = field + strlen(field);
  }
  else
    line = end + 1;

  *end = '\0';
  if (strlen(field))
    return (field);
  else
    return (NULL);
} /* getfield() */

/*
GetUserAndHost()
 Called from ParseConfig() when a possible string fitting
"user@host" is read. Set the 'user' variable to "user" and
the 'host' variable to "host".
 Assume 'user' initially points to the entire string "user@host".
*/

static void
GetUserAndHost(char **user, char **host)

{
  char *ch;

  if ((ch = strchr(*user, '@')))
  {
    *ch++ = '\0';
    *host = ch;
  }
  else
  {
    *host = *user;
    *user = NULL;
  }
} /* GetUserAndHost() */

/*
AddServerBan()
 Add a kline to the list with the given information. Return
pointer to new kline.
*/

static struct ServerBan *
AddServerBan(char *user, char *host, char *reason, time_t expires)

{
  struct ServerBan *ptr;

  ptr = (struct ServerBan *) MyMalloc(sizeof(struct ServerBan));
  memset(ptr, 0, sizeof(struct ServerBan));

  if (user)
    ptr->username = MyStrdup(user);
  else
    ptr->username = MyStrdup("*");

  ptr->hostname = MyStrdup(host);

  if (reason)
    ptr->reason = MyStrdup(reason);
  else
    ptr->reason = MyStrdup("No reason");

  ptr->expires = expires;

  ptr->prev = NULL;
  ptr->next = ServerBanList;
  if (ptr->next)
    ptr->next->prev = ptr;
  ServerBanList = ptr;

  TreeAddKline(ptr);

  return (ptr);
} /* AddServerBan() */

/*
DeleteServerBan()
 Remove kptr from the kline list
*/

static void
DeleteServerBan(struct ServerBan *kptr)

{
} /* DeleteServerBan() */

/*
FindServerBan()
 Determine if 'user' and 'host' match a kline, if so,
return a pointer to it.
*/

struct ServerBan *
FindServerBan(char *user, char *host)

{
  struct ServerBan *tmp;

  tmp = (struct ServerBan *) SearchKlineTree(user, host);
  if (tmp)
    fprintf(stderr, "FOUND KLINE: [%s@%s]\n", tmp->username, tmp->hostname);
  return (tmp);

#if 0
  for (tmp = ServerBanList; tmp; tmp = tmp->next)
  {
    if (match(tmp->username, user) && match(tmp->hostname, host))
      return (tmp);
  }

  return (NULL);
#endif /* 0 */
} /* FindServerBan() */

/*
ParseIlineFlags()
 Determine if iptr has any special flags in it's user field.
If so, update iptr->flags accordingly.

Valid flags:
 =  : spoof their ip
 !  : limit to 1 client per ip
 -  : don't give them a ~ even if they have no identd
 +  : don't let them connect unless they have an ident
 $  :
 %  :
 ^  : user is exempt from K/G lines
 >  : user is also exempt from connection limits
*/

static char *
ParseIlineFlags(struct Iline *iptr, char *user)

{
  while (*user)
  {
    switch (*user)
    {
      case '=':
        SetIlineSpoof(iptr);
        break;

      case '!':
        SetIlineLimit(iptr);
        break;

      case '-':
        SetIlineNoTilde(iptr);
        break;

      case '+':
        SetIlineForceId(iptr);
        break;

#ifdef bingo
      case '$':
        SetIlinePassIdent(iptr);
        break;

      case '%':
        SetIlineNoMatch(iptr);
        break;
#endif /* bingo */

      case '^':
        SetIlineExempt(iptr);
        break;

      case '>':
        SetIlineExempt(iptr);
        SetIlineSuperExempt(iptr);
        break;

      default:
        return (user);
    }

    ++user;
  }

  return (user);
} /* ParseIlineFlags() */

/*
AddIline()
 Allocate a new Iline structure with the given information.
Return a pointer to it.

 Assume everything is NON-NULL, except for user and password. user
may be null in case there was no @ in the userhost field.
*/

static struct Iline *
AddIline(char *spoofhost, char *password, char *user, char *host,
         int class)

{
  struct Iline *ptr;
  char *tmpuser;

  ptr = (struct Iline *) MyMalloc(sizeof(struct Iline));
  memset(ptr, 0, sizeof(struct Iline));

  if (password)
    ptr->password = MyStrdup(password);

  if (user)
  {
    /*
     * If this Iline has any special flags, they would
     * be in the user field - parse them
     */
    tmpuser = ParseIlineFlags(ptr, user);

    ptr->username = MyStrdup(tmpuser);
  }
  else
    ptr->username = MyStrdup("*");

  if (spoofhost && IsIlineSpoof(ptr))
    ptr->spoofhost = MyStrdup(spoofhost);

  ptr->hostname = MyStrdup(host);

  ptr->class = FindClass(class);
  ptr->classnum = class;

  ptr->prev = NULL;
  ptr->next = IlineList;
  if (ptr->next)
    ptr->next->prev = ptr;
  IlineList = ptr;

  /*
   * Add ptr to the Iline tree
   */
  TreeAddIline(ptr);

  return (ptr);
} /* AddIline() */

/*
FindIline()
 Determine if user and host match an I line, if so return
a pointer to it.
*/

struct Iline *
FindIline(char *user, char *host)

{
  struct Iline *iptr;

  for (iptr = IlineList; iptr; iptr = iptr->next)
  {
    if (match(iptr->username, user) && match(iptr->hostname, host))
      return (iptr);
  }

  return (NULL);
} /* FindIline() */

/*
CheckIline()
 Called when a client connects to an ircd server to determine
if they have a valid I: line.

Return values:

IL_ERR_NOTAUTHORIZED - An I-line could not be found matching
                       the given user.

IL_ERR_FULL          - The given Iline is full - no more clients
                       may use it.

IL_ERR_NEEDIDENT     - The given Iline requires identd, and the
                       client did not have an ident reply.

IL_ERR_BADPASS       - The given Iline requires a password, which
                       was not correctly supplied by the client.

IL_ERR_GOODMATCH     - Not actually an error - the client is an
                       acceptable match for the given Iline.

  If the client matches an Iline, the Iline structure is stored
in 'imatch'
*/

int
CheckIline(char *user, char *host, char *pass, struct Iline **imatch)

{
  struct Iline *iptr;

  iptr = (struct Iline *) SearchIlineTree(user, host);
  if (iptr)
    fprintf(stderr, "FOUND IN TREE: [%s@%s]\n", iptr->username, iptr->hostname);

  if (!iptr)
    return (IL_ERR_NOTAUTHORIZED);

  if (iptr->class)
  {
    if ((iptr->class->links >= iptr->class->maxLinks) &&
        !IsIlineSuperExempt(iptr))
      return (IL_ERR_FULL);

    ++iptr->class->links;
  }

  if (IsIlineForceId(iptr) && (*user == '~'))
    return (IL_ERR_NEEDIDENT);

  if (iptr->password && (strcmp(iptr->password, pass) != 0))
    return (IL_ERR_BADPASS);

  *imatch = iptr;

  return (IL_ERR_GOODMATCH);
} /* CheckIline() */

/*
AddPort()
 Add a port number to the list of listen ports
*/

static void
AddPort(int portnum)

{
  struct Port *ptr;

  ptr = (struct Port *) MyMalloc(sizeof(struct Port));
  memset(ptr, 0, sizeof(struct Port));

  ptr->port = portnum;
  ptr->sockfd = NOSOCK;

  ptr->next = PortList;
  PortList = ptr;
} /* AddPort() */

/*
AddQuarantine()
 Add a nickname quarantine with the given information
*/

static void
AddQuarantine(char *nick, char *reason, char *user, char *host)

{
  struct Quarantine *ptr;

  ptr = (struct Quarantine *) MyMalloc(sizeof(struct Quarantine));
  memset(ptr, 0, sizeof(struct Quarantine));

  ptr->nickname = MyStrdup(nick);
  if (reason)
    ptr->reason = MyStrdup(reason);
  else
    ptr->reason = MyStrdup("No reason");

  if (host)
  {
    ptr->hostname = MyStrdup(host);
    if (user)
      ptr->username = MyStrdup(user);
    else
      ptr->username = MyStrdup("*");
  }

  ptr->next = QuarantineList;
  QuarantineList = ptr;
} /* AddQuarantine() */

/*
FindQuarantine()
 Attempt to locate the given nickname in the quarantine list -
return a pointer to it if found
*/

struct Quarantine *
FindQuarantine(char *nick, char *user, char *host)

{
  struct Quarantine *qptr;

  for (qptr = QuarantineList; qptr; qptr = qptr->next)
  {
    if (match(qptr->nickname, nick))
    {
      /*
       * The user may be exempt from this quarantine if
       * their username and hostname match qptr's
       */
      if (qptr->username && qptr->hostname)
      {
        if (match(qptr->username, user) && match(qptr->hostname, host))
          continue;
      }

      return (qptr);
    }
  }

  return (NULL);
} /* FindQuarantine() */
