/* yapp.c
 *
 * Copyright (C) 1994 by Jonathan Naylor
 *
 * This module implements the YAPP file transfer protocol as defined by Jeff
 * Jacobsen WA7MBL in the files yappxfer.doc and yappxfer.pas.
 *
 *
 * 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.
 */
#define _DEFAULT_SOURCE
#define _XOPEN_SOURCE
#define _XOPEN_SOURCE_EXTENDED

/*
 * Yapp C and Resume support added by S N Henson.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <linux/ax25.h>

#include "call.h"

#define	TIMEOUT		300	/* 5 Minutes */

#define	NUL		0x00
#define	SOH		0x01
#define	STX		0x02
#define	ETX		0x03
#define	EOT		0x04
#define	ENQ		0x05
#define	ACK		0x06
#define	DLE		0x10
#define	NAK		0x15
#define	CAN		0x18

#define	STATE_S		0
#define	STATE_SH	1
#define	STATE_SD	2
#define	STATE_SE	3
#define	STATE_ST	4
#define	STATE_R		5
#define	STATE_RH	6
#define	STATE_RD	7

static int state;
static int total = 0;

static int readlen = 0;
static int outlen = 0;
static int outbufptr = 0;
static unsigned char outbuffer[512];
static char yappc;		/* Nonzero if using YAPP C */

static void Write_Status(char *s)
{
	fprintf(stdout, "State: %-60s\r", s);
	fflush(stdout);
}

static void Send_RR(void)
{
	char buffer[2];

	buffer[0] = ACK;
	buffer[1] = 0x01;

	write(fd, buffer, 2);
}

static void Send_RF(void)
{
	char buffer[2];

	buffer[0] = ACK;
	buffer[1] = 0x02;

	write(fd, buffer, 2);
}

static void Send_RT(void)
{
	char buffer[2];

	buffer[0] = ACK;
	buffer[1] = ACK;

	write(fd, buffer, 2);
}

static void Send_AF(void)
{
	char buffer[2];

	buffer[0] = ACK;
	buffer[1] = 0x03;

	write(fd, buffer, 2);
}

static void Send_AT(void)
{
	char buffer[2];

	buffer[0] = ACK;
	buffer[1] = 0x04;

	write(fd, buffer, 2);
}

static void Send_NR(char *reason)
{
	char buffer[257];
	int length;

	length = strlen(reason);
	if (length > 255)
		length = 255;

	buffer[0] = NAK;
	buffer[1] = length;
	memcpy(buffer + 2, reason, length);

	write(fd, buffer, length + 2);
}

/* Send a Resume Sequence */

static void Send_RS(int length)
{
	char buffer[256];
	int len;

	buffer[0] = NAK;
	buffer[2] = 'R';
	buffer[3] = 0;

	len = sprintf(buffer + 4, "%d", length) + 5;

	buffer[len] = 'C';
	buffer[len + 1] = 0;
	buffer[1] = len;

	write(fd, buffer, len + 2);
}

static void Send_SI(void)
{
	char buffer[2];

	buffer[0] = ENQ;
	buffer[1] = 0x01;

	write(fd, buffer, 2);
}

static void Send_CN(char *reason)
{
	char buffer[257];
	int length;

	length = strlen(reason);
	if (length > 255)
		length = 255;

	buffer[0] = CAN;
	buffer[1] = length;
	memcpy(buffer + 2, reason, length);

	write(fd, buffer, length + 2);
}

static void Send_HD(char *filename, long length)
{
	char buffer[257];
	char size_buffer[12];
	int len_filename;
	int len_size;
	int len;
	struct stat sb;

	sprintf(size_buffer, "%ld", length);

	len_filename = strlen(filename) + 1;	/* Include the NUL */
	len_size = strlen(size_buffer) + 1;	/* Include the NUL */

	len = len_filename + len_size;

	if (!stat(filename, &sb)) {
		unix2yapp(sb.st_mtime, buffer + len + 2);
		len += 9;
	}

	buffer[0] = SOH;
	buffer[1] = len;

	memcpy(buffer + 2, filename, len_filename);
	memcpy(buffer + len_filename + 2, size_buffer, len_size);

	write(fd, buffer, len + 2);
}

static void Send_ET(void)
{
	char buffer[2];

	buffer[0] = EOT;
	buffer[1] = 0x01;

	write(fd, buffer, 2);
}

static void Send_DT(int length)
{
	char buffer[2];

	if (length > 255)
		length = 0;

	buffer[0] = STX;
	buffer[1] = length;

	write(fd, buffer, 2);
}

static void Send_EF(void)
{
	char buffer[2];

	buffer[0] = ETX;
	buffer[1] = 0x01;

	write(fd, buffer, 2);
}

static unsigned char checksum(unsigned char *buf, int len)
{
	int i;
	unsigned char sum = 0;

	for (i = 0; i < len; i++)
		sum += buf[i];

	return sum;
}

static int yapp_download_data(int *filefd, unsigned char *buffer)
{
	int length, file_time;
	char Message[50];

	if (buffer[0] == CAN || buffer[0] == NAK) {
		Write_Status("RcdABORT");
		return FALSE;
	}

	switch (state) {
	case STATE_R:
		if (buffer[0] == ENQ && buffer[1] == 0x01) {
			Send_RR();
			Write_Status("RcvHeader");
			state = STATE_RH;
			break;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;

	case STATE_RH:
		if (buffer[0] == SOH) {
			/* Parse header: 3 fields == YAPP C */
			unsigned char *hptr, *hfield[3];
			length = buffer[1];
			if (length == 0)
				length = 256;
			hptr = buffer + 2;
			while (length > 0) {
				int hlen;
				hlen = strlen((char *)hptr) + 1;
				hfield[(int) yappc++] = hptr;
				hptr += hlen;
				length -= hlen;
			}

			if (yappc < 3) {
				yappc = 0;
			} else {
				file_time = yapp2unix((char *)hfield[2]);
				yappc = 1;
			}

			if (*filefd == -1) {
				*filefd = open((char *)hfield[0],
					       O_RDWR | O_APPEND | O_CREAT,
					       0666);
				if (*filefd == -1) {
					printf("\n[Unable to open %s]\n",
					       hfield[0]);
					Send_NR("Invalid filename");
					return FALSE;
				}
			}

			printf("Receiving %s %s %s", hfield[0], hfield[1],
			       yappc ? ctime((time_t *) & file_time) :
			       " ");

			if (yappc) {
				struct stat sb;

				if (!fstat(*filefd, &sb) && sb.st_size)
					Send_RS(sb.st_size);
				else
					Send_RT();
			} else {
				Send_RF();
			}

			state = STATE_RD;
			break;
		}

		if (buffer[0] == ENQ && buffer[1] == 0x01) {
			break;
		}

		if (buffer[0] == EOT && buffer[1] == 0x01) {
			Send_AT();
			Write_Status("RcvEOT");
			return FALSE;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;

	case STATE_RD:
		if (buffer[0] == STX) {
			length = buffer[1];
			if (length == 0)
				length = 256;
			total += length;
			sprintf(Message, "RcvData  %5d bytes received",
				total);
			Write_Status(Message);

			if (yappc) {
				int i;
				unsigned char checksum = 0;

				for (i = 0; i < length; i++)
					checksum += buffer[i + 2];

				if (checksum != buffer[length + 2]) {
					Send_CN("Bad Checksum");
					Write_Status
					    ("SndABORT: Bad Checksum");
					return FALSE;
				}
			}

			write(*filefd, buffer + 2, length);
			break;
		}

		if (buffer[0] == ETX && buffer[1] == 0x01) {
			Send_AF();
			Write_Status("RcvEof");
			state = STATE_RH;
			close(*filefd);
			*filefd = -1;
			break;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;
	}

	return TRUE;
}

static void yapp_download(int filefd)
{
	struct timeval timeval;
	fd_set sock_read;
	int n;
	int buflen = 0;
	int length;
	int used;
	unsigned char buffer[1024];

	Write_Status("RcvWait");

	state = STATE_R;
	total = 0;
	yappc = 0;

	while (TRUE) {
		FD_ZERO(&sock_read);
		FD_SET(STDIN_FILENO, &sock_read);
		FD_SET(fd, &sock_read);

		timeval.tv_usec = 0;
		timeval.tv_sec = TIMEOUT;

		n = select(fd + 1, &sock_read, NULL, NULL, &timeval);

		if (n == -1) {
			if (!interrupted && errno == EAGAIN)
				continue;
			if (!interrupted)
				perror("select");
			Send_CN("Internal error");
			Write_Status("SndABORT");
			return;
		}

		if (n == 0) {	/* Timeout */
			Send_CN("Timeout");
			Write_Status("SndABORT");
			return;
		}

		if (FD_ISSET(STDIN_FILENO, &sock_read)) {
			Send_CN("Cancelled by user");
			Write_Status("SndABORT");
			return;
		}

		if (FD_ISSET(fd, &sock_read)) {
			length = read(fd, buffer + buflen, 511);
			if (length > 0) {
				buflen += length;

				do {
					used = FALSE;

					switch (buffer[0]) {
					case ACK:
					case ENQ:
					case ETX:
					case EOT:
						if (buflen >= 2) {
							if (!yapp_download_data(&filefd, buffer))
								return;
							buflen -= 2;
							memcpy(buffer,
							       buffer + 2,
							       buflen);
							used = TRUE;
						}
						break;
					default:
						length = buffer[1];
						if (length == 0)
							length = 256;
						if (buffer[0] == STX)
							length += yappc;
						if (buflen >= (length + 2)) {
							if (!yapp_download_data(&filefd, buffer))
								return;
							buflen -=
							    length + 2;
							memcpy(buffer,
							       buffer +
							       length + 2,
							       buflen);
							used = TRUE;
						}
						break;
					}
				}
				while (used);
			}
		}
	}
}

static int yapp_upload_data(int filefd, char *filename, int filelength,
			    unsigned char *buffer)
{
	char Message[80];

	if (buffer[0] == CAN || buffer[0] == NAK) {
		Write_Status("RcvABORT");
		return FALSE;
	}

	switch (state) {
	case STATE_S:
		if (buffer[0] == ACK && buffer[1] == 0x01) {
			Write_Status("SendHeader");
			Send_HD(filename, filelength);
			state = STATE_SH;
			break;
		}

		if (buffer[0] == ACK && buffer[1] == 0x02) {
			sprintf(Message, "SendData  %5d bytes transmitted",
				total);
			Write_Status(Message);
			outlen = read(filefd, outbuffer, readlen);
			outbufptr = 0;

			if (outlen)
				Send_DT(outlen);

			if (yappc) {
				outbuffer[outlen] =
				    checksum(outbuffer, outlen);
				outlen++;
			}

			state = STATE_SD;
			break;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;

	case STATE_SH:
		/* Could get three replies here:
		 * ACK 02 : normal acknowledge.
		 * ACK ACK: yappc acknowledge.
		 * NAK ...: resume request.
		 */
		if (buffer[0] == NAK && buffer[2] == 'R') {
			int len;
			off_t rpos;

			len = buffer[1];
			if (buffer[len] == 'C')
				yappc = 1;
			rpos = atol((char *)buffer + 4);
			lseek(filefd, rpos, SEEK_SET);
			buffer[0] = ACK;
			buffer[1] = yappc ? ACK : 0x02;
		}

		if (buffer[0] == ACK &&
		    (buffer[1] == 0x02 || buffer[1] == ACK)) {
			if (buffer[1] == ACK)
				yappc = 1;

			sprintf(Message, "SendData  %5d bytes transmitted",
				total);
			Write_Status(Message);
			outlen = read(filefd, outbuffer, readlen);
			outbufptr = 0;
			if (outlen)
				Send_DT(outlen);
			state = STATE_SD;

			if (yappc) {
				outbuffer[outlen] =
				    checksum(outbuffer, outlen);
				outlen++;
			}
			break;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;

	case STATE_SD:
		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;

	case STATE_SE:
		if (buffer[0] == ACK && buffer[1] == 0x03) {
			Write_Status("SendEOT");
			Send_ET();
			state = STATE_ST;
			break;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;

	case STATE_ST:
		if (buffer[0] == ACK && buffer[1] == 0x04) {
			return FALSE;
		}

		Send_CN("Unknown code");
		Write_Status("SndABORT");
		return FALSE;
	}

	return TRUE;
}

static void yapp_upload(int filefd, char *filename, long filelength)
{
	struct timeval timeval;
	fd_set sock_read;
	fd_set sock_write;
	int n;
	unsigned char buffer[1024];
	int buflen = 0;
	int length;
	int used;
	char Message[80];

	Write_Status("SendInit");

	readlen = (paclen - 2 > 253) ? 253 : paclen - 2;
	state = STATE_S;
	total = 0;
	yappc = 0;

	Send_SI();

	while (TRUE) {
		FD_ZERO(&sock_read);
		FD_ZERO(&sock_write);
		FD_SET(STDIN_FILENO, &sock_read);
		FD_SET(fd, &sock_read);

		if (state == STATE_SD) {
			FD_SET(fd, &sock_write);

			n = select(fd + 1, &sock_read, &sock_write, NULL,
				   NULL);
		} else {
			timeval.tv_usec = 0;
			timeval.tv_sec = TIMEOUT;

			n = select(fd + 1, &sock_read, NULL, NULL,
				   &timeval);
		}

		if (n == -1) {
			if (!interrupted && errno == EAGAIN)
				continue;
			if (!interrupted)
				perror("select");
			Write_Status("SndABORT");
			Send_CN("Internal error");
			return;
		}

		if (n == 0) {	/* Timeout, not STATE_SD */
			Write_Status("SndABORT");
			Send_CN("Timeout");
			return;
		}

		if (FD_ISSET(STDIN_FILENO, &sock_read)) {
			Write_Status("SndABORT");
			Send_CN("Cancelled by user");
			return;
		}

		if (FD_ISSET(fd, &sock_write)) {	/* Writable, only STATE_SD */
			if (outlen > 0) {
				n = write(fd, outbuffer + outbufptr, outlen);
				if (n > 0) {
					outbufptr += n;
					outlen -= n;
					total += n;
				}
			}

			if (outlen == 0) {
				total -= yappc;
				if ((outlen =
				     read(filefd, outbuffer,
					  readlen)) > 0) {
					sprintf(Message,
						"SendData  %5d bytes transmitted",
						total);
					Write_Status(Message);

					outbufptr = 0;
					Send_DT(outlen);

					if (yappc) {
						outbuffer[outlen] =
						    checksum(outbuffer,
							     outlen);
						outlen++;
					}
				} else {
					Write_Status("SendEof");
					state = STATE_SE;
					Send_EF();
				}
			}
		}

		if (FD_ISSET(fd, &sock_read)) {
			length = read(fd, buffer + buflen, 511);
			if (length > 0) {
				buflen += length;

				do {
					used = FALSE;

					switch (buffer[0]) {
					case ACK:
					case ENQ:
					case ETX:
					case EOT:
						if (buflen >= 2) {
							if (!yapp_upload_data(filefd, filename, filelength, buffer))
								return;
							buflen -= 2;
							memcpy(buffer,
							       buffer + 2,
							       buflen);
							used = TRUE;
						}
						break;
					default:
						length = buffer[1];
						if (length == 0)
							length = 256;
						if (buflen >= (length + 2)) {
							if (!yapp_upload_data(filefd, filename, filelength, buffer))
								return;
							buflen -=
							    length + 2;
							memcpy(buffer,
							       buffer +
							       length + 2,
							       buflen);
							used = TRUE;
						}
						break;
					}
				}
				while (used);
			}
		}
	}
}

void cmd_yapp(char *buf, int bytes)
{
	int filefd;
	long size = 0L;
	char *t;

	if (bytes == 0)
		return;

	switch (buf[0]) {
	case 'U':
	case 'u':
		t = strchr(buf, '\n');
		if (t != NULL)
			*t = '\0';
		t = buf + 2;
		while (*t != '\0' && isspace(*t))
			t++;
		if (*t == '\0') {
			printf
			    ("\n[YAPP Upload requires a filename - eg ~yu hello.txt]\n");
			Send_NR("No filename");
			return;
		}
		filefd = open(t, O_RDONLY);
		if (filefd == -1) {
			printf("\n[Unable to open upload file]\n");
			Send_NR("Invalid filename");
			return;
		}
		if (lseek(filefd, 0L, SEEK_END) != -1)
			size = lseek(filefd, 0L, SEEK_CUR);
		lseek(filefd, 0L, SEEK_SET);
		if (size != -1)
			printf
			    ("\n[Uploading %ld bytes from %s using YAPP]\n",
			     size, t);
		else
			printf("\n[Uploading from %s using YAPP]\n", t);
		yapp_upload(filefd, t, size);
		close(filefd);
		printf("[Finished YAPP Upload, %d bytes Transmitted]\n",
		       total);
		break;

	case 'D':
	case 'd':
		t = strchr(buf, '\n');
		if (t != NULL)
			*t = '\0';
		t = buf + 2;
		while (*t != '\0' && isspace(*t))
			t++;
		if (*t == '\0')
			filefd = -1;
		else {filefd = open(t, O_RDWR | O_APPEND | O_CREAT, 0666);
			if (filefd == -1) {
				printf("\n[Unable to open %s]\n", buf + 2);
				Send_NR("Invalid filename");
				return;
			}
		}
		printf("\n[Downloading using YAPP]\n");
		yapp_download(filefd);
		close(filefd);
		printf("[Finished YAPP Download, %d bytes received]\n",
		       total);
		break;

	default:
		printf("\nUnknown '~y' escape. Type ~h for a list.\n");
		break;
	}
}
