/*****************************************************************************
 * Copyright (c) 1990-2000 Digi International Inc., All Rights Reserved
 * 
 * This software contains proprietary and confidential information of Digi
 * International Inc.  By accepting transfer of this copy, Recipient agrees
 * to retain this software in confidence, to prevent disclosure to others,
 * and to make no use of this software other than that for which it was
 * delivered.  This is an unpublished copyrighted work of Digi International
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 * prohibited.
 * 
 * Restricted Rights Legend
 *
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 * Technical Data and Computer Software clause at DFARS 252.227-7031 or
 * subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 * Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 *****************************************************************************/

static const char whatiam[] = "@(#) " "Digi PortAssist Manager Communication Agent";
static const char copyright[] = "@(#) " "Copyright (c) 1990-2000 Digi International Inc., All Rights Reserved";
static const char pamsocket_c_rcsid[] = "@(#) " "$Id: pamsocket.c,v 1.18 2000/04/19 23:01:55 chrism Exp $";


#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#if defined PLATFORM_WINDOWS
#include "winstart.h"
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

extern int errno;

#include "genstart.h"

#endif

#include "pam_snmp.h"
#include "dbgprintf.h"

#define BUFFER_SIZE (8 * 1024)

static unsigned char incoming[BUFFER_SIZE];

unsigned char oid_prefix[] =
{
	0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x4c, 0x39, 0x02, 0x01
};

#define OID_DIGIPAM_TABLE			8
#define OID_DIGIPAM_ENTRIES		9
#define OID_DIGIPAM_ENTRY			10
#define OID_DIGIPAM_BOARD			11
#define OID_DIGIPAM_PORT			12
#define OID_DIGIPAM_CHANNEL		13

static unsigned char stupid_programming_buffer[BUFFER_SIZE];

static unsigned char response_buffer[BUFFER_SIZE];

static unsigned char rsp_header[] =
{
	0x30, 0x82, 0xFF, 0xFF, 0x02, 0x01, 0x00, 0x04,
	0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2,
	0x82, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x01,
	0x00, 0x02, 0x01, 0x00, 0x30, 0x82, 0x00, 0x00,
	0x30, 0x82, 0x00, 0x00, 0x06, 0x0e
};

/* the offset to the zero'd length fields above */
#define TOTAL_LENGTH_OFFSET		2
#define REQUEST_LENGTH_OFFSET		17
#define OID_LENGTH_OFFSET			30

static int get_ber_length(unsigned char *rcvd, int *ber_length)
{
	int length, byte_count;

	byte_count = 1;
	length = *rcvd++;
	if (length < 0x80)
		goto gbl_exit;

	if ((length == 0x80) || (length > 0x82))
	{
		errprintf("[ PAMA ] get_ber_length - bad byte length field\n");
		return (-1);
	}

	byte_count += (length - 0x80);
	length = *rcvd++;
	if (byte_count == 2)
		goto gbl_exit;

	length = (16 * length) + *rcvd;

 gbl_exit:
	*ber_length = length;
	return (byte_count);
}

static int validate_request(unsigned char *rcvd, int rcvd_len, unsigned char **request_type,
									unsigned char **error_status, unsigned char **error_index,
									unsigned char *oid, unsigned char *set_value)
{
	int bytes_read, length_of_remainder;

	if (*rcvd++ != 0x30)
		goto genErr;

	rcvd_len--;
	length_of_remainder = 0;
	bytes_read = get_ber_length(rcvd, &length_of_remainder);

	rcvd_len -= bytes_read;
	if (length_of_remainder != rcvd_len)
		goto genErr;

	rcvd += bytes_read;

	/* test version */
	if ((*rcvd++ != 0x02) || (*rcvd++ != 0x01) || (*rcvd++ != 0x00))
		goto genErr;

	/* test community - assume "public" */
	if ((*rcvd++ != 0x04) || (*rcvd++ != 0x06))
		goto genErr;

	if (strncmp((char *) rcvd, "public", 6) != 0)
		goto genErr;

	rcvd += 6;

	/* test for GET-REQUEST */
	if ((*rcvd != 0xA0) && (*rcvd != 0xA3))
		goto genErr;

	*request_type = rcvd++;

	rcvd_len -= 12;

	/* count 'em */
	length_of_remainder = 0;
	bytes_read = get_ber_length(rcvd, &length_of_remainder);

	rcvd_len -= bytes_read;
	if (length_of_remainder != rcvd_len)
		goto genErr;

	rcvd += bytes_read;

	/* get request ID */
	if ((*rcvd++ != 0x02) || (*rcvd++ != 0x01))
		goto genErr;

	/* skip past id value */
	rcvd++;

	/* test error fields */
	if ((*rcvd++ != 0x02) || (*rcvd++ != 0x01) || (*rcvd != 0x00))
		goto genErr;

	*error_status = rcvd++;

	if ((*rcvd++ != 0x02) || (*rcvd++ != 0x01) || (*rcvd != 0x00))
		goto genErr;

	*error_index = rcvd++;

	rcvd_len -= 9;

	/* OK, the header is done, test the opening for the request fields */

	if (*rcvd++ != 0x30)
		goto genErr;

	rcvd_len -= 1;

	length_of_remainder = 0;
	bytes_read = get_ber_length(rcvd, &length_of_remainder);

	rcvd_len -= bytes_read;
	if (length_of_remainder != rcvd_len)
		goto genErr;

	rcvd += bytes_read;

	/* test each request individually */

	if (*rcvd++ != 0x30)
		goto genErr;

	rcvd_len--;

	length_of_remainder = 0;
	bytes_read = get_ber_length(rcvd, &length_of_remainder);

	rcvd_len -= bytes_read;
	if (length_of_remainder != rcvd_len)
		goto genErr;

	rcvd += bytes_read;

	if (*rcvd++ != 0x06)
		goto noSuchName;

	bytes_read = *rcvd++;
	if (bytes_read < sizeof (oid_prefix))
		goto noSuchName;

	if (memcmp(&oid_prefix[0], rcvd, sizeof (oid_prefix)))
		goto noSuchName;

	memcpy(oid, rcvd, bytes_read);

	rcvd += bytes_read;

	if (**request_type == 0xA0)
	{									/* GET processing */

		if ((*rcvd++ != 0x05) || (*rcvd != 0x00))
			goto noSuchName;

		return (0);
	}

	/* SET processing */
	if (*rcvd++ != 0x04)
		goto noSuchName;

	bytes_read = *rcvd++;
	strncpy((char *) set_value, (char *) rcvd, bytes_read);
	*((char *) (set_value + bytes_read)) = '\0';
	return (0);

 noSuchName:
	errprintf("[ PAMA ] noSuchName parse error\n");
	**error_index = 1;
	return (2);

 genErr:
	errprintf("[ PAMA ] genErr parse error\n");
	return (5);
}

static void build_response_string(unsigned char *oid_stream, unsigned char *set_value,
											unsigned char request_type, unsigned char *error_indicator,
											char **response, int *rsp_length)
{
	unsigned char board, port, channel;
	unsigned char *rsp_buf;
	int success = FALSE;

	*error_indicator = 5;		/* genErr */

	board = oid_stream[OID_DIGIPAM_BOARD];
	port = oid_stream[OID_DIGIPAM_PORT];
	channel = oid_stream[OID_DIGIPAM_CHANNEL];

	switch (request_type)
	{
		case 0xA3: /* a SET request */
			switch (oid_stream[OID_DIGIPAM_ENTRY])
			{
				case PAM_ModemExtendedConfiguration:		success = AlterModemExtendedConfiguration(board, channel, set_value);						break;
				case PAM_SerialTestChannelMode:				success = AlterSerialTestChannelMode(board, channel, set_value);								break;
				case PAM_SerialTestStatus:						success = AlterSerialTestStatus(board, channel, set_value);										break;
				case PAM_SerialTestConfiguration:			success = AlterSerialTestConfiguration(board, channel, set_value);							break;
				case PAM_SerialExtendedConfiguration:		success = AlterSerialExtendedConfiguration(board, channel, set_value);						break;
				case PAM_ModemConfiguration:					success = AlterModemConfiguration(board, channel, set_value);									break;
				case PAM_BChannelExtendedConfiguration:	success = AlterBChannelExtendedConfiguration(board, channel, set_value);					break;
				default:	errprintf("[ PAMA ] unknown set command %d\n", oid_stream[OID_DIGIPAM_ENTRY]); *error_indicator = 3;	/* bad value */	break;
			}
			break;

		case 0xA0:
			rsp_buf = &stupid_programming_buffer[0];
			*rsp_buf = '\0';

			switch (oid_stream[OID_DIGIPAM_ENTRY])
			{
				case PAM_AgentInfo:								success = UpdateAgentInfo(rsp_buf);																		break;
				case PAM_ManagementShimVersion:				success = UpdateManagementShimVersion(rsp_buf, board);											break;
				case PAM_ManagementBoardCount:				success = UpdateManagementBoardCount(rsp_buf, board);												break;
				case PAM_ManagementBoardInfo:					success = UpdateManagementBoardInfo(rsp_buf, board);												break;
				case PAM_ManagementDriverVersions:			success = UpdateManagementDriverVersions(rsp_buf, board);										break;
				case PAM_AdapterInfo:							success = UpdateAdapterInfo(rsp_buf, board);															break;
				case PAM_AdapterState:							success = UpdateAdapterState(rsp_buf, board);														break;
				case PAM_ModemNationality:						success = UpdateModemNationality(rsp_buf, board);													break;
				case PAM_ManagementProtocolVersion:			success = UpdateManagementProtocolVersion(rsp_buf, board);										break;
				case PAM_DS1Configuration:						success = UpdateDS1Configuration(rsp_buf, board, port);											break;
				case PAM_DS1ExtendedConfiguration:			success = UpdateDS1ExtendedConfiguration(rsp_buf, board, port);								break;
				case PAM_DS1Status:								success = UpdateDS1Status(rsp_buf, board, port);													break;
				case PAM_DS1ExtendedStatus:					success = UpdateDS1ExtendedStatus(rsp_buf, board, port);											break;
				case PAM_DS1CT1SignalStatus:					success = UpdateDS1CT1SignalStatus(rsp_buf, board, port, channel);							break;
				case PAM_DS1AllCT1SignalStatus:				success = UpdateDS1AllCT1SignalStatus(rsp_buf, board, port);									break;
				case PAM_BRIConfiguration:						success = UpdateBRIConfiguration(rsp_buf, board, port);											break;
				case PAM_BRIStatus:								success = UpdateBRIStatus(rsp_buf, board, port);													break;
				case PAM_SerialConfiguration:					success = UpdateSerialConfiguration(rsp_buf, board, channel);									break;
				case PAM_SerialStatus:							success = UpdateSerialStatus(rsp_buf, board, channel);											break;
				case PAM_SerialExtendedConfiguration:		success = UpdateSerialExtendedConfiguration(rsp_buf, board, channel);						break;
				case PAM_SerialExtendedStatus:				success = UpdateSerialExtendedStatus(rsp_buf, board, channel);									break;
				case PAM_CallStatus:								success = UpdateCallStatus(rsp_buf, board, port, channel);										break;
				case PAM_AllCallStatus:							success = UpdateAllCallStatus(rsp_buf, board, port);												break;
				case PAM_ModemConfiguration:					success = UpdateModemConfiguration(rsp_buf, board, channel);									break;
				case PAM_ModemStatus:							success = UpdateModemStatus(rsp_buf, board, channel);												break;
				case PAM_ModemExtendedConfiguration:		success = UpdateModemExtendedConfiguration(rsp_buf, board, channel);							break;
				case PAM_ModemExtendedStatus:					success = UpdateModemExtendedStatus(rsp_buf, board, channel);									break;
				case PAM_DChannelExtendedConfiguration:	success = UpdateDChannelExtendedConfiguration(rsp_buf, board, port);							break;
				case PAM_DChannelExtendedStatus:				success = UpdateDChannelExtendedStatus(rsp_buf, board, port);									break;
				case PAM_BChannelStatus:						success = UpdateBChannelStatus(rsp_buf, board, channel);											break;
				case PAM_BChannelExtendedConfiguration:	success = UpdateBChannelExtendedConfiguration(rsp_buf, board, channel);						break;
				case PAM_BChannelExtendedStatus:				success = UpdateBChannelExtendedStatus(rsp_buf, board, channel);								break;
				case PAM_SystemStatus:							success = UpdateSystemStatus(rsp_buf);																	break;
				case PAM_SerialTestChannelMode:				success = UpdateSerialTestChannelMode(rsp_buf, board, channel);								break;
				case PAM_AllChannelInfos:						success = UpdateAllChannelInfos(rsp_buf, board, FALSE);											break;
				case PAM_AllChannelInfosExt:					success = UpdateAllChannelInfos(rsp_buf, board, TRUE);											break;
				case PAM_AllSerialSummaries:					success = UpdateAllSerialSummaries(rsp_buf, board);												break;
				case PAM_AllSerialDetails:						success = UpdateAllSerialDetails(rsp_buf, board);													break;
				default:	errprintf("[ PAMA ] unknown get command %d\n", oid_stream[OID_DIGIPAM_ENTRY]); *error_indicator = 2;	/* noSuchName */	break;
			}

			*response = (char *)rsp_buf;
			*rsp_length = (int)strlen((char *)rsp_buf);

			break;

		default:
			errprintf("[ PAMA ] unknown request type %d\n", request_type);
			break;
	}

	if (success)
		*error_indicator = 0;
}

static int build_response_packet(unsigned char *oid, unsigned char *rsp, int rsp_len)
{
	unsigned char *bptr, *tl_ptr, *rl_ptr, *ol_ptr;
	int total_length, request_length, oid_length;

	bptr = &response_buffer[0];
	tl_ptr = &response_buffer[TOTAL_LENGTH_OFFSET];
	rl_ptr = &response_buffer[REQUEST_LENGTH_OFFSET];
	ol_ptr = &response_buffer[OID_LENGTH_OFFSET];

	total_length = sizeof (rsp_header);

	memcpy(bptr, &rsp_header[0], total_length);
	bptr += total_length;

	memcpy(bptr, oid, 14);
	bptr += 14;
	total_length += 14;

	*bptr++ = 0x04;
	*bptr++ = 0x82;
	*bptr++ = rsp_len >> 8;
	*bptr++ = rsp_len & 0xFF;
	memcpy(bptr, rsp, rsp_len);
	total_length += (4 + rsp_len);

	*tl_ptr++ = (total_length - 4) >> 8;
	*tl_ptr = (total_length - (TOTAL_LENGTH_OFFSET + 2)) & 0xFF;

	request_length = (total_length - (REQUEST_LENGTH_OFFSET + 2));
	*rl_ptr++ = request_length >> 8;
	*rl_ptr = request_length & 0xFF;

	oid_length = (total_length - (OID_LENGTH_OFFSET + 2));
	*ol_ptr++ = oid_length >> 8;
	*ol_ptr = oid_length & 0xFF;
	ol_ptr += 3;
	oid_length -= 4;

	*ol_ptr++ = oid_length >> 8;
	*ol_ptr = oid_length & 0xFF;

	return (total_length);
}

static int read_and_respond(unsigned char *rcvd, int rcvd_len, unsigned char **response)
{
	int temp, rsp_length;
	unsigned char *request_type, *error_status, *error_index;
	unsigned char error;
	unsigned char oid[16];
	unsigned char set_value[128];
	char *rsp_string;

	set_value[0] = '\0';
	temp = validate_request(rcvd, rcvd_len, &request_type, &error_status, &error_index, &oid[0], &set_value[0]);

	if (temp)
	{									/* NOT validated */
		*request_type = 0xA2;
		*error_status = temp;
		*response = rcvd;
		return (rcvd_len);
	}

	build_response_string(&oid[0], &set_value[0], *request_type, &error,
								 &rsp_string, &rsp_length);
	if (*request_type == 0xA3)
	{
		*request_type = 0xA2;
		*error_status = error;
		if (error == 2)
			*error_index = 1;
		*response = rcvd;
		return (rcvd_len);
	}

	if (error != 0)
	{
		*request_type = 0xA2;
		*error_status = error;
		*response = rcvd;
		if (error != 5)
			*error_index = 1;
		return (rcvd_len);
	}

	rsp_length = build_response_packet(&oid[0], (unsigned char *) rsp_string, rsp_length);
	*response = &response_buffer[0];
	return (rsp_length);
}

#if !_M_IA64
	static int snmp_sock = -1;
#else
	static INT_PTR snmp_sock = -1;
#endif

static void close_it(void)
{
	if (snmp_sock >= 0)
	{
#if !defined PLATFORM_WINDOWS
		close(snmp_sock);
#else
		closesocket(snmp_sock);
#endif
		snmp_sock = -1;
	}
}

static int keep_working = TRUE;
static int work_has_stopped = FALSE;

void sigterm_hndlr(int whatever)
{
	keep_working = FALSE;
	close_it();

#if defined PLATFORM_NETWARE
	while (!work_has_stopped)
		ThreadSwitchWithDelay();
#endif
}

/*
#define PACKETDUMP
*/
#if defined(PACKETDUMP)
void packetdump(unsigned char * buf, int cb)
{
	char raw[64] = {'\0'}, *rp = &raw[0];
	char ascii[64] = {'\0'}, *ap = &ascii[0];
	int i;

	trcprintf("[ PAMA ] Packet Dump:\n");

	for (i = 0; i < cb; i++)
	{
		if ((i != 0) && ((i % 8) == 0))
		{
			rp += sprintf(rp, " ");
			ap += sprintf(ap, " ");
		}

		if ((i != 0) && ((i % 16) == 0))
		{
			trcprintf("%-50s %s\n", raw, ascii);

			rp = &raw[0];
			ap = &ascii[0];

			*rp = '\0';
			*ap = '\0';
		}

		rp += sprintf(rp, "%02x ", buf[i]);
		ap += sprintf(ap, "%c", isprint(buf[i]) ? buf[i] : '.');
	}

	if (raw[0] != '\0')
		trcprintf("%-50s %s\n", raw, ascii);
}
#endif

int work(unsigned short our_port)
{
	int msg_length, flags, byte_count, them_length;
	struct sockaddr_in us, them;
	unsigned char *outgoing;

	snmp_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (snmp_sock == -1)
	{
		errprintf("[ PAMA ] socket call failed %d\n", errno);
		return 0;
	}

	memset(&us, '\0', sizeof (us));
	us.sin_family = AF_INET;
	us.sin_addr.s_addr = htonl(INADDR_ANY);
	us.sin_port = our_port;
	if (bind(snmp_sock, (struct sockaddr *) &us, sizeof (us)) == -1)
	{
		errprintf("[ PAMA ] bind call failed %d\n", errno);
		close_it();
		return 0;
	}

	/* just because... */
	memset(&them, '\0', sizeof (them));

	keep_working = TRUE;
	work_has_stopped = FALSE;

	signal(SIGTERM, sigterm_hndlr);

	while (keep_working)
	{
		flags = 0;
		them_length = sizeof (them);

#if defined PLATFORM_AIX
		byte_count = recvfrom(snmp_sock, (char *) &incoming[0], 256,
			flags, (struct sockaddr *) &them, (unsigned long *)&them_length);
#else
		byte_count = recvfrom(snmp_sock, (char *) &incoming[0], 256,
			flags, (struct sockaddr *) &them, &them_length);
#endif
		if (!keep_working) break;

#if defined(PACKETDUMP)
		packetdump(incoming, byte_count);
#endif

		if (byte_count < 0)
		{
			errprintf("[ PAMA ] recvfrom failed: errno = %d\n", errno);
			break;
		}

		outgoing = NULL;
		msg_length = read_and_respond(&incoming[0], byte_count, &outgoing);
		if (!keep_working) break;

#if defined(PACKETDUMP)
		packetdump(outgoing, msg_length);
#endif

		if (!msg_length)
			continue;

		byte_count = sendto(snmp_sock, (char *) outgoing, msg_length, flags, (struct sockaddr *) &them, them_length);
		if (!keep_working) break;

		if ((byte_count < 0) || (byte_count != msg_length))
		{
			errprintf("[ PAMA ] sendto failed: errno = %d, byte_count = %d, msg_length = %d\n", errno, byte_count, msg_length);
			break;
		}
	}

	close_it();

	work_has_stopped = TRUE;
	return 1;
}

int main(int argc, char *argv[])
{
	start(argc, argv);
	return 0;
}
