/*
** pat_subnet.c for  in 
** 
** Made by 
** Login   <vianney@epita.fr>
** 
** Started on  Wed Sep  1 06:41:17 1999 
** Last update Thu Oct 28 20:19:36 1999 
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "pat.h"
#include "pat_subnet.h"
#include "pat_32.h"

/* gets the default bit shift of the historical classful addressing,
   Returns IN_CLASSA_NSHIFT, IN_CLASSB_NSHIFT or IN_CLASSC_NSHIFT 
   according address class. Returns 0 if none of the above */
int			in_class_default_nshift(addr)
struct in_addr		*addr;		/* Aligned in_addr struct pointer */
{
  if (IN_CLASSA(UNSAFE_NTOHL(addr->s_addr)))
    return (IN_CLASSA_NSHIFT);
  if (IN_CLASSB(UNSAFE_NTOHL(addr->s_addr)))
    return (IN_CLASSB_NSHIFT);
  if (IN_CLASSC(UNSAFE_NTOHL(addr->s_addr)))
    return (IN_CLASSC_NSHIFT);
  return (0);
}

/* converts a string to a subnet structure.
   It understands the following syntax:
   A.B.C.D[/bits|:netmask]. If bits or netmask aren't specified, it uses
   the historical netmasks. It performs possibly multiple calls to
   inet_addr(3).
   Returns 0 if OK. Might return ERR_PAT_MALFORMED if network address 
   doesn't contain 3 dots. */
t_status		subnet_from_str(str,subnet)
char			*str;		/* A subnet definition */
t_subnet		*subnet;	/* The returned subnet */
{
  char			*bits_str;
  int			nshift;
  char			*mask_str;
  char			safe_str[STR_BUFSIZ];
  int			len;
  t_status		status;

  safe_str[0] = 0;
  if ((status = str_cat_str(safe_str,
			    sizeof (safe_str),
			    str)) != 0)
    return (status);
  if (indexcount(safe_str,'.') != 3)
    return (ERR_PAT_MALFORMED);
  mask_str = NULL;
  if (bits_str = index(safe_str,'/'))
    {
      *bits_str++ = 0;
    }
  else
    if (mask_str = index(safe_str,':'))
      {
	*mask_str++ = 0;
      }
  subnet->addr.s_addr = inet_addr(safe_str);
  if (mask_str)
    {
      subnet->mask.s_addr = inet_addr(mask_str);
    }
  else
    {
      if (bits_str)
	nshift = 32 - atoi(bits_str);
      else
	nshift = in_class_default_nshift(&(subnet->addr));
      subnet->mask.s_addr = ~0 << nshift;
      subnet->mask.s_addr = UNSAFE_HTONL(subnet->mask.s_addr);
    }
  subnet->cable.s_addr = subnet->addr.s_addr & subnet->mask.s_addr;
  subnet->broadcast.s_addr = subnet->addr.s_addr | (~subnet->mask.s_addr);
  return (0);
}

/* converts a netmask to a prefix.
   Returns the prefix (number of bits from 32 to 0). 
   Returns ERR_PAT_HOLE if netmask 
   contains holes */
int			prefix_from_netmask(netmask,status)
struct in_addr		*netmask;	/* An aligned in_addr struct pointer*/
t_status		*status;
{
  switch (UNSAFE_NTOHL(netmask->s_addr))
    {
    case 0xffffffff:      return (32);
    case 0xfffffffe:      return (31);
    case 0xfffffffc:      return (30);
    case 0xfffffff8:      return (29);
    case 0xfffffff0:      return (28);
    case 0xffffffe0:      return (27);
    case 0xffffffc0:      return (26);
    case 0xffffff80:      return (25);
    
    case 0xffffff00:      return (24);
    case 0xfffffe00:      return (23);
    case 0xfffffc00:      return (22);
    case 0xfffff800:      return (21);
    case 0xfffff000:      return (20);
    case 0xffffe000:      return (19);
    case 0xffffc000:      return (18);
    case 0xffff8000:      return (17);

    case 0xffff0000:      return (16);
    case 0xfffe0000:      return (15);
    case 0xfffc0000:      return (14);
    case 0xfff80000:      return (13);
    case 0xfff00000:      return (12);
    case 0xffe00000:      return (11);
    case 0xffc00000:      return (10);
    case 0xff800000:      return (19);
      
    case 0xff000000:      return (8);
    case 0xfe000000:      return (7);
    case 0xfc000000:      return (6);
    case 0xf8000000:      return (5);
    case 0xf0000000:      return (4);
    case 0xe0000000:      return (3);
    case 0xc0000000:      return (2);
    case 0x80000000:      return (1);
      
    case 0x00000000:	  return (0);
    }
  (*status) = ERR_PAT_HOLE;
  return (-1);
}

/* converts-and-catenates a subnet structure to a bridled string.
   Style can be SUBNET_STYLE_NETMASK (old style) or SUBNET_STYLE_PREFIX 
   (CIDR notation).
   Returns 0 if OK. Might return various errors */
t_status		subnet_to_str(subnet,str,max_len,style)
t_subnet		*subnet;	/* A subnet structure */
char			*str;		/* A valid string */
int			max_len;	/* Maximum length */
int			style;		/* Style used */
{
  t_status		status;

  if ((status = inaddr_to_str(&(subnet->addr),
			      str,
			      max_len,
			      FALSE)) != 0)
    return (status);
  if (style == SUBNET_STYLE_NETMASK)
    {
      if ((status = str_cat_char(str,
				 max_len,
				 ':')) != 0)
	return (status);
      if ((status = inaddr_to_str(&(subnet->mask),
				  str,
				  max_len,
				  FALSE)) != 0)
	return (status);
    }
  else
    if (style == SUBNET_STYLE_PREFIX)
      {
	int		prefix;

	if ((status = str_cat_char(str,
				   max_len,
				   '/')) != 0)
	  return (status);
	if ((prefix = prefix_from_netmask(&(subnet->mask),&status)) < 0)
	  return (status);
	if ((status = long_to_str((signed long)prefix,
				  10,
				  str,
				  max_len)) != 0)
	  return (status);
      }
    else
      return (ERR_PAT_NI);
  return (0);
}

/* determines if an internet address is member of a subnet.
   Returns TRUE or FALSE */
t_boolean		is_subnet_member(inaddr,subnet)
struct in_addr		*inaddr;   /* An aligned in_addr structure pointer*/
t_subnet		*subnet;	/* A subnet structure */
{
  int			hip;
  int			hmask;
  int			hcable;
    
  hip = UNSAFE_NTOHL(inaddr->s_addr);
  hmask = UNSAFE_NTOHL(subnet->mask.s_addr);
  hcable = UNSAFE_NTOHL(subnet->cable.s_addr); 
  return ((hip & hmask) == hcable);
}

/* applies a procedure to a whole subnet.
   Note that this procedure doesn't manage holes in netmask correctly.
   If the current internet address is a special address (cable 
   or broadcast), it fills an appropriate flag.
   Returns 0 if OK. If a proc fails, it breaks an returns the proc status. */
t_status		subnet_walk(subnet,proc,data)
t_subnet		*subnet;	/* A subnet structure */
t_subnet_walk_proc	proc;		/* A subnet walk proc */
VOID_PTR		data;		/* Data passed to proc */
{
  int			s;
  int			haddr;
  int			hmask;
  int			hcable;
  int			hbroadcast;
  t_status		status;

  haddr = UNSAFE_NTOHL(subnet->addr.s_addr);
  hmask = UNSAFE_NTOHL(subnet->mask.s_addr);
  hcable = UNSAFE_NTOHL(subnet->cable.s_addr);
  hbroadcast = UNSAFE_NTOHL(subnet->broadcast.s_addr);
  s = haddr;
  while ((s & hmask) == hcable) 
    {
      struct in_addr	addr;
      int		flag;

      addr.s_addr = UNSAFE_HTONL(s);
      if (s == hcable)
	flag = SUBNET_CABLE;
      else
	if (s == hbroadcast)
	  flag = SUBNET_BROADCAST;
	else
	  flag = SUBNET_ADDR;
      if ((status = proc(&addr,flag,data)) != 0)
	return (status);
      s++;
    }
  return (0);
} 

/* converts a vector of strings to a vector of subnets.
   It uses subnet_from_str(3).
   Returns 0 if OK. Might return various errors */
t_status		vec_prefixes_from_vec_str(vec_pref,vec_str)
t_vec			*vec_pref;	/* Vector of subnets */
t_vec			*vec_str;	/* Vector of strings */
{
  t_status		status;

  VEC_FOR(vec_str,char *str)
    {
      t_subnet		subnet;
      t_subnet		*nsubnet;
      
      if ((status = subnet_from_str(str,&subnet)) != 0)
	goto bad;
      if ((nsubnet = vec_pref->alloc_proc(sizeof (t_subnet),
					  "miscinet",
				      "vec_prefixes_from_vec_str:subnet",
					  &status)) == NULL)
	goto bad;
      FBCOPY(&subnet,nsubnet,sizeof (t_subnet));
      if ((status = vec_add(vec_pref,nsubnet)) != 0)
	{
	  vec_pref->free_proc(nsubnet,
			      "miscinet",
			      "vec_prefixes_from_vec_str:subnet");
	  goto bad;
	}
    }
  VEC_ENDFOR;
  return (status);
bad:
  vec_ptr_delete(vec_pref);
  return (status);
}

#ifdef DEBUG
/* shows a subnet structure.
   This is a debug function */
VOID_FUNC		subnet_show(subnet)
t_subnet		*subnet;
{
  fprintf(stderr,"addr=%s\n",inet_ntoa(subnet->addr));
  fprintf(stderr,"mask=%s\n",inet_ntoa(subnet->mask));
  fprintf(stderr,"cable=%s\n",inet_ntoa(subnet->cable));
  fprintf(stderr,"broadcast=%s\n",inet_ntoa(subnet->broadcast));
}
#endif

int	subnet_chan;

PAT_NAME_GENERIC(subnet_pat_name,
		 (VOID_PTR)&subnet_chan,
		 "subnet")

PAT_EXTRACT_DECL(subnet_pat_extract)
{
  t_subnet	subnet;

  if (len < sizeof (subnet))
    return (ERR_PAT_TRUNC_FIELD);
  FBCOPY(buf,&subnet,sizeof (t_subnet));
  return (subnet_to_str(&subnet,
			str,
			max_len,
			(int)(data)));
}

PAT_INSERT_DECL(subnet_pat_insert)
{
  t_subnet	subnet;
  t_status	status;	

  if (len < sizeof (subnet))
    return (ERR_PAT_TRUNC_FIELD);
  if ((status = subnet_from_str(value,
				&subnet)) != 0)
    return (status);
  FBCOPY(&subnet,buf,sizeof (t_subnet));
  return (0);
}

t_pat				subnet_pat = 
{
  subnet_pat_name,		/* t_pat_name_proc		*/
  NULL,				/* t_pat_off_proc		*/
  NULL,				/* t_pat_sub_proc		*/
  NULL,				/* t_pat_sum_proc		*/
  NULL,				/* t_pat_get_field_proc		*/
  NULL,				/* t_pat_set_field_proc		*/
  NULL,				/* t_pat_get_fields_proc	*/
  NULL,				/* t_pat_get_tmpl_proc		*/
  NULL,				/* t_pat_get_tmpl2_proc		*/
  NULL,				/* t_pat_has_opt_proc		*/
  NULL,				/* t_pat_adapt_len_proc		*/
  NULL,				/* t_pat_get_field_pat_proc	*/
  subnet_pat_extract,		/* t_pat_extract_proc		*/
  subnet_pat_insert,		/* t_pat_insert_proc		*/
  NULL,				/* t_pat_get_choices_proc	*/
};
