summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorflorian <florian@openbsd.org>2019-05-16 12:44:17 +0000
committerflorian <florian@openbsd.org>2019-05-16 12:44:17 +0000
commitbfd817adb3f919c43054683845609a478bb1ddbf (patch)
treece73a2f9e0a297c948bfd9be33d57977ab1d0302
parentMove a variable declaration to the block where it's used to make things (diff)
downloadwireguard-openbsd-bfd817adb3f919c43054683845609a478bb1ddbf.tar.xz
wireguard-openbsd-bfd817adb3f919c43054683845609a478bb1ddbf.zip
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.
-rw-r--r--distrib/special/ftp-ssl/Makefile4
-rw-r--r--distrib/special/ftp/Makefile4
-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
31 files changed, 11524 insertions, 3514 deletions
diff --git a/distrib/special/ftp-ssl/Makefile b/distrib/special/ftp-ssl/Makefile
index f6fc49dad57..0fa3a1f5c8b 100644
--- a/distrib/special/ftp-ssl/Makefile
+++ b/distrib/special/ftp-ssl/Makefile
@@ -1,9 +1,9 @@
-# $OpenBSD: Makefile,v 1.2 2019/05/12 20:44:39 kmos Exp $
+# $OpenBSD: Makefile,v 1.3 2019/05/16 12:44:17 florian Exp $
CFLAGS+=-DSMALL
PROG= ftp
-SRCS= file.c ftp.c http.c main.c progressmeter.c url.c util.c xmalloc.c
+SRCS= fetch.c ftp.c main.c small.c util.c cookie.c
.PATH: ${.CURDIR}/../../../usr.bin/ftp
LDADD+= -lutil -ltls -lssl -lcrypto
diff --git a/distrib/special/ftp/Makefile b/distrib/special/ftp/Makefile
index ada7ee7f2d8..c394dfc815d 100644
--- a/distrib/special/ftp/Makefile
+++ b/distrib/special/ftp/Makefile
@@ -1,9 +1,9 @@
-# $OpenBSD: Makefile,v 1.13 2019/05/12 20:44:39 kmos Exp $
+# $OpenBSD: Makefile,v 1.14 2019/05/16 12:44:17 florian Exp $
CFLAGS+=-DSMALL -DNOSSL
PROG= ftp
-SRCS= file.c ftp.c http.c main.c progressmeter.c url.c util.c xmalloc.c
+SRCS= fetch.c ftp.c main.c small.c util.c
.PATH: ${.CURDIR}/../../../usr.bin/ftp
LDADD+= -lutil
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 */