   /****************************************************************
    Copyright (C) 1986-2000 by

    F6FBB - Jean-Paul ROUBELAT
    6, rue George Sand
    31120 - Roquettes - France
	jpr@f6fbb.org

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    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 <errno.h>
#include <fcntl.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>

#include <fbb_orb.h>

#include "terminal.h"

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

static char *usage =
"Usage: xfbbC [-c | -m channel] [-d] [-f] [-h hostname] [-p port] [-i mycall] [-w password]\n"
"-c         : console connection\n"
"-d file    : download remote configuration file\n"
"-f         : data filtering. Control characters are discarded\n"
"-h address : hostname address of the xfbbd server\n"
"-i mycall  : callsign used for connection\n"
"-m channel : displays a connected channel (0 = all channels)\n"
"-p port    : IP port number (default 3286)\n"
"-r         : don't use curses\n"
"-l         : display remote directory\n"
"-s svc_nb  : request service number\n"
"-u file    : upload configuration file\n"
"-w password: password of the callsign as defined in passwd.sys\n"
"\nEnvironment 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);
}

static int send_file(int sock, char *filename)
{
	int fd;
	int nb;
	char buffer[1024];

	buffer[0] = ORB_REQUEST;
	buffer[1] = 3;

	fd = open(filename, O_RDONLY);
	if (fd == -1)
	{
		buffer[2] = 0;
		buffer[3] = 0;
		write(sock, buffer, 4);
		return errno;
	}
		
	for (;;)
	{
		nb = read(fd, buffer+4, 1000);
		if (nb < 0)
			nb = 0;
		buffer[2] = nb % 256;
		buffer[3] = nb >> 8;
		write(sock, buffer, nb+4);
		printf("%d data sent\n", nb);
		if (nb == 0)
			break;
	}
	
	close(fd);
	return 0;
}

int main (int ac, char *av[])
{
	char host[80];
	char pass[256];
	char key[256];
	char mycall[80];
	char filename[256];
	char arg[256];
	char buffer[1024];
	char *ptr;
	int len;
	int s;
	int nb;
	int console;
	int service;
	int transfer;
	int filelen = 0;
	int filetotal;
	int channel;
	int sock;
	int mask = 0;
	int debug = 0;
	int filter = 0;
	int use_curses = 1;
	int port;
	int mode;

	fprintf (stderr, "\nClient application for xfbbd v3.2 ( 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 = ORB_MONITOR;
	transfer = 0;
	
	service = 0;

	while ((s = getopt (ac, av, "cfd:h:i:l:m:p:rs:u:w:?")) != -1)
	{
		switch (s)
		{
		case 'h':
			strcpy (host, optarg);
			break;
		case 'd':
			strcpy (filename, optarg);
			transfer = SVC_RECV;
			mask = 0;
			break;
		case 'u':
			strcpy (filename, optarg);
			transfer = SVC_SEND;
			mask = 0;
			break;
		case 'l':
			strcpy (filename, optarg);
			transfer = SVC_DIR;
			mask = 0;
			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 = ORB_CONSOLE;
			break;
		case 'm':
			channel = atoi (optarg);
			mask = ORB_CHANNEL;
			break;
		case 'r':
			use_curses = 0;
			break;
		case 's':
			service = atoi (optarg);
			break;
		case ':':
			fprintf (stderr, "xfbbC: option needs an argument\n");
			return 1;
		case '?':
			fprintf (stderr, usage);
			return 1;
		}
	}

	if (debug && !transfer)
	{
		/* if -d option, ask all services */
		mask |= (ORB_MSGS | ORB_STATUS | ORB_NBCNX | ORB_LISTCNX);
	}

	fprintf(stderr, "Connecting %s ... ", host); fflush(stdout);
	sock = open_connection (host, port, mask);

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

	fprintf (stderr, " Ok\n");

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

	fprintf (stderr, "Authentication in progress ... ");
	fflush (stderr);

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

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

	fprintf (stderr, " Ok\n");

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

	mode = 0;
	if (mask & ORB_CONSOLE)
	{
		mode = 1;
		fprintf (stderr, "Console connection ...\n\n");
		
	}
	if (mask & ORB_CHANNEL)
		fprintf (stderr, "Monitoring channel %d ...\n\n", channel);
	if (mask & ORB_MONITOR)
		fprintf (stderr, "Monitoring all ports ...\n\n");

	if (transfer)
	{
		len = strlen(filename);
		
		switch(transfer)
		{
		case SVC_DIR:
			fprintf (stderr, "Requesting directory %s ...\n\n", filename);
			buffer[0] = ORB_REQUEST;
			buffer[1] = SVC_DIR;
			buffer[2] = len;
			buffer[3] = 0;
			strcpy(buffer+4, filename);
			write(sock, buffer, len+4);
			break;
		case SVC_RECV:
			fprintf (stderr, "Requesting file %s ...\n\n", filename);
			buffer[0] = ORB_REQUEST;
			buffer[1] = SVC_RECV;
			buffer[2] = len;
			buffer[3] = 0;
			strcpy(buffer+4, filename);
			write(sock, buffer, len+4);
			break;
		case SVC_SEND:
			fprintf (stderr, "Sending file %s ...\n\n", filename);
			buffer[0] = ORB_REQUEST;
			buffer[1] = SVC_SEND;
			buffer[2] = len;
			buffer[3] = 0;
			strcpy(buffer+4, filename);
			write(sock, buffer, len+4);
			send_file(sock, filename);
			break;
		}
		
		filename[0] = '\0';
		filelen = 0;
	}
	else if (service)
	{
		switch(service)
		{
		case SVC_FWD:
			strcpy(buffer+4, "LIST");
			len = 4;
			
			fprintf (stderr, "Requesting Pending Forward ...\n\n");
			buffer[0] = ORB_REQUEST;
			buffer[1] = SVC_FWD;
			buffer[2] = len;
			buffer[3] = 0;
			write(sock, buffer, len+4);
			break;
		case SVC_DISC:
			sprintf(buffer+4, "01 F6FBB-1 0");
			len = strlen(buffer+4);
			
			fprintf (stderr, "Requesting Disconnection ...\n\n");
			buffer[0] = ORB_REQUEST;
			buffer[1] = SVC_DISC;
			buffer[2] = len;
			buffer[3] = 0;
			write(sock, buffer, len+4);
			break;
		}
	}
	else
	{
		sprintf(buffer, " xfbbC V3.01  -  Callsign : %s  -  Remote host : %s", mycall, host);
#ifdef USE_NCURSES
	if (use_curses)
		init_terminal(mode, buffer);
	else
#endif
		fprintf (stderr, "%s\n", buffer);
	}
	
	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))
		{
#ifdef USE_NCURSES
			if (use_curses)
				nb = read_terminal(buffer, sizeof(buffer));
			else
			{
#endif
				nb = read (STDIN_FILENO, buffer, sizeof (buffer));
				if (nb == -1)
				{
					perror ("read");
					break;
				}
#ifdef USE_NCURSES
			}
#endif
			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 command;
			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");
#ifdef USE_NCURSES
					if (use_curses)
						end_terminal();
#endif
					return (0);
				}

				total += nb;
			}

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

			/* printf("\nservice=%d command=%d len=%d : ", service, command, len); fflush(stdout);*/
				
			/* 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");
#ifdef USE_NCURSES
					if (use_curses)
						end_terminal();
#endif
					return (0);
				}
				total += nb;
			}

			if (total == 0)
			{
				return 0;	/* end of transfer */
			}
			else
			{
				/* decodes and displays the services */
				switch (service)
				{
				case ORB_CONSOLE:
				case ORB_MONITOR:
				case ORB_CHANNEL:
					if (total > 3)
					{
						/* skip color and header information */
						total -= 3;
						ptr = buffer + 3;
						if (filter)
							total = do_filter (ptr, total);
#ifdef USE_NCURSES
						if (use_curses)
							write_terminal(ptr, total);
						else
#endif
							write (1, ptr, total);
					}
					break;
				case ORB_MSGS:
					{
						int nbPriv, nbHeld, nbTotal;

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

						buffer[total] = '\0';
						sscanf (buffer, "%d %d %d %d",
								&MemUsed, &MemAvail, &Disk1, &Disk2);
						if (debug)
							printf ("Status   : LMemUsed %d  GMemUsed %dk  Disk1 %dk  Disk2 %dk\n",
								MemUsed, MemAvail, Disk1, Disk2);
					}
					break;
				case ORB_NBCNX:
					buffer[total] = '\0';
					if (debug)
						printf ("Nb Conn  : %s\n", buffer);
					break;
				case ORB_LISTCNX:
					buffer[total] = '\0';
					if (debug)
						printf ("ConnLine :%s\n", buffer);
					break;
				case ORB_DATA:
					switch (command)
					{
					case SVC_LIST:
						/*
						int i;
						printf("Implemented services :\n");
						for (i = 0 ; i < total ; i++)
							printf("   %d : %s\n", buffer[i] & 0xff, service_name(buffer[i]));
						*/
						break;
					case SVC_DIR:
						buffer[total] = '\0';
						if (filename[0] == '\0')
						{
							filelen = filetotal = 0;
							strcpy(filename, buffer);
							sscanf(buffer+strlen(filename)+1, "%d", &filetotal);
						}
						else
						{
							filelen += total;
							write(STDOUT_FILENO, buffer, total);
						}
						fprintf(stderr, "receiving directory %s %d/%d bytes\n", filename, filelen, filetotal);
						break;
					case SVC_RECV:
						buffer[total] = '\0';
						if (filename[0] == '\0')
						{
							filelen = filetotal = 0;
							strcpy(filename, buffer);
							sscanf(buffer+strlen(filename)+1, "%d", &filetotal);
						}
						else
						{
							filelen += total;
							write(STDOUT_FILENO, buffer, total);
						}
						fprintf(stderr, "receiving file %s %d/%d bytes\n", filename, filelen, filetotal);
						break;
					case SVC_FWD:
						buffer[total] = '\0';
						if (filename[0] == '\0')
						{
							filelen = filetotal = 0;
							*arg = '\0';
							sscanf(buffer, "%s %s", filename, arg);
							sscanf(buffer+strlen(buffer)+1, "%d", &filetotal);
							if (*arg)
								fprintf(stderr, "%s : %s\n", filename, arg);
						}
						else
						{
							filelen += total;
							write(STDOUT_FILENO, buffer, total);
						}
						if (filetotal)
							fprintf(stderr, "receiving fwd list %s %d/%d bytes\n", filename, filelen, filetotal);
						break;
					}
					break;
				}
			}
		}
	}

	close (sock);
#ifdef USE_NCURSES
	if (use_curses)
		end_terminal();
#endif
	printf ("Abnormal termination\n");
	return (1);
}

#define PROTOTYPES 1
#include "global.h"
#include "md5.h"

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

	MD5Init (&context);
	MD5Update (&context, source, len);
	MD5Final (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);
	MD5String (buffer, source);
}
