/*
* Copyright (c) 1995-1999 Whistle Communications, Inc.
* 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
* Whistle Communications; 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
* Whistle Communications, Inc. trademarks, including the mark "WHISTLE
* COMMUNICATIONS" on advertising, endorsements, or otherwise except
* as such appears in the above copyright notice or in the software.
*
* THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS",
* AND TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS
* 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. WHISTLE COMMUNICATIONS 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 WHISTLE COMMUNICATIONS 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 WHISTLE COMMUNICATIONS
* IS ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: <archie@freebsd.org>
*/
#include "ppp/ppp_defs.h"
#include "ppp/ppp_log.h"
#include "ppp/ppp_pptp_ctrl.h"
#include "ppp/ppp_pptp_ctrl_defs.h"
#include <netinet/tcp.h>
/*
* DEFINITIONS
*/
#define RANDOMIZE_CID 1
#define CHECK_RESERVED_FIELDS 0
#define LOGNAME_MAX 32
#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif
#define PPTP_MTYPE "ppp_pptp_ctrl"
#define PPTP_CTRL_MTYPE "ppp_pptp_ctrl.ctrl"
#define PPTP_CHAN_MTYPE "ppp_pptp_ctrl.chan"
#define PPTP_PREP_MTYPE "ppp_pptp_ctrl.prep"
#define PPTP_FIRMWARE_REV 0x0101
#define PPTP_STR_INTERNAL_CALLING "Internally originated VPN call"
/* Limits on how long we wait for replies to things */
#define PPTP_DFL_REPLY_TIME PPTP_IDLE_TIMEOUT
#define PPTP_OUTCALLREQ_REPLY_TIME 60
#define PPTP_INCALLREP_REPLY_TIME 60
#define PPTP_STOPCCR_REPLY_TIME 3
/* Logging */
#define PLOG(sev, fmt, args...) \
ppp_log_put(pptp->log, sev, fmt , ## args)
#define CLOG(sev, fmt, args...) \
ppp_log_put(c->log, sev, fmt , ## args)
#define CHLOG(sev, fmt, args...) \
ppp_log_put(ch->log, sev, fmt , ## args)
struct pptp_engine;
/* This describes how/if a reply is required */
struct pptpreqrep {
u_char reply; /* required reply (or zero) */
u_char killCtrl; /* fatal to ctrl or just to channel */
u_short timeout; /* max time to wait for reply */
};
typedef struct pptpreqrep *PptpReqRep;
/* This represents a pending reply we're waiting for */
struct pptppendrep {
const struct pptpmsginfo *request; /* original message info */
struct pptpctrl *ctrl; /* control channel */
struct pptpchan *chan; /* channel (NULL if none) */
struct pevent *timer; /* reply timeout timer */
struct pptppendrep *next; /* next in list */
};
typedef struct pptppendrep *PptpPendRep;
/* This describes how to match a message to the corresponding channel */
struct pptpchanid {
u_char findIn; /* how to find channel (incoming) */
u_char findOut; /* how to find channel (outgoing) */
const char *inField; /* field used to find channel (in) */
const char *outField; /* field used to find channel (out) */
};
typedef struct pptpchanid *PptpChanId;
#define PPTP_FIND_CHAN_MY_CID 1 /* match field vs. my cid */
#define PPTP_FIND_CHAN_PEER_CID 2 /* match field vs. peer cid */
#define PPTP_FIND_CHAN_PNS_CID 3 /* match field vs. PNS cid */
#define PPTP_FIND_CHAN_PAC_CID 4 /* match field vs. PAC cid */
/* Message handler function type */
typedef void pptpmsghandler_t(void *, void *);
/* Total info about a message type (except field layout) */
struct pptpmsginfo {
const char *name; /* name for this message type */
pptpmsghandler_t *handler; /* message handler function */
u_char isReply; /* this is always a reply message */
u_char length; /* length of message (sans header) */
u_short states; /* states which admit this message */
struct pptpchanid match; /* how to find corresponding channel */
struct pptpreqrep reqrep; /* what kind of reply we expect */
};
typedef const struct pptpmsginfo *PptpMsgInfo;
/* Receive window size XXX */
#define PPTP_RECV_WIN 16
/* Packet processing delay XXX */
#define PPTP_PPD 1
/* Channel state */
struct pptpchan {
u_char state; /* channel state */
u_char id; /* channel index */
u_char orig:1; /* call originated from us */
u_char incoming:1; /* call is incoming, not outgoing */
u_char killing:1; /* channel is being killed */
u_int16_t cid; /* my call id */
u_int16_t serno; /* call serial number */
u_int16_t peerCid; /* peer call id */
u_int16_t peerPpd; /* peer's packet processing delay */
u_int16_t recvWin; /* peer's recv window size */
u_int32_t recvSeq; /* last seq # we rcv'd */
u_int32_t xmitSeq; /* last seq # we sent */
u_int32_t recvAck; /* last seq # peer acknowledged */
u_int32_t xmitAck; /* last seq # we acknowledged */
u_int32_t bearType; /* call bearer type */
u_int32_t frameType; /* call framing type */
u_int32_t minBps; /* minimum acceptable speed */
u_int32_t maxBps; /* maximum acceptable speed */
struct pptplinkinfo linfo; /* info about corresponding link */
struct pptpctrl *ctrl; /* my control channel */
struct ppp_log *log; /* log */
char callingNum[PPTP_PHONE_LEN + 1]; /* calling number */
char calledNum[PPTP_PHONE_LEN + 1]; /* called number */
char subAddress[PPTP_SUBADDR_LEN + 1];/* sub-address */
};
typedef struct pptpchan *PptpChan;
#define PPTP_CHAN_IS_PNS(ch) (!(ch)->orig ^ !(ch)->incoming)
/* Control channel state */
struct pptpctrl {
u_char frame[PPTP_CTRL_MAX_FRAME];
u_char state; /* state */
u_char id; /* channel index */
u_char orig:1; /* we originated connection */
u_char killing:1; /* connection is being killed */
u_int16_t flen; /* length of partial frame */
int csock; /* peer control messages */
struct in_addr self_addr; /* local IP address */
struct in_addr peer_addr; /* peer we're talking to */
u_int16_t self_port;
u_int16_t peer_port;
struct pptp_engine *pptp; /* engine that owns me */
struct pevent *connEvent; /* connection event */
struct pevent *ctrlEvent; /* control connection input */
struct pevent *killEvent; /* kill this ctrl in separate thread */
struct pevent *idleTimer; /* idle timer */
struct ppp_log *log; /* log */
u_int32_t echoId; /* last echo id # sent */
PptpPendRep reps; /* pending replies to msgs */
PptpChan *channels; /* array of channels */
int numChannels; /* length of channels array */
char logname[LOGNAME_MAX]; /* name for logging */
};
typedef struct pptpctrl *PptpCtrl;
#define MAX_IOVEC 32
/* Our physical channel ID */
#define PHYS_CHAN(ch) (((ch)->ctrl->id << 16) | (ch)->id)
/*
* INTERNAL FUNCTIONS
*/
/* Methods for each control message type */
static void PptpStartCtrlConnRequest(PptpCtrl c,
struct pptpStartCtrlConnRequest *m);
static void PptpStartCtrlConnReply(PptpCtrl c,
struct pptpStartCtrlConnReply *m);
static void PptpStopCtrlConnRequest(PptpCtrl c,
struct pptpStopCtrlConnRequest *m);
static void PptpStopCtrlConnReply(PptpCtrl c,
struct pptpStopCtrlConnReply *m);
static void PptpEchoRequest(PptpCtrl c, struct pptpEchoRequest *m);
static void PptpEchoReply(PptpCtrl c, struct pptpEchoReply *m);
static void PptpOutCallRequest(PptpCtrl c, struct pptpOutCallRequest *m);
static void PptpOutCallReply(PptpChan ch, struct pptpOutCallReply *m);
static void PptpInCallRequest(PptpCtrl c, struct pptpInCallRequest *m);
static void PptpInCallReply(PptpChan ch, struct pptpInCallReply *m);
static void PptpInCallConn(PptpChan ch, struct pptpInCallConn *m);
static void PptpCallClearRequest(PptpChan ch,
struct pptpCallClearRequest *m);
static void PptpCallDiscNotify(PptpChan ch, struct pptpCallDiscNotify *m);
static void PptpWanErrorNotify(PptpChan ch, struct pptpWanErrorNotify *m);
static void PptpSetLinkInfo(PptpChan ch, struct pptpSetLinkInfo *m);
/* Link layer callbacks */
static void PptpCtrlCloseChan(PptpChan ch,
int result, int error, int cause);
static void PptpCtrlKillChan(PptpChan ch, const char *errmsg);
static void PptpCtrlDialResult(void *cookie,
int result, int error, int cause, int speed);
/* Internal event handlers */
static pevent_handler_t PptpCtrlListenEvent;
static pevent_handler_t PptpCtrlConnEvent;
static pevent_handler_t PptpCtrlReadCtrl;
/* Shutdown routines */
static void PptpCtrlCloseCtrl(PptpCtrl c);
static void PptpCtrlKillCtrl(PptpCtrl c);
/* Timer routines */
static void PptpCtrlResetIdleTimer(PptpCtrl c);
static void PptpCtrlIdleTimeout(void *arg);
static void PptpCtrlReplyTimeout(void *arg);
/* Misc routines */
static void PptpCtrlInitCtrl(PptpCtrl c, int orig);
static void PptpCtrlMsg(PptpCtrl c, int type, void *msg);
static void PptpCtrlWriteMsg(PptpCtrl c, int type, void *msg);
static int PptpCtrlGetSocket(struct in_addr ip,
u_int16_t port, char *ebuf, size_t elen);
static void PptpCtrlSwap(int type, void *buf);
static void PptpCtrlDump(int sev, PptpCtrl c, int type, void *msg);
static void PptpCtrlDumpBuf(int sev, PptpCtrl c,
const void *data, size_t len, const char *fmt, ...);
static int PptpCtrlFindField(int type, const char *name, u_int *offset);
static void PptpCtrlInitCinfo(PptpChan ch, PptpCtrlInfo ci);
static void PptpCtrlNewCtrlState(PptpCtrl c, int new);
static void PptpCtrlNewChanState(PptpChan ch, int new);
static int PptpCtrlExtendArray(const char *mtype,
void *arrayp, int esize, int *alenp);
static struct pptpctrlinfo
PptpCtrlOrigCall(struct pptp_engine *pptp, int incmg,
struct pptplinkinfo linfo, struct in_addr ip,
u_int16_t port, const char *logname,
int bearType, int frameType, int minBps, int maxBps,
const char *callingNum, const char *calledNum,
const char *subAddress);
static PptpCtrl PptpCtrlGetCtrl(struct pptp_engine *pptp,
int orig, struct in_addr peer_addr,
u_int16_t peer_port, const char *logname,
char *buf, int bsiz);
static PptpChan PptpCtrlGetChan(PptpCtrl c, int chanState, int orig,
int incoming, int bearType, int frameType, int minBps,
int maxBps, const char *callingNum,
const char *calledNum, const char *subAddress);
static PptpChan PptpCtrlFindChan(PptpCtrl c, int type,
void *msg, int incoming);
static void PptpCtrlCheckConn(PptpCtrl c);
/*
* PPTP ENGINE STATE
*/
/* PPTP engine structure */
struct pptp_engine {
int listen_sock; /* listening socket */
struct in_addr listen_addr; /* listen ip address */
u_int16_t listen_port; /* listen port */
u_char nocd; /* no collision detection */
struct pevent_ctx *ev_ctx; /* event context */
pthread_mutex_t *mutex; /* mutex */
struct pevent *listen_event; /* incoming connection event */
PptpCheckNewConn_t *check_new_conn;/* callback for new connectn. */
PptpGetInLink_t *get_in_link; /* callback for incoming call */
PptpGetOutLink_t *get_out_link; /* callback for outgoing call */
struct ppp_log *log; /* log */
void *arg; /* client arg */
u_int16_t last_call_id; /* last used call id */
PptpCtrl *ctrl; /* array of control channels */
int nctrl; /* length of ctrl array */
char vendor[PPTP_VENDOR_LEN]; /* vendor name */
};
/*
* INTERNAL VARIABLES
*/
/* Control message field layout */
static const struct pptpfield
gPptpMsgLayout[PPTP_MAX_CTRL_TYPE][PPTP_CTRL_MAX_FIELDS] =
{
#define _WANT_PPTP_FIELDS
#include "ppp/ppp_pptp_ctrl_defs.h"
};
/* Control channel and call state names */
static const char *gPptpCtrlStates[] = {
#define PPTP_CTRL_ST_FREE 0
"FREE",
#define PPTP_CTRL_ST_IDLE 1
"IDLE",
#define PPTP_CTRL_ST_WAIT_CTL_REPLY 2
"WAIT_CTL_REPLY",
#define PPTP_CTRL_ST_WAIT_STOP_REPLY 3
"WAIT_STOP_REPLY",
#define PPTP_CTRL_ST_ESTABLISHED 4
"ESTABLISHED",
};
static const char *gPptpChanStates[] = {
#define PPTP_CHAN_ST_FREE 0
"FREE",
#define PPTP_CHAN_ST_WAIT_IN_REPLY 1
"WAIT_IN_REPLY",
#define PPTP_CHAN_ST_WAIT_OUT_REPLY 2
"WAIT_OUT_REPLY",
#define PPTP_CHAN_ST_WAIT_CONNECT 3
"WAIT_CONNECT",
#define PPTP_CHAN_ST_WAIT_DISCONNECT 4
"WAIT_DISCONNECT",
#define PPTP_CHAN_ST_WAIT_ANSWER 5
"WAIT_ANSWER",
#define PPTP_CHAN_ST_ESTABLISHED 6
"ESTABLISHED",
#define PPTP_CHAN_ST_WAIT_CTRL 7
"WAIT_CTRL",
};
/* Control message descriptors */
#define CL(s) (1 << (PPTP_CTRL_ST_ ## s))
#define CH(s) ((1 << (PPTP_CHAN_ST_ ## s)) | 0x8000)
static const struct pptpmsginfo gPptpMsgInfo[PPTP_MAX_CTRL_TYPE] = {
{ "PptpMsgHead", NULL, /* placeholder */
FALSE, sizeof(struct pptpMsgHead),
},
{ "StartCtrlConnRequest", (pptpmsghandler_t *)PptpStartCtrlConnRequest,
FALSE, sizeof(struct pptpStartCtrlConnRequest),
CL(IDLE),
{ 0, 0 }, /* no associated channel */
{ PPTP_StartCtrlConnReply, TRUE, PPTP_DFL_REPLY_TIME },
},
{ "StartCtrlConnReply", (pptpmsghandler_t *)PptpStartCtrlConnReply,
TRUE, sizeof(struct pptpStartCtrlConnReply),
CL(WAIT_CTL_REPLY),
{ 0, 0 }, /* no associated channel */
{ 0 }, /* no reply expected */
},
{ "StopCtrlConnRequest", (pptpmsghandler_t *)PptpStopCtrlConnRequest,
FALSE, sizeof(struct pptpStopCtrlConnRequest),
CL(WAIT_CTL_REPLY)|CL(WAIT_STOP_REPLY)|CL(ESTABLISHED),
{ 0, 0 }, /* no associated channel */
{ PPTP_StopCtrlConnReply, TRUE, PPTP_STOPCCR_REPLY_TIME },
},
{ "StopCtrlConnReply", (pptpmsghandler_t *)PptpStopCtrlConnReply,
TRUE, sizeof(struct pptpStopCtrlConnReply),
CL(WAIT_STOP_REPLY),
{ 0, 0 }, /* no associated channel */
{ 0 }, /* no reply expected */
},
{ "EchoRequest", (pptpmsghandler_t *)PptpEchoRequest,
FALSE, sizeof(struct pptpEchoRequest),
CL(ESTABLISHED),
{ 0, 0 }, /* no associated channel */
{ PPTP_EchoReply, TRUE, PPTP_DFL_REPLY_TIME },
},
{ "EchoReply", (pptpmsghandler_t *)PptpEchoReply,
TRUE, sizeof(struct pptpEchoReply),
CL(ESTABLISHED),
{ 0, 0 }, /* no associated channel */
{ 0 }, /* no reply expected */
},
{ "OutCallRequest", (pptpmsghandler_t *)PptpOutCallRequest,
FALSE, sizeof(struct pptpOutCallRequest),
CL(ESTABLISHED),
{ 0, PPTP_FIND_CHAN_MY_CID, NULL, "cid" },
{ PPTP_OutCallReply, TRUE, PPTP_OUTCALLREQ_REPLY_TIME },
},
{ "OutCallReply", (pptpmsghandler_t *)PptpOutCallReply,
TRUE, sizeof(struct pptpOutCallReply),
CH(WAIT_OUT_REPLY),
{ PPTP_FIND_CHAN_MY_CID, PPTP_FIND_CHAN_MY_CID, "peerCid", "cid" },
{ 0 }, /* no reply expected */
},
{ "InCallRequest", (pptpmsghandler_t *)PptpInCallRequest,
FALSE, sizeof(struct pptpInCallRequest),
CL(ESTABLISHED),
{ 0, PPTP_FIND_CHAN_MY_CID, NULL, "cid" },
{ PPTP_InCallReply, FALSE, PPTP_DFL_REPLY_TIME },
},
{ "InCallReply", (pptpmsghandler_t *)PptpInCallReply,
TRUE, sizeof(struct pptpInCallReply),
CH(WAIT_IN_REPLY),
{ PPTP_FIND_CHAN_MY_CID, PPTP_FIND_CHAN_MY_CID, "peerCid", "cid" },
{ PPTP_InCallConn, FALSE, PPTP_INCALLREP_REPLY_TIME },
},
{ "InCallConn", (pptpmsghandler_t *)PptpInCallConn,
TRUE, sizeof(struct pptpInCallConn),
CH(WAIT_CONNECT),
{ PPTP_FIND_CHAN_MY_CID, PPTP_FIND_CHAN_PEER_CID, "peerCid", "peerCid" },
{ 0 }, /* no reply expected */
},
{ "CallClearRequest", (pptpmsghandler_t *)PptpCallClearRequest,
FALSE, sizeof(struct pptpCallClearRequest),
CH(WAIT_IN_REPLY)|CH(WAIT_ANSWER)|CH(ESTABLISHED),
{ PPTP_FIND_CHAN_PNS_CID, PPTP_FIND_CHAN_PNS_CID, "cid", "cid" },
{ PPTP_CallDiscNotify, TRUE, PPTP_DFL_REPLY_TIME },
},
{ "CallDiscNotify", (pptpmsghandler_t *)PptpCallDiscNotify,
FALSE, sizeof(struct pptpCallDiscNotify),
CH(WAIT_OUT_REPLY)|CH(WAIT_CONNECT)|CH(WAIT_DISCONNECT)|CH(ESTABLISHED),
{ PPTP_FIND_CHAN_PAC_CID, PPTP_FIND_CHAN_PAC_CID, "cid", "cid" },
{ 0 }, /* no reply expected */
},
{ "WanErrorNotify", (pptpmsghandler_t *)PptpWanErrorNotify,
FALSE, sizeof(struct pptpWanErrorNotify),
CH(ESTABLISHED),
{ PPTP_FIND_CHAN_PNS_CID, PPTP_FIND_CHAN_PNS_CID, "cid", "cid" },
{ 0 }, /* no reply expected */
},
{ "SetLinkInfo", (pptpmsghandler_t *)PptpSetLinkInfo,
FALSE, sizeof(struct pptpSetLinkInfo),
CH(ESTABLISHED),
{ PPTP_FIND_CHAN_PAC_CID, PPTP_FIND_CHAN_PAC_CID, "cid", "cid" },
{ 0 }, /* no reply expected */
},
};
#undef CL
#undef CH
/* Error code to string converters */
#define DECODE(a, n) ((u_int)(n) < (sizeof(a) / sizeof(*(a))) ? \
(a)[(u_int)(n)] : "[out of range]")
static const char *const gPptpErrorCodes[] = {
"none",
"not connected",
"bad format",
"bad value",
"no resource",
"bad call ID",
"pac error",
};
#define PPTP_ERROR_CODE(n) DECODE(gPptpErrorCodes, (n))
static const char *const gPptpSccrReslCodes[] = {
"zero?",
"OK",
"general error",
"channel exists",
"not authorized",
"bad protocol version",
};
#define PPTP_SCCR_RESL_CODE(n) DECODE(gPptpSccrReslCodes, (n))
static const char *const gPptpSccrReasCodes[] = {
"zero?",
"none",
"bad protocol version",
"local shutdown",
};
#define PPTP_SCCR_REAS_CODE(n) DECODE(gPptpSccrReasCodes, (n))
static const char *const gPptpEchoReslCodes[] = {
"zero?",
"OK",
"general error",
};
#define PPTP_ECHO_RESL_CODE(n) DECODE(gPptpEchoReslCodes, (n))
static const char *const gPptpOcrReslCodes[] = {
"zero?",
"OK",
"general error",
"no carrier",
"busy",
"no dialtone",
"timed out",
"admin prohib",
};
#define PPTP_OCR_RESL_CODE(n) DECODE(gPptpOcrReslCodes, (n))
static const char *const gPptpIcrReslCodes[] = {
"zero?",
"OK",
"general error",
"not accepted",
};
#define PPTP_ICR_RESL_CODE(n) DECODE(gPptpIcrReslCodes, (n))
static const char *const gPptpCdnReslCodes[] = {
"zero?",
"lost carrier",
"general error",
"admin action",
"disconnect request",
};
#define PPTP_CDN_RESL_CODE(n) DECODE(gPptpCdnReslCodes, (n))
/*************************************************************************
EXPORTED FUNCTIONS
*************************************************************************/
/*
* PptpCtrlInit()
*
* Initialize PPTP state and set up callbacks. This must be called
* first, and any calls after the first will ignore the ip parameter.
* Returns 0 if successful, -1 otherwise.
*
* Parameters:
* arg Client opaque argument.
* getInLink Function to call when a peer has requested to establish
* an incoming call. If returned cookie is NULL, call failed.
* This pointer may be NULL to deny all incoming calls.
* getOutLink Function to call when a peer has requested to establish
* an outgoming call. If returned cookie is NULL, call failed.
* This pointer may be NULL to deny all outgoing calls.
* ip The IP address for my server to use (cannot be zero).
* port The TCP port for my server to use (zero for default).
* log The log to use. Note: the log is consumed.
*/
struct pptp_engine *
PptpCtrlInit(void *arg, struct pevent_ctx *ev_ctx, pthread_mutex_t *mutex,
PptpCheckNewConn_t *checkNewConn, PptpGetInLink_t *getInLink,
PptpGetOutLink_t *getOutLink, struct in_addr ip, u_int16_t port,
const char *vendor, struct ppp_log *log, int nocd)
{
struct pptp_engine *pptp;
int type;
/* Sanity check structure lengths and valid state bits */
for (type = 0; type < PPTP_MAX_CTRL_TYPE; type++) {
PptpField field = gPptpMsgLayout[type];
int total;
assert((gPptpMsgInfo[type].match.inField != NULL)
^ !(gPptpMsgInfo[type].states & 0x8000));
for (total = 0; field->name; field++)
total += field->length;
assert(total == gPptpMsgInfo[type].length);
}
/* Create new pptp object */
if ((pptp = MALLOC(PPTP_MTYPE, sizeof(*pptp))) == NULL)
return(NULL);
memset(pptp, 0, sizeof(*pptp));
pptp->ev_ctx = ev_ctx;
pptp->mutex = mutex;
pptp->log = log;
pptp->nocd = nocd;
pptp->arg = arg;
pptp->listen_sock = -1;
pptp->check_new_conn = checkNewConn;
pptp->get_in_link = getInLink;
pptp->get_out_link = getOutLink;
pptp->listen_addr = ip;
pptp->listen_port = port ? port : PPTP_PORT;
if (vendor != NULL)
strlcpy(pptp->vendor, vendor, sizeof(pptp->vendor));
#if RANDOMIZE_CID
pptp->last_call_id = time(NULL) ^ (getpid() << 5);
#endif
/* Done */
return(pptp);
}
/*
* PptpCtrlShutdown()
*
* Destroy a PPTP engine
*/
void
PptpCtrlShutdown(struct pptp_engine **enginep)
{
struct pptp_engine *const pptp = *enginep;
int i;
if (pptp == NULL)
return;
*enginep = NULL;
if (pptp->listen_sock != -1)
(void)close(pptp->listen_sock);
for (i = 0; i < pptp->nctrl; i++) {
if (pptp->ctrl[i] == NULL)
continue;
PptpCtrlKillCtrl(pptp->ctrl[i]);
}
pevent_unregister(&pptp->listen_event);
ppp_log_close(&pptp->log);
FREE(PPTP_MTYPE, pptp->ctrl);
FREE(PPTP_MTYPE, pptp);
}
/*
* PptpCtrlListen()
*
* Enable or disable incoming PPTP TCP connections.
* Returns 0 if successful, -1 otherwise.
*/
int
PptpCtrlListen(struct pptp_engine *pptp, int enable)
{
char ebuf[128];
/* Enable or disable */
if (enable) {
/* Already enabled? */
if (pptp->listen_sock != -1)
return(0);
/* We must have a callback for incoming connections */
if (pptp->check_new_conn == NULL) {
errno = ENXIO;
return(-1);
}
/* Create listening socket */
if ((pptp->listen_sock = PptpCtrlGetSocket(pptp->listen_addr,
pptp->listen_port, ebuf, sizeof(ebuf))) == -1) {
PLOG(LOG_ERR, "pptp: can't get listen socket: %s", ebuf);
return(-1);
}
/* Listen for connections */
if (listen(pptp->listen_sock, 10) == -1) {
PLOG(LOG_ERR, "pptp: %s: %s", "listen", strerror(errno));
goto sock_fail;
}
/* Wait for incoming connections */
if (pevent_register(pptp->ev_ctx, &pptp->listen_event, PEVENT_RECURRING,
pptp->mutex, PptpCtrlListenEvent, pptp, PEVENT_READ,
pptp->listen_sock) == -1) {
PLOG(LOG_ERR, "pptp: %s: %s", "pevent_register", strerror(errno));
sock_fail:
(void)close(pptp->listen_sock);
pptp->listen_sock = -1;
return(-1);
}
} else {
/* Already disabled? */
if (pptp->listen_sock == -1)
return(0);
/* Stop listening */
(void)close(pptp->listen_sock);
pptp->listen_sock = -1;
pevent_unregister(&pptp->listen_event);
}
/* Done */
return(0);
}
/*
* PptpCtrlInCall()
*
* Initiate an incoming call
*/
struct pptpctrlinfo
PptpCtrlInCall(struct pptp_engine *engine,
struct pptplinkinfo linfo, struct in_addr ip, u_int16_t port,
const char *logname, int bearType, int frameType, int minBps,
int maxBps, const char *callingNum, const char *calledNum,
const char *subAddress)
{
return(PptpCtrlOrigCall(engine, TRUE, linfo, ip, port, logname,
bearType, frameType, minBps, maxBps, callingNum, calledNum, subAddress));
}
/*
* PptpCtrlOutCall()
*
* Initiate an outgoing call
*/
struct pptpctrlinfo
PptpCtrlOutCall(struct pptp_engine *engine,
struct pptplinkinfo linfo,
struct in_addr ip, u_int16_t port, const char *logname,
int bearType, int frameType, int minBps, int maxBps,
const char *calledNum, const char *subAddress)
{
return(PptpCtrlOrigCall(engine, FALSE, linfo, ip, port, logname,
bearType, frameType, minBps, maxBps, PPTP_STR_INTERNAL_CALLING,
calledNum, subAddress));
}
/*
* PptpCtrlOrigCall()
*
* Request from the PPTP peer at ip:port the establishment of an
* incoming or outgoing call (as viewed by the peer). The "result"
* callback will be called when the connection has been established
* or failed to do so. This initiates a TCP control connection if
* needed; otherwise it uses the existing connection. If port is
* zero, then use the normal PPTP port.
*/
static struct pptpctrlinfo
PptpCtrlOrigCall(struct pptp_engine *pptp, int incoming,
struct pptplinkinfo linfo, struct in_addr ip, u_int16_t port,
const char *logname, int bearType, int frameType, int minBps,
int maxBps, const char *callingNum, const char *calledNum,
const char *subAddress)
{
PptpCtrl c;
PptpChan ch;
struct pptpctrlinfo cinfo;
char ebuf[100];
/* Init */
port = port ? port : PPTP_PORT;
memset(&cinfo, 0, sizeof(cinfo));
/* Sanity check */
if (linfo.result == NULL) {
PLOG(LOG_ERR, "pptp: can't originate call without 'result' callback");
errno = EINVAL;
return(cinfo);
}
/* Find/create control block */
if ((c = PptpCtrlGetCtrl(pptp, TRUE, ip, port,
logname, ebuf, sizeof(ebuf))) == NULL) {
PLOG(LOG_INFO, "pptp: %s", ebuf);
return(cinfo);
}
/* Get new channel */
if ((ch = PptpCtrlGetChan(c, PPTP_CHAN_ST_WAIT_CTRL, TRUE, incoming,
bearType, frameType, minBps, maxBps,
callingNum, calledNum, subAddress)) == NULL) {
PptpCtrlKillCtrl(c);
return(cinfo);
}
ch->linfo = linfo;
/* Control channel may be ready already; start channel if so */
PptpCtrlCheckConn(c);
/* Return OK */
PptpCtrlInitCinfo(ch, &cinfo);
return(cinfo);
}
/*
* PptpCtrlGetSessionInfo()
*
* Returns information associated with a call.
*/
int
PptpCtrlGetSessionInfo(const struct pptpctrlinfo *cp,
struct in_addr *selfAddr, struct in_addr *peerAddr,
u_int16_t *selfCid, u_int16_t *peerCid,
u_int16_t *peerWin, u_int16_t *peerPpd)
{
PptpChan const ch = (PptpChan)cp->cookie;
switch (ch->state) {
case PPTP_CHAN_ST_WAIT_IN_REPLY:
case PPTP_CHAN_ST_WAIT_OUT_REPLY:
case PPTP_CHAN_ST_WAIT_CONNECT:
case PPTP_CHAN_ST_WAIT_DISCONNECT:
case PPTP_CHAN_ST_WAIT_ANSWER:
case PPTP_CHAN_ST_ESTABLISHED:
case PPTP_CHAN_ST_WAIT_CTRL:
{
PptpCtrl const c = ch->ctrl;
if (selfAddr != NULL)
*selfAddr = c->self_addr;
if (peerAddr != NULL)
*peerAddr = c->peer_addr;
if (selfCid != NULL)
*selfCid = ch->cid;
if (peerCid != NULL)
*peerCid = ch->peerCid;
if (peerWin != NULL)
*peerWin = ch->recvWin;
if (peerPpd != NULL)
*peerPpd = ch->peerPpd;
return(0);
}
case PPTP_CHAN_ST_FREE:
errno = ENXIO;
return(-1);
break;
default:
assert(0);
}
return(-1); /* NOTREACHED */
}
/*************************************************************************
CONTROL CONNECTION SETUP
*************************************************************************/
/*
* PptpCtrlListenEvent()
*
* Someone has connected to our TCP socket on which we were listening.
*/
static void
PptpCtrlListenEvent(void *cookie)
{
struct pptp_engine *const pptp = cookie;
struct sockaddr_in peer;
int psize = sizeof(peer);
char logname[LOGNAME_MAX];
char ebuf[100];
PptpCtrl c;
int sock;
/* Accept connection */
if ((sock = accept(pptp->listen_sock,
(struct sockaddr *)&peer, &psize)) == -1)
return;
(void)fcntl(sock, F_SETFD, 1);
/* See if accepting connection is ok */
snprintf(logname, sizeof(logname), "%s:%u",
inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
if ((*pptp->check_new_conn)(pptp->arg, peer.sin_addr,
ntohs(peer.sin_port), logname, sizeof(logname)) != 0) {
PLOG(LOG_INFO, "pptp connection from %s rejected", logname);
(void)close(sock);
return;
}
/* Initialize a new control block */
PLOG(LOG_INFO, "pptp connection from %s", logname);
if ((c = PptpCtrlGetCtrl(pptp, FALSE, peer.sin_addr,
ntohs(peer.sin_port), logname, ebuf, sizeof(ebuf))) == NULL) {
PLOG(LOG_ERR, "pptp connection failed: %s", ebuf);
(void)close(sock);
return;
}
c->csock = sock;
/* Initialize the session */
PptpCtrlInitCtrl(c, FALSE);
}
/*
* PptpCtrlConnEvent()
*
* We are trying to make a TCP connection to the peer. When this
* either succeeds or fails, we jump to here.
*/
static void
PptpCtrlConnEvent(void *cookie)
{
PptpCtrl const c = (PptpCtrl) cookie;
struct sockaddr_in addr;
int addrLen = sizeof(addr);
/* Unregister event */
assert(c->state == PPTP_CTRL_ST_IDLE);
pevent_unregister(&c->connEvent);
/* Check whether the connection was successful or not */
if (getpeername(c->csock, (struct sockaddr *) &addr, &addrLen) < 0) {
CLOG(LOG_INFO, "connection to peer failed");
PptpCtrlKillCtrl(c);
return;
}
/* Initialize the session */
CLOG(LOG_INFO, "successfully connected to peer");
PptpCtrlInitCtrl(c, TRUE);
}
/*
* PptpCtrlInitCtrl()
*
* A TCP connection has just been established. Initialize the
* control block for this connection and initiate the session.
*/
static void
PptpCtrlInitCtrl(PptpCtrl c, int orig)
{
struct pptp_engine *const pptp = c->pptp;
struct sockaddr_in self, peer;
static const int one = 1;
int k, addrLen;
/* Good time for a sanity check */
assert(c->state == PPTP_CTRL_ST_IDLE);
assert(c->connEvent == NULL);
assert(c->ctrlEvent == NULL);
assert(c->reps == NULL);
for (k = 0; k < c->numChannels; k++) {
assert(c->channels[k] == NULL
|| c->channels[k]->state == PPTP_CHAN_ST_WAIT_CTRL);
}
/* Initialize control state */
c->orig = orig;
c->echoId = 0;
c->flen = 0;
/* Get local IP address */
addrLen = sizeof(self);
if (getsockname(c->csock, (struct sockaddr *) &self, &addrLen) < 0) {
CLOG(LOG_ERR, "%s: %s", "getsockname", strerror(errno));
abort:
PptpCtrlKillCtrl(c);
return;
}
c->self_addr = self.sin_addr;
c->self_port = ntohs(self.sin_port);
/* Get remote IP address */
addrLen = sizeof(peer);
if (getpeername(c->csock, (struct sockaddr *) &peer, &addrLen) < 0) {
CLOG(LOG_ERR, "%s: %s", "getpeername", strerror(errno));
goto abort;
}
c->peer_addr = peer.sin_addr;
c->peer_port = ntohs(peer.sin_port);
/* Turn of Nagle algorithm on the TCP socket, since we are going to
be writing complete control frames one at a time */
if (setsockopt(c->csock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0)
CLOG(LOG_ERR, "%s: %s", "setsockopt", strerror(errno));
/* Register for events on control and data sockets */
if (pevent_register(pptp->ev_ctx, &c->ctrlEvent, PEVENT_RECURRING,
pptp->mutex, PptpCtrlReadCtrl, c, PEVENT_READ, c->csock) == -1) {
CLOG(LOG_ERR, "%s: %s", "pevent_register", strerror(errno));
goto abort;
}
/* Start echo keep-alive timer */
PptpCtrlResetIdleTimer(c);
/* If we originated the call, we start the conversation */
if (c->orig) {
struct pptpStartCtrlConnRequest msg;
memset(&msg, 0, sizeof(msg));
msg.vers = PPTP_PROTO_VERS;
msg.frameCap = PPTP_FRAMECAP_SYNC;
msg.bearCap = PPTP_BEARCAP_ANY;
msg.firmware = PPTP_FIRMWARE_REV;
gethostname(msg.host, sizeof(msg.host));
strncpy(msg.vendor, pptp->vendor, sizeof(msg.vendor));
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_WAIT_CTL_REPLY);
PptpCtrlWriteMsg(c, PPTP_StartCtrlConnRequest, &msg);
}
}
/*************************************************************************
CONTROL CONNECTION MESSAGE HANDLING
*************************************************************************/
/*
* PptpCtrlReadCtrl()
*/
static void
PptpCtrlReadCtrl(void *cookie)
{
PptpCtrl const c = (PptpCtrl) cookie;
PptpMsgHead const hdr = (PptpMsgHead) c->frame;
int nread;
/* Figure how much to read and read it */
nread = (c->flen < sizeof(*hdr) ? sizeof(*hdr) : hdr->length) - c->flen;
if ((nread = read(c->csock, c->frame + c->flen, nread)) <= 0) {
if (nread < 0)
CLOG(LOG_ERR, "%s: %s", "read", strerror(errno));
else
CLOG(LOG_INFO, "control connection closed by peer");
goto abort;
}
PptpCtrlDumpBuf(LOG_DEBUG + 1, c,
c->frame + c->flen, nread, "read ctrl data");
c->flen += nread;
/* Do whatever with what we got */
if (c->flen < sizeof(*hdr)) /* incomplete header */
return;
if (c->flen == sizeof(*hdr)) { /* complete header */
PptpCtrlSwap(0, hdr); /* byte swap header */
CLOG(LOG_DEBUG + 1, "rec'd hdr");
PptpCtrlDump(LOG_DEBUG + 1, c, 0, hdr);
if (hdr->msgType != PPTP_CTRL_MSG_TYPE) {
CLOG(LOG_NOTICE, "rec'd invalid msg type %d", hdr->msgType);
goto abort;
}
if (hdr->magic != PPTP_MAGIC) {
CLOG(LOG_NOTICE, "rec'd invalid magic 0x%08x", hdr->type);
goto abort;
}
if (!PPTP_VALID_CTRL_TYPE(hdr->type)) {
CLOG(LOG_NOTICE, "rec'd invalid ctrl type %d", hdr->type);
goto abort;
}
#if CHECK_RESERVED_FIELDS
if (hdr->resv0 != 0) {
CLOG(LOG_NOTICE, "rec'd non-zero reserved field in header");
goto abort;
}
#endif
if (hdr->length != sizeof(*hdr) + gPptpMsgInfo[hdr->type].length) {
CLOG(LOG_NOTICE, "rec'd invalid length %u for type %s",
hdr->length, gPptpMsgInfo[hdr->type].name);
abort:
PptpCtrlKillCtrl(c);
return;
}
return;
}
if (c->flen == hdr->length) { /* complete message */
void *const msg = ((u_char *) hdr) + sizeof(*hdr);
PptpCtrlSwap(hdr->type, msg); /* byte swap message */
CLOG(LOG_DEBUG, "recv %s", gPptpMsgInfo[hdr->type].name);
PptpCtrlDump(LOG_DEBUG, c, hdr->type, msg);
c->flen = 0;
PptpCtrlResetIdleTimer(c);
PptpCtrlMsg(c, hdr->type, msg);
}
}
/*
* PptpCtrlMsg()
*
* We read a complete control message. Sanity check it and handle it.
*/
static void
PptpCtrlMsg(PptpCtrl c, int type, void *msg)
{
PptpMsgInfo const mi = &gPptpMsgInfo[type];
PptpChan ch = NULL;
PptpPendRep *pp;
#if CHECK_RESERVED_FIELDS
{
PptpField field = gPptpMsgLayout[type];
u_int off;
static u_char zeros[4];
/* Make sure all reserved fields are zero */
for (off = 0; field->name; off += field->length, field++) {
if (!strncmp(field->name, PPTP_RESV_PREF, strlen(PPTP_RESV_PREF))
&& memcmp((u_char *) msg + off, zeros, field->length)) {
CLOG(LOG_INFO, "rec'd non-zero reserved field %s in %s",
field->name, mi->name);
PptpCtrlKillCtrl(c);
return;
}
}
}
#endif
/* Find channel this message corresponds to (if any) */
if (mi->match.inField && !(ch = PptpCtrlFindChan(c, type, msg, TRUE)))
return;
/* See if this message qualifies as the reply to a previously sent message */
for (pp = &c->reps; *pp; pp = &(*pp)->next) {
if ((*pp)->request->reqrep.reply == type && (*pp)->chan == ch)
break;
}
/* If not, and this message is *always* a reply, ignore it */
if (*pp == NULL && mi->isReply) {
CLOG(LOG_NOTICE, "rec'd spurious %s", mi->name);
return;
}
/* If so, cancel the matching pending reply */
if (*pp) {
PptpPendRep const prep = *pp;
pevent_unregister(&prep->timer);
*pp = prep->next;
FREE(PPTP_PREP_MTYPE, prep);
}
/* Check for invalid message and call or control state combinations */
if (!ch && ((1 << c->state) & mi->states) == 0) {
CLOG(LOG_NOTICE, "rec'd %s in %s state %s (not valid)",
gPptpMsgInfo[type].name, "control channel", gPptpCtrlStates[c->state]);
PptpCtrlKillCtrl(c);
return;
}
if (ch && ((1 << ch->state) & mi->states) == 0) {
CHLOG(LOG_NOTICE, "rec'd %s in %s state %s (not valid)",
gPptpMsgInfo[type].name, "channel", gPptpChanStates[ch->state]);
PptpCtrlKillCtrl(c);
return;
}
/* Things look OK; process message */
(*mi->handler)(ch ? (void *) ch : (void *) c, msg);
}
/*
* PptpCtrlWriteMsg()
*
* Write out a control message. If we should expect a reply,
* register a matching pending reply for it.
*/
static void
PptpCtrlWriteMsg(PptpCtrl c, int type, void *msg)
{
struct pptp_engine *const pptp = c->pptp;
PptpMsgInfo const mi = &gPptpMsgInfo[type];
u_char buf[PPTP_CTRL_MAX_FRAME];
PptpMsgHead const hdr = (PptpMsgHead) buf;
u_char *const payload = (u_char *) (hdr + 1);
const int totlen = sizeof(*hdr) + gPptpMsgInfo[type].length;
int nwrote;
/* Build message */
assert(PPTP_VALID_CTRL_TYPE(type));
memset(hdr, 0, sizeof(*hdr));
hdr->length = totlen;
hdr->msgType = PPTP_CTRL_MSG_TYPE;
hdr->magic = PPTP_MAGIC;
hdr->type = type;
memcpy(payload, msg, gPptpMsgInfo[type].length);
CLOG(LOG_DEBUG, "send %s msg", gPptpMsgInfo[hdr->type].name);
PptpCtrlDump(LOG_DEBUG, c, 0, hdr);
PptpCtrlDump(LOG_DEBUG, c, type, msg);
/* Byte swap it */
PptpCtrlSwap(0, hdr);
PptpCtrlSwap(type, payload);
/* Send it; if TCP buffer is full, we abort the connection */
if ((nwrote = write(c->csock, buf, totlen)) != totlen) {
if (nwrote < 0)
CLOG(LOG_ERR, "%s: %s", "write", strerror(errno));
else
CLOG(LOG_ERR, "only wrote %d/%d", nwrote, totlen);
goto kill;
}
PptpCtrlDumpBuf(LOG_DEBUG + 1, c, buf, totlen, "wrote ctrl data");
/* If we expect a reply to this message, start expecting it now */
if (PPTP_VALID_CTRL_TYPE(mi->reqrep.reply)) {
PptpPendRep prep;
if ((prep = MALLOC(PPTP_PREP_MTYPE, sizeof(*prep))) == NULL) {
CLOG(LOG_ERR, "%s: %s", "malloc", strerror(errno));
goto kill;
}
memset(prep, 0, sizeof(*prep));
prep->ctrl = c;
prep->chan = PptpCtrlFindChan(c, type, msg, FALSE);
prep->request = mi;
if (pevent_register(pptp->ev_ctx, &prep->timer, 0,
pptp->mutex, PptpCtrlReplyTimeout, prep, PEVENT_TIME,
mi->reqrep.timeout * 1000) == -1) {
CLOG(LOG_ERR, "%s: %s", "pevent_register", strerror(errno));
FREE(PPTP_PREP_MTYPE, prep);
goto kill;
}
prep->next = c->reps;
c->reps = prep;
}
/* Done */
return;
kill:
/* There was an error, kill connection (later) */
pevent_unregister(&c->killEvent);
if (pevent_register(pptp->ev_ctx, &c->killEvent, 0, pptp->mutex,
(pevent_handler_t *)PptpCtrlKillCtrl, c, PEVENT_TIME, 0) == -1)
CLOG(LOG_ERR, "pevent_register: %m");
}
/*************************************************************************
CONTROL AND CHANNEL ALLOCATION FUNCTIONS
*************************************************************************/
/*
* PptpCtrlGetCtrl()
*
* Get existing or create new control bock for given peer and return it.
* Returns NULL if there was some problem, and puts an error message
* into the buffer.
*
* If "orig" is TRUE, and we currently have no TCP connection to the peer,
* then initiate one. Otherwise, make sure we don't already have one,
* because that would mean we'd have two connections to the same peer.
*/
static PptpCtrl
PptpCtrlGetCtrl(struct pptp_engine *pptp, int orig, struct in_addr peer_addr,
u_int16_t peer_port, const char *logname, char *buf, int bsiz)
{
PptpCtrl c;
struct sockaddr_in peer;
static const struct in_addr any = { 0 };
int flags;
int k;
/* See if we're already have a control block matching this address and port */
for (k = 0; k < pptp->nctrl; k++) {
PptpCtrl const c = pptp->ctrl[k];
if (c != NULL
&& c->peer_addr.s_addr == peer_addr.s_addr
&& c->peer_port == peer_port) {
if (orig)
return(c);
snprintf(buf, bsiz, "connection to %s already exists", logname);
return(NULL);
}
}
/* Find/create a free one */
for (k = 0; k < pptp->nctrl && pptp->ctrl[k] != NULL; k++);
if (k == pptp->nctrl) {
if (PptpCtrlExtendArray(PPTP_MTYPE, &pptp->ctrl,
sizeof(*pptp->ctrl), &pptp->nctrl) == -1) {
snprintf(buf, bsiz, "%s: %s", "malloc", strerror(errno));
return(NULL);
}
}
if ((c = MALLOC(PPTP_CTRL_MTYPE, sizeof(*c))) == NULL) {
snprintf(buf, bsiz, "%s: %s", "malloc", strerror(errno));
return(NULL);
}
memset(c, 0, sizeof(*c));
pptp->ctrl[k] = c;
/* Initialize it */
c->id = k;
c->pptp = pptp;
c->orig = orig;
c->csock = -1;
c->peer_addr = peer_addr;
c->peer_port = peer_port;
strlcpy(c->logname, logname, sizeof(c->logname));
if ((c->log = ppp_log_prefix(pptp->log, "%s: ", c->logname)) == NULL) {
snprintf(buf, bsiz, "%s: %s", "ppp_log_prefix", strerror(errno));
pptp->ctrl[k] = NULL;
FREE(PPTP_CTRL_MTYPE, c);
return(NULL);
}
/* Go to the idle state */
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_IDLE);
/* If not doing the connecting, return here */
if (!c->orig)
return(c);
/* Get socket */
if ((c->csock = PptpCtrlGetSocket(any, 0, buf, bsiz)) < 0) {
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_FREE);
return(NULL);
}
/* Put socket in non-blocking mode */
if (fcntl(c->csock, F_GETFL, &flags) == -1
|| fcntl(c->csock, F_SETFL, flags | O_NONBLOCK) == -1) {
snprintf(buf, bsiz, "pptp: connect to %s failed: %s: %s",
logname, "fcntl", strerror(errno));
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_FREE);
return(NULL);
}
/* Initiate connection to peer */
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr = c->peer_addr;
peer.sin_port = htons(c->peer_port);
if (connect(c->csock, (struct sockaddr *) &peer, sizeof(peer)) < 0
&& errno != EINPROGRESS) {
(void) close(c->csock);
c->csock = -1;
snprintf(buf, bsiz, "pptp: connect to %s failed: %s",
logname, strerror(errno));
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_FREE);
return(NULL);
}
/* Put socket back in blocking mode */
if (fcntl(c->csock, F_SETFL, flags) == -1) {
(void) close(c->csock);
c->csock = -1;
snprintf(buf, bsiz, "pptp: connect to %s failed: %s: %s",
logname, "fcntl", strerror(errno));
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_FREE);
return(NULL);
}
/* Wait for it to go through */
if (pevent_register(pptp->ev_ctx, &c->connEvent, PEVENT_RECURRING,
pptp->mutex, PptpCtrlConnEvent, c, PEVENT_READ, c->csock) == -1) {
CLOG(LOG_ERR, "%s: %s", "pevent_register", strerror(errno));
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_FREE);
return(NULL);
}
/* Done */
CLOG(LOG_INFO, "intiating connection to peer");
return(c);
}
/*
* PptpCtrlGetChan()
*
* Find a free data channel and attach it to the control channel.
*/
static PptpChan
PptpCtrlGetChan(PptpCtrl c, int chanState, int orig, int incoming,
int bearType, int frameType, int minBps, int maxBps,
const char *callingNum, const char *calledNum, const char *subAddress)
{
struct pptp_engine *const pptp = c->pptp;
PptpChan ch;
int k;
/* Get a free data channel */
for (k = 0; k < c->numChannels && c->channels[k] != NULL; k++);
if (k == c->numChannels) {
if (PptpCtrlExtendArray(PPTP_CTRL_MTYPE, &c->channels,
sizeof(*c->channels), &c->numChannels) == -1) {
CLOG(LOG_ERR, "%s: %s", "malloc", strerror(errno));
return(NULL);
}
}
if ((ch = MALLOC(PPTP_CHAN_MTYPE, sizeof(*ch))) == NULL) {
CLOG(LOG_ERR, "%s: %s", "malloc", strerror(errno));
return(NULL);
}
memset(ch, 0, sizeof(*ch));
c->channels[k] = ch;
ch->id = k;
ch->cid = ++pptp->last_call_id;
ch->ctrl = c;
ch->orig = orig;
ch->incoming = incoming;
ch->minBps = minBps;
ch->maxBps = maxBps;
ch->bearType = bearType;
ch->frameType = frameType;
snprintf(ch->calledNum, sizeof(ch->calledNum), "%s", calledNum);
snprintf(ch->callingNum, sizeof(ch->callingNum), "%s", callingNum);
snprintf(ch->subAddress, sizeof(ch->subAddress), "%s", subAddress);
/* Get log for this channel */
if ((ch->log = ppp_log_prefix(pptp->log,
"%s[%d]: ", c->logname, ch->id)) == NULL) {
CLOG(LOG_ERR, "%s: %s", "ppp_log_prefix", strerror(errno));
pptp->last_call_id--;
c->channels[k] = NULL;
FREE(PPTP_CHAN_MTYPE, ch);
return(NULL);
}
/* Go to starting state */
PptpCtrlNewChanState(ch, chanState);
return(ch);
}
/*
* PptpCtrlDialResult()
*
* Link layer calls this to let us know whether an outgoing call
* has been successfully completed or has failed.
*/
static void
PptpCtrlDialResult(void *cookie, int result, int error, int cause, int speed)
{
PptpChan const ch = (PptpChan) cookie;
PptpCtrl const c = ch->ctrl;
struct pptpOutCallReply rep;
memset(&rep, 0, sizeof(rep));
rep.cid = ch->cid;
rep.peerCid = ch->peerCid;
rep.result = result;
if (rep.result == PPTP_OCR_RESL_ERR)
rep.err = error;
rep.cause = cause;
rep.speed = speed;
rep.ppd = PPTP_PPD; /* XXX should get this value from link layer */
rep.recvWin = PPTP_RECV_WIN; /* XXX */
rep.channel = PHYS_CHAN(ch);
PptpCtrlWriteMsg(c, PPTP_OutCallReply, &rep);
if (rep.result == PPTP_OCR_RESL_OK)
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_ESTABLISHED);
else
PptpCtrlKillChan(ch, "local outgoing call failed");
}
/*************************************************************************
SHUTDOWN FUNCTIONS
*************************************************************************/
/*
* PptpCtrlCloseCtrl()
*/
static void
PptpCtrlCloseCtrl(PptpCtrl c)
{
CLOG(LOG_INFO, "closing PPTP control connection");
switch (c->state) {
case PPTP_CTRL_ST_IDLE:
case PPTP_CTRL_ST_WAIT_STOP_REPLY:
case PPTP_CTRL_ST_WAIT_CTL_REPLY:
PptpCtrlKillCtrl(c);
return;
case PPTP_CTRL_ST_ESTABLISHED:
{
struct pptpStopCtrlConnRequest req;
memset(&req, 0, sizeof(req));
req.reason = PPTP_SCCR_REAS_LOCAL;
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_WAIT_STOP_REPLY);
PptpCtrlWriteMsg(c, PPTP_StopCtrlConnRequest, &req);
return;
}
break;
default:
assert(0);
}
}
/*
* PptpCtrlKillCtrl()
*/
static void
PptpCtrlKillCtrl(PptpCtrl c)
{
PptpPendRep prep, next;
int k;
/* Cancel kill event */
pevent_unregister(&c->killEvent);
/* Don't recurse */
assert(c);
if (c->killing || c->state == PPTP_CTRL_ST_FREE)
return;
c->killing = 1;
/* Do ungraceful shutdown */
CLOG(LOG_DEBUG, "killing control connection");
for (k = 0; k < c->numChannels; k++) {
PptpChan const ch = c->channels[k];
if (ch != NULL)
PptpCtrlKillChan(ch, "control channel shutdown");
}
if (c->csock >= 0) {
close(c->csock);
c->csock = -1;
}
pevent_unregister(&c->connEvent);
pevent_unregister(&c->ctrlEvent);
pevent_unregister(&c->idleTimer);
for (prep = c->reps; prep; prep = next) {
next = prep->next;
pevent_unregister(&prep->timer);
FREE(PPTP_PREP_MTYPE, prep);
}
c->reps = NULL;
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_FREE);
}
/*
* PptpCtrlCloseChan()
*
* Gracefully clear a call.
*/
static void
PptpCtrlCloseChan(PptpChan ch, int result, int error, int cause)
{
PptpCtrl const c = ch->ctrl;
/* Don't recurse */
if (ch->killing)
return;
/* Check call state */
switch (ch->state) {
case PPTP_CHAN_ST_ESTABLISHED:
if (PPTP_CHAN_IS_PNS(ch))
goto pnsClear;
else
goto pacClear;
break;
case PPTP_CHAN_ST_WAIT_ANSWER:
{
struct pptpOutCallReply reply;
memset(&reply, 0, sizeof(reply));
reply.peerCid = ch->peerCid;
reply.result = PPTP_OCR_RESL_ADMIN;
PptpCtrlWriteMsg(c, PPTP_OutCallReply, &reply);
PptpCtrlKillChan(ch, "link layer shutdown"); /* XXX errmsg */
return;
}
break;
case PPTP_CHAN_ST_WAIT_IN_REPLY: /* we are the PAC */
pacClear:
{
struct pptpCallDiscNotify disc;
CHLOG(LOG_INFO, "clearing call");
memset(&disc, 0, sizeof(disc));
disc.cid = ch->cid;
disc.result = result;
if (disc.result == PPTP_CDN_RESL_ERR)
disc.err = error;
disc.cause = cause;
/* XXX stats? */
PptpCtrlWriteMsg(c, PPTP_CallDiscNotify, &disc);
PptpCtrlKillChan(ch, "link layer shutdown"); /* XXX errmsg */
}
break;
case PPTP_CHAN_ST_WAIT_OUT_REPLY: /* we are the PNS */
case PPTP_CHAN_ST_WAIT_CONNECT: /* we are the PNS */
pnsClear:
{
struct pptpCallClearRequest req;
CHLOG(LOG_INFO, "clearing call");
memset(&req, 0, sizeof(req));
req.cid = ch->cid;
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_WAIT_DISCONNECT);
PptpCtrlWriteMsg(c, PPTP_CallClearRequest, &req);
}
break;
case PPTP_CHAN_ST_WAIT_DISCONNECT: /* call was already cleared */
return;
case PPTP_CHAN_ST_WAIT_CTRL:
PptpCtrlKillChan(ch, "link layer shutdown");
return;
default:
assert(0);
}
}
/*
* PptpCtrlKillChan()
*/
static void
PptpCtrlKillChan(PptpChan ch, const char *errmsg)
{
PptpCtrl const c = ch->ctrl;
PptpPendRep *pp;
int k;
/* Don't recurse */
assert(ch);
if (ch->killing) /* should never happen anyway */
return;
ch->killing = 1;
/* If link layer needs notification, tell it */
CHLOG(LOG_DEBUG, "killing channel");
switch (ch->state) {
case PPTP_CHAN_ST_WAIT_IN_REPLY:
case PPTP_CHAN_ST_WAIT_OUT_REPLY:
case PPTP_CHAN_ST_WAIT_CONNECT:
case PPTP_CHAN_ST_ESTABLISHED:
case PPTP_CHAN_ST_WAIT_CTRL:
(*ch->linfo.result)(ch->linfo.cookie, errmsg);
break;
case PPTP_CHAN_ST_WAIT_DISCONNECT:
break;
case PPTP_CHAN_ST_WAIT_ANSWER:
(*ch->linfo.cancel)(ch->linfo.cookie);
break;
default:
assert(0);
}
/* Nuke any pending replies pertaining to this channel */
for (pp = &c->reps; *pp; ) {
PptpPendRep const prep = *pp;
if (prep->chan == ch) {
pevent_unregister(&prep->timer);
*pp = prep->next;
FREE(PPTP_PREP_MTYPE, prep);
} else
pp = &prep->next;
}
/* Free channel */
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_FREE);
/* When the last channel is closed, close the control channel too,
unless we're already in the process of killing it. */
for (k = 0; k < c->numChannels; k++) {
PptpChan const ch2 = c->channels[k];
if (ch2 != NULL && ch2->ctrl == c)
break;
}
if (k == c->numChannels
&& c->state == PPTP_CTRL_ST_ESTABLISHED
&& !c->killing)
PptpCtrlCloseCtrl(c);
}
/*************************************************************************
TIMER RELATED FUNCTIONS
*************************************************************************/
/*
* PptpCtrlReplyTimeout()
*/
static void
PptpCtrlReplyTimeout(void *arg)
{
PptpPendRep const prep = (PptpPendRep) arg;
PptpPendRep *pp;
PptpChan const ch = prep->chan;
PptpCtrl const c = prep->ctrl;
/* Cancel timer event */
pevent_unregister(&prep->timer);
/* Log it */
if (ch) {
CHLOG(LOG_NOTICE, "no reply to %s after %d sec",
prep->request->name, prep->request->reqrep.timeout);
} else if (prep->request - gPptpMsgInfo != PPTP_StopCtrlConnRequest) {
CLOG(LOG_NOTICE, "no reply to %s after %d sec",
prep->request->name, prep->request->reqrep.timeout);
}
/* Unlink pending reply */
for (pp = &c->reps; *pp != prep; pp = &(*pp)->next);
assert(*pp);
*pp = prep->next;
/* Either close this channel or kill entire control connection */
if (prep->request->reqrep.killCtrl)
PptpCtrlKillCtrl(c);
else
PptpCtrlCloseChan(ch, PPTP_CDN_RESL_ERR, PPTP_ERROR_PAC_ERROR, 0);
/* Done */
FREE(PPTP_PREP_MTYPE, prep);
}
/*
* PptpCtrlIdleTimeout()
*
* We've heard PPTP_IDLE_TIMEOUT seconds of silence from the peer.
* Send an echo request to make sure it's alive.
*/
static void
PptpCtrlIdleTimeout(void *arg)
{
PptpCtrl const c = (PptpCtrl) arg;
struct pptpEchoRequest msg;
int k;
/* Cancel timer event */
pevent_unregister(&c->idleTimer);
/* If no channels are left on this control connection, shut it down */
for (k = 0; k < c->numChannels && c->channels[k] == NULL; k++);
if (k == c->numChannels) {
PptpCtrlCloseCtrl(c);
return;
}
/* Send echo request */
memset(&msg, 0, sizeof(msg));
msg.id = ++c->echoId;
PptpCtrlWriteMsg(c, PPTP_EchoRequest, &msg);
}
/*
* PptpCtrlResetIdleTimer()
*
* Reset the idle timer back up to PPTP_IDLE_TIMEOUT seconds.
*/
static void
PptpCtrlResetIdleTimer(PptpCtrl c)
{
struct pptp_engine *const pptp = c->pptp;
pevent_unregister(&c->idleTimer);
if (pevent_register(pptp->ev_ctx, &c->idleTimer, 0, pptp->mutex,
PptpCtrlIdleTimeout, c, PEVENT_TIME, PPTP_IDLE_TIMEOUT * 1000) == -1)
CLOG(LOG_ERR, "%s: %s", "pevent_register", strerror(errno));
}
/*************************************************************************
CHANNEL MAINTENANCE FUNCTIONS
*************************************************************************/
/*
* PptpCtrlCheckConn()
*
* Check whether we have any pending connection requests, and whether
* we can send them yet, or what.
*/
static void
PptpCtrlCheckConn(PptpCtrl c)
{
int k;
switch (c->state) {
case PPTP_CTRL_ST_IDLE:
case PPTP_CTRL_ST_WAIT_CTL_REPLY:
case PPTP_CTRL_ST_WAIT_STOP_REPLY:
break;
case PPTP_CTRL_ST_ESTABLISHED:
for (k = 0; k < c->numChannels; k++) {
PptpChan const ch = c->channels[k];
if (ch == NULL || ch->state != PPTP_CHAN_ST_WAIT_CTRL)
continue;
if (ch->incoming) {
struct pptpInCallRequest req;
memset(&req, 0, sizeof(req));
req.cid = ch->cid;
req.serno = req.cid;
req.bearType = PPTP_BEARCAP_DIGITAL;
req.channel = PHYS_CHAN(ch);
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_WAIT_IN_REPLY);
PptpCtrlWriteMsg(c, PPTP_InCallRequest, &req);
} else {
struct pptpOutCallRequest req;
memset(&req, 0, sizeof(req));
req.cid = ch->cid;
req.serno = req.cid;
req.minBps = ch->minBps;
req.maxBps = ch->maxBps;
req.bearType = ch->bearType;
req.frameType = ch->frameType;
req.recvWin = PPTP_RECV_WIN; /* XXX */
req.ppd = PPTP_PPD; /* XXX */
req.numLen = strlen(ch->calledNum);
strncpy(req.phone, ch->calledNum, sizeof(req.phone));
strncpy(req.subaddr, ch->subAddress, sizeof(req.subaddr));
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_WAIT_OUT_REPLY);
PptpCtrlWriteMsg(c, PPTP_OutCallRequest, &req);
}
return;
}
break;
default:
assert(0);
}
}
/*
* PptpCtrlFindChan()
*
* Find the channel identified by this message. Returns NULL if
* the message is not channel specific, or the channel was not found.
*/
static PptpChan
PptpCtrlFindChan(PptpCtrl c, int type, void *msg, int incoming)
{
PptpMsgInfo const mi = &gPptpMsgInfo[type];
const char *fname = incoming ? mi->match.inField : mi->match.outField;
const int how = incoming ? mi->match.findIn : mi->match.findOut;
u_int16_t cid;
int k, off;
/* Get the identifying CID field */
if (!fname)
return(NULL);
(void) PptpCtrlFindField(type, fname, &off); /* we know len == 2 */
cid = *((u_int16_t *) ((u_char *) msg + off));
/* Match the CID against our list of active channels */
for (k = 0; k < c->numChannels; k++) {
PptpChan const ch = c->channels[k];
u_int16_t tryCid = 0;
if (ch == NULL)
continue;
switch (how) {
case PPTP_FIND_CHAN_MY_CID:
tryCid = ch->cid;
break;
case PPTP_FIND_CHAN_PEER_CID:
tryCid = ch->peerCid;
break;
case PPTP_FIND_CHAN_PNS_CID:
tryCid = PPTP_CHAN_IS_PNS(ch) ? ch->cid : ch->peerCid;
break;
case PPTP_FIND_CHAN_PAC_CID:
tryCid = !PPTP_CHAN_IS_PNS(ch) ? ch->cid : ch->peerCid;
break;
default:
assert(0);
}
if (cid == tryCid)
return(ch);
}
/* Not found */
CLOG(LOG_INFO, "CID 0x%04x in %s not found", cid, mi->name);
return(NULL);
}
/*************************************************************************
MISC FUNCTIONS
*************************************************************************/
/*
* PptpCtrlNewCtrlState()
*/
static void
PptpCtrlNewCtrlState(PptpCtrl c, int new)
{
struct pptp_engine *const pptp = c->pptp;
assert(c->state != new);
CLOG(LOG_DEBUG, "ctrl state %s --> %s",
gPptpCtrlStates[c->state], gPptpCtrlStates[new]);
if (new == PPTP_CTRL_ST_FREE) {
pptp->ctrl[c->id] = NULL;
ppp_log_close(&c->log);
FREE(PPTP_CTRL_MTYPE, c->channels);
memset(c, 0, sizeof(*c));
FREE(PPTP_CTRL_MTYPE, c);
return;
}
c->state = new;
}
/*
* PptpCtrlNewChanState()
*/
static void
PptpCtrlNewChanState(PptpChan ch, int new)
{
PptpCtrl const c = ch->ctrl;
assert(ch->state != new);
CHLOG(LOG_DEBUG, "chan state %s --> %s",
gPptpChanStates[ch->state], gPptpChanStates[new]);
switch (new) {
case PPTP_CHAN_ST_FREE:
c->channels[ch->id] = NULL;
ppp_log_close(&ch->log);
memset(ch, 0, sizeof(*ch));
FREE(PPTP_CHAN_MTYPE, ch);
return;
case PPTP_CHAN_ST_WAIT_IN_REPLY:
case PPTP_CHAN_ST_WAIT_OUT_REPLY:
case PPTP_CHAN_ST_WAIT_CONNECT:
case PPTP_CHAN_ST_WAIT_DISCONNECT:
case PPTP_CHAN_ST_WAIT_ANSWER:
case PPTP_CHAN_ST_ESTABLISHED:
case PPTP_CHAN_ST_WAIT_CTRL:
break;
}
ch->state = new;
}
/*
* PptpCtrlSwap()
*
* Byteswap message between host order <--> network order. Note:
* this relies on the fact that ntohs == htons and ntohl == htonl.
*/
static void
PptpCtrlSwap(int type, void *buf)
{
PptpField field = gPptpMsgLayout[type];
int off;
for (off = 0; field->name; off += field->length, field++) {
switch (field->length) {
case 4:
{
u_int32_t *const valp = (u_int32_t *) ((u_char *) buf + off);
*valp = ntohl(*valp);
}
break;
case 2:
{
u_int16_t *const valp = (u_int16_t *) ((u_char *) buf + off);
*valp = ntohs(*valp);
}
break;
}
}
}
/*
* PptpCtrlDump()
*
* Debugging display of a control message.
*/
#define DUMP_DING 65
#define DUMP_MAX_DEC 100
#define DUMP_MAX_BUF 200
static void
PptpCtrlDump(int sev, PptpCtrl c, int type, void *msg)
{
struct pptp_engine *const pptp = c->pptp;
PptpField field = gPptpMsgLayout[type];
char line[DUMP_MAX_BUF];
int off;
for (*line = off = 0; field->name; off += field->length, field++) {
u_char *data = (u_char *) msg + off;
char buf[DUMP_MAX_BUF];
const char *fmt;
if (!strncmp(field->name, PPTP_RESV_PREF, strlen(PPTP_RESV_PREF)))
continue;
snprintf(buf, sizeof(buf), " %s=", field->name);
switch (field->length) {
case 4:
fmt = (*((u_int16_t *) data) <= DUMP_MAX_DEC) ? "%d" : "0x%x";
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
fmt, *((u_int32_t *) data));
break;
case 2:
fmt = (*((u_int16_t *) data) <= DUMP_MAX_DEC) ? "%d" : "0x%x";
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
fmt, *((u_int16_t *) data));
break;
case 1:
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
"%d", *((u_int8_t *) data));
break;
default:
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
"\"%s\"", (char *) data);
break;
}
if (*line && strlen(line) + strlen(buf) > DUMP_DING) {
PLOG(sev, " %s", line);
*line = 0;
}
snprintf(line + strlen(line), sizeof(line) - strlen(line), "%s", buf);
}
if (*line)
PLOG(sev, " %s", line);
}
/*
* PptpCtrlDumpBuf()
*
* Debugging display of a some bytes with a prefix line.
*/
static void
PptpCtrlDumpBuf(int sev, PptpCtrl c,
const void *data, size_t len, const char *fmt, ...)
{
struct pptp_engine *const pptp = c->pptp;
va_list args;
va_start(args, fmt);
ppp_log_vput(pptp->log, sev, fmt, args);
va_end(args);
ppp_log_dump(pptp->log, sev, data, len);
}
/*
* PptpCtrlFindField()
*
* Locate a field in a structure, returning length and offset (*offset).
* Die if field was not found.
*/
static int
PptpCtrlFindField(int type, const char *name, u_int *offp)
{
PptpField field = gPptpMsgLayout[type];
u_int off;
for (off = 0; field->name; off += field->length, field++) {
if (!strcmp(field->name, name)) {
*offp = off;
return(field->length);
}
}
assert(0);
return(0);
}
/*
* PptpCtrlInitCinfo()
*/
static void
PptpCtrlInitCinfo(PptpChan ch, PptpCtrlInfo ci)
{
PptpCtrl const c = ch->ctrl;
memset(ci, 0, sizeof(*ci));
ci->cookie = ch;
ci->peer_addr = c->peer_addr;
ci->peer_port = c->peer_port;
ci->close = (void (*)(void *, int, int, int))PptpCtrlCloseChan;
ci->answer = PptpCtrlDialResult;
}
/*
* PptpCtrlExtendArray()
*
* Extend an array
*/
static int
PptpCtrlExtendArray(const char *mtype, void *array, int esize, int *alenp)
{
void **const arrayp = (void **)array;
void *newa;
if ((newa = REALLOC(mtype, *arrayp, (*alenp + 1) * esize)) == NULL)
return(-1);
*arrayp = newa;
memset((u_char *)*arrayp + (*alenp * esize), 0, esize);
(*alenp)++;
return(0);
}
/*
* PptpCtrlGetSocket()
*
* Get and (if port != 0) bind a TCP socket.
*/
static int
PptpCtrlGetSocket(struct in_addr ip, u_int16_t port, char *ebuf, size_t elen)
{
static const int one = 1;
struct sockaddr_in addr;
int sock;
int addr_size = sizeof(addr);
/* Get socket */
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
snprintf(ebuf, elen, "socket: %s", strerror(errno));
return(-1);
}
(void)fcntl(sock, F_SETFD, 1);
/* Set reusable address and port */
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
snprintf(ebuf, elen, "setsockopt: %s", strerror(errno));
(void)close(sock);
return(-1);
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))) {
snprintf(ebuf, elen, "setsockopt: %s", strerror(errno));
(void)close(sock);
return(-1);
}
/* Bind socket */
if (port != 0) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr = ip;
addr.sin_port = htons(port);
if (bind(sock, (struct sockaddr *) &addr, addr_size) == -1) {
snprintf(ebuf, elen, "bind: %s", strerror(errno));
(void)close(sock);
return(-1);
}
}
/* Done */
return(sock);
}
/*************************************************************************
CONTROL MESSAGE FUNCTIONS
*************************************************************************/
/*
* PptpStartCtrlConnRequest()
*/
static void
PptpStartCtrlConnRequest(PptpCtrl c, struct pptpStartCtrlConnRequest *req)
{
struct pptp_engine *const pptp = c->pptp;
struct pptpStartCtrlConnReply reply;
int k;
/* Check for a collision */
if (!pptp->nocd) {
for (k = 0; k < pptp->nctrl; k++) {
PptpCtrl const c2 = pptp->ctrl[k];
int iwin;
if (c2 == NULL
|| c2 == c
|| c2->peer_addr.s_addr != c->peer_addr.s_addr)
continue;
iwin = (u_int32_t) ntohl(c->self_addr.s_addr)
> (u_int32_t) ntohl(c->peer_addr.s_addr);
CLOG(LOG_INFO, "collision with peer! %s", iwin ? "i win" : "peer wins");
if (iwin)
goto abort; /* Kill this peer-initiated connection */
else
PptpCtrlKillCtrl(c2); /* Kill the connection that I initiated */
}
}
/* Initialize reply */
memset(&reply, 0, sizeof(reply));
reply.vers = PPTP_PROTO_VERS;
reply.frameCap = PPTP_FRAMECAP_SYNC;
reply.bearCap = PPTP_BEARCAP_ANY;
reply.firmware = PPTP_FIRMWARE_REV;
reply.result = PPTP_SCCR_RESL_OK;
gethostname(reply.host, sizeof(reply.host));
strncpy(reply.vendor, pptp->vendor, sizeof(reply.vendor));
/* Check protocol version */
if (req->vers != PPTP_PROTO_VERS) {
CLOG(LOG_ERR, "incompatible PPTP protocol version 0x%04x", req->vers);
reply.result = PPTP_SCCR_RESL_VERS;
PptpCtrlWriteMsg(c, PPTP_StartCtrlConnReply, &reply);
abort:
PptpCtrlKillCtrl(c);
return;
}
/* OK */
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_ESTABLISHED);
PptpCtrlWriteMsg(c, PPTP_StartCtrlConnReply, &reply);
}
/*
* PptpStartCtrlConnReply()
*/
static void
PptpStartCtrlConnReply(PptpCtrl c, struct pptpStartCtrlConnReply *rep)
{
/* Is peer happy? */
if (rep->result != PPTP_SCCR_RESL_OK) {
CLOG(LOG_NOTICE, "my %s failed, result=%s err=%s",
gPptpMsgInfo[PPTP_StartCtrlConnRequest].name,
PPTP_SCCR_RESL_CODE(rep->result), PPTP_ERROR_CODE(rep->err));
PptpCtrlKillCtrl(c);
return;
}
/* Check peer's protocol version */
if (rep->vers != PPTP_PROTO_VERS) {
struct pptpStopCtrlConnRequest req;
CLOG(LOG_ERR, "incompatible PPTP protocol version 0x%04x", rep->vers);
memset(&req, 0, sizeof(req));
req.reason = PPTP_SCCR_REAS_PROTO;
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_WAIT_STOP_REPLY);
PptpCtrlWriteMsg(c, PPTP_StopCtrlConnRequest, &req);
return;
}
/* OK */
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_ESTABLISHED);
PptpCtrlCheckConn(c);
}
/*
* PptpStopCtrlConnRequest()
*/
static void
PptpStopCtrlConnRequest(PptpCtrl c, struct pptpStopCtrlConnRequest *req)
{
struct pptpStopCtrlConnReply rep;
CLOG(LOG_INFO, "rec'd %s: reason=%s",
gPptpMsgInfo[PPTP_StopCtrlConnRequest].name,
PPTP_SCCR_REAS_CODE(req->reason));
memset(&rep, 0, sizeof(rep));
rep.result = PPTP_SCCR_RESL_OK;
PptpCtrlNewCtrlState(c, PPTP_CTRL_ST_IDLE);
PptpCtrlWriteMsg(c, PPTP_StopCtrlConnReply, &rep);
PptpCtrlKillCtrl(c);
}
/*
* PptpStopCtrlConnReply()
*/
static void
PptpStopCtrlConnReply(PptpCtrl c, struct pptpStopCtrlConnReply *rep)
{
PptpCtrlKillCtrl(c);
}
/*
* PptpEchoRequest()
*/
static void
PptpEchoRequest(PptpCtrl c, struct pptpEchoRequest *req)
{
struct pptpEchoReply reply;
memset(&reply, 0, sizeof(reply));
reply.id = req->id;
reply.result = PPTP_ECHO_RESL_OK;
PptpCtrlWriteMsg(c, PPTP_EchoReply, &reply);
}
/*
* PptpEchoReply()
*/
static void
PptpEchoReply(PptpCtrl c, struct pptpEchoReply *rep)
{
if (rep->result != PPTP_ECHO_RESL_OK) {
CLOG(LOG_NOTICE, "echo reply failed: res=%s err=%s",
PPTP_ECHO_RESL_CODE(rep->result), PPTP_ERROR_CODE(rep->err));
PptpCtrlKillCtrl(c);
} else if (rep->id != c->echoId) {
CLOG(LOG_NOTICE, "bogus echo reply: %u != %u", rep->id, c->echoId);
PptpCtrlKillCtrl(c);
}
}
/*
* PptpOutCallRequest()
*/
static void
PptpOutCallRequest(PptpCtrl c, struct pptpOutCallRequest *req)
{
struct pptp_engine *const pptp = c->pptp;
struct pptpOutCallReply reply;
struct pptpctrlinfo cinfo;
struct pptplinkinfo linfo;
PptpChan ch = NULL;
char calledNum[PPTP_PHONE_LEN + 1];
char subAddress[PPTP_SUBADDR_LEN + 1];
/* Does link allow this? */
if (pptp->get_out_link == NULL)
goto denied;
/* Copy out fields */
strncpy(calledNum, req->phone, sizeof(calledNum) - 1);
calledNum[sizeof(calledNum) - 1] = 0;
strncpy(subAddress, req->subaddr, sizeof(subAddress) - 1);
subAddress[sizeof(subAddress) - 1] = 0;
/* Get a data channel */
if ((ch = PptpCtrlGetChan(c, PPTP_CHAN_ST_WAIT_ANSWER, FALSE, FALSE,
req->bearType, req->frameType, req->minBps, req->maxBps,
PPTP_STR_INTERNAL_CALLING, calledNum, subAddress)) == NULL) {
CLOG(LOG_ERR, "can't get channel for %s call", "outgoing");
goto chFail;
}
/* Fill in details */
ch->serno = req->serno;
ch->peerCid = req->cid;
ch->peerPpd = req->ppd;
ch->recvWin = req->recvWin;
/* Ask link layer about making the outgoing call */
PptpCtrlInitCinfo(ch, &cinfo);
linfo = (*pptp->get_out_link)(pptp->arg,
cinfo, c->peer_addr, c->peer_port, req->bearType,
req->frameType, req->minBps, req->maxBps, calledNum, subAddress);
if (linfo.cookie == NULL || linfo.cancel == NULL || linfo.result == NULL)
goto denied;
/* Link layer says it's OK; wait for link layer to report back later */
ch->linfo = linfo;
return;
/* Failed */
denied:
CLOG(LOG_NOTICE, "peer's %s call request was denied", "outgoing");
if (ch)
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_FREE);
chFail:
memset(&reply, 0, sizeof(reply));
reply.peerCid = req->cid;
reply.result = PPTP_OCR_RESL_ADMIN;
PptpCtrlWriteMsg(c, PPTP_OutCallReply, &reply);
}
/*
* PptpOutCallReply()
*/
static void
PptpOutCallReply(PptpChan ch, struct pptpOutCallReply *reply)
{
/* Did call go through? */
if (reply->result != PPTP_OCR_RESL_OK) {
char errmsg[100];
snprintf(errmsg, sizeof(errmsg),
"outgoing call failed: res=%s err=%s",
PPTP_OCR_RESL_CODE(reply->result), PPTP_ERROR_CODE(reply->err));
CHLOG(LOG_NOTICE, "%s", errmsg);
(*ch->linfo.result)(ch->linfo.cookie, errmsg);
PptpCtrlKillChan(ch, "remote outgoing call failed");
return;
}
/* Call succeeded */
ch->peerPpd = reply->ppd;
ch->recvWin = reply->recvWin;
ch->peerCid = reply->cid;
CHLOG(LOG_INFO, "outgoing call connected at %d bps", reply->speed);
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_ESTABLISHED);
(*ch->linfo.result)(ch->linfo.cookie, NULL);
}
/*
* PptpInCallRequest()
*/
static void
PptpInCallRequest(PptpCtrl c, struct pptpInCallRequest *req)
{
struct pptp_engine *const pptp = c->pptp;
struct pptpInCallReply reply;
struct pptpctrlinfo cinfo;
struct pptplinkinfo linfo;
PptpChan ch;
char callingNum[PPTP_PHONE_LEN + 1];
char calledNum[PPTP_PHONE_LEN + 1];
char subAddress[PPTP_SUBADDR_LEN + 1];
/* Copy out fields */
if (req->dialedLen > PPTP_PHONE_LEN)
req->dialedLen = PPTP_PHONE_LEN;
if (req->dialingLen > PPTP_PHONE_LEN)
req->dialingLen = PPTP_PHONE_LEN;
strncpy(callingNum, req->dialing, sizeof(callingNum) - 1);
callingNum[req->dialingLen] = 0;
strncpy(calledNum, req->dialed, sizeof(calledNum) - 1);
calledNum[req->dialedLen] = 0;
strncpy(subAddress, req->subaddr, sizeof(subAddress) - 1);
subAddress[sizeof(subAddress) - 1] = 0;
CLOG(LOG_INFO, "peer incoming call to \"%s\" from \"%s\"",
calledNum, callingNum);
/* Initialize reply */
memset(&reply, 0, sizeof(reply));
reply.peerCid = req->cid;
reply.recvWin = PPTP_RECV_WIN; /* XXX */
reply.ppd = PPTP_PPD; /* XXX */
/* Get a data channel */
if ((ch = PptpCtrlGetChan(c, PPTP_CHAN_ST_WAIT_CONNECT, FALSE, TRUE,
req->bearType, 0, 0, INT_MAX,
callingNum, calledNum, subAddress)) == NULL) {
CLOG(LOG_ERR, "can't get channel for %s call", "incoming");
reply.result = PPTP_ICR_RESL_ERR;
reply.err = PPTP_ERROR_NO_RESOURCE;
goto done;
}
reply.cid = ch->cid;
/* Fill in details */
ch->serno = req->serno;
ch->peerCid = req->cid;
ch->bearType = req->bearType;
/* Ask link layer about accepting the incoming call */
PptpCtrlInitCinfo(ch, &cinfo);
linfo.cookie = NULL;
if (pptp->get_in_link != NULL)
linfo = (*pptp->get_in_link)(pptp->arg, cinfo, c->peer_addr, c->peer_port,
req->bearType, callingNum, calledNum, subAddress);
if (linfo.cookie == NULL) {
CLOG(LOG_NOTICE, "peer's %s call request was denied", "incoming");
reply.result = PPTP_ICR_RESL_NAK;
goto done;
}
/* Link layer says it's OK */
CHLOG(LOG_INFO, "accepting incoming call to \"%s\" from \"%s\"",
calledNum, callingNum);
reply.result = PPTP_ICR_RESL_OK;
ch->linfo = linfo;
strncpy(ch->callingNum, req->dialing, sizeof(ch->callingNum));
strncpy(ch->calledNum, req->dialed, sizeof(ch->calledNum));
strncpy(ch->subAddress, req->subaddr, sizeof(ch->subAddress));
/* Return result */
done:
PptpCtrlWriteMsg(c, PPTP_InCallReply, &reply);
if (reply.result != PPTP_ICR_RESL_OK)
PptpCtrlKillChan(ch, "peer incoming call failed");
}
/*
* PptpInCallReply()
*/
static void
PptpInCallReply(PptpChan ch, struct pptpInCallReply *reply)
{
PptpCtrl const c = ch->ctrl;
struct pptpInCallConn con;
/* Did call go through? */
if (reply->result != PPTP_ICR_RESL_OK) {
char errmsg[100];
snprintf(errmsg, sizeof(errmsg),
"peer denied incoming call: res=%s err=%s",
PPTP_ICR_RESL_CODE(reply->result), PPTP_ERROR_CODE(reply->err));
CHLOG(LOG_NOTICE, "%s", errmsg);
(*ch->linfo.result)(ch->linfo.cookie, errmsg);
PptpCtrlKillChan(ch, "peer denied incoming call");
return;
}
/* Call succeeded */
CHLOG(LOG_INFO, "incoming call accepted by peer");
ch->peerCid = reply->cid;
ch->peerPpd = reply->ppd;
ch->recvWin = reply->recvWin;
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_ESTABLISHED);
(*ch->linfo.result)(ch->linfo.cookie, NULL);
/* Send back connected message */
memset(&con, 0, sizeof(con));
con.peerCid = reply->cid;
con.speed = 64000; /* XXX */
con.recvWin = PPTP_RECV_WIN; /* XXX */
con.ppd = PPTP_PPD; /* XXX */
con.frameType = PPTP_FRAMECAP_SYNC;
PptpCtrlWriteMsg(c, PPTP_InCallConn, &con);
}
/*
* PptpInCallConn()
*/
static void
PptpInCallConn(PptpChan ch, struct pptpInCallConn *con)
{
CHLOG(LOG_INFO, "peer incoming call connected at %d bps", con->speed);
ch->peerPpd = con->ppd;
ch->recvWin = con->recvWin;
ch->frameType = con->frameType;
(*ch->linfo.result)(ch->linfo.cookie, NULL);
PptpCtrlNewChanState(ch, PPTP_CHAN_ST_ESTABLISHED);
}
/*
* PptpCallClearRequest()
*/
static void
PptpCallClearRequest(PptpChan ch, struct pptpCallClearRequest *req)
{
struct pptpCallDiscNotify notify;
PptpCtrl const c = ch->ctrl;
if (PPTP_CHAN_IS_PNS(ch)) {
CHLOG(LOG_NOTICE, "rec'd %s, but we are PNS for this call",
gPptpMsgInfo[PPTP_CallClearRequest].name);
PptpCtrlKillCtrl(c);
return;
}
CHLOG(LOG_INFO, "call cleared by peer");
memset(¬ify, 0, sizeof(notify));
notify.cid = ch->cid; /* we are the PAC, use our CID */
notify.result = PPTP_CDN_RESL_REQ;
/* XXX stats? */
PptpCtrlWriteMsg(c, PPTP_CallDiscNotify, ¬ify);
PptpCtrlKillChan(ch, "cleared by peer");
}
/*
* PptpCallDiscNotify()
*/
static void
PptpCallDiscNotify(PptpChan ch, struct pptpCallDiscNotify *notify)
{
CHLOG(LOG_INFO, "peer call disconnected res=%s err=%s",
PPTP_CDN_RESL_CODE(notify->result), PPTP_ERROR_CODE(notify->err));
PptpCtrlKillChan(ch, "disconnected by peer");
}
/*
* PptpWanErrorNotify()
*/
static void
PptpWanErrorNotify(PptpChan ch, struct pptpWanErrorNotify *notif)
{
CHLOG(LOG_DEBUG, "ignoring %s", gPptpMsgInfo[PPTP_WanErrorNotify].name);
}
/*
* PptpSetLinkInfo()
*/
static void
PptpSetLinkInfo(PptpChan ch, struct pptpSetLinkInfo *info)
{
if (ch->linfo.setLinkInfo)
(*ch->linfo.setLinkInfo)(ch->linfo.cookie, info->sendAccm, info->recvAccm);
else {
CHLOG(LOG_DEBUG, "ignoring %s", gPptpMsgInfo[PPTP_SetLinkInfo].name);
}
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>