/* 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.
*/
/*
* Yapp C and Resume support added by S N Henson.
*/
/*
* Slightly modified for use with MFJTerm by Mats Petersson.
*/
#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 <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <curses.h>
#include <ncinput.h>
#include "mfjterm.h"
#include "yapp.h"
#include "dostime.h"
#include "misc.h"
#include "timefuncs.h"
struct termios yapp_settings, yapp_old_settings;
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 */
int paclen = 128;
WINDOW *yappwin;
static void Write_Status(char *s)
{
char tmp[80];
wmove(yappwin, 2, 3);
sprintf(tmp, "State: %-60s", s);
waddstr(yappwin, tmp);
wrefresh(yappwin);
}
static void Send_RR(void)
{
char buffer[2];
buffer[0] = ACK;
buffer[1] = 0x01;
write(tnc, buffer, 2);
}
static void Send_RF(void)
{
char buffer[2];
buffer[0] = ACK;
buffer[1] = 0x02;
write(tnc, buffer, 2);
}
static void Send_RT(void)
{
char buffer[2];
buffer[0] = ACK;
buffer[1] = ACK;
write(tnc, buffer, 2);
}
static void Send_AF(void)
{
char buffer[2];
buffer[0] = ACK;
buffer[1] = 0x03;
write(tnc, buffer, 2);
}
static void Send_AT(void)
{
char buffer[2];
buffer[0] = ACK;
buffer[1] = 0x04;
write(tnc, buffer, 2);
}
static void Send_NR(char *reason)
{
char buffer[257];
int length;
if ((length = strlen(reason)) > 255)
length = 255;
buffer[0] = NAK;
buffer[1] = length;
memcpy(buffer + 2, reason, length);
write(tnc, 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(tnc, buffer, len + 2);
}
static void Send_SI(void)
{
char buffer[2];
buffer[0] = ENQ;
buffer[1] = 0x01;
write(tnc, buffer, 2);
}
static void Send_CN(char *reason)
{
char buffer[257];
int length;
if ((length = strlen(reason)) > 255)
length = 255;
buffer[0] = CAN;
buffer[1] = length;
memcpy(buffer + 2, reason, length);
write(tnc, buffer, length + 2);
}
static void Send_HD(char *filename, long length)
{
char buffer[257];
char size_buffer[10];
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(tnc, buffer, len + 2);
}
static void Send_ET(void)
{
char buffer[2];
buffer[0] = EOT;
buffer[1] = 0x01;
write(tnc, buffer, 2);
}
static void Send_DT(int length)
{
char buffer[2];
if (length > 255) length = 0;
buffer[0] = STX;
buffer[1] = length;
write(tnc, buffer, 2);
}
static void Send_EF(void)
{
char buffer[2];
buffer[0] = ETX;
buffer[1] = 0x01;
write(tnc, 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], tmp[80], tstr[80];
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 */
char *hptr, *hfield[3];
if ((length = buffer[1]) == 0) length = 256;
hptr = (char *)buffer + 2;
while (length > 0) {
int hlen;
hlen = strlen(hptr) + 1;
hfield[(int)yappc++] = hptr;
hptr += hlen;
length -= hlen;
}
if (yappc < 3)
yappc = 0;
else {
file_time = yapp2unix(hfield[2]);
yappc = 1;
}
if (*filefd == -1) {
if ((*filefd = open(hfield[0], O_RDWR | O_APPEND | O_CREAT, 0666)) == -1) {
wmove(yappwin, 1, 3);
sprintf(tmp, "Unable to open %s", hfield[0]);
waddstr(yappwin, tmp);
wrefresh(yappwin);
Send_NR("Invalid filename");
return(FALSE);
}
}
wmove(yappwin, 1, 3);
strcpy(tstr, yappc ? ctime((time_t *)&file_time) : " ");
if(tstr[strlen(tstr) - 1] == '\n')
tstr[strlen(tstr) - 1] = 0;
sprintf(tmp, "Receiving %s %s %s", hfield[0], hfield[1],
tstr);
waddstr(yappwin, tmp);
wrefresh(yappwin);
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) {
if ((length = buffer[1]) == 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;
int c;
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(tnc, &sock_read);
timeval.tv_usec = 0;
timeval.tv_sec = TIMEOUT;
n = select(tnc + 1, &sock_read, NULL, NULL, &timeval);
if (n == -1) {
if (errno == EAGAIN)
continue;
wmove(yappwin, 1, 3);
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)) {
c = wgetch(yappwin);
if((c == 'a') || (c == 'A')) {
Send_CN("Cancelled by user");
Write_Status("SndABORT");
return;
}
}
if (FD_ISSET(tnc, &sock_read)) {
if ((length = read(tnc, buffer + buflen, 511)) > 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:
if ((length = buffer[1]) == 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);
}
}
if(chk_time(0))
wrefresh(yappwin);
}
}
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;
int c;
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(tnc, &sock_read);
if (state == STATE_SD) {
FD_SET(tnc, &sock_write);
n = select(tnc + 1, &sock_read, &sock_write, NULL, NULL);
} else {
timeval.tv_usec = 0;
timeval.tv_sec = TIMEOUT;
n = select(tnc + 1, &sock_read, NULL, NULL, &timeval);
}
if (n == -1) {
if (errno == EAGAIN)
continue;
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)) {
c = wgetch(yappwin);
if((c == 'a') || (c == 'A')) {
Write_Status("SndABORT");
Send_CN("Cancelled by user");
return;
}
}
if (FD_ISSET(tnc, &sock_write)) { /* Writable, only STATE_SD */
if (outlen > 0) {
if ((n = write(tnc, outbuffer + outbufptr, outlen)) > 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(tnc, &sock_read)) {
if ((length = read(tnc, buffer + buflen, 511)) > 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:
if ((length = buffer[1]) == 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);
}
}
if(chk_time(0))
wrefresh(yappwin);
}
}
static void transp_mode(void)
{
write(tnc, "\003", 1);
usleep(500000);
write(tnc, "t\r", 2);
}
static void exit_transp_mode(void)
{
tcsendbreak(tnc, 0);
write(tnc, "k\r", 2);
}
static void raw_tty(void)
{
tcgetattr(tnc, &yapp_old_settings);
tcgetattr(tnc, &yapp_settings);
yapp_settings.c_iflag = 0;
yapp_settings.c_lflag = 0;
yapp_settings.c_oflag = 0;
tcsetattr(tnc, TCSAFLUSH, &yapp_settings);
}
static void rest_tty(void)
{
tcsetattr(tnc, TCSAFLUSH, &yapp_old_settings);
}
void yapp(int mode)
{
int filefd;
long size = 0L;
char *modestr[2] = {"Download", "Upload"}, tmp[80];
static char fname[51];
attrset(A_REVERSE);
move(19, 1);
sprintf(tmp, "YAPP %s filename: ", modestr[mode]);
addstr(tmp);
edgets(stdscr, fname, 50, FALSE);
curs_set(0);
move(19, 1);
addstr(" ");
refresh();
yappwin = newwin(4, 76, 9, 2);
keypad(yappwin, TRUE);
wbkgd(yappwin, A_REVERSE);
werase(yappwin);
box(yappwin, 0, 0);
wrefresh(yappwin);
transp_mode();
raw_tty();
switch (mode) {
case YAPP_DL:
if(! strlen(fname))
filefd = -1;
else if ((filefd = open(fname, O_RDWR | O_APPEND | O_CREAT, 0666)) == -1) {
wmove(yappwin, 1, 3);
sprintf(tmp, "Unable to open %s", fname);
waddstr(yappwin, tmp);
wrefresh(yappwin);
Send_NR("Invalid filename");
break;
}
wmove(yappwin, 1, 3);
waddstr(yappwin, "Downloading using YAPP");
wrefresh(yappwin);
yapp_download(filefd);
close(filefd);
wmove(yappwin, 1, 3);
sprintf(tmp, "Finished YAPP Download, %d bytes received ",
total);
waddstr(yappwin, tmp);
wrefresh(yappwin);
break;
case YAPP_UL:
if ((filefd = open(fname, O_RDONLY)) == -1) {
wmove(yappwin, 1, 3);
sprintf(tmp, "Unable to open '%s'", fname);
waddstr(yappwin, tmp);
wrefresh(yappwin);
Send_NR("Invalid filename");
break;
}
if (lseek(filefd, 0L, SEEK_END) != -1)
size = lseek(filefd, 0L, SEEK_CUR);
lseek(filefd, 0L, SEEK_SET);
wmove(yappwin, 1, 3);
if (size != -1)
sprintf(tmp, "Uploading %ld bytes from '%s' using YAPP", size, fname);
else
sprintf(tmp, "Uploading from '%s' using YAPP", fname);
waddstr(yappwin, tmp);
wrefresh(yappwin);
yapp_upload(filefd, fname, size);
close(filefd);
wmove(yappwin, 1, 3);
sprintf(tmp, "Finished YAPP Upload, %d bytes Transmitted ",
total);
waddstr(yappwin, tmp);
wrefresh(yappwin);
break;
}
sleep(2);
delwin(yappwin);
refresh();
rest_tty();
exit_transp_mode();
curs_set(1);
move(tnc_y, tnc_x);
old_min = 0xffff;
}