/*
 *      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: radipa.c,v 1.2 1996/12/12 00:04:50 baskar Exp $ */

/* Interface library for clients of radipad. */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include "radius.h"
#include "protos.h"
#include "radipa.h"

int radipa_socket = -1;
ipaddr_t *radipa_routers;
static char white_space[] = " \t\r\n";

int radipa_bind_socket P__((void));
int radipa_connect_to P__((ipaddr_t radipad_address,
			   CONST char *name, CONST char *proto, int port));

int
radipa_bind_socket ()
{
  int sock;
  struct sockaddr_in client_sin;

  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    return -1;
  client_sin.sin_family = AF_INET;
  client_sin.sin_addr.s_addr = htonl (INADDR_ANY);
  client_sin.sin_port = 0;

  if (bind (sock, (struct sockaddr *) &client_sin, sizeof (client_sin)) < 0)
    {
      log_err ("Can't bind to local radipa socket: %s\n", xstrerror (errno));
      close (sock);
      return -1;
    }
  return sock;
}

int
radipa_connect_to (radipad_address, name, proto, port)
     ipaddr_t radipad_address;
     CONST char *name;
     CONST char *proto;
     int port;
{
  struct sockaddr_in server_sin;
  struct servent *servent;
  int sock;

  server_sin.sin_family = AF_INET;
  server_sin.sin_addr.s_addr = radipad_address;

  sock = radipa_bind_socket ();
  if (sock < 0)
    return -1;
  servent = getservbyname (name, proto);
  server_sin.sin_port = (servent ? servent->s_port : port);
  if (connect (sock, (struct sockaddr *) &server_sin, sizeof (server_sin)) < 0)
    {
      close (sock);
      return -1;
    }
  return sock;
}

int
radipa_connect (radipad_address)
     ipaddr_t radipad_address;
{
  int sock;

  sock = radipa_connect_to (radipad_address, RADIPA_SERVICE_NAME,
			    RADIPA_SERVICE_PROTO, htons (RADIPA_SERVICE_PORT));
#if RADIPA_STARTUP_NAME
  if (sock < 0)
    sock = radipa_connect_to (radipad_address, RADIPA_STARTUP_NAME,
			      RADIPA_STARTUP_PROTO, htons (RADIPA_STARTUP_PORT));
#endif
  if (sock < 0)
    log_err ("Can't connect to radipad: %s\n", xstrerror (errno));
  return sock;
}

int
radipa_request_allocate_ip_address (sock, handle, router_address, chunks, count)
     int sock;
     void *handle;
     ipaddr_t router_address;
     struct address_chunk *chunks;
     int count;
{
  struct radipa_packet packet;
  int bytes_sent;

  packet.rp_code = RADIPA_ALLOCATE;
  packet.rp_pad = ~0;
  packet.rp_ip_address = htonl (INADDR_NONE);
  packet.rp_router_address = router_address;
  packet.rp_count = htons (count);
  packet.rp_handle = handle;
  memcpy (packet.rp_chunks, chunks, sizeof (struct address_chunk) * count);
  bytes_sent = send (sock, (char *)&packet, sizeof_RADIPA_ALLOCATE (count), 0);
  if (bytes_sent < 0)
    log_err ("Can't send ip address allocation request: %s\n", xstrerror (errno));
  return bytes_sent;
}

int
radipa_request_release_ip_address (sock, handle, router_address, ip_address)
     int sock;
     void *handle;
     ipaddr_t router_address;
     ipaddr_t ip_address;
{
  struct radipa_packet packet;
  int bytes_sent;

  packet.rp_code = RADIPA_RELEASE;
  packet.rp_pad = ~0;
  packet.rp_count = 0;
  packet.rp_handle = handle;
  packet.rp_router_address = router_address;
  packet.rp_ip_address = ip_address;
  bytes_sent = send (sock, (char *)&packet, sizeof_RADIPA, 0);
  if (bytes_sent < 0)
    log_err ("Can't send release ip address request: %s\n", xstrerror (errno));
  return bytes_sent;
}

/* Extract the "radipa-hosts" pseudo-user from users file and parse
   the ip addresses.  Return the address of the radipad server as
   hosts_buf[0], and fill in the remainder of the array with the
   addresses of routers that can use radipad.  if hosts_buf_ptr is
   zero, malloc one, otherwise use the buffer given.  */

int
radipa_parse_hosts (hosts_buf_ptr)
     ipaddr_t **hosts_buf_ptr;
{
  ipaddr_t *hosts_buf;
  VALUE_PAIR *check_pairs = 0;
  VALUE_PAIR *reply_pairs = 0;
  VALUE_PAIR *vp;
  int hosts_malloced = 0;
  int count;

  if (user_find (RADIPA_HOSTS_USER, &check_pairs, &reply_pairs))
    {
      log_err ("Pseudo-user `%s' not found\n", RADIPA_HOSTS_USER);
      return 0;
    }
  pairfree (check_pairs);

  if (*hosts_buf_ptr == 0)
    {
      *hosts_buf_ptr = MALLOC (ipaddr_t, MAX_CLIENT_ADDRESSES + 1);
      hosts_malloced = 1;
    }
  hosts_buf = *hosts_buf_ptr + 1;
    
  for (vp = reply_pairs; vp; vp = vp->next)
    {
      if (vp->attribute == ASCEND_ASSIGN_IP_SERVER)
	**hosts_buf_ptr = htonl (vp->lvalue);
      else if (vp->attribute == ASCEND_ASSIGN_IP_CLIENT)
	*hosts_buf++ = htonl (vp->lvalue);
    }
  pairfree (reply_pairs);
  count = hosts_buf - *hosts_buf_ptr;
  if (hosts_malloced)
    *hosts_buf_ptr = REALLOC (*hosts_buf_ptr, ipaddr_t, count);
  return count;
}

int
radipa_init ()
{
  ipaddr_t hosts_0[MAX_CLIENT_ADDRESSES + 1];
  ipaddr_t *hosts = hosts_0;
  int count = radipa_parse_hosts (&hosts);

  if (count == 0)
    return -1;
  return radipa_connect (hosts[0]);
}

int
radipa_parse_users_pool (user_name, chunks_ptr)
     char *user_name;
     struct address_chunk **chunks_ptr;
{
  struct address_chunk *chunks;
  VALUE_PAIR *check_pairs;
  VALUE_PAIR *reply_pairs;
  VALUE_PAIR *vp;
  int chunks_malloced = 0;
  int count;

  if (user_find (user_name, &check_pairs, &reply_pairs))
    {
      log_err ("User `%s' not found\n", user_name);
      return 0;
    }
  pairfree (check_pairs);

  if (*chunks_ptr == 0)
    {
      *chunks_ptr = MALLOC (struct address_chunk, MAX_ADDRESS_CHUNKS);
      chunks_malloced = 1;
    }
  chunks = *chunks_ptr;

  for (vp = reply_pairs; vp; vp = vp->next)
    {
      if (vp->attribute == ASCEND_ASSIGN_IP_GLOBAL_POOL)
	{
	  char pool_name[AUTH_STRING_LEN + 1];
	  strncpy (pool_name, vp->strvalue, vp->size);
	  pool_name[vp->size] = '\0';
	  chunks += radipa_parse_pool (pool_name, chunks);
	}
    }
  pairfree (reply_pairs);

  count = chunks - *chunks_ptr;
  if (chunks_malloced)
    *chunks_ptr = REALLOC (*chunks_ptr, struct address_chunk, count);
  return count;
}

int
radipa_parse_pool (pool_name, chunk_0)
     char *pool_name;
     struct address_chunk *chunk_0;
{
  struct address_chunk *chunk = chunk_0;
  VALUE_PAIR *check_pairs;
  VALUE_PAIR *reply_pairs;
  VALUE_PAIR *vp;

  if (user_find (pool_name, &check_pairs, &reply_pairs))
    {
      log_err ("Pseudo-user `%s' not found\n", pool_name);
      return 0;
    }
  pairfree (check_pairs);
  
  for (vp = reply_pairs; vp; vp = vp->next)
    {
      if (vp->attribute == ASCEND_IP_POOL_DEFINITION)
	{
	  char *base_addr_string;
	  char *count_string;
	  ipaddr_t base_address;
	  long count;

	  vp->strvalue[vp->size] = '\0';
	  base_addr_string = strtok (vp->strvalue, white_space);
	  if (!strchr (base_addr_string, '.'))
	    base_addr_string = strtok (0, white_space);
	  count_string = strtok (0, white_space);
	  base_address = inet_addr (base_addr_string);
	  count = strtol (count_string, 0, 0);
	  if (base_address == INADDR_NONE)
	    log_err ("Bogus pool definition address: %s\n", base_addr_string);
	  else if (count == 0)
	    log_err ("Bogus pool definition count: %s\n", count_string);
	  else
	    {
	      chunk->base_address = base_address;
	      chunk->count = htonl (count);
	      chunk++;
	    }
	}
    }
  pairfree (reply_pairs);
  return chunk - chunk_0;
}

/* 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;
}

#ifdef TESTING

ipaddr_t
radipa_receive_response (int sock)
{
  char buf[RADIPA_BUFFER_SIZE];
  int bytes_received = recv (sock, buf, sizeof (buf), 0);
  if (bytes_received < 0) {
    log_err ("Can't recv from radipad socket: %s\n", xstrerror (errno));
    return 0;
  } else if (bytes_received == 0) {
    log_err ("EOF on radipad socket\n", xstrerror (errno));
    return 0;
  } else {
    struct radipa_packet *packet = (struct radipa_packet *) buf;
    return packet->rp_ip_address;
  }
}

ipaddr_t
radipa_allocate_ip_address (int sock, ipaddr_t router_address, struct address_chunk *chunks, int count)
{
  ipaddr_t ip_address;

  int result = radipa_request_allocate_ip_address (sock, 0, router_address, chunks, count);
  if (result < 0)
    {
      log_err ("Can't send ip address allocation request");
      return 0;
    }
  return radipa_receive_response (sock);
}

void
radipa_release_ip_address (int sock, ipaddr_t router_address, ipaddr_t ip_address)
{
  int result = radipa_request_release_ip_address (sock, 0, router_address, ip_address);
  if (result < 0)
    log_err ("Can't send release ip address request");
  radipa_receive_response (sock);
}

#if __STDC__ == 1
# include <stdarg.h>
#else
# include <varargs.h>
#endif

void vdebugf P__((CONST char *fmt, va_list ap));
ipaddr_t get_addr_from_string P__((char *ip_addr_string));
char *inet_dot_addr P__((char *buf, ipaddr_t ip_address));

/* These globals are expected by various radius library functions we
   need for the stand-alone testing mode */

char *progname;
char *radacct_dir = RADACCT_DIR;
char *radius_dir = RADIUS_DIR;
char *radius_users = RADIUS_USERS;
FILE *errf;

/*
  Commands:

  a(llocate) router-addr user-name
  r(elease) router-addr ip-addr
  */

int
main (argc, argv)
     int argc;
     char **argv;
{
  char buf[BUFSIZ];
  int count;
  int sock;

  errf = stderr;
  progname = (argc--, *argv++);

  if (dict_init())
    return 1;

  sock = radipa_init ();
  if (sock < 1)
    return 1;

  printf ("read line: ");
  fflush (stdout);
  while (fgets (buf, sizeof (buf), stdin))
    {
      char *cmd;
      char *router_addr_string;
      ipaddr_t router_addr;
      char router_addr_dot_buf[16];
      char *ip_addr_string;
      ipaddr_t ip_addr;
      char *user_name;
      struct address_chunk chunks_0[MAX_ADDRESS_CHUNKS];
      struct address_chunk *chunks = chunks_0;

      printf ("%s\n", buf);
      cmd = strtok (buf, white_space);
      router_addr_string = strtok (0, white_space);
      router_addr = get_addr_from_string (router_addr_string);
      inet_dot_addr (router_addr_dot_buf, router_addr);
      switch (*cmd)
	{
	case 'a':
	  user_name = strtok (0, white_space);
	  count = radipa_parse_users_pool (user_name, &chunks);
	  printf ("Assign address to user `%s' through router `%s' [%s] ==> ",
		  user_name, router_addr_string, router_addr_dot_buf);
	  fflush (stdout);
	  ip_addr = radipa_allocate_ip_address (sock, router_addr, chunks, count);
	  printf ("%s\n", inet_dot_addr (0, ip_addr));
	  break;

	case 'r':
	  ip_addr_string = strtok (0, white_space);
	  ip_addr = get_addr_from_string (ip_addr_string);
	  if (ip_addr == INADDR_ANY)
	    ip_addr = INADDR_BROADCAST;
	  if (ip_addr == INADDR_BROADCAST)
	    printf ("Release all address assigned through router `%s' [%s]\n",
		    router_addr_string, router_addr_dot_buf);
	  else
	    printf ("Release address [%s] assigned through router `%s' [%s]\n",
		    inet_dot_addr (0, ip_addr),
		    router_addr_string, router_addr_dot_buf, user_name);
	  radipa_release_ip_address (sock, router_addr, ip_addr);
	  break;

	case 0:
	  break;

	default:
	  log_err ("Unknown command `%s'\n", cmd);
	  break;
	}
    }
  close (sock);

  return 0;
}

void
#if __STDC__ == 1
debugf (CONST char *fmt, ...)
#else
debugf (va_alist) va_dcl
#endif
{
	va_list ap;
#if __STDC__ == 1
	va_start (ap, fmt);
#else
	CONST char *fmt;
	va_start (ap);
	fmt = va_arg(ap, char *);
#endif
	vdebugf (fmt, ap);
	va_end (ap);
}

int
#if __STDC__ == 1
log_err (CONST char *fmt, ...)
#else
log_err (va_alist) va_dcl
#endif
{
	va_list ap;
#if __STDC__ == 1
	va_start (ap, fmt);
#else
	CONST char *fmt;
	va_start (ap);
	fmt = va_arg(ap, char *);
#endif
	vdebugf (fmt, ap);
	va_end (ap);
	return 0;
}

ipaddr_t
get_addr_from_string (ip_addr_string)
     char *ip_addr_string;
{
  ipaddr_t ip_address = inet_addr (ip_addr_string);

  if (ip_address == INADDR_NONE)
    {
      struct hostent *hostent = gethostbyname (ip_addr_string);
      if (!hostent)
	log_err ("%s: unknown host\n", ip_addr_string);
      else
	ip_address = *(ipaddr_t *) hostent->h_addr_list[0];
    }
  return ip_address;
}

/* 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 (buf, ip_address)
     char *buf;
     ipaddr_t ip_address;
{
  static char buf_0[16];
  unsigned char *ip_addr_bytes = (unsigned char *) &ip_address;

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

/* vprintf-like interface to the debug trace.
   Prepends timestamp and pid to each message.  */

void
vdebugf (fmt, ap)
     CONST char *fmt;
     va_list ap;
{
	struct timeval tv;
	struct tm *tm;

#if defined(_SVID_GETTOD) || defined(aix)
	gettimeofday(&tv);
#else
	gettimeofday(&tv, 0);
#endif
	tm = localtime (&tv.tv_sec);
	printf ("%02d:%02d:%02d.%03d [%d] ",
		tm->tm_hour, tm->tm_min, tm->tm_sec,
		tv.tv_usec / 1000, getpid());
	vprintf (fmt, ap);
	fflush (stdout);
}

#endif
