aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncaen <mail@duncano.de>2017-06-11 19:18:44 +0200
committerDuncaen <mail@duncano.de>2017-06-11 19:18:44 +0200
commit53f9101a113ec1693a519594a2ccaab32efc7c62 (patch)
treef04d19a87ad812ca25148692084319e8cff48969
parent31f0970ad6c2a7f1b3ba6e0493ff2df44888f5fb (diff)
downloadlobase-53f9101a113ec1693a519594a2ccaab32efc7c62.tar.gz
usr.bin/ftp: import
-rw-r--r--usr.bin/Makefile2
-rw-r--r--usr.bin/ftp/Makefile17
-rw-r--r--usr.bin/ftp/cmds.c1688
-rw-r--r--usr.bin/ftp/cmds.h83
-rw-r--r--usr.bin/ftp/cmdtab.c215
-rw-r--r--usr.bin/ftp/complete.c382
-rw-r--r--usr.bin/ftp/cookie.c231
-rw-r--r--usr.bin/ftp/domacro.c149
-rw-r--r--usr.bin/ftp/extern.h148
-rw-r--r--usr.bin/ftp/fetch.c1602
-rw-r--r--usr.bin/ftp/ftp.11792
-rw-r--r--usr.bin/ftp/ftp.c2095
-rw-r--r--usr.bin/ftp/ftp_var.h229
-rw-r--r--usr.bin/ftp/list.c86
-rw-r--r--usr.bin/ftp/main.c921
-rw-r--r--usr.bin/ftp/pathnames.h37
-rw-r--r--usr.bin/ftp/ruserpass.c317
-rw-r--r--usr.bin/ftp/small.c728
-rw-r--r--usr.bin/ftp/small.h35
-rw-r--r--usr.bin/ftp/stringlist.c97
-rw-r--r--usr.bin/ftp/stringlist.h56
-rw-r--r--usr.bin/ftp/util.c1071
22 files changed, 11980 insertions, 1 deletions
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index c29d19f..8511745 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -1,6 +1,6 @@
TOPDIR?=..
SUBDIR= apply awk basename bc biff cal calendar cmp colrm col column comm \
- cut dc dirname du diff3 diff env expand false file fmt fold getopt \
+ cut dc dirname du diff3 diff env expand false file fmt fold ftp getopt \
grep head hexdump id indent join jot lam lndir logger logname look \
mktemp nice nl nohup paste patch printenv printf readlink renice rev \
rs sed shar sort spell split stat tee time touch tr true tsort tty ul \
diff --git a/usr.bin/ftp/Makefile b/usr.bin/ftp/Makefile
new file mode 100644
index 0000000..28ce7a0
--- /dev/null
+++ b/usr.bin/ftp/Makefile
@@ -0,0 +1,17 @@
+# $OpenBSD: Makefile,v 1.30 2016/05/06 22:06:09 jca Exp $
+
+.TOPDIR?=../..
+
+# Define SMALL to disable command line editing and https support
+#CFLAGS+=-DSMALL
+
+PROG= ftp
+SRCS= cmds.c cmdtab.c complete.c cookie.c domacro.c fetch.c ftp.c \
+ list.c main.c ruserpass.c small.c stringlist.c util.c
+
+LDADD+= -ledit -lcurses -lutil -ltls -lssl -lcrypto
+DPADD+= ${LIBEDIT} ${LIBCURSES} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO}
+
+#COPTS+= -Wall -Wconversion -Wstrict-prototypes -Wmissing-prototypes
+
+include ${.TOPDIR}/mk/bsd.prog.mk
diff --git a/usr.bin/ftp/cmds.c b/usr.bin/ftp/cmds.c
new file mode 100644
index 0000000..b05b8b6
--- /dev/null
+++ b/usr.bin/ftp/cmds.c
@@ -0,0 +1,1688 @@
+/* $OpenBSD: cmds.c,v 1.77 2016/05/25 15:36:01 krw Exp $ */
+/* $NetBSD: cmds.c,v 1.27 1997/08/18 10:20:15 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef SMALL
+
+/*
+ * 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 <fnmatch.h>
+#include <glob.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "ftp_var.h"
+#include "pathnames.h"
+#include "cmds.h"
+
+/*
+ * Set ascii transfer type.
+ */
+/*ARGSUSED*/
+void
+setascii(int argc, char *argv[])
+{
+
+ stype[1] = "ascii";
+ settype(2, stype);
+}
+
+/*
+ * Set file transfer mode.
+ */
+/*ARGSUSED*/
+void
+setftmode(int argc, char *argv[])
+{
+
+ fprintf(ttyout, "We only support %s mode, sorry.\n", modename);
+ code = -1;
+}
+
+/*
+ * Set file transfer format.
+ */
+/*ARGSUSED*/
+void
+setform(int argc, char *argv[])
+{
+
+ fprintf(ttyout, "We only support %s format, sorry.\n", formname);
+ code = -1;
+}
+
+/*
+ * Set file transfer structure.
+ */
+/*ARGSUSED*/
+void
+setstruct(int argc, char *argv[])
+{
+
+ fprintf(ttyout, "We only support %s structure, sorry.\n", structname);
+ code = -1;
+}
+
+void
+reput(int argc, char *argv[])
+{
+
+ (void)putit(argc, argv, 1);
+}
+
+void
+put(int argc, char *argv[])
+{
+
+ (void)putit(argc, argv, 0);
+}
+
+/*
+ * Send a single file.
+ */
+void
+putit(int argc, char *argv[], int restartit)
+{
+ char *cmd;
+ int loc = 0;
+ char *oldargv1, *oldargv2;
+
+ if (argc == 2) {
+ argc++;
+ argv[2] = argv[1];
+ loc++;
+ }
+ if (argc < 2 && !another(&argc, &argv, "local-file"))
+ goto usage;
+ if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) {
+usage:
+ fprintf(ttyout, "usage: %s local-file [remote-file]\n",
+ argv[0]);
+ code = -1;
+ return;
+ }
+ oldargv1 = argv[1];
+ oldargv2 = argv[2];
+ if (!globulize(&argv[1])) {
+ code = -1;
+ return;
+ }
+ /*
+ * If "globulize" modifies argv[1], and argv[2] is a copy of
+ * the old argv[1], make it a copy of the new argv[1].
+ */
+ if (argv[1] != oldargv1 && argv[2] == oldargv1) {
+ argv[2] = argv[1];
+ }
+ if (restartit == 1) {
+ if (curtype != type)
+ changetype(type, 0);
+ restart_point = remotesize(argv[2], 1);
+ if (restart_point < 0) {
+ restart_point = 0;
+ code = -1;
+ return;
+ }
+ }
+ if (strcmp(argv[0], "append") == 0) {
+ restartit = 1;
+ }
+ cmd = restartit ? "APPE" : ((sunique) ? "STOU" : "STOR");
+ if (loc && ntflag) {
+ argv[2] = dotrans(argv[2]);
+ }
+ if (loc && mapflag) {
+ argv[2] = domap(argv[2]);
+ }
+ sendrequest(cmd, argv[1], argv[2],
+ argv[1] != oldargv1 || argv[2] != oldargv2);
+ restart_point = 0;
+ if (oldargv1 != argv[1]) /* free up after globulize() */
+ free(argv[1]);
+}
+
+/*
+ * Send multiple files.
+ */
+void
+mput(int argc, char *argv[])
+{
+ extern int optind, optreset;
+ int ch, i, restartit = 0;
+ sig_t oldintr;
+ char *cmd, *tp, *xargv[] = { argv[0], NULL, NULL };
+ const char *errstr;
+ static int depth = 0, max_depth = 0;
+
+ optind = optreset = 1;
+
+ if (depth)
+ depth++;
+
+ while ((ch = getopt(argc, argv, "cd:r")) != -1) {
+ switch(ch) {
+ case 'c':
+ restartit = 1;
+ break;
+ case 'd':
+ max_depth = strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ fprintf(ttyout, "bad depth value, %s: %s\n",
+ errstr, optarg);
+ code = -1;
+ return;
+ }
+ break;
+ case 'r':
+ depth = 1;
+ break;
+ default:
+ goto usage;
+ }
+ }
+
+ if (argc - optind < 1 && !another(&argc, &argv, "local-files")) {
+usage:
+ fprintf(ttyout, "usage: %s [-cr] [-d depth] local-files\n",
+ argv[0]);
+ code = -1;
+ return;
+ }
+
+ argv[optind - 1] = argv[0];
+ argc -= optind - 1;
+ argv += optind - 1;
+
+ mname = argv[0];
+ mflag = 1;
+
+ oldintr = signal(SIGINT, mabort);
+ (void)setjmp(jabort);
+ if (proxy) {
+ char *cp, *tp2, tmpbuf[PATH_MAX];
+
+ while ((cp = remglob(argv, 0, NULL)) != NULL) {
+ if (*cp == '\0') {
+ mflag = 0;
+ continue;
+ }
+ if (mflag && confirm(argv[0], cp)) {
+ tp = cp;
+ if (mcase) {
+ while (*tp && !islower((unsigned char)*tp)) {
+ tp++;
+ }
+ if (!*tp) {
+ tp = cp;
+ tp2 = tmpbuf;
+ while ((*tp2 = *tp) != '\0') {
+ if (isupper((unsigned char)*tp2)) {
+ *tp2 =
+ tolower((unsigned char)*tp2);
+ }
+ tp++;
+ tp2++;
+ }
+ }
+ tp = tmpbuf;
+ }
+ if (ntflag) {
+ tp = dotrans(tp);
+ }
+ if (mapflag) {
+ tp = domap(tp);
+ }
+ if (restartit == 1) {
+ off_t ret;
+
+ if (curtype != type)
+ changetype(type, 0);
+ ret = remotesize(tp, 0);
+ restart_point = (ret < 0) ? 0 : ret;
+ }
+ cmd = restartit ? "APPE" : ((sunique) ?
+ "STOU" : "STOR");
+ sendrequest(cmd, cp, tp,
+ cp != tp || !interactive);
+ restart_point = 0;
+ if (!mflag && fromatty) {
+ if (confirm(argv[0], NULL))
+ mflag = 1;
+ }
+ }
+ }
+ (void)signal(SIGINT, oldintr);
+ mflag = 0;
+ return;
+ }
+
+ for (i = 1; i < argc; i++) {
+ char **cpp;
+ glob_t gl;
+ int flags;
+
+ /* Copy files without word expansion */
+ if (!doglob) {
+ if (mflag && confirm(argv[0], argv[i])) {
+ tp = (ntflag) ? dotrans(argv[i]) : argv[i];
+ tp = (mapflag) ? domap(tp) : tp;
+ if (restartit == 1) {
+ off_t ret;
+
+ if (curtype != type)
+ changetype(type, 0);
+ ret = remotesize(tp, 0);
+ restart_point = (ret < 0) ? 0 : ret;
+ }
+ cmd = restartit ? "APPE" : ((sunique) ?
+ "STOU" : "STOR");
+ sendrequest(cmd, argv[i], tp,
+ tp != argv[i] || !interactive);
+ restart_point = 0;
+ if (!mflag && fromatty) {
+ if (confirm(argv[0], NULL))
+ mflag = 1;
+ }
+ }
+ continue;
+ }
+
+ /* expanding file names */
+ memset(&gl, 0, sizeof(gl));
+ flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
+ if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) {
+ warnx("%s: not found", argv[i]);
+ globfree(&gl);
+ continue;
+ }
+
+ /* traverse all expanded file names */
+ for (cpp = gl.gl_pathv; cpp && *cpp != NULL; cpp++) {
+ struct stat filestat;
+
+ if (!mflag)
+ continue;
+ if (stat(*cpp, &filestat) != 0) {
+ warn("local: %s", *cpp);
+ continue;
+ }
+ if (S_ISDIR(filestat.st_mode) && depth == max_depth)
+ continue;
+ if (!confirm(argv[0], *cpp))
+ continue;
+
+ /*
+ * If file is a directory then create a new one
+ * at the remote machine.
+ */
+ if (S_ISDIR(filestat.st_mode)) {
+ xargv[1] = *cpp;
+ makedir(2, xargv);
+ cd(2, xargv);
+ if (dirchange != 1) {
+ warnx("remote: %s", *cpp);
+ continue;
+ }
+
+ if (chdir(*cpp) != 0) {
+ warn("local: %s", *cpp);
+ goto out;
+ }
+
+ /* Copy the whole directory recursively. */
+ xargv[1] = "*";
+ mput(2, xargv);
+
+ if (chdir("..") != 0) {
+ mflag = 0;
+ warn("local: %s", *cpp);
+ goto out;
+ }
+
+ out:
+ xargv[1] = "..";
+ cd(2, xargv);
+ if (dirchange != 1) {
+ warnx("remote: %s", *cpp);
+ mflag = 0;
+ }
+ continue;
+ }
+
+ tp = (ntflag) ? dotrans(*cpp) : *cpp;
+ tp = (mapflag) ? domap(tp) : tp;
+ if (restartit == 1) {
+ off_t ret;
+
+ if (curtype != type)
+ changetype(type, 0);
+ ret = remotesize(tp, 0);
+ restart_point = (ret < 0) ? 0 : ret;
+ }
+ cmd = restartit ? "APPE" : ((sunique) ?
+ "STOU" : "STOR");
+ sendrequest(cmd, *cpp, tp,
+ *cpp != tp || !interactive);
+ restart_point = 0;
+ if (!mflag && fromatty) {
+ if (confirm(argv[0], NULL))
+ mflag = 1;
+ }
+ }
+ globfree(&gl);
+ }
+
+ (void)signal(SIGINT, oldintr);
+
+ if (depth)
+ depth--;
+ if (depth == 0 || mflag == 0)
+ depth = max_depth = mflag = 0;
+}
+
+void
+reget(int argc, char *argv[])
+{
+
+ (void)getit(argc, argv, 1, "a+w");
+}
+
+char *
+onoff(int bool)
+{
+
+ return (bool ? "on" : "off");
+}
+
+/*
+ * Show status.
+ */
+/*ARGSUSED*/
+void
+status(int argc, char *argv[])
+{
+ int i;
+
+ 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.\n", onoff(passivemode));
+ 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, "Use of PORT/LPRT 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", onoff(editing));
+ if (macnum > 0) {
+ fputs("Macros:\n", ttyout);
+ for (i=0; i<macnum; i++) {
+ fprintf(ttyout, "\t%s\n", macros[i].mac_name);
+ }
+ }
+ code = 0;
+}
+
+/*
+ * Toggle a variable
+ */
+int
+togglevar(int argc, char *argv[], int *var, const char *mesg)
+{
+ if (argc < 2) {
+ *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 {
+ fprintf(ttyout, "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.
+ */
+/*ARGSUSED*/
+void
+setbell(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &bell, "Bell mode");
+}
+
+/*
+ * Set command line editing
+ */
+/*ARGSUSED*/
+void
+setedit(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &editing, "Editing mode");
+ controlediting();
+}
+
+/*
+ * Toggle use of IPv4 EPSV/EPRT
+ */
+/*ARGSUSED*/
+void
+setepsv4(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &epsv4, "EPSV/EPRT on IPv4");
+ epsv4bad = 0;
+}
+
+/*
+ * Turn on packet tracing.
+ */
+/*ARGSUSED*/
+void
+settrace(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &trace, "Packet tracing");
+}
+
+/*
+ * Toggle hash mark printing during transfers, or set hash mark bytecount.
+ */
+/*ARGSUSED*/
+void
+sethash(int argc, char *argv[])
+{
+ if (argc == 1)
+ hash = !hash;
+ else if (argc != 2) {
+ fprintf(ttyout, "usage: %s [on | off | size]\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;
+ const char *errstr;
+
+ nmark = strtonum(argv[1], 1, INT_MAX, &errstr);
+ if (errstr) {
+ fprintf(ttyout, "bytecount value is %s: %s\n",
+ errstr, 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);
+ code = hash;
+}
+
+/*
+ * Turn on printing of server echo's.
+ */
+/*ARGSUSED*/
+void
+setverbose(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &verbose, "Verbose mode");
+}
+
+/*
+ * Toggle PORT/LPRT cmd use before each data connection.
+ */
+/*ARGSUSED*/
+void
+setport(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds");
+}
+
+/*
+ * Toggle transfer progress bar.
+ */
+/*ARGSUSED*/
+void
+setprogress(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &progress, "Progress bar");
+}
+
+/*
+ * Turn on interactive prompting during mget, mput, and mdelete.
+ */
+/*ARGSUSED*/
+void
+setprompt(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &interactive, "Interactive mode");
+}
+
+/*
+ * Toggle gate-ftp mode, or set gate-ftp server
+ */
+/*ARGSUSED*/
+void
+setgate(int argc, char *argv[])
+{
+ static char gsbuf[HOST_NAME_MAX+1];
+
+ if (argc > 3) {
+ fprintf(ttyout, "usage: %s [on | off | host [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 = strdup(argv[2]);
+ if (gateport == NULL)
+ err(1, NULL);
+ }
+ 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.
+ */
+/*ARGSUSED*/
+void
+setglob(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &doglob, "Globbing");
+}
+
+/*
+ * Toggle preserving modification times on retrieved files.
+ */
+/*ARGSUSED*/
+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.
+ */
+/*ARGSUSED*/
+void
+setdebug(int argc, char *argv[])
+{
+ if (argc > 2) {
+ fprintf(ttyout, "usage: %s [on | off | debuglevel]\n", argv[0]);
+ code = -1;
+ return;
+ } else if (argc == 2) {
+ if (strcasecmp(argv[1], "on") == 0)
+ debug = 1;
+ else if (strcasecmp(argv[1], "off") == 0)
+ debug = 0;
+ else {
+ const char *errstr;
+ int val;
+
+ val = strtonum(argv[1], 0, INT_MAX, &errstr);
+ if (errstr) {
+ fprintf(ttyout, "debugging value is %s: %s\n",
+ errstr, argv[1]);
+ code = -1;
+ return;
+ }
+ debug = val;
+ }
+ } else
+ debug = !debug;
+ if (debug)
+ options |= SO_DEBUG;
+ else
+ options &= ~SO_DEBUG;
+ fprintf(ttyout, "Debugging %s (debug=%d).\n", onoff(debug), debug);
+ code = debug > 0;
+}
+
+/*
+ * Set current working directory on local machine.
+ */
+void
+lcd(int argc, char *argv[])
+{
+ char buf[PATH_MAX];
+ char *oldargv1;
+
+ if (argc < 2)
+ argc++, argv[1] = home;
+ if (argc != 2) {
+ fprintf(ttyout, "usage: %s [local-directory]\n", argv[0]);
+ code = -1;
+ return;
+ }
+ oldargv1 = argv[1];
+ if (!globulize(&argv[1])) {
+ code = -1;
+ return;
+ }
+ if (chdir(argv[1]) < 0) {
+ warn("local: %s", argv[1]);
+ code = -1;
+ } else {
+ if (getcwd(buf, sizeof(buf)) != NULL)
+ fprintf(ttyout, "Local directory now %s\n", buf);
+ else
+ warn("getcwd: %s", argv[1]);
+ code = 0;
+ }
+ if (oldargv1 != argv[1]) /* free up after globulize() */
+ free(argv[1]);
+}
+
+/*
+ * Delete a single file.
+ */
+void
+deletecmd(int argc, char *argv[])
+{
+
+ if ((argc < 2 && !another(&argc, &argv, "remote-file")) || argc > 2) {
+ fprintf(ttyout, "usage: %s remote-file\n", argv[0]);
+ code = -1;
+ return;
+ }
+ (void)command("DELE %s", argv[1]);
+}
+
+/*
+ * Delete multiple files.
+ */
+void
+mdelete(int argc, char *argv[])
+{
+ sig_t oldintr;
+ char *cp;
+
+ if (argc < 2 && !another(&argc, &argv, "remote-files")) {
+ fprintf(ttyout, "usage: %s remote-files\n", argv[0]);
+ code = -1;
+ return;
+ }
+ mname = argv[0];
+ mflag = 1;
+ oldintr = signal(SIGINT, mabort);
+ (void)setjmp(jabort);
+ while ((cp = remglob(argv, 0, NULL)) != NULL) {
+ if (*cp == '\0') {
+ mflag = 0;
+ continue;
+ }
+ if (mflag && confirm(argv[0], cp)) {
+ (void)command("DELE %s", cp);
+ if (!mflag && fromatty) {
+ if (confirm(argv[0], NULL))
+ mflag = 1;
+ }
+ }
+ }
+ (void)signal(SIGINT, oldintr);
+ mflag = 0;
+}
+
+/*
+ * Rename a remote file.
+ */
+void
+renamefile(int argc, char *argv[])
+{
+
+ if (argc < 2 && !another(&argc, &argv, "from-name"))
+ goto usage;
+ if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) {
+usage:
+ fprintf(ttyout, "usage: %s from-name to-name\n", argv[0]);
+ code = -1;
+ return;
+ }
+ if (command("RNFR %s", argv[1]) == CONTINUE)
+ (void)command("RNTO %s", argv[2]);
+}
+
+/*
+ * Get a directory listing of remote files.
+ */
+void
+ls(int argc, char *argv[])
+{
+ const char *cmd;
+ char *oldargv2, *globargv2;
+
+ if (argc < 2)
+ argc++, argv[1] = NULL;
+ if (argc < 3)
+ argc++, argv[2] = "-";
+ if (argc > 3) {
+ fprintf(ttyout, "usage: %s [remote-directory [local-file]]\n",
+ argv[0]);
+ code = -1;
+ return;
+ }
+ cmd = strcmp(argv[0], "nlist") == 0 ? "NLST" : "LIST";
+ oldargv2 = argv[2];
+ if (strcmp(argv[2], "-") && !globulize(&argv[2])) {
+ code = -1;
+ return;
+ }
+ globargv2 = argv[2];
+ if (strcmp(argv[2], "-") && *argv[2] != '|' && (!globulize(&argv[2]) ||
+ !confirm("output to local-file:", argv[2]))) {
+ code = -1;
+ goto freels;
+ }
+ recvrequest(cmd, argv[2], argv[1], "w", 0, 0);
+
+ /* flush results in case commands are coming from a pipe */
+ fflush(ttyout);
+freels:
+ if (argv[2] != globargv2) /* free up after globulize() */
+ free(argv[2]);
+ if (globargv2 != oldargv2)
+ free(globargv2);
+}
+
+/*
+ * Get a directory listing of multiple remote files.
+ */
+void
+mls(int argc, char *argv[])
+{
+ sig_t oldintr;
+ int i;
+ char lmode[1], *dest, *odest;
+
+ if (argc < 2 && !another(&argc, &argv, "remote-files"))
+ goto usage;
+ if (argc < 3 && !another(&argc, &argv, "local-file")) {
+usage:
+ fprintf(ttyout, "usage: %s remote-files local-file\n", argv[0]);
+ code = -1;
+ return;
+ }
+ odest = dest = argv[argc - 1];
+ argv[argc - 1] = NULL;
+ if (strcmp(dest, "-") && *dest != '|')
+ if (!globulize(&dest) ||
+ !confirm("output to local-file:", dest)) {
+ code = -1;
+ return;
+ }
+ mname = argv[0];
+ mflag = 1;
+ oldintr = signal(SIGINT, mabort);
+ (void)setjmp(jabort);
+ for (i = 1; mflag && i < argc-1; ++i) {
+ *lmode = (i == 1) ? 'w' : 'a';
+ recvrequest("LIST", dest, argv[i], lmode, 0, 0);
+ if (!mflag && fromatty) {
+ if (confirm(argv[0], NULL))
+ mflag ++;
+ }
+ }
+ (void)signal(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;
+ sig_t old1, old2;
+ char shellnam[PATH_MAX], *shellp, *namep;
+ int wait_status;
+
+ old1 = signal (SIGINT, SIG_IGN);
+ old2 = signal (SIGQUIT, SIG_IGN);
+ if ((pid = fork()) == 0) {
+ for (pid = 3; pid < 20; pid++)
+ (void)close(pid);
+ (void)signal(SIGINT, SIG_DFL);
+ (void)signal(SIGQUIT, SIG_DFL);
+ shellp = getenv("SHELL");
+ if (shellp == NULL || *shellp == '\0')
+ shellp = _PATH_BSHELL;
+ namep = strrchr(shellp, '/');
+ if (namep == NULL)
+ namep = shellp;
+ shellnam[0] = '-';
+ (void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1);
+ if (strcmp(namep, "sh") != 0)
+ shellnam[0] = '+';
+ if (debug) {
+ fputs(shellp, ttyout);
+ fputc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (argc > 1) {
+ execl(shellp, shellnam, "-c", altarg, (char *)NULL);
+ }
+ else {
+ execl(shellp, shellnam, (char *)NULL);
+ }
+ warn("%s", shellp);
+ code = -1;
+ exit(1);
+ }
+ if (pid > 0)
+ while (wait(&wait_status) != pid)
+ ;
+ (void)signal(SIGINT, old1);
+ (void)signal(SIGQUIT, old2);
+ 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 acctname[80];
+ int n, aflag = 0;
+
+ if (argc < 2)
+ (void)another(&argc, &argv, "username");
+ if (argc < 2 || argc > 4) {
+ fprintf(ttyout, "usage: %s username [password [account]]\n",
+ argv[0]);
+ code = -1;
+ return;
+ }
+ n = command("USER %s", argv[1]);
+ if (n == CONTINUE) {
+ if (argc < 3 )
+ argv[2] = getpass("Password:"), argc++;
+ n = command("PASS %s", argv[2]);
+ }
+ if (n == CONTINUE) {
+ if (argc < 4) {
+ (void)fputs("Account: ", ttyout);
+ (void)fflush(ttyout);
+ if (fgets(acctname, sizeof(acctname), stdin) == NULL) {
+ clearerr(stdin);
+ goto fail;
+ }
+
+ acctname[strcspn(acctname, "\n")] = '\0';
+
+ argv[3] = acctname;
+ argc++;
+ }
+ n = command("ACCT %s", argv[3]);
+ aflag++;
+ }
+ if (n != COMPLETE) {
+ fail:
+ fputs("Login failed.\n", ttyout);
+ return;
+ }
+ if (!aflag && argc == 4) {
+ (void)command("ACCT %s", argv[3]);
+ }
+ connected = -1;
+}
+
+/*
+ * Print working directory on remote machine.
+ */
+/*ARGSUSED*/
+void
+pwd(int argc, char *argv[])
+{
+ int oldverbose = verbose;
+
+ /*
+ * If we aren't verbose, this doesn't do anything!
+ */
+ verbose = 1;
+ if (command("PWD") == ERROR && code == 500) {
+ fputs("PWD command not recognized, trying XPWD.\n", ttyout);
+ (void)command("XPWD");
+ }
+ verbose = oldverbose;
+}
+
+/*
+ * Print working directory on local machine.
+ */
+/* ARGSUSED */
+void
+lpwd(int argc, char *argv[])
+{
+ char buf[PATH_MAX];
+
+ if (getcwd(buf, sizeof(buf)) != NULL)
+ fprintf(ttyout, "Local directory %s\n", buf);
+ else
+ warn("getcwd");
+ code = 0;
+}
+
+/*
+ * Make a directory.
+ */
+void
+makedir(int argc, char *argv[])
+{
+
+ if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
+ argc > 2) {
+ fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
+ code = -1;
+ return;
+ }
+ if (command("MKD %s", argv[1]) == ERROR && code == 500) {
+ if (verbose)
+ fputs("MKD command not recognized, trying XMKD.\n", ttyout);
+ (void)command("XMKD %s", argv[1]);
+ }
+}
+
+/*
+ * Remove a directory.
+ */
+void
+removedir(int argc, char *argv[])
+{
+
+ if ((argc < 2 && !another(&argc, &argv, "directory-name")) ||
+ argc > 2) {
+ fprintf(ttyout, "usage: %s directory-name\n", argv[0]);
+ code = -1;
+ return;
+ }
+ if (command("RMD %s", argv[1]) == ERROR && code == 500) {
+ if (verbose)
+ fputs("RMD command not recognized, trying XRMD.\n", ttyout);
+ (void)command("XRMD %s", argv[1]);
+ }
+}
+
+/*
+ * Send a line, verbatim, to the remote machine.
+ */
+void
+quote(int argc, char *argv[])
+{
+
+ if (argc < 2 && !another(&argc, &argv, "command line to send")) {
+ fprintf(ttyout, "usage: %s arg ...\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 < 2 && !another(&argc, &argv, "arguments to SITE command")) {
+ fprintf(ttyout, "usage: %s arg ...\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, len;
+ char buf[BUFSIZ]; /* must be >= sizeof(line) */
+
+ (void)strlcpy(buf, initial, sizeof(buf));
+ if (argc > 1) {
+ for (i = 1, len = strlen(buf); i < argc && len < sizeof(buf)-1; i++) {
+ /* Space for next arg */
+ if (len > 1)
+ buf[len++] = ' ';
+
+ /* Sanity check */
+ if (len >= sizeof(buf) - 1)
+ break;
+
+ /* Copy next argument, NUL terminate always */
+ strlcpy(&buf[len], argv[i], sizeof(buf) - len);
+
+ /* Update string length */
+ len = strlen(buf);
+ }
+ }
+
+ /* Make double (triple?) sure the sucker is NUL terminated */
+ buf[sizeof(buf) - 1] = '\0';
+
+ if (command("%s", buf) == PRELIM) {
+ while (getreply(0) == PRELIM)
+ continue;
+ }
+}
+
+void
+do_chmod(int argc, char *argv[])
+{
+
+ if (argc < 2 && !another(&argc, &argv, "mode"))
+ goto usage;
+ if ((argc < 3 && !another(&argc, &argv, "file")) || argc > 3) {
+usage:
+ fprintf(ttyout, "usage: %s mode file\n", argv[0]);
+ code = -1;
+ return;
+ }
+ (void)command("SITE CHMOD %s %s", argv[1], argv[2]);
+}
+
+void
+do_umask(int argc, char *argv[])
+{
+ int oldverbose = verbose;
+
+ verbose = 1;
+ (void)command(argc == 1 ? "SITE UMASK" : "SITE UMASK %s", argv[1]);
+ verbose = oldverbose;
+}
+
+void
+idle(int argc, char *argv[])
+{
+ int oldverbose = verbose;
+
+ verbose = 1;
+ (void)command(argc == 1 ? "SITE IDLE" : "SITE IDLE %s", argv[1]);
+ verbose = oldverbose;
+}
+
+/*
+ * Ask the other side for help.
+ */
+void
+rmthelp(int argc, char *argv[])
+{
+ int oldverbose = verbose;
+
+ verbose = 1;
+ (void)command(argc == 1 ? "HELP" : "HELP %s", argv[1]);
+ verbose = oldverbose;
+}
+
+/*
+ * Terminate session and exit.
+ */
+/*ARGSUSED*/
+void
+quit(int argc, char *argv[])
+{
+
+ if (connected)
+ disconnect(0, 0);
+ pswitch(1);
+ if (connected) {
+ disconnect(0, 0);
+ }
+ exit(0);
+}
+
+void
+account(int argc, char *argv[])
+{
+ char *ap;
+
+ if (argc > 2) {
+ fprintf(ttyout, "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);
+}
+
+jmp_buf abortprox;
+
+/* ARGSUSED */
+void
+proxabort(int signo)
+{
+ int save_errno = errno;
+
+ alarmtimer(0);
+ if (!proxy) {
+ pswitch(1);
+ }
+ if (connected) {
+ proxflag = 1;
+ }
+ else {
+ proxflag = 0;
+ }
+ pswitch(0);
+ errno = save_errno;
+ longjmp(abortprox, 1);
+}
+
+void
+doproxy(int argc, char *argv[])
+{
+ struct cmd *c;
+ int cmdpos;
+ sig_t oldintr;
+
+ if (argc < 2 && !another(&argc, &argv, "command")) {
+ fprintf(ttyout, "usage: %s command\n", argv[0]);
+ code = -1;
+ return;
+ }
+ c = getcmd(argv[1]);
+ if (c == (struct cmd *) -1) {
+ fputs("?Ambiguous command.\n", ttyout);
+ (void)fflush(ttyout);
+ code = -1;
+ return;
+ }
+ if (c == 0) {
+ fputs("?Invalid command.\n", ttyout);
+ (void)fflush(ttyout);
+ code = -1;
+ return;
+ }
+ if (!c->c_proxy) {
+ fputs("?Invalid proxy command.\n", ttyout);
+ (void)fflush(ttyout);
+ code = -1;
+ return;
+ }
+ if (setjmp(abortprox)) {
+ code = -1;
+ return;
+ }
+ oldintr = signal(SIGINT, proxabort);
+ pswitch(1);
+ if (c->c_conn && !connected) {
+ fputs("Not connected.\n", ttyout);
+ (void)fflush(ttyout);
+ pswitch(0);
+ (void)signal(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);
+ (*c->c_handler)(argc-1, argv+1);
+ if (connected) {
+ proxflag = 1;
+ }
+ else {
+ proxflag = 0;
+ }
+ pswitch(0);
+ (void)signal(SIGINT, oldintr);
+}
+
+void
+setcase(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &mcase, "Case mapping");
+}
+
+void
+setcr(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &crflag, "Carriage Return stripping");
+}
+
+void
+setntrans(int argc, char *argv[])
+{
+ 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));
+}
+
+void
+setnmap(int argc, char *argv[])
+{
+ char *cp;
+
+ if (argc == 1) {
+ mapflag = 0;
+ fputs("Nmap off.\n", ttyout);
+ code = mapflag;
+ return;
+ }
+ if ((argc < 3 && !another(&argc, &argv, "outpattern")) || argc > 3) {
+ fprintf(ttyout, "usage: %s [inpattern outpattern]\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)strncpy(mapin, altarg, PATH_MAX - 1);
+ while (*++cp == ' ')
+ continue;
+ (void)strncpy(mapout, cp, PATH_MAX - 1);
+}
+
+void
+setpassive(int argc, char *argv[])
+{
+
+ code = togglevar(argc, argv, &passivemode,
+ verbose ? "Passive mode" : NULL);
+}
+
+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");
+}
+
+/* change directory to parent directory */
+/* ARGSUSED */
+void
+cdup(int argc, char *argv[])
+{
+ int r;
+
+ 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;
+}
+
+/*
+ * Restart transfer at specific point
+ */
+void
+restart(int argc, char *argv[])
+{
+ quad_t nrestart_point;
+ char *ep;
+
+ if (argc != 2)
+ fputs("restart: offset not specified.\n", ttyout);
+ else {
+ nrestart_point = strtoq(argv[1], &ep, 10);
+ if (nrestart_point == LLONG_MAX || *ep != '\0')
+ fputs("restart: invalid offset.\n", ttyout);
+ else {
+ fprintf(ttyout, "Restarting at %lld. Execute get, put "
+ "or append to initiate transfer\n",
+ (long long)nrestart_point);
+ restart_point = nrestart_point;
+ }
+ }
+}
+
+/*
+ * Show remote system type
+ */
+/* ARGSUSED */
+void
+syst(int argc, char *argv[])
+{
+
+ (void)command("SYST");
+}
+
+void
+macdef(int argc, char *argv[])
+{
+ char *tmp;
+ int c;
+
+ 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) {
+ fprintf(ttyout, "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 < 2 && !another(&argc, &argv, "file")) || argc > 2) {
+ fprintf(ttyout, "usage: %s file\n", argv[0]);
+ code = -1;
+ return;
+ }
+ size = remotesize(argv[1], 1);
+ if (size != -1)
+ fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size);
+ code = size;
+}
+
+/*
+ * Get last modification time of file on remote machine
+ */
+void
+modtime(int argc, char *argv[])
+{
+ time_t mtime;
+
+ if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
+ fprintf(ttyout, "usage: %s 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;
+}
+
+/*
+ * Show status on remote machine
+ */
+void
+rmtstatus(int argc, char *argv[])
+{
+
+ (void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]);
+}
+
+/*
+ * Get file if modtime is more recent than current file
+ */
+void
+newer(int argc, char *argv[])
+{
+
+ (void)getit(argc, argv, -1, "w");
+}
+
+/*
+ * Display one file through $PAGER (defaults to "more").
+ */
+void
+page(int argc, char *argv[])
+{
+ int orestart_point, ohash, overbose;
+ char *p, *pager, *oldargv1;
+
+ if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) {
+ fprintf(ttyout, "usage: %s file\n", argv[0]);
+ code = -1;
+ return;
+ }
+ oldargv1 = argv[1];
+ if (!globulize(&argv[1])) {
+ code = -1;
+ return;
+ }
+ p = getenv("PAGER");
+ if (p == NULL || (*p == '\0'))
+ p = PAGER;
+ if (asprintf(&pager, "|%s", p) == -1)
+ errx(1, "Can't allocate memory for $PAGER");
+
+ orestart_point = restart_point;
+ ohash = hash;
+ overbose = verbose;
+ restart_point = hash = verbose = 0;
+ recvrequest("RETR", pager, argv[1], "r+w", 1, 0);
+ (void)free(pager);
+ restart_point = orestart_point;
+ hash = ohash;
+ verbose = overbose;
+ if (oldargv1 != argv[1]) /* free up after globulize() */
+ free(argv[1]);
+}
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/cmds.h b/usr.bin/ftp/cmds.h
new file mode 100644
index 0000000..1ddae53
--- /dev/null
+++ b/usr.bin/ftp/cmds.h
@@ -0,0 +1,83 @@
+/* $OpenBSD: cmds.h,v 1.2 2015/01/30 04:45:45 tedu Exp $ */
+
+/*
+ * Copyright (c) 2009 Martynas Venckus <martynas@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+void setascii(int, char **);
+void setftmode(int, char **);
+void setform(int, char **);
+void setstruct(int, char **);
+void reput(int, char **);
+void put(int, char **);
+void putit(int, char **, int);
+void mput(int, char **);
+void reget(int, char **);
+char *onoff(int);
+void status(int, char **);
+int togglevar(int, char **, int *, const char *);
+void setbell(int, char **);
+void setedit(int, char **);
+void setepsv4(int, char **);
+void settrace(int, char **);
+void sethash(int, char **);
+void setverbose(int, char **);
+void setport(int, char **);
+void setprogress(int, char **);
+void setprompt(int, char **);
+void setgate(int, char **);
+void setglob(int, char **);
+void setpreserve(int, char **);
+void setdebug(int, char **);
+void lcd(int, char **);
+void deletecmd(int, char **);
+void mdelete(int, char **);
+void renamefile(int, char **);
+void ls(int, char **);
+void mls(int, char **);
+void shell(int, char **);
+void user(int, char **);
+void pwd(int, char **);
+void lpwd(int, char **);
+void makedir(int, char **);
+void removedir(int, char **);
+void quote(int, char **);
+void site(int, char **);
+void quote1(const char *, int, char **);
+void do_chmod(int, char **);
+void do_umask(int, char **);
+void idle(int, char **);
+void rmthelp(int, char **);
+void quit(int, char **);
+void account(int, char **);
+void proxabort(int);
+void doproxy(int, char **);
+void setcase(int, char **);
+void setcr(int, char **);
+void setntrans(int, char **);
+void setnmap(int, char **);
+void setpassive(int, char **);
+void setsunique(int, char **);
+void setrunique(int, char **);
+void cdup(int, char **);
+void restart(int, char **);
+void syst(int, char **);
+void macdef(int, char **);
+void sizecmd(int, char **);
+void modtime(int, char **);
+void rmtstatus(int, char **);
+void newer(int, char **);
+void page(int, char **);
+
diff --git a/usr.bin/ftp/cmdtab.c b/usr.bin/ftp/cmdtab.c
new file mode 100644
index 0000000..40389c3
--- /dev/null
+++ b/usr.bin/ftp/cmdtab.c
@@ -0,0 +1,215 @@
+/* $OpenBSD: cmdtab.c,v 1.28 2015/01/30 04:45:45 tedu Exp $ */
+/* $NetBSD: cmdtab.c,v 1.17 1997/08/18 10:20:17 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+#ifndef SMALL
+
+#include <stdio.h>
+#include "ftp_var.h"
+#include "cmds.h"
+
+/*
+ * User FTP -- Command Tables.
+ */
+
+char accounthelp[] = "send account command to remote server";
+char appendhelp[] = "append to a file";
+char asciihelp[] = "set ascii transfer type";
+char beephelp[] = "beep when command completed";
+char binaryhelp[] = "set binary transfer type";
+char casehelp[] = "toggle mget upper/lower case id mapping";
+char cdhelp[] = "change remote working directory";
+char cduphelp[] = "change remote working directory to parent directory";
+char chmodhelp[] = "change file permissions of remote file";
+char connecthelp[] = "connect to remote ftp server";
+char crhelp[] = "toggle carriage return stripping on ascii gets";
+char debughelp[] = "toggle/set debugging mode";
+char deletehelp[] = "delete remote file";
+char dirhelp[] = "list contents of remote directory";
+char disconhelp[] = "terminate ftp session";
+char domachelp[] = "execute macro";
+char edithelp[] = "toggle command line editing";
+char epsv4help[] = "toggle use of EPSV/EPRT on IPv4 ftp";
+char formhelp[] = "set file transfer format";
+char gatehelp[] = "toggle gate-ftp; specify host[:port] to change proxy";
+char globhelp[] = "toggle metacharacter expansion of local file names";
+char hashhelp[] = "toggle printing `#' marks; specify number to set size";
+char helphelp[] = "print local help information";
+char idlehelp[] = "get (set) idle timer on remote side";
+char lcdhelp[] = "change local working directory";
+char lpwdhelp[] = "print local working directory";
+char lshelp[] = "list contents of remote directory";
+char macdefhelp[] = "define a macro";
+char mdeletehelp[] = "delete multiple files";
+char mdirhelp[] = "list contents of multiple remote directories";
+char mgethelp[] = "get multiple files";
+char mkdirhelp[] = "make directory on the remote machine";
+char mlshelp[] = "list contents of multiple remote directories";
+char modehelp[] = "set file transfer mode";
+char modtimehelp[] = "show last modification time of remote file";
+char mputhelp[] = "send multiple files";
+char newerhelp[] = "get file if remote file is newer than local file ";
+char nlisthelp[] = "nlist contents of remote directory";
+char nmaphelp[] = "set templates for default file name mapping";
+char ntranshelp[] = "set translation table for default file name mapping";
+char pagehelp[] = "view a remote file through your pager";
+char passivehelp[] = "toggle passive transfer mode";
+char porthelp[] = "toggle use of PORT/LPRT cmd for each data connection";
+char preservehelp[] ="toggle preservation of modification time of "
+ "retrieved files";
+char progresshelp[] ="toggle transfer progress meter";
+char prompthelp[] = "toggle interactive prompting on multiple commands";
+char proxyhelp[] = "issue command on alternate connection";
+char pwdhelp[] = "print working directory on remote machine";
+char quithelp[] = "terminate ftp session and exit";
+char quotehelp[] = "send arbitrary ftp command";
+char receivehelp[] = "receive file";
+char regethelp[] = "get file restarting at end of local file";
+char reputhelp[] = "put file restarting at end of remote file";
+char remotehelp[] = "get help from remote server";
+char renamehelp[] = "rename file";
+char resethelp[] = "clear queued command replies";
+char restarthelp[]= "restart file transfer at bytecount";
+char rmdirhelp[] = "remove directory on the remote machine";
+char rmtstatushelp[]="show status of remote machine";
+char runiquehelp[] = "toggle store unique for local files";
+char sendhelp[] = "send one file";
+char shellhelp[] = "escape to the shell";
+char sitehelp[] = "send site specific command to remote server\n"
+ "\t\tTry \"rhelp site\" or \"site help\" "
+ "for more information";
+char sizecmdhelp[] = "show size of remote file";
+char statushelp[] = "show current status";
+char structhelp[] = "set file transfer structure";
+char suniquehelp[] = "toggle store unique on remote machine";
+char systemhelp[] = "show remote system type";
+char tracehelp[] = "toggle packet tracing";
+char typehelp[] = "set file transfer type";
+char umaskhelp[] = "get (set) umask on remote side";
+char userhelp[] = "send new user information";
+char verbosehelp[] = "toggle verbose mode";
+
+#define CMPL(x) __STRING(x),
+#define CMPL0 "",
+#define H(x) x
+
+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 },
+ { "debug", H(debughelp), 0, 0, 0, CMPL0 setdebug },
+ { "delete", H(deletehelp), 0, 1, 1, CMPL(r) deletecmd },
+ { "dir", H(dirhelp), 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 },
+ { "form", H(formhelp), 0, 1, 1, CMPL0 setform },
+ { "ftp", H(connecthelp), 0, 0, 1, CMPL0 setpeer },
+ { "get", H(receivehelp), 1, 1, 1, CMPL(rl) get },
+ { "gate", H(gatehelp), 0, 0, 0, CMPL0 setgate },
+ { "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 idle },
+ { "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 },
+ { "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(mdirhelp), 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 },
+ { "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 },
+ { "msend", H(mputhelp), 1, 1, 1, CMPL(L) mput },
+ { "newer", H(newerhelp), 1, 1, 1, CMPL(r) newer },
+ { "nlist", H(nlisthelp), 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 },
+ { "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 },
+ { "recv", H(receivehelp), 1, 1, 1, CMPL(rl) get },
+ { "reget", H(regethelp), 1, 1, 1, CMPL(rl) reget },
+ { "rename", H(renamehelp), 0, 1, 1, CMPL(rr) renamefile },
+ { "reput", H(reputhelp), 1, 1, 1, CMPL(lr) reput },
+ { "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 },
+ { "site", H(sitehelp), 0, 1, 1, CMPL0 site },
+ { "size", H(sizecmdhelp), 1, 1, 1, CMPL(r) sizecmd },
+ { "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 },
+ { "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 },
+ { "user", H(userhelp), 0, 1, 1, CMPL0 user },
+ { "verbose", H(verbosehelp), 0, 0, 0, CMPL0 setverbose },
+ { "?", H(helphelp), 0, 0, 1, CMPL(C) help },
+ { 0 }
+};
+
+int NCMDS = (sizeof(cmdtab) / sizeof(cmdtab[0])) - 1;
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/complete.c b/usr.bin/ftp/complete.c
new file mode 100644
index 0000000..5e0b00a
--- /dev/null
+++ b/usr.bin/ftp/complete.c
@@ -0,0 +1,382 @@
+/* $OpenBSD: complete.c,v 1.29 2015/10/18 03:04:11 mmcc Exp $ */
+/* $NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1997 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.
+ *
+ * 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.
+ */
+
+#ifndef SMALL
+
+/*
+ * FTP user program - command and file completion routines
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ftp_var.h"
+
+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_remote(char *, int);
+static void ftpvis(char *, size_t, const char *, size_t);
+
+static int
+comparstr(const void *a, const void *b)
+{
+ return (strcmp(*(char **)a, *(char **)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
+ */
+static unsigned char
+complete_ambiguous(char *word, int list, StringList *words)
+{
+ char insertstr[PATH_MAX * 2];
+ char *lastmatch;
+ 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 */
+ char *p = words->sl_str[0] + wordlen;
+ ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
+ if (el_insertstr(el, insertstr) == -1)
+ return (CC_ERROR);
+ else
+ return (CC_REFRESH);
+ }
+
+ if (!list) {
+ 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
+ /*
+ * XXX: really want CC_REFRESH_BEEP
+ */
+ return (CC_REFRESH);
+ }
+ }
+
+ 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 = 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)
+ sl_add(words, c->c_name);
+ }
+
+ rv = complete_ambiguous(word, list, words);
+ sl_free(words, 0);
+ return (rv);
+}
+
+/*
+ * Complete a local file
+ */
+static unsigned char
+complete_local(char *word, int list)
+{
+ StringList *words;
+ char dir[PATH_MAX];
+ char *file;
+ DIR *dd;
+ struct dirent *dp;
+ unsigned char rv;
+
+ 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, (size_t)(file - word) + 1);
+ }
+ file++;
+ }
+
+ if ((dd = opendir(dir)) == NULL)
+ return (CC_ERROR);
+
+ words = sl_init();
+
+ for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
+ if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+ continue;
+ if (strlen(file) > dp->d_namlen)
+ continue;
+ if (strncmp(file, dp->d_name, strlen(file)) == 0) {
+ char *tcp;
+
+ tcp = strdup(dp->d_name);
+ if (tcp == NULL)
+ errx(1, "Can't allocate memory for local dir");
+ sl_add(words, tcp);
+ }
+ }
+ closedir(dd);
+
+ rv = complete_ambiguous(file, list, words);
+ sl_free(words, 1);
+ return (rv);
+}
+
+/*
+ * Complete a remote file
+ */
+static unsigned char
+complete_remote(char *word, int list)
+{
+ static StringList *dirlist;
+ static char lastdir[PATH_MAX];
+ StringList *words;
+ char dir[PATH_MAX];
+ char *file, *cp;
+ int i;
+ unsigned char rv;
+
+ char *dummyargv[] = { "complete", dir, NULL };
+
+ if ((file = strrchr(word, '/')) == NULL) {
+ dir[0] = '.';
+ dir[1] = '\0';
+ file = word;
+ } else {
+ cp = file;
+ while (*cp == '/' && cp > word)
+ cp--;
+ (void)strlcpy(dir, word, (size_t)(cp - word + 2));
+ file++;
+ }
+
+ if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */
+ char *emesg;
+
+ sl_free(dirlist, 1);
+ dirlist = sl_init();
+
+ mflag = 1;
+ emesg = NULL;
+ if (debug)
+ (void)putc('\n', ttyout);
+ 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 = strdup(tcp);
+ if (tcp == NULL)
+ errx(1, "Can't allocate memory for remote dir");
+ 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 = 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)
+ 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;
+ size_t len;
+
+ ch = ch; /* not used */
+ lf = el_line(el);
+ len = lf->lastchar - lf->buffer;
+ if (len >= sizeof(line))
+ return (CC_ERROR);
+ (void)memcpy(line, lf->buffer, len);
+ line[len] = '\0';
+ 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], cursor_argo) == 0)
+ dolist = 1;
+ else if (cursor_argo)
+ memcpy(word, margv[cursor_argc], cursor_argo);
+ 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);
+
+ switch (c->c_complete[cursor_argc - 1]) {
+ case 'l': /* local complete */
+ case 'L':
+ return (complete_local(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));
+ case 'c': /* command complete */
+ case 'C':
+ return (complete_command(word, dolist));
+ case 'n': /* no complete */
+ return (CC_ERROR);
+ }
+
+ return (CC_ERROR);
+}
+
+/*
+ * Copy characters from src into dst, \ quoting characters that require it.
+ */
+static void
+ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
+{
+ size_t di, si;
+
+ di = si = 0;
+ while (di + 1 < dstlen && si < srclen && src[si] != '\0') {
+ switch (src[si]) {
+ case '\\':
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ case '"':
+ /* Need room for two characters and NUL, avoiding
+ * incomplete escape sequences at end of dst. */
+ if (di + 3 >= dstlen)
+ break;
+ dst[di++] = '\\';
+ /* FALLTHROUGH */
+ default:
+ dst[di++] = src[si++];
+ }
+ }
+ if (dstlen != 0)
+ dst[di] = '\0';
+}
+#endif /* !SMALL */
diff --git a/usr.bin/ftp/cookie.c b/usr.bin/ftp/cookie.c
new file mode 100644
index 0000000..266f24d
--- /dev/null
+++ b/usr.bin/ftp/cookie.c
@@ -0,0 +1,231 @@
+/* $OpenBSD: cookie.c,v 1.5 2009/05/05 19:35:30 martynas Exp $ */
+
+/*
+ * Copyright (c) 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SMALL
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "ftp_var.h"
+
+struct cookie {
+ TAILQ_ENTRY(cookie) entry;
+ TAILQ_ENTRY(cookie) tempentry;
+ u_int8_t flags;
+#define F_SECURE 0x01
+#define F_TAILMATCH 0x02
+#define F_NOEXPIRY 0x04
+#define F_MATCHPATH 0x08
+ time_t expires;
+ char *domain;
+ char *path;
+ char *key;
+ char *val;
+};
+TAILQ_HEAD(cookiejar, cookie);
+
+typedef enum {
+ DOMAIN = 0, TAILMATCH = 1, PATH = 2, SECURE = 3,
+ EXPIRES = 4, NAME = 5, VALUE = 6, DONE = 7
+} field_t;
+
+static struct cookiejar jar;
+
+void
+cookie_load(void)
+{
+ field_t field;
+ size_t len;
+ time_t date;
+ char *line;
+ char *lbuf;
+ char *param;
+ const char *estr;
+ FILE *fp;
+ struct cookie *ck;
+
+ if (cookiefile == NULL)
+ return;
+
+ TAILQ_INIT(&jar);
+ fp = fopen(cookiefile, "r");
+ if (fp == NULL)
+ err(1, "cannot open cookie file %s", cookiefile);
+ date = time(NULL);
+ lbuf = NULL;
+ while ((line = fgetln(fp, &len)) != NULL) {
+ if (line[len - 1] == '\n') {
+ line[len - 1] = '\0';
+ --len;
+ } else {
+ if ((lbuf = malloc(len + 1)) == NULL)
+ err(1, NULL);
+ memcpy(lbuf, line, len);
+ lbuf[len] = '\0';
+ line = lbuf;
+ }
+ line[strcspn(line, "\r")] = '\0';
+
+ line += strspn(line, " \t");
+ if ((*line == '#') || (*line == '\0')) {
+ continue;
+ }
+ field = DOMAIN;
+ ck = calloc(1, sizeof(*ck));
+ if (ck == NULL)
+ err(1, NULL);
+ while ((param = strsep(&line, "\t")) != NULL) {
+ switch (field) {
+ case DOMAIN:
+ if (*param == '.') {
+ if (asprintf(&ck->domain,
+ "*%s", param) == -1)
+ err(1, NULL);
+ } else {
+ ck->domain = strdup(param);
+ if (ck->domain == NULL)
+ err(1, NULL);
+ }
+ break;
+ case TAILMATCH:
+ if (strcasecmp(param, "TRUE") == 0) {
+ ck->flags |= F_TAILMATCH;
+ } else if (strcasecmp(param, "FALSE") != 0) {
+ errx(1, "invalid cookie file");
+ }
+ break;
+ case PATH:
+ if (strcmp(param, "/") != 0) {
+ ck->flags |= F_MATCHPATH;
+ if (asprintf(&ck->path,
+ "%s*", param) == -1)
+ err(1, NULL);
+ }
+ break;
+ case SECURE:
+ if (strcasecmp(param, "TRUE") == 0) {
+ ck->flags |= F_SECURE;
+ } else if (strcasecmp(param, "FALSE") != 0) {
+ errx(1, "invalid cookie file");
+ }
+ break;
+ case EXPIRES:
+ /*
+ * rely on sizeof(time_t) being 4
+ */
+ ck->expires = strtonum(param, 0,
+ INT_MAX, &estr);
+ if (estr) {
+ if (errno == ERANGE)
+ ck->flags |= F_NOEXPIRY;
+ else
+ errx(1, "invalid cookie file");
+ }
+ break;
+ case NAME:
+ ck->key = strdup(param);
+ if (ck->key == NULL)
+ err(1, NULL);
+ break;
+ case VALUE:
+ ck->val = strdup(param);
+ if (ck->val == NULL)
+ err(1, NULL);
+ break;
+ case DONE:
+ errx(1, "invalid cookie file");
+ break;
+ }
+ field++;
+ }
+ if (field != DONE)
+ errx(1, "invalid cookie file");
+ if (ck->expires < date && !(ck->flags & F_NOEXPIRY)) {
+ free(ck->val);
+ free(ck->key);
+ free(ck->path);
+ free(ck->domain);
+ free(ck);
+ } else
+ TAILQ_INSERT_TAIL(&jar, ck, entry);
+ }
+ free(lbuf);
+ fclose(fp);
+}
+
+void
+cookie_get(const char *domain, const char *path, int secure, char **pstr)
+{
+ size_t len;
+ size_t headlen;
+ char *head;
+ char *str;
+ struct cookie *ck;
+ struct cookiejar tempjar;
+
+ *pstr = NULL;
+
+ if (cookiefile == NULL)
+ return;
+
+ TAILQ_INIT(&tempjar);
+ len = strlen("Cookie\r\n");
+
+ TAILQ_FOREACH(ck, &jar, entry) {
+ if (fnmatch(ck->domain, domain, 0) == 0 &&
+ (secure || !(ck->flags & F_SECURE))) {
+
+ if (ck->flags & F_MATCHPATH &&
+ fnmatch(ck->path, path, 0) != 0)
+ continue;
+
+ len += strlen(ck->key) + strlen(ck->val) +
+ strlen("; =");
+ TAILQ_INSERT_TAIL(&tempjar, ck, tempentry);
+ }
+ }
+ if (TAILQ_EMPTY(&tempjar))
+ return;
+ len += 1;
+ str = malloc(len);
+ if (str == NULL)
+ err(1, NULL);
+
+ (void)strlcpy(str, "Cookie:", len);
+ TAILQ_FOREACH(ck, &tempjar, tempentry) {
+ head = str + strlen(str);
+ headlen = len - strlen(str);
+
+ snprintf(head, headlen, "%s %s=%s",
+ (ck == TAILQ_FIRST(&tempjar))? "" : ";", ck->key, ck->val);
+ }
+ if (strlcat(str, "\r\n", len) >= len)
+ errx(1, "cookie header truncated");
+ *pstr = str;
+}
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/domacro.c b/usr.bin/ftp/domacro.c
new file mode 100644
index 0000000..72b59b5
--- /dev/null
+++ b/usr.bin/ftp/domacro.c
@@ -0,0 +1,149 @@
+/* $OpenBSD: domacro.c,v 1.18 2015/10/18 03:04:11 mmcc Exp $ */
+/* $NetBSD: domacro.c,v 1.10 1997/07/20 09:45:45 lukem 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.
+ */
+
+#ifndef SMALL
+
+#include <ctype.h>
+#include <signal.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 < 2 && !another(&argc, &argv, "macro name")) {
+ fprintf(ttyout, "usage: %s macro-name\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;
+ }
+ /* FALLTHROUGH */
+ 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);
+ fputc('\n', ttyout);
+ }
+ (*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;
+ }
+}
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/extern.h b/usr.bin/ftp/extern.h
new file mode 100644
index 0000000..8df91d5
--- /dev/null
+++ b/usr.bin/ftp/extern.h
@@ -0,0 +1,148 @@
+/* $OpenBSD: extern.h,v 1.42 2014/01/23 00:39:15 deraadt Exp $ */
+/* $NetBSD: extern.h,v 1.17 1997/08/18 10:20:19 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+/*-
+ * 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
+ */
+
+#include <sys/types.h>
+
+void abort_remote(FILE *);
+void abortpt(int);
+void abortrecv(int);
+void alarmtimer(int);
+int another(int *, char ***, const char *);
+int auto_fetch(int, char **, char *);
+void blkfree(char **);
+void cdup(int, char **);
+void cmdabort(int);
+void cmdscanner(int);
+int command(const char *, ...);
+int confirm(const char *, const char *);
+FILE *dataconn(const char *);
+int foregroundproc(void);
+int fileindir(const char *, const char *);
+struct cmd *getcmd(const char *);
+int getreply(int);
+int globulize(char **);
+char *gunique(const char *);
+void help(int, char **);
+char *hookup(char *, char *);
+int initconn(void);
+void intr(void);
+int isurl(const char *);
+int ftp_login(const char *, char *, char *);
+void lostpeer(void);
+void makeargv(void);
+void progressmeter(int, const char *);
+char *prompt(void);
+void proxtrans(const char *, const char *, const char *);
+void psabort(int);
+void psummary(int);
+void pswitch(int);
+void ptransfer(int);
+void recvrequest(const char *, const char *, const char *,
+ const char *, int, int);
+char *remglob(char **, int, char **);
+#ifndef SMALL
+#endif /* !SMALL */
+off_t remotesize(const char *, int);
+time_t remotemodtime(const char *, int);
+void reset(int, char **);
+void rmthelp(int, char **);
+void sethash(int, char **);
+void setpeer(int, char **);
+void setttywidth(int);
+char *slurpstring(void);
+void usage(void);
+
+#ifndef SMALL
+void abortsend(int);
+unsigned char complete(EditLine *, int);
+void controlediting(void);
+void cookie_get(const char *, const char *, int, char **);
+void cookie_load(void);
+void domacro(int, char **);
+void list_vertical(StringList *);
+void parse_list(char **, char *);
+char *remglob2(char **, int, char **, FILE **ftemp, char *type);
+int ruserpass(const char *, char **, char **, char **);
+void sendrequest(const char *, const char *, const char *, int);
+#endif /* !SMALL */
+
+extern jmp_buf abortprox;
+extern int abrtflag;
+extern FILE *cout;
+extern int data;
+extern char *home;
+extern jmp_buf jabort;
+extern int family;
+extern int proxy;
+extern char reply_string[];
+extern off_t restart_point;
+extern int keep_alive_timeout;
+extern int pipeout;
+extern char *action;
+
+#ifndef SMALL
+extern int NCMDS;
+#endif /* !SMALL */
+
+extern char *__progname; /* from crt0.o */
+
diff --git a/usr.bin/ftp/fetch.c b/usr.bin/ftp/fetch.c
new file mode 100644
index 0000000..233769e
--- /dev/null
+++ b/usr.bin/ftp/fetch.c
@@ -0,0 +1,1602 @@
+/* $OpenBSD: fetch.c,v 1.147 2016/05/27 15:16:16 jsing Exp $ */
+/* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1997 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason Thorpe and 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.
+ *
+ * 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.
+ */
+
+/*
+ * FTP User Program -- Command line file retrieval
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+
+#include <arpa/ftp.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <resolv.h>
+
+#ifndef SMALL
+#include <tls.h>
+#else /* !SMALL */
+struct tls;
+#endif /* !SMALL */
+
+#include "ftp_var.h"
+#include "cmds.h"
+
+static int url_get(const char *, const char *, const char *);
+void aborthttp(int);
+void abortfile(int);
+char hextochar(const char *);
+char *urldecode(const char *);
+char *recode_credentials(const char *_userinfo);
+int ftp_printf(FILE *, struct tls *, const char *, ...) __attribute__((format(printf, 3, 4)));
+char *ftp_readline(FILE *, struct tls *, size_t *);
+size_t ftp_read(FILE *, struct tls *, char *, size_t);
+#ifndef SMALL
+int proxy_connect(int, char *, char *);
+int SSL_vprintf(struct tls *, const char *, va_list);
+char *SSL_readline(struct tls *, size_t *);
+#endif /* !SMALL */
+
+#define FTP_URL "ftp://" /* ftp URL prefix */
+#define HTTP_URL "http://" /* http URL prefix */
+#define HTTPS_URL "https://" /* https URL prefix */
+#define FILE_URL "file:" /* file URL prefix */
+#define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */
+#define HTTP_PROXY "http_proxy" /* env var with http proxy location */
+
+#define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0'))
+
+static const char at_encoding_warning[] =
+ "Extra `@' characters in usernames and passwords should be encoded as %%40";
+
+jmp_buf httpabort;
+
+static int redirect_loop;
+
+/*
+ * Determine whether the character needs encoding, per RFC1738:
+ * - No corresponding graphic US-ASCII.
+ * - Unsafe characters.
+ */
+static int
+unsafe_char(const char *c0)
+{
+ const char *unsafe_chars = " <>\"#{}|\\^~[]`";
+ const unsigned char *c = (const unsigned char *)c0;
+
+ /*
+ * No corresponding graphic US-ASCII.
+ * Control characters and octets not used in US-ASCII.
+ */
+ return (iscntrl(*c) || !isascii(*c) ||
+
+ /*
+ * Unsafe characters.
+ * '%' is also unsafe, if is not followed by two
+ * hexadecimal digits.
+ */
+ strchr(unsafe_chars, *c) != NULL ||
+ (*c == '%' && (!isxdigit(*++c) || !isxdigit(*++c))));
+}
+
+/*
+ * Encode given URL, per RFC1738.
+ * Allocate and return string to the caller.
+ */
+static char *
+url_encode(const char *path)
+{
+ size_t i, length, new_length;
+ char *epath, *epathp;
+
+ length = new_length = strlen(path);
+
+ /*
+ * First pass:
+ * Count unsafe characters, and determine length of the
+ * final URL.
+ */
+ for (i = 0; i < length; i++)
+ if (unsafe_char(path + i))
+ new_length += 2;
+
+ epath = epathp = malloc(new_length + 1); /* One more for '\0'. */
+ if (epath == NULL)
+ err(1, "Can't allocate memory for URL encoding");
+
+ /*
+ * Second pass:
+ * Encode, and copy final URL.
+ */
+ for (i = 0; i < length; i++)
+ if (unsafe_char(path + i)) {
+ snprintf(epathp, 4, "%%" "%02x",
+ (unsigned char)path[i]);
+ epathp += 3;
+ } else
+ *(epathp++) = path[i];
+
+ *epathp = '\0';
+ return (epath);
+}
+
+/*
+ * Retrieve URL, via the proxy in $proxyvar if necessary.
+ * Modifies the string argument given.
+ * Returns -1 on failure, 0 on success
+ */
+static int
+url_get(const char *origline, const char *proxyenv, const char *outfile)
+{
+ char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4];
+ char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL;
+ char *epath, *redirurl, *loctail, *h, *p;
+ int error, i, isftpurl = 0, isfileurl = 0, isredirect = 0, rval = -1;
+ struct addrinfo hints, *res0, *res, *ares = NULL;
+ const char * volatile savefile;
+ char * volatile proxyurl = NULL;
+ char *credentials = NULL;
+ volatile int s = -1, out;
+ volatile sig_t oldintr, oldinti;
+ FILE *fin = NULL;
+ off_t hashbytes;
+ const char *errstr;
+ ssize_t len, wlen;
+ char *proxyhost = NULL;
+#ifndef SMALL
+ char *sslpath = NULL, *sslhost = NULL;
+ char *locbase, *full_host = NULL;
+ const char *scheme;
+ int ishttpurl = 0, ishttpsurl = 0;
+#endif /* !SMALL */
+ struct tls *tls = NULL;
+ int status;
+ int save_errno;
+ const size_t buflen = 128 * 1024;
+
+ direction = "received";
+
+ newline = strdup(origline);
+ if (newline == NULL)
+ errx(1, "Can't allocate memory to parse URL");
+ if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
+ host = newline + sizeof(HTTP_URL) - 1;
+#ifndef SMALL
+ ishttpurl = 1;
+ scheme = HTTP_URL;
+#endif /* !SMALL */
+ } else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
+ host = newline + sizeof(FTP_URL) - 1;
+ isftpurl = 1;
+#ifndef SMALL
+ scheme = FTP_URL;
+#endif /* !SMALL */
+ } else if (strncasecmp(newline, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
+ host = newline + sizeof(FILE_URL) - 1;
+ isfileurl = 1;
+#ifndef SMALL
+ scheme = FILE_URL;
+ } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) {
+ host = newline + sizeof(HTTPS_URL) - 1;
+ ishttpsurl = 1;
+ scheme = HTTPS_URL;
+#endif /* !SMALL */
+ } else
+ errx(1, "url_get: Invalid URL '%s'", newline);
+
+ if (isfileurl) {
+ path = host;
+ } else {
+ path = strchr(host, '/'); /* Find path */
+ if (EMPTYSTRING(path)) {
+ if (outfile) { /* No slash, but */
+ path=strchr(host,'\0'); /* we have outfile. */
+ goto noslash;
+ }
+ if (isftpurl)
+ goto noftpautologin;
+ warnx("No `/' after host (use -o): %s", origline);
+ goto cleanup_url_get;
+ }
+ *path++ = '\0';
+ if (EMPTYSTRING(path) && !outfile) {
+ if (isftpurl)
+ goto noftpautologin;
+ warnx("No filename after host (use -o): %s", origline);
+ goto cleanup_url_get;
+ }
+ }
+
+noslash:
+
+#ifndef SMALL
+ /*
+ * Look for auth header in host, since now host does not
+ * contain the path. Basic auth from RFC 2617, valid
+ * characters for path are in RFC 3986 section 3.3.
+ */
+ if (proxyenv == NULL && (ishttpurl || ishttpsurl)) {
+ if ((p = strchr(host, '@')) != NULL) {
+ *p = '\0';
+ credentials = recode_credentials(host);
+ host = p + 1;
+ }
+ }
+#endif /* SMALL */
+
+ if (outfile)
+ savefile = outfile;
+ else {
+ if (path[strlen(path) - 1] == '/') /* Consider no file */
+ savefile = NULL; /* after dir invalid. */
+ else
+ savefile = basename(path);
+ }
+
+ if (EMPTYSTRING(savefile)) {
+ if (isftpurl)
+ goto noftpautologin;
+ warnx("No filename after directory (use -o): %s", origline);
+ goto cleanup_url_get;
+ }
+
+#ifndef SMALL
+ if (resume && pipeout) {
+ warnx("can't append to stdout");
+ goto cleanup_url_get;
+ }
+#endif /* !SMALL */
+
+ if (!isfileurl && proxyenv != NULL) { /* use proxy */
+#ifndef SMALL
+ if (ishttpsurl) {
+ sslpath = strdup(path);
+ sslhost = strdup(host);
+ if (! sslpath || ! sslhost)
+ errx(1, "Can't allocate memory for https path/host.");
+ }
+#endif /* !SMALL */
+ proxyhost = strdup(host);
+ if (proxyhost == NULL)
+ errx(1, "Can't allocate memory for proxy host.");
+ proxyurl = strdup(proxyenv);
+ if (proxyurl == NULL)
+ errx(1, "Can't allocate memory for proxy URL.");
+ if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
+ host = proxyurl + sizeof(HTTP_URL) - 1;
+ else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0)
+ host = proxyurl + sizeof(FTP_URL) - 1;
+ else {
+ warnx("Malformed proxy URL: %s", proxyenv);
+ goto cleanup_url_get;
+ }
+ if (EMPTYSTRING(host)) {
+ warnx("Malformed proxy URL: %s", proxyenv);
+ goto cleanup_url_get;
+ }
+ if (*--path == '\0')
+ *path = '/'; /* add / back to real path */
+ path = strchr(host, '/'); /* remove trailing / on host */
+ if (!EMPTYSTRING(path))
+ *path++ = '\0'; /* i guess this ++ is useless */
+
+ path = strchr(host, '@'); /* look for credentials in proxy */
+ if (!EMPTYSTRING(path)) {
+ *path = '\0';
+ if (strchr(host, ':') == NULL) {
+ warnx("Malformed proxy URL: %s", proxyenv);
+ goto cleanup_url_get;
+ }
+ credentials = recode_credentials(host);
+ *path = '@'; /* restore @ in proxyurl */
+
+ /*
+ * This removes the password from proxyurl,
+ * filling with stars
+ */
+ for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@';
+ host++)
+ *host = '*';
+
+ host = path + 1;
+ }
+
+ path = newline;
+ }
+
+ if (isfileurl) {
+ struct stat st;
+
+ s = open(path, O_RDONLY);
+ if (s == -1) {
+ warn("Can't open file %s", path);
+ goto cleanup_url_get;
+ }
+
+ if (fstat(s, &st) == -1)
+ filesize = -1;
+ else
+ filesize = st.st_size;
+
+ /* Open the output file. */
+ if (!pipeout) {
+#ifndef SMALL
+ if (resume)
+ out = open(savefile, O_CREAT | O_WRONLY |
+ O_APPEND, 0666);
+
+ else
+#endif /* !SMALL */
+ out = open(savefile, O_CREAT | O_WRONLY |
+ O_TRUNC, 0666);
+ if (out < 0) {
+ warn("Can't open %s", savefile);
+ goto cleanup_url_get;
+ }
+ } else
+ out = fileno(stdout);
+
+#ifndef SMALL
+ if (resume) {
+ if (fstat(out, &st) == -1) {
+ warn("Can't fstat %s", savefile);
+ goto cleanup_url_get;
+ }
+ if (lseek(s, st.st_size, SEEK_SET) == -1) {
+ warn("Can't lseek %s", path);
+ goto cleanup_url_get;
+ }
+ restart_point = st.st_size;
+ }
+#endif /* !SMALL */
+
+ /* Trap signals */
+ oldintr = NULL;
+ oldinti = NULL;
+ if (setjmp(httpabort)) {
+ if (oldintr)
+ (void)signal(SIGINT, oldintr);
+ if (oldinti)
+ (void)signal(SIGINFO, oldinti);
+ goto cleanup_url_get;
+ }
+ oldintr = signal(SIGINT, abortfile);
+
+ bytes = 0;
+ hashbytes = mark;
+ progressmeter(-1, path);
+
+ if ((buf = malloc(buflen)) == NULL)
+ errx(1, "Can't allocate memory for transfer buffer");
+
+ /* Finally, suck down the file. */
+ i = 0;
+ oldinti = signal(SIGINFO, psummary);
+ while ((len = read(s, buf, buflen)) > 0) {
+ bytes += len;
+ for (cp = buf; len > 0; len -= i, cp += i) {
+ if ((i = write(out, cp, len)) == -1) {
+ warn("Writing %s", savefile);
+ signal(SIGINFO, oldinti);
+ goto cleanup_url_get;
+ }
+ else if (i == 0)
+ break;
+ }
+ if (hash && !progress) {
+ while (bytes >= hashbytes) {
+ (void)putc('#', ttyout);
+ hashbytes += mark;
+ }
+ (void)fflush(ttyout);
+ }
+ }
+ signal(SIGINFO, oldinti);
+ if (hash && !progress && bytes > 0) {
+ if (bytes < mark)
+ (void)putc('#', ttyout);
+ (void)putc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (len != 0) {
+ warn("Reading from file");
+ goto cleanup_url_get;
+ }
+ progressmeter(1, NULL);
+ if (verbose)
+ ptransfer(0);
+ (void)signal(SIGINT, oldintr);
+
+ rval = 0;
+ goto cleanup_url_get;
+ }
+
+ if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
+ (hosttail[1] == '\0' || hosttail[1] == ':')) {
+ host++;
+ *hosttail++ = '\0';
+#ifndef SMALL
+ if (asprintf(&full_host, "[%s]", host) == -1)
+ errx(1, "Cannot allocate memory for hostname");
+#endif /* !SMALL */
+ } else
+ hosttail = host;
+
+ portnum = strrchr(hosttail, ':'); /* find portnum */
+ if (portnum != NULL)
+ *portnum++ = '\0';
+
+#ifndef SMALL
+ if (full_host == NULL)
+ if ((full_host = strdup(host)) == NULL)
+ errx(1, "Cannot allocate memory for hostname");
+ if (debug)
+ fprintf(ttyout, "host %s, port %s, path %s, "
+ "save as %s, auth %s.\n",
+ host, portnum, path, savefile, credentials);
+#endif /* !SMALL */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+#ifndef SMALL
+ port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
+#else /* !SMALL */
+ port = portnum ? portnum : httpport;
+#endif /* !SMALL */
+ error = getaddrinfo(host, port, &hints, &res0);
+ /*
+ * If the services file is corrupt/missing, fall back
+ * on our hard-coded defines.
+ */
+ if (error == EAI_SERVICE && port == httpport) {
+ snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
+ error = getaddrinfo(host, pbuf, &hints, &res0);
+#ifndef SMALL
+ } else if (error == EAI_SERVICE && port == httpsport) {
+ snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
+ error = getaddrinfo(host, pbuf, &hints, &res0);
+#endif /* !SMALL */
+ }
+ if (error) {
+ warnx("%s: %s", host, gai_strerror(error));
+ goto cleanup_url_get;
+ }
+
+#ifndef SMALL
+ if (srcaddr) {
+ hints.ai_flags |= AI_NUMERICHOST;
+ error = getaddrinfo(srcaddr, NULL, &hints, &ares);
+ if (error) {
+ warnx("%s: %s", srcaddr, gai_strerror(error));
+ goto cleanup_url_get;
+ }
+ }
+#endif /* !SMALL */
+
+ /* ensure consistent order of the output */
+ if (verbose)
+ setvbuf(ttyout, NULL, _IOLBF, 0);
+
+ s = -1;
+ for (res = res0; res; res = res->ai_next) {
+ if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
+ sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "(unknown)", sizeof(hbuf));
+ if (verbose)
+ fprintf(ttyout, "Trying %s...\n", hbuf);
+
+ s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s == -1) {
+ cause = "socket";
+ continue;
+ }
+
+#ifndef SMALL
+ if (srcaddr) {
+ if (ares->ai_family != res->ai_family) {
+ close(s);
+ s = -1;
+ errno = EINVAL;
+ cause = "bind";
+ continue;
+ }
+ if (bind(s, ares->ai_addr, ares->ai_addrlen) < 0) {
+ save_errno = errno;
+ close(s);
+ errno = save_errno;
+ s = -1;
+ cause = "bind";
+ continue;
+ }
+ }
+#endif /* !SMALL */
+
+again:
+ if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
+ if (errno == EINTR)
+ goto again;
+ save_errno = errno;
+ close(s);
+ errno = save_errno;
+ s = -1;
+ cause = "connect";
+ continue;
+ }
+
+ /* get port in numeric */
+ if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
+ pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
+ port = pbuf;
+ else
+ port = NULL;
+
+#ifndef SMALL
+ if (proxyenv && sslhost)
+ proxy_connect(s, sslhost, credentials);
+#endif /* !SMALL */
+ break;
+ }
+ freeaddrinfo(res0);
+#ifndef SMALL
+ if (srcaddr)
+ freeaddrinfo(ares);
+#endif /* !SMALL */
+ if (s < 0) {
+ warn("%s", cause);
+ goto cleanup_url_get;
+ }
+
+#ifndef SMALL
+ if (ishttpsurl) {
+ if (proxyenv && sslpath) {
+ ishttpsurl = 0;
+ proxyurl = NULL;
+ path = sslpath;
+ }
+ if (sslhost == NULL) {
+ sslhost = strdup(host);
+ if (sslhost == NULL)
+ errx(1, "Can't allocate memory for https host.");
+ }
+ if ((tls = tls_client()) == NULL) {
+ fprintf(ttyout, "failed to create SSL client\n");
+ goto cleanup_url_get;
+ }
+ if (tls_configure(tls, tls_config) != 0) {
+ fprintf(ttyout, "SSL configuration failure: %s\n",
+ tls_error(tls));
+ goto cleanup_url_get;
+ }
+ if (tls_connect_socket(tls, s, sslhost) != 0) {
+ fprintf(ttyout, "SSL failure: %s\n", tls_error(tls));
+ goto cleanup_url_get;
+ }
+ } else {
+ fin = fdopen(s, "r+");
+ }
+#else /* !SMALL */
+ fin = fdopen(s, "r+");
+#endif /* !SMALL */
+
+ if (verbose)
+ fprintf(ttyout, "Requesting %s", origline);
+
+ /*
+ * Construct and send the request. Proxy requests don't want leading /.
+ */
+#ifndef SMALL
+ cookie_get(host, path, ishttpsurl, &buf);
+#endif /* !SMALL */
+
+ epath = url_encode(path);
+ if (proxyurl) {
+ if (verbose)
+ fprintf(ttyout, " (via %s)\n", proxyurl);
+ /*
+ * Host: directive must use the destination host address for
+ * the original URI (path).
+ */
+ if (credentials)
+ ftp_printf(fin, tls, "GET %s HTTP/1.0\r\n"
+ "Proxy-Authorization: Basic %s\r\n"
+ "Host: %s\r\n%s%s\r\n\r\n",
+ epath, credentials,
+ proxyhost, buf ? buf : "", httpuseragent);
+ else
+ ftp_printf(fin, tls, "GET %s HTTP/1.0\r\n"
+ "Host: %s\r\n%s%s\r\n\r\n",
+ epath, proxyhost, buf ? buf : "", httpuseragent);
+ } else {
+#ifndef SMALL
+ if (resume) {
+ struct stat stbuf;
+
+ if (stat(savefile, &stbuf) == 0)
+ restart_point = stbuf.st_size;
+ else
+ restart_point = 0;
+ }
+ if (credentials) {
+ ftp_printf(fin, tls,
+ "GET /%s %s\r\nAuthorization: Basic %s\r\nHost: ",
+ epath, restart_point ?
+ "HTTP/1.1\r\nConnection: close" : "HTTP/1.0",
+ credentials);
+ free(credentials);
+ credentials = NULL;
+ } else
+#endif /* SMALL */
+ ftp_printf(fin, tls, "GET /%s %s\r\nHost: ", epath,
+#ifndef SMALL
+ restart_point ? "HTTP/1.1\r\nConnection: close" :
+#endif /* !SMALL */
+ "HTTP/1.0");
+ if (proxyhost) {
+ ftp_printf(fin, tls, "%s", proxyhost);
+ port = NULL;
+ } else if (strchr(host, ':')) {
+ /*
+ * strip off scoped address portion, since it's
+ * local to node
+ */
+ h = strdup(host);
+ if (h == NULL)
+ errx(1, "Can't allocate memory.");
+ if ((p = strchr(h, '%')) != NULL)
+ *p = '\0';
+ ftp_printf(fin, tls, "[%s]", h);
+ free(h);
+ } else
+ ftp_printf(fin, tls, "%s", host);
+
+ /*
+ * Send port number only if it's specified and does not equal
+ * 80. Some broken HTTP servers get confused if you explicitly
+ * send them the port number.
+ */
+#ifndef SMALL
+ if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0)
+ ftp_printf(fin, tls, ":%s", port);
+ if (restart_point)
+ ftp_printf(fin, tls, "\r\nRange: bytes=%lld-",
+ (long long)restart_point);
+#else /* !SMALL */
+ if (port && strcmp(port, "80") != 0)
+ ftp_printf(fin, tls, ":%s", port);
+#endif /* !SMALL */
+ ftp_printf(fin, tls, "\r\n%s%s\r\n\r\n",
+ buf ? buf : "", httpuseragent);
+ if (verbose)
+ fprintf(ttyout, "\n");
+ }
+ free(epath);
+
+#ifndef SMALL
+ free(buf);
+#endif /* !SMALL */
+ buf = NULL;
+
+ if (fin != NULL && fflush(fin) == EOF) {
+ warn("Writing HTTP request");
+ goto cleanup_url_get;
+ }
+ if ((buf = ftp_readline(fin, tls, &len)) == NULL) {
+ warn("Receiving HTTP reply");
+ goto cleanup_url_get;
+ }
+
+ while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+ buf[--len] = '\0';
+#ifndef SMALL
+ if (debug)
+ fprintf(ttyout, "received '%s'\n", buf);
+#endif /* !SMALL */
+
+ cp = strchr(buf, ' ');
+ if (cp == NULL)
+ goto improper;
+ else
+ cp++;
+
+ strlcpy(ststr, cp, sizeof(ststr));
+ status = strtonum(ststr, 200, 416, &errstr);
+ if (errstr) {
+ warnx("Error retrieving file: %s", cp);
+ goto cleanup_url_get;
+ }
+
+ switch (status) {
+ case 200: /* OK */
+#ifndef SMALL
+ /*
+ * When we request a partial file, and we receive an HTTP 200
+ * it is a good indication that the server doesn't support
+ * range requests, and is about to send us the entire file.
+ * If the restart_point == 0, then we are not actually
+ * requesting a partial file, and an HTTP 200 is appropriate.
+ */
+ if (resume && restart_point != 0) {
+ warnx("Server does not support resume.");
+ restart_point = resume = 0;
+ }
+ /* FALLTHROUGH */
+ case 206: /* Partial Content */
+#endif /* !SMALL */
+ break;
+ case 301: /* Moved Permanently */
+ case 302: /* Found */
+ case 303: /* See Other */
+ case 307: /* Temporary Redirect */
+ isredirect++;
+ if (redirect_loop++ > 10) {
+ warnx("Too many redirections requested");
+ goto cleanup_url_get;
+ }
+ break;
+#ifndef SMALL
+ case 416: /* Requested Range Not Satisfiable */
+ warnx("File is already fully retrieved.");
+ goto cleanup_url_get;
+#endif /* !SMALL */
+ default:
+ warnx("Error retrieving file: %s", cp);
+ goto cleanup_url_get;
+ }
+
+ /*
+ * Read the rest of the header.
+ */
+ free(buf);
+ filesize = -1;
+
+ for (;;) {
+ if ((buf = ftp_readline(fin, tls, &len)) == NULL) {
+ warn("Receiving HTTP reply");
+ goto cleanup_url_get;
+ }
+
+ while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+ buf[--len] = '\0';
+ if (len == 0)
+ break;
+#ifndef SMALL
+ if (debug)
+ fprintf(ttyout, "received '%s'\n", buf);
+#endif /* !SMALL */
+
+ /* Look for some headers */
+ cp = buf;
+#define CONTENTLEN "Content-Length: "
+ if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
+ size_t s;
+ cp += sizeof(CONTENTLEN) - 1;
+ if ((s = strcspn(cp, " \t")))
+ *(cp+s) = 0;
+ filesize = strtonum(cp, 0, LLONG_MAX, &errstr);
+ if (errstr != NULL)
+ goto improper;
+#ifndef SMALL
+ if (restart_point)
+ filesize += restart_point;
+#endif /* !SMALL */
+#define LOCATION "Location: "
+ } else if (isredirect &&
+ strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
+ cp += sizeof(LOCATION) - 1;
+ /*
+ * If there is a colon before the first slash, this URI
+ * is not relative. RFC 3986 4.2
+ */
+ if (cp[strcspn(cp, ":/")] != ':') {
+#ifdef SMALL
+ errx(1, "Relative redirect not supported");
+#else /* SMALL */
+ /* XXX doesn't handle protocol-relative URIs */
+ if (*cp == '/') {
+ locbase = NULL;
+ cp++;
+ } else {
+ locbase = strdup(path);
+ if (locbase == NULL)
+ errx(1, "Can't allocate memory"
+ " for location base");
+ loctail = strchr(locbase, '#');
+ if (loctail != NULL)
+ *loctail = '\0';
+ loctail = strchr(locbase, '?');
+ if (loctail != NULL)
+ *loctail = '\0';
+ loctail = strrchr(locbase, '/');
+ if (loctail == NULL) {
+ free(locbase);
+ locbase = NULL;
+ } else
+ loctail[1] = '\0';
+ }
+ /* Contruct URL from relative redirect */
+ if (asprintf(&redirurl, "%s%s%s%s/%s%s",
+ scheme, full_host,
+ portnum ? ":" : "",
+ portnum ? portnum : "",
+ locbase ? locbase : "",
+ cp) == -1)
+ errx(1, "Cannot build "
+ "redirect URL");
+ free(locbase);
+#endif /* SMALL */
+ } else if ((redirurl = strdup(cp)) == NULL)
+ errx(1, "Cannot allocate memory for URL");
+ loctail = strchr(redirurl, '#');
+ if (loctail != NULL)
+ *loctail = '\0';
+ if (verbose)
+ fprintf(ttyout, "Redirected to %s\n", redirurl);
+ if (fin != NULL)
+ fclose(fin);
+ else if (s != -1)
+ close(s);
+ rval = url_get(redirurl, proxyenv, savefile);
+ free(redirurl);
+ goto cleanup_url_get;
+ }
+ free(buf);
+ }
+
+ /* Open the output file. */
+ if (!pipeout) {
+#ifndef SMALL
+ if (resume)
+ out = open(savefile, O_CREAT | O_WRONLY | O_APPEND,
+ 0666);
+ else
+#endif /* !SMALL */
+ out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC,
+ 0666);
+ if (out < 0) {
+ warn("Can't open %s", savefile);
+ goto cleanup_url_get;
+ }
+ } else
+ out = fileno(stdout);
+
+ /* Trap signals */
+ oldintr = NULL;
+ oldinti = NULL;
+ if (setjmp(httpabort)) {
+ if (oldintr)
+ (void)signal(SIGINT, oldintr);
+ if (oldinti)
+ (void)signal(SIGINFO, oldinti);
+ goto cleanup_url_get;
+ }
+ oldintr = signal(SIGINT, aborthttp);
+
+ bytes = 0;
+ hashbytes = mark;
+ progressmeter(-1, path);
+
+ free(buf);
+
+ /* Finally, suck down the file. */
+ if ((buf = malloc(buflen)) == NULL)
+ errx(1, "Can't allocate memory for transfer buffer");
+ i = 0;
+ len = 1;
+ oldinti = signal(SIGINFO, psummary);
+ while (len > 0) {
+ len = ftp_read(fin, tls, buf, buflen);
+ bytes += len;
+ for (cp = buf, wlen = len; wlen > 0; wlen -= i, cp += i) {
+ if ((i = write(out, cp, wlen)) == -1) {
+ warn("Writing %s", savefile);
+ signal(SIGINFO, oldinti);
+ goto cleanup_url_get;
+ }
+ else if (i == 0)
+ break;
+ }
+ if (hash && !progress) {
+ while (bytes >= hashbytes) {
+ (void)putc('#', ttyout);
+ hashbytes += mark;
+ }
+ (void)fflush(ttyout);
+ }
+ }
+ signal(SIGINFO, oldinti);
+ if (hash && !progress && bytes > 0) {
+ if (bytes < mark)
+ (void)putc('#', ttyout);
+ (void)putc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (len != 0) {
+ warn("Reading from socket");
+ goto cleanup_url_get;
+ }
+ progressmeter(1, NULL);
+ if (
+#ifndef SMALL
+ !resume &&
+#endif /* !SMALL */
+ filesize != -1 && len == 0 && bytes != filesize) {
+ if (verbose)
+ fputs("Read short file.\n", ttyout);
+ goto cleanup_url_get;
+ }
+
+ if (verbose)
+ ptransfer(0);
+ (void)signal(SIGINT, oldintr);
+
+ rval = 0;
+ goto cleanup_url_get;
+
+noftpautologin:
+ warnx(
+ "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
+ goto cleanup_url_get;
+
+improper:
+ warnx("Improper response from %s", host);
+
+cleanup_url_get:
+#ifndef SMALL
+ if (tls != NULL) {
+ tls_close(tls);
+ tls_free(tls);
+ }
+ free(full_host);
+ free(sslhost);
+#endif /* !SMALL */
+ if (fin != NULL)
+ fclose(fin);
+ else if (s != -1)
+ close(s);
+ free(buf);
+ free(proxyhost);
+ free(proxyurl);
+ free(newline);
+ free(credentials);
+ return (rval);
+}
+
+/*
+ * Abort a http retrieval
+ */
+/* ARGSUSED */
+void
+aborthttp(int signo)
+{
+
+ alarmtimer(0);
+ fputs("\nhttp fetch aborted.\n", ttyout);
+ (void)fflush(ttyout);
+ longjmp(httpabort, 1);
+}
+
+/*
+ * Abort a http retrieval
+ */
+/* ARGSUSED */
+void
+abortfile(int signo)
+{
+
+ alarmtimer(0);
+ fputs("\nfile fetch aborted.\n", ttyout);
+ (void)fflush(ttyout);
+ longjmp(httpabort, 1);
+}
+
+/*
+ * Retrieve multiple files from the command line, transferring
+ * files of the form "host:path", "ftp://host/path" using the
+ * ftp protocol, and files of the form "http://host/path" using
+ * the http protocol.
+ * If path has a trailing "/", then return (-1);
+ * 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[], char *outfile)
+{
+ char *xargv[5];
+ char *cp, *url, *host, *dir, *file, *portnum;
+ char *username, *pass, *pathstart;
+ char *ftpproxy, *httpproxy;
+ int rval, xargc;
+ volatile int argpos;
+ int dirhasglob, filehasglob, oautologin;
+ char rempath[PATH_MAX];
+
+ argpos = 0;
+
+ if (setjmp(toplevel)) {
+ if (connected)
+ disconnect(0, NULL);
+ return (argpos + 1);
+ }
+ (void)signal(SIGINT, (sig_t)intr);
+ (void)signal(SIGPIPE, (sig_t)lostpeer);
+
+ if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0')
+ ftpproxy = NULL;
+ if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0')
+ httpproxy = NULL;
+
+ /*
+ * Loop through as long as there's files to fetch.
+ */
+ username = pass = NULL;
+ for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) {
+ if (strchr(argv[argpos], ':') == NULL)
+ break;
+
+ free(username);
+ free(pass);
+ host = dir = file = portnum = username = pass = NULL;
+
+ /*
+ * We muck with the string, so we make a copy.
+ */
+ url = strdup(argv[argpos]);
+ if (url == NULL)
+ errx(1, "Can't allocate memory for auto-fetch.");
+
+ /*
+ * Try HTTP URL-style arguments first.
+ */
+ if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
+#ifndef SMALL
+ /* even if we compiled without SSL, url_get will check */
+ strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0 ||
+#endif /* !SMALL */
+ strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
+ redirect_loop = 0;
+ if (url_get(url, httpproxy, outfile) == -1)
+ rval = argpos + 1;
+ continue;
+ }
+
+ /*
+ * Try FTP URL-style arguments next. If ftpproxy is
+ * set, use url_get() instead of standard ftp.
+ * Finally, try host:file.
+ */
+ host = url;
+ if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
+ char *passend, *passagain, *userend;
+
+ if (ftpproxy) {
+ if (url_get(url, ftpproxy, outfile) == -1)
+ rval = argpos + 1;
+ continue;
+ }
+ host += sizeof(FTP_URL) - 1;
+ dir = strchr(host, '/');
+
+ /* Look for [user:pass@]host[:port] */
+
+ /* check if we have "user:pass@" */
+ userend = strchr(host, ':');
+ passend = strchr(host, '@');
+ if (passend && userend && userend < passend &&
+ (!dir || passend < dir)) {
+ username = host;
+ pass = userend + 1;
+ host = passend + 1;
+ *userend = *passend = '\0';
+ passagain = strchr(host, '@');
+ if (strchr(pass, '@') != NULL ||
+ (passagain != NULL && passagain < dir)) {
+ warnx(at_encoding_warning);
+ username = pass = NULL;
+ goto bad_ftp_url;
+ }
+
+ if (EMPTYSTRING(username)) {
+bad_ftp_url:
+ warnx("Invalid URL: %s", argv[argpos]);
+ rval = argpos + 1;
+ username = pass = NULL;
+ continue;
+ }
+ username = urldecode(username);
+ pass = urldecode(pass);
+ }
+
+ /* check [host]:port, or [host] */
+ if (host[0] == '[') {
+ cp = strchr(host, ']');
+ if (cp && (!dir || cp < dir)) {
+ if (cp + 1 == dir || cp[1] == ':') {
+ host++;
+ *cp++ = '\0';
+ } else
+ cp = NULL;
+ } else
+ cp = host;
+ } else
+ cp = host;
+
+ /* split off host[:port] if there is */
+ if (cp) {
+ portnum = strchr(cp, ':');
+ pathstart = strchr(cp, '/');
+ /* : in path is not a port # indicator */
+ if (portnum && pathstart &&
+ pathstart < portnum)
+ portnum = NULL;
+
+ if (!portnum)
+ ;
+ else {
+ if (!dir)
+ ;
+ else if (portnum + 1 < dir) {
+ *portnum++ = '\0';
+ /*
+ * XXX should check if portnum
+ * is decimal number
+ */
+ } else {
+ /* empty portnum */
+ goto bad_ftp_url;
+ }
+ }
+ } else
+ portnum = NULL;
+ } else { /* classic style `host:file' */
+ dir = strchr(host, ':');
+ }
+ if (EMPTYSTRING(host)) {
+ rval = argpos + 1;
+ continue;
+ }
+
+ /*
+ * If dir is NULL, the file wasn't specified
+ * (URL looked something like ftp://host)
+ */
+ if (dir != NULL)
+ *dir++ = '\0';
+
+ /*
+ * Extract the file and (if present) directory name.
+ */
+ if (!EMPTYSTRING(dir)) {
+ cp = strrchr(dir, '/');
+ if (cp != NULL) {
+ *cp++ = '\0';
+ file = cp;
+ } else {
+ file = dir;
+ dir = NULL;
+ }
+ }
+#ifndef SMALL
+ if (debug)
+ fprintf(ttyout,
+ "user %s:%s host %s port %s dir %s file %s\n",
+ username, pass ? "XXXX" : NULL, host, portnum,
+ dir, file);
+#endif /* !SMALL */
+
+ /*
+ * Set up the connection.
+ */
+ if (connected)
+ disconnect(0, NULL);
+ xargv[0] = __progname;
+ xargv[1] = host;
+ xargv[2] = NULL;
+ xargc = 2;
+ if (!EMPTYSTRING(portnum)) {
+ xargv[2] = portnum;
+ xargv[3] = NULL;
+ xargc = 3;
+ }
+ oautologin = autologin;
+ if (username == NULL)
+ anonftp = 1;
+ else {
+ anonftp = 0;
+ autologin = 0;
+ }
+ setpeer(xargc, xargv);
+ autologin = oautologin;
+ if (connected == 0 ||
+ (connected == 1 && autologin && (username == NULL ||
+ !ftp_login(host, username, pass)))) {
+ warnx("Can't connect or login to host `%s'", host);
+ rval = argpos + 1;
+ continue;
+ }
+
+ /* Always use binary transfers. */
+ setbinary(0, NULL);
+
+ dirhasglob = filehasglob = 0;
+ if (doglob) {
+ if (!EMPTYSTRING(dir) &&
+ strpbrk(dir, "*?[]{}") != NULL)
+ dirhasglob = 1;
+ if (!EMPTYSTRING(file) &&
+ strpbrk(file, "*?[]{}") != NULL)
+ filehasglob = 1;
+ }
+
+ /* Change directories, if necessary. */
+ if (!EMPTYSTRING(dir) && !dirhasglob) {
+ xargv[0] = "cd";
+ xargv[1] = dir;
+ xargv[2] = NULL;
+ cd(2, xargv);
+ if (!dirchange) {
+ rval = argpos + 1;
+ continue;
+ }
+ }
+
+ if (EMPTYSTRING(file)) {
+#ifndef SMALL
+ rval = -1;
+#else /* !SMALL */
+ recvrequest("NLST", "-", NULL, "w", 0, 0);
+ rval = 0;
+#endif /* !SMALL */
+ continue;
+ }
+
+ if (verbose)
+ fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
+
+ if (dirhasglob) {
+ snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
+ 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;
+ xargv[0] = "mget";
+#ifndef SMALL
+ if (resume) {
+ xargc = 3;
+ xargv[1] = "-c";
+ xargv[2] = file;
+ xargv[3] = NULL;
+ }
+#endif /* !SMALL */
+ mget(xargc, xargv);
+ interactive = ointeractive;
+ } else {
+ if (outfile != NULL) {
+ xargv[2] = outfile;
+ xargv[3] = NULL;
+ xargc++;
+ }
+#ifndef SMALL
+ if (resume)
+ reget(xargc, xargv);
+ else
+#endif /* !SMALL */
+ get(xargc, xargv);
+ }
+
+ if ((code / 100) != COMPLETE)
+ rval = argpos + 1;
+ }
+ if (connected && rval != -1)
+ disconnect(0, NULL);
+ return (rval);
+}
+
+char *
+urldecode(const char *str)
+{
+ char *ret, c;
+ int i, reallen;
+
+ if (str == NULL)
+ return NULL;
+ if ((ret = malloc(strlen(str)+1)) == NULL)
+ err(1, "Can't allocate memory for URL decoding");
+ for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
+ c = str[i];
+ if (c == '+') {
+ *ret = ' ';
+ continue;
+ }
+
+ /* Cannot use strtol here because next char
+ * after %xx may be a digit.
+ */
+ if (c == '%' && isxdigit((unsigned char)str[i+1]) &&
+ isxdigit((unsigned char)str[i+2])) {
+ *ret = hextochar(&str[i+1]);
+ i+=2;
+ continue;
+ }
+ *ret = c;
+ }
+ *ret = '\0';
+
+ return ret-reallen;
+}
+
+char *
+recode_credentials(const char *userinfo)
+{
+ char *ui, *creds;
+ size_t ulen, credsize;
+
+ /* url-decode the user and pass */
+ ui = urldecode(userinfo);
+
+ ulen = strlen(ui);
+ credsize = (ulen + 2) / 3 * 4 + 1;
+ creds = malloc(credsize);
+ if (creds == NULL)
+ errx(1, "out of memory");
+ if (b64_ntop(ui, ulen, creds, credsize) == -1)
+ errx(1, "error in base64 encoding");
+ free(ui);
+ return (creds);
+}
+
+char
+hextochar(const char *str)
+{
+ unsigned char c, ret;
+
+ c = str[0];
+ ret = c;
+ if (isalpha(c))
+ ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ else
+ ret -= '0';
+ ret *= 16;
+
+ c = str[1];
+ ret += c;
+ if (isalpha(c))
+ ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ else
+ ret -= '0';
+ return ret;
+}
+
+int
+isurl(const char *p)
+{
+
+ if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
+ strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
+#ifndef SMALL
+ strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 ||
+#endif /* !SMALL */
+ strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
+ strstr(p, ":/"))
+ return (1);
+ return (0);
+}
+
+char *
+ftp_readline(FILE *fp, struct tls *tls, size_t *lenp)
+{
+ if (fp != NULL)
+ return fparseln(fp, lenp, NULL, "\0\0\0", 0);
+#ifndef SMALL
+ else if (tls != NULL)
+ return SSL_readline(tls, lenp);
+#endif /* !SMALL */
+ else
+ return NULL;
+}
+
+size_t
+ftp_read(FILE *fp, struct tls *tls, char *buf, size_t len)
+{
+ ssize_t tls_ret;
+ size_t ret = 0;
+
+ if (fp != NULL)
+ ret = fread(buf, sizeof(char), len, fp);
+#ifndef SMALL
+ else if (tls != NULL) {
+ if ((tls_ret = tls_read(tls, buf, len)) >= 0)
+ ret = (size_t)tls_ret;
+ }
+#endif /* !SMALL */
+ return (ret);
+}
+
+int
+ftp_printf(FILE *fp, struct tls *tls, const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ if (fp != NULL)
+ ret = vfprintf(fp, fmt, ap);
+#ifndef SMALL
+ else if (tls != NULL)
+ ret = SSL_vprintf(tls, fmt, ap);
+#endif /* !SMALL */
+ else
+ ret = 0;
+
+ va_end(ap);
+#ifndef SMALL
+ if (debug) {
+ va_start(ap, fmt);
+ ret = vfprintf(ttyout, fmt, ap);
+ va_end(ap);
+ }
+#endif /* !SMALL */
+ return (ret);
+}
+
+#ifndef SMALL
+int
+SSL_vprintf(struct tls *tls, const char *fmt, va_list ap)
+{
+ char *string, *buf;
+ size_t len;
+ int ret;
+
+ if ((ret = vasprintf(&string, fmt, ap)) == -1)
+ return ret;
+ buf = string;
+ len = ret;
+ while (len > 0) {
+ ret = tls_write(tls, buf, len);
+ if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
+ continue;
+ if (ret < 0)
+ break;
+ buf += ret;
+ len -= ret;
+ }
+ free(string);
+ return ret;
+}
+
+char *
+SSL_readline(struct tls *tls, size_t *lenp)
+{
+ size_t i, len;
+ char *buf, *q, c;
+ int ret;
+
+ len = 128;
+ if ((buf = malloc(len)) == NULL)
+ errx(1, "Can't allocate memory for transfer buffer");
+ for (i = 0; ; i++) {
+ if (i >= len - 1) {
+ if ((q = reallocarray(buf, len, 2)) == NULL)
+ errx(1, "Can't expand transfer buffer");
+ buf = q;
+ len *= 2;
+ }
+again:
+ ret = tls_read(tls, &c, 1);
+ if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
+ goto again;
+ if (ret < 0)
+ errx(1, "SSL read error: %s", tls_error(tls));
+
+ buf[i] = c;
+ if (c == '\n') {
+ buf[i] = '\0';
+ break;
+ }
+ }
+ *lenp = i;
+ return (buf);
+}
+
+int
+proxy_connect(int socket, char *host, char *cookie)
+{
+ int l;
+ char buf[1024];
+ char *connstr, *hosttail, *port;
+
+ if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
+ (hosttail[1] == '\0' || hosttail[1] == ':')) {
+ host++;
+ *hosttail++ = '\0';
+ } else
+ hosttail = host;
+
+ port = strrchr(hosttail, ':'); /* find portnum */
+ if (port != NULL)
+ *port++ = '\0';
+ if (!port)
+ port = "443";
+
+ if (cookie) {
+ l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
+ "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n",
+ host, port, cookie, HTTP_USER_AGENT);
+ } else {
+ l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n%s\r\n\r\n",
+ host, port, HTTP_USER_AGENT);
+ }
+
+ if (l == -1)
+ errx(1, "Could not allocate memory to assemble connect string!");
+#ifndef SMALL
+ if (debug)
+ printf("%s", connstr);
+#endif /* !SMALL */
+ if (write(socket, connstr, l) != l)
+ err(1, "Could not send connect string");
+ read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */
+ free(connstr);
+ return(200);
+}
+#endif /* !SMALL */
diff --git a/usr.bin/ftp/ftp.1 b/usr.bin/ftp/ftp.1
new file mode 100644
index 0000000..17af978
--- /dev/null
+++ b/usr.bin/ftp/ftp.1
@@ -0,0 +1,1792 @@
+.\" $OpenBSD: ftp.1,v 1.101 2015/11/05 16:25:57 schwarze Exp $
+.\" $NetBSD: ftp.1,v 1.22 1997/08/18 10:20:22 lukem Exp $
+.\"
+.\" 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 $Mdocdate: November 5 2015 $
+.Dt FTP 1
+.Os
+.Sh NAME
+.Nm ftp
+.Nd Internet file transfer program
+.Sh SYNOPSIS
+.Nm ftp
+.Op Fl 46AadEegiMmnptVv
+.Op Fl D Ar title
+.Op Fl k Ar seconds
+.Op Fl P Ar port
+.Op Fl r Ar seconds
+.Op Fl s Ar srcaddr
+.Op Ar host Op Ar port
+.Nm ftp
+.Op Fl C
+.Op Fl o Ar output
+.Op Fl s Ar srcaddr
+.Sm off
+.Pf ftp:// Op Ar user : password No @
+.Ar host Op : Ar port
+.No / Ar file Op /
+.Sm on
+.Ar ...
+.Nm ftp
+.Op Fl C
+.Op Fl c Ar cookie
+.Op Fl o Ar output
+.Op Fl S Ar ssl_options
+.Op Fl s Ar srcaddr
+.Op Fl U Ar useragent
+.Sm off
+.Pf http Oo s Oc ://
+.Op Ar user : password No @
+.Ar host Op : Ar port
+.No / Ar file
+.Sm on
+.Ar ...
+.Nm ftp
+.Op Fl C
+.Op Fl o Ar output
+.Op Fl s Ar srcaddr
+.Pf file: Ar
+.Nm ftp
+.Op Fl C
+.Op Fl o Ar output
+.Op Fl s Ar srcaddr
+.Ar host : Ns / Ns Ar file Ns Op /
+.Ar ...
+.Sh DESCRIPTION
+.Nm
+is the user interface to the Internet standard File Transfer
+Protocol (FTP).
+The program allows a user to transfer files to and from a
+remote network site.
+.Pp
+The latter four usage formats will fetch a file using either the
+FTP, HTTP, or HTTPS protocols into the current directory.
+This is ideal for scripts.
+Refer to
+.Sx AUTO-FETCHING FILES
+below for more information.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 4
+Forces
+.Nm
+to use IPv4 addresses only.
+.It Fl 6
+Forces
+.Nm
+to use IPv6 addresses only.
+.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 the normal login procedure and use an anonymous login instead.
+.It Fl C
+Continue a previously interrupted file transfer.
+.Nm
+will continue transferring from an offset equal to the length of
+.Ar file .
+.Pp
+Resuming HTTP(S) transfers are only supported
+if the remote server supports the
+.Dq Range
+header.
+.It Fl c Ar cookie
+Load a Netscape-like cookiejar file
+for HTTP and HTTPS transfers.
+With this option relevant cookies from the jar are sent with each HTTP(S)
+request.
+Setting the
+.Ev http_cookies
+environment variable has the same effect.
+If both the
+.Ev http_cookies
+environment variable is set and the
+.Fl c
+argument is given, the latter takes precedence.
+.It Fl D Ar title
+Specify a short
+.Ar title
+for the start of the progress bar.
+.It Fl d
+Enables debugging.
+.It Fl E
+Disables EPSV/EPRT command on IPv4 connections.
+.It Fl e
+Disables command line editing.
+Useful for Emacs ange-ftp.
+.It Fl g
+Disables file name globbing.
+.It Fl i
+Turns off interactive prompting during
+multiple file transfers.
+.It Fl k Ar seconds
+When greater than zero,
+sends a byte after each
+.Ar seconds
+period over the control connection during long transfers,
+so that incorrectly configured network equipment won't
+aggressively drop it.
+The FTP protocol supports a
+.Dv NOOP
+command that can be used for that purpose.
+This assumes the FTP server can deal with extra commands coming over
+the control connection during a transfer.
+Well-behaved servers queue those commands, and process them after the
+transfer.
+By default,
+.Nm
+will send a byte every 60 seconds.
+.It Fl M
+Causes
+.Nm
+to never display the progress meter in cases where it would do
+so by default.
+.It Fl m
+Causes
+.Nm
+to always display the progress meter in cases where it would not do
+so by default.
+.It Fl n
+Restrains
+.Nm
+from attempting
+.Dq auto-login
+upon initial connection.
+If auto-login is enabled,
+.Nm
+will check the
+.Pa .netrc
+file (see below) 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 log in.
+.It Fl o Ar output
+When fetching a single file or URL, save the contents in
+.Ar output .
+To make the contents go to stdout,
+use
+.Sq -
+for
+.Ar output .
+.It Fl P Ar port
+Sets the port number to
+.Ar port .
+.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 r Ar seconds
+Retry to connect if failed, pausing for number of
+.Ar seconds .
+.It Fl S Ar ssl_options
+SSL/TLS options to use with HTTPS transfers.
+The following settings are available:
+.Bl -tag -width Ds
+.It Cm cafile Ns = Ns Ar /path/to/cert.pem
+PEM encoded file containing CA certificates used for certificate
+validation.
+.It Cm capath Ns = Ns Ar /path/to/certs/
+Directory containing PEM encoded CA certificates used for certificate
+validation.
+Such a directory can be prepared using the c_rehash script distributed with
+OpenSSL.
+.It Cm ciphers Ns = Ns Ar cipher_list
+Specify the list of ciphers that will be used by
+.Nm .
+See the
+.Xr openssl 1
+.Cm ciphers
+subcommand.
+.It Cm depth Ns = Ns Ar max_depth
+Maximum depth of the certificate chain allowed when performing
+validation.
+.It Cm do
+Perform server certificate validation.
+.It Cm dont
+Don't perform server certificate validation.
+.El
+.Pp
+By default, server certificate validation is performed, and if it fails
+.Nm
+will abort.
+If no
+.Cm cafile
+or
+.Cm capath
+setting is provided,
+.Pa /etc/ssl/cert.pem
+will be used.
+.It Fl s Ar srcaddr
+Use
+.Ar srcaddr
+on the local machine as the source address
+of the connection.
+Only useful on systems with more than one address.
+.It Fl t
+Enables packet tracing.
+.It Fl U Ar useragent
+Set
+.Ar useragent
+as the User-Agent for HTTP(S) URL requests.
+If not specified, the default User-Agent is
+.Dq OpenBSD ftp .
+.It Fl V
+Disable verbose mode, overriding the default of enabled when input
+is from a terminal.
+.It Fl v
+Enable verbose mode.
+This is the default if input is from a terminal.
+Forces
+.Nm
+to show all responses from the remote server, as well
+as report on data transfer statistics.
+.El
+.Pp
+The 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
+FTP server on that host; otherwise,
+.Nm
+will enter its command interpreter and await instructions
+from the user.
+When
+.Nm
+is awaiting commands, the prompt
+.Dq ftp\*(Gt
+is provided to the user.
+The following commands are recognized
+by
+.Nm :
+.Bl -tag -width Fl
+.It Ic \&! Oo Ar command
+.Op Ar arg ...
+.Oc
+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 arg ...
+Execute the macro
+.Ar macro-name
+that was defined with the
+.Ic macdef
+command.
+Arguments are passed to the macro unglobbed.
+.It Ic \&? Op Ar command
+A synonym for
+.Ic help .
+.It Ic account Op Ar password
+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 .
+.It Ic bell Op Ic on | off
+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.
+This is the default type.
+.It Ic bye
+Terminate the FTP session with the remote server and exit
+.Nm .
+An end-of-file will also terminate the session and exit.
+.It Ic case Op Ic on | off
+Toggle remote computer file name case mapping during
+.Ic mget
+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 file
+Change the permission modes of
+.Ar file
+on the remote
+system to
+.Ar mode .
+.It Ic close
+Terminate the FTP session with the remote server and
+return to the command interpreter.
+Any defined macros are erased.
+.It Ic cr Op Ic on | off
+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 non-UNIX
+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 debug Oo Ic on | off |
+.Ar debuglevel
+.Oc
+Toggle debugging mode.
+If an optional
+.Ar debuglevel
+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-directory Op Ar local-file
+A synonym for
+.Ic ls .
+.It Ic disconnect
+A synonym for
+.Ic close .
+.It Ic edit Op Ic on | off
+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 Op Ic on | off
+Toggle use of EPSV/EPRT command on IPv4 connection.
+.It Ic exit
+A synonym for
+.Ic bye .
+.It Ic form Ar format
+Set the file transfer
+.Ic form
+to
+.Ar format .
+The default format is
+.Dq file .
+.It Ic ftp Ar host Op Ar port
+A synonym for
+.Ic open .
+.It Ic gate Oo Ic on | off |
+.Ar host Op Ar port
+.Oc
+Toggle gate-ftp mode.
+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 Op Ic on | off
+Toggle filename expansion for
+.Ic mdelete ,
+.Ic mget
+and
+.Ic mput .
+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
+and
+.Ic mget ,
+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
+.Dq mls remote-files - .
+Note:
+.Ic mget
+and
+.Ic mput
+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 Oo Ic on | off |
+.Ar size
+.Oc
+Toggle hash mark
+.Pq Ql #
+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.
+.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 lcd Op Ar local-directory
+Change the working directory on the local machine.
+If
+no
+.Ar local-directory
+is specified, the user's home directory is used.
+.It Ic less Ar file
+A synonym for
+.Ic page .
+.It Ic lpwd
+Print the working directory on the local machine.
+.It Ic ls Op Ar remote-directory 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-directory
+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 ls
+output.
+If no local file is specified, or if
+.Ar local-file
+is
+.Sq - ,
+the output is sent to the terminal.
+.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 in (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
+.Ql $
+and
+.Ql \e
+as special characters.
+A
+.Ql $
+followed by a number (or numbers) is replaced by the
+corresponding argument on the macro invocation command line.
+A
+.Ql $
+followed by an
+.Sq i
+tells the macro processor that the
+executing macro is to be looped.
+On the first pass
+.Ql $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
+.Ql \e
+followed by any character is replaced by that character.
+Use the
+.Ql \e
+to prevent special treatment of the
+.Ql $ .
+.It Ic mdelete Op Ar remote-files
+Delete the
+.Ar remote-files
+on the remote machine.
+.It Ic mdir Ar remote-files local-file
+A synonym for
+.Ic mls .
+.It Xo Ic mget
+.Op Fl cnr
+.Op Fl d Ar depth
+.Ar remote-files
+.Xc
+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" .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Use
+.Ic reget
+instead of
+.Ic get .
+.It Fl d Ar depth
+Specify the maximum recursion level
+.Ar depth .
+The default is 0, which means unlimited.
+.It Fl n
+Use
+.Ic newer
+instead of
+.Ic get .
+.It Fl r
+Recursively descend the directory tree, transferring all files and
+directories.
+.El
+.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 mode Op Ar mode-name
+Set the file transfer
+.Ic mode
+to
+.Ar mode-name .
+The default mode is
+.Dq stream
+mode.
+.It Ic modtime Ar file
+Show the last modification time of
+.Ar file
+on the remote machine.
+.It Ic more Ar file
+A synonym for
+.Ic page .
+.It Xo Ic mput
+.Op Fl cr
+.Op Fl d Ar depth
+.Ar local-files
+.Xc
+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.
+.Pp
+If the
+.Fl c
+flag is specified then
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Use
+.Ic reput
+instead of
+.Ic put .
+.It Fl d Ar depth
+Specify the maximum recursion level
+.Ar depth .
+The default is 0, which means unlimited.
+.It Fl r
+Recursively descend the directory tree, transferring all files and
+directories.
+.El
+.It Xo Ic msend
+.Op Fl c
+.Ar local-files
+.Xc
+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 than 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-directory Op Ar local-file
+Print a list of the files in a
+directory on the remote machine.
+If
+.Ar remote-directory
+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 nlist
+output.
+If no local file is specified, or if
+.Ar local-file
+is
+.Sq - ,
+the output is sent to the terminal.
+Note that on some servers, the
+.Ic nlist
+command will only return information on normal files (not directories
+or special files).
+.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 non-UNIX remote computer
+with different file naming conventions or practices.
+.Pp
+The mapping follows the pattern set by
+.Ar inpattern
+and
+.Ar outpattern .
+.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
+.Ql $1 ,
+.Ql $2 ,
+\&...,
+.Ql $9
+in
+.Ar inpattern .
+Use
+.Ql \e
+to prevent this special treatment of the
+.Ql $
+character.
+All other characters are treated literally, and are used to determine the
+.Ic nmap
+.Ar inpattern
+variable values.
+.Pp
+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
+.Ql $1 ,
+.Ql $2 ,
+\&...,
+.Ql $9
+are replaced by any value resulting from the
+.Ar inpattern
+template.
+The sequence
+.Ql $0
+is replaced by the original filename.
+Additionally, the sequence
+.Sq Op Ar seq1 , Ar seq2
+is replaced by
+.Ar seq1
+if
+.Ar seq1
+is not a null string; otherwise it is replaced by
+.Ar seq2 .
+For example:
+.Pp
+.Dl nmap $1.$2.$3 [$1,$2].[$2,file]
+.Pp
+This command would yield the output filename
+.Pa myfile.data
+for input filenames
+.Pa myfile.data
+and
+.Pa myfile.data.old ;
+.Pa myfile.file
+for the input filename
+.Pa myfile ;
+and
+.Pa myfile.myfile
+for the input filename
+.Pa .myfile .
+Spaces may be included in
+.Ar outpattern
+by quoting them,
+as in the following example:
+.Bd -literal -offset indent
+nmap $1.$2 "$1 $2"
+.Ed
+.Pp
+Use the
+.Ql \e
+character to prevent special treatment
+of the
+.Ql $ ,
+.Ql \&[ ,
+.Ql \&] ,
+and
+.Ql \&,
+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 non-UNIX 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
+FTP server.
+An optional port number may be supplied,
+in which case
+.Nm
+will attempt to contact an FTP server at that port.
+If the
+.Ic auto-login
+option is on (default),
+.Nm
+will also attempt to automatically log the user in to
+the FTP server (see below).
+.It Ic page Ar file
+Retrieve
+.Ic file
+and display with the program defined in
+.Ev PAGER
+(defaulting to
+.Xr more 1
+if
+.Ev PAGER
+is null or not defined).
+.It Ic passive Op Ic on | off
+Toggle passive mode.
+If passive mode is turned on (default is on),
+.Nm
+will send a
+.Dv EPSV
+command for all data connections instead of the usual
+.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 FTP servers are required to support the
+.Dv PASV
+command by RFC 1123, some do not.)
+.It Ic preserve Op Ic on | off
+Toggle preservation of modification times on retrieved files.
+.It Ic progress Op Ic on | off
+Toggle display of transfer progress bar.
+The progress bar will be disabled for a transfer that has
+.Ar local-file
+as
+.Sq -
+or a command that starts with
+.Sq \&| .
+Refer to
+.Sx FILE NAMING CONVENTIONS
+for more information.
+.It Ic prompt Op Ic on | off
+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 Ic ?\&
+Print help message.
+.It Ic a
+Answer
+.Dq yes
+to the current file and automatically answer
+.Dq yes
+to any remaining files for the current command.
+.It Ic n
+Do not transfer the file.
+.It Ic p
+Answer
+.Dq yes
+to the current file and turn off prompt mode
+(as if
+.Dq prompt off
+had been given).
+.It Ic q
+Answer
+.Dq no
+to the current file and automatically answer
+.Dq no
+to any remaining files for the current command.
+.It Ic y
+Transfer the file.
+.El
+.It Ic proxy Ar command
+Execute an FTP command on a secondary control connection.
+This command allows simultaneous connection to two remote 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
+.Ic proxy ?\&
+to see other 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 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 arg ...
+The arguments specified are sent, verbatim, to the remote FTP server.
+.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
+Reget acts like 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 rename Ar from-name to-name
+Rename the file
+.Ar from-name
+on the remote machine to the file
+.Ar to-name .
+.It Ic reput Ar local-file Op Ar remote-file
+Reput acts like put, except that if
+.Ar remote-file
+exists and is
+smaller than
+.Ar local-file ,
+.Ar remote-file
+is presumed to be
+a partially transferred copy of
+.Ar local-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 reset
+Clear reply queue.
+This command re-synchronizes command/reply sequencing with the remote
+FTP server.
+Resynchronization may be necessary following a violation of the 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,
+.Ar marker
+is usually a byte
+offset into the file.
+.It Ic rhelp Op Ar command-name
+Request help from the remote 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 file
+With no arguments, show status of remote machine.
+If
+.Ar file
+is specified, show status of
+.Ar file
+on remote machine.
+.It Ic runique Op Ic on | off
+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
+.Dq .1
+is appended to the name.
+If the resulting name matches another existing file,
+a
+.Dq .2
+is appended to the original name.
+If this process continues up to
+.Dq .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 Op Ic on | off
+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 FTP implementations which do ignore
+.Dv PORT
+commands but, incorrectly, indicate they've been accepted.
+.It Ic site Ar arg ...
+The arguments specified are sent, verbatim, to the remote FTP server as a
+.Dv SITE
+command.
+.It Ic size Ar file
+Return size of
+.Ar file
+on remote machine.
+.It Ic status
+Show the current status of
+.Nm .
+.\" .It Ic struct Op Ar struct-name
+.\" Set the file transfer
+.\" .Ar structure
+.\" to
+.\" .Ar struct-name .
+.\" By default,
+.\" .Dq file
+.\" structure is used.
+.It Ic sunique Op Ic on | off
+Toggle storing of files on remote machine under unique file names.
+The remote FTP server must support the FTP protocol
+.Dv STOU
+command for
+successful completion.
+The remote server will report the unique name.
+Default value is off.
+.It Ic system
+Show the type of operating system running on the remote machine.
+.It Ic trace Op Ic on | off
+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
+.Dq binary .
+.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 Xo
+.Ic user Ar username
+.Op Ar password Op Ar account
+.Xc
+Identify yourself to the remote 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 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
+FTP server.
+.It Ic verbose Op Ic on | off
+Toggle verbose mode.
+In verbose mode, all responses from
+the 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.
+.El
+.Pp
+Command arguments which have embedded spaces may be quoted with
+quote
+.Pq Ql \&"
+marks.
+.Pp
+Commands which toggle settings can take an explicit
+.Ic on
+or
+.Ic off
+argument to force the setting appropriately.
+.Pp
+If
+.Nm
+receives a
+.Dv SIGINFO
+(see the
+.Dq status
+argument of
+.Xr stty 1 )
+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 Ds
+.It Ar host : Ns / Ns Ar file Ns Op /
+.Dq Classic
+.Nm
+format.
+.Sm off
+.It Xo
+.Pf ftp:// Op Ar user : password No @
+.Ar host Op : Ar port
+.No / Ar file Op /
+.Xc
+.Sm on
+An FTP URL, retrieved using the FTP protocol if
+.Ev ftp_proxy
+isn't defined.
+Otherwise, transfer using HTTP via the proxy defined in
+.Ev ftp_proxy .
+If a
+.Ar user
+and
+.Ar password
+are given and
+.Ev ftp_proxy
+isn't defined,
+log in as
+.Ar user
+with a password of
+.Ar password .
+.Sm off
+.It Xo
+.Pf http:// Op Ar user : password No @
+.Ar host Op : Ar port
+.No / Ar file
+.Xc
+.Sm on
+An HTTP URL, retrieved using the HTTP protocol.
+If
+.Ev http_proxy
+is defined, it is used as a URL to an HTTP proxy server.
+If a
+.Ar user
+and
+.Ar password
+are given and
+.Ev http_proxy
+isn't defined,
+log in as
+.Ar user
+with a password of
+.Ar password
+using Basic authentication.
+.Sm off
+.It Xo
+.Pf https:// Op Ar user : password No @
+.Ar host Op : Ar port
+.No / Ar file
+.Xc
+.Sm on
+An HTTPS URL, retrieved using the HTTPS protocol.
+If
+.Ev http_proxy
+is defined, this HTTPS proxy server will be used to fetch the
+file using the CONNECT method.
+If a
+.Ar user
+and
+.Ar password
+are given and
+.Ev http_proxy
+isn't defined,
+log in as
+.Ar user
+with a password of
+.Ar password
+using Basic authentication.
+.It Pf file: Ar file
+.Ar file
+is retrieved from a mounted file system.
+.El
+.Pp
+If a classic format or an FTP URL format has a trailing
+.Sq / ,
+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.
+.Pp
+If successive auto-fetch FTP elements refer to the same host, then
+the connection is maintained between transfers, reducing overhead on
+connection creation and deletion.
+.Pp
+If
+.Ar file
+contains a glob character and globbing is enabled
+(see
+.Ic glob ) ,
+then the equivalent of
+.Ic mget Ar file
+is performed.
+.Pp
+If no
+.Fl o
+option is specified, and
+the directory component of
+.Ar file
+contains no globbing characters,
+then
+it is stored in the current directory as the
+.Xr basename 1
+of
+.Ar file .
+If
+.Fl o Ar output
+is specified, then
+.Ar file
+is stored as
+.Ar output .
+Otherwise, the remote name is used as the local name.
+.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 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, an
+.Ql ftp\*(Gt
+prompt will not appear until the remote server has completed
+sending the requested file.
+.Pp
+The terminal interrupt key sequence will be ignored when
+.Nm
+has completed any local processing and is awaiting a reply
+from the remote server.
+A long delay in this mode may result from the ABOR processing described
+above, or from unexpected behavior by the remote server, including
+violations of the FTP protocol.
+If the delay results from unexpected remote server behavior, the local
+.Nm
+program must be killed by hand.
+.Sh FILE NAMING CONVENTIONS
+Files specified as arguments to
+.Nm
+commands are processed according to the following rules.
+.Bl -enum
+.It
+If
+.Sq -
+is specified as a local file name, the standard input (for reading)
+or standard output (for writing)
+is used.
+.It
+If the first character of a local 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 standard output
+(standard input).
+If the shell command includes spaces, the argument
+must be quoted; e.g.,
+.Qq ls -lt .
+A particularly
+useful example of this mechanism is:
+.Qq ls \&. |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
+.Ic glob
+command.
+If the
+.Nm
+command expects a single local file (e.g.,
+.Ic put ) ,
+only the first filename generated by the
+.Dq 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 FTP specification specifies many parameters which may
+affect a file transfer.
+The
+.Ic type
+may be one of
+.Dq ascii ,
+.Dq binary ,
+or
+.Dq image .
+.Nm
+supports the ASCII and image types of file transfer.
+.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.
+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
+.Ar automatic
+anonymous 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 null 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.
+.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 ENVIRONMENT
+.Nm
+utilizes the following environment variables:
+.Bl -tag -width "FTPSERVERPORT"
+.It Ev FTPMODE
+Overrides the default operation mode.
+Recognized values are:
+.Pp
+.Bl -tag -width "passive " -offset indent -compact
+.It passive
+passive mode FTP only
+.It active
+active mode FTP only
+.It auto
+automatic determination of passive or active (this is the default)
+.It gate
+gate-ftp mode
+.El
+.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 HOME
+For default location of a
+.Pa .netrc
+file, if one exists.
+.It Ev PAGER
+Used by
+.Ic page
+to display files.
+.It Ev SHELL
+For default shell.
+.It Ev TMPDIR
+Directory in which temporary files are stored.
+.It Ev ftp_proxy
+URL of FTP proxy to use when making FTP URL requests
+(if not defined, use the standard FTP protocol).
+.It Ev http_proxy
+URL of HTTP proxy to use when making HTTP or HTTPS URL requests.
+.It Ev http_cookies
+Path of a Netscape-like cookiejar file to use when making
+HTTP or HTTPS URL requests.
+.El
+.Sh PORT ALLOCATION
+For active mode data connections,
+.Nm
+will listen to a random high TCP port.
+The interval of ports used are configurable using
+.Xr sysctl 8
+variables
+.Va net.inet.ip.porthifirst
+and
+.Va net.inet.ip.porthilast .
+.Sh SEE ALSO
+.Xr basename 1 ,
+.Xr csh 1 ,
+.Xr more 1 ,
+.Xr stty 1 ,
+.Xr tar 1 ,
+.Xr tftp 1 ,
+.Xr editline 3 ,
+.Xr getservbyname 3 ,
+.Xr popen 3 ,
+.Xr editrc 5 ,
+.Xr services 5 ,
+.Xr ftp-proxy 8 ,
+.Xr ftpd 8
+.Sh STANDARDS
+.Rs
+.%A J. Postel
+.%A J. Reynolds
+.%D October 1985
+.%R RFC 959
+.%T FILE TRANSFER PROTOCOL (FTP)
+.Re
+.Pp
+.Rs
+.%A P. Hethmon
+.%D March 2007
+.%R RFC 3659
+.%T Extensions to FTP
+.Re
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Sh BUGS
+Correct execution of many commands depends upon proper behavior
+by the remote server.
+.Pp
+In the recursive mode of
+.Ic mget ,
+files and directories starting with whitespace are ignored
+because the list cannot be parsed any other way.
diff --git a/usr.bin/ftp/ftp.c b/usr.bin/ftp/ftp.c
new file mode 100644
index 0000000..b4c17c2
--- /dev/null
+++ b/usr.bin/ftp/ftp.c
@@ -0,0 +1,2095 @@
+/* $OpenBSD: ftp.c,v 1.96 2016/03/16 15:41:11 krw Exp $ */
+/* $NetBSD: ftp.c,v 1.27 1997/08/18 10:20:23 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <netinet/in.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 <netdb.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include "ftp_var.h"
+
+union sockunion {
+ struct sockinet {
+ u_char si_len;
+ u_char si_family;
+ u_short si_port;
+ } su_si;
+ struct sockaddr_in su_sin;
+ struct sockaddr_in6 su_sin6;
+};
+#define su_len su_si.si_len
+#define su_family su_si.si_family
+#define su_port su_si.si_port
+
+union sockunion myctladdr, hisctladdr, data_addr;
+
+int data = -1;
+int abrtflag = 0;
+jmp_buf ptabort;
+int ptabflg;
+int ptflag = 0;
+off_t restart_point = 0;
+
+
+FILE *cin, *cout;
+
+char *
+hookup(char *host, char *port)
+{
+ int s, tos, error;
+ static char hostnamebuf[HOST_NAME_MAX+1];
+ struct addrinfo hints, *res, *res0;
+#ifndef SMALL
+ struct addrinfo *ares;
+#endif
+ char hbuf[NI_MAXHOST];
+ char *cause = "unknown";
+ socklen_t namelen;
+
+ epsv4bad = 0;
+
+ memset((char *)&hisctladdr, 0, sizeof (hisctladdr));
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ error = getaddrinfo(host, port, &hints, &res0);
+ if (error == EAI_SERVICE) {
+ /*
+ * If the services file is corrupt/missing, fall back
+ * on our hard-coded defines.
+ */
+ char pbuf[NI_MAXSERV];
+
+ pbuf[0] = '\0';
+ if (strcmp(port, "ftp") == 0)
+ snprintf(pbuf, sizeof(pbuf), "%d", FTP_PORT);
+ else if (strcmp(port, "ftpgate") == 0)
+ snprintf(pbuf, sizeof(pbuf), "%d", GATE_PORT);
+ else if (strcmp(port, "http") == 0)
+ snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
+#ifndef SMALL
+ else if (strcmp(port, "https") == 0)
+ snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
+#endif /* !SMALL */
+ if (pbuf[0])
+ error = getaddrinfo(host, pbuf, &hints, &res0);
+ }
+ if (error) {
+ if (error == EAI_SERVICE)
+ warnx("%s: bad port number `%s'", host, port);
+ else
+ warnx("%s: %s", host, gai_strerror(error));
+ code = -1;
+ return (0);
+ }
+
+ if (res0->ai_canonname)
+ strlcpy(hostnamebuf, res0->ai_canonname, sizeof(hostnamebuf));
+ else
+ strlcpy(hostnamebuf, host, sizeof(hostnamebuf));
+ hostname = hostnamebuf;
+
+#ifndef SMALL
+ if (srcaddr) {
+ struct addrinfo ahints;
+
+ memset(&ahints, 0, sizeof(ahints));
+ ahints.ai_family = family;
+ ahints.ai_socktype = SOCK_STREAM;
+ ahints.ai_flags |= AI_NUMERICHOST;
+ ahints.ai_protocol = 0;
+
+ error = getaddrinfo(srcaddr, NULL, &ahints, &ares);
+ if (error) {
+ warnx("%s: %s", srcaddr, gai_strerror(error));
+ code = -1;
+ return (0);
+ }
+ }
+#endif /* !SMALL */
+
+ s = -1;
+ for (res = res0; res; res = res->ai_next) {
+ if (res0->ai_next) /* if we have multiple possibilities */
+ {
+ if (getnameinfo(res->ai_addr, res->ai_addrlen,
+ hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "unknown", sizeof(hbuf));
+ if (verbose)
+ fprintf(ttyout, "Trying %s...\n", hbuf);
+ }
+ s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s < 0) {
+ cause = "socket";
+ continue;
+ }
+#ifndef SMALL
+ if (srcaddr) {
+ if (ares->ai_family != res->ai_family) {
+ close(s);
+ s = -1;
+ errno = EINVAL;
+ cause = "bind";
+ continue;
+ }
+ if (bind(s, ares->ai_addr, ares->ai_addrlen) < 0) {
+ cause = "bind";
+ error = errno;
+ close(s);
+ errno = error;
+ s = -1;
+ continue;
+ }
+ }
+#endif /* !SMALL */
+ while ((error = connect(s, res->ai_addr, res->ai_addrlen)) < 0
+ && errno == EINTR) {
+ ;
+ }
+ if (error) {
+ /* this "if" clause is to prevent print warning twice */
+ if (verbose && res->ai_next) {
+ if (getnameinfo(res->ai_addr, res->ai_addrlen,
+ hbuf, sizeof(hbuf), NULL, 0,
+ NI_NUMERICHOST) != 0)
+ strlcpy(hbuf, "(unknown)",
+ sizeof(hbuf));
+ warn("connect to address %s", hbuf);
+ }
+ cause = "connect";
+ error = errno;
+ close(s);
+ errno = error;
+ s = -1;
+ continue;
+ }
+
+ /* finally we got one */
+ break;
+ }
+ if (s < 0) {
+ warn("%s", cause);
+ code = -1;
+ freeaddrinfo(res0);
+ return 0;
+ }
+ memcpy(&hisctladdr, res->ai_addr, res->ai_addrlen);
+ namelen = res->ai_addrlen;
+ freeaddrinfo(res0);
+ res0 = res = NULL;
+#ifndef SMALL
+ if (srcaddr) {
+ freeaddrinfo(ares);
+ ares = NULL;
+ }
+#endif /* !SMALL */
+ if (getsockname(s, (struct sockaddr *)&myctladdr, &namelen) < 0) {
+ warn("getsockname");
+ code = -1;
+ goto bad;
+ }
+ if (hisctladdr.su_family == AF_INET) {
+ tos = IPTOS_LOWDELAY;
+ if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0)
+ warn("setsockopt TOS (ignored)");
+ }
+ 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;
+ }
+ {
+ int ret, on = 1;
+
+ ret = setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on));
+#ifndef SMALL
+ if (ret < 0 && debug)
+ warn("setsockopt");
+#endif /* !SMALL */
+ }
+
+ return (hostname);
+bad:
+ (void)close(s);
+ return (NULL);
+}
+
+/* ARGSUSED */
+void
+cmdabort(int signo)
+{
+ int save_errno = errno;
+
+ alarmtimer(0);
+ (void) write(fileno(ttyout), "\n\r", 2);
+ abrtflag++;
+
+ errno = save_errno;
+ if (ptflag)
+ longjmp(ptabort, 1);
+}
+
+int
+command(const char *fmt, ...)
+{
+ va_list ap;
+ int r;
+ sig_t oldintr;
+
+ abrtflag = 0;
+#ifndef SMALL
+ if (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);
+ (void)fflush(ttyout);
+ }
+#endif /* !SMALL */
+ if (cout == NULL) {
+ warnx("No control connection for command.");
+ code = -1;
+ return (0);
+ }
+ oldintr = signal(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 && oldintr != SIG_IGN)
+ (*oldintr)(SIGINT);
+ (void)signal(SIGINT, oldintr);
+ return (r);
+}
+
+int keep_alive_timeout = 60; /* 0 -> no timeout */
+
+static int full_noops_sent = 0;
+static time_t last_timestamp = 0; /* 0 -> no measurement yet */
+static char noop[] = "NOOP\r\n";
+#define NOOP_LENGTH (sizeof noop - 1)
+static int current_nop_pos = 0; /* 0 -> no noop started */
+
+/* to achieve keep alive, we send noop one byte at a time */
+static void
+send_noop_char(void)
+{
+#ifndef SMALL
+ if (debug)
+ fprintf(ttyout, "---> %c\n", noop[current_nop_pos]);
+#endif /* !SMALL */
+ fputc(noop[current_nop_pos++], cout);
+ (void)fflush(cout);
+ if (current_nop_pos >= NOOP_LENGTH) {
+ full_noops_sent++;
+ current_nop_pos = 0;
+ }
+}
+
+static void
+may_reset_noop_timeout(void)
+{
+ if (keep_alive_timeout != 0)
+ last_timestamp = time(NULL);
+}
+
+static void
+may_receive_noop_ack(void)
+{
+ int i;
+
+ if (cout == NULL) {
+ /* Lost connection; so just pretend we're fine. */
+ current_nop_pos = full_noops_sent = 0;
+ return;
+ }
+
+ /* finish sending last incomplete noop */
+ if (current_nop_pos != 0) {
+ fputs(&(noop[current_nop_pos]), cout);
+#ifndef SMALL
+ if (debug)
+ fprintf(ttyout, "---> %s\n", &(noop[current_nop_pos]));
+#endif /* !SMALL */
+ (void)fflush(cout);
+ current_nop_pos = 0;
+ full_noops_sent++;
+ }
+ /* and get the replies */
+ for (i = 0; i < full_noops_sent; i++)
+ (void)getreply(0);
+
+ full_noops_sent = 0;
+}
+
+static void
+may_send_noop_char(void)
+{
+ if (keep_alive_timeout != 0) {
+ if (last_timestamp != 0) {
+ time_t t = time(NULL);
+
+ if (t - last_timestamp >= keep_alive_timeout) {
+ last_timestamp = t;
+ send_noop_char();
+ }
+ } else {
+ last_timestamp = time(NULL);
+ }
+ }
+}
+
+char reply_string[BUFSIZ]; /* first line of previous reply */
+
+int
+getreply(int expecteof)
+{
+ char current_line[BUFSIZ]; /* last line of previous reply */
+ int c, n, lineno;
+ int dig;
+ int originalcode = 0, continuation = 0;
+ sig_t oldintr;
+ int pflag = 0;
+ char *cp, *pt = pasv;
+
+ memset(current_line, 0, sizeof(current_line));
+ oldintr = signal(SIGINT, cmdabort);
+ for (lineno = 0 ;; lineno++) {
+ dig = n = code = 0;
+ cp = current_line;
+ while ((c = fgetc(cin)) != '\n') {
+ if (c == IAC) { /* handle telnet commands */
+ switch (c = fgetc(cin)) {
+ case WILL:
+ case WONT:
+ c = fgetc(cin);
+ fprintf(cout, "%c%c%c", IAC, DONT, c);
+ (void)fflush(cout);
+ break;
+ case DO:
+ case DONT:
+ c = fgetc(cin);
+ fprintf(cout, "%c%c%c", IAC, WONT, c);
+ (void)fflush(cout);
+ break;
+ default:
+ break;
+ }
+ continue;
+ }
+ dig++;
+ if (c == EOF) {
+ if (expecteof) {
+ (void)signal(SIGINT, oldintr);
+ code = 221;
+ return (0);
+ }
+ lostpeer();
+ if (verbose) {
+ fputs(
+"421 Service not available, remote server has closed connection.\n", ttyout);
+ (void)fflush(ttyout);
+ }
+ code = 421;
+ 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 < 4 && isdigit(c))
+ code = code * 10 + (c - '0');
+ if (!pflag && (code == 227 || code == 228))
+ pflag = 1;
+ else if (!pflag && code == 229)
+ pflag = 100;
+ if (dig > 4 && pflag == 1 && isdigit(c))
+ pflag = 2;
+ if (pflag == 2) {
+ if (c != '\r' && c != ')') {
+ if (pt < &pasv[sizeof(pasv) - 1])
+ *pt++ = c;
+ } else {
+ *pt = '\0';
+ pflag = 3;
+ }
+ }
+ if (pflag == 100 && c == '(')
+ pflag = 2;
+ if (dig == 4 && c == '-') {
+ if (continuation)
+ code = 0;
+ continuation++;
+ }
+ if (n == 0)
+ n = c;
+ if (cp < &current_line[sizeof(current_line) - 1])
+ *cp++ = c;
+ }
+ if (verbose > 0 || ((verbose > -1 && n == '5') &&
+ (n < '5' || !retry_connect))) {
+ (void)putc(c, ttyout);
+ (void)fflush (ttyout);
+ }
+ if (lineno == 0) {
+ size_t len = cp - current_line;
+
+ if (len > sizeof(reply_string))
+ len = sizeof(reply_string);
+
+ (void)strlcpy(reply_string, current_line, len);
+ }
+ if (continuation && code != originalcode) {
+ if (originalcode == 0)
+ originalcode = code;
+ continue;
+ }
+ *cp = '\0';
+ if (n != '1')
+ cpend = 0;
+ (void)signal(SIGINT, oldintr);
+ if (code == 421 || originalcode == 421)
+ lostpeer();
+ if (abrtflag && oldintr != cmdabort && oldintr != SIG_IGN)
+ (*oldintr)(SIGINT);
+ return (n - '0');
+ }
+}
+
+#ifndef SMALL
+jmp_buf sendabort;
+
+/* ARGSUSED */
+void
+abortsend(int signo)
+{
+ int save_errno = errno;
+ alarmtimer(0);
+ mflag = 0;
+ abrtflag = 0;
+#define MSG "\nsend aborted\nwaiting for remote to finish abort.\n"
+ (void) write(fileno(ttyout), MSG, strlen(MSG));
+#undef MSG
+
+ errno = save_errno;
+ longjmp(sendabort, 1);
+}
+
+void
+sendrequest(const char *cmd, const char *local, const char *remote,
+ int printnames)
+{
+ struct stat st;
+ int c, d;
+ FILE * volatile fin, * volatile dout;
+ int (* volatile closefunc)(FILE *);
+ volatile sig_t oldinti, oldintr, oldintp;
+ volatile off_t hashbytes;
+ char * volatile lmode;
+ char buf[BUFSIZ], *bufp;
+ int oprogress, serrno;
+
+ hashbytes = mark;
+ direction = "sent";
+ dout = NULL;
+ bytes = 0;
+ filesize = -1;
+ oprogress = progress;
+ if (verbose && printnames) {
+ if (local && *local != '-')
+ fprintf(ttyout, "local: %s ", local);
+ if (remote)
+ fprintf(ttyout, "remote: %s\n", remote);
+ }
+ if (proxy) {
+ proxtrans(cmd, local, remote);
+ return;
+ }
+ if (curtype != type)
+ changetype(type, 0);
+ closefunc = NULL;
+ oldintr = NULL;
+ oldintp = NULL;
+ oldinti = NULL;
+ lmode = "w";
+ if (setjmp(sendabort)) {
+ while (cpend) {
+ (void)getreply(0);
+ }
+ if (data >= 0) {
+ (void)close(data);
+ data = -1;
+ }
+ if (oldintr)
+ (void)signal(SIGINT, oldintr);
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ if (oldinti)
+ (void)signal(SIGINFO, oldinti);
+ progress = oprogress;
+ code = -1;
+ return;
+ }
+ oldintr = signal(SIGINT, abortsend);
+ oldinti = signal(SIGINFO, psummary);
+ if (strcmp(local, "-") == 0) {
+ fin = stdin;
+ if (progress == 1)
+ progress = 0;
+ } else if (*local == '|') {
+ oldintp = signal(SIGPIPE, SIG_IGN);
+ fin = popen(local + 1, "r");
+ if (fin == NULL) {
+ warn("%s", local + 1);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGPIPE, oldintp);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ if (progress == 1)
+ progress = 0;
+ closefunc = pclose;
+ } else {
+ fin = fopen(local, "r");
+ if (fin == NULL) {
+ warn("local: %s", local);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ closefunc = fclose;
+ if (fstat(fileno(fin), &st) < 0 ||
+ (st.st_mode & S_IFMT) != S_IFREG) {
+ fprintf(ttyout, "%s: not a plain file.\n", local);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ fclose(fin);
+ code = -1;
+ return;
+ }
+ filesize = st.st_size;
+ }
+ if (initconn()) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ code = -1;
+ progress = oprogress;
+ if (closefunc != NULL)
+ (*closefunc)(fin);
+ return;
+ }
+ if (setjmp(sendabort))
+ goto abort;
+
+ if (restart_point &&
+ (strcmp(cmd, "STOR") == 0 || strcmp(cmd, "APPE") == 0)) {
+ int rc = -1;
+
+ switch (curtype) {
+ case TYPE_A:
+ rc = fseeko(fin, restart_point, SEEK_SET);
+ break;
+ case TYPE_I:
+ if (lseek(fileno(fin), restart_point, SEEK_SET) != -1)
+ rc = 0;
+ break;
+ }
+ if (rc == -1) {
+ warn("local: %s", local);
+ progress = oprogress;
+ if (closefunc != NULL)
+ (*closefunc)(fin);
+ return;
+ }
+ if (command("REST %lld", (long long) restart_point)
+ != CONTINUE) {
+ progress = oprogress;
+ if (closefunc != NULL)
+ (*closefunc)(fin);
+ return;
+ }
+ lmode = "r+w";
+ }
+ if (remote) {
+ if (command("%s %s", cmd, remote) != PRELIM) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ progress = oprogress;
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ if (closefunc != NULL)
+ (*closefunc)(fin);
+ return;
+ }
+ } else
+ if (command("%s", cmd) != PRELIM) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ progress = oprogress;
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ if (closefunc != NULL)
+ (*closefunc)(fin);
+ return;
+ }
+ dout = dataconn(lmode);
+ if (dout == NULL)
+ goto abort;
+ progressmeter(-1, remote);
+ may_reset_noop_timeout();
+ oldintp = signal(SIGPIPE, SIG_IGN);
+ serrno = 0;
+ switch (curtype) {
+
+ case TYPE_I:
+ d = 0;
+ while ((c = read(fileno(fin), buf, sizeof(buf))) > 0) {
+ may_send_noop_char();
+ bytes += c;
+ for (bufp = buf; c > 0; c -= d, bufp += d)
+ if ((d = write(fileno(dout), bufp, (size_t)c))
+ <= 0)
+ break;
+ if (hash && (!progress || filesize < 0) ) {
+ while (bytes >= hashbytes) {
+ (void)putc('#', ttyout);
+ hashbytes += mark;
+ }
+ (void)fflush(ttyout);
+ }
+ }
+ if (c == -1 || d == -1)
+ serrno = errno;
+ if (hash && (!progress || filesize < 0) && bytes > 0) {
+ if (bytes < mark)
+ (void)putc('#', ttyout);
+ (void)putc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (c < 0)
+ warnc(serrno, "local: %s", local);
+ if (d < 0) {
+ if (serrno != EPIPE)
+ warnc(serrno, "netout");
+ bytes = -1;
+ }
+ break;
+
+ case TYPE_A:
+ while ((c = fgetc(fin)) != EOF) {
+ may_send_noop_char();
+ if (c == '\n') {
+ while (hash && (!progress || filesize < 0) &&
+ (bytes >= hashbytes)) {
+ (void)putc('#', ttyout);
+ (void)fflush(ttyout);
+ hashbytes += mark;
+ }
+ if (ferror(dout))
+ break;
+ (void)putc('\r', dout);
+ bytes++;
+ }
+ (void)putc(c, dout);
+ bytes++;
+ }
+ if (ferror(fin) || ferror(dout))
+ serrno = errno;
+ if (hash && (!progress || filesize < 0)) {
+ if (bytes < hashbytes)
+ (void)putc('#', ttyout);
+ (void)putc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (ferror(fin))
+ warnc(serrno, "local: %s", local);
+ if (ferror(dout)) {
+ if (errno != EPIPE)
+ warnc(serrno, "netout");
+ bytes = -1;
+ }
+ break;
+ }
+ progressmeter(1, NULL);
+ progress = oprogress;
+ if (closefunc != NULL)
+ (*closefunc)(fin);
+ (void)fclose(dout);
+ (void)getreply(0);
+ may_receive_noop_ack();
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ if (bytes > 0)
+ ptransfer(0);
+ return;
+abort:
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ progress = oprogress;
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ if (!cpend) {
+ code = -1;
+ return;
+ }
+ if (data >= 0) {
+ (void)close(data);
+ data = -1;
+ }
+ if (dout)
+ (void)fclose(dout);
+ (void)getreply(0);
+ code = -1;
+ if (closefunc != NULL && fin != NULL)
+ (*closefunc)(fin);
+ if (bytes > 0)
+ ptransfer(0);
+}
+#endif /* !SMALL */
+
+jmp_buf recvabort;
+
+/* ARGSUSED */
+void
+abortrecv(int signo)
+{
+
+ alarmtimer(0);
+ mflag = 0;
+ abrtflag = 0;
+ fputs("\nreceive aborted\nwaiting for remote to finish abort.\n", ttyout);
+ (void)fflush(ttyout);
+ longjmp(recvabort, 1);
+}
+
+void
+recvrequest(const char *cmd, const char * volatile local, const char *remote,
+ const char *lmode, int printnames, int ignorespecial)
+{
+ FILE * volatile fout, * volatile din;
+ int (* volatile closefunc)(FILE *);
+ volatile sig_t oldinti, oldintr, oldintp;
+ int c, d, serrno;
+ volatile int is_retr, tcrflag, bare_lfs;
+ static size_t bufsize;
+ static char *buf;
+ volatile off_t hashbytes;
+ struct stat st;
+ time_t mtime;
+ int oprogress;
+ int opreserve;
+
+ fout = NULL;
+ din = NULL;
+ oldinti = NULL;
+ hashbytes = mark;
+ direction = "received";
+ bytes = 0;
+ bare_lfs = 0;
+ filesize = -1;
+ oprogress = progress;
+ opreserve = preserve;
+ is_retr = strcmp(cmd, "RETR") == 0;
+ if (is_retr && verbose && printnames) {
+ if (local && (ignorespecial || *local != '-'))
+ fprintf(ttyout, "local: %s ", local);
+ if (remote)
+ fprintf(ttyout, "remote: %s\n", remote);
+ }
+ if (proxy && is_retr) {
+ proxtrans(cmd, local, remote);
+ return;
+ }
+ closefunc = NULL;
+ oldintr = NULL;
+ oldintp = NULL;
+ tcrflag = !crflag && is_retr;
+ if (setjmp(recvabort)) {
+ while (cpend) {
+ (void)getreply(0);
+ }
+ if (data >= 0) {
+ (void)close(data);
+ data = -1;
+ }
+ if (oldintr)
+ (void)signal(SIGINT, oldintr);
+ if (oldinti)
+ (void)signal(SIGINFO, oldinti);
+ progress = oprogress;
+ preserve = opreserve;
+ code = -1;
+ return;
+ }
+ oldintr = signal(SIGINT, abortrecv);
+ oldinti = signal(SIGINFO, psummary);
+ if (ignorespecial || (strcmp(local, "-") && *local != '|')) {
+ if (access(local, W_OK) < 0) {
+ char *dir;
+
+ if (errno != ENOENT && errno != EACCES) {
+ warn("local: %s", local);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ dir = strrchr(local, '/');
+ if (dir != NULL)
+ *dir = 0;
+ d = access(dir == local ? "/" : dir ? local : ".", W_OK);
+ if (dir != NULL)
+ *dir = '/';
+ if (d < 0) {
+ warn("local: %s", local);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ if (!runique && errno == EACCES &&
+ chmod(local, (S_IRUSR|S_IWUSR)) < 0) {
+ warn("local: %s", local);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ if (runique && errno == EACCES &&
+ (local = gunique(local)) == NULL) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ }
+ else if (runique && (local = gunique(local)) == NULL) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ }
+ if (!is_retr) {
+ if (curtype != TYPE_A)
+ changetype(TYPE_A, 0);
+ } else {
+ if (curtype != type)
+ changetype(type, 0);
+ filesize = remotesize(remote, 0);
+ }
+ if (initconn()) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ code = -1;
+ return;
+ }
+ if (setjmp(recvabort))
+ goto abort;
+ if (is_retr && restart_point &&
+ command("REST %lld", (long long) restart_point) != CONTINUE)
+ return;
+ if (remote) {
+ if (command("%s %s", cmd, remote) != PRELIM) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ return;
+ }
+ } else {
+ if (command("%s", cmd) != PRELIM) {
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ return;
+ }
+ }
+ din = dataconn("r");
+ if (din == NULL)
+ goto abort;
+ if (!ignorespecial && strcmp(local, "-") == 0) {
+ fout = stdout;
+ preserve = 0;
+ } else if (!ignorespecial && *local == '|') {
+ oldintp = signal(SIGPIPE, SIG_IGN);
+ fout = popen(local + 1, "w");
+ if (fout == NULL) {
+ warn("%s", local+1);
+ goto abort;
+ }
+ if (progress == 1)
+ progress = 0;
+ preserve = 0;
+ closefunc = pclose;
+ } else {
+ fout = fopen(local, lmode);
+ if (fout == NULL) {
+ warn("local: %s", local);
+ goto abort;
+ }
+ closefunc = fclose;
+ }
+ if (fstat(fileno(fout), &st) < 0 || st.st_blksize == 0)
+ st.st_blksize = BUFSIZ;
+ if (st.st_blksize > bufsize) {
+ (void)free(buf);
+ buf = malloc((unsigned)st.st_blksize);
+ if (buf == NULL) {
+ warn("malloc");
+ bufsize = 0;
+ goto abort;
+ }
+ bufsize = st.st_blksize;
+ }
+ if ((st.st_mode & S_IFMT) != S_IFREG) {
+ if (progress == 1)
+ progress = 0;
+ preserve = 0;
+ }
+ progressmeter(-1, remote);
+ may_reset_noop_timeout();
+ serrno = 0;
+ switch (curtype) {
+
+ case TYPE_I:
+ if (restart_point &&
+ lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
+ warn("local: %s", local);
+ progress = oprogress;
+ preserve = opreserve;
+ if (closefunc != NULL)
+ (*closefunc)(fout);
+ return;
+ }
+ errno = d = 0;
+ while ((c = read(fileno(din), buf, bufsize)) > 0) {
+ ssize_t wr;
+ size_t rd = c;
+
+ may_send_noop_char();
+ d = 0;
+ do {
+ wr = write(fileno(fout), buf + d, rd);
+ if (wr == -1) {
+ d = -1;
+ break;
+ }
+ d += wr;
+ rd -= wr;
+ } while (d < c);
+ if (rd != 0)
+ break;
+ bytes += c;
+ if (hash && (!progress || filesize < 0)) {
+ while (bytes >= hashbytes) {
+ (void)putc('#', ttyout);
+ hashbytes += mark;
+ }
+ (void)fflush(ttyout);
+ }
+ }
+ if (c == -1 || d < c)
+ serrno = errno;
+ if (hash && (!progress || filesize < 0) && bytes > 0) {
+ if (bytes < mark)
+ (void)putc('#', ttyout);
+ (void)putc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (c < 0) {
+ if (serrno != EPIPE)
+ warnc(serrno, "netin");
+ bytes = -1;
+ }
+ if (d < c) {
+ if (d < 0)
+ warnc(serrno, "local: %s", local);
+ else
+ warnx("%s: short write", local);
+ }
+ break;
+
+ case TYPE_A:
+ if (restart_point) {
+ int i, n, ch;
+
+ if (fseek(fout, 0L, SEEK_SET) < 0)
+ goto done;
+ n = restart_point;
+ for (i = 0; i++ < n;) {
+ if ((ch = fgetc(fout)) == EOF) {
+ if (!ferror(fout))
+ errno = 0;
+ goto done;
+ }
+ if (ch == '\n')
+ i++;
+ }
+ if (fseek(fout, 0L, SEEK_CUR) < 0) {
+done:
+ if (errno)
+ warn("local: %s", local);
+ else
+ warnx("local: %s", local);
+ progress = oprogress;
+ preserve = opreserve;
+ if (closefunc != NULL)
+ (*closefunc)(fout);
+ return;
+ }
+ }
+ while ((c = fgetc(din)) != EOF) {
+ may_send_noop_char();
+ if (c == '\n')
+ bare_lfs++;
+ while (c == '\r') {
+ while (hash && (!progress || filesize < 0) &&
+ (bytes >= hashbytes)) {
+ (void)putc('#', ttyout);
+ (void)fflush(ttyout);
+ hashbytes += mark;
+ }
+ bytes++;
+ if ((c = fgetc(din)) != '\n' || tcrflag) {
+ if (ferror(fout))
+ goto break2;
+ (void)putc('\r', fout);
+ if (c == '\0') {
+ bytes++;
+ goto contin2;
+ }
+ if (c == EOF)
+ goto contin2;
+ }
+ }
+ (void)putc(c, fout);
+ bytes++;
+ contin2: ;
+ }
+break2:
+ if (ferror(din) || ferror(fout))
+ serrno = errno;
+ if (bare_lfs) {
+ fprintf(ttyout,
+"WARNING! %d bare linefeeds received in ASCII mode.\n", bare_lfs);
+ fputs("File may not have transferred correctly.\n",
+ ttyout);
+ }
+ if (hash && (!progress || filesize < 0)) {
+ if (bytes < hashbytes)
+ (void)putc('#', ttyout);
+ (void)putc('\n', ttyout);
+ (void)fflush(ttyout);
+ }
+ if (ferror(din)) {
+ if (serrno != EPIPE)
+ warnc(serrno, "netin");
+ bytes = -1;
+ }
+ if (ferror(fout))
+ warnc(serrno, "local: %s", local);
+ break;
+ }
+ progressmeter(1, NULL);
+ progress = oprogress;
+ preserve = opreserve;
+ if (closefunc != NULL)
+ (*closefunc)(fout);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ (void)fclose(din);
+ (void)getreply(0);
+ may_receive_noop_ack();
+ if (bytes >= 0 && is_retr) {
+ if (bytes > 0)
+ ptransfer(0);
+ if (preserve && (closefunc == fclose)) {
+ mtime = remotemodtime(remote, 0);
+ if (mtime != -1) {
+ struct utimbuf ut;
+
+ ut.actime = time(NULL);
+ ut.modtime = mtime;
+ if (utime(local, &ut) == -1)
+ fprintf(ttyout,
+ "Can't change modification time on %s to %s",
+ local, asctime(localtime(&mtime)));
+ }
+ }
+ }
+ return;
+
+abort:
+ /* abort using RFC959 recommended IP,SYNC sequence */
+ progress = oprogress;
+ preserve = opreserve;
+ if (oldintp)
+ (void)signal(SIGPIPE, oldintp);
+ (void)signal(SIGINT, SIG_IGN);
+ if (!cpend) {
+ code = -1;
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+ return;
+ }
+
+ abort_remote(din);
+ code = -1;
+ if (data >= 0) {
+ (void)close(data);
+ data = -1;
+ }
+ if (closefunc != NULL && fout != NULL)
+ (*closefunc)(fout);
+ if (din)
+ (void)fclose(din);
+ if (bytes > 0)
+ ptransfer(0);
+ (void)signal(SIGINT, oldintr);
+ (void)signal(SIGINFO, oldinti);
+}
+
+/*
+ * Need to start a listen on the data channel before we send the command,
+ * otherwise the server's connect may fail.
+ */
+int
+initconn(void)
+{
+ char *p, *a;
+ int result = ERROR, tmpno = 0;
+ int on = 1;
+ int error;
+ u_int addr[16], port[2];
+ u_int af, hal, pal;
+ char *pasvcmd = NULL;
+ socklen_t namelen;
+#ifndef SMALL
+ struct addrinfo *ares;
+#endif
+
+ if (myctladdr.su_family == AF_INET6
+ && (IN6_IS_ADDR_LINKLOCAL(&myctladdr.su_sin6.sin6_addr)
+ || IN6_IS_ADDR_SITELOCAL(&myctladdr.su_sin6.sin6_addr))) {
+ warnx("use of scoped address can be troublesome");
+ }
+#ifndef SMALL
+ if (srcaddr) {
+ struct addrinfo ahints;
+
+ memset(&ahints, 0, sizeof(ahints));
+ ahints.ai_family = family;
+ ahints.ai_socktype = SOCK_STREAM;
+ ahints.ai_flags |= AI_NUMERICHOST;
+ ahints.ai_protocol = 0;
+
+ error = getaddrinfo(srcaddr, NULL, &ahints, &ares);
+ if (error) {
+ warnx("%s: %s", srcaddr, gai_strerror(error));
+ code = -1;
+ return (0);
+ }
+ }
+#endif /* !SMALL */
+reinit:
+ if (passivemode) {
+ data_addr = myctladdr;
+ data = socket(data_addr.su_family, SOCK_STREAM, 0);
+ if (data < 0) {
+ warn("socket");
+ return (1);
+ }
+#ifndef SMALL
+ if (srcaddr) {
+ if (bind(data, ares->ai_addr, ares->ai_addrlen) < 0) {
+ warn("bind");
+ close(data);
+ return (1);
+ }
+ }
+ if ((options & SO_DEBUG) &&
+ setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on,
+ sizeof(on)) < 0)
+ warn("setsockopt (ignored)");
+#endif /* !SMALL */
+ switch (data_addr.su_family) {
+ case AF_INET:
+ if (epsv4 && !epsv4bad) {
+ int ov;
+ /* shut this command up in case it fails */
+ ov = verbose;
+ verbose = -1;
+ result = command(pasvcmd = "EPSV");
+ /*
+ * now back to whatever verbosity we had before
+ * and we can try PASV
+ */
+ verbose = ov;
+ if (code / 10 == 22 && code != 229) {
+ fputs(
+"wrong server: return code must be 229\n",
+ ttyout);
+ result = COMPLETE + 1;
+ }
+ if (result != COMPLETE) {
+ epsv4bad = 1;
+#ifndef SMALL
+ if (debug) {
+ fputs(
+"disabling epsv4 for this connection\n",
+ ttyout);
+ }
+#endif /* !SMALL */
+ }
+ }
+ if (result != COMPLETE)
+ result = command(pasvcmd = "PASV");
+ break;
+ case AF_INET6:
+ result = command(pasvcmd = "EPSV");
+ if (code / 10 == 22 && code != 229) {
+ fputs(
+"wrong server: return code must be 229\n",
+ ttyout);
+ result = COMPLETE + 1;
+ }
+ if (result != COMPLETE)
+ result = command(pasvcmd = "LPSV");
+ break;
+ default:
+ result = COMPLETE + 1;
+ break;
+ }
+ if (result != COMPLETE) {
+ if (activefallback) {
+ (void)close(data);
+ data = -1;
+ passivemode = 0;
+ activefallback = 0;
+ goto reinit;
+ }
+ fputs("Passive mode refused.\n", ttyout);
+ goto bad;
+ }
+
+#define pack2(var, off) \
+ (((var[(off) + 0] & 0xff) << 8) | ((var[(off) + 1] & 0xff) << 0))
+#define pack4(var, off) \
+ (((var[(off) + 0] & 0xff) << 24) | ((var[(off) + 1] & 0xff) << 16) | \
+ ((var[(off) + 2] & 0xff) << 8) | ((var[(off) + 3] & 0xff) << 0))
+
+ /*
+ * What we've got at this point is a string of comma separated
+ * one-byte unsigned integer values, separated by commas.
+ */
+ if (!pasvcmd)
+ goto bad;
+ if (strcmp(pasvcmd, "PASV") == 0) {
+ if (data_addr.su_family != AF_INET) {
+ fputs(
+"Passive mode AF mismatch. Shouldn't happen!\n", ttyout);
+ goto bad;
+ }
+ if (code / 10 == 22 && code != 227) {
+ fputs("wrong server: return code must be 227\n",
+ ttyout);
+ goto bad;
+ }
+ error = sscanf(pasv, "%u,%u,%u,%u,%u,%u",
+ &addr[0], &addr[1], &addr[2], &addr[3],
+ &port[0], &port[1]);
+ if (error != 6) {
+ fputs(
+"Passive mode address scan failure. Shouldn't happen!\n", ttyout);
+ goto bad;
+ }
+ memset(&data_addr, 0, sizeof(data_addr));
+ data_addr.su_family = AF_INET;
+ data_addr.su_len = sizeof(struct sockaddr_in);
+ data_addr.su_sin.sin_addr.s_addr =
+ htonl(pack4(addr, 0));
+ data_addr.su_port = htons(pack2(port, 0));
+ } else if (strcmp(pasvcmd, "LPSV") == 0) {
+ if (code / 10 == 22 && code != 228) {
+ fputs("wrong server: return code must be 228\n",
+ ttyout);
+ goto bad;
+ }
+ switch (data_addr.su_family) {
+ case AF_INET:
+ error = sscanf(pasv,
+"%u,%u,%u,%u,%u,%u,%u,%u,%u",
+ &af, &hal,
+ &addr[0], &addr[1], &addr[2], &addr[3],
+ &pal, &port[0], &port[1]);
+ if (error != 9) {
+ fputs(
+"Passive mode address scan failure. Shouldn't happen!\n", ttyout);
+ goto bad;
+ }
+ if (af != 4 || hal != 4 || pal != 2) {
+ fputs(
+"Passive mode AF mismatch. Shouldn't happen!\n", ttyout);
+ error = 1;
+ goto bad;
+ }
+
+ memset(&data_addr, 0, sizeof(data_addr));
+ data_addr.su_family = AF_INET;
+ data_addr.su_len = sizeof(struct sockaddr_in);
+ data_addr.su_sin.sin_addr.s_addr =
+ htonl(pack4(addr, 0));
+ data_addr.su_port = htons(pack2(port, 0));
+ break;
+ case AF_INET6:
+ error = sscanf(pasv,
+"%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u",
+ &af, &hal,
+ &addr[0], &addr[1], &addr[2], &addr[3],
+ &addr[4], &addr[5], &addr[6], &addr[7],
+ &addr[8], &addr[9], &addr[10],
+ &addr[11], &addr[12], &addr[13],
+ &addr[14], &addr[15],
+ &pal, &port[0], &port[1]);
+ if (error != 21) {
+ fputs(
+"Passive mode address scan failure. Shouldn't happen!\n", ttyout);
+ goto bad;
+ }
+ if (af != 6 || hal != 16 || pal != 2) {
+ fputs(
+"Passive mode AF mismatch. Shouldn't happen!\n", ttyout);
+ goto bad;
+ }
+
+ memset(&data_addr, 0, sizeof(data_addr));
+ data_addr.su_family = AF_INET6;
+ data_addr.su_len = sizeof(struct sockaddr_in6);
+ {
+ u_int32_t *p32;
+ p32 = (u_int32_t *)&data_addr.su_sin6.sin6_addr;
+ p32[0] = htonl(pack4(addr, 0));
+ p32[1] = htonl(pack4(addr, 4));
+ p32[2] = htonl(pack4(addr, 8));
+ p32[3] = htonl(pack4(addr, 12));
+ }
+ data_addr.su_port = htons(pack2(port, 0));
+ break;
+ default:
+ fputs("Bad family!\n", ttyout);
+ goto bad;
+ }
+ } else if (strcmp(pasvcmd, "EPSV") == 0) {
+ char delim[4];
+
+ port[0] = 0;
+ if (code / 10 == 22 && code != 229) {
+ fputs("wrong server: return code must be 229\n",
+ ttyout);
+ goto bad;
+ }
+ if (sscanf(pasv, "%c%c%c%d%c", &delim[0],
+ &delim[1], &delim[2], &port[1],
+ &delim[3]) != 5) {
+ fputs("parse error!\n", ttyout);
+ goto bad;
+ }
+ if (delim[0] != delim[1] || delim[0] != delim[2]
+ || delim[0] != delim[3]) {
+ fputs("parse error!\n", ttyout);
+ goto bad;
+ }
+ data_addr = hisctladdr;
+ data_addr.su_port = htons(port[1]);
+ } else
+ goto bad;
+
+ while (connect(data, (struct sockaddr *)&data_addr,
+ data_addr.su_len) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (activefallback) {
+ (void)close(data);
+ data = -1;
+ passivemode = 0;
+ activefallback = 0;
+ goto reinit;
+ }
+ warn("connect");
+ goto bad;
+ }
+ if (data_addr.su_family == AF_INET) {
+ on = IPTOS_THROUGHPUT;
+ if (setsockopt(data, IPPROTO_IP, IP_TOS, (char *)&on,
+ sizeof(int)) < 0)
+ warn("setsockopt TOS (ignored)");
+ }
+ return (0);
+ }
+
+noport:
+ data_addr = myctladdr;
+ if (sendport)
+ data_addr.su_port = 0; /* let system pick one */
+ if (data != -1)
+ (void)close(data);
+ data = socket(data_addr.su_family, SOCK_STREAM, 0);
+ if (data < 0) {
+ warn("socket");
+ if (tmpno)
+ sendport = 1;
+ return (1);
+ }
+ if (!sendport)
+ if (setsockopt(data, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
+ sizeof(on)) < 0) {
+ warn("setsockopt (reuse address)");
+ goto bad;
+ }
+ switch (data_addr.su_family) {
+ case AF_INET:
+ on = IP_PORTRANGE_HIGH;
+ if (setsockopt(data, IPPROTO_IP, IP_PORTRANGE,
+ (char *)&on, sizeof(on)) < 0)
+ warn("setsockopt IP_PORTRANGE (ignored)");
+ break;
+ case AF_INET6:
+ on = IPV6_PORTRANGE_HIGH;
+ if (setsockopt(data, IPPROTO_IPV6, IPV6_PORTRANGE,
+ (char *)&on, sizeof(on)) < 0)
+ warn("setsockopt IPV6_PORTRANGE (ignored)");
+ break;
+ }
+ if (bind(data, (struct sockaddr *)&data_addr, data_addr.su_len) < 0) {
+ warn("bind");
+ goto bad;
+ }
+#ifndef SMALL
+ if (options & SO_DEBUG &&
+ setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on,
+ sizeof(on)) < 0)
+ warn("setsockopt (ignored)");
+#endif /* !SMALL */
+ namelen = sizeof(data_addr);
+ if (getsockname(data, (struct sockaddr *)&data_addr, &namelen) < 0) {
+ warn("getsockname");
+ goto bad;
+ }
+ if (listen(data, 1) < 0)
+ warn("listen");
+
+#define UC(b) (((int)b)&0xff)
+
+ if (sendport) {
+ char hname[NI_MAXHOST], pbuf[NI_MAXSERV];
+ int af_tmp;
+ union sockunion tmp;
+
+ tmp = data_addr;
+ switch (tmp.su_family) {
+ case AF_INET:
+ if (!epsv4 || epsv4bad) {
+ result = COMPLETE +1;
+ break;
+ }
+ /*FALLTHROUGH*/
+ case AF_INET6:
+ if (tmp.su_family == AF_INET6)
+ tmp.su_sin6.sin6_scope_id = 0;
+ af_tmp = (tmp.su_family == AF_INET) ? 1 : 2;
+ if (getnameinfo((struct sockaddr *)&tmp,
+ tmp.su_len, hname, sizeof(hname),
+ pbuf, sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
+ result = ERROR;
+ } else {
+ result = command("EPRT |%d|%s|%s|",
+ af_tmp, hname, pbuf);
+ if (result != COMPLETE) {
+ epsv4bad = 1;
+#ifndef SMALL
+ if (debug) {
+ fputs(
+"disabling epsv4 for this connection\n",
+ ttyout);
+ }
+#endif /* !SMALL */
+ }
+ }
+ break;
+ default:
+ result = COMPLETE + 1;
+ break;
+ }
+ if (result == COMPLETE)
+ goto skip_port;
+
+ switch (data_addr.su_family) {
+ case AF_INET:
+ a = (char *)&data_addr.su_sin.sin_addr;
+ p = (char *)&data_addr.su_port;
+ result = command("PORT %d,%d,%d,%d,%d,%d",
+ UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
+ UC(p[0]), UC(p[1]));
+ break;
+ case AF_INET6:
+ a = (char *)&data_addr.su_sin6.sin6_addr;
+ p = (char *)&data_addr.su_port;
+ result = command(
+"LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+ 6, 16,
+ UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]),
+ UC(a[4]),UC(a[5]),UC(a[6]),UC(a[7]),
+ UC(a[8]),UC(a[9]),UC(a[10]),UC(a[11]),
+ UC(a[12]),UC(a[13]),UC(a[14]),UC(a[15]),
+ 2, UC(p[0]), UC(p[1]));
+ break;
+ default:
+ result = COMPLETE + 1; /* xxx */
+ }
+ skip_port:
+
+ if (result == ERROR && sendport == -1) {
+ sendport = 0;
+ tmpno = 1;
+ goto noport;
+ }
+ return (result != COMPLETE);
+ }
+ if (tmpno)
+ sendport = 1;
+ if (data_addr.su_family == AF_INET) {
+ on = IPTOS_THROUGHPUT;
+ if (setsockopt(data, IPPROTO_IP, IP_TOS, (char *)&on,
+ sizeof(int)) < 0)
+ warn("setsockopt TOS (ignored)");
+ }
+ return (0);
+bad:
+ (void)close(data), data = -1;
+ if (tmpno)
+ sendport = 1;
+ return (1);
+}
+
+FILE *
+dataconn(const char *lmode)
+{
+ union sockunion from;
+ socklen_t fromlen = myctladdr.su_len;
+ int s;
+
+ if (passivemode)
+ return (fdopen(data, lmode));
+
+ s = accept(data, (struct sockaddr *) &from, &fromlen);
+ if (s < 0) {
+ warn("accept");
+ (void)close(data), data = -1;
+ return (NULL);
+ }
+ (void)close(data);
+ data = s;
+ if (from.su_family == AF_INET) {
+ int tos = IPTOS_THROUGHPUT;
+ if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos,
+ sizeof(int)) < 0) {
+ warn("setsockopt TOS (ignored)");
+ }
+ }
+ return (fdopen(data, lmode));
+}
+
+/* ARGSUSED */
+void
+psummary(int signo)
+{
+ int save_errno = errno;
+
+ if (bytes > 0)
+ ptransfer(1);
+ errno = save_errno;
+}
+
+/* ARGSUSED */
+void
+psabort(int signo)
+{
+
+ alarmtimer(0);
+ abrtflag++;
+}
+
+void
+pswitch(int flag)
+{
+ sig_t oldintr;
+ static struct comvars {
+ int connect;
+ char name[HOST_NAME_MAX+1];
+ union sockunion mctl;
+ union sockunion hctl;
+ FILE *in;
+ FILE *out;
+ int tpe;
+ int curtpe;
+ int cpnd;
+ int sunqe;
+ int runqe;
+ int mcse;
+ int ntflg;
+ char nti[17];
+ char nto[17];
+ int mapflg;
+ char mi[PATH_MAX];
+ char mo[PATH_MAX];
+ } proxstruct, tmpstruct;
+ struct comvars *ip, *op;
+
+ abrtflag = 0;
+ oldintr = signal(SIGINT, psabort);
+ if (flag) {
+ if (proxy)
+ return;
+ ip = &tmpstruct;
+ op = &proxstruct;
+ proxy++;
+ } else {
+ if (!proxy)
+ return;
+ ip = &proxstruct;
+ op = &tmpstruct;
+ proxy = 0;
+ }
+ ip->connect = connected;
+ connected = op->connect;
+ if (hostname) {
+ (void)strlcpy(ip->name, hostname, sizeof(ip->name));
+ } else
+ ip->name[0] = '\0';
+ hostname = op->name;
+ ip->hctl = hisctladdr;
+ hisctladdr = op->hctl;
+ ip->mctl = myctladdr;
+ myctladdr = op->mctl;
+ ip->in = cin;
+ cin = op->in;
+ ip->out = cout;
+ cout = op->out;
+ ip->tpe = type;
+ type = op->tpe;
+ ip->curtpe = curtype;
+ curtype = op->curtpe;
+ ip->cpnd = cpend;
+ cpend = op->cpnd;
+ ip->sunqe = sunique;
+ sunique = op->sunqe;
+ ip->runqe = runique;
+ runique = op->runqe;
+ ip->mcse = mcase;
+ mcase = op->mcse;
+ ip->ntflg = ntflag;
+ ntflag = op->ntflg;
+ (void)strlcpy(ip->nti, ntin, sizeof(ip->nti));
+ (void)strlcpy(ntin, op->nti, sizeof ntin);
+ (void)strlcpy(ip->nto, ntout, sizeof(ip->nto));
+ (void)strlcpy(ntout, op->nto, sizeof ntout);
+ ip->mapflg = mapflag;
+ mapflag = op->mapflg;
+ (void)strlcpy(ip->mi, mapin, sizeof(ip->mi));
+ (void)strlcpy(mapin, op->mi, sizeof mapin);
+ (void)strlcpy(ip->mo, mapout, sizeof(ip->mo));
+ (void)strlcpy(mapout, op->mo, sizeof mapout);
+ (void)signal(SIGINT, oldintr);
+ if (abrtflag) {
+ abrtflag = 0;
+ (*oldintr)(SIGINT);
+ }
+}
+
+/* ARGSUSED */
+void
+abortpt(int signo)
+{
+
+ alarmtimer(0);
+ putc('\n', ttyout);
+ (void)fflush(ttyout);
+ ptabflg++;
+ mflag = 0;
+ abrtflag = 0;
+ longjmp(ptabort, 1);
+}
+
+void
+proxtrans(const char *cmd, const char *local, const char *remote)
+{
+ volatile sig_t oldintr;
+ int prox_type, nfnd;
+ volatile int secndflag;
+ char * volatile cmd2;
+ struct pollfd pfd[1];
+
+ oldintr = NULL;
+ secndflag = 0;
+ if (strcmp(cmd, "RETR"))
+ cmd2 = "RETR";
+ else
+ cmd2 = runique ? "STOU" : "STOR";
+ if ((prox_type = type) == 0) {
+ if (unix_server && unix_proxy)
+ prox_type = TYPE_I;
+ else
+ prox_type = TYPE_A;
+ }
+ if (curtype != prox_type)
+ changetype(prox_type, 1);
+ if (command("PASV") != COMPLETE) {
+ fputs("proxy server does not support third party transfers.\n",
+ ttyout);
+ return;
+ }
+ pswitch(0);
+ if (!connected) {
+ fputs("No primary connection.\n", ttyout);
+ pswitch(1);
+ code = -1;
+ return;
+ }
+ if (curtype != prox_type)
+ changetype(prox_type, 1);
+ if (command("PORT %s", pasv) != COMPLETE) {
+ pswitch(1);
+ return;
+ }
+ if (setjmp(ptabort))
+ goto abort;
+ oldintr = signal(SIGINT, abortpt);
+ if (command("%s %s", cmd, remote) != PRELIM) {
+ (void)signal(SIGINT, oldintr);
+ pswitch(1);
+ return;
+ }
+ sleep(2);
+ pswitch(1);
+ secndflag++;
+ if (command("%s %s", cmd2, local) != PRELIM)
+ goto abort;
+ ptflag++;
+ (void)getreply(0);
+ pswitch(0);
+ (void)getreply(0);
+ (void)signal(SIGINT, oldintr);
+ pswitch(1);
+ ptflag = 0;
+ fprintf(ttyout, "local: %s remote: %s\n", local, remote);
+ return;
+abort:
+ (void)signal(SIGINT, SIG_IGN);
+ ptflag = 0;
+ if (strcmp(cmd, "RETR") && !proxy)
+ pswitch(1);
+ else if (!strcmp(cmd, "RETR") && proxy)
+ pswitch(0);
+ if (!cpend && !secndflag) { /* only here if cmd = "STOR" (proxy=1) */
+ if (command("%s %s", cmd2, local) != PRELIM) {
+ pswitch(0);
+ if (cpend)
+ abort_remote(NULL);
+ }
+ pswitch(1);
+ if (ptabflg)
+ code = -1;
+ (void)signal(SIGINT, oldintr);
+ return;
+ }
+ if (cpend)
+ abort_remote(NULL);
+ pswitch(!proxy);
+ if (!cpend && !secndflag) { /* only if cmd = "RETR" (proxy=1) */
+ if (command("%s %s", cmd2, local) != PRELIM) {
+ pswitch(0);
+ if (cpend)
+ abort_remote(NULL);
+ pswitch(1);
+ if (ptabflg)
+ code = -1;
+ (void)signal(SIGINT, oldintr);
+ return;
+ }
+ }
+ if (cpend)
+ abort_remote(NULL);
+ pswitch(!proxy);
+ if (cpend) {
+ pfd[0].fd = fileno(cin);
+ pfd[0].events = POLLIN;
+ if ((nfnd = poll(pfd, 1, 10 * 1000)) <= 0) {
+ if (nfnd < 0)
+ warn("abort");
+ if (ptabflg)
+ code = -1;
+ lostpeer();
+ }
+ (void)getreply(0);
+ (void)getreply(0);
+ }
+ if (proxy)
+ pswitch(0);
+ pswitch(1);
+ if (ptabflg)
+ code = -1;
+ (void)signal(SIGINT, oldintr);
+}
+
+#ifndef SMALL
+/* ARGSUSED */
+void
+reset(int argc, char *argv[])
+{
+ struct pollfd pfd[1];
+ int nfnd = 1;
+
+ pfd[0].fd = fileno(cin);
+ pfd[0].events = POLLIN;
+ while (nfnd > 0) {
+ if ((nfnd = poll(pfd, 1, 0)) < 0) {
+ warn("reset");
+ code = -1;
+ lostpeer();
+ } else if (nfnd) {
+ (void)getreply(0);
+ }
+ }
+}
+#endif
+
+char *
+gunique(const char *local)
+{
+ static char new[PATH_MAX];
+ char *cp = strrchr(local, '/');
+ int d, count=0;
+ char ext = '1';
+
+ if (cp)
+ *cp = '\0';
+ d = access(cp == local ? "/" : cp ? local : ".", W_OK);
+ if (cp)
+ *cp = '/';
+ if (d < 0) {
+ warn("local: %s", local);
+ return ((char *) 0);
+ }
+ (void)strlcpy(new, local, sizeof new);
+ cp = new + strlen(new);
+ *cp++ = '.';
+ while (!d) {
+ if (++count == 100) {
+ fputs("runique: can't find unique file name.\n", ttyout);
+ return ((char *) 0);
+ }
+ *cp++ = ext;
+ *cp = '\0';
+ if (ext == '9')
+ ext = '0';
+ else
+ ext++;
+ if ((d = access(new, F_OK)) < 0)
+ break;
+ if (ext != '0')
+ cp--;
+ else if (*(cp - 2) == '.')
+ *(cp - 1) = '1';
+ else {
+ *(cp - 2) = *(cp - 2) + 1;
+ cp--;
+ }
+ }
+ return (new);
+}
+
+jmp_buf forceabort;
+
+/* ARGSUSED */
+static void
+abortforce(int signo)
+{
+ int save_errno = errno;
+
+#define MSG "Forced abort. The connection will be closed.\n"
+ (void) write(fileno(ttyout), MSG, strlen(MSG));
+#undef MSG
+
+ errno = save_errno;
+ longjmp(forceabort, 1);
+}
+
+void
+abort_remote(FILE *din)
+{
+ char buf[BUFSIZ];
+ nfds_t nfds;
+ int nfnd;
+ struct pollfd pfd[2];
+ sig_t oldintr;
+
+ if (cout == NULL || setjmp(forceabort)) {
+ if (cout)
+ fclose(cout);
+ warnx("Lost control connection for abort.");
+ if (ptabflg)
+ code = -1;
+ lostpeer();
+ return;
+ }
+
+ oldintr = signal(SIGINT, abortforce);
+
+ /*
+ * send IAC in urgent mode instead of DM because 4.3BSD places oob mark
+ * after urgent byte rather than before as is protocol now
+ */
+ snprintf(buf, sizeof buf, "%c%c%c", IAC, IP, IAC);
+ if (send(fileno(cout), buf, 3, MSG_OOB) != 3)
+ warn("abort");
+ fprintf(cout, "%cABOR\r\n", DM);
+ (void)fflush(cout);
+ pfd[0].fd = fileno(cin);
+ pfd[0].events = POLLIN;
+ nfds = 1;
+ if (din) {
+ pfd[1].fd = fileno(din);
+ pfd[1].events = POLLIN;
+ nfds++;
+ }
+ if ((nfnd = poll(pfd, nfds, 10 * 1000)) <= 0) {
+ if (nfnd < 0)
+ warn("abort");
+ if (ptabflg)
+ code = -1;
+ lostpeer();
+ }
+ if (din && (pfd[1].revents & POLLIN)) {
+ while (read(fileno(din), buf, BUFSIZ) > 0)
+ /* LOOP */;
+ }
+ if (getreply(0) == ERROR && code == 552) {
+ /* 552 needed for nic style abort */
+ (void)getreply(0);
+ }
+ (void)getreply(0);
+ (void)signal(SIGINT, oldintr);
+}
diff --git a/usr.bin/ftp/ftp_var.h b/usr.bin/ftp/ftp_var.h
new file mode 100644
index 0000000..140abda
--- /dev/null
+++ b/usr.bin/ftp/ftp_var.h
@@ -0,0 +1,229 @@
+/* $OpenBSD: ftp_var.h,v 1.38 2015/02/09 08:24:21 tedu Exp $ */
+/* $NetBSD: ftp_var.h,v 1.18 1997/08/18 10:20:25 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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.
+ *
+ * @(#)ftp_var.h 8.4 (Berkeley) 10/9/94
+ */
+
+/*
+ * FTP global variables.
+ */
+
+#include <sys/signal.h>
+#include <limits.h>
+#include <setjmp.h>
+
+#ifndef SMALL
+#include <histedit.h>
+#endif /* !SMALL */
+
+#include <tls.h>
+
+#include "stringlist.h"
+#include "extern.h"
+#include "small.h"
+
+#define HASHBYTES 1024
+#define FTPBUFLEN PATH_MAX + 200
+
+#define STALLTIME 5 /* # of seconds of no xfer before "stalling" */
+
+#define FTP_PORT 21 /* default if ! getservbyname("ftp/tcp") */
+#define GATE_PORT 21 /* default if ! getservbyname("ftpgate/tcp") */
+#define HTTP_PORT 80 /* default if ! getservbyname("http/tcp") */
+#define HTTPS_PORT 443 /* default if ! getservbyname("https/tcp") */
+#define HTTP_USER_AGENT "User-Agent: OpenBSD ftp" /* User-Agent string sent to web server */
+
+#define PAGER "more" /* default pager if $PAGER isn't set */
+
+/*
+ * Options and other state info.
+ */
+int trace; /* trace packets exchanged */
+int hash; /* print # for each buffer transferred */
+int mark; /* number of bytes between hashes */
+int sendport; /* use PORT/LPRT cmd for each data connection */
+int verbose; /* print messages coming back from server */
+int connected; /* 1 = connected to server, -1 = logged in */
+int fromatty; /* input is from a terminal */
+int interactive; /* interactively prompt on m* cmds */
+#ifndef SMALL
+int confirmrest; /* confirm rest of current m* cmd */
+int debug; /* debugging level */
+int bell; /* ring bell on cmd completion */
+char *altarg; /* argv[1] with no shell-like preprocessing */
+#endif /* !SMALL */
+int doglob; /* glob local file names */
+int autologin; /* establish user account on connection */
+int proxy; /* proxy server connection active */
+int proxflag; /* proxy connection exists */
+int gatemode; /* use gate-ftp */
+char *gateserver; /* server to use for gate-ftp */
+int sunique; /* store files on server with unique name */
+int runique; /* store local files with unique name */
+int mcase; /* map upper to lower case for mget names */
+int ntflag; /* use ntin ntout tables for name translation */
+int mapflag; /* use mapin mapout templates on file names */
+int preserve; /* preserve modification time on files */
+int progress; /* display transfer progress bar */
+int code; /* return/reply code for ftp command */
+int crflag; /* if 1, strip car. rets. on ascii gets */
+char pasv[BUFSIZ]; /* passive port for proxy data connection */
+int passivemode; /* passive mode enabled */
+int activefallback; /* fall back to active mode if passive fails */
+char ntin[17]; /* input translation table */
+char ntout[17]; /* output translation table */
+char mapin[PATH_MAX]; /* input map template */
+char mapout[PATH_MAX]; /* output map template */
+char typename[32]; /* name of file transfer type */
+int type; /* requested file transfer type */
+int curtype; /* current file transfer type */
+char structname[32]; /* name of file transfer structure */
+int stru; /* file transfer structure */
+char formname[32]; /* name of file transfer format */
+int form; /* file transfer format */
+char modename[32]; /* name of file transfer mode */
+int mode; /* file transfer mode */
+char bytename[32]; /* local byte size in ascii */
+int bytesize; /* local byte size in binary */
+int anonftp; /* automatic anonymous login */
+int dirchange; /* remote directory changed by cd command */
+unsigned int retry_connect; /* retry connect if failed */
+int ttywidth; /* width of tty */
+int epsv4; /* use EPSV/EPRT on IPv4 connections */
+int epsv4bad; /* EPSV doesn't work on the current server */
+
+#ifndef SMALL
+int editing; /* command line editing enabled */
+EditLine *el; /* editline(3) status structure */
+History *hist; /* editline(3) history structure */
+char *cursor_pos; /* cursor position we're looking for */
+size_t cursor_argc; /* location of cursor in margv */
+size_t cursor_argo; /* offset of cursor in margv[cursor_argc] */
+char *cookiefile; /* cookie jar to use */
+int resume; /* continue transfer */
+char *srcaddr; /* source address to bind to */
+#endif /* !SMALL */
+
+off_t bytes; /* current # of bytes read */
+off_t filesize; /* size of file being transferred */
+char *direction; /* direction transfer is occurring */
+
+char *hostname; /* name of host connected to */
+int unix_server; /* server is unix, can use binary for ascii */
+int unix_proxy; /* proxy is unix, can use binary for ascii */
+
+char *ftpport; /* port number to use for ftp connections */
+char *httpport; /* port number to use for http connections */
+#ifndef SMALL
+char *httpsport; /* port number to use for https connections */
+#endif /* !SMALL */
+char *httpuseragent; /* user agent for http(s) connections */
+char *gateport; /* port number to use for gateftp connections */
+
+jmp_buf toplevel; /* non-local goto stuff for cmd scanner */
+
+#ifndef SMALL
+char line[FTPBUFLEN]; /* input line buffer */
+char *argbase; /* current storage point in arg buffer */
+char *stringbase; /* current scan point in line buffer */
+char argbuf[FTPBUFLEN]; /* argument storage buffer */
+StringList *marg_sl; /* stringlist containing margv */
+int margc; /* count of arguments on input line */
+int options; /* used during socket creation */
+#endif /* !SMALL */
+
+#define margv (marg_sl->sl_str) /* args parsed from input line */
+int cpend; /* flag: if != 0, then pending server reply */
+int mflag; /* flag: if != 0, then active multi command */
+
+/*
+ * Format of command table.
+ */
+struct cmd {
+ char *c_name; /* name of command */
+ char *c_help; /* help string */
+ char c_bell; /* give bell when command completes */
+ char c_conn; /* must be connected to use command */
+ char c_proxy; /* proxy server may execute */
+#ifndef SMALL
+ char *c_complete; /* context sensitive completion list */
+#endif /* !SMALL */
+ void (*c_handler)(int, char **); /* function to call */
+};
+
+struct macel {
+ char mac_name[9]; /* macro name */
+ char *mac_start; /* start of macro in macbuf */
+ char *mac_end; /* end of macro in macbuf */
+};
+
+#ifndef SMALL
+int macnum; /* number of defined macros */
+struct macel macros[16];
+char macbuf[4096];
+#endif /* !SMALL */
+
+FILE *ttyout; /* stdout or stderr, depending on interactive */
+
+extern struct cmd cmdtab[];
+
+#ifndef SMALL
+extern struct tls_config *tls_config;
+#endif /* !SMALL */
diff --git a/usr.bin/ftp/list.c b/usr.bin/ftp/list.c
new file mode 100644
index 0000000..f9e4ff5
--- /dev/null
+++ b/usr.bin/ftp/list.c
@@ -0,0 +1,86 @@
+/* $OpenBSD: list.c,v 1.7 2013/11/13 20:41:14 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2008 Martynas Venckus <martynas@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SMALL
+
+#include <string.h>
+
+void parse_list(char **, char *);
+
+static void
+parse_unix(char **line, char *type)
+{
+ char *tok;
+ int field = 0;
+
+ while ((tok = strsep(line, " \t")) != NULL) {
+ if (*tok == '\0')
+ continue;
+
+ if (field == 0)
+ *type = *tok;
+
+ if (field == 7) {
+ if (line == NULL || *line == NULL)
+ break;
+ while (**line == ' ' || **line == '\t')
+ (*line)++;
+ break;
+ }
+
+ field++;
+ }
+}
+
+static void
+parse_windows(char **line, char *type)
+{
+ char *tok;
+ int field = 0;
+
+ *type = '-';
+ while ((tok = strsep(line, " \t")) != NULL) {
+ if (*tok == '\0')
+ continue;
+
+ if (field == 2 && strcmp(tok, "<DIR>") == 0)
+ *type = 'd';
+
+ if (field == 2) {
+ if (line == NULL || *line == NULL)
+ break;
+ while (**line == ' ' || **line == '\t')
+ (*line)++;
+ break;
+ }
+
+ field++;
+ }
+}
+
+void
+parse_list(char **line, char *type)
+{
+ if (**line >= '0' && **line <= '9')
+ parse_windows(line, type);
+ else
+ parse_unix(line, type);
+}
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/main.c b/usr.bin/ftp/main.c
new file mode 100644
index 0000000..597d88f
--- /dev/null
+++ b/usr.bin/ftp/main.c
@@ -0,0 +1,921 @@
+/* $OpenBSD: main.c,v 1.109 2016/07/13 16:35:47 jsing Exp $ */
+/* $NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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.
+ */
+
+/*
+ * FTP User Program -- Command Interface.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <tls.h>
+
+#include "cmds.h"
+#include "ftp_var.h"
+
+#ifndef SMALL
+char * const ssl_verify_opts[] = {
+#define SSL_CAFILE 0
+ "cafile",
+#define SSL_CAPATH 1
+ "capath",
+#define SSL_CIPHERS 2
+ "ciphers",
+#define SSL_DONTVERIFY 3
+ "dont",
+#define SSL_DOVERIFY 4
+ "do",
+#define SSL_VERIFYDEPTH 5
+ "depth",
+ NULL
+};
+
+struct tls_config *tls_config;
+#endif /* !SMALL */
+
+int family = PF_UNSPEC;
+int pipeout;
+
+int
+main(volatile int argc, char *argv[])
+{
+ int ch, top, rval;
+ struct passwd *pw = NULL;
+ char *cp, homedir[PATH_MAX];
+ char *outfile = NULL;
+ const char *errstr;
+ int dumb_terminal = 0;
+#ifndef SMALL
+ long long depth;
+#endif
+
+ ftpport = "ftp";
+ httpport = "http";
+#ifndef SMALL
+ httpsport = "https";
+#endif /* !SMALL */
+ gateport = getenv("FTPSERVERPORT");
+ if (gateport == NULL || *gateport == '\0')
+ gateport = "ftpgate";
+ doglob = 1;
+ interactive = 1;
+ autologin = 1;
+ passivemode = 1;
+ activefallback = 1;
+ preserve = 1;
+ verbose = 0;
+ progress = 0;
+ gatemode = 0;
+#ifndef SMALL
+ editing = 0;
+ el = NULL;
+ hist = NULL;
+ cookiefile = NULL;
+ resume = 0;
+ srcaddr = NULL;
+ marg_sl = sl_init();
+#endif /* !SMALL */
+ mark = HASHBYTES;
+ epsv4 = 1;
+ epsv4bad = 0;
+
+ /* Set default operation mode based on FTPMODE environment variable */
+ if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') {
+ if (strcmp(cp, "passive") == 0) {
+ passivemode = 1;
+ activefallback = 0;
+ } else if (strcmp(cp, "active") == 0) {
+ passivemode = 0;
+ activefallback = 0;
+ } else if (strcmp(cp, "gate") == 0) {
+ gatemode = 1;
+ } else if (strcmp(cp, "auto") == 0) {
+ passivemode = 1;
+ activefallback = 1;
+ } else
+ warnx("unknown FTPMODE: %s. Using defaults", cp);
+ }
+
+ if (strcmp(__progname, "gate-ftp") == 0)
+ gatemode = 1;
+ gateserver = getenv("FTPSERVER");
+ if (gateserver == NULL)
+ gateserver = "";
+ if (gatemode) {
+ if (*gateserver == '\0') {
+ warnx(
+"Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp");
+ gatemode = 0;
+ }
+ }
+
+ cp = getenv("TERM");
+ dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") ||
+ !strcmp(cp, "emacs") || !strcmp(cp, "su"));
+ fromatty = isatty(fileno(stdin));
+ if (fromatty) {
+ verbose = 1; /* verbose if from a tty */
+#ifndef SMALL
+ if (!dumb_terminal)
+ editing = 1; /* editing mode on if tty is usable */
+#endif /* !SMALL */
+ }
+
+ ttyout = stdout;
+ if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc())
+ progress = 1; /* progress bar on if tty is usable */
+
+#ifndef SMALL
+ cookiefile = getenv("http_cookies");
+ if (tls_init() != 0)
+ errx(1, "tls init failed");
+ if (tls_config == NULL) {
+ tls_config = tls_config_new();
+ if (tls_config == NULL)
+ errx(1, "tls config failed");
+ tls_config_set_protocols(tls_config, TLS_PROTOCOLS_ALL);
+ if (tls_config_set_ciphers(tls_config, "all") != 0)
+ errx(1, "tls set ciphers failed");
+ }
+#endif /* !SMALL */
+
+ httpuseragent = NULL;
+
+ while ((ch = getopt(argc, argv,
+ "46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vV")) != -1) {
+ switch (ch) {
+ case '4':
+ family = PF_INET;
+ break;
+ case '6':
+ family = PF_INET6;
+ break;
+ case 'A':
+ activefallback = 0;
+ passivemode = 0;
+ break;
+
+ case 'a':
+ anonftp = 1;
+ break;
+
+ case 'C':
+#ifndef SMALL
+ resume = 1;
+#endif /* !SMALL */
+ break;
+
+ case 'c':
+#ifndef SMALL
+ cookiefile = optarg;
+#endif /* !SMALL */
+ break;
+
+ case 'D':
+ action = optarg;
+ break;
+ case 'd':
+#ifndef SMALL
+ options |= SO_DEBUG;
+ debug++;
+#endif /* !SMALL */
+ break;
+
+ case 'E':
+ epsv4 = 0;
+ break;
+
+ case 'e':
+#ifndef SMALL
+ editing = 0;
+#endif /* !SMALL */
+ break;
+
+ case 'g':
+ doglob = 0;
+ break;
+
+ case 'i':
+ interactive = 0;
+ break;
+
+ case 'k':
+ keep_alive_timeout = strtonum(optarg, 0, INT_MAX,
+ &errstr);
+ if (errstr != NULL) {
+ warnx("keep alive amount is %s: %s", errstr,
+ optarg);
+ usage();
+ }
+ break;
+ case 'M':
+ progress = 0;
+ break;
+ case 'm':
+ progress = -1;
+ break;
+
+ case 'n':
+ autologin = 0;
+ break;
+
+ case 'o':
+ outfile = optarg;
+ if (*outfile == '\0') {
+ pipeout = 0;
+ outfile = NULL;
+ ttyout = stdout;
+ } else {
+ pipeout = strcmp(outfile, "-") == 0;
+ ttyout = pipeout ? stderr : stdout;
+ }
+ break;
+
+ case 'p':
+ passivemode = 1;
+ activefallback = 0;
+ break;
+
+ case 'P':
+ ftpport = optarg;
+ break;
+
+ case 'r':
+ retry_connect = strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ warnx("retry amount is %s: %s", errstr,
+ optarg);
+ usage();
+ }
+ break;
+
+ case 'S':
+#ifndef SMALL
+ cp = optarg;
+ while (*cp) {
+ char *str;
+ switch (getsubopt(&cp, ssl_verify_opts, &str)) {
+ case SSL_CAFILE:
+ if (str == NULL)
+ errx(1, "missing CA file");
+ if (tls_config_set_ca_file(
+ tls_config, str) != 0)
+ errx(1, "tls ca file failed");
+ break;
+ case SSL_CAPATH:
+ if (str == NULL)
+ errx(1, "missing CA directory"
+ " path");
+ if (tls_config_set_ca_path(
+ tls_config, str) != 0)
+ errx(1, "tls ca path failed");
+ break;
+ case SSL_CIPHERS:
+ if (str == NULL)
+ errx(1, "missing cipher list");
+ if (tls_config_set_ciphers(
+ tls_config, str) != 0)
+ errx(1, "tls ciphers failed");
+ break;
+ case SSL_DONTVERIFY:
+ tls_config_insecure_noverifycert(
+ tls_config);
+ tls_config_insecure_noverifyname(
+ tls_config);
+ break;
+ case SSL_DOVERIFY:
+ tls_config_verify(tls_config);
+ break;
+ case SSL_VERIFYDEPTH:
+ if (str == NULL)
+ errx(1, "missing depth");
+ depth = strtonum(str, 0, INT_MAX,
+ &errstr);
+ if (errstr)
+ errx(1, "certificate "
+ "validation depth is %s",
+ errstr);
+ tls_config_set_verify_depth(
+ tls_config, (int)depth);
+ break;
+ default:
+ errx(1, "unknown -S suboption `%s'",
+ suboptarg ? suboptarg : "");
+ /* NOTREACHED */
+ }
+ }
+#endif
+ break;
+
+ case 's':
+#ifndef SMALL
+ srcaddr = optarg;
+#endif /* !SMALL */
+ break;
+
+ case 't':
+ trace = 1;
+ break;
+
+#ifndef SMALL
+ case 'U':
+ free (httpuseragent);
+ if (strcspn(optarg, "\r\n") != strlen(optarg))
+ errx(1, "Invalid User-Agent: %s.", optarg);
+ if (asprintf(&httpuseragent, "User-Agent: %s",
+ optarg) == -1)
+ errx(1, "Can't allocate memory for HTTP(S) "
+ "User-Agent");
+ break;
+#endif /* !SMALL */
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case 'V':
+ verbose = 0;
+ break;
+
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifndef SMALL
+ cookie_load();
+#endif /* !SMALL */
+ if (httpuseragent == NULL)
+ httpuseragent = HTTP_USER_AGENT;
+
+ cpend = 0; /* no pending replies */
+ proxy = 0; /* proxy not active */
+ crflag = 1; /* strip c.r. on ascii gets */
+ sendport = -1; /* not using ports */
+ /*
+ * Set up the home directory in case we're globbing.
+ */
+ cp = getlogin();
+ if (cp != NULL) {
+ pw = getpwnam(cp);
+ }
+ if (pw == NULL)
+ pw = getpwuid(getuid());
+ if (pw != NULL) {
+ (void)strlcpy(homedir, pw->pw_dir, sizeof homedir);
+ home = homedir;
+ }
+
+ setttywidth(0);
+ (void)signal(SIGWINCH, setttywidth);
+
+ if (argc > 0) {
+ if (isurl(argv[0])) {
+ if (pipeout) {
+#ifndef SMALL
+ if (pledge("stdio rpath dns tty inet proc exec fattr",
+ NULL) == -1)
+ err(1, "pledge");
+#else
+ if (pledge("stdio rpath dns tty inet fattr",
+ NULL) == -1)
+ err(1, "pledge");
+#endif
+ } else {
+#ifndef SMALL
+ if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr",
+ NULL) == -1)
+ err(1, "pledge");
+#else
+ if (pledge("stdio rpath wpath cpath dns tty inet fattr",
+ NULL) == -1)
+ err(1, "pledge");
+#endif
+ }
+
+ rval = auto_fetch(argc, argv, outfile);
+ if (rval >= 0) /* -1 == connected and cd-ed */
+ exit(rval);
+ } else {
+#ifndef SMALL
+ char *xargv[5];
+
+ if (setjmp(toplevel))
+ exit(0);
+ (void)signal(SIGINT, (sig_t)intr);
+ (void)signal(SIGPIPE, (sig_t)lostpeer);
+ xargv[0] = __progname;
+ xargv[1] = argv[0];
+ xargv[2] = argv[1];
+ xargv[3] = argv[2];
+ xargv[4] = NULL;
+ do {
+ setpeer(argc+1, xargv);
+ if (!retry_connect)
+ break;
+ if (!connected) {
+ macnum = 0;
+ fputs("Retrying...\n", ttyout);
+ sleep(retry_connect);
+ }
+ } while (!connected);
+ retry_connect = 0; /* connected, stop hiding msgs */
+#endif /* !SMALL */
+ }
+ }
+#ifndef SMALL
+ controlediting();
+ top = setjmp(toplevel) == 0;
+ if (top) {
+ (void)signal(SIGINT, (sig_t)intr);
+ (void)signal(SIGPIPE, (sig_t)lostpeer);
+ }
+ for (;;) {
+ cmdscanner(top);
+ top = 1;
+ }
+#else /* !SMALL */
+ usage();
+#endif /* !SMALL */
+}
+
+void
+intr(void)
+{
+ int save_errno = errno;
+
+ write(fileno(ttyout), "\n\r", 2);
+ alarmtimer(0);
+
+ errno = save_errno;
+ longjmp(toplevel, 1);
+}
+
+void
+lostpeer(void)
+{
+ int save_errno = errno;
+
+ alarmtimer(0);
+ if (connected) {
+ if (cout != NULL) {
+ (void)shutdown(fileno(cout), SHUT_RDWR);
+ (void)fclose(cout);
+ cout = NULL;
+ }
+ if (data >= 0) {
+ (void)shutdown(data, SHUT_RDWR);
+ (void)close(data);
+ data = -1;
+ }
+ connected = 0;
+ }
+ pswitch(1);
+ if (connected) {
+ if (cout != NULL) {
+ (void)shutdown(fileno(cout), SHUT_RDWR);
+ (void)fclose(cout);
+ cout = NULL;
+ }
+ connected = 0;
+ }
+ proxflag = 0;
+ pswitch(0);
+ errno = save_errno;
+}
+
+#ifndef SMALL
+/*
+ * Generate a prompt
+ */
+char *
+prompt(void)
+{
+ return ("ftp> ");
+}
+
+/*
+ * Command parser.
+ */
+void
+cmdscanner(int top)
+{
+ struct cmd *c;
+ int num;
+ HistEvent hev;
+
+ if (!top && !editing)
+ (void)putc('\n', ttyout);
+ for (;;) {
+ if (!editing) {
+ if (fromatty) {
+ fputs(prompt(), ttyout);
+ (void)fflush(ttyout);
+ }
+ if (fgets(line, sizeof(line), stdin) == NULL)
+ quit(0, 0);
+ num = strlen(line);
+ if (num == 0)
+ break;
+ if (line[--num] == '\n') {
+ if (num == 0)
+ break;
+ line[num] = '\0';
+ } else if (num == sizeof(line) - 2) {
+ fputs("sorry, input line too long.\n", ttyout);
+ while ((num = getchar()) != '\n' && num != EOF)
+ /* void */;
+ break;
+ } /* else it was a line without a newline */
+ } else {
+ const char *buf;
+ cursor_pos = NULL;
+
+ if ((buf = el_gets(el, &num)) == NULL || num == 0) {
+ putc('\n', ttyout);
+ fflush(ttyout);
+ quit(0, 0);
+ }
+ if (buf[--num] == '\n') {
+ if (num == 0)
+ break;
+ }
+ if (num >= sizeof(line)) {
+ fputs("sorry, input line too long.\n", ttyout);
+ break;
+ }
+ memcpy(line, buf, (size_t)num);
+ line[num] = '\0';
+ history(hist, &hev, H_ENTER, buf);
+ }
+
+ makeargv();
+ if (margc == 0)
+ continue;
+ c = getcmd(margv[0]);
+ if (c == (struct cmd *)-1) {
+ fputs("?Ambiguous command.\n", ttyout);
+ continue;
+ }
+ if (c == 0) {
+ /*
+ * Give editline(3) a shot at unknown commands.
+ * XXX - bogus commands with a colon in
+ * them will not elicit an error.
+ */
+ if (editing &&
+ el_parse(el, margc, (const char **)margv) != 0)
+ fputs("?Invalid command.\n", ttyout);
+ continue;
+ }
+ if (c->c_conn && !connected) {
+ fputs("Not connected.\n", ttyout);
+ continue;
+ }
+ confirmrest = 0;
+ (*c->c_handler)(margc, margv);
+ if (bell && c->c_bell)
+ (void)putc('\007', ttyout);
+ if (c->c_handler != help)
+ break;
+ }
+ (void)signal(SIGINT, (sig_t)intr);
+ (void)signal(SIGPIPE, (sig_t)lostpeer);
+}
+
+struct cmd *
+getcmd(const char *name)
+{
+ const char *p, *q;
+ struct cmd *c, *found;
+ int nmatches, longest;
+
+ if (name == NULL)
+ return (0);
+
+ longest = 0;
+ nmatches = 0;
+ found = 0;
+ for (c = cmdtab; (p = c->c_name) != NULL; c++) {
+ for (q = name; *q == *p++; q++)
+ if (*q == 0) /* exact match? */
+ return (c);
+ if (!*q) { /* the name was a prefix */
+ if (q - name > longest) {
+ longest = q - name;
+ nmatches = 1;
+ found = c;
+ } else if (q - name == longest)
+ nmatches++;
+ }
+ }
+ if (nmatches > 1)
+ return ((struct cmd *)-1);
+ return (found);
+}
+
+/*
+ * Slice a string up into argc/argv.
+ */
+
+int slrflag;
+
+void
+makeargv(void)
+{
+ char *argp;
+
+ stringbase = line; /* scan from first of buffer */
+ argbase = argbuf; /* store from first of buffer */
+ slrflag = 0;
+ marg_sl->sl_cur = 0; /* reset to start of marg_sl */
+ for (margc = 0; ; margc++) {
+ argp = slurpstring();
+ sl_add(marg_sl, argp);
+ if (argp == NULL)
+ break;
+ }
+ if (cursor_pos == line) {
+ cursor_argc = 0;
+ cursor_argo = 0;
+ } else if (cursor_pos != NULL) {
+ cursor_argc = margc;
+ cursor_argo = strlen(margv[margc-1]);
+ }
+}
+
+#define INC_CHKCURSOR(x) { (x)++ ; \
+ if (x == cursor_pos) { \
+ cursor_argc = margc; \
+ cursor_argo = ap-argbase; \
+ cursor_pos = NULL; \
+ } }
+
+/*
+ * Parse string into argbuf;
+ * implemented with FSM to
+ * handle quoting and strings
+ */
+char *
+slurpstring(void)
+{
+ int got_one = 0;
+ char *sb = stringbase;
+ char *ap = argbase;
+ char *tmp = argbase; /* will return this if token found */
+
+ if (*sb == '!' || *sb == '$') { /* recognize ! as a token for shell */
+ switch (slrflag) { /* and $ as token for macro invoke */
+ case 0:
+ slrflag++;
+ INC_CHKCURSOR(stringbase);
+ return ((*sb == '!') ? "!" : "$");
+ /* NOTREACHED */
+ case 1:
+ slrflag++;
+ altarg = stringbase;
+ break;
+ default:
+ break;
+ }
+ }
+
+S0:
+ switch (*sb) {
+
+ case '\0':
+ goto OUT;
+
+ case ' ':
+ case '\t':
+ INC_CHKCURSOR(sb);
+ goto S0;
+
+ default:
+ switch (slrflag) {
+ case 0:
+ slrflag++;
+ break;
+ case 1:
+ slrflag++;
+ altarg = sb;
+ break;
+ default:
+ break;
+ }
+ goto S1;
+ }
+
+S1:
+ switch (*sb) {
+
+ case ' ':
+ case '\t':
+ case '\0':
+ goto OUT; /* end of token */
+
+ case '\\':
+ INC_CHKCURSOR(sb);
+ goto S2; /* slurp next character */
+
+ case '"':
+ INC_CHKCURSOR(sb);
+ goto S3; /* slurp quoted string */
+
+ default:
+ *ap = *sb; /* add character to token */
+ ap++;
+ INC_CHKCURSOR(sb);
+ got_one = 1;
+ goto S1;
+ }
+
+S2:
+ switch (*sb) {
+
+ case '\0':
+ goto OUT;
+
+ default:
+ *ap = *sb;
+ ap++;
+ INC_CHKCURSOR(sb);
+ got_one = 1;
+ goto S1;
+ }
+
+S3:
+ switch (*sb) {
+
+ case '\0':
+ goto OUT;
+
+ case '"':
+ INC_CHKCURSOR(sb);
+ goto S1;
+
+ default:
+ *ap = *sb;
+ ap++;
+ INC_CHKCURSOR(sb);
+ got_one = 1;
+ goto S3;
+ }
+
+OUT:
+ if (got_one)
+ *ap++ = '\0';
+ argbase = ap; /* update storage pointer */
+ stringbase = sb; /* update scan pointer */
+ if (got_one) {
+ return (tmp);
+ }
+ switch (slrflag) {
+ case 0:
+ slrflag++;
+ break;
+ case 1:
+ slrflag++;
+ altarg = (char *) 0;
+ break;
+ default:
+ break;
+ }
+ return (NULL);
+}
+
+/*
+ * Help command.
+ * Call each command handler with argc == 0 and argv[0] == name.
+ */
+void
+help(int argc, char *argv[])
+{
+ struct cmd *c;
+
+ if (argc == 1) {
+ StringList *buf;
+
+ buf = sl_init();
+ fprintf(ttyout, "%sommands may be abbreviated. Commands are:\n\n",
+ proxy ? "Proxy c" : "C");
+ for (c = cmdtab; c < &cmdtab[NCMDS]; c++)
+ if (c->c_name && (!proxy || c->c_proxy))
+ sl_add(buf, c->c_name);
+ list_vertical(buf);
+ sl_free(buf, 0);
+ return;
+ }
+
+#define HELPINDENT ((int) sizeof("disconnect"))
+
+ while (--argc > 0) {
+ char *arg;
+
+ arg = *++argv;
+ c = getcmd(arg);
+ if (c == (struct cmd *)-1)
+ fprintf(ttyout, "?Ambiguous help command %s\n", arg);
+ else if (c == NULL)
+ fprintf(ttyout, "?Invalid help command %s\n", arg);
+ else
+ fprintf(ttyout, "%-*s\t%s\n", HELPINDENT,
+ c->c_name, c->c_help);
+ }
+}
+#endif /* !SMALL */
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: "
+#ifndef SMALL
+ "%1$s [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] "
+ "[-r seconds]\n"
+ " [-s srcaddr] [host [port]]\n"
+ " %1$s [-C] [-o output] [-s srcaddr]\n"
+ " ftp://[user:password@]host[:port]/file[/] ...\n"
+ " %1$s [-C] [-c cookie] [-o output] [-S ssl_options] "
+ "[-s srcaddr]\n"
+ " [-U useragent] "
+ "http[s]://[user:password@]host[:port]/file ...\n"
+ " %1$s [-C] [-o output] [-s srcaddr] file:file ...\n"
+ " %1$s [-C] [-o output] [-s srcaddr] host:/file[/] ...\n",
+#else /* !SMALL */
+ "%1$s [-o output] ftp://[user:password@]host[:port]/file[/] ...\n"
+ " %1$s [-o output] http://host[:port]/file ...\n"
+ " %1$s [-o output] file:file ...\n"
+ " %1$s [-o output] host:/file[/] ...\n",
+#endif /* !SMALL */
+ __progname);
+ exit(1);
+}
diff --git a/usr.bin/ftp/pathnames.h b/usr.bin/ftp/pathnames.h
new file mode 100644
index 0000000..3ac4dac
--- /dev/null
+++ b/usr.bin/ftp/pathnames.h
@@ -0,0 +1,37 @@
+/* $OpenBSD: pathnames.h,v 1.7 2003/06/03 02:56:08 millert Exp $ */
+/* $NetBSD: pathnames.h,v 1.7 1997/01/09 20:19:40 tls Exp $ */
+
+/*
+ * Copyright (c) 1989, 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/6/93
+ */
+
+#include <paths.h>
+
+#define TMPFILE "ftpXXXXXXXXXX"
diff --git a/usr.bin/ftp/ruserpass.c b/usr.bin/ftp/ruserpass.c
new file mode 100644
index 0000000..795b301
--- /dev/null
+++ b/usr.bin/ftp/ruserpass.c
@@ -0,0 +1,317 @@
+/* $OpenBSD: ruserpass.c,v 1.30 2015/01/16 06:40:08 deraadt Exp $ */
+/* $NetBSD: ruserpass.c,v 1.14 1997/07/20 09:46:01 lukem 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.
+ */
+
+#ifndef SMALL
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ftp_var.h"
+
+static int token(void);
+static FILE *cfile;
+
+#define DEFAULT 1
+#define LOGIN 2
+#define PASSWD 3
+#define ACCOUNT 4
+#define MACDEF 5
+#define ID 10
+#define MACH 11
+
+static char tokval[100];
+
+static struct toktab {
+ char *tokstr;
+ int tval;
+} toktab[]= {
+ { "default", DEFAULT },
+ { "login", LOGIN },
+ { "password", PASSWD },
+ { "passwd", PASSWD },
+ { "account", ACCOUNT },
+ { "machine", MACH },
+ { "macdef", MACDEF },
+ { NULL, 0 }
+};
+
+int
+ruserpass(const char *host, char **aname, char **apass, char **aacct)
+{
+ char *hdir, buf[PATH_MAX], *tmp;
+ char myname[HOST_NAME_MAX+1], *mydomain;
+ int t, i, c, usedefault = 0;
+ struct stat stb;
+
+ hdir = getenv("HOME");
+ if (hdir == NULL || *hdir == '\0')
+ return (0);
+ i = snprintf(buf, sizeof(buf), "%s/.netrc", hdir);
+ if (i < 0 || i >= sizeof(buf)) {
+ warnc(ENAMETOOLONG, "%s/.netrc", hdir);
+ return (0);
+ }
+ cfile = fopen(buf, "r");
+ if (cfile == NULL) {
+ if (errno != ENOENT)
+ warn("%s", buf);
+ return (0);
+ }
+ if (gethostname(myname, sizeof(myname)) < 0)
+ myname[0] = '\0';
+ if ((mydomain = strchr(myname, '.')) == NULL)
+ mydomain = "";
+next:
+ while ((t = token()) > 0) switch(t) {
+
+ case DEFAULT:
+ usedefault = 1;
+ /* FALLTHROUGH */
+
+ case MACH:
+ if (!usedefault) {
+ if ((t = token()) == -1)
+ goto bad;
+ if (t != ID)
+ continue;
+ /*
+ * Allow match either for user's input host name
+ * or official hostname. Also allow match of
+ * incompletely-specified host in local domain.
+ */
+ if (strcasecmp(host, tokval) == 0)
+ goto match;
+ if (strcasecmp(hostname, tokval) == 0)
+ goto match;
+ if ((tmp = strchr(hostname, '.')) != NULL &&
+ strcasecmp(tmp, mydomain) == 0 &&
+ strncasecmp(hostname, tokval,
+ (size_t)(tmp - hostname)) == 0 &&
+ tokval[tmp - hostname] == '\0')
+ goto match;
+ if ((tmp = strchr(host, '.')) != NULL &&
+ strcasecmp(tmp, mydomain) == 0 &&
+ strncasecmp(host, tokval,
+ (size_t)(tmp - host)) == 0 &&
+ tokval[tmp - host] == '\0')
+ goto match;
+ continue;
+ }
+ match:
+ while ((t = token()) > 0 &&
+ t != MACH && t != DEFAULT) switch(t) {
+
+ case LOGIN:
+ if ((t = token()) == -1)
+ goto bad;
+ if (t) {
+ if (*aname == 0) {
+ if ((*aname = strdup(tokval)) == NULL)
+ err(1, "strdup");
+ } else {
+ if (strcmp(*aname, tokval))
+ goto next;
+ }
+ }
+ break;
+ case PASSWD:
+ if ((*aname == NULL || strcmp(*aname, "anonymous")) &&
+ fstat(fileno(cfile), &stb) >= 0 &&
+ (stb.st_mode & 077) != 0) {
+ warnx("Error: .netrc file is readable by others.");
+ warnx("Remove password or make file unreadable by others.");
+ goto bad;
+ }
+ if ((t = token()) == -1)
+ goto bad;
+ if (t && *apass == 0) {
+ if ((*apass = strdup(tokval)) == NULL)
+ err(1, "strdup");
+ }
+ break;
+ case ACCOUNT:
+ if (fstat(fileno(cfile), &stb) >= 0
+ && (stb.st_mode & 077) != 0) {
+ warnx("Error: .netrc file is readable by others.");
+ warnx("Remove account or make file unreadable by others.");
+ goto bad;
+ }
+ if ((t = token()) == -1)
+ goto bad;
+ if (t && *aacct == 0) {
+ if ((*aacct = strdup(tokval)) == NULL)
+ err(1, "strdup");
+ }
+ break;
+ case MACDEF:
+ if (proxy) {
+ (void)fclose(cfile);
+ return (0);
+ }
+ while ((c = fgetc(cfile)) != EOF)
+ if (c != ' ' && c != '\t')
+ break;
+ if (c == EOF || c == '\n') {
+ fputs("Missing macdef name argument.\n", ttyout);
+ goto bad;
+ }
+ if (macnum == 16) {
+ fputs(
+"Limit of 16 macros have already been defined.\n", ttyout);
+ goto bad;
+ }
+ tmp = macros[macnum].mac_name;
+ *tmp++ = c;
+ for (i=0; i < 8 && (c = fgetc(cfile)) != EOF &&
+ !isspace(c); ++i) {
+ *tmp++ = c;
+ }
+ if (c == EOF) {
+ fputs(
+"Macro definition missing null line terminator.\n", ttyout);
+ goto bad;
+ }
+ *tmp = '\0';
+ if (c != '\n') {
+ while ((c = fgetc(cfile)) != EOF && c != '\n');
+ }
+ if (c == EOF) {
+ fputs(
+"Macro definition missing null line terminator.\n", ttyout);
+ goto bad;
+ }
+ 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 = fgetc(cfile)) == EOF) {
+ fputs(
+"Macro definition missing null line terminator.\n", ttyout);
+ goto bad;
+ }
+ *tmp = c;
+ if (*tmp == '\n') {
+ if (tmp == macros[macnum].mac_start) {
+ macros[macnum++].mac_end = tmp;
+ break;
+ } else if (*(tmp-1) == '\0') {
+ macros[macnum++].mac_end =
+ tmp - 1;
+ break;
+ }
+ *tmp = '\0';
+ }
+ tmp++;
+ }
+ if (tmp == macbuf + 4096) {
+ fputs("4K macro buffer exceeded.\n", ttyout);
+ goto bad;
+ }
+ break;
+ default:
+ warnx("Unknown .netrc keyword %s", tokval);
+ break;
+ }
+ goto done;
+ }
+done:
+ if (t == -1)
+ goto bad;
+ (void)fclose(cfile);
+ return (0);
+bad:
+ (void)fclose(cfile);
+ return (-1);
+}
+
+static int
+token(void)
+{
+ char *cp;
+ int c;
+ struct toktab *t;
+
+ if (feof(cfile) || ferror(cfile))
+ return (0);
+ while ((c = fgetc(cfile)) != EOF &&
+ (c == '\n' || c == '\t' || c == ' ' || c == ','))
+ continue;
+ if (c == EOF)
+ return (0);
+ cp = tokval;
+ if (c == '"') {
+ while ((c = fgetc(cfile)) != EOF && c != '"') {
+ if (c == '\\' && (c = fgetc(cfile)) == EOF)
+ break;
+ *cp++ = c;
+ if (cp == tokval + sizeof(tokval)) {
+ warnx("Token in .netrc too long");
+ return (-1);
+ }
+ }
+ } else {
+ *cp++ = c;
+ while ((c = fgetc(cfile)) != EOF
+ && c != '\n' && c != '\t' && c != ' ' && c != ',') {
+ if (c == '\\' && (c = fgetc(cfile)) == EOF)
+ break;
+ *cp++ = c;
+ if (cp == tokval + sizeof(tokval)) {
+ warnx("Token in .netrc too long");
+ return (-1);
+ }
+ }
+ }
+ *cp = 0;
+ if (tokval[0] == 0)
+ return (0);
+ for (t = toktab; t->tokstr; t++)
+ if (!strcmp(t->tokstr, tokval))
+ return (t->tval);
+ return (ID);
+}
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/small.c b/usr.bin/ftp/small.c
new file mode 100644
index 0000000..a0015a6
--- /dev/null
+++ b/usr.bin/ftp/small.c
@@ -0,0 +1,728 @@
+/* $OpenBSD: small.c,v 1.6 2016/05/25 15:36:01 krw Exp $ */
+/* $NetBSD: cmds.c,v 1.27 1997/08/18 10:20:15 lukem Exp $ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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 <fnmatch.h>
+#include <glob.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "ftp_var.h"
+#include "pathnames.h"
+#include "small.h"
+
+jmp_buf jabort;
+char *mname;
+char *home = "/";
+
+struct types {
+ char *t_name;
+ char *t_mode;
+ int t_type;
+} types[] = {
+ { "ascii", "A", TYPE_A },
+ { "binary", "I", TYPE_I },
+ { "image", "I", TYPE_I },
+ { NULL }
+};
+
+/*
+ * Set transfer type.
+ */
+void
+settype(int argc, char *argv[])
+{
+ struct types *p;
+ int comret;
+
+ if (argc > 2) {
+ char *sep;
+
+ fprintf(ttyout, "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;
+ }
+ 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 (
+#ifndef SMALL
+ !debug &&
+#endif /* !SMALL */
+ 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.
+ */
+/*ARGSUSED*/
+void
+setbinary(int argc, char *argv[])
+{
+
+ stype[1] = "binary";
+ settype(2, stype);
+}
+
+void
+get(int argc, char *argv[])
+{
+
+ (void)getit(argc, argv, 0, restart_point ? "a+w" : "w" );
+}
+
+/*
+ * Receive one file.
+ */
+int
+getit(int argc, char *argv[], int restartit, const char *mode)
+{
+ int loc = 0;
+ int rval = 0;
+ char *oldargv1, *oldargv2, *globargv2;
+
+ if (argc == 2) {
+ argc++;
+ argv[2] = argv[1];
+ loc++;
+ }
+#ifndef SMALL
+ if (argc < 2 && !another(&argc, &argv, "remote-file"))
+ goto usage;
+ if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) {
+usage:
+ fprintf(ttyout, "usage: %s remote-file [local-file]\n",
+ argv[0]);
+ code = -1;
+ return (0);
+ }
+#endif /* !SMALL */
+ oldargv1 = argv[1];
+ oldargv2 = argv[2];
+ if (!globulize(&argv[2])) {
+ code = -1;
+ return (0);
+ }
+ globargv2 = argv[2];
+ if (loc && mcase) {
+ char *tp = argv[1], *tp2, tmpbuf[PATH_MAX];
+
+ while (*tp && !islower((unsigned char)*tp)) {
+ tp++;
+ }
+ if (!*tp) {
+ tp = argv[2];
+ tp2 = tmpbuf;
+ while ((*tp2 = *tp) != '\0') {
+ if (isupper((unsigned char)*tp2)) {
+ *tp2 = tolower((unsigned char)*tp2);
+ }
+ tp++;
+ tp2++;
+ }
+ argv[2] = tmpbuf;
+ }
+ }
+ if (loc && ntflag)
+ argv[2] = dotrans(argv[2]);
+ if (loc && mapflag)
+ argv[2] = domap(argv[2]);
+#ifndef SMALL
+ if (restartit) {
+ struct stat stbuf;
+ int ret;
+
+ ret = stat(argv[2], &stbuf);
+ if (restartit == 1) {
+ restart_point = (ret < 0) ? 0 : 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;
+ fprintf(ttyout,
+ "Local file \"%s\" is newer "\
+ "than remote file \"%s\".\n",
+ argv[2], argv[1]);
+ goto freegetit;
+ }
+ }
+ }
+ }
+#endif /* !SMALL */
+
+ recvrequest("RETR", argv[2], argv[1], mode,
+ argv[1] != oldargv1 || argv[2] != oldargv2 || !interactive, loc);
+ restart_point = 0;
+freegetit:
+ if (oldargv2 != globargv2) /* free up after globulize() */
+ free(globargv2);
+ return (rval);
+}
+
+/* XXX - Signal race. */
+/* ARGSUSED */
+void
+mabort(int signo)
+{
+ int save_errno = errno;
+
+ alarmtimer(0);
+ (void) write(fileno(ttyout), "\n\r", 2);
+#ifndef SMALL
+ if (mflag && fromatty) {
+ /* XXX signal race, crazy unbelievable stdio misuse */
+ if (confirm(mname, NULL)) {
+ errno = save_errno;
+ longjmp(jabort, 1);
+ }
+ }
+#endif /* !SMALL */
+ mflag = 0;
+ errno = save_errno;
+ longjmp(jabort, 1);
+}
+
+/*
+ * Get multiple files.
+ */
+void
+mget(int argc, char *argv[])
+{
+ extern int optind, optreset;
+ sig_t oldintr;
+ int ch, xargc = 2;
+ char *cp, localcwd[PATH_MAX], *xargv[] = { argv[0], NULL, NULL };
+ static int restartit = 0;
+#ifndef SMALL
+ extern char *optarg;
+ const char *errstr;
+ int i = 1;
+ char type = 0, *dummyargv[] = { argv[0], ".", NULL };
+ FILE *ftemp = NULL;
+ static int depth = 0, max_depth = 0;
+
+ optind = optreset = 1;
+
+ if (depth)
+ depth++;
+
+ while ((ch = getopt(argc, argv, "cd:nr")) != -1) {
+ switch(ch) {
+ case 'c':
+ restartit = 1;
+ break;
+ case 'd':
+ max_depth = strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ fprintf(ttyout, "bad depth value, %s: %s\n",
+ errstr, optarg);
+ code = -1;
+ return;
+ }
+ break;
+ case 'n':
+ restartit = -1;
+ break;
+ case 'r':
+ depth = 1;
+ break;
+ default:
+ goto usage;
+ }
+ }
+
+ if (argc - optind < 1 && !another(&argc, &argv, "remote-files")) {
+usage:
+ fprintf(ttyout, "usage: %s [-cnr] [-d depth] remote-files\n",
+ argv[0]);
+ code = -1;
+ return;
+ }
+
+ argv[optind - 1] = argv[0];
+ argc -= optind - 1;
+ argv += optind - 1;
+#endif /* !SMALL */
+
+ mname = argv[0];
+ mflag = 1;
+ if (getcwd(localcwd, sizeof(localcwd)) == NULL)
+ err(1, "can't get cwd");
+
+ oldintr = signal(SIGINT, mabort);
+ (void)setjmp(jabort);
+ while ((cp =
+#ifdef SMALL
+ remglob(argv, proxy, NULL)) != NULL
+ ) {
+#else /* SMALL */
+ depth ? remglob2(dummyargv, proxy, NULL, &ftemp, &type) :
+ remglob(argv, proxy, NULL)) != NULL
+ || (mflag && depth && ++i < argc)
+ ) {
+ if (cp == NULL)
+ continue;
+#endif /* SMALL */
+ if (*cp == '\0') {
+ mflag = 0;
+ continue;
+ }
+ if (!mflag)
+ continue;
+#ifndef SMALL
+ if (depth && fnmatch(argv[i], cp, FNM_PATHNAME) != 0)
+ continue;
+#endif /* !SMALL */
+ if (!fileindir(cp, localcwd)) {
+ fprintf(ttyout, "Skipping non-relative filename `%s'\n",
+ cp);
+ continue;
+ }
+#ifndef SMALL
+ if (type == 'd' && depth == max_depth)
+ continue;
+ if (!confirm(argv[0], cp))
+ continue;
+ if (type == 'd') {
+ mkdir(cp, 0755);
+ if (chdir(cp) != 0) {
+ warn("local: %s", cp);
+ continue;
+ }
+
+ xargv[1] = cp;
+ cd(xargc, xargv);
+ if (dirchange != 1)
+ goto out;
+
+ xargv[1] = "*";
+ mget(xargc, xargv);
+
+ xargv[1] = "..";
+ cd(xargc, xargv);
+ if (dirchange != 1) {
+ mflag = 0;
+ goto out;
+ }
+
+out:
+ if (chdir("..") != 0) {
+ warn("local: %s", cp);
+ mflag = 0;
+ }
+ continue;
+ }
+ if (type == 's')
+ /* Currently ignored. */
+ continue;
+#endif /* !SMALL */
+ xargv[1] = cp;
+ (void)getit(xargc, xargv, restartit,
+ (restartit == 1 || restart_point) ? "a+w" : "w");
+#ifndef SMALL
+ if (!mflag && fromatty) {
+ if (confirm(argv[0], NULL))
+ mflag = 1;
+ }
+#endif /* !SMALL */
+ }
+ (void)signal(SIGINT, oldintr);
+#ifndef SMALL
+ if (depth)
+ depth--;
+ if (depth == 0 || mflag == 0)
+ depth = max_depth = mflag = restartit = 0;
+#else /* !SMALL */
+ mflag = 0;
+#endif /* !SMALL */
+}
+
+/*
+ * Set current working directory on remote machine.
+ */
+void
+cd(int argc, char *argv[])
+{
+ int r;
+
+#ifndef SMALL
+ if ((argc < 2 && !another(&argc, &argv, "remote-directory")) ||
+ argc > 2) {
+ fprintf(ttyout, "usage: %s remote-directory\n", argv[0]);
+ code = -1;
+ return;
+ }
+#endif /* !SMALL */
+ 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 == ERROR && code == 550) {
+ dirchange = 0;
+ return;
+ }
+ if (r == COMPLETE)
+ dirchange = 1;
+}
+
+/*
+ * Terminate session, but don't exit.
+ */
+/* ARGSUSED */
+void
+disconnect(int argc, char *argv[])
+{
+
+ if (!connected)
+ return;
+ (void)command("QUIT");
+ if (cout) {
+ (void)fclose(cout);
+ }
+ cout = NULL;
+ connected = 0;
+ data = -1;
+#ifndef SMALL
+ if (!proxy) {
+ macnum = 0;
+ }
+#endif /* !SMALL */
+}
+
+char *
+dotrans(char *name)
+{
+ static char new[PATH_MAX];
+ char *cp1, *cp2 = new;
+ int i, ostop, found;
+
+ for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++)
+ continue;
+ for (cp1 = name; *cp1; cp1++) {
+ found = 0;
+ for (i = 0; *(ntin + i) && i < 16; i++) {
+ if (*cp1 == *(ntin + i)) {
+ found++;
+ if (i < ostop) {
+ *cp2++ = *(ntout + i);
+ }
+ break;
+ }
+ }
+ if (!found) {
+ *cp2++ = *cp1;
+ }
+ }
+ *cp2 = '\0';
+ return (new);
+}
+
+char *
+domap(char *name)
+{
+ static char new[PATH_MAX];
+ char *cp1 = name, *cp2 = mapin;
+ 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;
+ }
+ cp1 = new;
+ *cp1 = '\0';
+ cp2 = mapout;
+ while (*cp2) {
+ match = 0;
+ switch (*cp2) {
+ case '\\':
+ if (*(cp2 + 1)) {
+ *cp1++ = *++cp2;
+ }
+ break;
+ case '[':
+LOOP:
+ if (*++cp2 == '$' && isdigit((unsigned char)*(cp2 + 1))) {
+ if (*++cp2 == '0') {
+ char *cp3 = name;
+
+ while (*cp3) {
+ *cp1++ = *cp3++;
+ }
+ match = 1;
+ }
+ else if (toks[toknum = *cp2 - '1']) {
+ char *cp3 = tp[toknum];
+
+ while (cp3 != te[toknum]) {
+ *cp1++ = *cp3++;
+ }
+ match = 1;
+ }
+ }
+ else {
+ while (*cp2 && *cp2 != ',' &&
+ *cp2 != ']') {
+ if (*cp2 == '\\') {
+ cp2++;
+ }
+ else if (*cp2 == '$' &&
+ isdigit((unsigned char)*(cp2 + 1))) {
+ if (*++cp2 == '0') {
+ char *cp3 = name;
+
+ while (*cp3) {
+ *cp1++ = *cp3++;
+ }
+ }
+ else if (toks[toknum =
+ *cp2 - '1']) {
+ char *cp3=tp[toknum];
+
+ while (cp3 !=
+ te[toknum]) {
+ *cp1++ = *cp3++;
+ }
+ }
+ }
+ else if (*cp2) {
+ *cp1++ = *cp2++;
+ }
+ }
+ if (!*cp2) {
+ fputs(
+"nmap: unbalanced brackets.\n", ttyout);
+ return (name);
+ }
+ match = 1;
+ cp2--;
+ }
+ if (match) {
+ while (*++cp2 && *cp2 != ']') {
+ if (*cp2 == '\\' && *(cp2 + 1)) {
+ cp2++;
+ }
+ }
+ if (!*cp2) {
+ fputs(
+"nmap: unbalanced brackets.\n", ttyout);
+ return (name);
+ }
+ break;
+ }
+ switch (*++cp2) {
+ case ',':
+ goto LOOP;
+ case ']':
+ break;
+ default:
+ cp2--;
+ goto LOOP;
+ }
+ break;
+ case '$':
+ if (isdigit((unsigned char)*(cp2 + 1))) {
+ if (*++cp2 == '0') {
+ char *cp3 = name;
+
+ while (*cp3) {
+ *cp1++ = *cp3++;
+ }
+ }
+ else if (toks[toknum = *cp2 - '1']) {
+ char *cp3 = tp[toknum];
+
+ while (cp3 != te[toknum]) {
+ *cp1++ = *cp3++;
+ }
+ }
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ *cp1++ = *cp2;
+ break;
+ }
+ cp2++;
+ }
+ *cp1 = '\0';
+ if (!*new) {
+ return (name);
+ }
+ return (new);
+}
+
diff --git a/usr.bin/ftp/small.h b/usr.bin/ftp/small.h
new file mode 100644
index 0000000..a051f2d
--- /dev/null
+++ b/usr.bin/ftp/small.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: small.h,v 1.1 2009/05/05 19:35:30 martynas Exp $ */
+
+/*
+ * Copyright (c) 2009 Martynas Venckus <martynas@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+extern jmp_buf jabort;
+extern char *mname;
+extern char *home;
+extern char *stype[];
+
+void settype(int, char **);
+void changetype(int, int);
+void setbinary(int, char **);
+void get(int, char **);
+int getit(int, char **, int, const char *);
+void mabort(int);
+void mget(int, char **);
+void cd(int, char **);
+void disconnect(int, char **);
+char *dotrans(char *);
+char *domap(char *);
+
diff --git a/usr.bin/ftp/stringlist.c b/usr.bin/ftp/stringlist.c
new file mode 100644
index 0000000..0e76287
--- /dev/null
+++ b/usr.bin/ftp/stringlist.c
@@ -0,0 +1,97 @@
+/* $OpenBSD: stringlist.c,v 1.12 2015/05/20 23:39:55 schwarze Exp $ */
+/* $NetBSD: stringlist.c,v 1.2 1997/01/17 07:26:20 lukem Exp $ */
+
+/*
+ * Copyright (c) 1994 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#ifndef SMALL
+
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+#include <stdlib.h>
+
+#include "stringlist.h"
+
+#define _SL_CHUNKSIZE 20
+
+/*
+ * sl_init(): Initialize a string list
+ */
+StringList *
+sl_init(void)
+{
+ StringList *sl = malloc(sizeof(StringList));
+ if (sl == NULL)
+ err(1, "stringlist");
+
+ sl->sl_cur = 0;
+ sl->sl_max = _SL_CHUNKSIZE;
+ sl->sl_str = calloc(sl->sl_max, sizeof(char *));
+ if (sl->sl_str == NULL)
+ err(1, "stringlist");
+ return sl;
+}
+
+
+/*
+ * sl_add(): Add an item to the string list
+ */
+void
+sl_add(StringList *sl, char *name)
+{
+ if (sl->sl_cur == sl->sl_max - 1) {
+ sl->sl_max += _SL_CHUNKSIZE;
+ sl->sl_str = reallocarray(sl->sl_str, sl->sl_max,
+ sizeof(char *));
+ if (sl->sl_str == NULL)
+ err(1, "stringlist");
+ }
+ sl->sl_str[sl->sl_cur++] = name;
+}
+
+
+/*
+ * sl_free(): Free a stringlist
+ */
+void
+sl_free(StringList *sl, int all)
+{
+ size_t i;
+
+ if (sl == NULL)
+ return;
+ if (sl->sl_str) {
+ if (all)
+ for (i = 0; i < sl->sl_cur; i++)
+ free(sl->sl_str[i]);
+ free(sl->sl_str);
+ }
+ free(sl);
+}
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/stringlist.h b/usr.bin/ftp/stringlist.h
new file mode 100644
index 0000000..b33bbf0
--- /dev/null
+++ b/usr.bin/ftp/stringlist.h
@@ -0,0 +1,56 @@
+/* $OpenBSD: stringlist.h,v 1.5 2015/05/20 23:39:55 schwarze Exp $ */
+/* $NetBSD: stringlist.h,v 1.2 1997/01/17 06:11:36 lukem Exp $ */
+
+/*
+ * Copyright (c) 1994 Christos Zoulas
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#ifndef SMALL
+
+#ifndef _STRINGLIST_H
+#define _STRINGLIST_H
+
+#include <sys/types.h>
+
+/*
+ * Simple string list
+ */
+typedef struct _stringlist {
+ char **sl_str;
+ size_t sl_max;
+ size_t sl_cur;
+} StringList;
+
+__BEGIN_DECLS
+StringList *sl_init(void);
+void sl_add(StringList *, char *);
+void sl_free(StringList *, int);
+char *sl_find(StringList *, char *);
+__END_DECLS
+
+#endif /* _STRINGLIST_H */
+
+#endif /* !SMALL */
+
diff --git a/usr.bin/ftp/util.c b/usr.bin/ftp/util.c
new file mode 100644
index 0000000..d1e52fa
--- /dev/null
+++ b/usr.bin/ftp/util.c
@@ -0,0 +1,1071 @@
+/* $OpenBSD: util.c,v 1.77 2016/03/16 15:41:11 krw Exp $ */
+/* $NetBSD: util.c,v 1.12 1997/08/18 10:20:27 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1997-1999 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.
+ *
+ * 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.
+ */
+
+/*
+ * FTP User Program -- Misc support routines
+ */
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <arpa/ftp.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <glob.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "ftp_var.h"
+#include "pathnames.h"
+
+#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+
+static void updateprogressmeter(int);
+
+/*
+ * Connect to peer server and
+ * auto-login, if possible.
+ */
+void
+setpeer(int argc, char *argv[])
+{
+ char *host, *port;
+
+ if (connected) {
+ fprintf(ttyout, "Already connected to %s, use close first.\n",
+ hostname);
+ code = -1;
+ return;
+ }
+#ifndef SMALL
+ if (argc < 2)
+ (void)another(&argc, &argv, "to");
+ if (argc < 2 || argc > 3) {
+ fprintf(ttyout, "usage: %s host [port]\n", argv[0]);
+ code = -1;
+ return;
+ }
+#endif /* !SMALL */
+ if (gatemode)
+ port = gateport;
+ else
+ port = ftpport;
+ if (argc > 2)
+ port = argv[2];
+
+ if (gatemode) {
+ if (gateserver == NULL || *gateserver == '\0')
+ errx(1, "gateserver not defined (shouldn't happen)");
+ host = hookup(gateserver, port);
+ } else
+ host = hookup(argv[1], port);
+
+ if (host) {
+ int overbose;
+
+ if (gatemode) {
+ if (command("PASSERVE %s", argv[1]) != COMPLETE)
+ return;
+ if (verbose)
+ fprintf(ttyout,
+ "Connected via pass-through server %s\n",
+ gateserver);
+ }
+
+ connected = 1;
+ /*
+ * Set up defaults for FTP.
+ */
+ (void)strlcpy(formname, "non-print", sizeof formname);
+ form = FORM_N;
+ (void)strlcpy(modename, "stream", sizeof modename);
+ mode = MODE_S;
+ (void)strlcpy(structname, "file", sizeof structname);
+ stru = STRU_F;
+ (void)strlcpy(bytename, "8", sizeof bytename);
+ bytesize = 8;
+
+ /*
+ * Set type to 0 (not specified by user),
+ * meaning binary by default, but don't bother
+ * telling server. We can use binary
+ * for text files unless changed by the user.
+ */
+ (void)strlcpy(typename, "binary", sizeof typename);
+ curtype = TYPE_A;
+ type = 0;
+ if (autologin)
+ (void)ftp_login(argv[1], NULL, NULL);
+
+ overbose = verbose;
+#ifndef SMALL
+ if (!debug)
+#endif /* !SMALL */
+ verbose = -1;
+ if (command("SYST") == COMPLETE && overbose) {
+ char *cp, c;
+ c = 0;
+ cp = strchr(reply_string + 4, ' ');
+ if (cp == NULL)
+ cp = strchr(reply_string + 4, '\r');
+ if (cp) {
+ if (cp[-1] == '.')
+ cp--;
+ c = *cp;
+ *cp = '\0';
+ }
+
+ fprintf(ttyout, "Remote system type is %s.\n", reply_string + 4);
+ if (cp)
+ *cp = c;
+ }
+ if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
+ if (proxy)
+ unix_proxy = 1;
+ else
+ unix_server = 1;
+ if (overbose)
+ fprintf(ttyout, "Using %s mode to transfer files.\n",
+ typename);
+ } else {
+ if (proxy)
+ unix_proxy = 0;
+ else
+ unix_server = 0;
+ }
+ verbose = overbose;
+ }
+}
+
+/*
+ * login to remote host, using given username & password if supplied
+ */
+int
+ftp_login(const char *host, char *user, char *pass)
+{
+ char tmp[80], *acctname = NULL, host_name[HOST_NAME_MAX+1];
+ char anonpass[LOGIN_NAME_MAX + 1 + HOST_NAME_MAX+1]; /* "user@hostname" */
+ int n, aflag = 0, retry = 0;
+ struct passwd *pw;
+
+#ifndef SMALL
+ if (user == NULL) {
+ if (ruserpass(host, &user, &pass, &acctname) < 0) {
+ code = -1;
+ return (0);
+ }
+ }
+#endif /* !SMALL */
+
+ /*
+ * Set up arguments for an anonymous FTP session, if necessary.
+ */
+ if ((user == NULL || pass == NULL) && anonftp) {
+ memset(anonpass, 0, sizeof(anonpass));
+ memset(host_name, 0, sizeof(host_name));
+
+ /*
+ * Set up anonymous login password.
+ */
+ if ((user = getlogin()) == NULL) {
+ if ((pw = getpwuid(getuid())) == NULL)
+ user = "anonymous";
+ else
+ user = pw->pw_name;
+ }
+ gethostname(host_name, sizeof(host_name));
+#ifndef DONT_CHEAT_ANONPASS
+ /*
+ * Every anonymous FTP server I've encountered
+ * will accept the string "username@", and will
+ * append the hostname itself. We do this by default
+ * since many servers are picky about not having
+ * a FQDN in the anonymous password. - thorpej@netbsd.org
+ */
+ snprintf(anonpass, sizeof(anonpass) - 1, "%s@",
+ user);
+#else
+ snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s",
+ user, hp->h_name);
+#endif
+ pass = anonpass;
+ user = "anonymous"; /* as per RFC 1635 */
+ }
+
+tryagain:
+ if (retry)
+ user = "ftp"; /* some servers only allow "ftp" */
+
+ while (user == NULL) {
+ char *myname = getlogin();
+
+ if (myname == NULL && (pw = getpwuid(getuid())) != NULL)
+ myname = pw->pw_name;
+ if (myname)
+ fprintf(ttyout, "Name (%s:%s): ", host, myname);
+ else
+ fprintf(ttyout, "Name (%s): ", host);
+ user = myname;
+ if (fgets(tmp, sizeof(tmp), stdin) != NULL) {
+ tmp[strcspn(tmp, "\n")] = '\0';
+ if (tmp[0] != '\0')
+ user = tmp;
+ }
+ else
+ exit(0);
+ }
+ n = command("USER %s", user);
+ if (n == CONTINUE) {
+ if (pass == NULL)
+ pass = getpass("Password:");
+ n = command("PASS %s", pass);
+ }
+ if (n == CONTINUE) {
+ aflag++;
+ if (acctname == NULL)
+ acctname = getpass("Account:");
+ n = command("ACCT %s", acctname);
+ }
+ if ((n != COMPLETE) ||
+ (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
+ warnx("Login %s failed.", user);
+ if (retry || !anonftp)
+ return (0);
+ else
+ retry = 1;
+ goto tryagain;
+ }
+ if (proxy)
+ return (1);
+ connected = -1;
+#ifndef SMALL
+ for (n = 0; n < macnum; ++n) {
+ if (!strcmp("init", macros[n].mac_name)) {
+ (void)strlcpy(line, "$init", sizeof line);
+ makeargv();
+ domacro(margc, margv);
+ break;
+ }
+ }
+#endif /* SMALL */
+ return (1);
+}
+
+/*
+ * `another' gets another argument, and stores the new argc and argv.
+ * It reverts to the top level (via main.c's intr()) on EOF/error.
+ *
+ * Returns false if no new arguments have been added.
+ */
+#ifndef SMALL
+int
+another(int *pargc, char ***pargv, const char *prompt)
+{
+ int len = strlen(line), ret;
+
+ if (len >= sizeof(line) - 3) {
+ fputs("sorry, arguments too long.\n", ttyout);
+ intr();
+ }
+ fprintf(ttyout, "(%s) ", prompt);
+ line[len++] = ' ';
+ if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) {
+ clearerr(stdin);
+ intr();
+ }
+ len += strlen(&line[len]);
+ if (len > 0 && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ makeargv();
+ ret = margc > *pargc;
+ *pargc = margc;
+ *pargv = margv;
+ return (ret);
+}
+#endif /* !SMALL */
+
+/*
+ * glob files given in argv[] from the remote server.
+ * if errbuf isn't NULL, store error messages there instead
+ * of writing to the screen.
+ * if type isn't NULL, use LIST instead of NLST, and store filetype.
+ * 'd' means directory, 's' means symbolic link, '-' means plain
+ * file.
+ */
+char *
+remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type)
+{
+ char temp[PATH_MAX], *bufp, *cp, *lmode;
+ static char buf[PATH_MAX], **args;
+ int oldverbose, oldhash, fd;
+
+ if (!mflag) {
+ if (!doglob)
+ args = NULL;
+ else {
+ if (*ftemp) {
+ (void)fclose(*ftemp);
+ *ftemp = NULL;
+ }
+ }
+ return (NULL);
+ }
+ if (!doglob) {
+ if (args == NULL)
+ args = argv;
+ if ((cp = *++args) == NULL)
+ args = NULL;
+ return (cp);
+ }
+ if (*ftemp == NULL) {
+ int len;
+
+ if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0')
+ cp = _PATH_TMP;
+ len = strlen(cp);
+ if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
+ warnx("unable to create temporary file: %s",
+ strerror(ENAMETOOLONG));
+ return (NULL);
+ }
+
+ (void)strlcpy(temp, cp, sizeof temp);
+ if (temp[len-1] != '/')
+ temp[len++] = '/';
+ (void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
+ if ((fd = mkstemp(temp)) < 0) {
+ warn("unable to create temporary file: %s", temp);
+ return (NULL);
+ }
+ close(fd);
+ oldverbose = verbose;
+ verbose = (errbuf != NULL) ? -1 : 0;
+ oldhash = hash;
+ hash = 0;
+ if (doswitch)
+ pswitch(!proxy);
+ for (lmode = "w"; *++argv != NULL; lmode = "a")
+ recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode,
+ 0, 0);
+ if ((code / 100) != COMPLETE) {
+ if (errbuf != NULL)
+ *errbuf = reply_string;
+ }
+ if (doswitch)
+ pswitch(!proxy);
+ verbose = oldverbose;
+ hash = oldhash;
+ *ftemp = fopen(temp, "r");
+ (void)unlink(temp);
+ if (*ftemp == NULL) {
+ if (errbuf == NULL)
+ fputs("can't find list of remote files, oops.\n",
+ ttyout);
+ else
+ *errbuf =
+ "can't find list of remote files, oops.";
+ return (NULL);
+ }
+ }
+again:
+ if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
+ (void)fclose(*ftemp);
+ *ftemp = NULL;
+ return (NULL);
+ }
+
+ buf[strcspn(buf, "\n")] = '\0';
+ bufp = buf;
+
+#ifndef SMALL
+ if (type) {
+ parse_list(&bufp, type);
+ if (!bufp ||
+ (bufp[0] == '.' && /* LIST defaults to -a on some ftp */
+ (bufp[1] == '\0' || /* servers. Ignore '.' and '..'. */
+ (bufp[1] == '.' && bufp[2] == '\0'))))
+ goto again;
+ }
+#endif /* !SMALL */
+
+ return (bufp);
+}
+
+/*
+ * wrapper for remglob2
+ */
+char *
+remglob(char *argv[], int doswitch, char **errbuf)
+{
+ static FILE *ftemp = NULL;
+
+ return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
+}
+
+#ifndef SMALL
+int
+confirm(const char *cmd, const char *file)
+{
+ char str[BUFSIZ];
+
+ if (file && (confirmrest || !interactive))
+ return (1);
+top:
+ if (file)
+ fprintf(ttyout, "%s %s? ", cmd, file);
+ else
+ fprintf(ttyout, "Continue with %s? ", cmd);
+ (void)fflush(ttyout);
+ if (fgets(str, sizeof(str), stdin) == NULL)
+ goto quit;
+ switch (tolower((unsigned char)*str)) {
+ case '?':
+ fprintf(ttyout,
+ "? help\n"
+ "a answer yes to all\n"
+ "n answer no\n"
+ "p turn off prompt mode\n"
+ "q answer no to all\n"
+ "y answer yes\n");
+ goto top;
+ case 'a':
+ confirmrest = 1;
+ fprintf(ttyout, "Prompting off for duration of %s.\n",
+ cmd);
+ break;
+ case 'n':
+ return (0);
+ case 'p':
+ interactive = 0;
+ fputs("Interactive mode: off.\n", ttyout);
+ break;
+ case 'q':
+quit:
+ mflag = 0;
+ clearerr(stdin);
+ return (0);
+ case 'y':
+ return(1);
+ break;
+ default:
+ fprintf(ttyout, "?, a, n, p, q, y "
+ "are the only acceptable commands!\n");
+ goto top;
+ break;
+ }
+ return (1);
+}
+#endif /* !SMALL */
+
+/*
+ * Glob a local file name specification with
+ * the expectation of a single return value.
+ * Can't control multiple values being expanded
+ * from the expression, we return only the first.
+ */
+int
+globulize(char **cpp)
+{
+ glob_t gl;
+ int flags;
+
+ if (!doglob)
+ return (1);
+
+ flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
+ memset(&gl, 0, sizeof(gl));
+ if (glob(*cpp, flags, NULL, &gl) ||
+ gl.gl_pathc == 0) {
+ warnx("%s: not found", *cpp);
+ globfree(&gl);
+ return (0);
+ }
+ /* XXX: caller should check if *cpp changed, and
+ * free(*cpp) if that is the case
+ */
+ *cpp = strdup(gl.gl_pathv[0]);
+ if (*cpp == NULL)
+ err(1, NULL);
+ globfree(&gl);
+ return (1);
+}
+
+/*
+ * determine size of remote file
+ */
+off_t
+remotesize(const char *file, int noisy)
+{
+ int overbose;
+ off_t size;
+
+ overbose = verbose;
+ size = -1;
+#ifndef SMALL
+ if (!debug)
+#endif /* !SMALL */
+ verbose = -1;
+ if (command("SIZE %s", file) == COMPLETE) {
+ char *cp, *ep;
+
+ cp = strchr(reply_string, ' ');
+ if (cp != NULL) {
+ cp++;
+ size = strtoq(cp, &ep, 10);
+ if (*ep != '\0' && !isspace((unsigned char)*ep))
+ size = -1;
+ }
+ } else if (noisy
+#ifndef SMALL
+ && !debug
+#endif /* !SMALL */
+ ) {
+ fputs(reply_string, ttyout);
+ fputc('\n', ttyout);
+ }
+ verbose = overbose;
+ return (size);
+}
+
+/*
+ * determine last modification time (in GMT) of remote file
+ */
+time_t
+remotemodtime(const char *file, int noisy)
+{
+ int overbose;
+ time_t rtime;
+ int ocode;
+
+ overbose = verbose;
+ ocode = code;
+ rtime = -1;
+#ifndef SMALL
+ if (!debug)
+#endif /* !SMALL */
+ verbose = -1;
+ if (command("MDTM %s", file) == COMPLETE) {
+ struct tm timebuf;
+ int yy, mo, day, hour, min, sec;
+ /*
+ * time-val = 14DIGIT [ "." 1*DIGIT ]
+ * YYYYMMDDHHMMSS[.sss]
+ * mdtm-response = "213" SP time-val CRLF / error-response
+ */
+ /* TODO: parse .sss as well, use timespecs. */
+ char *timestr = reply_string;
+
+ /* Repair `19%02d' bug on server side */
+ while (!isspace((unsigned char)*timestr))
+ timestr++;
+ while (isspace((unsigned char)*timestr))
+ timestr++;
+ if (strncmp(timestr, "191", 3) == 0) {
+ fprintf(ttyout,
+ "Y2K warning! Fixed incorrect time-val received from server.\n");
+ timestr[0] = ' ';
+ timestr[1] = '2';
+ timestr[2] = '0';
+ }
+ sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
+ &day, &hour, &min, &sec);
+ memset(&timebuf, 0, sizeof(timebuf));
+ timebuf.tm_sec = sec;
+ timebuf.tm_min = min;
+ timebuf.tm_hour = hour;
+ timebuf.tm_mday = day;
+ timebuf.tm_mon = mo - 1;
+ timebuf.tm_year = yy - 1900;
+ timebuf.tm_isdst = -1;
+ rtime = mktime(&timebuf);
+ if (rtime == -1 && (noisy
+#ifndef SMALL
+ || debug
+#endif /* !SMALL */
+ ))
+ fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
+ else
+ rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */
+ } else if (noisy
+#ifndef SMALL
+ && !debug
+#endif /* !SMALL */
+ ) {
+ fputs(reply_string, ttyout);
+ fputc('\n', ttyout);
+ }
+ verbose = overbose;
+ if (rtime == -1)
+ code = ocode;
+ return (rtime);
+}
+
+/*
+ * Ensure file is in or under dir.
+ * Returns 1 if so, 0 if not (or an error occurred).
+ */
+int
+fileindir(const char *file, const char *dir)
+{
+ char parentdirbuf[PATH_MAX], *parentdir;
+ char realdir[PATH_MAX];
+ size_t dirlen;
+
+ /* determine parent directory of file */
+ (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
+ parentdir = dirname(parentdirbuf);
+ if (strcmp(parentdir, ".") == 0)
+ return 1; /* current directory is ok */
+
+ /* find the directory */
+ if (realpath(parentdir, realdir) == NULL) {
+ warn("Unable to determine real path of `%s'", parentdir);
+ return 0;
+ }
+ if (realdir[0] != '/') /* relative result is ok */
+ return 1;
+
+ dirlen = strlen(dir);
+ if (strncmp(realdir, dir, dirlen) == 0 &&
+ (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
+ return 1;
+ return 0;
+}
+
+
+/*
+ * Returns true if this is the controlling/foreground process, else false.
+ */
+int
+foregroundproc(void)
+{
+ static pid_t pgrp = -1;
+ int ctty_pgrp;
+
+ if (pgrp == -1)
+ pgrp = getpgrp();
+
+ return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
+ ctty_pgrp == pgrp));
+}
+
+/* ARGSUSED */
+static void
+updateprogressmeter(int signo)
+{
+ int save_errno = errno;
+
+ /* update progressmeter if foreground process or in -m mode */
+ if (foregroundproc() || progress == -1)
+ progressmeter(0, NULL);
+ errno = save_errno;
+}
+
+/*
+ * Display a transfer progress bar if progress is non-zero.
+ * SIGALRM is hijacked for use by this function.
+ * - Before the transfer, set filesize to size of file (or -1 if unknown),
+ * and call with flag = -1. This starts the once per second timer,
+ * and a call to updateprogressmeter() upon SIGALRM.
+ * - During the transfer, updateprogressmeter will call progressmeter
+ * with flag = 0
+ * - After the transfer, call with flag = 1
+ */
+static struct timeval start;
+
+char *action;
+
+void
+progressmeter(int flag, const char *filename)
+{
+ /*
+ * List of order of magnitude prefixes.
+ * The last is `P', as 2^64 = 16384 Petabytes
+ */
+ static const char prefixes[] = " KMGTP";
+
+ static struct timeval lastupdate;
+ static off_t lastsize;
+ static char *title = NULL;
+ struct timeval now, td, wait;
+ off_t cursize, abbrevsize;
+ double elapsed;
+ int ratio, barlength, i, remaining, overhead = 30;
+ char buf[512];
+
+ if (flag == -1) {
+ (void)gettimeofday(&start, NULL);
+ lastupdate = start;
+ lastsize = restart_point;
+ }
+ (void)gettimeofday(&now, NULL);
+ if (!progress || filesize < 0)
+ return;
+ cursize = bytes + restart_point;
+
+ if (filesize)
+ ratio = cursize * 100 / filesize;
+ else
+ ratio = 100;
+ ratio = MAXIMUM(ratio, 0);
+ ratio = MINIMUM(ratio, 100);
+ if (!verbose && flag == -1) {
+ filename = basename(filename);
+ if (filename != NULL)
+ title = strdup(filename);
+ }
+
+ buf[0] = 0;
+ if (!verbose && action != NULL) {
+ int l = strlen(action);
+ char *dotdot = "";
+
+ if (l < 7)
+ l = 7;
+ else if (l > 12) {
+ l = 12;
+ dotdot = "...";
+ overhead += 3;
+ }
+ snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action,
+ dotdot);
+ overhead += l + 1;
+ } else
+ snprintf(buf, sizeof(buf), "\r");
+
+ if (!verbose && title != NULL) {
+ int l = strlen(title);
+ char *dotdot = "";
+
+ if (l < 12)
+ l = 12;
+ else if (l > 25) {
+ l = 22;
+ dotdot = "...";
+ overhead += 3;
+ }
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "%-*.*s%s %3d%% ", l, l, title,
+ dotdot, ratio);
+ overhead += l + 1;
+ } else
+ snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
+
+ barlength = ttywidth - overhead;
+ if (barlength > 0) {
+ i = barlength * ratio / 100;
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "|%.*s%*s|", i,
+ "*******************************************************"
+ "*******************************************************"
+ "*******************************************************"
+ "*******************************************************"
+ "*******************************************************"
+ "*******************************************************"
+ "*******************************************************",
+ barlength - i, "");
+ }
+
+ i = 0;
+ abbrevsize = cursize;
+ while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) {
+ i++;
+ abbrevsize >>= 10;
+ }
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
+ prefixes[i] == ' ' ? ' ' : 'B');
+
+ timersub(&now, &lastupdate, &wait);
+ if (cursize > lastsize) {
+ lastupdate = now;
+ lastsize = cursize;
+ if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */
+ start.tv_sec += wait.tv_sec;
+ start.tv_usec += wait.tv_usec;
+ }
+ wait.tv_sec = 0;
+ }
+
+ timersub(&now, &start, &td);
+ elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
+
+ if (flag == 1) {
+ i = (int)elapsed / 3600;
+ if (i)
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "%2d:", i);
+ else
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ " ");
+ i = (int)elapsed % 3600;
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "%02d:%02d ", i / 60, i % 60);
+ } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ " --:-- ETA");
+ } else if (wait.tv_sec >= STALLTIME) {
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ " - stalled -");
+ } else {
+ remaining = (int)((filesize - restart_point) /
+ (bytes / elapsed) - elapsed);
+ i = remaining / 3600;
+ if (i)
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "%2d:", i);
+ else
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ " ");
+ i = remaining % 3600;
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
+ "%02d:%02d ETA", i / 60, i % 60);
+ }
+ (void)write(fileno(ttyout), buf, strlen(buf));
+
+ if (flag == -1) {
+ (void)signal(SIGALRM, updateprogressmeter);
+ alarmtimer(1); /* set alarm timer for 1 Hz */
+ } else if (flag == 1) {
+ alarmtimer(0);
+ (void)putc('\n', ttyout);
+ free(title);
+ title = NULL;
+ }
+ fflush(ttyout);
+}
+
+/*
+ * Display transfer statistics.
+ * Requires start to be initialised by progressmeter(-1),
+ * direction to be defined by xfer routines, and filesize and bytes
+ * to be updated by xfer routines
+ * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
+ * instead of TTYOUT.
+ */
+void
+ptransfer(int siginfo)
+{
+ struct timeval now, td;
+ double elapsed;
+ off_t bs;
+ int meg, remaining, hh;
+ char buf[100];
+
+ if (!verbose && !siginfo)
+ return;
+
+ (void)gettimeofday(&now, NULL);
+ timersub(&now, &start, &td);
+ elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
+ bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
+ meg = 0;
+ if (bs > (1024 * 1024))
+ meg = 1;
+
+ /* XXX floating point printf in signal handler */
+ (void)snprintf(buf, sizeof(buf),
+ "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
+ (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
+ bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
+
+ if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 &&
+ bytes + restart_point <= filesize) {
+ remaining = (int)((filesize - restart_point) /
+ (bytes / elapsed) - elapsed);
+ hh = remaining / 3600;
+ remaining %= 3600;
+
+ /* "buf+len(buf) -1" to overwrite \n */
+ snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
+ " ETA: %02d:%02d:%02d\n", hh, remaining / 60,
+ remaining % 60);
+ }
+ (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
+}
+
+/*
+ * List words in stringlist, vertically arranged
+ */
+#ifndef SMALL
+void
+list_vertical(StringList *sl)
+{
+ int i, j, w;
+ int columns, width, lines;
+ char *p;
+
+ width = 0;
+
+ for (i = 0 ; i < sl->sl_cur ; i++) {
+ w = strlen(sl->sl_str[i]);
+ if (w > width)
+ width = w;
+ }
+ width = (width + 8) &~ 7;
+
+ columns = ttywidth / width;
+ if (columns == 0)
+ columns = 1;
+ lines = (sl->sl_cur + columns - 1) / columns;
+ for (i = 0; i < lines; i++) {
+ for (j = 0; j < columns; j++) {
+ p = sl->sl_str[j * lines + i];
+ if (p)
+ fputs(p, ttyout);
+ if (j * lines + i + lines >= sl->sl_cur) {
+ putc('\n', ttyout);
+ break;
+ }
+ w = strlen(p);
+ while (w < width) {
+ w = (w + 8) &~ 7;
+ (void)putc('\t', ttyout);
+ }
+ }
+ }
+}
+#endif /* !SMALL */
+
+/*
+ * Update the global ttywidth value, using TIOCGWINSZ.
+ */
+/* ARGSUSED */
+void
+setttywidth(int signo)
+{
+ int save_errno = errno;
+ struct winsize winsize;
+
+ if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
+ ttywidth = winsize.ws_col ? winsize.ws_col : 80;
+ else
+ ttywidth = 80;
+ errno = save_errno;
+}
+
+/*
+ * Set the SIGALRM interval timer for wait seconds, 0 to disable.
+ */
+void
+alarmtimer(int wait)
+{
+ int save_errno = errno;
+ struct itimerval itv;
+
+ itv.it_value.tv_sec = wait;
+ itv.it_value.tv_usec = 0;
+ itv.it_interval = itv.it_value;
+ setitimer(ITIMER_REAL, &itv, NULL);
+ errno = save_errno;
+}
+
+/*
+ * Setup or cleanup EditLine structures
+ */
+#ifndef SMALL
+void
+controlediting(void)
+{
+ HistEvent hev;
+
+ if (editing && el == NULL && hist == NULL) {
+ el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
+ hist = history_init(); /* init the builtin history */
+ history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */
+ el_set(el, EL_HIST, history, hist); /* use history */
+
+ el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */
+ el_set(el, EL_PROMPT, prompt); /* set the prompt function */
+
+ /* add local file completion, bind to TAB */
+ el_set(el, EL_ADDFN, "ftp-complete",
+ "Context sensitive argument completion",
+ complete);
+ el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
+
+ el_source(el, NULL); /* read ~/.editrc */
+ el_set(el, EL_SIGNAL, 1);
+ } else if (!editing) {
+ if (hist) {
+ history_end(hist);
+ hist = NULL;
+ }
+ if (el) {
+ el_end(el);
+ el = NULL;
+ }
+ }
+}
+#endif /* !SMALL */
+