/*
  chronyd/chronyc - Programs for keeping computer clocks accurate.

 **********************************************************************
 * Copyright (C) Richard P. Curnow  1997,1998,1999,2000,2001,2002,2005
 * Copyright (C) Miroslav Lichvar  2009, 2015
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * 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.
 * 
 **********************************************************************

  =======================================================================

  This module provides a set of routines for checking IP addresses
  against a set of rules and deciding whether they are allowed or
  disallowed.

  */

#include "config.h"

#include "sysincl.h"

#include "addrfilt.h"
#include "memory.h"

/* Define the number of bits which are stripped off per level of
   indirection in the tables */
#define NBITS 4

/* Define the table size */
#define TABLE_SIZE (1UL<<NBITS)

typedef enum {DENY, ALLOW, AS_PARENT} State;

typedef struct _TableNode {
  State state;
  struct _TableNode *extended;
} TableNode;

struct ADF_AuthTableInst {
  TableNode base4;      /* IPv4 node */
  TableNode base6;      /* IPv6 node */
};

/* ================================================== */

static void
split_ip6(IPAddr *ip, uint32_t *dst)
{
  int i;

  for (i = 0; i < 4; i++)
    dst[i] = (uint32_t)ip->addr.in6[i * 4 + 0] << 24 |
             ip->addr.in6[i * 4 + 1] << 16 |
             ip->addr.in6[i * 4 + 2] << 8 |
             ip->addr.in6[i * 4 + 3];
}

/* ================================================== */

inline static uint32_t
get_subnet(uint32_t *addr, unsigned int where)
{
  int off;

  off = where / 32;
  where %= 32;

  return (addr[off] >> (32 - NBITS - where)) & ((1UL << NBITS) - 1);
}

/* ================================================== */

ADF_AuthTable
ADF_CreateTable(void)
{
  ADF_AuthTable result;
  result = MallocNew(struct ADF_AuthTableInst);

  /* Default is that nothing is allowed */
  result->base4.state = DENY;
  result->base4.extended = NULL;
  result->base6.state = DENY;
  result->base6.extended = NULL;

  return result;
}

/* ================================================== */
/* This function deletes all definitions of child nodes, in effect
   pruning a whole subnet definition back to a single parent
   record. */
static void
close_node(TableNode *node)
{
  int i;
  TableNode *child_node;

  if (node->extended != NULL) {
    for (i=0; i<TABLE_SIZE; i++) {
      child_node = &(node->extended[i]);
      close_node(child_node);
    }
    Free(node->extended);
    node->extended = NULL;
  }
}


/* ================================================== */
/* Allocate the extension field in a node, and set all the children's
   states to default to that of the node being extended */

static void
open_node(TableNode *node)
{
  int i;
  TableNode *child_node;

  if (node->extended == NULL) {

    node->extended = MallocArray(struct _TableNode, TABLE_SIZE);

    for (i=0; i<TABLE_SIZE; i++) {
      child_node = &(node->extended[i]);
      child_node->state = AS_PARENT;
      child_node->extended = NULL;
    }
  }
}

/* ================================================== */

static ADF_Status
set_subnet(TableNode *start_node,
           uint32_t *ip,
           int ip_len,
           int subnet_bits,
           State new_state,
           int delete_children)
{
  int bits_to_go, bits_consumed;
  uint32_t subnet;
  TableNode *node;

  bits_consumed = 0;
  bits_to_go = subnet_bits;
  node = start_node;

  if ((subnet_bits < 0) ||
      (subnet_bits > 32 * ip_len)) {

    return ADF_BADSUBNET;

  } else {

    if ((bits_to_go & (NBITS-1)) == 0) {
    
      while (bits_to_go > 0) {
        subnet = get_subnet(ip, bits_consumed);
        if (!(node->extended)) {
          open_node(node);
        }
        node = &(node->extended[subnet]);
        bits_to_go -= NBITS;
        bits_consumed += NBITS;
      }

      if (delete_children) {
        close_node(node);
      }
      node->state = new_state;

    } else { /* Have to set multiple entries */
      int N, i, j;
      TableNode *this_node;

      while (bits_to_go >= NBITS) {
        subnet = get_subnet(ip, bits_consumed);
        if (!(node->extended)) {
          open_node(node);
        }
        node = &(node->extended[subnet]);
        bits_to_go -= NBITS;
        bits_consumed += NBITS;
      }

      /* How many subnet entries to set : 1->8, 2->4, 3->2 */
      N = 1 << (NBITS-bits_to_go);

      subnet = get_subnet(ip, bits_consumed) & ~(N - 1);
      assert(subnet + N <= TABLE_SIZE);

      if (!(node->extended)) {
        open_node(node);
      }
      
      for (i=subnet, j=0; j<N; i++, j++) {
        this_node = &(node->extended[i]);
        if (delete_children) {
          close_node(this_node);
        }
        this_node->state = new_state;
      }
    }
    
    return ADF_SUCCESS;
  }
  
}

/* ================================================== */

static ADF_Status
set_subnet_(ADF_AuthTable table,
           IPAddr *ip_addr,
           int subnet_bits,
           State new_state,
           int delete_children)
{
  uint32_t ip6[4];

  switch (ip_addr->family) {
    case IPADDR_INET4:
      return set_subnet(&table->base4, &ip_addr->addr.in4, 1, subnet_bits, new_state, delete_children);
    case IPADDR_INET6:
      split_ip6(ip_addr, ip6);
      return set_subnet(&table->base6, ip6, 4, subnet_bits, new_state, delete_children);
    case IPADDR_UNSPEC:
      /* Apply to both, subnet_bits has to be 0 */
      if (subnet_bits != 0)
        return ADF_BADSUBNET;
      memset(ip6, 0, sizeof (ip6));
      if (set_subnet(&table->base4, ip6, 1, 0, new_state, delete_children) == ADF_SUCCESS &&
          set_subnet(&table->base6, ip6, 4, 0, new_state, delete_children) == ADF_SUCCESS)
        return ADF_SUCCESS;
      break;
    default:
      break;
  }

  return ADF_BADSUBNET;
}

ADF_Status
ADF_Allow(ADF_AuthTable table,
          IPAddr *ip,
          int subnet_bits)
{
  return set_subnet_(table, ip, subnet_bits, ALLOW, 0);
}

/* ================================================== */


ADF_Status
ADF_AllowAll(ADF_AuthTable table,
             IPAddr *ip,
             int subnet_bits)
{
  return set_subnet_(table, ip, subnet_bits, ALLOW, 1);
}

/* ================================================== */

ADF_Status
ADF_Deny(ADF_AuthTable table,
         IPAddr *ip,
         int subnet_bits)
{
  return set_subnet_(table, ip, subnet_bits, DENY, 0);
}

/* ================================================== */

ADF_Status
ADF_DenyAll(ADF_AuthTable table,
            IPAddr *ip,
            int subnet_bits)
{
  return set_subnet_(table, ip, subnet_bits, DENY, 1);
}

/* ================================================== */

void
ADF_DestroyTable(ADF_AuthTable table)
{
  close_node(&table->base4);
  close_node(&table->base6);
  Free(table);
}

/* ================================================== */

static int
check_ip_in_node(TableNode *start_node, uint32_t *ip)
{
  uint32_t subnet;
  int bits_consumed = 0;
  int result = 0;
  int finished = 0;
  TableNode *node;
  State state=DENY;

  node = start_node;

  do {
    if (node->state != AS_PARENT) {
      state = node->state;
    }
    if (node->extended) {
      subnet = get_subnet(ip, bits_consumed);
      node = &(node->extended[subnet]);
      bits_consumed += NBITS;
    } else {
      /* Make decision on this node */
      finished = 1;
    }
  } while (!finished);

  switch (state) {
    case ALLOW:
      result = 1;
      break;
    case DENY:
      result = 0;
      break;
    case AS_PARENT:
      assert(0);
      break;
  }

  return result;
}


/* ================================================== */

int
ADF_IsAllowed(ADF_AuthTable table,
              IPAddr *ip_addr)
{
  uint32_t ip6[4];

  switch (ip_addr->family) {
    case IPADDR_INET4:
      return check_ip_in_node(&table->base4, &ip_addr->addr.in4);
    case IPADDR_INET6:
      split_ip6(ip_addr, ip6);
      return check_ip_in_node(&table->base6, ip6);
    default:
      return 0;
  }
}

/* ================================================== */

static int
is_any_allowed(TableNode *node, State parent)
{
  State state;
  int i;

  state = node->state != AS_PARENT ? node->state : parent;
  assert(state != AS_PARENT);

  if (node->extended) {
    for (i = 0; i < TABLE_SIZE; i++) {
      if (is_any_allowed(&node->extended[i], state))
        return 1;
    }
  } else if (state == ALLOW) {
    return 1;
  }

  return 0;
}

/* ================================================== */

int
ADF_IsAnyAllowed(ADF_AuthTable table, int family)
{
  switch (family) {
    case IPADDR_INET4:
      return is_any_allowed(&table->base4, AS_PARENT);
    case IPADDR_INET6:
      return is_any_allowed(&table->base6, AS_PARENT);
    default:
      return 0;
  }
}
