/* Copyright (c) 1985, 1989 Regents of the University of California.  All    */
/* rights reserved.                                                          */
/*                                                                           */
/* Redistribution and use in source and binary forms, with or without        */
/* modification, are permitted provided that the following conditions are    */
/* met: 1. Redistributions of source code must retain the above copyright    */
/*    notice, this list of conditions and the following disclaimer.          */
/* 2. Redistributions in binary form must reproduce the above copyright      */
/*    notice, this list of conditions and the following disclaimer in the    */
/*    documentation and/or other materials provided with the distribution.   */
/* 3. All advertising materials mentioning features or use of this software  */
/*    must display the following acknowledgement:                            */
/* 	This product includes software developed by the University of        */
/* 	California, Berkeley and its contributors.  4. Neither the name      */
/* of the University nor the names of its contributors                       */
/*    may be used to endorse or promote products derived from this software  */
/*    without specific prior written permission.                             */
/*                                                                           */
/* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
/* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
/* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE */
/* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR       */
/* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
/* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
/* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
/* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
/* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
/* THE POSSIBILITY OF SUCH DAMAGE.                                           */

#include "ftp_var.h"

#define DONT_NEED_SIGBLOCK
#define DONT_NEED_SIGPAUSE
#define DONT_NEED_SIGUNBLOCK
#define DONT_NEED_SIGPENDING
#include "sigfix.h"

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#include <netinet/ip.h>
#include <arpa/ftp.h>
#include <arpa/telnet.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include <varargs.h>

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

#ifdef HAVE_BSTRING_H
#include <bstring.h>
#endif

#ifndef HAVE_BCOPY
#define bcopy(b1,b2,len) memmove(b2, b1, (size_t)(len))
#endif

#ifndef HAVE_BZERO
#define bzero(b,len)     memset(b, 0, (size_t)(len))
#endif

#ifndef HAVE_BCMP
#define bcmp(b1,b2,len)  memcmp(b1, b2, (size_t)(len))
#endif

sig_t	lostpeer();

struct	sockaddr_in hisctladdr;
struct	sockaddr_in data_addr;
struct	sockaddr_in myctladdr;
int	data = -1;
int	abrtflag = 0;
int	ptflag = 0;
off_t	restart_point = 0;
unsigned long socks_bind_remoteAddr;		/* Socks */

extern int connected, errno;

FILE	*cin, *cout;
FILE	*dataconn();

jmp_buf ptabort;
int ptabflg;

#ifndef HAVE_HERROR
char	*h_errlist[] = {
    "Error 0",
    "Unknown host",				/* 1 HOST_NOT_FOUND */
    "Host name lookup failure",		/* 2 TRY_AGAIN */
    "Unknown server error",			/* 3 NO_RECOVERY */
    "No address associated with name",	/* 4 NO_ADDRESS */
};

int h_nerr = { sizeof(h_errlist)/sizeof(h_errlist[0]) };
int h_errno;

/* herror --                                                                 */
/* print the error indicated by the h_errno value.                           */
/*                                                                           */
void herror(const char *s) {
    if (s && *s) {
	fprintf(stderr, "%s: ", s);
    }
    if ((h_errno < 0) || (h_errno >= h_nerr)) {
	fprintf(stderr, "Unknown error\n");
    } else if (h_errno == 0) {
#ifdef sun
	fprintf(stderr, "Host unknown\n");
#endif
    } else {
	fprintf(stderr, "%s\n", h_errlist[h_errno]);
    }
}
#endif

char *hookup(char *host, int port) {
    register struct hostent *hp = 0;
    static char hostnamebuf[80];
    int s, len, tos;

    bzero((char *)&hisctladdr, sizeof (hisctladdr));
    hisctladdr.sin_addr.s_addr = inet_addr(host);

    if (hisctladdr.sin_addr.s_addr != -1) {
	hisctladdr.sin_family = AF_INET;
	strncpy(hostnamebuf, host, sizeof(hostnamebuf));
    } else {
	if ((hp = gethostbyname(host)) == NULL) {
	    fprintf(stderr, "ftp: %s: ", host);
	    herror(NULL);
	    code = -1;
	    return NULL;
	}

	hisctladdr.sin_family = hp->h_addrtype;
	bcopy(hp->h_addr_list[0], (caddr_t)&hisctladdr.sin_addr, hp->h_length);
	strncpy(hostnamebuf, hp->h_name, sizeof(hostnamebuf));
    }

    hostname = hostnamebuf;
    
    if ((s = socket(hisctladdr.sin_family, SOCK_STREAM, 0)) < 0) {
	perror("ftp: socket");
	code = -1;
	return NULL;
    }

    hisctladdr.sin_port = port;

    while (connect(s, (struct sockaddr *)&hisctladdr, sizeof (hisctladdr)) < 0) {
	if (hp && hp->h_addr_list[1]) {
	    int oerrno = errno;

	    fprintf(stderr, "ftp: connect to address %s: ", inet_ntoa(hisctladdr.sin_addr));
	    errno = oerrno;
	    perror(NULL);
	    hp->h_addr_list++;
	    bcopy(hp->h_addr_list[0], (caddr_t)&hisctladdr.sin_addr, hp->h_length);

	    fprintf(stdout, "Trying %s...\n", inet_ntoa(hisctladdr.sin_addr));
	    close(s);

	    if ((s = socket(hisctladdr.sin_family, SOCK_STREAM, 0)) < 0) {
		perror("ftp: socket");
		code = -1;
		return NULL;
	    }

	    continue;
	}

	perror("ftp: connect");
	code = -1;
	goto bad;
    }

    socks_bind_remoteAddr = hisctladdr.sin_addr.s_addr;	 /* Socks */
    len = sizeof (myctladdr);

    if (getsockname(s, (struct sockaddr *)&myctladdr, &len) < 0) {
	perror("ftp: getsockname");
	code = -1;
	goto bad;
    }

#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
    tos = IPTOS_LOWDELAY;
    if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0)
	perror("ftp: setsockopt TOS (ignored)");
#endif

    cin  = fdopen(s, "r");
    cout = fdopen(dup(s), "w"); /* when (if ever) do we need the dup ? */
    if (cin == NULL || cout == NULL) {
	fprintf(stderr, "ftp: fdopen failed.\n");
	if (cin)  fclose(cin);
	if (cout) fclose(cout);
	code = -1;
	goto bad;
    }
    
    if (verbose) printf("Connected to %s.\n", hostname);
    if (getreply(0) > 2) { 	/* read startup message from server */
	if (cin)  fclose(cin);
	if (cout) fclose(cout);
	code = -1;
	goto bad;
    }
#ifdef SO_OOBINLINE
    {
	int on = 1;
    
	if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0 && debug) {
	    perror("ftp: setsockopt");
	}
    }
#endif

    return (hostname);
bad:
    close(s);
    return NULL;
}

void cmdabort() {
    extern jmp_buf ptabort;
    
    printf("\n");
    fflush(stdout);
    abrtflag++;

    if (!ptflag) return;
    longjmp(ptabort,1);
}

/*VARARGS*/
int command(va_alist) va_dcl {
    va_list ap;
    char *fmt;
    int r;
    sig_t oldintr;
    
    abrtflag = 0;
    if (debug) {
	printf("---> ");

	va_start(ap);
	fmt = va_arg(ap, char *);
	if (strncmp("PASS ", fmt, 5) == 0)
	    printf("PASS XXXX");
	else 
	    vfprintf(stdout, fmt, ap);
	va_end(ap);

	printf("\n");
	fflush(stdout);
    }

    if (cout == NULL) {
	perror ("No control connection for command");
	code = -1;
	return (0);
    }

    oldintr = (sig_t)Signal(SIGINT, cmdabort);

    va_start(ap);
    fmt = va_arg(ap, char *);
    vfprintf(cout, fmt, ap);
    va_end(ap);

    fflush(cout);

    cpend = 1;
    r = getreply(!strcmp(fmt, "QUIT"));

    if (abrtflag && oldintr != SIG_IGN) (*oldintr)(SIGINT);
    Signal(SIGINT, oldintr);
    return(r);
}

int ftplogin(char *host) {
    char tmp[80];
    char *user, *pass, *acct, *getlogin(), *GETPASS();
    int n, aflag = 0;

    user = pass = acct = 0;
    if (ruserpass(host, &user, &pass, &acct) < 0) {
	code = -1;
	return(0);
    }
    while (user == NULL) {
	char *myname = getlogin();

	if (myname == NULL) {
	    struct passwd *pp = getpwuid(getuid());

	    if (pp != NULL)
		myname = pp->pw_name;
	}
	if (myname)
	    printf("Name (%s:%s): ", host, myname);
	else
	    printf("Name (%s): ", host);
	fgets(tmp, sizeof(tmp) - 1, stdin);
	tmp[strlen(tmp) - 1] = '\0';
	if (*tmp == '\0')
	    user = myname;
	else
	    user = tmp;
    }
    n = command("USER %s\r\n", user);
    if (n == CONTINUE) {
	if (pass == NULL) {
	    if (!strcmp(user, "anonymous") || !strcmp(user, "ftp")) {
		printf("Password: "); fflush(stdout);
		fgets(tmp, sizeof(tmp) - 1, stdin);
		tmp[strlen(tmp) - 1] = '\0';
		pass = tmp;
	    } else
		pass = GETPASS("Password:");
	}
	n = command("PASS %s\r\n", pass);
    }
    if (n == CONTINUE) {
	aflag++;
	acct = GETPASS("Account:");
	n = command("ACCT %s\r\n", acct);
    }
    if (n != COMPLETE) {
	fprintf(stderr, "Login failed.\n");
	return (0);
    }
    if (!aflag && acct != NULL)
	command("ACCT %s\r\n", acct);
    if (proxy)
	return(1);
    for (n = 0; n < macnum; ++n) {
	if (!strcmp("init", macros[n].mac_name)) {
	    strcpy(line, "$init");
	    makeargv();
	    domacro(margc, margv);
	    break;
	}
    }
    return (1);
}

char reply_string[BUFSIZ];		/* last line of previous reply */

#include <ctype.h>

int getreply(int expecteof) {
    register int c, n;
    register int dig;
    register char *cp;
    int originalcode = 0, continuation = 0;
    sig_t oldintr;
    int pflag = 0;
    char *pt = pasv;
    void cmdabort();

    oldintr = (sig_t)Signal(SIGINT, cmdabort);
    for (;;) {
	dig = n = code = 0;
	cp = reply_string;
	while ((c = getc(cin)) != '\n') {
	    if (c == IAC) {     /* handle telnet commands */
		switch (c = getc(cin)) {
		    case WILL:
		    case WONT:
			c = getc(cin);
			fprintf(cout, "%c%c%c", IAC, DONT, c);
			fflush(cout);
			break;
		    case DO:
		    case DONT:
			c = getc(cin);
			fprintf(cout, "%c%c%c", IAC, WONT, c);
			fflush(cout);
			break;
		    default:
			break;
		}
		continue;
	    }
	    dig++;
	    if (c == EOF) {
		if (expecteof) {
		    Signal(SIGINT,oldintr);
		    code = 221;
		    return (0);
		}
		lostpeer();
		if (verbose) {
		    printf("421 Service not available, remote server has closed connection\n");
		    fflush(stdout);
		}
		code = 421;
		return(4);
	    }
	    if (c != '\r' && (verbose > 0 ||
			      (verbose > -1 && n == '5' && dig > 4))) {
		if (proxflag && (dig == 1 || (dig == 5 && verbose == 0)))
		    printf("%s:",hostname);
		putchar(c);
	    }
	    if (dig < 4 && isdigit((unsigned char)c))
		code = code * 10 + (c - '0');
	    if (!pflag && code == 227)
		pflag = 1;
	    if (dig > 4 && pflag == 1 && isdigit((unsigned char)c))
		pflag = 2;
	    if (pflag == 2) {
		if (c != '\r' && c != ')')
		    *pt++ = c;
		else {
		    *pt = '\0';
		    pflag = 3;
		}
	    }
	    if (dig == 4 && c == '-') {
		if (continuation) code = 0;
		continuation++;
	    }
	    if (n == 0)
		n = c;
	    if (cp < &reply_string[sizeof(reply_string) - 1])
		*cp++ = c;
	}
	if (verbose > 0 || (verbose > -1 && n == '5')) {
	    putchar(c);
	    fflush(stdout);
	}
	if (continuation && code != originalcode) {
	    if (originalcode == 0) originalcode = code;
	    continue;
	}
	*cp = '\0';
	if (n != '1')
	    cpend = 0;
	Signal(SIGINT,oldintr);
	if (code == 421 || originalcode == 421)
	    lostpeer();
	if (abrtflag && oldintr != cmdabort && oldintr != SIG_IGN)
	    (*oldintr)(SIGINT);
	return (n - '0');
    }
}

int empty(fd_set *mask, int sec) {
    struct timeval t;
    
    t.tv_sec = (long) sec;
    t.tv_usec = 0;
    return(select(32, mask, NULL, NULL, &t));
}

jmp_buf	sendabort;

void abortsend() {

    mflag = 0;
    abrtflag = 0;
    printf("\nsend aborted\nwaiting for remote to finish abort\n");
    fflush(stdout);
    longjmp(sendabort, 1);
}

#define HASHBYTES 1024

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;
}

void ptransfer(char *direction, long bytes, struct timeval *t0, struct timeval *t1) {
    struct timeval td;
    float s, bs;
    
    if (verbose) {
	tvsub(&td, t1, t0);
	s = td.tv_sec + (td.tv_usec / 1000000.);
#define	nz(x)	((x) ? (x) : 1)
	bs = bytes / nz(s);
	printf("%ld bytes %s in %.2g seconds (%.2g Kbytes/s)\n",
	       bytes, direction, s, bs / 1024.);
    }
}

void abort_remote(FILE *din) {
    char buf[BUFSIZ];
    int nfnd;
    fd_set mask;

    /*
     * send IAC in urgent mode instead of DM because 4.3BSD places oob mark
     * after urgent byte rather than before as is protocol now
     */
    sprintf(buf, "%c%c%c", IAC, IP, IAC);
    if (send(fileno(cout), buf, 3, MSG_OOB) != 3)
	perror("abort");
    fprintf(cout,"%cABOR\r\n", DM);
    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");
	}
	if (ptabflg)
	    code = -1;
	lostpeer();
    }
    if (din && FD_ISSET(fileno(din), &mask)) {
	while (read(fileno(din), buf, BUFSIZ) > 0)
	    /* LOOP */;
    }
    if (getreply(0) == ERROR && code == 552) {
	/* 552 needed for nic style abort */
	getreply(0);
    }
    getreply(0);
}

void proxtrans(char *cmd, char *local, char *remote) {
    sig_t oldintr;
    int secndflag = 0, prox_type, nfnd;
    extern jmp_buf ptabort;
    char *cmd2;
    fd_set mask;
    void abortpt();

    if (strcmp(cmd, "RETR"))
	cmd2 = "RETR";
    else
	cmd2 = runique ? "STOU" : "STOR";
    if ((prox_type = type) == 0) {
	if (unix_server && unix_proxy)
	    prox_type = TYPE_I;
	else
	    prox_type = TYPE_A;
    }
    if (curtype != prox_type) changetype(prox_type, 1);
    if (command("PASV\r\n") != COMPLETE) {
	printf("proxy server does not support third party transfers.\n");
	return;
    }
    pswitch(0);
    if (!connected) {
	printf("No primary connection\n");
	pswitch(1);
	code = -1;
	return;
    }
    if (curtype != prox_type) changetype(prox_type, 1);
    if (command("PORT %s\r\n", pasv) != COMPLETE) {
	pswitch(1);
	return;
    }
    if (setjmp(ptabort)) goto abort;
    oldintr = (sig_t)Signal(SIGINT, abortpt);
    if (command("%s %s\r\n", cmd, remote) != PRELIM) {
	Signal(SIGINT, oldintr);
	pswitch(1);
	return;
    }
    sleep(2);
    pswitch(1);
    secndflag++;
    if (command("%s %s\r\n", cmd2, local) != PRELIM)
	goto abort;
    ptflag++;
    getreply(0);
    pswitch(0);
    getreply(0);
    Signal(SIGINT, oldintr);
    pswitch(1);
    ptflag = 0;
    printf("local: %s remote: %s\n", local, remote);
    return;
abort:
    Signal(SIGINT, SIG_IGN);
    ptflag = 0;
    if (strcmp(cmd, "RETR") && !proxy) pswitch(1);
    else if (!strcmp(cmd, "RETR") && proxy) pswitch(0);
    if (!cpend && !secndflag) {  /* only here if cmd = "STOR" (proxy=1) */
	if (command("%s %s\r\n", cmd2, local) != PRELIM) {
	    pswitch(0);
	    if (cpend) abort_remote((FILE *) NULL);
	}
	pswitch(1);
	if (ptabflg) code = -1;
	Signal(SIGINT, oldintr);
	return;
    }
    if (cpend)
	abort_remote((FILE *) NULL);
    pswitch(!proxy);
    if (!cpend && !secndflag) {  /* only if cmd = "RETR" (proxy=1) */
	if (command("%s %s\r\n", cmd2, local) != PRELIM) {
	    pswitch(0);
	    if (cpend) abort_remote((FILE *) NULL);
	    pswitch(1);
	    if (ptabflg) code = -1;
	    Signal(SIGINT, oldintr);
	    return;
	}
    }
    if (cpend) abort_remote((FILE *) NULL);
    pswitch(!proxy);
    if (cpend) {
	FD_ZERO(&mask);
	FD_SET(fileno(cin), &mask);
	if ((nfnd = empty(&mask, 10)) <= 0) {
	    if (nfnd < 0) perror("abort");
	    if (ptabflg) code = -1;
	    lostpeer();
	}
	getreply(0);
	getreply(0);
    }
    if (proxy) pswitch(0);
    pswitch(1);
    if (ptabflg) code = -1;
    Signal(SIGINT, oldintr);
}

/*
 * Need to start a listen on the data channel before we send the command,
 * otherwise the server's connect may fail.
 */
int initconn() {
    register char *p, *a;
    int result, len, tmpno = 0;
    int on = 1;

noport:
    data_addr = myctladdr;
    if (sendport)
	data_addr.sin_port = 0;	/* let system pick one */ 
    if (data != -1)
	close(data);
    data = socket(AF_INET, SOCK_STREAM, 0);
    if (data < 0) {
	perror("ftp: socket");
	if (tmpno)
	    sendport = 1;
	return (1);
    }
    if (!sendport)
	if (setsockopt(data, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)) < 0) {
	    perror("ftp: setsockopt (reuse address)");
	    goto bad;
	}
    if (bind(data, (struct sockaddr *)&data_addr, sizeof(data_addr)) < 0) {
	perror("ftp: bind");
	goto bad;
    }
    if (options & SO_DEBUG &&
	setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)) < 0)
	perror("ftp: setsockopt (ignored)");
    len = sizeof (data_addr);
    if (getsockname(data, (struct sockaddr *)&data_addr, &len) < 0) {
	perror("ftp: getsockname");
	goto bad;
    }
    if (listen(data, 1) < 0)
	perror("ftp: listen");
    if (sendport) {
	a = (char *)&data_addr.sin_addr;
	p = (char *)&data_addr.sin_port;
#define	UC(b)	(((int)b)&0xff)
	result =
	    command("PORT %d,%d,%d,%d,%d,%d\r\n",
		    UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
		    UC(p[0]), UC(p[1]));
	if (result == ERROR && sendport == -1) {
	    sendport = 0;
	    tmpno = 1;
	    goto noport;
	}
	return (result != COMPLETE);
    }
    if (tmpno)
	sendport = 1;
#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
    on = IPTOS_THROUGHPUT;
    if (setsockopt(data, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0)
	perror("ftp: setsockopt TOS (ignored)");
#endif
    return (0);
bad:
    close(data), data = -1;
    if (tmpno)
	sendport = 1;
    return (1);
}

void sendrequest(char *cmd, char *local, char *remote, int printnames) {
    struct stat st;
    struct timeval start, stop;
    register int c, d;
    FILE *fin, *dout = 0, *popen();
    int (*closefunc)(), pclose(), fclose();
    sig_t oldintr, oldintp;
    long bytes = 0, hashbytes = HASHBYTES;
    char *lmode, buf[BUFSIZ], *bufp;
    void abortsend();

    if (verbose && printnames) {
	if (local && *local != '-')
	    printf("local: %s ", local);
	if (remote)
	    printf("remote: %s\n", remote);
    }
    if (proxy) {
	proxtrans(cmd, local, remote);
	return;
    }
    if (curtype != type)
	changetype(type, 0);
    closefunc = NULL;
    oldintr = NULL;
    oldintp = NULL;
    lmode = "w";
    if (setjmp(sendabort)) {
	while (cpend) {
	    getreply(0);
	}
	if (data >= 0) {
	    close(data);
	    data = -1;
	}
	if (oldintr)
	    Signal(SIGINT,oldintr);
	if (oldintp)
	    Signal(SIGPIPE,oldintp);
	code = -1;
	return;
    }
    oldintr = (sig_t)Signal(SIGINT, abortsend);
    if (strcmp(local, "-") == 0)
	fin = stdin;
    else if (*local == '|') {
	oldintp = (sig_t)Signal(SIGPIPE,SIG_IGN);
	fin = popen(local + 1, "r");
	if (fin == NULL) {
	    perror(local + 1);
	    Signal(SIGINT, oldintr);
	    Signal(SIGPIPE, oldintp);
	    code = -1;
	    return;
	}
	closefunc = pclose;
    } else {
	fin = fopen(local, "r");
	if (fin == NULL) {
	    fprintf(stderr, "local: %s: %s\n", local,
		    strerror(errno));
	    Signal(SIGINT, oldintr);
	    code = -1;
	    return;
	}
	closefunc = fclose;
	if (fstat(fileno(fin), &st) < 0 ||
	    (st.st_mode&S_IFMT) != S_IFREG) {
	    fprintf(stdout, "%s: not a plain file.\n", local);
	    Signal(SIGINT, oldintr);
	    fclose(fin);
	    code = -1;
	    return;
	}
    }
    if (initconn()) {
	Signal(SIGINT, oldintr);
	if (oldintp)
	    Signal(SIGPIPE, oldintp);
	code = -1;
	if (closefunc != NULL)
	    (*closefunc)(fin);
	return;
    }
    if (setjmp(sendabort))
	goto abort;

    if (restart_point &&
	(strcmp(cmd, "STOR") == 0 || strcmp(cmd, "APPE") == 0)) {
	if (fseek(fin, (long) restart_point, 0) < 0) {
	    fprintf(stderr, "local: %s: %s\n", local,
		    strerror(errno));
	    restart_point = 0;
	    if (closefunc != NULL)
		(*closefunc)(fin);
	    return;
	}
	if (command("REST %ld\r\n", (long) restart_point)
	    != CONTINUE) {
	    restart_point = 0;
	    if (closefunc != NULL)
		(*closefunc)(fin);
	    return;
	}
	restart_point = 0;
	lmode = "r+w";
    }
    if (remote) {
	if (command("%s %s\r\n", cmd, remote) != PRELIM) {
	    Signal(SIGINT, oldintr);
	    if (oldintp)
		Signal(SIGPIPE, oldintp);
	    if (closefunc != NULL)
		(*closefunc)(fin);
	    return;
	}
    } else
	if (command("%s\r\n", cmd) != PRELIM) {
	    Signal(SIGINT, oldintr);
	    if (oldintp)
		Signal(SIGPIPE, oldintp);
	    if (closefunc != NULL)
		(*closefunc)(fin);
	    return;
	}
    dout = dataconn(lmode);
    if (dout == NULL)
	goto abort;
    gettimeofday(&start, (struct timezone *)0);
    oldintp = (sig_t)Signal(SIGPIPE, SIG_IGN);
    switch (curtype) {

	case TYPE_I:
	case TYPE_L:
	    errno = d = 0;
	    while ((c = read(fileno(fin), buf, sizeof (buf))) > 0) {
		bytes += c;
		for (bufp = buf; c > 0; c -= d, bufp += d)
		    if ((d = write(fileno(dout), bufp, c)) <= 0)
			break;
		if (hash) {
		    while (bytes >= hashbytes) {
			putchar('#');
			hashbytes += HASHBYTES;
		    }
		    fflush(stdout);
		}
	    }
	    if (hash && bytes > 0) {
		if (bytes < HASHBYTES)
		    putchar('#');
		putchar('\n');
		fflush(stdout);
	    }
	    if (c < 0)
		fprintf(stderr, "local: %s: %s\n", local,
			strerror(errno));
	    if (d < 0) {
		if (errno != EPIPE) 
		    perror("netout");
		bytes = -1;
	    }
	    break;

	case TYPE_A:
	    while ((c = getc(fin)) != EOF) {
		if (c == '\n') {
		    while (hash && (bytes >= hashbytes)) {
			putchar('#');
			fflush(stdout);
			hashbytes += HASHBYTES;
		    }
		    if (ferror(dout))
			break;
		    putc('\r', dout);
		    bytes++;
		}
		putc(c, dout);
		bytes++;
		/*		if (c == '\r') {			  	*/
		/*		(void)	putc('\0', dout);  /* this violates rfc */
		/*			bytes++;				*/
		/*		}                          			*/	
	    }
	    if (hash) {
		if (bytes < hashbytes)
		    putchar('#');
		putchar('\n');
		fflush(stdout);
	    }
	    if (ferror(fin))
		fprintf(stderr, "local: %s: %s\n", local,
			strerror(errno));
	    if (ferror(dout)) {
		if (errno != EPIPE)
		    perror("netout");
		bytes = -1;
	    }
	    break;
    }
    gettimeofday(&stop, (struct timezone *)0);
    if (closefunc != NULL)
	(*closefunc)(fin);
    fclose(dout);
    getreply(0);
    Signal(SIGINT, oldintr);
    if (oldintp)
	Signal(SIGPIPE, oldintp);
    if (bytes > 0) {
#ifdef FASCIST
	syslog(LOG_NOTICE, "Sent local file %s as %s -- %d bytes", local, remote, bytes);
#endif
	ptransfer("sent", bytes, &start, &stop);
    }
    return;
abort:
    gettimeofday(&stop, (struct timezone *)0);
    Signal(SIGINT, oldintr);
    if (oldintp)
	Signal(SIGPIPE, oldintp);
    if (!cpend) {
	code = -1;
	return;
    }
    if (data >= 0) {
	close(data);
	data = -1;
    }
    if (dout)
	fclose(dout);
    getreply(0);
    code = -1;
    if (closefunc != NULL && fin != NULL)
	(*closefunc)(fin);
    if (bytes > 0) {
#ifdef FASCIST
	syslog(LOG_NOTICE, "Sent local file %s as %s -- %d bytes", local, remote, bytes);
#endif

	ptransfer("sent", bytes, &start, &stop);
    }
}

jmp_buf	recvabort;

void
abortrecv()
{

    mflag = 0;
    abrtflag = 0;
    printf("\nreceive aborted\nwaiting for remote to finish abort\n");
    fflush(stdout);
    longjmp(recvabort, 1);
}

void recvrequest(char *cmd, char *local, char *remote, char *lmode, int printnames) {
    FILE *fout, *din = 0, *popen();
    int (*closefunc)(), pclose(), fclose();
    sig_t oldintr, oldintp;
    int is_retr, tcrflag, bare_lfs = 0;
    char *gunique();
    static int bufsize;
    static char *buf;
    long bytes = 0, hashbytes = HASHBYTES;
    register int c, d;
    struct timeval start, stop;
    struct stat st;
    off_t lseek();
    void abortrecv();

    is_retr = strcmp(cmd, "RETR") == 0;
    if (is_retr && verbose && printnames) {
	if (local && *local != '-')
	    printf("local: %s ", local);
	if (remote)
	    printf("remote: %s\n", remote);
    }
    if (proxy && is_retr) {
	proxtrans(cmd, local, remote);
	return;
    }
    closefunc = NULL;
    oldintr = NULL;
    oldintp = NULL;
    tcrflag = !crflag && is_retr;
    if (setjmp(recvabort)) {
	while (cpend) {
	    getreply(0);
	}
	if (data >= 0) {
	    close(data);
	    data = -1;
	}
	if (oldintr)
	    Signal(SIGINT, oldintr);
	code = -1;
	return;
    }
    oldintr = (sig_t)Signal(SIGINT, abortrecv);
    if (strcmp(local, "-") && *local != '|') {
	if (access(local, 2) < 0) {
	    char *dir = rindex(local, '/');

	    if (errno != ENOENT && errno != EACCES) {
		fprintf(stderr, "local: %s: %s\n", local,
			strerror(errno));
		Signal(SIGINT, oldintr);
		code = -1;
		return;
	    }
	    if (dir != NULL)
		*dir = 0;
	    d = access(dir ? local : ".", 2);
	    if (dir != NULL)
		*dir = '/';
	    if (d < 0) {
		fprintf(stderr, "local: %s: %s\n", local,
			strerror(errno));
		Signal(SIGINT, oldintr);
		code = -1;
		return;
	    }
	    if (!runique && errno == EACCES &&
		chmod(local, 0600) < 0) {
		fprintf(stderr, "local: %s: %s\n", local,
			strerror(errno));
		Signal(SIGINT, oldintr);
		Signal(SIGINT, oldintr);
		code = -1;
		return;
	    }
	    if (runique && errno == EACCES &&
		(local = gunique(local)) == NULL) {
		Signal(SIGINT, oldintr);
		code = -1;
		return;
	    }
	}
	else if (runique && (local = gunique(local)) == NULL) {
	    Signal(SIGINT, oldintr);
	    code = -1;
	    return;
	}
    }
    if (!is_retr) {
	if (curtype != TYPE_A)
	    changetype(TYPE_A, 0);
    } else if (curtype != type)
	changetype(type, 0);
    if (initconn()) {
	Signal(SIGINT, oldintr);
	code = -1;
	return;
    }
    if (setjmp(recvabort))
	goto abort;
    if (is_retr && restart_point &&
	command("REST %ld\r\n", (long) restart_point) != CONTINUE)
	return;
    if (remote) {
	if (command("%s %s\r\n", cmd, remote) != PRELIM) {
	    Signal(SIGINT, oldintr);
	    return;
	}
    } else {
	if (command("%s\r\n", cmd) != PRELIM) {
	    Signal(SIGINT, oldintr);
	    return;
	}
    }
    din = dataconn("r");
    if (din == NULL)
	goto abort;
    if (strcmp(local, "-") == 0)
	fout = stdout;
    else if (*local == '|') {
	oldintp = (sig_t)Signal(SIGPIPE, SIG_IGN);
	fout = popen(local + 1, "w");
	if (fout == NULL) {
	    perror(local+1);
	    goto abort;
	}
	closefunc = pclose;
    } else {
	fout = fopen(local, lmode);
	if (fout == NULL) {
	    fprintf(stderr, "local: %s: %s\n", local,
		    strerror(errno));
	    goto abort;
	}
	closefunc = fclose;
    }

#ifndef SVR3
    if (fstat(fileno(fout), &st) < 0 || st.st_blksize == 0)
#endif
	st.st_blksize = BUFSIZ;

    if (st.st_blksize > bufsize) {
	if (buf) free(buf);
	buf = malloc((unsigned)st.st_blksize);

	if (buf == NULL) {
	    perror("malloc");
	    bufsize = 0;
	    goto abort;
	}
    
	bufsize = st.st_blksize;
    
    }

    gettimeofday(&start, (struct timezone *)0);

    switch (curtype) {
	case TYPE_I:
	case TYPE_L:
#ifdef SEEK_SET
#define FTP_SEEK_SET SEEK_SET
#else
#define FTP_SEEK_SET L_SET
#endif

#ifdef SEEK_CUR
#define FTP_SEEK_CUR SEEK_CUR
#else
#define FTP_SEEK_CUR L_CUR
#endif
	    if (restart_point && lseek(fileno(fout), (long) restart_point, FTP_SEEK_SET) < 0) {
		fprintf(stderr, "local: %s: %s\n", local, strerror(errno));
		if (closefunc != NULL) (*closefunc)(fout);
		return;
	    }

	    errno = d = 0;

	    while ((c = read(fileno(din), buf, bufsize)) > 0) {
		if ((d = write(fileno(fout), buf, c)) != c) break;
		bytes += c;
	  
		if (hash) {
		    while (bytes >= hashbytes) {
			putchar('#');
			hashbytes += HASHBYTES;
		    }
		    fflush(stdout);
		}
	    }

	    if (hash && bytes > 0) {
		if (bytes < HASHBYTES) putchar('#');
		putchar('\n');
		fflush(stdout);
	    }
	
	    if (c < 0) {
		if (errno != EPIPE) perror("netin");
		bytes = -1;
	    }

	    if (d < c) {
		if (d < 0) fprintf(stderr, "local: %s: %s\n", local, strerror(errno));
		else fprintf(stderr, "%s: short write\n", local);
	    }

	    break;
	case TYPE_A:
	    if (restart_point) {
		register int i, n, ch;

		if (fseek(fout, 0L, FTP_SEEK_SET) < 0) goto done;
		n = restart_point;

		for (i = 0; i++ < n;) {
		    if ((ch = getc(fout)) == EOF) goto done;
		    if (ch == '\n') i++;
		}
	  
		if (fseek(fout, 0L, FTP_SEEK_CUR) < 0) {
		done:
		    fprintf(stderr, "local: %s: %s\n", local, strerror(errno));
		    if (closefunc != NULL) (*closefunc)(fout);
		    return;
		}
	    }

	    while ((c = getc(din)) != EOF) {
		if (c == '\n') bare_lfs++;

		while (c == '\r') {
		    while (hash && (bytes >= hashbytes)) {
			putchar('#');
			fflush(stdout);
			hashbytes += HASHBYTES;
		    }

		    bytes++;

		    if ((c = getc(din)) != '\n' || tcrflag) {
			if (ferror(fout)) goto break2;
			putc('\r', fout);

			if (c == '\0') {
			    bytes++;
			    goto contin2;
			}

			if (c == EOF) goto contin2;
		    }
		}

		putc(c, fout);
		bytes++;
	    contin2:	;
	    }
    break2:
	    if (bare_lfs) {
		printf("WARNING! %d bare linefeeds received in ASCII mode\n", bare_lfs);
		printf("File may not have transferred correctly.\n");
	    }
	    if (hash) {
		if (bytes < hashbytes) putchar('#');
		putchar('\n');
		fflush(stdout);
	    }

	    if (ferror(din)) {
		if (errno != EPIPE) perror("netin");
		bytes = -1;
	    }

	    if (ferror(fout)) fprintf(stderr, "local: %s: %s\n", local, strerror(errno));
	    break;
    }

    if (closefunc != NULL) (*closefunc)(fout);

    Signal(SIGINT, oldintr);
    if (oldintp) Signal(SIGPIPE, oldintp);

    gettimeofday(&stop, (struct timezone *)0);
    fclose(din);
    getreply(0);

    if (bytes > 0 && is_retr) {
#ifdef FASCIST
	syslog(LOG_NOTICE, "Received remote file %s as %s -- %d bytes", remote, local, bytes);
#endif
	ptransfer("received", bytes, &start, &stop);
    }
    return;

abort:

    /* abort using RFC959 recommended IP,SYNC sequence  */
  
    gettimeofday(&stop, (struct timezone *)0);
    if (oldintp) Signal(SIGPIPE, oldintr);
    Signal(SIGINT, SIG_IGN);

    if (!cpend) {
	code = -1;
	Signal(SIGINT, oldintr);
	return;
    }
  
    abort_remote(din);
    code = -1;

    if (data >= 0) {
	close(data);
	data = -1;
    }

    if (closefunc != NULL && fout != NULL) (*closefunc)(fout);
    if (din) fclose(din);

    if (bytes > 0) {
#ifdef FASCIST
	syslog(LOG_NOTICE, "Received remote file %s as %s -- %d bytes", remote, local, bytes);
#endif
	ptransfer("received", bytes, &start, &stop);
    }

    Signal(SIGINT, oldintr);
}
	
FILE *
dataconn(lmode)
    char *lmode;
{
    struct sockaddr_in from;
    int s, fromlen = sizeof (from), tos;

    if ((s = accept(data, (struct sockaddr *) &from, &fromlen)) < 0) {
	perror("ftp: accept");
	close(data), data = -1;
	return (NULL);
    }
    close(data);
    data = s;

#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
    tos = IPTOS_THROUGHPUT;
    if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0)
	perror("ftp: setsockopt TOS (ignored)");
#endif

    return (fdopen(data, lmode));
}

void psabort() {
    extern int abrtflag;
    abrtflag++;
}

void pswitch(int flag) {
    extern int proxy, abrtflag;
    sig_t oldintr;

    static struct comvars {
	int connect;
	char name[MAXHOSTNAMELEN];
	struct sockaddr_in mctl;
	struct sockaddr_in hctl;
	FILE *in;
	FILE *out;
	int tpe;
	int curtpe;
	int cpnd;
	int sunqe;
	int runqe;
	int mcse;
	int ntflg;
	char nti[17];
	char nto[17];
	int mapflg;
	char mi[MAXPATHLEN];
	char mo[MAXPATHLEN];
    } proxstruct, tmpstruct;

    struct comvars *ip, *op;

    abrtflag = 0;
    oldintr = (sig_t)Signal(SIGINT, psabort);

    if (flag) {
	if (proxy) return;
	ip = &tmpstruct;
	op = &proxstruct;
	proxy++;
    } else {
	if (!proxy) return;
	ip = &proxstruct;
	op = &tmpstruct;
	proxy = 0;
    }

    ip->connect = connected;
    connected = op->connect;

    if (hostname) {
	strncpy(ip->name, hostname, sizeof(ip->name) - 1);
	ip->name[strlen(ip->name)] = '\0';
    } else {
	ip->name[0] = '\0';
    }
    hostname = op->name;
    ip->hctl = hisctladdr;
    hisctladdr = op->hctl;
    ip->mctl = myctladdr;
    myctladdr = op->mctl;
    ip->in = cin;
    cin = op->in;
    ip->out = cout;
    cout = op->out;
    ip->tpe = type;
    type = op->tpe;
    ip->curtpe = curtype;
    curtype = op->curtpe;
    ip->cpnd = cpend;
    cpend = op->cpnd;
    ip->sunqe = sunique;
    sunique = op->sunqe;
    ip->runqe = runique;
    runique = op->runqe;
    ip->mcse = mcase;
    mcase = op->mcse;
    ip->ntflg = ntflag;
    ntflag = op->ntflg;
    strncpy(ip->nti, ntin, 16);
    (ip->nti)[strlen(ip->nti)] = '\0';
    strcpy(ntin, op->nti);
    strncpy(ip->nto, ntout, 16);
    (ip->nto)[strlen(ip->nto)] = '\0';
    strcpy(ntout, op->nto);
    ip->mapflg = mapflag;
    mapflag = op->mapflg;
    strncpy(ip->mi, mapin, MAXPATHLEN - 1);
    (ip->mi)[strlen(ip->mi)] = '\0';
    strcpy(mapin, op->mi);
    strncpy(ip->mo, mapout, MAXPATHLEN - 1);
    (ip->mo)[strlen(ip->mo)] = '\0';
    strcpy(mapout, op->mo);
    Signal(SIGINT, oldintr);

    if (abrtflag) {
	abrtflag = 0;
	(*oldintr)(SIGINT);
    }
}

void abortpt() {
    printf("\n");
    fflush(stdout);

    ptabflg++;
    mflag    = 0;
    abrtflag = 0;

    longjmp(ptabort, 1);
}

void reset() {
    fd_set mask;
    int nfnd = 1;
    
    FD_ZERO(&mask);

    while (nfnd > 0) {
	FD_SET(fileno(cin), &mask);

	if ((nfnd = empty(&mask,0)) < 0) {
	    perror("reset");
	    code = -1;
	    lostpeer();
	} else if (nfnd) {
	    getreply(0);
	}
    }
}

char *gunique(char *local) {
    static char new[MAXPATHLEN];
    char *cp = rindex(local, '/');
    int d, count=0;
    char ext = '1';

    if (cp) *cp = '\0';
    d = access(cp ? local : ".", 2);
    if (cp) *cp = '/';

    if (d < 0) {
	fprintf(stderr, "local: %s: %s\n", local, strerror(errno));
	return((char *) 0);
    }
    
    strcpy(new, local);
    cp = new + strlen(new);
    *cp++ = '.';

    while (!d) {
	if (++count == 100) {
	    printf("runique: can't find unique file name.\n");
	    return((char *) 0);
	}

	*cp++ = ext;
	*cp = '\0';
	if (ext == '9') ext = '0';
	else            ext++;

	if ((d = access(new, 0)) < 0) break;
	if (ext != '0') cp--;
	else if (*(cp - 2) == '.') *(cp - 1) = '1';
	else {
	    *(cp - 2) = *(cp - 2) + 1;
	    cp--;
	}
    }

    return(new);
}

