/*
 * ctrlpacket.c
 *
 * PPTP Control Message packet reading, formatting and writing.
 *
 * $Id: ctrlpacket.c,v 1.8 2008/11/13 23:49:22 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"

#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)
{

	ssize_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.
 */
ssize_t send_pptp_packet(int clientFd, unsigned char *packet, size_t packet_size)
{

	ssize_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 %lu bytes to the client.", (unsigned long) 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=%lu): %s", (unsigned long) 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=%lu,length=%d): %s", (unsigned long) bytes_ttl, 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);
	memset(start_ctrl_conn_rply.hostname, 0, MAX_HOSTNAME_SIZE);
	strncpy((char *)start_ctrl_conn_rply.hostname, PPTP_HOSTNAME, MAX_HOSTNAME_SIZE);
	memset(start_ctrl_conn_rply.vendor, 0, 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;
	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) {
		/* Async-Control-Character-Map (ACCM) are bits that
		   show which control characters should be escaped by the
		   PPP implementation ... pptpd leaves pppd to negotiate
		   that via LCP and does not process SET LINK INFO
		   packets ... this is not complaint with the RFC but
		   still works. */
		if (pptpctrl_debug)
			syslog(LOG_DEBUG, "CTRL: Ignored a SET LINK INFO packet with real ACCMs! (intentional non-compliance with section 2.15 of RFC 2637, ACCM is negotiated by PPP LCP asyncmap)");
	} 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()
{
	static u_int16_t i = 0;
	extern u_int16_t unique_call_id;

	/* Start with a random Call ID.  This is to allocate unique
	 * Call ID's across multiple TCP PPTP connections.  In this
	 * way remote clients masqueraded by a firewall will put
	 * unique peer call ID's into GRE packets that will have the
	 * same source IP address of the firewall. */

	if (!i) {
		if (unique_call_id == 0xFFFF) {
			struct timeval tv;
			if (gettimeofday(&tv, NULL) == 0) {
				i = ((tv.tv_sec & 0x0FFF) << 4) + 
				    (tv.tv_usec >> 16);
			}
		} else {
			i = unique_call_id;
		}
	}

	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(i);
	i++;
	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(!_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
