File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mpd / src / pptp.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Jul 22 08:44:29 2013 UTC (11 years, 5 months ago) by misho
Branches: mpd, MAIN
CVS tags: v5_8p7, v5_8p1_cross, v5_8p1, v5_8, v5_7p0, v5_7, HEAD
5.7


/*
 * pptp.c
 *
 * Written by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1998-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#include "phys.h"
#include "mbuf.h"
#include "ngfunc.h"
#include "pptp.h"
#include "pptp_ctrl.h"
#include "log.h"
#include "util.h"

#include <net/ethernet.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_ksocket.h>
#include <netgraph/ng_pptpgre.h>
#include <netgraph.h>

/*
 * DEFINITIONS
 */

  #define PPTP_MRU		PPTP_MTU

  #define PPTP_CALL_MIN_BPS	56000
  #define PPTP_CALL_MAX_BPS	64000

  struct pptptun {
    struct u_addr	self_addr;	/* Current self IP address */
    struct u_addr	peer_addr;	/* Current peer IP address */
    ng_ID_t		node_id;
    int			refs;
  };
  typedef struct pptptun	*PptpTun;

  struct pptpinfo {
    struct {
	struct u_addr	self_addr;	/* self IP address */
	struct u_range	peer_addr;	/* Peer IP addresses allowed */
	in_port_t	self_port;	/* self port */
	in_port_t	peer_port;	/* Peer port required (or zero) */
	struct optinfo	options;
	char		callingnum[64];	/* PPTP phone number to use */
	char		callednum[64];	/* PPTP phone number to use */
	char		*fqdn_peer_addr;	/* FQDN Peer address */
    } conf;
    void		*listener;	/* Listener pointer */
    struct u_addr	self_addr;	/* Current self IP address */
    struct u_addr	peer_addr;	/* Current peer IP address */
    char                peer_iface[IFNAMSIZ];	/* Peer iface */
    u_char		peer_mac_addr[6];	/* Peer MAC address */
    in_port_t		peer_port;	/* Current peer port */
    u_char		originate;	/* Call originated locally */
    u_char		outcall;	/* Call is outgoing vs. incoming */
    u_char		sync;		/* Call is sync vs. async */
    u_int16_t		cid;		/* call id */
    PptpTun		tun;
    struct pptpctrlinfo	cinfo;
    char		callingnum[64];	/* PPTP phone number to use */
    char		callednum[64];	/* PPTP phone number to use */
  };
  typedef struct pptpinfo	*PptpInfo;

  /* Set menu options */
  enum {
    SET_SELFADDR,
    SET_PEERADDR,
    SET_CALLINGNUM,
    SET_CALLEDNUM,
    SET_ENABLE,
    SET_DISABLE
  };

  /* Binary options */
  enum {
    PPTP_CONF_OUTCALL,		/* when originating, calls are "outgoing" */
    PPTP_CONF_DELAYED_ACK,	/* enable delayed receive ack algorithm */
    PPTP_CONF_ALWAYS_ACK,	/* include ack with all outgoing data packets */
    PPTP_CONF_RESOLVE_ONCE,	/* Only once resolve peer_addr */
#if NGM_PPTPGRE_COOKIE >= 1082548365
    PPTP_CONF_WINDOWING		/* control (stupid) windowing algorithm */
#endif
  };

/*
 * INTERNAL FUNCTIONS
 */

  static int	PptpTInit(void);
  static void	PptpTShutdown(void);
  static int	PptpInit(Link l);
  static int	PptpInst(Link l, Link lt);
  static void	PptpOpen(Link l);
  static void	PptpClose(Link l);
  static void	PptpShutdown(Link l);
  static void	PptpStat(Context ctx);
  static int	PptpOriginated(Link l);
  static int	PptpIsSync(Link l);
  static int	PptpSetAccm(Link l, u_int32_t xmit, u_int32_t recv);
  static int	PptpSetCallingNum(Link l, void *buf);
  static int	PptpSetCalledNum(Link l, void *buf);
  static int	PptpSelfName(Link l, void *buf, size_t buf_len);
  static int	PptpPeerName(Link l, void *buf, size_t buf_len);
  static int	PptpSelfAddr(Link l, void *buf, size_t buf_len);
  static int	PptpPeerAddr(Link l, void *buf, size_t buf_len);
  static int	PptpPeerPort(Link l, void *buf, size_t buf_len);
  static int	PptpPeerMacAddr(Link l, void *buf, size_t buf_len);
  static int	PptpPeerIface(Link l, void *buf, size_t buf_len);
  static int	PptpCallingNum(Link l, void *buf, size_t buf_len);
  static int	PptpCalledNum(Link l, void *buf, size_t buf_len);

  static int	PptpOriginate(Link l);
  static void	PptpDoClose(Link l);
  static void	PptpUnhook(Link l);
  static void	PptpResult(void *cookie, const char *errmsg, int frameType);
  static void	PptpSetLinkInfo(void *cookie, u_int32_t sa, u_int32_t ra);
  static void	PptpCancel(void *cookie);
  static int	PptpHookUp(Link l);
  static void	PptpListenUpdate(Link l);

  static struct pptplinkinfo	PptpIncoming(struct pptpctrlinfo *cinfo,
				  struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
				  const char *callingNum,
				  const char *calledNum,
				  const char *subAddress);

  static struct pptplinkinfo	PptpOutgoing(struct pptpctrlinfo *cinfo,
				  struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
				  int frameType, int minBps, int maxBps,
				  const char *calledNum,
				  const char *subAddress);

  static struct pptplinkinfo	PptpPeerCall(struct pptpctrlinfo *cinfo,
				  struct u_addr *self, struct u_addr *peer, in_port_t port, int incoming,
				  const char *callingNum,
				  const char *calledNum,
				  const char *subAddress);

  static int	PptpSetCommand(Context ctx, int ac, char *av[], void *arg);
  static int	PptpTunEQ(struct ghash *g, const void *item1, const void *item2);
  static u_int32_t	PptpTunHash(struct ghash *g, const void *item);


/*
 * GLOBAL VARIABLES
 */

  const struct phystype	gPptpPhysType = {
    .name		= "pptp",
    .descr		= "Point-to-Point Tunneling Protocol",
    .mtu		= PPTP_MTU,
    .mru		= PPTP_MRU,
    .tmpl		= 1,
    .tinit		= PptpTInit,
    .tshutdown		= PptpTShutdown,
    .init		= PptpInit,
    .inst		= PptpInst,
    .open		= PptpOpen,
    .close		= PptpClose,
    .update		= PptpListenUpdate,
    .shutdown		= PptpShutdown,
    .showstat		= PptpStat,
    .originate		= PptpOriginated,
    .issync		= PptpIsSync,
    .setaccm            = PptpSetAccm,
    .setcallingnum	= PptpSetCallingNum,
    .setcallednum	= PptpSetCalledNum,
    .selfname		= PptpSelfName,
    .peername		= PptpPeerName,
    .selfaddr		= PptpSelfAddr,
    .peeraddr		= PptpPeerAddr,
    .peerport		= PptpPeerPort,
    .peermacaddr	= PptpPeerMacAddr,
    .peeriface		= PptpPeerIface,
    .callingnum		= PptpCallingNum,
    .callednum		= PptpCalledNum,
  };

  const struct cmdtab	PptpSetCmds[] = {
    { "self {ip} [{port}]",		"Set local IP address",
	PptpSetCommand, NULL, 2, (void *) SET_SELFADDR },
    { "peer {ip} [{port}]",		"Set remote IP address",
	PptpSetCommand, NULL, 2, (void *) SET_PEERADDR },
    { "callingnum {number}",		"Set calling PPTP telephone number",
	PptpSetCommand, NULL, 2, (void *) SET_CALLINGNUM },
    { "callednum {number}",		"Set called PPTP telephone number",
	PptpSetCommand, NULL, 2, (void *) SET_CALLEDNUM },
    { "enable [opt ...]",		"Enable option",
	PptpSetCommand, NULL, 2, (void *) SET_ENABLE },
    { "disable [opt ...]",		"Disable option",
	PptpSetCommand, NULL, 2, (void *) SET_DISABLE },
    { NULL },
  };

/*
 * INTERNAL VARIABLES
 */

  static struct confinfo	gConfList[] = {
    { 0,	PPTP_CONF_OUTCALL,	"outcall"	},
    { 0,	PPTP_CONF_DELAYED_ACK,	"delayed-ack"	},
    { 0,	PPTP_CONF_ALWAYS_ACK,	"always-ack"	},
    { 0,	PPTP_CONF_RESOLVE_ONCE,	"resolve-once"	},
#if NGM_PPTPGRE_COOKIE >= 1082548365
    { 0,	PPTP_CONF_WINDOWING,	"windowing"	},
#endif
    { 0,	0,			NULL		},
  };

struct ghash    *gPptpTuns;

/*
 * PptpTInit()
 */

static int
PptpTInit(void)
{
    if ((gPptpTuns = ghash_create(NULL, 0, 0, MB_PHYS, PptpTunHash, PptpTunEQ, NULL, NULL))
	  == NULL)
	return(-1);
    return (PptpCtrlInit(PptpIncoming, PptpOutgoing));
}

/*
 * PptpTShutdown()
 */

static void
PptpTShutdown(void)
{
    Log(LG_PHYS2, ("PPTP: Total shutdown"));
    ghash_destroy(&gPptpTuns);
}

/*
 * PptpInit()
 */

static int
PptpInit(Link l)
{
    PptpInfo	pptp;

    /* Initialize this link */
    pptp = (PptpInfo) (l->info = Malloc(MB_PHYS, sizeof(*pptp)));

    pptp->conf.self_addr.family = AF_INET;
    pptp->conf.fqdn_peer_addr = NULL;
    Enable(&pptp->conf.options, PPTP_CONF_OUTCALL);
    Enable(&pptp->conf.options, PPTP_CONF_DELAYED_ACK);
    Enable(&pptp->conf.options, PPTP_CONF_RESOLVE_ONCE);

    return(0);
}

/*
 * PptpInst()
 */

static int
PptpInst(Link l, Link lt)
{
    PptpInfo	pptp;
    PptpInfo	const pptpt = (PptpInfo) lt->info;

    /* Initialize this link */
    pptp = (PptpInfo) (l->info = Mdup(MB_PHYS, lt->info, sizeof(*pptp)));
    if (pptpt->conf.fqdn_peer_addr != NULL)
        pptp->conf.fqdn_peer_addr =
            Mstrdup(MB_PHYS, pptpt->conf.fqdn_peer_addr);
    pptp->listener = NULL;

    return(0);
}

/*
 * PptpOpen()
 */

static void
PptpOpen(Link l)
{
    PptpInfo		const pptp = (PptpInfo) l->info;
    struct sockaddr_dl  hwa;

    /* Check state */
    switch (l->state) {
	case PHYS_STATE_DOWN:
    	    if (PptpOriginate(l) < 0) {
		Log(LG_PHYS, ("[%s] PPTP call failed", l->name));
		PhysDown(l, STR_ERROR, NULL);
		return;
    	    }
    	    l->state = PHYS_STATE_CONNECTING;
    	    break;

	case PHYS_STATE_CONNECTING:
    	    if (pptp->originate)	/* our call to peer is already in progress */
		break;
    	    if (pptp->outcall) {

		/* Hook up nodes */
		Log(LG_PHYS, ("[%s] PPTP: attaching to peer's outgoing call", l->name));
		if (PptpHookUp(l) < 0) {
		    PptpDoClose(l);
		    /* We should not set state=DOWN as PptpResult() will be called once more */
		    break;
		}

		if (GetPeerEther(&pptp->peer_addr, &hwa)) {
		    if_indextoname(hwa.sdl_index, pptp->peer_iface);
		    memcpy(pptp->peer_mac_addr, LLADDR(&hwa), sizeof(pptp->peer_mac_addr));
		};

		(*pptp->cinfo.answer)(pptp->cinfo.cookie,
		    PPTP_OCR_RESL_OK, 0, 0, 64000 /*XXX*/ );

		/* Report UP if there was no error. */
		if (l->state == PHYS_STATE_CONNECTING) {
		    l->state = PHYS_STATE_UP;
		    PhysUp(l);
		}
		return;
    	    }
    	    return; 	/* wait for peer's incoming pptp call to complete */

	case PHYS_STATE_UP:
    	    PhysUp(l);
    	    return;

	default:
    	    assert(0);
    }
}

/*
 * PptpOriginate()
 *
 * Initiate an "incoming" or an "outgoing" call to the remote site
 */

static int
PptpOriginate(Link l)
{
    PptpInfo		const pptp = (PptpInfo) l->info;
    struct pptplinkinfo	linfo;
    const u_short	port = pptp->conf.peer_port ?
			    pptp->conf.peer_port : PPTP_PORT;

    pptp->originate = TRUE;
    pptp->outcall = Enabled(&pptp->conf.options, PPTP_CONF_OUTCALL);
    memset(&linfo, 0, sizeof(linfo));
    linfo.cookie = l;
    linfo.result = PptpResult;
    linfo.setLinkInfo = PptpSetLinkInfo;
    linfo.cancel = PptpCancel;
    strlcpy(pptp->callingnum, pptp->conf.callingnum, sizeof(pptp->callingnum));
    strlcpy(pptp->callednum, pptp->conf.callednum, sizeof(pptp->callednum));
    if ((!Enabled(&pptp->conf.options, PPTP_CONF_RESOLVE_ONCE)) &&
	(pptp->conf.fqdn_peer_addr != NULL)) {
	struct u_range	rng;
	if (ParseRange(pptp->conf.fqdn_peer_addr, &rng, ALLOW_IPV4|ALLOW_IPV6))
	    pptp->conf.peer_addr = rng;
    }
    if (!pptp->outcall) {
	int frameType = PPTP_FRAMECAP_SYNC;
	if (l->rep && !RepIsSync(l))
	    frameType = PPTP_FRAMECAP_ASYNC;
	PptpCtrlInCall(&pptp->cinfo, &linfo, 
    	    &pptp->conf.self_addr, &pptp->conf.peer_addr.addr, port,
    	    PPTP_BEARCAP_ANY, frameType,
    	    PPTP_CALL_MIN_BPS, PPTP_CALL_MAX_BPS, 
    	    pptp->callingnum, pptp->callednum, "");
    } else {
	PptpCtrlOutCall(&pptp->cinfo, &linfo, 
    	    &pptp->conf.self_addr, &pptp->conf.peer_addr.addr, port,
    	    PPTP_BEARCAP_ANY, PPTP_FRAMECAP_ANY,
    	    PPTP_CALL_MIN_BPS, PPTP_CALL_MAX_BPS,
    	    pptp->callednum, "");
    }
    if (pptp->cinfo.cookie == NULL)
	return(-1);
    pptp->self_addr = pptp->conf.self_addr;
    pptp->peer_addr = pptp->conf.peer_addr.addr;
    pptp->peer_port = port;
    return(0);
}

/*
 * PptpClose()
 */

static void
PptpClose(Link l)
{
    PptpDoClose(l);
}

/*
 * PptpShutdown()
 */

static void
PptpShutdown(Link l)
{
    PptpInfo      const pptp = (PptpInfo) l->info;


    if (pptp->conf.fqdn_peer_addr)
        Freee(pptp->conf.fqdn_peer_addr);
    if (pptp->listener) {
	PptpCtrlUnListen(pptp->listener);
	pptp->listener = NULL;
    }
    PptpUnhook(l);
    Freee(l->info);
}

/*
 * PptpDoClose()
 */

static void
PptpDoClose(Link l)
{
    PptpInfo      const pptp = (PptpInfo) l->info;

    if (l->state != PHYS_STATE_DOWN)		/* avoid double close */
	(*pptp->cinfo.close)(pptp->cinfo.cookie, PPTP_CDN_RESL_ADMIN, 0, 0);
}

/*
 * PptpUnhook()
 */

static void
PptpUnhook(Link l)
{
	PptpInfo const	pptp = (PptpInfo) l->info;
	char		path[NG_PATHSIZ];
	int		csock = -1;

	if (pptp->tun == NULL)
		return;

	/* Get a temporary netgraph socket node */
	if (NgMkSockNode(NULL, &csock, NULL) == -1) {
		Perror("PPTP: NgMkSockNode");
		return;
	}
	
	pptp->tun->refs--;
	snprintf(path, sizeof(path), "[%lx]:", (u_long)pptp->tun->node_id);
	if (pptp->tun->refs == 0) {
	    /* Disconnect session hook. */
	    NgFuncShutdownNode(csock, l->name, path);
	    ghash_remove(gPptpTuns, pptp->tun);
	    Freee(pptp->tun);
#ifdef	NG_PPTPGRE_HOOK_SESSION_F
	} else {
	    char	hook[NG_HOOKSIZ];
	    snprintf(hook, sizeof(hook), NG_PPTPGRE_HOOK_SESSION_F, pptp->cid);
	    NgFuncDisconnect(csock, l->name, path, hook);
#endif
	}
	
	close(csock);
	
	pptp->tun = NULL;
}

/*
 * PptpOriginated()
 */

static int
PptpOriginated(Link l)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    return(pptp->originate ? LINK_ORIGINATE_LOCAL : LINK_ORIGINATE_REMOTE);
}

/*
 * PptpIsSync()
 */

static int
PptpIsSync(Link l)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    return (pptp->sync);
}

static int
PptpSetAccm(Link l, u_int32_t xmit, u_int32_t recv)
{
    PptpInfo	const pptp = (PptpInfo) l->info;
    
    if (!pptp->cinfo.close || !pptp->cinfo.cookie)
	    return (-1);

    (*pptp->cinfo.setLinkInfo)(pptp->cinfo.cookie, xmit, recv);
    return (0);
}

static int
PptpSetCallingNum(Link l, void *buf)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    strlcpy(pptp->conf.callingnum, buf, sizeof(pptp->conf.callingnum));
    return(0);
}

static int
PptpSetCalledNum(Link l, void *buf)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    strlcpy(pptp->conf.callednum, buf, sizeof(pptp->conf.callednum));
    return(0);
}

static int
PptpSelfName(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (pptp->cinfo.cookie)
	return(PptpCtrlGetSelfName(&pptp->cinfo, buf, buf_len));
    ((char*)buf)[0]=0;
    return (0);
}

static int
PptpPeerName(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (pptp->cinfo.cookie)
	return(PptpCtrlGetPeerName(&pptp->cinfo, buf, buf_len));
    ((char*)buf)[0]=0;
    return (0);
}

static int
PptpSelfAddr(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (u_addrtoa(&pptp->self_addr, buf, buf_len))
	return(0);
    else
	return(-1);
}

static int
PptpPeerAddr(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (u_addrtoa(&pptp->peer_addr, buf, buf_len))
	return(0);
    else
	return(-1);
}

static int
PptpPeerPort(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (snprintf(buf, buf_len, "%d", pptp->peer_port))
	return(0);
    else
	return(-1);
}

static int
PptpPeerMacAddr(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (pptp->peer_iface[0]) {
	ether_ntoa_r((struct ether_addr *)pptp->peer_mac_addr, buf);
	return (0);
    }
    ((char*)buf)[0]=0;
    return(0);
}

static int
PptpPeerIface(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    if (pptp->peer_iface[0]) {
	strlcpy(buf, pptp->peer_iface, buf_len);
	return (0);
    }
    ((char*)buf)[0]=0;
    return(0);
}

static int
PptpCallingNum(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    strlcpy((char*)buf, pptp->callingnum, buf_len);
    return(0);
}

static int
PptpCalledNum(Link l, void *buf, size_t buf_len)
{
    PptpInfo	const pptp = (PptpInfo) l->info;

    strlcpy((char*)buf, pptp->callednum, buf_len);
    return(0);
}

/*
 * PptpStat()
 */

void
PptpStat(Context ctx)
{
    PptpInfo	const pptp = (PptpInfo) ctx->lnk->info;
    char	buf[32];

    Printf("PPTP configuration:\r\n");
    Printf("\tSelf addr    : %s",
	u_addrtoa(&pptp->conf.self_addr, buf, sizeof(buf)));
    if (pptp->conf.self_port)
	Printf(", port %u", pptp->conf.self_port);
    Printf("\r\n");
    Printf("\tPeer FQDN    : %s\r\n", pptp->conf.fqdn_peer_addr);
    Printf("\tPeer range   : %s",
	u_rangetoa(&pptp->conf.peer_addr, buf, sizeof(buf)));
    if (pptp->conf.peer_port)
	Printf(", port %u", pptp->conf.peer_port);
    Printf("\r\n");
    Printf("\tCalling number: %s\r\n", pptp->conf.callingnum);
    Printf("\tCalled number: %s\r\n", pptp->conf.callednum);
    Printf("PPTP options:\r\n");
    OptStat(ctx, &pptp->conf.options, gConfList);
    Printf("PPTP status:\r\n");
    if (ctx->lnk->state != PHYS_STATE_DOWN) {
	Printf("\tIncoming     : %s\r\n", (pptp->originate?"NO":"YES"));
	Printf("\tCurrent self : %s",
	    u_addrtoa(&pptp->self_addr, buf, sizeof(buf)));
	PptpSelfName(ctx->lnk, buf, sizeof(buf));
	Printf(" (%s)\r\n", buf);
	Printf("\tCurrent peer : %s, port %u",
	    u_addrtoa(&pptp->peer_addr, buf, sizeof(buf)), pptp->peer_port);
	PptpPeerName(ctx->lnk, buf, sizeof(buf));
	Printf(" (%s)\r\n", buf);
	if (pptp->peer_iface[0]) {
	    ether_ntoa_r((struct ether_addr *)pptp->peer_mac_addr, buf);
	    Printf("\tCurrent peer : %s at %s\r\n", buf, pptp->peer_iface);
	}
	Printf("\tFraming      : %s\r\n", (pptp->sync?"Sync":"Async"));
	Printf("\tCalling number: %s\r\n", pptp->callingnum);
	Printf("\tCalled number: %s\r\n", pptp->callednum);
    }
}

/*
 * PptpResult()
 *
 * The control code calls this function to report a PPTP link
 * being connected, disconnected, or failing to connect.
 */

static void
PptpResult(void *cookie, const char *errmsg, int frameType)
{
    PptpInfo	pptp;
    Link 	l;
    struct sockaddr_dl  hwa;

    /* It this fake call? */
    if (!cookie)
	return;

    l = (Link)cookie;
    pptp = (PptpInfo) l->info;

    switch (l->state) {
	case PHYS_STATE_CONNECTING:
    	    if (!errmsg) {

		/* Hook up nodes */
		Log(LG_PHYS, ("[%s] PPTP call successful", l->name));
		if (PptpHookUp(l) < 0) {
		    PptpDoClose(l);
		    /* We should not set state=DOWN as PptpResult() will be called once more */
		    break;
		}

		if (pptp->originate && !pptp->outcall)
		    (*pptp->cinfo.connected)(pptp->cinfo.cookie, 64000 /*XXX*/ );

		/* Report UP if there was no error. */
		if (l->state == PHYS_STATE_CONNECTING) {
		    if (GetPeerEther(&pptp->peer_addr, &hwa)) {
			if_indextoname(hwa.sdl_index, pptp->peer_iface);
	    		memcpy(pptp->peer_mac_addr, LLADDR(&hwa), sizeof(pptp->peer_mac_addr));
		    };

		    /* OK */
		    l->state = PHYS_STATE_UP;
		    pptp->sync = (frameType&PPTP_FRAMECAP_ASYNC)?0:1;
		    PhysUp(l);
		}
	    } else {
		Log(LG_PHYS, ("[%s] PPTP call failed", l->name));
		PptpUnhook(l);		/* For the (*connected)() error. */
		l->state = PHYS_STATE_DOWN;
		u_addrclear(&pptp->self_addr);
		u_addrclear(&pptp->peer_addr);
		pptp->peer_port = 0;
    		pptp->callingnum[0]=0;
    		pptp->callednum[0]=0;
		pptp->peer_iface[0] = 0;
		PhysDown(l, STR_CON_FAILED, errmsg);
    	    }
    	    break;
	case PHYS_STATE_UP:
    	    assert(errmsg);
    	    Log(LG_PHYS, ("[%s] PPTP call terminated", l->name));
	    PptpUnhook(l);
    	    l->state = PHYS_STATE_DOWN;
            u_addrclear(&pptp->self_addr);
    	    u_addrclear(&pptp->peer_addr);
    	    pptp->peer_port = 0;
    	    pptp->callingnum[0]=0;
    	    pptp->callednum[0]=0;
	    pptp->peer_iface[0] = 0;
    	    PhysDown(l, STR_DROPPED, NULL);
    	    break;
	case PHYS_STATE_DOWN:
    	    return;
	default:
    	    assert(0);
    }
}

/*
 * PptpSetLinkInfo()
 *
 * Received LinkInfo from peer;
 */

void
PptpSetLinkInfo(void *cookie, u_int32_t sa, u_int32_t ra)
{
    Link 	l;

    /* It this fake call? */
    if (!cookie)
	    return;

    l = (Link)cookie;

    if (l->rep != NULL)
	    RepSetAccm(l, sa, ra);
}

static int
PptpTunEQ(struct ghash *g, const void *item1, const void *item2)
{
    const struct pptptun *tun1 = item1;
    const struct pptptun *tun2 = item2;
    if (u_addrcompare(&tun1->self_addr, &tun2->self_addr) == 0 &&
	u_addrcompare(&tun1->peer_addr, &tun2->peer_addr) == 0)
	    return (1);
    return (0);
}

static u_int32_t
PptpTunHash(struct ghash *g, const void *item)
{
    const struct pptptun *tun = item;
    return (u_addrtoid(&tun->self_addr) + u_addrtoid(&tun->peer_addr));
}

/*
 * PptpHookUp()
 *
 * Connect the PPTP/GRE node to the PPP node
 */

static int
PptpHookUp(Link l)
{
    const PptpInfo		pi = (PptpInfo)l->info;
    char	       		ksockpath[NG_PATHSIZ];
    char	       		pptppath[NG_PATHSIZ];
    struct ngm_mkpeer		mkp;
    struct ng_pptpgre_conf	gc;
    struct sockaddr_storage	self_addr, peer_addr;
    struct u_addr		u_self_addr, u_peer_addr;
    union {
	u_char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(int)];
	struct ng_ksocket_sockopt ksso;
    } u;
    struct ng_ksocket_sockopt *const ksso = &u.ksso;
    int		csock = -1;
    char        path[NG_PATHSIZ];
    char	hook[NG_HOOKSIZ];
    PptpTun	tun = NULL;

    /* Get session info */
    memset(&gc, 0, sizeof(gc));
    PptpCtrlGetSessionInfo(&pi->cinfo, &u_self_addr,
	&u_peer_addr, &gc.cid, &gc.peerCid, &gc.recvWin, &gc.peerPpd);
    pi->cid = gc.cid;
    
    u_addrtosockaddr(&u_self_addr, 0, &self_addr);
    u_addrtosockaddr(&u_peer_addr, 0, &peer_addr);

    if (!PhysGetUpperHook(l, path, hook)) {
        Log(LG_PHYS, ("[%s] PPTP: can't get upper hook", l->name));
        return(-1);
    }
    
    /* Get a temporary netgraph socket node */
    if (NgMkSockNode(NULL, &csock, NULL) == -1) {
	Perror("PPTP: NgMkSockNode");
	return(-1);
    }

#ifdef	NG_PPTPGRE_HOOK_SESSION_F
    {
	struct pptptun tmptun;
	tmptun.self_addr = u_self_addr;
	tmptun.peer_addr = u_peer_addr;
	tun = ghash_get(gPptpTuns, &tmptun);
    }
#endif

    snprintf(pptppath, sizeof(pptppath), "%s.%s", path, hook);
    if (tun == NULL) {
	tun = (PptpTun)Malloc(MB_PHYS, sizeof(*tun));
	tun->self_addr = u_self_addr;
	tun->peer_addr = u_peer_addr;
	if (ghash_put(gPptpTuns, tun) == -1) {
	    Perror("[%s] PPTP: ghash_put", l->name);
	    Freee(tun);
	    close(csock);
	    return(-1);
	}
    
	/* Attach PPTP/GRE node to PPP node */
	strcpy(mkp.type, NG_PPTPGRE_NODE_TYPE);
	strlcpy(mkp.ourhook, hook, sizeof(mkp.ourhook));
#ifdef	NG_PPTPGRE_HOOK_SESSION_F
	snprintf(mkp.peerhook, sizeof(mkp.peerhook), NG_PPTPGRE_HOOK_SESSION_F, pi->cid);
#else
	strcpy(mkp.peerhook, NG_PPTPGRE_HOOK_UPPER);
#endif
	if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
          NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
	    Perror("[%s] PPTP: can't attach %s node", l->name, NG_PPTPGRE_NODE_TYPE);
	    ghash_remove(gPptpTuns, tun);
	    Freee(tun);
	    close(csock);
	    return(-1);
	}

	/* Get pptpgre node ID */
	if ((tun->node_id = NgGetNodeID(csock, pptppath)) == 0) {
	    Perror("[%s] Cannot get %s node id", l->name, NG_PPTPGRE_NODE_TYPE);
	    ghash_remove(gPptpTuns, tun);
	    Freee(tun);
	    close(csock);
	    return(-1);
	};
	tun->refs++;
	pi->tun = tun;

	/* Attach ksocket node to PPTP/GRE node */
	strcpy(mkp.type, NG_KSOCKET_NODE_TYPE);
	strcpy(mkp.ourhook, NG_PPTPGRE_HOOK_LOWER);
	if (u_self_addr.family==AF_INET6) {
	    //ng_ksocket doesn't support inet6 name
	    snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%d/%d/%d", PF_INET6, SOCK_RAW, IPPROTO_GRE); 
	} else {
	    snprintf(mkp.peerhook, sizeof(mkp.peerhook), "inet/raw/gre");
	}
	if (NgSendMsg(csock, pptppath, NGM_GENERIC_COOKIE,
	  NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
	    Perror("[%s] PPTP: can't attach %s node", l->name, NG_KSOCKET_NODE_TYPE);
	    close(csock);
	    return(-1);
	}
	snprintf(ksockpath, sizeof(ksockpath),
	    "%s.%s", pptppath, NG_PPTPGRE_HOOK_LOWER);

	/* increase recvspace to avoid packet loss due to very small GRE recv buffer. */
	ksso->level=SOL_SOCKET;
	ksso->name=SO_RCVBUF;
	((int *)(ksso->value))[0]=48*1024;
	if (NgSendMsg(csock, ksockpath, NGM_KSOCKET_COOKIE,
	    NGM_KSOCKET_SETOPT, &u, sizeof(u)) < 0) {
		Perror("[%s] PPTP: can't setsockopt %s node",
		    l->name, NG_KSOCKET_NODE_TYPE);
	}

	/* Bind ksocket socket to local IP address */
	if (NgSendMsg(csock, ksockpath, NGM_KSOCKET_COOKIE,
          NGM_KSOCKET_BIND, &self_addr, self_addr.ss_len) < 0) {
	    Perror("[%s] PPTP: can't bind() %s node", l->name, NG_KSOCKET_NODE_TYPE);
	    close(csock);
	    return(-1);
	}

	/* Connect ksocket socket to remote IP address */
	if (NgSendMsg(csock, ksockpath, NGM_KSOCKET_COOKIE,
    	  NGM_KSOCKET_CONNECT, &peer_addr, peer_addr.ss_len) < 0 &&
    	  errno != EINPROGRESS) {	/* happens in -current (weird) */
	    Perror("[%s] PPTP: can't connect() %s node",
    	        l->name, NG_KSOCKET_NODE_TYPE);
	    close(csock);
    	    return(-1);
        }
#ifdef	NG_PPTPGRE_HOOK_SESSION_F
    } else {
	struct ngm_connect	cn;
	snprintf(cn.path, sizeof(cn.path), "[%x]:", tun->node_id);
	strlcpy(cn.ourhook, hook, sizeof(mkp.ourhook));
	snprintf(cn.peerhook, sizeof(mkp.peerhook), NG_PPTPGRE_HOOK_SESSION_F, pi->cid);
	if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
          NGM_CONNECT, &cn, sizeof(cn)) < 0) {
	    Perror("[%s] PPTP: can't connect to %s node",
    		l->name, NG_PPTPGRE_NODE_TYPE);
	    close(csock);
	    return(-1);
	}
	tun->refs++;
	pi->tun = tun;
#endif
    }

    /* Configure PPTP/GRE node */
    gc.enabled = 1;
    gc.enableDelayedAck = Enabled(&pi->conf.options, PPTP_CONF_DELAYED_ACK);
    gc.enableAlwaysAck = Enabled(&pi->conf.options, PPTP_CONF_ALWAYS_ACK);
#if NGM_PPTPGRE_COOKIE >= 1082548365
    gc.enableWindowing = Enabled(&pi->conf.options, PPTP_CONF_WINDOWING);
#endif

    if (NgSendMsg(csock, pptppath, NGM_PPTPGRE_COOKIE,
      NGM_PPTPGRE_SET_CONFIG, &gc, sizeof(gc)) < 0) {
	Perror("[%s] PPTP: can't config %s node", l->name, NG_PPTPGRE_NODE_TYPE);
	close(csock);
	return(-1);
    }
  
    close(csock);

    return(0);
}

/*
 * PptpIncoming()
 *
 * The control code calls this function to report that some
 * remote PPTP client has asked us if we will accept an incoming
 * call relayed over PPTP.
 */

static struct pptplinkinfo
PptpIncoming(struct pptpctrlinfo *cinfo,
	struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
	const char *callingNum,
	const char *calledNum,
	const char *subAddress)
{
    return(PptpPeerCall(cinfo, self, peer, port, TRUE, callingNum, calledNum, subAddress));
}

/*
 * PptpOutgoing()
 *
 * The control code calls this function to report that some
 * remote PPTP client has asked us if we will dial out to some
 * phone number. We don't actually do this, but some clients
 * initiate their connections as outgoing calls for some reason.
 */

static struct pptplinkinfo
PptpOutgoing(struct pptpctrlinfo *cinfo,
	struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
	int frameType, int minBps, int maxBps,
	const char *calledNum, const char *subAddress)
{
    return(PptpPeerCall(cinfo, self, peer, port, FALSE, "", calledNum, subAddress));
}

/*
 * PptpPeerCall()
 *
 * Peer has initiated a call (either incoming or outgoing; either
 * way it's the same to us). If we have an available link that may
 * accept calls from the peer's IP addresss and port, then say yes.
 */

static struct pptplinkinfo
PptpPeerCall(struct pptpctrlinfo *cinfo,
	struct u_addr *self, struct u_addr *peer, in_port_t port, int incoming,
	const char *callingNum,
	const char *calledNum,
	const char *subAddress)
{
    struct pptplinkinfo	linfo;
    Link		l = NULL;
    PptpInfo		pi = NULL;
    int			k;

    memset(&linfo, 0, sizeof(linfo));

    linfo.cookie = NULL;
    linfo.result = PptpResult;
    linfo.setLinkInfo = PptpSetLinkInfo;
    linfo.cancel = PptpCancel;

    if (gShutdownInProgress) {
	Log(LG_PHYS, ("Shutdown sequence in progress, ignoring request."));
	return(linfo);
    }

    if (OVERLOAD()) {
	Log(LG_PHYS, ("Daemon overloaded, ignoring request."));
	return(linfo);
    }

    /* Find a suitable link; prefer the link best matching peer's IP address */
    for (k = 0; k < gNumLinks; k++) {
	Link l2;
	PptpInfo pi2;

	if (!gLinks[k] || gLinks[k]->type != &gPptpPhysType)
		continue;

	l2 = gLinks[k];
	pi2 = (PptpInfo)l2->info;

	/* See if link is feasible */
	if ((!PhysIsBusy(l2)) &&
	    Enabled(&l2->conf.options, LINK_CONF_INCOMING) &&
	    (u_addrempty(&pi2->conf.self_addr) || (u_addrcompare(&pi2->conf.self_addr, self) == 0)) &&
	    IpAddrInRange(&pi2->conf.peer_addr, peer) &&
	    (!pi2->conf.peer_port || pi2->conf.peer_port == port)) {

    		/* Link is feasible; now see if it's preferable */
    		if (!pi || pi2->conf.peer_addr.width > pi->conf.peer_addr.width) {
			l = l2;
			pi = pi2;
			if (u_rangehost(&pi->conf.peer_addr)) {
				break;	/* Nothing could be better */
			}
    		}
	}
    }

    if (l != NULL && l->tmpl)
        l = LinkInst(l, NULL, 0, 0);

    /* If no link is suitable, can't take the call */
    if (l == NULL) {
	Log(LG_PHYS, ("No free PPTP link with requested parameters "
	    "was found"));
	return(linfo);
    }
    pi = (PptpInfo)l->info;

    Log(LG_PHYS, ("[%s] Accepting PPTP connection", l->name));

    /* Got one */
    linfo.cookie = l;
    l->state = PHYS_STATE_CONNECTING;
    pi->cinfo = *cinfo;
    pi->originate = FALSE;
    pi->outcall = !incoming;
    pi->sync = 1;
    pi->self_addr = *self;
    pi->peer_addr = *peer;
    pi->peer_port = port;
    strlcpy(pi->callingnum, callingNum, sizeof(pi->callingnum));
    strlcpy(pi->callednum, calledNum, sizeof(pi->callednum));

    PhysIncoming(l);
    return(linfo);
}

/*
 * PptpCancel()
 *
 * The control code calls this function to cancel a
 * local outgoing call in progress.
 */

static void
PptpCancel(void *cookie)
{
    PptpInfo	pi;
    Link 	l;

    /* It this fake call? */
    if (!cookie)
	return;

    l = (Link)cookie;
    pi = (PptpInfo) l->info;

    Log(LG_PHYS, ("[%s] PPTP call cancelled in state %s",
	l->name, gPhysStateNames[l->state]));
    if (l->state == PHYS_STATE_DOWN)
	return;
    l->state = PHYS_STATE_DOWN;
    u_addrclear(&pi->peer_addr);
    pi->peer_port = 0;
    pi->callingnum[0]=0;
    pi->callednum[0]=0;
    pi->peer_iface[0] = 0;
    PhysDown(l, STR_CON_FAILED0, NULL);
}

/*
 * PptpListenUpdate()
 */

static void
PptpListenUpdate(Link l)
{
    PptpInfo	pi = (PptpInfo) l->info;

    if (pi->listener == NULL) {
	if (Enabled(&l->conf.options, LINK_CONF_INCOMING)) {
	    /* Set up listening for incoming connections */
	    if ((pi->listener = 
		PptpCtrlListen(&pi->conf.self_addr, pi->conf.self_port))
		    == NULL) {
		Log(LG_ERR, ("PPTP: Error, can't listen for connection!"));
	    }
	}
    } else {
	if (!Enabled(&l->conf.options, LINK_CONF_INCOMING)) {
	    PptpCtrlUnListen(pi->listener);
	    pi->listener = NULL;
	}
    }
}

/*
 * PptpSetCommand()
 */

static int
PptpSetCommand(Context ctx, int ac, char *av[], void *arg)
{
    PptpInfo		const pi = (PptpInfo) ctx->lnk->info;
    char		**fqdn_peer_addr = &pi->conf.fqdn_peer_addr;
    struct u_range	rng;
    int			port;

    switch ((intptr_t)arg) {
	case SET_SELFADDR:
	case SET_PEERADDR:
	    if ((ac == 1 || ac == 2) && (intptr_t)arg == SET_PEERADDR) {
		if (*fqdn_peer_addr)
		    Freee(*fqdn_peer_addr);
		*fqdn_peer_addr = Mstrdup(MB_PHYS, av[0]);
	    }
    	    if (ac < 1 || ac > 2 || !ParseRange(av[0], &rng, ALLOW_IPV4|ALLOW_IPV6))
		return(-1);
    	    if (ac > 1) {
		if ((port = atoi(av[1])) < 0 || port > 0xffff)
		    return(-1);
    	    } else {
		port = 0;
    	    }
    	    if ((intptr_t)arg == SET_SELFADDR) {
		pi->conf.self_addr = rng.addr;
		pi->conf.self_port = port;
    	    } else {
		pi->conf.peer_addr = rng;
		pi->conf.peer_port = port;
    	    }
    	    break;
	case SET_CALLINGNUM:
    	    if (ac != 1)
		return(-1);
    	    strlcpy(pi->conf.callingnum, av[0], sizeof(pi->conf.callingnum));
    	    break;
	case SET_CALLEDNUM:
    	    if (ac != 1)
		return(-1);
    	    strlcpy(pi->conf.callednum, av[0], sizeof(pi->conf.callednum));
    	    break;
	case SET_ENABLE:
    	    EnableCommand(ac, av, &pi->conf.options, gConfList);
    	    PptpListenUpdate(ctx->lnk);
    	    break;
	case SET_DISABLE:
    	    DisableCommand(ac, av, &pi->conf.options, gConfList);
    	    PptpListenUpdate(ctx->lnk);
    	    break;
	default:
    	    assert(0);
    }
    return(0);
}


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