/* Routines common to both the FTP client and server
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by G1EMM */
/* Mods by KO4KS */
#include "global.h"
#include "mbuf.h"
#include "socket.h"
#include "ftp.h"
#include "ftpcli.h"
#include "usock.h"
#include "md5.h"


#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: ftpsubr.c,v 1.12 1996/12/23 22:44:37 root Exp $";
#endif

#define	MD5BLOCK	64	/* Preferred MD5 block size */

int md5hash (FILE * fp, char hash[16], int ascii);
static void hashit (int hash, long total, long *hmark, int sent);


static void
hashit (hash, total, hmark, sent)
int hash;
long total, *hmark;
int sent;
{
	while (hash && total >= (*hmark) + 1000) {
		if (hash == V_HASH)
			tputc ('#');
		if (hash == V_BYTE)
			tprintf ("Bytes %s : %ld\r", (sent) ? "sent" : "received", total);
		*hmark += 1000;
	}

}


/* Send a file (opened by caller) on a network socket.
 * Normal return: count of bytes sent
 * Error return: -1
 * re-written by KO4KS to (1) reduce code size (2) all binary compression
 */
long
sendfile (fp, s, mode, hash)
register FILE *fp;		/* File to be sent */
register int s;			/* Socket to be sent on */
int mode;			/* Transfer mode */
int hash;			/* Print hash marks every BLKSIZE bytes */
{
register int c;
register long total = 0;
long hmark = 0;
int oldf = 0;
struct usock *up;
register struct mbuf *bp;

	if (mode == ASCII_TYPE) {
		oldf = setflush (s, -1);
		/* Let the newline mapping code in usputc() do the work */
		(void) sockmode (s, SOCK_ASCII);
	} else
		(void) sockmode (s, SOCK_BINARY);
	up = itop (s);
	if (mode != ASCII_TYPE && up->zout == NULLLZW) {	/* also, not lzw compressed */
		for (;;) {
			kwait (0);	/* be a good neighbor and share! */
			bp = ambufw (BLKSIZE);
			if ((bp->cnt = (int16) fread (bp->data, 1, BLKSIZE, fp)) == 0) {
				free_p (bp);
				break;
			}
			kwait (0);	/* be a good neighbor and share! */
			total += bp->cnt;
			if (send_mbuf (s, bp, 0, NULLCHAR, 0) == -1) {
				total = -1;
				break;
			}
			hashit (hash, total, &hmark, 1);
		}
	} else {

		/* At this point, it is either ASCII_TYPE, or LZW compressed */
		while ((c = getc (fp)) != EOF) {
			if (mode == ASCII_TYPE) {
#ifndef TNOS_68K
#if !defined(UNIX) && !defined(__TURBOC__)
				if (c == '\r') {
					/* Needed only if the OS uses a CR/LF
					 * convention and getc doesn't do
					 * an automatic translation
					 */
					continue;
				}
#endif
#else
				if (c == '\l')
					continue;
#endif
				if (!c)
					continue;
			}
			if (usputc (s, uchar (c)) == -1) {
				total = -1;
				break;
			}
			total++;
			hashit (hash, total, &hmark, 1);
			if (!(total % 256))
				kwait (0);	/* be a good neighbor and share! */
		}
	}
	if (mode == ASCII_TYPE) {
		usflush (s);
		(void) setflush (s, oldf);
	}
	if (hash)
		tputc ('\n');
	return total;
}


/* Receive a file (opened by caller) from a network socket.
 * Normal return: count of bytes received
 * Error return: -1
 * re-written by KO4KS to (1) reduce code size (2) all binary compression
 */
long
recvfile (fp, s, mode, hash)
register FILE *fp;
register int s;
int mode;
int hash;
{
register int c;
register long total = 0;
long hmark = 0;
int cnt;
struct usock *up;
struct mbuf *bp;

	if (mode == ASCII_TYPE)
		(void) sockmode (s, SOCK_ASCII);
	else
		(void) sockmode (s, SOCK_BINARY);
	up = itop (s);
	if (mode != ASCII_TYPE && up->zout == NULLLZW) {	/* also, not lzw compressed */
		while ((cnt = recv_mbuf (s, &bp, 0, NULLCHAR, 0)) != 0) {
			kwait (0);	/* be a good neighbor and share! */
			if (cnt == -1) {
				total = -1;
				break;
			}
			total += cnt;
			hashit (hash, total, &hmark, 0);
			kwait (0);	/* be a good neighbor and share! */
			if (fp != NULLFILE) {
				if (write_p (fp, bp) == -1) {
					free_p (bp);
					total = -1;
					break;
				}
				free_p (bp);
			} else
				(void) send_mbuf (Curproc->output, bp, 0, NULLCHAR, 0);
			kwait (0);	/* be a good neighbor and share! */
		}
	} else {
		while ((c = recvchar (s)) != EOF) {
			if (fp != NULLFILE) {
#if !defined(UNIX) && !defined(__TURBOC__)
				if (mode == ASCII_TYPE) {
					if (c == '\n') {
						/* Needed only if the OS uses a CR/LF
						 * convention and putc doesn't do
						 * an automatic translation
						 */
						(void) putc ('\r', fp);
					}
				}
#endif
				if (putc (c, fp) == EOF) {
					total = -1;
					break;
				}
			} else
				tputc (uchar (c));
			total++;
			hashit (hash, total, &hmark, 0);
			if (!(total % 256))
				kwait (0);	/* be a good neighbor and share! */
		}
	}
	/* Detect an abnormal close */
	if (socklen (s, 0) == -1)
		total = -1;
	if (hash)
		tputc ('\n');
	return total;
}


/* Determine if a file appears to be binary (i.e., non-text).
 * Return 1 if binary, 0 if ascii text after rewinding the file pointer.
 *
 * Used by FTP to warn users when transferring a binary file in text mode.
 */
int
isbinary (fp)
FILE *fp;
{
int c, i;
int rval;

	rval = 0;
	for (i = 0; i < 512; i++) {
		if ((c = getc (fp)) == EOF)
			break;
		if (c & 0x80) {
			/* High bit is set, probably not text */
			rval = 1;
			break;
		}
	}
	/* Assume it was at beginning */
	fseek (fp, 0L, SEEK_SET);
	return rval;
}


unsigned long
getsize (fp)
FILE *fp;
{
	fseek (fp, 0L, SEEK_END);	/* Go to end of file */
	return (unsigned long) ftell (fp);	/* Return file pointer position */
}


/* Compute checksum of the first n bytes */
unsigned long
checksum (fp, n)
FILE *fp;
long n;
{
unsigned long sum;
long i;
int c;

	rewind (fp);
	sum = 0;
	for (i = 1; i <= n; i++) {
		if ((c = fgetc (fp)) == EOF)
			break;
		sum += (unsigned long) c;
	}
	return sum;
}


/* Compute MD5 hash of local file */
int
md5hash (fp, hash, ascii)
FILE *fp;
char hash[16];
int ascii;
{
MD5_CTX md;
char *buf;
int len;

	MD5Init (&md);

	if (ascii) {
		/* Text file, hash canonical form (newlines = crlf) */
		char *cp;
		int c;

		cp = buf = mallocw (MD5BLOCK);
		len = 0;
		while ((c = fgetc (fp)) != EOF) {
			if (c == '\n') {
				*cp++ = '\r';
				if (++len == MD5BLOCK) {
					MD5Update (&md, (unsigned char *) buf, (unsigned int) len);
					kwait (NULL);
					cp = buf;
					len = 0;
				}
			}
			*cp++ = (char) c;
			if (++len == MD5BLOCK) {
				MD5Update (&md, (unsigned char *) buf, (unsigned int) len);
				kwait (NULL);
				cp = buf;
				len = 0;
			}
		}
		if (len != 0)
			MD5Update (&md, (unsigned char *) buf, (unsigned int) len);
		free (buf);
	} else {
		buf = mallocw (BUFSIZ);
		while ((len = (int) fread (buf, 1, BUFSIZ, fp)) != 0) {
			MD5Update (&md, (unsigned char *) buf, (unsigned int) len);
			kwait (NULL);
		}
		free (buf);
	}
	MD5Final (&md);
	memcpy (hash, md.digest, 16);
	return 0;
}
