summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorFlorian Obser <florian@cvs.openbsd.org>2019-05-16 12:44:19 +0000
committerFlorian Obser <florian@cvs.openbsd.org>2019-05-16 12:44:19 +0000
commit3d0c792004dccce0a87f3c922e4c515acfc40b7b (patch)
tree626a028995b669e709e46bf56f0b50bd64f29b8d /usr.bin
parentd1ff65b97cc0d5c82b3107680b0e59be2be0cabd (diff)
Revert suni'ls ftp rewrite for now.
We are juggling too many things at the moment and we can't deal with the differences in behaviour right now.
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/ftp/Makefile9
-rw-r--r--usr.bin/ftp/cmd.c643
-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.c381
-rw-r--r--usr.bin/ftp/cookie.c231
-rw-r--r--usr.bin/ftp/domacro.c149
-rw-r--r--usr.bin/ftp/extern.h150
-rw-r--r--usr.bin/ftp/fetch.c1668
-rw-r--r--usr.bin/ftp/file.c57
-rw-r--r--usr.bin/ftp/ftp.11632
-rw-r--r--usr.bin/ftp/ftp.c2302
-rw-r--r--usr.bin/ftp/ftp.h120
-rw-r--r--usr.bin/ftp/ftp_var.h231
-rw-r--r--usr.bin/ftp/http.c801
-rw-r--r--usr.bin/ftp/list.c86
-rw-r--r--usr.bin/ftp/main.c1179
-rw-r--r--usr.bin/ftp/pathnames.h37
-rw-r--r--usr.bin/ftp/progressmeter.c366
-rw-r--r--usr.bin/ftp/ruserpass.c317
-rw-r--r--usr.bin/ftp/small.c730
-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/url.c419
-rw-r--r--usr.bin/ftp/util.c1160
-rw-r--r--usr.bin/ftp/xmalloc.c147
-rw-r--r--usr.bin/ftp/xmalloc.h41
29 files changed, 11520 insertions, 3510 deletions
diff --git a/usr.bin/ftp/Makefile b/usr.bin/ftp/Makefile
index 4d6ccd73649..4746b1e850e 100644
--- a/usr.bin/ftp/Makefile
+++ b/usr.bin/ftp/Makefile
@@ -1,12 +1,15 @@
-# $OpenBSD: Makefile,v 1.32 2019/05/12 20:58:19 jasper Exp $
+# $OpenBSD: Makefile,v 1.33 2019/05/16 12:44:17 florian Exp $
-# Define SMALL to disable command line editing
+# Define SMALL to disable command line editing and https support
#CFLAGS+=-DSMALL
PROG= ftp
-SRCS= cmd.c file.c ftp.c http.c main.c progressmeter.c url.c util.c xmalloc.c
+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 <bsd.prog.mk>
diff --git a/usr.bin/ftp/cmd.c b/usr.bin/ftp/cmd.c
deleted file mode 100644
index 544643b9ca5..00000000000
--- a/usr.bin/ftp/cmd.c
+++ /dev/null
@@ -1,643 +0,0 @@
-/* $OpenBSD: cmd.c,v 1.3 2019/05/15 13:42:40 florian Exp $ */
-
-/*
- * Copyright (c) 2018 Sunil Nimmagadda <sunil@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.
- */
-
-#include <sys/socket.h>
-#include <sys/stat.h>
-
-#include <arpa/telnet.h>
-
-#include <err.h>
-#include <errno.h>
-#include <histedit.h>
-#include <libgen.h>
-#include <limits.h>
-#include <pwd.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "ftp.h"
-
-#define ARGVMAX 64
-
-static void cmd_interrupt(int);
-static int cmd_lookup(const char *);
-static FILE *data_fopen(const char *);
-static void do_open(int, char **);
-static void do_help(int, char **);
-static void do_quit(int, char **);
-static void do_ls(int, char **);
-static void do_pwd(int, char **);
-static void do_cd(int, char **);
-static void do_get(int, char **);
-static void do_passive(int, char **);
-static void do_lcd(int, char **);
-static void do_lpwd(int, char **);
-static void do_put(int, char **);
-static void do_mget(int, char **);
-static void ftp_abort(void);
-static char *prompt(void);
-
-static FILE *ctrl_fp, *data_fp;
-
-static struct {
- const char *name;
- const char *info;
- void (*cmd)(int, char **);
- int conn_required;
-} cmd_tbl[] = {
- { "open", "connect to remote ftp server", do_open, 0 },
- { "close", "terminate ftp session", do_quit, 1 },
- { "help", "print local help information", do_help, 0 },
- { "?", "print local help information", do_help, 0 },
- { "quit", "terminate ftp session and exit", do_quit, 0 },
- { "exit", "terminate ftp session and exit", do_quit, 0 },
- { "ls", "list contents of remote directory", do_ls, 1 },
- { "pwd", "print working directory on remote machine", do_pwd, 1 },
- { "cd", "change remote working directory", do_cd, 1 },
- { "nlist", "nlist contents of remote directory", do_ls, 1 },
- { "get", "receive file", do_get, 1 },
- { "passive", "toggle passive transfer mode", do_passive, 0 },
- { "lcd", "change local working directory", do_lcd, 0 },
- { "lpwd", "print local working directory", do_lpwd, 0 },
- { "put", "send one file", do_put, 1 },
- { "mget", "get multiple files", do_mget, 1 },
- { "mput", "send multiple files", do_mget, 1 },
-};
-
-static void
-cmd_interrupt(int signo)
-{
- const char msg[] = "\rwaiting for remote to finish abort\n";
- int save_errno = errno;
-
- if (data_fp != NULL)
- (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
-
- interrupted = 1;
- errno = save_errno;
-}
-
-void
-cmd(const char *host, const char *port, const char *path)
-{
- HistEvent hev;
- EditLine *el;
- History *hist;
- const char *line;
- char **ap, *argv[ARGVMAX], *cp;
- int count, i;
-
- if ((el = el_init(getprogname(), stdin, stdout, stderr)) == NULL)
- err(1, "couldn't initialise editline");
-
- if ((hist = history_init()) == NULL)
- err(1, "couldn't initialise editline history");
-
- history(hist, &hev, H_SETSIZE, 100);
- el_set(el, EL_HIST, history, hist);
- el_set(el, EL_PROMPT, prompt);
- el_set(el, EL_EDITOR, "emacs");
- el_set(el, EL_TERMINAL, NULL);
- el_set(el, EL_SIGNAL, 1);
- el_source(el, NULL);
-
- if (host != NULL) {
- argv[0] = "open";
- argv[1] = (char *)host;
- argv[2] = port ? (char *)port : "21";
- do_open(3, argv);
- /* If we don't have a connection, exit */
- if (ctrl_fp == NULL)
- exit(1);
-
- if (path != NULL) {
- argv[0] = "cd";
- argv[1] = (char *)path;
- do_cd(2, argv);
- }
- }
-
- for (;;) {
- signal(SIGINT, SIG_IGN);
- if ((line = el_gets(el, &count)) == NULL || count <= 0) {
- if (verbose)
- fprintf(stderr, "\n");
- argv[0] = "quit";
- do_quit(1, argv);
- break;
- }
-
- if (count <= 1)
- continue;
-
- if ((cp = strrchr(line, '\n')) != NULL)
- *cp = '\0';
-
- history(hist, &hev, H_ENTER, line);
- for (ap = argv; ap < &argv[ARGVMAX - 1] &&
- (*ap = strsep((char **)&line, " \t")) != NULL;) {
- if (**ap != '\0')
- ap++;
- }
- *ap = NULL;
-
- if (argv[0] == NULL)
- continue;
-
- if ((i = cmd_lookup(argv[0])) == -1) {
- fprintf(stderr, "Invalid command.\n");
- continue;
- }
-
- if (cmd_tbl[i].conn_required && ctrl_fp == NULL) {
- fprintf(stderr, "Not connected.\n");
- continue;
- }
-
- interrupted = 0;
- signal(SIGINT, cmd_interrupt);
- cmd_tbl[i].cmd(ap - argv, argv);
-
- if (strcmp(cmd_tbl[i].name, "quit") == 0 ||
- strcmp(cmd_tbl[i].name, "exit") == 0)
- break;
- }
-
- el_end(el);
-}
-
-static int
-cmd_lookup(const char *cmd)
-{
- size_t i;
-
- for (i = 0; i < nitems(cmd_tbl); i++)
- if (strcmp(cmd, cmd_tbl[i].name) == 0)
- return i;
-
- return -1;
-}
-
-static char *
-prompt(void)
-{
- return "ftp> ";
-}
-
-static FILE *
-data_fopen(const char *mode)
-{
- int fd;
-
- fd = activemode ? ftp_eprt(ctrl_fp) : ftp_epsv(ctrl_fp);
- if (fd == -1) {
- if (io_debug)
- fprintf(stderr, "Failed to open data connection");
-
- return NULL;
- }
-
- return fdopen(fd, mode);
-}
-
-static void
-ftp_abort(void)
-{
- char buf[BUFSIZ];
-
- snprintf(buf, sizeof buf, "%c%c%c", IAC, IP, IAC);
- if (send(fileno(ctrl_fp), buf, 3, MSG_OOB) != 3)
- warn("abort");
-
- ftp_command(ctrl_fp, "%cABOR", DM);
-}
-
-static void
-do_open(int argc, char **argv)
-{
- const char *host = NULL, *port = "21";
- char *buf = NULL;
- size_t n = 0;
- int sock;
-
- if (ctrl_fp != NULL) {
- fprintf(stderr, "already connected, use close first.\n");
- return;
- }
-
- switch (argc) {
- case 3:
- port = argv[2];
- /* FALLTHROUGH */
- case 2:
- host = argv[1];
- break;
- default:
- fprintf(stderr, "usage: open host [port]\n");
- return;
- }
-
- if ((sock = tcp_connect(host, port, 0)) == -1)
- return;
-
- fprintf(stderr, "Connected to %s.\n", host);
- if ((ctrl_fp = fdopen(sock, "r+")) == NULL)
- err(1, "%s: fdopen", __func__);
-
- /* greeting */
- ftp_getline(&buf, &n, 0, ctrl_fp);
- free(buf);
- if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) {
- fclose(ctrl_fp);
- ctrl_fp = NULL;
- }
-}
-
-static void
-do_help(int argc, char **argv)
-{
- size_t i;
- int j;
-
- if (argc == 1) {
- for (i = 0; i < nitems(cmd_tbl); i++)
- fprintf(stderr, "%s\n", cmd_tbl[i].name);
-
- return;
- }
-
- for (i = 1; i < (size_t)argc; i++) {
- if ((j = cmd_lookup(argv[i])) == -1)
- fprintf(stderr, "invalid help command %s\n", argv[i]);
- else
- fprintf(stderr, "%s\t%s\n", argv[i], cmd_tbl[j].info);
- }
-}
-
-static void
-do_quit(int argc, char **argv)
-{
- if (ctrl_fp == NULL)
- return;
-
- ftp_command(ctrl_fp, "QUIT");
- fclose(ctrl_fp);
- ctrl_fp = NULL;
-}
-
-static void
-do_ls(int argc, char **argv)
-{
- FILE *dst_fp = stdout;
- const char *cmd, *local_fname = NULL, *remote_dir = NULL;
- char *buf = NULL;
- size_t n = 0;
- ssize_t len;
- int r;
-
- switch (argc) {
- case 3:
- if (strcmp(argv[2], "-") != 0)
- local_fname = argv[2];
- /* FALLTHROUGH */
- case 2:
- remote_dir = argv[1];
- /* FALLTHROUGH */
- case 1:
- break;
- default:
- fprintf(stderr, "usage: ls [remote-directory [local-file]]\n");
- return;
- }
-
- if ((data_fp = data_fopen("r")) == NULL)
- return;
-
- if (local_fname && (dst_fp = fopen(local_fname, "w")) == NULL) {
- warn("fopen %s", local_fname);
- fclose(data_fp);
- data_fp = NULL;
- return;
- }
-
- cmd = (strcmp(argv[0], "ls") == 0) ? "LIST" : "NLST";
- if (remote_dir != NULL)
- r = ftp_command(ctrl_fp, "%s %s", cmd, remote_dir);
- else
- r = ftp_command(ctrl_fp, "%s", cmd);
-
- if (r != P_PRE) {
- fclose(data_fp);
- data_fp = NULL;
- if (dst_fp != stdout)
- fclose(dst_fp);
-
- return;
- }
-
- while ((len = getline(&buf, &n, data_fp)) != -1 && !interrupted) {
- buf[len - 1] = '\0';
- if (len >= 2 && buf[len - 2] == '\r')
- buf[len - 2] = '\0';
-
- fprintf(dst_fp, "%s\n", buf);
- }
-
- if (interrupted)
- ftp_abort();
-
- fclose(data_fp);
- data_fp = NULL;
- ftp_getline(&buf, &n, 0, ctrl_fp);
- free(buf);
- if (dst_fp != stdout)
- fclose(dst_fp);
-}
-
-static void
-do_get(int argc, char **argv)
-{
- FILE *dst_fp;
- const char *local_fname = NULL, *p, *remote_fname;
- char *buf = NULL;
- size_t n = 0;
- off_t file_sz, offset = 0;
-
- switch (argc) {
- case 3:
- local_fname = argv[2];
- /* FALLTHROUGH */
- case 2:
- remote_fname = argv[1];
- break;
- default:
- fprintf(stderr, "usage: get remote-file [local-file]\n");
- return;
- }
-
- if (local_fname == NULL)
- local_fname = remote_fname;
-
- if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
- return;
-
- log_info("local: %s remote: %s\n", local_fname, remote_fname);
- if (ftp_size(ctrl_fp, remote_fname, &file_sz, &buf) != P_OK) {
- fprintf(stderr, "%s", buf);
- return;
- }
-
- if ((data_fp = data_fopen("r")) == NULL)
- return;
-
- if ((dst_fp = fopen(local_fname, "w")) == NULL) {
- warn("%s", local_fname);
- fclose(data_fp);
- data_fp = NULL;
- return;
- }
-
- if (ftp_command(ctrl_fp, "RETR %s", remote_fname) != P_PRE) {
- fclose(data_fp);
- data_fp = NULL;
- fclose(dst_fp);
- return;
- }
-
- init_stats(file_sz, &offset);
- if (progressmeter) {
- p = basename(remote_fname);
- start_progress_meter(p, NULL);
- }
-
- copy_file(dst_fp, data_fp, &offset);
- if (progressmeter)
- stop_progress_meter();
- finish_stats();
-
- if (interrupted)
- ftp_abort();
-
- fclose(data_fp);
- data_fp = NULL;
- fclose(dst_fp);
- ftp_getline(&buf, &n, 0, ctrl_fp);
- free(buf);
-}
-
-static void
-do_pwd(int argc, char **argv)
-{
- ftp_command(ctrl_fp, "PWD");
-}
-
-static void
-do_cd(int argc, char **argv)
-{
- if (argc != 2) {
- fprintf(stderr, "usage: cd remote-directory\n");
- return;
- }
-
- ftp_command(ctrl_fp, "CWD %s", argv[1]);
-}
-
-static void
-do_passive(int argc, char **argv)
-{
- switch (argc) {
- case 1:
- break;
- case 2:
- if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "off") == 0)
- break;
-
- /* FALLTHROUGH */
- default:
- fprintf(stderr, "usage: passive [on | off]\n");
- return;
- }
-
- if (argv[1] != NULL) {
- activemode = (strcmp(argv[1], "off") == 0) ? 1 : 0;
- fprintf(stderr, "passive mode is %s\n", argv[1]);
- return;
- }
-
- activemode = !activemode;
- fprintf(stderr, "passive mode is %s\n", activemode ? "off" : "on");
-}
-
-static void
-do_lcd(int argc, char **argv)
-{
- struct passwd *pw = NULL;
- const char *dir, *login;
- char cwd[PATH_MAX];
-
- switch (argc) {
- case 1:
- case 2:
- break;
- default:
- fprintf(stderr, "usage: lcd [local-directory]\n");
- return;
- }
-
- if ((login = getlogin()) != NULL)
- pw = getpwnam(login);
-
- if (pw == NULL && (pw = getpwuid(getuid())) == NULL) {
- fprintf(stderr, "Failed to get home directory\n");
- return;
- }
-
- dir = argv[1] ? argv[1] : pw->pw_dir;
- if (chdir(dir) != 0) {
- warn("local: %s", dir);
- return;
- }
-
- if (getcwd(cwd, sizeof cwd) == NULL) {
- warn("getcwd");
- return;
- }
-
- fprintf(stderr, "Local directory now %s\n", cwd);
-}
-
-static void
-do_lpwd(int argc, char **argv)
-{
- char cwd[PATH_MAX];
-
- if (getcwd(cwd, sizeof cwd) == NULL) {
- warn("getcwd");
- return;
- }
-
- fprintf(stderr, "Local directory %s\n", cwd);
-}
-
-static void
-do_put(int argc, char **argv)
-{
- struct stat sb;
- FILE *src_fp;
- const char *local_fname, *p, *remote_fname = NULL;
- char *buf = NULL;
- size_t n = 0;
- off_t file_sz, offset = 0;
-
- switch (argc) {
- case 3:
- remote_fname = argv[2];
- /* FALLTHROUGH */
- case 2:
- local_fname = argv[1];
- break;
- default:
- fprintf(stderr, "usage: put local-file [remote-file]\n");
- return;
- }
-
- if (remote_fname == NULL)
- remote_fname = local_fname;
-
- if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
- return;
-
- log_info("local: %s remote: %s\n", local_fname, remote_fname);
- if ((data_fp = data_fopen("w")) == NULL)
- return;
-
- if ((src_fp = fopen(local_fname, "r")) == NULL) {
- warn("%s", local_fname);
- fclose(data_fp);
- data_fp = NULL;
- return;
- }
-
- if (fstat(fileno(src_fp), &sb) != 0) {
- warn("%s", local_fname);
- fclose(data_fp);
- data_fp = NULL;
- fclose(src_fp);
- return;
- }
- file_sz = sb.st_size;
-
- if (ftp_command(ctrl_fp, "STOR %s", remote_fname) != P_PRE) {
- fclose(data_fp);
- data_fp = NULL;
- fclose(src_fp);
- return;
- }
-
- init_stats(file_sz, &offset);
- if (progressmeter) {
- p = basename(remote_fname);
- start_progress_meter(p, NULL);
- }
-
- copy_file(data_fp, src_fp, &offset);
- if (progressmeter)
- stop_progress_meter();
- finish_stats();
-
- if (interrupted)
- ftp_abort();
-
- fclose(data_fp);
- data_fp = NULL;
- fclose(src_fp);
- ftp_getline(&buf, &n, 0, ctrl_fp);
- free(buf);
-}
-
-static void
-do_mget(int argc, char **argv)
-{
- void (*fn)(int, char **);
- const char *usage;
- char *args[2];
- int i;
-
- if (strcmp(argv[0], "mget") == 0) {
- fn = do_get;
- args[0] = "get";
- usage = "mget remote-files";
- } else {
- fn = do_put;
- args[0] = "put";
- usage = "mput local-files";
- }
-
- if (argc == 1) {
- fprintf(stderr, "usage: %s\n", usage);
- return;
- }
-
- for (i = 1; i < argc && !interrupted; i++) {
- args[1] = argv[i];
- fn(2, args);
- }
-}
diff --git a/usr.bin/ftp/cmds.c b/usr.bin/ftp/cmds.c
new file mode 100644
index 00000000000..f51d558d21c
--- /dev/null
+++ b/usr.bin/ftp/cmds.c
@@ -0,0 +1,1688 @@
+/* $OpenBSD: cmds.c,v 1.82 2019/05/16 12:44:17 florian 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) {
+ (void)closefrom(3);
+ (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[])
+{
+ off_t nrestart_point;
+ char *ep;
+
+ if (argc != 2)
+ fputs("restart: offset not specified.\n", ttyout);
+ else {
+ nrestart_point = strtoll(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[])
+{
+ off_t orestart_point;
+ int 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 00000000000..ca01845f496
--- /dev/null
+++ b/usr.bin/ftp/cmds.h
@@ -0,0 +1,83 @@
+/* $OpenBSD: cmds.h,v 1.4 2019/05/16 12:44:17 florian 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 00000000000..5b871dc7ae9
--- /dev/null
+++ b/usr.bin/ftp/cmdtab.c
@@ -0,0 +1,215 @@
+/* $OpenBSD: cmdtab.c,v 1.31 2019/05/16 12:44:17 florian 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 00000000000..ef2478fbda8
--- /dev/null
+++ b/usr.bin/ftp/complete.c
@@ -0,0 +1,381 @@
+/* $OpenBSD: complete.c,v 1.33 2019/05/16 12:44:17 florian 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;
+
+ 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 00000000000..6b526a22d14
--- /dev/null
+++ b/usr.bin/ftp/cookie.c
@@ -0,0 +1,231 @@
+/* $OpenBSD: cookie.c,v 1.9 2019/05/16 12:44:17 florian 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 NOSSL
+
+#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 00000000000..0c1bf1eff05
--- /dev/null
+++ b/usr.bin/ftp/domacro.c
@@ -0,0 +1,149 @@
+/* $OpenBSD: domacro.c,v 1.21 2019/05/16 12:44:17 florian 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 00000000000..53908981b4e
--- /dev/null
+++ b/usr.bin/ftp/extern.h
@@ -0,0 +1,150 @@
+/* $OpenBSD: extern.h,v 1.51 2019/05/16 12:44:17 florian 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 *);
+int connect_wait(int);
+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 **);
+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);
+
+__dead void usage(void);
+
+void cookie_get(const char *, const char *, int, char **);
+void cookie_load(void);
+
+#ifndef SMALL
+void abortsend(int);
+unsigned char complete(EditLine *, int);
+void controlediting(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 connect_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 00000000000..f08c5501247
--- /dev/null
+++ b/usr.bin/ftp/fetch.c
@@ -0,0 +1,1668 @@
+/* $OpenBSD: fetch.c,v 1.169 2019/05/16 12:44:17 florian 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 NOSSL
+#include <tls.h>
+#else /* !NOSSL */
+struct tls;
+#endif /* !NOSSL */
+
+#include "ftp_var.h"
+#include "cmds.h"
+
+static int url_get(const char *, const char *, const char *, int);
+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 NOSSL
+int proxy_connect(int, char *, char *);
+int SSL_vprintf(struct tls *, const char *, va_list);
+char *SSL_readline(struct tls *, size_t *);
+#endif /* !NOSSL */
+
+#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);
+}
+
+/* ARGSUSED */
+static void
+tooslow(int signo)
+{
+ dprintf(STDERR_FILENO, "%s: connect taking too long\n", __progname);
+ _exit(2);
+}
+
+/*
+ * 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, int lastfile)
+{
+ 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;
+ const char * volatile savefile;
+ char * volatile proxyurl = NULL;
+ char *credentials = NULL;
+ volatile int fd = -1, out = -1;
+ volatile sig_t oldintr, oldinti;
+ FILE *fin = NULL;
+ off_t hashbytes;
+ const char *errstr;
+ ssize_t len, wlen;
+ char *proxyhost = NULL;
+#ifndef NOSSL
+ char *sslpath = NULL, *sslhost = NULL;
+ char *full_host = NULL;
+ const char *scheme;
+ int ishttpurl = 0, ishttpsurl = 0;
+#endif /* !NOSSL */
+#ifndef SMALL
+ char *locbase;
+ struct addrinfo *ares = NULL;
+#endif
+ 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 NOSSL
+ 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 /* !NOSSL */
+ } 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 NOSSL
+ /*
+ * 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 /* NOSSL */
+
+ 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 NOSSL
+ if (ishttpsurl) {
+ sslpath = strdup(path);
+ sslhost = strdup(host);
+ if (! sslpath || ! sslhost)
+ errx(1, "Can't allocate memory for https path/host.");
+ }
+#endif /* !NOSSL */
+ 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;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ warn("Can't open file %s", path);
+ goto cleanup_url_get;
+ }
+
+ if (fstat(fd, &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(fd, 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(fd, 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 NOSSL
+ port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
+#else /* !NOSSL */
+ port = portnum ? portnum : httpport;
+#endif /* !NOSSL */
+
+#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, port, path,
+ savefile, credentials ? credentials : "none");
+#endif /* !SMALL */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ 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 NOSSL
+ } else if (error == EAI_SERVICE && port == httpsport) {
+ snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
+ error = getaddrinfo(host, pbuf, &hints, &res0);
+#endif /* !NOSSL */
+ }
+ 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);
+
+ fd = -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);
+
+ fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (fd == -1) {
+ cause = "socket";
+ continue;
+ }
+
+#ifndef SMALL
+ if (srcaddr) {
+ if (ares->ai_family != res->ai_family) {
+ close(fd);
+ fd = -1;
+ errno = EINVAL;
+ cause = "bind";
+ continue;
+ }
+ if (bind(fd, ares->ai_addr, ares->ai_addrlen) < 0) {
+ save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ fd = -1;
+ cause = "bind";
+ continue;
+ }
+ }
+#endif /* !SMALL */
+
+ if (connect_timeout) {
+ (void)signal(SIGALRM, tooslow);
+ alarmtimer(connect_timeout);
+ }
+
+ for (error = connect(fd, res->ai_addr, res->ai_addrlen);
+ error != 0 && errno == EINTR; error = connect_wait(fd))
+ continue;
+ if (error != 0) {
+ save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ fd = -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 NOSSL
+ if (proxyenv && sslhost)
+ proxy_connect(fd, sslhost, credentials);
+#endif /* !NOSSL */
+ break;
+ }
+ freeaddrinfo(res0);
+#ifndef SMALL
+ if (srcaddr)
+ freeaddrinfo(ares);
+#endif /* !SMALL */
+ if (fd < 0) {
+ warn("%s", cause);
+ goto cleanup_url_get;
+ }
+
+#ifndef NOSSL
+ 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, fd, sslhost) != 0) {
+ fprintf(ttyout, "SSL failure: %s\n", tls_error(tls));
+ goto cleanup_url_get;
+ }
+ } else {
+ fin = fdopen(fd, "r+");
+ fd = -1;
+ }
+#else /* !NOSSL */
+ fin = fdopen(fd, "r+");
+ fd = -1;
+#endif /* !NOSSL */
+
+#ifdef SMALL
+ if (lastfile) {
+ if (pipeout) {
+ if (pledge("stdio rpath inet dns tty", NULL) == -1)
+ err(1, "pledge");
+ } else {
+ if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1)
+ err(1, "pledge");
+ }
+ }
+#endif
+
+ if (connect_timeout) {
+ signal(SIGALRM, SIG_DFL);
+ alarmtimer(0);
+ }
+
+ /*
+ * Construct and send the request. Proxy requests don't want leading /.
+ */
+#ifndef NOSSL
+ cookie_get(host, path, ishttpsurl, &buf);
+#endif /* !NOSSL */
+
+ epath = url_encode(path);
+ if (proxyurl) {
+ if (verbose) {
+ fprintf(ttyout, "Requesting %s (via %s)\n",
+ origline, 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 {
+ if (verbose)
+ fprintf(ttyout, "Requesting %s\n", origline);
+#ifndef SMALL
+ if (resume) {
+ struct stat stbuf;
+
+ if (stat(savefile, &stbuf) == 0)
+ restart_point = stbuf.st_size;
+ else
+ restart_point = 0;
+ }
+#endif /* SMALL */
+#ifndef NOSSL
+ 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 /* NOSSL */
+ 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 NOSSL
+ 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 /* !NOSSL */
+ if (port && strcmp(port, "80") != 0)
+ ftp_printf(fin, tls, ":%s", port);
+#endif /* !NOSSL */
+ ftp_printf(fin, tls, "\r\n%s%s\r\n\r\n",
+ buf ? buf : "", httpuseragent);
+ }
+ free(epath);
+
+#ifndef NOSSL
+ free(buf);
+#endif /* !NOSSL */
+ 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);
+ fin = NULL;
+ }
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ rval = url_get(redirurl, proxyenv, savefile, lastfile);
+ 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);
+#ifdef SMALL
+ if (lastfile) {
+ if (pledge("stdio tty", NULL) == -1)
+ err(1, "pledge");
+ }
+#endif
+ }
+
+ /* 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 NOSSL
+ if (tls != NULL) {
+ if (tls_session_fd != -1)
+ dprintf(STDERR_FILENO, "tls session resumed: %s\n",
+ tls_conn_session_resumed(tls) ? "yes" : "no");
+ do {
+ i = tls_close(tls);
+ } while (i == TLS_WANT_POLLIN || i == TLS_WANT_POLLOUT);
+ tls_free(tls);
+ }
+ free(full_host);
+ free(sslhost);
+#endif /* !NOSSL */
+ if (fin != NULL) {
+ fclose(fin);
+ fin = NULL;
+ }
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ if (out >= 0 && out != fileno(stdout))
+ close(out);
+ 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, lastfile;
+ 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;
+
+ lastfile = (argv[argpos+1] == 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 NOSSL
+ /* even if we compiled without SSL, url_get will check */
+ strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0 ||
+#endif /* !NOSSL */
+ strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
+ redirect_loop = 0;
+ if (url_get(url, httpproxy, outfile, lastfile) == -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, lastfile) == -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 NOSSL
+ strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 ||
+#endif /* !NOSSL */
+ 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 NOSSL
+ else if (tls != NULL)
+ return SSL_readline(tls, lenp);
+#endif /* !NOSSL */
+ else
+ return NULL;
+}
+
+size_t
+ftp_read(FILE *fp, struct tls *tls, char *buf, size_t len)
+{
+#ifndef NOSSL
+ ssize_t tret;
+#endif
+ size_t ret = 0;
+
+ if (fp != NULL)
+ ret = fread(buf, sizeof(char), len, fp);
+#ifndef NOSSL
+ else if (tls != NULL) {
+ do {
+ tret = tls_read(tls, buf, len);
+ } while (tret == TLS_WANT_POLLIN || tret == TLS_WANT_POLLOUT);
+ if (tret < 0)
+ errx(1, "SSL read error: %s", tls_error(tls));
+ ret = (size_t)tret;
+ }
+#endif /* !NOSSL */
+ 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 NOSSL
+ else if (tls != NULL)
+ ret = SSL_vprintf(tls, fmt, ap);
+#endif /* !NOSSL */
+ 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 NOSSL
+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)
+ errx(1, "SSL write error: %s", tls_error(tls));
+ 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;
+ }
+ do {
+ ret = tls_read(tls, &c, 1);
+ } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
+ 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 /* !NOSSL */
diff --git a/usr.bin/ftp/file.c b/usr.bin/ftp/file.c
deleted file mode 100644
index 63571e18352..00000000000
--- a/usr.bin/ftp/file.c
+++ /dev/null
@@ -1,57 +0,0 @@
-/* $OpenBSD: file.c,v 1.2 2019/05/12 20:58:19 jasper Exp $ */
-
-/*
- * Copyright (c) 2015 Sunil Nimmagadda <sunil@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.
- */
-
-#include <sys/stat.h>
-
-#include <err.h>
-#include <fcntl.h>
-#include <stdio.h>
-
-#include "ftp.h"
-
-struct imsgbuf;
-
-static FILE *src_fp;
-
-struct url *
-file_request(struct imsgbuf *ibuf, struct url *url, off_t *offset, off_t *sz)
-{
- struct stat sb;
- int src_fd;
-
- if ((src_fd = fd_request(url->path, O_RDONLY, NULL)) == -1)
- err(1, "Can't open file %s", url->path);
-
- if (fstat(src_fd, &sb) == 0)
- *sz = sb.st_size;
-
- if ((src_fp = fdopen(src_fd, "r")) == NULL)
- err(1, "%s: fdopen", __func__);
-
- if (*offset && fseeko(src_fp, *offset, SEEK_SET) == -1)
- err(1, "%s: fseeko", __func__);
-
- return url;
-}
-
-void
-file_save(struct url *url, FILE *dst_fp, off_t *offset)
-{
- copy_file(dst_fp, src_fp, offset);
- fclose(src_fp);
-}
diff --git a/usr.bin/ftp/ftp.1 b/usr.bin/ftp/ftp.1
index 32d90c81202..ed8d7e0c90f 100644
--- a/usr.bin/ftp/ftp.1
+++ b/usr.bin/ftp/ftp.1
@@ -1,4 +1,5 @@
-.\" $OpenBSD: ftp.1,v 1.114 2019/05/15 11:53:22 kmos Exp $
+.\" $OpenBSD: ftp.1,v 1.115 2019/05/16 12:44:17 florian 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.
@@ -29,39 +30,57 @@
.\"
.\" @(#)ftp.1 8.3 (Berkeley) 10/9/94
.\"
-.\" Copyright (c) 2015 Sunil Nimmagadda <sunil@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.
-.\"
-.Dd $Mdocdate: May 15 2019 $
+.Dd $Mdocdate: May 16 2019 $
.Dt FTP 1
.Os
.Sh NAME
.Nm ftp
.Nd Internet file transfer program
.Sh SYNOPSIS
-.Nm
-.Op Fl 46AVv
+.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
-.Op Fl 46ACMmVv
-.Op Fl D Ar title
+.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 tls_options
+.Op Fl S Ar ssl_options
+.Op Fl s Ar srcaddr
.Op Fl U Ar useragent
.Op Fl w Ar seconds
-.Ar url ...
+.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
@@ -69,8 +88,8 @@ Protocol (FTP).
The program allows a user to transfer files to and from a
remote network site.
.Pp
-The latter usage format will fetch a file using either the
-FTP, HTTP or HTTPS protocols into the current directory.
+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
@@ -85,7 +104,7 @@ to use IPv4 addresses only.
.It Fl 6
Forces
.Nm
-to use IPv6 addreses only.
+to use IPv6 addresses only.
.It Fl A
Force active mode FTP.
By default,
@@ -97,38 +116,125 @@ This option causes
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 file.
+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
+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 title for the start of the progress bar.
+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.
+to never display the progress meter in cases where it would do
+so by default.
.It Fl m
Causes
.Nm
-to display the progress meter in cases where it would not do so by default.
+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 file or URL, save the contents in
+When fetching a single file or URL, save the contents in
.Ar output .
-To make the contents go to stdout, use `-' for
+To make the contents go to stdout,
+use
+.Sq -
+for
.Ar output .
-.It Fl S Ar tls_options
-TLS options to use with HTTPS transfers.
+.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.
+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 .
@@ -137,17 +243,12 @@ See the
.Cm ciphers
subcommand.
.It Cm depth Ns = Ns Ar max_depth
-Maximum depth of the certificate chain allowed when performing validation.
+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.
-.It Cm protocols Ns = Ns Ar string
-Specify the TLS protocols to use.
-If not specified the value
-.Qq all
-is used.
-Refer to the
-.Xr tls_config_parse_protocols 3
-function for other valid protocol string values.
.It Cm muststaple
Require the server to present a valid OCSP stapling in the TLS handshake.
.It Cm noverifytime
@@ -156,8 +257,8 @@ Disable validation of certificate times and OCSP validation.
Specify a file to use for TLS session data.
If this file has a non-zero length, the session data will be read from this file
and the client will attempt to resume the TLS session with the server.
-Upon completion of a successful TLS handshake this file will be updated with
-new session data, if available.
+Upon completion of a successful TLS handshake this file will be updated
+with new session data, if available.
This file will be created if it does not already exist.
.El
.Pp
@@ -171,6 +272,14 @@ or
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
@@ -178,15 +287,18 @@ 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.
+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.
+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.
+to show all responses from the remote server, as well
+as report on data transfer statistics.
.It Fl w Ar seconds
-Abort a slow connection after
+For URL format connections to HTTP/HTTPS servers, abort a
+slow connection after
.Ar seconds .
.El
.Pp
@@ -209,32 +321,251 @@ The following commands are recognized
by
.Nm :
.Bl -tag -width Fl
-.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.
+.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 \&? Op Ar command
-A synonym for
-.Ic help .
-.It Ic quit
-Terminate the FTP session with the remote server and exit
-.Nm .
-.It Ic exit
+.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 quit .
+.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
@@ -245,17 +576,211 @@ systems will produce output from the command
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
@@ -265,20 +790,163 @@ Note that on some servers, the
.Ic nlist
command will only return information on normal files (not directories
or special files).
-.It Ic pwd
-Print the name of the current working directory on the remote
-machine.
-.It Ic cd Ar remote-directory
-Change the working directory on the remote machine
-to
-.Ar remote-directory .
-.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.
+.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),
@@ -286,43 +954,367 @@ If passive mode is turned on (default is on),
will send a
.Dv EPSV
command for all data connections instead of the usual
-.Dv EPRT
+.Dv PORT
command.
The
-.Dv EPSV
+.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 EPRT
+.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.
-.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 lpwd
-Print the working directory on the local machine.
+(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.
-.It Ic mget Ar remote-files
-Do a
+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
-for each file name specified.
-.It Ic mput Ar local-files
-Do a
+or
.Ic put
-for each file name specified.
+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
@@ -332,10 +1324,15 @@ 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 ftp://
+.It Xo
+.Pf ftp:// Op Ar user : password No @
.Ar host Op : Ar port
-.No / Ar file
+.No / Ar file Op /
.Xc
.Sm on
An FTP URL, retrieved using the FTP protocol if
@@ -343,8 +1340,20 @@ An FTP URL, retrieved using the FTP protocol if
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 http://
+.It Xo
+.Pf http:// Op Ar user : password No @
.Ar host Op : Ar port
.No / Ar file
.Xc
@@ -353,8 +1362,21 @@ 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 https://
+.It Xo
+.Pf https:// Op Ar user : password No @
.Ar host Op : Ar port
.No / Ar file
.Xc
@@ -364,19 +1386,367 @@ 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
+.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 Ds
+.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 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(S) URL requests.
+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,
@@ -388,19 +1758,45 @@ 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 first appeard in
+command appeared in
.Bx 4.2 .
-A complete rewrite of the
-.Nm
-command first appeared in
-.Ox 6.6 .
-.Sh AUTHORS
-.An Sunil Nimmagadda Aq Mt sunil@openbsd.org
-.Sh CAVEATS
-While aborting a data transfer, certain FTP servers violate
-the protocol by not responding with a 426 reply first, thereby making
-.Nm
-wait indefinitely for a correct reply.
+.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
index a68de0aa52a..cbab7ed0cfb 100644
--- a/usr.bin/ftp/ftp.c
+++ b/usr.bin/ftp/ftp.c
@@ -1,452 +1,2088 @@
-/* $OpenBSD: ftp.c,v 1.103 2019/05/13 16:04:49 tb Exp $ */
+/* $OpenBSD: ftp.c,v 1.104 2019/05/16 12:44:17 florian Exp $ */
+/* $NetBSD: ftp.c,v 1.27 1997/08/18 10:20:23 lukem Exp $ */
/*
- * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
*
- * 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.
+ * 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.
*
- * 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.
+ * 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 <arpa/inet.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 <libgen.h>
-#include <limits.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.h"
-#include "xmalloc.h"
+#include "ftp_var.h"
-static FILE *ctrl_fp;
-static int data_fd;
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+};
-void
-ftp_connect(struct url *url, struct url *proxy, int timeout)
-{
- char *buf = NULL;
- size_t n = 0;
- int sock;
+union sockaddr_union myctladdr, hisctladdr, data_addr;
- if (proxy) {
- http_connect(url, proxy, timeout);
- return;
- }
+int data = -1;
+int abrtflag = 0;
+jmp_buf ptabort;
+int ptabflg;
+int ptflag = 0;
+off_t restart_point = 0;
- if ((sock = tcp_connect(url->host, url->port, timeout)) == -1)
- exit(1);
- if ((ctrl_fp = fdopen(sock, "r+")) == NULL)
- err(1, "%s: fdopen", __func__);
+FILE *cin, *cout;
- /* greeting */
- if (ftp_getline(&buf, &n, 0, ctrl_fp) != P_OK) {
- warnx("Can't connect to host `%s'", url->host);
- ftp_command(ctrl_fp, "QUIT");
- exit(1);
- }
-
- free(buf);
- log_info("Connected to %s\n", url->host);
- if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) {
- warnx("Can't login to host `%s'", url->host);
- ftp_command(ctrl_fp, "QUIT");
- exit(1);
- }
-}
-
-struct url *
-ftp_get(struct url *url, struct url *proxy, off_t *offset, off_t *sz)
+char *
+hookup(char *host, char *port)
{
- char *buf = NULL, *dir, *file;
-
- if (proxy) {
- url = http_get(url, proxy, offset, sz);
- /* this url should now be treated as HTTP */
- url->scheme = S_HTTP;
- return url;
+ 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);
}
- log_info("Using binary mode to transfer files.\n");
- if (ftp_command(ctrl_fp, "TYPE I") != P_OK)
- errx(1, "Failed to set mode to binary");
-
- dir = dirname(url->path);
- if (ftp_command(ctrl_fp, "CWD %s", dir) != P_OK)
- errx(1, "CWD command failed");
-
- log_info("Retrieving %s\n", url->path);
- file = basename(url->path);
- if (strcmp(url->fname, "-"))
- log_info("local: %s remote: %s\n", url->fname, file);
+ if (res0->ai_canonname)
+ strlcpy(hostnamebuf, res0->ai_canonname, sizeof(hostnamebuf));
else
- log_info("remote: %s\n", file);
-
- if (ftp_size(ctrl_fp, file, sz, &buf) != P_OK) {
- fprintf(stderr, "%s", buf);
- ftp_command(ctrl_fp, "QUIT");
- exit(1);
+ 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);
+ }
}
- free(buf);
-
- if (activemode)
- data_fd = ftp_eprt(ctrl_fp);
- else if ((data_fd = ftp_epsv(ctrl_fp)) == -1)
- data_fd = ftp_eprt(ctrl_fp);
-
- if (data_fd == -1)
- errx(1, "Failed to establish data connection");
-
- if (*offset && ftp_command(ctrl_fp, "REST %lld", *offset) != P_INTER)
- errx(1, "REST command failed");
+#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 */
+ for (error = connect(s, res->ai_addr, res->ai_addrlen);
+ error != 0 && errno == EINTR; error = connect_wait(s))
+ continue;
+ if (error != 0) {
+ /* 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;
+ }
- if (ftp_command(ctrl_fp, "RETR %s", file) != P_PRE) {
- ftp_command(ctrl_fp, "QUIT");
- exit(1);
+ /* finally we got one */
+ break;
}
-
- return url;
-}
-
-void
-ftp_save(struct url *url, FILE *dst_fp, off_t *offset)
-{
- struct sockaddr_storage ss;
- FILE *data_fp;
- socklen_t len;
- int s;
-
- if (activemode) {
- len = sizeof(ss);
- if ((s = accept(data_fd, (struct sockaddr *)&ss, &len)) == -1)
- err(1, "%s: accept", __func__);
-
- close(data_fd);
- data_fd = s;
+ 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, &myctladdr.sa, &namelen) < 0) {
+ warn("getsockname");
+ code = -1;
+ goto bad;
+ }
+ if (hisctladdr.sa.sa_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 */
}
- if ((data_fp = fdopen(data_fd, "r")) == NULL)
- err(1, "%s: fdopen data_fd", __func__);
-
- copy_file(dst_fp, data_fp, offset);
- fclose(data_fp);
+ return (hostname);
+bad:
+ (void)close(s);
+ return (NULL);
}
+/* ARGSUSED */
void
-ftp_quit(struct url *url)
+cmdabort(int signo)
{
- char *buf = NULL;
- size_t n = 0;
+ int save_errno = errno;
- if (ftp_getline(&buf, &n, 0, ctrl_fp) != P_OK)
- errx(1, "error retrieving file %s", url->fname);
+ alarmtimer(0);
+ (void) write(fileno(ttyout), "\n\r", 2);
+ abrtflag++;
- free(buf);
- ftp_command(ctrl_fp, "QUIT");
- fclose(ctrl_fp);
+ errno = save_errno;
+ if (ptflag)
+ longjmp(ptabort, 1);
}
int
-ftp_getline(char **lineptr, size_t *n, int suppress_output, FILE *fp)
+command(const char *fmt, ...)
{
- ssize_t len;
- char *bufp, code[4];
- const char *errstr;
- int lookup[] = { P_PRE, P_OK, P_INTER, N_TRANS, N_PERM };
-
-
- if ((len = getline(lineptr, n, fp)) == -1)
- err(1, "%s: getline", __func__);
-
- bufp = *lineptr;
- if (!suppress_output)
- log_info("%s", bufp);
-
- if (len < 4)
- errx(1, "%s: line too short", __func__);
-
- (void)strlcpy(code, bufp, sizeof code);
- if (bufp[3] == ' ')
- goto done;
+ 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);
+}
- /* multi-line reply */
- while (!(strncmp(code, bufp, 3) == 0 && bufp[3] == ' ')) {
- if ((len = getline(lineptr, n, fp)) == -1)
- err(1, "%s: getline", __func__);
+int keep_alive_timeout = 60; /* 0 -> no timeout */
- bufp = *lineptr;
- if (!suppress_output)
- log_info("%s", bufp);
+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 */
- if (len < 4)
- continue;
+/* 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;
}
-
- done:
- (void)strtonum(code, 100, 553, &errstr);
- if (errstr)
- errx(1, "%s: Response code is %s: %s", __func__, errstr, code);
-
- return lookup[code[0] - '1'];
}
-int
-ftp_command(FILE *fp, const char *fmt, ...)
+static void
+may_reset_noop_timeout(void)
{
- va_list ap;
- char *buf = NULL, *cmd;
- size_t n = 0;
- int r;
+ if (keep_alive_timeout != 0)
+ last_timestamp = time(NULL);
+}
- va_start(ap, fmt);
- r = vasprintf(&cmd, fmt, ap);
- va_end(ap);
- if (r < 0)
- errx(1, "%s: vasprintf", __func__);
+static void
+may_receive_noop_ack(void)
+{
+ int i;
- if (io_debug)
- fprintf(stderr, ">>> %s\n", cmd);
+ if (cout == NULL) {
+ /* Lost connection; so just pretend we're fine. */
+ current_nop_pos = full_noops_sent = 0;
+ return;
+ }
- if (fprintf(fp, "%s\r\n", cmd) < 0)
- errx(1, "%s: fprintf", __func__);
+ /* 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);
- (void)fflush(fp);
- free(cmd);
- r = ftp_getline(&buf, &n, 0, fp);
- free(buf);
- return r;
+ 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
-ftp_auth(FILE *fp, const char *user, const char *pass)
+getreply(int expecteof)
{
- char *addr = NULL, hn[HOST_NAME_MAX+1], *un;
- int code;
+ 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');
+ }
+}
- code = ftp_command(fp, "USER %s", user ? user : "anonymous");
- if (code != P_OK && code != P_INTER)
- return code;
+#ifndef SMALL
+jmp_buf sendabort;
- if (pass == NULL) {
- if (gethostname(hn, sizeof hn) == -1)
- err(1, "%s: gethostname", __func__);
+/* 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);
+}
- un = getlogin();
- xasprintf(&addr, "%s@%s", un ? un : "anonymous", hn);
+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;
- code = ftp_command(fp, "PASS %s", pass ? pass : addr);
- free(addr);
- return code;
+ 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 */
-int
-ftp_size(FILE *fp, const char *fn, off_t *sizep, char **buf)
-{
- size_t n = 0;
- off_t file_sz;
- int code;
-
- if (io_debug)
- fprintf(stderr, ">>> SIZE %s\n", fn);
+jmp_buf recvabort;
- if (fprintf(fp, "SIZE %s\r\n", fn) < 0)
- errx(1, "%s: fprintf", __func__);
+/* ARGSUSED */
+void
+abortrecv(int signo)
+{
- (void)fflush(fp);
- if ((code = ftp_getline(buf, &n, 1, fp)) != P_OK)
- return code;
+ alarmtimer(0);
+ mflag = 0;
+ abrtflag = 0;
+ fputs("\nreceive aborted\nwaiting for remote to finish abort.\n", ttyout);
+ (void)fflush(ttyout);
+ longjmp(recvabort, 1);
+}
- if (sscanf(*buf, "%*u %lld", &file_sz) != 1)
- errx(1, "%s: sscanf size", __func__);
+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;
- if (sizep)
- *sizep = file_sz;
+ 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;
+ }
- return code;
+ 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
-ftp_eprt(FILE *fp)
+initconn(void)
{
- struct sockaddr_storage ss;
- char addr[NI_MAXHOST], port[NI_MAXSERV], *eprt;
- socklen_t len;
- int e, on, ret, sock;
-
- len = sizeof(ss);
- memset(&ss, 0, len);
- if (getsockname(fileno(fp), (struct sockaddr *)&ss, &len) == -1) {
- warn("%s: getsockname", __func__);
- return -1;
+ 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.sa.sa_family == AF_INET6
+ && (IN6_IS_ADDR_LINKLOCAL(&myctladdr.sin6.sin6_addr)
+ || IN6_IS_ADDR_SITELOCAL(&myctladdr.sin6.sin6_addr))) {
+ warnx("use of scoped address can be troublesome");
}
-
- /* pick a free port */
- switch (ss.ss_family) {
- case AF_INET:
- ((struct sockaddr_in *)&ss)->sin_port = 0;
- break;
- case AF_INET6:
- ((struct sockaddr_in6 *)&ss)->sin6_port = 0;
- break;
- default:
- errx(1, "%s: Invalid socket family", __func__);
+#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);
+ }
}
-
- if ((sock = socket(ss.ss_family, SOCK_STREAM, 0)) == -1) {
- warn("%s: socket", __func__);
- return -1;
+#endif /* !SMALL */
+reinit:
+ if (passivemode) {
+ data_addr = myctladdr;
+ data = socket(data_addr.sa.sa_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.sa.sa_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.sa.sa_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.sin.sin_family = AF_INET;
+ data_addr.sin.sin_len = sizeof(struct sockaddr_in);
+ data_addr.sin.sin_addr.s_addr =
+ htonl(pack4(addr, 0));
+ data_addr.sin.sin_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.sa.sa_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.sin.sin_family = AF_INET;
+ data_addr.sin.sin_len = sizeof(struct sockaddr_in);
+ data_addr.sin.sin_addr.s_addr =
+ htonl(pack4(addr, 0));
+ data_addr.sin.sin_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.sin6.sin6_family = AF_INET6;
+ data_addr.sin6.sin6_len = sizeof(struct sockaddr_in6);
+ {
+ u_int32_t *p32;
+ p32 = (u_int32_t *)&data_addr.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.sin6.sin6_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.sin.sin_port = htons(port[1]);
+ } else
+ goto bad;
+
+ for (error = connect(data, &data_addr.sa, data_addr.sa.sa_len);
+ error != 0 && errno == EINTR;
+ error = connect_wait(data))
+ continue;
+ if (error != 0) {
+ if (activefallback) {
+ (void)close(data);
+ data = -1;
+ passivemode = 0;
+ activefallback = 0;
+ goto reinit;
+ }
+ warn("connect");
+ goto bad;
+ }
+ if (data_addr.sa.sa_family == AF_INET) {
+ on = IPTOS_THROUGHPUT;
+ if (setsockopt(data, IPPROTO_IP, IP_TOS, (char *)&on,
+ sizeof(int)) < 0)
+ warn("setsockopt TOS (ignored)");
+ }
+ return (0);
}
- switch (ss.ss_family) {
+noport:
+ data_addr = myctladdr;
+ if (sendport)
+ data_addr.sin.sin_port = 0; /* let system pick one */
+ if (data != -1)
+ (void)close(data);
+ data = socket(data_addr.sa.sa_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.sa.sa_family) {
case AF_INET:
on = IP_PORTRANGE_HIGH;
- if (setsockopt(sock, IPPROTO_IP, IP_PORTRANGE,
+ 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(sock, IPPROTO_IPV6, IPV6_PORTRANGE,
+ if (setsockopt(data, IPPROTO_IPV6, IPV6_PORTRANGE,
(char *)&on, sizeof(on)) < 0)
warn("setsockopt IPV6_PORTRANGE (ignored)");
break;
}
-
- if (bind(sock, (struct sockaddr *)&ss, len) == -1) {
- close(sock);
- warn("%s: bind", __func__);
- return -1;
+ if (bind(data, &data_addr.sa, data_addr.sa.sa_len) < 0) {
+ warn("bind");
+ goto bad;
}
-
- if (listen(sock, 1) == -1) {
- close(sock);
- warn("%s: listen", __func__);
- return -1;
+#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, &data_addr.sa, &namelen) < 0) {
+ warn("getsockname");
+ goto bad;
}
-
- /* Find out the ephemeral port chosen */
- len = sizeof(ss);
- memset(&ss, 0, len);
- if (getsockname(sock, (struct sockaddr *)&ss, &len) == -1) {
- close(sock);
- warn("%s: getsockname", __func__);
- return -1;
+ 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 sockaddr_union tmp;
+
+ tmp = data_addr;
+ switch (tmp.sa.sa_family) {
+ case AF_INET:
+ if (!epsv4 || epsv4bad) {
+ result = COMPLETE +1;
+ break;
+ }
+ /*FALLTHROUGH*/
+ case AF_INET6:
+ if (tmp.sa.sa_family == AF_INET6)
+ tmp.sin6.sin6_scope_id = 0;
+ af_tmp = (tmp.sa.sa_family == AF_INET) ? 1 : 2;
+ if (getnameinfo(&tmp.sa, tmp.sa.sa_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.sa.sa_family) {
+ case AF_INET:
+ a = (char *)&data_addr.sin.sin_addr;
+ p = (char *)&data_addr.sin.sin_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.sin6.sin6_addr;
+ p = (char *)&data_addr.sin6.sin6_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.sa.sa_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);
+}
- if ((e = getnameinfo((struct sockaddr *)&ss, len,
- addr, sizeof(addr), port, sizeof(port),
- NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
- close(sock);
- warn("%s: getnameinfo: %s", __func__, gai_strerror(e));
- return -1;
+FILE *
+dataconn(const char *lmode)
+{
+ union sockaddr_union from;
+ socklen_t fromlen = myctladdr.sa.sa_len;
+ int s;
+
+ if (passivemode)
+ return (fdopen(data, lmode));
+
+ s = accept(data, &from.sa, &fromlen);
+ if (s < 0) {
+ warn("accept");
+ (void)close(data), data = -1;
+ return (NULL);
+ }
+ (void)close(data);
+ data = s;
+ if (from.sa.sa_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));
+}
- xasprintf(&eprt, "EPRT |%d|%s|%s|",
- ss.ss_family == AF_INET ? 1 : 2, addr, port);
+/* ARGSUSED */
+void
+psummary(int signo)
+{
+ int save_errno = errno;
- ret = ftp_command(fp, "%s", eprt);
- free(eprt);
- if (ret != P_OK) {
- close(sock);
- return -1;
- }
+ if (bytes > 0)
+ ptransfer(1);
+ errno = save_errno;
+}
- return sock;
+/* ARGSUSED */
+void
+psabort(int signo)
+{
+
+ alarmtimer(0);
+ abrtflag++;
}
-int
-ftp_epsv(FILE *fp)
+void
+pswitch(int flag)
{
- struct sockaddr_storage ss;
- char *buf = NULL, delim[4], *s, *e;
- size_t n = 0;
- socklen_t len;
- int error, port, sock;
+ sig_t oldintr;
+ static struct comvars {
+ int connect;
+ char name[HOST_NAME_MAX+1];
+ union sockaddr_union mctl;
+ union sockaddr_union 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);
+ }
+}
- if (io_debug)
- fprintf(stderr, ">>> EPSV\n");
+/* ARGSUSED */
+void
+abortpt(int signo)
+{
- if (fprintf(fp, "EPSV\r\n") < 0)
- errx(1, "%s: fprintf", __func__);
+ alarmtimer(0);
+ putc('\n', ttyout);
+ (void)fflush(ttyout);
+ ptabflg++;
+ mflag = 0;
+ abrtflag = 0;
+ longjmp(ptabort, 1);
+}
- (void)fflush(fp);
- if (ftp_getline(&buf, &n, 1, fp) != P_OK) {
- free(buf);
- return -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 ((s = strchr(buf, '(')) == NULL || (e = strchr(s, ')')) == NULL) {
- warnx("Malformed EPSV reply");
- free(buf);
- return -1;
+ if (curtype != prox_type)
+ changetype(prox_type, 1);
+ if (command("PASV") != COMPLETE) {
+ fputs("proxy server does not support third party transfers.\n",
+ ttyout);
+ return;
}
-
- s++;
- *e = '\0';
- if (sscanf(s, "%c%c%c%d%c", &delim[0], &delim[1], &delim[2],
- &port, &delim[3]) != 5) {
- warnx("EPSV parse error");
- free(buf);
- return -1;
+ pswitch(0);
+ if (!connected) {
+ fputs("No primary connection.\n", ttyout);
+ pswitch(1);
+ code = -1;
+ return;
}
- free(buf);
-
- if (delim[0] != delim[1] || delim[0] != delim[2]
- || delim[0] != delim[3]) {
- warnx("EPSV parse error");
- return -1;
+ if (curtype != prox_type)
+ changetype(prox_type, 1);
+ if (command("PORT %s", pasv) != COMPLETE) {
+ pswitch(1);
+ return;
}
-
- len = sizeof(ss);
- memset(&ss, 0, len);
- if (getpeername(fileno(fp), (struct sockaddr *)&ss, &len) == -1) {
- warn("%s: getpeername", __func__);
- return -1;
+ 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);
+}
- switch (ss.ss_family) {
- case AF_INET:
- ((struct sockaddr_in *)&ss)->sin_port = htons(port);
- break;
- case AF_INET6:
- ((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
- break;
- default:
- errx(1, "%s: Invalid socket family", __func__);
+#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
- if ((sock = socket(ss.ss_family, SOCK_STREAM, 0)) == -1) {
- warn("%s: socket", __func__);
- return -1;
+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);
+}
- for (error = connect(sock, (struct sockaddr *)&ss, len);
- error != 0 && errno == EINTR; error = connect_wait(sock))
- continue;
+jmp_buf forceabort;
- if (error != 0) {
- warn("%s: connect", __func__);
- return -1;
+/* 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;
}
- return sock;
+ 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.h b/usr.bin/ftp/ftp.h
deleted file mode 100644
index 011db6b7ed3..00000000000
--- a/usr.bin/ftp/ftp.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/* $OpenBSD: ftp.h,v 1.3 2019/05/15 13:42:40 florian Exp $ */
-
-/*
- * Copyright (c) 2015 Sunil Nimmagadda <sunil@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.
- */
-
-#include <sys/types.h>
-
-#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
-
-#define S_HTTP 0
-#define S_FTP 1
-#define S_FILE 2
-#define S_HTTPS 3
-
-#define TMPBUF_LEN 131072
-#define IMSG_OPEN 1
-
-#define P_PRE 100
-#define P_OK 200
-#define P_INTER 300
-#define N_TRANS 400
-#define N_PERM 500
-
-#ifndef nitems
-#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
-#endif
-
-struct imsg;
-struct imsgbuf;
-
-struct url {
- int scheme;
- int ipliteral;
- char *host;
- char *port;
- char *path;
- char *basic_auth;
-
- char *fname;
- int chunked;
-};
-
-/* cmd.c */
-void cmd(const char *, const char *, const char *);
-
-/* main.c */
-extern struct imsgbuf child_ibuf;
-extern const char *useragent;
-extern int activemode, family, io_debug, verbose, progressmeter;
-extern volatile sig_atomic_t interrupted;
-extern FILE *msgout;
-
-/* file.c */
-struct url *file_request(struct imsgbuf *, struct url *, off_t *, off_t *);
-void file_save(struct url *, FILE *, off_t *);
-
-/* ftp.c */
-void ftp_connect(struct url *, struct url *, int);
-struct url *ftp_get(struct url *, struct url *, off_t *, off_t *);
-void ftp_quit(struct url *);
-void ftp_save(struct url *, FILE *, off_t *);
-int ftp_auth(FILE *, const char *, const char *);
-int ftp_command(FILE *, const char *, ...)
- __attribute__((__format__ (printf, 2, 3)))
- __attribute__((__nonnull__ (2)));
-int ftp_eprt(FILE *);
-int ftp_epsv(FILE *);
-int ftp_getline(char **, size_t *, int, FILE *);
-int ftp_size(FILE *, const char *, off_t *, char **);
-
-/* http.c */
-void http_connect(struct url *, struct url *, int);
-struct url *http_get(struct url *, struct url *, off_t *, off_t *);
-void http_close(struct url *);
-void http_save(struct url *, FILE *, off_t *);
-void https_init(char *);
-
-/* progressmeter.c */
-void init_stats(off_t, off_t *);
-void start_progress_meter(const char *, const char *);
-void stop_progress_meter(void);
-void finish_stats(void);
-
-/* url.c */
-int scheme_lookup(const char *);
-void url_connect(struct url *, struct url *, int);
-char *url_encode(const char *);
-void url_free(struct url *);
-struct url *url_parse(const char *);
-struct url *url_request(struct url *, struct url *, off_t *, off_t *);
-void url_save(struct url *, FILE *, off_t *);
-void url_close(struct url *);
-char *url_str(struct url *);
-void log_request(const char *, struct url *, struct url *);
-
-/* util.c */
-int connect_wait(int);
-void copy_file(FILE *, FILE *, off_t *);
-int tcp_connect(const char *, const char *, int);
-int fd_request(char *, int, off_t *);
-int read_message(struct imsgbuf *, struct imsg *);
-void send_message(struct imsgbuf *, int, uint32_t, void *, size_t, int);
-void log_info(const char *, ...)
- __attribute__((__format__ (printf, 1, 2)))
- __attribute__((__nonnull__ (1)));
diff --git a/usr.bin/ftp/ftp_var.h b/usr.bin/ftp/ftp_var.h
new file mode 100644
index 00000000000..cbd316107b4
--- /dev/null
+++ b/usr.bin/ftp/ftp_var.h
@@ -0,0 +1,231 @@
+/* $OpenBSD: ftp_var.h,v 1.43 2019/05/16 12:44:18 florian 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] */
+int resume; /* continue transfer */
+char *srcaddr; /* source address to bind to */
+#endif /* !SMALL */
+
+char *cookiefile; /* cookie jar to use */
+
+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 NOSSL
+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 NOSSL
+extern struct tls_config *tls_config;
+extern int tls_session_fd;
+#endif /* !NOSSL */
diff --git a/usr.bin/ftp/http.c b/usr.bin/ftp/http.c
deleted file mode 100644
index 64ff6109187..00000000000
--- a/usr.bin/ftp/http.c
+++ /dev/null
@@ -1,801 +0,0 @@
-/* $OpenBSD: http.c,v 1.9 2019/05/14 18:51:07 deraadt Exp $ */
-
-/*
- * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
- * Copyright (c) 2012 - 2015 Reyk Floeter <reyk@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.
- */
-
-#include <err.h>
-#include <fcntl.h>
-#include <libgen.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-#include <unistd.h>
-#ifndef NOSSL
-#include <tls.h>
-#endif
-
-#include "ftp.h"
-#include "xmalloc.h"
-
-#define MAX_REDIRECTS 10
-
-#ifndef NOSSL
-#define MINBUF 128
-
-static struct tls_config *tls_config;
-static struct tls *ctx;
-static int tls_session_fd = -1;
-static char * const tls_verify_opts[] = {
-#define HTTP_TLS_CAFILE 0
- "cafile",
-#define HTTP_TLS_CAPATH 1
- "capath",
-#define HTTP_TLS_CIPHERS 2
- "ciphers",
-#define HTTP_TLS_DONTVERIFY 3
- "dont",
-#define HTTP_TLS_VERIFYDEPTH 4
- "depth",
-#define HTTP_TLS_MUSTSTAPLE 5
- "muststaple",
-#define HTTP_TLS_NOVERIFYTIME 6
- "noverifytime",
-#define HTTP_TLS_SESSION 7
- "session",
-#define HTTP_TLS_DOVERIFY 8
- "do",
- NULL
-};
-#endif /* NOSSL */
-
-/*
- * HTTP status codes based on IANA assignments (2014-06-11 version):
- * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
- * plus legacy (306) and non-standard (420).
- */
-static struct http_status {
- int code;
- const char *name;
-} http_status[] = {
- { 100, "Continue" },
- { 101, "Switching Protocols" },
- { 102, "Processing" },
- /* 103-199 unassigned */
- { 200, "OK" },
- { 201, "Created" },
- { 202, "Accepted" },
- { 203, "Non-Authoritative Information" },
- { 204, "No Content" },
- { 205, "Reset Content" },
- { 206, "Partial Content" },
- { 207, "Multi-Status" },
- { 208, "Already Reported" },
- /* 209-225 unassigned */
- { 226, "IM Used" },
- /* 227-299 unassigned */
- { 300, "Multiple Choices" },
- { 301, "Moved Permanently" },
- { 302, "Found" },
- { 303, "See Other" },
- { 304, "Not Modified" },
- { 305, "Use Proxy" },
- { 306, "Switch Proxy" },
- { 307, "Temporary Redirect" },
- { 308, "Permanent Redirect" },
- /* 309-399 unassigned */
- { 400, "Bad Request" },
- { 401, "Unauthorized" },
- { 402, "Payment Required" },
- { 403, "Forbidden" },
- { 404, "Not Found" },
- { 405, "Method Not Allowed" },
- { 406, "Not Acceptable" },
- { 407, "Proxy Authentication Required" },
- { 408, "Request Timeout" },
- { 409, "Conflict" },
- { 410, "Gone" },
- { 411, "Length Required" },
- { 412, "Precondition Failed" },
- { 413, "Payload Too Large" },
- { 414, "URI Too Long" },
- { 415, "Unsupported Media Type" },
- { 416, "Range Not Satisfiable" },
- { 417, "Expectation Failed" },
- { 418, "I'm a teapot" },
- /* 419-421 unassigned */
- { 420, "Enhance Your Calm" },
- { 422, "Unprocessable Entity" },
- { 423, "Locked" },
- { 424, "Failed Dependency" },
- /* 425 unassigned */
- { 426, "Upgrade Required" },
- /* 427 unassigned */
- { 428, "Precondition Required" },
- { 429, "Too Many Requests" },
- /* 430 unassigned */
- { 431, "Request Header Fields Too Large" },
- /* 432-450 unassigned */
- { 451, "Unavailable For Legal Reasons" },
- /* 452-499 unassigned */
- { 500, "Internal Server Error" },
- { 501, "Not Implemented" },
- { 502, "Bad Gateway" },
- { 503, "Service Unavailable" },
- { 504, "Gateway Timeout" },
- { 505, "HTTP Version Not Supported" },
- { 506, "Variant Also Negotiates" },
- { 507, "Insufficient Storage" },
- { 508, "Loop Detected" },
- /* 509 unassigned */
- { 510, "Not Extended" },
- { 511, "Network Authentication Required" },
- /* 512-599 unassigned */
- { 0, NULL },
- };
-
-struct http_headers {
- char *location;
- off_t content_length;
- int chunked;
-};
-
-static void decode_chunk(int, uint, FILE *);
-static char *header_lookup(const char *, const char *);
-static const char *http_error(int);
-static void http_headers_free(struct http_headers *);
-static ssize_t http_getline(int, char **, size_t *);
-static size_t http_read(int, char *, size_t);
-static struct url *http_redirect(struct url *, char *);
-static void http_save_chunks(struct url *, FILE *, off_t *);
-static int http_status_cmp(const void *, const void *);
-static int http_request(int, const char *,
- struct http_headers **);
-static char *relative_path_resolve(const char *, const char *);
-
-#ifndef NOSSL
-static void tls_copy_file(struct url *, FILE *, off_t *);
-static ssize_t tls_getline(char **, size_t *, struct tls *);
-#endif
-
-static FILE *fp;
-
-void
-http_connect(struct url *url, struct url *proxy, int timeout)
-{
- const char *host, *port;
- int sock;
-
- host = proxy ? proxy->host : url->host;
- port = proxy ? proxy->port : url->port;
- if ((sock = tcp_connect(host, port, timeout)) == -1)
- exit(1);
-
- if ((fp = fdopen(sock, "r+")) == NULL)
- err(1, "%s: fdopen", __func__);
-
-#ifndef NOSSL
- struct http_headers *headers;
- char *auth = NULL, *req;
- int authlen = 0, code;
-
- if (url->scheme != S_HTTPS)
- return;
-
- if (proxy) {
- if (url->basic_auth)
- authlen = xasprintf(&auth,
- "Proxy-Authorization: Basic %s\r\n",
- url->basic_auth);
-
- xasprintf(&req,
- "CONNECT %s:%s HTTP/1.0\r\n"
- "User-Agent: %s\r\n"
- "%s"
- "\r\n",
- url->host, url->port,
- useragent,
- url->basic_auth ? auth : "");
-
- freezero(auth, authlen);
- if ((code = http_request(S_HTTP, req, &headers)) != 200)
- errx(1, "%s: failed to CONNECT to %s:%s: %s",
- __func__, url->host, url->port, http_error(code));
-
- free(req);
- http_headers_free(headers);
- }
-
- if ((ctx = tls_client()) == NULL)
- errx(1, "failed to create tls client");
-
- if (tls_configure(ctx, tls_config) != 0)
- errx(1, "%s: %s", __func__, tls_error(ctx));
-
- if (tls_connect_socket(ctx, sock, url->host) != 0)
- errx(1, "%s: %s", __func__, tls_error(ctx));
-#endif /* NOSSL */
-}
-
-struct url *
-http_get(struct url *url, struct url *proxy, off_t *offset, off_t *sz)
-{
- struct http_headers *headers;
- char *auth = NULL, *path = NULL, *range = NULL, *req;
- int authlen = 0, code, redirects = 0;
-
- redirected:
- log_request("Requesting", url, proxy);
- if (*offset)
- xasprintf(&range, "Range: bytes=%lld-\r\n", *offset);
-
- if (url->basic_auth)
- authlen = xasprintf(&auth, "Authorization: Basic %s\r\n",
- url->basic_auth);
-
- if (proxy && url->scheme != S_HTTPS)
- path = url_str(url);
- else if (url->path)
- path = url_encode(url->path);
-
- xasprintf(&req,
- "GET %s HTTP/1.1\r\n"
- "Host: %s\r\n"
- "%s"
- "%s"
- "Connection: close\r\n"
- "User-Agent: %s\r\n"
- "\r\n",
- path ? path : "/",
- url->host,
- *offset ? range : "",
- url->basic_auth ? auth : "",
- useragent);
- code = http_request(url->scheme, req, &headers);
- freezero(auth, authlen);
- auth = NULL;
- authlen = 0;
- free(range);
- range = NULL;
- free(path);
- path = NULL;
- free(req);
- req = NULL;
- switch (code) {
- case 200:
- if (*offset) {
- warnx("Server does not support resume.");
- *offset = 0;
- }
- break;
- case 206:
- break;
- case 301:
- case 302:
- case 303:
- case 307:
- http_close(url);
- if (++redirects > MAX_REDIRECTS)
- errx(1, "Too many redirections requested");
-
- if (headers->location == NULL)
- errx(1, "%s: Location header missing", __func__);
-
- url = http_redirect(url, headers->location);
- http_headers_free(headers);
- log_request("Redirected to", url, proxy);
- http_connect(url, proxy, 0);
- goto redirected;
- case 416:
- errx(1, "File is already fully retrieved.");
- break;
- default:
- errx(1, "Error retrieving file: %d %s", code, http_error(code));
- }
-
- *sz = headers->content_length + *offset;
- url->chunked = headers->chunked;
- http_headers_free(headers);
- return url;
-}
-
-void
-http_save(struct url *url, FILE *dst_fp, off_t *offset)
-{
- if (url->chunked)
- http_save_chunks(url, dst_fp, offset);
-#ifndef NOSSL
- else if (url->scheme == S_HTTPS)
- tls_copy_file(url, dst_fp, offset);
-#endif
- else
- copy_file(dst_fp, fp, offset);
-}
-
-static struct url *
-http_redirect(struct url *old_url, char *location)
-{
- struct url *new_url;
-
- /* absolute uri reference */
- if (strncasecmp(location, "http", 4) == 0 ||
- strncasecmp(location, "https", 5) == 0) {
- if ((new_url = url_parse(location)) == NULL)
- exit(1);
-
- goto done;
- }
-
- /* relative uri reference */
- new_url = xcalloc(1, sizeof *new_url);
- new_url->scheme = old_url->scheme;
- new_url->host = xstrdup(old_url->host);
- new_url->port = xstrdup(old_url->port);
-
- /* absolute-path reference */
- if (location[0] == '/')
- new_url->path = xstrdup(location);
- else
- new_url->path = relative_path_resolve(old_url->path, location);
-
- done:
- new_url->fname = xstrdup(old_url->fname);
- url_free(old_url);
- return new_url;
-}
-
-static char *
-relative_path_resolve(const char *base_path, const char *location)
-{
- char *new_path, *p;
-
- /* trim fragment component from both uri */
- if ((p = strchr(location, '#')) != NULL)
- *p = '\0';
- if (base_path && (p = strchr(base_path, '#')) != NULL)
- *p = '\0';
-
- if (base_path == NULL)
- xasprintf(&new_path, "/%s", location);
- else if (base_path[strlen(base_path) - 1] == '/')
- xasprintf(&new_path, "%s%s", base_path, location);
- else {
- p = dirname(base_path);
- xasprintf(&new_path, "%s/%s",
- strcmp(p, ".") == 0 ? "" : p, location);
- }
-
- return new_path;
-}
-
-static void
-http_save_chunks(struct url *url, FILE *dst_fp, off_t *offset)
-{
- char *buf = NULL;
- size_t n = 0;
- uint chunk_sz;
-
- http_getline(url->scheme, &buf, &n);
- if (sscanf(buf, "%x", &chunk_sz) != 1)
- errx(1, "%s: Failed to get chunk size", __func__);
-
- while (chunk_sz > 0) {
- decode_chunk(url->scheme, chunk_sz, dst_fp);
- *offset += chunk_sz;
- http_getline(url->scheme, &buf, &n);
- if (sscanf(buf, "%x", &chunk_sz) != 1)
- errx(1, "%s: Failed to get chunk size", __func__);
- }
-
- free(buf);
-}
-
-static void
-decode_chunk(int scheme, uint sz, FILE *dst_fp)
-{
- size_t bufsz;
- size_t r;
- char buf[BUFSIZ], crlf[2];
-
- bufsz = sizeof(buf);
- while (sz > 0) {
- if (sz < bufsz)
- bufsz = sz;
-
- r = http_read(scheme, buf, bufsz);
- if (fwrite(buf, 1, r, dst_fp) != r)
- errx(1, "%s: fwrite", __func__);
-
- sz -= r;
- }
-
- /* CRLF terminating the chunk */
- if (http_read(scheme, crlf, sizeof(crlf)) != sizeof(crlf))
- errx(1, "%s: Failed to read terminal crlf", __func__);
-
- if (crlf[0] != '\r' || crlf[1] != '\n')
- errx(1, "%s: Invalid chunked encoding", __func__);
-}
-
-void
-http_close(struct url *url)
-{
-#ifndef NOSSL
- ssize_t r;
-
- if (url->scheme == S_HTTPS) {
- if (tls_session_fd != -1)
- fprintf(stderr, "tls session resumed: %s\n",
- tls_conn_session_resumed(ctx) ? "yes" : "no");
-
- do {
- r = tls_close(ctx);
- } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT);
- tls_free(ctx);
- }
-
-#endif
- fclose(fp);
-}
-
-static int
-http_request(int scheme, const char *req, struct http_headers **hdrs)
-{
- struct http_headers *headers;
- const char *e;
- char *buf = NULL, *p;
- size_t n = 0;
- ssize_t buflen;
- uint code;
-#ifndef NOSSL
- size_t len;
- ssize_t nw;
-#endif
-
- if (io_debug)
- fprintf(stderr, "<<< %s", req);
-
- switch (scheme) {
-#ifndef NOSSL
- case S_HTTPS:
- len = strlen(req);
- while (len > 0) {
- nw = tls_write(ctx, req, len);
- if (nw == TLS_WANT_POLLIN || nw == TLS_WANT_POLLOUT)
- continue;
- if (nw < 0)
- errx(1, "tls_write: %s", tls_error(ctx));
- req += nw;
- len -= nw;
- }
- break;
-#endif
- case S_FTP:
- case S_HTTP:
- if (fprintf(fp, "%s", req) < 0)
- errx(1, "%s: fprintf", __func__);
- (void)fflush(fp);
- break;
- }
-
- http_getline(scheme, &buf, &n);
- if (io_debug)
- fprintf(stderr, ">>> %s", buf);
-
- if (sscanf(buf, "%*s %u %*s", &code) != 1)
- errx(1, "%s: failed to extract status code", __func__);
-
- if (code < 100 || code > 511)
- errx(1, "%s: invalid status code %d", __func__, code);
-
- headers = xcalloc(1, sizeof *headers);
- for (;;) {
- buflen = http_getline(scheme, &buf, &n);
- buflen -= 1;
- if (buflen > 0 && buf[buflen - 1] == '\r')
- buflen -= 1;
- buf[buflen] = '\0';
-
- if (io_debug)
- fprintf(stderr, ">>> %s\n", buf);
-
- if (buflen == 0)
- break; /* end of headers */
-
- if ((p = header_lookup(buf, "Content-Length:")) != NULL) {
- headers->content_length = strtonum(p, 0, INT64_MAX, &e);
- if (e)
- err(1, "%s: Content-Length is %s: %lld",
- __func__, e, headers->content_length);
- }
-
- if ((p = header_lookup(buf, "Location:")) != NULL)
- headers->location = xstrdup(p);
-
- if ((p = header_lookup(buf, "Transfer-Encoding:")) != NULL)
- if (strcasestr(p, "chunked") != NULL)
- headers->chunked = 1;
-
- }
-
- *hdrs = headers;
- free(buf);
- return code;
-}
-
-static void
-http_headers_free(struct http_headers *headers)
-{
- if (headers == NULL)
- return;
-
- free(headers->location);
- free(headers);
-}
-
-static char *
-header_lookup(const char *buf, const char *key)
-{
- char *p;
-
- if (strncasecmp(buf, key, strlen(key)) == 0) {
- if ((p = strchr(buf, ' ')) == NULL)
- errx(1, "Failed to parse %s", key);
- return ++p;
- }
-
- return NULL;
-}
-
-static const char *
-http_error(int code)
-{
- struct http_status error, *res;
-
- /* Set up key */
- error.code = code;
-
- if ((res = bsearch(&error, http_status,
- sizeof(http_status) / sizeof(http_status[0]) - 1,
- sizeof(http_status[0]), http_status_cmp)) != NULL)
- return (res->name);
-
- return (NULL);
-}
-
-static int
-http_status_cmp(const void *a, const void *b)
-{
- const struct http_status *ea = a;
- const struct http_status *eb = b;
-
- return (ea->code - eb->code);
-}
-
-
-static ssize_t
-http_getline(int scheme, char **buf, size_t *n)
-{
- ssize_t buflen;
-
- switch (scheme) {
-#ifndef NOSSL
- case S_HTTPS:
- if ((buflen = tls_getline(buf, n, ctx)) == -1)
- errx(1, "%s: tls_getline", __func__);
- break;
-#endif
- case S_FTP:
- case S_HTTP:
- if ((buflen = getline(buf, n, fp)) == -1)
- err(1, "%s: getline", __func__);
- break;
- default:
- errx(1, "%s: invalid scheme", __func__);
- }
-
- return buflen;
-}
-
-static size_t
-http_read(int scheme, char *buf, size_t size)
-{
- size_t r;
-#ifndef NOSSL
- ssize_t rs;
-#endif
-
- switch (scheme) {
-#ifndef NOSSL
- case S_HTTPS:
- do {
- rs = tls_read(ctx, buf, size);
- } while (rs == TLS_WANT_POLLIN || rs == TLS_WANT_POLLOUT);
- if (rs == -1)
- errx(1, "%s: tls_read: %s", __func__, tls_error(ctx));
- r = rs;
- break;
-#endif
- case S_HTTP:
- if ((r = fread(buf, 1, size, fp)) < size)
- if (!feof(fp))
- errx(1, "%s: fread", __func__);
- break;
- default:
- errx(1, "%s: invalid scheme", __func__);
- }
-
- return r;
-}
-
-#ifndef NOSSL
-void
-https_init(char *tls_options)
-{
- char *str;
- int depth;
- const char *ca_file, *errstr;
-
- if (tls_init() != 0)
- errx(1, "tls_init failed");
-
- if ((tls_config = tls_config_new()) == NULL)
- errx(1, "tls_config_new failed");
-
- if (tls_config_set_protocols(tls_config, TLS_PROTOCOLS_ALL) != 0)
- errx(1, "tls set protocols failed: %s",
- tls_config_error(tls_config));
-
- if (tls_config_set_ciphers(tls_config, "legacy") != 0)
- errx(1, "tls set ciphers failed: %s",
- tls_config_error(tls_config));
-
- ca_file = tls_default_ca_cert_file();
- while (tls_options && *tls_options) {
- switch (getsubopt(&tls_options, tls_verify_opts, &str)) {
- case HTTP_TLS_CAFILE:
- if (str == NULL)
- errx(1, "missing CA file");
- ca_file = str;
- break;
- case HTTP_TLS_CAPATH:
- if (str == NULL)
- errx(1, "missing ca path");
- if (tls_config_set_ca_path(tls_config, str) != 0)
- errx(1, "tls ca path failed");
- break;
- case HTTP_TLS_CIPHERS:
- if (str == NULL)
- errx(1, "missing cipher list");
- if (tls_config_set_ciphers(tls_config, str) != 0)
- errx(1, "tls set ciphers failed");
- break;
- case HTTP_TLS_DONTVERIFY:
- tls_config_insecure_noverifycert(tls_config);
- tls_config_insecure_noverifyname(tls_config);
- break;
- case HTTP_TLS_VERIFYDEPTH:
- if (str == NULL)
- errx(1, "missing depth");
- depth = strtonum(str, 0, INT_MAX, &errstr);
- if (errstr)
- errx(1, "Cert validation depth is %s", errstr);
- tls_config_set_verify_depth(tls_config, depth);
- break;
- case HTTP_TLS_MUSTSTAPLE:
- tls_config_ocsp_require_stapling(tls_config);
- break;
- case HTTP_TLS_NOVERIFYTIME:
- tls_config_insecure_noverifytime(tls_config);
- break;
- case HTTP_TLS_SESSION:
- if (str == NULL)
- errx(1, "missing session file");
- tls_session_fd = open(str, O_RDWR|O_CREAT, 0600);
- if (tls_session_fd == -1)
- err(1, "failed to open or create session file "
- "'%s'", str);
- if (tls_config_set_session_fd(tls_config,
- tls_session_fd) == -1)
- errx(1, "failed to set session: %s",
- tls_config_error(tls_config));
- break;
- case HTTP_TLS_DOVERIFY:
- /* For compatibility, we do verify by default */
- break;
- default:
- errx(1, "Unknown -S suboption `%s'",
- suboptarg ? suboptarg : "");
- }
- }
-
- if (tls_config_set_ca_file(tls_config, ca_file) == -1)
- errx(1, "tls_config_set_ca_file failed");
-}
-
-static ssize_t
-tls_getline(char **buf, size_t *buflen, struct tls *tls)
-{
- char *newb;
- size_t newlen, off;
- int ret;
- unsigned char c;
-
- if (buf == NULL || buflen == NULL)
- return -1;
-
- /* If buf is NULL, we have to assume a size of zero */
- if (*buf == NULL)
- *buflen = 0;
-
- off = 0;
- do {
- do {
- ret = tls_read(tls, &c, 1);
- } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
- if (ret == -1)
- return -1;
-
- /* Ensure we can handle it */
- if (off + 2 > SSIZE_MAX)
- return -1;
-
- newlen = off + 2; /* reserve space for NUL terminator */
- if (newlen > *buflen) {
- newlen = newlen < MINBUF ? MINBUF : *buflen * 2;
- newb = recallocarray(*buf, *buflen, newlen, 1);
- if (newb == NULL)
- return -1;
-
- *buf = newb;
- *buflen = newlen;
- }
-
- *(*buf + off) = c;
- off += 1;
- } while (c != '\n');
-
- *(*buf + off) = '\0';
- return off;
-}
-
-static void
-tls_copy_file(struct url *url, FILE *dst_fp, off_t *offset)
-{
- char *tmp_buf;
- ssize_t r;
-
- tmp_buf = xmalloc(TMPBUF_LEN);
- for (;;) {
- do {
- r = tls_read(ctx, tmp_buf, TMPBUF_LEN);
- } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT);
-
- if (r == -1)
- errx(1, "%s: tls_read: %s", __func__, tls_error(ctx));
- else if (r == 0)
- break;
-
- *offset += r;
- if (fwrite(tmp_buf, 1, r, dst_fp) != (size_t)r)
- err(1, "%s: fwrite", __func__);
- }
- free(tmp_buf);
-}
-#endif /* NOSSL */
diff --git a/usr.bin/ftp/list.c b/usr.bin/ftp/list.c
new file mode 100644
index 00000000000..d95945e0a6e
--- /dev/null
+++ b/usr.bin/ftp/list.c
@@ -0,0 +1,86 @@
+/* $OpenBSD: list.c,v 1.9 2019/05/16 12:44:18 florian 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
index a528266f8e0..822dab3b382 100644
--- a/usr.bin/ftp/main.c
+++ b/usr.bin/ftp/main.c
@@ -1,153 +1,449 @@
-/* $OpenBSD: main.c,v 1.128 2019/05/15 13:42:40 florian Exp $ */
+/* $OpenBSD: main.c,v 1.129 2019/05/16 12:44:18 florian Exp $ */
+/* $NetBSD: main.c,v 1.24 1997/08/18 10:20:26 lukem Exp $ */
/*
- * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
+ * Copyright (C) 1997 and 1998 WIDE Project.
+ * All rights reserved.
*
- * 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.
+ * 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.
*
- * 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.
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
*/
-#include <sys/cdefs.h>
+/*
+ * 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/queue.h>
-#include <sys/stat.h>
#include <sys/socket.h>
-#include <sys/wait.h>
+#include <ctype.h>
#include <err.h>
-#include <errno.h>
#include <fcntl.h>
-#include <imsg.h>
-#include <libgen.h>
-#include <signal.h>
+#include <netdb.h>
+#include <pwd.h>
#include <stdio.h>
+#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include "ftp.h"
-#include "xmalloc.h"
-
-static int auto_fetch(int, char **);
-static void child(int, int, char **);
-static int parent(int, pid_t);
-static struct url *proxy_parse(const char *);
-static struct url *get_proxy(int);
-static void re_exec(int, int, char **);
-static void validate_output_fname(struct url *, const char *);
-static __dead void usage(void);
-
-struct imsgbuf child_ibuf;
-const char *useragent = "OpenBSD ftp";
-int activemode, family = AF_UNSPEC, io_debug;
-int progressmeter, verbose = 1;
-volatile sig_atomic_t interrupted = 0;
-FILE *msgout = stdout;
-
-static const char *title;
-static char *tls_options, *oarg;
-static int connect_timeout, resume;
+#include <tls.h>
+
+#include "cmds.h"
+#include "ftp_var.h"
+
+int connect_timeout;
+
+#ifndef NOSSL
+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",
+#define SSL_MUSTSTAPLE 6
+ "muststaple",
+#define SSL_NOVERIFYTIME 7
+ "noverifytime",
+#define SSL_SESSION 8
+ "session",
+ NULL
+};
+
+struct tls_config *tls_config;
+int tls_session_fd = -1;
+
+static void
+process_ssl_options(char *cp)
+{
+ const char *errstr;
+ long long depth;
+ char *str;
+
+ while (*cp) {
+ 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: %s",
+ tls_config_error(tls_config));
+ 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: %s",
+ tls_config_error(tls_config));
+ 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: %s",
+ tls_config_error(tls_config));
+ 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;
+ case SSL_MUSTSTAPLE:
+ tls_config_ocsp_require_stapling(tls_config);
+ break;
+ case SSL_NOVERIFYTIME:
+ tls_config_insecure_noverifytime(tls_config);
+ break;
+ case SSL_SESSION:
+ if (str == NULL)
+ errx(1, "missing session file");
+ if ((tls_session_fd = open(str, O_RDWR|O_CREAT,
+ 0600)) == -1)
+ err(1, "failed to open or create session file "
+ "'%s'", str);
+ if (tls_config_set_session_fd(tls_config,
+ tls_session_fd) == -1)
+ errx(1, "failed to set session: %s",
+ tls_config_error(tls_config));
+ break;
+ default:
+ errx(1, "unknown -S suboption `%s'",
+ suboptarg ? suboptarg : "");
+ /* NOTREACHED */
+ }
+ }
+}
+#endif /* !NOSSL */
+
+int family = PF_UNSPEC;
+int pipeout;
int
-main(int argc, char **argv)
+main(volatile int argc, char *argv[])
{
- const char *e;
- char **save_argv, *term;
- int ch, csock, dumb_terminal, rexec, save_argc;
-
- if (isatty(fileno(stdin)) != 1)
- verbose = 0;
-
- io_debug = getenv("IO_DEBUG") != NULL;
- term = getenv("TERM");
- dumb_terminal = (term == NULL || *term == '\0' ||
- !strcmp(term, "dumb") || !strcmp(term, "emacs") ||
- !strcmp(term, "su"));
- if (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) && !dumb_terminal)
- progressmeter = 1;
-
- csock = rexec = 0;
- save_argc = argc;
- save_argv = argv;
+ int ch, rval;
+#ifndef SMALL
+ int top;
+#endif
+ struct passwd *pw = NULL;
+ char *cp, homedir[PATH_MAX];
+ char *outfile = NULL;
+ const char *errstr;
+ int dumb_terminal = 0;
+
+ ftpport = "ftp";
+ httpport = "http";
+#ifndef NOSSL
+ httpsport = "https";
+#endif /* !NOSSL */
+ 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 NOSSL
+ cookiefile = NULL;
+#endif /* NOSSL */
+#ifndef SMALL
+ editing = 0;
+ el = NULL;
+ hist = 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 NOSSL
+ 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");
+ if (tls_config_set_protocols(tls_config,
+ TLS_PROTOCOLS_ALL) != 0)
+ errx(1, "tls set protocols failed: %s",
+ tls_config_error(tls_config));
+ if (tls_config_set_ciphers(tls_config, "legacy") != 0)
+ errx(1, "tls set ciphers failed: %s",
+ tls_config_error(tls_config));
+ }
+#endif /* !NOSSL */
+
+ httpuseragent = NULL;
+
while ((ch = getopt(argc, argv,
- "46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:xz:")) != -1) {
+ "46AaCc:dD:Eegik:Mmno:pP:r:S:s:tU:vVw:")) != -1) {
switch (ch) {
case '4':
- family = AF_INET;
+ family = PF_INET;
break;
case '6':
- family = AF_INET6;
+ family = PF_INET6;
break;
case 'A':
- activemode = 1;
+ 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':
- title = optarg;
+ action = optarg;
break;
- case 'o':
- oarg = optarg;
- if (!strlen(oarg))
- oarg = NULL;
+ case 'd':
+#ifndef SMALL
+ options |= SO_DEBUG;
+ debug++;
+#endif /* !SMALL */
break;
- case 'M':
- progressmeter = 0;
+
+ case 'E':
+ epsv4 = 0;
break;
- case 'm':
- progressmeter = 1;
+
+ case 'e':
+#ifndef SMALL
+ editing = 0;
+#endif /* !SMALL */
break;
- case 'S':
- tls_options = optarg;
+
+ case 'g':
+ doglob = 0;
break;
- case 'U':
- useragent = optarg;
+
+ case 'i':
+ interactive = 0;
break;
- case 'V':
- verbose = 0;
+
+ 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 'v':
- verbose = 1;
+ case 'M':
+ progress = 0;
break;
- case 'w':
- connect_timeout = strtonum(optarg, 0, 200, &e);
- if (e)
- errx(1, "-w: %s", e);
+ case 'm':
+ progress = -1;
break;
- /* options for internal use only */
- case 'x':
- rexec = 1;
+
+ case 'n':
+ autologin = 0;
break;
- case 'z':
- csock = strtonum(optarg, 3, getdtablesize() - 1, &e);
- if (e)
- errx(1, "-z: %s", e);
+
+ case 'o':
+ outfile = optarg;
+ if (*outfile == '\0') {
+ pipeout = 0;
+ outfile = NULL;
+ ttyout = stdout;
+ } else {
+ pipeout = strcmp(outfile, "-") == 0;
+ ttyout = pipeout ? stderr : stdout;
+ }
break;
- /* Ignoring all remaining options */
- case 'a':
- case 'c':
- case 'd':
- case 'E':
- case 'e':
- case 'g':
- case 'i':
- case 'k':
- case 'n':
- case 'P':
+
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 NOSSL
+ process_ssl_options(optarg);
+#endif /* !NOSSL */
+ 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;
+
+ case 'w':
+ connect_timeout = strtonum(optarg, 0, 200, &errstr);
+ if (errstr)
+ errx(1, "-w: %s", errstr);
break;
default:
usage();
@@ -156,268 +452,517 @@ main(int argc, char **argv)
argc -= optind;
argv += optind;
- if (rexec)
- child(csock, argc, argv);
+#ifndef NOSSL
+ cookie_load();
+#endif /* !NOSSL */
+ 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
- struct url *url;
-
- switch (argc) {
- case 0:
- cmd(NULL, NULL, NULL);
- return 0;
- case 1:
- case 2:
- switch (scheme_lookup(argv[0])) {
- case -1:
- cmd(argv[0], argv[1], NULL);
- return 0;
- case S_FTP:
- if ((url = url_parse(argv[0])) == NULL)
- exit(1);
-
- if (url->path &&
- url->path[strlen(url->path) - 1] != '/')
- break; /* auto fetch */
-
- cmd(url->host, url->port, url->path);
- return 0;
- }
- break;
- }
+ if (pledge("stdio rpath dns tty inet proc exec fattr",
+ NULL) == -1)
+ err(1, "pledge");
#else
- if (argc == 0)
- usage();
+ 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
+ }
- return auto_fetch(save_argc, save_argv);
+ 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 */
}
-static int
-auto_fetch(int sargc, char **sargv)
+void
+intr(void)
{
- pid_t pid;
- int sp[2];
-
- if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) != 0)
- err(1, "socketpair");
-
- switch (pid = fork()) {
- case -1:
- err(1, "fork");
- case 0:
- close(sp[0]);
- re_exec(sp[1], sargc, sargv);
- }
+ int save_errno = errno;
+
+ write(fileno(ttyout), "\n\r", 2);
+ alarmtimer(0);
- close(sp[1]);
- return parent(sp[0], pid);
+ errno = save_errno;
+ longjmp(toplevel, 1);
}
-static void
-re_exec(int sock, int argc, char **argv)
+void
+lostpeer(void)
{
- char **nargv, *sock_str;
- int i, j, nargc;
-
- nargc = argc + 4;
- nargv = xcalloc(nargc, sizeof(*nargv));
- xasprintf(&sock_str, "%d", sock);
- i = 0;
- nargv[i++] = argv[0];
- nargv[i++] = "-z";
- nargv[i++] = sock_str;
- nargv[i++] = "-x";
- for (j = 1; j < argc; j++)
- nargv[i++] = argv[j];
-
- execvp(nargv[0], nargv);
- err(1, "execvp");
+ 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;
}
-static int
-parent(int sock, pid_t child_pid)
+#ifndef SMALL
+/*
+ * Generate a prompt
+ */
+char *
+prompt(void)
{
- struct imsgbuf ibuf;
- struct imsg imsg;
- struct stat sb;
- off_t offset;
- int fd, save_errno, sig, status;
+ return ("ftp> ");
+}
- setproctitle("%s", "parent");
- if (pledge("stdio cpath rpath wpath sendfd", NULL) == -1)
- err(1, "pledge");
+/*
+ * Command parser.
+ */
+void
+cmdscanner(int top)
+{
+ struct cmd *c;
+ int num;
+ HistEvent hev;
- imsg_init(&ibuf, sock);
+ if (!top && !editing)
+ (void)putc('\n', ttyout);
for (;;) {
- if (read_message(&ibuf, &imsg) == 0)
- break;
-
- if (imsg.hdr.type != IMSG_OPEN)
- errx(1, "%s: IMSG_OPEN expected", __func__);
-
- offset = 0;
- fd = open(imsg.data, imsg.hdr.peerid, 0666);
- save_errno = errno;
- if (fd != -1 && fstat(fd, &sb) == 0) {
- if (sb.st_mode & S_IFDIR) {
- close(fd);
- fd = -1;
- save_errno = EISDIR;
- } else
- offset = sb.st_size;
+ 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);
}
- send_message(&ibuf, IMSG_OPEN, save_errno,
- &offset, sizeof offset, fd);
- imsg_free(&imsg);
+ 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);
+}
- close(sock);
- if (waitpid(child_pid, &status, 0) == -1 && errno != ECHILD)
- err(1, "wait");
+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);
+}
- sig = WTERMSIG(status);
- if (WIFSIGNALED(status) && sig != SIGPIPE)
- errx(1, "child terminated: signal %d", sig);
+/*
+ * Slice a string up into argc/argv.
+ */
- return WEXITSTATUS(status);
+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]);
+ }
}
-static void
-child(int sock, int argc, char **argv)
+#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)
{
- struct url *url;
- FILE *dst_fp;
- char *p;
- off_t offset, sz;
- int fd, i, tostdout;
+ 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;
+ }
+ }
- setproctitle("%s", "child");
-#ifndef NOSSL
- https_init(tls_options);
-#endif
- if (pledge("stdio inet dns recvfd tty", NULL) == -1)
- err(1, "pledge");
- if (!progressmeter && pledge("stdio inet dns recvfd", NULL) == -1)
- err(1, "pledge");
-
- imsg_init(&child_ibuf, sock);
- tostdout = oarg && (strcmp(oarg, "-") == 0);
- if (tostdout)
- msgout = stderr;
- if (resume && tostdout)
- errx(1, "can't append to stdout");
-
- for (i = 0; i < argc; i++) {
- fd = -1;
- offset = sz = 0;
-
- if ((url = url_parse(argv[i])) == NULL)
- exit(1);
-
- validate_output_fname(url, argv[i]);
- url_connect(url, get_proxy(url->scheme), connect_timeout);
- if (resume)
- fd = fd_request(url->fname, O_WRONLY|O_APPEND, &offset);
-
- url = url_request(url, get_proxy(url->scheme), &offset, &sz);
- if (resume && offset == 0 && fd != -1)
- if (ftruncate(fd, 0) != 0)
- err(1, "ftruncate");
-
- if (fd == -1 && !tostdout &&
- (fd = fd_request(url->fname,
- O_CREAT|O_TRUNC|O_WRONLY, NULL)) == -1)
- err(1, "Can't open file %s", url->fname);
-
- if (tostdout) {
- dst_fp = stdout;
- } else if ((dst_fp = fdopen(fd, "w")) == NULL)
- err(1, "%s: fdopen", __func__);
-
- init_stats(sz, &offset);
- if (progressmeter) {
- p = basename(url->path);
- start_progress_meter(p, title);
+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 */
- url_save(url, dst_fp, &offset);
- if (progressmeter)
- stop_progress_meter();
- finish_stats();
+ case '\\':
+ INC_CHKCURSOR(sb);
+ goto S2; /* slurp next character */
- if (!tostdout)
- fclose(dst_fp);
+ case '"':
+ INC_CHKCURSOR(sb);
+ goto S3; /* slurp quoted string */
- url_close(url);
- url_free(url);
+ default:
+ *ap = *sb; /* add character to token */
+ ap++;
+ INC_CHKCURSOR(sb);
+ got_one = 1;
+ goto S1;
}
- exit(0);
-}
+S2:
+ switch (*sb) {
-static struct url *
-get_proxy(int scheme)
-{
- static struct url *ftp_proxy, *http_proxy;
+ case '\0':
+ goto OUT;
- switch (scheme) {
- case S_HTTP:
- case S_HTTPS:
- if (http_proxy)
- return http_proxy;
- else
- return (http_proxy = proxy_parse("http_proxy"));
- case S_FTP:
- if (ftp_proxy)
- return ftp_proxy;
- else
- return (ftp_proxy = proxy_parse("ftp_proxy"));
default:
- return NULL;
+ *ap = *sb;
+ ap++;
+ INC_CHKCURSOR(sb);
+ got_one = 1;
+ goto S1;
}
-}
-static void
-validate_output_fname(struct url *url, const char *name)
-{
- url->fname = xstrdup(oarg ? oarg : basename(url->path));
- if (strcmp(url->fname, "/") == 0)
- errx(1, "No filename after host (use -o): %s", name);
+S3:
+ switch (*sb) {
- if (strcmp(url->fname, ".") == 0)
- errx(1, "No '/' after host (use -o): %s", name);
-}
+ case '\0':
+ goto OUT;
-static struct url *
-proxy_parse(const char *name)
-{
- struct url *proxy;
- char *str;
+ case '"':
+ INC_CHKCURSOR(sb);
+ goto S1;
- if ((str = getenv(name)) == NULL)
- return NULL;
+ default:
+ *ap = *sb;
+ ap++;
+ INC_CHKCURSOR(sb);
+ got_one = 1;
+ goto S3;
+ }
- if (strlen(str) == 0)
- return NULL;
+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;
+ }
- if ((proxy = url_parse(str)) == NULL)
- exit(1);
+#define HELPINDENT ((int) sizeof("disconnect"))
- if (proxy->scheme != S_HTTP)
- errx(1, "Malformed proxy URL: %s", str);
+ while (--argc > 0) {
+ char *arg;
- return proxy;
+ 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 */
-static __dead void
+__dead void
usage(void)
{
- fprintf(stderr, "usage:\t%s [-46AVv] [-D title] [host [port]]\n"
- "\t%s [-46ACMmVv] [-D title] [-o output] [-S tls_options]\n"
- "\t\t[-U useragent] [-w seconds] url ...\n", getprogname(),
- getprogname());
-
+ fprintf(stderr, "usage: "
+#ifndef SMALL
+ "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] "
+ "[-r seconds]\n"
+ " [-s srcaddr] [host [port]]\n"
+ " ftp [-C] [-o output] [-s srcaddr]\n"
+ " ftp://[user:password@]host[:port]/file[/] ...\n"
+ " ftp [-C] [-c cookie] [-o output] [-S ssl_options] "
+ "[-s srcaddr]\n"
+ " [-U useragent] [-w seconds] "
+ "http[s]://[user:password@]host[:port]/file ...\n"
+ " ftp [-C] [-o output] [-s srcaddr] file:file ...\n"
+ " ftp [-C] [-o output] [-s srcaddr] host:/file[/] ...\n"
+#else /* !SMALL */
+ "ftp [-o output] "
+ "ftp://[user:password@]host[:port]/file[/] ...\n"
+#ifndef NOSSL
+ " ftp [-o output] [-S ssl_options] [-w seconds] "
+ "http[s]://[user:password@]host[:port]/file ...\n"
+#else
+ " ftp [-o output] [-w seconds] http://host[:port]/file ...\n"
+#endif /* NOSSL */
+ " ftp [-o output] file:file ...\n"
+ " ftp [-o output] host:/file[/] ...\n"
+#endif /* !SMALL */
+ );
exit(1);
}
diff --git a/usr.bin/ftp/pathnames.h b/usr.bin/ftp/pathnames.h
new file mode 100644
index 00000000000..75244604a58
--- /dev/null
+++ b/usr.bin/ftp/pathnames.h
@@ -0,0 +1,37 @@
+/* $OpenBSD: pathnames.h,v 1.9 2019/05/16 12:44:18 florian 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/progressmeter.c b/usr.bin/ftp/progressmeter.c
deleted file mode 100644
index 15bbef752c3..00000000000
--- a/usr.bin/ftp/progressmeter.c
+++ /dev/null
@@ -1,366 +0,0 @@
-/* $OpenBSD: progressmeter.c,v 1.5 2019/05/15 13:42:40 florian Exp $ */
-
-/*
- * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
- * Copyright (c) 2003 Nils Nordman. 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.
- */
-
-#include <sys/types.h>
-#include <sys/ioctl.h>
-
-#include <err.h>
-#include <errno.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "ftp.h"
-
-#define DEFAULT_WINSIZE 80
-#define MAX_WINSIZE 512
-#define UPDATE_INTERVAL 1 /* update the progress meter every second */
-#define STALL_TIME 5 /* we're stalled after this many seconds */
-
-time_t monotime(void);
-
-/* formats and inserts the specified size into the given buffer */
-static void format_size(char *, int, off_t);
-static void format_rate(char *, int, off_t);
-
-/* window resizing */
-static void sig_winch(int);
-static void setscreensize(void);
-
-/* updates the progressmeter to reflect the current state of the transfer */
-void refresh_progress_meter(void);
-
-/* signal handler for updating the progress meter */
-static void update_progress_meter(int);
-
-static const char *title; /* short title for the start of progress bar */
-static time_t start; /* start progress */
-static time_t last_update; /* last progress update */
-static off_t start_pos; /* initial position of transfer */
-static off_t end_pos; /* ending position of transfer */
-static off_t cur_pos; /* transfer position as of last refresh */
-static off_t offset; /* initial offset from start_pos */
-static volatile off_t *counter; /* progress counter */
-static long stalled; /* how long we have been stalled */
-static int bytes_per_second; /* current speed in bytes per second */
-static int win_size; /* terminal window size */
-static volatile sig_atomic_t win_resized; /* for window resizing */
-static const char *filename; /* To be displayed in non-verbose mode */
-/* units for format_size */
-static const char unit[] = " KMGT";
-
-time_t
-monotime(void)
-{
- struct timespec ts;
-
- if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
- err(1, "monotime");
-
- return ts.tv_sec;
-}
-
-static void
-format_rate(char *buf, int size, off_t bytes)
-{
- int i;
-
- bytes *= 100;
- for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++)
- bytes = (bytes + 512) / 1024;
- if (i == 0) {
- i++;
- bytes = (bytes + 512) / 1024;
- }
- snprintf(buf, size, "%lld.%02lld %c%s",
- (long long) (bytes + 5) / 100,
- (long long) (bytes + 5) / 10 % 10,
- unit[i],
- "B");
-}
-
-static void
-format_size(char *buf, int size, off_t bytes)
-{
- int i;
-
- for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++)
- bytes = (bytes + 512) / 1024;
- snprintf(buf, size, "%4lld%c%s",
- (long long) bytes,
- unit[i],
- i ? "B" : " ");
-}
-
-void
-refresh_progress_meter(void)
-{
- char buf[MAX_WINSIZE + 1];
- const char *dot = "";
- time_t now;
- off_t transferred, bytes_left;
- double elapsed;
- int len, cur_speed, hours, minutes, seconds, barlength, i;
- int percent, overhead = 30;
-
- transferred = *counter - (cur_pos ? cur_pos : start_pos);
- cur_pos = *counter;
- now = monotime();
- bytes_left = end_pos - cur_pos;
-
- if (bytes_left > 0)
- elapsed = now - last_update;
- else {
- elapsed = now - start;
- /* Calculate true total speed when done */
- transferred = end_pos - start_pos;
- bytes_per_second = 0;
- }
-
- /* calculate speed */
- if (elapsed != 0)
- cur_speed = (transferred / elapsed);
- else
- cur_speed = transferred;
-
-#define AGE_FACTOR 0.9
- if (bytes_per_second != 0) {
- bytes_per_second = (bytes_per_second * AGE_FACTOR) +
- (cur_speed * (1.0 - AGE_FACTOR));
- } else
- bytes_per_second = cur_speed;
-
- buf[0] = '\0';
- /* title */
- if (!verbose && title != NULL) {
- len = strlen(title);
- if (len < 7)
- len = 7;
- else if (len > 12) {
- len = 12;
- dot = "...";
- overhead += 3;
- }
- snprintf(buf, sizeof buf, "\r%-*.*s%s ", len, len, title, dot);
- overhead += len + 1;
- } else
- snprintf(buf, sizeof buf, "\r");
-
- if (end_pos == 0 || cur_pos == end_pos)
- percent = 100;
- else
- percent = ((float)cur_pos / end_pos) * 100;
-
- /* filename and percent */
- if (!verbose && filename != NULL) {
- len = strlen(filename);
- if (len < 12)
- len = 12;
- else if (len > 25) {
- len = 22;
- dot = "...";
- overhead += 3;
- }
- snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
- "%-*.*s%s %3d%% ", len, len, filename, dot, percent);
- overhead += len + 1;
- } else
- snprintf(buf, sizeof buf, "\r%3d%% ", percent);
-
- /* bar */
- barlength = win_size - overhead;
- if (barlength > 0) {
- i = barlength * percent / 100;
- snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
- "|%.*s%*s| ", i,
- "*******************************************************"
- "*******************************************************"
- "*******************************************************"
- "*******************************************************"
- "*******************************************************"
- "*******************************************************"
- "*******************************************************",
- barlength - i, "");
-
- }
-
- /* amount transferred */
- format_size(buf + strlen(buf), win_size - strlen(buf), cur_pos);
- strlcat(buf, " ", win_size);
-
- /* ETA */
- if (!transferred)
- stalled += elapsed;
- else
- stalled = 0;
-
- if (stalled >= STALL_TIME)
- strlcat(buf, "- stalled -", win_size);
- else if (bytes_per_second == 0 && bytes_left)
- strlcat(buf, " --:-- ETA", win_size);
- else {
- if (bytes_left > 0)
- seconds = bytes_left / bytes_per_second;
- else
- seconds = elapsed;
-
- hours = seconds / 3600;
- seconds -= hours * 3600;
- minutes = seconds / 60;
- seconds -= minutes * 60;
-
- if (hours != 0)
- snprintf(buf + strlen(buf), win_size - strlen(buf),
- "%d:%02d:%02d", hours, minutes, seconds);
- else
- snprintf(buf + strlen(buf), win_size - strlen(buf),
- " %02d:%02d", minutes, seconds);
-
- if (bytes_left > 0)
- strlcat(buf, " ETA", win_size);
- else
- strlcat(buf, " ", win_size);
- }
-
- fwrite(buf, strlen(buf), 1, stderr);
- last_update = now;
-}
-
-static void
-update_progress_meter(int ignore)
-{
- int save_errno;
-
- save_errno = errno;
-
- if (win_resized) {
- setscreensize();
- win_resized = 0;
- }
-
- refresh_progress_meter();
-
- signal(SIGALRM, update_progress_meter);
- alarm(UPDATE_INTERVAL);
- errno = save_errno;
-}
-
-void
-init_stats(off_t filesize, off_t *ctr)
-{
- start = last_update = monotime();
- start_pos = *ctr;
- offset = *ctr;
- cur_pos = 0;
- end_pos = 0;
- counter = ctr;
- stalled = 0;
- bytes_per_second = 0;
-
- if (filesize > 0)
- end_pos = filesize;
-}
-
-void
-start_progress_meter(const char *fn, const char *t)
-{
- filename = fn;
- title = t;
-
- /*
- * Suppress progressmeter if filesize isn't known when
- * Content-Length header has bogus values.
- */
-
- if (end_pos == 0)
- return;
-
- setscreensize();
- refresh_progress_meter();
-
- signal(SIGALRM, update_progress_meter);
- signal(SIGWINCH, sig_winch);
- alarm(UPDATE_INTERVAL);
-}
-
-void
-stop_progress_meter(void)
-{
- alarm(0);
-
- /* Ensure we complete the progress */
- if (end_pos && cur_pos != end_pos)
- refresh_progress_meter();
-
- if (end_pos)
- fprintf(stderr, "\n");
-}
-
-void
-finish_stats(void)
-{
- char rate_str[32];
- double elapsed;
-
- if (!verbose)
- return;
-
- elapsed = monotime() - start;
-
- if (elapsed != 0)
- bytes_per_second = *counter / elapsed;
- else
- bytes_per_second = *counter;
-
- format_rate(rate_str, sizeof rate_str, bytes_per_second);
- log_info("%lld byte%s received in %.2f seconds (%s/s)\n",
- *counter, *counter != 1 ? "s" : "", elapsed, rate_str);
-}
-
-static void
-sig_winch(int sig)
-{
- win_resized = 1;
-}
-
-static void
-setscreensize(void)
-{
- struct winsize winsize;
-
- if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 &&
- winsize.ws_col != 0) {
- if (winsize.ws_col > MAX_WINSIZE)
- win_size = MAX_WINSIZE;
- else
- win_size = winsize.ws_col;
- } else
- win_size = DEFAULT_WINSIZE;
- win_size += 1; /* trailing \0 */
-}
diff --git a/usr.bin/ftp/ruserpass.c b/usr.bin/ftp/ruserpass.c
new file mode 100644
index 00000000000..469d1434469
--- /dev/null
+++ b/usr.bin/ftp/ruserpass.c
@@ -0,0 +1,317 @@
+/* $OpenBSD: ruserpass.c,v 1.32 2019/05/16 12:44:18 florian 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 00000000000..5e652b3ed4d
--- /dev/null
+++ b/usr.bin/ftp/small.c
@@ -0,0 +1,730 @@
+/* $OpenBSD: small.c,v 1.11 2019/05/16 12:44:18 florian 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;
+#ifndef SMALL
+freegetit:
+#endif
+ 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 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 ch, 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 00000000000..6dc289ffdaa
--- /dev/null
+++ b/usr.bin/ftp/small.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: small.h,v 1.3 2019/05/16 12:44:18 florian 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 00000000000..00eb711441c
--- /dev/null
+++ b/usr.bin/ftp/stringlist.c
@@ -0,0 +1,97 @@
+/* $OpenBSD: stringlist.c,v 1.14 2019/05/16 12:44:18 florian 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 00000000000..bb2eaf5ee19
--- /dev/null
+++ b/usr.bin/ftp/stringlist.h
@@ -0,0 +1,56 @@
+/* $OpenBSD: stringlist.h,v 1.8 2019/05/16 12:44:18 florian 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/url.c b/usr.bin/ftp/url.c
deleted file mode 100644
index 86bbb2b590f..00000000000
--- a/usr.bin/ftp/url.c
+++ /dev/null
@@ -1,419 +0,0 @@
-/* $OpenBSD: url.c,v 1.2 2019/05/12 20:58:19 jasper Exp $ */
-
-/*
- * Copyright (c) 2017 Sunil Nimmagadda <sunil@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.
- */
-
-/*-
- * 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.
- */
-#include <sys/types.h>
-
-#include <netinet/in.h>
-#include <resolv.h>
-
-#include <ctype.h>
-#include <err.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-
-#include "ftp.h"
-#include "xmalloc.h"
-
-#define BASICAUTH_LEN 1024
-
-static void authority_parse(const char *, char **, char **, char **);
-static int ipv6_parse(const char *, char **, char **);
-static int unsafe_char(const char *);
-
-#ifndef NOSSL
-const char *scheme_str[] = { "http:", "ftp:", "file:", "https:" };
-const char *port_str[] = { "80", "21", NULL, "443" };
-#else
-const char *scheme_str[] = { "http:", "ftp:", "file:" };
-const char *port_str[] = { "80", "21", NULL };
-#endif
-
-int
-scheme_lookup(const char *str)
-{
- size_t i;
-
- for (i = 0; i < nitems(scheme_str); i++)
- if (strncasecmp(str, scheme_str[i], strlen(scheme_str[i])) == 0)
- return i;
-
- return -1;
-}
-
-static int
-ipv6_parse(const char *str, char **host, char **port)
-{
- char *p;
-
- if ((p = strchr(str, ']')) == NULL) {
- warnx("%s: invalid IPv6 address: %s", __func__, str);
- return 1;
- }
-
- *p++ = '\0';
- if (strlen(str + 1) > 0)
- *host = xstrdup(str + 1);
-
- if (*p == '\0')
- return 0;
-
- if (*p++ != ':') {
- warnx("%s: invalid port: %s", __func__, p);
- free(*host);
- return 1;
- }
-
- if (strlen(p) > 0)
- *port = xstrdup(p);
-
- return 0;
-}
-
-static void
-authority_parse(const char *str, char **host, char **port, char **basic_auth)
-{
- char *p;
-
- if ((p = strchr(str, '@')) != NULL) {
- *basic_auth = xcalloc(1, BASICAUTH_LEN);
- if (b64_ntop((unsigned char *)str, p - str,
- *basic_auth, BASICAUTH_LEN) == -1)
- errx(1, "base64 encode failed");
-
- str = ++p;
- }
-
- if ((p = strchr(str, ':')) != NULL) {
- *p++ = '\0';
- if (strlen(p) > 0)
- *port = xstrdup(p);
- }
-
- if (strlen(str) > 0)
- *host = xstrdup(str);
-}
-
-struct url *
-url_parse(const char *str)
-{
- struct url *url;
- const char *p, *q;
- char *basic_auth, *host, *port, *path, *s;
- size_t len;
- int ipliteral, scheme;
-
- p = str;
- ipliteral = 0;
- host = port = path = basic_auth = NULL;
- while (isblank((unsigned char)*p))
- p++;
-
- if ((q = strchr(p, ':')) == NULL) {
- warnx("%s: scheme missing: %s", __func__, str);
- return NULL;
- }
-
- if ((scheme = scheme_lookup(p)) == -1) {
- warnx("%s: invalid scheme: %s", __func__, p);
- return NULL;
- }
-
- p = ++q;
- if (strncmp(p, "//", 2) != 0) {
- if (scheme == S_FILE)
- goto done;
- else {
- warnx("%s: invalid url: %s", __func__, str);
- return NULL;
- }
- }
-
- p += 2;
- len = strlen(p);
- /* Authority terminated by a '/' if present */
- if ((q = strchr(p, '/')) != NULL)
- len = q - p;
-
- s = xstrndup(p, len);
- if (*p == '[') {
- if (ipv6_parse(s, &host, &port) != 0) {
- free(s);
- return NULL;
- }
- ipliteral = 1;
- } else
- authority_parse(s, &host, &port, &basic_auth);
-
- free(s);
- if (port == NULL && scheme != S_FILE)
- port = xstrdup(port_str[scheme]);
-
- done:
- if (q != NULL)
- path = xstrdup(q);
-
- if (io_debug) {
- fprintf(stderr,
- "scheme: %s\nhost: %s\nport: %s\npath: %s\n",
- scheme_str[scheme], host, port, path);
- }
-
- url = xcalloc(1, sizeof *url);
- url->scheme = scheme;
- url->host = host;
- url->port = port;
- url->path = path;
- url->basic_auth = basic_auth;
- url->ipliteral = ipliteral;
- return url;
-}
-
-void
-url_free(struct url *url)
-{
- if (url == NULL)
- return;
-
- free(url->host);
- free(url->port);
- free(url->path);
- freezero(url->basic_auth, BASICAUTH_LEN);
- free(url->fname);
- free(url);
-}
-
-void
-url_connect(struct url *url, struct url *proxy, int timeout)
-{
- switch (url->scheme) {
- case S_HTTP:
- case S_HTTPS:
- http_connect(url, proxy, timeout);
- break;
- case S_FTP:
- ftp_connect(url, proxy, timeout);
- break;
- }
-}
-
-struct url *
-url_request(struct url *url, struct url *proxy, off_t *offset, off_t *sz)
-{
- switch (url->scheme) {
- case S_HTTP:
- case S_HTTPS:
- return http_get(url, proxy, offset, sz);
- case S_FTP:
- return ftp_get(url, proxy, offset, sz);
- case S_FILE:
- return file_request(&child_ibuf, url, offset, sz);
- }
-
- return NULL;
-}
-
-void
-url_save(struct url *url, FILE *dst_fp, off_t *offset)
-{
- switch (url->scheme) {
- case S_HTTP:
- case S_HTTPS:
- http_save(url, dst_fp, offset);
- break;
- case S_FTP:
- ftp_save(url, dst_fp, offset);
- break;
- case S_FILE:
- file_save(url, dst_fp, offset);
- break;
- }
-}
-
-void
-url_close(struct url *url)
-{
- switch (url->scheme) {
- case S_HTTP:
- case S_HTTPS:
- http_close(url);
- break;
- case S_FTP:
- ftp_quit(url);
- break;
- }
-}
-
-char *
-url_str(struct url *url)
-{
- char *host, *str;
- int custom_port;
-
- custom_port = strcmp(url->port, port_str[url->scheme]) ? 1 : 0;
- if (url->ipliteral)
- xasprintf(&host, "[%s]", url->host);
- else
- host = xstrdup(url->host);
-
- xasprintf(&str, "%s//%s%s%s%s",
- scheme_str[url->scheme],
- host,
- custom_port ? ":" : "",
- custom_port ? url->port : "",
- url->path ? url->path : "/");
-
- free(host);
- return str;
-}
-
-/*
- * Encode given URL, per RFC1738.
- * Allocate and return string to the caller.
- */
-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 = xmalloc(new_length + 1); /* One more for '\0'. */
-
- /*
- * 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;
-}
-
-/*
- * 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))));
-}
-
-void
-log_request(const char *prefix, struct url *url, struct url *proxy)
-{
- char *host;
- int custom_port;
-
- if (url->scheme == S_FILE)
- return;
-
- custom_port = strcmp(url->port, port_str[url->scheme]) ? 1 : 0;
- if (url->ipliteral)
- xasprintf(&host, "[%s]", url->host);
- else
- host = xstrdup(url->host);
-
- if (proxy)
- log_info("%s %s//%s%s%s%s"
- " (via %s//%s%s%s)\n",
- prefix,
- scheme_str[url->scheme],
- host,
- custom_port ? ":" : "",
- custom_port ? url->port : "",
- url->path ? url->path : "",
-
- /* via proxy part */
- (proxy->scheme == S_HTTP) ? "http" : "https",
- proxy->host,
- proxy->port ? ":" : "",
- proxy->port ? proxy->port : "");
- else
- log_info("%s %s//%s%s%s%s\n",
- prefix,
- scheme_str[url->scheme],
- host,
- custom_port ? ":" : "",
- custom_port ? url->port : "",
- url->path ? url->path : "");
-
- free(host);
-}
diff --git a/usr.bin/ftp/util.c b/usr.bin/ftp/util.c
index dba5943a5b1..bde12df9901 100644
--- a/usr.bin/ftp/util.c
+++ b/usr.bin/ftp/util.c
@@ -1,229 +1,1099 @@
-/* $OpenBSD: util.c,v 1.88 2019/05/12 20:58:19 jasper Exp $ */
+/* $OpenBSD: util.c,v 1.89 2019/05/16 12:44:18 florian 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) 2015 Sunil Nimmagadda <sunil@openbsd.org>
+ * Copyright (c) 1985, 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
*
- * 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.
+ * 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.
*
- * 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.
+ * 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/queue.h>
+/*
+ * FTP User Program -- Misc support routines
+ */
+#include <sys/ioctl.h>
#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/ftp.h>
+#include <ctype.h>
#include <err.h>
#include <errno.h>
-#include <imsg.h>
-#include <netdb.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <glob.h>
#include <poll.h>
+#include <pwd.h>
#include <signal.h>
-#include <stdarg.h>
#include <stdio.h>
-#include <string.h>
#include <stdlib.h>
+#include <string.h>
+#include <time.h>
#include <unistd.h>
-#include "ftp.h"
-#include "xmalloc.h"
+#include "ftp_var.h"
+#include "pathnames.h"
-static void tooslow(int);
+#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+
+static void updateprogressmeter(int);
/*
- * Wait for an asynchronous connect(2) attempt to finish.
+ * Connect to peer server and
+ * auto-login, if possible.
*/
-int
-connect_wait(int s)
+void
+setpeer(int argc, char *argv[])
{
- struct pollfd pfd[1];
- int error = 0;
- socklen_t len = sizeof(error);
+ char *host, *port;
- pfd[0].fd = s;
- pfd[0].events = POLLOUT;
-
- if (poll(pfd, 1, -1) == -1)
- return -1;
- if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
- return -1;
- if (error != 0) {
- errno = error;
- return -1;
+ if (connected) {
+ fprintf(ttyout, "Already connected to %s, use close first.\n",
+ hostname);
+ code = -1;
+ return;
}
- return 0;
-}
+#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];
-static void
-tooslow(int signo)
-{
- dprintf(STDERR_FILENO, "%s: connect taking too long\n", getprogname());
- _exit(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
-tcp_connect(const char *host, const char *port, int timeout)
+ftp_login(const char *host, char *user, char *pass)
{
- struct addrinfo hints, *res, *res0;
- char hbuf[NI_MAXHOST];
- const char *cause = NULL;
- int error, s = -1, save_errno;
+ 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;
- if (host == NULL) {
- warnx("hostname missing");
- return -1;
+#ifndef SMALL
+ if (user == NULL && !anonftp) {
+ if (ruserpass(host, &user, &pass, &acctname) < 0) {
+ code = -1;
+ return (0);
+ }
}
+#endif /* !SMALL */
- memset(&hints, 0, sizeof hints);
- hints.ai_family = family;
- hints.ai_socktype = SOCK_STREAM;
- if ((error = getaddrinfo(host, port, &hints, &res0))) {
- warnx("%s: %s", host, gai_strerror(error));
- return -1;
- }
+ /*
+ * 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));
- if (timeout) {
- (void)signal(SIGALRM, tooslow);
- alarm(timeout);
+ /*
+ * 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 */
}
- for (res = res0; res; res = res->ai_next) {
- if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
- sizeof hbuf, NULL, 0, NI_NUMERICHOST) != 0)
- (void)strlcpy(hbuf, "(unknown)", sizeof hbuf);
+tryagain:
+ if (retry)
+ user = "ftp"; /* some servers only allow "ftp" */
- log_info("Trying %s...\n", hbuf);
- s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
- if (s == -1) {
- cause = "socket";
- continue;
+ 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);
+}
- for (error = connect(s, res->ai_addr, res->ai_addrlen);
- error != 0 && errno == EINTR; error = connect_wait(s))
- continue;
+/*
+ * `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 (error != 0) {
- cause = "connect";
- save_errno = errno;
- close(s);
- errno = save_errno;
- s = -1;
- continue;
- }
+ 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 */
- break;
+/*
+ * 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;
- freeaddrinfo(res0);
- if (s == -1) {
- warn("%s", cause);
- return -1;
+ 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);
+ }
}
+#ifndef SMALL
+again:
+#endif
+ if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
+ (void)fclose(*ftemp);
+ *ftemp = NULL;
+ return (NULL);
+ }
+
+ buf[strcspn(buf, "\n")] = '\0';
+ bufp = buf;
- if (timeout) {
- signal(SIGALRM, SIG_DFL);
- alarm(0);
+#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 s;
+ 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
-fd_request(char *path, int flags, off_t *offset)
+confirm(const char *cmd, const char *file)
{
- struct imsg imsg;
- off_t *poffset;
- int fd, save_errno;
+ char str[BUFSIZ];
- send_message(&child_ibuf, IMSG_OPEN, flags, path, strlen(path) + 1, -1);
- if (read_message(&child_ibuf, &imsg) == 0)
- return -1;
+ 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 (imsg.hdr.type != IMSG_OPEN)
- errx(1, "%s: IMSG_OPEN expected", __func__);
+ if (!doglob)
+ return (1);
- fd = imsg.fd;
- if (offset) {
- poffset = imsg.data;
- *offset = *poffset;
+ 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);
+}
- save_errno = imsg.hdr.peerid;
- imsg_free(&imsg);
- errno = save_errno;
- return fd;
+/*
+ * 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 = strtoll(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);
}
-void
-send_message(struct imsgbuf *ibuf, int type, uint32_t peerid,
- void *msg, size_t msglen, int fd)
+/*
+ * determine last modification time (in GMT) of remote file
+ */
+time_t
+remotemodtime(const char *file, int noisy)
{
- if (imsg_compose(ibuf, type, peerid, 0, fd, msg, msglen) != 1)
- err(1, "imsg_compose");
+ int overbose;
+ time_t rtime;
+ int ocode;
- if (imsg_flush(ibuf) != 0)
- err(1, "imsg_flush");
+ 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
-read_message(struct imsgbuf *ibuf, struct imsg *imsg)
+fileindir(const char *file, const char *dir)
{
- int n;
+ char parentdirbuf[PATH_MAX], *parentdir;
+ char realdir[PATH_MAX];
+ size_t dirlen;
- if ((n = imsg_read(ibuf)) == -1)
- err(1, "%s: imsg_read", __func__);
- if (n == 0)
- return 0;
+ /* determine parent directory of file */
+ (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
+ parentdir = dirname(parentdirbuf);
+ if (strcmp(parentdir, ".") == 0)
+ return 1; /* current directory is ok */
- if ((n = imsg_get(ibuf, imsg)) == -1)
- err(1, "%s: imsg_get", __func__);
- if (n == 0)
+ /* 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;
- return n;
+ 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 timespec start;
+
+char *action;
+
void
-log_info(const char *fmt, ...)
+progressmeter(int flag, const char *filename)
{
- va_list ap;
+ /*
+ * List of order of magnitude prefixes.
+ * The last is `P', as 2^64 = 16384 Petabytes
+ */
+ static const char prefixes[] = " KMGTP";
- if (verbose == 0)
+ static struct timespec lastupdate;
+ static off_t lastsize;
+ static char *title = NULL;
+ struct timespec now, td, wait;
+ off_t cursize, abbrevsize;
+ double elapsed;
+ int ratio, barlength, i, remaining, overhead = 30;
+ char buf[512];
+
+ if (flag == -1) {
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ lastupdate = start;
+ lastsize = restart_point;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (!progress || filesize < 0)
return;
+ cursize = bytes + restart_point;
- va_start(ap, fmt);
- vfprintf(msgout, fmt, ap);
- va_end(ap);
+ 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) {
+ free(title);
+ 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');
+
+ timespecsub(&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_nsec += wait.tv_nsec;
+ }
+ wait.tv_sec = 0;
+ }
+
+ timespecsub(&now, &start, &td);
+ elapsed = td.tv_sec + (td.tv_nsec / 1000000000.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
-copy_file(FILE *dst, FILE *src, off_t *offset)
+ptransfer(int siginfo)
{
- char *tmp_buf;
- size_t r;
+ struct timespec now, td;
+ double elapsed;
+ off_t bs;
+ int meg, remaining, hh;
+ char buf[100];
+
+ if (!verbose && !siginfo)
+ return;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, &start, &td);
+ elapsed = td.tv_sec + (td.tv_nsec / 1000000000.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");
- tmp_buf = xmalloc(TMPBUF_LEN);
- while ((r = fread(tmp_buf, 1, TMPBUF_LEN, src)) != 0 && !interrupted) {
- *offset += r;
- if (fwrite(tmp_buf, 1, r, dst) != r)
- err(1, "%s: fwrite", __func__);
+ 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));
+}
- if (interrupted) {
- free(tmp_buf);
- return;
+/*
+ * 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 */
+
+/*
+ * Wait for an asynchronous connect(2) attempt to finish.
+ */
+int
+connect_wait(int s)
+{
+ struct pollfd pfd[1];
+ int error = 0;
+ socklen_t len = sizeof(error);
- if (!feof(src))
- errx(1, "%s: fread", __func__);
+ pfd[0].fd = s;
+ pfd[0].events = POLLOUT;
- free(tmp_buf);
+ if (poll(pfd, 1, -1) == -1)
+ return -1;
+ if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+ return -1;
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+ return 0;
}
diff --git a/usr.bin/ftp/xmalloc.c b/usr.bin/ftp/xmalloc.c
deleted file mode 100644
index c934355ce96..00000000000
--- a/usr.bin/ftp/xmalloc.c
+++ /dev/null
@@ -1,147 +0,0 @@
-/* $OpenBSD: xmalloc.c,v 1.1 2019/05/12 20:44:39 kmos Exp $ */
-
-/*
- * Author: Tatu Ylonen <ylo@cs.hut.fi>
- * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
- * All rights reserved
- * Versions of malloc and friends that check their results, and never return
- * failure (they call fatalx if they encounter an error).
- *
- * As far as I am concerned, the code I have written for this software
- * can be used freely for any purpose. Any derived versions of this
- * software must be clearly marked as such, and if the derived work is
- * incompatible with the protocol description in the RFC file, it must be
- * called by a name other than "ssh" or "Secure Shell".
- */
-
-#include <err.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdarg.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "xmalloc.h"
-
-void *
-xmalloc(size_t size)
-{
- void *ptr;
-
- if (size == 0)
- errx(1, "xmalloc: zero size");
- ptr = malloc(size);
- if (ptr == NULL)
- err(1, "xmalloc: allocating %zu bytes", size);
- return ptr;
-}
-
-void *
-xcalloc(size_t nmemb, size_t size)
-{
- void *ptr;
-
- if (size == 0 || nmemb == 0)
- errx(1, "xcalloc: zero size");
- ptr = calloc(nmemb, size);
- if (ptr == NULL)
- err(1, "xcalloc: allocating %zu * %zu bytes", nmemb, size);
- return ptr;
-}
-
-void *
-xrealloc(void *ptr, size_t size)
-{
- return xreallocarray(ptr, 1, size);
-}
-
-void *
-xreallocarray(void *ptr, size_t nmemb, size_t size)
-{
- void *new_ptr;
-
- if (nmemb == 0 || size == 0)
- errx(1, "xreallocarray: zero size");
- new_ptr = reallocarray(ptr, nmemb, size);
- if (new_ptr == NULL)
- err(1, "xreallocarray: allocating %zu * %zu bytes",
- nmemb, size);
- return new_ptr;
-}
-
-char *
-xstrdup(const char *str)
-{
- char *cp;
-
- if ((cp = strdup(str)) == NULL)
- err(1, "xstrdup");
- return cp;
-}
-
-char *
-xstrndup(const char *str, size_t maxlen)
-{
- char *cp;
-
- if ((cp = strndup(str, maxlen)) == NULL)
- err(1, "xstrndup");
- return cp;
-}
-
-int
-xasprintf(char **ret, const char *fmt, ...)
-{
- va_list ap;
- int i;
-
- va_start(ap, fmt);
- i = xvasprintf(ret, fmt, ap);
- va_end(ap);
-
- return i;
-}
-
-int
-xvasprintf(char **ret, const char *fmt, va_list ap)
-{
- int i;
-
- i = vasprintf(ret, fmt, ap);
-
- if (i < 0 || *ret == NULL)
- err(1, "xasprintf");
-
- return i;
-}
-
-int
-xsnprintf(char *str, size_t len, const char *fmt, ...)
-{
- va_list ap;
- int i;
-
- va_start(ap, fmt);
- i = xvsnprintf(str, len, fmt, ap);
- va_end(ap);
-
- return i;
-}
-
-int
-xvsnprintf(char *str, size_t len, const char *fmt, va_list ap)
-{
- int i;
-
- if (len > INT_MAX)
- errx(1, "xsnprintf: len > INT_MAX");
-
- i = vsnprintf(str, len, fmt, ap);
-
- if (i < 0 || i >= (int)len)
- errx(1, "xsnprintf: overflow");
-
- return i;
-}
diff --git a/usr.bin/ftp/xmalloc.h b/usr.bin/ftp/xmalloc.h
deleted file mode 100644
index 8b6c2c0452e..00000000000
--- a/usr.bin/ftp/xmalloc.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* $OpenBSD: xmalloc.h,v 1.1 2019/05/12 20:44:39 kmos Exp $ */
-
-/*
- * Author: Tatu Ylonen <ylo@cs.hut.fi>
- * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
- * All rights reserved
- * Created: Mon Mar 20 22:09:17 1995 ylo
- *
- * Versions of malloc and friends that check their results, and never return
- * failure (they call fatal if they encounter an error).
- *
- * As far as I am concerned, the code I have written for this software
- * can be used freely for any purpose. Any derived versions of this
- * software must be clearly marked as such, and if the derived work is
- * incompatible with the protocol description in the RFC file, it must be
- * called by a name other than "ssh" or "Secure Shell".
- */
-
-#ifndef XMALLOC_H
-#define XMALLOC_H
-
-void *xmalloc(size_t);
-void *xcalloc(size_t, size_t);
-void *xrealloc(void *, size_t);
-void *xreallocarray(void *, size_t, size_t);
-char *xstrdup(const char *);
-char *xstrndup(const char *, size_t);
-int xasprintf(char **, const char *, ...)
- __attribute__((__format__ (printf, 2, 3)))
- __attribute__((__nonnull__ (2)));
-int xvasprintf(char **, const char *, va_list)
- __attribute__((__nonnull__ (2)));
-int xsnprintf(char *, size_t, const char *, ...)
- __attribute__((__format__ (printf, 3, 4)))
- __attribute__((__nonnull__ (3)))
- __attribute__((__bounded__ (__string__, 1, 2)));
-int xvsnprintf(char *, size_t, const char *, va_list)
- __attribute__((__nonnull__ (3)))
- __attribute__((__bounded__ (__string__, 1, 2)));
-
-#endif /* XMALLOC_H */