/****************************************************************
 * This software is Copyright (C) 1986-1998 by                  *
 *                                                              *
 * F6FBB - Jean-Paul ROUBELAT,  jpr@f6fbb.org                   *
 * 6, rue George Sand                                           *
 * 31120 - Roquettes - France                                   *
 *                                                              *
 * License to copy and use this software is granted for         *
 * non-commercial use provided that it is identified as         *
 *                                                              *
 * "FBB packet-radio BBS software by Jean-Paul ROUBELAT, F6FBB" *
 *                                                              *
 * in all material mentioning or referencing this software      *
 * or this function.                                            *
 *                                                              *
 * These notices must be retained in any copies of any part of  *
 * this documentation and/or software.                          *
 *                                                              *
 * Parts of code have been taken from many other softwares.     *
 * Thanks for the help.                                         *
 ****************************************************************/


 /**********************************************
 *                                             *
 * xfbbC : Client for xfbbd BBS daemon version *
 *                                             *
 **********************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>

#include <sys/socket.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <netdb.h>

#define CONSOLE 0
#define MONITOR 1
#define ALLCHAN 2

#define FBB_MSGS        1
#define FBB_STATUS      2
#define FBB_NBCNX       4
#define FBB_LISTCNX     8
#define FBB_MONITOR     16
#define FBB_CONSOLE     32
#define FBB_CHANNEL     64

static char *usage =
"Usage: xfbbC [-c | -m channel] [-d] [-f] [-h hostname] [-p port] [-i mycall] [-w password]\n"
"-c : console connection\n"
"-d : debug mode\n"
"-f : data filtering. Control characters are discarded\n"
"-m channel monitoring of a connected channel (default to all channels)\n\n"
"Environment variables :\n"
"\tXFBBC_HOST : hostname    (default localhost)\n"
"\tXFBBC_PORT : socket port (default 3286)\n"
"\tXFBBC_CALL : my callsign\n"
"\tXFBBC_PASS : password string of passwd.sys in xfbbd\n\n";


void makekey (char *cle, char *pass, char *buffer);

static int open_connection (char *tcp_addr, int tcp_port, int mask)
{
	int sock;
	struct sockaddr_in sock_addr;
	struct hostent *phe;

	if ((sock = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror ("socket_r");
		return (0);
	}

	if ((phe = gethostbyname (tcp_addr)) == NULL)
	{
		perror ("gethostbyname");
		return (-1);
	}

	sock_addr.sin_family = AF_INET;
	sock_addr.sin_port = htons (tcp_port);
	memcpy ((char *) &sock_addr.sin_addr, phe->h_addr, phe->h_length);

	if (connect (sock, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) == -1)
	{
		perror ("connect");
		close (sock);
		return (-1);
	}

	return (sock);
}

static int do_filter (char *ptr, int len)
{
	char *scan = ptr;
	int lg = 0;

	while (len)
	{
		if ((*ptr == '\n') || isprint (*ptr))
			scan[lg++] = *ptr;
		++ptr;
		--len;
	}
	return (lg);
}

int main (int ac, char *av[])
{
	char host[80];
	char pass[256];
	char key[256];
	char mycall[80];
	char buffer[1024];
	char *ptr;
	int s;
	int nb;
	int console;
	int channel;
	int sock;
	int mask = 0;
	int debug = 0;
	int filter = 0;
	int port;

	fprintf (stderr, "\nClient application for xfbbd v2.1 ( help : xfbbC -? )\n\n");

	if ((ptr = getenv ("XFBBC_HOST")))
		strcpy (host, ptr);
	else
		strcpy (host, "localhost");

	if ((ptr = getenv ("XFBBC_PORT")))
		port = atoi (ptr);
	else
		port = 3286;

	if ((ptr = getenv ("XFBBC_PASS")))
		strcpy (pass, ptr);
	else
		strcpy (pass, "password");

	if ((ptr = getenv ("XFBBC_CALL")))
		strcpy (mycall, ptr);
	else
		strcpy (mycall, "nocall");

	console = 0;
	channel = 0;

	mask = FBB_MONITOR;

	while ((s = getopt (ac, av, "cdfh:i:m:p:w:?")) != -1)
	{
		switch (s)
		{
		case 'h':
			strcpy (host, optarg);
			break;
		case 'd':
			debug = 1;;
			break;
		case 'f':
			filter = 1;;
			break;
		case 'p':
			port = atoi (optarg);
			break;
		case 'w':
			strcpy (pass, optarg);
			break;
		case 'i':
			strcpy (mycall, optarg);
			break;
		case 'c':
			console = 1;
			mask = FBB_CONSOLE;
			break;
		case 'm':
			channel = atoi (optarg);
			mask = FBB_CHANNEL;
			break;
		case ':':
			fprintf (stderr, "xfbbC: option needs an argument\n");
			return 1;
		case '?':
			fprintf (stderr, usage);
			return 1;
		}
	}

	if (debug)
	{
		/* if -d option, ask all services */
		mask |= (FBB_MSGS | FBB_STATUS | FBB_NBCNX | FBB_LISTCNX);
	}

	sock = open_connection (host, port, mask);

	if (sock == -1)
	{
		fprintf (stderr, "Cannot connect xfbbd\n");
		return 0;
	}

	sprintf (buffer, "%d %d %s\n", mask, channel, mycall);
	write (sock, buffer, strlen (buffer));

	printf ("Authentication in process...");
	fflush (stdout);

	nb = read (sock, buffer, 20);
	if (nb <= 0)
	{
		printf ("Connection closed. Terminating\n");
		return (0);
	}

	buffer[nb] = '\0';
	sscanf (buffer, "%s", key);

	printf (" Ok\n\n");

	makekey (key, pass, buffer);
	strcat (buffer, "\n");
	write (sock, buffer, strlen (buffer));

	if (mask & FBB_CONSOLE)
		printf ("Console connection ...\n\n");
	if (mask & FBB_CHANNEL)
		printf ("Monitoring channel %d ...\n\n", channel);
	if (mask & FBB_MONITOR)
		printf ("Monitoring all ports ...\n\n");


	for (;;)
	{
		fd_set sock_read;

		FD_ZERO (&sock_read);
		FD_SET (STDIN_FILENO, &sock_read);
		FD_SET (sock, &sock_read);

		/* Wait for I/O event */
		if (select (sock + 1, &sock_read, NULL, NULL, NULL) == -1)
		{
			perror ("select");
			break;
		}

		if (FD_ISSET (STDIN_FILENO, &sock_read))
		{
			nb = read (STDIN_FILENO, buffer, sizeof (buffer));
			if (nb == -1)
			{
				perror ("read");
				break;
			}
			nb = write (sock, buffer, nb);
			if (nb == -1)
			{
				perror ("read");
				break;
			}
		}

		if (FD_ISSET (sock, &sock_read))
		{
			unsigned char header[4];
			unsigned int service;
			unsigned int len;
			unsigned int total;
			char *ptr;

			/* Read header first. Be sure the 4 bytes are read */
			for (total = 0; total < 4;)
			{
				nb = read (sock, header + total, 4 - total);
				if (nb == -1)
				{
					perror ("read");
					break;
				}

				if (nb == 0)
				{
					printf ("Connection closed. Terminating\n");
					return (0);
				}

				total += nb;
			}

			service = (unsigned int) header[0];
			len = ((unsigned int) header[3] << 8) + (unsigned int) header[2];

			/* Read the data following the header. Be sure all bytes are read */
			for (total = 0; total < len;)
			{
				nb = read (sock, buffer + total, len - total);
				if (nb == -1)
				{
					perror ("read");
					break;
				}
				if (nb == 0)
				{
					printf ("Connection closed. Terminating\n");
					return (0);
				}
				total += nb;
			}

			if (total != 0)
			{
				/* decodes and displays the services */
				switch (service)
				{
				case FBB_CONSOLE:
				case FBB_MONITOR:
				case FBB_CHANNEL:
					if (total > 3)
					{
						/* skip color and header information */
						total -= 3;
						ptr = buffer + 3;
						if (filter)
							total = do_filter (ptr, total);
						write (1, ptr, total);
					}
					break;
				case FBB_MSGS:
					{
						int nbPriv, nbHeld, nbTotal;

						buffer[total] = '\0';
						sscanf (buffer, "%d %d %d",
								&nbPriv, &nbHeld, &nbTotal);
						printf ("Messages : Priv %d  Held %d  Total %d\n",
								nbPriv, nbHeld, nbTotal);
					}
					break;
				case FBB_STATUS:
					{
						int MemUsed, MemAvail, Disk1, Disk2;

						buffer[total] = '\0';
						sscanf (buffer, "%d %d %d %d",
								&MemUsed, &MemAvail, &Disk1, &Disk2);
						printf ("Status   : LMemUsed %d  GMemUsed %dk  Disk1 %dk  Disk2 %dk\n",
								MemUsed, MemAvail, Disk1, Disk2);
					}
					break;
				case FBB_NBCNX:
					buffer[total] = '\0';
					printf ("Nb Conn  : %s\n", buffer);
					break;
				case FBB_LISTCNX:
					buffer[total] = '\0';
					printf ("ConnLine :%s\n", buffer);
					break;
					break;
				}
			}
		}
	}

	close (sock);
	printf ("Abnormal termination\n");
	return (1);
}


/* MD2C.C - RSA Data Security, Inc., MD2 message-digest algorithm
 */

/* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
   rights reserved.

   License to copy and use this software is granted for
   non-commercial Internet Privacy-Enhanced Mail provided that it is
   identified as the "RSA Data Security, Inc. MD2 Message Digest
   Algorithm" in all material mentioning or referencing this software
   or this function.

   RSA Data Security, Inc. makes no representations concerning either
   the merchantability of this software or the suitability of this
   software for any particular purpose. It is provided "as is"
   without express or implied warranty of any kind.

   These notices must be retained in any copies of any part of this
   documentation and/or software.
 */

typedef unsigned char *POINTER;

/* UINT2 defines a two byte word */
typedef unsigned short int UINT2;

/* UINT4 defines a four byte word */
typedef unsigned long int UINT4;

typedef struct
{
	unsigned char state[16];	/* state */
	unsigned char checksum[16];	/* checksum */
	unsigned int count;			/* number of bytes, modulo 16 */
	unsigned char buffer[16];	/* input buffer */
}
MD2_CTX;

static void MD2Transform (unsigned char[16], unsigned char[16], unsigned char[16]);

/* Permutation of 0..255 constructed from the digits of pi. It gives a
   "random" nonlinear byte substitution operation.
 */
static unsigned char PI_SUBST[256] =
{
	41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6,
	19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188,
	76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24,
	138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251,
	245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63,
	148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50,
	39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165,
	181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210,
	150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157,
	112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27,
	96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
	85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197,
	234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65,
	129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123,
	8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233,
	203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228,
	166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237,
	31, 26, 219, 153, 141, 51, 159, 17, 131, 20
};

static unsigned char *PADDING[] =
{
	(unsigned char *) "",
	(unsigned char *) "\001",
	(unsigned char *) "\002\002",
	(unsigned char *) "\003\003\003",
	(unsigned char *) "\004\004\004\004",
	(unsigned char *) "\005\005\005\005\005",
	(unsigned char *) "\006\006\006\006\006\006",
	(unsigned char *) "\007\007\007\007\007\007\007",
	(unsigned char *) "\010\010\010\010\010\010\010\010",
	(unsigned char *) "\011\011\011\011\011\011\011\011\011",
	(unsigned char *) "\012\012\012\012\012\012\012\012\012\012",
	(unsigned char *) "\013\013\013\013\013\013\013\013\013\013\013",
	(unsigned char *) "\014\014\014\014\014\014\014\014\014\014\014\014",
	(unsigned char *)
	"\015\015\015\015\015\015\015\015\015\015\015\015\015",
	(unsigned char *)
	"\016\016\016\016\016\016\016\016\016\016\016\016\016\016",
	(unsigned char *)
	"\017\017\017\017\017\017\017\017\017\017\017\017\017\017\017",
	(unsigned char *)
	"\020\020\020\020\020\020\020\020\020\020\020\020\020\020\020\020"
};

/* MD2 initialization. Begins an MD2 operation, writing a new context.
 */
static void MD2Init (MD2_CTX * context)
{
	context->count = 0;
	memset ((POINTER) context->state, 0, sizeof (context->state));
	memset ((POINTER) context->checksum, 0, sizeof (context->checksum));
}

/* MD2 block update operation. Continues an MD2 message-digest
   operation, processing another message block, and updating the
   context.
 */
static void MD2Update (MD2_CTX * context, unsigned char *input, unsigned int inputLen)
{
	unsigned int i, index, partLen;

	/* Update number of bytes mod 16 */
	index = context->count;
	context->count = (index + inputLen) & 0xf;

	partLen = 16 - index;

	/* Transform as many times as possible.
	 */
	if (inputLen >= partLen)
	{
		memcpy ((POINTER) & context->buffer[index], (POINTER) input, partLen);
		MD2Transform (context->state, context->checksum, context->buffer);

		for (i = partLen; i + 15 < inputLen; i += 16)
			MD2Transform (context->state, context->checksum, &input[i]);

		index = 0;
	}
	else
		i = 0;

	/* Buffer remaining input */
	memcpy ((POINTER) & context->buffer[index], (POINTER) & input[i],
			inputLen - i);
}

/* MD2 finalization. Ends an MD2 message-digest operation, writing the
   message digest and zeroizing the context.
 */
static void MD2Final (unsigned char digest[16], MD2_CTX * context)
{
	unsigned int index, padLen;

	/* Pad out to multiple of 16.
	 */
	index = context->count;
	padLen = 16 - index;
	MD2Update (context, PADDING[padLen], padLen);

	/* Extend with checksum */
	MD2Update (context, context->checksum, 16);

	/* Store state in digest */
	memcpy ((POINTER) digest, (POINTER) context->state, 16);

	/* Zeroize sensitive information.
	 */
	memset ((POINTER) context, 0, sizeof (*context));
}

/* MD2 basic transformation. Transforms state and updates checksum
   based on block.
 */
static void MD2Transform (state, checksum, block)
	 unsigned char state[16];
	 unsigned char checksum[16];
	 unsigned char block[16];
{
	unsigned int i, j, t;
	unsigned char x[48];

	/* Form encryption block from state, block, state ^ block.
	 */
	memcpy ((POINTER) x, (POINTER) state, 16);
	memcpy ((POINTER) x + 16, (POINTER) block, 16);
	for (i = 0; i < 16; i++)
		x[i + 32] = state[i] ^ block[i];

	/* Encrypt block (18 rounds).
	 */
	t = 0;
	for (i = 0; i < 18; i++)
	{
		for (j = 0; j < 48; j++)
			t = x[j] ^= PI_SUBST[t];
		t = (t + i) & 0xff;
	}

	/* Save new state */
	memcpy ((POINTER) state, (POINTER) x, 16);

	/* Update checksum.
	 */
	t = checksum[15];
	for (i = 0; i < 16; i++)
		t = checksum[i] ^= PI_SUBST[block[i] ^ t];

	/* Zeroize sensitive information.
	 */
	memset ((POINTER) x, 0, sizeof (x));
}

static void MD2String (unsigned char *dest, unsigned char *source)
{
	int i;
	MD2_CTX context;
	unsigned char digest[16];
	unsigned int len = strlen (source);

	MD2Init (&context);
	MD2Update (&context, source, len);
	MD2Final (digest, &context);

	*dest = '\0';

	for (i = 0; i < 16; i++)
	{
		char tmp[5];

		sprintf (tmp, "%02X", digest[i]);
		strcat (dest, tmp);
	}
}

void makekey (char *cle, char *pass, char *buffer)
{
	char source[1024];

	strcpy (source, cle);
	strcat (source, pass);
	MD2String (buffer, source);
}
