File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / ppp / ppp_pptp_server.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 3 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include "ppp/ppp_defs.h"
#include "ppp/ppp_log.h"
#include "ppp/ppp_engine.h"
#include "ppp/ppp_fsm_option.h"
#include "ppp/ppp_auth.h"
#include "ppp/ppp_lcp.h"
#include "ppp/ppp_link.h"
#include "ppp/ppp_channel.h"
#include "ppp/ppp_pptp_server.h"
#include "ppp/ppp_pptp_ctrl.h"
#include "ppp/ppp_pptp_ctrl_defs.h"

#include <net/ethernet.h>
#include <netgraph/ng_pptpgre.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>

#define PPTP_MTYPE		"ppp_pptp"
#define PPTP_OUTPUT_MTYPE	"ppp_pptp.output"
#define PPTP_DEVICE_MTYPE	"ppp_pptp.device"
#define PPTP_PEER_MTYPE		"ppp_pptp.peer"

#if 0			/* win2k seems to require at least 1500 */
#define PPTP_MRU							\
    (ETHER_MAX_LEN		/* standard ethernet frame */		\
	- ETHER_CRC_LEN		/* ethernet crc */			\
	- ETHER_HDR_LEN		/* ethernet header */			\
	- sizeof(struct ip)	/* ip header */				\
	- 16			/* gre header */			\
	- 2)			/* ppp address & control fields */
#else
#define PPTP_MRU		LCP_DEFAULT_MRU
#endif

#define PPTP_MRRU		LCP_DEFAULT_MRRU

#define PPTPGRE_ALWAYS_ACK	1

/* PPTP server info */
struct pptp_server {
	struct ppp_pptp_server_info	info;	/* client info */
	struct ppp_log			*log;	/* log */
	struct ppp_engine		*engine;/* associated ppp engine */
	struct pptp_engine		*pptp;	/* pptp control engine */
	struct pevent_ctx		*ev_ctx;/* event context */
	pthread_mutex_t			*mutex;	/* mutex */
	struct in_addr			ip;	/* my ip address */
	u_int16_t			port;	/* my port */
	u_char				shutdown;/* server is shutdown */
	u_int				npeers;	/* number of extant devices */
};

/* Remote peer info */
struct ppp_pptp_peer {
	struct pptp_server		*s;	/* back pointer to server */
	struct ppp_channel		*chan;	/* back pointer to channel */
	struct pptpctrlinfo		cinfo;	/* pptp control channel info */
	struct nodeinfo			ninfo;	/* ng_pptpgre(4) node info */
	struct pevent			*answer;	/* pptp answer event */
	char				path[32];	/* pptpgre node path */
	char				logname[32];	/* peer logname */
	void				*carg;	/* client callback arg */
	struct in_addr			ip;	/* peer remote ip address */
	u_int16_t			port;	/* peer remote port */
	u_char				closed;	/* closed by client side */
};

/* PPTP control callbacks */
static PptpCheckNewConn_t	ppp_pptp_server_check_new_conn;
static PptpGetInLink_t		ppp_pptp_server_get_in_link;
static PptpGetOutLink_t		ppp_pptp_server_get_out_link;

/* Device methods */
static ppp_channel_open_t		ppp_pptp_server_device_open;
static ppp_channel_close_t		ppp_pptp_server_device_close;
static ppp_channel_destroy_t		ppp_pptp_server_device_destroy;
static ppp_channel_free_output_t	ppp_pptp_server_device_free_output;
static ppp_channel_set_link_info_t	ppp_pptp_server_device_set_link_info;
static ppp_channel_get_origination_t	ppp_pptp_server_device_get_origination;
static ppp_channel_get_node_t		ppp_pptp_server_device_get_node;
static ppp_channel_get_hook_t		ppp_pptp_server_device_get_hook;
static ppp_channel_is_async_t		ppp_pptp_server_device_is_async;
static ppp_channel_get_mtu_t		ppp_pptp_server_device_get_mtu;
static ppp_channel_get_acfcomp_t	ppp_pptp_server_device_get_acfcomp;
static ppp_channel_get_pfcomp_t		ppp_pptp_server_device_get_pfcomp;

static struct ppp_channel_meth ppp_pptp_server_device_meth = {
	ppp_pptp_server_device_open,
	ppp_pptp_server_device_close,
	ppp_pptp_server_device_destroy,
	ppp_pptp_server_device_free_output,
	ppp_pptp_server_device_set_link_info,
	ppp_pptp_server_device_get_origination,
	ppp_pptp_server_device_get_node,
	ppp_pptp_server_device_get_hook,
	ppp_pptp_server_device_is_async,
	ppp_pptp_server_device_get_mtu,
	ppp_pptp_server_device_get_acfcomp,
	ppp_pptp_server_device_get_pfcomp,
};

/* Other internal functions */
static struct	ppp_pptp_peer *ppp_pptp_server_new_peer(struct pptp_server *s,
			struct in_addr ip, u_int16_t port,
			const struct pptpctrlinfo *cinfo);
static void	ppp_pptp_server_device_output(struct ppp_pptp_peer *peer,
			enum ppp_channeloutput type, ...);

static void	ppp_pptp_server_cancel(void *cookie);
static void	ppp_pptp_server_result(void *cookie, const char *errmsg);

static pevent_handler_t	ppp_pptp_server_answer;

/* Macro for logging */
#define LOG(sev, fmt, args...)	PPP_LOG(s->log, sev, fmt , ## args)

/***********************************************************************
			PUBLIC FUNCTIONS
***********************************************************************/

/*
 * Start the PPTP server associated with a ppp engine.
 */
int
ppp_pptp_server_start(struct ppp_engine *engine,
	const struct ppp_pptp_server_info *info,
	struct in_addr ip, u_int16_t port, u_int max_conn)
{
	struct ppp_log *const elog = ppp_engine_get_log(engine);
	struct pptp_server *s;

	/* Sanity */
	if (engine == NULL || info->arg == NULL) {
		errno = EINVAL;
		return (-1);
	}

	/* See if server already exists */
	if ((s = ppp_engine_get_pptp_server(engine)) != NULL) {
		errno = EALREADY;
		return (-1);
	}

	/* Create new server */
	if ((s = MALLOC(PPTP_MTYPE, sizeof(*s))) == NULL)
		return (-1);
	memset(s, 0, sizeof(*s));
	s->engine = engine;
	s->ev_ctx = ppp_engine_get_ev_ctx(engine);
	s->mutex = ppp_engine_get_mutex(engine);
	s->log = ppp_log_dup(elog);
	s->info = *info;
	s->ip = ip;
	s->port = port;

	/* Start PPTP */
	if ((s->pptp = PptpCtrlInit(s, s->ev_ctx, s->mutex,
	    ppp_pptp_server_check_new_conn, ppp_pptp_server_get_in_link,
	    ppp_pptp_server_get_out_link, ip, port, info->vendor,
	    ppp_log_dup(elog), 1)) == NULL) {
		ppp_log_put(elog, LOG_ERR, "failed to initialize pptp");
		FREE(PPTP_MTYPE, s);
		return (-1);
	}

	/* Enable incoming connections */
	if (PptpCtrlListen(s->pptp, 1) == -1) {
		ppp_log_put(elog, LOG_ERR, "failed start pptp server");
		PptpCtrlShutdown(&s->pptp);
		FREE(PPTP_MTYPE, s);
		return (-1);
	}

	/* Done */
	ppp_engine_set_pptp_server(engine, s);
	return (0);
}

/*
 * Stop the PPTP server associated with a ppp engine.
 *
 * We can't completely destroy it, because there may be PPTP devices
 * in use by ppp_link's that still exist. The ppp_link's are responsible
 * for destroying their devices, not us.
 */
void
ppp_pptp_server_stop(struct ppp_engine *engine)
{
	struct pptp_server *s;

	if ((s = ppp_engine_get_pptp_server(engine)) == NULL)
		return;
	ppp_engine_set_pptp_server(s->engine, NULL);
	PptpCtrlShutdown(&s->pptp);
	if (s->npeers == 0) {
		ppp_log_close(&s->log);
		FREE(PPTP_MTYPE, s);
		return;
	}

	/* Wait for all devices to be destroyed */
	s->shutdown = 1;
}

/*
 * Close a PPTP connection.
 */
void
ppp_pptp_server_close(struct ppp_engine *engine, struct ppp_pptp_peer **peerp)
{
	struct ppp_pptp_peer *const peer = *peerp;

	if (peer == NULL)
		return;
	*peerp = NULL;
	peer->carg = NULL;		/* don't call client 'destroy' method */
	ppp_pptp_server_device_close(peer->chan);
}

/*
 * Get the client handle for the PPTP channel associated with a device.
 */
void *
ppp_pptp_server_get_client_info(struct ppp_channel *chan)
{
	struct ppp_pptp_peer *const peer = chan->priv;

	if (chan->meth != &ppp_pptp_server_device_meth) {
		errno = EINVAL;
		return (NULL);
	}
	if (peer->carg == NULL)
		errno = ENXIO;
	return (peer->carg);
}

/***********************************************************************
			PPTP CONTROL CALLBACKS
***********************************************************************/

static int
ppp_pptp_server_check_new_conn(void *arg, struct in_addr ip,
	u_int16_t port, char *logname, size_t max)
{
	struct pptp_server *const s = arg;

	if (s->info.getlogname != NULL)
		(*s->info.getlogname)(s->info.arg, ip, port, logname, max);
	return (0);
}

static struct pptplinkinfo
ppp_pptp_server_get_in_link(void *arg, struct pptpctrlinfo cinfo,
	struct in_addr ip, u_int16_t port, int bearType,
	const char *callingNum, const char *calledNum, const char *subAddress)
{
	struct pptp_server *const s = arg;
	struct pptplinkinfo info;
	struct ppp_pptp_peer *peer;

	/* Create new peer */
	memset(&info, 0, sizeof(info));
	if ((peer = ppp_pptp_server_new_peer(s, ip, port, &cinfo)) == NULL)
		return (info);

	/* Fill in response */
	info.cookie = peer;
	info.cancel = ppp_pptp_server_cancel;
	info.result = ppp_pptp_server_result;

	/* Output 'up' message */
	ppp_pptp_server_device_output(peer, PPP_CHANNEL_OUTPUT_UP);

	/* Done */
	return (info);
}

static struct pptplinkinfo
ppp_pptp_server_get_out_link(void *arg, struct pptpctrlinfo cinfo,
	struct in_addr ip, u_int16_t port, int bearType, int frameType,
	int minBps, int maxBps, const char *calledNum, const char *subAddress)
{
	struct pptp_server *const s = arg;
	struct pptplinkinfo info;
	struct ppp_pptp_peer *peer;

	/* Create new peer */
	memset(&info, 0, sizeof(info));
	if ((peer = ppp_pptp_server_new_peer(s, ip, port, &cinfo)) == NULL)
		return (info);

	/* Set up to 'answer' the outgoing call (but not reentrantly) */
	pevent_unregister(&peer->answer);
	if (pevent_register(s->ev_ctx, &peer->answer, 0, s->mutex,
	    ppp_pptp_server_answer, peer, PEVENT_TIME, 0) == -1) {
		LOG(LOG_ERR, "pevent_register: %m");
		return (info);
	}

	/* Fill in response */
	info.cookie = peer;
	info.cancel = ppp_pptp_server_cancel;
	info.result = ppp_pptp_server_result;

	/* Done */
	return (info);
}

static void
ppp_pptp_server_result(void *cookie, const char *errmsg)
{
	struct ppp_pptp_peer *const peer = cookie;
	struct pptp_server *const s = peer->s;

	LOG(LOG_INFO, "call from %s terminated: %s", peer->logname, errmsg);
	peer->cinfo.cookie = NULL;	/* don't call cinfo close() method */
	ppp_pptp_server_device_close(peer->chan);
}

static void
ppp_pptp_server_cancel(void *cookie)
{
	struct ppp_pptp_peer *const peer = cookie;

	(void)peer;
	assert(0);
}

/***********************************************************************
			INTERNAL FUNCTIONS
***********************************************************************/

/*
 * Create a new PPTP peer object corresponding to a PPTP channel
 * and start up a new PPP link/bundle.
 */
static struct ppp_pptp_peer *
ppp_pptp_server_new_peer(struct pptp_server *s, struct in_addr ip,
	u_int16_t port, const struct pptpctrlinfo *cinfo)
{
	union {
	    u_char repbuf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
	    struct ng_mesg reply;
	} repbuf;
	struct ng_mesg *reply = &repbuf.reply;
	struct ppp_link_config link_config;
	struct ng_pptpgre_conf greconf;
	struct ppp_pptp_peer *peer = NULL;
	struct ppp_auth_config auth;
	struct ppp_log *log = NULL;
	struct in_addr pptp_ip[2];
	struct ngm_mkpeer mkpeer;
	struct ngm_rmhook rmhook;
	int csock = -1;
	int esave;

	/* Create peer info structure */
	if ((peer = MALLOC(PPTP_PEER_MTYPE, sizeof(*peer))) == NULL) {
		LOG(LOG_ERR, "can't allocate new device: %m");
		goto fail;
	}
	memset(peer, 0, sizeof(*peer));
	peer->s = s;
	peer->cinfo = *cinfo;
	peer->ip = ip;
	peer->port = port;

	/* Create ng_pptpgre(4) node */
	if (NgMkSockNode(NULL, &csock, NULL) == -1) {
		LOG(LOG_ERR, "can't create socket node: %m");
		goto fail;
	}
	memset(&mkpeer, 0, sizeof(mkpeer));
	strlcpy(mkpeer.type, NG_PPTPGRE_NODE_TYPE, sizeof(mkpeer.type));
	strlcpy(mkpeer.ourhook, NG_PPTPGRE_HOOK_UPPER, sizeof(mkpeer.ourhook));
	strlcpy(mkpeer.peerhook,
	    NG_PPTPGRE_HOOK_UPPER, sizeof(mkpeer.peerhook));
	if (NgSendMsg(csock, ".",
	    NGM_GENERIC_COOKIE, NGM_MKPEER, &mkpeer, sizeof(mkpeer)) == -1) {
		LOG(LOG_ERR, "can't create pptpgre node: %m");
		goto fail;
	}
	snprintf(peer->path, sizeof(peer->path), "%s", NG_PPTPGRE_HOOK_UPPER);

	/* Get node info including 'id' and create absolute path for node */
	if (NgSendMsg(csock, NG_PPTPGRE_HOOK_UPPER,
	    NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) == -1) {
		LOG(LOG_ERR, "can't get node info: %m");
		goto fail;
	}
	memset(&repbuf, 0, sizeof(repbuf));
	if (NgRecvMsg(csock, reply, sizeof(repbuf), NULL) == -1) {
		LOG(LOG_ERR, "can't read node info: %m");
		goto fail;
	}
	memcpy(&peer->ninfo, reply->data, sizeof(peer->ninfo));
	snprintf(peer->path, sizeof(peer->path),
	    "[%lx]:", (long)peer->ninfo.id);

	/* Check with client library */
	strlcpy(peer->logname, inet_ntoa(ip), sizeof(peer->logname));
	memset(&auth, 0, sizeof(auth));
	if ((peer->carg = (*s->info.admit)(s->info.arg, peer, ip, port,
	    &auth, peer->logname, sizeof(peer->logname))) == NULL)
		goto fail;

	/* Get PPTP session info */
	memset(&greconf, 0, sizeof(greconf));
	if (PptpCtrlGetSessionInfo(cinfo, &pptp_ip[PPP_SELF],
	    &pptp_ip[PPP_PEER], &greconf.cid, &greconf.peerCid,
	    &greconf.recvWin, &greconf.peerPpd) == -1) {
		LOG(LOG_ERR, "can't get pptp session info: %m");
		goto fail;
	}

	/* Configure pptpgre node */
	greconf.enabled = 1;
	greconf.enableDelayedAck = 1;
#if PPTPGRE_ALWAYS_ACK
	greconf.enableAlwaysAck = 1;
#endif
	if (NgSendMsg(csock, peer->path, NGM_PPTPGRE_COOKIE,
	    NGM_PPTPGRE_SET_CONFIG, &greconf, sizeof(greconf)) == -1) {
		LOG(LOG_ERR, "can't configure pptpgre node: %m");
		goto fail;
	}

	/* Plumb the 'lower' side of the ng_pptpgre(4) node */
	if ((*s->info.plumb)(s->info.arg,
	    peer->carg, peer->path, NG_PPTPGRE_HOOK_LOWER, pptp_ip) == -1) {
		LOG(LOG_ERR, "error plumbing node: %m");
		goto fail;
	}

	/* Disconnect from the node so the link can connect to it */
	memset(&rmhook, 0, sizeof(rmhook));
	strlcpy(rmhook.ourhook, NG_PPTPGRE_HOOK_UPPER, sizeof(rmhook.ourhook));
	if (NgSendMsg(csock, ".", NGM_GENERIC_COOKIE,
	      NGM_RMHOOK, &rmhook, sizeof(rmhook)) == -1) {
		LOG(LOG_ERR, "can't unhook from node: %m");
		goto fail;
	}

	/* Create a new PPP device for this pptp connection */
	if ((peer->chan = MALLOC(PPTP_DEVICE_MTYPE,
	    sizeof(*peer->chan))) == NULL) {
		LOG(LOG_ERR, "can't allocate new channel: %m");
		goto fail;
	}
	memset(peer->chan, 0, sizeof(*peer->chan));
	peer->chan->meth = &ppp_pptp_server_device_meth;
	peer->chan->priv = peer;

	/* Create device output message port */
	if ((peer->chan->outport
	    = mesg_port_create("ppp_pptp_server")) == NULL) {
		LOG(LOG_ERR, "can't create mesg_port: %m");
		goto fail;
	}

	/* Create log for the new link by prefixing the engine's log */
	if ((log = ppp_engine_get_log(s->engine)) != NULL
	    && (log = ppp_log_prefix(log, "%s: ", peer->logname)) == NULL) {
		LOG(LOG_ERR, "can't create link log: %m");
		goto fail;
	}

	/* Configure new link */
	memset(&link_config, 0, sizeof(link_config));
	link_config.auth = auth;
	link_config.max_self_mru = PPTP_MRU;
	link_config.max_self_mrru = PPTP_MRRU;
	link_config.multilink = 1;
	link_config.eid.class = PPP_EID_CLASS_IP;
	link_config.eid.length = sizeof(s->ip);
	memcpy(link_config.eid.value, &s->ip, sizeof(s->ip));

	/* Add new link to the PPP engine */
	if (ppp_link_create(s->engine, peer->chan, &link_config, log) == -1) {
		LOG(LOG_ERR, "can't create link: %m");
		goto fail;
	}
	log = NULL;

	/* Done */
	(void)close(csock);
	s->npeers++;
	return (peer);

fail:
	/* Clean up after failure */
	esave = errno;
	ppp_log_close(&log);
	if (peer != NULL) {
		if (peer->carg != NULL)
			(*s->info.destroy)(s->info.arg, peer->carg, peer->path);
		if (peer->chan != NULL) {
			if (peer->chan->outport != NULL)
				mesg_port_destroy(&peer->chan->outport);
			FREE(PPTP_DEVICE_MTYPE, peer->chan);
		}
		if (csock != -1) {
			(void)NgSendMsg(csock, peer->path,
			    NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0);
		}
		FREE(PPTP_PEER_MTYPE, peer);
	}
	if (csock != -1)
		(void)close(csock);
	errno = esave;
	return (NULL);
}

/*
 * "Answer" peer's outgoing call.
 */
static void
ppp_pptp_server_answer(void *arg)
{
	struct ppp_pptp_peer *const peer = arg;

	pevent_unregister(&peer->answer);
	(*peer->cinfo.answer)(peer->cinfo.cookie, PPTP_OCR_RESL_OK,
	    0, 0, 10000000 /* XXX */);
	ppp_pptp_server_device_output(peer, PPP_CHANNEL_OUTPUT_UP);
}

/*
 * Output indication from the device.
 */
static void
ppp_pptp_server_device_output(struct ppp_pptp_peer *peer,
	enum ppp_channeloutput type, ...)
{
	struct pptp_server *const s = peer->s;
	struct ppp_channel_output *output;

	/* Get output object */
	if ((output = MALLOC(PPTP_OUTPUT_MTYPE, sizeof(*output))) == NULL) {
		LOG(LOG_ERR, "can't create pptp output: %m");
		return;
	}
	memset(output, 0, sizeof(*output));
	output->type = type;

	/* Get extra args */
	switch (output->type) {
	case PPP_CHANNEL_OUTPUT_DOWN_FATAL:
	case PPP_CHANNEL_OUTPUT_DOWN_NONFATAL:
	    {
		const char *msg;
		va_list args;

		/* Get string message */
		va_start(args, type);
		msg = va_arg(args, const char *);
		va_end(args);
		if ((output->info = STRDUP(PPTP_OUTPUT_MTYPE, msg)) == NULL) {
			LOG(LOG_ERR, "can't create pptp output: %m");
			FREE(PPTP_OUTPUT_MTYPE, output);
			return;
		}
		break;
	    }
	case PPP_CHANNEL_OUTPUT_UP:
		break;
	}

	/* Send message */
	if (mesg_port_put(peer->chan->outport, output) == -1) {
		LOG(LOG_ERR, "can't send pptp output: %m");
		ppp_pptp_server_device_free_output(peer->chan, output);
		return;
	}
}

/***********************************************************************
			PPTP DEVICE METHODS
***********************************************************************/

static void
ppp_pptp_server_device_open(struct ppp_channel *chan)
{
	return;
}

static void
ppp_pptp_server_device_close(struct ppp_channel *chan)
{
	struct ppp_pptp_peer *const peer = chan->priv;
	struct pptp_server *const s = peer->s;
	struct ng_pptpgre_conf greconf;
	int csock;

	/* Logging */
	if (!peer->closed) {
		LOG(LOG_INFO, "closing PPTP connection with %s", peer->logname);
		peer->closed = 1;
	}

	/* Disable the pptpgre device */
	if (NgMkSockNode(NULL, &csock, NULL) == -1)
		LOG(LOG_ERR, "can't create socket node: %m");
	else {
		memset(&greconf, 0, sizeof(greconf));
		(void)NgSendMsg(csock, peer->path, NGM_PPTPGRE_COOKIE,
		    NGM_PPTPGRE_SET_CONFIG, &greconf, sizeof(greconf));
		(void)close(csock);
	}

	/* Output 'down' message from the device */
	ppp_pptp_server_device_output(peer,
	    PPP_CHANNEL_OUTPUT_DOWN_FATAL, "administratively closed");
}

static void
ppp_pptp_server_device_destroy(struct ppp_channel **chanp)
{
	struct ppp_channel *const chan = *chanp;
	struct ppp_channel_output *output;
	struct ppp_pptp_peer *peer;
	struct pptp_server *s;
	int csock;

	/* Sanity */
	if (chan == NULL)
		return;
	*chanp = NULL;
	peer = chan->priv;
	s = peer->s;

	/* Close client code's side of the device */
	if (peer->carg != NULL) {
		(*s->info.destroy)(s->info.arg, peer->carg, peer->path);
		peer->carg = NULL;
	}

	/* Close the PPTP channel */
	pevent_unregister(&peer->answer);
	if (peer->cinfo.cookie != NULL) {
		(*peer->cinfo.close)(peer->cinfo.cookie,
		    PPTP_CDN_RESL_ADMIN, 0, 0);
		peer->cinfo.cookie = NULL;
	}

	/* Destroy the ng_pptpgre(4) node */
	if (NgMkSockNode(NULL, &csock, NULL) == -1)
		LOG(LOG_ERR, "can't create socket node: %m");
	else {
		(void)NgSendMsg(csock, peer->path,
		    NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0);
		(void)close(csock);
	}

	/* Destroy the 'peer' object */
	FREE(PPTP_PEER_MTYPE, peer);

	/* Destroy the device object */
	while ((output = mesg_port_get(chan->outport, 0)) != NULL)
		ppp_pptp_server_device_free_output(chan, output);
	mesg_port_destroy(&chan->outport);
	FREE(PPTP_DEVICE_MTYPE, chan);
	s->npeers--;

	/* Check if shutting down PPTP server */
	if (s->npeers == 0 && s->shutdown) {
		ppp_log_close(&s->log);
		FREE(PPTP_MTYPE, s);
	}
}

static void
ppp_pptp_server_device_free_output(struct ppp_channel *chan,
	struct ppp_channel_output *output)
{
	FREE(PPTP_OUTPUT_MTYPE, output->info);
	FREE(PPTP_OUTPUT_MTYPE, output);
}

static void
ppp_pptp_server_device_set_link_info(struct ppp_channel *chan, u_int32_t accm)
{
	/* XXX implement me? */
}

static int
ppp_pptp_server_device_get_origination(struct ppp_channel *chan)
{
	return (PPP_PEER);	/* we don't initiate any calls ourself */
}

static const char *
ppp_pptp_server_device_get_node(struct ppp_channel *chan)
{
	struct ppp_pptp_peer *const peer = chan->priv;

	return (peer->path);
}

static const char *
ppp_pptp_server_device_get_hook(struct ppp_channel *chan)
{
	return (NG_PPTPGRE_HOOK_UPPER);
}

static int
ppp_pptp_server_device_is_async(struct ppp_channel *chan)
{
	return (0);
}

static u_int
ppp_pptp_server_device_get_mtu(struct ppp_channel *chan)
{
	return (PPTP_MRU);
}

static int
ppp_pptp_server_device_get_acfcomp(struct ppp_channel *chan)
{
	return (1);
}

static int
ppp_pptp_server_device_get_pfcomp(struct ppp_channel *chan)
{
	return (1);
}


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>