/* tables.c is Copyright (c) 2014 Sven Falempin  All Rights Reserved.

   Author's email: sfalempin@citypassenger.com 

   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; version 2 dated June, 1991, or
   (at your option) version 3 dated 29 June, 2007.
 
   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, see <http://www.gnu.org/licenses/>.
*/

#include "dnsmasq.h"

#if defined(HAVE_IPSET) && defined(HAVE_BSD_NETWORK)

#ifndef __FreeBSD__
#include <string.h>
#endif

#include <sys/types.h>
#include <sys/ioctl.h>

#include <net/if.h>
#include <netinet/in.h>
#include <net/pfvar.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>

#define UNUSED(x) (void)(x)

static char *pf_device = "/dev/pf";
static int dev = -1;

static char *pfr_strerror(int errnum)
{
  switch (errnum) 
    {
    case ESRCH:
      return "Table does not exist";
    case ENOENT:
      return "Anchor or Ruleset does not exist";
    default:
      return strerror(errnum);
    }
}

static int pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags)
{
  struct pfioc_table io;
  
  if (size < 0 || (size && tbl == NULL)) 
    {
      errno = EINVAL;
      return (-1);
    }
  bzero(&io, sizeof io);
  io.pfrio_flags = flags;
  io.pfrio_buffer = tbl;
  io.pfrio_esize = sizeof(*tbl);
  io.pfrio_size = size;
  if (ioctl(dev, DIOCRADDTABLES, &io))
    return (-1);
  if (nadd != NULL)
    *nadd = io.pfrio_nadd;
  return (0);
}

static int fill_addr(const struct all_addr *ipaddr, int flags, struct pfr_addr* addr) {
  if ( !addr || !ipaddr)
    {
      my_syslog(LOG_ERR, _("error: fill_addr missused"));
      return -1;
    }
  bzero(addr, sizeof(*addr));
#ifdef HAVE_IPV6
  if (flags & F_IPV6) 
    {
      addr->pfra_af = AF_INET6;
      addr->pfra_net = 0x80;
      memcpy(&(addr->pfra_ip6addr), &(ipaddr->addr), sizeof(struct in6_addr));
    } 
  else 
#endif
    {
      addr->pfra_af = AF_INET;
      addr->pfra_net = 0x20;
      addr->pfra_ip4addr.s_addr = ipaddr->addr.addr4.s_addr;
    }
  return 1;
}

/*****************************************************************************/

void ipset_init(void) 
{
  dev = open( pf_device, O_RDWR);
  if (dev == -1)
    {
      err(1, "%s", pf_device);
      die (_("failed to access pf devices: %s"), NULL, EC_MISC);
    }
}

int add_to_ipset(const char *setname, const struct all_addr *ipaddr,
		      int flags, int remove)
{
  struct pfr_addr addr;
  struct pfioc_table io;
  struct pfr_table table;
  int n = 0, rc = 0;

  if ( dev == -1 ) 
    {
      my_syslog(LOG_ERR, _("warning: no opened pf devices %s"), pf_device);
      return -1;
    }

  bzero(&table, sizeof(struct pfr_table));
  table.pfrt_flags |= PFR_TFLAG_PERSIST;
  if ( strlen(setname) >= PF_TABLE_NAME_SIZE )
    {
      my_syslog(LOG_ERR, _("error: cannot use table name %s"), setname);
      errno = ENAMETOOLONG;
      return -1;
    }
  
  if ( strlcpy(table.pfrt_name, setname,
               sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) 
    {
      my_syslog(LOG_ERR, _("error: cannot strlcpy table name %s"), setname);
      return -1;
    }
  
  if ((rc = pfr_add_tables(&table, 1, &n, 0))) 
    {
      my_syslog(LOG_WARNING, _("warning: pfr_add_tables: %s(%d)"),
		pfr_strerror(errno),rc);
      return -1;
    }
  table.pfrt_flags &= ~PFR_TFLAG_PERSIST;
  if (n)
    my_syslog(LOG_INFO, _("info: table created"));
  
  fill_addr(ipaddr,flags,&addr);
  bzero(&io, sizeof(io));
  io.pfrio_flags = 0;
  io.pfrio_table = table;
  io.pfrio_buffer = &addr;
  io.pfrio_esize = sizeof(addr);
  io.pfrio_size = 1;
  if (ioctl(dev, ( remove ? DIOCRDELADDRS : DIOCRADDADDRS ), &io)) 
    {
      my_syslog(LOG_WARNING, _("warning: DIOCR%sADDRS: %s"), ( remove ? "DEL" : "ADD" ), pfr_strerror(errno));
      return -1;
    }
  
  my_syslog(LOG_INFO, _("%d addresses %s"),
            io.pfrio_nadd, ( remove ? "removed" : "added" ));
  
  return io.pfrio_nadd;
}


#endif
