/*
 *      Copyright (c) 1996 Ascend Communications, Inc.
 *      All rights reserved.
 *
 *	Permission to copy all or part of this material for any purpose is
 *	granted provided that the above copyright notice and this paragraph
 *	are duplicated in all copies.  THIS SOFTWARE IS PROVIDED ``AS IS''
 *	AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 *	LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *	FOR A PARTICULAR PURPOSE.
 *
 *	Written by Greg McGary <gkm@eng.ascend.com>, August 1996
 */

/* $Id: radipad.c,v 1.2 1996/12/12 00:04:52 baskar Exp $ */

/* Greg McGary <gkm@eng.ascend.com> wrote this beast to
   allocate from IP address pools from RADIUS.

   See the function `usage ()' below for a summary of command-line options.

   This program operates as a stand-alone server.  Invoke the daemon
   from a suitable startup file (e.g., /etc/rc.local).

   Add this line to /etc/services:

radipa		9992/tcp	# RADIUS IP Address Allocation from global pools

   */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <time.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/errno.h>
#include <ctype.h>
#include "radipa.h"
#include "hash.h"
#if __STDC__ == 1
# include <stdarg.h>
#else
# include <varargs.h>
#endif

char version[] = "Version 1.1";

typedef unsigned short bitmap_int_t;
#define BITMAP_BIT_MASK		(0xffff)
#define BITMAP_DECLARE(bm, bits) bitmap_int_t bitmap[BITMAP_WORD_OFFSET (bits)]

#define BITMAP_BITS_PER_MAP(bm)	(sizeof (bm) * 8)
#define BITMAP_BITS_PER_WORD	(sizeof (bitmap_int_t) * 8)
#define BITMAP_BIT_OFFSET_MASK	(BITMAP_BITS_PER_WORD - 1)
#define BITMAP_WORD_OFFSET(o)	((o) / BITMAP_BITS_PER_WORD)
#define BITMAP_WORD(bm, o)	((bm)[BITMAP_WORD_OFFSET (o)])
#define BITMAP_BIT_OFFSET(o)	((o) & BITMAP_BIT_OFFSET_MASK)
#define BITMAP_BIT(o)		(1 << BITMAP_BIT_OFFSET (o))
#define BITMAP_BIT_CLEAR(bm, o)	(BITMAP_WORD (bm, o) &= ~BITMAP_BIT (o))
#define BITMAP_BIT_SET(bm, o)	(BITMAP_WORD (bm, o) |= BITMAP_BIT (o))
#define BITMAP_BIT_IS_SET(bm, o)(BITMAP_WORD (bm, o) & BITMAP_BIT (o))

struct router_allocs
{
  ipaddr_t router_address;
  struct hash_table address_table;
};

struct address_map
{
  ipaddr_t prefix_address;	/* 24 most significant bits of ip address */
  BITMAP_DECLARE(bitmap, 256);
};

struct most_recent
{
  struct address_chunk *chunks;	/* chunks signature */
  long count;
  struct address_chunk *current_chunk;	/* ip_address (below) is form this chunk */
  ipaddr_t current_ip_address;		/* address most recently allocated */
};

void usage P__((void));
int main P__((int argc, char **argv));

void init_syslog P__((void));
void daemonify P__((void));
void setup_listener P__((void));
void collect_router_addresses P__((int sock));
void recover_allocation_state P__((void));
void setup_signal_handlers P__((void));
void init_hash_tables P__((void));
unsigned long address_map_hash_1 P__((void CONST *key));
unsigned long address_map_hash_2 P__((void CONST *key));
int address_map_hash_compare P__((void CONST *x, void CONST *y));
unsigned long router_allocs_hash_1 P__((void CONST *key));
unsigned long router_allocs_hash_2 P__((void CONST *key));
int router_allocs_hash_compare P__((void CONST *x, void CONST *y));
unsigned long most_recent_hash_1 P__((void CONST *key));
unsigned long most_recent_hash_2 P__((void CONST *key));
int most_recent_hash_compare P__((void CONST *x, void CONST *y));
struct most_recent *find_most_recent P__((struct address_chunk *chunks, long count));
int most_recent_compare P__((void CONST *x, void CONST *y));
void catch_fatal_signal P__((int sig));
void catch_alarm_signal P__((int sig));
void get_addresses_in_use P__((void));
void service_radiusd_requests P__((void));
int service_one_radiusd_request P__((int sock));
ipaddr_t allocate_ip_address P__((ipaddr_t router_address, struct address_chunk *chunks, long count));
void return_ip_address P__ ((int sock, struct radipa_packet *request, ipaddr_t ip_address));
struct router_allocs *find_router_allocs P__((ipaddr_t router_address));
unsigned long ipaddr_hash_1 P__((void CONST *key));
unsigned long ipaddr_hash_2 P__((void CONST *key));
int ipaddr_hash_compare P__((void CONST *x, void CONST *y));
void router_allocs_insert P__((ipaddr_t router_address, ipaddr_t ip_address));
void router_allocs_delete P__((ipaddr_t router_address, ipaddr_t ip_address));
ipaddr_t allocate_from_chunk P__((struct address_chunk *chunk, ipaddr_t base_address));
struct address_map *find_address_map P__((ipaddr_t ip_address));
void clear_bit_in_address_map P__((ipaddr_t ip_address));
void set_bit_in_address_map P__((ipaddr_t ip_address));
int bitmap_set_first_zero P__((bitmap_int_t *bitmap, int offset, int total));
int bitmap_set_first_zero_1 P__((bitmap_int_t *bitmap, int offset));
void release_ip_address P__((ipaddr_t router_address, ipaddr_t ip_address));
void ack_release P__((int sock, struct radipa_packet *request));
void release_orphaned_ip_addresses P__((ipaddr_t router_address));
void release_orphaned_ip_address P__((void *payload, void *arg));
void poll_routers_for_addresses P__((ipaddr_t *ip_addresses, long count));
void ack_router_addresses P__((int sock, struct radipa_packet *request));
void mark_ip_addresses_as_allocated P__((ipaddr_t router_address,
					 ipaddr_t *ip_addresses, long count));
void ack_mark_addresses P__((int sock, struct radipa_packet *request));
void update_allocation_state P__((void));
void write_router_allocation_state P__((void *payload, void *arg));
void count_routers_with_addresses P__((void *payload, void *arg));
long read_long P__((FILE *in_FILE));
ipaddr_t read_ip_address P__((FILE *in_FILE));
void write_long P__((FILE *out_FILE, long l));
void write_ip_address P__((FILE *out_FILE, ipaddr_t ip_address));
int parse_interval P__((char *interval));
int sock_has_input P__((int sock, int seconds));
char CONST *get_peer_host_name P__((int sock));
char CONST *get_host_name_from_address P__((ipaddr_t address));
ipaddr_t get_peer_ip_address P__((int sock));
char *inet_dot_addr P__((ipaddr_t ip_address));
int is_socket P__((int fd));
#if __STDC__ == 1
void error (int exit_status, int error_number, char CONST *format, ...);
void inform (char CONST *format, ...);
void debug (char CONST *format, ...);
#else
void error (); /* varargs */
void inform (); /* varargs */
void debug (); /* varargs */
#endif
void error_stdio P__((int error_number, char CONST *format, void *args));
void error_syslog P__((int error_number, char CONST *format, void *args));
void inform_stdio P__((char CONST *format, void *args));
void inform_syslog P__((char CONST *format, void *args));
void prefix_date_time P__((FILE *stream));

#define PAGE_LENGTH 1024
#define MAX_TIMEOUT 16

#define MINUTES_BETWEEN_POLLS 30
#define LISTENER_QUEUE_SIZE 5

#ifndef NFILE
# define NFILE 1024		/* more than enough */
#endif

char *program_name;
int verbose_flag = 0;
int debug_flag = 0;
int syslog_flag = 1;
int minutes_between_polls = MINUTES_BETWEEN_POLLS;
int timer_expired = 0;
int listener_socket = -1;
int radiusd_sockets[NFILE];
int delay_seconds = 0;
int *radiusd_sockets_end = radiusd_sockets;
char CONST *state_file_name = RADIPA_STATE_FILE_NAME;
char CONST *new_state_file_name;
struct hash_table address_map_table;
struct hash_table router_allocs_table;
struct hash_table most_recent_table;

void
usage ()
{
  fprintf (stderr, "\
Usage: %s [ -v -p ] [ -i <interval> ]\n\
-v		Verbose: report detailed progress.\n\
-p		Report diagnostics with printf rather than syslog.\n\
-d <sec>	Delay <sec> seconds before answering a request.\n\
		(This debug option is not intended for normal operation)\n\
%s\n\
", program_name, version);
  exit (1);
}

/* Unimplemented option:
"\
-i <interval>	Poll routers for addresses in use every <interval>.\n\
		<interval> is expressed as HH:MM (hours & minutes),\n\
		or the number minutes (e.g., 155 == 2:35).\n\
		The default interval is %d minutes.\n\
", MINUTES_BETWEEN_POLLS
*/



int
main (argc, argv)
     int argc;
     char **argv;
{
  char *arg;

  program_name = (argc--, *argv++);

  init_syslog ();

  while (argc)
    {
      arg = (argc--, *argv++);
      if (arg[0] != '-')
	usage ();
      switch (arg[1])
	{
	case 'd':
	  delay_seconds = strtol ((argc--, *argv++), 0, 0);
	  break;
	  
	case 'v':
	  verbose_flag = 1;
	  break;

	case 'p':
	  syslog_flag = 0;
	  break;

	case 'i':
	  minutes_between_polls = parse_interval ((argc--, *argv++));
	  break;

	default:
	  usage ();
	}
    }

  daemonify ();
  setup_listener ();		/* do this ASAP so we can accept connections */
  setup_signal_handlers ();
  init_hash_tables ();
  recover_allocation_state ();
  get_addresses_in_use ();
  service_radiusd_requests ();

  return 0;
}

void
init_syslog ()
{
  if (syslog_flag)
    {
      openlog ("radipad", LOG_PID | LOG_NDELAY, LOG_DAEMON);
      setlogmask (LOG_UPTO (debug_flag ? LOG_DEBUG : LOG_INFO));
    }
}

void
daemonify ()
{
  if (!debug_flag)
    {
      pid_t pid = fork();
      if (pid < 0)
	error (1, errno, "Can't fork");
      if (pid > 0)
	exit (0);
    }
}

#define MAX_BIND_RETRIES 9
#define BIND_RETRY_DELAY_SECONDS 10

void
setup_listener ()
{
  struct sockaddr_in listener_sin;
  struct servent *servent;
  int retries = 0;

  listener_socket = socket (AF_INET, SOCK_STREAM, 0);
  listener_sin.sin_family = AF_INET;
  listener_sin.sin_addr.s_addr = htonl (INADDR_ANY);
  servent = getservbyname (RADIPA_SERVICE_NAME, RADIPA_SERVICE_PROTO);
  listener_sin.sin_port = (servent ? servent->s_port : htons (RADIPA_SERVICE_PORT));

  while (bind (listener_socket, (struct sockaddr *) &listener_sin, sizeof (listener_sin)) < 0)
    {
     if (errno != EADDRINUSE)
       error (1, errno, "Can't bind listener socket");
     else if (retries++ < MAX_BIND_RETRIES)
       {
	 if (retries == 1)
	   inform ("Listener address in use.  Retrying...");
	 sleep (BIND_RETRY_DELAY_SECONDS);
       }
     else
       error (1, errno, "Can't bind listener socket");
  }
  if (listen (listener_socket, LISTENER_QUEUE_SIZE) < 0)
    error (1, errno, "Can't listen");
}

/* Read state file left behind by the previous incarnation of the
   (presumably crashed) daemon.  */

void
recover_allocation_state ()
{
  FILE *state_FILE;
  char *name;
  int total_addresses = 0;
  int total_routers = 0;
  char CONST *dir_name;

  if (chdir (dir_name = "/var/run") < 0
      && chdir (dir_name = "/var/adm") < 0
      && chdir (dir_name = "/usr/adm") < 0
      && chdir (dir_name = "/etc") < 0)
    error (1, errno, "Can't chdir to /var/run, /var/adm, /usr/adm or /etc");

  state_FILE = fopen (state_file_name, "r");
  name = malloc (strlen (state_file_name) + 2);
  sprintf (name, "%s+", state_file_name);
  new_state_file_name = name;

  if (state_FILE == 0)
    {
      error (0, errno, "Can't open `%s/%s' for reading", dir_name, state_file_name);
      inform ("Allocation state of IP addresses not recovered");
    }
  else
    {
      long router_count = read_long (state_FILE);
      total_routers = router_count;
      while (router_count--)
	{
	  ipaddr_t router_address = read_ip_address (state_FILE);
	  struct router_allocs *allocs = find_router_allocs (router_address);
	  long address_count = read_long (state_FILE);
	  total_addresses += address_count;
	  while (address_count--)
	    {
	      ipaddr_t ip_address = read_ip_address (state_FILE);
	      ipaddr_t *ip_address_ptr = MALLOC (ipaddr_t, 1);
	      set_bit_in_address_map (ip_address);
	      *ip_address_ptr = ip_address;
	      hash_insert (&allocs->address_table, ip_address_ptr);
	    }
	}
      inform ("Recovered allocation state: %d IP address%s in use by %d router%s",
	      total_addresses, (total_addresses == 1 ? "" : "es"),
	      total_routers, (total_routers == 1 ? "" : "s"));
      fclose (state_FILE);
    }
}

/* Arrange for periodic processing via SIGALRM, and for saving
   allocation state on receipt of fatal signals.  */

void
setup_signal_handlers ()
{
#ifdef SOLARIS
/*
//	This error is weird - under solaris NSIG is defined in sys/signal.h
//	however, it doesn't seem to be added - you might want to look into
//	the correct defines and whatnot to pass though the many
//	#ifdef(s) in that include file. Meanwhile, here's a nasty fix
//	to get it to compile:
*/
#define NSIG    45  /* valid signals range from 1 to NSIG-1 */
#endif
  int sig;
  for (sig = 0; sig < NSIG; sig++)
    {
      switch (sig)
	{
	case SIGSTOP:
	case SIGTSTP:
	case SIGCONT:
	case SIGCHLD:
	case SIGWINCH:
	case SIGHUP:
	  signal (sig, SIG_IGN);
	  break;
	case SIGALRM:
	  signal (sig, catch_alarm_signal);
	  break;
	default:
	  signal (sig, catch_fatal_signal);
	  break;
	}
    }
}

void
catch_fatal_signal (sig)
     int sig;
{
  error (1, 0, "Caught fatal signal %d", sig);
}

void
catch_alarm_signal (sig)
     int sig;
{
  timer_expired = 1;
}

void
init_hash_tables ()
{
  hash_init (&address_map_table, 32,
	     address_map_hash_1, address_map_hash_2, address_map_hash_compare);
  hash_init (&router_allocs_table, 16,
	     router_allocs_hash_1, router_allocs_hash_2, router_allocs_hash_compare);
  hash_init (&most_recent_table, 32,
	     most_recent_hash_1, most_recent_hash_2,
	     most_recent_hash_compare);
}

#define IP_ADDRESS_HASH_1(addr) \
  (((unsigned char CONST *) &(addr))[0] \
   ^ (((unsigned char CONST *) &(addr))[1] << 1) \
   ^ (((unsigned char CONST *) &(addr))[2] << 2) \
   ^ (((unsigned char CONST *) &(addr))[2] << 3))
#define IP_ADDRESS_HASH_2(addr) \
  (((unsigned char CONST *) &(addr))[0] \
   ^ (((unsigned char CONST *) &(addr))[1]) \
   ^ (((unsigned char CONST *) &(addr))[2]) \
   ^ (((unsigned char CONST *) &(addr))[2]))

unsigned long
address_map_hash_1 (key)
     void CONST *key;
{
  return IP_ADDRESS_HASH_1 (((struct address_map CONST *) key)->prefix_address);
}

unsigned long
address_map_hash_2 (key)
     void CONST *key;
{
  return IP_ADDRESS_HASH_2 (((struct address_map CONST *) key)->prefix_address);
}

int
address_map_hash_compare (x, y)
     void CONST *x;
     void CONST *y;
{
  return (((struct address_map CONST *) x)->prefix_address
	  - ((struct address_map CONST *) y)->prefix_address);
}

unsigned long
router_allocs_hash_1 (key)
     void CONST *key;
{
  return IP_ADDRESS_HASH_1 (((struct router_allocs CONST *) key)->router_address);
}

unsigned long
router_allocs_hash_2 (key)
     void CONST *key;
{
  return IP_ADDRESS_HASH_2 (((struct router_allocs CONST *) key)->router_address);
}

int
router_allocs_hash_compare (x, y)
     void CONST *x;
     void CONST *y;
{
  /* return non-zero (logical TRUE) when the addresses are unequal.
     Subtraction won't work here because of the danger of overflow.  */
  return (((struct router_allocs CONST *) x)->router_address
	  != ((struct router_allocs CONST *) y)->router_address);
}

unsigned long
most_recent_hash_1 (key)
     void CONST *key;
{
  struct most_recent CONST *recent = (struct most_recent CONST *) key;
  struct address_chunk *chunks = recent->chunks;
  int count = recent->count;
  unsigned long result = 0;
  while (count--)
    result ^= (IP_ADDRESS_HASH_1 (chunks->base_address) ^ chunks->count);
  return result;
}

unsigned long
most_recent_hash_2 (key)
     void CONST *key;
{
  struct most_recent CONST *recent = (struct most_recent CONST *) key;
  struct address_chunk *chunks = recent->chunks;
  int count = recent->count;
  unsigned long result = 0;
  while (count--)
    result ^= (IP_ADDRESS_HASH_2 (chunks->base_address) ^ ~chunks->count);
  return result;
}

int
most_recent_hash_compare (x, y)
     void CONST *x;
     void CONST *y;
{
  struct most_recent CONST *recent_x = (struct most_recent CONST *) x;
  struct most_recent CONST *recent_y = (struct most_recent CONST *) y;
  int count = recent_x->count;
  int result = count - recent_y->count;

  if (result == 0)
    {
      struct address_chunk *chunks_x = recent_x->chunks;
      struct address_chunk *chunks_y = recent_y->chunks;
      while (count--)
	{
	  result = chunks_x->count - chunks_y->count;
	  if (result)
	    return result;
	  /* return non-zero (logical TRUE) when the addresses are unequal.
	     Subtraction won't work here because of the danger of overflow.  */
	  if (chunks_x->base_address != chunks_y->base_address)
	    return 1;
	}
    }
  return result;
}

struct most_recent *
find_most_recent (chunks, count)
     struct address_chunk *chunks;
     long count;
{
  struct most_recent key;
  struct most_recent *recent;
  struct most_recent **slot;

  key.chunks = chunks;
  key.count = count;
  slot = (struct most_recent **) hash_find_slot (&most_recent_table, &key);
  recent = *slot;
  if (HASH_VACANT (recent))
    {
      recent = MALLOC (struct most_recent, 1);
      recent->count = count;
      recent->chunks = MALLOC (struct address_chunk, count);
      memcpy (recent->chunks, chunks, sizeof (struct address_chunk) * count);
      recent->current_ip_address = recent->chunks->base_address - 1;
      recent->current_chunk = recent->chunks;
      hash_insert_at (&most_recent_table, recent, slot);
    }
  return recent;
}

/* Poll routers for addresses in use by active connections.  */

void
get_addresses_in_use ()
{
  /* FIXME */
#if 0
  timer_expired = 0;
  alarm (minutes_between_polls * 60);
#endif
}

/* Loop on active radiusd sockets servicing requests.  */

void
service_radiusd_requests ()
{
  int *socks = radiusd_sockets;

  if (is_socket (0))
    *radiusd_sockets_end++ = 0;
  for (;;)
    {
      fd_set reads;
      int result;
#define SHUTDOWN_CURRENT_SOCKET() do { \
	close (*socks); \
	*socks-- = *--radiusd_sockets_end; \
    } while (0)

      FD_ZERO (&reads);
      FD_SET (listener_socket, &reads);
      for (socks = radiusd_sockets; socks < radiusd_sockets_end; socks++)
	FD_SET (*socks, &reads);

      result = select (FD_SETSIZE, &reads, 0, 0, 0);
      if (result < 0)
	{
	  if (errno == EBADF)
	    {
	      for (socks = radiusd_sockets; socks < radiusd_sockets_end; socks++)
		if (!is_socket (*socks))
		  {
		    inform ("Closing moribund socket");
		    SHUTDOWN_CURRENT_SOCKET ();
		  }
	    }
	  else if (errno != EINTR)
	    error (0, errno, "Can't select");
	  if (timer_expired)
	    get_addresses_in_use ();
	}
      else
	{
	  if (FD_ISSET (listener_socket, &reads))
	    {
	      struct sockaddr_in new_sin;
	      int length = sizeof (new_sin);
	      int new_socket = accept (listener_socket, (struct sockaddr *) &new_sin, &length);
	      if (new_socket < 0)
		error (0, errno, "Can't accept");
	      else
		{
		  *radiusd_sockets_end++ = new_socket;
		  inform ("Accepting connection to %s",
			  get_peer_host_name (new_socket));
		}
	    }
	  for (socks = radiusd_sockets; socks < radiusd_sockets_end; socks++)
	    {
	      int sock = *socks;
	      int bytes_received;
	      if (!FD_ISSET (sock, &reads))
		continue;
	      bytes_received = service_one_radiusd_request (sock);
	      if (bytes_received <= 0)
		{
		  char CONST *peer_name = get_peer_host_name (sock);
		  if (bytes_received < 0)
		    error (0, errno, "Can't read from %s", peer_name);
		  inform ("Closing connection to %s", peer_name);
		  SHUTDOWN_CURRENT_SOCKET ();
		}
	    }
	}
    }
}

/* Handle a single remote procedure call: decode, process and return
   results to remote radiusd.  */

int
service_one_radiusd_request (sock)
     int sock;
{
  char buf[RADIPA_BUFFER_SIZE];
  int bytes_received;
  struct radipa_packet *request;
  ipaddr_t ip_address;

  bytes_received = read (sock, buf, sizeof (buf));
  if (bytes_received <= 0)
    return bytes_received;
  request = radipa_packet_reorder_integers (buf);

  switch (request->rp_code)
    {
    case RADIPA_ALLOCATE:
      ip_address = allocate_ip_address (request->rp_router_address,
					request->rp_chunks, request->rp_count);
      return_ip_address (sock, request, ip_address);
      break;

    case RADIPA_RELEASE:
      release_ip_address (request->rp_router_address, request->rp_ip_address);
      ack_release (sock, request);
      break;

    case RADIPA_POLL_ROUTERS:
      poll_routers_for_addresses (request->rp_client_addresses, request->rp_count);
      ack_router_addresses (sock, request);
      break;

    case RADIPA_ROUTER_ADDRESSES:
      mark_ip_addresses_as_allocated (get_peer_ip_address (sock),
				      request->rp_client_addresses, request->rp_count);
      ack_mark_addresses (sock, request);
      break;

    default:
      error (0, 0, "Unknown request from RADIUS: %d", *buf);
      break;
    }
  return bytes_received;
}

/* Convert a request block from network to host byte ordering in situ.  */

struct radipa_packet *
radipa_packet_reorder_integers (buf)
     char *buf;
{
  struct radipa_packet *request = (struct radipa_packet *) buf;

  request->rp_count = ntohs (request->rp_count);
  request->rp_ip_address = ntohl (request->rp_ip_address);
  request->rp_router_address = ntohl (request->rp_router_address);
  if (request->rp_code == RADIPA_ALLOCATE)
    {
      struct address_chunk *chunk = request->rp_chunks;
      struct address_chunk *end = &chunk[request->rp_count];
      for ( ; chunk < end; chunk++)
	{
	  chunk->base_address = ntohl (chunk->base_address);
	  chunk->count = ntohl (chunk->count);
	}
    }
  else if (request->rp_code == RADIPA_POLL_ROUTERS)
    {
      ipaddr_t *address = request->rp_client_addresses;
      ipaddr_t *end = &address[request->rp_count];
      for ( ; address < end; address++)
	*address = ntohl (*address);
    }
  return request;
}

/* Find a free address from the address ranges encoded in CHUNKS.  */

ipaddr_t
allocate_ip_address (router_address, chunks, count)
     ipaddr_t router_address;
     struct address_chunk *chunks;
     long count;
{
  struct most_recent *recent = find_most_recent (chunks, count);
  struct address_chunk *chunk = recent->current_chunk;
  ipaddr_t ip_address = recent->current_ip_address + 1;
  if (count == 0)
    return 0;
  count++;
  while (count--)
    {
      ip_address = allocate_from_chunk (chunk, ip_address);
      if (ip_address)
	{
	  router_allocs_insert (router_address, ip_address);
	  if (verbose_flag)
	    inform ("Address %s allocated to session on %s",
		    inet_dot_addr (htonl (ip_address)),
		    get_host_name_from_address (htonl (router_address)));
	  recent->current_chunk = chunk;
	  recent->current_ip_address = ip_address;
	  return htonl (ip_address);
	}
      chunk++;
      if (chunk == &recent->chunks[recent->count])
	chunk = recent->chunks;
    }
  error (0, 0, "can't allocate ip address: all addresses in use");
  return 0;
}

void
return_ip_address (sock, request, ip_address)
     int sock;
     struct radipa_packet *request;
     ipaddr_t ip_address;
{
  struct radipa_packet response;
  
  response.rp_code = request->rp_code;
  response.rp_pad = ~0;
  response.rp_count = 0;
  response.rp_handle = request->rp_handle;
  response.rp_ip_address = ip_address;
  response.rp_router_address = request->rp_router_address;
  errno = 0;
  if (delay_seconds)
    sleep (delay_seconds);
  if (send (sock, (char *)&response, sizeof_RADIPA, 0) != sizeof_RADIPA)
    error (0, errno, "can't send newly allocated ip address to %s",
	   get_peer_host_name (sock));
}

struct router_allocs *
find_router_allocs (router_address)
     ipaddr_t router_address;
{
  struct router_allocs key;
  struct router_allocs *allocs;
  struct router_allocs **slot;
  
  key.router_address = router_address;
  slot = (struct router_allocs **) hash_find_slot (&router_allocs_table, &key);
  allocs = *slot;
  if (HASH_VACANT (allocs))
    {
      allocs = CALLOC (struct router_allocs, 1);
      allocs->router_address = key.router_address;
      hash_init (&allocs->address_table, 64,
		 ipaddr_hash_1, ipaddr_hash_2, ipaddr_hash_compare);
      hash_insert_at (&router_allocs_table, allocs, slot);
    }
  return allocs;
}

unsigned long
ipaddr_hash_1 (key)
     void CONST *key;
{
  unsigned char CONST *bytes = (unsigned char CONST *) key;
  return (bytes[0] ^ (bytes[1] << 1) ^ (bytes[2] << 2) ^ (bytes[2] << 3));
}

unsigned long
ipaddr_hash_2 (key)
     void CONST *key;
{
  unsigned char CONST *bytes = (unsigned char CONST *) key;
  return (bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]);
}

int
ipaddr_hash_compare (x, y)
     void CONST *x;
     void CONST *y;
{
  return (*(ipaddr_t CONST *) x - *(ipaddr_t CONST *) y);
}

/* Add a newly allocated address to the table of addresses associated
   with ROUTER_ADDRESS.  */

void
router_allocs_insert (router_address, ip_address)
     ipaddr_t router_address;
     ipaddr_t ip_address;
{
  struct router_allocs *allocs = find_router_allocs (router_address);
  ipaddr_t **slot = (ipaddr_t **) hash_find_slot (&allocs->address_table, &ip_address);
  if (HASH_VACANT (*slot))
    {
      ipaddr_t *ip_address_ptr = MALLOC (ipaddr_t, 1);
      *ip_address_ptr = ip_address;
      hash_insert_at (&allocs->address_table, ip_address_ptr, slot);
    }
  else
    error (0, 0, "Duplicate allocation to router");
  update_allocation_state ();
}

/* Remove a newly released address from the table of addresses associated
   with ROUTER_ADDRESS.  */

void
router_allocs_delete (router_address, ip_address)
     ipaddr_t router_address;
     ipaddr_t ip_address;
{
  struct router_allocs *allocs = find_router_allocs (router_address);
  ipaddr_t **slot = (ipaddr_t **) hash_find_slot (&allocs->address_table, &ip_address);
  if (HASH_VACANT (*slot))
    error (0, 0, "Missing allocation to router");
  hash_delete_at (&allocs->address_table, slot);
  update_allocation_state ();
}

#define ADDR_SUFFIX_SHIFT 8
#define ADDR_SUFFIX_RANGE (1 << ADDR_SUFFIX_SHIFT)
#define ADDR_SUFFIX_MASK (ADDR_SUFFIX_RANGE - 1)
#define ADDR_PREFIX_MASK (~ADDR_SUFFIX_MASK)
#define ADDR_SUFFIX_MAP_BYTES (ADDR_SUFFIX_RANGE / 8)
#ifndef MIN
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
#endif

ipaddr_t
allocate_from_chunk (chunk, base_address)
     struct address_chunk *chunk;
     ipaddr_t base_address;
{
  struct address_map *map;
  int count;

  if (base_address == 0)
    base_address = chunk->base_address;
  count = chunk->count - (base_address - chunk->base_address);
  if (count <= 0)
    return 0;
  for (;;)
    {
      int first_bit_offset = (base_address & ADDR_SUFFIX_MASK);
      int sentinel_bit_offset = MIN (ADDR_SUFFIX_RANGE, first_bit_offset + count);
      int addrs_in_chunk = (ADDR_SUFFIX_RANGE - first_bit_offset);

      map = find_address_map (base_address);
      first_bit_offset = bitmap_set_first_zero (map->bitmap, first_bit_offset,
						sentinel_bit_offset);
      if (first_bit_offset >= 0)
	return (ADDR_PREFIX_MASK & base_address) + first_bit_offset;

      count -= addrs_in_chunk;
      if (count <= 0)
	return 0;
      base_address += addrs_in_chunk;
    }
}

struct address_map *
find_address_map (ip_address)
     ipaddr_t ip_address;
{
  struct address_map key;
  struct address_map **slot;
  
  key.prefix_address = (ip_address >> ADDR_SUFFIX_SHIFT);
  slot = (struct address_map **) hash_find_slot (&address_map_table, &key);
  if (HASH_VACANT (*slot))
    {
      struct address_map *map = CALLOC (struct address_map, 1);
      map->prefix_address = key.prefix_address;
      hash_insert_at (&address_map_table, map, slot);
    }
  return *slot;
}

void
clear_bit_in_address_map (ip_address)
     ipaddr_t ip_address;
{
  struct address_map *map = find_address_map (ip_address);
  int bit_offset = (ip_address & ADDR_SUFFIX_MASK);

  if (BITMAP_BIT_IS_SET (map->bitmap, bit_offset))
    BITMAP_BIT_CLEAR (map->bitmap, bit_offset);
  else
    error (0, 0, "Releasing unallocated address: %s", inet_dot_addr (htonl (ip_address)));
}

void
set_bit_in_address_map (ip_address)
     ipaddr_t ip_address;
{
  struct address_map *map = find_address_map (ip_address);
  int bit_offset = (ip_address & ADDR_SUFFIX_MASK);

  if (BITMAP_BIT_IS_SET (map->bitmap, bit_offset))
    error (0, 0, "Allocating allocated address: %s", inet_dot_addr (htonl (ip_address)));
  else
    BITMAP_BIT_SET (map->bitmap, bit_offset);
}

int
bitmap_set_first_zero (bitmap, first_bit_offset, sentinel_bit_offset)
     bitmap_int_t *bitmap;
     int first_bit_offset;
     int sentinel_bit_offset;
{
  int mask;
  int head_bit_offset = BITMAP_BIT_OFFSET (first_bit_offset);
  int tail_bit_offset = BITMAP_BIT_OFFSET (sentinel_bit_offset);
  bitmap_int_t *bitmap_tail = &bitmap[BITMAP_WORD_OFFSET (sentinel_bit_offset)];

  bitmap += BITMAP_WORD_OFFSET (first_bit_offset);

  if (head_bit_offset)
    {
      mask = ((BITMAP_BIT_MASK << head_bit_offset) & BITMAP_BIT_MASK);
      if (bitmap == bitmap_tail)
	mask &= (BITMAP_BIT_MASK >> (BITMAP_BITS_PER_WORD - tail_bit_offset));
      if ((*bitmap & mask) != mask)
	return bitmap_set_first_zero_1 (bitmap, first_bit_offset);
      else if (bitmap == bitmap_tail)
	return -1;
      first_bit_offset += (BITMAP_BITS_PER_WORD - head_bit_offset);
      bitmap++;
    }
  while (bitmap < bitmap_tail && *bitmap == BITMAP_BIT_MASK)
    {
      bitmap++;
      first_bit_offset += BITMAP_BITS_PER_WORD;
    }
  mask = BITMAP_BIT_MASK;
  if (bitmap == bitmap_tail)
    {
      if (tail_bit_offset)
	mask >>= (BITMAP_BITS_PER_WORD - tail_bit_offset);
      else
	return -1;
    }
  if ((*bitmap & mask) != mask)
    return bitmap_set_first_zero_1 (bitmap, first_bit_offset);
  return -1;
}

int
bitmap_set_first_zero_1 (bitmap, offset)
     bitmap_int_t *bitmap;
     int offset;
{
  int bit = BITMAP_BIT (offset);
  while (bit & BITMAP_BIT_MASK)
    {
      if (!(bit & *bitmap))
	{
	  *bitmap |= bit;
	  return offset;
	}
      bit <<= 1;
      offset += 1;
    }
  return -1;			/* can't happen */
}

void
release_ip_address (router_address, ip_address)
     ipaddr_t router_address;
     ipaddr_t ip_address;
{
  if (ip_address == INADDR_BROADCAST)
    {
      struct router_allocs *allocs = find_router_allocs (router_address);
      hash_iterate (&allocs->address_table, release_orphaned_ip_address, 0);
      if (verbose_flag)
	inform ("Releasing all addresses allocated to %s",
		get_host_name_from_address (htonl (router_address)));
      hash_delete_all (&allocs->address_table);
    }
  else
    {
      char CONST *freed_address = inet_dot_addr (htonl (ip_address));
      char CONST *router_name = get_host_name_from_address (htonl (router_address));
      if (verbose_flag)
	inform ("Attempting release of address %s held by %s", freed_address, router_name);
      clear_bit_in_address_map (ip_address);
      router_allocs_delete (router_address, ip_address);
      if (verbose_flag)
	inform ("Address %s released by %s", freed_address, router_name);
    }
}

void
release_orphaned_ip_address (payload, unused)
     void *payload;
     void *unused;
{
  ipaddr_t ip_address = *(ipaddr_t *) payload;
  clear_bit_in_address_map (ip_address);
  free (payload);
}

void
ack_release (sock, request)
     int sock;
     struct radipa_packet *request;
{
  struct radipa_packet response;
  
  response.rp_code = request->rp_code;
  response.rp_pad = ~0;
  response.rp_count = 0;
  response.rp_handle = request->rp_handle;
  response.rp_ip_address = request->rp_ip_address;
  response.rp_router_address = request->rp_router_address;
  errno = 0;
  if (send (sock, (char *)&response, sizeof_RADIPA, 0) != sizeof_RADIPA)
    error (0, errno, "can't ACK newly released ip address from %s",
	   get_peer_host_name (sock));
}

void
poll_routers_for_addresses (ip_addresses, count)
     ipaddr_t *ip_addresses;
     long count;
{
  /* FIXME
     for each address in ip_addresses,
     fire off a request for radipad'ed addresses
     */
}

void
ack_router_addresses (sock, request)
     int sock;
     struct radipa_packet *request;
{
  /* FIXME */
}

void
mark_ip_addresses_as_allocated (router_address, ip_addresses, count)
     ipaddr_t router_address;
     ipaddr_t *ip_addresses;
     long count;
{
  /* FIXME
     find router_allocs
     sort ip_addresses
     dump sorted allocs->address_table
     compare sorted address arrays and report discrepancies
     if different, update allocs->address_table
     */
}

void
ack_mark_addresses (sock, request)
     int sock;
     struct radipa_packet *request;
{
  /* FIXME */
}

/* Record the state of the allocator on an external file.
   Store all counts and ip-addresses in network byte order. */

void
update_allocation_state ()
{
  FILE *state_FILE = fopen (new_state_file_name, "w");
  if (state_FILE == 0)
    error (0, errno, "Can't open `%s' for writing", state_file_name);
  else
    {
      long count = 0;
      hash_iterate (&router_allocs_table, count_routers_with_addresses, &count);
      write_long (state_FILE, count);
      hash_iterate (&router_allocs_table, write_router_allocation_state, state_FILE);
      fsync (fileno (state_FILE));
      fclose (state_FILE);
      if (verbose_flag)
	inform ("Renaming new state file as `%s'", state_file_name);
      rename (new_state_file_name, state_file_name);
    }
}

void
count_routers_with_addresses (payload, arg)
     void *payload;
     void *arg;
{
  struct router_allocs *allocs = (struct router_allocs *) payload;
  if (allocs->address_table.ht_cardinality)
    (*(long *) arg)++;
}

void
write_router_allocation_state (payload, arg)
     void *payload;
     void *arg;
{
  struct router_allocs *allocs = (struct router_allocs *) payload;
  int count = allocs->address_table.ht_cardinality;
  if (count)
    {
      ipaddr_t **ip_addresses_0 = (ipaddr_t **) hash_dump (&allocs->address_table, 0);
      ipaddr_t **ip_addresses = ip_addresses_0;
      FILE *state_FILE = (FILE *) arg;

      write_ip_address (state_FILE, allocs->router_address);
      write_long (state_FILE, count);
      while (count--)
	write_ip_address (state_FILE, **ip_addresses++);
      free (ip_addresses_0);
    }
}

long
read_long (in_FILE)
     FILE *in_FILE;
{
  long l;
  fread (&l, sizeof (l), 1, in_FILE);
  return ntohl (l);
}

ipaddr_t
read_ip_address (in_FILE)
     FILE *in_FILE;
{
  ipaddr_t ip_address;
  fread (&ip_address, sizeof (ip_address), 1, in_FILE);
  return ntohl (ip_address);
}

void
write_long (out_FILE, l)
     FILE *out_FILE;
     long l;
{
  l = htonl (l);
  fwrite (&l, sizeof (l), 1, out_FILE);
}

void
write_ip_address (out_FILE, ip_address)
     FILE *out_FILE;
     ipaddr_t ip_address;
{
  ip_address = htonl (ip_address);
  fwrite (&ip_address, sizeof (ip_address), 1, out_FILE);
}


/* convert an interval string to an integer number of minutes */

int
parse_interval (interval)
     char *interval;
{
  int minutes = strtol (interval, &interval, 0);
  if (tolower (*interval) == 'h')
    minutes *= 60;
  else if (*interval == ':')
    {
      minutes *= 60;
      minutes += strtol (++interval, 0, 0);
    }
  return (minutes ? minutes : MINUTES_BETWEEN_POLLS);
}

/* Return 1 if SOCK has input within the timeout period of SECONDS.
   Return 0 if no input is available after SECONDS have expired.
   Return -1 if select(2) has an error.  */

int
sock_has_input (sock, seconds)
     int sock;
     int seconds;
{
  fd_set set;
  struct timeval timeout;
     
  FD_ZERO (&set);
  FD_SET (sock, &set);
  timeout.tv_sec = seconds;
  timeout.tv_usec = 0;
  return select (FD_SETSIZE, &set, 0, 0, ((seconds < 0) ? 0 : &timeout));
}

/* Return the name of the host at the other end of SOCK.  If can find
   the host's address but not its name, return the address in dotted
   decimal notation.  */

char CONST *
get_peer_host_name (sock)
     int sock;
{
  ipaddr_t ip_address = get_peer_ip_address (sock);
  return get_host_name_from_address (ip_address);
}

char CONST *
get_host_name_from_address (ip_address)
     ipaddr_t ip_address;
{
  static char buf_0[128][4];
  static int slot = 0;
  char *buf = buf_0[slot = (slot + 1) % 4];

  if (ip_address == INADDR_NONE)
    strcpy (buf, "UNKNOWN");
  else
    {
      struct hostent *hostent = gethostbyaddr ((char *) &ip_address,
					       sizeof (ip_address), AF_INET);
      if (hostent)
	strcpy (buf, hostent->h_name);
      else
	buf = inet_dot_addr (ip_address);
    }
  return buf;
}

ipaddr_t
get_peer_ip_address (sock)
     int sock;
{
  struct sockaddr_in crashed_sin;
  int length = sizeof (crashed_sin);

  if (getpeername (sock, (struct sockaddr *) &crashed_sin, &length) < 0)
    {
      error (0, errno, "Can't get peer's address");
      return INADDR_NONE;
    }
  else
    return crashed_sin.sin_addr.s_addr;
}

/* Return a string representation of an IP address in dotted notation.
   If BUF is non-zero, place the result there.  Otherwise, write the
   result into a static buffer that's overwritten with each call.  */

char *
inet_dot_addr (ip_address)
     ipaddr_t ip_address;
{
  static char buf_0[16][4];
  static int slot = 0;
  char *buf = buf_0[slot = (slot + 1) % 4];
  unsigned char *ip_addr_bytes = (unsigned char *) &ip_address;

  sprintf(buf, "[%d.%d.%d.%d]",
	  ip_addr_bytes[0], ip_addr_bytes[1],
	  ip_addr_bytes[2], ip_addr_bytes[3]);
  return buf;
}

/* Is FD a socket?  */

int
is_socket (fd)
     int fd;
{
  int dummy;
  int length = sizeof (dummy);
  return (0 == getsockopt (fd, SOL_SOCKET, SO_TYPE, (char *)&dummy, &length));
}


/* Report an error and optionally terminate the program.  */

void
#if __STDC__ == 1
error (int exit_status, int error_number, char CONST *format, ...)
#else
error (va_alist) va_dcl
#endif
{
  va_list args;
#if __STDC__ == 1
  va_start (args, format);
#else
  int exit_status;
  int error_number;
  char CONST *format;
  va_start (args);
  exit_status = va_arg (args, int);
  error_number = va_arg (args, int);
  format = va_arg (args, char *);
#endif
  if (syslog_flag)
    error_syslog (error_number, format, args);
  else
    error_stdio (error_number, format, args);
  va_end (args);

  if (exit_status)
    exit (exit_status);
}

void
error_stdio (error_number, format, args)
     int error_number;
     char CONST *format;
     void *args;
{
  fflush (stdout);
  prefix_date_time (stderr);
  vfprintf (stderr, format, args);
  if (error_number)
    fprintf (stderr, ": %s", xstrerror (error_number));
  putc ('\n', stderr);
  fflush (stderr);
}

void
error_syslog (error_number, format, args)
     int error_number;
     char CONST *format;
     void *args;
{
  char format_buf[BUFSIZ];
  if (error_number)
    {
      sprintf (format_buf, "%s: %%m", format);
      format = format_buf;
    }
  vsyslog (LOG_ERR, format, args);
}

/* Report an infomrative message (not an error).  */

void
#if __STDC__ == 1
inform (char CONST *format, ...)
#else
inform (va_alist) va_dcl
#endif
{
  va_list args;
#if __STDC__ == 1
  va_start (args, format);
#else
  char CONST *format;
  va_start (args);
  format = va_arg (args, char *);
#endif
  if (syslog_flag)
    inform_syslog (format, args);
  else
    inform_stdio (format, args);
  va_end (args);
}

void
inform_stdio (format, args)
     char CONST *format;
     void *args;
{
  fflush (stderr);
  prefix_date_time (stdout);
  vprintf (format, args);
  putchar ('\n');
  fflush (stdout);
}

void
inform_syslog (format, args)
     char CONST *format;
     void *args;
{
  vsyslog (LOG_INFO, format, args);
}

/* Print a debug message.  */

void
#if __STDC__ == 1
debug (char CONST *format, ...)
#else
debug (va_alist) va_dcl
#endif
{
  if (!syslog_flag) {
    va_list args;
#if __STDC__ == 1
    va_start (args, format);
#else
    char CONST *format;
    va_start (args);
    format = va_arg (args, char *);
#endif
    fflush (stderr);
    prefix_date_time (stdout);
    vprintf (format, args);
    va_end (args);
    fflush (stdout);
  }
}

void
prefix_date_time (stream)
     FILE *stream;
{
  struct timeval tv;
  struct tm *tm;
  static char CONST *CONST month_names[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };

#if defined(_SVID_GETTOD) || defined(aix)
  gettimeofday(&tv);
#else
  gettimeofday(&tv, 0);
#endif
  tm = localtime ((time_t *) &tv.tv_sec);
  fprintf (stream, "%s %d %02d:%02d:%02d.%03d radipad[%d] ",
	   month_names[tm->tm_mon], tm->tm_mday,
	   tm->tm_hour, tm->tm_min, tm->tm_sec,
	   tv.tv_usec / 1000, getpid());
}
