Import of NetBSD's ftp client under its new name (tnftp)
authorPeter Avalos <pavalos@dragonflybsd.org>
Wed, 8 Nov 2006 21:21:34 +0000 (21:21 +0000)
committerPeter Avalos <pavalos@dragonflybsd.org>
Wed, 8 Nov 2006 21:21:34 +0000 (21:21 +0000)
15 files changed:
contrib/tnftp/cmds.c [new file with mode: 0644]
contrib/tnftp/cmdtab.c [new file with mode: 0644]
contrib/tnftp/complete.c [new file with mode: 0644]
contrib/tnftp/domacro.c [new file with mode: 0644]
contrib/tnftp/extern.h [new file with mode: 0644]
contrib/tnftp/fetch.c [new file with mode: 0644]
contrib/tnftp/ftp.1 [new file with mode: 0644]
contrib/tnftp/ftp.c [new file with mode: 0644]
contrib/tnftp/ftp_var.h [new file with mode: 0644]
contrib/tnftp/main.c [new file with mode: 0644]
contrib/tnftp/progressbar.c [new file with mode: 0644]
contrib/tnftp/progressbar.h [new file with mode: 0644]
contrib/tnftp/ruserpass.c [new file with mode: 0644]
contrib/tnftp/util.c [new file with mode: 0644]
contrib/tnftp/version.h [new file with mode: 0644]

diff --git a/contrib/tnftp/cmds.c b/contrib/tnftp/cmds.c
new file mode 100644 (file)
index 0000000..10bebee
--- /dev/null
@@ -0,0 +1,2745 @@
+/*     $NetBSD: cmds.c,v 1.118 2006/01/31 20:05:35 christos Exp $      */
+
+/*-
+ * Copyright (c) 1996-2005 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
+ * NASA Ames Research Center.
+ *
+ * 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 NetBSD
+ *     Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The 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. 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.
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * 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. Neither the name of the project 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 PROJECT 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 PROJECT 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 <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmds.c     8.6 (Berkeley) 10/9/94";
+#else
+__RCSID("$NetBSD: cmds.c,v 1.118 2006/01/31 20:05:35 christos Exp $");
+#endif
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Command Routines.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <arpa/ftp.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <glob.h>
+#include <limits.h>
+#include <netdb.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "ftp_var.h"
+#include "version.h"
+
+struct types {
+       char    *t_name;
+       char    *t_mode;
+       int     t_type;
+       char    *t_arg;
+} types[] = {
+       { "ascii",      "A",    TYPE_A, 0 },
+       { "binary",     "I",    TYPE_I, 0 },
+       { "image",      "I",    TYPE_I, 0 },
+       { "ebcdic",     "E",    TYPE_E, 0 },
+       { "tenex",      "L",    TYPE_L, bytename },
+       { NULL }
+};
+
+sigjmp_buf      jabort;
+const char     *mname;
+
+static int     confirm(const char *, const char *);
+
+static const char *doprocess(char *, size_t, const char *, int, int, int);
+static const char *domap(char *, size_t, const char *);
+static const char *docase(char *, size_t, const char *);
+static const char *dotrans(char *, size_t, const char *);
+
+static int
+confirm(const char *cmd, const char *file)
+{
+       const char *errormsg;
+       char line[BUFSIZ];
+
+       if (!interactive || confirmrest)
+               return (1);
+       while (1) {
+               fprintf(ttyout, "%s %s [anpqy?]? ", cmd, file);
+               (void)fflush(ttyout);
+               if (getline(stdin, line, sizeof(line), &errormsg) < 0) {
+                       mflag = 0;
+                       fprintf(ttyout, "%s; %s aborted\n", errormsg, mname);
+                       return (0);
+               }
+               switch (tolower((unsigned char)*line)) {
+                       case 'a':
+                               confirmrest = 1;
+                               fprintf(ttyout,
+                                   "Prompting off for duration of %s.\n", cmd);
+                               break;
+                       case 'p':
+                               interactive = 0;
+                               fputs("Interactive mode: off.\n", ttyout);
+                               break;
+                       case 'q':
+                               mflag = 0;
+                               fprintf(ttyout, "%s aborted.\n", mname);
+                               /* FALLTHROUGH */
+                       case 'n':
+                               return (0);
+                       case '?':
+                               fprintf(ttyout,
+                                   "  confirmation options:\n"
+                                   "\ta  answer `yes' for the duration of %s\n"
+                                   "\tn  answer `no' for this file\n"
+                                   "\tp  turn off `prompt' mode\n"
+                                   "\tq  stop the current %s\n"
+                                   "\ty  answer `yes' for this file\n"
+                                   "\t?  this help list\n",
+                                   cmd, cmd);
+                               continue;       /* back to while(1) */
+               }
+               return (1);
+       }
+       /* NOTREACHED */
+}
+
+/*
+ * Set transfer type.
+ */
+void
+settype(int argc, char *argv[])
+{
+       struct types *p;
+       int comret;
+
+       if (argc == 0 || argc > 2) {
+               char *sep;
+
+               UPRINTF("usage: %s [", argv[0]);
+               sep = " ";
+               for (p = types; p->t_name; p++) {
+                       fprintf(ttyout, "%s%s", sep, p->t_name);
+                       sep = " | ";
+               }
+               fputs(" ]\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (argc < 2) {
+               fprintf(ttyout, "Using %s mode to transfer files.\n", typename);
+               code = 0;
+               return;
+       }
+       for (p = types; p->t_name; p++)
+               if (strcmp(argv[1], p->t_name) == 0)
+                       break;
+       if (p->t_name == 0) {
+               fprintf(ttyout, "%s: unknown mode.\n", argv[1]);
+               code = -1;
+               return;
+       }
+       if ((p->t_arg != NULL) && (*(p->t_arg) != '\0'))
+               comret = command("TYPE %s %s", p->t_mode, p->t_arg);
+       else
+               comret = command("TYPE %s", p->t_mode);
+       if (comret == COMPLETE) {
+               (void)strlcpy(typename, p->t_name, sizeof(typename));
+               curtype = type = p->t_type;
+       }
+}
+
+/*
+ * Internal form of settype; changes current type in use with server
+ * without changing our notion of the type for data transfers.
+ * Used to change to and from ascii for listings.
+ */
+void
+changetype(int newtype, int show)
+{
+       struct types *p;
+       int comret, oldverbose = verbose;
+
+       if (newtype == 0)
+               newtype = TYPE_I;
+       if (newtype == curtype)
+               return;
+       if (ftp_debug == 0 && show == 0)
+               verbose = 0;
+       for (p = types; p->t_name; p++)
+               if (newtype == p->t_type)
+                       break;
+       if (p->t_name == 0) {
+               warnx("internal error: unknown type %d.", newtype);
+               return;
+       }
+       if (newtype == TYPE_L && bytename[0] != '\0')
+               comret = command("TYPE %s %s", p->t_mode, bytename);
+       else
+               comret = command("TYPE %s", p->t_mode);
+       if (comret == COMPLETE)
+               curtype = newtype;
+       verbose = oldverbose;
+}
+
+char *stype[] = {
+       "type",
+       "",
+       0
+};
+
+/*
+ * Set binary transfer type.
+ */
+/*VARARGS*/
+void
+setbinary(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       stype[1] = "binary";
+       settype(2, stype);
+}
+
+/*
+ * Set ascii transfer type.
+ */
+/*VARARGS*/
+void
+setascii(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       stype[1] = "ascii";
+       settype(2, stype);
+}
+
+/*
+ * Set tenex transfer type.
+ */
+/*VARARGS*/
+void
+settenex(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       stype[1] = "tenex";
+       settype(2, stype);
+}
+
+/*
+ * Set file transfer mode.
+ */
+/*ARGSUSED*/
+void
+setftmode(int argc, char *argv[])
+{
+
+       if (argc != 2) {
+               UPRINTF("usage: %s mode-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       fprintf(ttyout, "We only support %s mode, sorry.\n", modename);
+       code = -1;
+}
+
+/*
+ * Set file transfer format.
+ */
+/*ARGSUSED*/
+void
+setform(int argc, char *argv[])
+{
+
+       if (argc != 2) {
+               UPRINTF("usage: %s format\n", argv[0]);
+               code = -1;
+               return;
+       }
+       fprintf(ttyout, "We only support %s format, sorry.\n", formname);
+       code = -1;
+}
+
+/*
+ * Set file transfer structure.
+ */
+/*ARGSUSED*/
+void
+setstruct(int argc, char *argv[])
+{
+
+       if (argc != 2) {
+               UPRINTF("usage: %s struct-mode\n", argv[0]);
+               code = -1;
+               return;
+       }
+       fprintf(ttyout, "We only support %s structure, sorry.\n", structname);
+       code = -1;
+}
+
+/*
+ * Send a single file.
+ */
+void
+put(int argc, char *argv[])
+{
+       char buf[MAXPATHLEN];
+       char *cmd;
+       int loc = 0;
+       char *locfile;
+       const char *remfile;
+
+       if (argc == 2) {
+               argc++;
+               argv[2] = argv[1];
+               loc++;
+       }
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "local-file")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s local-file [remote-file]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if ((locfile = globulize(argv[1])) == NULL) {
+               code = -1;
+               return;
+       }
+       remfile = argv[2];
+       if (loc)        /* If argv[2] is a copy of the old argv[1], update it */
+               remfile = locfile;
+       cmd = (argv[0][0] == 'a') ? "APPE" : ((sunique) ? "STOU" : "STOR");
+       remfile = doprocess(buf, sizeof(buf), remfile,
+               0, loc && ntflag, loc && mapflag);
+       sendrequest(cmd, locfile, remfile,
+           locfile != argv[1] || remfile != argv[2]);
+       free(locfile);
+}
+
+static const char *
+doprocess(char *dst, size_t dlen, const char *src,
+    int casef, int transf, int mapf)
+{
+       if (casef)
+               src = docase(dst, dlen, src);
+       if (transf)
+               src = dotrans(dst, dlen, src);
+       if (mapf)
+               src = domap(dst, dlen, src);
+       return src;
+}
+
+/*
+ * Send multiple files.
+ */
+void
+mput(int argc, char *argv[])
+{
+       int i;
+       sigfunc oldintr;
+       int ointer;
+       const char *tp;
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "local-files"))) {
+               UPRINTF("usage: %s local-files\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mname = argv[0];
+       mflag = 1;
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort();
+       if (proxy) {
+               char *cp;
+
+               while ((cp = remglob(argv, 0, NULL)) != NULL) {
+                       if (*cp == '\0' || !connected) {
+                               mflag = 0;
+                               continue;
+                       }
+                       if (mflag && confirm(argv[0], cp)) {
+                               char buf[MAXPATHLEN];
+                               tp = doprocess(buf, sizeof(buf), cp,
+                                   mcase, ntflag, mapflag);
+                               sendrequest((sunique) ? "STOU" : "STOR",
+                                   cp, tp, cp != tp || !interactive);
+                               if (!mflag && fromatty) {
+                                       ointer = interactive;
+                                       interactive = 1;
+                                       if (confirm("Continue with", "mput")) {
+                                               mflag++;
+                                       }
+                                       interactive = ointer;
+                               }
+                       }
+               }
+               goto cleanupmput;
+       }
+       for (i = 1; i < argc && connected; i++) {
+               char **cpp;
+               glob_t gl;
+               int flags;
+
+               if (!doglob) {
+                       if (mflag && confirm(argv[0], argv[i])) {
+                               char buf[MAXPATHLEN];
+                               tp = doprocess(buf, sizeof(buf), argv[i],
+                                       0, ntflag, mapflag);
+                               sendrequest((sunique) ? "STOU" : "STOR",
+                                   argv[i], tp, tp != argv[i] || !interactive);
+                               if (!mflag && fromatty) {
+                                       ointer = interactive;
+                                       interactive = 1;
+                                       if (confirm("Continue with", "mput")) {
+                                               mflag++;
+                                       }
+                                       interactive = ointer;
+                               }
+                       }
+                       continue;
+               }
+
+               memset(&gl, 0, sizeof(gl));
+               flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
+               if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) {
+                       warnx("%s: not found", argv[i]);
+                       globfree(&gl);
+                       continue;
+               }
+               for (cpp = gl.gl_pathv; cpp && *cpp != NULL && connected;
+                   cpp++) {
+                       if (mflag && confirm(argv[0], *cpp)) {
+                               char buf[MAXPATHLEN];
+                               tp = *cpp;
+                               tp = doprocess(buf, sizeof(buf), *cpp,
+                                   0, ntflag, mapflag);
+                               sendrequest((sunique) ? "STOU" : "STOR",
+                                   *cpp, tp, *cpp != tp || !interactive);
+                               if (!mflag && fromatty) {
+                                       ointer = interactive;
+                                       interactive = 1;
+                                       if (confirm("Continue with", "mput")) {
+                                               mflag++;
+                                       }
+                                       interactive = ointer;
+                               }
+                       }
+               }
+               globfree(&gl);
+       }
+ cleanupmput:
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+}
+
+void
+reget(int argc, char *argv[])
+{
+
+       (void)getit(argc, argv, 1, "r+");
+}
+
+void
+get(int argc, char *argv[])
+{
+
+       (void)getit(argc, argv, 0, restart_point ? "r+" : "w" );
+}
+
+/*
+ * Receive one file.
+ * If restartit is  1, restart the xfer always.
+ * If restartit is -1, restart the xfer only if the remote file is newer.
+ */
+int
+getit(int argc, char *argv[], int restartit, const char *mode)
+{
+       int     loc, rval;
+       char    *remfile, *olocfile;
+       const char *locfile;
+       char    buf[MAXPATHLEN];
+
+       loc = rval = 0;
+       if (argc == 2) {
+               argc++;
+               argv[2] = argv[1];
+               loc++;
+       }
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "remote-file")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s remote-file [local-file]\n", argv[0]);
+               code = -1;
+               return (0);
+       }
+       remfile = argv[1];
+       if ((olocfile = globulize(argv[2])) == NULL) {
+               code = -1;
+               return (0);
+       }
+       locfile = doprocess(buf, sizeof(buf), olocfile,
+               loc && mcase, loc && ntflag, loc && mapflag);
+       if (restartit) {
+               struct stat stbuf;
+               int ret;
+
+               if (! features[FEAT_REST_STREAM]) {
+                       fprintf(ttyout,
+                           "Restart is not supported by the remote server.\n");
+                       return (0);
+               }
+               ret = stat(locfile, &stbuf);
+               if (restartit == 1) {
+                       if (ret < 0) {
+                               warn("local: %s", locfile);
+                               goto freegetit;
+                       }
+                       restart_point = stbuf.st_size;
+               } else {
+                       if (ret == 0) {
+                               time_t mtime;
+
+                               mtime = remotemodtime(argv[1], 0);
+                               if (mtime == -1)
+                                       goto freegetit;
+                               if (stbuf.st_mtime >= mtime) {
+                                       rval = 1;
+                                       goto freegetit;
+                               }
+                       }
+               }
+       }
+
+       recvrequest("RETR", locfile, remfile, mode,
+           remfile != argv[1] || locfile != argv[2], loc);
+       restart_point = 0;
+ freegetit:
+       (void)free(olocfile);
+       return (rval);
+}
+
+/* ARGSUSED */
+void
+mintr(int signo)
+{
+
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       siglongjmp(jabort, 1);
+}
+
+void
+mabort(void)
+{
+       int ointer, oconf;
+
+       if (mflag && fromatty) {
+               ointer = interactive;
+               oconf = confirmrest;
+               interactive = 1;
+               confirmrest = 0;
+               if (confirm("Continue with", mname)) {
+                       interactive = ointer;
+                       confirmrest = oconf;
+                       return;
+               }
+               interactive = ointer;
+               confirmrest = oconf;
+       }
+       mflag = 0;
+}
+
+/*
+ * Get multiple files.
+ */
+void
+mget(int argc, char *argv[])
+{
+       sigfunc oldintr;
+       int ointer;
+       char *cp;
+       const char *tp;
+       int restartit;
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "remote-files"))) {
+               UPRINTF("usage: %s remote-files\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mname = argv[0];
+       mflag = 1;
+       restart_point = 0;
+       restartit = 0;
+       if (strcmp(argv[0], "mreget") == 0) {
+               if (! features[FEAT_REST_STREAM]) {
+                       fprintf(ttyout,
+                   "Restart is not supported by the remote server.\n");
+                       return;
+               }
+               restartit = 1;
+       }
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort();
+       while ((cp = remglob(argv, proxy, NULL)) != NULL) {
+               char buf[MAXPATHLEN];
+               if (*cp == '\0' || !connected) {
+                       mflag = 0;
+                       continue;
+               }
+               if (! mflag)
+                       continue;
+               if (! fileindir(cp, localcwd)) {
+                       fprintf(ttyout, "Skipping non-relative filename `%s'\n",
+                           cp);
+                       continue;
+               }
+               if (!confirm(argv[0], cp))
+                       continue;
+               tp = doprocess(buf, sizeof(buf), cp, mcase, ntflag, mapflag);
+               if (restartit) {
+                       struct stat stbuf;
+
+                       if (stat(tp, &stbuf) == 0)
+                               restart_point = stbuf.st_size;
+                       else
+                               warn("stat %s", tp);
+               }
+               recvrequest("RETR", tp, cp, restart_point ? "r+" : "w",
+                   tp != cp || !interactive, 1);
+               restart_point = 0;
+               if (!mflag && fromatty) {
+                       ointer = interactive;
+                       interactive = 1;
+                       if (confirm("Continue with", "mget"))
+                               mflag++;
+                       interactive = ointer;
+               }
+       }
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+}
+
+/*
+ * Read list of filenames from a local file and get those
+ */
+void
+fget(int argc, char *argv[])
+{
+       char    *buf, *mode;
+       FILE    *fp;
+
+       if (argc != 2) {
+               UPRINTF("usage: %s localfile\n", argv[0]);
+               code = -1;
+               return;
+       }
+
+       fp = fopen(argv[1], "r");
+       if (fp == NULL) {
+               fprintf(ttyout, "Cannot open source file %s\n", argv[1]);
+               code = -1;
+               return;
+       }
+
+       argv[0] = "get";
+       mode = restart_point ? "r+" : "w";
+
+       for (;
+           (buf = fparseln(fp, NULL, NULL, "\0\0\0", 0)) != NULL;
+           free(buf)) {
+               if (buf[0] == '\0')
+                       continue;
+               argv[1] = buf;
+               (void)getit(argc, argv, 0, mode);
+       }
+       fclose(fp);
+}
+
+const char *
+onoff(int bool)
+{
+
+       return (bool ? "on" : "off");
+}
+
+/*
+ * Show status.
+ */
+/*ARGSUSED*/
+void
+status(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+#ifndef NO_STATUS
+       if (connected)
+               fprintf(ttyout, "Connected %sto %s.\n",
+                   connected == -1 ? "and logged in" : "", hostname);
+       else
+               fputs("Not connected.\n", ttyout);
+       if (!proxy) {
+               pswitch(1);
+               if (connected) {
+                       fprintf(ttyout, "Connected for proxy commands to %s.\n",
+                           hostname);
+               }
+               else {
+                       fputs("No proxy connection.\n", ttyout);
+               }
+               pswitch(0);
+       }
+       fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode),
+           *gateserver ? gateserver : "(none)", gateport);
+       fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
+           onoff(passivemode), onoff(activefallback));
+       fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n",
+           modename, typename, formname, structname);
+       fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n",
+           onoff(verbose), onoff(bell), onoff(interactive), onoff(doglob));
+       fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n",
+           onoff(sunique), onoff(runique));
+       fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve));
+       fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase),
+           onoff(crflag));
+       if (ntflag) {
+               fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout);
+       }
+       else {
+               fputs("Ntrans: off.\n", ttyout);
+       }
+       if (mapflag) {
+               fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout);
+       }
+       else {
+               fputs("Nmap: off.\n", ttyout);
+       }
+       fprintf(ttyout,
+           "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n",
+           onoff(hash), mark, onoff(progress));
+       fprintf(ttyout,
+           "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
+           onoff(rate_get), rate_get, rate_get_incr);
+       fprintf(ttyout,
+           "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
+           onoff(rate_put), rate_put, rate_put_incr);
+       fprintf(ttyout,
+           "Socket buffer sizes: send %d, receive %d.\n",
+           sndbuf_size, rcvbuf_size);
+       fprintf(ttyout, "Use of PORT cmds: %s.\n", onoff(sendport));
+       fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4),
+           epsv4bad ? " (disabled for this connection)" : "");
+       fprintf(ttyout, "Command line editing: %s.\n",
+#ifdef NO_EDITCOMPLETE
+           "support not compiled in"
+#else  /* !def NO_EDITCOMPLETE */
+           onoff(editing)
+#endif /* !def NO_EDITCOMPLETE */
+           );
+       if (macnum > 0) {
+               int i;
+
+               fputs("Macros:\n", ttyout);
+               for (i=0; i<macnum; i++) {
+                       fprintf(ttyout, "\t%s\n", macros[i].mac_name);
+               }
+       }
+#endif /* !def NO_STATUS */
+       fprintf(ttyout, "Version: %s %s\n", FTP_PRODUCT, FTP_VERSION);
+       code = 0;
+}
+
+/*
+ * Toggle a variable
+ */
+int
+togglevar(int argc, char *argv[], int *var, const char *mesg)
+{
+       if (argc == 1) {
+               *var = !*var;
+       } else if (argc == 2 && strcasecmp(argv[1], "on") == 0) {
+               *var = 1;
+       } else if (argc == 2 && strcasecmp(argv[1], "off") == 0) {
+               *var = 0;
+       } else {
+               UPRINTF("usage: %s [ on | off ]\n", argv[0]);
+               return (-1);
+       }
+       if (mesg)
+               fprintf(ttyout, "%s %s.\n", mesg, onoff(*var));
+       return (*var);
+}
+
+/*
+ * Set beep on cmd completed mode.
+ */
+/*VARARGS*/
+void
+setbell(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &bell, "Bell mode");
+}
+
+/*
+ * Set command line editing
+ */
+/*VARARGS*/
+void
+setedit(int argc, char *argv[])
+{
+
+#ifdef NO_EDITCOMPLETE
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (verbose)
+               fputs("Editing support not compiled in; ignoring command.\n",
+                   ttyout);
+#else  /* !def NO_EDITCOMPLETE */
+       code = togglevar(argc, argv, &editing, "Editing mode");
+       controlediting();
+#endif /* !def NO_EDITCOMPLETE */
+}
+
+/*
+ * Turn on packet tracing.
+ */
+/*VARARGS*/
+void
+settrace(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &trace, "Packet tracing");
+}
+
+/*
+ * Toggle hash mark printing during transfers, or set hash mark bytecount.
+ */
+/*VARARGS*/
+void
+sethash(int argc, char *argv[])
+{
+       if (argc == 1)
+               hash = !hash;
+       else if (argc != 2) {
+               UPRINTF("usage: %s [ on | off | bytecount ]\n",
+                   argv[0]);
+               code = -1;
+               return;
+       } else if (strcasecmp(argv[1], "on") == 0)
+               hash = 1;
+       else if (strcasecmp(argv[1], "off") == 0)
+               hash = 0;
+       else {
+               int nmark;
+
+               nmark = strsuftoi(argv[1]);
+               if (nmark < 1) {
+                       fprintf(ttyout, "mark: bad bytecount value `%s'.\n",
+                           argv[1]);
+                       code = -1;
+                       return;
+               }
+               mark = nmark;
+               hash = 1;
+       }
+       fprintf(ttyout, "Hash mark printing %s", onoff(hash));
+       if (hash)
+               fprintf(ttyout, " (%d bytes/hash mark)", mark);
+       fputs(".\n", ttyout);
+       if (hash)
+               progress = 0;
+       code = hash;
+}
+
+/*
+ * Turn on printing of server echo's.
+ */
+/*VARARGS*/
+void
+setverbose(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &verbose, "Verbose mode");
+}
+
+/*
+ * Toggle PORT/LPRT cmd use before each data connection.
+ */
+/*VARARGS*/
+void
+setport(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
+}
+
+/*
+ * Toggle transfer progress bar.
+ */
+/*VARARGS*/
+void
+setprogress(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &progress, "Progress bar");
+       if (progress)
+               hash = 0;
+}
+
+/*
+ * Turn on interactive prompting during mget, mput, and mdelete.
+ */
+/*VARARGS*/
+void
+setprompt(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &interactive, "Interactive mode");
+}
+
+/*
+ * Toggle gate-ftp mode, or set gate-ftp server
+ */
+/*VARARGS*/
+void
+setgate(int argc, char *argv[])
+{
+       static char gsbuf[MAXHOSTNAMELEN];
+
+       if (argc == 0 || argc > 3) {
+               UPRINTF(
+                   "usage: %s [ on | off | gateserver [port] ]\n", argv[0]);
+               code = -1;
+               return;
+       } else if (argc < 2) {
+               gatemode = !gatemode;
+       } else {
+               if (argc == 2 && strcasecmp(argv[1], "on") == 0)
+                       gatemode = 1;
+               else if (argc == 2 && strcasecmp(argv[1], "off") == 0)
+                       gatemode = 0;
+               else {
+                       if (argc == 3)
+                               gateport = ftp_strdup(argv[2]);
+                       (void)strlcpy(gsbuf, argv[1], sizeof(gsbuf));
+                       gateserver = gsbuf;
+                       gatemode = 1;
+               }
+       }
+       if (gatemode && (gateserver == NULL || *gateserver == '\0')) {
+               fprintf(ttyout,
+                   "Disabling gate-ftp mode - no gate-ftp server defined.\n");
+               gatemode = 0;
+       } else {
+               fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n",
+                   onoff(gatemode), *gateserver ? gateserver : "(none)",
+                   gateport);
+       }
+       code = gatemode;
+}
+
+/*
+ * Toggle metacharacter interpretation on local file names.
+ */
+/*VARARGS*/
+void
+setglob(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &doglob, "Globbing");
+}
+
+/*
+ * Toggle preserving modification times on retrieved files.
+ */
+/*VARARGS*/
+void
+setpreserve(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &preserve, "Preserve modification times");
+}
+
+/*
+ * Set debugging mode on/off and/or set level of debugging.
+ */
+/*VARARGS*/
+void
+setdebug(int argc, char *argv[])
+{
+       if (argc == 0 || argc > 2) {
+               UPRINTF("usage: %s [ on | off | debuglevel ]\n", argv[0]);
+               code = -1;
+               return;
+       } else if (argc == 2) {
+               if (strcasecmp(argv[1], "on") == 0)
+                       ftp_debug = 1;
+               else if (strcasecmp(argv[1], "off") == 0)
+                       ftp_debug = 0;
+               else {
+                       int val;
+
+                       val = strsuftoi(argv[1]);
+                       if (val < 0) {
+                               fprintf(ttyout, "%s: bad debugging value.\n",
+                                   argv[1]);
+                               code = -1;
+                               return;
+                       }
+                       ftp_debug = val;
+               }
+       } else
+               ftp_debug = !ftp_debug;
+       if (ftp_debug)
+               options |= SO_DEBUG;
+       else
+               options &= ~SO_DEBUG;
+       fprintf(ttyout, "Debugging %s (ftp_debug=%d).\n", onoff(ftp_debug), ftp_debug);
+       code = ftp_debug > 0;
+}
+
+/*
+ * Set current working directory on remote machine.
+ */
+void
+cd(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-directory"))) {
+               UPRINTF("usage: %s remote-directory\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("CWD %s", argv[1]);
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("CWD command not recognized, trying XCWD.\n",
+                           ttyout);
+               r = command("XCWD %s", argv[1]);
+       }
+       if (r == COMPLETE) {
+               dirchange = 1;
+               updateremotecwd();
+       }
+}
+
+/*
+ * Set current working directory on local machine.
+ */
+void
+lcd(int argc, char *argv[])
+{
+       char *locdir;
+
+       code = -1;
+       if (argc == 1) {
+               argc++;
+               argv[1] = localhome;
+       }
+       if (argc != 2) {
+               UPRINTF("usage: %s [local-directory]\n", argv[0]);
+               return;
+       }
+       if ((locdir = globulize(argv[1])) == NULL)
+               return;
+       if (chdir(locdir) == -1)
+               warn("lcd %s", locdir);
+       else {
+               updatelocalcwd();
+               if (localcwd[0]) {
+                       fprintf(ttyout, "Local directory now: %s\n", localcwd);
+                       code = 0;
+               } else {
+                       fprintf(ttyout, "Unable to determine local directory\n");
+               }
+       }
+       (void)free(locdir);
+}
+
+/*
+ * Delete a single file.
+ */
+void
+delete(int argc, char *argv[])
+{
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (command("DELE %s", argv[1]) == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Delete multiple files.
+ */
+void
+mdelete(int argc, char *argv[])
+{
+       sigfunc oldintr;
+       int ointer;
+       char *cp;
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "remote-files"))) {
+               UPRINTF("usage: %s [remote-files]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mname = argv[0];
+       mflag = 1;
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort();
+       while ((cp = remglob(argv, 0, NULL)) != NULL) {
+               if (*cp == '\0') {
+                       mflag = 0;
+                       continue;
+               }
+               if (mflag && confirm(argv[0], cp)) {
+                       if (command("DELE %s", cp) == COMPLETE)
+                               dirchange = 1;
+                       if (!mflag && fromatty) {
+                               ointer = interactive;
+                               interactive = 1;
+                               if (confirm("Continue with", "mdelete")) {
+                                       mflag++;
+                               }
+                               interactive = ointer;
+                       }
+               }
+       }
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+}
+
+/*
+ * Rename a remote file.
+ */
+void
+renamefile(int argc, char *argv[])
+{
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "from-name")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s from-name to-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (command("RNFR %s", argv[1]) == CONTINUE &&
+           command("RNTO %s", argv[2]) == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Get a directory listing of remote files.
+ * Supports being invoked as:
+ *     cmd             runs
+ *     ---             ----
+ *     dir, ls         LIST
+ *     mlsd            MLSD
+ *     nlist           NLST
+ *     pdir, pls       LIST |$PAGER
+ *     mmlsd           MLSD |$PAGER
+ */
+void
+ls(int argc, char *argv[])
+{
+       const char *cmd;
+       char *remdir, *locfile;
+       int freelocfile, pagecmd, mlsdcmd;
+
+       remdir = NULL;
+       locfile = "-";
+       freelocfile = pagecmd = mlsdcmd = 0;
+                       /*
+                        * the only commands that start with `p' are
+                        * the `pager' versions.
+                        */
+       if (argv[0][0] == 'p')
+               pagecmd = 1;
+       if (strcmp(argv[0] + pagecmd , "mlsd") == 0) {
+               if (! features[FEAT_MLST]) {
+                       fprintf(ttyout,
+                          "MLSD is not supported by the remote server.\n");
+                       return;
+               }
+               mlsdcmd = 1;
+       }
+       if (argc == 0)
+               goto usage;
+
+       if (mlsdcmd)
+               cmd = "MLSD";
+       else if (strcmp(argv[0] + pagecmd, "nlist") == 0)
+               cmd = "NLST";
+       else
+               cmd = "LIST";
+
+       if (argc > 1)
+               remdir = argv[1];
+       if (argc > 2)
+               locfile = argv[2];
+       if (argc > 3 || ((pagecmd | mlsdcmd) && argc > 2)) {
+ usage:
+               if (pagecmd || mlsdcmd)
+                       UPRINTF("usage: %s [remote-path]\n", argv[0]);
+               else
+                       UPRINTF("usage: %s [remote-path [local-file]]\n",
+                           argv[0]);
+               code = -1;
+               goto freels;
+       }
+
+       if (pagecmd) {
+               char *p;
+               size_t len;
+
+               p = getoptionvalue("pager");
+               if (EMPTYSTRING(p))
+                       p = DEFAULTPAGER;
+               len = strlen(p) + 2;
+               locfile = ftp_malloc(len);
+               locfile[0] = '|';
+               (void)strlcpy(locfile + 1, p, len - 1);
+               freelocfile = 1;
+       } else if ((strcmp(locfile, "-") != 0) && *locfile != '|') {
+               mname = argv[0];
+               if ((locfile = globulize(locfile)) == NULL ||
+                   !confirm("output to local-file:", locfile)) {
+                       code = -1;
+                       goto freels;
+               }
+               freelocfile = 1;
+       }
+       recvrequest(cmd, locfile, remdir, "w", 0, 0);
+ freels:
+       if (freelocfile && locfile)
+               (void)free(locfile);
+}
+
+/*
+ * Get a directory listing of multiple remote files.
+ */
+void
+mls(int argc, char *argv[])
+{
+       sigfunc oldintr;
+       int ointer, i;
+       int dolist;
+       char *mode, *dest, *odest;
+
+       if (argc == 0)
+               goto usage;
+       if (argc < 2 && !another(&argc, &argv, "remote-files"))
+               goto usage;
+       if (argc < 3 && !another(&argc, &argv, "local-file")) {
+ usage:
+               UPRINTF("usage: %s remote-files local-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       odest = dest = argv[argc - 1];
+       argv[argc - 1] = NULL;
+       mname = argv[0];
+       if (strcmp(dest, "-") && *dest != '|')
+               if (((dest = globulize(dest)) == NULL) ||
+                   !confirm("output to local-file:", dest)) {
+                       code = -1;
+                       return;
+       }
+       dolist = strcmp(argv[0], "mls");
+       mflag = 1;
+       oldintr = xsignal(SIGINT, mintr);
+       if (sigsetjmp(jabort, 1))
+               mabort();
+       for (i = 1; mflag && i < argc-1 && connected; i++) {
+               mode = (i == 1) ? "w" : "a";
+               recvrequest(dolist ? "LIST" : "NLST", dest, argv[i], mode,
+                   0, 0);
+               if (!mflag && fromatty) {
+                       ointer = interactive;
+                       interactive = 1;
+                       if (confirm("Continue with", argv[0])) {
+                               mflag++;
+                       }
+                       interactive = ointer;
+               }
+       }
+       (void)xsignal(SIGINT, oldintr);
+       mflag = 0;
+       if (dest != odest)                      /* free up after globulize() */
+               free(dest);
+}
+
+/*
+ * Do a shell escape
+ */
+/*ARGSUSED*/
+void
+shell(int argc, char *argv[])
+{
+       pid_t pid;
+       sigfunc oldintr;
+       char shellnam[MAXPATHLEN], *shell, *namep;
+       int wait_status;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s [command [args]]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       oldintr = xsignal(SIGINT, SIG_IGN);
+       if ((pid = fork()) == 0) {
+               for (pid = 3; pid < 20; pid++)
+                       (void)close(pid);
+               (void)xsignal(SIGINT, SIG_DFL);
+               shell = getenv("SHELL");
+               if (shell == NULL)
+                       shell = _PATH_BSHELL;
+               namep = strrchr(shell, '/');
+               if (namep == NULL)
+                       namep = shell;
+               else
+                       namep++;
+               (void)strlcpy(shellnam, namep, sizeof(shellnam));
+               if (ftp_debug) {
+                       fputs(shell, ttyout);
+                       putc('\n', ttyout);
+               }
+               if (argc > 1) {
+                       execl(shell, shellnam, "-c", altarg, (char *)0);
+               }
+               else {
+                       execl(shell, shellnam, (char *)0);
+               }
+               warn("%s", shell);
+               code = -1;
+               exit(1);
+       }
+       if (pid > 0)
+               while (wait(&wait_status) != pid)
+                       ;
+       (void)xsignal(SIGINT, oldintr);
+       if (pid == -1) {
+               warn("Try again later");
+               code = -1;
+       } else
+               code = 0;
+}
+
+/*
+ * Send new user information (re-login)
+ */
+void
+user(int argc, char *argv[])
+{
+       char *password;
+       int n, aflag = 0;
+
+       if (argc == 0)
+               goto usage;
+       if (argc < 2)
+               (void)another(&argc, &argv, "username");
+       if (argc < 2 || argc > 4) {
+ usage:
+               UPRINTF("usage: %s username [password [account]]\n",
+                   argv[0]);
+               code = -1;
+               return;
+       }
+       n = command("USER %s", argv[1]);
+       if (n == CONTINUE) {
+               if (argc < 3) {
+                       password = getpass("Password: ");
+               } else {
+                       password = argv[2];
+               }
+               n = command("PASS %s", password);
+               memset(password, 0, strlen(password));
+       }
+       if (n == CONTINUE) {
+               aflag++;
+               if (argc < 4) {
+                       password = getpass("Account: ");
+               } else {
+                       password = argv[3];
+               }
+               n = command("ACCT %s", password);
+               memset(password, 0, strlen(password));
+       }
+       if (n != COMPLETE) {
+               fputs("Login failed.\n", ttyout);
+               return;
+       }
+       if (!aflag && argc == 4) {
+               password = argv[3];
+               (void)command("ACCT %s", password);
+               memset(password, 0, strlen(password));
+       }
+       connected = -1;
+       getremoteinfo();
+}
+
+/*
+ * Print working directory on remote machine.
+ */
+/*VARARGS*/
+void
+pwd(int argc, char *argv[])
+{
+
+       code = -1;
+       if (argc != 1) {
+               UPRINTF("usage: %s\n", argv[0]);
+               return;
+       }
+       if (! remotecwd[0])
+               updateremotecwd();
+       if (! remotecwd[0])
+               fprintf(ttyout, "Unable to determine remote directory\n");
+       else {
+               fprintf(ttyout, "Remote directory: %s\n", remotecwd);
+               code = 0;
+       }
+}
+
+/*
+ * Print working directory on local machine.
+ */
+void
+lpwd(int argc, char *argv[])
+{
+
+       code = -1;
+       if (argc != 1) {
+               UPRINTF("usage: %s\n", argv[0]);
+               return;
+       }
+       if (! localcwd[0])
+               updatelocalcwd();
+       if (! localcwd[0])
+               fprintf(ttyout, "Unable to determine local directory\n");
+       else {
+               fprintf(ttyout, "Local directory: %s\n", localcwd);
+               code = 0;
+       }
+}
+
+/*
+ * Make a directory.
+ */
+void
+makedir(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "directory-name"))) {
+               UPRINTF("usage: %s directory-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("MKD %s", argv[1]);
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("MKD command not recognized, trying XMKD.\n",
+                           ttyout);
+               r = command("XMKD %s", argv[1]);
+       }
+       if (r == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Remove a directory.
+ */
+void
+removedir(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "directory-name"))) {
+               UPRINTF("usage: %s directory-name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("RMD %s", argv[1]);
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("RMD command not recognized, trying XRMD.\n",
+                           ttyout);
+               r = command("XRMD %s", argv[1]);
+       }
+       if (r == COMPLETE)
+               dirchange = 1;
+}
+
+/*
+ * Send a line, verbatim, to the remote machine.
+ */
+void
+quote(int argc, char *argv[])
+{
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "command line to send"))) {
+               UPRINTF("usage: %s line-to-send\n", argv[0]);
+               code = -1;
+               return;
+       }
+       quote1("", argc, argv);
+}
+
+/*
+ * Send a SITE command to the remote machine.  The line
+ * is sent verbatim to the remote machine, except that the
+ * word "SITE" is added at the front.
+ */
+void
+site(int argc, char *argv[])
+{
+
+       if (argc == 0 ||
+           (argc == 1 && !another(&argc, &argv, "arguments to SITE command"))){
+               UPRINTF("usage: %s line-to-send\n", argv[0]);
+               code = -1;
+               return;
+       }
+       quote1("SITE ", argc, argv);
+}
+
+/*
+ * Turn argv[1..argc) into a space-separated string, then prepend initial text.
+ * Send the result as a one-line command and get response.
+ */
+void
+quote1(const char *initial, int argc, char *argv[])
+{
+       int i;
+       char buf[BUFSIZ];               /* must be >= sizeof(line) */
+
+       (void)strlcpy(buf, initial, sizeof(buf));
+       for (i = 1; i < argc; i++) {
+               (void)strlcat(buf, argv[i], sizeof(buf));
+               if (i < (argc - 1))
+                       (void)strlcat(buf, " ", sizeof(buf));
+       }
+       if (command("%s", buf) == PRELIM) {
+               while (getreply(0) == PRELIM)
+                       continue;
+       }
+       dirchange = 1;
+}
+
+void
+do_chmod(int argc, char *argv[])
+{
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "mode")))
+               goto usage;
+       if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
+ usage:
+               UPRINTF("usage: %s mode remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       (void)command("SITE CHMOD %s %s", argv[1], argv[2]);
+}
+
+#define COMMAND_1ARG(argc, argv, cmd)                  \
+       if (argc == 1)                                  \
+               command(cmd);                           \
+       else                                            \
+               command(cmd " %s", argv[1])
+
+void
+do_umask(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s [umask]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;
+       COMMAND_1ARG(argc, argv, "SITE UMASK");
+       verbose = oldverbose;
+}
+
+void
+idlecmd(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc < 1 || argc > 2) {
+               UPRINTF("usage: %s [seconds]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;
+       COMMAND_1ARG(argc, argv, "SITE IDLE");
+       verbose = oldverbose;
+}
+
+/*
+ * Ask the other side for help.
+ */
+void
+rmthelp(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;
+       COMMAND_1ARG(argc, argv, "HELP");
+       verbose = oldverbose;
+}
+
+/*
+ * Terminate session and exit.
+ * May be called with 0, NULL.
+ */
+/*VARARGS*/
+void
+quit(int argc, char *argv[])
+{
+
+                       /* this may be called with argc == 0, argv == NULL */
+       if (argc == 0 && argv != NULL) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (connected)
+               disconnect(0, NULL);
+       pswitch(1);
+       if (connected)
+               disconnect(0, NULL);
+       exit(0);
+}
+
+/*
+ * Terminate session, but don't exit.
+ * May be called with 0, NULL.
+ */
+void
+disconnect(int argc, char *argv[])
+{
+
+                       /* this may be called with argc == 0, argv == NULL */
+       if (argc == 0 && argv != NULL) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (!connected)
+               return;
+       (void)command("QUIT");
+       cleanuppeer();
+}
+
+void
+account(int argc, char *argv[])
+{
+       char *ap;
+
+       if (argc == 0 || argc > 2) {
+               UPRINTF("usage: %s [password]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       else if (argc == 2)
+               ap = argv[1];
+       else
+               ap = getpass("Account:");
+       (void)command("ACCT %s", ap);
+}
+
+sigjmp_buf abortprox;
+
+void
+proxabort(int notused)
+{
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       if (!proxy) {
+               pswitch(1);
+       }
+       if (connected) {
+               proxflag = 1;
+       }
+       else {
+               proxflag = 0;
+       }
+       pswitch(0);
+       siglongjmp(abortprox, 1);
+}
+
+void
+doproxy(int argc, char *argv[])
+{
+       struct cmd *c;
+       int cmdpos;
+       sigfunc oldintr;
+
+       if (argc == 0 || (argc == 1 && !another(&argc, &argv, "command"))) {
+               UPRINTF("usage: %s command\n", argv[0]);
+               code = -1;
+               return;
+       }
+       c = getcmd(argv[1]);
+       if (c == (struct cmd *) -1) {
+               fputs("?Ambiguous command.\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (c == 0) {
+               fputs("?Invalid command.\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (!c->c_proxy) {
+               fputs("?Invalid proxy command.\n", ttyout);
+               code = -1;
+               return;
+       }
+       if (sigsetjmp(abortprox, 1)) {
+               code = -1;
+               return;
+       }
+       oldintr = xsignal(SIGINT, proxabort);
+       pswitch(1);
+       if (c->c_conn && !connected) {
+               fputs("Not connected.\n", ttyout);
+               pswitch(0);
+               (void)xsignal(SIGINT, oldintr);
+               code = -1;
+               return;
+       }
+       cmdpos = strcspn(line, " \t");
+       if (cmdpos > 0)         /* remove leading "proxy " from input buffer */
+               memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1);
+       argv[1] = c->c_name;
+       (*c->c_handler)(argc-1, argv+1);
+       if (connected) {
+               proxflag = 1;
+       }
+       else {
+               proxflag = 0;
+       }
+       pswitch(0);
+       (void)xsignal(SIGINT, oldintr);
+}
+
+void
+setcase(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &mcase, "Case mapping");
+}
+
+/*
+ * convert the given name to lower case if it's all upper case, into
+ * a static buffer which is returned to the caller
+ */
+static const char *
+docase(char *dst, size_t dlen, const char *src)
+{
+       size_t i;
+       int dochange = 1;
+
+       for (i = 0; src[i] != '\0' && i < dlen - 1; i++) {
+               dst[i] = src[i];
+               if (islower((unsigned char)dst[i]))
+                       dochange = 0;
+       }
+       dst[i] = '\0';
+
+       if (dochange) {
+               for (i = 0; dst[i] != '\0'; i++)
+                       if (isupper((unsigned char)dst[i]))
+                               dst[i] = tolower((unsigned char)dst[i]);
+       }
+       return dst;
+}
+
+void
+setcr(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
+}
+
+void
+setntrans(int argc, char *argv[])
+{
+
+       if (argc == 0 || argc > 3) {
+               UPRINTF("usage: %s [inchars [outchars]]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (argc == 1) {
+               ntflag = 0;
+               fputs("Ntrans off.\n", ttyout);
+               code = ntflag;
+               return;
+       }
+       ntflag++;
+       code = ntflag;
+       (void)strlcpy(ntin, argv[1], sizeof(ntin));
+       if (argc == 2) {
+               ntout[0] = '\0';
+               return;
+       }
+       (void)strlcpy(ntout, argv[2], sizeof(ntout));
+}
+
+static const char *
+dotrans(char *dst, size_t dlen, const char *src)
+{
+       const char *cp1;
+       char *cp2 = dst;
+       size_t i, ostop;
+
+       for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
+               continue;
+       for (cp1 = src; *cp1; cp1++) {
+               int found = 0;
+               for (i = 0; *(ntin + i) && i < 16; i++) {
+                       if (*cp1 == *(ntin + i)) {
+                               found++;
+                               if (i < ostop) {
+                                       *cp2++ = *(ntout + i);
+                                       if (cp2 - dst >= dlen - 1)
+                                               goto out;
+                               }
+                               break;
+                       }
+               }
+               if (!found) {
+                       *cp2++ = *cp1;
+               }
+       }
+out:
+       *cp2 = '\0';
+       return dst;
+}
+
+void
+setnmap(int argc, char *argv[])
+{
+       char *cp;
+
+       if (argc == 1) {
+               mapflag = 0;
+               fputs("Nmap off.\n", ttyout);
+               code = mapflag;
+               return;
+       }
+       if (argc == 0 ||
+           (argc < 3 && !another(&argc, &argv, "mapout")) || argc > 3) {
+               UPRINTF("usage: %s [mapin mapout]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mapflag = 1;
+       code = 1;
+       cp = strchr(altarg, ' ');
+       if (proxy) {
+               while(*++cp == ' ')
+                       continue;
+               altarg = cp;
+               cp = strchr(altarg, ' ');
+       }
+       *cp = '\0';
+       (void)strlcpy(mapin, altarg, MAXPATHLEN);
+       while (*++cp == ' ')
+               continue;
+       (void)strlcpy(mapout, cp, MAXPATHLEN);
+}
+
+static const char *
+domap(char *dst, size_t dlen, const char *src)
+{
+       const char *cp1 = src;
+       char *cp2 = mapin;
+       const char *tp[9], *te[9];
+       int i, toks[9], toknum = 0, match = 1;
+
+       for (i=0; i < 9; ++i) {
+               toks[i] = 0;
+       }
+       while (match && *cp1 && *cp2) {
+               switch (*cp2) {
+                       case '\\':
+                               if (*++cp2 != *cp1) {
+                                       match = 0;
+                               }
+                               break;
+                       case '$':
+                               if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
+                                       if (*cp1 != *(++cp2+1)) {
+                                               toks[toknum = *cp2 - '1']++;
+                                               tp[toknum] = cp1;
+                                               while (*++cp1 && *(cp2+1)
+                                                       != *cp1);
+                                               te[toknum] = cp1;
+                                       }
+                                       cp2++;
+                                       break;
+                               }
+                               /* FALLTHROUGH */
+                       default:
+                               if (*cp2 != *cp1) {
+                                       match = 0;
+                               }
+                               break;
+               }
+               if (match && *cp1) {
+                       cp1++;
+               }
+               if (match && *cp2) {
+                       cp2++;
+               }
+       }
+       if (!match && *cp1) /* last token mismatch */
+       {
+               toks[toknum] = 0;
+       }
+       cp2 = dst;
+       *cp2 = '\0';
+       cp1 = mapout;
+       while (*cp1) {
+               match = 0;
+               switch (*cp1) {
+                       case '\\':
+                               if (*(cp1 + 1)) {
+                                       *cp2++ = *++cp1;
+                               }
+                               break;
+                       case '[':
+LOOP:
+                               if (*++cp1 == '$' &&
+                                   isdigit((unsigned char)*(cp1+1))) {
+                                       if (*++cp1 == '0') {
+                                               const char *cp3 = src;
+
+                                               while (*cp3) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                               match = 1;
+                                       }
+                                       else if (toks[toknum = *cp1 - '1']) {
+                                               const char *cp3 = tp[toknum];
+
+                                               while (cp3 != te[toknum]) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                               match = 1;
+                                       }
+                               }
+                               else {
+                                       while (*cp1 && *cp1 != ',' &&
+                                           *cp1 != ']') {
+                                               if (*cp1 == '\\') {
+                                                       cp1++;
+                                               }
+                                               else if (*cp1 == '$' &&
+                                                   isdigit((unsigned char)*(cp1+1))) {
+                                                       if (*++cp1 == '0') {
+                                                          const char *cp3 = src;
+
+                                                          while (*cp3) {
+                                                               *cp2++ = *cp3++;
+                                                          }
+                                                       }
+                                                       else if (toks[toknum =
+                                                           *cp1 - '1']) {
+                                                          const char *cp3=tp[toknum];
+
+                                                          while (cp3 !=
+                                                                 te[toknum]) {
+                                                               *cp2++ = *cp3++;
+                                                          }
+                                                       }
+                                               }
+                                               else if (*cp1) {
+                                                       *cp2++ = *cp1++;
+                                               }
+                                       }
+                                       if (!*cp1) {
+                                               fputs(
+                                               "nmap: unbalanced brackets.\n",
+                                                   ttyout);
+                                               return (src);
+                                       }
+                                       match = 1;
+                                       cp1--;
+                               }
+                               if (match) {
+                                       while (*++cp1 && *cp1 != ']') {
+                                             if (*cp1 == '\\' && *(cp1 + 1)) {
+                                                       cp1++;
+                                             }
+                                       }
+                                       if (!*cp1) {
+                                               fputs(
+                                               "nmap: unbalanced brackets.\n",
+                                                   ttyout);
+                                               return (src);
+                                       }
+                                       break;
+                               }
+                               switch (*++cp1) {
+                                       case ',':
+                                               goto LOOP;
+                                       case ']':
+                                               break;
+                                       default:
+                                               cp1--;
+                                               goto LOOP;
+                               }
+                               break;
+                       case '$':
+                               if (isdigit((unsigned char)*(cp1 + 1))) {
+                                       if (*++cp1 == '0') {
+                                               const char *cp3 = src;
+
+                                               while (*cp3) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                       }
+                                       else if (toks[toknum = *cp1 - '1']) {
+                                               const char *cp3 = tp[toknum];
+
+                                               while (cp3 != te[toknum]) {
+                                                       *cp2++ = *cp3++;
+                                               }
+                                       }
+                                       break;
+                               }
+                               /* intentional drop through */
+                       default:
+                               *cp2++ = *cp1;
+                               break;
+               }
+               cp1++;
+       }
+       *cp2 = '\0';
+       return *dst ? dst : src;
+}
+
+void
+setpassive(int argc, char *argv[])
+{
+
+       if (argc == 1) {
+               passivemode = !passivemode;
+               activefallback = passivemode;
+       } else if (argc != 2) {
+ passiveusage:
+               UPRINTF("usage: %s [ on | off | auto ]\n", argv[0]);
+               code = -1;
+               return;
+       } else if (strcasecmp(argv[1], "on") == 0) {
+               passivemode = 1;
+               activefallback = 0;
+       } else if (strcasecmp(argv[1], "off") == 0) {
+               passivemode = 0;
+               activefallback = 0;
+       } else if (strcasecmp(argv[1], "auto") == 0) {
+               passivemode = 1;
+               activefallback = 1;
+       } else
+               goto passiveusage;
+       fprintf(ttyout, "Passive mode: %s; fallback to active mode: %s.\n",
+           onoff(passivemode), onoff(activefallback));
+       code = passivemode;
+}
+
+void
+setepsv4(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &epsv4,
+           verbose ? "EPSV/EPRT on IPv4" : NULL);
+       epsv4bad = 0;
+}
+
+void
+setsunique(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &sunique, "Store unique");
+}
+
+void
+setrunique(int argc, char *argv[])
+{
+
+       code = togglevar(argc, argv, &runique, "Receive unique");
+}
+
+int
+parserate(int argc, char *argv[], int cmdlineopt)
+{
+       int dir, max, incr, showonly;
+       sigfunc oldusr1, oldusr2;
+
+       if (argc > 4 || (argc < (cmdlineopt ? 3 : 2))) {
+ usage:
+               if (cmdlineopt)
+                       UPRINTF(
+       "usage: %s (all|get|put),maximum-bytes[,increment-bytes]]\n",
+                           argv[0]);
+               else
+                       UPRINTF(
+       "usage: %s (all|get|put) [maximum-bytes [increment-bytes]]\n",
+                           argv[0]);
+               return -1;
+       }
+       dir = max = incr = showonly = 0;
+#define        RATE_GET        1
+#define        RATE_PUT        2
+#define        RATE_ALL        (RATE_GET | RATE_PUT)
+
+       if (strcasecmp(argv[1], "all") == 0)
+               dir = RATE_ALL;
+       else if (strcasecmp(argv[1], "get") == 0)
+               dir = RATE_GET;
+       else if (strcasecmp(argv[1], "put") == 0)
+               dir = RATE_PUT;
+       else
+               goto usage;
+
+       if (argc >= 3) {
+               if ((max = strsuftoi(argv[2])) < 0)
+                       goto usage;
+       } else
+               showonly = 1;
+
+       if (argc == 4) {
+               if ((incr = strsuftoi(argv[3])) <= 0)
+                       goto usage;
+       } else
+               incr = DEFAULTINCR;
+
+       oldusr1 = xsignal(SIGUSR1, SIG_IGN);
+       oldusr2 = xsignal(SIGUSR2, SIG_IGN);
+       if (dir & RATE_GET) {
+               if (!showonly) {
+                       rate_get = max;
+                       rate_get_incr = incr;
+               }
+               if (!cmdlineopt || verbose)
+                       fprintf(ttyout,
+               "Get transfer rate throttle: %s; maximum: %d; increment %d.\n",
+                           onoff(rate_get), rate_get, rate_get_incr);
+       }
+       if (dir & RATE_PUT) {
+               if (!showonly) {
+                       rate_put = max;
+                       rate_put_incr = incr;
+               }
+               if (!cmdlineopt || verbose)
+                       fprintf(ttyout,
+               "Put transfer rate throttle: %s; maximum: %d; increment %d.\n",
+                           onoff(rate_put), rate_put, rate_put_incr);
+       }
+       (void)xsignal(SIGUSR1, oldusr1);
+       (void)xsignal(SIGUSR2, oldusr2);
+       return 0;
+}
+
+void
+setrate(int argc, char *argv[])
+{
+
+       code = parserate(argc, argv, 0);
+}
+
+/* change directory to parent directory */
+void
+cdup(int argc, char *argv[])
+{
+       int r;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       r = command("CDUP");
+       if (r == ERROR && code == 500) {
+               if (verbose)
+                       fputs("CDUP command not recognized, trying XCUP.\n",
+                           ttyout);
+               r = command("XCUP");
+       }
+       if (r == COMPLETE) {
+               dirchange = 1;
+               updateremotecwd();
+       }
+}
+
+/*
+ * Restart transfer at specific point
+ */
+void
+restart(int argc, char *argv[])
+{
+
+       if (argc == 0 || argc > 2) {
+               UPRINTF("usage: %s [restart-point]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_REST_STREAM]) {
+               fprintf(ttyout,
+                   "Restart is not supported by the remote server.\n");
+               return;
+       }
+       if (argc == 2) {
+               off_t rp;
+               char *ep;
+
+               rp = STRTOLL(argv[1], &ep, 10);
+               if (rp < 0 || *ep != '\0')
+                       fprintf(ttyout, "restart: Invalid offset `%s'\n",
+                           argv[1]);
+               else
+                       restart_point = rp;
+       }
+       if (restart_point == 0)
+               fputs("No restart point defined.\n", ttyout);
+       else
+               fprintf(ttyout,
+                   "Restarting at " LLF " for next get, put or append\n",
+                   (LLT)restart_point);
+}
+
+/*
+ * Show remote system type
+ */
+void
+syst(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       (void)command("SYST");
+       verbose = oldverbose;
+}
+
+void
+macdef(int argc, char *argv[])
+{
+       char *tmp;
+       int c;
+
+       if (argc == 0)
+               goto usage;
+       if (macnum == 16) {
+               fputs("Limit of 16 macros have already been defined.\n",
+                   ttyout);
+               code = -1;
+               return;
+       }
+       if ((argc < 2 && !another(&argc, &argv, "macro name")) || argc > 2) {
+ usage:
+               UPRINTF("usage: %s macro_name\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (interactive)
+               fputs(
+               "Enter macro line by line, terminating it with a null line.\n",
+                   ttyout);
+       (void)strlcpy(macros[macnum].mac_name, argv[1],
+           sizeof(macros[macnum].mac_name));
+       if (macnum == 0)
+               macros[macnum].mac_start = macbuf;
+       else
+               macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
+       tmp = macros[macnum].mac_start;
+       while (tmp != macbuf+4096) {
+               if ((c = getchar()) == EOF) {
+                       fputs("macdef: end of file encountered.\n", ttyout);
+                       code = -1;
+                       return;
+               }
+               if ((*tmp = c) == '\n') {
+                       if (tmp == macros[macnum].mac_start) {
+                               macros[macnum++].mac_end = tmp;
+                               code = 0;
+                               return;
+                       }
+                       if (*(tmp-1) == '\0') {
+                               macros[macnum++].mac_end = tmp - 1;
+                               code = 0;
+                               return;
+                       }
+                       *tmp = '\0';
+               }
+               tmp++;
+       }
+       while (1) {
+               while ((c = getchar()) != '\n' && c != EOF)
+                       /* LOOP */;
+               if (c == EOF || getchar() == '\n') {
+                       fputs("Macro not defined - 4K buffer exceeded.\n",
+                           ttyout);
+                       code = -1;
+                       return;
+               }
+       }
+}
+
+/*
+ * Get size of file on remote machine
+ */
+void
+sizecmd(int argc, char *argv[])
+{
+       off_t size;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       size = remotesize(argv[1], 1);
+       if (size != -1)
+               fprintf(ttyout,
+                   "%s\t" LLF "\n", argv[1], (LLT)size);
+       code = (size > 0);
+}
+
+/*
+ * Get last modification time of file on remote machine
+ */
+void
+modtime(int argc, char *argv[])
+{
+       time_t mtime;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       mtime = remotemodtime(argv[1], 1);
+       if (mtime != -1)
+               fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime)));
+       code = (mtime > 0);
+}
+
+/*
+ * Show status on remote machine
+ */
+void
+rmtstatus(int argc, char *argv[])
+{
+
+       if (argc == 0) {
+               UPRINTF("usage: %s [remote-file]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       COMMAND_1ARG(argc, argv, "STAT");
+}
+
+/*
+ * Get file if modtime is more recent than current file
+ */
+void
+newer(int argc, char *argv[])
+{
+
+       if (getit(argc, argv, -1, "w"))
+               fprintf(ttyout,
+                   "Local file \"%s\" is newer than remote file \"%s\".\n",
+                   argv[2], argv[1]);
+}
+
+/*
+ * Display one local file through $PAGER.
+ */
+void
+lpage(int argc, char *argv[])
+{
+       size_t len;
+       char *p, *pager, *locfile;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "local-file"))) {
+               UPRINTF("usage: %s local-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if ((locfile = globulize(argv[1])) == NULL) {
+               code = -1;
+               return;
+       }
+       p = getoptionvalue("pager");
+       if (EMPTYSTRING(p))
+               p = DEFAULTPAGER;
+       len = strlen(p) + strlen(locfile) + 2;
+       pager = ftp_malloc(len);
+       (void)strlcpy(pager, p,         len);
+       (void)strlcat(pager, " ",       len);
+       (void)strlcat(pager, locfile,   len);
+       system(pager);
+       code = 0;
+       (void)free(pager);
+       (void)free(locfile);
+}
+
+/*
+ * Display one remote file through $PAGER.
+ */
+void
+page(int argc, char *argv[])
+{
+       int ohash, orestart_point, overbose;
+       size_t len;
+       char *p, *pager;
+
+       if (argc == 0 || argc > 2 ||
+           (argc == 1 && !another(&argc, &argv, "remote-file"))) {
+               UPRINTF("usage: %s remote-file\n", argv[0]);
+               code = -1;
+               return;
+       }
+       p = getoptionvalue("pager");
+       if (EMPTYSTRING(p))
+               p = DEFAULTPAGER;
+       len = strlen(p) + 2;
+       pager = ftp_malloc(len);
+       pager[0] = '|';
+       (void)strlcpy(pager + 1, p, len - 1);
+
+       ohash = hash;
+       orestart_point = restart_point;
+       overbose = verbose;
+       hash = restart_point = verbose = 0;
+       recvrequest("RETR", pager, argv[1], "r+", 1, 0);
+       hash = ohash;
+       restart_point = orestart_point;
+       verbose = overbose;
+       (void)free(pager);
+}
+
+/*
+ * Set the socket send or receive buffer size.
+ */
+void
+setxferbuf(int argc, char *argv[])
+{
+       int size, dir;
+
+       if (argc != 2) {
+ usage:
+               UPRINTF("usage: %s size\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (strcasecmp(argv[0], "sndbuf") == 0)
+               dir = RATE_PUT;
+       else if (strcasecmp(argv[0], "rcvbuf") == 0)
+               dir = RATE_GET;
+       else if (strcasecmp(argv[0], "xferbuf") == 0)
+               dir = RATE_ALL;
+       else
+               goto usage;
+
+       if ((size = strsuftoi(argv[1])) == -1)
+               goto usage;
+
+       if (size == 0) {
+               fprintf(ttyout, "%s: size must be positive.\n", argv[0]);
+               goto usage;
+       }
+
+       if (dir & RATE_PUT)
+               sndbuf_size = size;
+       if (dir & RATE_GET)
+               rcvbuf_size = size;
+       fprintf(ttyout, "Socket buffer sizes: send %d, receive %d.\n",
+           sndbuf_size, rcvbuf_size);
+       code = 0;
+}
+
+/*
+ * Set or display options (defaults are provided by various env vars)
+ */
+void
+setoption(int argc, char *argv[])
+{
+       struct option *o;
+
+       code = -1;
+       if (argc == 0 || (argc != 1 && argc != 3)) {
+               UPRINTF("usage: %s [option value]\n", argv[0]);
+               return;
+       }
+
+#define        OPTIONINDENT ((int) sizeof("http_proxy"))
+       if (argc == 1) {
+               for (o = optiontab; o->name != NULL; o++) {
+                       fprintf(ttyout, "%-*s\t%s\n", OPTIONINDENT,
+                           o->name, o->value ? o->value : "");
+               }
+       } else {
+               o = getoption(argv[1]);
+               if (o == NULL) {
+                       fprintf(ttyout, "No such option `%s'.\n", argv[1]);
+                       return;
+               }
+               FREEPTR(o->value);
+               o->value = ftp_strdup(argv[2]);
+               if (verbose)
+                       fprintf(ttyout, "Setting `%s' to `%s'.\n",
+                           o->name, o->value);
+       }
+       code = 0;
+}
+
+/*
+ * Unset an option
+ */
+void
+unsetoption(int argc, char *argv[])
+{
+       struct option *o;
+
+       code = -1;
+       if (argc == 0 || argc != 2) {
+               UPRINTF("usage: %s option\n", argv[0]);
+               return;
+       }
+
+       o = getoption(argv[1]);
+       if (o == NULL) {
+               fprintf(ttyout, "No such option `%s'.\n", argv[1]);
+               return;
+       }
+       FREEPTR(o->value);
+       fprintf(ttyout, "Unsetting `%s'.\n", o->name);
+       code = 0;
+}
+
+/*
+ * Display features supported by the remote host.
+ */
+void
+feat(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc == 0) {
+               UPRINTF("usage: %s\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_FEAT]) {
+               fprintf(ttyout,
+                   "FEAT is not supported by the remote server.\n");
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       (void)command("FEAT");
+       verbose = oldverbose;
+}
+
+void
+mlst(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc < 1 || argc > 2) {
+               UPRINTF("usage: %s [remote-path]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_MLST]) {
+               fprintf(ttyout,
+                   "MLST is not supported by the remote server.\n");
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       COMMAND_1ARG(argc, argv, "MLST");
+       verbose = oldverbose;
+}
+
+void
+opts(int argc, char *argv[])
+{
+       int oldverbose = verbose;
+
+       if (argc < 2 || argc > 3) {
+               UPRINTF("usage: %s command [options]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       if (! features[FEAT_FEAT]) {
+               fprintf(ttyout,
+                   "OPTS is not supported by the remote server.\n");
+               return;
+       }
+       verbose = 1;    /* If we aren't verbose, this doesn't do anything! */
+       if (argc == 2)
+               command("OPTS %s", argv[1]);
+       else
+               command("OPTS %s %s", argv[1], argv[2]);
+       verbose = oldverbose;
+}
diff --git a/contrib/tnftp/cmdtab.c b/contrib/tnftp/cmdtab.c
new file mode 100644 (file)
index 0000000..6ebe22e
--- /dev/null
@@ -0,0 +1,309 @@
+/*     $NetBSD: cmdtab.c,v 1.46 2006/01/31 20:05:36 christos Exp $     */
+
+/*-
+ * Copyright (c) 1996-2005 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * 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 NetBSD
+ *     Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The 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. 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 <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cmdtab.c   8.4 (Berkeley) 10/9/94";
+#else
+__RCSID("$NetBSD: cmdtab.c,v 1.46 2006/01/31 20:05:36 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <stdio.h>
+#include "ftp_var.h"
+
+/*
+ * User FTP -- Command Tables.
+ */
+
+#define HSTR   static const char
+
+#ifndef NO_HELP
+HSTR   accounthelp[] = "send account command to remote server";
+HSTR   appendhelp[] =  "append to a file";
+HSTR   asciihelp[] =   "set ascii transfer type";
+HSTR   beephelp[] =    "beep when command completed";
+HSTR   binaryhelp[] =  "set binary transfer type";
+HSTR   casehelp[] =    "toggle mget upper/lower case id mapping";
+HSTR   cdhelp[] =      "change remote working directory";
+HSTR   cduphelp[] =    "change remote working directory to parent directory";
+HSTR   chmodhelp[] =   "change file permissions of remote file";
+HSTR   connecthelp[] = "connect to remote ftp server";
+HSTR   crhelp[] =      "toggle carriage return stripping on ascii gets";
+HSTR   debughelp[] =   "toggle/set debugging mode";
+HSTR   deletehelp[] =  "delete remote file";
+HSTR   disconhelp[] =  "terminate ftp session";
+HSTR   domachelp[] =   "execute macro";
+HSTR   edithelp[] =    "toggle command line editing";
+HSTR   epsv4help[] =   "toggle use of EPSV/EPRT on IPv4 ftp";
+HSTR   feathelp[] =    "show FEATures supported by remote system";
+HSTR   formhelp[] =    "set file transfer format";
+HSTR   gatehelp[] =    "toggle gate-ftp; specify host[:port] to change proxy";
+HSTR   globhelp[] =    "toggle metacharacter expansion of local file names";
+HSTR   hashhelp[] =    "toggle printing `#' marks; specify number to set size";
+HSTR   helphelp[] =    "print local help information";
+HSTR   idlehelp[] =    "get (set) idle timer on remote side";
+HSTR   lcdhelp[] =     "change local working directory";
+HSTR   lpagehelp[] =   "view a local file through your pager";
+HSTR   lpwdhelp[] =    "print local working directory";
+HSTR   lshelp[] =      "list contents of remote path";
+HSTR   macdefhelp[] =  "define a macro";
+HSTR   mdeletehelp[] = "delete multiple files";
+HSTR   mgethelp[] =    "get multiple files";
+HSTR   mregethelp[] =  "get multiple files restarting at end of local file";
+HSTR   fgethelp[] =    "get files using a localfile as a source of names";
+HSTR   mkdirhelp[] =   "make directory on the remote machine";
+HSTR   mlshelp[] =     "list contents of multiple remote directories";
+HSTR   mlsdhelp[] =    "list contents of remote directory in a machine "
+                       "parsable form";
+HSTR   mlsthelp[] =    "list remote path in a machine parsable form";
+HSTR   modehelp[] =    "set file transfer mode";
+HSTR   modtimehelp[] = "show last modification time of remote file";
+HSTR   mputhelp[] =    "send multiple files";
+HSTR   newerhelp[] =   "get file if remote file is newer than local file ";
+HSTR   nmaphelp[] =    "set templates for default file name mapping";
+HSTR   ntranshelp[] =  "set translation table for default file name mapping";
+HSTR   optshelp[] =    "show or set options for remote commands";
+HSTR   pagehelp[] =    "view a remote file through your pager";
+HSTR   passivehelp[] = "toggle use of passive transfer mode";
+HSTR   plshelp[] =     "list contents of remote path through your pager";
+HSTR   pmlsdhelp[] =   "list contents of remote directory in a machine "
+                       "parsable form through your pager";
+HSTR   porthelp[] =    "toggle use of PORT/LPRT cmd for each data connection";
+HSTR   preservehelp[] ="toggle preservation of modification time of "
+                       "retrieved files";
+HSTR   progresshelp[] ="toggle transfer progress meter";
+HSTR   prompthelp[] =  "force interactive prompting on multiple commands";
+HSTR   proxyhelp[] =   "issue command on alternate connection";
+HSTR   pwdhelp[] =     "print working directory on remote machine";
+HSTR   quithelp[] =    "terminate ftp session and exit";
+HSTR   quotehelp[] =   "send arbitrary ftp command";
+HSTR   ratehelp[] =    "set transfer rate limit (in bytes/second)";
+HSTR   receivehelp[] = "receive file";
+HSTR   regethelp[] =   "get file restarting at end of local file";
+HSTR   remotehelp[] =  "get help from remote server";
+HSTR   renamehelp[] =  "rename file";
+HSTR   resethelp[] =   "clear queued command replies";
+HSTR   restarthelp[]=  "restart file transfer at bytecount";
+HSTR   rmdirhelp[] =   "remove directory on the remote machine";
+HSTR   rmtstatushelp[]="show status of remote machine";
+HSTR   runiquehelp[] = "toggle store unique for local files";
+HSTR   sendhelp[] =    "send one file";
+HSTR   sethelp[] =     "set or display options";
+HSTR   shellhelp[] =   "escape to the shell";
+HSTR   sitehelp[] =    "send site specific command to remote server\n"
+                       "\t\tTry \"rhelp site\" or \"site help\" "
+                       "for more information";
+HSTR   sizecmdhelp[] = "show size of remote file";
+HSTR   statushelp[] =  "show current status";
+HSTR   structhelp[] =  "set file transfer structure";
+HSTR   suniquehelp[] = "toggle store unique on remote machine";
+HSTR   systemhelp[] =  "show remote system type";
+HSTR   tenexhelp[] =   "set tenex file transfer type";
+HSTR   tracehelp[] =   "toggle packet tracing";
+HSTR   typehelp[] =    "set file transfer type";
+HSTR   umaskhelp[] =   "get (set) umask on remote side";
+HSTR   unsethelp[] =   "unset an option";
+HSTR   usagehelp[] =   "show command usage";
+HSTR   userhelp[] =    "send new user information";
+HSTR   verbosehelp[] = "toggle verbose mode";
+HSTR   xferbufhelp[] = "set socket send/receive buffer size";
+#endif
+
+HSTR   empty[] = "";
+
+#ifdef NO_HELP
+#define H(x)   empty
+#else
+#define H(x)   x
+#endif
+
+#ifdef NO_EDITCOMPLETE
+#define        CMPL(x)
+#define        CMPL0
+#else  /* !NO_EDITCOMPLETE */
+#define        CMPL(x) #x,
+#define        CMPL0   empty,
+#endif /* !NO_EDITCOMPLETE */
+
+struct cmd cmdtab[] = {
+       { "!",          H(shellhelp),   0, 0, 0, CMPL0          shell },
+       { "$",          H(domachelp),   1, 0, 0, CMPL0          domacro },
+       { "account",    H(accounthelp), 0, 1, 1, CMPL0          account},
+       { "append",     H(appendhelp),  1, 1, 1, CMPL(lr)       put },
+       { "ascii",      H(asciihelp),   0, 1, 1, CMPL0          setascii },
+       { "bell",       H(beephelp),    0, 0, 0, CMPL0          setbell },
+       { "binary",     H(binaryhelp),  0, 1, 1, CMPL0          setbinary },
+       { "bye",        H(quithelp),    0, 0, 0, CMPL0          quit },
+       { "case",       H(casehelp),    0, 0, 1, CMPL0          setcase },
+       { "cd",         H(cdhelp),      0, 1, 1, CMPL(r)        cd },
+       { "cdup",       H(cduphelp),    0, 1, 1, CMPL0          cdup },
+       { "chmod",      H(chmodhelp),   0, 1, 1, CMPL(nr)       do_chmod },
+       { "close",      H(disconhelp),  0, 1, 1, CMPL0          disconnect },
+       { "cr",         H(crhelp),      0, 0, 0, CMPL0          setcr },
+       { "ftp_debug",  H(debughelp),   0, 0, 0, CMPL0          setdebug },
+       { "delete",     H(deletehelp),  0, 1, 1, CMPL(r)        delete },
+       { "dir",        H(lshelp),      1, 1, 1, CMPL(rl)       ls },
+       { "disconnect", H(disconhelp),  0, 1, 1, CMPL0          disconnect },
+       { "edit",       H(edithelp),    0, 0, 0, CMPL0          setedit },
+       { "epsv4",      H(epsv4help),   0, 0, 0, CMPL0          setepsv4 },
+       { "exit",       H(quithelp),    0, 0, 0, CMPL0          quit },
+       { "features",   H(feathelp),    0, 1, 1, CMPL0          feat },
+       { "fget",       H(fgethelp),    1, 1, 1, CMPL(l)        fget },
+       { "form",       H(formhelp),    0, 1, 1, CMPL0          setform },
+       { "ftp",        H(connecthelp), 0, 0, 1, CMPL0          setpeer },
+       { "gate",       H(gatehelp),    0, 0, 0, CMPL0          setgate },
+       { "get",        H(receivehelp), 1, 1, 1, CMPL(rl)       get },
+       { "glob",       H(globhelp),    0, 0, 0, CMPL0          setglob },
+       { "hash",       H(hashhelp),    0, 0, 0, CMPL0          sethash },
+       { "help",       H(helphelp),    0, 0, 1, CMPL(C)        help },
+       { "idle",       H(idlehelp),    0, 1, 1, CMPL0          idlecmd },
+       { "image",      H(binaryhelp),  0, 1, 1, CMPL0          setbinary },
+       { "lcd",        H(lcdhelp),     0, 0, 0, CMPL(l)        lcd },
+       { "less",       H(pagehelp),    1, 1, 1, CMPL(r)        page },
+       { "lpage",      H(lpagehelp),   0, 0, 0, CMPL(l)        lpage },
+       { "lpwd",       H(lpwdhelp),    0, 0, 0, CMPL0          lpwd },
+       { "ls",         H(lshelp),      1, 1, 1, CMPL(rl)       ls },
+       { "macdef",     H(macdefhelp),  0, 0, 0, CMPL0          macdef },
+       { "mdelete",    H(mdeletehelp), 1, 1, 1, CMPL(R)        mdelete },
+       { "mdir",       H(mlshelp),     1, 1, 1, CMPL(R)        mls },
+       { "mget",       H(mgethelp),    1, 1, 1, CMPL(R)        mget },
+       { "mkdir",      H(mkdirhelp),   0, 1, 1, CMPL(r)        makedir },
+       { "mls",        H(mlshelp),     1, 1, 1, CMPL(R)        mls },
+       { "mlsd",       H(mlsdhelp),    1, 1, 1, CMPL(r)        ls },
+       { "mlst",       H(mlsthelp),    1, 1, 1, CMPL(r)        mlst },
+       { "mode",       H(modehelp),    0, 1, 1, CMPL0          setftmode },
+       { "modtime",    H(modtimehelp), 0, 1, 1, CMPL(r)        modtime },
+       { "more",       H(pagehelp),    1, 1, 1, CMPL(r)        page },
+       { "mput",       H(mputhelp),    1, 1, 1, CMPL(L)        mput },
+       { "mreget",     H(mregethelp),  1, 1, 1, CMPL(R)        mget },
+       { "msend",      H(mputhelp),    1, 1, 1, CMPL(L)        mput },
+       { "newer",      H(newerhelp),   1, 1, 1, CMPL(r)        newer },
+       { "nlist",      H(lshelp),      1, 1, 1, CMPL(rl)       ls },
+       { "nmap",       H(nmaphelp),    0, 0, 1, CMPL0          setnmap },
+       { "ntrans",     H(ntranshelp),  0, 0, 1, CMPL0          setntrans },
+       { "open",       H(connecthelp), 0, 0, 1, CMPL0          setpeer },
+       { "page",       H(pagehelp),    1, 1, 1, CMPL(r)        page },
+       { "passive",    H(passivehelp), 0, 0, 0, CMPL0          setpassive },
+       { "pdir",       H(plshelp),     1, 1, 1, CMPL(r)        ls },
+       { "pls",        H(plshelp),     1, 1, 1, CMPL(r)        ls },
+       { "pmlsd",      H(pmlsdhelp),   1, 1, 1, CMPL(r)        ls },
+       { "preserve",   H(preservehelp),0, 0, 0, CMPL0          setpreserve },
+       { "progress",   H(progresshelp),0, 0, 0, CMPL0          setprogress },
+       { "prompt",     H(prompthelp),  0, 0, 0, CMPL0          setprompt },
+       { "proxy",      H(proxyhelp),   0, 0, 1, CMPL(c)        doproxy },
+       { "put",        H(sendhelp),    1, 1, 1, CMPL(lr)       put },
+       { "pwd",        H(pwdhelp),     0, 1, 1, CMPL0          pwd },
+       { "quit",       H(quithelp),    0, 0, 0, CMPL0          quit },
+       { "quote",      H(quotehelp),   1, 1, 1, CMPL0          quote },
+       { "rate",       H(ratehelp),    0, 0, 0, CMPL0          setrate },
+       { "rcvbuf",     H(xferbufhelp), 0, 0, 0, CMPL0          setxferbuf },
+       { "recv",       H(receivehelp), 1, 1, 1, CMPL(rl)       get },
+       { "reget",      H(regethelp),   1, 1, 1, CMPL(rl)       reget },
+       { "remopts",    H(optshelp),    0, 1, 1, CMPL0          opts },
+       { "rename",     H(renamehelp),  0, 1, 1, CMPL(rr)       renamefile },
+       { "reset",      H(resethelp),   0, 1, 1, CMPL0          reset },
+       { "restart",    H(restarthelp), 1, 1, 1, CMPL0          restart },
+       { "rhelp",      H(remotehelp),  0, 1, 1, CMPL0          rmthelp },
+       { "rmdir",      H(rmdirhelp),   0, 1, 1, CMPL(r)        removedir },
+       { "rstatus",    H(rmtstatushelp),0, 1, 1, CMPL(r)       rmtstatus },
+       { "runique",    H(runiquehelp), 0, 0, 1, CMPL0          setrunique },
+       { "send",       H(sendhelp),    1, 1, 1, CMPL(lr)       put },
+       { "sendport",   H(porthelp),    0, 0, 0, CMPL0          setport },
+       { "set",        H(sethelp),     0, 0, 0, CMPL(o)        setoption },
+       { "site",       H(sitehelp),    0, 1, 1, CMPL0          site },
+       { "size",       H(sizecmdhelp), 1, 1, 1, CMPL(r)        sizecmd },
+       { "sndbuf",     H(xferbufhelp), 0, 0, 0, CMPL0          setxferbuf },
+       { "status",     H(statushelp),  0, 0, 1, CMPL0          status },
+       { "struct",     H(structhelp),  0, 1, 1, CMPL0          setstruct },
+       { "sunique",    H(suniquehelp), 0, 0, 1, CMPL0          setsunique },
+       { "system",     H(systemhelp),  0, 1, 1, CMPL0          syst },
+       { "tenex",      H(tenexhelp),   0, 1, 1, CMPL0          settenex },
+       { "throttle",   H(ratehelp),    0, 0, 0, CMPL0          setrate },
+       { "trace",      H(tracehelp),   0, 0, 0, CMPL0          settrace },
+       { "type",       H(typehelp),    0, 1, 1, CMPL0          settype },
+       { "umask",      H(umaskhelp),   0, 1, 1, CMPL0          do_umask },
+       { "unset",      H(unsethelp),   0, 0, 0, CMPL(o)        unsetoption },
+       { "usage",      H(usagehelp),   0, 0, 1, CMPL(C)        help },
+       { "user",       H(userhelp),    0, 1, 1, CMPL0          user },
+       { "verbose",    H(verbosehelp), 0, 0, 0, CMPL0          setverbose },
+       { "xferbuf",    H(xferbufhelp), 0, 0, 0, CMPL0          setxferbuf },
+       { "?",          H(helphelp),    0, 0, 1, CMPL(C)        help },
+       { 0 },
+};
+
+struct option optiontab[] = {
+       { "anonpass",   NULL },
+       { "ftp_proxy",  NULL },
+       { "http_proxy", NULL },
+       { "no_proxy",   NULL },
+       { "pager",      NULL },
+       { "prompt",     NULL },
+       { "rprompt",    NULL },
+       { 0 },
+};
diff --git a/contrib/tnftp/complete.c b/contrib/tnftp/complete.c
new file mode 100644 (file)
index 0000000..0f593b3
--- /dev/null
@@ -0,0 +1,435 @@
+/*     $NetBSD: complete.c,v 1.41 2006/01/31 20:01:23 christos Exp $   */
+
+/*-
+ * Copyright (c) 1997-2000,2005 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * 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 NetBSD
+ *     Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: complete.c,v 1.41 2006/01/31 20:01:23 christos Exp $");
+#endif /* not lint */
+
+/*
+ * FTP user program - command and file completion routines
+ */
+
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ftp_var.h"
+
+#ifndef NO_EDITCOMPLETE
+
+static int          comparstr          (const void *, const void *);
+static unsigned char complete_ambiguous        (char *, int, StringList *);
+static unsigned char complete_command  (char *, int);
+static unsigned char complete_local    (char *, int);
+static unsigned char complete_option   (char *, int);
+static unsigned char complete_remote   (char *, int);
+
+static int
+comparstr(const void *a, const void *b)
+{
+       return (strcmp(*(const char * const *)a, *(const char * const *)b));
+}
+
+/*
+ * Determine if complete is ambiguous. If unique, insert.
+ * If no choices, error. If unambiguous prefix, insert that.
+ * Otherwise, list choices. words is assumed to be filtered
+ * to only contain possible choices.
+ * Args:
+ *     word    word which started the match
+ *     list    list by default
+ *     words   stringlist containing possible matches
+ * Returns a result as per el_set(EL_ADDFN, ...)
+ */
+static unsigned char
+complete_ambiguous(char *word, int list, StringList *words)
+{
+       char insertstr[MAXPATHLEN];
+       char *lastmatch, *p;
+       int i, j;
+       size_t matchlen, wordlen;
+
+       wordlen = strlen(word);
+       if (words->sl_cur == 0)
+               return (CC_ERROR);      /* no choices available */
+
+       if (words->sl_cur == 1) {       /* only once choice available */
+               p = words->sl_str[0] + wordlen;
+               if (*p == '\0')         /* at end of word? */
+                       return (CC_REFRESH);
+               ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
+               if (el_insertstr(el, insertstr) == -1)
+                       return (CC_ERROR);
+               else
+                       return (CC_REFRESH);
+       }
+
+       if (!list) {
+               matchlen = 0;
+               lastmatch = words->sl_str[0];
+               matchlen = strlen(lastmatch);
+               for (i = 1 ; i < words->sl_cur ; i++) {
+                       for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
+                               if (lastmatch[j] != words->sl_str[i][j])
+                                       break;
+                       if (j < matchlen)
+                               matchlen = j;
+               }
+               if (matchlen > wordlen) {
+                       ftpvis(insertstr, sizeof(insertstr),
+                           lastmatch + wordlen, matchlen - wordlen);
+                       if (el_insertstr(el, insertstr) == -1)
+                               return (CC_ERROR);
+                       else
+                               return (CC_REFRESH_BEEP);
+               }
+       }
+
+       putc('\n', ttyout);
+       qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
+       list_vertical(words);
+       return (CC_REDISPLAY);
+}
+
+/*
+ * Complete a command
+ */
+static unsigned char
+complete_command(char *word, int list)
+{
+       struct cmd *c;
+       StringList *words;
+       size_t wordlen;
+       unsigned char rv;
+
+       words = ftp_sl_init();
+       wordlen = strlen(word);
+
+       for (c = cmdtab; c->c_name != NULL; c++) {
+               if (wordlen > strlen(c->c_name))
+                       continue;
+               if (strncmp(word, c->c_name, wordlen) == 0)
+                       ftp_sl_add(words, c->c_name);
+       }
+
+       rv = complete_ambiguous(word, list, words);
+       if (rv == CC_REFRESH) {
+               if (el_insertstr(el, " ") == -1)
+                       rv = CC_ERROR;
+       }
+       sl_free(words, 0);
+       return (rv);
+}
+
+/*
+ * Complete a local file
+ */
+static unsigned char
+complete_local(char *word, int list)
+{
+       StringList *words;
+       char dir[MAXPATHLEN];
+       char *file;
+       DIR *dd;
+       struct dirent *dp;
+       unsigned char rv;
+       size_t len;
+
+       if ((file = strrchr(word, '/')) == NULL) {
+               dir[0] = '.';
+               dir[1] = '\0';
+               file = word;
+       } else {
+               if (file == word) {
+                       dir[0] = '/';
+                       dir[1] = '\0';
+               } else
+                       (void)strlcpy(dir, word, file - word + 1);
+               file++;
+       }
+       if (dir[0] == '~') {
+               char *p;
+
+               if ((p = globulize(dir)) == NULL)
+                       return (CC_ERROR);
+               (void)strlcpy(dir, p, sizeof(dir));
+               free(p);
+       }
+
+       if ((dd = opendir(dir)) == NULL)
+               return (CC_ERROR);
+
+       words = ftp_sl_init();
+       len = strlen(file);
+
+       for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
+               if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+                       continue;
+
+#if defined(DIRENT_MISSING_D_NAMLEN)
+               if (len > strlen(dp->d_name))
+                       continue;
+#else
+               if (len > dp->d_namlen)
+                       continue;
+#endif
+               if (strncmp(file, dp->d_name, len) == 0) {
+                       char *tcp;
+
+                       tcp = ftp_strdup(dp->d_name);
+                       ftp_sl_add(words, tcp);
+               }
+       }
+       closedir(dd);
+
+       rv = complete_ambiguous(file, list, words);
+       if (rv == CC_REFRESH) {
+               struct stat sb;
+               char path[MAXPATHLEN];
+
+               (void)strlcpy(path, dir,                sizeof(path));
+               (void)strlcat(path, "/",                sizeof(path));
+               (void)strlcat(path, words->sl_str[0],   sizeof(path));
+
+               if (stat(path, &sb) >= 0) {
+                       char suffix[2] = " ";
+
+                       if (S_ISDIR(sb.st_mode))
+                               suffix[0] = '/';
+                       if (el_insertstr(el, suffix) == -1)
+                               rv = CC_ERROR;
+               }
+       }
+       sl_free(words, 1);
+       return (rv);
+}
+/*
+ * Complete an option
+ */
+static unsigned char
+complete_option(char *word, int list)
+{
+       struct option *o;
+       StringList *words;
+       size_t wordlen;
+       unsigned char rv;
+
+       words = ftp_sl_init();
+       wordlen = strlen(word);
+
+       for (o = optiontab; o->name != NULL; o++) {
+               if (wordlen > strlen(o->name))
+                       continue;
+               if (strncmp(word, o->name, wordlen) == 0)
+                       ftp_sl_add(words, o->name);
+       }
+
+       rv = complete_ambiguous(word, list, words);
+       if (rv == CC_REFRESH) {
+               if (el_insertstr(el, " ") == -1)
+                       rv = CC_ERROR;
+       }
+       sl_free(words, 0);
+       return (rv);
+}
+
+/*
+ * Complete a remote file
+ */
+static unsigned char
+complete_remote(char *word, int list)
+{
+       static StringList *dirlist;
+       static char      lastdir[MAXPATHLEN];
+       StringList      *words;
+       char             dir[MAXPATHLEN];
+       char            *file, *cp;
+       int              i;
+       unsigned char    rv;
+
+       char *dummyargv[] = { "complete", NULL, NULL };
+       dummyargv[1] = dir;
+
+       if ((file = strrchr(word, '/')) == NULL) {
+               dir[0] = '\0';
+               file = word;
+       } else {
+               cp = file;
+               while (*cp == '/' && cp > word)
+                       cp--;
+               (void)strlcpy(dir, word, cp - word + 2);
+               file++;
+       }
+
+       if (dirchange || dirlist == NULL ||
+           strcmp(dir, lastdir) != 0) {                /* dir not cached */
+               const char *emesg;
+
+               if (dirlist != NULL)
+                       sl_free(dirlist, 1);
+               dirlist = ftp_sl_init();
+
+               mflag = 1;
+               emesg = NULL;
+               while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
+                       char *tcp;
+
+                       if (!mflag)
+                               continue;
+                       if (*cp == '\0') {
+                               mflag = 0;
+                               continue;
+                       }
+                       tcp = strrchr(cp, '/');
+                       if (tcp)
+                               tcp++;
+                       else
+                               tcp = cp;
+                       tcp = ftp_strdup(tcp);
+                       ftp_sl_add(dirlist, tcp);
+               }
+               if (emesg != NULL) {
+                       fprintf(ttyout, "\n%s\n", emesg);
+                       return (CC_REDISPLAY);
+               }
+               (void)strlcpy(lastdir, dir, sizeof(lastdir));
+               dirchange = 0;
+       }
+
+       words = ftp_sl_init();
+       for (i = 0; i < dirlist->sl_cur; i++) {
+               cp = dirlist->sl_str[i];
+               if (strlen(file) > strlen(cp))
+                       continue;
+               if (strncmp(file, cp, strlen(file)) == 0)
+                       ftp_sl_add(words, cp);
+       }
+       rv = complete_ambiguous(file, list, words);
+       sl_free(words, 0);
+       return (rv);
+}
+
+/*
+ * Generic complete routine
+ */
+unsigned char
+complete(EditLine *el, int ch)
+{
+       static char word[FTPBUFLEN];
+       static int lastc_argc, lastc_argo;
+
+       struct cmd *c;
+       const LineInfo *lf;
+       int celems, dolist, cmpltype;
+       size_t len;
+
+       lf = el_line(el);
+       len = lf->lastchar - lf->buffer;
+       if (len >= sizeof(line))
+               return (CC_ERROR);
+       (void)strlcpy(line, lf->buffer, len + 1);
+       cursor_pos = line + (lf->cursor - lf->buffer);
+       lastc_argc = cursor_argc;       /* remember last cursor pos */
+       lastc_argo = cursor_argo;
+       makeargv();                     /* build argc/argv of current line */
+
+       if (cursor_argo >= sizeof(word))
+               return (CC_ERROR);
+
+       dolist = 0;
+                       /* if cursor and word is same, list alternatives */
+       if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
+           && strncmp(word, margv[cursor_argc] ? margv[cursor_argc] : "",
+                       cursor_argo) == 0)
+               dolist = 1;
+       else if (cursor_argc < margc)
+               (void)strlcpy(word, margv[cursor_argc], cursor_argo + 1);
+       word[cursor_argo] = '\0';
+
+       if (cursor_argc == 0)
+               return (complete_command(word, dolist));
+
+       c = getcmd(margv[0]);
+       if (c == (struct cmd *)-1 || c == 0)
+               return (CC_ERROR);
+       celems = strlen(c->c_complete);
+
+               /* check for 'continuation' completes (which are uppercase) */
+       if ((cursor_argc > celems) && (celems > 0)
+           && isupper((unsigned char) c->c_complete[celems-1]))
+               cursor_argc = celems;
+
+       if (cursor_argc > celems)
+               return (CC_ERROR);
+
+       cmpltype = c->c_complete[cursor_argc - 1];
+       switch (cmpltype) {
+               case 'c':                       /* command complete */
+               case 'C':
+                       return (complete_command(word, dolist));
+               case 'l':                       /* local complete */
+               case 'L':
+                       return (complete_local(word, dolist));
+               case 'n':                       /* no complete */
+               case 'N':                       /* no complete */
+                       return (CC_ERROR);
+               case 'o':                       /* local complete */
+               case 'O':
+                       return (complete_option(word, dolist));
+               case 'r':                       /* remote complete */
+               case 'R':
+                       if (connected != -1) {
+                               fputs("\nMust be logged in to complete.\n",
+                                   ttyout);
+                               return (CC_REDISPLAY);
+                       }
+                       return (complete_remote(word, dolist));
+               default:
+                       errx(1, "unknown complete type `%c'", cmpltype);
+                       return (CC_ERROR);
+       }
+       /* NOTREACHED */
+}
+
+#endif /* !NO_EDITCOMPLETE */
diff --git a/contrib/tnftp/domacro.c b/contrib/tnftp/domacro.c
new file mode 100644 (file)
index 0000000..4b76a68
--- /dev/null
@@ -0,0 +1,143 @@
+/*     $NetBSD: domacro.c,v 1.21 2005/06/29 02:31:19 christos Exp $    */
+
+/*
+ * Copyright (c) 1985, 1993, 1994
+ *     The 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. 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 <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)domacro.c  8.3 (Berkeley) 4/2/94";
+#else
+__RCSID("$NetBSD: domacro.c,v 1.21 2005/06/29 02:31:19 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ftp_var.h"
+
+void
+domacro(int argc, char *argv[])
+{
+       int i, j, count = 2, loopflg = 0;
+       char *cp1, *cp2, line2[FTPBUFLEN];
+       struct cmd *c;
+
+       if ((argc == 0 && argv != NULL) ||
+           (argc < 2 && !another(&argc, &argv, "macro name"))) {
+               UPRINTF("usage: %s macro_name [args]\n", argv[0]);
+               code = -1;
+               return;
+       }
+       for (i = 0; i < macnum; ++i) {
+               if (!strncmp(argv[1], macros[i].mac_name, 9))
+                       break;
+       }
+       if (i == macnum) {
+               fprintf(ttyout, "'%s' macro not found.\n", argv[1]);
+               code = -1;
+               return;
+       }
+       (void)strlcpy(line2, line, sizeof(line2));
+ TOP:
+       cp1 = macros[i].mac_start;
+       while (cp1 != macros[i].mac_end) {
+               while (isspace((unsigned char)*cp1))
+                       cp1++;
+               cp2 = line;
+               while (*cp1 != '\0') {
+                       switch(*cp1) {
+                       case '\\':
+                               *cp2++ = *++cp1;
+                               break;
+                       case '$':
+                               if (isdigit((unsigned char)*(cp1+1))) {
+                                       j = 0;
+                                       while (isdigit((unsigned char)*++cp1))
+                                               j = 10*j +  *cp1 - '0';
+                                       cp1--;
+                                       if (argc - 2 >= j) {
+                                               (void)strlcpy(cp2, argv[j+1],
+                                                   sizeof(line) - (cp2 - line));
+                                               cp2 += strlen(argv[j+1]);
+                                       }
+                                       break;
+                               }
+                               if (*(cp1+1) == 'i') {
+                                       loopflg = 1;
+                                       cp1++;
+                                       if (count < argc) {
+                                               (void)strlcpy(cp2, argv[count],
+                                                   sizeof(line) - (cp2 - line));
+                                               cp2 += strlen(argv[count]);
+                                       }
+                                       break;
+                               }
+                               /* intentional drop through */
+                       default:
+                               *cp2++ = *cp1;
+                               break;
+                       }
+                       if (*cp1 != '\0')
+                               cp1++;
+               }
+               *cp2 = '\0';
+               makeargv();
+               c = getcmd(margv[0]);
+               if (c == (struct cmd *)-1) {
+                       fputs("?Ambiguous command.\n", ttyout);
+                       code = -1;
+               } else if (c == 0) {
+                       fputs("?Invalid command.\n", ttyout);
+                       code = -1;
+               } else if (c->c_conn && !connected) {
+                       fputs("Not connected.\n", ttyout);
+                       code = -1;
+               } else {
+                       if (verbose) {
+                               fputs(line, ttyout);
+                               putc('\n', ttyout);
+                       }
+                       margv[0] = c->c_name;
+                       (*c->c_handler)(margc, margv);
+                       if (bell && c->c_bell)
+                               (void)putc('\007', ttyout);
+                       (void)strlcpy(line, line2, sizeof(line));
+                       makeargv();
+                       argc = margc;
+                       argv = margv;
+               }
+               if (cp1 != macros[i].mac_end)
+                       cp1++;
+       }
+       if (loopflg && ++count < argc)
+               goto TOP;
+}
diff --git a/contrib/tnftp/extern.h b/contrib/tnftp/extern.h
new file mode 100644 (file)
index 0000000..206fc29
--- /dev/null
@@ -0,0 +1,257 @@
+/*     $NetBSD: extern.h,v 1.70 2006/01/31 20:01:23 christos Exp $     */
+
+/*-
+ * Copyright (c) 1996-2005 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * 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 NetBSD
+ *     Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*-
+ * Copyright (c) 1994 The 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. 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.
+ *
+ *     @(#)extern.h    8.3 (Berkeley) 10/9/94
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * 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. Neither the name of the project 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 PROJECT 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 PROJECT 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.
+ */
+
+struct sockaddr;
+struct tm;
+struct addrinfo;
+
+void   abort_remote(FILE *);
+void   abort_squared(int);
+void   abortpt(int);
+void   abortxfer(int);
+void   account(int, char **);
+void   ai_unmapped(struct addrinfo *);
+int    another(int *, char ***, const char *);
+int    auto_fetch(int, char **);
+int    auto_put(int, char **, const char *);
+void   blkfree(char **);
+void   cd(int, char **);
+void   cdup(int, char **);
+void   changetype(int, int);
+void   cleanuppeer(void);
+void   cmdabort(int);
+void   cmdtimeout(int);
+void   cmdscanner(void);
+int    command(const char *, ...)
+     __attribute__((__format__(__printf__, 1, 2)));
+#ifndef NO_EDITCOMPLETE
+unsigned char complete(EditLine *, int);
+void   controlediting(void);
+#endif /* !NO_EDITCOMPLETE */
+void   crankrate(int);
+FILE   *dataconn(const char *);
+void   delete(int, char **);
+void   disconnect(int, char **);
+void   do_chmod(int, char **);
+void   do_umask(int, char **);
+void   domacro(int, char **);
+void   doproxy(int, char **);
+void   feat(int, char **);
+void   fget(int, char **);
+int    fileindir(const char *, const char *);
+int    foregroundproc(void);
+void   formatbuf(char *, size_t, const char *);
+void   ftpvis(char *, size_t, const char *, size_t);
+int    ftp_login(const char *, const char *, const char *);
+void   get(int, char **);
+struct cmd *getcmd(const char *);
+int    getit(int, char **, int, const char *);
+int    getline(FILE *, char *, size_t, const char **);
+struct option *getoption(const char *);
+char   *getoptionvalue(const char *);
+void   getremoteinfo(void);
+int    getreply(int);
+char   *globulize(const char *);
+char   *gunique(const char *);
+void   help(int, char **);
+char   *hookup(char *, char *);
+void   idlecmd(int, char **);
+int    initconn(void);
+void   intr(int);
+int    isipv6addr(const char *);
+void   list_vertical(StringList *);
+void   lcd(int, char **);
+void   lostpeer(int);
+void   lpage(int, char **);
+void   lpwd(int, char **);
+void   ls(int, char **);
+void   mabort(void);
+void   macdef(int, char **);
+void   makeargv(void);
+void   makedir(int, char **);
+void   mdelete(int, char **);
+void   mget(int, char **);
+void   mintr(int);
+void   mls(int, char **);
+void   mlst(int, char **);
+void   modtime(int, char **);
+void   mput(int, char **);
+const char *onoff(int);
+void   opts(int, char **);
+void   newer(int, char **);
+void   page(int, char **);
+int    parseport(const char *, int);
+int    parserate(int, char **, int);
+char   *prompt(void);
+void   proxabort(int);
+void   proxtrans(const char *, const char *, const char *);
+void   psabort(int);
+void   pswitch(int);
+void   put(int, char **);
+void   pwd(int, char **);
+void   quit(int, char **);
+void   quote(int, char **);
+void   quote1(const char *, int, char **);
+void   recvrequest(const char *, const char *, const char *,
+           const char *, int, int);
+void   reget(int, char **);
+char   *remglob(char **, int, const char **);
+time_t remotemodtime(const char *, int);
+off_t  remotesize(const char *, int);
+void   removedir(int, char **);
+void   renamefile(int, char **);
+void   reset(int, char **);
+void   restart(int, char **);
+void   rmthelp(int, char **);
+void   rmtstatus(int, char **);
+char   *rprompt(void);
+int    ruserpass(const char *, char **, char **, char **);
+void   sendrequest(const char *, const char *, const char *, int);
+void   setascii(int, char **);
+void   setbell(int, char **);
+void   setbinary(int, char **);
+void   setcase(int, char **);
+void   setcr(int, char **);
+void   setdebug(int, char **);
+void   setedit(int, char **);
+void   setepsv4(int, char **);
+void   setform(int, char **);
+void   setftmode(int, char **);
+void   setgate(int, char **);
+void   setglob(int, char **);
+void   sethash(int, char **);
+void   setnmap(int, char **);
+void   setntrans(int, char **);
+void   setoption(int, char **);
+void   setpassive(int, char **);
+void   setpeer(int, char **);
+void   setport(int, char **);
+void   setpreserve(int, char **);
+void   setprogress(int, char **);
+void   setprompt(int, char **);
+void   setrate(int, char **);
+void   setrunique(int, char **);
+void   setstruct(int, char **);
+void   setsunique(int, char **);
+void   settenex(int, char **);
+void   settrace(int, char **);
+void   setttywidth(int);
+void   settype(int, char **);
+void   setupsockbufsize(int);
+void   setverbose(int, char **);
+void   setxferbuf(int, char **);
+void   shell(int, char **);
+void   site(int, char **);
+void   sizecmd(int, char **);
+char   *slurpstring(void);
+void   status(int, char **);
+int    strsuftoi(const char *);
+void   syst(int, char **);
+int    togglevar(int, char **, int *, const char *);
+void   unsetoption(int, char **);
+void   updatelocalcwd(void);
+void   updateremotecwd(void);
+void   usage(void);
+void   user(int, char **);
+int    ftp_connect(int, const struct sockaddr *, socklen_t);
+int    ftp_listen(int, int);
+int    ftp_poll(struct pollfd *, int, int);
+void   *ftp_malloc(size_t);
+StringList *ftp_sl_init(void);
+void   ftp_sl_add(StringList *, char *);
+char   *ftp_strdup(const char *);
diff --git a/contrib/tnftp/fetch.c b/contrib/tnftp/fetch.c
new file mode 100644 (file)
index 0000000..922752f
--- /dev/null
@@ -0,0 +1,1840 @@
+/*     $NetBSD: fetch.c,v 1.171 2006/09/22 22:29:25 elad Exp $ */
+
+/*-
+ * Copyright (c) 1997-2006 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Scott Aaron Bamford.
+ *
+ * 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 NetBSD
+ *     Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: fetch.c,v 1.171 2006/09/22 22:29:25 elad Exp $");
+#endif /* not lint */
+
+/*
+ * FTP User Program -- Command line file retrieval
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+
+#include <arpa/ftp.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "ftp_var.h"
+#include "version.h"
+
+typedef enum {
+       UNKNOWN_URL_T=-1,
+       HTTP_URL_T,
+       FTP_URL_T,
+       FILE_URL_T,
+       CLASSIC_URL_T
+} url_t;
+
+void           aborthttp(int);
+#ifndef NO_AUTH
+static int     auth_url(const char *, char **, const char *, const char *);
+static void    base64_encode(const unsigned char *, size_t, unsigned char *);
+#endif
+static int     go_fetch(const char *);
+static int     fetch_ftp(const char *);
+static int     fetch_url(const char *, const char *, char *, char *);
+static const char *match_token(const char **, const char *);
+static int     parse_url(const char *, const char *, url_t *, char **,
+                           char **, char **, char **, in_port_t *, char **);
+static void    url_decode(char *);
+
+static int     redirect_loop;
+
+
+#define        STRNEQUAL(a,b)  (strncasecmp((a), (b), sizeof((b))-1) == 0)
+#define        ISLWS(x)        ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
+#define        SKIPLWS(x)      do { while (ISLWS((*x))) x++; } while (0)
+
+
+#define        ABOUT_URL       "about:"        /* propaganda */
+#define        FILE_URL        "file://"       /* file URL prefix */
+#define        FTP_URL         "ftp://"        /* ftp URL prefix */
+#define        HTTP_URL        "http://"       /* http URL prefix */
+
+
+/*
+ * Determine if token is the next word in buf (case insensitive).
+ * If so, advance buf past the token and any trailing LWS, and
+ * return a pointer to the token (in buf).  Otherwise, return NULL.
+ * token may be preceeded by LWS.
+ * token must be followed by LWS or NUL.  (I.e, don't partial match).
+ */
+static const char *
+match_token(const char **buf, const char *token)
+{
+       const char      *p, *orig;
+       size_t          tlen;
+
+       tlen = strlen(token);
+       p = *buf;
+       SKIPLWS(p);
+       orig = p;
+       if (strncasecmp(p, token, tlen) != 0)
+               return NULL;
+       p += tlen;
+       if (*p != '\0' && !ISLWS(*p))
+               return NULL;
+       SKIPLWS(p);
+       orig = *buf;
+       *buf = p;
+       return orig;
+}
+
+#ifndef NO_AUTH
+/*
+ * Generate authorization response based on given authentication challenge.
+ * Returns -1 if an error occurred, otherwise 0.
+ * Sets response to a malloc(3)ed string; caller should free.
+ */
+static int
+auth_url(const char *challenge, char **response, const char *guser,
+       const char *gpass)
+{
+       const char      *cp, *scheme, *errormsg;
+       char            *ep, *clear, *realm;
+       char             user[BUFSIZ], *pass;
+       int              rval;
+       size_t           len, clen, rlen;
+
+       *response = NULL;
+       clear = realm = NULL;
+       rval = -1;
+       cp = challenge;
+       scheme = "Basic";       /* only support Basic authentication */
+
+       DPRINTF("auth_url: challenge `%s'\n", challenge);
+
+       if (! match_token(&cp, scheme)) {
+               warnx("Unsupported authentication challenge - `%s'",
+                   challenge);
+               goto cleanup_auth_url;
+       }
+
+#define        REALM "realm=\""
+       if (STRNEQUAL(cp, REALM))
+               cp += sizeof(REALM) - 1;
+       else {
+               warnx("Unsupported authentication challenge - `%s'",
+                   challenge);
+               goto cleanup_auth_url;
+       }
+/* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
+       if ((ep = strchr(cp, '\"')) != NULL) {
+               size_t len = ep - cp;
+
+               realm = (char *)ftp_malloc(len + 1);
+               (void)strlcpy(realm, cp, len + 1);
+       } else {
+               warnx("Unsupported authentication challenge - `%s'",
+                   challenge);
+               goto cleanup_auth_url;
+       }
+
+       fprintf(ttyout, "Username for `%s': ", realm);
+       if (guser != NULL) {
+               (void)strlcpy(user, guser, sizeof(user));
+               fprintf(ttyout, "%s\n", user);
+       } else {
+               (void)fflush(ttyout);
+               if (getline(stdin, user, sizeof(user), &errormsg) < 0) {
+                       warnx("%s; can't authenticate", errormsg);
+                       goto cleanup_auth_url;
+               }
+       }
+       if (gpass != NULL)
+               pass = (char *)gpass;
+       else
+               pass = getpass("Password: ");
+
+       clen = strlen(user) + strlen(pass) + 2; /* user + ":" + pass + "\0" */
+       clear = (char *)ftp_malloc(clen);
+       (void)strlcpy(clear, user, clen);
+       (void)strlcat(clear, ":", clen);
+       (void)strlcat(clear, pass, clen);
+       if (gpass == NULL)
+               memset(pass, 0, strlen(pass));
+
+                                               /* scheme + " " + enc + "\0" */
+       rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
+       *response = (char *)ftp_malloc(rlen);
+       (void)strlcpy(*response, scheme, rlen);
+       len = strlcat(*response, " ", rlen);
+                       /* use  `clen - 1'  to not encode the trailing NUL */
+       base64_encode((unsigned char *)clear, clen - 1,
+           (unsigned char *)*response + len);
+       memset(clear, 0, clen);
+       rval = 0;
+
+ cleanup_auth_url:
+       FREEPTR(clear);
+       FREEPTR(realm);
+       return (rval);
+}
+
+/*
+ * Encode len bytes starting at clear using base64 encoding into encoded,
+ * which should be at least ((len + 2) * 4 / 3 + 1) in size.
+ */
+static void
+base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
+{
+       static const unsigned char enc[] =
+           "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+       unsigned char   *cp;
+       int      i;
+
+       cp = encoded;
+       for (i = 0; i < len; i += 3) {
+               *(cp++) = enc[((clear[i + 0] >> 2))];
+               *(cp++) = enc[((clear[i + 0] << 4) & 0x30)
+                           | ((clear[i + 1] >> 4) & 0x0f)];
+               *(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
+                           | ((clear[i + 2] >> 6) & 0x03)];
+               *(cp++) = enc[((clear[i + 2]     ) & 0x3f)];
+       }
+       *cp = '\0';
+       while (i-- > len)
+               *(--cp) = '=';
+}
+#endif
+
+/*
+ * Decode %xx escapes in given string, `in-place'.
+ */
+static void
+url_decode(char *url)
+{
+       unsigned char *p, *q;
+
+       if (EMPTYSTRING(url))
+               return;
+       p = q = (unsigned char *)url;
+
+#define        HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
+       while (*p) {
+               if (p[0] == '%'
+                   && p[1] && isxdigit((unsigned char)p[1])
+                   && p[2] && isxdigit((unsigned char)p[2])) {
+                       *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
+                       p+=3;
+               } else
+                       *q++ = *p++;
+       }
+       *q = '\0';
+}
+
+
+/*
+ * Parse URL of form:
+ *     <type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
+ * Returns -1 if a parse error occurred, otherwise 0.
+ * It's the caller's responsibility to url_decode() the returned
+ * user, pass and path.
+ *
+ * Sets type to url_t, each of the given char ** pointers to a
+ * malloc(3)ed strings of the relevant section, and port to
+ * the number given, or ftpport if ftp://, or httpport if http://.
+ *
+ * If <host> is surrounded by `[' and ']', it's parsed as an
+ * IPv6 address (as per RFC 2732).
+ *
+ * XXX: this is not totally RFC 1738 compliant; <path> will have the
+ * leading `/' unless it's an ftp:// URL, as this makes things easier
+ * for file:// and http:// URLs. ftp:// URLs have the `/' between the
+ * host and the URL-path removed, but any additional leading slashes
+ * in the URL-path are retained (because they imply that we should
+ * later do "CWD" with a null argument).
+ *
+ * Examples:
+ *      input URL                       output path
+ *      ---------                       -----------
+ *     "ftp://host"                    NULL
+ *     "http://host/"                  NULL
+ *     "file://host/dir/file"          "dir/file"
+ *     "ftp://host/"                   ""
+ *     "ftp://host//"                  NULL
+ *     "ftp://host//dir/file"          "/dir/file"
+ */
+static int
+parse_url(const char *url, const char *desc, url_t *type,
+               char **user, char **pass, char **host, char **port,
+               in_port_t *portnum, char **path)
+{
+       const char      *origurl;
+       char            *cp, *ep, *thost, *tport;
+       size_t           len;
+
+       if (url == NULL || desc == NULL || type == NULL || user == NULL
+           || pass == NULL || host == NULL || port == NULL || portnum == NULL
+           || path == NULL)
+               errx(1, "parse_url: invoked with NULL argument!");
+
+       origurl = url;
+       *type = UNKNOWN_URL_T;
+       *user = *pass = *host = *port = *path = NULL;
+       *portnum = 0;
+       tport = NULL;
+
+       if (STRNEQUAL(url, HTTP_URL)) {
+               url += sizeof(HTTP_URL) - 1;
+               *type = HTTP_URL_T;
+               *portnum = HTTP_PORT;
+               tport = httpport;
+       } else if (STRNEQUAL(url, FTP_URL)) {
+               url += sizeof(FTP_URL) - 1;
+               *type = FTP_URL_T;
+               *portnum = FTP_PORT;
+               tport = ftpport;
+       } else if (STRNEQUAL(url, FILE_URL)) {
+               url += sizeof(FILE_URL) - 1;
+               *type = FILE_URL_T;
+       } else {
+               warnx("Invalid %s `%s'", desc, url);
+ cleanup_parse_url:
+               FREEPTR(*user);
+               if (*pass != NULL)
+                       memset(*pass, 0, strlen(*pass));
+               FREEPTR(*pass);
+               FREEPTR(*host);
+               FREEPTR(*port);
+               FREEPTR(*path);
+               return (-1);
+       }
+
+       if (*url == '\0')
+               return (0);
+
+                       /* find [user[:pass]@]host[:port] */
+       ep = strchr(url, '/');
+       if (ep == NULL)
+               thost = ftp_strdup(url);
+       else {
+               len = ep - url;
+               thost = (char *)ftp_malloc(len + 1);
+               (void)strlcpy(thost, url, len + 1);
+               if (*type == FTP_URL_T) /* skip first / for ftp URLs */
+                       ep++;
+               *path = ftp_strdup(ep);
+       }
+
+       cp = strchr(thost, '@');        /* look for user[:pass]@ in URLs */
+       if (cp != NULL) {
+               if (*type == FTP_URL_T)
+                       anonftp = 0;    /* disable anonftp */
+               *user = thost;
+               *cp = '\0';
+               thost = ftp_strdup(cp + 1);
+               cp = strchr(*user, ':');
+               if (cp != NULL) {
+                       *cp = '\0';
+                       *pass = ftp_strdup(cp + 1);
+               }
+               url_decode(*user);
+               if (*pass)
+                       url_decode(*pass);
+       }
+
+#ifdef INET6
+                       /*
+                        * Check if thost is an encoded IPv6 address, as per
+                        * RFC 2732:
+                        *      `[' ipv6-address ']'
+                        */
+       if (*thost == '[') {
+               cp = thost + 1;
+               if ((ep = strchr(cp, ']')) == NULL ||
+                   (ep[1] != '\0' && ep[1] != ':')) {
+                       warnx("Invalid address `%s' in %s `%s'",
+                           thost, desc, origurl);
+                       goto cleanup_parse_url;
+               }
+               len = ep - cp;          /* change `[xyz]' -> `xyz' */
+               memmove(thost, thost + 1, len);
+               thost[len] = '\0';
+               if (! isipv6addr(thost)) {
+                       warnx("Invalid IPv6 address `%s' in %s `%s'",
+                           thost, desc, origurl);
+                       goto cleanup_parse_url;
+               }
+               cp = ep + 1;
+               if (*cp == ':')
+                       cp++;
+               else
+                       cp = NULL;
+       } else
+#endif /* INET6 */
+           if ((cp = strchr(thost, ':')) != NULL)
+               *cp++ =  '\0';
+       *host = thost;
+
+                       /* look for [:port] */
+       if (cp != NULL) {
+               long    nport;
+
+               nport = parseport(cp, -1);
+               if (nport == -1) {
+                       warnx("Unknown port `%s' in %s `%s'",
+                           cp, desc, origurl);
+                       goto cleanup_parse_url;
+               }
+               *portnum = nport;
+               tport = cp;
+       }
+
+       if (tport != NULL)
+               *port = ftp_strdup(tport);
+       if (*path == NULL)
+               *path = ftp_strdup("/");
+
+       DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
+           "path `%s'\n",
+           *user ? *user : "<null>", *pass ? *pass : "<null>",
+           *host ? *host : "<null>", *port ? *port : "<null>",
+           *portnum ? *portnum : -1, *path ? *path : "<null>");
+
+       return (0);
+}
+
+sigjmp_buf     httpabort;
+
+/*
+ * Retrieve URL, via a proxy if necessary, using HTTP.
+ * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
+ * http_proxy as appropriate.
+ * Supports HTTP redirects.
+ * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
+ * is still open (e.g, ftp xfer with trailing /)
+ */
+static int
+fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
+{
+       struct addrinfo         hints, *res, *res0 = NULL;
+       int                     error;
+       char                    hbuf[NI_MAXHOST];
+       volatile sigfunc        oldintr, oldintp;
+       volatile int            s;
+       struct stat             sb;
+       int                     ischunked, isproxy, rval, hcode;
+       size_t                  len;
+       static size_t           bufsize;
+       static char             *xferbuf;
+       const char              *cp, *token;
+       char                    *ep, *buf, *savefile;
+       char                    *auth, *location, *message;
+       char                    *user, *pass, *host, *port, *path, *decodedpath;
+       char                    *puser, *ppass, *useragent;
+       off_t                   hashbytes, rangestart, rangeend, entitylen;
+       int                      (*closefunc)(FILE *);
+       FILE                    *fin, *fout;
+       time_t                  mtime;
+       url_t                   urltype;
+       in_port_t               portnum;
+
+       oldintr = oldintp = NULL;
+       closefunc = NULL;
+       fin = fout = NULL;
+       s = -1;
+       buf = savefile = NULL;
+       auth = location = message = NULL;
+       ischunked = isproxy = hcode = 0;
+       rval = 1;
+       user = pass = host = path = decodedpath = puser = ppass = NULL;
+
+#ifdef __GNUC__                        /* shut up gcc warnings */
+       (void)&closefunc;
+       (void)&fin;
+       (void)&fout;
+       (void)&buf;
+       (void)&savefile;
+       (void)&rval;
+       (void)&isproxy;
+       (void)&hcode;
+       (void)&ischunked;
+       (void)&message;
+       (void)&location;
+       (void)&auth;
+       (void)&decodedpath;
+#endif
+
+       if (parse_url(url, "URL", &urltype, &user, &pass, &host, &port,
+           &portnum, &path) == -1)
+               goto cleanup_fetch_url;
+
+       if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
+           && strcasecmp(host, "localhost") != 0) {
+               warnx("No support for non local file URL `%s'", url);
+               goto cleanup_fetch_url;
+       }
+
+       if (EMPTYSTRING(path)) {
+               if (urltype == FTP_URL_T) {
+                       rval = fetch_ftp(url);
+                       goto cleanup_fetch_url;
+               }
+               if (urltype != HTTP_URL_T || outfile == NULL)  {
+                       warnx("Invalid URL (no file after host) `%s'", url);
+                       goto cleanup_fetch_url;
+               }
+       }
+
+       decodedpath = ftp_strdup(path);
+       url_decode(decodedpath);
+
+       if (outfile)
+               savefile = ftp_strdup(outfile);
+       else {
+               cp = strrchr(decodedpath, '/');         /* find savefile */
+               if (cp != NULL)
+                       savefile = ftp_strdup(cp + 1);
+               else
+                       savefile = ftp_strdup(decodedpath);
+       }
+       if (EMPTYSTRING(savefile)) {
+               if (urltype == FTP_URL_T) {
+                       rval = fetch_ftp(url);
+                       goto cleanup_fetch_url;
+               }
+               warnx("no file after directory (you must specify an "
+                   "output file) `%s'", url);
+               goto cleanup_fetch_url;
+       } else {
+               DPRINTF("savefile `%s'\n", savefile);
+       }
+
+       restart_point = 0;
+       filesize = -1;
+       rangestart = rangeend = entitylen = -1;
+       mtime = -1;
+       if (restartautofetch) {
+               if (strcmp(savefile, "-") != 0 && *savefile != '|' &&
+                   stat(savefile, &sb) == 0)
+                       restart_point = sb.st_size;
+       }
+       if (urltype == FILE_URL_T) {            /* file:// URLs */
+               direction = "copied";
+               fin = fopen(decodedpath, "r");
+               if (fin == NULL) {
+                       warn("Cannot open file `%s'", decodedpath);
+                       goto cleanup_fetch_url;
+               }
+               if (fstat(fileno(fin), &sb) == 0) {
+                       mtime = sb.st_mtime;
+                       filesize = sb.st_size;
+               }
+               if (restart_point) {
+                       if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
+                               warn("Can't lseek to restart `%s'",
+                                   decodedpath);
+                               goto cleanup_fetch_url;
+                       }
+               }
+               if (verbose) {
+                       fprintf(ttyout, "Copying %s", decodedpath);
+                       if (restart_point)
+                               fprintf(ttyout, " (restarting at " LLF ")",
+                                   (LLT)restart_point);
+                       fputs("\n", ttyout);
+               }
+       } else {                                /* ftp:// or http:// URLs */
+               char *leading;
+               int hasleading;
+
+               if (proxyenv == NULL) {
+                       if (urltype == HTTP_URL_T)
+                               proxyenv = getoptionvalue("http_proxy");
+                       else if (urltype == FTP_URL_T)
+                               proxyenv = getoptionvalue("ftp_proxy");
+               }
+               direction = "retrieved";
+               if (! EMPTYSTRING(proxyenv)) {                  /* use proxy */
+                       url_t purltype;
+                       char *phost, *ppath;
+                       char *pport, *no_proxy;
+
+                       isproxy = 1;
+
+                               /* check URL against list of no_proxied sites */
+                       no_proxy = getoptionvalue("no_proxy");
+                       if (! EMPTYSTRING(no_proxy)) {
+                               char *np, *np_copy, *np_iter;
+                               long np_port;
+                               size_t hlen, plen;
+
+                               np_iter = np_copy = ftp_strdup(no_proxy);
+                               hlen = strlen(host);
+                               while ((cp = strsep(&np_iter, " ,")) != NULL) {
+                                       if (*cp == '\0')
+                                               continue;
+                                       if ((np = strrchr(cp, ':')) != NULL) {
+                                               *np = '\0';
+                                               np_port =
+                                                   strtol(np + 1, &ep, 10);
+                                               if (*ep != '\0')
+                                                       continue;
+                                               if (np_port != portnum)
+                                                       continue;
+                                       }
+                                       plen = strlen(cp);
+                                       if (hlen < plen)
+                                               continue;
+                                       if (strncasecmp(host + hlen - plen,
+                                           cp, plen) == 0) {
+                                               isproxy = 0;
+                                               break;
+                                       }
+                               }
+                               FREEPTR(np_copy);
+                               if (isproxy == 0 && urltype == FTP_URL_T) {
+                                       rval = fetch_ftp(url);
+                                       goto cleanup_fetch_url;
+                               }
+                       }
+
+                       if (isproxy) {
+                               if (parse_url(proxyenv, "proxy URL", &purltype,
+                                   &puser, &ppass, &phost, &pport, &portnum,
+                                   &ppath) == -1)
+                                       goto cleanup_fetch_url;
+
+                               if ((purltype != HTTP_URL_T
+                                    && purltype != FTP_URL_T) ||
+                                   EMPTYSTRING(phost) ||
+                                   (! EMPTYSTRING(ppath)
+                                    && strcmp(ppath, "/") != 0)) {
+                                       warnx("Malformed proxy URL `%s'",
+                                           proxyenv);
+                                       FREEPTR(phost);
+                                       FREEPTR(pport);
+                                       FREEPTR(ppath);
+                                       goto cleanup_fetch_url;
+                               }
+                               if (isipv6addr(host) &&
+                                   strchr(host, '%') != NULL) {
+                                       warnx(
+"Scoped address notation `%s' disallowed via web proxy",
+                                           host);
+                                       FREEPTR(phost);
+                                       FREEPTR(pport);
+                                       FREEPTR(ppath);
+                                       goto cleanup_fetch_url;
+                               }
+
+                               FREEPTR(host);
+                               host = phost;
+                               FREEPTR(port);
+                               port = pport;
+                               FREEPTR(path);
+                               path = ftp_strdup(url);
+                               FREEPTR(ppath);
+                       }
+               } /* ! EMPTYSTRING(proxyenv) */
+
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_flags = 0;
+               hints.ai_family = family;
+               hints.ai_socktype = SOCK_STREAM;
+               hints.ai_protocol = 0;
+               error = getaddrinfo(host, NULL, &hints, &res0);
+               if (error) {
+                       warnx("%s: %s", host, gai_strerror(error));
+                       goto cleanup_fetch_url;
+               }
+               if (res0->ai_canonname)
+                       host = res0->ai_canonname;
+
+               s = -1;
+               for (res = res0; res; res = res->ai_next) {
+                       /*
+                        * see comment in hookup()
+                        */
+                       ai_unmapped(res);
+                       if (getnameinfo(res->ai_addr, res->ai_addrlen,
+                           hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+                               strlcpy(hbuf, "invalid", sizeof(hbuf));
+
+                       if (verbose && res0->ai_next) {
+                               fprintf(ttyout, "Trying %s...\n", hbuf);
+                       }
+
+                       ((struct sockaddr_in *)res->ai_addr)->sin_port =
+                           htons(portnum);
+                       s = socket(res->ai_family, SOCK_STREAM,
+                           res->ai_protocol);
+                       if (s < 0) {
+                               warn("Can't create socket");
+                               continue;
+                       }
+
+                       if (ftp_connect(s, res->ai_addr, res->ai_addrlen) < 0) {
+                               warn("Connect to address `%s'", hbuf);
+                               close(s);
+                               s = -1;
+                               continue;
+                       }
+
+                       /* success */
+                       break;
+               }
+
+               if (s < 0) {
+                       warn("Can't connect to %s", host);
+                       goto cleanup_fetch_url;
+               }
+
+               fin = fdopen(s, "r+");
+               /*
+                * Construct and send the request.
+                */
+               if (verbose)
+                       fprintf(ttyout, "Requesting %s\n", url);
+               leading = "  (";
+               hasleading = 0;
+               if (isproxy) {
+                       if (verbose) {
+                               fprintf(ttyout, "%svia %s:%s", leading,
+                                   host, port);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       fprintf(fin, "GET %s HTTP/1.0\r\n", path);
+                       if (flushcache)
+                               fprintf(fin, "Pragma: no-cache\r\n");
+               } else {
+                       fprintf(fin, "GET %s HTTP/1.1\r\n", path);
+                       if (strchr(host, ':')) {
+                               char *h, *p;
+
+                               /*
+                                * strip off IPv6 scope identifier, since it is
+                                * local to the node
+                                */
+                               h = ftp_strdup(host);
+                               if (isipv6addr(h) &&
+                                   (p = strchr(h, '%')) != NULL) {
+                                       *p = '\0';
+                               }
+                               fprintf(fin, "Host: [%s]", h);
+                               free(h);
+                       } else
+                               fprintf(fin, "Host: %s", host);
+                       if (portnum != HTTP_PORT)
+                               fprintf(fin, ":%u", portnum);
+                       fprintf(fin, "\r\n");
+                       fprintf(fin, "Accept: */*\r\n");
+                       fprintf(fin, "Connection: close\r\n");
+                       if (restart_point) {
+                               fputs(leading, ttyout);
+                               fprintf(fin, "Range: bytes=" LLF "-\r\n",
+                                   (LLT)restart_point);
+                               fprintf(ttyout, "restarting at " LLF,
+                                   (LLT)restart_point);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       if (flushcache)
+                               fprintf(fin, "Cache-Control: no-cache\r\n");
+               }
+               if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
+                       fprintf(fin, "User-Agent: %s\r\n", useragent);
+               } else {
+                       fprintf(fin, "User-Agent: %s/%s\r\n",
+                           FTP_PRODUCT, FTP_VERSION);
+               }
+               if (wwwauth) {
+                       if (verbose) {
+                               fprintf(ttyout, "%swith authorization",
+                                   leading);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       fprintf(fin, "Authorization: %s\r\n", wwwauth);
+               }
+               if (proxyauth) {
+                       if (verbose) {
+                               fprintf(ttyout,
+                                   "%swith proxy authorization", leading);
+                               leading = ", ";
+                               hasleading++;
+                       }
+                       fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
+               }
+               if (verbose && hasleading)
+                       fputs(")\n", ttyout);
+               fprintf(fin, "\r\n");
+               if (fflush(fin) == EOF) {
+                       warn("Writing HTTP request");
+                       goto cleanup_fetch_url;
+               }
+
+                               /* Read the response */
+               if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0)) == NULL) {
+                       warn("Receiving HTTP reply");
+                       goto cleanup_fetch_url;
+               }
+               while (len > 0 && (ISLWS(buf[len-1])))
+                       buf[--len] = '\0';
+               DPRINTF("received `%s'\n", buf);
+
+                               /* Determine HTTP response code */
+               cp = strchr(buf, ' ');
+               if (cp == NULL)
+                       goto improper;
+               else
+                       cp++;
+               hcode = strtol(cp, &ep, 10);
+               if (*ep != '\0' && !isspace((unsigned char)*ep))
+                       goto improper;
+               message = ftp_strdup(cp);
+
+                               /* Read the rest of the header. */
+               while (1) {
+                       FREEPTR(buf);
+                       if ((buf = fparseln(fin, &len, NULL, "\0\0\0", 0))
+                           == NULL) {
+                               warn("Receiving HTTP reply");
+                               goto cleanup_fetch_url;
+                       }
+                       while (len > 0 && (ISLWS(buf[len-1])))
+                               buf[--len] = '\0';
+                       if (len == 0)
+                               break;
+                       DPRINTF("received `%s'\n", buf);
+
+               /*
+                * Look for some headers
+                */
+
+                       cp = buf;
+
+                       if (match_token(&cp, "Content-Length:")) {
+                               filesize = STRTOLL(cp, &ep, 10);
+                               if (filesize < 0 || *ep != '\0')
+                                       goto improper;
+                               DPRINTF("parsed len as: " LLF "\n",
+                                   (LLT)filesize);
+
+                       } else if (match_token(&cp, "Content-Range:")) {
+                               if (! match_token(&cp, "bytes"))
+                                       goto improper;
+
+                               if (*cp == '*')
+                                       cp++;
+                               else {
+                                       rangestart = STRTOLL(cp, &ep, 10);
+                                       if (rangestart < 0 || *ep != '-')
+                                               goto improper;
+                                       cp = ep + 1;
+                                       rangeend = STRTOLL(cp, &ep, 10);
+                                       if (rangeend < 0 || rangeend < rangestart)
+                                               goto improper;
+                                       cp = ep;
+                               }
+                               if (*cp != '/')
+                                       goto improper;
+                               cp++;
+                               if (*cp == '*')
+                                       cp++;
+                               else {
+                                       entitylen = STRTOLL(cp, &ep, 10);
+                                       if (entitylen < 0)
+                                               goto improper;
+                                       cp = ep;
+                               }
+                               if (*cp != '\0')
+                                       goto improper;
+
+#ifndef NO_DEBUG
+                               if (ftp_debug) {
+                                       fprintf(ttyout, "parsed range as: ");
+                                       if (rangestart == -1)
+                                               fprintf(ttyout, "*");
+                                       else
+                                               fprintf(ttyout, LLF "-" LLF,
+                                                   (LLT)rangestart,
+                                                   (LLT)rangeend);
+                                       fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
+                               }
+#endif
+                               if (! restart_point) {
+                                       warnx(
+                                   "Received unexpected Content-Range header");
+                                       goto cleanup_fetch_url;
+                               }
+
+                       } else if (match_token(&cp, "Last-Modified:")) {
+                               struct tm parsed;
+                               char *t;
+
+                                                       /* RFC 1123 */
+                               if ((t = strptime(cp,
+                                               "%a, %d %b %Y %H:%M:%S GMT",
+                                               &parsed))
+                                                       /* RFC 850 */
+                                   || (t = strptime(cp,
+                                               "%a, %d-%b-%y %H:%M:%S GMT",
+                                               &parsed))
+                                                       /* asctime */
+                                   || (t = strptime(cp,
+                                               "%a, %b %d %H:%M:%S %Y",
+                                               &parsed))) {
+                                       parsed.tm_isdst = -1;
+                                       if (*t == '\0')
+                                               mtime = timegm(&parsed);
+#ifndef NO_DEBUG
+                                       if (ftp_debug && mtime != -1) {
+                                               fprintf(ttyout,
+                                                   "parsed date as: %s",
+                                                   ctime(&mtime));
+                                       }
+#endif
+                               }
+
+                       } else if (match_token(&cp, "Location:")) {
+                               location = ftp_strdup(cp);
+                               DPRINTF("parsed location as `%s'\n", cp);
+
+                       } else if (match_token(&cp, "Transfer-Encoding:")) {
+                               if (match_token(&cp, "binary")) {
+                                       warnx(
+                       "Bogus transfer encoding - `binary' (fetching anyway)");
+                                       continue;
+                               }
+                               if (! (token = match_token(&cp, "chunked"))) {
+                                       warnx(
+                                   "Unsupported transfer encoding - `%s'",
+                                           token);
+                                       goto cleanup_fetch_url;
+                               }
+                               ischunked++;
+                               DPRINTF("using chunked encoding\n");
+
+                       } else if (match_token(&cp, "Proxy-Authenticate:")
+                               || match_token(&cp, "WWW-Authenticate:")) {
+                               if (! (token = match_token(&cp, "Basic"))) {
+                                       DPRINTF(
+                               "skipping unknown auth scheme `%s'\n",
+                                                   token);
+                                       continue;
+                               }
+                               FREEPTR(auth);
+                               auth = ftp_strdup(token);
+                               DPRINTF("parsed auth as `%s'\n", cp);
+                       }
+
+               }
+                               /* finished parsing header */
+               FREEPTR(buf);
+
+               switch (hcode) {
+               case 200:
+                       break;
+               case 206:
+                       if (! restart_point) {
+                               warnx("Not expecting partial content header");
+                               goto cleanup_fetch_url;
+                       }
+                       break;
+               case 300:
+               case 301:
+               case 302:
+               case 303:
+               case 305:
+               case 307:
+                       if (EMPTYSTRING(location)) {
+                               warnx(
+                               "No redirection Location provided by server");
+                               goto cleanup_fetch_url;
+                       }
+                       if (redirect_loop++ > 5) {
+                               warnx("Too many redirections requested");
+                               goto cleanup_fetch_url;
+                       }
+                       if (hcode == 305) {
+                               if (verbose)
+                                       fprintf(ttyout, "Redirected via %s\n",
+                                           location);
+                               rval = fetch_url(url, location,
+                                   proxyauth, wwwauth);
+                       } else {
+                               if (verbose)
+                                       fprintf(ttyout, "Redirected to %s\n",
+                                           location);
+                               rval = go_fetch(location);
+                       }
+                       goto cleanup_fetch_url;
+#ifndef NO_AUTH
+               case 401:
+               case 407:
+                   {
+                       char **authp;
+                       char *auser, *apass;
+
+                       if (hcode == 401) {
+                               authp = &wwwauth;
+                               auser = user;
+                               apass = pass;
+                       } else {
+                               authp = &proxyauth;
+                               auser = puser;
+                               apass = ppass;
+                       }
+                       if (verbose || *authp == NULL ||
+                           auser == NULL || apass == NULL)
+                               fprintf(ttyout, "%s\n", message);
+                       if (EMPTYSTRING(auth)) {
+                               warnx(
+                           "No authentication challenge provided by server");
+                               goto cleanup_fetch_url;
+                       }
+                       if (*authp != NULL) {
+                               char reply[10];
+
+                               fprintf(ttyout,
+                                   "Authorization failed. Retry (y/n)? ");
+                               if (getline(stdin, reply, sizeof(reply), NULL)
+                                   < 0) {
+                                       goto cleanup_fetch_url;
+                               }
+                               if (tolower((unsigned char)reply[0]) != 'y')
+                                       goto cleanup_fetch_url;
+                               auser = NULL;
+                               apass = NULL;
+                       }
+                       if (auth_url(auth, authp, auser, apass) == 0) {
+                               rval = fetch_url(url, proxyenv,
+                                   proxyauth, wwwauth);
+                               memset(*authp, 0, strlen(*authp));
+                               FREEPTR(*authp);
+                       }
+                       goto cleanup_fetch_url;
+                   }
+#endif
+               default:
+                       if (message)
+                               warnx("Error retrieving file - `%s'", message);
+                       else
+                               warnx("Unknown error retrieving file");
+                       goto cleanup_fetch_url;
+               }
+       }               /* end of ftp:// or http:// specific setup */
+
+                       /* Open the output file. */
+       if (strcmp(savefile, "-") == 0) {
+               fout = stdout;
+       } else if (*savefile == '|') {
+               oldintp = xsignal(SIGPIPE, SIG_IGN);
+               fout = popen(savefile + 1, "w");
+               if (fout == NULL) {
+                       warn("Can't run `%s'", savefile + 1);
+                       goto cleanup_fetch_url;
+               }
+               closefunc = pclose;
+       } else {
+               if ((rangeend != -1 && rangeend <= restart_point) ||
+                   (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
+                       /* already done */
+                       if (verbose)
+                               fprintf(ttyout, "already done\n");
+                       rval = 0;
+                       goto cleanup_fetch_url;
+               }
+               if (restart_point && rangestart != -1) {
+                       if (entitylen != -1)
+                               filesize = entitylen;
+                       if (rangestart != restart_point) {
+                               warnx(
+                                   "Size of `%s' differs from save file `%s'",
+                                   url, savefile);
+                               goto cleanup_fetch_url;
+                       }
+                       fout = fopen(savefile, "a");
+               } else
+                       fout = fopen(savefile, "w");
+               if (fout == NULL) {
+                       warn("Can't open `%s'", savefile);
+                       goto cleanup_fetch_url;
+               }
+               closefunc = fclose;
+       }
+
+                       /* Trap signals */
+       if (sigsetjmp(httpabort, 1))
+               goto cleanup_fetch_url;
+       (void)xsignal(SIGQUIT, psummary);
+       oldintr = xsignal(SIGINT, aborthttp);
+
+       if (rcvbuf_size > bufsize) {
+               if (xferbuf)
+                       (void)free(xferbuf);
+               bufsize = rcvbuf_size;
+               xferbuf = ftp_malloc(bufsize);
+       }
+
+       bytes = 0;
+       hashbytes = mark;
+       progressmeter(-1);
+
+                       /* Finally, suck down the file. */
+       do {
+               long chunksize;
+
+               chunksize = 0;
+                                       /* read chunksize */
+               if (ischunked) {
+                       if (fgets(xferbuf, bufsize, fin) == NULL) {
+                               warnx("Unexpected EOF reading chunksize");
+                               goto cleanup_fetch_url;
+                       }
+                       chunksize = strtol(xferbuf, &ep, 16);
+
+                               /*
+                                * XXX: Work around bug in Apache 1.3.9 and
+                                *      1.3.11, which incorrectly put trailing
+                                *      space after the chunksize.
+                                */
+                       while (*ep == ' ')
+                               ep++;
+
+                       if (strcmp(ep, "\r\n") != 0) {
+                               warnx("Unexpected data following chunksize");
+                               goto cleanup_fetch_url;
+                       }
+                       DPRINTF("got chunksize of " LLF "\n", (LLT)chunksize);
+                       if (chunksize == 0)
+                               break;
+               }
+                                       /* transfer file or chunk */
+               while (1) {
+                       struct timeval then, now, td;
+                       off_t bufrem;
+
+                       if (rate_get)
+                               (void)gettimeofday(&then, NULL);
+                       bufrem = rate_get ? rate_get : bufsize;
+                       if (ischunked)
+                               bufrem = MIN(chunksize, bufrem);
+                       while (bufrem > 0) {
+                               len = fread(xferbuf, sizeof(char),
+                                   MIN(bufsize, bufrem), fin);
+                               if (len <= 0)
+                                       goto chunkdone;
+                               bytes += len;
+                               bufrem -= len;
+                               if (fwrite(xferbuf, sizeof(char), len, fout)
+                                   != len) {
+                                       warn("Writing `%s'", savefile);
+                                       goto cleanup_fetch_url;
+                               }
+                               if (hash && !progress) {
+                                       while (bytes >= hashbytes) {
+                                               (void)putc('#', ttyout);
+                                               hashbytes += mark;
+                                       }
+                                       (void)fflush(ttyout);
+                               }
+                               if (ischunked) {
+                                       chunksize -= len;
+                                       if (chunksize <= 0)
+                                               break;
+                               }
+                       }
+                       if (rate_get) {
+                               while (1) {
+                                       (void)gettimeofday(&now, NULL);
+                                       timersub(&now, &then, &td);
+                                       if (td.tv_sec > 0)
+                                               break;
+                                       usleep(1000000 - td.tv_usec);
+                               }
+                       }
+                       if (ischunked && chunksize <= 0)
+                               break;
+               }
+                                       /* read CRLF after chunk*/
+ chunkdone:
+               if (ischunked) {
+                       if (fgets(xferbuf, bufsize, fin) == NULL)
+                               break;
+                       if (strcmp(xferbuf, "\r\n") != 0) {
+                               warnx("Unexpected data following chunk");
+                               goto cleanup_fetch_url;
+                       }
+               }
+       } while (ischunked);
+       if (hash && !progress && bytes > 0) {
+               if (bytes < mark)
+                       (void)putc('#', ttyout);
+               (void)putc('\n', ttyout);
+       }
+       if (ferror(fin)) {
+               warn("Reading file");
+               goto cleanup_fetch_url;
+       }
+       progressmeter(1);
+       (void)fflush(fout);
+       if (closefunc == fclose && mtime != -1) {
+               struct timeval tval[2];
+
+               (void)gettimeofday(&tval[0], NULL);
+               tval[1].tv_sec = mtime;
+               tval[1].tv_usec = 0;
+               (*closefunc)(fout);
+               fout = NULL;
+
+               if (utimes(savefile, tval) == -1) {
+                       fprintf(ttyout,
+                           "Can't change modification time to %s",
+                           asctime(localtime(&mtime)));
+               }
+       }
+       if (bytes > 0)
+               ptransfer(0);
+       bytes = 0;
+
+       rval = 0;
+       goto cleanup_fetch_url;
+
+ improper:
+       warnx("Improper response from `%s'", host);
+
+ cleanup_fetch_url:
+       if (oldintr)
+               (void)xsignal(SIGINT, oldintr);
+       if (oldintp)
+               (void)xsignal(SIGPIPE, oldintp);
+       if (fin != NULL)
+               fclose(fin);
+       else if (s != -1)
+               close(s);
+       if (closefunc != NULL && fout != NULL)
+               (*closefunc)(fout);
+       if (res0)
+               freeaddrinfo(res0);
+       FREEPTR(savefile);
+       FREEPTR(user);
+       if (pass != NULL)
+               memset(pass, 0, strlen(pass));
+       FREEPTR(pass);
+       FREEPTR(host);
+       FREEPTR(port);
+       FREEPTR(path);
+       FREEPTR(decodedpath);
+       FREEPTR(puser);
+       if (ppass != NULL)
+               memset(ppass, 0, strlen(ppass));
+       FREEPTR(ppass);
+       FREEPTR(buf);
+       FREEPTR(auth);
+       FREEPTR(location);
+       FREEPTR(message);
+       return (rval);
+}
+
+/*
+ * Abort a HTTP retrieval
+ */
+void
+aborthttp(int notused)
+{
+       char msgbuf[100];
+       size_t len;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
+       write(fileno(ttyout), msgbuf, len);
+       siglongjmp(httpabort, 1);
+}
+
+/*
+ * Retrieve ftp URL or classic ftp argument using FTP.
+ * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
+ * is still open (e.g, ftp xfer with trailing /)
+ */
+static int
+fetch_ftp(const char *url)
+{
+       char            *cp, *xargv[5], rempath[MAXPATHLEN];
+       char            *host, *path, *dir, *file, *user, *pass;
+       char            *port;
+       int              dirhasglob, filehasglob, rval, type, xargc;
+       int              oanonftp, oautologin;
+       in_port_t        portnum;
+       url_t            urltype;
+
+       host = path = dir = file = user = pass = NULL;
+       port = NULL;
+       rval = 1;
+       type = TYPE_I;
+
+       if (STRNEQUAL(url, FTP_URL)) {
+               if ((parse_url(url, "URL", &urltype, &user, &pass,
+                   &host, &port, &portnum, &path) == -1) ||
+                   (user != NULL && *user == '\0') ||
+                   EMPTYSTRING(host)) {
+                       warnx("Invalid URL `%s'", url);
+                       goto cleanup_fetch_ftp;
+               }
+               /*
+                * Note: Don't url_decode(path) here.  We need to keep the
+                * distinction between "/" and "%2F" until later.
+                */
+
+                                       /* check for trailing ';type=[aid]' */
+               if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
+                       if (strcasecmp(cp, ";type=a") == 0)
+                               type = TYPE_A;
+                       else if (strcasecmp(cp, ";type=i") == 0)
+                               type = TYPE_I;
+                       else if (strcasecmp(cp, ";type=d") == 0) {
+                               warnx(
+                           "Directory listing via a URL is not supported");
+                               goto cleanup_fetch_ftp;
+                       } else {
+                               warnx("Invalid suffix `%s' in URL `%s'", cp,
+                                   url);
+                               goto cleanup_fetch_ftp;
+                       }
+                       *cp = 0;
+               }
+       } else {                        /* classic style `[user@]host:[file]' */
+               urltype = CLASSIC_URL_T;
+               host = ftp_strdup(url);
+               cp = strchr(host, '@');
+               if (cp != NULL) {
+                       *cp = '\0';
+                       user = host;
+                       anonftp = 0;    /* disable anonftp */
+                       host = ftp_strdup(cp + 1);
+               }
+               cp = strchr(host, ':');
+               if (cp != NULL) {
+                       *cp = '\0';
+                       path = ftp_strdup(cp + 1);
+               }
+       }
+       if (EMPTYSTRING(host))
+               goto cleanup_fetch_ftp;
+
+                       /* Extract the file and (if present) directory name. */
+       dir = path;
+       if (! EMPTYSTRING(dir)) {
+               /*
+                * If we are dealing with classic `[user@]host:[path]' syntax,
+                * then a path of the form `/file' (resulting from input of the
+                * form `host:/file') means that we should do "CWD /" before
+                * retrieving the file.  So we set dir="/" and file="file".
+                *
+                * But if we are dealing with URLs like `ftp://host/path' then
+                * a path of the form `/file' (resulting from a URL of the form
+                * `ftp://host//file') means that we should do `CWD ' (with an
+                * empty argument) before retrieving the file.  So we set
+                * dir="" and file="file".
+                *
+                * If the path does not contain / at all, we set dir=NULL.
+                * (We get a path without any slashes if we are dealing with
+                * classic `[user@]host:[file]' or URL `ftp://host/file'.)
+                *
+                * In all other cases, we set dir to a string that does not
+                * include the final '/' that separates the dir part from the
+                * file part of the path.  (This will be the empty string if
+                * and only if we are dealing with a path of the form `/file'
+                * resulting from an URL of the form `ftp://host//file'.)
+                */
+               cp = strrchr(dir, '/');
+               if (cp == dir && urltype == CLASSIC_URL_T) {
+                       file = cp + 1;
+                       dir = "/";
+               } else if (cp != NULL) {
+                       *cp++ = '\0';
+                       file = cp;
+               } else {
+                       file = dir;
+                       dir = NULL;
+               }
+       } else
+               dir = NULL;
+       if (urltype == FTP_URL_T && file != NULL) {
+               url_decode(file);
+               /* but still don't url_decode(dir) */
+       }
+       DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
+           "path `%s' dir `%s' file `%s'\n",
+           user ? user : "<null>", pass ? pass : "<null>",
+           host ? host : "<null>", port ? port : "<null>",
+           path ? path : "<null>",
+           dir ? dir : "<null>", file ? file : "<null>");
+
+       dirhasglob = filehasglob = 0;
+       if (doglob && urltype == CLASSIC_URL_T) {
+               if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
+                       dirhasglob = 1;
+               if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
+                       filehasglob = 1;
+       }
+
+                       /* Set up the connection */
+       oanonftp = anonftp;
+       if (connected)
+               disconnect(0, NULL);
+       anonftp = oanonftp;
+       xargv[0] = (char *)getprogname();       /* XXX discards const */
+       xargv[1] = host;
+       xargv[2] = NULL;
+       xargc = 2;
+       if (port) {
+               xargv[2] = port;
+               xargv[3] = NULL;
+               xargc = 3;
+       }
+       oautologin = autologin;
+               /* don't autologin in setpeer(), use ftp_login() below */
+       autologin = 0;
+       setpeer(xargc, xargv);
+       autologin = oautologin;
+       if ((connected == 0) ||
+           (connected == 1 && !ftp_login(host, user, pass))) {
+               warnx("Can't connect or login to host `%s'", host);
+               goto cleanup_fetch_ftp;
+       }
+
+       switch (type) {
+       case TYPE_A:
+               setascii(1, xargv);
+               break;
+       case TYPE_I:
+               setbinary(1, xargv);
+               break;
+       default:
+               errx(1, "fetch_ftp: unknown transfer type %d", type);
+       }
+
+               /*
+                * Change directories, if necessary.
+                *
+                * Note: don't use EMPTYSTRING(dir) below, because
+                * dir=="" means something different from dir==NULL.
+                */
+       if (dir != NULL && !dirhasglob) {
+               char *nextpart;
+
+               /*
+                * If we are dealing with a classic `[user@]host:[path]'
+                * (urltype is CLASSIC_URL_T) then we have a raw directory
+                * name (not encoded in any way) and we can change
+                * directories in one step.
+                *
+                * If we are dealing with an `ftp://host/path' URL
+                * (urltype is FTP_URL_T), then RFC 1738 says we need to
+                * send a separate CWD command for each unescaped "/"
+                * in the path, and we have to interpret %hex escaping
+                * *after* we find the slashes.  It's possible to get
+                * empty components here, (from multiple adjacent
+                * slashes in the path) and RFC 1738 says that we should
+                * still do `CWD ' (with a null argument) in such cases.
+                *
+                * Many ftp servers don't support `CWD ', so if there's an
+                * error performing that command, bail out with a descriptive
+                * message.
+                *
+                * Examples:
+                *
+                * host:                        dir="", urltype=CLASSIC_URL_T
+                *              logged in (to default directory)
+                * host:file                    dir=NULL, urltype=CLASSIC_URL_T
+                *              "RETR file"
+                * host:dir/                    dir="dir", urltype=CLASSIC_URL_T
+                *              "CWD dir", logged in
+                * ftp://host/                  dir="", urltype=FTP_URL_T
+                *              logged in (to default directory)
+                * ftp://host/dir/              dir="dir", urltype=FTP_URL_T
+                *              "CWD dir", logged in
+                * ftp://host/file              dir=NULL, urltype=FTP_URL_T
+                *              "RETR file"
+                * ftp://host//file             dir="", urltype=FTP_URL_T
+                *              "CWD ", "RETR file"
+                * host:/file                   dir="/", urltype=CLASSIC_URL_T
+                *              "CWD /", "RETR file"
+                * ftp://host///file            dir="/", urltype=FTP_URL_T
+                *              "CWD ", "CWD ", "RETR file"
+                * ftp://host/%2F/file          dir="%2F", urltype=FTP_URL_T
+                *              "CWD /", "RETR file"
+                * ftp://host/foo/file          dir="foo", urltype=FTP_URL_T
+                *              "CWD foo", "RETR file"
+                * ftp://host/foo/bar/file      dir="foo/bar"
+                *              "CWD foo", "CWD bar", "RETR file"
+                * ftp://host//foo/bar/file     dir="/foo/bar"
+                *              "CWD ", "CWD foo", "CWD bar", "RETR file"
+                * ftp://host/foo//bar/file     dir="foo//bar"
+                *              "CWD foo", "CWD ", "CWD bar", "RETR file"
+                * ftp://host/%2F/foo/bar/file  dir="%2F/foo/bar"
+                *              "CWD /", "CWD foo", "CWD bar", "RETR file"
+                * ftp://host/%2Ffoo/bar/file   dir="%2Ffoo/bar"
+                *              "CWD /foo", "CWD bar", "RETR file"
+                * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
+                *              "CWD /foo/bar", "RETR file"
+                * ftp://host/%2Ffoo%2Fbar%2Ffile       dir=NULL
+                *              "RETR /foo/bar/file"
+                *
+                * Note that we don't need `dir' after this point.
+                */
+               do {
+                       if (urltype == FTP_URL_T) {
+                               nextpart = strchr(dir, '/');
+                               if (nextpart) {
+                                       *nextpart = '\0';
+                                       nextpart++;
+                               }
+                               url_decode(dir);
+                       } else
+                               nextpart = NULL;
+                       DPRINTF("dir `%s', nextpart `%s'\n",
+                           dir ? dir : "<null>",
+                           nextpart ? nextpart : "<null>");
+                       if (urltype == FTP_URL_T || *dir != '\0') {
+                               xargv[0] = "cd";
+                               xargv[1] = dir;
+                               xargv[2] = NULL;
+                               dirchange = 0;
+                               cd(2, xargv);
+                               if (! dirchange) {
+                                       if (*dir == '\0' && code == 500)
+                                               fprintf(stderr,
+"\n"
+"ftp: The `CWD ' command (without a directory), which is required by\n"
+"     RFC 1738 to support the empty directory in the URL pathname (`//'),\n"
+"     conflicts with the server's conformance to RFC 959.\n"
+"     Try the same URL without the `//' in the URL pathname.\n"
+"\n");
+                                       goto cleanup_fetch_ftp;
+                               }
+                       }
+                       dir = nextpart;
+               } while (dir != NULL);
+       }
+
+       if (EMPTYSTRING(file)) {
+               rval = -1;
+               goto cleanup_fetch_ftp;
+       }
+
+       if (dirhasglob) {
+               (void)strlcpy(rempath, dir,     sizeof(rempath));
+               (void)strlcat(rempath, "/",     sizeof(rempath));
+               (void)strlcat(rempath, file,    sizeof(rempath));
+               file = rempath;
+       }
+
+                       /* Fetch the file(s). */
+       xargc = 2;
+       xargv[0] = "get";
+       xargv[1] = file;
+       xargv[2] = NULL;
+       if (dirhasglob || filehasglob) {
+               int ointeractive;
+
+               ointeractive = interactive;
+               interactive = 0;
+               if (restartautofetch)
+                       xargv[0] = "mreget";
+               else
+                       xargv[0] = "mget";
+               mget(xargc, xargv);
+               interactive = ointeractive;
+       } else {
+               if (outfile == NULL) {
+                       cp = strrchr(file, '/');        /* find savefile */
+                       if (cp != NULL)
+                               outfile = cp + 1;
+                       else
+                               outfile = file;
+               }
+               xargv[2] = (char *)outfile;
+               xargv[3] = NULL;
+               xargc++;
+               if (restartautofetch)
+                       reget(xargc, xargv);
+               else
+                       get(xargc, xargv);
+       }
+
+       if ((code / 100) == COMPLETE)
+               rval = 0;
+
+ cleanup_fetch_ftp:
+       FREEPTR(port);
+       FREEPTR(host);
+       FREEPTR(path);
+       FREEPTR(user);
+       if (pass)
+               memset(pass, 0, strlen(pass));
+       FREEPTR(pass);
+       return (rval);
+}
+
+/*
+ * Retrieve the given file to outfile.
+ * Supports arguments of the form:
+ *     "host:path", "ftp://host/path"  if $ftpproxy, call fetch_url() else
+ *                                     call fetch_ftp()
+ *     "http://host/path"              call fetch_url() to use HTTP
+ *     "file:///path"                  call fetch_url() to copy
+ *     "about:..."                     print a message
+ *
+ * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
+ * is still open (e.g, ftp xfer with trailing /)
+ */
+static int
+go_fetch(const char *url)
+{
+       char *proxy;
+
+#ifndef NO_ABOUT
+       /*
+        * Check for about:*
+        */
+       if (STRNEQUAL(url, ABOUT_URL)) {
+               url += sizeof(ABOUT_URL) -1;
+               if (strcasecmp(url, "ftp") == 0 ||
+                   strcasecmp(url, "tnftp") == 0) {
+                       fputs(
+"This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
+"for the NetBSD project.  Execute `man ftp' for more details.\n", ttyout);
+               } else if (strcasecmp(url, "lukem") == 0) {
+                       fputs(
+"Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
+"Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
+               } else if (strcasecmp(url, "netbsd") == 0) {
+                       fputs(
+"NetBSD is a freely available and redistributable UNIX-like operating system.\n"
+"For more information, see http://www.NetBSD.org/\n", ttyout);
+               } else if (strcasecmp(url, "version") == 0) {
+                       fprintf(ttyout, "Version: %s %s%s\n",
+                           FTP_PRODUCT, FTP_VERSION,
+#ifdef INET6
+                           ""
+#else
+                           " (-IPv6)"
+#endif
+                       );
+               } else {
+                       fprintf(ttyout, "`%s' is an interesting topic.\n", url);
+               }
+               fputs("\n", ttyout);
+               return (0);
+       }
+#endif
+
+       /*
+        * Check for file:// and http:// URLs.
+        */
+       if (STRNEQUAL(url, HTTP_URL) || STRNEQUAL(url, FILE_URL))
+               return (fetch_url(url, NULL, NULL, NULL));
+
+       /*
+        * Try FTP URL-style and host:file arguments next.
+        * If ftpproxy is set with an FTP URL, use fetch_url()
+        * Othewise, use fetch_ftp().
+        */
+       proxy = getoptionvalue("ftp_proxy");
+       if (!EMPTYSTRING(proxy) && STRNEQUAL(url, FTP_URL))
+               return (fetch_url(url, NULL, NULL, NULL));
+
+       return (fetch_ftp(url));
+}
+
+/*
+ * Retrieve multiple files from the command line,
+ * calling go_fetch() for each file.
+ *
+ * If an ftp path has a trailing "/", the path will be cd-ed into and
+ * the connection remains open, and the function will return -1
+ * (to indicate the connection is alive).
+ * If an error occurs the return value will be the offset+1 in
+ * argv[] of the file that caused a problem (i.e, argv[x]
+ * returns x+1)
+ * Otherwise, 0 is returned if all files retrieved successfully.
+ */
+int
+auto_fetch(int argc, char *argv[])
+{
+       volatile int    argpos, rval;
+
+       argpos = rval = 0;
+
+       if (sigsetjmp(toplevel, 1)) {
+               if (connected)
+                       disconnect(0, NULL);
+               if (rval > 0)
+                       rval = argpos + 1;
+               return (rval);
+       }
+       (void)xsignal(SIGINT, intr);
+       (void)xsignal(SIGPIPE, lostpeer);
+
+       /*
+        * Loop through as long as there's files to fetch.
+        */
+       for (; (rval == 0) && (argpos < argc); argpos++) {
+               if (strchr(argv[argpos], ':') == NULL)
+                       break;
+               redirect_loop = 0;
+               if (!anonftp)
+                       anonftp = 2;    /* Handle "automatic" transfers. */
+               rval = go_fetch(argv[argpos]);
+               if (outfile != NULL && strcmp(outfile, "-") != 0
+                   && outfile[0] != '|')
+                       outfile = NULL;
+               if (rval > 0)
+                       rval = argpos + 1;
+       }
+
+       if (connected && rval != -1)
+               disconnect(0, NULL);
+       return (rval);
+}
+
+
+/*
+ * Upload multiple files from the command line.
+ *
+ * If an error occurs the return value will be the offset+1 in
+ * argv[] of the file that caused a problem (i.e, argv[x]
+ * returns x+1)
+ * Otherwise, 0 is returned if all files uploaded successfully.
+ */
+int
+auto_put(int argc, char **argv, const char *uploadserver)
+{
+       char    *uargv[4], *path, *pathsep;
+       int      uargc, rval, argpos;
+       size_t   len;
+
+       uargc = 0;
+       uargv[uargc++] = "mput";
+       uargv[uargc++] = argv[0];
+       uargv[2] = uargv[3] = NULL;
+       pathsep = NULL;
+       rval = 1;
+
+       DPRINTF("auto_put: target `%s'\n", uploadserver);
+
+       path = ftp_strdup(uploadserver);
+       len = strlen(path);
+       if (path[len - 1] != '/' && path[len - 1] != ':') {
+                       /*
+                        * make sure we always pass a directory to auto_fetch
+                        */
+               if (argc > 1) {         /* more than one file to upload */
+                       len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
+                       free(path);
+                       path = (char *)ftp_malloc(len);
+                       (void)strlcpy(path, uploadserver, len);
+                       (void)strlcat(path, "/", len);
+               } else {                /* single file to upload */
+                       uargv[0] = "put";
+                       pathsep = strrchr(path, '/');
+                       if (pathsep == NULL) {
+                               pathsep = strrchr(path, ':');
+                               if (pathsep == NULL) {
+                                       warnx("Invalid URL `%s'", path);
+                                       goto cleanup_auto_put;
+                               }
+                               pathsep++;
+                               uargv[2] = ftp_strdup(pathsep);
+                               pathsep[0] = '/';
+                       } else
+                               uargv[2] = ftp_strdup(pathsep + 1);
+                       pathsep[1] = '\0';
+                       uargc++;
+               }
+       }
+       DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
+           path, uargv[2] ? uargv[2] : "<null>");
+
+                       /* connect and cwd */
+       rval = auto_fetch(1, &path);
+       if(rval >= 0)
+               goto cleanup_auto_put;
+
+       rval = 0;
+
+                       /* target filename provided; upload 1 file */
+                       /* XXX : is this the best way? */
+       if (uargc == 3) {
+               uargv[1] = argv[0];
+               put(uargc, uargv);
+               if ((code / 100) != COMPLETE)
+                       rval = 1;
+       } else {        /* otherwise a target dir: upload all files to it */
+               for(argpos = 0; argv[argpos] != NULL; argpos++) {
+                       uargv[1] = argv[argpos];
+                       mput(uargc, uargv);
+                       if ((code / 100) != COMPLETE) {
+                               rval = argpos + 1;
+                               break;
+                       }
+               }
+       }
+
+ cleanup_auto_put:
+       free(path);
+       FREEPTR(uargv[2]);
+       return (rval);
+}
diff --git a/contrib/tnftp/ftp.1 b/contrib/tnftp/ftp.1
new file mode 100644 (file)
index 0000000..f1cffbe
--- /dev/null
@@ -0,0 +1,2424 @@
+.\"    $NetBSD: ftp.1,v 1.114 2006/10/27 01:29:17 lukem Exp $
+.\"
+.\" Copyright (c) 1996-2006 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Luke Mewburn.
+.\"
+.\" 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 NetBSD
+.\"    Foundation, Inc. and its contributors.
+.\" 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+.\"
+.\"
+.\" Copyright (c) 1985, 1989, 1990, 1993
+.\"    The 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. 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.
+.\"
+.\"    @(#)ftp.1       8.3 (Berkeley) 10/9/94
+.\"
+.Dd October 27, 2006
+.Dt FTP 1
+.Os
+.Sh NAME
+.Nm ftp
+.Nd
+Internet file transfer program
+.Sh SYNOPSIS
+.Nm
+.Op Fl 46AadefginpRtvV
+.Bk -words
+.Op Fl N Ar netrc
+.Ek
+.Bk -words
+.Op Fl o Ar output
+.Ek
+.Bk -words
+.Op Fl P Ar port
+.Ek
+.Bk -words
+.Op Fl q Ar quittime
+.Ek
+.Bk -words
+.Op Fl r Ar retry
+.Ek
+.Bk -words
+.\" [-T dir,max[,inc]]
+.Oo
+.Fl T Xo
+.Sm off
+.Ar dir ,
+.Ar max
+.Op , Ar inc
+.Sm on
+.Xc
+.Oc
+.Ek
+.Bk -words
+.\" [[user@]host [port]]
+.Oo
+.Oo Ar user Ns Li \&@ Oc Ns Ar host
+.Op Ar port
+.Oc
+.Ek
+.Bk -words
+.\" [[user@]host:[path][/]]
+.Sm off
+.Oo
+.Op Ar user Li \&@
+.Ar host Li \&:
+.Op Ar path
+.Op Li /
+.Oc
+.Sm on
+.Ek
+.Bk -words
+.\" [file:///path]
+.Sm off
+.Oo
+.Li file:/// Ar path
+.Oc
+.Sm on
+.Ek
+.Bk -words
+.\" [ftp://[user[:password]@]host[:port]/path[/]]
+.Sm off
+.Oo
+.Li ftp://
+.Oo Ar user
+.Op Li \&: Ar password
+.Li \&@ Oc
+.Ar host Oo Li \&: Ar port Oc
+.Li / Ar path
+.Op Li /
+.Op Li ;type= Ar X
+.Oc
+.Sm on
+.Ek
+.Bk -words
+.\" [http://[user[:password]@]host[:port]/path]
+.Sm off
+.Oo
+.Li http://
+.Oo Ar user
+.Op Li \&: Ar password
+.Li \&@ Oc
+.Ar host Oo Li \&: Ar port Oc
+.Li / Ar path
+.Oc
+.Sm on
+.Ek
+.Op Ar \&.\&.\&.
+.Nm
+.Bk -words
+.Fl u Ar URL Ar file
+.Ek
+.Op Ar \&.\&.\&.
+.Sh DESCRIPTION
+.Nm
+is the user interface to the Internet standard File Transfer Protocol.
+The program allows a user to transfer files to and from a
+remote network site.
+.Pp
+The last five arguments will fetch a file using the
+.Tn FTP
+or
+.Tn HTTP
+protocols, or by direct copying, into the current directory.
+This is ideal for scripts.
+Refer to
+.Sx AUTO-FETCHING FILES
+below for more information.
+.Pp
+Options may be specified at the command line, or to the
+command interpreter.
+.Bl -tag -width "port   "
+.It Fl 4
+Forces
+.Nm
+to only use IPv4 addresses.
+.It Fl 6
+Forces
+.Nm
+to only use IPv6 addresses.
+.It Fl A
+Force active mode ftp.
+By default,
+.Nm
+will try to use passive mode ftp and fall back to active mode
+if passive is not supported by the server.
+This option causes
+.Nm
+to always use an active connection.
+It is only useful for connecting to very old servers that do not
+implement passive mode properly.
+.It Fl a
+Causes
+.Nm
+to bypass normal login procedure, and use an anonymous login instead.
+.It Fl d
+Enables debugging.
+.It Fl e
+Disables command line editing.
+This is useful for Emacs ange-ftp mode.
+.It Fl f
+Forces a cache reload for transfers that go through the
+.Tn FTP
+or
+.Tn HTTP
+proxies.
+.It Fl g
+Disables file name globbing.
+.It Fl i
+Turns off interactive prompting during
+multiple file transfers.
+.It Fl n
+Restrains
+.Nm
+from attempting
+.Dq auto-login
+upon initial connection for non auto-fetch transfers.
+If auto-login is enabled,
+.Nm
+will check the
+.Pa .netrc
+(see below) file in the user's home directory for an entry describing
+an account on the remote machine.
+If no entry exists,
+.Nm
+will prompt for the remote machine login name (default is the user
+identity on the local machine), and, if necessary, prompt for a password
+and an account with which to login.
+To override the auto-login for auto-fetch transfers, specify the
+username (and optionally, password) as appropriate.
+.It Fl N Ar netrc
+Use
+.Ar netrc
+instead of
+.Pa ~/.netrc .
+Refer to
+.Sx THE .netrc FILE
+for more information.
+.It Fl o Ar output
+When auto-fetching files, save the contents in
+.Ar output .
+.Ar output
+is parsed according to the
+.Sx FILE NAMING CONVENTIONS
+below.
+If
+.Ar output
+is not
+.Sq -
+or doesn't start with
+.Sq \&| ,
+then only the first file specified will be retrieved into
+.Ar output ;
+all other files will be retrieved into the basename of their
+remote name.
+.It Fl p
+Enable passive mode operation for use behind connection filtering firewalls.
+This option has been deprecated as
+.Nm
+now tries to use passive mode by default, falling back to active mode
+if the server does not support passive connections.
+.It Fl P Ar port
+Sets the port number to
+.Ar port .
+.It Fl r Ar wait
+Retry the connection attempt if it failed, pausing for
+.Ar wait
+seconds.
+.It Fl q Ar quittime
+Quit if the connection has stalled for
+.Ar quittime
+seconds.
+.It Fl R
+Restart all non-proxied auto-fetches.
+.It Fl t
+Enables packet tracing.
+.It Xo
+.Fl T
+.Sm off
+.Ar direction ,
+.Ar maximum
+.Op , Ar increment
+.Sm on
+.Xc
+Set the maximum transfer rate for
+.Ar direction
+to
+.Ar maximum
+bytes/second,
+and if specified, the increment to
+.Ar increment
+bytes/second.
+Refer to
+.Ic rate
+for more information.
+.It Fl u Ar URL file Op \&.\&.\&.
+Upload files on the command line to
+.Ar URL
+where
+.Ar URL
+is one of the ftp URL types as supported by auto-fetch
+(with an optional target filename for single file uploads), and
+.Ar file
+is one or more local files to be uploaded.
+.It Fl v
+Enable
+.Ic verbose
+and
+.Ic progress .
+This is the default if output is to a terminal (and in the case of
+.Ic progress ,
+.Nm
+is the foreground process).
+Forces
+.Nm
+to show all responses from the remote server, as well
+as report on data transfer statistics.
+.It Fl V
+Disable
+.Ic verbose
+and
+.Ic progress ,
+overriding the default of enabled when output is to a terminal.
+.El
+.Pp
+The client host with which
+.Nm
+is to communicate may be specified on the command line.
+If this is done,
+.Nm
+will immediately attempt to establish a connection to an
+.Tn FTP
+server on that host; otherwise,
+.Nm
+will enter its command interpreter and await instructions
+from the user.
+When
+.Nm
+is awaiting commands from the user the prompt
+.Ql ftp\*[Gt]
+is provided to the user.
+The following commands are recognized
+by
+.Nm ftp  :
+.Bl -tag -width Fl
+.It Ic \&! Op Ar command Op Ar args
+Invoke an interactive shell on the local machine.
+If there are arguments, the first is taken to be a command to execute
+directly, with the rest of the arguments as its arguments.
+.It Ic \&$ Ar macro-name Op Ar args
+Execute the macro
+.Ar macro-name
+that was defined with the
+.Ic macdef
+command.
+Arguments are passed to the macro unglobbed.
+.It Ic account Op Ar passwd
+Supply a supplemental password required by a remote system for access
+to resources once a login has been successfully completed.
+If no argument is included, the user will be prompted for an account
+password in a non-echoing input mode.
+.It Ic append Ar local-file Op Ar remote-file
+Append a local file to a file on the remote machine.
+If
+.Ar remote-file
+is left unspecified, the local file name is used in naming the
+remote file after being altered by any
+.Ic ntrans
+or
+.Ic nmap
+setting.
+File transfer uses the current settings for
+.Ic type  ,
+.Ic format ,
+.Ic mode  ,
+and
+.Ic structure .
+.It Ic ascii
+Set the file transfer
+.Ic type
+to network
+.Tn ASCII .
+This is the default type.
+.It Ic bell
+Arrange that a bell be sounded after each file transfer
+command is completed.
+.It Ic binary
+Set the file transfer
+.Ic type
+to support binary image transfer.
+.It Ic bye
+Terminate the
+.Tn FTP
+session with the remote server
+and exit
+.Nm ftp .
+An end of file will also terminate the session and exit.
+.It Ic case
+Toggle remote computer file name case mapping during
+.Ic get ,
+.Ic mget
+and
+.Ic mput
+commands.
+When
+.Ic case
+is on (default is off), remote computer file names with all letters in
+upper case are written in the local directory with the letters mapped
+to lower case.
+.It Ic \&cd Ar remote-directory
+Change the working directory on the remote machine
+to
+.Ar remote-directory .
+.It Ic cdup
+Change the remote machine working directory to the parent of the
+current remote machine working directory.
+.It Ic chmod Ar mode remote-file
+Change the permission modes of the file
+.Ar remote-file
+on the remote
+system to
+.Ar mode .
+.It Ic close
+Terminate the
+.Tn FTP
+session with the remote server, and
+return to the command interpreter.
+Any defined macros are erased.
+.It Ic \&cr
+Toggle carriage return stripping during
+ascii type file retrieval.
+Records are denoted by a carriage return/linefeed sequence
+during ascii type file transfer.
+When
+.Ic \&cr
+is on (the default), carriage returns are stripped from this
+sequence to conform with the
+.Ux
+single linefeed record
+delimiter.
+Records on
+.Pf non\- Ns Ux
+remote systems may contain single linefeeds;
+when an ascii type transfer is made, these linefeeds may be
+distinguished from a record delimiter only when
+.Ic \&cr
+is off.
+.It Ic ftp_debug Op Ar ftp_debug-value
+Toggle debugging mode.
+If an optional
+.Ar ftp_debug-value
+is specified it is used to set the debugging level.
+When debugging is on,
+.Nm
+prints each command sent to the remote machine, preceded
+by the string
+.Ql \-\-\*[Gt]
+.It Ic delete Ar remote-file
+Delete the file
+.Ar remote-file
+on the remote machine.
+.It Ic dir Op Ar remote-path Op Ar local-file
+Print a listing of the contents of a
+directory on the remote machine.
+The listing includes any system-dependent information that the server
+chooses to include; for example, most
+.Ux
+systems will produce
+output from the command
+.Ql ls \-l .
+If
+.Ar remote-path
+is left unspecified, the current working directory is used.
+If interactive prompting is on,
+.Nm
+will prompt the user to verify that the last argument is indeed the
+target local file for receiving
+.Ic dir
+output.
+If no local file is specified, or if
+.Ar local-file
+is
+.Sq Fl ,
+the output is sent to the terminal.
+.It Ic disconnect
+A synonym for
+.Ic close .
+.It Ic edit
+Toggle command line editing, and context sensitive command and file
+completion.
+This is automatically enabled if input is from a terminal, and
+disabled otherwise.
+.It Ic epsv4
+Toggle the use of the extended
+.Dv EPSV
+and
+.Dv EPRT
+commands on IPv4 connections; first try
+.Dv EPSV /
+.Dv EPRT ,
+and then
+.Dv PASV /
+.Dv PORT .
+This is enabled by default.
+If an extended command fails then this option will be temporarily
+disabled for the duration of the current connection, or until
+.Ic epsv4
+is executed again.
+.It Ic exit
+A synonym for
+.Ic bye .
+.It Ic features
+Display what features the remote server supports (using the
+.Dv FEAT
+command).
+.It Ic fget Ar localfile
+Retrieve the files listed in
+.Ar localfile ,
+which has one line per filename.
+.It Ic form Ar format
+Set the file transfer
+.Ic form
+to
+.Ar format .
+The default (and only supported)
+format is
+.Dq non-print .
+.It Ic ftp Ar host Op Ar port
+A synonym for
+.Ic open .
+.It Ic gate Op Ar host Op Ar port
+Toggle gate-ftp mode, which used to connect through the
+TIS FWTK and Gauntlet ftp proxies.
+This will not be permitted if the gate-ftp server hasn't been set
+(either explicitly by the user, or from the
+.Ev FTPSERVER
+environment variable).
+If
+.Ar host
+is given,
+then gate-ftp mode will be enabled, and the gate-ftp server will be set to
+.Ar host .
+If
+.Ar port
+is also given, that will be used as the port to connect to on the
+gate-ftp server.
+.It Ic get Ar remote-file Op Ar local-file
+Retrieve the
+.Ar remote-file
+and store it on the local machine.
+If the local
+file name is not specified, it is given the same
+name it has on the remote machine, subject to
+alteration by the current
+.Ic case  ,
+.Ic ntrans ,
+and
+.Ic nmap
+settings.
+The current settings for
+.Ic type  ,
+.Ic form ,
+.Ic mode  ,
+and
+.Ic structure
+are used while transferring the file.
+.It Ic glob
+Toggle filename expansion for
+.Ic mdelete  ,
+.Ic mget ,
+.Ic mput ,
+and
+.Ic mreget .
+If globbing is turned off with
+.Ic glob  ,
+the file name arguments
+are taken literally and not expanded.
+Globbing for
+.Ic mput
+is done as in
+.Xr csh 1 .
+For
+.Ic mdelete ,
+.Ic mget ,
+and
+.Ic mreget ,
+each remote file name is expanded
+separately on the remote machine and the lists are not merged.
+Expansion of a directory name is likely to be
+different from expansion of the name of an ordinary file:
+the exact result depends on the foreign operating system and ftp server,
+and can be previewed by doing
+.Ql mls remote-files \-
+Note:
+.Ic mget ,
+.Ic mput
+and
+.Ic mreget
+are not meant to transfer
+entire directory subtrees of files.
+That can be done by
+transferring a
+.Xr tar 1
+archive of the subtree (in binary mode).
+.It Ic hash Op Ar size
+Toggle hash-sign
+.Pq Sq #
+printing for each data block transferred.
+The size of a data block defaults to 1024 bytes.
+This can be changed by specifying
+.Ar size
+in bytes.
+Enabling
+.Ic hash
+disables
+.Ic progress .
+.It Ic help Op Ar command
+Print an informative message about the meaning of
+.Ar command .
+If no argument is given,
+.Nm
+prints a list of the known commands.
+.It Ic idle Op Ar seconds
+Set the inactivity timer on the remote server to
+.Ar seconds
+seconds.
+If
+.Ar seconds
+is omitted, the current inactivity timer is printed.
+.It Ic image
+A synonym for
+.Ic binary .
+.It Ic lcd Op Ar directory
+Change the working directory on the local machine.
+If
+no
+.Ar directory
+is specified, the user's home directory is used.
+.It Ic less Ar file
+A synonym for
+.Ic page .
+.It Ic lpage Ar local-file
+Display
+.Ar local-file
+with the program specified by the
+.Ic "set pager"
+option.
+.It Ic lpwd
+Print the working directory on the local machine.
+.It Ic \&ls Op Ar remote-path Op Ar local-file
+A synonym for
+.Ic dir .
+.It Ic macdef Ar macro-name
+Define a macro.
+Subsequent lines are stored as the macro
+.Ar macro-name  ;
+a null line (consecutive newline characters in a file or carriage
+returns from the terminal) terminates macro input mode.
+There is a limit of 16 macros and 4096 total characters in all
+defined macros.
+Macro names can be a maximum of 8 characters.
+Macros are only applicable to the current session they are
+defined within (or if defined outside a session, to the session
+invoked with the next
+.Ic open
+command), and remain defined until a
+.Ic close
+command is executed.
+To invoke a macro, use the
+.Ic $
+command (see above).
+.Pp
+The macro processor interprets
+.Sq $
+and
+.Sq \e
+as special characters.
+A
+.Sq $
+followed by a number (or numbers) is replaced by the
+corresponding argument on the macro invocation command line.
+A
+.Sq $
+followed by an
+.Sq i
+signals the macro processor that the executing macro is to be
+looped.
+On the first pass
+.Dq $i
+is replaced by the first argument on the macro invocation command
+line, on the second pass it is replaced by the second argument,
+and so on.
+A
+.Sq \e
+followed by any character is replaced by that character.
+Use the
+.Sq \e
+to prevent special treatment of the
+.Sq $ .
+.It Ic mdelete Op Ar remote-files
+Delete the
+.Ar remote-files
+on the remote machine.
+.It Ic mdir Ar remote-files local-file
+Like
+.Ic dir  ,
+except multiple remote files may be specified.
+If interactive prompting is on,
+.Nm
+will prompt the user to verify that the last argument is indeed the
+target local file for receiving
+.Ic mdir
+output.
+.It Ic mget Ar remote-files
+Expand the
+.Ar remote-files
+on the remote machine
+and do a
+.Ic get
+for each file name thus produced.
+See
+.Ic glob
+for details on the filename expansion.
+Resulting file names will then be processed according to
+.Ic case  ,
+.Ic ntrans ,
+and
+.Ic nmap
+settings.
+Files are transferred into the local working directory,
+which can be changed with
+.Ql lcd directory ;
+new local directories can be created with
+.Ql "\&! mkdir directory" .
+.It Ic mkdir Ar directory-name
+Make a directory on the remote machine.
+.It Ic mls Ar remote-files local-file
+Like
+.Ic ls  ,
+except multiple remote files may be specified,
+and the
+.Ar local-file
+must be specified.
+If interactive prompting is on,
+.Nm
+will prompt the user to verify that the last argument is indeed the
+target local file for receiving
+.Ic mls
+output.
+.It Ic mlsd Op Ar remote-path
+Display the contents of
+.Ar remote-path
+(which should default to the current directory if not given)
+in a machine-parsable form, using
+.Dv MLSD .
+The format of display can be changed with
+.Sq "remopts mlst ..." .
+.It Ic mlst Op Ar remote-path
+Display the details about
+.Ar remote-path
+(which should default to the current directory if not given)
+in a machine-parsable form, using
+.Dv MLST .
+The format of display can be changed with
+.Sq "remopts mlst ..." .
+.It Ic mode Ar mode-name
+Set the file transfer
+.Ic mode
+to
+.Ar mode-name .
+The default (and only supported)
+mode is
+.Dq stream .
+.It Ic modtime Ar remote-file
+Show the last modification time of the file on the remote machine.
+.It Ic more Ar file
+A synonym for
+.Ic page .
+.It Ic mput Ar local-files
+Expand wild cards in the list of local files given as arguments
+and do a
+.Ic put
+for each file in the resulting list.
+See
+.Ic glob
+for details of filename expansion.
+Resulting file names will then be processed according to
+.Ic ntrans
+and
+.Ic nmap
+settings.
+.It Ic mreget Ar remote-files
+As per
+.Ic mget ,
+but performs a
+.Ic reget
+instead of
+.Ic get .
+.It Ic msend Ar local-files
+A synonym for
+.Ic mput .
+.It Ic newer Ar remote-file Op Ar local-file
+Get the file only if the modification time of the remote file is more
+recent that the file on the current system.
+If the file does not
+exist on the current system, the remote file is considered
+.Ic newer .
+Otherwise, this command is identical to
+.Ar get .
+.It Ic nlist Op Ar remote-path Op Ar local-file
+A synonym for
+.Ic ls .
+.It Ic nmap Op Ar inpattern outpattern
+Set or unset the filename mapping mechanism.
+If no arguments are specified, the filename mapping mechanism is unset.
+If arguments are specified, remote filenames are mapped during
+.Ic mput
+commands and
+.Ic put
+commands issued without a specified remote target filename.
+If arguments are specified, local filenames are mapped during
+.Ic mget
+commands and
+.Ic get
+commands issued without a specified local target filename.
+This command is useful when connecting to a
+.No non\- Ns Ux
+remote computer
+with different file naming conventions or practices.
+The mapping follows the pattern set by
+.Ar inpattern
+and
+.Ar outpattern .
+.Op Ar Inpattern
+is a template for incoming filenames (which may have already been
+processed according to the
+.Ic ntrans
+and
+.Ic case
+settings).
+Variable templating is accomplished by including the
+sequences
+.Dq $1 ,
+.Dq $2 ,
+\&...
+.Dq $9
+in
+.Ar inpattern .
+Use
+.Sq \e
+to prevent this special treatment of the
+.Sq $
+character.
+All other characters are treated literally, and are used to determine the
+.Ic nmap
+.Op Ar inpattern
+variable values.
+For example, given
+.Ar inpattern
+$1.$2 and the remote file name "mydata.data", $1 would have the value
+"mydata", and $2 would have the value "data".
+The
+.Ar outpattern
+determines the resulting mapped filename.
+The sequences
+.Dq $1 ,
+.Dq $2 ,
+\&...
+.Dq $9
+are replaced by any value resulting from the
+.Ar inpattern
+template.
+The sequence
+.Dq $0
+is replaced by the original filename.
+Additionally, the sequence
+.Dq Op Ar seq1 , Ar seq2
+is replaced by
+.Op Ar seq1
+if
+.Ar seq1
+is not a null string; otherwise it is replaced by
+.Ar seq2 .
+For example, the command
+.Pp
+.Bd -literal -offset indent -compact
+nmap $1.$2.$3 [$1,$2].[$2,file]
+.Ed
+.Pp
+would yield
+the output filename "myfile.data" for input filenames "myfile.data" and
+"myfile.data.old", "myfile.file" for the input filename "myfile", and
+"myfile.myfile" for the input filename ".myfile".
+Spaces may be included in
+.Ar outpattern  ,
+as in the example:
+.Dl nmap $1 sed "s/  *$//" \*[Gt] $1
+Use the
+.Sq \e
+character to prevent special treatment
+of the
+.Sq $ ,
+.Sq \&[ ,
+.Sq \&] ,
+and
+.Sq \&,
+characters.
+.It Ic ntrans Op Ar inchars Op Ar outchars
+Set or unset the filename character translation mechanism.
+If no arguments are specified, the filename character
+translation mechanism is unset.
+If arguments are specified, characters in
+remote filenames are translated during
+.Ic mput
+commands and
+.Ic put
+commands issued without a specified remote target filename.
+If arguments are specified, characters in
+local filenames are translated during
+.Ic mget
+commands and
+.Ic get
+commands issued without a specified local target filename.
+This command is useful when connecting to a
+.No non\- Ns Ux
+remote computer
+with different file naming conventions or practices.
+Characters in a filename matching a character in
+.Ar inchars
+are replaced with the corresponding character in
+.Ar outchars .
+If the character's position in
+.Ar inchars
+is longer than the length of
+.Ar outchars  ,
+the character is deleted from the file name.
+.It Ic open Ar host Op Ar port
+Establish a connection to the specified
+.Ar host
+.Tn FTP
+server.
+An optional port number may be supplied,
+in which case,
+.Nm
+will attempt to contact an
+.Tn FTP
+server at that port.
+If the
+.Ic "set auto-login"
+option is on (default),
+.Nm
+will also attempt to automatically log the user in to
+the
+.Tn FTP
+server (see below).
+.It Ic page Ar file
+Retrieve
+.Ic file
+and display with the program specified by the
+.Ic "set pager"
+option.
+.It Ic passive Op Cm auto
+Toggle passive mode (if no arguments are given).
+If
+.Cm auto
+is given, act as if
+.Ev FTPMODE
+is set to
+.Sq auto .
+If passive mode is turned on (default),
+.Nm
+will send a
+.Dv PASV
+command for all data connections instead of a
+.Dv PORT
+command.
+The
+.Dv PASV
+command requests that the remote server open a port for the data connection
+and return the address of that port.
+The remote server listens on that port and the client connects to it.
+When using the more traditional
+.Dv PORT
+command, the client listens on a port and sends that address to the remote
+server, who connects back to it.
+Passive mode is useful when using
+.Nm
+through a gateway router or host that controls the directionality of
+traffic.
+(Note that though
+.Tn FTP
+servers are required to support the
+.Dv PASV
+command by
+.Li RFC 1123 ,
+some do not.)
+.It Ic pdir Op Ar remote-path
+Perform
+.Ic dir
+.Op Ar remote-path ,
+and display the result with the program specified by the
+.Ic "set pager"
+option.
+.It Ic pls Op Ar remote-path
+Perform
+.Ic ls
+.Op Ar remote-path ,
+and display the result with the program specified by the
+.Ic "set pager"
+option.
+.It Ic pmlsd Op Ar remote-path
+Perform
+.Ic mlsd
+.Op Ar remote-path ,
+and display the result with the program specified by the
+.Ic "set pager"
+option.
+.It Ic preserve
+Toggle preservation of modification times on retrieved files.
+.It Ic progress
+Toggle display of transfer progress bar.
+The progress bar will be disabled for a transfer that has
+.Ar local-file
+as
+.Sq Fl
+or a command that starts with
+.Sq \&| .
+Refer to
+.Sx FILE NAMING CONVENTIONS
+for more information.
+Enabling
+.Ic progress
+disables
+.Ic hash .
+.It Ic prompt
+Toggle interactive prompting.
+Interactive prompting
+occurs during multiple file transfers to allow the
+user to selectively retrieve or store files.
+If prompting is turned off (default is on), any
+.Ic mget
+or
+.Ic mput
+will transfer all files, and any
+.Ic mdelete
+will delete all files.
+.Pp
+When prompting is on, the following commands are available at a prompt:
+.Bl -tag -width 2n -offset indent
+.It Cm a
+Answer
+.Sq yes
+to the current file, and automatically answer
+.Sq yes
+to any remaining files for the current command.
+.It Cm n
+Answer
+.Sq no ,
+and do not transfer the file.
+.It Cm p
+Answer
+.Sq yes
+to the current file, and turn off prompt mode
+(as is
+.Dq prompt off
+had been given).
+.It Cm q
+Terminate the current operation.
+.It Cm y
+Answer
+.Sq yes ,
+and transfer the file.
+.It Cm \&?
+Display a help message.
+.El
+.Pp
+Any other response will answer
+.Sq yes
+to the current file.
+.It Ic proxy Ar ftp-command
+Execute an ftp command on a secondary control connection.
+This command allows simultaneous connection to two remote
+.Tn FTP
+servers for transferring files between the two servers.
+The first
+.Ic proxy
+command should be an
+.Ic open  ,
+to establish the secondary control connection.
+Enter the command "proxy ?" to see other
+.Tn FTP
+commands executable on the secondary connection.
+The following commands behave differently when prefaced by
+.Ic proxy  :
+.Ic open
+will not define new macros during the auto-login process,
+.Ic close
+will not erase existing macro definitions,
+.Ic get
+and
+.Ic mget
+transfer files from the host on the primary control connection
+to the host on the secondary control connection, and
+.Ic put  ,
+.Ic mput ,
+and
+.Ic append
+transfer files from the host on the secondary control connection
+to the host on the primary control connection.
+Third party file transfers depend upon support of the
+.Tn FTP
+protocol
+.Dv PASV
+command by the server on the secondary control connection.
+.It Ic put Ar local-file Op Ar remote-file
+Store a local file on the remote machine.
+If
+.Ar remote-file
+is left unspecified, the local file name is used
+after processing according to any
+.Ic ntrans
+or
+.Ic nmap
+settings
+in naming the remote file.
+File transfer uses the
+current settings for
+.Ic type  ,
+.Ic format ,
+.Ic mode  ,
+and
+.Ic structure .
+.It Ic pwd
+Print the name of the current working directory on the remote
+machine.
+.It Ic quit
+A synonym for
+.Ic bye .
+.It Ic quote Ar arg1 arg2 ...
+The arguments specified are sent, verbatim, to the remote
+.Tn FTP
+server.
+.It Xo
+.Ic rate Ar direction
+.Op Ar maximum Op Ar increment
+.Xc
+Throttle the maximum transfer rate to
+.Ar maximum
+bytes/second.
+If
+.Ar maximum
+is 0, disable the throttle.
+.Pp
+.Ar direction
+may be one of:
+.Bl -tag -width "all" -offset indent -compact
+.It Cm all
+Both directions.
+.It Cm get
+Incoming transfers.
+.It Cm put
+Outgoing transfers.
+.El
+.Pp
+.Ar maximum
+can be modified on the fly by
+.Ar increment
+bytes (default: 1024) each time a given signal is received:
+.B
+.Bl -tag -width "SIGUSR1" -offset indent
+.It Dv SIGUSR1
+Increment
+.Ar maximum
+by
+.Ar increment
+bytes.
+.It Dv SIGUSR2
+Decrement
+.Ar maximum
+by
+.Ar increment
+bytes.
+The result must be a positive number.
+.El
+.Pp
+If
+.Ar maximum
+is not supplied, the current throttle rates are displayed.
+.Pp
+Note:
+.Ic rate
+is not yet implemented for ascii mode transfers.
+.It Ic rcvbuf Ar size
+Set the size of the socket receive buffer to
+.Ar size .
+.It Ic recv Ar remote-file Op Ar local-file
+A synonym for
+.Ic get .
+.It Ic reget Ar remote-file Op Ar local-file
+.Ic reget
+acts like
+.Ic get ,
+except that if
+.Ar local-file
+exists and is
+smaller than
+.Ar remote-file  ,
+.Ar local-file
+is presumed to be
+a partially transferred copy of
+.Ar remote-file
+and the transfer
+is continued from the apparent point of failure.
+This command
+is useful when transferring very large files over networks that
+are prone to dropping connections.
+.It Ic remopts Ar command Op Ar command-options
+Set options on the remote
+.Tn FTP
+server for
+.Ar command
+to
+.Ar command-options
+(whose absence is handled on a command-specific basis).
+Remote
+.Tn FTP
+commands known to support options include:
+.Sq MLST
+(used for
+.Dv MLSD
+and
+.Dv MLST ) .
+.It Ic rename Op Ar from Op Ar to
+Rename the file
+.Ar from
+on the remote machine, to the file
+.Ar to .
+.It Ic reset
+Clear reply queue.
+This command re-synchronizes command/reply sequencing with the remote
+.Tn FTP
+server.
+Resynchronization may be necessary following a violation of the
+.Tn FTP
+protocol by the remote server.
+.It Ic restart Ar marker
+Restart the immediately following
+.Ic get
+or
+.Ic put
+at the
+indicated
+.Ar marker .
+On
+.Ux
+systems, marker is usually a byte
+offset into the file.
+.It Ic rhelp Op Ar command-name
+Request help from the remote
+.Tn FTP
+server.
+If a
+.Ar command-name
+is specified it is supplied to the server as well.
+.It Ic rmdir Ar directory-name
+Delete a directory on the remote machine.
+.It Ic rstatus Op Ar remote-file
+With no arguments, show status of remote machine.
+If
+.Ar remote-file
+is specified, show status of
+.Ar remote-file
+on remote machine.
+.It Ic runique
+Toggle storing of files on the local system with unique filenames.
+If a file already exists with a name equal to the target
+local filename for a
+.Ic get
+or
+.Ic mget
+command, a ".1" is appended to the name.
+If the resulting name matches another existing file,
+a ".2" is appended to the original name.
+If this process continues up to ".99", an error
+message is printed, and the transfer does not take place.
+The generated unique filename will be reported.
+Note that
+.Ic runique
+will not affect local files generated from a shell command
+(see below).
+The default value is off.
+.It Ic send Ar local-file Op Ar remote-file
+A synonym for
+.Ic put .
+.It Ic sendport
+Toggle the use of
+.Dv PORT
+commands.
+By default,
+.Nm
+will attempt to use a
+.Dv PORT
+command when establishing
+a connection for each data transfer.
+The use of
+.Dv PORT
+commands can prevent delays
+when performing multiple file transfers.
+If the
+.Dv PORT
+command fails,
+.Nm
+will use the default data port.
+When the use of
+.Dv PORT
+commands is disabled, no attempt will be made to use
+.Dv PORT
+commands for each data transfer.
+This is useful
+for certain
+.Tn FTP
+implementations which do ignore
+.Dv PORT
+commands but, incorrectly, indicate they've been accepted.
+.It Ic set Op Ar option Ar value
+Set
+.Ar option
+to
+.Ar value .
+If
+.Ar option
+and
+.Ar value
+are not given, display all of the options and their values.
+The currently supported options are:
+.Bl -tag -width "http_proxy" -offset indent
+.It Cm anonpass
+Defaults to
+.Ev $FTPANONPASS
+.It Cm ftp_proxy
+Defaults to
+.Ev $ftp_proxy .
+.It Cm http_proxy
+Defaults to
+.Ev $http_proxy .
+.It Cm no_proxy
+Defaults to
+.Ev $no_proxy .
+.It Cm pager
+Defaults to
+.Ev $PAGER .
+.It Cm prompt
+Defaults to
+.Ev $FTPPROMPT .
+.It Cm rprompt
+Defaults to
+.Ev $FTPRPROMPT .
+.El
+.It Ic site Ar arg1 arg2 ...
+The arguments specified are sent, verbatim, to the remote
+.Tn FTP
+server as a
+.Dv SITE
+command.
+.It Ic size Ar remote-file
+Return size of
+.Ar remote-file
+on remote machine.
+.It Ic sndbuf Ar size
+Set the size of the socket send buffer to
+.Ar size .
+.It Ic status
+Show the current status of
+.Nm ftp .
+.It Ic struct Ar struct-name
+Set the file transfer
+.Ar structure
+to
+.Ar struct-name .
+The default (and only supported)
+structure is
+.Dq file .
+.It Ic sunique
+Toggle storing of files on remote machine under unique file names.
+The remote
+.Tn FTP
+server must support
+.Tn FTP
+protocol
+.Dv STOU
+command for
+successful completion.
+The remote server will report unique name.
+Default value is off.
+.It Ic system
+Show the type of operating system running on the remote machine.
+.It Ic tenex
+Set the file transfer type to that needed to
+talk to
+.Tn TENEX
+machines.
+.It Ic throttle
+A synonym for
+.Ic rate .
+.It Ic trace
+Toggle packet tracing.
+.It Ic type Op Ar type-name
+Set the file transfer
+.Ic type
+to
+.Ar type-name .
+If no type is specified, the current type
+is printed.
+The default type is network
+.Tn ASCII .
+.It Ic umask Op Ar newmask
+Set the default umask on the remote server to
+.Ar newmask .
+If
+.Ar newmask
+is omitted, the current umask is printed.
+.It Ic unset Ar option
+Unset
+.Ar option .
+Refer to
+.Ic set
+for more information.
+.It Ic usage Ar command
+Print the usage message for
+.Ar command .
+.It Xo
+.Ic user Ar user-name
+.Op Ar password Op Ar account
+.Xc
+Identify yourself to the remote
+.Tn FTP
+server.
+If the
+.Ar password
+is not specified and the server requires it,
+.Nm
+will prompt the user for it (after disabling local echo).
+If an
+.Ar account
+field is not specified, and the
+.Tn FTP
+server
+requires it, the user will be prompted for it.
+If an
+.Ar account
+field is specified, an account command will
+be relayed to the remote server after the login sequence
+is completed if the remote server did not require it
+for logging in.
+Unless
+.Nm
+is invoked with
+.Dq auto-login
+disabled, this process is done automatically on initial connection to the
+.Tn FTP
+server.
+.It Ic verbose
+Toggle verbose mode.
+In verbose mode, all responses from
+the
+.Tn FTP
+server are displayed to the user.
+In addition,
+if verbose is on, when a file transfer completes, statistics
+regarding the efficiency of the transfer are reported.
+By default,
+verbose is on.
+.It Ic xferbuf Ar size
+Set the size of the socket send and receive buffers to
+.Ar size .
+.It Ic \&? Op Ar command
+A synonym for
+.Ic help .
+.El
+.Pp
+Command arguments which have embedded spaces may be quoted with
+quote
+.Sq \&"
+marks.
+.Pp
+Commands which toggle settings can take an explicit
+.Ic on
+or
+.Ic off
+argument to force the setting appropriately.
+.Pp
+Commands which take a byte count as an argument
+(e.g.,
+.Ic hash ,
+.Ic rate ,
+and
+.Ic xferbuf )
+support an optional suffix on the argument which changes the
+interpretation of the argument.
+Supported suffixes are:
+.Bl -tag -width 3n -offset indent -compact
+.It Li b
+Causes no modification.
+(Optional)
+.It Li k
+Kilo; multiply the argument by 1024
+.It Li m
+Mega; multiply the argument by 1048576
+.It Li g
+Giga; multiply the argument by 1073741824
+.El
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+(see the
+.Dq status
+argument of
+.Xr stty 1 )
+or
+.Dv SIGQUIT
+signal whilst a transfer is in progress, the current transfer rate
+statistics will be written to the standard error output, in the
+same format as the standard completion message.
+.Sh AUTO-FETCHING FILES
+In addition to standard commands, this version of
+.Nm
+supports an auto-fetch feature.
+To enable auto-fetch, simply pass the list of hostnames/files
+on the command line.
+.Pp
+The following formats are valid syntax for an auto-fetch element:
+.Bl -tag -width "FOO "
+.\" [user@]host:[path][/]
+.It Xo
+.Sm off
+.Op Ar user Li \&@
+.Ar host Li \&:
+.Op Ar path
+.Op Li /
+.Sm on
+.Xc
+.Dq Classic
+.Tn FTP
+format.
+.Pp
+If
+.Ar path
+contains a glob character and globbing is enabled,
+(see
+.Ic glob ) ,
+then the equivalent of
+.Ql mget path
+is performed.
+.Pp
+If the directory component of
+.Ar path
+contains no globbing characters,
+it is stored locally with the name basename (see
+.Xr basename 1 )
+of
+.Ic path ,
+in the current directory.
+Otherwise, the full remote name is used as the local name,
+relative to the local root directory.
+.\" ftp://[user[:password]@]host[:port]/path[/][;type=X]
+.It Xo
+.Sm off
+.Li ftp://
+.Oo Ar user
+.Op Li \&: Ar password
+.Li \&@ Oc
+.Ar host Oo Li \&: Ar port Oc
+.Li / Ar path
+.Op Li /
+.Op Li ;type= Ar X
+.Sm on
+.Xc
+An
+.Tn FTP
+URL, retrieved using the
+.Tn FTP
+protocol if
+.Ic "set ftp_proxy"
+isn't defined.
+Otherwise, transfer the URL using
+.Tn HTTP
+via the proxy defined in
+.Ic "set ftp_proxy" .
+If
+.Ic "set ftp_proxy"
+isn't defined and
+.Ar user
+is given, login as
+.Ar user .
+In this case, use
+.Ar password
+if supplied, otherwise prompt the user for one.
+.Pp
+If a suffix of
+.Sq ;type=A
+or
+.Sq ;type=I
+is supplied, then the transfer type will take place as
+ascii or binary (respectively).
+The default transfer type is binary.
+.Pp
+In order to be compliant with
+.Li RFC 1738 ,
+.Nm
+interprets the
+.Ar path
+part of an
+.Dq ftp://
+auto-fetch URL as follows:
+.Bl -bullet
+.It
+The
+.Sq Li /
+immediately after the
+.Ar host Ns Oo Li \&: Ns Ar port Oc
+is interpreted as a separator before the
+.Ar path ,
+and not as part of the
+.Ar path
+itself.
+.It
+The
+.Ar path
+is interpreted as a
+.So Li / Sc Ns -separated
+list of name components.
+For all but the last such component,
+.Nm
+performs the equivalent of a
+.Ic cd
+command.
+For the last path component,
+.Nm
+performs the equivalent of a
+.Ic get
+command.
+.It
+Empty name components,
+which result from
+.Sq Li //
+within the
+.Ar path ,
+or from an extra
+.Sq Li /
+at the beginning of the
+.Ar path ,
+will cause the equivalent of a
+.Ic cd
+command without a directory name.
+This is unlikely to be useful.
+.It
+Any
+.Sq Li \&% Ns Ar XX
+codes
+(per
+.Li RFC 1738 )
+within the path components are decoded, with
+.Ar XX
+representing a character code in hexadecimal.
+This decoding takes place after the
+.Ar path
+has been split into components,
+but before each component is used in the equivalent of a
+.Ic cd
+or
+.Ic get
+command.
+Some often-used codes are
+.Sq Li \&%2F
+(which represents
+.Sq Li / )
+and
+.Sq Li \&%7E
+(which represents
+.Sq Li ~ ) .
+.El
+.Pp
+The above interpretation has the following consequences:
+.Bl -bullet
+.It
+The path is interpreted relative to the
+default login directory of the specified user or of the
+.Sq anonymous
+user.
+If the
+.Pa /
+directory is required, use a leading path of
+.Dq %2F .
+If a user's home directory is required (and the remote server supports
+the syntax), use a leading path of
+.Dq %7Euser/ .
+For example, to retrieve
+.Pa /etc/motd
+from
+.Sq localhost
+as the user
+.Sq myname
+with the password
+.Sq mypass ,
+use
+.Dq ftp://myname:mypass@localhost/%2fetc/motd
+.It
+The exact
+.Ic cd
+and
+.Ic get
+commands can be controlled by careful choice of
+where to use
+.Sq /
+and where to use
+.Sq %2F
+(or
+.Sq %2f ) .
+For example, the following URLs correspond to the
+equivalents of the indicated commands:
+.Bl -tag -width "ftp://host/%2Fdir1%2Fdir2%2Ffile"
+.It ftp://host/dir1/dir2/file
+.Dq "cd dir1" ,
+.Dq "cd dir2" ,
+.Dq "get file" .
+.It ftp://host/%2Fdir1/dir2/file
+.Dq "cd /dir1" ,
+.Dq "cd dir2" ,
+.Dq "get file" .
+.It ftp://host/dir1%2Fdir2/file
+.Dq "cd dir1/dir2" ,
+.Dq "get file" .
+.It ftp://host/%2Fdir1%2Fdir2/file
+.Dq "cd /dir1/dir2" ,
+.Dq "get file" .
+.It ftp://host/dir1%2Fdir2%2Ffile
+.Dq "get dir1/dir2/file" .
+.It ftp://host/%2Fdir1%2Fdir2%2Ffile
+.Dq "get /dir1/dir2/file" .
+.El
+.It
+You must have appropriate access permission for each of the
+intermediate directories that is used in the equivalent of a
+.Ic cd
+command.
+.El
+.\" http://[user[:password]@]host[:port]/path
+.It Xo
+.Sm off
+.Li http://
+.Oo Ar user
+.Op Li \&: Ar password
+.Li \&@ Oc
+.Ar host Oo Li \&: Ar port Oc
+.Li / Ar path
+.Sm on
+.Xc
+An
+.Tn HTTP
+URL, retrieved using the
+.Tn HTTP
+protocol.
+If
+.Ic "set http_proxy"
+is defined, it is used as a URL to an
+.Tn HTTP
+proxy server.
+If
+.Tn HTTP
+authorization is required to retrieve
+.Ar path ,
+and
+.Sq user
+(and optionally
+.Sq password )
+is in the URL, use them for the first attempt to authenticate.
+.\" file:///path
+.It Xo
+.Sm off
+.Li file:/// Ar path
+.Sm on
+.Xc
+A local URL, copied from
+.Pa / Ns Ar path
+on the local host.
+.El
+.Pp
+Unless noted otherwise above, and
+.Fl o Ar output
+is not given, the file is stored in the current directory as the
+.Xr basename 1
+of
+.Ar path .
+Note that if a
+.Tn HTTP
+redirect is received, the fetch is retried using the new target URL
+supplied by the server, with a corresponding new
+.Ar path .
+Using an explicit
+.Fl o Ar output
+is recommended, to avoid writing to unexpected file names.
+.Pp
+If a classic format or an
+.Tn FTP
+URL format has a trailing
+.Sq /
+or an empty
+.Ar path
+component, then
+.Nm
+will connect to the site and
+.Ic cd
+to the directory given as the path, and leave the user in interactive
+mode ready for further input.
+This will not work if
+.Ic "set ftp_proxy"
+is being used.
+.Pp
+Direct
+.Tn HTTP
+transfers use HTTP 1.1.
+Proxied
+.Tn FTP
+and
+.Tn HTTP
+transfers use HTTP 1.0.
+.Pp
+If
+.Fl R
+is given, all auto-fetches that don't go via the
+.Tn FTP
+or
+.Tn HTTP
+proxies will be restarted.
+For
+.Tn FTP ,
+this is implemented by using
+.Nm reget
+instead of
+.Nm get .
+For
+.Tn HTTP ,
+this is implemented by using the
+.Sq "Range: bytes="
+.Tn "HTTP/1.1"
+directive.
+.Pp
+If WWW or proxy WWW authentication is required, you will be prompted
+to enter a username and password to authenticate with.
+.Pp
+When specifying IPv6 numeric addresses in a URL, you need to
+surround the address in square brackets.
+E.g.:
+.Dq ftp://[::1]:21/ .
+This is because colons are used in IPv6 numeric address as well as
+being the separator for the port number.
+.Sh ABORTING A FILE TRANSFER
+To abort a file transfer, use the terminal interrupt key
+(usually Ctrl-C).
+Sending transfers will be immediately halted.
+Receiving transfers will be halted by sending an
+.Tn FTP
+protocol
+.Dv ABOR
+command to the remote server, and discarding any further data received.
+The speed at which this is accomplished depends upon the remote
+server's support for
+.Dv ABOR
+processing.
+If the remote server does not support the
+.Dv ABOR
+command, the prompt will not appear until the remote server has completed
+sending the requested file.
+.Pp
+If the terminal interrupt key sequence is used whilst
+.Nm
+is awaiting a reply from the remote server for the ABOR processing,
+then the connection will be closed.
+This is different from the traditional behaviour (which ignores the
+terminal interrupt during this phase), but is considered more useful.
+.Sh FILE NAMING CONVENTIONS
+Files specified as arguments to
+.Nm
+commands are processed according to the following rules.
+.Bl -enum
+.It
+If the file name
+.Sq Fl
+is specified, the
+.Ar stdin
+(for reading) or
+.Ar stdout
+(for writing) is used.
+.It
+If the first character of the file name is
+.Sq \&| ,
+the
+remainder of the argument is interpreted as a shell command.
+.Nm
+then forks a shell, using
+.Xr popen 3
+with the argument supplied, and reads (writes) from the stdout
+(stdin).
+If the shell command includes spaces, the argument
+must be quoted; e.g.
+.Dq Qq Li \&| ls\ \-lt .
+A particularly
+useful example of this mechanism is:
+.Dq Li dir \&"\&" \&|more .
+.It
+Failing the above checks, if
+.Dq globbing
+is enabled, local file names are expanded according to the rules
+used in the
+.Xr csh  1  ;
+see the
+.Ic glob
+command.
+If the
+.Nm
+command expects a single local file (e.g.
+.Ic put  ) ,
+only the first filename generated by the "globbing" operation is used.
+.It
+For
+.Ic mget
+commands and
+.Ic get
+commands with unspecified local file names, the local filename is
+the remote filename, which may be altered by a
+.Ic case  ,
+.Ic ntrans ,
+or
+.Ic nmap
+setting.
+The resulting filename may then be altered if
+.Ic runique
+is on.
+.It
+For
+.Ic mput
+commands and
+.Ic put
+commands with unspecified remote file names, the remote filename is
+the local filename, which may be altered by a
+.Ic ntrans
+or
+.Ic nmap
+setting.
+The resulting filename may then be altered by the remote server if
+.Ic sunique
+is on.
+.El
+.Sh FILE TRANSFER PARAMETERS
+The
+.Tn FTP
+specification specifies many parameters which may affect a file transfer.
+The
+.Ic type
+may be one of
+.Dq ascii ,
+.Dq image
+(binary),
+.Dq ebcdic ,
+and
+.Dq local byte size
+(for
+.Tn PDP Ns -10's
+and
+.Tn PDP Ns -20's
+mostly).
+.Nm
+supports the ascii and image types of file transfer,
+plus local byte size 8 for
+.Ic tenex
+mode transfers.
+.Pp
+.Nm
+supports only the default values for the remaining
+file transfer parameters:
+.Ic mode ,
+.Ic form ,
+and
+.Ic struct .
+.Sh THE .netrc FILE
+The
+.Pa .netrc
+file contains login and initialization information
+used by the auto-login process.
+It resides in the user's home directory,
+unless overridden with the
+.Fl N Ar netrc
+option, or specified in the
+.Ev NETRC
+environment variable.
+The following tokens are recognized; they may be separated by spaces,
+tabs, or new-lines:
+.Bl -tag -width password
+.It Ic machine Ar name
+Identify a remote machine
+.Ar name .
+The auto-login process searches the
+.Pa .netrc
+file for a
+.Ic machine
+token that matches the remote machine specified on the
+.Nm
+command line or as an
+.Ic open
+command argument.
+Once a match is made, the subsequent
+.Pa .netrc
+tokens are processed,
+stopping when the end of file is reached or another
+.Ic machine
+or a
+.Ic default
+token is encountered.
+.It Ic default
+This is the same as
+.Ic machine
+.Ar name
+except that
+.Ic default
+matches any name.
+There can be only one
+.Ic default
+token, and it must be after all
+.Ic machine
+tokens.
+This is normally used as:
+.Pp
+.Dl default login anonymous password user@site
+.Pp
+thereby giving the user an automatic anonymous
+.Tn FTP
+login to
+machines not specified in
+.Pa .netrc .
+This can be overridden
+by using the
+.Fl n
+flag to disable auto-login.
+.It Ic login Ar name
+Identify a user on the remote machine.
+If this token is present, the auto-login process will initiate
+a login using the specified
+.Ar name .
+.It Ic password Ar string
+Supply a password.
+If this token is present, the auto-login process will supply the
+specified string if the remote server requires a password as part
+of the login process.
+Note that if this token is present in the
+.Pa .netrc
+file for any user other
+than
+.Ar anonymous  ,
+.Nm
+will abort the auto-login process if the
+.Pa .netrc
+is readable by
+anyone besides the user.
+.It Ic account Ar string
+Supply an additional account password.
+If this token is present, the auto-login process will supply the
+specified string if the remote server requires an additional
+account password, or the auto-login process will initiate an
+.Dv ACCT
+command if it does not.
+.It Ic macdef Ar name
+Define a macro.
+This token functions like the
+.Nm
+.Ic macdef
+command functions.
+A macro is defined with the specified name; its contents begin with the
+next
+.Pa .netrc
+line and continue until a blank line (consecutive new-line
+characters) is encountered.
+Like the other tokens in the
+.Pa .netrc
+file, a
+.Ic macdef
+is applicable only to the
+.Ic machine
+definition preceding it.
+A
+.Ic macdef
+entry cannot be utilized by multiple
+.Ic machine
+definitions; rather, it must be defined following each
+.Ic machine
+it is intended to be used with.
+If a macro named
+.Ic init
+is defined, it is automatically executed as the last step in the
+auto-login process.
+For example,
+.Bd -literal -offset indent
+default
+macdef init
+epsv4 off
+.Ed
+.Pp
+followed by a blank line.
+.El
+.Sh COMMAND LINE EDITING
+.Nm
+supports interactive command line editing, via the
+.Xr editline 3
+library.
+It is enabled with the
+.Ic edit
+command, and is enabled by default if input is from a tty.
+Previous lines can be recalled and edited with the arrow keys,
+and other GNU Emacs-style editing keys may be used as well.
+.Pp
+The
+.Xr editline 3
+library is configured with a
+.Pa .editrc
+file - refer to
+.Xr editrc 5
+for more information.
+.Pp
+An extra key binding is available to
+.Nm
+to provide context sensitive command and filename completion
+(including remote file completion).
+To use this, bind a key to the
+.Xr editline 3
+command
+.Ic ftp-complete .
+By default, this is bound to the TAB key.
+.Sh COMMAND LINE PROMPT
+By default,
+.Nm
+displays a command line prompt of
+.Dq "ftp\*[Gt] "
+to the user.
+This can be changed with the
+.Ic "set prompt"
+command.
+.Pp
+A prompt can be displayed on the right side of the screen (after the
+command input) with the
+.Ic "set rprompt"
+command.
+.Pp
+The following formatting sequences are replaced by the given
+information:
+.Bl -tag -width "%% " -offset indent
+.It Li \&%/
+The current remote working directory.
+.\" %c[[0]n], %.[[0]n]
+.It Xo
+.Sm off
+.Li \&%c
+.Op Oo Li 0 Oc Ar n Ns ,
+.Li \&%.
+.Op Oo Li 0 Oc Ar n
+.Sm on
+.Xc
+The trailing component of the current remote working directory, or
+.Em n
+trailing components if a digit
+.Em n
+is given.
+If
+.Em n
+begins with
+.Sq 0 ,
+the number of skipped components precede the trailing component(s) in
+the format
+.\" ``/<number>trailing''
+.Do
+.Sm off
+.Li / Li \*[Lt] Va number Li \*[Gt]
+.Va trailing
+.Sm on
+.Dc
+(for
+.Sq \&%c )
+or
+.\" ``...trailing''
+.Dq Li \&... Ns Va trailing
+(for
+.Sq \&%. ) .
+.It Li \&%M
+The remote host name.
+.It Li \&%m
+The remote host name, up to the first
+.Sq \&. .
+.It Li \&%n
+The remote user name.
+.It Li \&%%
+A single
+.Sq % .
+.El
+.Sh ENVIRONMENT
+.Nm
+uses the following environment variables.
+.Bl -tag -width "FTPSERVERPORT"
+.It Ev FTPANONPASS
+Password to send in an anonymous
+.Tn FTP
+transfer.
+Defaults to
+.Dq Li `whoami`@ .
+.It Ev FTPMODE
+Overrides the default operation mode.
+Support values are:
+.Bl -tag -width "passive"
+.It Cm active
+active mode
+.Tn FTP
+only
+.It Cm auto
+automatic determination of passive or active (this is the default)
+.It Cm gate
+gate-ftp mode
+.It Cm passive
+passive mode
+.Tn FTP
+only
+.El
+.It Ev FTPPROMPT
+Command-line prompt to use.
+Defaults to
+.Dq "ftp\*[Gt] " .
+Refer to
+.Sx COMMAND LINE PROMPT
+for more information.
+.It Ev FTPRPROMPT
+Command-line right side prompt to use.
+Defaults to
+.Dq "" .
+Refer to
+.Sx COMMAND LINE PROMPT
+for more information.
+.It Ev FTPSERVER
+Host to use as gate-ftp server when
+.Ic gate
+is enabled.
+.It Ev FTPSERVERPORT
+Port to use when connecting to gate-ftp server when
+.Ic gate
+is enabled.
+Default is port returned by a
+.Fn getservbyname
+lookup of
+.Dq ftpgate/tcp .
+.It Ev FTPUSERAGENT
+The value to send for the
+.Tn HTTP
+User-Agent
+header.
+.It Ev HOME
+For default location of a
+.Pa .netrc
+file, if one exists.
+.It Ev NETRC
+An alternate location of the
+.Pa .netrc
+file.
+.It Ev PAGER
+Used by various commands to display files.
+Defaults to
+.Xr more 1
+if empty or not set.
+.It Ev SHELL
+For default shell.
+.It Ev ftp_proxy
+URL of
+.Tn FTP
+proxy to use when making
+.Tn FTP
+URL requests
+(if not defined, use the standard
+.Tn FTP
+protocol).
+.Pp
+See
+.Ev http_proxy
+for further notes about proxy use.
+.It Ev http_proxy
+URL of
+.Tn HTTP
+proxy to use when making
+.Tn HTTP
+URL requests.
+If proxy authentication is required and there is a username and
+password in this URL, they will automatically be used in the first
+attempt to authenticate to the proxy.
+.Pp
+If
+.Dq unsafe
+URL characters are required in the username or password
+(for example
+.Sq @
+or
+.Sq / ) ,
+encode them with
+.Li RFC 1738
+.Sq Li \&% Ns Ar XX
+encoding.
+.Pp
+Note that the use of a username and password in
+.Ev ftp_proxy
+and
+.Ev http_proxy
+may be incompatible with other programs that use it
+(such as
+.Xr lynx 1 ) .
+.Pp
+.Em NOTE :
+this is not used for interactive sessions, only for command-line
+fetches.
+.It Ev no_proxy
+A space or comma separated list of hosts (or domains) for which
+proxying is not to be used.
+Each entry may have an optional trailing ":port", which restricts
+the matching to connections to that port.
+.El
+.Sh EXTENDED PASSIVE MODE AND FIREWALLS
+Some firewall configurations do not allow
+.Nm
+to use extended passive mode.
+If you find that even a simple
+.Ic ls
+appears to hang after printing a message such as this:
+.Pp
+.Dl 229 Entering Extended Passive Mode (|||58551|)
+.Pp
+then you will need to disable extended passive mode with
+.Ic epsv4 off .
+See the above section
+.Sx The .netrc File
+for an example of how to make this automatic.
+.Sh SEE ALSO
+.Xr getservbyname 3 ,
+.Xr editrc 5 ,
+.Xr services 5 ,
+.Xr ftpd 8
+.Sh STANDARDS
+.Nm
+attempts to be compliant with
+.Li RFC 959 ,
+.Li RFC 1123 ,
+.Li RFC 1738 ,
+.Li RFC 2068 ,
+.Li RFC 2389 ,
+.Li RFC 2428 ,
+.Li RFC 2732 ,
+and
+.Cm draft-ietf-ftpext-mlst-11 .
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Pp
+Various features such as command line editing, context sensitive
+command and file completion, dynamic progress bar, automatic
+fetching of files and URLs, modification time preservation,
+transfer rate throttling, configurable command line prompt,
+and other enhancements over the standard
+.Bx
+.Nm
+were implemented in
+.Nx 1.3
+and later releases
+by
+.An Luke Mewburn
+.Aq lukem@NetBSD.org .
+.Pp
+IPv6 support was added by the WIDE/KAME project
+(but may not be present in all non-NetBSD versions of this program, depending
+if the operating system supports IPv6 in a similar manner to KAME).
+.Sh BUGS
+Correct execution of many commands depends upon proper behavior
+by the remote server.
+.Pp
+An error in the treatment of carriage returns
+in the
+.Bx 4.2
+ascii-mode transfer code
+has been corrected.
+This correction may result in incorrect transfers of binary files
+to and from
+.Bx 4.2
+servers using the ascii type.
+Avoid this problem by using the binary image type.
+.Pp
+.Nm
+assumes that all IPv4 mapped addresses
+.Po
+IPv6 addresses with a form like
+.Li ::ffff:10.1.1.1
+.Pc
+indicate IPv4 destinations which can be handled by
+.Dv AF_INET
+sockets.
+However, in certain IPv6 network configurations, this assumption is not true.
+In such an environment, IPv4 mapped addresses must be passed to
+.Dv AF_INET6
+sockets directly.
+For example, if your site uses a SIIT translator for IPv6-to-IPv4 translation,
+.Nm
+is unable to support your configuration.
diff --git a/contrib/tnftp/ftp.c b/contrib/tnftp/ftp.c
new file mode 100644 (file)
index 0000000..5a77d3b
--- /dev/null
@@ -0,0 +1,2171 @@
+/*     $NetBSD: ftp.c,v 1.142 2006/10/23 19:53:24 christos Exp $       */
+
+/*-
+ * Copyright (c) 1996-2005 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * 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 NetBSD
+ *     Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * Copyright (c) 1985, 1989, 1993, 1994
+ *     The 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. 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.
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * 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. Neither the name of the project 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 PROJECT 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 PROJECT 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 <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)ftp.c      8.6 (Berkeley) 10/27/94";
+#else
+__RCSID("$NetBSD: ftp.c,v 1.142 2006/10/23 19:53:24 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <arpa/ftp.h>
+#include <arpa/telnet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "ftp_var.h"
+
+volatile sig_atomic_t  abrtflag;
+volatile sig_atomic_t  timeoutflag;
+
+sigjmp_buf     ptabort;
+int    ptabflg;
+int    ptflag = 0;
+char   pasv[BUFSIZ];   /* passive port for proxy data connection */
+
+static int empty(FILE *, FILE *, int);
+
+struct sockinet {
+       union sockunion {
+               struct sockaddr_in  su_sin;
+#ifdef INET6
+               struct sockaddr_in6 su_sin6;
+#endif
+       } si_su;
+#if !HAVE_SOCKADDR_SA_LEN
+       int     si_len;
+#endif
+};
+
+#if !HAVE_SOCKADDR_SA_LEN
+# define su_len                si_len
+#else
+# define su_len                si_su.su_sin.sin_len
+#endif
+#define su_family      si_su.su_sin.sin_family
+#define su_port                si_su.su_sin.sin_port
+
+struct sockinet myctladdr, hisctladdr, data_addr;
+
+char *
+hookup(char *host, char *port)
+{
+       int s = -1, error, portnum;
+       struct addrinfo hints, *res, *res0;
+       char hbuf[MAXHOSTNAMELEN];
+       static char hostnamebuf[MAXHOSTNAMELEN];
+       char *cause = "unknown";
+       socklen_t len;
+       int on = 1;
+
+       memset((char *)&hisctladdr, 0, sizeof (hisctladdr));
+       memset((char *)&myctladdr, 0, sizeof (myctladdr));
+       memset(&hints, 0, sizeof(hints));
+       portnum = parseport(port, FTP_PORT);
+       hints.ai_flags = AI_CANONNAME;
+       hints.ai_family = family;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = 0;
+       error = getaddrinfo(host, NULL, &hints, &res0);
+       if (error) {
+               warnx("%s: %s", host, gai_strerror(error));
+               code = -1;
+               return (0);
+       }
+
+       if (res0->ai_canonname)
+               (void)strlcpy(hostnamebuf, res0->ai_canonname,
+                   sizeof(hostnamebuf));
+       else
+               (void)strlcpy(hostnamebuf, host, sizeof(hostnamebuf));
+       hostname = hostnamebuf;
+
+       for (res = res0; res; res = res->ai_next) {
+               /*
+                * make sure that ai_addr is NOT an IPv4 mapped address.
+                * IPv4 mapped address complicates too many things in FTP
+                * protocol handling, as FTP protocol is defined differently
+                * between IPv4 and IPv6.
+                *
+                * This may not be the best way to handle this situation,
+                * since the semantics of IPv4 mapped address is defined in
+                * the kernel.  There are configurations where we should use
+                * IPv4 mapped address as native IPv6 address, not as
+                * "an IPv6 address that embeds IPv4 address" (namely, SIIT).
+                *
+                * More complete solution would be to have an additional
+                * getsockopt to grab "real" peername/sockname.  "real"
+                * peername/sockname will be AF_INET if IPv4 mapped address
+                * is used to embed IPv4 address, and will be AF_INET6 if
+                * we use it as native.  What a mess!
+                */
+               ai_unmapped(res);
+               if (verbose && res0->ai_next) {
+                               /* if we have multiple possibilities */
+                       if (getnameinfo(res->ai_addr, res->ai_addrlen,
+                           hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST))
+                               strlcpy(hbuf, "?", sizeof(hbuf));
+                       fprintf(ttyout, "Trying %s...\n", hbuf);
+               }
+               ((struct sockaddr_in *)res->ai_addr)->sin_port = htons(portnum);
+               s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol);
+               if (s < 0) {
+                       cause = "socket";
+                       continue;
+               }
+               error = ftp_connect(s, res->ai_addr, res->ai_addrlen);
+               if (error) {
+                       /* this "if" clause is to prevent print warning twice */
+                       if (res->ai_next) {
+                               if (getnameinfo(res->ai_addr, res->ai_addrlen,
+                                   hbuf, sizeof(hbuf), NULL, 0,
+                                   NI_NUMERICHOST))
+                                       strlcpy(hbuf, "?", sizeof(hbuf));
+                               warn("connect to address %s", hbuf);
+                       }
+                       cause = "connect";
+                       close(s);
+                       s = -1;
+                       continue;
+               }
+
+               /* finally we got one */
+               break;
+       }
+       if (s < 0) {
+               warn("%s", cause);
+               code = -1;
+               freeaddrinfo(res0);
+               return 0;
+       }
+       memcpy(&hisctladdr.si_su, res->ai_addr, res->ai_addrlen);
+       hisctladdr.su_len = res->ai_addrlen;
+       freeaddrinfo(res0);
+       res0 = res = NULL;
+
+       len = hisctladdr.su_len;
+       if (getsockname(s, (struct sockaddr *)&myctladdr.si_su, &len) == -1) {
+               warn("getsockname");
+               code = -1;
+               goto bad;
+       }
+       myctladdr.su_len = len;
+
+#ifdef IPTOS_LOWDELAY
+       if (hisctladdr.su_family == AF_INET) {
+               int tos = IPTOS_LOWDELAY;
+               if (setsockopt(s, IPPROTO_IP, IP_TOS,
+                               (void *)&tos, sizeof(tos)) == -1) {
+                               DWARN("setsockopt %s (ignored)",
+                                   "IPTOS_LOWDELAY");
+               }
+       }
+#endif
+       cin = fdopen(s, "r");
+       cout = fdopen(s, "w");
+       if (cin == NULL || cout == NULL) {
+               warnx("fdopen failed.");
+               if (cin)
+                       (void)fclose(cin);
+               if (cout)
+                       (void)fclose(cout);
+               code = -1;
+               goto bad;
+       }
+       if (verbose)
+               fprintf(ttyout, "Connected to %s.\n", hostname);
+       if (getreply(0) > 2) {  /* read startup message from server */
+               if (cin)
+                       (void)fclose(cin);
+               if (cout)
+                       (void)fclose(cout);
+               code = -1;
+               goto bad;
+       }
+
+       if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE,
+                       (void *)&on, sizeof(on)) == -1) {
+               DWARN("setsockopt %s (ignored)", "SO_OOBINLINE");
+       }
+
+       return (hostname);
+ bad:
+       (void)close(s);
+       return (NULL);
+}
+
+void
+cmdabort(int notused)
+{
+       int oerrno = errno;
+
+       sigint_raised = 1;
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       abrtflag++;
+       if (ptflag)
+               siglongjmp(ptabort, 1);
+       errno = oerrno;
+}
+
+void
+cmdtimeout(int notused)
+{
+       int oerrno = errno;
+
+       alarmtimer(0);
+       if (fromatty)
+               write(fileno(ttyout), "\n", 1);
+       timeoutflag++;
+       if (ptflag)
+               siglongjmp(ptabort, 1);
+       errno = oerrno;
+}
+
+/*VARARGS*/
+int
+command(const char *fmt, ...)
+{
+       va_list ap;
+       int r;
+       sigfunc oldsigint;
+
+#ifndef NO_DEBUG
+       if (ftp_debug) {
+               fputs("---> ", ttyout);
+               va_start(ap, fmt);
+               if (strncmp("PASS ", fmt, 5) == 0)
+                       fputs("PASS XXXX", ttyout);
+               else if (strncmp("ACCT ", fmt, 5) == 0)
+                       fputs("ACCT XXXX", ttyout);
+               else
+                       vfprintf(ttyout, fmt, ap);
+               va_end(ap);
+               putc('\n', ttyout);
+       }
+#endif
+       if (cout == NULL) {
+               warnx("No control connection for command.");
+               code = -1;
+               return (0);
+       }
+
+       abrtflag = 0;
+
+       oldsigint = xsignal(SIGINT, cmdabort);
+
+       va_start(ap, fmt);
+       vfprintf(cout, fmt, ap);
+       va_end(ap);
+       fputs("\r\n", cout);
+       (void)fflush(cout);
+       cpend = 1;
+       r = getreply(!strcmp(fmt, "QUIT"));
+       if (abrtflag && oldsigint != SIG_IGN)
+               (*oldsigint)(SIGINT);
+       (void)xsignal(SIGINT, oldsigint);
+       return (r);
+}
+
+static const char *m421[] = {
+       "remote server timed out. Connection closed",
+       "user interrupt. Connection closed",
+       "remote server has closed connection",
+};
+
+int
+getreply(int expecteof)
+{
+       char current_line[BUFSIZ];      /* last line of previous reply */
+       int c, n, line;
+       int dig;
+       int originalcode = 0, continuation = 0;
+       sigfunc oldsigint, oldsigalrm;
+       int pflag = 0;
+       char *cp, *pt = pasv;
+
+       abrtflag = 0;
+       timeoutflag = 0;
+
+       oldsigint = xsignal(SIGINT, cmdabort);
+       oldsigalrm = xsignal(SIGALRM, cmdtimeout);
+
+       for (line = 0 ;; line++) {
+               dig = n = code = 0;
+               cp = current_line;
+               while (alarmtimer(quit_time ? quit_time : 60),
+                      ((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);
+                                       (void)fflush(cout);
+                                       break;
+                               case DO:
+                               case DONT:
+                                       c = getc(cin);
+                                       fprintf(cout, "%c%c%c", IAC, WONT, c);
+                                       (void)fflush(cout);
+                                       break;
+                               default:
+                                       break;
+                               }
+                               continue;
+                       }
+                       dig++;
+                       if (c == EOF) {
+                               /*
+                                * these will get trashed by pswitch()
+                                * in lostpeer()
+                                */
+                               int reply_timeoutflag = timeoutflag;
+                               int reply_abrtflag = abrtflag;
+
+                               alarmtimer(0);
+                               if (expecteof && feof(cin)) {
+                                       (void)xsignal(SIGINT, oldsigint);
+                                       (void)xsignal(SIGALRM, oldsigalrm);
+                                       code = 221;
+                                       return (0);
+                               }
+                               cpend = 0;
+                               lostpeer(0);
+                               if (verbose) {
+                                       size_t midx;
+                                       if (reply_timeoutflag)
+                                               midx = 0;
+                                       else if (reply_abrtflag)
+                                               midx = 1;
+                                       else
+                                               midx = 2;
+                                       (void)fprintf(ttyout,
+                           "421 Service not available, %s.\n", m421[midx]);
+                                       (void)fflush(ttyout);
+                               }
+                               code = 421;
+                               (void)xsignal(SIGINT, oldsigint);
+                               (void)xsignal(SIGALRM, oldsigalrm);
+                               return (4);
+                       }
+                       if (c != '\r' && (verbose > 0 ||
+                           ((verbose > -1 && n == '5' && dig > 4) &&
+                           (((!n && c < '5') || (n && n < '5'))
+                            || !retry_connect)))) {
+                               if (proxflag &&
+                                  (dig == 1 || (dig == 5 && verbose == 0)))
+                                       fprintf(ttyout, "%s:", hostname);
+                               (void)putc(c, ttyout);
+                       }
+                       if (dig <