root/usr/src/cmd/cmd-inet/usr.bin/ftp/ftp.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 *      Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 *      Use is subject to license terms.
 */

/*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
/*      All Rights Reserved     */

/*
 *      University Copyright- Copyright (c) 1982, 1986, 1988
 *      The Regents of the University of California
 *      All Rights Reserved
 *
 *      University Acknowledgment- Portions of this document are derived from
 *      software developed by the University of California, Berkeley, and its
 *      contributors.
 */

#include "ftp_var.h"
#include <arpa/nameser.h>
#include <sys/types.h>

/*
 * WRITE() returns:
 *      >0      no error
 *      -1      error, errorno is set
 *      -2      security error (secure_write() only)
 */
#define PUTC(x, y)      secure_putc(x, y)
#define READ(x, y, z)   secure_read(x, y, z)
#define WRITE(x, y, z)  secure_write(x, y, z)

static struct   sockaddr_in6 data_addr;
int     data = -1;
static int      abrtflag = 0;
static int      ptflag = 0;
static jmp_buf  sendabort;
static jmp_buf  recvabort;
static jmp_buf  ptabort;
static int ptabflg;
static boolean_t pasv_refused;
boolean_t       eport_supported = B_TRUE;
/*
 * For IPv6 addresses, EPSV will be the default (rather than EPRT/LPRT).
 * The EPSV/ERPT ftp protocols are specified in RFC 2428.
 *
 * Perform EPSV if passivemode is set and ipv6rem is TRUE.
 */
static boolean_t ipv6rem;
int     use_eprt = 0;   /* Testing option that specifies EPRT by default */
FILE    *ctrl_in, *ctrl_out;

static void abortsend(int sig);
static void abortpt(int sig);
static void proxtrans(char *cmd, char *local, char *remote);
static void cmdabort(int sig);
static int empty(struct fd_set *mask, int sec, int nfds);
static void abortrecv(int sig);
static int initconn(void);
static FILE *dataconn(char *mode);
static void ptransfer(char *direction, off_t bytes, hrtime_t t0,
    hrtime_t t1, char *local, char *remote);
static void psabort(int sig);
static char *gunique(char *local);
static const char *inet_ntop_native(int af, const void *src, char *dst,
    size_t size);
static ssize_t timedread(int fd, void *buf, size_t maxlen, int timeout);

static int secure_command(char *);
static int decode_reply(uchar_t *, int, uchar_t *, int, boolean_t *);

static ssize_t  bufcnt;         /* number of bytes in buf[]     */
static char     *bufp;          /* next character in buf        */
static int      buferr;         /* last errno                   */
static size_t   bufsize;

static void fdio_setbuf(char *buffer, size_t bufsize);
static int fdio_fillbuf(int fd);
static int fdio_error(int fd);
#define fdio_getc(fd)   (--bufcnt < 0 ? fdio_fillbuf((fd)) : \
                            ((unsigned char)*bufp++))

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define NONZERO(x)      ((x) == 0 ? 1 : (x))

static void
fdio_setbuf(char *buffer, size_t maxsize)
{
        buf = buffer;
        bufp = buf;
        bufcnt = 0;
        buferr = 0;
        bufsize = maxsize;
}

static int
fdio_fillbuf(int fd)
{
        bufcnt = timedread(fd, buf, bufsize, timeout);
        if (bufcnt < 0)
                buferr = errno;
        if (bufcnt <= 0)
                return (EOF);
        bufp = buf;
        bufcnt--;
        return ((unsigned char)*bufp++);
}

/*
 * fdio_error - used on a file descriptor instead of ferror()
 */

/*ARGSUSED*/
static int
fdio_error(int fd)
{
        return (buferr);
}

/*
 * timedread - read buffer (like "read"), but with timeout (in seconds)
 */

static ssize_t
timedread(int fd, void *buf, size_t size, int timeout)
{
        struct fd_set mask;
        struct timeval tv;
        int err;

        if (!timeout)
                return (READ(fd, buf, size));

        tv.tv_sec = (time_t)timeout;
        tv.tv_usec = 0;

        FD_ZERO(&mask);
        FD_SET(fd, &mask);

        err = select(fd + 1, &mask, NULL, NULL, &tv);
        if (err == 0)
                errno = ETIMEDOUT;
        if (err <= 0)
                return (-1);

        return (READ(fd, buf, size));
}


char *
hookup(char *host, char *service)
{
        struct addrinfo hints, *ai = NULL, *ai_head;
        int s;
        socklen_t len;
        static char hostnamebuf[80];
        struct in6_addr ipv6addr;
        char abuf[INET6_ADDRSTRLEN];
        int error_num;
        int on = 1;

        /*
         * There appears to be a bug in getaddrinfo() where, if the
         * ai_family is set to AF_INET6, and the host is a v4-only
         * host, getaddrinfo() returns an error instead of returning
         * an v4-mapped ipv6 address. Therefore the ai_family is
         * set to AF_UNSPEC and any returned v4 addresses are
         * explicitly mapped within ftp.
         */
        bzero((char *)&remctladdr, sizeof (remctladdr));
        bzero((char *)&hints, sizeof (hints));
        hints.ai_flags = AI_CANONNAME;
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;

        error_num = getaddrinfo(host, service, &hints, &ai);
        if (error_num != 0) {
                if (error_num == EAI_AGAIN) {
                        (void) printf(
                            "%s: unknown host or invalid literal address "
                            "(try again later)\n", host);
                } else {
                        (void) printf(
                            "%s: unknown host or invalid literal address\n",
                            host);
                }
                code = -1;
                return ((char *)0);
        }
        ai_head = ai;


        /*
         * If ai_canonname is a IPv4-mapped IPv6 literal, we'll convert it to
         * IPv4 literal address.
         */
        if (ai->ai_canonname != NULL &&
            (inet_pton(AF_INET6, ai->ai_canonname, &ipv6addr) > 0) &&
            IN6_IS_ADDR_V4MAPPED(&ipv6addr)) {
                struct in_addr src4;
                hostnamebuf[0] = '\0';
                IN6_V4MAPPED_TO_INADDR(&ipv6addr, &src4);
                (void) inet_ntop(AF_INET, &src4, hostnamebuf,
                    sizeof (hostnamebuf));

                /*
                 * It can even be the case that the "host" supplied by the user
                 * can be a IPv4-mapped IPv6 literal. So, let's fix that too.
                 */
                if ((inet_pton(AF_INET6, host, &ipv6addr) > 0) &&
                    IN6_IS_ADDR_V4MAPPED(&ipv6addr) &&
                    strlen(hostnamebuf) <= strlen(host)) {
                        (void) strlcpy(host, hostnamebuf, strlen(host) + 1);
                }
        } else {
                reset_timer();
                (void) strlcpy(hostnamebuf,
                    (ai->ai_canonname ? ai->ai_canonname : host),
                    sizeof (hostnamebuf));
        }

        hostname = hostnamebuf;
        for (;;) {
                int oerrno;

                bcopy(ai->ai_addr, &remctladdr, ai->ai_addrlen);
                if (ai->ai_addr->sa_family == AF_INET) {
                        IN6_INADDR_TO_V4MAPPED(
                            &(((struct sockaddr_in *)ai->ai_addr)->sin_addr),
                            &remctladdr.sin6_addr);
                        remctladdr.sin6_family = AF_INET6;
                }

                s = socket(AF_INET6, SOCK_STREAM, 0);
                if (s < 0) {
                        perror("ftp: socket");
                        code = -1;
                        freeaddrinfo(ai_head);
                        return (0);
                }
                if (timeout && setsockopt(s, IPPROTO_TCP, TCP_ABORT_THRESHOLD,
                    (char *)&timeoutms, sizeof (timeoutms)) < 0 && debug)
                        perror("ftp: setsockopt (TCP_ABORT_THRESHOLD)");
                reset_timer();

                error_num = connect(s, (struct sockaddr *)&remctladdr,
                    sizeof (remctladdr));
                oerrno = errno;
                if (error_num >= 0)
                        break;

                /*
                 * Maintain message behavior: only include the address in
                 * our error message if we have another one to try; if this
                 * is the last address on our list, just print the error.
                 */
                if (ai->ai_next != NULL) {
                        (void) fprintf(stderr, "ftp: connect to address %s: ",
                            inet_ntop_native(ai->ai_addr->sa_family,
                            (void *)ai->ai_addr, abuf, sizeof (abuf)));
                        errno = oerrno;
                        perror((char *)0);
                } else {
                        perror("ftp: connect");
                        code = -1;
                        freeaddrinfo(ai_head);
                        goto bad;
                }
                ai = ai->ai_next;
                (void) fprintf(stdout, "Trying %s...\n",
                    inet_ntop_native(ai->ai_addr->sa_family,
                    (void *)ai->ai_addr, abuf, sizeof (abuf)));
                (void) close(s);

        }

        /* Set ipv6rem to TRUE if control connection is a native IPv6 address */
        if (IN6_IS_ADDR_V4MAPPED(&remctladdr.sin6_addr))
                ipv6rem = B_FALSE;
        else
                ipv6rem = B_TRUE;


        freeaddrinfo(ai_head);
        ai = NULL;

        /*
         * Set passive mode flag on by default only if a native IPv6 address
         * is being used -and- the use_eprt is not set.
         */
        if (ipv6rem == B_TRUE && use_eprt == 0)
                passivemode = 1;

        len = sizeof (myctladdr);
        if (getsockname(s, (struct sockaddr *)&myctladdr, &len) < 0) {
                perror("ftp: getsockname");
                code = -1;
                goto bad;
        }
        ctrl_in = fdopen(s, "r");
        ctrl_out = fdopen(s, "w");
        if (ctrl_in == NULL || ctrl_out == NULL) {
                (void) fprintf(stderr, "ftp: fdopen failed.\n");
                if (ctrl_in)
                        (void) fclose(ctrl_in);
                if (ctrl_out)
                        (void) fclose(ctrl_out);
                code = -1;
                goto bad;
        }
        if (verbose)
                (void) printf("Connected to %s.\n", hostname);
        if (getreply(0) > 2) {  /* read startup message from server */
                if (ctrl_in)
                        (void) fclose(ctrl_in);
                if (ctrl_out)
                        (void) fclose(ctrl_out);
                ctrl_in = ctrl_out = NULL;
                ctrl_in = ctrl_out = NULL;
                code = -1;
                goto bad;
        }
        if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (char *)&on,
            sizeof (on)) < 0 && debug)
                perror("ftp: setsockopt (SO_OOBINLINE)");

        return (hostname);
bad:
        (void) close(s);
        return ((char *)0);
}

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

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

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

                        if (pp != NULL)
                                myname = pp->pw_name;
                }
                stop_timer();
                (void) printf("Name (%s:%s): ", host,
                    (myname == NULL) ? "" : myname);
                *tmp = '\0';
                if (fgets(tmp, sizeof (tmp) - 1, stdin) != NULL)
                        tmp[strlen(tmp) - 1] = '\0';
                if (*tmp != '\0')
                        user = tmp;
                else if (myname != NULL)
                        user = myname;
                else
                        return (0);
        }
        n = command("USER %s", user);
        if (n == CONTINUE) {
                int oldclevel;
                if (pass == NULL)
                        pass = mygetpass("Password:");
                oldclevel = clevel;
                clevel = PROT_P;
                n = command("PASS %s", pass);
                /* level may have changed */
                if (clevel == PROT_P)
                        clevel = oldclevel;
        }
        if (n == CONTINUE) {
                aflag++;
                if (acct == NULL)
                        acct = mygetpass("Account:");
                n = command("ACCT %s", acct);
        }
        if (n != COMPLETE) {
                (void) fprintf(stderr, "Login failed.\n");
                return (0);
        }
        if (!aflag && acct != NULL)
                (void) command("ACCT %s", acct);
        if (proxy)
                return (1);
        for (n = 0; n < macnum; ++n) {
                if (strcmp("init", macros[n].mac_name) == 0) {
                        (void) strlcpy(line, "$init", sizeof (line));
                        makeargv();
                        domacro(margc, margv);
                        break;
                }
        }
        return (1);
}

/*ARGSUSED*/
static void
cmdabort(int sig)
{
        (void) printf("\n");
        (void) fflush(stdout);
        abrtflag++;
        if (ptflag)
                longjmp(ptabort, 1);
}

int
command(char *fmt, ...)
{
        int r;
        void (*oldintr)();
        va_list ap;
        char command_buf[FTPBUFSIZ];

        va_start(ap, fmt);
        abrtflag = 0;
        if (debug) {
                (void) printf("---> ");
                if (strncmp("PASS ", fmt, 5) == 0)
                        (void) printf("PASS XXXX");
                else if (strncmp("ACCT ", fmt, 5) == 0)
                        (void) printf("ACCT XXXX");
                else
                        (void) vfprintf(stdout, fmt, ap);
                (void) printf("\n");
                (void) fflush(stdout);
        }
        if (ctrl_out == NULL) {
                perror("No control connection for command");
                code = -1;
                return (0);
        }
        oldintr = signal(SIGINT, cmdabort);
        (void) vsnprintf(command_buf, FTPBUFSIZ, fmt, ap);
        va_end(ap);

again:  if (secure_command(command_buf) == 0)
                return (0);

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

        if (r == 533 && clevel == PROT_P) {
                (void) fprintf(stderr, "ENC command not supported at server; "
                    "retrying under MIC...\n");
                clevel = PROT_S;
                goto again;
        }

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

/* Need to save reply reponse from server for use in EPSV mode */
char reply_string[BUFSIZ];

int
getreply(int expecteof)
{
        /*
         * 'code' is the 3 digit reply code, form xyz
         * 'dig'  counts the number of digits we are along in the code
         * 'n'  is the first digit of 'code'
         *      4yz: resource unavailable
         *      5yz: an error occurred, failure
         *      6yz: protected reply (is_base64 == TRUE)
         *              631 - base 64 encoded safe message
         *              632 - base 64 encoded private message
         *              633 - base 64 encoded confidential message
         * 'c'  is a wide char type, for international char sets
         */
        wint_t c;
        int i, n;
        int dig;
        int originalcode = 0, continuation = 0;
        void (*oldintr)();
        int pflag = 0;
        char *pt = pasv;
        /*
         * this is the input and output buffers needed for
         * radix_encode()
         */
        unsigned char ibuf[FTPBUFSIZ];
        unsigned char obuf[FTPBUFSIZ];
        boolean_t is_base64;
        int len;
        char *cp;

        if (!ctrl_in)
                return (0);
        oldintr = signal(SIGINT, cmdabort);

        ibuf[0] = '\0';

        if (reply_parse)
                reply_ptr = reply_buf;

        for (;;) {
                obuf[0] = '\0';
                dig = n = code = 0;
                i = is_base64 = 0;
                cp = reply_string;
                reset_timer();  /* once per line */

                while ((c = ibuf[0] ?
                    (wint_t)ibuf[i++] : fgetwc(ctrl_in)) != '\n') {

                        if (i >= FTPBUFSIZ)
                                break;

                        if (c == IAC) { /* handle telnet commands */
                                switch (c = fgetwc(ctrl_in)) {
                                case WILL:
                                case WONT:
                                        c = fgetwc(ctrl_in);
                                        (void) fprintf(ctrl_out, "%c%c%wc", IAC,
                                            WONT, c);
                                        (void) fflush(ctrl_out);
                                        break;
                                case DO:
                                case DONT:
                                        c = fgetwc(ctrl_in);
                                        (void) fprintf(ctrl_out, "%c%c%wc", IAC,
                                            DONT, c);
                                        (void) fflush(ctrl_out);
                                        break;
                                default:
                                        break;
                                }
                                continue;
                        }
                        dig++;
                        if (c == EOF) {
                                if (expecteof) {
                                        (void) signal(SIGINT, oldintr);
                                        code = 221;
                                        return (0);
                                }
                                lostpeer(0);
                                if (verbose) {
                                        (void) printf(
                                            "421 Service not available, remote"
                                            " server has closed connection\n");
                                } else {
                                        (void) printf("Lost connection\n");
                                }
                                (void) fflush(stdout);
                                code = 421;
                                return (4);
                        }
                        if (n == 0)
                                n = c;

                        if (n == '6')
                                is_base64 = 1;

                        if ((auth_type != AUTHTYPE_NONE) && !ibuf[0] &&
                            (is_base64 || continuation))  {
                                /* start storing chars in obuf */
                                if (c != '\r' && dig > 4)
                                        obuf[i++] = (char)c;
                        } else {
                                if ((auth_type != AUTHTYPE_NONE) && !ibuf[0] &&
                                    dig == 1 && verbose)
                                        (void) printf("Unauthenticated reply "
                                            "received from server:\n");
                                if (reply_parse)
                                        *reply_ptr++ = (char)c;
                                if (c != '\r' && (verbose > 0 ||
                                    (verbose > -1 && n == '5' && dig > 4))) {
                                        if (proxflag &&
                                            (dig == 1 || dig == 5 &&
                                            verbose == 0))
                                                (void) printf("%s:", hostname);
                                        (void) putwchar(c);
                                }
                        } /* endif auth_type && !ibuf[0] ... */

                        if ((auth_type != AUTHTYPE_NONE) && !ibuf[0] &&
                            !is_base64)
                                continue;

                        /* we are still extracting the 3 digit code */
                        if (dig < 4 && isascii(c) && isdigit(c))
                                code = code * 10 + (c - '0');

                        /* starting passive mode */
                        if (!pflag && code == 227)
                                pflag = 1;

                        /* start to store characters, when dig > 4 */
                        if (dig > 4 && pflag == 1 && isascii(c) && isdigit(c))
                                pflag = 2;
                        if (pflag == 2) {
                                if (c != '\r' && c != ')') {
                                        /*
                                         * the mb array is to deal with
                                         * the wchar_t
                                         */
                                        char mb[MB_LEN_MAX];
                                        int avail;

                                        /*
                                         * space available in pasv[], accounting
                                         * for trailing NULL
                                         */
                                        avail = &pasv[sizeof (pasv)] - pt - 1;

                                        len = wctomb(mb, c);
                                        if (len <= 0 && avail > 0) {
                                                *pt++ = (unsigned char)c;
                                        } else if (len > 0 && avail >= len) {
                                                bcopy(mb, pt, (size_t)len);
                                                pt += len;
                                        } else {
                                                /*
                                                 * no room in pasv[];
                                                 * close connection
                                                 */
                                                (void) printf("\nReply too long"
                                                    " - closing connection\n");
                                                lostpeer(0);
                                                (void) fflush(stdout);
                                                (void) signal(SIGINT, oldintr);
                                                return (4);
                                        }
                                } else {
                                        *pt = '\0';
                                        pflag = 3;
                                }
                        } /* endif pflag == 2 */
                        if (dig == 4 && c == '-' && !is_base64) {
                                if (continuation)
                                        code = 0;
                                continuation++;
                        }
                        if (cp < &reply_string[sizeof (reply_string) - 1])
                                *cp++ = c;

                } /* end while */

                if ((auth_type != AUTHTYPE_NONE) && !ibuf[0] && !is_base64)
                        return (getreply(expecteof));

                ibuf[0] = obuf[i] = '\0';

                if (code && is_base64) {
                        boolean_t again = 0;
                        n = decode_reply(ibuf, sizeof (ibuf), obuf, n, &again);
                        if (again)
                                continue;
                } else {
                        if (verbose > 0 || verbose > -1 && n == '5') {
                                (void) putwchar(c);
                                (void) fflush(stdout);
                        }
                }

                if (continuation && code != originalcode) {
                        ibuf[0] = obuf[i] = '\0';
                        if (originalcode == 0)
                                originalcode = code;
                        continue;
                }
                *cp = '\0';
                if (n != '1')
                        cpend = 0;
                (void) signal(SIGINT, oldintr);
                if (code == 421 || originalcode == 421)
                        lostpeer(0);
                if (abrtflag && oldintr != cmdabort && oldintr != SIG_IGN)
                        (*oldintr)();

                if (reply_parse) {
                        *reply_ptr = '\0';
                        if (reply_ptr = strstr(reply_buf, reply_parse)) {
                                reply_parse = reply_ptr + strlen(reply_parse);
                                if (reply_ptr = strpbrk(reply_parse, " \r"))
                                        *reply_ptr = '\0';
                        } else {
                                reply_parse = reply_ptr;
                        }
                }

                return (n - '0');
        } /* end for */
}

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

        reset_timer();
        t.tv_sec = (time_t)sec;
        t.tv_usec = 0;
        return (select(nfds, mask, NULL, NULL, &t));
}

/*ARGSUSED*/
static void
abortsend(int sig)
{
        mflag = 0;
        abrtflag = 0;
        (void) printf("\nsend aborted\n");
        (void) fflush(stdout);
        longjmp(sendabort, 1);
}

void
sendrequest(char *cmd, char *local, char *remote, int allowpipe)
{
        FILE *fin, *dout = 0;
        int (*closefunc)();
        void (*oldintr)(), (*oldintp)();
        off_t bytes = 0, hashbytes = HASHSIZ;
        int c;
        /*
         * d >=  0 if there is no error
         *      -1 if there was a normal file i/o error
         *      -2 if there was a security error
         */
        int d;
        struct stat st;
        hrtime_t start, stop;
        char *dmode;

        if (proxy) {
                proxtrans(cmd, local, remote);
                return;
        }
        closefunc = NULL;
        oldintr = NULL;
        oldintp = NULL;
        dmode = "w";
        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;
                restart_point = 0;
                return;
        }
        oldintr = signal(SIGINT, abortsend);
        if (strcmp(local, "-") == 0)
                fin = stdin;
        else if (allowpipe && *local == '|') {
                oldintp = signal(SIGPIPE, SIG_IGN);
                fin = mypopen(local + 1, "r");
                if (fin == NULL) {
                        perror(local + 1);
                        (void) signal(SIGINT, oldintr);
                        (void) signal(SIGPIPE, oldintp);
                        code = -1;
                        restart_point = 0;
                        return;
                }
                closefunc = mypclose;
        } else {
                fin = fopen(local, "r");
                if (fin == NULL) {
                        perror(local);
                        (void) signal(SIGINT, oldintr);
                        code = -1;
                        restart_point = 0;
                        return;
                }
                closefunc = fclose;
                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);
                        code = -1;
                        (void) fclose(fin);
                        restart_point = 0;
                        return;
                }
        }
        if (initconn()) {
                (void) signal(SIGINT, oldintr);
                if (oldintp)
                        (void) signal(SIGPIPE, oldintp);
                code = -1;
                if (closefunc != NULL)
                        (*closefunc)(fin);
                restart_point = 0;
                return;
        }
        if (setjmp(sendabort))
                goto abort;
        if ((restart_point > 0) &&
            (strcmp(cmd, "STOR") == 0 || strcmp(cmd, "APPE") == 0)) {
                if (fseeko(fin, restart_point, SEEK_SET) < 0) {
                        perror(local);
                        if (closefunc != NULL)
                                (*closefunc)(fin);
                        restart_point = 0;
                        return;
                }
                if (command("REST %lld", (longlong_t)restart_point)
                    != CONTINUE) {
                        if (closefunc != NULL)
                                (*closefunc)(fin);
                        restart_point = 0;
                        return;
                }
                dmode = "r+w";
        }
        restart_point = 0;
        if (remote) {
                if (command("%s %s", cmd, remote) != PRELIM) {
                        (void) signal(SIGINT, oldintr);
                        if (oldintp)
                                (void) signal(SIGPIPE, oldintp);
                        if (closefunc != NULL)
                                (*closefunc)(fin);
                        if (data >= 0) {
                                (void) close(data);
                                data = -1;
                        }
                        return;
                }
        } else
                if (command("%s", cmd) != PRELIM) {
                        (void) signal(SIGINT, oldintr);
                        if (oldintp)
                                (void) signal(SIGPIPE, oldintp);
                        if (closefunc != NULL)
                                (*closefunc)(fin);
                        if (data >= 0) {
                                (void) close(data);
                                data = -1;
                        }
                        return;
                }
        dout = dataconn(dmode);
        if (dout == NULL)
                goto abort;
        stop_timer();
        oldintp = signal(SIGPIPE, SIG_IGN);
        start = gethrtime();
        switch (type) {

        case TYPE_I:
        case TYPE_L:
                errno = d = 0;
                while ((c = read(fileno(fin), buf, FTPBUFSIZ)) > 0) {
                        if ((d = WRITE(fileno(dout), buf, c)) < 0)
                                break;
                        bytes += c;
                        if (hash) {
                                while (bytes >= hashbytes) {
                                        (void) putchar('#');
                                        hashbytes += HASHSIZ;
                                }
                                (void) fflush(stdout);
                        }
                }
                if (hash && bytes > 0) {
                        if (bytes < hashbytes)
                                (void) putchar('#');
                        (void) putchar('\n');
                        (void) fflush(stdout);
                }
                if (c < 0)
                        perror(local);

                if (d >= 0)
                        d = secure_flush(fileno(dout));

                if (d < 0) {
                        if ((d == -1) && (errno != EPIPE))
                                perror("netout");
                        bytes = -1;
                }
                break;

        case TYPE_A:
                while ((c = getc(fin)) != EOF) {
                        if (c == '\n') {
                                while (hash && (bytes >= hashbytes)) {
                                        (void) putchar('#');
                                        (void) fflush(stdout);
                                        hashbytes += HASHSIZ;
                                }
                                if (ferror(dout) || PUTC('\r', dout) < 0)
                                        break;
                                bytes++;
                        }

                        if (PUTC(c, dout) < 0)
                                break;
                        bytes++;
#ifdef notdef
                        if (c == '\r') {
                                /* this violates rfc */
                                (void) PUTC('\0', dout);
                                bytes++;
                        }
#endif
                }
                if (hash && bytes > 0) {
                        if (bytes < hashbytes)
                                (void) putchar('#');
                        (void) putchar('\n');
                        (void) fflush(stdout);
                }
                if (ferror(fin))
                        perror(local);

                d = ferror(dout) ? -1 : 0;
                if (d == 0)
                        d = secure_flush(fileno(dout));

                if (d < 0) {
                        if ((d == -1) && (errno != EPIPE))
                                perror("netout");
                        bytes = -1;
                }
                break;
        }
        reset_timer();
        if (closefunc != NULL)
                (*closefunc)(fin);
        if (ctrl_in != NULL) {
                int     dfn     = fileno(dout);
                int     nfds    = fileno(ctrl_in);
                fd_set  mask;

                /*
                 * There could be data not yet written to dout,
                 * in the stdio buffer; so, before a shutdown()
                 * on further sends, do fflush(dout)
                 */
                (void) fflush(dout);

                /* sending over; shutdown sending on dfn */
                (void) shutdown(dfn, SHUT_WR);
                FD_ZERO(&mask);
                FD_SET(dfn, &mask);
                FD_SET(nfds, &mask);
                nfds = MAX(dfn, nfds);

                /*
                 * Wait for remote end to either close data socket
                 * or ack that we've closed our end; it doesn't
                 * matter which happens first.
                 */
                (void) select(nfds + 1, &mask, NULL, NULL, NULL);
        }
        (void) fclose(dout); data = -1;
        stop = gethrtime();
        (void) getreply(0);
        (void) signal(SIGINT, oldintr);
        if (oldintp)
                (void) signal(SIGPIPE, oldintp);

        /*
         * Only print the transfer successful message if the code returned
         * from remote is 226 or 250. All other codes are error codes.
         */
        if ((bytes > 0) && verbose && ((code == 226) || (code == 250)))
                ptransfer("sent", bytes, start, stop, local, remote);
        if (!ctrl_in)
                (void) printf("Lost connection\n");
        return;
abort:
        (void) signal(SIGINT, oldintr);
        if (oldintp)
                (void) signal(SIGPIPE, oldintp);
        if (!cpend) {
                code = -1;
                return;
        }
        if (data >= 0) {
                (void) close(data);
                data = -1;
        }
        if (dout) {
                (void) fclose(dout);
                data = -1;
        }
        (void) getreply(0);
        code = -1;
        if (closefunc != NULL && fin != NULL)
                (*closefunc)(fin);
        stop = gethrtime();
        /*
         * Only print the transfer successful message if the code returned
         * from remote is 226 or 250. All other codes are error codes.
         */
        if ((bytes > 0) && verbose && ((code == 226) || (code == 250)))
                ptransfer("sent", bytes, start, stop, local, remote);
        if (!ctrl_in)
                (void) printf("Lost connection\n");
        restart_point = 0;
}

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

void
recvrequest(char *cmd, char *local, char *remote, char *mode, int allowpipe)
{
        FILE *fout, *din = 0;
        int (*closefunc)();
        void (*oldintr)(), (*oldintp)();
        int oldverbose, oldtype = 0, tcrflag, nfnd;
        char msg;
        off_t bytes = 0, hashbytes = HASHSIZ;
        struct fd_set mask;
        int c, d, n;
        hrtime_t start, stop;
        int errflg = 0;
        int infd;
        int nfds;
        int retrcmd;

        retrcmd = (strcmp(cmd, "RETR") == 0);
        if (proxy && retrcmd) {
                proxtrans(cmd, local, remote);
                return;
        }
        closefunc = NULL;
        oldintr = NULL;
        oldintp = NULL;
        tcrflag = !crflag && retrcmd;
        if (setjmp(recvabort)) {
                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 (local != NULL &&
            strcmp(local, "-") != 0 &&
            (*local != '|' || !allowpipe)) {
                if (access(local, W_OK) < 0) {
                        char *dir = rindex(local, '/');
                        int file_errno = errno;

                        if (file_errno != ENOENT && file_errno != EACCES) {
                                perror(local);
                                (void) signal(SIGINT, oldintr);
                                code = -1;
                                return;
                        }
                        if ((dir != NULL) && (dir != local))
                                *dir = 0;
                        if (dir == local)
                                d = access("/", W_OK);
                        else
                                d = access(dir ? local : ".", W_OK);
                        if ((dir != NULL) && (dir != local))
                                *dir = '/';
                        if (d < 0) {
                                perror(local);
                                (void) signal(SIGINT, oldintr);
                                code = -1;
                                return;
                        }
                        if (!runique && file_errno == EACCES) {
                                errno = file_errno;
                                perror(local);
                                (void) signal(SIGINT, oldintr);
                                code = -1;
                                return;
                        }
                        if (runique && file_errno == EACCES &&
                            (local = gunique(local)) == NULL) {
                                (void) signal(SIGINT, oldintr);
                                code = -1;
                                return;
                        }
                } else if (runique && (local = gunique(local)) == NULL) {
                        (void) signal(SIGINT, oldintr);
                        code = -1;
                        return;
                }
        }
        if (initconn()) {
                (void) signal(SIGINT, oldintr);
                code = -1;
                return;
        }
        if (setjmp(recvabort))
                goto abort;
        if (!retrcmd && type != TYPE_A) {
                oldtype = type;
                oldverbose = verbose;
                if (!debug)
                        verbose = 0;
                setascii(0, NULL);
                verbose = oldverbose;
        }
        if ((restart_point > 0) && retrcmd &&
            command("REST %lld", (longlong_t)restart_point) != CONTINUE) {
                return;
        }
        if (remote) {
                if (command("%s %s", cmd, remote) != PRELIM) {
                        (void) signal(SIGINT, oldintr);
                        if (oldtype) {
                                if (!debug)
                                        verbose = 0;
                                switch (oldtype) {
                                        case TYPE_I:
                                                setbinary(0, NULL);
                                                break;
                                        case TYPE_E:
                                                setebcdic(0, NULL);
                                                break;
                                        case TYPE_L:
                                                settenex(0, NULL);
                                                break;
                                }
                                verbose = oldverbose;
                        }
                        return;
                }
        } else {
                if (command("%s", cmd) != PRELIM) {
                        (void) signal(SIGINT, oldintr);
                        if (oldtype) {
                                if (!debug)
                                        verbose = 0;
                                switch (oldtype) {
                                        case TYPE_I:
                                                setbinary(0, NULL);
                                                break;
                                        case TYPE_E:
                                                setebcdic(0, NULL);
                                                break;
                                        case TYPE_L:
                                                settenex(0, NULL);
                                                break;
                                }
                                verbose = oldverbose;
                        }
                        return;
                }
        }
        din = dataconn("r");
        if (din == NULL)
                goto abort;

        if (local == NULL) {
                fout = tmp_nlst;
        } else if (strcmp(local, "-") == 0) {
                fout = stdout;
        } else if (allowpipe && *local == '|') {
                oldintp = signal(SIGPIPE, SIG_IGN);
                fout = mypopen(local + 1, "w");
                if (fout == NULL) {
                        perror(local+1);
                        goto abort;
                }
                closefunc = mypclose;
        } else {
                fout = fopen(local, mode);
                if (fout == NULL) {
                        perror(local);
                        goto abort;
                }
                closefunc = fclose;
        }
        start = gethrtime();
        stop_timer();
        switch (type) {

        case TYPE_I:
        case TYPE_L:
                if ((restart_point > 0) && retrcmd &&
                    lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
                        perror(local);
                        goto abort;
                }
                errno = d = 0;
                infd = fileno(din);
                while ((c = timedread(infd, buf, FTPBUFSIZ, timeout)) > 0) {
                        for (n = 0; n < c; n += d) {
                                d = write(fileno(fout), &buf[n], c - n);
                                if (d == -1)
                                        goto writeerr;
                        }
                        bytes += c;
                        if (hash) {
                                while (bytes >= hashbytes) {
                                        (void) putchar('#');
                                        hashbytes += HASHSIZ;
                                }
                                (void) fflush(stdout);
                        }
                }
                if (hash && bytes > 0) {
                        if (bytes < hashbytes)
                                (void) putchar('#');
                        (void) putchar('\n');
                        (void) fflush(stdout);
                }
                if (c < 0) {
                        errflg = 1;
                        perror("netin");
                }
                if ((d < 0) || ((c == 0) && (fsync(fileno(fout)) == -1))) {
writeerr:
                        errflg = 1;
                        perror(local);
                }
                break;

        case TYPE_A:
                if ((restart_point > 0) && retrcmd) {
                        int c;
                        off_t i = 0;

                        if (fseek(fout, 0L, SEEK_SET) < 0) {
                                perror(local);
                                goto abort;
                        }
                        while (i++ < restart_point) {
                                if ((c = getc(fout)) == EOF) {
                                        if (ferror(fout))
                                                perror(local);
                                        else
                                                (void) fprintf(stderr,
                                                    "%s: Unexpected end of "
                                                    "file\n", local);
                                        goto abort;
                                }
                                if (c == '\n')
                                        i++;
                        }
                        if (fseeko(fout, 0L, SEEK_CUR) < 0) {
                                perror(local);
                                goto abort;
                        }
                }
                fdio_setbuf(buf, FTPBUFSIZ);
                infd = fileno(din);
                while ((c = fdio_getc(infd)) != EOF) {
                        while (c == '\r') {
                                while (hash && (bytes >= hashbytes)) {
                                        (void) putchar('#');
                                        (void) fflush(stdout);
                                        hashbytes += HASHSIZ;
                                }
                                bytes++;

                                if ((c = fdio_getc(infd)) != '\n' || tcrflag) {
                                        if (ferror(fout))
                                                break;
                                        if (putc('\r', fout) == EOF)
                                                goto writer_ascii_err;
                                }
#ifdef notdef
                                if (c == '\0') {
                                        bytes++;
                                        continue;
                                }
#endif
                                if (c == EOF)
                                        goto endread;
                        }
                        if (putc(c, fout) == EOF)
                                goto writer_ascii_err;
                        bytes++;
                }
endread:
                if (hash && bytes > 0) {
                        if (bytes < hashbytes)
                                (void) putchar('#');
                        (void) putchar('\n');
                        (void) fflush(stdout);
                }
                if (fdio_error(infd)) {
                        errflg = 1;
                        perror("netin");
                }
                if ((fflush(fout) == EOF) || ferror(fout) ||
                    (fsync(fileno(fout)) == -1)) {
writer_ascii_err:
                        errflg = 1;
                        perror(local);
                }
                break;
        }
        reset_timer();
        if (closefunc != NULL)
                (*closefunc)(fout);
        (void) signal(SIGINT, oldintr);
        if (oldintp)
                (void) signal(SIGPIPE, oldintp);
        (void) fclose(din); data = -1;
        stop = gethrtime();
        (void) getreply(0);
        if (bytes > 0 && verbose && !errflg)
                ptransfer("received", bytes, start, stop, local, remote);
        if (!ctrl_in)
                (void) printf("Lost connection\n");
        if (oldtype) {
                if (!debug)
                        verbose = 0;
                switch (oldtype) {
                        case TYPE_I:
                                setbinary(0, NULL);
                                break;
                        case TYPE_E:
                                setebcdic(0, NULL);
                                break;
                        case TYPE_L:
                                settenex(0, NULL);
                                break;
                }
                verbose = oldverbose;
        }
        return;
abort:

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

        stop = gethrtime();
        if (oldintp)
                (void) signal(SIGPIPE, oldintp);
        (void) signal(SIGINT, SIG_IGN);
        if (!cpend) {
                code = -1;
                (void) signal(SIGINT, oldintr);
                return;
        }

        (void) fprintf(ctrl_out, "%c%c", IAC, IP);
        (void) fflush(ctrl_out);
        msg = (char)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(ctrl_out), &msg, 1, MSG_OOB) != 1) {
                perror("abort");
        }
        (void) fprintf(ctrl_out, "%cABOR\r\n", DM);
        (void) fflush(ctrl_out);
        nfds = fileno(ctrl_in) + 1;
        FD_ZERO(&mask);
        FD_SET(fileno(ctrl_in), &mask);
        if (din) {
                FD_SET(fileno(din), &mask);
                nfds = MAX(fileno(din) + 1, nfds);
        }
        if ((nfnd = empty(&mask, 10, nfds)) <= 0) {
                if (nfnd < 0) {
                        perror("abort");
                }
                code = -1;
                lostpeer(0);
        }
        if (din && FD_ISSET(fileno(din), &mask)) {
                do {
                        reset_timer();
                } while ((c = read(fileno(din), buf, FTPBUFSIZ)) > 0);
        }
        if ((c = getreply(0)) == ERROR && code == 552) {
                /* needed for nic style abort */
                if (data >= 0) {
                        (void) close(data);
                        data = -1;
                }
                (void) getreply(0);
        }
        if (oldtype) {
                if (!debug)
                        verbose = 0;
                switch (oldtype) {
                case TYPE_I:
                        setbinary(0, NULL);
                        break;
                case TYPE_E:
                        setebcdic(0, NULL);
                        break;
                case TYPE_L:
                        settenex(0, NULL);
                        break;
                }
                verbose = oldverbose;
        }
        (void) getreply(0);
        code = -1;
        if (data >= 0) {
                (void) close(data);
                data = -1;
        }
        if (closefunc != NULL && fout != NULL)
                (*closefunc)(fout);
        if (din) {
                (void) fclose(din);
                data = -1;
        }
        if (bytes > 0 && verbose)
                ptransfer("received", bytes, start, stop, local, remote);
        if (!ctrl_in)
                (void) printf("Lost connection\n");
        (void) signal(SIGINT, oldintr);
}

/*
 * Need to start a listen on the data channel
 * before we send the command, otherwise the
 * server's connect may fail.
 */

static int
initconn(void)
{
        unsigned char *p, *a;
        int result, tmpno = 0;
        int on = 1;
        socklen_t len;
        int v4_addr;
        char *c, *c2, delm;
        in_port_t ports;

        pasv_refused = B_FALSE;
        if (passivemode) {
                data = socket(AF_INET6, SOCK_STREAM, 0);
                if (data < 0) {
                        perror("socket");
                        return (1);
                }
                if (timeout && setsockopt(data, IPPROTO_TCP,
                    TCP_ABORT_THRESHOLD, (char *)&timeoutms,
                    sizeof (timeoutms)) < 0 && debug)
                        perror("ftp: setsockopt (TCP_ABORT_THRESHOLD)");
                if ((options & SO_DEBUG) &&
                    setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on,
                    sizeof (on)) < 0)
                        perror("setsockopt (ignored)");
                /*
                 * Use the system wide default send and receive buffer sizes
                 * unless one has been specified.
                 */
                if (tcpwindowsize) {
                        if (setsockopt(data, SOL_SOCKET, SO_SNDBUF,
                            (char *)&tcpwindowsize, sizeof (tcpwindowsize)) < 0)
                                perror("ftp: setsockopt (SO_SNDBUF - ignored)");
                        if (setsockopt(data, SOL_SOCKET, SO_RCVBUF,
                            (char *)&tcpwindowsize, sizeof (tcpwindowsize)) < 0)
                                perror("ftp: setsockopt (SO_RCVBUF - ignored)");
                }

                data_addr = remctladdr;

                if (ipv6rem == B_TRUE) {
                        if (command("EPSV") != COMPLETE) {
                                (void) fprintf(stderr,
                                    "Passive mode refused. Try EPRT\n");
                                pasv_refused = B_TRUE;
                                goto noport;
                        }

                        /*
                         * Get the data port from reply string from the
                         * server.  The format of the reply string is:
                         * 229 Entering Extended Passive Mode (|||port|)
                         * where | is the delimiter being used.
                         */
                        c = strchr(reply_string, '(');
                        c2 = strchr(reply_string, ')');
                        if (c == NULL || c2 == NULL) {
                                (void) fprintf(stderr, "Extended passive mode"
                                    "parsing failure.\n");
                                goto bad;
                        }
                        *(c2 - 1) = '\0';
                        /* Delimiter is the next char in the reply string */
                        delm = *(++c);
                        while (*c == delm) {
                                if (!*(c++)) {
                                        (void) fprintf(stderr,
                                            "Extended passive mode"
                                            "parsing failure.\n");
                                        goto bad;
                                }
                        }
                        /* assign the port for data connection */
                        ports = (in_port_t)atoi(c);
                        data_addr.sin6_port =  htons(ports);
                } else {
                        int a1, a2, a3, a4, p1, p2;

                        if (command("PASV") != COMPLETE) {
                                (void) fprintf(stderr,
                                    "Passive mode refused. Try PORT\n");
                                pasv_refused = B_TRUE;
                                goto noport;
                        }

                        /*
                         * Get the data port from reply string from the
                         * server.  The format of the reply string is:
                         * 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)
                         */
                        if (sscanf(pasv, "%d,%d,%d,%d,%d,%d",
                            &a1, &a2, &a3, &a4, &p1, &p2) != 6) {
                                (void) fprintf(stderr,
                                    "Passive mode parsing failure.\n");
                                goto bad;
                        }
                        /*
                         * Set the supplied address and port in an
                         * IPv4-mapped IPv6 address.
                         */
                        a = (unsigned char *)&data_addr.sin6_addr +
                            sizeof (struct in6_addr) -
                            sizeof (struct in_addr);
#define UC(b)   ((b)&0xff)
                        a[0] = UC(a1);
                        a[1] = UC(a2);
                        a[2] = UC(a3);
                        a[3] = UC(a4);
                        p = (unsigned char *)&data_addr.sin6_port;
                        p[0] = UC(p1);
                        p[1] = UC(p2);
                }

                if (connect(data, (struct sockaddr *)&data_addr,
                    sizeof (data_addr)) < 0) {
                        perror("connect");
                        goto bad;
                }
                return (0);
        }

noport:
        data_addr = myctladdr;
        if (sendport)
                data_addr.sin6_port = 0;        /* let system pick one */

        if (data != -1)
                (void) close(data);
        data = socket(AF_INET6, 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 (SO_REUSEADDR)");
                        goto bad;
                }
        if (bind(data,
            (struct sockaddr *)&data_addr, sizeof (data_addr)) < 0) {
                perror("ftp: bind");
                goto bad;
        }
        if (timeout && setsockopt(data, IPPROTO_TCP, TCP_ABORT_THRESHOLD,
            (char *)&timeoutms, sizeof (timeoutms)) < 0 && debug)
                perror("ftp: setsockopt (TCP_ABORT_THRESHOLD)");
        if (options & SO_DEBUG &&
            setsockopt(data, SOL_SOCKET, SO_DEBUG,
            (char *)&on, sizeof (on)) < 0)
                perror("ftp: setsockopt (SO_DEBUG - ignored)");
        /*
         * Use the system wide default send and receive buffer sizes unless
         * one has been specified.
         */
        if (tcpwindowsize) {
                if (setsockopt(data, SOL_SOCKET, SO_SNDBUF,
                    (char *)&tcpwindowsize, sizeof (tcpwindowsize)) < 0)
                        perror("ftp: setsockopt (SO_SNDBUF - ignored)");
                if (setsockopt(data, SOL_SOCKET, SO_RCVBUF,
                    (char *)&tcpwindowsize, sizeof (tcpwindowsize)) < 0)
                        perror("ftp: setsockopt (SO_RCVBUF - ignored)");
        }
        len = sizeof (data_addr);
        if (getsockname(data, (struct sockaddr *)&data_addr, &len) < 0) {
                perror("ftp: getsockname");
                goto bad;
        }

        v4_addr = IN6_IS_ADDR_V4MAPPED(&data_addr.sin6_addr);
        if (listen(data, 1) < 0)
                perror("ftp: listen");

        if (sendport) {
                a = (unsigned char *)&data_addr.sin6_addr;
                p = (unsigned char *)&data_addr.sin6_port;
                if (v4_addr) {
                        result =
                            command("PORT %d,%d,%d,%d,%d,%d",
                            UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
                            UC(p[0]), UC(p[1]));
                } else {
                        char hname[INET6_ADDRSTRLEN];

                        result = COMPLETE + 1;
                        /*
                         * if on previous try to server, it was
                         * determined that the server doesn't support
                         * EPRT, don't bother trying again.  Just try
                         * LPRT.
                         */
                        if (eport_supported == B_TRUE) {
                                if (inet_ntop(AF_INET6, &data_addr.sin6_addr,
                                    hname, sizeof (hname)) != NULL) {
                                        result = command("EPRT |%d|%s|%d|", 2,
                                            hname, htons(data_addr.sin6_port));
                                        if (result != COMPLETE)
                                                eport_supported = B_FALSE;
                                }
                        }
                        /* Try LPRT */
                        if (result != COMPLETE) {
                                result = command(
                                    "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,"
                                    "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
                                    6, 16,
                                    UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
                                    UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
                                    UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
                                    UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
                                    2, UC(p[0]), UC(p[1]));
                        }
                }

                if (result == ERROR && sendport == -1) {
                        sendport = 0;
                        tmpno = 1;
                        goto noport;
                }
                return (result != COMPLETE);
        }
        if (tmpno)
                sendport = 1;
        return (0);
bad:
        (void) close(data), data = -1;
        if (tmpno)
                sendport = 1;
        return (1);
}

static FILE *
dataconn(char *mode)
{
        struct sockaddr_in6 from;
        int s;
        socklen_t fromlen = sizeof (from);

        reset_timer();
        if (passivemode && !pasv_refused)
                return (fdopen(data, mode));

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

static void
ptransfer(char *direction, off_t bytes, hrtime_t t0,
    hrtime_t t1, char *local, char *remote)
{
        hrtime_t td; /* nanoseconds in a 64 bit int */
        double s, bs;

        td = t1 - t0;
        s = (double)td / 1000000000.0; /* seconds */
        bs = (double)bytes / NONZERO(s);
        if (local && *local != '-')
                (void) printf("local: %s ", local);
        if (remote)
                (void) printf("remote: %s\n", remote);
        (void) printf("%lld bytes %s in %.2g seconds (%.2f Kbytes/s)\n",
            (longlong_t)bytes, direction, s, bs / 1024.0);
}

/*ARGSUSED*/
static void
psabort(int sig)
{
        abrtflag++;
}

void
pswitch(int flag)
{
        void (*oldintr)();
        static struct comvars {
                int connect;
                char name[MAXHOSTNAMELEN];
                struct sockaddr_in6 mctl;
                struct sockaddr_in6 hctl;
                FILE *in;
                FILE *out;
                int tpe;
                int cpnd;
                int sunqe;
                int runqe;
                int mcse;
                int ntflg;
                char nti[17];
                char nto[17];
                int mapflg;
                char mi[MAXPATHLEN];
                char mo[MAXPATHLEN];
                int authtype;
                int clvl;
                int dlvl;
                } proxstruct, tmpstruct;
        struct comvars *ip, *op;

        abrtflag = 0;
        oldintr = 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)
                (void) strlcpy(ip->name, hostname, sizeof (ip->name));
        else
                ip->name[0] = 0;
        hostname = op->name;
        ip->hctl = remctladdr;
        remctladdr = op->hctl;
        ip->mctl = myctladdr;
        myctladdr = op->mctl;
        ip->in = ctrl_in;
        ctrl_in = op->in;
        ip->out = ctrl_out;
        ctrl_out = op->out;
        ip->tpe = type;
        type = op->tpe;
        if (!type)
                type = 1;
        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;
        (void) strlcpy(ip->nti, ntin, sizeof (ip->nti));
        (void) strlcpy(ntin, op->nti, sizeof (ntin));
        (void) strlcpy(ip->nto, ntout, sizeof (ip->nto));
        (void) strlcpy(ntout, op->nto, sizeof (ntout));
        ip->mapflg = mapflag;
        mapflag = op->mapflg;
        (void) strlcpy(ip->mi, mapin, sizeof (ip->mi));
        (void) strlcpy(mapin, op->mi, sizeof (mapin));
        (void) strlcpy(ip->mo, mapout, sizeof (ip->mo));
        (void) strlcpy(mapout, op->mo, sizeof (mapout));

        ip->authtype = auth_type;
        auth_type = op->authtype;
        ip->clvl = clevel;
        clevel = op->clvl;
        ip->dlvl = dlevel;
        dlevel = op->dlvl;
        if (!clevel)
                clevel = PROT_C;
        if (!dlevel)
                dlevel = PROT_C;

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

/*ARGSUSED*/
static void
abortpt(int sig)
{
        (void) printf("\n");
        (void) fflush(stdout);
        ptabflg++;
        mflag = 0;
        abrtflag = 0;
        longjmp(ptabort, 1);
}

static void
proxtrans(char *cmd, char *local, char *remote)
{
        void (*oldintr)();
        int tmptype, oldtype = 0, secndflag = 0, nfnd;
        extern jmp_buf ptabort;
        char *cmd2;
        struct fd_set mask;
        int ipv4_addr = IN6_IS_ADDR_V4MAPPED(&remctladdr.sin6_addr);

        if (strcmp(cmd, "RETR"))
                cmd2 = "RETR";
        else
                cmd2 = runique ? "STOU" : "STOR";
        if (command(ipv4_addr ? "PASV" : "EPSV") != COMPLETE) {
                (void) printf(
                    "proxy server does not support third part transfers.\n");
                return;
        }
        tmptype = type;
        pswitch(0);
        if (!connected) {
                (void) printf("No primary connection\n");
                pswitch(1);
                code = -1;
                return;
        }
        if (type != tmptype) {
                oldtype = type;
                switch (tmptype) {
                        case TYPE_A:
                                setascii(0, NULL);
                                break;
                        case TYPE_I:
                                setbinary(0, NULL);
                                break;
                        case TYPE_E:
                                setebcdic(0, NULL);
                                break;
                        case TYPE_L:
                                settenex(0, NULL);
                                break;
                }
        }
        if (command(ipv4_addr ? "PORT %s" : "EPRT %s", pasv) != COMPLETE) {
                switch (oldtype) {
                        case 0:
                                break;
                        case TYPE_A:
                                setascii(0, NULL);
                                break;
                        case TYPE_I:
                                setbinary(0, NULL);
                                break;
                        case TYPE_E:
                                setebcdic(0, NULL);
                                break;
                        case TYPE_L:
                                settenex(0, NULL);
                                break;
                }
                pswitch(1);
                return;
        }
        if (setjmp(ptabort))
                goto abort;
        oldintr = signal(SIGINT, (void (*)())abortpt);
        if (command("%s %s", cmd, remote) != PRELIM) {
                (void) signal(SIGINT, oldintr);
                switch (oldtype) {
                        case 0:
                                break;
                        case TYPE_A:
                                setascii(0, NULL);
                                break;
                        case TYPE_I:
                                setbinary(0, NULL);
                                break;
                        case TYPE_E:
                                setebcdic(0, NULL);
                                break;
                        case TYPE_L:
                                settenex(0, NULL);
                                break;
                }
                pswitch(1);
                return;
        }
        (void) sleep(2);
        pswitch(1);
        secndflag++;
        if (command("%s %s", cmd2, local) != PRELIM)
                goto abort;
        ptflag++;
        (void) getreply(0);
        pswitch(0);
        (void) getreply(0);
        (void) signal(SIGINT, oldintr);
        switch (oldtype) {
                case 0:
                        break;
                case TYPE_A:
                        setascii(0, NULL);
                        break;
                case TYPE_I:
                        setbinary(0, NULL);
                        break;
                case TYPE_E:
                        setebcdic(0, NULL);
                        break;
                case TYPE_L:
                        settenex(0, NULL);
                        break;
        }
        pswitch(1);
        ptflag = 0;
        (void) printf("local: %s remote: %s\n", local, remote);
        return;
abort:
        (void) signal(SIGINT, SIG_IGN);
        ptflag = 0;
        if (strcmp(cmd, "RETR") && !proxy)
                pswitch(1);
        else if ((strcmp(cmd, "RETR") == 0) && proxy)
                pswitch(0);
        if (!cpend && !secndflag) {  /* only here if cmd = "STOR" (proxy=1) */
                if (command("%s %s", cmd2, local) != PRELIM) {
                        pswitch(0);
                        switch (oldtype) {
                                case 0:
                                        break;
                                case TYPE_A:
                                        setascii(0, NULL);
                                        break;
                                case TYPE_I:
                                        setbinary(0, NULL);
                                        break;
                                case TYPE_E:
                                        setebcdic(0, NULL);
                                        break;
                                case TYPE_L:
                                        settenex(0, NULL);
                                        break;
                        }
                        if (cpend) {
                                char msg[2];

                                (void) fprintf(ctrl_out, "%c%c", IAC, IP);
                                (void) fflush(ctrl_out);
                                *msg = (char)IAC;
                                *(msg+1) = (char)DM;
                                if (send(fileno(ctrl_out), msg, 2, MSG_OOB)
                                    != 2)
                                        perror("abort");
                                (void) fprintf(ctrl_out, "ABOR\r\n");
                                (void) fflush(ctrl_out);
                                FD_ZERO(&mask);
                                FD_SET(fileno(ctrl_in), &mask);
                                if ((nfnd = empty(&mask, 10,
                                    fileno(ctrl_in) + 1)) <= 0) {
                                        if (nfnd < 0) {
                                                perror("abort");
                                        }
                                        if (ptabflg)
                                                code = -1;
                                        lostpeer(0);
                                }
                                (void) getreply(0);
                                (void) getreply(0);
                        }
                }
                pswitch(1);
                if (ptabflg)
                        code = -1;
                (void) signal(SIGINT, oldintr);
                return;
        }
        if (cpend) {
                char msg[2];

                (void) fprintf(ctrl_out, "%c%c", IAC, IP);
                (void) fflush(ctrl_out);
                *msg = (char)IAC;
                *(msg+1) = (char)DM;
                if (send(fileno(ctrl_out), msg, 2, MSG_OOB) != 2)
                        perror("abort");
                (void) fprintf(ctrl_out, "ABOR\r\n");
                (void) fflush(ctrl_out);
                FD_ZERO(&mask);
                FD_SET(fileno(ctrl_in), &mask);
                if ((nfnd = empty(&mask, 10, fileno(ctrl_in) + 1)) <= 0) {
                        if (nfnd < 0) {
                                perror("abort");
                        }
                        if (ptabflg)
                                code = -1;
                        lostpeer(0);
                }
                (void) getreply(0);
                (void) getreply(0);
        }
        pswitch(!proxy);
        if (!cpend && !secndflag) {  /* only if cmd = "RETR" (proxy=1) */
                if (command("%s %s", cmd2, local) != PRELIM) {
                        pswitch(0);
                        switch (oldtype) {
                                case 0:
                                        break;
                                case TYPE_A:
                                        setascii(0, NULL);
                                        break;
                                case TYPE_I:
                                        setbinary(0, NULL);
                                        break;
                                case TYPE_E:
                                        setebcdic(0, NULL);
                                        break;
                                case TYPE_L:
                                        settenex(0, NULL);
                                        break;
                        }
                        if (cpend) {
                                char msg[2];

                                (void) fprintf(ctrl_out, "%c%c", IAC, IP);
                                (void) fflush(ctrl_out);
                                *msg = (char)IAC;
                                *(msg+1) = (char)DM;
                                if (send(fileno(ctrl_out), msg, 2, MSG_OOB)
                                    != 2)
                                        perror("abort");
                                (void) fprintf(ctrl_out, "ABOR\r\n");
                                (void) fflush(ctrl_out);
                                FD_ZERO(&mask);
                                FD_SET(fileno(ctrl_in), &mask);
                                if ((nfnd = empty(&mask, 10,
                                    fileno(ctrl_in) + 1)) <= 0) {
                                        if (nfnd < 0) {
                                                perror("abort");
                                        }
                                        if (ptabflg)
                                                code = -1;
                                        lostpeer(0);
                                }
                                (void) getreply(0);
                                (void) getreply(0);
                        }
                        pswitch(1);
                        if (ptabflg)
                                code = -1;
                        (void) signal(SIGINT, oldintr);
                        return;
                }
        }
        if (cpend) {
                char msg[2];

                (void) fprintf(ctrl_out, "%c%c", IAC, IP);
                (void) fflush(ctrl_out);
                *msg = (char)IAC;
                *(msg+1) = (char)DM;
                if (send(fileno(ctrl_out), msg, 2, MSG_OOB) != 2)
                        perror("abort");
                (void) fprintf(ctrl_out, "ABOR\r\n");
                (void) fflush(ctrl_out);
                FD_ZERO(&mask);
                FD_SET(fileno(ctrl_in), &mask);
                if ((nfnd = empty(&mask, 10, fileno(ctrl_in) + 1)) <= 0) {
                        if (nfnd < 0) {
                                perror("abort");
                        }
                        if (ptabflg)
                                code = -1;
                        lostpeer(0);
                }
                (void) getreply(0);
                (void) getreply(0);
        }
        pswitch(!proxy);
        if (cpend) {
                FD_ZERO(&mask);
                FD_SET(fileno(ctrl_in), &mask);
                if ((nfnd = empty(&mask, 10, fileno(ctrl_in) + 1)) <= 0) {
                        if (nfnd < 0) {
                                perror("abort");
                        }
                        if (ptabflg)
                                code = -1;
                        lostpeer(0);
                }
                (void) getreply(0);
                (void) getreply(0);
        }
        if (proxy)
                pswitch(0);
        switch (oldtype) {
                case 0:
                        break;
                case TYPE_A:
                        setascii(0, NULL);
                        break;
                case TYPE_I:
                        setbinary(0, NULL);
                        break;
                case TYPE_E:
                        setebcdic(0, NULL);
                        break;
                case TYPE_L:
                        settenex(0, NULL);
                        break;
        }
        pswitch(1);
        if (ptabflg)
                code = -1;
        (void) signal(SIGINT, oldintr);
}

/*ARGSUSED*/
void
reset(int argc, char *argv[])
{
        struct fd_set mask;
        int nfnd = 1;

        FD_ZERO(&mask);
        while (nfnd > 0) {
                FD_SET(fileno(ctrl_in), &mask);
                if ((nfnd = empty(&mask, 0, fileno(ctrl_in) + 1)) < 0) {
                        perror("reset");
                        code = -1;
                        lostpeer(0);
                } else if (nfnd > 0) {
                        (void) getreply(0);
                }
        }
}

static 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) {
                perror(local);
                return ((char *)0);
        }
        if (strlcpy(new, local, sizeof (new)) >= sizeof (new))
                (void) printf("gunique: too long: local %s, %d, new %d\n",
                    local, strlen(local), sizeof (new));

        cp = new + strlen(new);
        *cp++ = '.';
        while (!d) {
                if (++count == 100) {
                        (void) printf(
                            "gunique: can't find unique file name.\n");
                        return (NULL);
                }
                *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);
}

/*
 * This is a wrap-around function for inet_ntop(). In case the af is AF_INET6
 * and the address pointed by src is a IPv4-mapped IPv6 address, it
 * returns printable IPv4 address, not IPv4-mapped IPv6 address. In other cases
 * it behaves just like inet_ntop().
 */
const char *
inet_ntop_native(int af, const void *src, char *dst, size_t size)
{
        struct in_addr src4;
        const char *result;
        struct sockaddr_in *sin;
        struct sockaddr_in6 *sin6;

        if (af == AF_INET6) {
                sin6 = (struct sockaddr_in6 *)src;
                if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
                        IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &src4);
                        result = inet_ntop(AF_INET, &src4, dst, size);
                } else {
                        result = inet_ntop(AF_INET6, &sin6->sin6_addr,
                            dst, size);
                }
        } else {
                sin = (struct sockaddr_in *)src;
                result = inet_ntop(af, &sin->sin_addr, dst, size);
        }

        return (result);
}

int
secure_command(char *cmd)
{
        unsigned char *in = NULL, *out = NULL;
        int length = 0;
        size_t inlen;

        if ((auth_type != AUTHTYPE_NONE) && clevel != PROT_C) {
                gss_buffer_desc in_buf, out_buf;
                OM_uint32 maj_stat, min_stat;

                /* secure_command (based on level) */
                if (auth_type == AUTHTYPE_GSSAPI) {
                        OM_uint32 expire_time;
                        int conf_state;
                        /* clevel = PROT_P; */
                        in_buf.value = cmd;
                        in_buf.length = strlen(cmd) + 1;

                        maj_stat = gss_context_time(&min_stat, gcontext,
                            &expire_time);
                        if (GSS_ERROR(maj_stat)) {
                                user_gss_error(maj_stat, min_stat,
                                    "gss context has expired");
                                fatal("Your gss credentials have expired.  "
                                    "Good-bye!");
                        }
                        maj_stat = gss_seal(&min_stat, gcontext,
                            (clevel == PROT_P), /* private */
                            GSS_C_QOP_DEFAULT,
                            &in_buf, &conf_state,
                            &out_buf);
                        if (maj_stat != GSS_S_COMPLETE) {
                                /* generally need to deal */
                                user_gss_error(maj_stat, min_stat,
                                    (clevel == PROT_P) ?
                                    "gss_seal ENC didn't complete":
                                    "gss_seal MIC didn't complete");
                        } else if ((clevel == PROT_P) && !conf_state) {
                                (void) fprintf(stderr,
                                    "GSSAPI didn't encrypt message");
                                out = out_buf.value;
                        } else {
                                if (debug)
                                        (void) fprintf(stderr,
                                            "sealed (%s) %d bytes\n",
                                            clevel == PROT_P ? "ENC" : "MIC",
                                            out_buf.length);

                                out = out_buf.value;
                        }
                }
                /* Other auth types go here ... */
                inlen = ((4 * out_buf.length) / 3) + 4;
                in = (uchar_t *)malloc(inlen);
                if (in == NULL) {
                        gss_release_buffer(&min_stat, &out_buf);
                        fatal("Memory error allocating space for response.");
                }
                length = out_buf.length;
                if (auth_error = radix_encode(out, in, inlen, &length, 0)) {
                        (void) fprintf(stderr,
                            "Couldn't base 64 encode command (%s)\n",
                            radix_error(auth_error));
                        free(in);
                        gss_release_buffer(&min_stat, &out_buf);
                        return (0);
                }

                (void) fprintf(ctrl_out, "%s %s",
                    clevel == PROT_P ? "ENC" : "MIC", in);

                free(in);
                gss_release_buffer(&min_stat, &out_buf);

                if (debug)
                        (void) fprintf(stderr,
                            "secure_command(%s)\nencoding %d bytes %s %s\n",
                            cmd, length,
                            (clevel == PROT_P) ? "ENC" : "MIC", in);
        } else {
                /*
                 * auth_type = AUTHTYPE_NONE or
                 * command channel is not protected
                 */
                fputs(cmd, ctrl_out);
        }

        (void) fprintf(ctrl_out, "\r\n");
        (void) fflush(ctrl_out);
        return (1);
}

unsigned int maxbuf;
unsigned char *ucbuf;

void
setpbsz(unsigned int size)
{
        unsigned int actualbuf;
        int oldverbose;

        if (ucbuf)
                (void) free(ucbuf);
        actualbuf = size;
        while ((ucbuf = (unsigned char *)malloc(actualbuf)) == NULL) {
                if (actualbuf)
                        actualbuf >>= 2;
                else {
                        perror("Error while trying to malloc PROT buffer:");
                        exit(1);
                }
        }
        oldverbose = verbose;
        verbose = 0;
        reply_parse = "PBSZ=";
        if (command("PBSZ %u", actualbuf) != COMPLETE)
                fatal("Cannot set PROT buffer size");
        if (reply_parse) {
                if ((maxbuf = (unsigned int) atol(reply_parse)) > actualbuf)
                        maxbuf = actualbuf;
        } else
                maxbuf = actualbuf;
        reply_parse = NULL;
        verbose = oldverbose;
}

/*
 * Do the base 64 decoding of the raw input buffer, b64_buf.
 * Also do the verification and decryption, if required.
 * retval contains the current error code number
 *
 * returns:
 *      (RFC 2228:  error returns are 3 digit numbers of the form 5xy)
 *      5       if an error occurred
 */
static int
decode_reply(uchar_t *plain_buf, int ilen, uchar_t *b64_buf, int retval,
    boolean_t *again)
{
        int len;
        int safe = 0;

        *again = 0;

        if (!b64_buf[0])        /* if there is no string, no problem */
                return (retval);

        if ((auth_type == AUTHTYPE_NONE)) {
                (void) printf("Cannot decode reply:\n%d %s\n", code, b64_buf);
                return ('5');
        }

        switch (code) {

        case 631:       /* 'safe' */
                safe = 1;
                break;

        case 632:       /* 'private' */
                break;

        case 633:       /* 'confidential' */
                break;

        default:
                (void) printf("Unknown reply: %d %s\n", code, b64_buf);
                return ('5');
        }

        /* decode the base64 encoded message */
        auth_error = radix_encode(b64_buf, plain_buf, ilen, &len, 1);

        if (auth_error) {
                (void) printf("Can't base 64 decode reply %d (%s)\n\"%s\"\n",
                    code, radix_error(auth_error), b64_buf);
                return ('5');
        }

        if (auth_type == AUTHTYPE_GSSAPI) {
                gss_buffer_desc xmit_buf, msg_buf;
                OM_uint32 maj_stat, min_stat;
                int conf_state = safe;
                xmit_buf.value = plain_buf;
                xmit_buf.length = len;

                /* decrypt/verify the message */
                maj_stat = gss_unseal(&min_stat, gcontext,
                    &xmit_buf, &msg_buf, &conf_state, NULL);
                if (maj_stat != GSS_S_COMPLETE) {
                        user_gss_error(maj_stat, min_stat,
                            "failed unsealing reply");
                        return ('5');
                }
                if (msg_buf.length < ilen - 2 - 1) {
                        memcpy(plain_buf, msg_buf.value, msg_buf.length);
                        strcpy((char *)&plain_buf[msg_buf.length], "\r\n");
                        gss_release_buffer(&min_stat, &msg_buf);
                        *again = 1;
                } else {
                        user_gss_error(maj_stat, min_stat,
                            "reply was too long");
                        return ('5');
                }
        } /* end if GSSAPI */

        /* Other auth types go here... */

        return (retval);
}