/*
 * Copyright (c) 2004, Archie L. Cobbs.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * 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.
 * * Neither the name "sl2tps" 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 COPYRIGHT HOLDERS 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
 * COPYRIGHT OWNER 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.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include "sls_global.h"
#include "sls_config.h"

#define MANAGER_MEM_TYPE			"sls_bundle"

/*
 * PPP manager definition.
 */
static ppp_manager_bundle_config_t	sls_manager_bundle_config;
static ppp_manager_bundle_plumb_t	sls_manager_bundle_plumb;
static ppp_manager_bundle_unplumb_t	sls_manager_bundle_unplumb;
static ppp_manager_release_ip_t		sls_manager_release_ip;

static struct ppp_manager_meth sls_manager_methods = {
	sls_manager_bundle_config,
	sls_manager_bundle_plumb,
	sls_manager_bundle_unplumb,
	sls_manager_release_ip,
};

struct ppp_manager sls_manager = {
	&sls_manager_methods
};

/* Internal functions */
static int	sls_manager_proxy_arp(struct in_addr ip, u_char *ether);

/*
 * Private per-bundle info.
 */
struct sls_bundle {
	int		pool_index;
	int		proxy_arp;
	struct in_addr	ip;
	char		nodepath[64];		/* ng_iface(4) node path */
};

/***********************************************************************
			MANAGER METHODS
***********************************************************************/

static void *
sls_manager_bundle_config(struct ppp_manager *manager,
	struct ppp_link *link, struct ppp_bundle_config *conf)
{
	const char *const username = ppp_link_get_authname(link, PPP_PEER);
	u_char ether[ETHER_ADDR_LEN];
	struct ppp_bundle **bundles;
	struct in_addr ip = { 0 };
	struct sls_bundle *info;
	struct sls_user *user;
	int num_bundles;
	int i;

	/* Allocate return value */
	if ((info = MALLOC(MANAGER_MEM_TYPE, sizeof(*info))) == NULL) {
		alog(LOG_ERR, "%s: %m", "malloc");
		goto fail;
	}
	memset(info, 0, sizeof(*info));
	info->pool_index = -1;
	info->ip = ip;

	/* Find user */
	for (i = 0; i < sls_curconf->users.length
	    && strcmp(sls_curconf->users.elems[i].name, username) != 0; i++);
	if (i == sls_curconf->users.length) {
		errno = ENOENT;		/* this should never happen */
		goto fail;
	}
	user = &sls_curconf->users.elems[i];

	/* See if user is already connected */
	if ((num_bundles = ppp_engine_get_bundles(engine,
	    &bundles, TYPED_MEM_TEMP)) == -1) {
		alog(LOG_ERR, "%s: %m", "ppp_engine_get_bundles");
		goto fail;
	}
	for (i = 0; i < num_bundles; i++) {
		const char *const bundle_owner
		    = ppp_bundle_get_authname(bundles[i], PPP_PEER);
		struct ppp_link *links[64];
		int num_links;
		int j;

		/* Skip the same bundle we're configuring */
		num_links = ppp_bundle_get_links(bundles[i],
		    links, sizeof(links) / sizeof(*links));
		for (j = 0; j < num_links && links[j] != link; j++);
		if (j < num_links)
			continue;

		/* See if user is also connected via a different bundle */
		if (strcmp(username, bundle_owner) == 0) {
			alog(LOG_NOTICE, "user \"%s\" tried to connect twice",
			    username);
			FREE(TYPED_MEM_TEMP, bundles);
			goto fail;
		}
	}
	FREE(TYPED_MEM_TEMP, bundles);

	/* Allocate an IP address */
	if (user->ip.s_addr != 0)
		ip = user->ip;
	else if (ip_pool != NULL) {
		for (i = 0; i < sls_curconf->ip_pool_size; i++) {
			if ((ip_pool[i / 32] & (1 << (i % 32))) == 0) {
				ip.s_addr = htonl(ntohl(
				    sls_curconf->ip_pool_start.s_addr) + i);
				break;
			}
		}
		if (ip.s_addr != 0) {
			ip_pool[i / 32] |= (1 << (i % 32));
			info->pool_index = i;
		}
	}
	if (ip.s_addr == 0) {
		alog(LOG_ERR, "no IP address available for \"%s\"", username);
		errno = EALREADY;
		goto fail;
	}

	/* See if we should proxy-ARP */
	if (sls_manager_proxy_arp(ip, ether)) {
		if (if_set_arp(ip, ether, 0, 1) == -1) {
			alog(LOG_ERR, "can't proxy-arp for %s: %m",
			    inet_ntoa(ip));
			goto fail;
		}
		info->proxy_arp = 1;
	}

	/* Configure bundle */
	memset(conf, 0, sizeof(*conf));
	conf->ip[PPP_SELF] = sls_curconf->inside_ip;
	conf->ip[PPP_PEER] = ip;
	for (i = 0; i < 2 && i < sls_curconf->dns_servers.length; i++)
		conf->dns_servers[i] = sls_curconf->dns_servers.elems[i];
	for (i = 0; i < 2 && i < sls_curconf->nbns_servers.length; i++)
		conf->nbns_servers[i] = sls_curconf->nbns_servers.elems[i];
	conf->vjc = 1;
	conf->mppe_128 = 1;
	conf->mppe_stateless = 1;

	/* Done */
	return info;

fail:
	/* Clean up after failure */
	if (info != NULL) {
		if (info->proxy_arp) {
			if (if_set_arp(info->ip, NULL, 0, 0) == -1)
				alog(LOG_ERR, "%s: %m", "if_set_arp");
		}
		if ((i = info->pool_index) != -1)
			ip_pool[i / 32] &= ~(1 << (i % 32));
		FREE(MANAGER_MEM_TYPE, info);
	}
	return NULL;
}

static void *
sls_manager_bundle_plumb(struct ppp_manager *manager,
	struct ppp_bundle *bundle, const char *path, const char *hook,
	struct in_addr *ips, struct in_addr *dns, struct in_addr *nbns,
	u_int mtu)
{
	struct sls_bundle *const info = ppp_bundle_get_cookie(bundle);
	static const struct in_addr fullmask = { ~0 };
	union {
	    u_char buf[sizeof(struct ng_mesg) + 128];
	    struct ng_mesg reply;
	} repbuf;
	struct ng_mesg *const reply = &repbuf.reply;
	char *const ifname = (char *)reply->data;
	char ifpath[64];
	int csock = -1;
	int esave;

	/* Get temporary socket node */
	if (NgMkSockNode(NULL, &csock, NULL) == -1) {
		alog(LOG_ERR, "%s: %m", "NgMkSockNode");
		goto fail;
	}
	snprintf(ifpath, sizeof(ifpath), "%s%s", path, hook);

	/* Attach iface node */
	if (NgSendAsciiMsg(csock, path, "mkpeer { type=\"%s\""
	    " ourhook=\"%s\" peerhook=\"%s\" }", NG_IFACE_NODE_TYPE,
	    hook, NG_IFACE_HOOK_INET) == -1) {
		alog(LOG_ERR, "%s: %m", "mkpeer");
		goto fail;
	}

	/* Get node name */
	if (NgSendMsg(csock, ifpath, NGM_IFACE_COOKIE, NGM_IFACE_GET_IFNAME,
	    NULL, 0) == -1) {
		alog(LOG_ERR, "%s: %m", "NgSendMsg");
		goto fail;
	}
	if (NgRecvMsg(csock, reply, sizeof(repbuf), NULL) == -1) {
		alog(LOG_ERR, "%s: %m", "NgRecvMsg");
		goto fail;
	}

	/* Configure iface node */
	if (if_add_ip_addr(ifname,
	    ips[PPP_SELF], fullmask, ips[PPP_PEER]) == -1) {
		alog(LOG_ERR, "if_add_ip(%s, %s): %m",
		    ifname, inet_ntoa(ips[PPP_SELF]));
		goto fail;
	}
	if (if_set_mtu(ifname, mtu) == -1) {
		alog(LOG_ERR, "if_setmtu(%s, %d): %m", ifname, mtu);
		goto fail;
	}

	/* Get return value which is the name of the interface node */
	snprintf(info->nodepath, sizeof(info->nodepath), "%s:", ifname);

	/* Done */
	(void)close(csock);
	return info;

fail:
	/* Clean up after failure */
	esave = errno;
	if (csock != -1) {
		(void)NgSendMsg(csock, ifpath, NGM_GENERIC_COOKIE,
		    NGM_SHUTDOWN, NULL, 0);
		(void)close(csock);
	}
	errno = esave;
	return NULL;
}

static void
sls_manager_bundle_unplumb(struct ppp_manager *manager, void *arg,
	struct ppp_bundle *bundle)
{
	struct sls_bundle *const info = ppp_bundle_get_cookie(bundle);
	char *const ifpath = arg;
	int csock;

	/* Get temporary socket node */
	if (NgMkSockNode(NULL, &csock, NULL) == -1) {
		alog(LOG_ERR, "%s: %m", "NgMkSockNode");
		return;
	}

	/* Remove proxy ARP entry (if any) */
	if (info->proxy_arp) {
		if (if_set_arp(info->ip, NULL, 0, 0) == -1)
			alog(LOG_ERR, "%s: %m", "if_set_arp");
	}

	/* Kill iface node */
	if (NgSendMsg(csock, info->nodepath,
	    NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0) == -1)
		alog(LOG_ERR, "shutdown(%s): %m", ifpath);

	/* Done */
	(void)close(csock);
}

static void
sls_manager_release_ip(struct ppp_manager *manager,
	struct ppp_bundle *bundle, struct in_addr ip)
{
	struct sls_bundle *const info = ppp_bundle_get_cookie(bundle);
	int i;

	/* Free up pool IP address */
	if ((i = info->pool_index) != -1 && ip_pool != NULL) {
		assert((ip_pool[i / 32] & (1 << (i % 32))) != 0);
		ip_pool[i / 32] &= ~(1 << (i % 32));
	}
	FREE(MANAGER_MEM_TYPE, info);
}

/*
 * Determine if IP address should be proxy-ARP'd.
 * If so, return 1 and fill in the ethernet address.
 */
static int
sls_manager_proxy_arp(struct in_addr ip, u_char *ether)
{
	char **ifaces;
	int num_ifaces;
	int found = 0;
	int i;

	/* Get interface list */
	if ((num_ifaces = if_get_list(&ifaces, TYPED_MEM_TEMP)) == -1) {
		alog(LOG_ERR, "%s: %m", "if_get_list");
		return 0;
	}

	/* Check for a suitable match */
	for (i = 0; i < num_ifaces; i++) {
		const char *const iface = ifaces[i];
		const int flags = if_get_flags(iface);
		struct sockaddr_dl *sdl;
		struct in_addr *ips;
		struct in_addr *masks;
		int num_ips;
		int j;

		/* Check interface */
		if ((flags & (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT
		    |IFF_LOOPBACK|IFF_NOARP)) != (IFF_UP|IFF_BROADCAST))
			continue;

		/* Get configured subnets */
		if ((num_ips = if_get_ip_addrs(iface,
		    &ips, &masks, TYPED_MEM_TEMP)) == -1) {
			alog(LOG_ERR, "%s: %m", "if_get_ip_addrs");
			continue;
		}

		/* Look for a matching subnet */
		for (j = 0; j < num_ips; j++) {
			const struct in_addr netip = ips[j];
			const struct in_addr netmask = masks[j];

			if ((netip.s_addr & netmask.s_addr)
			    == (ip.s_addr & netmask.s_addr))
				break;
		}
		FREE(TYPED_MEM_TEMP, ips);
		FREE(TYPED_MEM_TEMP, masks);
		if (j == num_ips)
			continue;

		/* Get Ethernet address associated with interface */
		if (if_get_link_addr(iface, &sdl, TYPED_MEM_TEMP) == -1) {
			alog(LOG_ERR, "%s: %m", "if_get_link_addr");
			continue;
		}
		if (sdl->sdl_alen != ETHER_ADDR_LEN) {
			FREE(TYPED_MEM_TEMP, sdl);
			continue;
		}
		memcpy(ether, LLADDR(sdl), ETHER_ADDR_LEN);
		FREE(TYPED_MEM_TEMP, sdl);
		found = 1;
		break;
	}

	/* Clean up */
	for (i = 0; i < num_ifaces; i++)
		FREE(TYPED_MEM_TEMP, ifaces[i]);
	FREE(TYPED_MEM_TEMP, ifaces);

	/* Done */
	return found;
}



syntax highlighted by Code2HTML, v. 0.9.1