/*-
 * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: src/usr.sbin/ppp/iplist.c,v 1.9 1999/08/28 01:18:31 peter Exp $
 */

#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <string.h>
#include <termios.h>

#include "log.h"
#include "defs.h"
#include "iplist.h"

static int
do_inet_aton(const char *start, const char *end, struct in_addr *ip)
{
  char ipstr[16];

  if (end - start > 15) {
    log_Printf(LogWARN, "%.*s: Invalid IP address\n", (int)(end-start), start);
    return 0;
  }
  strncpy(ipstr, start, end-start);
  ipstr[end-start] = '\0';
  return inet_aton(ipstr, ip);
}

static void
iplist_first(struct iplist *list)
{
  list->cur.pos = -1;
}

static int
iplist_setrange(struct iplist *list, char *range)
{
  char *ptr, *to;

  if ((ptr = strpbrk(range, ",-")) == NULL) {
    if (!inet_aton(range, &list->cur.ip))
      return 0;
    list->cur.lstart = ntohl(list->cur.ip.s_addr);
    list->cur.nItems = 1;
  } else {
    if (!do_inet_aton(range, ptr, &list->cur.ip))
      return 0;
    if (*ptr == ',') {
      list->cur.lstart = ntohl(list->cur.ip.s_addr);
      list->cur.nItems = 1;
    } else {
      struct in_addr endip;

      to = ptr+1;
      if ((ptr = strpbrk(to, ",-")) == NULL)
        ptr = to + strlen(to);
      if (*to == '-')
        return 0;
      if (!do_inet_aton(to, ptr, &endip))
        return 0;
      list->cur.lstart = ntohl(list->cur.ip.s_addr);
      list->cur.nItems = ntohl(endip.s_addr) - list->cur.lstart + 1;
      if (list->cur.nItems < 1)
        return 0;
    }
  }
  list->cur.srcitem = 0;
  list->cur.srcptr = range;
  return 1;
}

static int
iplist_nextrange(struct iplist *list)
{
  char *ptr, *to, *end;

  ptr = list->cur.srcptr;
  if (ptr != NULL && (ptr = strchr(ptr, ',')) != NULL)
    ptr++;
  else
    ptr = list->src;

  while (*ptr != '\0' && !iplist_setrange(list, ptr)) {
    if ((end = strchr(ptr, ',')) == NULL)
      end = ptr + strlen(ptr);
    if (end == ptr)
      return 0;
    log_Printf(LogWARN, "%.*s: Invalid IP range (skipping)\n",
               (int)(end - ptr), ptr);
    to = ptr;
    do
      *to = *end++;
    while (*to++ != '\0');
    if (*ptr == '\0')
      ptr = list->src;
  }

  return 1;
}

struct in_addr
iplist_next(struct iplist *list)
{
  if (list->cur.pos == -1) {
    list->cur.srcptr = NULL;
    if (!iplist_nextrange(list)) {
      list->cur.ip.s_addr = INADDR_ANY;
      return list->cur.ip;
    }
  } else if (++list->cur.srcitem == list->cur.nItems) {
    if (!iplist_nextrange(list)) {
      list->cur.ip.s_addr = INADDR_ANY;
      list->cur.pos = -1;
      return list->cur.ip;
    }
  } else
    list->cur.ip.s_addr = htonl(list->cur.lstart + list->cur.srcitem);
  list->cur.pos++;

  return list->cur.ip;
}

int
iplist_setsrc(struct iplist *list, const char *src)
{
  strncpy(list->src, src, sizeof list->src - 1);
  list->src[sizeof list->src - 1] = '\0';
  list->cur.srcptr = list->src;
  do {
    if (iplist_nextrange(list))
      list->nItems += list->cur.nItems;
    else
      return 0;
  } while (list->cur.srcptr != list->src);
  return 1;
}

void
iplist_reset(struct iplist *list)
{
  list->src[0] = '\0';
  list->nItems = 0;
  list->cur.pos = -1;
}

struct in_addr
iplist_setcurpos(struct iplist *list, long pos)
{
  if (pos < 0 || pos >= list->nItems) {
    list->cur.pos = -1;
    list->cur.ip.s_addr = INADDR_ANY;
    return list->cur.ip;
  }

  list->cur.srcptr = NULL;
  list->cur.pos = 0;
  while (1) {
    iplist_nextrange(list);
    if (pos < list->cur.nItems) {
      if (pos) {
        list->cur.srcitem = pos;
        list->cur.pos += pos;
        list->cur.ip.s_addr = htonl(list->cur.lstart + list->cur.srcitem);
      }
      break;
    }
    pos -= list->cur.nItems;
    list->cur.pos += list->cur.nItems;
  }

  return list->cur.ip;
}

struct in_addr
iplist_setrandpos(struct iplist *list)
{
  randinit();
  return iplist_setcurpos(list, random() % list->nItems);
}

int
iplist_ip2pos(struct iplist *list, struct in_addr ip)
{
  struct iplist_cur cur;
  u_long f;
  int result;

  result = -1;
  memcpy(&cur, &list->cur, sizeof cur);

  for (iplist_first(list), f = 0; f < list->nItems; f++)
    if (iplist_next(list).s_addr == ip.s_addr) {
      result = list->cur.pos;
      break;
    }

  memcpy(&list->cur, &cur, sizeof list->cur);
  return result;
}
