summaryrefslogtreecommitdiff
path: root/pptpd-1.3.3/ctrlpacket.c
diff options
context:
space:
mode:
Diffstat (limited to 'pptpd-1.3.3/ctrlpacket.c')
-rw-r--r--pptpd-1.3.3/ctrlpacket.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/pptpd-1.3.3/ctrlpacket.c b/pptpd-1.3.3/ctrlpacket.c
new file mode 100644
index 0000000..b463941
--- /dev/null
+++ b/pptpd-1.3.3/ctrlpacket.c
@@ -0,0 +1,719 @@
+/*
+ * ctrlpacket.c
+ *
+ * PPTP Control Message packet reading, formatting and writing.
+ *
+ * $Id: ctrlpacket.c,v 1.6 2005/08/03 09:10:59 quozl Exp $
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SYSLOG_H
+#include <syslog.h>
+#else
+#include "our_syslog.h"
+#endif
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "pptpdefs.h"
+#include "pptpctrl.h"
+#include "ctrlpacket.h"
+
+#include <net/if.h>
+#include <net/ethernet.h>
+#include "if_pppox.h"
+
+#ifndef HAVE_STRERROR
+#include "compat.h"
+#endif
+
+/* Local function prototypes */
+static ssize_t read_pptp_header(int clientFd, unsigned char *packet, int *ctrl_message_type);
+static void deal_start_ctrl_conn(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size);
+static void deal_stop_ctrl_conn(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size);
+static void deal_out_call(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size);
+static void deal_echo(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size);
+static void deal_call_clr(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size);
+static void deal_set_link_info(unsigned char *packet);
+static u_int16_t getcall();
+static u_int16_t freecall();
+
+#if notyet
+static int make_out_call_rqst(unsigned char *rply_packet, ssize_t * rply_size);
+#endif
+
+/*
+ * read_pptp_packet
+ *
+ * Sees if a packet can be read and if so what type of packet it is. The
+ * method then calls the appropriate function to examine the details of the
+ * packet and form a suitable reply packet.
+ *
+ * args: clientFd (IN) - Client socket to read from.
+ * packet (OUT) - Packet read from the client.
+ * rply_packet (OUT) - Reply packet for the client.
+ * rply_size (OUT) - Size of the reply packet.
+ *
+ * retn: PPTP control message type of the packet on success.
+ * -1 on retryable error.
+ * 0 on error to abort on.
+ */
+int read_pptp_packet(int clientFd, unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size)
+{
+
+ size_t bytes_read;
+ int pptp_ctrl_type = 0; /* Control Message Type */
+
+ /* read a packet and parse header */
+ if ((bytes_read = read_pptp_header(clientFd, packet, &pptp_ctrl_type)) <= 0) {
+ /* error reading packet */
+ syslog(LOG_ERR, "CTRL: couldn't read packet header (%s)", bytes_read ? "retry" : "exit");
+ return bytes_read;
+ }
+
+ /* launch appropriate method to form suitable reply to the packet */
+ switch (pptp_ctrl_type) {
+ case START_CTRL_CONN_RQST: /* Start Control Connection Request */
+ deal_start_ctrl_conn(packet, rply_packet, rply_size);
+ break;
+
+ case STOP_CTRL_CONN_RQST:
+ deal_stop_ctrl_conn(packet, rply_packet, rply_size);
+ break;
+
+ case OUT_CALL_RQST: /* Outgoing Call Request */
+ deal_out_call(packet, rply_packet, rply_size);
+ break;
+
+ case ECHO_RQST: /* Echo Request */
+ deal_echo(packet, rply_packet, rply_size);
+ break;
+
+ case CALL_CLR_RQST: /* Call Clear Request (Disconnect Request) */
+ deal_call_clr(packet, rply_packet, rply_size);
+ break;
+
+ case SET_LINK_INFO: /* Set Link Info */
+ /* no reply packet but process it */
+ deal_set_link_info(packet);
+ break;
+
+ case ECHO_RPLY: /* Echo Reply */
+ case STOP_CTRL_CONN_RPLY: /* Stop Control Connection Reply */
+ case CALL_DISCONN_NTFY: /* Call Disconnect Notify */
+ /* no reply packet */
+ break;
+
+ default:
+ syslog(LOG_ERR, "CTRL: PPTP Control Message type %d not supported.", pptp_ctrl_type);
+ pptp_ctrl_type = -1;
+ }
+
+ return pptp_ctrl_type;
+}
+
+
+/*
+ * send_pptp_packet
+ *
+ * Sends a PPTP packet to a file descriptor.
+ *
+ * args: clientFd (IN) - file descriptor to write the packet to.
+ * packet (IN) - the packet data to write.
+ * packet_size (IN) - the packet size.
+ *
+ * retn: Number of bytes written on success.
+ * -1 on write failure.
+ */
+size_t send_pptp_packet(int clientFd, unsigned char *packet, size_t packet_size)
+{
+
+ size_t bytes_written;
+
+ if ((bytes_written = write(clientFd, packet, packet_size)) == -1) {
+ /* write failed */
+ syslog(LOG_ERR, "CTRL: Couldn't write packet to client.");
+ return -1;
+
+ } else {
+ /* debugging */
+ if (pptpctrl_debug) {
+ syslog(LOG_DEBUG, "CTRL: I wrote %i bytes to the client.", (int)packet_size);
+ syslog(LOG_DEBUG, "CTRL: Sent packet to client");
+ }
+ return bytes_written;
+ }
+}
+
+/*
+ * ignoreErrno
+ *
+ * Check if an errno represents a read error which should be ignored, and
+ * put back to be select()ed on again later.
+ *
+ * Very similar to the function in Squid by Duane Wessels (under GPL).
+ *
+ * args: an errno value
+ *
+ * retn: 1 if the error is unimportant
+ * 0 if the error is important
+ */
+static int ignoreErrno(int ierrno) {
+ switch (ierrno) {
+ case EAGAIN: /* nothing to read */
+ case EINTR: /* signal received */
+#ifdef ERESTART
+#if ERESTART != EINTR
+ case ERESTART: /* signal received, should restart syscall */
+#endif
+#endif
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK: /* shouldn't get this one but anyway, just in case */
+#endif
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * read_pptp_header
+ *
+ * Reads a packet from a file descriptor and determines whether it is a
+ * valid PPTP Control Message. If a valid PPTP Control Message is detected
+ * it extracts the Control Message type from the packet header.
+ *
+ * args: clientFd (IN) - Clients file descriptor.
+ * packet (OUT) - Packet we read from the client.
+ * pptp_ctrl_type (OUT) - PPTP Control Message type of the packet.
+ *
+ * retn: Number of bytes read on success.
+ * -1 on retryable error.
+ * 0 on error to exit on.
+ */
+ssize_t read_pptp_header(int clientFd, unsigned char *packet, int *pptp_ctrl_type)
+{
+
+ ssize_t bytes_ttl, bytes_this; /* quantities read (total and this read) */
+ u_int16_t length; /* length of this packet */
+ struct pptp_header *header; /* the received header */
+
+ static char *buffer = NULL; /* buffer between calls */
+ static int buffered = 0; /* size of buffer */
+
+ *pptp_ctrl_type = 0; /* initialise return arg */
+
+ /* read any previously buffered data */
+ if (buffered) {
+ memcpy(packet, buffer, buffered);
+ free(buffer);
+ buffer = NULL;
+ bytes_ttl = buffered;
+ buffered = 0;
+ if (pptpctrl_debug)
+ syslog(LOG_DEBUG, "CTRL: Read in previous incomplete ctrl packet");
+ } else
+ bytes_ttl = 0;
+
+ /* try and get the length in */
+ if (bytes_ttl < 2) {
+ bytes_this = read(clientFd, packet + bytes_ttl, 2 - bytes_ttl);
+ switch (bytes_this) {
+ case -1:
+ if (ignoreErrno(errno)) {
+ /* re-tryable error, re-buffer and return */
+ if (bytes_ttl) {
+ buffered = bytes_ttl;
+ buffer = malloc(bytes_ttl);
+ if (!buffer)
+ return(0);
+ memcpy(buffer, packet, bytes_ttl);
+ }
+ syslog(LOG_ERR, "CTRL: Error reading ctrl packet length (bytes_ttl=%i): %s", (int)bytes_ttl, strerror(errno));
+ return -1;
+ }
+ /* FALLTHRU */
+ case 0:
+ syslog(LOG_ERR, "CTRL: EOF or bad error reading ctrl packet length.");
+ return 0;
+ default:
+ bytes_ttl += bytes_this;
+ /* Not enough data to proceed */
+ if (bytes_ttl == 1) {
+ buffered = bytes_ttl;
+ buffer = malloc(bytes_ttl);
+ if (!buffer)
+ return(0);
+ memcpy(buffer, packet, bytes_ttl);
+ if (pptpctrl_debug)
+ syslog(LOG_DEBUG, "CTRL: Incomplete ctrl packet length, retry later");
+ return -1;
+ }
+ }
+ }
+ /* OK, we have (at least) the first 2 bytes, and there is data waiting
+ *
+ * length includes the header, so a length less than 2 is someone
+ * trying to hack into us or a badly corrupted packet.
+ * Why not require length to be at least 10? Since we later cast
+ * packet to struct pptp_header and use at least the 10 first bytes..
+ * Thanks to Timo Sirainen for mentioning this.
+ */
+ length = htons(*(u_int16_t *) packet);
+ if (length <= 10 || length > PPTP_MAX_CTRL_PCKT_SIZE) {
+ syslog(LOG_ERR, "CTRL: 11 < Control packet (length=%d) < "
+ "PPTP_MAX_CTRL_PCKT_SIZE (%d)",
+ length, PPTP_MAX_CTRL_PCKT_SIZE);
+ /* we loose sync (unless we malloc something big, which isn't a good
+ * idea - potential DoS) so we must close connection (draft states that
+ * if you loose sync you must close the control connection immediately)
+ */
+ return 0;
+ }
+ /* Now read the actual control packet */
+ bytes_this = read(clientFd, packet + bytes_ttl, length - bytes_ttl);
+ switch (bytes_this) {
+ case -1:
+ if(ignoreErrno(errno)) {
+ /* re-tryable error, re-buffer and return */
+ if (bytes_ttl) {
+ buffered = bytes_ttl;
+ buffer = malloc(bytes_ttl);
+ if (!buffer)
+ return(0);
+ memcpy(buffer, packet, bytes_ttl);
+ }
+ syslog(LOG_ERR, "CTRL: Error reading ctrl packet (bytes_ttl=%d,length=%d): %s", (int)bytes_ttl, (int)length, strerror(errno));
+ return -1;
+ }
+ /* FALLTHRU */
+ case 0:
+ syslog(LOG_ERR, "CTRL: EOF or bad error reading ctrl packet.");
+ return 0;
+ default:
+ bytes_ttl += bytes_this;
+ /* not enough data to proceed */
+ if (bytes_ttl != length) {
+ buffered = bytes_ttl;
+ buffer = malloc(bytes_ttl);
+ if (!buffer)
+ return(0);
+ memcpy(buffer, packet, bytes_ttl);
+ if (pptpctrl_debug)
+ syslog(LOG_DEBUG, "CTRL: Incomplete ctrl packet, retry later");
+ return -1;
+ }
+ }
+
+ /* We got one :-) */
+
+ /* Cast the packet into the PPTP Control Message format */
+ header = (struct pptp_header *) packet;
+
+ /* Packet sanity check on magic cookie */
+ if (ntohl(header->magic) != PPTP_MAGIC_COOKIE) {
+ /* Oops! Not a valid Control Message */
+ syslog(LOG_ERR, "CTRL: Bad magic cookie - lost syncronization, closing control connection.");
+ /* draft states loss of syncronization must result in
+ * immediate closing of the control connection
+ */
+ return 0;
+ }
+ /* Woohoo! Looks like we got a valid PPTP packet */
+ *pptp_ctrl_type = (int) (ntohs(header->ctrl_type));
+ if (pptpctrl_debug)
+ syslog(LOG_DEBUG, "CTRL: Received PPTP Control Message (type: %d)", *pptp_ctrl_type);
+ return bytes_ttl;
+}
+
+/* Macros to use in making response packets */
+
+#define MAKE_CTRL_HEADER(where, what) \
+ where.header.length = htons(sizeof(where)); \
+ where.header.pptp_type = htons(PPTP_CTRL_MESSAGE); \
+ where.header.magic = htonl(PPTP_MAGIC_COOKIE); \
+ where.header.ctrl_type = htons(what); \
+ where.header.reserved0 = htons(RESERVED)
+
+#define COPY_CTRL_PACKET(from, to, size) \
+ memcpy(to, &from, ((*size) = sizeof(from)))
+
+#define DEBUG_PACKET(what) \
+ if(pptpctrl_debug) \
+ syslog(LOG_DEBUG, "CTRL: Made a " what " packet")
+
+/*
+ * deal_start_ctrl_conn
+ *
+ * This method 'deals' with a START-CONTROL-CONNECTION-REQUEST. After
+ * stripping down the connection request a suitable reply is formed and
+ * stored in 'rply_packet' ready for sending.
+ *
+ * args: packet (IN) - the packet that we have to deal with (should be a
+ * START-CONTROL-CONNECTION-REQUEST packet)
+ * rply_packet (OUT) - suitable reply to the 'packet' we got.
+ * rply_size (OUT) - size of the reply packet
+ */
+void deal_start_ctrl_conn(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size)
+{
+ struct pptp_start_ctrl_conn_rqst *start_ctrl_conn_rqst;
+ struct pptp_start_ctrl_conn_rply start_ctrl_conn_rply;
+
+ start_ctrl_conn_rqst = (struct pptp_start_ctrl_conn_rqst *) packet;
+
+ MAKE_CTRL_HEADER(start_ctrl_conn_rply, START_CTRL_CONN_RPLY);
+ start_ctrl_conn_rply.version = htons(PPTP_VERSION);
+ start_ctrl_conn_rply.result_code = CONNECTED;
+ start_ctrl_conn_rply.error_code = NO_ERROR;
+ start_ctrl_conn_rply.framing_cap = htons(OUR_FRAMING);
+ start_ctrl_conn_rply.bearer_cap = htons(OUR_BEARER);
+ start_ctrl_conn_rply.max_channels = htons(MAX_CHANNELS);
+ start_ctrl_conn_rply.firmware_rev = htons(PPTP_FIRMWARE_VERSION);
+ bzero(start_ctrl_conn_rply.hostname, MAX_HOSTNAME_SIZE);
+ strncpy((char *)start_ctrl_conn_rply.hostname, PPTP_HOSTNAME, MAX_HOSTNAME_SIZE);
+ bzero(start_ctrl_conn_rply.vendor, MAX_VENDOR_SIZE);
+ strncpy((char *)start_ctrl_conn_rply.vendor, PPTP_VENDOR, MAX_VENDOR_SIZE);
+ COPY_CTRL_PACKET(start_ctrl_conn_rply, rply_packet, rply_size);
+ DEBUG_PACKET("START CTRL CONN RPLY");
+}
+
+/*
+ * deal_stop_ctrl_conn
+ *
+ * This method response to a STOP-CONTROL-CONNECTION-REQUEST with a
+ * STOP-CONTROL-CONNECTION-REPLY.
+ */
+void deal_stop_ctrl_conn(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size)
+{
+ struct pptp_stop_ctrl_conn_rply stop_ctrl_conn_rply;
+
+ MAKE_CTRL_HEADER(stop_ctrl_conn_rply, STOP_CTRL_CONN_RPLY);
+ stop_ctrl_conn_rply.result_code = DISCONNECTED;
+ stop_ctrl_conn_rply.error_code = NO_ERROR;
+ stop_ctrl_conn_rply.reserved1 = htons(RESERVED);
+ COPY_CTRL_PACKET(stop_ctrl_conn_rply, rply_packet, rply_size);
+ DEBUG_PACKET("STOP CTRL CONN RPLY");
+}
+
+/*
+ * deal_out_call
+ *
+ * This method 'deals' with a OUT-GOING-CALL-REQUEST. After
+ * stripping down the request a suitable reply is formed and stored in
+ * 'rply_packet' ready for sending.
+ *
+ * args: packet (IN) - the packet that we have to deal with (should be a
+ * OUT-GOING-CALL-REQUEST packet)
+ * rply_packet (OUT) - suitable reply to the 'packet' we got.
+ * rply_size (OUT) - size of the reply packet
+ *
+ */
+void deal_out_call(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size)
+{
+ u_int16_t pac_call_id;
+ struct pptp_out_call_rqst *out_call_rqst;
+ struct pptp_out_call_rply out_call_rply;
+
+ out_call_rqst = (struct pptp_out_call_rqst *) packet;
+
+ if ((pac_call_id = getcall()) == htons(-1)) {
+ /* XXX should reject call */
+ syslog(LOG_ERR, "CTRL: No free Call IDs!");
+ pac_call_id = 0;
+ }
+ MAKE_CTRL_HEADER(out_call_rply, OUT_CALL_RPLY);
+ /* call_id is used for ctrl, call_id_peer is used for GRE
+ * call_id_peer is what we were sent by the other end in ctrl initilization
+ */
+ out_call_rply.call_id = pac_call_id;
+ out_call_rply.call_id_peer = out_call_rqst->call_id;
+ out_call_rply.result_code = CONNECTED;
+ out_call_rply.error_code = NO_ERROR;
+ out_call_rply.cause_code = NO_ERROR;
+ /* maybe limit to pppd speed? but pppd doesn't accept 10Mbps as a speed and yet
+ * still performs at over 115200, eg, 60kbyte/sec and higher observed.
+ */
+ out_call_rply.speed = out_call_rqst->max_bps;
+ /* lets match their window size for now... was htons(PCKT_RECV_WINDOW_SIZE)
+ */
+ out_call_rply.pckt_recv_size = out_call_rqst->pckt_recv_size;
+ window=htons(out_call_rply.pckt_recv_size);
+ if(pptpctrl_debug)
+ syslog(LOG_DEBUG, "CTRL: Set parameters to %d maxbps, %d window size",
+ ntohl(out_call_rply.speed), ntohs(out_call_rply.pckt_recv_size));
+ out_call_rply.pckt_delay = htons(PCKT_PROCESS_DELAY);
+ out_call_rply.channel_id = htonl(CHANNEL_ID);
+ COPY_CTRL_PACKET(out_call_rply, rply_packet, rply_size);
+ DEBUG_PACKET("OUT CALL RPLY");
+}
+
+
+/*
+ * deal_echo
+ *
+ * This method 'deals' with a ECHO-REQUEST. After stripping down the
+ * connection request a suitable reply is formed and stored in
+ * 'rply_packet' ready for sending.
+ *
+ * args: packet (IN) - the packet that we have to deal with (should be a
+ * ECHO-REQUEST packet)
+ * rply_packet (OUT) - suitable reply to the 'packet' we got.
+ * rply_size (OUT) - size of the reply packet
+ *
+ */
+void deal_echo(unsigned char *packet, unsigned char *rply_packet, ssize_t * rply_size)
+{
+ struct pptp_echo_rqst *echo_rqst;
+ struct pptp_echo_rply echo_rply;
+
+ echo_rqst = (struct pptp_echo_rqst *) packet;
+
+ MAKE_CTRL_HEADER(echo_rply, ECHO_RPLY);
+ echo_rply.identifier = echo_rqst->identifier;
+ echo_rply.result_code = CONNECTED;
+ echo_rply.error_code = NO_ERROR;
+ echo_rply.reserved1 = htons(RESERVED);
+ COPY_CTRL_PACKET(echo_rply, rply_packet, rply_size);
+ DEBUG_PACKET("ECHO RPLY");
+}
+
+/*
+ * deal_call_clr
+ *
+ * This method 'deals' with a CALL-CLEAR-REQUEST. After stripping down the
+ * connection request a suitable reply is formed and stored in
+ * 'rply_packet' ready for sending.
+ *
+ * args: packet (IN) - the packet that we have to deal with (should be a
+ * CALL-CLEAR-REQUEST packet)
+ * rply_packet (OUT) - suitable reply to the 'packet' we got.
+ * rply_size (OUT) - size of the reply packet
+ *
+ */
+void deal_call_clr(unsigned char *packet, unsigned char *rply_packet, ssize_t *rply_size)
+{
+ struct pptp_call_disconn_ntfy call_disconn_ntfy;
+ u_int16_t pac_call_id;
+
+ /* Form a reply
+ * The reply packet is a CALL-DISCONECT-NOTIFY
+ * In single call mode we don't care what peer's call ID is, so don't even bother looking
+ */
+ if ((pac_call_id = freecall()) == htons(-1)) {
+ /* XXX should return an error */
+ syslog(LOG_ERR, "CTRL: Could not free Call ID [call clear]!");
+ }
+ MAKE_CTRL_HEADER(call_disconn_ntfy, CALL_DISCONN_NTFY);
+ call_disconn_ntfy.call_id = pac_call_id;
+ call_disconn_ntfy.result_code = CALL_CLEAR_REQUEST; /* disconnected by call_clr_rqst */
+ call_disconn_ntfy.error_code = NO_ERROR;
+ call_disconn_ntfy.cause_code = htons(NO_ERROR);
+ call_disconn_ntfy.reserved1 = htons(RESERVED);
+ memset(call_disconn_ntfy.call_stats, 0, 128);
+ COPY_CTRL_PACKET(call_disconn_ntfy, rply_packet, rply_size);
+ DEBUG_PACKET("CALL DISCONNECT RPLY");
+}
+
+/*
+ * deal_set_link_info
+ *
+ * @FIXME This function is *not* completed
+ *
+ * This method 'deals' with a SET-LINK-INFO. After stripping down the
+ * connection request a suitable reply is formed and stored in
+ * 'rply_packet' ready for sending.
+ *
+ * args: packet (IN) - the packet that we have to deal with (should be a
+ * SET-LINK-INFO packet)
+ * rply_packet (OUT) - suitable reply to the 'packet' we got.
+ * rply_size (OUT) - size of the reply packet
+ *
+ */
+void deal_set_link_info(unsigned char *packet)
+{
+ struct pptp_set_link_info *set_link_info;
+
+ set_link_info = (struct pptp_set_link_info *) packet;
+ if(set_link_info->send_accm != 0xffffffff || set_link_info->recv_accm != 0xffffffff)
+ syslog(LOG_ERR, "CTRL: Ignored a SET LINK INFO packet with real ACCMs!");
+ else if(pptpctrl_debug)
+ syslog(LOG_DEBUG, "CTRL: Got a SET LINK INFO packet with standard ACCMs");
+}
+
+void make_echo_req_packet(unsigned char *rply_packet, ssize_t * rply_size, u_int32_t echo_id)
+{
+ struct pptp_echo_rqst echo_packet;
+
+ MAKE_CTRL_HEADER(echo_packet, ECHO_RQST);
+ echo_packet.identifier = echo_id;
+ COPY_CTRL_PACKET(echo_packet, rply_packet, rply_size);
+ DEBUG_PACKET("ECHO REQ");
+}
+
+void make_stop_ctrl_req(unsigned char *rply_packet, ssize_t * rply_size)
+{
+ struct pptp_stop_ctrl_conn_rqst stop_ctrl;
+
+ MAKE_CTRL_HEADER(stop_ctrl, STOP_CTRL_CONN_RQST);
+ stop_ctrl.reason = GENERAL_STOP_CTRL;
+ stop_ctrl.reserved1 = RESERVED;
+ stop_ctrl.reserved2 = htons(RESERVED);
+ COPY_CTRL_PACKET(stop_ctrl, rply_packet, rply_size);
+ DEBUG_PACKET("STOP CTRL REQ");
+}
+
+void make_call_admin_shutdown(unsigned char *rply_packet, ssize_t * rply_size)
+{
+ struct pptp_call_disconn_ntfy call_disconn_ntfy;
+ u_int16_t pac_call_id;
+
+ /* Form a reply
+ * The reply packet is a CALL-DISCONECT-NOTIFY
+ * In single call mode we don't care what peer's call ID is, so don't even bother looking
+ */
+ if ((pac_call_id = freecall()) == htons(-1)) {
+ /* XXX should return an error */
+ syslog(LOG_ERR, "CTRL: Could not free Call ID [admin shutdown]!");
+ }
+ MAKE_CTRL_HEADER(call_disconn_ntfy, CALL_DISCONN_NTFY);
+ call_disconn_ntfy.call_id = pac_call_id;
+ call_disconn_ntfy.result_code = ADMIN_SHUTDOWN; /* disconnected by admin shutdown */
+ call_disconn_ntfy.error_code = NO_ERROR;
+ call_disconn_ntfy.cause_code = htons(NO_ERROR);
+ call_disconn_ntfy.reserved1 = htons(RESERVED);
+ memset(call_disconn_ntfy.call_stats, 0, 128);
+ COPY_CTRL_PACKET(call_disconn_ntfy, rply_packet, rply_size);
+ DEBUG_PACKET("CALL DISCONNECT RPLY");
+}
+
+#if PNS_MODE
+/* out of date. really PNS isn't 'trivially different', it's quite different */
+
+#define C_BITS (sizeof(unsigned int) * 8)
+#define C_SEG(x) (x/C_BITS)
+#define C_BIT(x) ((1U)<<(x%C_BITS))
+static unsigned int activeCalls[(MAX_CALLS / C_BITS) + 1];
+
+/*
+ * get_call_id
+ *
+ * Assigns a call ID and peer call ID to the session.
+ *
+ * args: call_id (OUT) - the call ID for the session
+ * retn: 0 on success, -1 on failure
+ */
+int get_call_id(u_int16_t * loc)
+{
+ for (i = 0; i < MAX_CALLS; i++) {
+ if (!(activeCalls[C_SEG(i)] & C_BIT(i))) {
+ activeCalls[C_SEG(i)] |= C_BIT(i);
+ *loc = i;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/*
+ * free_call_id
+ *
+ * args: call_id (IN) - the call ID for a terminated session
+ * retn: 0 on success, -1 on failure
+ */
+int free_call_id(u_int16_t call_id)
+{
+ if (!(activeCalls[C_SEG(i)] & C_BIT(i)))
+ return -1;
+ activeCalls[C_SEG(i)] &= ~C_BIT(i);
+ return 0;
+}
+#else
+static int _pac_call_id;
+static u_int16_t _pac_init = 0;
+
+/*
+ * getcall
+ *
+ * Assigns a call ID to the session and stores/returns it
+ *
+ * we only permit one call at a time, so the chance of wrapping 65k on one
+ * control connection is zero to none...
+ */
+u_int16_t getcall()
+{
+ struct sockaddr_pppox src_addr;
+ socklen_t addrlen;
+
+ src_addr.sa_family=AF_PPPOX;
+ src_addr.sa_protocol=PX_PROTO_PPTP;
+ src_addr.sa_addr.pptp.call_id=0;
+ src_addr.sa_addr.pptp.sin_addr=inetaddrs[0];
+
+ if (pptp_sock!=-1) close(pptp_sock);
+
+ pptp_sock=socket(AF_PPPOX,SOCK_STREAM,PX_PROTO_PPTP);
+ if (pptp_sock<0)
+ {
+ syslog(LOG_ERR,"CTRL: failed to create PPTP socket (%s)\n",strerror(errno));
+ exit(-1);
+ }
+ if (bind(pptp_sock,(struct sockaddr*)&src_addr,sizeof(src_addr)))
+ {
+ syslog(LOG_ERR,"CTRL: failed to bind PPTP socket (%s)\n",strerror(errno));
+ close(pptp_sock);
+ pptp_sock=-1;
+ exit(-1);
+ }
+ addrlen=sizeof(src_addr);
+ getsockname(pptp_sock,(struct sockaddr*)&src_addr,&addrlen);
+
+ if(!_pac_init) {
+ _pac_call_id = htons(-1);
+ _pac_init = 1;
+ }
+ if(_pac_call_id != htons(-1))
+ syslog(LOG_ERR, "CTRL: Asked to allocate call id when call open, not handled well");
+
+ _pac_call_id = htons(src_addr.sa_addr.pptp.call_id) ;
+ return _pac_call_id;
+}
+
+/*
+ * freecall
+ *
+ * Notes termination of current call
+ *
+ * retn: -1 on failure, PAC call ID on success
+ */
+u_int16_t freecall()
+{
+ u_int16_t ret;
+
+ if (pptp_sock!=-1){
+ close(pptp_sock);
+ pptp_sock=-1;
+ }
+
+ if(!_pac_init) {
+ _pac_call_id = htons(-1);
+ _pac_init = 1;
+ }
+ ret = _pac_call_id;
+ if(_pac_call_id == htons(-1))
+ syslog(LOG_ERR, "CTRL: Asked to free call when no call open, not handled well");
+ _pac_call_id = htons(-1);
+ return ret;
+}
+#endif