/*	$KAME: config.c,v 1.11 2003/09/23 11:10:09 itojun Exp $	*/

/*
 * Copyright (c) 1998-2001
 * The University of Southern California/Information Sciences Institute.
 * 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.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
 */
/*
 * Part of this program has been derived from mrouted.
 * The mrouted program is covered by the license in the accompanying file
 * named "LICENSE.mrouted".
 *
 * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
 * Leland Stanford Junior University.
 *
 */

#include "defs.h"
#include <ifaddrs.h>

/*
 * Forward declarations.
 */
static char *next_word  __P((char **));
static int parse_phyint __P((char *s));
static void add_phaddr  __P((struct uvif *v, struct sockaddr_in6 *addr,
			     struct in6_addr *mask,
			     struct sockaddr_in6 *rmt_addr));
static mifi_t ifname2mifi __P((char *ifname));
static int wordToOption __P((char *));
static int parse_filter __P((char *s));
#if 0 /* not used */
static int parse_default_source_metric __P((char *));
static int parse_default_source_preference __P((char *));
#endif

/*
 * Query the kernel to find network interfaces that are multicast-capable
 * and install them in the uvifs array.
 */
void 
config_vifs_from_kernel()
{
    register struct uvif *v;
    register vifi_t vifi;
    struct sockaddr_in6 addr, rmt_addr, *rmt;
    struct in6_addr mask, prefix;
    short flags;
    struct ifaddrs *ifap, *ifa;

    total_interfaces = 0; /* The total number of physical interfaces */
    
    if (getifaddrs(&ifap))
	log_msg(LOG_ERR, errno, "getiaddrs");

    /*
     * Loop through all of the interfaces.
     */
    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
	/*
	 * Ignore any interface for an address family other than IPv6.
	 */
	if (ifa->ifa_addr->sa_family != AF_INET6) {
	    total_interfaces++;  /* Eventually may have IPv6 address later */
	    continue;
	}

	memcpy(&addr, ifa->ifa_addr, sizeof(struct sockaddr_in6));
	
	flags = ifa->ifa_flags;
	if ((flags & (IFF_LOOPBACK | IFF_MULTICAST)) != IFF_MULTICAST)
	    continue;

	/* Get remote address only in case of P2P I/F */
	if (ifa->ifa_dstaddr != NULL && (flags & IFF_POINTOPOINT)) {
		memcpy(&rmt_addr, ifa->ifa_dstaddr, sizeof(rmt_addr));
		rmt = &rmt_addr;
	} else {
		memset(&rmt_addr, 0, sizeof(rmt_addr));
		rmt = NULL;
	}

	/*
	 * Get netmask of the address.
	 */
	memcpy(&mask, &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr,
	       sizeof(mask));

	if (IN6_IS_ADDR_LINKLOCAL(&addr.sin6_addr)) {
		addr.sin6_scope_id = if_nametoindex(ifa->ifa_name);
#ifdef __KAME__
		/*
		 * Hack for KAME kernel. Set sin6_scope_id field of a
		 * link local address and clear the index embedded in
		 * the address.
		 */
		/* clear interface index */
		addr.sin6_addr.s6_addr[2] = 0;
		addr.sin6_addr.s6_addr[3] = 0;
#endif
	}
	if (rmt && IN6_IS_ADDR_LINKLOCAL(&rmt->sin6_addr))
	{
		rmt->sin6_scope_id = if_nametoindex(ifa->ifa_name);
#ifdef __KAME__
		/*
		 * Hack for KAME kernel.
		 * Set sin6_scope_id field of a link local address and clear
		 * the index embedded in the address.
		 */
		/* clear interface index */
		rmt->sin6_addr.s6_addr[2] = 0;
		rmt->sin6_addr.s6_addr[3] = 0;
#endif
	}


	/*
	 * If the address is connected to the same subnet as one already
	 * installed in the uvifs array, just add the address to the list
	 * of addresses of the uvif.
	 */
	for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) {
	    if (strcmp(v->uv_name, ifa->ifa_name) == 0) {
		    add_phaddr(v, &addr, &mask, rmt);
		    break;
	    }
	}
	if (vifi != numvifs)
	    continue;

	/*
	 * If there is room in the uvifs array, install this interface.
	 */
	if (numvifs == MAXMIFS) {
	    log_msg(LOG_WARNING, 0, "too many ifs, ignoring %s", ifa->ifa_name);
	    continue;
	}

	/*
	 * Everyone below is a potential vif interface.
	 * We don't care if it has wrong configuration or not configured
	 * at all.
	 */
	total_interfaces++;
	
	v = &uvifs[numvifs];
	v->uv_flags		= 0;
	v->uv_metric		= DEFAULT_METRIC;
	v->uv_admetric		= 0;
	v->uv_rate_limit	= DEFAULT_PHY_RATE_LIMIT;
	v->uv_dst_addr		= allpim6routers_group;
	v->uv_prefix.sin6_addr	= prefix;
	v->uv_subnetmask	= mask;
#ifdef HAVE_STRLCPY
	strlcpy(v->uv_name, ifa->ifa_name, IFNAMSIZ);
#elif HAVE_STRNCPY
	strncpy(v->uv_name, ifa->ifa_name, IFNAMSIZ);
#else
	strcpy(v->uv_name, ifa->ifa_name);
#endif
	v->uv_ifindex	        = if_nametoindex(v->uv_name);
	v->uv_groups		= (struct listaddr *)NULL;
	v->uv_dvmrp_neighbors   = (struct listaddr *)NULL;
	NBRM_CLRALL(v->uv_nbrmap);
	v->uv_querier           = (struct listaddr *)NULL;
	v->uv_prune_lifetime    = 0;
	v->uv_acl               = (struct vif_acl *)NULL;
	v->uv_leaf_timer        = 0;
	v->uv_addrs		= (struct phaddr *)NULL;
	v->uv_filter		= (struct vif_filter *)NULL;
	v->uv_pim_hello_timer   = 0;
	v->uv_gq_timer          = 0;
	v->uv_pim_neighbors	= (struct pim_nbr_entry *)NULL;
	v->uv_local_pref        = default_source_preference;
	v->uv_local_metric      = default_source_metric;
	add_phaddr(v, &addr, &mask, rmt);
	
	if (flags & IFF_POINTOPOINT)
	    v->uv_flags |= (VIFF_REXMIT_PRUNES | VIFF_POINT_TO_POINT);
	log_msg(LOG_INFO, 0,
	    "installing %s as if #%u - rate=%d",
	    v->uv_name, numvifs, v->uv_rate_limit);
	++numvifs;
	
	/*
	 * If the interface is not yet up, set the vifs_down flag to
	 * remind us to check again later.
	 */
	if (!(flags & IFF_UP)) {
	    v->uv_flags |= VIFF_DOWN;
	    vifs_down = TRUE;
	}
    }

    freeifaddrs(ifap);
}

static void
add_phaddr(v, addr, mask, rmt)
	struct uvif *v;
	struct sockaddr_in6 *addr;
	struct in6_addr *mask;
	struct sockaddr_in6 *rmt;
{
	struct phaddr *pa;
	int i;

	if ((pa = malloc(sizeof(*pa))) == NULL)
		log_msg(LOG_ERR, 0, "add_phaddr: memory exhausted");

	memset(pa, 0, sizeof(*pa));
	pa->pa_addr = *addr;
	pa->pa_subnetmask = *mask;
	if (rmt)
		pa->pa_rmt_addr= *rmt;

	/*
	 * install the prefix of the address derived from the address
	 * and the mask.
	 */
	for (i = 0; i < sizeof(struct in6_addr); i++)
		pa->pa_prefix.sin6_addr.s6_addr[i] =
			addr->sin6_addr.s6_addr[i] & mask->s6_addr[i];
	pa->pa_prefix.sin6_scope_id = addr->sin6_scope_id;

	if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
		if (v->uv_linklocal) {
			log_msg(LOG_WARNING, 0,
			    "add_phaddr: found more than one link-local "
			    "address on %s",
			    v->uv_name);
		}
		v->uv_linklocal = pa;	/* relace anyway */
	}

	/* link into chain */
	pa->pa_next = v->uv_addrs;
	v->uv_addrs = pa;
}

static mifi_t
ifname2mifi(ifname)
	char *ifname;
{
	mifi_t mifi;
	struct uvif *v;

	for (mifi = 0, v = uvifs; mifi < numvifs; ++mifi, ++v) {
		if (strcmp(v->uv_name, ifname) == 0)
		    break;
	}
	return(mifi);
}

#define UNKNOWN        -1
#define EMPTY           1
#define PHYINT          2
#define DEFAULT_SOURCE_METRIC     3
#define DEFAULT_SOURCE_PREFERENCE 4
#define FILTER 5

/*
 * function name: wordToOption
 * input: char *word, a pointer to the word
 * output: int; a number corresponding to the code of the word
 * operation: converts the result of the string comparisons into numerics.
 * comments: called by config_vifs_from_file()
 */
static int 
wordToOption(word)
    char *word;
{
	if (EQUAL(word, ""))
		return EMPTY;
	if (EQUAL(word, "phyint"))
		return PHYINT;
	if (EQUAL(word, "default_source_metric"))
		return DEFAULT_SOURCE_METRIC;
	if (EQUAL(word, "default_source_preference"))
		return DEFAULT_SOURCE_PREFERENCE;
	if (EQUAL(word, "filter"))
		return FILTER;
	return UNKNOWN;
}

/*
 * function name: parse_phyint
 * input: char *s, pointing to the parsing point of the file
 * output: int (TRUE if the parsing was successful, o.w. FALSE)
 * operation: parses the physical interface file configurations, if any.
 * The general form is:
 *     phyint <ifname> [disable]
 */
static int
parse_phyint(s)
    char *s;
{
    char *w, c, *ifname;
    vifi_t vifi;
    struct uvif *v;
    u_int n;
    
    if (EQUAL((w = next_word(&s)), "")) {
	log_msg(LOG_WARNING, 0, "Missing phyint in %s", configfilename);
	return(FALSE);
    }		/* if empty */
    ifname = w;
    
    for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) {
	if (vifi == numvifs) {
	    log_msg(LOG_WARNING, 0,
		"phyint %s in %s is not a configured interface",
		ifname, configfilename);
	    return(FALSE);
	}	/* if vifi == numvifs */

	if (strcmp(v->uv_name, ifname))
	    continue;
	
	while (!EQUAL((w = next_word(&s)), "")) {
	    if (EQUAL(w, "disable"))
		v->uv_flags |= VIFF_DISABLED;
	    else if (EQUAL(w, "nolistener"))
		v->uv_flags |= VIFF_NOLISTENER;
	    else if(EQUAL(w, "preference")) {
                if(EQUAL((w = next_word(&s)), "")) 
                    log_msg(LOG_WARNING, 0,
                        "Missing preference for phyint %s in %s",
                        ifname, configfilename);
                else if (sscanf(w, "%u%c", &n, &c) != 1 ||
                         n < 1 || n > 255 )
                    log_msg(LOG_WARNING, 0,
                        "Invalid preference '%s' for phyint %s in %s",
                        w, ifname,
                        configfilename);
		else {
		    IF_DEBUG(DEBUG_ASSERT)
			log_msg(LOG_DEBUG, 0,
			    "Config setting default local preference on %s to %d.", 
			    ifname, n);
		    v->uv_local_pref = n;
		}
	    
	    } else if(EQUAL(w, "metric")) {
                if(EQUAL((w = next_word(&s)), "")) 
                    log_msg(LOG_WARNING, 0,
                        "Missing metric for phyint %s in %s",
                        ifname, configfilename);
                else if (sscanf(w, "%u%c", &n, &c) != 1 ||
                         n < 1 || n > 1024 )
                    log_msg(LOG_WARNING, 0,
                        "Invalid metric '%s' for phyint %s in %s",
                        w, ifname,
                        configfilename);
		else {
		    IF_DEBUG(DEBUG_ASSERT)
			log_msg(LOG_DEBUG, 0,
			    "Config setting default local metric on %s to %d.", 
			    ifname, n);
		    v->uv_local_metric = n;
		}
	    }
	}		/* if not empty */
	break;
    }
    return(TRUE);
}

static int
parse_filter(s)
	char *s;
{
	char *w, *groups, *p;
	mifi_t mifi;
	struct in6_addr grp1, grp2;
	if_set filterset;
	struct mrtfilter *filter;
	int plen = 0, filtertype;

	if (EQUAL((groups = next_word(&s)), "")) {
		log_msg(LOG_WARNING, 0, "Missing multicast group in %s",
		    configfilename);
		return(FALSE);
	}

	/*
	 * Group address specification. Valid format are the followings.
	 * - Group1-Group2: specifies a numerical range of a scope.
	 * - GroupPrefix/Prefixlen: specifies a prefix of a scope. If the
	 *   Prefixlen is omitted, it means the exact match.
	 */
	if ((p = strchr(groups, '-')) != NULL) { /* Group1-Group2 */
		char *maddr1, *maddr2;

		maddr1 = groups;
		maddr2 = p + 1;
		*p = '\0';
		if (inet_pton(AF_INET6, maddr1, (void *)&grp1) != 1 ||
		    !IN6_IS_ADDR_MULTICAST(&grp1)) {
			log_msg(LOG_WARNING, 0, "invalid group address %s", maddr1);
			return(FALSE);
		}
		if (inet_pton(AF_INET6, maddr2, (void *)&grp2) != 1 ||
		    !IN6_IS_ADDR_MULTICAST(&grp2)) {
			log_msg(LOG_WARNING, 0, "invalid group address %s", maddr2);
			return(FALSE);
		}
		filtertype = FILTER_RANGE;
	}
	else if ((p = strchr(groups, '/')) != NULL) { /* GroupPrefix/Plen */
		char *mprefix = groups;
		int plen = atoi(p + 1);
		*p = '\0';
		if (inet_pton(AF_INET6, mprefix, (void *)&grp1) != 1 ||
		    !IN6_IS_ADDR_MULTICAST(&grp1)) {
			log_msg(LOG_WARNING, 0, "invalid group prefix %s", mprefix);
			return(FALSE);
		}
		if (plen < 0 || plen > 128) {
			log_msg(LOG_WARNING, 0, "invalid prefix length %s", p + 1);
			return(FALSE);
		}
		filtertype = FILTER_PREFIX;
	}
	else {
		if (inet_pton(AF_INET6, groups, (void *)&grp1) != 1) {
			log_msg(LOG_WARNING, 0, "invalid group address %s", groups);
			return(FALSE);
		}
		plen = 128;	/* exact match */
		filtertype = FILTER_PREFIX;
	}

	IF_ZERO(&filterset);
	while (!EQUAL((w = next_word(&s)), "")) {
		if ((mifi = ifname2mifi(w)) == MAXMIFS) {
			/* XXX: scope consideration?? */
			log_msg(LOG_WARNING, 0,
			    "phyint %s in %s is not a configured interface",
			    w, configfilename);
			return(FALSE);
		}

		IF_SET(mifi, &filterset);
	}
	if (IF_ISEMPTY(&filterset)) {
		log_msg(LOG_WARNING, 0,
		    "filter set is empty. ignore it.");
		return(FALSE);
	}

	filter = add_filter(filtertype, &grp1, &grp2, plen);
	IF_COPY(&filterset, &filter->ifset);

	return(TRUE);
}

#if 0 /* not used */
/*
 * function name: parse_default_source_metric
 * input: char *s
 * output: int
 * operation: reads and assigns the default source metric, if no reliable
 *            unicast routing information available.
 *            General form: 
 *              'default_source_metric <number>'.
 *            default pref and metric statements should precede all phyint
 *            statements in the config file.
 */
static int
parse_default_source_metric(s)
    char *s;
{
    char *w;
    u_int value;
    vifi_t vifi;
    struct uvif *v;

    value = DEFAULT_LOCAL_METRIC;
    if (EQUAL((w = next_word(&s)), "")) {
        log_msg(LOG_WARNING, 0,
            "Missing default source metric; set to default %u",
            DEFAULT_LOCAL_METRIC);
    } else if (sscanf(w, "%u", &value) != 1) {
        log_msg(LOG_WARNING, 0,
            "Invalid default source metric; set to default %u",
            DEFAULT_LOCAL_METRIC);
        value = DEFAULT_LOCAL_METRIC;
    }
    default_source_metric = value;
    log_msg(LOG_INFO, 0, "default_source_metric is %u", value);

    for (vifi = 0, v = uvifs; vifi < MAXMIFS; ++vifi, ++v) {
	v->uv_local_metric = default_source_metric;
    }
	
    return(TRUE);
}


/*
 * function name: parse_default_source_preference
 * input: char *s
 * output: int
 * operation: reads and assigns the default source preference, if no reliable
 *            unicast routing information available.
 *            General form: 
 *              'default_source_preference <number>'.
 *            default pref and metric statements should precede all phyint
 *            statements in the config file.
*/
static int
parse_default_source_preference(s)
    char *s;
{
    char *w;
    u_int value;
    vifi_t vifi;
    struct uvif *v;

    value = DEFAULT_LOCAL_PREF;
    if (EQUAL((w = next_word(&s)), "")) {
        log_msg(LOG_WARNING, 0,
            "Missing default source preference; set to default %u",
            DEFAULT_LOCAL_PREF);
    } else if (sscanf(w, "%u", &value) != 1) {
        log_msg(LOG_WARNING, 0,
            "Invalid default source preference; set to default %u",
            DEFAULT_LOCAL_PREF);
        value = DEFAULT_LOCAL_PREF;
    }
    default_source_preference = value;
    log_msg(LOG_INFO, 0, "default_source_preference is %u", value);

    for (vifi = 0, v = uvifs; vifi < MAXMIFS; ++vifi, ++v) {
	v->uv_local_pref = default_source_preference;
    }

    return(TRUE);
}
#endif

void 
config_vifs_from_file()
{
	FILE *f;
	char linebuf[100];
	char *w, *s;
	int option;

	if ((f = fopen(configfilename, "r")) == NULL) {
		if (errno != ENOENT) log_msg(LOG_WARNING, errno, "can't open %s", 
					 configfilename);
		return;
	}

	while (fgets(linebuf, sizeof(linebuf), f) != NULL) {
		s = linebuf;
		w = next_word(&s);
		option = wordToOption(w);
		switch(option) {
		 case EMPTY:
			 continue;
			 break;
		 case PHYINT:
			 parse_phyint(s);
			 break;
		 case FILTER:
			 parse_filter(s);
			 break;
		 default:
			 log_msg(LOG_WARNING, 0, "unknown command '%s' in %s",
			     w, configfilename);
		}
	}
	fclose(f);
}

static char *
next_word(s)
    char **s;
{
    char *w;
    
    w = *s;
    while (*w == ' ' || *w == '\t')
	w++;
    
    *s = w;
    for(;;) {
	switch (**s) {
	case ' '  :
	case '\t' :
	    **s = '\0';
	    (*s)++;
	    return(w);
	case '\n' :
	case '#'  :
	    **s = '\0';
	    return(w);
	case '\0' :
	    return(w);
	default   :
	    if (isascii(**s) && isupper(**s))
		**s = tolower(**s);
	    (*s)++;
	}
    }
}



syntax highlighted by Code2HTML, v. 0.9.1