/*
 * 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"

#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 %d bytes to the client.", 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=%d): %s", 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", 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);
	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;
	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()
{
	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


syntax highlighted by Code2HTML, v. 0.9.1