// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016 by Open Source Routing.
 */

#include <zebra.h>

#include "ldpd.h"
#include "ldpe.h"
#include "lde.h"
#include "log.h"

#include "command.h"
#include "vrf.h"
#include "if.h"
#include "vty.h"
#include "ldp_vty.h"

static int	 ldp_config_write(struct vty *);
static void	 ldp_af_iface_config_write(struct vty *, int);
static void	 ldp_af_config_write(struct vty *, int, struct ldpd_conf *,
		    struct ldpd_af_conf *);
static int	 ldp_l2vpn_config_write(struct vty *);
static void	 ldp_l2vpn_pw_config_write(struct vty *, struct l2vpn_pw *);
static int	 ldp_vty_get_af(struct vty *);
static int	 ldp_iface_is_configured(struct ldpd_conf *, const char *);

struct cmd_node ldp_node = {
	.name = "ldp",
	.node = LDP_NODE,
	.parent_node = CONFIG_NODE,
	.prompt = "%s(config-ldp)# ",
	.config_write = ldp_config_write,
};

struct cmd_node ldp_ipv4_node = {
	.name = "ldp ipv4",
	.node = LDP_IPV4_NODE,
	.parent_node = LDP_NODE,
	.prompt = "%s(config-ldp-af)# ",
};

struct cmd_node ldp_ipv6_node = {
	.name = "ldp ipv6",
	.node = LDP_IPV6_NODE,
	.parent_node = LDP_NODE,
	.prompt = "%s(config-ldp-af)# ",
};

struct cmd_node ldp_ipv4_iface_node = {
	.name = "ldp ipv4 interface",
	.node = LDP_IPV4_IFACE_NODE,
	.parent_node = LDP_IPV4_NODE,
	.prompt = "%s(config-ldp-af-if)# ",
};

struct cmd_node ldp_ipv6_iface_node = {
	.name = "ldp ipv6 interface",
	.node = LDP_IPV6_IFACE_NODE,
	.parent_node = LDP_IPV6_NODE,
	.prompt = "%s(config-ldp-af-if)# ",
};

struct cmd_node ldp_l2vpn_node = {
	.name = "ldp l2vpn",
	.node = LDP_L2VPN_NODE,
	.parent_node = CONFIG_NODE,
	.prompt = "%s(config-l2vpn)# ",
	.config_write = ldp_l2vpn_config_write,
};

struct cmd_node ldp_pseudowire_node = {
	.name = "ldp",
	.node = LDP_PSEUDOWIRE_NODE,
	.parent_node = LDP_L2VPN_NODE,
	.prompt = "%s(config-l2vpn-pw)# ",
};

int
ldp_get_address(const char *str, int *af, union ldpd_addr *addr)
{
	if (!str || !af || !addr)
		return (-1);

	memset(addr, 0, sizeof(*addr));

	if (inet_pton(AF_INET, str, &addr->v4) == 1) {
		*af = AF_INET;
		return (0);
	}

	if (inet_pton(AF_INET6, str, &addr->v6) == 1) {
		*af = AF_INET6;
		return (0);
	}

	return (-1);
}

static void
ldp_af_iface_config_write(struct vty *vty, int af)
{
	struct iface		*iface;
	struct iface_af		*ia;

	RB_FOREACH(iface, iface_head, &ldpd_conf->iface_tree) {
		ia = iface_af_get(iface, af);
		if (!ia->enabled)
			continue;

		vty_out (vty, "  !\n");
		vty_out (vty, "  interface %s\n", iface->name);

		if (ia->hello_holdtime != LINK_DFLT_HOLDTIME &&
		    ia->hello_holdtime != 0)
			vty_out (vty, "   discovery hello holdtime %u\n",
			    ia->hello_holdtime);
		if (ia->hello_interval != DEFAULT_HELLO_INTERVAL &&
		    ia->hello_interval != 0)
			vty_out (vty, "   discovery hello interval %u\n",
			    ia->hello_interval);

		vty_out (vty, "  exit\n");
	}
}

static void
ldp_af_config_write(struct vty *vty, int af, struct ldpd_conf *conf,
    struct ldpd_af_conf *af_conf)
{
	struct tnbr		*tnbr;

	if (!(af_conf->flags & F_LDPD_AF_ENABLED))
		return;

	vty_out (vty, " !\n");
	vty_out (vty, " address-family %s\n", af_name(af));

	if (af_conf->lhello_holdtime != LINK_DFLT_HOLDTIME &&
	    af_conf->lhello_holdtime != 0 )
		vty_out (vty, "  discovery hello holdtime %u\n",
		    af_conf->lhello_holdtime);
	if (af_conf->lhello_interval != DEFAULT_HELLO_INTERVAL &&
	    af_conf->lhello_interval != 0)
		vty_out (vty, "  discovery hello interval %u\n",
		    af_conf->lhello_interval);

	if (af_conf->flags & F_LDPD_AF_THELLO_ACCEPT) {
		vty_out(vty, "  discovery targeted-hello accept");
		if (af_conf->acl_thello_accept_from[0] != '\0')
			vty_out(vty, " from %s",
			    af_conf->acl_thello_accept_from);
		vty_out (vty, "\n");
	}

	if (af_conf->thello_holdtime != TARGETED_DFLT_HOLDTIME &&
	    af_conf->thello_holdtime != 0)
		vty_out (vty, "  discovery targeted-hello holdtime %u\n",
		    af_conf->thello_holdtime);
	if (af_conf->thello_interval != DEFAULT_HELLO_INTERVAL &&
	    af_conf->thello_interval != 0)
		vty_out (vty, "  discovery targeted-hello interval %u\n",
		    af_conf->thello_interval);

	if (ldp_addrisset(af, &af_conf->trans_addr))
		vty_out (vty, "  discovery transport-address %s\n",
		    log_addr(af, &af_conf->trans_addr));
		else
			vty_out (vty,
				  "  ! Incomplete config, specify a discovery transport-address\n");

	if ((af_conf->flags & F_LDPD_AF_ALLOCHOSTONLY) ||
	    af_conf->acl_label_allocate_for[0] != '\0') {
		vty_out(vty, "  label local allocate");
		if (af_conf->flags & F_LDPD_AF_ALLOCHOSTONLY)
			vty_out(vty, " host-routes");
		else
			vty_out(vty, " for %s",
			    af_conf->acl_label_allocate_for);
		vty_out (vty, "\n");
	}

	if (af_conf->acl_label_advertise_for[0] != '\0' ||
	    af_conf->acl_label_advertise_to[0] != '\0') {
		vty_out(vty, "  label local advertise");
		if (af_conf->acl_label_advertise_to[0] != '\0')
			vty_out(vty, " to %s",
			    af_conf->acl_label_advertise_to);
		if (af_conf->acl_label_advertise_for[0] != '\0')
			vty_out(vty, " for %s",
			    af_conf->acl_label_advertise_for);
		vty_out (vty, "\n");
	}

	if (af_conf->flags & F_LDPD_AF_EXPNULL) {
		vty_out(vty, "  label local advertise explicit-null");
		if (af_conf->acl_label_expnull_for[0] != '\0')
			vty_out(vty, " for %s",
			    af_conf->acl_label_expnull_for);
		vty_out (vty, "\n");
	}

	if (af_conf->acl_label_accept_for[0] != '\0' ||
	    af_conf->acl_label_accept_from[0] != '\0') {
		vty_out(vty, "  label remote accept");
		if (af_conf->acl_label_accept_from[0] != '\0')
			vty_out(vty, " from %s",
			    af_conf->acl_label_accept_from);
		if (af_conf->acl_label_accept_for[0] != '\0')
			vty_out(vty, " for %s",
			    af_conf->acl_label_accept_for);
		vty_out (vty, "\n");
	}

	if (af_conf->flags & F_LDPD_AF_NO_GTSM)
		vty_out (vty, "  ttl-security disable\n");

	if (af_conf->keepalive != DEFAULT_KEEPALIVE)
		vty_out (vty, "  session holdtime %u\n",af_conf->keepalive);

	RB_FOREACH(tnbr, tnbr_head, &ldpd_conf->tnbr_tree) {
		if (tnbr->af == af) {
			vty_out (vty, "  !\n");
			vty_out (vty, "  neighbor %s targeted\n",
			    log_addr(tnbr->af, &tnbr->addr));
		}
	}

	ldp_af_iface_config_write(vty, af);

	vty_out(vty, "  !\n");
	vty_out(vty, " exit-address-family\n");
}

static int
ldp_config_write(struct vty *vty)
{
	struct nbr_params	*nbrp;

	if (!(ldpd_conf->flags & F_LDPD_ENABLED))
		return (0);

	vty_out (vty, "mpls ldp\n");

	if (ldpd_conf->rtr_id.s_addr != INADDR_ANY)
		vty_out(vty, " router-id %pI4\n", &ldpd_conf->rtr_id);

	if (ldpd_conf->lhello_holdtime != LINK_DFLT_HOLDTIME &&
	    ldpd_conf->lhello_holdtime != 0)
		vty_out (vty, " discovery hello holdtime %u\n",
		    ldpd_conf->lhello_holdtime);
	if (ldpd_conf->lhello_interval != DEFAULT_HELLO_INTERVAL &&
	    ldpd_conf->lhello_interval != 0)
		vty_out (vty, " discovery hello interval %u\n",
		    ldpd_conf->lhello_interval);

	if (ldpd_conf->thello_holdtime != TARGETED_DFLT_HOLDTIME &&
	    ldpd_conf->thello_holdtime != 0)
		vty_out (vty, " discovery targeted-hello holdtime %u\n",
		    ldpd_conf->thello_holdtime);
	if (ldpd_conf->thello_interval != DEFAULT_HELLO_INTERVAL &&
	    ldpd_conf->thello_interval != 0)
		vty_out (vty, " discovery targeted-hello interval %u\n",
		    ldpd_conf->thello_interval);

	if (ldpd_conf->trans_pref == DUAL_STACK_LDPOV4)
		vty_out (vty,
			  " dual-stack transport-connection prefer ipv4\n");

	if (ldpd_conf->flags & F_LDPD_DS_CISCO_INTEROP)
		vty_out (vty, " dual-stack cisco-interop\n");

	if (ldpd_conf->flags & F_LDPD_ORDERED_CONTROL)
		vty_out (vty, " ordered-control\n");

	if (ldpd_conf->wait_for_sync_interval != DFLT_WAIT_FOR_SYNC &&
	    ldpd_conf->wait_for_sync_interval != 0)
		vty_out (vty, " wait-for-sync %u\n",
		    ldpd_conf->wait_for_sync_interval);

	if (ldpd_conf->flags & F_LDPD_ALLOW_BROKEN_LSP)
		vty_out(vty, " install allow-broken-lsp\n");

	RB_FOREACH(nbrp, nbrp_head, &ldpd_conf->nbrp_tree) {
		if (nbrp->flags & F_NBRP_KEEPALIVE)
			vty_out (vty, " neighbor %pI4 session holdtime %u\n",
			    &nbrp->lsr_id,nbrp->keepalive);

		if (nbrp->flags & F_NBRP_GTSM) {
			if (nbrp->gtsm_enabled)
				vty_out (vty, " neighbor %pI4 ttl-security hops %u\n",  &nbrp->lsr_id,
				    nbrp->gtsm_hops);
			else
				vty_out (vty, " neighbor %pI4 ttl-security disable\n",&nbrp->lsr_id);
		}

		if (nbrp->auth.method == AUTH_MD5SIG)
			vty_out (vty, " neighbor %pI4 password %s\n",
			    &nbrp->lsr_id,nbrp->auth.md5key);
	}

	ldp_af_config_write(vty, AF_INET, ldpd_conf, &ldpd_conf->ipv4);
	ldp_af_config_write(vty, AF_INET6, ldpd_conf, &ldpd_conf->ipv6);
	vty_out (vty, " !\n");
	vty_out (vty, "exit\n");
	vty_out (vty, "!\n");

	return (1);
}

static void
ldp_l2vpn_pw_config_write(struct vty *vty, struct l2vpn_pw *pw)
{
	int	 missing_lsrid = 0;
	int	 missing_pwid = 0;

	vty_out (vty, " !\n");
	vty_out (vty, " member pseudowire %s\n", pw->ifname);

	if (pw->lsr_id.s_addr != INADDR_ANY)
		vty_out (vty, "  neighbor lsr-id %pI4\n",&pw->lsr_id);
		else
			missing_lsrid = 1;

	if (pw->flags & F_PW_STATIC_NBR_ADDR)
		vty_out (vty, "  neighbor address %s\n",
		          log_addr(pw->af, &pw->addr));

	if (pw->pwid != 0)
		vty_out (vty, "  pw-id %u\n", pw->pwid);
	else
		missing_pwid = 1;

	if (!(pw->flags & F_PW_CWORD_CONF))
		vty_out (vty, "  control-word exclude\n");

	if (!(pw->flags & F_PW_STATUSTLV_CONF))
		vty_out (vty, "  pw-status disable\n");

	if (missing_lsrid)
		vty_out (vty,
		          "  ! Incomplete config, specify a neighbor lsr-id\n");
	if (missing_pwid)
		vty_out (vty,"  ! Incomplete config, specify a pw-id\n");

	vty_out (vty, " exit\n");
}

static int
ldp_l2vpn_config_write(struct vty *vty)
{
	struct l2vpn		*l2vpn;
	struct l2vpn_if		*lif;
	struct l2vpn_pw		*pw;

	RB_FOREACH(l2vpn, l2vpn_head, &ldpd_conf->l2vpn_tree) {
		vty_out (vty, "l2vpn %s type vpls\n", l2vpn->name);

		if (l2vpn->pw_type != DEFAULT_PW_TYPE)
			vty_out (vty, " vc type ethernet-tagged\n");

		if (l2vpn->mtu != DEFAULT_L2VPN_MTU)
			vty_out (vty, " mtu %u\n", l2vpn->mtu);

		if (l2vpn->br_ifname[0] != '\0')
			vty_out (vty, " bridge %s\n",l2vpn->br_ifname);

		RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree)
			vty_out (vty, " member interface %s\n",lif->ifname);

		RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree)
			ldp_l2vpn_pw_config_write(vty, pw);
		RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree)
			ldp_l2vpn_pw_config_write(vty, pw);

		vty_out (vty, " !\n");
		vty_out (vty, "exit\n");
		vty_out (vty, "!\n");
	}

	return (0);
}

static int
ldp_vty_get_af(struct vty *vty)
{
	switch (vty->node) {
	case LDP_IPV4_NODE:
	case LDP_IPV4_IFACE_NODE:
		return (AF_INET);
	case LDP_IPV6_NODE:
	case LDP_IPV6_IFACE_NODE:
		return (AF_INET6);
	default:
		fatalx("ldp_vty_get_af: unexpected node");
	}
}

static int
ldp_iface_is_configured(struct ldpd_conf *xconf, const char *ifname)
{
	struct l2vpn	*l2vpn;

	if (if_lookup_name(xconf, ifname))
		return (1);

	RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) {
		if (l2vpn_if_find(l2vpn, ifname))
			return (1);
		if (l2vpn_pw_find(l2vpn, ifname))
			return (1);
	}

	return (0);
}

int
ldp_vty_mpls_ldp(struct vty *vty, const char *negate)
{
	if (negate)
		vty_conf->flags &= ~F_LDPD_ENABLED;
	else {
		vty->node = LDP_NODE;
		vty_conf->flags |= F_LDPD_ENABLED;
	}

	/* register / de-register to recv info from zebra */
	ldp_zebra_regdereg_zebra_info(!negate);

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_address_family(struct vty *vty, const char *negate, const char *af_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	if (af_str == NULL)
		return (CMD_WARNING_CONFIG_FAILED);

	if (strcmp(af_str, "ipv4") == 0) {
		af = AF_INET;
		af_conf = &vty_conf->ipv4;
	} else if (strcmp(af_str, "ipv6") == 0) {
		af = AF_INET6;
		af_conf = &vty_conf->ipv6;
	} else
		return (CMD_WARNING_CONFIG_FAILED);

	if (negate) {
		af_conf->flags &= ~F_LDPD_AF_ENABLED;
		ldp_config_apply(vty, vty_conf);
		return (CMD_SUCCESS);
	}

	switch (af) {
	case AF_INET:
		vty->node = LDP_IPV4_NODE;
		break;
	case AF_INET6:
		vty->node = LDP_IPV6_NODE;
		break;
	default:
		fatalx("ldp_vty_address_family: unknown af");
	}
	af_conf->flags |= F_LDPD_AF_ENABLED;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int ldp_vty_disc_holdtime(struct vty *vty, const char *negate,
    enum hello_type hello_type, long secs)
{
	struct ldpd_af_conf	*af_conf;
	struct iface		*iface;
	struct iface_af		*ia;
	int			 af;
	switch (vty->node) {
	case LDP_NODE:
		if (negate) {
			switch (hello_type) {
			case HELLO_LINK:
				vty_conf->lhello_holdtime = LINK_DFLT_HOLDTIME;
				break;
			case HELLO_TARGETED:
				vty_conf->thello_holdtime =
				    TARGETED_DFLT_HOLDTIME;
				break;
			}
		} else {
			switch (hello_type) {
			case HELLO_LINK:
				vty_conf->lhello_holdtime = secs;
				break;
			case HELLO_TARGETED:
				vty_conf->thello_holdtime = secs;
				break;
			}
		}
		ldp_config_apply(vty, vty_conf);
		break;
	case LDP_IPV4_NODE:
	case LDP_IPV6_NODE:
		af = ldp_vty_get_af(vty);
		af_conf = ldp_af_conf_get(vty_conf, af);

		if (negate) {
			switch (hello_type) {
			case HELLO_LINK:
				af_conf->lhello_holdtime = 0;
				break;
			case HELLO_TARGETED:
				af_conf->thello_holdtime = 0;
				break;
			}
		} else {
			switch (hello_type) {
			case HELLO_LINK:
				af_conf->lhello_holdtime = secs;
				break;
			case HELLO_TARGETED:
				af_conf->thello_holdtime = secs;
				break;
			}
		}
		ldp_config_apply(vty, vty_conf);
		break;
	case LDP_IPV4_IFACE_NODE:
	case LDP_IPV6_IFACE_NODE:
		af = ldp_vty_get_af(vty);
		iface = VTY_GET_CONTEXT(iface);
		VTY_CHECK_CONTEXT(iface);

		ia = iface_af_get(iface, af);
		if (negate)
			ia->hello_holdtime = 0;
		else
			ia->hello_holdtime = secs;

		ldp_config_apply(vty, vty_conf);
		break;
	default:
		fatalx("ldp_vty_disc_holdtime: unexpected node");
	}

	return (CMD_SUCCESS);
}

int
ldp_vty_disc_interval(struct vty *vty, const char *negate,
    enum hello_type hello_type, long secs)
{
	struct ldpd_af_conf	*af_conf;
	struct iface		*iface;
	struct iface_af		*ia;
	int			 af;

	switch (vty->node) {
	case LDP_NODE:
		if (negate) {
			switch (hello_type) {
			case HELLO_LINK:
				vty_conf->lhello_interval =
				    DEFAULT_HELLO_INTERVAL;
				break;
			case HELLO_TARGETED:
				vty_conf->thello_interval =
				    DEFAULT_HELLO_INTERVAL;
				break;
			}
		} else {
			switch (hello_type) {
			case HELLO_LINK:
				vty_conf->lhello_interval = secs;
				break;
			case HELLO_TARGETED:
				vty_conf->thello_interval = secs;
				break;
			}
		}
		ldp_config_apply(vty, vty_conf);
		break;
	case LDP_IPV4_NODE:
	case LDP_IPV6_NODE:
		af = ldp_vty_get_af(vty);
		af_conf = ldp_af_conf_get(vty_conf, af);

		if (negate) {
			switch (hello_type) {
			case HELLO_LINK:
				af_conf->lhello_interval = 0;
				break;
			case HELLO_TARGETED:
				af_conf->thello_interval = 0;
				break;
			}
		} else {
			switch (hello_type) {
			case HELLO_LINK:
				af_conf->lhello_interval = secs;
				break;
			case HELLO_TARGETED:
				af_conf->thello_interval = secs;
				break;
			}
		}
		ldp_config_apply(vty, vty_conf);
		break;
	case LDP_IPV4_IFACE_NODE:
	case LDP_IPV6_IFACE_NODE:
		af = ldp_vty_get_af(vty);
		iface = VTY_GET_CONTEXT(iface);
		VTY_CHECK_CONTEXT(iface);

		ia = iface_af_get(iface, af);
		if (negate)
			ia->hello_interval = 0;
		else
			ia->hello_interval = secs;

		ldp_config_apply(vty, vty_conf);
		break;
	default:
		fatalx("ldp_vty_disc_interval: unexpected node");
	}

	return (CMD_SUCCESS);
}

int
ldp_vty_targeted_hello_accept(struct vty *vty, const char *negate,
    const char *acl_from_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate) {
		af_conf->flags &= ~F_LDPD_AF_THELLO_ACCEPT;
		af_conf->acl_thello_accept_from[0] = '\0';
	} else {
		af_conf->flags |= F_LDPD_AF_THELLO_ACCEPT;
		if (acl_from_str)
			strlcpy(af_conf->acl_thello_accept_from, acl_from_str,
			    sizeof(af_conf->acl_thello_accept_from));
		else
			af_conf->acl_thello_accept_from[0] = '\0';
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_nbr_session_holdtime(struct vty *vty, const char *negate,
    struct in_addr lsr_id, long secs)
{
	struct nbr_params	*nbrp;

	if (bad_addr_v4(lsr_id)) {
		vty_out (vty, "%% Malformed address\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	nbrp = nbr_params_find(vty_conf, lsr_id);

	if (negate) {
		if (nbrp == NULL)
			return (CMD_SUCCESS);

		nbrp->keepalive = 0;
		nbrp->flags &= ~F_NBRP_KEEPALIVE;
	} else {
		if (nbrp == NULL) {
			nbrp = nbr_params_new(lsr_id);
			RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp);
			QOBJ_REG(nbrp, nbr_params);
		} else if (nbrp->keepalive == secs)
			return (CMD_SUCCESS);

		nbrp->keepalive = secs;
		nbrp->flags |= F_NBRP_KEEPALIVE;
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_af_session_holdtime(struct vty *vty, const char *negate, long secs)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate)
		af_conf->keepalive = DEFAULT_KEEPALIVE;
	else
		af_conf->keepalive = secs;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_interface(struct vty *vty, const char *negate, const char *ifname)
{
	int			 af;
	struct iface		*iface;
	struct iface_af		*ia;

	if (ifname == NULL) {
		vty_out (vty, "%% Missing IF name\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	af = ldp_vty_get_af(vty);
	iface = if_lookup_name(vty_conf, ifname);

	if (negate) {
		if (iface == NULL)
			return (CMD_SUCCESS);

		ia = iface_af_get(iface, af);
		if (ia->enabled == 0)
			return (CMD_SUCCESS);

		ia->enabled = 0;
		ia->hello_holdtime = 0;
		ia->hello_interval = 0;

		ldp_config_apply(vty, vty_conf);

		return (CMD_SUCCESS);
	}

	if (iface == NULL) {
		if (ldp_iface_is_configured(vty_conf, ifname)) {
			vty_out (vty,"%% Interface is already in use\n");
			return (CMD_SUCCESS);
		}

		iface = if_new(ifname);
		ia = iface_af_get(iface, af);
		ia->enabled = 1;
		RB_INSERT(iface_head, &vty_conf->iface_tree, iface);
		QOBJ_REG(iface, iface);

		ldp_config_apply(vty, vty_conf);
	} else {
		ia = iface_af_get(iface, af);
		if (!ia->enabled) {
			ia->enabled = 1;
			ldp_config_apply(vty, vty_conf);
		}
	}

	switch (af) {
	case AF_INET:
		VTY_PUSH_CONTEXT(LDP_IPV4_IFACE_NODE, iface);
		break;
	case AF_INET6:
		VTY_PUSH_CONTEXT(LDP_IPV6_IFACE_NODE, iface);
		break;
	default:
		break;
	}

	return (CMD_SUCCESS);
}

int
ldp_vty_trans_addr(struct vty *vty, const char *negate, const char *addr_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate)
		memset(&af_conf->trans_addr, 0, sizeof(af_conf->trans_addr));
	else {
		if (addr_str == NULL
		    || inet_pton(af, addr_str, &af_conf->trans_addr) != 1
		    || bad_addr(af, &af_conf->trans_addr)) {
			vty_out (vty, "%% Malformed address\n");
			return (CMD_SUCCESS);
		}
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_neighbor_targeted(struct vty *vty, const char *negate, const char *addr_str)
{
	int			 af;
	union ldpd_addr		 addr;
	struct tnbr		*tnbr;

	af = ldp_vty_get_af(vty);

	if (addr_str == NULL || inet_pton(af, addr_str, &addr) != 1 ||
	    bad_addr(af, &addr)) {
		vty_out (vty, "%% Malformed address\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}
	if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&addr.v6)) {
		vty_out (vty, "%% Address can not be link-local\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	tnbr = tnbr_find(vty_conf, af, &addr);

	if (negate) {
		if (tnbr == NULL)
			return (CMD_SUCCESS);

		QOBJ_UNREG(tnbr);
		RB_REMOVE(tnbr_head, &vty_conf->tnbr_tree, tnbr);
		free(tnbr);

		ldp_config_apply(vty, vty_conf);

		return (CMD_SUCCESS);
	}

	if (tnbr)
		return (CMD_SUCCESS);

	tnbr = tnbr_new(af, &addr);
	tnbr->flags |= F_TNBR_CONFIGURED;
	RB_INSERT(tnbr_head, &vty_conf->tnbr_tree, tnbr);
	QOBJ_REG(tnbr, tnbr);

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_label_advertise(struct vty *vty, const char *negate, const char *acl_to_str,
    const char *acl_for_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate) {
		af_conf->acl_label_advertise_to[0] = '\0';
		af_conf->acl_label_advertise_for[0] = '\0';
	} else {
		if (acl_to_str)
			strlcpy(af_conf->acl_label_advertise_to, acl_to_str,
			    sizeof(af_conf->acl_label_advertise_to));
		else
			af_conf->acl_label_advertise_to[0] = '\0';
		if (acl_for_str)
			strlcpy(af_conf->acl_label_advertise_for, acl_for_str,
			    sizeof(af_conf->acl_label_advertise_for));
		else
			af_conf->acl_label_advertise_for[0] = '\0';
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_label_allocate(struct vty *vty, const char *negate, const char *host_routes,
    const char *acl_for_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	af_conf->flags &= ~F_LDPD_AF_ALLOCHOSTONLY;
	af_conf->acl_label_allocate_for[0] = '\0';
	if (!negate) {
		if (host_routes)
			af_conf->flags |= F_LDPD_AF_ALLOCHOSTONLY;
		else
			strlcpy(af_conf->acl_label_allocate_for, acl_for_str,
			    sizeof(af_conf->acl_label_allocate_for));
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_label_expnull(struct vty *vty, const char *negate, const char *acl_for_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate) {
		af_conf->flags &= ~F_LDPD_AF_EXPNULL;
		af_conf->acl_label_expnull_for[0] = '\0';
	} else {
		af_conf->flags |= F_LDPD_AF_EXPNULL;
		if (acl_for_str)
			strlcpy(af_conf->acl_label_expnull_for, acl_for_str,
			    sizeof(af_conf->acl_label_expnull_for));
		else
			af_conf->acl_label_expnull_for[0] = '\0';
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_label_accept(struct vty *vty, const char *negate, const char *acl_from_str,
    const char *acl_for_str)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate) {
		af_conf->acl_label_accept_from[0] = '\0';
		af_conf->acl_label_accept_for[0] = '\0';
	} else {
		if (acl_from_str)
			strlcpy(af_conf->acl_label_accept_from, acl_from_str,
			    sizeof(af_conf->acl_label_accept_from));
		else
			af_conf->acl_label_accept_from[0] = '\0';
		if (acl_for_str)
			strlcpy(af_conf->acl_label_accept_for, acl_for_str,
			    sizeof(af_conf->acl_label_accept_for));
		else
			af_conf->acl_label_accept_for[0] = '\0';
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_ttl_security(struct vty *vty, const char *negate)
{
	struct ldpd_af_conf	*af_conf;
	int			 af;

	af = ldp_vty_get_af(vty);
	af_conf = ldp_af_conf_get(vty_conf, af);

	if (negate)
		af_conf->flags &= ~F_LDPD_AF_NO_GTSM;
	else
		af_conf->flags |= F_LDPD_AF_NO_GTSM;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_router_id(struct vty *vty, const char *negate, struct in_addr address)
{
	if (negate)
		vty_conf->rtr_id.s_addr = INADDR_ANY;
	else {
		if (bad_addr_v4(address)) {
			vty_out (vty, "%% Malformed address\n");
			return (CMD_SUCCESS);
		}
		vty_conf->rtr_id = address;
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_ordered_control(struct vty *vty, const char *negate)
{
	if (negate)
		vty_conf->flags &= ~F_LDPD_ORDERED_CONTROL;
	else
		vty_conf->flags |= F_LDPD_ORDERED_CONTROL;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int ldp_vty_wait_for_sync_interval(struct vty *vty, const char *negate,
    long secs)
{
	switch (vty->node) {
	case LDP_NODE:
		if (negate)
			vty_conf->wait_for_sync_interval = DFLT_WAIT_FOR_SYNC;
		else
			vty_conf->wait_for_sync_interval = secs;

		ldp_config_apply(vty, vty_conf);
		break;
	default:
		fatalx("ldp_vty_wait_for_sync_interval: unexpected node");
	}
	return (CMD_SUCCESS);
}

int
ldp_vty_allow_broken_lsp(struct vty *vty, const char *negate)
{
	if (negate)
		vty_conf->flags &= ~F_LDPD_ALLOW_BROKEN_LSP;
	else
		vty_conf->flags |= F_LDPD_ALLOW_BROKEN_LSP;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_ds_cisco_interop(struct vty *vty, const char * negate)
{
	if (negate)
		vty_conf->flags &= ~F_LDPD_DS_CISCO_INTEROP;
	else
		vty_conf->flags |= F_LDPD_DS_CISCO_INTEROP;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_trans_pref_ipv4(struct vty *vty, const char *negate)
{
	if (negate)
		vty_conf->trans_pref = DUAL_STACK_LDPOV6;
	else
		vty_conf->trans_pref = DUAL_STACK_LDPOV4;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_neighbor_password(struct vty *vty, const char *negate, struct in_addr lsr_id,
    const char *password_str)
{
	size_t			 password_len;
	struct nbr_params	*nbrp;

	if (password_str == NULL) {
		vty_out (vty, "%% Missing password\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	if (bad_addr_v4(lsr_id)) {
		vty_out (vty, "%% Malformed address\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	nbrp = nbr_params_find(vty_conf, lsr_id);

	if (negate) {
		if (nbrp == NULL)
			return (CMD_SUCCESS);

		memset(&nbrp->auth, 0, sizeof(nbrp->auth));
		nbrp->auth.method = AUTH_NONE;
	} else {
		if (nbrp == NULL) {
			nbrp = nbr_params_new(lsr_id);
			RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp);
			QOBJ_REG(nbrp, nbr_params);
		} else if (nbrp->auth.method == AUTH_MD5SIG &&
		    strcmp(nbrp->auth.md5key, password_str) == 0)
			return (CMD_SUCCESS);

		password_len = strlcpy(nbrp->auth.md5key, password_str,
		    sizeof(nbrp->auth.md5key));
		if (password_len >= sizeof(nbrp->auth.md5key))
			vty_out(vty, "%% password has been truncated to %zu characters.", sizeof(nbrp->auth.md5key) - 1);
		nbrp->auth.md5key_len = strlen(nbrp->auth.md5key);
		nbrp->auth.method = AUTH_MD5SIG;
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_neighbor_ttl_security(struct vty *vty, const char *negate,
    struct in_addr lsr_id, const char *hops_str)
{
	struct nbr_params	*nbrp;
	long int		 hops = 0;
	char			*ep;

	if (bad_addr_v4(lsr_id)) {
		vty_out (vty, "%% Malformed address\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	if (hops_str) {
		hops = strtol(hops_str, &ep, 10);
		if (*ep != '\0' || hops < 1 || hops > 254) {
			vty_out (vty, "%% Invalid hop count\n");
			return (CMD_SUCCESS);
		}
	}

	nbrp = nbr_params_find(vty_conf, lsr_id);

	if (negate) {
		if (nbrp == NULL)
			return (CMD_SUCCESS);

		nbrp->flags &= ~(F_NBRP_GTSM|F_NBRP_GTSM_HOPS);
		nbrp->gtsm_enabled = 0;
		nbrp->gtsm_hops = 0;
	} else {
		if (nbrp == NULL) {
			nbrp = nbr_params_new(lsr_id);
			RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp);
			QOBJ_REG(nbrp, nbr_params);
		}

		nbrp->flags |= F_NBRP_GTSM;
		nbrp->flags &= ~F_NBRP_GTSM_HOPS;
		if (hops_str) {
			nbrp->gtsm_enabled = 1;
			nbrp->gtsm_hops = hops;
			nbrp->flags |= F_NBRP_GTSM_HOPS;
		} else
			nbrp->gtsm_enabled = 0;
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn(struct vty *vty, const char *negate, const char *name_str)
{
	struct l2vpn		*l2vpn;
	struct l2vpn_if		*lif;
	struct l2vpn_pw		*pw;

	if (name_str == NULL) {
		vty_out (vty, "%% Missing name\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	l2vpn = l2vpn_find(vty_conf, name_str);

	if (negate) {
		if (l2vpn == NULL)
			return (CMD_SUCCESS);

		RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree)
			QOBJ_UNREG(lif);
		RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree)
			QOBJ_UNREG(pw);
		RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree)
			QOBJ_UNREG(pw);
		QOBJ_UNREG(l2vpn);
		RB_REMOVE(l2vpn_head, &vty_conf->l2vpn_tree, l2vpn);
		l2vpn_del(l2vpn);

		ldp_config_apply(vty, vty_conf);

		return (CMD_SUCCESS);
	}

	if (l2vpn) {
		VTY_PUSH_CONTEXT(LDP_L2VPN_NODE, l2vpn);
		return (CMD_SUCCESS);
	}

	l2vpn = l2vpn_new(name_str);
	l2vpn->type = L2VPN_TYPE_VPLS;
	RB_INSERT(l2vpn_head, &vty_conf->l2vpn_tree, l2vpn);
	QOBJ_REG(l2vpn, l2vpn);

	VTY_PUSH_CONTEXT(LDP_L2VPN_NODE, l2vpn);

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_bridge(struct vty *vty, const char *negate, const char *ifname)
{
	VTY_DECLVAR_CONTEXT(l2vpn, l2vpn);

	if (negate)
		memset(l2vpn->br_ifname, 0, sizeof(l2vpn->br_ifname));
	else {
		if (ifname == NULL) {
			vty_out (vty, "%% Missing IF name\n");
			return (CMD_WARNING_CONFIG_FAILED);
		}
		strlcpy(l2vpn->br_ifname, ifname, sizeof(l2vpn->br_ifname));
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_mtu(struct vty *vty, const char *negate, long mtu)
{
	VTY_DECLVAR_CONTEXT(l2vpn, l2vpn);

	if (negate)
		l2vpn->mtu = DEFAULT_L2VPN_MTU;
	else
		l2vpn->mtu = mtu;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pwtype(struct vty *vty, const char *negate, const char *type_str)
{
	VTY_DECLVAR_CONTEXT(l2vpn, l2vpn);
	int			 pw_type;

	if (type_str == NULL) {
		vty_out (vty, "%% Missing type\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	if (strcmp(type_str, "ethernet") == 0)
		pw_type = PW_TYPE_ETHERNET;
	else
		pw_type = PW_TYPE_ETHERNET_TAGGED;

	if (negate)
		l2vpn->pw_type = DEFAULT_PW_TYPE;
	else
		l2vpn->pw_type = pw_type;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_interface(struct vty *vty, const char *negate, const char *ifname)
{
	VTY_DECLVAR_CONTEXT(l2vpn, l2vpn);
	struct l2vpn_if		*lif;

	if (ifname == NULL) {
		vty_out (vty, "%% Missing IF name\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	lif = l2vpn_if_find(l2vpn, ifname);

	if (negate) {
		if (lif == NULL)
			return (CMD_SUCCESS);

		QOBJ_UNREG(lif);
		RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif);
		free(lif);

		ldp_config_apply(vty, vty_conf);

		return (CMD_SUCCESS);
	}

	if (lif)
		return (CMD_SUCCESS);

	if (ldp_iface_is_configured(vty_conf, ifname)) {
		vty_out (vty, "%% Interface is already in use\n");
		return (CMD_SUCCESS);
	}

	lif = l2vpn_if_new(l2vpn, ifname);
	RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif);
	QOBJ_REG(lif, l2vpn_if);

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pseudowire(struct vty *vty, const char *negate, const char *ifname)
{
	VTY_DECLVAR_CONTEXT(l2vpn, l2vpn);
	struct l2vpn_pw		*pw;

	if (ifname == NULL) {
		vty_out (vty, "%% Missing IF name\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	pw = l2vpn_pw_find(l2vpn, ifname);

	if (negate) {
		if (pw == NULL)
			return (CMD_SUCCESS);

		QOBJ_UNREG(pw);
		if (pw->lsr_id.s_addr == INADDR_ANY || pw->pwid == 0)
			RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw);
		else
			RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw);
		free(pw);

		ldp_config_apply(vty, vty_conf);

		return (CMD_SUCCESS);
	}

	if (pw) {
		VTY_PUSH_CONTEXT_SUB(LDP_PSEUDOWIRE_NODE, pw);
		return (CMD_SUCCESS);
	}

	if (ldp_iface_is_configured(vty_conf, ifname)) {
		vty_out (vty, "%% Interface is already in use\n");
		return (CMD_SUCCESS);
	}

	pw = l2vpn_pw_new(l2vpn, ifname);
	pw->flags = F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF;
	RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw);
	QOBJ_REG(pw, l2vpn_pw);

	VTY_PUSH_CONTEXT_SUB(LDP_PSEUDOWIRE_NODE, pw);

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pw_cword(struct vty *vty, const char *negate, const char *preference_str)
{
	VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw);

	if (negate)
		pw->flags |= F_PW_CWORD_CONF;
	else {
		if (!preference_str) {
			vty_out (vty, "%% Missing preference\n");
			return (CMD_WARNING_CONFIG_FAILED);
		}
		if (preference_str[0] == 'e')
			pw->flags &= ~F_PW_CWORD_CONF;
		else
			pw->flags |= F_PW_CWORD_CONF;
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pw_nbr_addr(struct vty *vty, const char *negate, const char *addr_str)
{
	VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw);
	int			 af;
	union ldpd_addr		 addr;

	if (ldp_get_address(addr_str, &af, &addr) == -1 ||
	    bad_addr(af, &addr)) {
		vty_out (vty, "%% Malformed address\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	if (negate) {
		pw->af = AF_UNSPEC;
		memset(&pw->addr, 0, sizeof(pw->addr));
		pw->flags &= ~F_PW_STATIC_NBR_ADDR;
	} else {
		pw->af = af;
		pw->addr = addr;
		pw->flags |= F_PW_STATIC_NBR_ADDR;
	}

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pw_nbr_id(struct vty *vty, const char *negate, struct in_addr lsr_id)
{
	VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw);

	if (bad_addr_v4(lsr_id)) {
		vty_out (vty, "%% Malformed address\n");
		return (CMD_WARNING_CONFIG_FAILED);
	}

	if (negate)
		pw->lsr_id.s_addr = INADDR_ANY;
	else
		pw->lsr_id = lsr_id;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pw_pwid(struct vty *vty, const char *negate, long pwid)
{
	VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw);

	if (negate)
		pw->pwid = 0;
	else
		pw->pwid = pwid;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

int
ldp_vty_l2vpn_pw_pwstatus(struct vty *vty, const char *negate)
{
	VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw);

	if (negate)
		pw->flags |= F_PW_STATUSTLV_CONF;
	else
		pw->flags &= ~F_PW_STATUSTLV_CONF;

	ldp_config_apply(vty, vty_conf);

	return (CMD_SUCCESS);
}

struct iface *
iface_new_api(struct ldpd_conf *conf, const char *name)
{
	const char		*ifname = name;
	struct iface		*iface;

	if (ldp_iface_is_configured(conf, ifname))
		return (NULL);

	iface = if_new(name);
	RB_INSERT(iface_head, &conf->iface_tree, iface);
	QOBJ_REG(iface, iface);
	return (iface);
}

void
iface_del_api(struct ldpd_conf *conf, struct iface *iface)
{
	QOBJ_UNREG(iface);
	RB_REMOVE(iface_head, &conf->iface_tree, iface);
	free(iface);
}

struct tnbr *
tnbr_new_api(struct ldpd_conf *conf, int af, union ldpd_addr *addr)
{
	struct tnbr		*tnbr;

	if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&addr->v6))
		return (NULL);

	if (tnbr_find(conf, af, addr))
		return (NULL);

	tnbr = tnbr_new(af, addr);
	tnbr->flags |= F_TNBR_CONFIGURED;
	RB_INSERT(tnbr_head, &conf->tnbr_tree, tnbr);
	QOBJ_REG(tnbr, tnbr);
	return (tnbr);
}

void
tnbr_del_api(struct ldpd_conf *conf, struct tnbr *tnbr)
{
	QOBJ_UNREG(tnbr);
	RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr);
	free(tnbr);
}

struct nbr_params *
nbrp_new_api(struct ldpd_conf *conf, struct in_addr lsr_id)
{
	struct nbr_params	*nbrp;

	if (nbr_params_find(conf, lsr_id))
		return (NULL);

	nbrp = nbr_params_new(lsr_id);
	RB_INSERT(nbrp_head, &conf->nbrp_tree, nbrp);
	QOBJ_REG(nbrp, nbr_params);
	return (nbrp);
}

void
nbrp_del_api(struct ldpd_conf *conf, struct nbr_params *nbrp)
{
	QOBJ_UNREG(nbrp);
	RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp);
	free(nbrp);
}

struct l2vpn *
l2vpn_new_api(struct ldpd_conf *conf, const char *name)
{
	struct l2vpn		*l2vpn;

	if (l2vpn_find(conf, name))
		return (NULL);

	l2vpn = l2vpn_new(name);
	l2vpn->type = L2VPN_TYPE_VPLS;
	RB_INSERT(l2vpn_head, &conf->l2vpn_tree, l2vpn);
	QOBJ_REG(l2vpn, l2vpn);
	return (l2vpn);
}

void
l2vpn_del_api(struct ldpd_conf *conf, struct l2vpn *l2vpn)
{
	struct l2vpn_if		*lif;
	struct l2vpn_pw		*pw;

	while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) {
		lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree);

		QOBJ_UNREG(lif);
		RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif);
		free(lif);
	}
	while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) {
		pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree);

		QOBJ_UNREG(pw);
		RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw);
		free(pw);
	}
	while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) {
		pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree);

		QOBJ_UNREG(pw);
		RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw);
		free(pw);
	}
	QOBJ_UNREG(l2vpn);
	RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn);
	free(l2vpn);
}

struct l2vpn_if *
l2vpn_if_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn,
    const char *ifname)
{
	struct l2vpn_if		*lif;

	if (ldp_iface_is_configured(conf, ifname))
		return (NULL);

	lif = l2vpn_if_new(l2vpn, ifname);
	RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif);
	QOBJ_REG(lif, l2vpn_if);
	return (lif);
}

void
l2vpn_if_del_api(struct l2vpn *l2vpn, struct l2vpn_if *lif)
{
	QOBJ_UNREG(lif);
	RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif);
	free(lif);
}

struct l2vpn_pw *
l2vpn_pw_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn,
    const char *ifname)
{
	struct l2vpn_pw		*pw;

	if (ldp_iface_is_configured(conf, ifname))
		return (NULL);

	pw = l2vpn_pw_new(l2vpn, ifname);
	pw->flags = F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF;
	RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw);
	QOBJ_REG(pw, l2vpn_pw);
	return (pw);
}

void
l2vpn_pw_del_api(struct l2vpn *l2vpn, struct l2vpn_pw *pw)
{
	QOBJ_UNREG(pw);
	RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw);
	free(pw);
}
