/* ftp.c */

#include "sys.h"
#include <sys/types.h>
#include <sys/param.h>
#include <setjmp.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/file.h>

#ifdef SYSLOG
#	include <syslog.h>
#endif

/* You may need this for declarations of fd_set, etc. */
#ifdef SYSSELECTH
#   include <sys/select.h>
#else
#ifndef linux
extern int select (int, void *, void *, void *, struct timeval *);
#endif /* linux */
#endif /* SYSSELECTH */

#ifndef NO_UNISTDH		/* for prototypes only. */
#	include <unistd.h>
#endif

#ifdef TERM
#       include <client.h>
#endif /* TERM */

#include <netinet/in.h>
#include <arpa/ftp.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <pwd.h>
#include <ctype.h>
#include "ftpdefs.h"
#include "defaults.h"
#include "ftp.h"
#include "cmds.h"
#include "main.h"
#include "ftprc.h"
#include "getpass.h"
#include "copyright.h"

#ifdef TERM
#include <client.h>
extern int lcompression, rcompression;
static int compress_toggle = 1;

int toggle_compress()
{
  compress_toggle = 1 - compress_toggle;

  printf("data compression is now o%s\n", compress_toggle ? "n" : "ff");
}
#endif /* TERM */

/* ftp.c globals */
struct				sockaddr_in hisctladdr;
struct				sockaddr_in data_addr;
int					data = -1;
int					abrtflag = 0;
int					connected;			/* connected to server */
struct sockaddr_in	myctladdr;
FILE				*cin = NULL, *cout = NULL;
char				*reply_string = NULL;
jmp_buf				sendabort, recvabort;
int					progress_meter = 1;
int					cur_progress_meter;
int					sendport = -1;		/* use PORT cmd for each data connection */
int					code;				/* return/reply code for ftp command */
string				hostname;			/* name of host connected to */
int     			cpend;				/* flag: if != 0, then pending server reply */
char				*xferbuf;			/* buffer for local and remote I/O */
size_t				xferbufsize;		/* size in bytes, of the transfer buffer. */
long				next_report;
long				bytes;
long				now_sec;
long				file_size;
struct timeval		start, stop;

/* ftp.c externs */
extern FILE					*logf;
extern string				hostname, cwd, anon_password;
extern int					verbose, debug, macnum, margc;
extern int					curtype, creating;
extern int					options, activemcmd, paging;
extern int					ansi_escapes;
extern char					*line, *margv[];
extern char					*tcap_normal, *tcap_boldface;
extern char					*tcap_underline, *tcap_reverse;
extern struct userinfo		uinfo;
extern struct macel			macros[];

#ifdef REDIR
extern struct lslist		*lshead, *lstail;
extern int					is_ls;
#endif

#define UPDATE_100 2


int hookup(char *host, int port)
{
	register struct hostent *hp = 0;
	int s, len, hErr = -1;

#ifdef TERM
	lcompression = rcompression = compress_toggle;
	if (verbose >= V_TERSE)
	  fprintf(stderr, "Opening term connection to %s\n", host);

	if ((s = connect_server(0)) < 0) {
	  perror("couldn't connect to term server ");
	  return(-10);		/* fatal error */
	}
	if (send_command(s, C_PORT, 0, "%s:%d", host, port) < 0) {
	  fprintf(stderr, "Open failed: %s.\n", command_result);
	  return(-2);
	}			/* unrecoverable error */
	if (send_command(s, C_COMPRESS, 1, "n") <0) {
	  fprintf(stderr, "Error trying to turn off compression: %s.\n",command_result);
	}
	send_command(s, C_DUMB, 1, 0);
#else /* TERM */
	bzero((char *)&hisctladdr, sizeof (hisctladdr));
#ifdef BAD_INETADDR
	hisctladdr.sin_addr = inet_addr(host);
#else
 	hisctladdr.sin_addr.s_addr = inet_addr(host);
#endif /* BAD_INETADDR */
	if (hisctladdr.sin_addr.s_addr != -1) {
		hisctladdr.sin_family = AF_INET;
		(void) Strncpy(hostname, host);
	} else {
		hp = gethostbyname(host);
		if (hp == NULL) {
#ifdef HERROR
			extern int h_errno;
			if (h_errno == HOST_NOT_FOUND)
				(void) printf("%s: unknown host\n", host);
			else (void) fprintf(stderr, "%s: gethostbyname herror (%d):  ",
				host, h_errno);
			herror(NULL);
#else /* HERROR */
			(void) printf("%s: unknown host\n", host);
#endif /* HERROR */
			goto done;
		}
		hisctladdr.sin_family = hp->h_addrtype;
		bcopy(hp->h_addr_list[0],
		    (caddr_t)&hisctladdr.sin_addr, hp->h_length);
		(void) Strncpy(hostname, hp->h_name);
	}
	s = socket(hisctladdr.sin_family, SOCK_STREAM, 0);
	if (s < 0) {
		Perror("socket");
		goto done;
	}
	hisctladdr.sin_port = port;
	while (connect(s, (struct sockaddr *) &hisctladdr, (int) sizeof (hisctladdr)) < 0) {
		if (hp && hp->h_addr_list[1]) {
			int oerrno = errno;

			(void) fprintf(stderr, "NcFTP: connect error (%d) to address %s: ",
				errno, inet_ntoa(hisctladdr.sin_addr));
			errno = oerrno;
			Perror((char *) 0);
			hp->h_addr_list++;
			bcopy(hp->h_addr_list[0],
			     (caddr_t)&hisctladdr.sin_addr, hp->h_length);
			(void) fprintf(stdout, "Trying %s...\n",
				inet_ntoa(hisctladdr.sin_addr));
			(void) close(s);
			s = socket(hisctladdr.sin_family, SOCK_STREAM, 0);
			if (s < 0) {
				Perror("socket");
				goto done;
			}
			continue;
		}
		Perror("connect");
		switch (errno) {
			case ENETDOWN:
			case ENETUNREACH:
			case ECONNABORTED:
			case ETIMEDOUT:
			case ECONNREFUSED:
			case EHOSTDOWN:
				hErr = -2;	/* we can re-try later. */
		}
		goto bad;
	}
	len = sizeof (myctladdr);
	if (getsockname(s, (char *)&myctladdr, &len) < 0) {
		Perror("getsockname");
		goto bad;
	}
#endif /* TERM */
	cin = fdopen(s, "r");
	cout = fdopen(s, "w");
	if (cin == NULL || cout == NULL) {
		(void) fprintf(stderr, "ftp: fdopen failed.\n");
		close_streams(0);
		goto bad;
	}
	if (IS_VVERBOSE)
		(void) printf("Connected to %s.\n", hostname);
	if (getreply(0) > 2) { 	/* read startup message from server */
		close_streams(0);
		goto bad;
	}
#ifdef SO_OOBINLINE
	{
	int on = 1;

	if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (char *) &on, sizeof(on))
		< 0 && debug) {
			Perror("setsockopt");
		}
      }
#endif /* SO_OOBINLINE */

	hErr = 0;
	goto done;

bad:
	(void) close(s);
done:
	code = hErr;
	return (hErr);
}	/* hookup */




int login(char *host, int openmode, int ignore_rc)
{
	string				tmp, str;
	char				*username, *pass, *acct;
	int					n, aflag = 0, prompted_login;
	int					user_in_rc, tmpverbose;

	username = pass = acct = NULL;
	if (!ignore_rc) {
		if ((ruserpass2(host, &username, &pass, &acct)) < 0) {
			/*NOTREACHED */
			code = -1;
			return(0);
		}
	}

	user_in_rc = 1;
	prompted_login = 0;
	if (username == NULL) {
		user_in_rc = 0;
		prompted_login = 1;
	} 

	if (!user_in_rc) {
		/* There was no username in the rc. */
		if (openmode == OPEN_A) {
			username = "anonymous";
			prompted_login = 0;
		} else { /* openmode == OPEN_U */
			/* Prompt for a username. */
			(void) printf("Name (%s:%s): ", host, uinfo.username);
			(void) FGets(tmp, stdin);
			tmp[strlen(tmp) - 1] = '\0';
			/*
			 *	User can hit return if he wants to enter his username
			 *	automatically.
			 */
			if (*tmp == '\0')
				username = uinfo.username;
			else
				username = tmp;
		}
	}
	(void) sprintf(str, "USER %s", username);
	n = command(str);
	if (n == CONTINUE) {
		if (pass == NULL) {
			if (strcmp("anonymous", username) == 0)
				pass = anon_password;
			else
				pass = Getpass("Password:");
		}
		(void) sprintf(str, "PASS %s", pass);
		n = command(str);
	}
	if (n == CONTINUE) {
		aflag++;
		acct = Getpass("Account:");
		(void) sprintf(str, "ACCT %s", acct);
		n = command(str);
	}
	if (n != COMPLETE) {
noLogin:
		(void) fprintf(stderr, "Login failed.\n");
		return (0);
	}
#ifdef SYSLOG
	syslog (LOG_INFO, "%s connected to %s as %s.",
		uinfo.username, host, username);
#endif

	if (!aflag && acct != NULL) {
		(void) sprintf(str, "ACCT %s", acct);
		(void) command(str);
	}

	/* See if remote host dropped connection. */
	tmpverbose = verbose;
	verbose = V_QUIET;
	n = command("NOOP");
	verbose = tmpverbose;
	if (n == 4)
		goto noLogin;

	if (NOT_VQUIET && !prompted_login)
		(void) printf("Logged into %s.\n", host);
	if (!ignore_rc)
		for (n = 0; n < macnum; ++n) {
			if (!strcmp("init", macros[n].mac_name)) {
				(void) strcpy(line, "$init");
				makeargv();
				domacro(margc, margv);
				break;
			}
		}
	return (1);
}	/* login */



/*ARGSUSED*/
void cmdabort(int unused)
{
	(void) printf("\n");
	(void) fflush(stdout);
	abrtflag++;
}	/* cmdabort */




command(char *cmd)
{
	int r;
	void (*oldintr)(int);
	string str;

	abrtflag = 0;
	if (debug) {
		(void) printf("---> \"%s\" (length %lu)\n", cmd, strlen(cmd));
	}
	if (cout == NULL) {
		(void) sprintf(str, "%s: No control connection for command", cmd);
		Perror(str);
		code = -1;
		return (0);
	}
	oldintr = signal(SIGINT, /* cmdabort */ SIG_IGN);
#ifndef SCO324
	if (cout != NULL)
		(void) fprintf(cout, "%s\r\n", cmd);
#else
	{
		/*
		 * The fprintf() above gives me a core-dump in memcpy()...
		 * This does the trick though...
		 */

		char *p = cmd;
		while (*p)
			fputc(*p++, cout);
		fputc('\r', cout);
		fputc('\n', cout);
	}
#endif /* !SCO324 */
	(void) fflush(cout);
	cpend = 1;
	r = getreply(strcmp(cmd, "QUIT") == 0);
	if (abrtflag && oldintr != SIG_IGN && oldintr != NULL)
		(*oldintr)(0);
	(void) signal(SIGINT, oldintr);
	return(r);
}	/* command */




getreply(int expecteof)
{
	register int c, n;
	int dig;
	char *cp, *end, *dp;
	int thiscode, originalcode = 0, continuation = 0;
	void (*oldintr)(int);

	if (cin == NULL)
		return (-1);
	oldintr = signal(SIGINT, /* cmdabort */ SIG_IGN);
	end = reply_string + RECEIVEDLINELEN - 2;
	for (;;) {
		dig = n = code = 0;
		cp = reply_string;
		for (;;) {
			c = getc(cin);
			if (c == IAC) {     /* handle telnet commands */
				switch (c = getc(cin)) {
				case WILL:
				case WONT:
					c = getc(cin);
					(void) fprintf(cout, "%c%c%c",IAC,DONT,c);
					(void) fflush(cout);
					break;
				case DO:
				case DONT:
					c = getc(cin);
					(void) fprintf(cout, "%c%c%c",IAC,WONT,c);
					(void) fflush(cout);
					break;
				default:
					break;
				}
				continue;
			}
			dig++;
			if (c == EOF) {
				if (expecteof) {
					(void) signal(SIGINT,oldintr);
					code = 221;
					return (0);
				}
				lostpeer(0);
				if (NOT_VQUIET) {
					(void) printf("421 Service not available, remote server has closed connection\n");
					(void) fflush(stdout);
				}
				code = 421;
				return(4);
			}
			if (cp < end && c != '\r')
				*cp++ = c;

			if (c == '\n')
				break;
			if (dig < 4 && isdigit(c))
				code = thiscode = code * 10 + (c - '0');
			else if (dig == 4 && c == '-') {
				if (continuation)
					code = 0;
				continuation++;
			}
			if (n == 0)
				n = c;
		}	/* end for(;;) #2 */
		
		*cp = '\0';
		switch (verbose) {
			case V_QUIET:
				/* Don't print anything. */
				break;
			case V_ERRS:
				if (n == '5') {
					dp = reply_string;
					goto stripCode;
				}
				break;	
			case V_IMPLICITCD:
			case V_TERSE:
				dp = NULL;
				if (n == '5' && verbose == V_TERSE)
					dp = reply_string;
				else {
					switch (thiscode) {
						case 230:
						case 214:
						case 332:
							dp = reply_string;
							break;
						case 220:
							/*
							 * Skip the foo FTP server ready line.
							 */
							if (strstr(reply_string, "ready.") == NULL)
								dp = reply_string;
							break;
						case 250:
							/*
							 * Print 250 lines if they aren't
							 * "250 CWD command successful."
							 */
							if (strncmp(reply_string + 4, "CWD ", (size_t) 4))
								dp = reply_string;
					}
				}
				if (dp == NULL) break;			
stripCode:
				/* Try to strip out the code numbers, etc. */
				if (isdigit(*dp++) && isdigit(*dp++) && isdigit(*dp++)) {
					if (*dp == ' ' || *dp == '-') {
						dp++;
						if (*dp == ' ') dp++;
					} else dp = reply_string;			
				} else {
					int spaces;
					dp = reply_string;
					for (spaces = 0; spaces < 4; ++spaces)
						if (dp[spaces] != ' ')
							break;
					if (spaces == 4)
						dp += spaces;
				}					
				goto printLine;
			case V_VERBOSE:
				dp = reply_string;
printLine:		(void) fputs(dp, stdout);
		}	/* end switch */

		if (continuation && code != originalcode) {
			if (originalcode == 0)
				originalcode = code;
			continue;
		}
		if (n != '1')
			cpend = 0;
		(void) signal(SIGINT,oldintr);
		if (code == 421 || originalcode == 421)
			lostpeer(0);
		if (abrtflag && oldintr != cmdabort && oldintr != SIG_IGN && oldintr)
			(*oldintr)(0);
		return (n - '0');
	}	/* end for(;;) #1 */
}	/* getreply */




static int empty(struct fd_set *mask, int sec)
{
	struct timeval t;

	t.tv_sec = (long) sec;
	t.tv_usec = 0;

	return(select(32, mask, NULL, NULL, &t));
}	/* empty */




static void tvsub(struct timeval *tdiff, struct timeval *t1, struct timeval *t0)
{
	tdiff->tv_sec = t1->tv_sec - t0->tv_sec;
	tdiff->tv_usec = t1->tv_usec - t0->tv_usec;
	if (tdiff->tv_usec < 0)
		tdiff->tv_sec--, tdiff->tv_usec += 1000000;
}	/* tvsub */



static int barlen;

int start_progress(int sending, char *local)
{
	long s;
	str32 spec;

	cur_progress_meter = progress_meter;
	if ((cur_progress_meter > pr_kbytes) || (cur_progress_meter < 0))
		cur_progress_meter = pr_percent;
	if ((file_size <= 0) && ((cur_progress_meter == pr_percent) || (cur_progress_meter == pr_philbar)))
		cur_progress_meter = pr_kbytes;
	if (!ansi_escapes && (cur_progress_meter == pr_philbar))
		cur_progress_meter = pr_none;

	(void) gettimeofday(&start, (struct timezone *)0);
	now_sec = start.tv_sec;

	switch (cur_progress_meter) {
		case pr_none:
			break;
		case pr_percent:
			(void) printf("%s:     ", local);
			goto zz;
		case pr_kbytes:
			(void) printf("%s:       ", local);
			goto zz;
		case pr_philbar:
			printf("%s%s file: %s %s\n", 
				tcap_boldface,
				sending ? "Sending" : "Receiving",
				local,
				tcap_normal
			);
			barlen = 64;
			for (s = file_size; s > 0; s /= 10L) barlen--;
			(void) sprintf(spec, "      0 %%%ds %%ld bytes.\r", barlen);
			printf(spec, " ", file_size);
		zz:
			(void) fflush(stdout);
			echo(stdin, 0);
	}	/* end switch */
	return (cur_progress_meter);
}	/* start_progress */




void progress_report(int finish_up)
{
	int i;
	int size;
	str32 spec;

	next_report += xferbufsize;
	(void) gettimeofday(&stop, (struct timezone *)0);
	if ((stop.tv_sec > now_sec) || finish_up) {
		switch (cur_progress_meter) {
			case pr_none:
				break;
			case pr_percent:
				(void) printf("\b\b\b\b%3ld%%", 100L * bytes / file_size);
				(void) fflush(stdout);
				break;
			case pr_philbar:
				size = (int) ((float)barlen * ((float) bytes/file_size));
				(void) sprintf(spec, "%%3ld%%%%  0 %%s%%%ds%%s\r", size);
				(void) printf(
					spec,
					100L * bytes / file_size,
					tcap_reverse,
					" ",
					tcap_normal
				);
				(void) fflush(stdout);
				break;
			case pr_kbytes:
				if ((bytes / 1024) > 0) {
					(void) printf("\b\b\b\b\b\b%5ldK", bytes / 1024);
					(void) fflush(stdout);
				}
		}	/* end switch */
		now_sec = stop.tv_sec;
	}	/* end if we updated */
}	/* progress_report */




void end_progress(char *direction, char *local, char *remote)
{
    struct timeval			td;
    float					s, bs;
    char					*cp, *bsstr;
	string					str;

	if (bytes <= 0)
		return;
	progress_report(1);		/* tell progress proc to cleanup. */

	tvsub(&td, &stop, &start);
	s = td.tv_sec + (td.tv_usec / 1000000.0);
	if (s != 0.0)
		bs = bytes / s;
	if (bs > 1024.0) {
		bs /= 1024.0;
		bsstr = "K/s.\n";
	} else
		bsstr = "Bytes/s.\n";

	if (NOT_VQUIET) switch(cur_progress_meter) {
		case pr_none:
			(void) printf("%s: %ld bytes %s in %.2f seconds, %.2f %s", local, bytes, direction, s, bs, bsstr);
			break;
		case pr_kbytes:
		case pr_percent:
			(void) printf("%s%ld bytes %s in %.2f seconds, %.2f %s",
			cur_progress_meter == pr_kbytes ? "\b\b\b\b\b\b" : "\b\b\b\b",
			bytes, direction, s, bs, bsstr);
			echo(stdin, 1);
			break;
		case pr_philbar:
			printf("\n");
			echo(stdin, 1);
			break;
	}
	
	/* Save transfers to the logfile. */
    if (logf != NULL) {
		/* if a simple path is given, try to log the full path */
		if (rindex(remote, '/') == NULL && cwd != NULL) {
			(void) sprintf(str, "%s/%s", cwd, remote);
			 cp = str;
		} else
			cp = remote;
		(void) fprintf(logf, "\t-> \"%s\" %s, %.2f %s", cp, direction, bs, bsstr);
    } 
#ifdef SYSLOG
	if (direction[0] == 'r')
		syslog (LOG_INFO, "%s %s %s as %s from %s (%ld bytes).",
			uinfo.username, direction, remote, local, hostname, bytes);
	else
		syslog (LOG_INFO, "%s %s %s as %s to %s (%ld bytes).",
			uinfo.username, direction, local, remote, hostname, bytes);
#endif
}   /* end_progress */



void close_file(FILE **fin, int filetype)
{
	if (*fin != NULL) {
		if (filetype == IS_FILE) {
			(void) fclose(*fin);
			*fin = NULL;
		} else if (filetype == IS_PIPE) {
			(void) pclose(*fin);
			*fin = NULL;
		}
	}
}	/* close_file */




/*ARGSUSED*/
void abortsend(int unused)
{
	activemcmd = 0;
	abrtflag = 0;
	(void) printf("\nSend aborted.\n");
	(void) fflush(stdout);
	longjmp(sendabort, 1);
}	/* abortsend */



void sendrequest(char *cmd, char *local, char *remote)
{
	FILE					*fin, *dout = NULL;
	void					(*oldintr)(int), (*oldintp)(int);
	string					str;
	register int			c, d;
	struct stat				st;
	int						filetype;
	int						do_reports = 0;
	char					*mode;
	register char			*bufp;

	oldintr = NULL;
	oldintp = NULL;
	mode = "w";
	bytes = file_size = 0L;
	if (setjmp(sendabort)) {
		while (cpend) {
			(void) getreply(0);
		}
		if (data >= 0) {
			(void) close(data);
			data = -1;
		}
		if (oldintr)
			(void) signal(SIGINT,oldintr);
		if (oldintp)
			(void) signal(SIGPIPE,oldintp);
		code = -1;
		echo(stdin, 1);
		return;
	}
	oldintr = signal(SIGINT, abortsend);
	file_size = -1;
	if (strcmp(local, "-") == 0)  {
		fin = stdin;
		filetype = IS_STREAM;
	} else if (*local == '|') {
		filetype = IS_PIPE;
		oldintp = signal(SIGPIPE,SIG_IGN);
		fin = popen(local + 1, "r");
		if (fin == NULL) {
			Perror(local + 1);
			(void) signal(SIGINT, oldintr);
			(void) signal(SIGPIPE, oldintp);
			code = -1;
			return;
		}
	} else {
		filetype = IS_FILE;
		fin = fopen(local, "r");
		if (fin == NULL) {
			Perror(local);
			(void) signal(SIGINT, oldintr);
			code = -1;
			return;
		}
		if (fstat(fileno(fin), &st) < 0 ||
		    (st.st_mode&S_IFMT) != S_IFREG) {
			(void) fprintf(stdout, "%s: not a plain file.\n", local);
			(void) signal(SIGINT, oldintr);
			(void) fclose(fin);
			code = -1;
			return;
		}
		file_size = st.st_size;
	}
	if (initconn()) {
		(void) signal(SIGINT, oldintr);
		if (oldintp)
			(void) signal(SIGPIPE, oldintp);
		code = -1;
		close_file(&fin, filetype);
		return;
	}
	if (setjmp(sendabort))
		goto Abort;

	if (remote) {
		(void) sprintf(str, "%s %s", cmd, remote);
		if (command(str) != PRELIM) {
			(void) signal(SIGINT, oldintr);
			if (oldintp)
				(void) signal(SIGPIPE, oldintp);
			close_file(&fin, filetype);
			return;
		}
	} else
		if (command(cmd) != PRELIM) {
			(void) signal(SIGINT, oldintr);
			if (oldintp)
				(void) signal(SIGPIPE, oldintp);
			close_file(&fin, filetype);
			return;
		}
	dout = dataconn(mode);
	if (dout == NULL)
		goto Abort;
	(void) gettimeofday(&start, (struct timezone *)0);
	oldintp = signal(SIGPIPE, SIG_IGN);
	if (do_reports = (filetype == IS_FILE && NOT_VQUIET))
		do_reports = start_progress(1, local);

	switch (curtype) {

	case TYPE_I:
	case TYPE_L:
		errno = d = 0;
		while ((c = read(fileno(fin), xferbuf, (int)xferbufsize)) > 0) {
			bytes += c;
			for (bufp = xferbuf; c > 0; c -= d, bufp += d)
				if ((d = write(fileno(dout), bufp, c)) <= 0)
					break;
			/* Print progress indicator. */
			if (do_reports)
				progress_report(0);
		}
		if (c < 0)
			Perror(local);
		if (d <= 0) {
			if (d == 0 && !creating)
				(void) fprintf(stderr, "netout: write returned 0?\n");
			else if (errno != EPIPE) 
				Perror("netout");
			bytes = -1;
		}
		break;

	case TYPE_A:
		next_report = xferbufsize;
		while ((c = getc(fin)) != EOF) {
			if (c == '\n') {
				if (ferror(dout))
					break;
				(void) putc('\r', dout);
				bytes++;
			}
			(void) putc(c, dout);
			bytes++;

			/* Print progress indicator. */
			if (do_reports && bytes > next_report)
				progress_report(0);
		}
		if (ferror(fin))
			Perror(local);
		if (ferror(dout)) {
			if (errno != EPIPE)
				Perror("netout");
			bytes = -1;
		}
		break;
	}
Done:
	close_file(&fin, filetype);
	if (dout)
		(void) fclose(dout);
	(void) getreply(0);
	(void) signal(SIGINT, oldintr);
	if (oldintp)
		(void) signal(SIGPIPE, oldintp);
	end_progress("sent", local, remote);
	return;
Abort:
	code = -1;
	echo(stdin, 1);
	if (!cpend)
		return;
	if (data >= 0) {
		(void) close(data);
		data = -1;
	}
	goto Done;
}	/* sendrequest */



long get_remote_size(char *remote, int filetype)
{
	int oldverbose;
	long rmt_size;
	string str;

	rmt_size = -1;				/*
								 * Return -1 if we could't get it. 
								 * Not all sites support SIZE.
								 */
	
	if (filetype == IS_FILE) {
		/* Won't make sense for a pipe or stream. */
		(void) sprintf(str, "SIZE %s", remote);
		*reply_string = 0;
		oldverbose = verbose;
		verbose = V_QUIET;
		(void) command(str);
		verbose = oldverbose;
		if (*reply_string != 5)		/* 5xx is an error. */
			(void) sscanf(reply_string, "%*d %ld", &rmt_size);	
	}
	return rmt_size;
}	/* get_remote_size */



/*ARGSUSED*/
void abortrecv(int unused)
{
	activemcmd = 0;
	abrtflag = 0;
	(void) printf("(abort)\n");
	(void) fflush(stdout);
	longjmp(recvabort, 1);
}	/* abortrecv */



void recvrequest(char *cmd, char *local, char *remote, char *mode)
{
	FILE						*fout, *din;
	void						(*oldintr)(int), (*oldintp)(int); 
	int							oldverbose, oldtype = 0, is_retr;
	int							tcrflag, nfnd;
	char						msg;
	string						str;
	struct fd_set				mask;
	int							c, d;
	int							filetype, do_reports = 0;
	string						remote_dir;
	char						*cp;
#ifdef REDIR
	char						*linePtr;
	int							nchars;
	string						str2;
#endif

	bytes = 0;
	is_retr = strcmp(cmd, "RETR") == 0;
	oldintr = NULL;
	oldintp = NULL;
	tcrflag = /* !crflag && */ is_retr;

	/*
	 * The ls() function can specify a directory to list along with ls flags,
	 * if it sends the flags first followed by the directory name.
	 *
	 * So far, we don't care about the remote directory being listed.  I put
	 * it now so I won't forget in case I need to do something with it later.
	 */
	remote_dir[0] = 0;
	if (remote != NULL) {
		cp = index(remote, LS_FLAGS_AND_FILE);
		if (cp == NULL)
			(void) Strncpy(remote_dir, remote);
		else {
			*cp++ = ' ';
			(void) Strncpy(remote_dir, cp);
		}
	}

	if (setjmp(recvabort)) {
		echo(stdin, 1);
		while (cpend) {
			(void) getreply(0);
		}
		if (data >= 0) {
			(void) close(data);
			data = -1;
		}
		if (oldintr)
			(void) signal(SIGINT, oldintr);
		code = -1;
		return;
	}
	oldintr = signal(SIGINT, abortrecv);

	if (strcmp(local, "-") == 0)
		filetype = IS_STREAM;
	else if (*local == '|')
		filetype = IS_PIPE;
	else {
		filetype = IS_FILE;  /* is_retr ? IS_FILE : IS_STREAM; */
		if (access(local, 2) < 0) {
			char *dir = rindex(local, '/');

			if (errno != ENOENT && errno != EACCES) {
				Perror(local);
				(void) signal(SIGINT, oldintr);
				code = -1;
				return;
			}
			/* See if we have write permission on this directory. */
			if (dir != NULL) {
				/* Special case: /filename. */
				if (dir != local)
					*dir = 0;
				if (access(dir == local ? "/" : local, 2) < 0) {
					/*
					 *	We have a big long pathname, like /a/b/c/d,
					 *	but see if we can write into the current
					 *	directory and call the file ./d.
					 */
					if (access(".", 2) < 0) {
						(void) strcpy(local, " and .");
						goto noaccess;
					}
					(void) strcpy(local, dir + 1);	/* use simple filename. */
				} else
					*dir = '/';
			} else {
				/* We have a simple path name (file name only). */
				if (access(".", 2) < 0) {
noaccess:			Perror(local);
					(void) signal(SIGINT, oldintr);
					code = -1;
					return;
				}
			}
		}
	}
	if (initconn()) {
		(void) signal(SIGINT, oldintr);
		code = -1;
		return;
	}
	if (!is_retr) {
		if (curtype != TYPE_A) {
			oldtype = curtype;
			oldverbose = verbose;
			if (!debug)
				verbose = V_QUIET;
			setascii(0, NULL);
			verbose = oldverbose;
		}
	}

	file_size = -1;
	if (remote) {
		file_size = get_remote_size(remote, filetype);
		(void) sprintf(str, "%s %s", cmd, remote);
		if (command(str) != PRELIM) 
			goto nevrmind;
	} else {
		if (command(cmd) != PRELIM) {
nevrmind:	(void) signal(SIGINT, oldintr);
			if (oldtype) {
				if (!debug)
					verbose = V_QUIET;
				if (oldtype == TYPE_I)
					setbinary(0, NULL);
				verbose = oldverbose;
			}
			return;
		}
	}
	din = dataconn("r");
	if (din == NULL)
		goto Abort;
	if (filetype == IS_STREAM) {
		fout = stdout;
	} else if (filetype == IS_PIPE) {
		oldintp = signal(SIGPIPE, SIG_IGN);
		fout = popen(local + 1, "w");
		if (fout == NULL) {
			Perror(local+1);
			goto Abort;
		}
	} else {
		fout = fopen(local, mode);
		if (fout == NULL) {
			Perror(local);
			goto Abort;
		}
	}
	do_reports = NOT_VQUIET && is_retr && filetype == IS_FILE;
	if (do_reports)
		do_reports = start_progress(0, local);

	if (setjmp(recvabort))
		goto Abort;

	switch (curtype) {

	case TYPE_I:
	case TYPE_L:
		errno = d = 0;
		while ((c = read(fileno(din), xferbuf, (int)xferbufsize)) > 0) {
			if ((d = write(fileno(fout), xferbuf, c)) != c)
				break;
			bytes += c;

			/* Print progress indicator. */
			if (do_reports)
				progress_report(0);
		}
		if (c < 0) {
			if (errno != EPIPE)
				Perror("netin");
			bytes = -1;
		}
		if (d < c) {
			if (d < 0) {
				if (errno != EPIPE)
					Perror(local);
			} else
				(void) fprintf(stderr, "%s: short write\n", local);
		}
		break;

	case TYPE_A:
#ifdef REDIR
		nchars = 0;
		linePtr = str2;
#endif
		next_report = xferbufsize;
		while ((c = getc(din)) != EOF) {
			while (c == '\r') {
				bytes++;
				if ((c = getc(din)) != '\n' || tcrflag) {
					if (ferror(fout))
						goto break2;
					(void) putc('\r', fout);
					if (c == '\0') {
						bytes++;
						goto contin2;
					}
					if (c == EOF)
						goto contin2;
				}
			}
			(void) putc(c, fout);
			bytes++;
			
			/* Print progress indicator. */
			if (do_reports && bytes > next_report)
				progress_report(0);
#ifdef REDIR
			if (nchars < sizeof(str2) - 1) {		/* No seg violations, please */
         		*linePtr++ = c;  /* build redir string */
         		nchars++;
         	}
#endif
   contin2:
#ifdef REDIR
			/* Save the input line in the buffer for recall later. */
	         if (c=='\n' && is_ls) {
	            register struct lslist *new;
	
				*--linePtr = '\0';
				nchars--;
				new = (struct lslist *) malloc((size_t) sizeof(struct lslist));
				if (new != NULL) {
					if (new->string = malloc(strlen(str2) + 1))
						(void) strcpy(new->string, str2);
		   			new->next = NULL;
	   				if (lshead == NULL)
	   					lshead = lstail = new;
	  				else {
	  					lstail->next = new;
	  					lstail = new;
			      	}
		      	}
		      	/* reset line buffer */
				linePtr = str2;
				nchars = 0;
			}	/* ls mode & last char is a newline */
#else
			;
#endif
		}	/* while ((c = getc(din)) != EOF) */
break2:
		if (ferror(din)) {
			if (errno != EPIPE)
				Perror("netin");
			bytes = -1;
		}
		if (ferror(fout)) {
			if (errno != EPIPE)
				Perror(local);
		}
		break;
	}	/* end switch (curtype) */
	
	close_file(&fout, filetype);
	(void) signal(SIGINT, oldintr);
	if (oldintp)
		(void) signal(SIGPIPE, oldintp);
	if (din)
		(void) fclose(din);
	(void) getreply(0);
	if (bytes > 0 && is_retr && filetype == IS_FILE)
		end_progress("received", local, remote);
	if (oldtype) {
		if (!debug)
			verbose = V_QUIET;
			if (oldtype == TYPE_I)
				setbinary(0, NULL);
		verbose = oldverbose;
	}
	return;
Abort:
	echo(stdin, 1);

/* Abort using RFC959 recommended IP,SYNC sequence  */

	if (oldintp)
		(void) signal(SIGPIPE, oldintr);
	(void) signal(SIGINT,SIG_IGN);
	if (oldtype) {
		if (!debug)
			verbose = V_QUIET;
			if (oldtype == TYPE_I)
				setbinary(0, NULL);
		verbose = oldverbose;
	}
	if (!cpend) {
		code = -1;
		(void) signal(SIGINT,oldintr);
		return;
	}

	if (!cout) return;
	(void) fprintf(cout,"%c%c",IAC,IP);
	(void) fflush(cout); 
	msg = IAC;
/* send IAC in urgent mode instead of DM because UNIX places oob mark */
/* after urgent byte rather than before as now is protocol            */
	if (send(fileno(cout),&msg,1,MSG_OOB) != 1) {
		Perror("abort");
	}
	(void) fprintf(cout,"%cABOR\r\n",DM);
	(void) fflush(cout);
	FD_ZERO(&mask);
	FD_SET(fileno(cin), &mask);
	if (din) { 
		FD_SET(fileno(din), &mask);
	}
	if ((nfnd = empty(&mask,10)) <= 0) {
		if (nfnd < 0) {
			Perror("abort");
		}
		code = -1;
		lostpeer(0);
	}
	if (din && FD_ISSET(fileno(din), &mask)) {
		while ((c = read(fileno(din), xferbuf, xferbufsize)) > 0)
			;
	}
	if ((c = getreply(0)) == ERROR && code == 552) { /* needed for nic style abort */
		if (data >= 0) {
			(void) close(data);
			data = -1;
		}
		(void) getreply(0);
	}
	(void) getreply(0);
	code = -1;
	if (data >= 0) {
		(void) close(data);
		data = -1;
	}
	close_file(&fout, filetype);
	if (din)
		(void) fclose(din);
	end_progress("received", local, remote);
	(void) signal(SIGINT,oldintr);
}	/* recvrequest */


#ifdef TERM


/*
 * Need to start a listen on the data channel
 * before we send the command, otherwise the
 * server's connect may fail.
 */
/*
 * Need to request that the server go into passive mode and
 * then get the address and port for the term server to connect to.
 */
initconn()
{
        int result;
        int n[6];
        int s;

        if (data != -1)
                (void) close(data);
        result = command("PASV");
        if (result == COMPLETE) {
                if (sscanf(reply_string, "%*[^(](%d,%d,%d,%d,%d,%d)",
                        &n[0], &n[1], &n[2], &n[3], &n[4], &n[5]) != 6) {
                        printf("Cannot parse PASV response: %s\n",
                                 reply_string);
                        return 1;
                }
                close(data);
                lcompression = rcompression = compress_toggle;
                if ((s = connect_server(0)) < 0) {
                        perror("ftp: connect to term server");
                        data = -1;
                        return 1;
                }
                data = s;
                send_command(s, C_PORT, 0, "%d.%d.%d.%d:%d",
                        n[0], n[1], n[2], n[3], 256*n[4] + n[5]);
                send_command(s, C_COMPRESS, 1, "y");
                send_command(s, C_DUMB, 1, 0);
                return 0;
        }
        return 1;
}

FILE *
dataconn(lmode)
        char *lmode;
{
        return (fdopen(data, lmode));
}

#else /* TERM */

initconn(void)
{
	register char		*p, *a;
	int					result, len, tmpno = 0;
	int					on = 1, rval;
	string				str;
	void				(*oldintr)(int);

	oldintr = signal(SIGINT, SIG_IGN);
noport:
	data_addr = myctladdr;
	if (sendport)
		data_addr.sin_port = 0;	/* let system pick one */ 
	if (data != -1)
		(void) close (data);
	data = socket(AF_INET, SOCK_STREAM, 0);
	if (data < 0) {
		Perror("socket");
		if (tmpno)
			sendport = 1;
		rval = 1;  goto Return;
	}
	if (!sendport)
		if (setsockopt(data, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)) < 0) {
			Perror("setsockopt (reuse address)");
			goto bad;
		}
	if (bind(data, (struct sockaddr *)&data_addr, sizeof (data_addr)) < 0) {
		Perror("bind");
		goto bad;
	}
	if (options & SO_DEBUG &&
	    setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)) < 0)
		Perror("setsockopt (ignored)");
	len = sizeof (data_addr);
	if (getsockname(data, (char *)&data_addr, &len) < 0) {
		Perror("getsockname");
		goto bad;
	}
	if (listen(data, 1) < 0)
		Perror("listen");
	if (sendport) {
		a = (char *)&data_addr.sin_addr;
		p = (char *)&data_addr.sin_port;
#define UC(x) (int) (((int) x) & 0xff)
		(void) sprintf(str, "PORT %d,%d,%d,%d,%d,%d",
			UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
        result = command(str);
		if (result == ERROR && sendport == -1) {
			sendport = 0;
			tmpno = 1;
			goto noport;
		}
		rval = (result != COMPLETE);  goto Return;
	}
	if (tmpno)
		sendport = 1;
	rval = 0;  goto Return;
bad:
	(void) close(data), data = -1;
	if (tmpno)
		sendport = 1;
	rval = 1;
Return:
	(void) signal(SIGINT, oldintr);
	return (rval);
}	/* initconn */




FILE *
dataconn(char *mode)
{
	struct sockaddr_in from;
	FILE *fp;
	int s, fromlen = sizeof (from);

	s = accept(data, (struct sockaddr *) &from, &fromlen);
	if (s < 0) {
		Perror("accept");
		(void) close(data), data = -1;
		fp = NULL;
	} else {
		(void) close(data);
		data = s;
		fp = fdopen(data, mode);
	}
	return (fp);
}	/* dataconn */

#endif /* TERM */

/* eof ftp.c */



