diff options
author | Florian Obser <florian@cvs.openbsd.org> | 2019-05-16 12:44:19 +0000 |
---|---|---|
committer | Florian Obser <florian@cvs.openbsd.org> | 2019-05-16 12:44:19 +0000 |
commit | 3d0c792004dccce0a87f3c922e4c515acfc40b7b (patch) | |
tree | 626a028995b669e709e46bf56f0b50bd64f29b8d /usr.bin | |
parent | d1ff65b97cc0d5c82b3107680b0e59be2be0cabd (diff) |
Revert suni'ls ftp rewrite for now.
We are juggling too many things at the moment and we can't deal with
the differences in behaviour right now.
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/ftp/Makefile | 9 | ||||
-rw-r--r-- | usr.bin/ftp/cmd.c | 643 | ||||
-rw-r--r-- | usr.bin/ftp/cmds.c | 1688 | ||||
-rw-r--r-- | usr.bin/ftp/cmds.h | 83 | ||||
-rw-r--r-- | usr.bin/ftp/cmdtab.c | 215 | ||||
-rw-r--r-- | usr.bin/ftp/complete.c | 381 | ||||
-rw-r--r-- | usr.bin/ftp/cookie.c | 231 | ||||
-rw-r--r-- | usr.bin/ftp/domacro.c | 149 | ||||
-rw-r--r-- | usr.bin/ftp/extern.h | 150 | ||||
-rw-r--r-- | usr.bin/ftp/fetch.c | 1668 | ||||
-rw-r--r-- | usr.bin/ftp/file.c | 57 | ||||
-rw-r--r-- | usr.bin/ftp/ftp.1 | 1632 | ||||
-rw-r--r-- | usr.bin/ftp/ftp.c | 2302 | ||||
-rw-r--r-- | usr.bin/ftp/ftp.h | 120 | ||||
-rw-r--r-- | usr.bin/ftp/ftp_var.h | 231 | ||||
-rw-r--r-- | usr.bin/ftp/http.c | 801 | ||||
-rw-r--r-- | usr.bin/ftp/list.c | 86 | ||||
-rw-r--r-- | usr.bin/ftp/main.c | 1179 | ||||
-rw-r--r-- | usr.bin/ftp/pathnames.h | 37 | ||||
-rw-r--r-- | usr.bin/ftp/progressmeter.c | 366 | ||||
-rw-r--r-- | usr.bin/ftp/ruserpass.c | 317 | ||||
-rw-r--r-- | usr.bin/ftp/small.c | 730 | ||||
-rw-r--r-- | usr.bin/ftp/small.h | 35 | ||||
-rw-r--r-- | usr.bin/ftp/stringlist.c | 97 | ||||
-rw-r--r-- | usr.bin/ftp/stringlist.h | 56 | ||||
-rw-r--r-- | usr.bin/ftp/url.c | 419 | ||||
-rw-r--r-- | usr.bin/ftp/util.c | 1160 | ||||
-rw-r--r-- | usr.bin/ftp/xmalloc.c | 147 | ||||
-rw-r--r-- | usr.bin/ftp/xmalloc.h | 41 |
29 files changed, 11520 insertions, 3510 deletions
diff --git a/usr.bin/ftp/Makefile b/usr.bin/ftp/Makefile index 4d6ccd73649..4746b1e850e 100644 --- a/usr.bin/ftp/Makefile +++ b/usr.bin/ftp/Makefile @@ -1,12 +1,15 @@ -# $OpenBSD: Makefile,v 1.32 2019/05/12 20:58:19 jasper Exp $ +# $OpenBSD: Makefile,v 1.33 2019/05/16 12:44:17 florian Exp $ -# Define SMALL to disable command line editing +# Define SMALL to disable command line editing and https support #CFLAGS+=-DSMALL PROG= ftp -SRCS= cmd.c file.c ftp.c http.c main.c progressmeter.c url.c util.c xmalloc.c +SRCS= cmds.c cmdtab.c complete.c cookie.c domacro.c fetch.c ftp.c \ + list.c main.c ruserpass.c small.c stringlist.c util.c LDADD+= -ledit -lcurses -lutil -ltls -lssl -lcrypto DPADD+= ${LIBEDIT} ${LIBCURSES} ${LIBUTIL} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} +#COPTS+= -Wall -Wconversion -Wstrict-prototypes -Wmissing-prototypes + .include <bsd.prog.mk> diff --git a/usr.bin/ftp/cmd.c b/usr.bin/ftp/cmd.c deleted file mode 100644 index 544643b9ca5..00000000000 --- a/usr.bin/ftp/cmd.c +++ /dev/null @@ -1,643 +0,0 @@ -/* $OpenBSD: cmd.c,v 1.3 2019/05/15 13:42:40 florian Exp $ */ - -/* - * Copyright (c) 2018 Sunil Nimmagadda <sunil@openbsd.org> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sys/socket.h> -#include <sys/stat.h> - -#include <arpa/telnet.h> - -#include <err.h> -#include <errno.h> -#include <histedit.h> -#include <libgen.h> -#include <limits.h> -#include <pwd.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "ftp.h" - -#define ARGVMAX 64 - -static void cmd_interrupt(int); -static int cmd_lookup(const char *); -static FILE *data_fopen(const char *); -static void do_open(int, char **); -static void do_help(int, char **); -static void do_quit(int, char **); -static void do_ls(int, char **); -static void do_pwd(int, char **); -static void do_cd(int, char **); -static void do_get(int, char **); -static void do_passive(int, char **); -static void do_lcd(int, char **); -static void do_lpwd(int, char **); -static void do_put(int, char **); -static void do_mget(int, char **); -static void ftp_abort(void); -static char *prompt(void); - -static FILE *ctrl_fp, *data_fp; - -static struct { - const char *name; - const char *info; - void (*cmd)(int, char **); - int conn_required; -} cmd_tbl[] = { - { "open", "connect to remote ftp server", do_open, 0 }, - { "close", "terminate ftp session", do_quit, 1 }, - { "help", "print local help information", do_help, 0 }, - { "?", "print local help information", do_help, 0 }, - { "quit", "terminate ftp session and exit", do_quit, 0 }, - { "exit", "terminate ftp session and exit", do_quit, 0 }, - { "ls", "list contents of remote directory", do_ls, 1 }, - { "pwd", "print working directory on remote machine", do_pwd, 1 }, - { "cd", "change remote working directory", do_cd, 1 }, - { "nlist", "nlist contents of remote directory", do_ls, 1 }, - { "get", "receive file", do_get, 1 }, - { "passive", "toggle passive transfer mode", do_passive, 0 }, - { "lcd", "change local working directory", do_lcd, 0 }, - { "lpwd", "print local working directory", do_lpwd, 0 }, - { "put", "send one file", do_put, 1 }, - { "mget", "get multiple files", do_mget, 1 }, - { "mput", "send multiple files", do_mget, 1 }, -}; - -static void -cmd_interrupt(int signo) -{ - const char msg[] = "\rwaiting for remote to finish abort\n"; - int save_errno = errno; - - if (data_fp != NULL) - (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); - - interrupted = 1; - errno = save_errno; -} - -void -cmd(const char *host, const char *port, const char *path) -{ - HistEvent hev; - EditLine *el; - History *hist; - const char *line; - char **ap, *argv[ARGVMAX], *cp; - int count, i; - - if ((el = el_init(getprogname(), stdin, stdout, stderr)) == NULL) - err(1, "couldn't initialise editline"); - - if ((hist = history_init()) == NULL) - err(1, "couldn't initialise editline history"); - - history(hist, &hev, H_SETSIZE, 100); - el_set(el, EL_HIST, history, hist); - el_set(el, EL_PROMPT, prompt); - el_set(el, EL_EDITOR, "emacs"); - el_set(el, EL_TERMINAL, NULL); - el_set(el, EL_SIGNAL, 1); - el_source(el, NULL); - - if (host != NULL) { - argv[0] = "open"; - argv[1] = (char *)host; - argv[2] = port ? (char *)port : "21"; - do_open(3, argv); - /* If we don't have a connection, exit */ - if (ctrl_fp == NULL) - exit(1); - - if (path != NULL) { - argv[0] = "cd"; - argv[1] = (char *)path; - do_cd(2, argv); - } - } - - for (;;) { - signal(SIGINT, SIG_IGN); - if ((line = el_gets(el, &count)) == NULL || count <= 0) { - if (verbose) - fprintf(stderr, "\n"); - argv[0] = "quit"; - do_quit(1, argv); - break; - } - - if (count <= 1) - continue; - - if ((cp = strrchr(line, '\n')) != NULL) - *cp = '\0'; - - history(hist, &hev, H_ENTER, line); - for (ap = argv; ap < &argv[ARGVMAX - 1] && - (*ap = strsep((char **)&line, " \t")) != NULL;) { - if (**ap != '\0') - ap++; - } - *ap = NULL; - - if (argv[0] == NULL) - continue; - - if ((i = cmd_lookup(argv[0])) == -1) { - fprintf(stderr, "Invalid command.\n"); - continue; - } - - if (cmd_tbl[i].conn_required && ctrl_fp == NULL) { - fprintf(stderr, "Not connected.\n"); - continue; - } - - interrupted = 0; - signal(SIGINT, cmd_interrupt); - cmd_tbl[i].cmd(ap - argv, argv); - - if (strcmp(cmd_tbl[i].name, "quit") == 0 || - strcmp(cmd_tbl[i].name, "exit") == 0) - break; - } - - el_end(el); -} - -static int -cmd_lookup(const char *cmd) -{ - size_t i; - - for (i = 0; i < nitems(cmd_tbl); i++) - if (strcmp(cmd, cmd_tbl[i].name) == 0) - return i; - - return -1; -} - -static char * -prompt(void) -{ - return "ftp> "; -} - -static FILE * -data_fopen(const char *mode) -{ - int fd; - - fd = activemode ? ftp_eprt(ctrl_fp) : ftp_epsv(ctrl_fp); - if (fd == -1) { - if (io_debug) - fprintf(stderr, "Failed to open data connection"); - - return NULL; - } - - return fdopen(fd, mode); -} - -static void -ftp_abort(void) -{ - char buf[BUFSIZ]; - - snprintf(buf, sizeof buf, "%c%c%c", IAC, IP, IAC); - if (send(fileno(ctrl_fp), buf, 3, MSG_OOB) != 3) - warn("abort"); - - ftp_command(ctrl_fp, "%cABOR", DM); -} - -static void -do_open(int argc, char **argv) -{ - const char *host = NULL, *port = "21"; - char *buf = NULL; - size_t n = 0; - int sock; - - if (ctrl_fp != NULL) { - fprintf(stderr, "already connected, use close first.\n"); - return; - } - - switch (argc) { - case 3: - port = argv[2]; - /* FALLTHROUGH */ - case 2: - host = argv[1]; - break; - default: - fprintf(stderr, "usage: open host [port]\n"); - return; - } - - if ((sock = tcp_connect(host, port, 0)) == -1) - return; - - fprintf(stderr, "Connected to %s.\n", host); - if ((ctrl_fp = fdopen(sock, "r+")) == NULL) - err(1, "%s: fdopen", __func__); - - /* greeting */ - ftp_getline(&buf, &n, 0, ctrl_fp); - free(buf); - if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) { - fclose(ctrl_fp); - ctrl_fp = NULL; - } -} - -static void -do_help(int argc, char **argv) -{ - size_t i; - int j; - - if (argc == 1) { - for (i = 0; i < nitems(cmd_tbl); i++) - fprintf(stderr, "%s\n", cmd_tbl[i].name); - - return; - } - - for (i = 1; i < (size_t)argc; i++) { - if ((j = cmd_lookup(argv[i])) == -1) - fprintf(stderr, "invalid help command %s\n", argv[i]); - else - fprintf(stderr, "%s\t%s\n", argv[i], cmd_tbl[j].info); - } -} - -static void -do_quit(int argc, char **argv) -{ - if (ctrl_fp == NULL) - return; - - ftp_command(ctrl_fp, "QUIT"); - fclose(ctrl_fp); - ctrl_fp = NULL; -} - -static void -do_ls(int argc, char **argv) -{ - FILE *dst_fp = stdout; - const char *cmd, *local_fname = NULL, *remote_dir = NULL; - char *buf = NULL; - size_t n = 0; - ssize_t len; - int r; - - switch (argc) { - case 3: - if (strcmp(argv[2], "-") != 0) - local_fname = argv[2]; - /* FALLTHROUGH */ - case 2: - remote_dir = argv[1]; - /* FALLTHROUGH */ - case 1: - break; - default: - fprintf(stderr, "usage: ls [remote-directory [local-file]]\n"); - return; - } - - if ((data_fp = data_fopen("r")) == NULL) - return; - - if (local_fname && (dst_fp = fopen(local_fname, "w")) == NULL) { - warn("fopen %s", local_fname); - fclose(data_fp); - data_fp = NULL; - return; - } - - cmd = (strcmp(argv[0], "ls") == 0) ? "LIST" : "NLST"; - if (remote_dir != NULL) - r = ftp_command(ctrl_fp, "%s %s", cmd, remote_dir); - else - r = ftp_command(ctrl_fp, "%s", cmd); - - if (r != P_PRE) { - fclose(data_fp); - data_fp = NULL; - if (dst_fp != stdout) - fclose(dst_fp); - - return; - } - - while ((len = getline(&buf, &n, data_fp)) != -1 && !interrupted) { - buf[len - 1] = '\0'; - if (len >= 2 && buf[len - 2] == '\r') - buf[len - 2] = '\0'; - - fprintf(dst_fp, "%s\n", buf); - } - - if (interrupted) - ftp_abort(); - - fclose(data_fp); - data_fp = NULL; - ftp_getline(&buf, &n, 0, ctrl_fp); - free(buf); - if (dst_fp != stdout) - fclose(dst_fp); -} - -static void -do_get(int argc, char **argv) -{ - FILE *dst_fp; - const char *local_fname = NULL, *p, *remote_fname; - char *buf = NULL; - size_t n = 0; - off_t file_sz, offset = 0; - - switch (argc) { - case 3: - local_fname = argv[2]; - /* FALLTHROUGH */ - case 2: - remote_fname = argv[1]; - break; - default: - fprintf(stderr, "usage: get remote-file [local-file]\n"); - return; - } - - if (local_fname == NULL) - local_fname = remote_fname; - - if (ftp_command(ctrl_fp, "TYPE I") != P_OK) - return; - - log_info("local: %s remote: %s\n", local_fname, remote_fname); - if (ftp_size(ctrl_fp, remote_fname, &file_sz, &buf) != P_OK) { - fprintf(stderr, "%s", buf); - return; - } - - if ((data_fp = data_fopen("r")) == NULL) - return; - - if ((dst_fp = fopen(local_fname, "w")) == NULL) { - warn("%s", local_fname); - fclose(data_fp); - data_fp = NULL; - return; - } - - if (ftp_command(ctrl_fp, "RETR %s", remote_fname) != P_PRE) { - fclose(data_fp); - data_fp = NULL; - fclose(dst_fp); - return; - } - - init_stats(file_sz, &offset); - if (progressmeter) { - p = basename(remote_fname); - start_progress_meter(p, NULL); - } - - copy_file(dst_fp, data_fp, &offset); - if (progressmeter) - stop_progress_meter(); - finish_stats(); - - if (interrupted) - ftp_abort(); - - fclose(data_fp); - data_fp = NULL; - fclose(dst_fp); - ftp_getline(&buf, &n, 0, ctrl_fp); - free(buf); -} - -static void -do_pwd(int argc, char **argv) -{ - ftp_command(ctrl_fp, "PWD"); -} - -static void -do_cd(int argc, char **argv) -{ - if (argc != 2) { - fprintf(stderr, "usage: cd remote-directory\n"); - return; - } - - ftp_command(ctrl_fp, "CWD %s", argv[1]); -} - -static void -do_passive(int argc, char **argv) -{ - switch (argc) { - case 1: - break; - case 2: - if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "off") == 0) - break; - - /* FALLTHROUGH */ - default: - fprintf(stderr, "usage: passive [on | off]\n"); - return; - } - - if (argv[1] != NULL) { - activemode = (strcmp(argv[1], "off") == 0) ? 1 : 0; - fprintf(stderr, "passive mode is %s\n", argv[1]); - return; - } - - activemode = !activemode; - fprintf(stderr, "passive mode is %s\n", activemode ? "off" : "on"); -} - -static void -do_lcd(int argc, char **argv) -{ - struct passwd *pw = NULL; - const char *dir, *login; - char cwd[PATH_MAX]; - - switch (argc) { - case 1: - case 2: - break; - default: - fprintf(stderr, "usage: lcd [local-directory]\n"); - return; - } - - if ((login = getlogin()) != NULL) - pw = getpwnam(login); - - if (pw == NULL && (pw = getpwuid(getuid())) == NULL) { - fprintf(stderr, "Failed to get home directory\n"); - return; - } - - dir = argv[1] ? argv[1] : pw->pw_dir; - if (chdir(dir) != 0) { - warn("local: %s", dir); - return; - } - - if (getcwd(cwd, sizeof cwd) == NULL) { - warn("getcwd"); - return; - } - - fprintf(stderr, "Local directory now %s\n", cwd); -} - -static void -do_lpwd(int argc, char **argv) -{ - char cwd[PATH_MAX]; - - if (getcwd(cwd, sizeof cwd) == NULL) { - warn("getcwd"); - return; - } - - fprintf(stderr, "Local directory %s\n", cwd); -} - -static void -do_put(int argc, char **argv) -{ - struct stat sb; - FILE *src_fp; - const char *local_fname, *p, *remote_fname = NULL; - char *buf = NULL; - size_t n = 0; - off_t file_sz, offset = 0; - - switch (argc) { - case 3: - remote_fname = argv[2]; - /* FALLTHROUGH */ - case 2: - local_fname = argv[1]; - break; - default: - fprintf(stderr, "usage: put local-file [remote-file]\n"); - return; - } - - if (remote_fname == NULL) - remote_fname = local_fname; - - if (ftp_command(ctrl_fp, "TYPE I") != P_OK) - return; - - log_info("local: %s remote: %s\n", local_fname, remote_fname); - if ((data_fp = data_fopen("w")) == NULL) - return; - - if ((src_fp = fopen(local_fname, "r")) == NULL) { - warn("%s", local_fname); - fclose(data_fp); - data_fp = NULL; - return; - } - - if (fstat(fileno(src_fp), &sb) != 0) { - warn("%s", local_fname); - fclose(data_fp); - data_fp = NULL; - fclose(src_fp); - return; - } - file_sz = sb.st_size; - - if (ftp_command(ctrl_fp, "STOR %s", remote_fname) != P_PRE) { - fclose(data_fp); - data_fp = NULL; - fclose(src_fp); - return; - } - - init_stats(file_sz, &offset); - if (progressmeter) { - p = basename(remote_fname); - start_progress_meter(p, NULL); - } - - copy_file(data_fp, src_fp, &offset); - if (progressmeter) - stop_progress_meter(); - finish_stats(); - - if (interrupted) - ftp_abort(); - - fclose(data_fp); - data_fp = NULL; - fclose(src_fp); - ftp_getline(&buf, &n, 0, ctrl_fp); - free(buf); -} - -static void -do_mget(int argc, char **argv) -{ - void (*fn)(int, char **); - const char *usage; - char *args[2]; - int i; - - if (strcmp(argv[0], "mget") == 0) { - fn = do_get; - args[0] = "get"; - usage = "mget remote-files"; - } else { - fn = do_put; - args[0] = "put"; - usage = "mput local-files"; - } - - if (argc == 1) { - fprintf(stderr, "usage: %s\n", usage); - return; - } - - for (i = 1; i < argc && !interrupted; i++) { - args[1] = argv[i]; - fn(2, args); - } -} diff --git a/usr.bin/ftp/cmds.c b/usr.bin/ftp/cmds.c new file mode 100644 index 00000000000..f51d558d21c --- /dev/null +++ b/usr.bin/ftp/cmds.c @@ -0,0 +1,1688 @@ +/* $OpenBSD: cmds.c,v 1.82 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: cmds.c,v 1.27 1997/08/18 10:20:15 lukem Exp $ */ + +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1985, 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SMALL + +/* + * FTP User Program -- Command Routines. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <arpa/ftp.h> + +#include <ctype.h> +#include <err.h> +#include <fnmatch.h> +#include <glob.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ftp_var.h" +#include "pathnames.h" +#include "cmds.h" + +/* + * Set ascii transfer type. + */ +/*ARGSUSED*/ +void +setascii(int argc, char *argv[]) +{ + + stype[1] = "ascii"; + settype(2, stype); +} + +/* + * Set file transfer mode. + */ +/*ARGSUSED*/ +void +setftmode(int argc, char *argv[]) +{ + + fprintf(ttyout, "We only support %s mode, sorry.\n", modename); + code = -1; +} + +/* + * Set file transfer format. + */ +/*ARGSUSED*/ +void +setform(int argc, char *argv[]) +{ + + fprintf(ttyout, "We only support %s format, sorry.\n", formname); + code = -1; +} + +/* + * Set file transfer structure. + */ +/*ARGSUSED*/ +void +setstruct(int argc, char *argv[]) +{ + + fprintf(ttyout, "We only support %s structure, sorry.\n", structname); + code = -1; +} + +void +reput(int argc, char *argv[]) +{ + + (void)putit(argc, argv, 1); +} + +void +put(int argc, char *argv[]) +{ + + (void)putit(argc, argv, 0); +} + +/* + * Send a single file. + */ +void +putit(int argc, char *argv[], int restartit) +{ + char *cmd; + int loc = 0; + char *oldargv1, *oldargv2; + + if (argc == 2) { + argc++; + argv[2] = argv[1]; + loc++; + } + if (argc < 2 && !another(&argc, &argv, "local-file")) + goto usage; + if ((argc < 3 && !another(&argc, &argv, "remote-file")) || argc > 3) { +usage: + fprintf(ttyout, "usage: %s local-file [remote-file]\n", + argv[0]); + code = -1; + return; + } + oldargv1 = argv[1]; + oldargv2 = argv[2]; + if (!globulize(&argv[1])) { + code = -1; + return; + } + /* + * If "globulize" modifies argv[1], and argv[2] is a copy of + * the old argv[1], make it a copy of the new argv[1]. + */ + if (argv[1] != oldargv1 && argv[2] == oldargv1) { + argv[2] = argv[1]; + } + if (restartit == 1) { + if (curtype != type) + changetype(type, 0); + restart_point = remotesize(argv[2], 1); + if (restart_point < 0) { + restart_point = 0; + code = -1; + return; + } + } + if (strcmp(argv[0], "append") == 0) { + restartit = 1; + } + cmd = restartit ? "APPE" : ((sunique) ? "STOU" : "STOR"); + if (loc && ntflag) { + argv[2] = dotrans(argv[2]); + } + if (loc && mapflag) { + argv[2] = domap(argv[2]); + } + sendrequest(cmd, argv[1], argv[2], + argv[1] != oldargv1 || argv[2] != oldargv2); + restart_point = 0; + if (oldargv1 != argv[1]) /* free up after globulize() */ + free(argv[1]); +} + +/* + * Send multiple files. + */ +void +mput(int argc, char *argv[]) +{ + extern int optind, optreset; + int ch, i, restartit = 0; + sig_t oldintr; + char *cmd, *tp, *xargv[] = { argv[0], NULL, NULL }; + const char *errstr; + static int depth = 0, max_depth = 0; + + optind = optreset = 1; + + if (depth) + depth++; + + while ((ch = getopt(argc, argv, "cd:r")) != -1) { + switch(ch) { + case 'c': + restartit = 1; + break; + case 'd': + max_depth = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + fprintf(ttyout, "bad depth value, %s: %s\n", + errstr, optarg); + code = -1; + return; + } + break; + case 'r': + depth = 1; + break; + default: + goto usage; + } + } + + if (argc - optind < 1 && !another(&argc, &argv, "local-files")) { +usage: + fprintf(ttyout, "usage: %s [-cr] [-d depth] local-files\n", + argv[0]); + code = -1; + return; + } + + argv[optind - 1] = argv[0]; + argc -= optind - 1; + argv += optind - 1; + + mname = argv[0]; + mflag = 1; + + oldintr = signal(SIGINT, mabort); + (void)setjmp(jabort); + if (proxy) { + char *cp, *tp2, tmpbuf[PATH_MAX]; + + while ((cp = remglob(argv, 0, NULL)) != NULL) { + if (*cp == '\0') { + mflag = 0; + continue; + } + if (mflag && confirm(argv[0], cp)) { + tp = cp; + if (mcase) { + while (*tp && !islower((unsigned char)*tp)) { + tp++; + } + if (!*tp) { + tp = cp; + tp2 = tmpbuf; + while ((*tp2 = *tp) != '\0') { + if (isupper((unsigned char)*tp2)) { + *tp2 = + tolower((unsigned char)*tp2); + } + tp++; + tp2++; + } + } + tp = tmpbuf; + } + if (ntflag) { + tp = dotrans(tp); + } + if (mapflag) { + tp = domap(tp); + } + if (restartit == 1) { + off_t ret; + + if (curtype != type) + changetype(type, 0); + ret = remotesize(tp, 0); + restart_point = (ret < 0) ? 0 : ret; + } + cmd = restartit ? "APPE" : ((sunique) ? + "STOU" : "STOR"); + sendrequest(cmd, cp, tp, + cp != tp || !interactive); + restart_point = 0; + if (!mflag && fromatty) { + if (confirm(argv[0], NULL)) + mflag = 1; + } + } + } + (void)signal(SIGINT, oldintr); + mflag = 0; + return; + } + + for (i = 1; i < argc; i++) { + char **cpp; + glob_t gl; + int flags; + + /* Copy files without word expansion */ + if (!doglob) { + if (mflag && confirm(argv[0], argv[i])) { + tp = (ntflag) ? dotrans(argv[i]) : argv[i]; + tp = (mapflag) ? domap(tp) : tp; + if (restartit == 1) { + off_t ret; + + if (curtype != type) + changetype(type, 0); + ret = remotesize(tp, 0); + restart_point = (ret < 0) ? 0 : ret; + } + cmd = restartit ? "APPE" : ((sunique) ? + "STOU" : "STOR"); + sendrequest(cmd, argv[i], tp, + tp != argv[i] || !interactive); + restart_point = 0; + if (!mflag && fromatty) { + if (confirm(argv[0], NULL)) + mflag = 1; + } + } + continue; + } + + /* expanding file names */ + memset(&gl, 0, sizeof(gl)); + flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; + if (glob(argv[i], flags, NULL, &gl) || gl.gl_pathc == 0) { + warnx("%s: not found", argv[i]); + globfree(&gl); + continue; + } + + /* traverse all expanded file names */ + for (cpp = gl.gl_pathv; cpp && *cpp != NULL; cpp++) { + struct stat filestat; + + if (!mflag) + continue; + if (stat(*cpp, &filestat) != 0) { + warn("local: %s", *cpp); + continue; + } + if (S_ISDIR(filestat.st_mode) && depth == max_depth) + continue; + if (!confirm(argv[0], *cpp)) + continue; + + /* + * If file is a directory then create a new one + * at the remote machine. + */ + if (S_ISDIR(filestat.st_mode)) { + xargv[1] = *cpp; + makedir(2, xargv); + cd(2, xargv); + if (dirchange != 1) { + warnx("remote: %s", *cpp); + continue; + } + + if (chdir(*cpp) != 0) { + warn("local: %s", *cpp); + goto out; + } + + /* Copy the whole directory recursively. */ + xargv[1] = "*"; + mput(2, xargv); + + if (chdir("..") != 0) { + mflag = 0; + warn("local: %s", *cpp); + goto out; + } + + out: + xargv[1] = ".."; + cd(2, xargv); + if (dirchange != 1) { + warnx("remote: %s", *cpp); + mflag = 0; + } + continue; + } + + tp = (ntflag) ? dotrans(*cpp) : *cpp; + tp = (mapflag) ? domap(tp) : tp; + if (restartit == 1) { + off_t ret; + + if (curtype != type) + changetype(type, 0); + ret = remotesize(tp, 0); + restart_point = (ret < 0) ? 0 : ret; + } + cmd = restartit ? "APPE" : ((sunique) ? + "STOU" : "STOR"); + sendrequest(cmd, *cpp, tp, + *cpp != tp || !interactive); + restart_point = 0; + if (!mflag && fromatty) { + if (confirm(argv[0], NULL)) + mflag = 1; + } + } + globfree(&gl); + } + + (void)signal(SIGINT, oldintr); + + if (depth) + depth--; + if (depth == 0 || mflag == 0) + depth = max_depth = mflag = 0; +} + +void +reget(int argc, char *argv[]) +{ + + (void)getit(argc, argv, 1, "a+w"); +} + +char * +onoff(int bool) +{ + + return (bool ? "on" : "off"); +} + +/* + * Show status. + */ +/*ARGSUSED*/ +void +status(int argc, char *argv[]) +{ + int i; + + if (connected) + fprintf(ttyout, "Connected %sto %s.\n", + connected == -1 ? "and logged in" : "", hostname); + else + fputs("Not connected.\n", ttyout); + if (!proxy) { + pswitch(1); + if (connected) { + fprintf(ttyout, "Connected for proxy commands to %s.\n", + hostname); + } + else { + fputs("No proxy connection.\n", ttyout); + } + pswitch(0); + } + fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", onoff(gatemode), + *gateserver ? gateserver : "(none)", gateport); + fprintf(ttyout, "Passive mode: %s.\n", onoff(passivemode)); + fprintf(ttyout, "Mode: %s; Type: %s; Form: %s; Structure: %s.\n", + modename, typename, formname, structname); + fprintf(ttyout, "Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s.\n", + onoff(verbose), onoff(bell), onoff(interactive), + onoff(doglob)); + fprintf(ttyout, "Store unique: %s; Receive unique: %s.\n", onoff(sunique), + onoff(runique)); + fprintf(ttyout, "Preserve modification times: %s.\n", onoff(preserve)); + fprintf(ttyout, "Case: %s; CR stripping: %s.\n", onoff(mcase), onoff(crflag)); + if (ntflag) { + fprintf(ttyout, "Ntrans: (in) %s (out) %s\n", ntin, ntout); + } + else { + fputs("Ntrans: off.\n", ttyout); + } + if (mapflag) { + fprintf(ttyout, "Nmap: (in) %s (out) %s\n", mapin, mapout); + } + else { + fputs("Nmap: off.\n", ttyout); + } + fprintf(ttyout, "Hash mark printing: %s; Mark count: %d; Progress bar: %s.\n", + onoff(hash), mark, onoff(progress)); + fprintf(ttyout, "Use of PORT/LPRT cmds: %s.\n", onoff(sendport)); + fprintf(ttyout, "Use of EPSV/EPRT cmds for IPv4: %s%s.\n", onoff(epsv4), + epsv4bad ? " (disabled for this connection)" : ""); + fprintf(ttyout, "Command line editing: %s.\n", onoff(editing)); + if (macnum > 0) { + fputs("Macros:\n", ttyout); + for (i=0; i<macnum; i++) { + fprintf(ttyout, "\t%s\n", macros[i].mac_name); + } + } + code = 0; +} + +/* + * Toggle a variable + */ +int +togglevar(int argc, char *argv[], int *var, const char *mesg) +{ + if (argc < 2) { + *var = !*var; + } else if (argc == 2 && strcasecmp(argv[1], "on") == 0) { + *var = 1; + } else if (argc == 2 && strcasecmp(argv[1], "off") == 0) { + *var = 0; + } else { + fprintf(ttyout, "usage: %s [on | off]\n", argv[0]); + return (-1); + } + if (mesg) + fprintf(ttyout, "%s %s.\n", mesg, onoff(*var)); + return (*var); +} + +/* + * Set beep on cmd completed mode. + */ +/*ARGSUSED*/ +void +setbell(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &bell, "Bell mode"); +} + +/* + * Set command line editing + */ +/*ARGSUSED*/ +void +setedit(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &editing, "Editing mode"); + controlediting(); +} + +/* + * Toggle use of IPv4 EPSV/EPRT + */ +/*ARGSUSED*/ +void +setepsv4(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &epsv4, "EPSV/EPRT on IPv4"); + epsv4bad = 0; +} + +/* + * Turn on packet tracing. + */ +/*ARGSUSED*/ +void +settrace(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &trace, "Packet tracing"); +} + +/* + * Toggle hash mark printing during transfers, or set hash mark bytecount. + */ +/*ARGSUSED*/ +void +sethash(int argc, char *argv[]) +{ + if (argc == 1) + hash = !hash; + else if (argc != 2) { + fprintf(ttyout, "usage: %s [on | off | size]\n", argv[0]); + code = -1; + return; + } else if (strcasecmp(argv[1], "on") == 0) + hash = 1; + else if (strcasecmp(argv[1], "off") == 0) + hash = 0; + else { + int nmark; + const char *errstr; + + nmark = strtonum(argv[1], 1, INT_MAX, &errstr); + if (errstr) { + fprintf(ttyout, "bytecount value is %s: %s\n", + errstr, argv[1]); + code = -1; + return; + } + mark = nmark; + hash = 1; + } + fprintf(ttyout, "Hash mark printing %s", onoff(hash)); + if (hash) + fprintf(ttyout, " (%d bytes/hash mark)", mark); + fputs(".\n", ttyout); + code = hash; +} + +/* + * Turn on printing of server echo's. + */ +/*ARGSUSED*/ +void +setverbose(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &verbose, "Verbose mode"); +} + +/* + * Toggle PORT/LPRT cmd use before each data connection. + */ +/*ARGSUSED*/ +void +setport(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &sendport, "Use of PORT/LPRT cmds"); +} + +/* + * Toggle transfer progress bar. + */ +/*ARGSUSED*/ +void +setprogress(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &progress, "Progress bar"); +} + +/* + * Turn on interactive prompting during mget, mput, and mdelete. + */ +/*ARGSUSED*/ +void +setprompt(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &interactive, "Interactive mode"); +} + +/* + * Toggle gate-ftp mode, or set gate-ftp server + */ +/*ARGSUSED*/ +void +setgate(int argc, char *argv[]) +{ + static char gsbuf[HOST_NAME_MAX+1]; + + if (argc > 3) { + fprintf(ttyout, "usage: %s [on | off | host [port]]\n", + argv[0]); + code = -1; + return; + } else if (argc < 2) { + gatemode = !gatemode; + } else { + if (argc == 2 && strcasecmp(argv[1], "on") == 0) + gatemode = 1; + else if (argc == 2 && strcasecmp(argv[1], "off") == 0) + gatemode = 0; + else { + if (argc == 3) { + gateport = strdup(argv[2]); + if (gateport == NULL) + err(1, NULL); + } + strlcpy(gsbuf, argv[1], sizeof(gsbuf)); + gateserver = gsbuf; + gatemode = 1; + } + } + if (gatemode && (gateserver == NULL || *gateserver == '\0')) { + fprintf(ttyout, + "Disabling gate-ftp mode - no gate-ftp server defined.\n"); + gatemode = 0; + } else { + fprintf(ttyout, "Gate ftp: %s, server %s, port %s.\n", + onoff(gatemode), + *gateserver ? gateserver : "(none)", gateport); + } + code = gatemode; +} + +/* + * Toggle metacharacter interpretation on local file names. + */ +/*ARGSUSED*/ +void +setglob(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &doglob, "Globbing"); +} + +/* + * Toggle preserving modification times on retrieved files. + */ +/*ARGSUSED*/ +void +setpreserve(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &preserve, "Preserve modification times"); +} + +/* + * Set debugging mode on/off and/or set level of debugging. + */ +/*ARGSUSED*/ +void +setdebug(int argc, char *argv[]) +{ + if (argc > 2) { + fprintf(ttyout, "usage: %s [on | off | debuglevel]\n", argv[0]); + code = -1; + return; + } else if (argc == 2) { + if (strcasecmp(argv[1], "on") == 0) + debug = 1; + else if (strcasecmp(argv[1], "off") == 0) + debug = 0; + else { + const char *errstr; + int val; + + val = strtonum(argv[1], 0, INT_MAX, &errstr); + if (errstr) { + fprintf(ttyout, "debugging value is %s: %s\n", + errstr, argv[1]); + code = -1; + return; + } + debug = val; + } + } else + debug = !debug; + if (debug) + options |= SO_DEBUG; + else + options &= ~SO_DEBUG; + fprintf(ttyout, "Debugging %s (debug=%d).\n", onoff(debug), debug); + code = debug > 0; +} + +/* + * Set current working directory on local machine. + */ +void +lcd(int argc, char *argv[]) +{ + char buf[PATH_MAX]; + char *oldargv1; + + if (argc < 2) + argc++, argv[1] = home; + if (argc != 2) { + fprintf(ttyout, "usage: %s [local-directory]\n", argv[0]); + code = -1; + return; + } + oldargv1 = argv[1]; + if (!globulize(&argv[1])) { + code = -1; + return; + } + if (chdir(argv[1]) < 0) { + warn("local: %s", argv[1]); + code = -1; + } else { + if (getcwd(buf, sizeof(buf)) != NULL) + fprintf(ttyout, "Local directory now %s\n", buf); + else + warn("getcwd: %s", argv[1]); + code = 0; + } + if (oldargv1 != argv[1]) /* free up after globulize() */ + free(argv[1]); +} + +/* + * Delete a single file. + */ +void +deletecmd(int argc, char *argv[]) +{ + + if ((argc < 2 && !another(&argc, &argv, "remote-file")) || argc > 2) { + fprintf(ttyout, "usage: %s remote-file\n", argv[0]); + code = -1; + return; + } + (void)command("DELE %s", argv[1]); +} + +/* + * Delete multiple files. + */ +void +mdelete(int argc, char *argv[]) +{ + sig_t oldintr; + char *cp; + + if (argc < 2 && !another(&argc, &argv, "remote-files")) { + fprintf(ttyout, "usage: %s remote-files\n", argv[0]); + code = -1; + return; + } + mname = argv[0]; + mflag = 1; + oldintr = signal(SIGINT, mabort); + (void)setjmp(jabort); + while ((cp = remglob(argv, 0, NULL)) != NULL) { + if (*cp == '\0') { + mflag = 0; + continue; + } + if (mflag && confirm(argv[0], cp)) { + (void)command("DELE %s", cp); + if (!mflag && fromatty) { + if (confirm(argv[0], NULL)) + mflag = 1; + } + } + } + (void)signal(SIGINT, oldintr); + mflag = 0; +} + +/* + * Rename a remote file. + */ +void +renamefile(int argc, char *argv[]) +{ + + if (argc < 2 && !another(&argc, &argv, "from-name")) + goto usage; + if ((argc < 3 && !another(&argc, &argv, "to-name")) || argc > 3) { +usage: + fprintf(ttyout, "usage: %s from-name to-name\n", argv[0]); + code = -1; + return; + } + if (command("RNFR %s", argv[1]) == CONTINUE) + (void)command("RNTO %s", argv[2]); +} + +/* + * Get a directory listing of remote files. + */ +void +ls(int argc, char *argv[]) +{ + const char *cmd; + char *oldargv2, *globargv2; + + if (argc < 2) + argc++, argv[1] = NULL; + if (argc < 3) + argc++, argv[2] = "-"; + if (argc > 3) { + fprintf(ttyout, "usage: %s [remote-directory [local-file]]\n", + argv[0]); + code = -1; + return; + } + cmd = strcmp(argv[0], "nlist") == 0 ? "NLST" : "LIST"; + oldargv2 = argv[2]; + if (strcmp(argv[2], "-") && !globulize(&argv[2])) { + code = -1; + return; + } + globargv2 = argv[2]; + if (strcmp(argv[2], "-") && *argv[2] != '|' && (!globulize(&argv[2]) || + !confirm("output to local-file:", argv[2]))) { + code = -1; + goto freels; + } + recvrequest(cmd, argv[2], argv[1], "w", 0, 0); + + /* flush results in case commands are coming from a pipe */ + fflush(ttyout); +freels: + if (argv[2] != globargv2) /* free up after globulize() */ + free(argv[2]); + if (globargv2 != oldargv2) + free(globargv2); +} + +/* + * Get a directory listing of multiple remote files. + */ +void +mls(int argc, char *argv[]) +{ + sig_t oldintr; + int i; + char lmode[1], *dest, *odest; + + if (argc < 2 && !another(&argc, &argv, "remote-files")) + goto usage; + if (argc < 3 && !another(&argc, &argv, "local-file")) { +usage: + fprintf(ttyout, "usage: %s remote-files local-file\n", argv[0]); + code = -1; + return; + } + odest = dest = argv[argc - 1]; + argv[argc - 1] = NULL; + if (strcmp(dest, "-") && *dest != '|') + if (!globulize(&dest) || + !confirm("output to local-file:", dest)) { + code = -1; + return; + } + mname = argv[0]; + mflag = 1; + oldintr = signal(SIGINT, mabort); + (void)setjmp(jabort); + for (i = 1; mflag && i < argc-1; ++i) { + *lmode = (i == 1) ? 'w' : 'a'; + recvrequest("LIST", dest, argv[i], lmode, 0, 0); + if (!mflag && fromatty) { + if (confirm(argv[0], NULL)) + mflag ++; + } + } + (void)signal(SIGINT, oldintr); + mflag = 0; + if (dest != odest) /* free up after globulize() */ + free(dest); +} + +/* + * Do a shell escape + */ +/*ARGSUSED*/ +void +shell(int argc, char *argv[]) +{ + pid_t pid; + sig_t old1, old2; + char shellnam[PATH_MAX], *shellp, *namep; + int wait_status; + + old1 = signal (SIGINT, SIG_IGN); + old2 = signal (SIGQUIT, SIG_IGN); + if ((pid = fork()) == 0) { + (void)closefrom(3); + (void)signal(SIGINT, SIG_DFL); + (void)signal(SIGQUIT, SIG_DFL); + shellp = getenv("SHELL"); + if (shellp == NULL || *shellp == '\0') + shellp = _PATH_BSHELL; + namep = strrchr(shellp, '/'); + if (namep == NULL) + namep = shellp; + shellnam[0] = '-'; + (void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1); + if (strcmp(namep, "sh") != 0) + shellnam[0] = '+'; + if (debug) { + fputs(shellp, ttyout); + fputc('\n', ttyout); + (void)fflush(ttyout); + } + if (argc > 1) { + execl(shellp, shellnam, "-c", altarg, (char *)NULL); + } + else { + execl(shellp, shellnam, (char *)NULL); + } + warn("%s", shellp); + code = -1; + exit(1); + } + if (pid > 0) + while (wait(&wait_status) != pid) + ; + (void)signal(SIGINT, old1); + (void)signal(SIGQUIT, old2); + if (pid == -1) { + warn("Try again later"); + code = -1; + } + else { + code = 0; + } +} + +/* + * Send new user information (re-login) + */ +void +user(int argc, char *argv[]) +{ + char acctname[80]; + int n, aflag = 0; + + if (argc < 2) + (void)another(&argc, &argv, "username"); + if (argc < 2 || argc > 4) { + fprintf(ttyout, "usage: %s username [password [account]]\n", + argv[0]); + code = -1; + return; + } + n = command("USER %s", argv[1]); + if (n == CONTINUE) { + if (argc < 3 ) + argv[2] = getpass("Password:"), argc++; + n = command("PASS %s", argv[2]); + } + if (n == CONTINUE) { + if (argc < 4) { + (void)fputs("Account: ", ttyout); + (void)fflush(ttyout); + if (fgets(acctname, sizeof(acctname), stdin) == NULL) { + clearerr(stdin); + goto fail; + } + + acctname[strcspn(acctname, "\n")] = '\0'; + + argv[3] = acctname; + argc++; + } + n = command("ACCT %s", argv[3]); + aflag++; + } + if (n != COMPLETE) { + fail: + fputs("Login failed.\n", ttyout); + return; + } + if (!aflag && argc == 4) { + (void)command("ACCT %s", argv[3]); + } + connected = -1; +} + +/* + * Print working directory on remote machine. + */ +/*ARGSUSED*/ +void +pwd(int argc, char *argv[]) +{ + int oldverbose = verbose; + + /* + * If we aren't verbose, this doesn't do anything! + */ + verbose = 1; + if (command("PWD") == ERROR && code == 500) { + fputs("PWD command not recognized, trying XPWD.\n", ttyout); + (void)command("XPWD"); + } + verbose = oldverbose; +} + +/* + * Print working directory on local machine. + */ +/* ARGSUSED */ +void +lpwd(int argc, char *argv[]) +{ + char buf[PATH_MAX]; + + if (getcwd(buf, sizeof(buf)) != NULL) + fprintf(ttyout, "Local directory %s\n", buf); + else + warn("getcwd"); + code = 0; +} + +/* + * Make a directory. + */ +void +makedir(int argc, char *argv[]) +{ + + if ((argc < 2 && !another(&argc, &argv, "directory-name")) || + argc > 2) { + fprintf(ttyout, "usage: %s directory-name\n", argv[0]); + code = -1; + return; + } + if (command("MKD %s", argv[1]) == ERROR && code == 500) { + if (verbose) + fputs("MKD command not recognized, trying XMKD.\n", ttyout); + (void)command("XMKD %s", argv[1]); + } +} + +/* + * Remove a directory. + */ +void +removedir(int argc, char *argv[]) +{ + + if ((argc < 2 && !another(&argc, &argv, "directory-name")) || + argc > 2) { + fprintf(ttyout, "usage: %s directory-name\n", argv[0]); + code = -1; + return; + } + if (command("RMD %s", argv[1]) == ERROR && code == 500) { + if (verbose) + fputs("RMD command not recognized, trying XRMD.\n", ttyout); + (void)command("XRMD %s", argv[1]); + } +} + +/* + * Send a line, verbatim, to the remote machine. + */ +void +quote(int argc, char *argv[]) +{ + + if (argc < 2 && !another(&argc, &argv, "command line to send")) { + fprintf(ttyout, "usage: %s arg ...\n", argv[0]); + code = -1; + return; + } + quote1("", argc, argv); +} + +/* + * Send a SITE command to the remote machine. The line + * is sent verbatim to the remote machine, except that the + * word "SITE" is added at the front. + */ +void +site(int argc, char *argv[]) +{ + + if (argc < 2 && !another(&argc, &argv, "arguments to SITE command")) { + fprintf(ttyout, "usage: %s arg ...\n", argv[0]); + code = -1; + return; + } + quote1("SITE", argc, argv); +} + +/* + * Turn argv[1..argc) into a space-separated string, then prepend initial text. + * Send the result as a one-line command and get response. + */ +void +quote1(const char *initial, int argc, char *argv[]) +{ + int i, len; + char buf[BUFSIZ]; /* must be >= sizeof(line) */ + + (void)strlcpy(buf, initial, sizeof(buf)); + if (argc > 1) { + for (i = 1, len = strlen(buf); i < argc && len < sizeof(buf)-1; i++) { + /* Space for next arg */ + if (len > 1) + buf[len++] = ' '; + + /* Sanity check */ + if (len >= sizeof(buf) - 1) + break; + + /* Copy next argument, NUL terminate always */ + strlcpy(&buf[len], argv[i], sizeof(buf) - len); + + /* Update string length */ + len = strlen(buf); + } + } + + /* Make double (triple?) sure the sucker is NUL terminated */ + buf[sizeof(buf) - 1] = '\0'; + + if (command("%s", buf) == PRELIM) { + while (getreply(0) == PRELIM) + continue; + } +} + +void +do_chmod(int argc, char *argv[]) +{ + + if (argc < 2 && !another(&argc, &argv, "mode")) + goto usage; + if ((argc < 3 && !another(&argc, &argv, "file")) || argc > 3) { +usage: + fprintf(ttyout, "usage: %s mode file\n", argv[0]); + code = -1; + return; + } + (void)command("SITE CHMOD %s %s", argv[1], argv[2]); +} + +void +do_umask(int argc, char *argv[]) +{ + int oldverbose = verbose; + + verbose = 1; + (void)command(argc == 1 ? "SITE UMASK" : "SITE UMASK %s", argv[1]); + verbose = oldverbose; +} + +void +idle(int argc, char *argv[]) +{ + int oldverbose = verbose; + + verbose = 1; + (void)command(argc == 1 ? "SITE IDLE" : "SITE IDLE %s", argv[1]); + verbose = oldverbose; +} + +/* + * Ask the other side for help. + */ +void +rmthelp(int argc, char *argv[]) +{ + int oldverbose = verbose; + + verbose = 1; + (void)command(argc == 1 ? "HELP" : "HELP %s", argv[1]); + verbose = oldverbose; +} + +/* + * Terminate session and exit. + */ +/*ARGSUSED*/ +void +quit(int argc, char *argv[]) +{ + + if (connected) + disconnect(0, 0); + pswitch(1); + if (connected) { + disconnect(0, 0); + } + exit(0); +} + +void +account(int argc, char *argv[]) +{ + char *ap; + + if (argc > 2) { + fprintf(ttyout, "usage: %s [password]\n", argv[0]); + code = -1; + return; + } + else if (argc == 2) + ap = argv[1]; + else + ap = getpass("Account:"); + (void)command("ACCT %s", ap); +} + +jmp_buf abortprox; + +/* ARGSUSED */ +void +proxabort(int signo) +{ + int save_errno = errno; + + alarmtimer(0); + if (!proxy) { + pswitch(1); + } + if (connected) { + proxflag = 1; + } + else { + proxflag = 0; + } + pswitch(0); + errno = save_errno; + longjmp(abortprox, 1); +} + +void +doproxy(int argc, char *argv[]) +{ + struct cmd *c; + int cmdpos; + sig_t oldintr; + + if (argc < 2 && !another(&argc, &argv, "command")) { + fprintf(ttyout, "usage: %s command\n", argv[0]); + code = -1; + return; + } + c = getcmd(argv[1]); + if (c == (struct cmd *) -1) { + fputs("?Ambiguous command.\n", ttyout); + (void)fflush(ttyout); + code = -1; + return; + } + if (c == 0) { + fputs("?Invalid command.\n", ttyout); + (void)fflush(ttyout); + code = -1; + return; + } + if (!c->c_proxy) { + fputs("?Invalid proxy command.\n", ttyout); + (void)fflush(ttyout); + code = -1; + return; + } + if (setjmp(abortprox)) { + code = -1; + return; + } + oldintr = signal(SIGINT, proxabort); + pswitch(1); + if (c->c_conn && !connected) { + fputs("Not connected.\n", ttyout); + (void)fflush(ttyout); + pswitch(0); + (void)signal(SIGINT, oldintr); + code = -1; + return; + } + cmdpos = strcspn(line, " \t"); + if (cmdpos > 0) /* remove leading "proxy " from input buffer */ + memmove(line, line + cmdpos + 1, strlen(line) - cmdpos + 1); + (*c->c_handler)(argc-1, argv+1); + if (connected) { + proxflag = 1; + } + else { + proxflag = 0; + } + pswitch(0); + (void)signal(SIGINT, oldintr); +} + +void +setcase(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &mcase, "Case mapping"); +} + +void +setcr(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &crflag, "Carriage Return stripping"); +} + +void +setntrans(int argc, char *argv[]) +{ + if (argc == 1) { + ntflag = 0; + fputs("Ntrans off.\n", ttyout); + code = ntflag; + return; + } + ntflag++; + code = ntflag; + (void)strlcpy(ntin, argv[1], sizeof(ntin)); + if (argc == 2) { + ntout[0] = '\0'; + return; + } + (void)strlcpy(ntout, argv[2], sizeof(ntout)); +} + +void +setnmap(int argc, char *argv[]) +{ + char *cp; + + if (argc == 1) { + mapflag = 0; + fputs("Nmap off.\n", ttyout); + code = mapflag; + return; + } + if ((argc < 3 && !another(&argc, &argv, "outpattern")) || argc > 3) { + fprintf(ttyout, "usage: %s [inpattern outpattern]\n", argv[0]); + code = -1; + return; + } + mapflag = 1; + code = 1; + cp = strchr(altarg, ' '); + if (proxy) { + while(*++cp == ' ') + continue; + altarg = cp; + cp = strchr(altarg, ' '); + } + *cp = '\0'; + (void)strncpy(mapin, altarg, PATH_MAX - 1); + while (*++cp == ' ') + continue; + (void)strncpy(mapout, cp, PATH_MAX - 1); +} + +void +setpassive(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &passivemode, + verbose ? "Passive mode" : NULL); +} + +void +setsunique(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &sunique, "Store unique"); +} + +void +setrunique(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &runique, "Receive unique"); +} + +/* change directory to parent directory */ +/* ARGSUSED */ +void +cdup(int argc, char *argv[]) +{ + int r; + + r = command("CDUP"); + if (r == ERROR && code == 500) { + if (verbose) + fputs("CDUP command not recognized, trying XCUP.\n", ttyout); + r = command("XCUP"); + } + if (r == COMPLETE) + dirchange = 1; +} + +/* + * Restart transfer at specific point + */ +void +restart(int argc, char *argv[]) +{ + off_t nrestart_point; + char *ep; + + if (argc != 2) + fputs("restart: offset not specified.\n", ttyout); + else { + nrestart_point = strtoll(argv[1], &ep, 10); + if (nrestart_point == LLONG_MAX || *ep != '\0') + fputs("restart: invalid offset.\n", ttyout); + else { + fprintf(ttyout, "Restarting at %lld. Execute get, put " + "or append to initiate transfer\n", + (long long)nrestart_point); + restart_point = nrestart_point; + } + } +} + +/* + * Show remote system type + */ +/* ARGSUSED */ +void +syst(int argc, char *argv[]) +{ + + (void)command("SYST"); +} + +void +macdef(int argc, char *argv[]) +{ + char *tmp; + int c; + + if (macnum == 16) { + fputs("Limit of 16 macros have already been defined.\n", ttyout); + code = -1; + return; + } + if ((argc < 2 && !another(&argc, &argv, "macro-name")) || argc > 2) { + fprintf(ttyout, "usage: %s macro-name\n", argv[0]); + code = -1; + return; + } + if (interactive) + fputs( +"Enter macro line by line, terminating it with a null line.\n", ttyout); + (void)strlcpy(macros[macnum].mac_name, argv[1], + sizeof(macros[macnum].mac_name)); + if (macnum == 0) + macros[macnum].mac_start = macbuf; + else + macros[macnum].mac_start = macros[macnum - 1].mac_end + 1; + tmp = macros[macnum].mac_start; + while (tmp != macbuf+4096) { + if ((c = getchar()) == EOF) { + fputs("macdef: end of file encountered.\n", ttyout); + code = -1; + return; + } + if ((*tmp = c) == '\n') { + if (tmp == macros[macnum].mac_start) { + macros[macnum++].mac_end = tmp; + code = 0; + return; + } + if (*(tmp-1) == '\0') { + macros[macnum++].mac_end = tmp - 1; + code = 0; + return; + } + *tmp = '\0'; + } + tmp++; + } + while (1) { + while ((c = getchar()) != '\n' && c != EOF) + /* LOOP */; + if (c == EOF || getchar() == '\n') { + fputs("Macro not defined - 4K buffer exceeded.\n", ttyout); + code = -1; + return; + } + } +} + +/* + * Get size of file on remote machine + */ +void +sizecmd(int argc, char *argv[]) +{ + off_t size; + + if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) { + fprintf(ttyout, "usage: %s file\n", argv[0]); + code = -1; + return; + } + size = remotesize(argv[1], 1); + if (size != -1) + fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size); + code = size; +} + +/* + * Get last modification time of file on remote machine + */ +void +modtime(int argc, char *argv[]) +{ + time_t mtime; + + if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) { + fprintf(ttyout, "usage: %s file\n", argv[0]); + code = -1; + return; + } + mtime = remotemodtime(argv[1], 1); + if (mtime != -1) + fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime))); + code = mtime; +} + +/* + * Show status on remote machine + */ +void +rmtstatus(int argc, char *argv[]) +{ + + (void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]); +} + +/* + * Get file if modtime is more recent than current file + */ +void +newer(int argc, char *argv[]) +{ + + (void)getit(argc, argv, -1, "w"); +} + +/* + * Display one file through $PAGER (defaults to "more"). + */ +void +page(int argc, char *argv[]) +{ + off_t orestart_point; + int ohash, overbose; + char *p, *pager, *oldargv1; + + if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) { + fprintf(ttyout, "usage: %s file\n", argv[0]); + code = -1; + return; + } + oldargv1 = argv[1]; + if (!globulize(&argv[1])) { + code = -1; + return; + } + p = getenv("PAGER"); + if (p == NULL || (*p == '\0')) + p = PAGER; + if (asprintf(&pager, "|%s", p) == -1) + errx(1, "Can't allocate memory for $PAGER"); + + orestart_point = restart_point; + ohash = hash; + overbose = verbose; + restart_point = hash = verbose = 0; + recvrequest("RETR", pager, argv[1], "r+w", 1, 0); + (void)free(pager); + restart_point = orestart_point; + hash = ohash; + verbose = overbose; + if (oldargv1 != argv[1]) /* free up after globulize() */ + free(argv[1]); +} + +#endif /* !SMALL */ + diff --git a/usr.bin/ftp/cmds.h b/usr.bin/ftp/cmds.h new file mode 100644 index 00000000000..ca01845f496 --- /dev/null +++ b/usr.bin/ftp/cmds.h @@ -0,0 +1,83 @@ +/* $OpenBSD: cmds.h,v 1.4 2019/05/16 12:44:17 florian Exp $ */ + +/* + * Copyright (c) 2009 Martynas Venckus <martynas@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +void setascii(int, char **); +void setftmode(int, char **); +void setform(int, char **); +void setstruct(int, char **); +void reput(int, char **); +void put(int, char **); +void putit(int, char **, int); +void mput(int, char **); +void reget(int, char **); +char *onoff(int); +void status(int, char **); +int togglevar(int, char **, int *, const char *); +void setbell(int, char **); +void setedit(int, char **); +void setepsv4(int, char **); +void settrace(int, char **); +void sethash(int, char **); +void setverbose(int, char **); +void setport(int, char **); +void setprogress(int, char **); +void setprompt(int, char **); +void setgate(int, char **); +void setglob(int, char **); +void setpreserve(int, char **); +void setdebug(int, char **); +void lcd(int, char **); +void deletecmd(int, char **); +void mdelete(int, char **); +void renamefile(int, char **); +void ls(int, char **); +void mls(int, char **); +void shell(int, char **); +void user(int, char **); +void pwd(int, char **); +void lpwd(int, char **); +void makedir(int, char **); +void removedir(int, char **); +void quote(int, char **); +void site(int, char **); +void quote1(const char *, int, char **); +void do_chmod(int, char **); +void do_umask(int, char **); +void idle(int, char **); +void rmthelp(int, char **); +void quit(int, char **); +void account(int, char **); +void proxabort(int); +void doproxy(int, char **); +void setcase(int, char **); +void setcr(int, char **); +void setntrans(int, char **); +void setnmap(int, char **); +void setpassive(int, char **); +void setsunique(int, char **); +void setrunique(int, char **); +void cdup(int, char **); +void restart(int, char **); +void syst(int, char **); +void macdef(int, char **); +void sizecmd(int, char **); +void modtime(int, char **); +void rmtstatus(int, char **); +void newer(int, char **); +void page(int, char **); + diff --git a/usr.bin/ftp/cmdtab.c b/usr.bin/ftp/cmdtab.c new file mode 100644 index 00000000000..5b871dc7ae9 --- /dev/null +++ b/usr.bin/ftp/cmdtab.c @@ -0,0 +1,215 @@ +/* $OpenBSD: cmdtab.c,v 1.31 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: cmdtab.c,v 1.17 1997/08/18 10:20:17 lukem Exp $ */ + +/* + * Copyright (c) 1985, 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SMALL + +#include <stdio.h> +#include "ftp_var.h" +#include "cmds.h" + +/* + * User FTP -- Command Tables. + */ + +char accounthelp[] = "send account command to remote server"; +char appendhelp[] = "append to a file"; +char asciihelp[] = "set ascii transfer type"; +char beephelp[] = "beep when command completed"; +char binaryhelp[] = "set binary transfer type"; +char casehelp[] = "toggle mget upper/lower case id mapping"; +char cdhelp[] = "change remote working directory"; +char cduphelp[] = "change remote working directory to parent directory"; +char chmodhelp[] = "change file permissions of remote file"; +char connecthelp[] = "connect to remote ftp server"; +char crhelp[] = "toggle carriage return stripping on ascii gets"; +char debughelp[] = "toggle/set debugging mode"; +char deletehelp[] = "delete remote file"; +char dirhelp[] = "list contents of remote directory"; +char disconhelp[] = "terminate ftp session"; +char domachelp[] = "execute macro"; +char edithelp[] = "toggle command line editing"; +char epsv4help[] = "toggle use of EPSV/EPRT on IPv4 ftp"; +char formhelp[] = "set file transfer format"; +char gatehelp[] = "toggle gate-ftp; specify host[:port] to change proxy"; +char globhelp[] = "toggle metacharacter expansion of local file names"; +char hashhelp[] = "toggle printing `#' marks; specify number to set size"; +char helphelp[] = "print local help information"; +char idlehelp[] = "get (set) idle timer on remote side"; +char lcdhelp[] = "change local working directory"; +char lpwdhelp[] = "print local working directory"; +char lshelp[] = "list contents of remote directory"; +char macdefhelp[] = "define a macro"; +char mdeletehelp[] = "delete multiple files"; +char mdirhelp[] = "list contents of multiple remote directories"; +char mgethelp[] = "get multiple files"; +char mkdirhelp[] = "make directory on the remote machine"; +char mlshelp[] = "list contents of multiple remote directories"; +char modehelp[] = "set file transfer mode"; +char modtimehelp[] = "show last modification time of remote file"; +char mputhelp[] = "send multiple files"; +char newerhelp[] = "get file if remote file is newer than local file "; +char nlisthelp[] = "nlist contents of remote directory"; +char nmaphelp[] = "set templates for default file name mapping"; +char ntranshelp[] = "set translation table for default file name mapping"; +char pagehelp[] = "view a remote file through your pager"; +char passivehelp[] = "toggle passive transfer mode"; +char porthelp[] = "toggle use of PORT/LPRT cmd for each data connection"; +char preservehelp[] ="toggle preservation of modification time of " + "retrieved files"; +char progresshelp[] ="toggle transfer progress meter"; +char prompthelp[] = "toggle interactive prompting on multiple commands"; +char proxyhelp[] = "issue command on alternate connection"; +char pwdhelp[] = "print working directory on remote machine"; +char quithelp[] = "terminate ftp session and exit"; +char quotehelp[] = "send arbitrary ftp command"; +char receivehelp[] = "receive file"; +char regethelp[] = "get file restarting at end of local file"; +char reputhelp[] = "put file restarting at end of remote file"; +char remotehelp[] = "get help from remote server"; +char renamehelp[] = "rename file"; +char resethelp[] = "clear queued command replies"; +char restarthelp[]= "restart file transfer at bytecount"; +char rmdirhelp[] = "remove directory on the remote machine"; +char rmtstatushelp[]="show status of remote machine"; +char runiquehelp[] = "toggle store unique for local files"; +char sendhelp[] = "send one file"; +char shellhelp[] = "escape to the shell"; +char sitehelp[] = "send site specific command to remote server\n" + "\t\tTry \"rhelp site\" or \"site help\" " + "for more information"; +char sizecmdhelp[] = "show size of remote file"; +char statushelp[] = "show current status"; +char structhelp[] = "set file transfer structure"; +char suniquehelp[] = "toggle store unique on remote machine"; +char systemhelp[] = "show remote system type"; +char tracehelp[] = "toggle packet tracing"; +char typehelp[] = "set file transfer type"; +char umaskhelp[] = "get (set) umask on remote side"; +char userhelp[] = "send new user information"; +char verbosehelp[] = "toggle verbose mode"; + +#define CMPL(x) __STRING(x), +#define CMPL0 "", +#define H(x) x + +struct cmd cmdtab[] = { + { "!", H(shellhelp), 0, 0, 0, CMPL0 shell }, + { "$", H(domachelp), 1, 0, 0, CMPL0 domacro }, + { "account", H(accounthelp), 0, 1, 1, CMPL0 account}, + { "append", H(appendhelp), 1, 1, 1, CMPL(lr) put }, + { "ascii", H(asciihelp), 0, 1, 1, CMPL0 setascii }, + { "bell", H(beephelp), 0, 0, 0, CMPL0 setbell }, + { "binary", H(binaryhelp), 0, 1, 1, CMPL0 setbinary }, + { "bye", H(quithelp), 0, 0, 0, CMPL0 quit }, + { "case", H(casehelp), 0, 0, 1, CMPL0 setcase }, + { "cd", H(cdhelp), 0, 1, 1, CMPL(r) cd }, + { "cdup", H(cduphelp), 0, 1, 1, CMPL0 cdup }, + { "chmod", H(chmodhelp), 0, 1, 1, CMPL(nr) do_chmod }, + { "close", H(disconhelp), 0, 1, 1, CMPL0 disconnect }, + { "cr", H(crhelp), 0, 0, 0, CMPL0 setcr }, + { "debug", H(debughelp), 0, 0, 0, CMPL0 setdebug }, + { "delete", H(deletehelp), 0, 1, 1, CMPL(r) deletecmd }, + { "dir", H(dirhelp), 1, 1, 1, CMPL(rl) ls }, + { "disconnect", H(disconhelp), 0, 1, 1, CMPL0 disconnect }, + { "edit", H(edithelp), 0, 0, 0, CMPL0 setedit }, + { "epsv4", H(epsv4help), 0, 0, 0, CMPL0 setepsv4 }, + { "exit", H(quithelp), 0, 0, 0, CMPL0 quit }, + { "form", H(formhelp), 0, 1, 1, CMPL0 setform }, + { "ftp", H(connecthelp), 0, 0, 1, CMPL0 setpeer }, + { "get", H(receivehelp), 1, 1, 1, CMPL(rl) get }, + { "gate", H(gatehelp), 0, 0, 0, CMPL0 setgate }, + { "glob", H(globhelp), 0, 0, 0, CMPL0 setglob }, + { "hash", H(hashhelp), 0, 0, 0, CMPL0 sethash }, + { "help", H(helphelp), 0, 0, 1, CMPL(C) help }, + { "idle", H(idlehelp), 0, 1, 1, CMPL0 idle }, + { "image", H(binaryhelp), 0, 1, 1, CMPL0 setbinary }, + { "lcd", H(lcdhelp), 0, 0, 0, CMPL(l) lcd }, + { "less", H(pagehelp), 1, 1, 1, CMPL(r) page }, + { "lpwd", H(lpwdhelp), 0, 0, 0, CMPL0 lpwd }, + { "ls", H(lshelp), 1, 1, 1, CMPL(rl) ls }, + { "macdef", H(macdefhelp), 0, 0, 0, CMPL0 macdef }, + { "mdelete", H(mdeletehelp), 1, 1, 1, CMPL(R) mdelete }, + { "mdir", H(mdirhelp), 1, 1, 1, CMPL(R) mls }, + { "mget", H(mgethelp), 1, 1, 1, CMPL(R) mget }, + { "mkdir", H(mkdirhelp), 0, 1, 1, CMPL(r) makedir }, + { "mls", H(mlshelp), 1, 1, 1, CMPL(R) mls }, + { "mode", H(modehelp), 0, 1, 1, CMPL0 setftmode }, + { "modtime", H(modtimehelp), 0, 1, 1, CMPL(r) modtime }, + { "more", H(pagehelp), 1, 1, 1, CMPL(r) page }, + { "mput", H(mputhelp), 1, 1, 1, CMPL(L) mput }, + { "msend", H(mputhelp), 1, 1, 1, CMPL(L) mput }, + { "newer", H(newerhelp), 1, 1, 1, CMPL(r) newer }, + { "nlist", H(nlisthelp), 1, 1, 1, CMPL(rl) ls }, + { "nmap", H(nmaphelp), 0, 0, 1, CMPL0 setnmap }, + { "ntrans", H(ntranshelp), 0, 0, 1, CMPL0 setntrans }, + { "open", H(connecthelp), 0, 0, 1, CMPL0 setpeer }, + { "page", H(pagehelp), 1, 1, 1, CMPL(r) page }, + { "passive", H(passivehelp), 0, 0, 0, CMPL0 setpassive }, + { "preserve", H(preservehelp),0, 0, 0, CMPL0 setpreserve }, + { "progress", H(progresshelp),0, 0, 0, CMPL0 setprogress }, + { "prompt", H(prompthelp), 0, 0, 0, CMPL0 setprompt }, + { "proxy", H(proxyhelp), 0, 0, 1, CMPL(c) doproxy }, + { "put", H(sendhelp), 1, 1, 1, CMPL(lr) put }, + { "pwd", H(pwdhelp), 0, 1, 1, CMPL0 pwd }, + { "quit", H(quithelp), 0, 0, 0, CMPL0 quit }, + { "quote", H(quotehelp), 1, 1, 1, CMPL0 quote }, + { "recv", H(receivehelp), 1, 1, 1, CMPL(rl) get }, + { "reget", H(regethelp), 1, 1, 1, CMPL(rl) reget }, + { "rename", H(renamehelp), 0, 1, 1, CMPL(rr) renamefile }, + { "reput", H(reputhelp), 1, 1, 1, CMPL(lr) reput }, + { "reset", H(resethelp), 0, 1, 1, CMPL0 reset }, + { "restart", H(restarthelp), 1, 1, 1, CMPL0 restart }, + { "rhelp", H(remotehelp), 0, 1, 1, CMPL0 rmthelp }, + { "rmdir", H(rmdirhelp), 0, 1, 1, CMPL(r) removedir }, + { "rstatus", H(rmtstatushelp),0, 1, 1, CMPL(r) rmtstatus }, + { "runique", H(runiquehelp), 0, 0, 1, CMPL0 setrunique }, + { "send", H(sendhelp), 1, 1, 1, CMPL(lr) put }, + { "sendport", H(porthelp), 0, 0, 0, CMPL0 setport }, + { "site", H(sitehelp), 0, 1, 1, CMPL0 site }, + { "size", H(sizecmdhelp), 1, 1, 1, CMPL(r) sizecmd }, + { "status", H(statushelp), 0, 0, 1, CMPL0 status }, + { "struct", H(structhelp), 0, 1, 1, CMPL0 setstruct }, + { "sunique", H(suniquehelp), 0, 0, 1, CMPL0 setsunique }, + { "system", H(systemhelp), 0, 1, 1, CMPL0 syst }, + { "trace", H(tracehelp), 0, 0, 0, CMPL0 settrace }, + { "type", H(typehelp), 0, 1, 1, CMPL0 settype }, + { "umask", H(umaskhelp), 0, 1, 1, CMPL0 do_umask }, + { "user", H(userhelp), 0, 1, 1, CMPL0 user }, + { "verbose", H(verbosehelp), 0, 0, 0, CMPL0 setverbose }, + { "?", H(helphelp), 0, 0, 1, CMPL(C) help }, + { 0 } +}; + +int NCMDS = (sizeof(cmdtab) / sizeof(cmdtab[0])) - 1; + +#endif /* !SMALL */ + diff --git a/usr.bin/ftp/complete.c b/usr.bin/ftp/complete.c new file mode 100644 index 00000000000..ef2478fbda8 --- /dev/null +++ b/usr.bin/ftp/complete.c @@ -0,0 +1,381 @@ +/* $OpenBSD: complete.c,v 1.33 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SMALL + +/* + * FTP user program - command and file completion routines + */ + +#include <ctype.h> +#include <err.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ftp_var.h" + +static int comparstr(const void *, const void *); +static unsigned char complete_ambiguous(char *, int, StringList *); +static unsigned char complete_command(char *, int); +static unsigned char complete_local(char *, int); +static unsigned char complete_remote(char *, int); +static void ftpvis(char *, size_t, const char *, size_t); + +static int +comparstr(const void *a, const void *b) +{ + return (strcmp(*(char **)a, *(char **)b)); +} + +/* + * Determine if complete is ambiguous. If unique, insert. + * If no choices, error. If unambiguous prefix, insert that. + * Otherwise, list choices. words is assumed to be filtered + * to only contain possible choices. + * Args: + * word word which started the match + * list list by default + * words stringlist containing possible matches + */ +static unsigned char +complete_ambiguous(char *word, int list, StringList *words) +{ + char insertstr[PATH_MAX * 2]; + char *lastmatch; + int i, j; + size_t matchlen, wordlen; + + wordlen = strlen(word); + if (words->sl_cur == 0) + return (CC_ERROR); /* no choices available */ + + if (words->sl_cur == 1) { /* only once choice available */ + char *p = words->sl_str[0] + wordlen; + ftpvis(insertstr, sizeof(insertstr), p, strlen(p)); + if (el_insertstr(el, insertstr) == -1) + return (CC_ERROR); + else + return (CC_REFRESH); + } + + if (!list) { + lastmatch = words->sl_str[0]; + matchlen = strlen(lastmatch); + for (i = 1 ; i < words->sl_cur ; i++) { + for (j = wordlen ; j < strlen(words->sl_str[i]); j++) + if (lastmatch[j] != words->sl_str[i][j]) + break; + if (j < matchlen) + matchlen = j; + } + if (matchlen > wordlen) { + ftpvis(insertstr, sizeof(insertstr), + lastmatch + wordlen, matchlen - wordlen); + if (el_insertstr(el, insertstr) == -1) + return (CC_ERROR); + else + /* + * XXX: really want CC_REFRESH_BEEP + */ + return (CC_REFRESH); + } + } + + putc('\n', ttyout); + qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); + list_vertical(words); + return (CC_REDISPLAY); +} + +/* + * Complete a command + */ +static unsigned char +complete_command(char *word, int list) +{ + struct cmd *c; + StringList *words; + size_t wordlen; + unsigned char rv; + + words = sl_init(); + wordlen = strlen(word); + + for (c = cmdtab; c->c_name != NULL; c++) { + if (wordlen > strlen(c->c_name)) + continue; + if (strncmp(word, c->c_name, wordlen) == 0) + sl_add(words, c->c_name); + } + + rv = complete_ambiguous(word, list, words); + sl_free(words, 0); + return (rv); +} + +/* + * Complete a local file + */ +static unsigned char +complete_local(char *word, int list) +{ + StringList *words; + char dir[PATH_MAX]; + char *file; + DIR *dd; + struct dirent *dp; + unsigned char rv; + + if ((file = strrchr(word, '/')) == NULL) { + dir[0] = '.'; + dir[1] = '\0'; + file = word; + } else { + if (file == word) { + dir[0] = '/'; + dir[1] = '\0'; + } else { + (void)strlcpy(dir, word, (size_t)(file - word) + 1); + } + file++; + } + + if ((dd = opendir(dir)) == NULL) + return (CC_ERROR); + + words = sl_init(); + + for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (strlen(file) > dp->d_namlen) + continue; + if (strncmp(file, dp->d_name, strlen(file)) == 0) { + char *tcp; + + tcp = strdup(dp->d_name); + if (tcp == NULL) + errx(1, "Can't allocate memory for local dir"); + sl_add(words, tcp); + } + } + closedir(dd); + + rv = complete_ambiguous(file, list, words); + sl_free(words, 1); + return (rv); +} + +/* + * Complete a remote file + */ +static unsigned char +complete_remote(char *word, int list) +{ + static StringList *dirlist; + static char lastdir[PATH_MAX]; + StringList *words; + char dir[PATH_MAX]; + char *file, *cp; + int i; + unsigned char rv; + + char *dummyargv[] = { "complete", dir, NULL }; + + if ((file = strrchr(word, '/')) == NULL) { + dir[0] = '.'; + dir[1] = '\0'; + file = word; + } else { + cp = file; + while (*cp == '/' && cp > word) + cp--; + (void)strlcpy(dir, word, (size_t)(cp - word + 2)); + file++; + } + + if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */ + char *emesg; + + sl_free(dirlist, 1); + dirlist = sl_init(); + + mflag = 1; + emesg = NULL; + if (debug) + (void)putc('\n', ttyout); + while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) { + char *tcp; + + if (!mflag) + continue; + if (*cp == '\0') { + mflag = 0; + continue; + } + tcp = strrchr(cp, '/'); + if (tcp) + tcp++; + else + tcp = cp; + tcp = strdup(tcp); + if (tcp == NULL) + errx(1, "Can't allocate memory for remote dir"); + sl_add(dirlist, tcp); + } + if (emesg != NULL) { + fprintf(ttyout, "\n%s\n", emesg); + return (CC_REDISPLAY); + } + (void)strlcpy(lastdir, dir, sizeof lastdir); + dirchange = 0; + } + + words = sl_init(); + for (i = 0; i < dirlist->sl_cur; i++) { + cp = dirlist->sl_str[i]; + if (strlen(file) > strlen(cp)) + continue; + if (strncmp(file, cp, strlen(file)) == 0) + sl_add(words, cp); + } + rv = complete_ambiguous(file, list, words); + sl_free(words, 0); + return (rv); +} + +/* + * Generic complete routine + */ +unsigned char +complete(EditLine *el, int ch) +{ + static char word[FTPBUFLEN]; + static int lastc_argc, lastc_argo; + struct cmd *c; + const LineInfo *lf; + int celems, dolist; + size_t len; + + lf = el_line(el); + len = lf->lastchar - lf->buffer; + if (len >= sizeof(line)) + return (CC_ERROR); + (void)memcpy(line, lf->buffer, len); + line[len] = '\0'; + cursor_pos = line + (lf->cursor - lf->buffer); + lastc_argc = cursor_argc; /* remember last cursor pos */ + lastc_argo = cursor_argo; + makeargv(); /* build argc/argv of current line */ + + if (cursor_argo >= sizeof(word)) + return (CC_ERROR); + + dolist = 0; + /* if cursor and word is same, list alternatives */ + if (lastc_argc == cursor_argc && lastc_argo == cursor_argo + && strncmp(word, margv[cursor_argc], cursor_argo) == 0) + dolist = 1; + else if (cursor_argo) + memcpy(word, margv[cursor_argc], cursor_argo); + word[cursor_argo] = '\0'; + + if (cursor_argc == 0) + return (complete_command(word, dolist)); + + c = getcmd(margv[0]); + if (c == (struct cmd *)-1 || c == 0) + return (CC_ERROR); + celems = strlen(c->c_complete); + + /* check for 'continuation' completes (which are uppercase) */ + if ((cursor_argc > celems) && (celems > 0) + && isupper((unsigned char)c->c_complete[celems - 1])) + cursor_argc = celems; + + if (cursor_argc > celems) + return (CC_ERROR); + + switch (c->c_complete[cursor_argc - 1]) { + case 'l': /* local complete */ + case 'L': + return (complete_local(word, dolist)); + case 'r': /* remote complete */ + case 'R': + if (connected != -1) { + fputs("\nMust be logged in to complete.\n", ttyout); + return (CC_REDISPLAY); + } + return (complete_remote(word, dolist)); + case 'c': /* command complete */ + case 'C': + return (complete_command(word, dolist)); + case 'n': /* no complete */ + return (CC_ERROR); + } + + return (CC_ERROR); +} + +/* + * Copy characters from src into dst, \ quoting characters that require it. + */ +static void +ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen) +{ + size_t di, si; + + di = si = 0; + while (di + 1 < dstlen && si < srclen && src[si] != '\0') { + switch (src[si]) { + case '\\': + case ' ': + case '\t': + case '\r': + case '\n': + case '"': + /* Need room for two characters and NUL, avoiding + * incomplete escape sequences at end of dst. */ + if (di + 3 >= dstlen) + break; + dst[di++] = '\\'; + /* FALLTHROUGH */ + default: + dst[di++] = src[si++]; + } + } + if (dstlen != 0) + dst[di] = '\0'; +} +#endif /* !SMALL */ diff --git a/usr.bin/ftp/cookie.c b/usr.bin/ftp/cookie.c new file mode 100644 index 00000000000..6b526a22d14 --- /dev/null +++ b/usr.bin/ftp/cookie.c @@ -0,0 +1,231 @@ +/* $OpenBSD: cookie.c,v 1.9 2019/05/16 12:44:17 florian Exp $ */ + +/* + * Copyright (c) 2007 Pierre-Yves Ritschard <pyr@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef NOSSL + +#include <sys/types.h> +#include <sys/queue.h> + +#include <err.h> +#include <errno.h> +#include <fnmatch.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> + +#include "ftp_var.h" + +struct cookie { + TAILQ_ENTRY(cookie) entry; + TAILQ_ENTRY(cookie) tempentry; + u_int8_t flags; +#define F_SECURE 0x01 +#define F_TAILMATCH 0x02 +#define F_NOEXPIRY 0x04 +#define F_MATCHPATH 0x08 + time_t expires; + char *domain; + char *path; + char *key; + char *val; +}; +TAILQ_HEAD(cookiejar, cookie); + +typedef enum { + DOMAIN = 0, TAILMATCH = 1, PATH = 2, SECURE = 3, + EXPIRES = 4, NAME = 5, VALUE = 6, DONE = 7 +} field_t; + +static struct cookiejar jar; + +void +cookie_load(void) +{ + field_t field; + size_t len; + time_t date; + char *line; + char *lbuf; + char *param; + const char *estr; + FILE *fp; + struct cookie *ck; + + if (cookiefile == NULL) + return; + + TAILQ_INIT(&jar); + fp = fopen(cookiefile, "r"); + if (fp == NULL) + err(1, "cannot open cookie file %s", cookiefile); + date = time(NULL); + lbuf = NULL; + while ((line = fgetln(fp, &len)) != NULL) { + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + --len; + } else { + if ((lbuf = malloc(len + 1)) == NULL) + err(1, NULL); + memcpy(lbuf, line, len); + lbuf[len] = '\0'; + line = lbuf; + } + line[strcspn(line, "\r")] = '\0'; + + line += strspn(line, " \t"); + if ((*line == '#') || (*line == '\0')) { + continue; + } + field = DOMAIN; + ck = calloc(1, sizeof(*ck)); + if (ck == NULL) + err(1, NULL); + while ((param = strsep(&line, "\t")) != NULL) { + switch (field) { + case DOMAIN: + if (*param == '.') { + if (asprintf(&ck->domain, + "*%s", param) == -1) + err(1, NULL); + } else { + ck->domain = strdup(param); + if (ck->domain == NULL) + err(1, NULL); + } + break; + case TAILMATCH: + if (strcasecmp(param, "TRUE") == 0) { + ck->flags |= F_TAILMATCH; + } else if (strcasecmp(param, "FALSE") != 0) { + errx(1, "invalid cookie file"); + } + break; + case PATH: + if (strcmp(param, "/") != 0) { + ck->flags |= F_MATCHPATH; + if (asprintf(&ck->path, + "%s*", param) == -1) + err(1, NULL); + } + break; + case SECURE: + if (strcasecmp(param, "TRUE") == 0) { + ck->flags |= F_SECURE; + } else if (strcasecmp(param, "FALSE") != 0) { + errx(1, "invalid cookie file"); + } + break; + case EXPIRES: + /* + * rely on sizeof(time_t) being 4 + */ + ck->expires = strtonum(param, 0, + INT_MAX, &estr); + if (estr) { + if (errno == ERANGE) + ck->flags |= F_NOEXPIRY; + else + errx(1, "invalid cookie file"); + } + break; + case NAME: + ck->key = strdup(param); + if (ck->key == NULL) + err(1, NULL); + break; + case VALUE: + ck->val = strdup(param); + if (ck->val == NULL) + err(1, NULL); + break; + case DONE: + errx(1, "invalid cookie file"); + break; + } + field++; + } + if (field != DONE) + errx(1, "invalid cookie file"); + if (ck->expires < date && !(ck->flags & F_NOEXPIRY)) { + free(ck->val); + free(ck->key); + free(ck->path); + free(ck->domain); + free(ck); + } else + TAILQ_INSERT_TAIL(&jar, ck, entry); + } + free(lbuf); + fclose(fp); +} + +void +cookie_get(const char *domain, const char *path, int secure, char **pstr) +{ + size_t len; + size_t headlen; + char *head; + char *str; + struct cookie *ck; + struct cookiejar tempjar; + + *pstr = NULL; + + if (cookiefile == NULL) + return; + + TAILQ_INIT(&tempjar); + len = strlen("Cookie\r\n"); + + TAILQ_FOREACH(ck, &jar, entry) { + if (fnmatch(ck->domain, domain, 0) == 0 && + (secure || !(ck->flags & F_SECURE))) { + + if (ck->flags & F_MATCHPATH && + fnmatch(ck->path, path, 0) != 0) + continue; + + len += strlen(ck->key) + strlen(ck->val) + + strlen("; ="); + TAILQ_INSERT_TAIL(&tempjar, ck, tempentry); + } + } + if (TAILQ_EMPTY(&tempjar)) + return; + len += 1; + str = malloc(len); + if (str == NULL) + err(1, NULL); + + (void)strlcpy(str, "Cookie:", len); + TAILQ_FOREACH(ck, &tempjar, tempentry) { + head = str + strlen(str); + headlen = len - strlen(str); + + snprintf(head, headlen, "%s %s=%s", + (ck == TAILQ_FIRST(&tempjar))? "" : ";", ck->key, ck->val); + } + if (strlcat(str, "\r\n", len) >= len) + errx(1, "cookie header truncated"); + *pstr = str; +} + +#endif /* !SMALL */ + diff --git a/usr.bin/ftp/domacro.c b/usr.bin/ftp/domacro.c new file mode 100644 index 00000000000..0c1bf1eff05 --- /dev/null +++ b/usr.bin/ftp/domacro.c @@ -0,0 +1,149 @@ +/* $OpenBSD: domacro.c,v 1.21 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: domacro.c,v 1.10 1997/07/20 09:45:45 lukem Exp $ */ + +/* + * Copyright (c) 1985, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SMALL + +#include <ctype.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> + +#include "ftp_var.h" + +void +domacro(int argc, char *argv[]) +{ + int i, j, count = 2, loopflg = 0; + char *cp1, *cp2, line2[FTPBUFLEN]; + struct cmd *c; + + if (argc < 2 && !another(&argc, &argv, "macro name")) { + fprintf(ttyout, "usage: %s macro-name\n", argv[0]); + code = -1; + return; + } + for (i = 0; i < macnum; ++i) { + if (!strncmp(argv[1], macros[i].mac_name, 9)) { + break; + } + } + if (i == macnum) { + fprintf(ttyout, "'%s' macro not found.\n", argv[1]); + code = -1; + return; + } + (void)strlcpy(line2, line, sizeof(line2)); +TOP: + cp1 = macros[i].mac_start; + while (cp1 != macros[i].mac_end) { + while (isspace((unsigned char)*cp1)) { + cp1++; + } + cp2 = line; + while (*cp1 != '\0') { + switch(*cp1) { + case '\\': + *cp2++ = *++cp1; + break; + case '$': + if (isdigit((unsigned char)*(cp1 + 1))) { + j = 0; + while (isdigit((unsigned char)*++cp1)) { + j = 10*j + *cp1 - '0'; + } + cp1--; + if (argc - 2 >= j) { + (void)strlcpy(cp2, argv[j+1], + sizeof(line) - (cp2 - line)); + cp2 += strlen(argv[j+1]); + } + break; + } + if (*(cp1+1) == 'i') { + loopflg = 1; + cp1++; + if (count < argc) { + (void)strlcpy(cp2, argv[count], + sizeof(line) - (cp2 - line)); + cp2 += strlen(argv[count]); + } + break; + } + /* FALLTHROUGH */ + default: + *cp2++ = *cp1; + break; + } + if (*cp1 != '\0') { + cp1++; + } + } + *cp2 = '\0'; + makeargv(); + c = getcmd(margv[0]); + if (c == (struct cmd *)-1) { + fputs("?Ambiguous command.\n", ttyout); + code = -1; + } + else if (c == 0) { + fputs("?Invalid command.\n", ttyout); + code = -1; + } + else if (c->c_conn && !connected) { + fputs("Not connected.\n", ttyout); + code = -1; + } + else { + if (verbose) { + fputs(line, ttyout); + fputc('\n', ttyout); + } + (*c->c_handler)(margc, margv); + if (bell && c->c_bell) { + (void)putc('\007', ttyout); + } + (void)strlcpy(line, line2, sizeof(line)); + makeargv(); + argc = margc; + argv = margv; + } + if (cp1 != macros[i].mac_end) { + cp1++; + } + } + if (loopflg && ++count < argc) { + goto TOP; + } +} + +#endif /* !SMALL */ + diff --git a/usr.bin/ftp/extern.h b/usr.bin/ftp/extern.h new file mode 100644 index 00000000000..53908981b4e --- /dev/null +++ b/usr.bin/ftp/extern.h @@ -0,0 +1,150 @@ +/* $OpenBSD: extern.h,v 1.51 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: extern.h,v 1.17 1997/08/18 10:20:19 lukem Exp $ */ + +/* + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1994 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.3 (Berkeley) 10/9/94 + */ + +#include <sys/types.h> + +void abort_remote(FILE *); +void abortpt(int); +void abortrecv(int); +void alarmtimer(int); +int another(int *, char ***, const char *); +int auto_fetch(int, char **, char *); +void blkfree(char **); +void cdup(int, char **); +void cmdabort(int); +void cmdscanner(int); +int command(const char *, ...); +int confirm(const char *, const char *); +int connect_wait(int); +FILE *dataconn(const char *); +int foregroundproc(void); +int fileindir(const char *, const char *); +struct cmd *getcmd(const char *); +int getreply(int); +int globulize(char **); +char *gunique(const char *); +void help(int, char **); +char *hookup(char *, char *); +int initconn(void); +void intr(void); +int isurl(const char *); +int ftp_login(const char *, char *, char *); +void lostpeer(void); +void makeargv(void); +void progressmeter(int, const char *); +char *prompt(void); +void proxtrans(const char *, const char *, const char *); +void psabort(int); +void psummary(int); +void pswitch(int); +void ptransfer(int); +void recvrequest(const char *, const char *, const char *, + const char *, int, int); +char *remglob(char **, int, char **); +off_t remotesize(const char *, int); +time_t remotemodtime(const char *, int); +void reset(int, char **); +void rmthelp(int, char **); +void sethash(int, char **); +void setpeer(int, char **); +void setttywidth(int); +char *slurpstring(void); + +__dead void usage(void); + +void cookie_get(const char *, const char *, int, char **); +void cookie_load(void); + +#ifndef SMALL +void abortsend(int); +unsigned char complete(EditLine *, int); +void controlediting(void); +void domacro(int, char **); +void list_vertical(StringList *); +void parse_list(char **, char *); +char *remglob2(char **, int, char **, FILE **ftemp, char *type); +int ruserpass(const char *, char **, char **, char **); +void sendrequest(const char *, const char *, const char *, int); +#endif /* !SMALL */ + +extern jmp_buf abortprox; +extern int abrtflag; +extern FILE *cout; +extern int data; +extern char *home; +extern jmp_buf jabort; +extern int family; +extern int proxy; +extern char reply_string[]; +extern off_t restart_point; +extern int keep_alive_timeout; +extern int connect_timeout; +extern int pipeout; +extern char *action; + +#ifndef SMALL +extern int NCMDS; +#endif /* !SMALL */ + +extern char *__progname; /* from crt0.o */ + diff --git a/usr.bin/ftp/fetch.c b/usr.bin/ftp/fetch.c new file mode 100644 index 00000000000..f08c5501247 --- /dev/null +++ b/usr.bin/ftp/fetch.c @@ -0,0 +1,1668 @@ +/* $OpenBSD: fetch.c,v 1.169 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jason Thorpe and Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * FTP User Program -- Command line file retrieval + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> + +#include <arpa/ftp.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <libgen.h> +#include <netdb.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> +#include <resolv.h> + +#ifndef NOSSL +#include <tls.h> +#else /* !NOSSL */ +struct tls; +#endif /* !NOSSL */ + +#include "ftp_var.h" +#include "cmds.h" + +static int url_get(const char *, const char *, const char *, int); +void aborthttp(int); +void abortfile(int); +char hextochar(const char *); +char *urldecode(const char *); +char *recode_credentials(const char *_userinfo); +int ftp_printf(FILE *, struct tls *, const char *, ...) __attribute__((format(printf, 3, 4))); +char *ftp_readline(FILE *, struct tls *, size_t *); +size_t ftp_read(FILE *, struct tls *, char *, size_t); +#ifndef NOSSL +int proxy_connect(int, char *, char *); +int SSL_vprintf(struct tls *, const char *, va_list); +char *SSL_readline(struct tls *, size_t *); +#endif /* !NOSSL */ + +#define FTP_URL "ftp://" /* ftp URL prefix */ +#define HTTP_URL "http://" /* http URL prefix */ +#define HTTPS_URL "https://" /* https URL prefix */ +#define FILE_URL "file:" /* file URL prefix */ +#define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */ +#define HTTP_PROXY "http_proxy" /* env var with http proxy location */ + +#define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0')) + +static const char at_encoding_warning[] = + "Extra `@' characters in usernames and passwords should be encoded as %%40"; + +jmp_buf httpabort; + +static int redirect_loop; + +/* + * Determine whether the character needs encoding, per RFC1738: + * - No corresponding graphic US-ASCII. + * - Unsafe characters. + */ +static int +unsafe_char(const char *c0) +{ + const char *unsafe_chars = " <>\"#{}|\\^~[]`"; + const unsigned char *c = (const unsigned char *)c0; + + /* + * No corresponding graphic US-ASCII. + * Control characters and octets not used in US-ASCII. + */ + return (iscntrl(*c) || !isascii(*c) || + + /* + * Unsafe characters. + * '%' is also unsafe, if is not followed by two + * hexadecimal digits. + */ + strchr(unsafe_chars, *c) != NULL || + (*c == '%' && (!isxdigit(*++c) || !isxdigit(*++c)))); +} + +/* + * Encode given URL, per RFC1738. + * Allocate and return string to the caller. + */ +static char * +url_encode(const char *path) +{ + size_t i, length, new_length; + char *epath, *epathp; + + length = new_length = strlen(path); + + /* + * First pass: + * Count unsafe characters, and determine length of the + * final URL. + */ + for (i = 0; i < length; i++) + if (unsafe_char(path + i)) + new_length += 2; + + epath = epathp = malloc(new_length + 1); /* One more for '\0'. */ + if (epath == NULL) + err(1, "Can't allocate memory for URL encoding"); + + /* + * Second pass: + * Encode, and copy final URL. + */ + for (i = 0; i < length; i++) + if (unsafe_char(path + i)) { + snprintf(epathp, 4, "%%" "%02x", + (unsigned char)path[i]); + epathp += 3; + } else + *(epathp++) = path[i]; + + *epathp = '\0'; + return (epath); +} + +/* ARGSUSED */ +static void +tooslow(int signo) +{ + dprintf(STDERR_FILENO, "%s: connect taking too long\n", __progname); + _exit(2); +} + +/* + * Retrieve URL, via the proxy in $proxyvar if necessary. + * Modifies the string argument given. + * Returns -1 on failure, 0 on success + */ +static int +url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile) +{ + char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4]; + char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; + char *epath, *redirurl, *loctail, *h, *p; + int error, i, isftpurl = 0, isfileurl = 0, isredirect = 0, rval = -1; + struct addrinfo hints, *res0, *res; + const char * volatile savefile; + char * volatile proxyurl = NULL; + char *credentials = NULL; + volatile int fd = -1, out = -1; + volatile sig_t oldintr, oldinti; + FILE *fin = NULL; + off_t hashbytes; + const char *errstr; + ssize_t len, wlen; + char *proxyhost = NULL; +#ifndef NOSSL + char *sslpath = NULL, *sslhost = NULL; + char *full_host = NULL; + const char *scheme; + int ishttpurl = 0, ishttpsurl = 0; +#endif /* !NOSSL */ +#ifndef SMALL + char *locbase; + struct addrinfo *ares = NULL; +#endif + struct tls *tls = NULL; + int status; + int save_errno; + const size_t buflen = 128 * 1024; + + direction = "received"; + + newline = strdup(origline); + if (newline == NULL) + errx(1, "Can't allocate memory to parse URL"); + if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) { + host = newline + sizeof(HTTP_URL) - 1; +#ifndef SMALL + ishttpurl = 1; + scheme = HTTP_URL; +#endif /* !SMALL */ + } else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) { + host = newline + sizeof(FTP_URL) - 1; + isftpurl = 1; +#ifndef SMALL + scheme = FTP_URL; +#endif /* !SMALL */ + } else if (strncasecmp(newline, FILE_URL, sizeof(FILE_URL) - 1) == 0) { + host = newline + sizeof(FILE_URL) - 1; + isfileurl = 1; +#ifndef NOSSL + scheme = FILE_URL; + } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) { + host = newline + sizeof(HTTPS_URL) - 1; + ishttpsurl = 1; + scheme = HTTPS_URL; +#endif /* !NOSSL */ + } else + errx(1, "url_get: Invalid URL '%s'", newline); + + if (isfileurl) { + path = host; + } else { + path = strchr(host, '/'); /* Find path */ + if (EMPTYSTRING(path)) { + if (outfile) { /* No slash, but */ + path=strchr(host,'\0'); /* we have outfile. */ + goto noslash; + } + if (isftpurl) + goto noftpautologin; + warnx("No `/' after host (use -o): %s", origline); + goto cleanup_url_get; + } + *path++ = '\0'; + if (EMPTYSTRING(path) && !outfile) { + if (isftpurl) + goto noftpautologin; + warnx("No filename after host (use -o): %s", origline); + goto cleanup_url_get; + } + } + +noslash: + +#ifndef NOSSL + /* + * Look for auth header in host, since now host does not + * contain the path. Basic auth from RFC 2617, valid + * characters for path are in RFC 3986 section 3.3. + */ + if (proxyenv == NULL && (ishttpurl || ishttpsurl)) { + if ((p = strchr(host, '@')) != NULL) { + *p = '\0'; + credentials = recode_credentials(host); + host = p + 1; + } + } +#endif /* NOSSL */ + + if (outfile) + savefile = outfile; + else { + if (path[strlen(path) - 1] == '/') /* Consider no file */ + savefile = NULL; /* after dir invalid. */ + else + savefile = basename(path); + } + + if (EMPTYSTRING(savefile)) { + if (isftpurl) + goto noftpautologin; + warnx("No filename after directory (use -o): %s", origline); + goto cleanup_url_get; + } + +#ifndef SMALL + if (resume && pipeout) { + warnx("can't append to stdout"); + goto cleanup_url_get; + } +#endif /* !SMALL */ + + if (!isfileurl && proxyenv != NULL) { /* use proxy */ +#ifndef NOSSL + if (ishttpsurl) { + sslpath = strdup(path); + sslhost = strdup(host); + if (! sslpath || ! sslhost) + errx(1, "Can't allocate memory for https path/host."); + } +#endif /* !NOSSL */ + proxyhost = strdup(host); + if (proxyhost == NULL) + errx(1, "Can't allocate memory for proxy host."); + proxyurl = strdup(proxyenv); + if (proxyurl == NULL) + errx(1, "Can't allocate memory for proxy URL."); + if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) + host = proxyurl + sizeof(HTTP_URL) - 1; + else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0) + host = proxyurl + sizeof(FTP_URL) - 1; + else { + warnx("Malformed proxy URL: %s", proxyenv); + goto cleanup_url_get; + } + if (EMPTYSTRING(host)) { + warnx("Malformed proxy URL: %s", proxyenv); + goto cleanup_url_get; + } + if (*--path == '\0') + *path = '/'; /* add / back to real path */ + path = strchr(host, '/'); /* remove trailing / on host */ + if (!EMPTYSTRING(path)) + *path++ = '\0'; /* i guess this ++ is useless */ + + path = strchr(host, '@'); /* look for credentials in proxy */ + if (!EMPTYSTRING(path)) { + *path = '\0'; + if (strchr(host, ':') == NULL) { + warnx("Malformed proxy URL: %s", proxyenv); + goto cleanup_url_get; + } + credentials = recode_credentials(host); + *path = '@'; /* restore @ in proxyurl */ + + /* + * This removes the password from proxyurl, + * filling with stars + */ + for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@'; + host++) + *host = '*'; + + host = path + 1; + } + + path = newline; + } + + if (isfileurl) { + struct stat st; + + fd = open(path, O_RDONLY); + if (fd == -1) { + warn("Can't open file %s", path); + goto cleanup_url_get; + } + + if (fstat(fd, &st) == -1) + filesize = -1; + else + filesize = st.st_size; + + /* Open the output file. */ + if (!pipeout) { +#ifndef SMALL + if (resume) + out = open(savefile, O_CREAT | O_WRONLY | + O_APPEND, 0666); + + else +#endif /* !SMALL */ + out = open(savefile, O_CREAT | O_WRONLY | + O_TRUNC, 0666); + if (out < 0) { + warn("Can't open %s", savefile); + goto cleanup_url_get; + } + } else + out = fileno(stdout); + +#ifndef SMALL + if (resume) { + if (fstat(out, &st) == -1) { + warn("Can't fstat %s", savefile); + goto cleanup_url_get; + } + if (lseek(fd, st.st_size, SEEK_SET) == -1) { + warn("Can't lseek %s", path); + goto cleanup_url_get; + } + restart_point = st.st_size; + } +#endif /* !SMALL */ + + /* Trap signals */ + oldintr = NULL; + oldinti = NULL; + if (setjmp(httpabort)) { + if (oldintr) + (void)signal(SIGINT, oldintr); + if (oldinti) + (void)signal(SIGINFO, oldinti); + goto cleanup_url_get; + } + oldintr = signal(SIGINT, abortfile); + + bytes = 0; + hashbytes = mark; + progressmeter(-1, path); + + if ((buf = malloc(buflen)) == NULL) + errx(1, "Can't allocate memory for transfer buffer"); + + /* Finally, suck down the file. */ + i = 0; + oldinti = signal(SIGINFO, psummary); + while ((len = read(fd, buf, buflen)) > 0) { + bytes += len; + for (cp = buf; len > 0; len -= i, cp += i) { + if ((i = write(out, cp, len)) == -1) { + warn("Writing %s", savefile); + signal(SIGINFO, oldinti); + goto cleanup_url_get; + } + else if (i == 0) + break; + } + if (hash && !progress) { + while (bytes >= hashbytes) { + (void)putc('#', ttyout); + hashbytes += mark; + } + (void)fflush(ttyout); + } + } + signal(SIGINFO, oldinti); + if (hash && !progress && bytes > 0) { + if (bytes < mark) + (void)putc('#', ttyout); + (void)putc('\n', ttyout); + (void)fflush(ttyout); + } + if (len != 0) { + warn("Reading from file"); + goto cleanup_url_get; + } + progressmeter(1, NULL); + if (verbose) + ptransfer(0); + (void)signal(SIGINT, oldintr); + + rval = 0; + goto cleanup_url_get; + } + + if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && + (hosttail[1] == '\0' || hosttail[1] == ':')) { + host++; + *hosttail++ = '\0'; +#ifndef SMALL + if (asprintf(&full_host, "[%s]", host) == -1) + errx(1, "Cannot allocate memory for hostname"); +#endif /* !SMALL */ + } else + hosttail = host; + + portnum = strrchr(hosttail, ':'); /* find portnum */ + if (portnum != NULL) + *portnum++ = '\0'; +#ifndef NOSSL + port = portnum ? portnum : (ishttpsurl ? httpsport : httpport); +#else /* !NOSSL */ + port = portnum ? portnum : httpport; +#endif /* !NOSSL */ + +#ifndef SMALL + if (full_host == NULL) + if ((full_host = strdup(host)) == NULL) + errx(1, "Cannot allocate memory for hostname"); + if (debug) + fprintf(ttyout, "host %s, port %s, path %s, " + "save as %s, auth %s.\n", host, port, path, + savefile, credentials ? credentials : "none"); +#endif /* !SMALL */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + /* + * If the services file is corrupt/missing, fall back + * on our hard-coded defines. + */ + if (error == EAI_SERVICE && port == httpport) { + snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT); + error = getaddrinfo(host, pbuf, &hints, &res0); +#ifndef NOSSL + } else if (error == EAI_SERVICE && port == httpsport) { + snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT); + error = getaddrinfo(host, pbuf, &hints, &res0); +#endif /* !NOSSL */ + } + if (error) { + warnx("%s: %s", host, gai_strerror(error)); + goto cleanup_url_get; + } + +#ifndef SMALL + if (srcaddr) { + hints.ai_flags |= AI_NUMERICHOST; + error = getaddrinfo(srcaddr, NULL, &hints, &ares); + if (error) { + warnx("%s: %s", srcaddr, gai_strerror(error)); + goto cleanup_url_get; + } + } +#endif /* !SMALL */ + + /* ensure consistent order of the output */ + if (verbose) + setvbuf(ttyout, NULL, _IOLBF, 0); + + fd = -1; + for (res = res0; res; res = res->ai_next) { + if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + strlcpy(hbuf, "(unknown)", sizeof(hbuf)); + if (verbose) + fprintf(ttyout, "Trying %s...\n", hbuf); + + fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd == -1) { + cause = "socket"; + continue; + } + +#ifndef SMALL + if (srcaddr) { + if (ares->ai_family != res->ai_family) { + close(fd); + fd = -1; + errno = EINVAL; + cause = "bind"; + continue; + } + if (bind(fd, ares->ai_addr, ares->ai_addrlen) < 0) { + save_errno = errno; + close(fd); + errno = save_errno; + fd = -1; + cause = "bind"; + continue; + } + } +#endif /* !SMALL */ + + if (connect_timeout) { + (void)signal(SIGALRM, tooslow); + alarmtimer(connect_timeout); + } + + for (error = connect(fd, res->ai_addr, res->ai_addrlen); + error != 0 && errno == EINTR; error = connect_wait(fd)) + continue; + if (error != 0) { + save_errno = errno; + close(fd); + errno = save_errno; + fd = -1; + cause = "connect"; + continue; + } + + /* get port in numeric */ + if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0, + pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0) + port = pbuf; + else + port = NULL; + +#ifndef NOSSL + if (proxyenv && sslhost) + proxy_connect(fd, sslhost, credentials); +#endif /* !NOSSL */ + break; + } + freeaddrinfo(res0); +#ifndef SMALL + if (srcaddr) + freeaddrinfo(ares); +#endif /* !SMALL */ + if (fd < 0) { + warn("%s", cause); + goto cleanup_url_get; + } + +#ifndef NOSSL + if (ishttpsurl) { + if (proxyenv && sslpath) { + ishttpsurl = 0; + proxyurl = NULL; + path = sslpath; + } + if (sslhost == NULL) { + sslhost = strdup(host); + if (sslhost == NULL) + errx(1, "Can't allocate memory for https host."); + } + if ((tls = tls_client()) == NULL) { + fprintf(ttyout, "failed to create SSL client\n"); + goto cleanup_url_get; + } + if (tls_configure(tls, tls_config) != 0) { + fprintf(ttyout, "SSL configuration failure: %s\n", + tls_error(tls)); + goto cleanup_url_get; + } + if (tls_connect_socket(tls, fd, sslhost) != 0) { + fprintf(ttyout, "SSL failure: %s\n", tls_error(tls)); + goto cleanup_url_get; + } + } else { + fin = fdopen(fd, "r+"); + fd = -1; + } +#else /* !NOSSL */ + fin = fdopen(fd, "r+"); + fd = -1; +#endif /* !NOSSL */ + +#ifdef SMALL + if (lastfile) { + if (pipeout) { + if (pledge("stdio rpath inet dns tty", NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1) + err(1, "pledge"); + } + } +#endif + + if (connect_timeout) { + signal(SIGALRM, SIG_DFL); + alarmtimer(0); + } + + /* + * Construct and send the request. Proxy requests don't want leading /. + */ +#ifndef NOSSL + cookie_get(host, path, ishttpsurl, &buf); +#endif /* !NOSSL */ + + epath = url_encode(path); + if (proxyurl) { + if (verbose) { + fprintf(ttyout, "Requesting %s (via %s)\n", + origline, proxyurl); + } + /* + * Host: directive must use the destination host address for + * the original URI (path). + */ + if (credentials) + ftp_printf(fin, tls, "GET %s HTTP/1.0\r\n" + "Proxy-Authorization: Basic %s\r\n" + "Host: %s\r\n%s%s\r\n\r\n", + epath, credentials, + proxyhost, buf ? buf : "", httpuseragent); + else + ftp_printf(fin, tls, "GET %s HTTP/1.0\r\n" + "Host: %s\r\n%s%s\r\n\r\n", + epath, proxyhost, buf ? buf : "", httpuseragent); + } else { + if (verbose) + fprintf(ttyout, "Requesting %s\n", origline); +#ifndef SMALL + if (resume) { + struct stat stbuf; + + if (stat(savefile, &stbuf) == 0) + restart_point = stbuf.st_size; + else + restart_point = 0; + } +#endif /* SMALL */ +#ifndef NOSSL + if (credentials) { + ftp_printf(fin, tls, + "GET /%s %s\r\nAuthorization: Basic %s\r\nHost: ", + epath, restart_point ? + "HTTP/1.1\r\nConnection: close" : "HTTP/1.0", + credentials); + free(credentials); + credentials = NULL; + } else +#endif /* NOSSL */ + ftp_printf(fin, tls, "GET /%s %s\r\nHost: ", epath, +#ifndef SMALL + restart_point ? "HTTP/1.1\r\nConnection: close" : +#endif /* !SMALL */ + "HTTP/1.0"); + if (proxyhost) { + ftp_printf(fin, tls, "%s", proxyhost); + port = NULL; + } else if (strchr(host, ':')) { + /* + * strip off scoped address portion, since it's + * local to node + */ + h = strdup(host); + if (h == NULL) + errx(1, "Can't allocate memory."); + if ((p = strchr(h, '%')) != NULL) + *p = '\0'; + ftp_printf(fin, tls, "[%s]", h); + free(h); + } else + ftp_printf(fin, tls, "%s", host); + + /* + * Send port number only if it's specified and does not equal + * 80. Some broken HTTP servers get confused if you explicitly + * send them the port number. + */ +#ifndef NOSSL + if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0) + ftp_printf(fin, tls, ":%s", port); + if (restart_point) + ftp_printf(fin, tls, "\r\nRange: bytes=%lld-", + (long long)restart_point); +#else /* !NOSSL */ + if (port && strcmp(port, "80") != 0) + ftp_printf(fin, tls, ":%s", port); +#endif /* !NOSSL */ + ftp_printf(fin, tls, "\r\n%s%s\r\n\r\n", + buf ? buf : "", httpuseragent); + } + free(epath); + +#ifndef NOSSL + free(buf); +#endif /* !NOSSL */ + buf = NULL; + + if (fin != NULL && fflush(fin) == EOF) { + warn("Writing HTTP request"); + goto cleanup_url_get; + } + if ((buf = ftp_readline(fin, tls, &len)) == NULL) { + warn("Receiving HTTP reply"); + goto cleanup_url_get; + } + + while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) + buf[--len] = '\0'; +#ifndef SMALL + if (debug) + fprintf(ttyout, "received '%s'\n", buf); +#endif /* !SMALL */ + + cp = strchr(buf, ' '); + if (cp == NULL) + goto improper; + else + cp++; + + strlcpy(ststr, cp, sizeof(ststr)); + status = strtonum(ststr, 200, 416, &errstr); + if (errstr) { + warnx("Error retrieving file: %s", cp); + goto cleanup_url_get; + } + + switch (status) { + case 200: /* OK */ +#ifndef SMALL + /* + * When we request a partial file, and we receive an HTTP 200 + * it is a good indication that the server doesn't support + * range requests, and is about to send us the entire file. + * If the restart_point == 0, then we are not actually + * requesting a partial file, and an HTTP 200 is appropriate. + */ + if (resume && restart_point != 0) { + warnx("Server does not support resume."); + restart_point = resume = 0; + } + /* FALLTHROUGH */ + case 206: /* Partial Content */ +#endif /* !SMALL */ + break; + case 301: /* Moved Permanently */ + case 302: /* Found */ + case 303: /* See Other */ + case 307: /* Temporary Redirect */ + isredirect++; + if (redirect_loop++ > 10) { + warnx("Too many redirections requested"); + goto cleanup_url_get; + } + break; +#ifndef SMALL + case 416: /* Requested Range Not Satisfiable */ + warnx("File is already fully retrieved."); + goto cleanup_url_get; +#endif /* !SMALL */ + default: + warnx("Error retrieving file: %s", cp); + goto cleanup_url_get; + } + + /* + * Read the rest of the header. + */ + free(buf); + filesize = -1; + + for (;;) { + if ((buf = ftp_readline(fin, tls, &len)) == NULL) { + warn("Receiving HTTP reply"); + goto cleanup_url_get; + } + + while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n')) + buf[--len] = '\0'; + if (len == 0) + break; +#ifndef SMALL + if (debug) + fprintf(ttyout, "received '%s'\n", buf); +#endif /* !SMALL */ + + /* Look for some headers */ + cp = buf; +#define CONTENTLEN "Content-Length: " + if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) { + size_t s; + cp += sizeof(CONTENTLEN) - 1; + if ((s = strcspn(cp, " \t"))) + *(cp+s) = 0; + filesize = strtonum(cp, 0, LLONG_MAX, &errstr); + if (errstr != NULL) + goto improper; +#ifndef SMALL + if (restart_point) + filesize += restart_point; +#endif /* !SMALL */ +#define LOCATION "Location: " + } else if (isredirect && + strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) { + cp += sizeof(LOCATION) - 1; + /* + * If there is a colon before the first slash, this URI + * is not relative. RFC 3986 4.2 + */ + if (cp[strcspn(cp, ":/")] != ':') { +#ifdef SMALL + errx(1, "Relative redirect not supported"); +#else /* SMALL */ + /* XXX doesn't handle protocol-relative URIs */ + if (*cp == '/') { + locbase = NULL; + cp++; + } else { + locbase = strdup(path); + if (locbase == NULL) + errx(1, "Can't allocate memory" + " for location base"); + loctail = strchr(locbase, '#'); + if (loctail != NULL) + *loctail = '\0'; + loctail = strchr(locbase, '?'); + if (loctail != NULL) + *loctail = '\0'; + loctail = strrchr(locbase, '/'); + if (loctail == NULL) { + free(locbase); + locbase = NULL; + } else + loctail[1] = '\0'; + } + /* Contruct URL from relative redirect */ + if (asprintf(&redirurl, "%s%s%s%s/%s%s", + scheme, full_host, + portnum ? ":" : "", + portnum ? portnum : "", + locbase ? locbase : "", + cp) == -1) + errx(1, "Cannot build " + "redirect URL"); + free(locbase); +#endif /* SMALL */ + } else if ((redirurl = strdup(cp)) == NULL) + errx(1, "Cannot allocate memory for URL"); + loctail = strchr(redirurl, '#'); + if (loctail != NULL) + *loctail = '\0'; + if (verbose) + fprintf(ttyout, "Redirected to %s\n", redirurl); + if (fin != NULL) { + fclose(fin); + fin = NULL; + } + if (fd != -1) { + close(fd); + fd = -1; + } + rval = url_get(redirurl, proxyenv, savefile, lastfile); + free(redirurl); + goto cleanup_url_get; + } + free(buf); + } + + /* Open the output file. */ + if (!pipeout) { +#ifndef SMALL + if (resume) + out = open(savefile, O_CREAT | O_WRONLY | O_APPEND, + 0666); + else +#endif /* !SMALL */ + out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, + 0666); + if (out < 0) { + warn("Can't open %s", savefile); + goto cleanup_url_get; + } + } else { + out = fileno(stdout); +#ifdef SMALL + if (lastfile) { + if (pledge("stdio tty", NULL) == -1) + err(1, "pledge"); + } +#endif + } + + /* Trap signals */ + oldintr = NULL; + oldinti = NULL; + if (setjmp(httpabort)) { + if (oldintr) + (void)signal(SIGINT, oldintr); + if (oldinti) + (void)signal(SIGINFO, oldinti); + goto cleanup_url_get; + } + oldintr = signal(SIGINT, aborthttp); + + bytes = 0; + hashbytes = mark; + progressmeter(-1, path); + + free(buf); + + /* Finally, suck down the file. */ + if ((buf = malloc(buflen)) == NULL) + errx(1, "Can't allocate memory for transfer buffer"); + i = 0; + len = 1; + oldinti = signal(SIGINFO, psummary); + while (len > 0) { + len = ftp_read(fin, tls, buf, buflen); + bytes += len; + for (cp = buf, wlen = len; wlen > 0; wlen -= i, cp += i) { + if ((i = write(out, cp, wlen)) == -1) { + warn("Writing %s", savefile); + signal(SIGINFO, oldinti); + goto cleanup_url_get; + } + else if (i == 0) + break; + } + if (hash && !progress) { + while (bytes >= hashbytes) { + (void)putc('#', ttyout); + hashbytes += mark; + } + (void)fflush(ttyout); + } + } + signal(SIGINFO, oldinti); + if (hash && !progress && bytes > 0) { + if (bytes < mark) + (void)putc('#', ttyout); + (void)putc('\n', ttyout); + (void)fflush(ttyout); + } + if (len != 0) { + warn("Reading from socket"); + goto cleanup_url_get; + } + progressmeter(1, NULL); + if ( +#ifndef SMALL + !resume && +#endif /* !SMALL */ + filesize != -1 && len == 0 && bytes != filesize) { + if (verbose) + fputs("Read short file.\n", ttyout); + goto cleanup_url_get; + } + + if (verbose) + ptransfer(0); + (void)signal(SIGINT, oldintr); + + rval = 0; + goto cleanup_url_get; + +noftpautologin: + warnx( + "Auto-login using ftp URLs isn't supported when using $ftp_proxy"); + goto cleanup_url_get; + +improper: + warnx("Improper response from %s", host); + +cleanup_url_get: +#ifndef NOSSL + if (tls != NULL) { + if (tls_session_fd != -1) + dprintf(STDERR_FILENO, "tls session resumed: %s\n", + tls_conn_session_resumed(tls) ? "yes" : "no"); + do { + i = tls_close(tls); + } while (i == TLS_WANT_POLLIN || i == TLS_WANT_POLLOUT); + tls_free(tls); + } + free(full_host); + free(sslhost); +#endif /* !NOSSL */ + if (fin != NULL) { + fclose(fin); + fin = NULL; + } + if (fd != -1) { + close(fd); + fd = -1; + } + if (out >= 0 && out != fileno(stdout)) + close(out); + free(buf); + free(proxyhost); + free(proxyurl); + free(newline); + free(credentials); + return (rval); +} + +/* + * Abort a http retrieval + */ +/* ARGSUSED */ +void +aborthttp(int signo) +{ + + alarmtimer(0); + fputs("\nhttp fetch aborted.\n", ttyout); + (void)fflush(ttyout); + longjmp(httpabort, 1); +} + +/* + * Abort a http retrieval + */ +/* ARGSUSED */ +void +abortfile(int signo) +{ + + alarmtimer(0); + fputs("\nfile fetch aborted.\n", ttyout); + (void)fflush(ttyout); + longjmp(httpabort, 1); +} + +/* + * Retrieve multiple files from the command line, transferring + * files of the form "host:path", "ftp://host/path" using the + * ftp protocol, and files of the form "http://host/path" using + * the http protocol. + * If path has a trailing "/", then return (-1); + * the path will be cd-ed into and the connection remains open, + * and the function will return -1 (to indicate the connection + * is alive). + * If an error occurs the return value will be the offset+1 in + * argv[] of the file that caused a problem (i.e, argv[x] + * returns x+1) + * Otherwise, 0 is returned if all files retrieved successfully. + */ +int +auto_fetch(int argc, char *argv[], char *outfile) +{ + char *xargv[5]; + char *cp, *url, *host, *dir, *file, *portnum; + char *username, *pass, *pathstart; + char *ftpproxy, *httpproxy; + int rval, xargc, lastfile; + volatile int argpos; + int dirhasglob, filehasglob, oautologin; + char rempath[PATH_MAX]; + + argpos = 0; + + if (setjmp(toplevel)) { + if (connected) + disconnect(0, NULL); + return (argpos + 1); + } + (void)signal(SIGINT, (sig_t)intr); + (void)signal(SIGPIPE, (sig_t)lostpeer); + + if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0') + ftpproxy = NULL; + if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0') + httpproxy = NULL; + + /* + * Loop through as long as there's files to fetch. + */ + username = pass = NULL; + for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) { + if (strchr(argv[argpos], ':') == NULL) + break; + + free(username); + free(pass); + host = dir = file = portnum = username = pass = NULL; + + lastfile = (argv[argpos+1] == NULL); + + /* + * We muck with the string, so we make a copy. + */ + url = strdup(argv[argpos]); + if (url == NULL) + errx(1, "Can't allocate memory for auto-fetch."); + + /* + * Try HTTP URL-style arguments first. + */ + if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || +#ifndef NOSSL + /* even if we compiled without SSL, url_get will check */ + strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0 || +#endif /* !NOSSL */ + strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) { + redirect_loop = 0; + if (url_get(url, httpproxy, outfile, lastfile) == -1) + rval = argpos + 1; + continue; + } + + /* + * Try FTP URL-style arguments next. If ftpproxy is + * set, use url_get() instead of standard ftp. + * Finally, try host:file. + */ + host = url; + if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) { + char *passend, *passagain, *userend; + + if (ftpproxy) { + if (url_get(url, ftpproxy, outfile, lastfile) == -1) + rval = argpos + 1; + continue; + } + host += sizeof(FTP_URL) - 1; + dir = strchr(host, '/'); + + /* Look for [user:pass@]host[:port] */ + + /* check if we have "user:pass@" */ + userend = strchr(host, ':'); + passend = strchr(host, '@'); + if (passend && userend && userend < passend && + (!dir || passend < dir)) { + username = host; + pass = userend + 1; + host = passend + 1; + *userend = *passend = '\0'; + passagain = strchr(host, '@'); + if (strchr(pass, '@') != NULL || + (passagain != NULL && passagain < dir)) { + warnx(at_encoding_warning); + username = pass = NULL; + goto bad_ftp_url; + } + + if (EMPTYSTRING(username)) { +bad_ftp_url: + warnx("Invalid URL: %s", argv[argpos]); + rval = argpos + 1; + username = pass = NULL; + continue; + } + username = urldecode(username); + pass = urldecode(pass); + } + + /* check [host]:port, or [host] */ + if (host[0] == '[') { + cp = strchr(host, ']'); + if (cp && (!dir || cp < dir)) { + if (cp + 1 == dir || cp[1] == ':') { + host++; + *cp++ = '\0'; + } else + cp = NULL; + } else + cp = host; + } else + cp = host; + + /* split off host[:port] if there is */ + if (cp) { + portnum = strchr(cp, ':'); + pathstart = strchr(cp, '/'); + /* : in path is not a port # indicator */ + if (portnum && pathstart && + pathstart < portnum) + portnum = NULL; + + if (!portnum) + ; + else { + if (!dir) + ; + else if (portnum + 1 < dir) { + *portnum++ = '\0'; + /* + * XXX should check if portnum + * is decimal number + */ + } else { + /* empty portnum */ + goto bad_ftp_url; + } + } + } else + portnum = NULL; + } else { /* classic style `host:file' */ + dir = strchr(host, ':'); + } + if (EMPTYSTRING(host)) { + rval = argpos + 1; + continue; + } + + /* + * If dir is NULL, the file wasn't specified + * (URL looked something like ftp://host) + */ + if (dir != NULL) + *dir++ = '\0'; + + /* + * Extract the file and (if present) directory name. + */ + if (!EMPTYSTRING(dir)) { + cp = strrchr(dir, '/'); + if (cp != NULL) { + *cp++ = '\0'; + file = cp; + } else { + file = dir; + dir = NULL; + } + } +#ifndef SMALL + if (debug) + fprintf(ttyout, + "user %s:%s host %s port %s dir %s file %s\n", + username, pass ? "XXXX" : NULL, host, portnum, + dir, file); +#endif /* !SMALL */ + + /* + * Set up the connection. + */ + if (connected) + disconnect(0, NULL); + xargv[0] = __progname; + xargv[1] = host; + xargv[2] = NULL; + xargc = 2; + if (!EMPTYSTRING(portnum)) { + xargv[2] = portnum; + xargv[3] = NULL; + xargc = 3; + } + oautologin = autologin; + if (username == NULL) + anonftp = 1; + else { + anonftp = 0; + autologin = 0; + } + setpeer(xargc, xargv); + autologin = oautologin; + if (connected == 0 || + (connected == 1 && autologin && (username == NULL || + !ftp_login(host, username, pass)))) { + warnx("Can't connect or login to host `%s'", host); + rval = argpos + 1; + continue; + } + + /* Always use binary transfers. */ + setbinary(0, NULL); + + dirhasglob = filehasglob = 0; + if (doglob) { + if (!EMPTYSTRING(dir) && + strpbrk(dir, "*?[]{}") != NULL) + dirhasglob = 1; + if (!EMPTYSTRING(file) && + strpbrk(file, "*?[]{}") != NULL) + filehasglob = 1; + } + + /* Change directories, if necessary. */ + if (!EMPTYSTRING(dir) && !dirhasglob) { + xargv[0] = "cd"; + xargv[1] = dir; + xargv[2] = NULL; + cd(2, xargv); + if (!dirchange) { + rval = argpos + 1; + continue; + } + } + + if (EMPTYSTRING(file)) { +#ifndef SMALL + rval = -1; +#else /* !SMALL */ + recvrequest("NLST", "-", NULL, "w", 0, 0); + rval = 0; +#endif /* !SMALL */ + continue; + } + + if (verbose) + fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file); + + if (dirhasglob) { + snprintf(rempath, sizeof(rempath), "%s/%s", dir, file); + file = rempath; + } + + /* Fetch the file(s). */ + xargc = 2; + xargv[0] = "get"; + xargv[1] = file; + xargv[2] = NULL; + if (dirhasglob || filehasglob) { + int ointeractive; + + ointeractive = interactive; + interactive = 0; + xargv[0] = "mget"; +#ifndef SMALL + if (resume) { + xargc = 3; + xargv[1] = "-c"; + xargv[2] = file; + xargv[3] = NULL; + } +#endif /* !SMALL */ + mget(xargc, xargv); + interactive = ointeractive; + } else { + if (outfile != NULL) { + xargv[2] = outfile; + xargv[3] = NULL; + xargc++; + } +#ifndef SMALL + if (resume) + reget(xargc, xargv); + else +#endif /* !SMALL */ + get(xargc, xargv); + } + + if ((code / 100) != COMPLETE) + rval = argpos + 1; + } + if (connected && rval != -1) + disconnect(0, NULL); + return (rval); +} + +char * +urldecode(const char *str) +{ + char *ret, c; + int i, reallen; + + if (str == NULL) + return NULL; + if ((ret = malloc(strlen(str)+1)) == NULL) + err(1, "Can't allocate memory for URL decoding"); + for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) { + c = str[i]; + if (c == '+') { + *ret = ' '; + continue; + } + + /* Cannot use strtol here because next char + * after %xx may be a digit. + */ + if (c == '%' && isxdigit((unsigned char)str[i+1]) && + isxdigit((unsigned char)str[i+2])) { + *ret = hextochar(&str[i+1]); + i+=2; + continue; + } + *ret = c; + } + *ret = '\0'; + + return ret-reallen; +} + +char * +recode_credentials(const char *userinfo) +{ + char *ui, *creds; + size_t ulen, credsize; + + /* url-decode the user and pass */ + ui = urldecode(userinfo); + + ulen = strlen(ui); + credsize = (ulen + 2) / 3 * 4 + 1; + creds = malloc(credsize); + if (creds == NULL) + errx(1, "out of memory"); + if (b64_ntop(ui, ulen, creds, credsize) == -1) + errx(1, "error in base64 encoding"); + free(ui); + return (creds); +} + +char +hextochar(const char *str) +{ + unsigned char c, ret; + + c = str[0]; + ret = c; + if (isalpha(c)) + ret -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + ret -= '0'; + ret *= 16; + + c = str[1]; + ret += c; + if (isalpha(c)) + ret -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + ret -= '0'; + return ret; +} + +int +isurl(const char *p) +{ + + if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 || + strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || +#ifndef NOSSL + strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 || +#endif /* !NOSSL */ + strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 || + strstr(p, ":/")) + return (1); + return (0); +} + +char * +ftp_readline(FILE *fp, struct tls *tls, size_t *lenp) +{ + if (fp != NULL) + return fparseln(fp, lenp, NULL, "\0\0\0", 0); +#ifndef NOSSL + else if (tls != NULL) + return SSL_readline(tls, lenp); +#endif /* !NOSSL */ + else + return NULL; +} + +size_t +ftp_read(FILE *fp, struct tls *tls, char *buf, size_t len) +{ +#ifndef NOSSL + ssize_t tret; +#endif + size_t ret = 0; + + if (fp != NULL) + ret = fread(buf, sizeof(char), len, fp); +#ifndef NOSSL + else if (tls != NULL) { + do { + tret = tls_read(tls, buf, len); + } while (tret == TLS_WANT_POLLIN || tret == TLS_WANT_POLLOUT); + if (tret < 0) + errx(1, "SSL read error: %s", tls_error(tls)); + ret = (size_t)tret; + } +#endif /* !NOSSL */ + return (ret); +} + +int +ftp_printf(FILE *fp, struct tls *tls, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + + if (fp != NULL) + ret = vfprintf(fp, fmt, ap); +#ifndef NOSSL + else if (tls != NULL) + ret = SSL_vprintf(tls, fmt, ap); +#endif /* !NOSSL */ + else + ret = 0; + + va_end(ap); +#ifndef SMALL + if (debug) { + va_start(ap, fmt); + ret = vfprintf(ttyout, fmt, ap); + va_end(ap); + } +#endif /* !SMALL */ + return (ret); +} + +#ifndef NOSSL +int +SSL_vprintf(struct tls *tls, const char *fmt, va_list ap) +{ + char *string, *buf; + size_t len; + int ret; + + if ((ret = vasprintf(&string, fmt, ap)) == -1) + return ret; + buf = string; + len = ret; + while (len > 0) { + ret = tls_write(tls, buf, len); + if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) + continue; + if (ret < 0) + errx(1, "SSL write error: %s", tls_error(tls)); + buf += ret; + len -= ret; + } + free(string); + return ret; +} + +char * +SSL_readline(struct tls *tls, size_t *lenp) +{ + size_t i, len; + char *buf, *q, c; + int ret; + + len = 128; + if ((buf = malloc(len)) == NULL) + errx(1, "Can't allocate memory for transfer buffer"); + for (i = 0; ; i++) { + if (i >= len - 1) { + if ((q = reallocarray(buf, len, 2)) == NULL) + errx(1, "Can't expand transfer buffer"); + buf = q; + len *= 2; + } + do { + ret = tls_read(tls, &c, 1); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + if (ret < 0) + errx(1, "SSL read error: %s", tls_error(tls)); + + buf[i] = c; + if (c == '\n') { + buf[i] = '\0'; + break; + } + } + *lenp = i; + return (buf); +} + +int +proxy_connect(int socket, char *host, char *cookie) +{ + int l; + char buf[1024]; + char *connstr, *hosttail, *port; + + if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && + (hosttail[1] == '\0' || hosttail[1] == ':')) { + host++; + *hosttail++ = '\0'; + } else + hosttail = host; + + port = strrchr(hosttail, ':'); /* find portnum */ + if (port != NULL) + *port++ = '\0'; + if (!port) + port = "443"; + + if (cookie) { + l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n" + "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n", + host, port, cookie, HTTP_USER_AGENT); + } else { + l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n%s\r\n\r\n", + host, port, HTTP_USER_AGENT); + } + + if (l == -1) + errx(1, "Could not allocate memory to assemble connect string!"); +#ifndef SMALL + if (debug) + printf("%s", connstr); +#endif /* !SMALL */ + if (write(socket, connstr, l) != l) + err(1, "Could not send connect string"); + read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */ + free(connstr); + return(200); +} +#endif /* !NOSSL */ diff --git a/usr.bin/ftp/file.c b/usr.bin/ftp/file.c deleted file mode 100644 index 63571e18352..00000000000 --- a/usr.bin/ftp/file.c +++ /dev/null @@ -1,57 +0,0 @@ -/* $OpenBSD: file.c,v 1.2 2019/05/12 20:58:19 jasper Exp $ */ - -/* - * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <sys/stat.h> - -#include <err.h> -#include <fcntl.h> -#include <stdio.h> - -#include "ftp.h" - -struct imsgbuf; - -static FILE *src_fp; - -struct url * -file_request(struct imsgbuf *ibuf, struct url *url, off_t *offset, off_t *sz) -{ - struct stat sb; - int src_fd; - - if ((src_fd = fd_request(url->path, O_RDONLY, NULL)) == -1) - err(1, "Can't open file %s", url->path); - - if (fstat(src_fd, &sb) == 0) - *sz = sb.st_size; - - if ((src_fp = fdopen(src_fd, "r")) == NULL) - err(1, "%s: fdopen", __func__); - - if (*offset && fseeko(src_fp, *offset, SEEK_SET) == -1) - err(1, "%s: fseeko", __func__); - - return url; -} - -void -file_save(struct url *url, FILE *dst_fp, off_t *offset) -{ - copy_file(dst_fp, src_fp, offset); - fclose(src_fp); -} diff --git a/usr.bin/ftp/ftp.1 b/usr.bin/ftp/ftp.1 index 32d90c81202..ed8d7e0c90f 100644 --- a/usr.bin/ftp/ftp.1 +++ b/usr.bin/ftp/ftp.1 @@ -1,4 +1,5 @@ -.\" $OpenBSD: ftp.1,v 1.114 2019/05/15 11:53:22 kmos Exp $ +.\" $OpenBSD: ftp.1,v 1.115 2019/05/16 12:44:17 florian Exp $ +.\" $NetBSD: ftp.1,v 1.22 1997/08/18 10:20:22 lukem Exp $ .\" .\" Copyright (c) 1985, 1989, 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,39 +30,57 @@ .\" .\" @(#)ftp.1 8.3 (Berkeley) 10/9/94 .\" -.\" Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org> -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: May 15 2019 $ +.Dd $Mdocdate: May 16 2019 $ .Dt FTP 1 .Os .Sh NAME .Nm ftp .Nd Internet file transfer program .Sh SYNOPSIS -.Nm -.Op Fl 46AVv +.Nm ftp +.Op Fl 46AadEegiMmnptVv .Op Fl D Ar title +.Op Fl k Ar seconds +.Op Fl P Ar port +.Op Fl r Ar seconds +.Op Fl s Ar srcaddr .Op Ar host Op Ar port -.Nm -.Op Fl 46ACMmVv -.Op Fl D Ar title +.Nm ftp +.Op Fl C +.Op Fl o Ar output +.Op Fl s Ar srcaddr +.Sm off +.Pf ftp:// Op Ar user : password No @ +.Ar host Op : Ar port +.No / Ar file Op / +.Sm on +.Ar ... +.Nm ftp +.Op Fl C +.Op Fl c Ar cookie .Op Fl o Ar output -.Op Fl S Ar tls_options +.Op Fl S Ar ssl_options +.Op Fl s Ar srcaddr .Op Fl U Ar useragent .Op Fl w Ar seconds -.Ar url ... +.Sm off +.Pf http Oo s Oc :// +.Op Ar user : password No @ +.Ar host Op : Ar port +.No / Ar file +.Sm on +.Ar ... +.Nm ftp +.Op Fl C +.Op Fl o Ar output +.Op Fl s Ar srcaddr +.Pf file: Ar +.Nm ftp +.Op Fl C +.Op Fl o Ar output +.Op Fl s Ar srcaddr +.Ar host : Ns / Ns Ar file Ns Op / +.Ar ... .Sh DESCRIPTION .Nm is the user interface to the Internet standard File Transfer @@ -69,8 +88,8 @@ Protocol (FTP). The program allows a user to transfer files to and from a remote network site. .Pp -The latter usage format will fetch a file using either the -FTP, HTTP or HTTPS protocols into the current directory. +The latter four usage formats will fetch a file using either the +FTP, HTTP, or HTTPS protocols into the current directory. This is ideal for scripts. Refer to .Sx AUTO-FETCHING FILES @@ -85,7 +104,7 @@ to use IPv4 addresses only. .It Fl 6 Forces .Nm -to use IPv6 addreses only. +to use IPv6 addresses only. .It Fl A Force active mode FTP. By default, @@ -97,38 +116,125 @@ This option causes to always use an active connection. It is only useful for connecting to very old servers that do not implement passive mode properly. +.It Fl a +Causes +.Nm +to bypass the normal login procedure and use an anonymous login instead. .It Fl C Continue a previously interrupted file transfer. .Nm -will continue transferring from an offset equal to the length of file. +will continue transferring from an offset equal to the length of +.Ar file . .Pp -Resuming HTTP(S) transfers are only supported if the remote server supports the +Resuming HTTP(S) transfers are only supported +if the remote server supports the .Dq Range header. +.It Fl c Ar cookie +Load a Netscape-like cookiejar file +for HTTP and HTTPS transfers. +With this option relevant cookies from the jar are sent with each HTTP(S) +request. +Setting the +.Ev http_cookies +environment variable has the same effect. +If both the +.Ev http_cookies +environment variable is set and the +.Fl c +argument is given, the latter takes precedence. .It Fl D Ar title -Specify a short title for the start of the progress bar. +Specify a short +.Ar title +for the start of the progress bar. +.It Fl d +Enables debugging. +.It Fl E +Disables EPSV/EPRT command on IPv4 connections. +.It Fl e +Disables command line editing. +Useful for Emacs ange-ftp. +.It Fl g +Disables file name globbing. +.It Fl i +Turns off interactive prompting during +multiple file transfers. +.It Fl k Ar seconds +When greater than zero, +sends a byte after each +.Ar seconds +period over the control connection during long transfers, +so that incorrectly configured network equipment won't +aggressively drop it. +The FTP protocol supports a +.Dv NOOP +command that can be used for that purpose. +This assumes the FTP server can deal with extra commands coming over +the control connection during a transfer. +Well-behaved servers queue those commands, and process them after the +transfer. +By default, +.Nm +will send a byte every 60 seconds. .It Fl M Causes .Nm -to never display the progress meter in cases where it would do so by default. +to never display the progress meter in cases where it would do +so by default. .It Fl m Causes .Nm -to display the progress meter in cases where it would not do so by default. +to always display the progress meter in cases where it would not do +so by default. +.It Fl n +Restrains +.Nm +from attempting +.Dq auto-login +upon initial connection. +If auto-login is enabled, +.Nm +will check the +.Pa .netrc +file (see below) in the user's home directory for an entry describing +an account on the remote machine. +If no entry exists, +.Nm +will prompt for the remote machine login name (default is the user +identity on the local machine) and, if necessary, prompt for a password +and an account with which to log in. .It Fl o Ar output -When fetching a file or URL, save the contents in +When fetching a single file or URL, save the contents in .Ar output . -To make the contents go to stdout, use `-' for +To make the contents go to stdout, +use +.Sq - +for .Ar output . -.It Fl S Ar tls_options -TLS options to use with HTTPS transfers. +.It Fl P Ar port +Sets the port number to +.Ar port . +.It Fl p +Enable passive mode operation for use behind connection filtering firewalls. +This option has been deprecated as +.Nm +now tries to use passive mode by default, falling back to active mode +if the server does not support passive connections. +.It Fl r Ar seconds +Retry to connect if failed, pausing for number of +.Ar seconds . +.It Fl S Ar ssl_options +SSL/TLS options to use with HTTPS transfers. The following settings are available: .Bl -tag -width Ds .It Cm cafile Ns = Ns Ar /path/to/cert.pem -PEM encoded file containing CA certificates used for certificate validation. +PEM encoded file containing CA certificates used for certificate +validation. .It Cm capath Ns = Ns Ar /path/to/certs/ Directory containing PEM encoded CA certificates used for certificate validation. +Such a directory can be prepared using the c_rehash script distributed with +OpenSSL. .It Cm ciphers Ns = Ns Ar cipher_list Specify the list of ciphers that will be used by .Nm . @@ -137,17 +243,12 @@ See the .Cm ciphers subcommand. .It Cm depth Ns = Ns Ar max_depth -Maximum depth of the certificate chain allowed when performing validation. +Maximum depth of the certificate chain allowed when performing +validation. +.It Cm do +Perform server certificate validation. .It Cm dont Don't perform server certificate validation. -.It Cm protocols Ns = Ns Ar string -Specify the TLS protocols to use. -If not specified the value -.Qq all -is used. -Refer to the -.Xr tls_config_parse_protocols 3 -function for other valid protocol string values. .It Cm muststaple Require the server to present a valid OCSP stapling in the TLS handshake. .It Cm noverifytime @@ -156,8 +257,8 @@ Disable validation of certificate times and OCSP validation. Specify a file to use for TLS session data. If this file has a non-zero length, the session data will be read from this file and the client will attempt to resume the TLS session with the server. -Upon completion of a successful TLS handshake this file will be updated with -new session data, if available. +Upon completion of a successful TLS handshake this file will be updated +with new session data, if available. This file will be created if it does not already exist. .El .Pp @@ -171,6 +272,14 @@ or setting is provided, .Pa /etc/ssl/cert.pem will be used. +.It Fl s Ar srcaddr +Use +.Ar srcaddr +on the local machine as the source address +of the connection. +Only useful on systems with more than one address. +.It Fl t +Enables packet tracing. .It Fl U Ar useragent Set .Ar useragent @@ -178,15 +287,18 @@ as the User-Agent for HTTP(S) URL requests. If not specified, the default User-Agent is .Dq OpenBSD ftp . .It Fl V -Disable verbose mode. +Disable verbose mode, overriding the default of enabled when input +is from a terminal. .It Fl v -Enable verbose mode. This is the default if input is from a terminal. +Enable verbose mode. +This is the default if input is from a terminal. Forces .Nm -to show all responses from the remote server, as well as report on data -transfer statistics. +to show all responses from the remote server, as well +as report on data transfer statistics. .It Fl w Ar seconds -Abort a slow connection after +For URL format connections to HTTP/HTTPS servers, abort a +slow connection after .Ar seconds . .El .Pp @@ -209,32 +321,251 @@ The following commands are recognized by .Nm : .Bl -tag -width Fl -.It Ic open Ar host Op Ar port -Establish a connection to the specified -.Ar host -FTP server. -An optional port number may be supplied, -in which case -.Nm -will attempt to contact an FTP server at that port. +.It Ic \&! Oo Ar command +.Op Ar arg ... +.Oc +Invoke an interactive shell on the local machine. +If there are arguments, the first is taken to be a command to execute +directly, with the rest of the arguments as its arguments. +.It Ic \&$ Ar macro-name Op Ar arg ... +Execute the macro +.Ar macro-name +that was defined with the +.Ic macdef +command. +Arguments are passed to the macro unglobbed. +.It Ic \&? Op Ar command +A synonym for +.Ic help . +.It Ic account Op Ar password +Supply a supplemental password required by a remote system for access +to resources once a login has been successfully completed. +If no argument is included, the user will be prompted for an account +password in a non-echoing input mode. +.It Ic append Ar local-file Op Ar remote-file +Append a local file to a file on the remote machine. +If +.Ar remote-file +is left unspecified, the local file name is used in naming the +remote file after being altered by any +.Ic ntrans +or +.Ic nmap +setting. +File transfer uses the current settings for +.Ic type , +.Ic format , +.Ic mode , +and +.Ic structure . +.It Ic ascii +Set the file transfer +.Ic type +to network +.Tn ASCII . +.It Ic bell Op Ic on | off +Arrange that a bell be sounded after each file transfer +command is completed. +.It Ic binary +Set the file transfer +.Ic type +to support binary image transfer. +This is the default type. +.It Ic bye +Terminate the FTP session with the remote server and exit +.Nm . +An end-of-file will also terminate the session and exit. +.It Ic case Op Ic on | off +Toggle remote computer file name case mapping during +.Ic mget +commands. +When +.Ic case +is on (default is off), remote computer file names with all letters in +upper case are written in the local directory with the letters mapped +to lower case. +.It Ic cd Ar remote-directory +Change the working directory on the remote machine +to +.Ar remote-directory . +.It Ic cdup +Change the remote machine working directory to the parent of the +current remote machine working directory. +.It Ic chmod Ar mode file +Change the permission modes of +.Ar file +on the remote +system to +.Ar mode . .It Ic close Terminate the FTP session with the remote server and return to the command interpreter. +Any defined macros are erased. +.It Ic cr Op Ic on | off +Toggle carriage return stripping during +ASCII type file retrieval. +Records are denoted by a carriage return/linefeed sequence +during ASCII type file transfer. +When +.Ic cr +is on (the default), carriage returns are stripped from this +sequence to conform with the +.Ux +single linefeed record delimiter. +Records on non-UNIX +remote systems may contain single linefeeds; +when an ASCII type transfer is made, these linefeeds may be +distinguished from a record delimiter only when +.Ic cr +is off. +.It Ic debug Oo Ic on | off | +.Ar debuglevel +.Oc +Toggle debugging mode. +If an optional +.Ar debuglevel +is specified, it is used to set the debugging level. +When debugging is on, +.Nm +prints each command sent to the remote machine, +preceded by the string +.Ql --\*(Gt . +.It Ic delete Ar remote-file +Delete the file +.Ar remote-file +on the remote machine. +.It Ic dir Op Ar remote-directory Op Ar local-file +A synonym for +.Ic ls . +.It Ic disconnect +A synonym for +.Ic close . +.It Ic edit Op Ic on | off +Toggle command line editing, and context sensitive command and file +completion. +This is automatically enabled if input is from a terminal, and +disabled otherwise. +.It Ic epsv4 Op Ic on | off +Toggle use of EPSV/EPRT command on IPv4 connection. +.It Ic exit +A synonym for +.Ic bye . +.It Ic form Ar format +Set the file transfer +.Ic form +to +.Ar format . +The default format is +.Dq file . +.It Ic ftp Ar host Op Ar port +A synonym for +.Ic open . +.It Ic gate Oo Ic on | off | +.Ar host Op Ar port +.Oc +Toggle gate-ftp mode. +This will not be permitted if the gate-ftp server hasn't been set +(either explicitly by the user, or from the +.Ev FTPSERVER +environment variable). +If +.Ar host +is given, +then gate-ftp mode will be enabled, and the gate-ftp server will be set to +.Ar host . +If +.Ar port +is also given, that will be used as the port to connect to on the +gate-ftp server. +.It Ic get Ar remote-file Op Ar local-file +Retrieve the +.Ar remote-file +and store it on the local machine. +If the local +file name is not specified, it is given the same +name it has on the remote machine, subject to +alteration by the current +.Ic case , +.Ic ntrans , +and +.Ic nmap +settings. +The current settings for +.Ic type , +.Ic form , +.Ic mode , +and +.Ic structure +are used while transferring the file. +.It Ic glob Op Ic on | off +Toggle filename expansion for +.Ic mdelete , +.Ic mget +and +.Ic mput . +If globbing is turned off with +.Ic glob , +the file name arguments +are taken literally and not expanded. +Globbing for +.Ic mput +is done as in +.Xr csh 1 . +For +.Ic mdelete +and +.Ic mget , +each remote file name is expanded +separately on the remote machine and the lists are not merged. +Expansion of a directory name is likely to be +different from expansion of the name of an ordinary file: +the exact result depends on the foreign operating system and FTP server, +and can be previewed by doing +.Dq mls remote-files - . +Note: +.Ic mget +and +.Ic mput +are not meant to transfer +entire directory subtrees of files. +That can be done by +transferring a +.Xr tar 1 +archive of the subtree (in binary mode). +.It Ic hash Oo Ic on | off | +.Ar size +.Oc +Toggle hash mark +.Pq Ql # +printing for each data block transferred. +The size of a data block defaults to 1024 bytes. +This can be changed by specifying +.Ar size +in bytes. .It Ic help Op Ar command Print an informative message about the meaning of .Ar command . If no argument is given, .Nm prints a list of the known commands. -.It Ic \&? Op Ar command -A synonym for -.Ic help . -.It Ic quit -Terminate the FTP session with the remote server and exit -.Nm . -.It Ic exit +.It Ic idle Op Ar seconds +Set the inactivity timer on the remote server to +.Ar seconds +seconds. +If +.Ar seconds +is omitted, the current inactivity timer is printed. +.It Ic lcd Op Ar local-directory +Change the working directory on the local machine. +If +no +.Ar local-directory +is specified, the user's home directory is used. +.It Ic less Ar file A synonym for -.Ic quit . +.Ic page . +.It Ic lpwd +Print the working directory on the local machine. .It Ic ls Op Ar remote-directory Op Ar local-file Print a listing of the contents of a directory on the remote machine. The listing includes any system-dependent information that the server @@ -245,17 +576,211 @@ systems will produce output from the command If .Ar remote-directory is left unspecified, the current working directory is used. +If interactive prompting is on, +.Nm +will prompt the user to verify that the last argument is indeed the +target local file for receiving +.Ic ls +output. If no local file is specified, or if .Ar local-file is .Sq - , the output is sent to the terminal. +.It Ic macdef Ar macro-name +Define a macro. +Subsequent lines are stored as the macro +.Ar macro-name ; +a null line (consecutive newline characters +in a file or +carriage returns from the terminal) terminates macro input mode. +There is a limit of 16 macros and 4096 total characters in all +defined macros. +Macro names can be a maximum of 8 characters. +Macros are only applicable to the current session they are +defined in (or if defined outside a session, to the session +invoked with the next +.Ic open +command), and remain defined until a +.Ic close +command is executed. +To invoke a macro, +use the +.Ic $ +command (see above). +.Pp +The macro processor interprets +.Ql $ +and +.Ql \e +as special characters. +A +.Ql $ +followed by a number (or numbers) is replaced by the +corresponding argument on the macro invocation command line. +A +.Ql $ +followed by an +.Sq i +tells the macro processor that the +executing macro is to be looped. +On the first pass +.Ql $i +is +replaced by the first argument on the macro invocation command line, +on the second pass it is replaced by the second argument, and so on. +A +.Ql \e +followed by any character is replaced by that character. +Use the +.Ql \e +to prevent special treatment of the +.Ql $ . +.It Ic mdelete Op Ar remote-files +Delete the +.Ar remote-files +on the remote machine. +.It Ic mdir Ar remote-files local-file +A synonym for +.Ic mls . +.It Xo Ic mget +.Op Fl cnr +.Op Fl d Ar depth +.Ar remote-files +.Xc +Expand the +.Ar remote-files +on the remote machine +and do a +.Ic get +for each file name thus produced. +See +.Ic glob +for details on the filename expansion. +Resulting file names will then be processed according to +.Ic case , +.Ic ntrans , +and +.Ic nmap +settings. +Files are transferred into the local working directory, +which can be changed with +.Ql lcd directory ; +new local directories can be created with +.Ql "\&! mkdir directory" . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c +Use +.Ic reget +instead of +.Ic get . +.It Fl d Ar depth +Specify the maximum recursion level +.Ar depth . +The default is 0, which means unlimited. +.It Fl n +Use +.Ic newer +instead of +.Ic get . +.It Fl r +Recursively descend the directory tree, transferring all files and +directories. +.El +.It Ic mkdir Ar directory-name +Make a directory on the remote machine. +.It Ic mls Ar remote-files local-file +Like +.Ic ls , +except multiple remote files may be specified, +and the +.Ar local-file +must be specified. +If interactive prompting is on, +.Nm +will prompt the user to verify that the last argument is indeed the +target local file for receiving +.Ic mls +output. +.It Ic mode Op Ar mode-name +Set the file transfer +.Ic mode +to +.Ar mode-name . +The default mode is +.Dq stream +mode. +.It Ic modtime Ar file +Show the last modification time of +.Ar file +on the remote machine. +.It Ic more Ar file +A synonym for +.Ic page . +.It Xo Ic mput +.Op Fl cr +.Op Fl d Ar depth +.Ar local-files +.Xc +Expand wild cards in the list of local files given as arguments +and do a +.Ic put +for each file in the resulting list. +See +.Ic glob +for details of filename expansion. +Resulting file names will then be processed according to +.Ic ntrans +and +.Ic nmap +settings. +.Pp +If the +.Fl c +flag is specified then +The options are as follows: +.Bl -tag -width Ds +.It Fl c +Use +.Ic reput +instead of +.Ic put . +.It Fl d Ar depth +Specify the maximum recursion level +.Ar depth . +The default is 0, which means unlimited. +.It Fl r +Recursively descend the directory tree, transferring all files and +directories. +.El +.It Xo Ic msend +.Op Fl c +.Ar local-files +.Xc +A synonym for +.Ic mput . +.It Ic newer Ar remote-file Op Ar local-file +Get the file only if the modification time of the remote file is more +recent than the file on the current system. +If the file does not +exist on the current system, the remote file is considered +.Ic newer . +Otherwise, this command is identical to +.Ar get . .It Ic nlist Op Ar remote-directory Op Ar local-file Print a list of the files in a directory on the remote machine. If .Ar remote-directory is left unspecified, the current working directory is used. +If interactive prompting is on, +.Nm +will prompt the user to verify that the last argument is indeed the +target local file for receiving +.Ic nlist +output. If no local file is specified, or if .Ar local-file is @@ -265,20 +790,163 @@ Note that on some servers, the .Ic nlist command will only return information on normal files (not directories or special files). -.It Ic pwd -Print the name of the current working directory on the remote -machine. -.It Ic cd Ar remote-directory -Change the working directory on the remote machine -to -.Ar remote-directory . -.It Ic get Ar remote-file Op Ar local-file -Retrieve the -.Ar remote-file -and store it on the local machine. -If the local -file name is not specified, it is given the same -name it has on the remote machine. +.It Ic nmap Op Ar inpattern outpattern +Set or unset the filename mapping mechanism. +If no arguments are specified, the filename mapping mechanism is unset. +If arguments are specified, remote filenames are mapped during +.Ic mput +commands and +.Ic put +commands issued without a specified remote target filename. +If arguments are specified, local filenames are mapped during +.Ic mget +commands and +.Ic get +commands issued without a specified local target filename. +This command is useful when connecting to a non-UNIX remote computer +with different file naming conventions or practices. +.Pp +The mapping follows the pattern set by +.Ar inpattern +and +.Ar outpattern . +.Ar inpattern +is a template for incoming filenames (which may have already been +processed according to the +.Ic ntrans +and +.Ic case +settings). +Variable templating is accomplished by including the +sequences +.Ql $1 , +.Ql $2 , +\&..., +.Ql $9 +in +.Ar inpattern . +Use +.Ql \e +to prevent this special treatment of the +.Ql $ +character. +All other characters are treated literally, and are used to determine the +.Ic nmap +.Ar inpattern +variable values. +.Pp +For example, given +.Ar inpattern +$1.$2 and the remote file name "mydata.data", $1 would have the value +"mydata", and $2 would have the value "data". +The +.Ar outpattern +determines the resulting mapped filename. +The sequences +.Ql $1 , +.Ql $2 , +\&..., +.Ql $9 +are replaced by any value resulting from the +.Ar inpattern +template. +The sequence +.Ql $0 +is replaced by the original filename. +Additionally, the sequence +.Sq Op Ar seq1 , Ar seq2 +is replaced by +.Ar seq1 +if +.Ar seq1 +is not a null string; otherwise it is replaced by +.Ar seq2 . +For example: +.Pp +.Dl nmap $1.$2.$3 [$1,$2].[$2,file] +.Pp +This command would yield the output filename +.Pa myfile.data +for input filenames +.Pa myfile.data +and +.Pa myfile.data.old ; +.Pa myfile.file +for the input filename +.Pa myfile ; +and +.Pa myfile.myfile +for the input filename +.Pa .myfile . +Spaces may be included in +.Ar outpattern +by quoting them, +as in the following example: +.Bd -literal -offset indent +nmap $1.$2 "$1 $2" +.Ed +.Pp +Use the +.Ql \e +character to prevent special treatment +of the +.Ql $ , +.Ql \&[ , +.Ql \&] , +and +.Ql \&, +characters. +.It Ic ntrans Op Ar inchars Op Ar outchars +Set or unset the filename character translation mechanism. +If no arguments are specified, the filename character +translation mechanism is unset. +If arguments are specified, characters in +remote filenames are translated during +.Ic mput +commands and +.Ic put +commands issued without a specified remote target filename. +If arguments are specified, characters in +local filenames are translated during +.Ic mget +commands and +.Ic get +commands issued without a specified local target filename. +This command is useful when connecting to a non-UNIX remote computer +with different file naming conventions or practices. +Characters in a filename matching a character in +.Ar inchars +are replaced with the corresponding character in +.Ar outchars . +If the character's position in +.Ar inchars +is longer than the length of +.Ar outchars , +the character is deleted from the file name. +.It Ic open Ar host Op Ar port +Establish a connection to the specified +.Ar host +FTP server. +An optional port number may be supplied, +in which case +.Nm +will attempt to contact an FTP server at that port. +If the +.Ic auto-login +option is on (default), +.Nm +will also attempt to automatically log the user in to +the FTP server (see below). +.It Ic page Ar file +Retrieve +.Ic file +and display with the program defined in +.Ev PAGER +(defaulting to +.Xr more 1 +if +.Ev PAGER +is null or not defined). .It Ic passive Op Ic on | off Toggle passive mode. If passive mode is turned on (default is on), @@ -286,43 +954,367 @@ If passive mode is turned on (default is on), will send a .Dv EPSV command for all data connections instead of the usual -.Dv EPRT +.Dv PORT command. The -.Dv EPSV +.Dv PASV command requests that the remote server open a port for the data connection and return the address of that port. The remote server listens on that port and the client connects to it. When using the more traditional -.Dv EPRT +.Dv PORT command, the client listens on a port and sends that address to the remote server, who connects back to it. Passive mode is useful when using .Nm through a gateway router or host that controls the directionality of traffic. -.It Ic lcd Op Ar local-directory -Change the working directory on the local machine. -If -no -.Ar local-directory -is specified, the user's home directory is used. -.It Ic lpwd -Print the working directory on the local machine. +(Note that though FTP servers are required to support the +.Dv PASV +command by RFC 1123, some do not.) +.It Ic preserve Op Ic on | off +Toggle preservation of modification times on retrieved files. +.It Ic progress Op Ic on | off +Toggle display of transfer progress bar. +The progress bar will be disabled for a transfer that has +.Ar local-file +as +.Sq - +or a command that starts with +.Sq \&| . +Refer to +.Sx FILE NAMING CONVENTIONS +for more information. +.It Ic prompt Op Ic on | off +Toggle interactive prompting. +Interactive prompting +occurs during multiple file transfers to allow the +user to selectively retrieve or store files. +If prompting is turned off (default is on), any +.Ic mget +or +.Ic mput +will transfer all files, and any +.Ic mdelete +will delete all files. +.Pp +When prompting is on, the following commands are available at a prompt: +.Bl -tag -width 2n -offset indent +.It Ic ?\& +Print help message. +.It Ic a +Answer +.Dq yes +to the current file and automatically answer +.Dq yes +to any remaining files for the current command. +.It Ic n +Do not transfer the file. +.It Ic p +Answer +.Dq yes +to the current file and turn off prompt mode +(as if +.Dq prompt off +had been given). +.It Ic q +Answer +.Dq no +to the current file and automatically answer +.Dq no +to any remaining files for the current command. +.It Ic y +Transfer the file. +.El +.It Ic proxy Ar command +Execute an FTP command on a secondary control connection. +This command allows simultaneous connection to two remote FTP +servers for transferring files between the two servers. +The first +.Ic proxy +command should be an +.Ic open , +to establish the secondary control connection. +Enter the command +.Ic proxy ?\& +to see other FTP commands executable on the +secondary connection. +The following commands behave differently when prefaced by +.Ic proxy : +.Ic open +will not define new macros during the auto-login process; +.Ic close +will not erase existing macro definitions; +.Ic get +and +.Ic mget +transfer files from the host on the primary control connection +to the host on the secondary control connection; and +.Ic put , +.Ic mput , +and +.Ic append +transfer files from the host on the secondary control connection +to the host on the primary control connection. +Third party file transfers depend upon support of the FTP protocol +.Dv PASV +command by the server on the secondary control connection. .It Ic put Ar local-file Op Ar remote-file Store a local file on the remote machine. If .Ar remote-file -is left unspecified, the local file name is used. -.It Ic mget Ar remote-files -Do a +is left unspecified, the local file name is used +after processing according to any +.Ic ntrans +or +.Ic nmap +settings +in naming the remote file. +File transfer uses the +current settings for +.Ic type , +.Ic format , +.Ic mode , +and +.Ic structure . +.It Ic pwd +Print the name of the current working directory on the remote +machine. +.It Ic quit +A synonym for +.Ic bye . +.It Ic quote Ar arg ... +The arguments specified are sent, verbatim, to the remote FTP server. +.It Ic recv Ar remote-file Op Ar local-file +A synonym for +.Ic get . +.It Ic reget Ar remote-file Op Ar local-file +Reget acts like get, except that if +.Ar local-file +exists and is +smaller than +.Ar remote-file , +.Ar local-file +is presumed to be +a partially transferred copy of +.Ar remote-file +and the transfer +is continued from the apparent point of failure. +This command +is useful when transferring very large files over networks that +are prone to dropping connections. +.It Ic rename Ar from-name to-name +Rename the file +.Ar from-name +on the remote machine to the file +.Ar to-name . +.It Ic reput Ar local-file Op Ar remote-file +Reput acts like put, except that if +.Ar remote-file +exists and is +smaller than +.Ar local-file , +.Ar remote-file +is presumed to be +a partially transferred copy of +.Ar local-file +and the transfer +is continued from the apparent point of failure. +This command +is useful when transferring very large files over networks that +are prone to dropping connections. +.It Ic reset +Clear reply queue. +This command re-synchronizes command/reply sequencing with the remote +FTP server. +Resynchronization may be necessary following a violation of the FTP protocol +by the remote server. +.It Ic restart Ar marker +Restart the immediately following .Ic get -for each file name specified. -.It Ic mput Ar local-files -Do a +or .Ic put -for each file name specified. +at the +indicated +.Ar marker . +On +.Ux +systems, +.Ar marker +is usually a byte +offset into the file. +.It Ic rhelp Op Ar command-name +Request help from the remote FTP server. +If a +.Ar command-name +is specified, it is supplied to the server as well. +.It Ic rmdir Ar directory-name +Delete a directory on the remote machine. +.It Ic rstatus Op Ar file +With no arguments, show status of remote machine. +If +.Ar file +is specified, show status of +.Ar file +on remote machine. +.It Ic runique Op Ic on | off +Toggle storing of files on the local system with unique filenames. +If a file already exists with a name equal to the target +local filename for a +.Ic get +or +.Ic mget +command, a +.Dq .1 +is appended to the name. +If the resulting name matches another existing file, +a +.Dq .2 +is appended to the original name. +If this process continues up to +.Dq .99 , +an error message is printed, and the transfer does not take place. +The generated unique filename will be reported. +Note that +.Ic runique +will not affect local files generated from a shell command +(see below). +The default value is off. +.It Ic send Ar local-file Op Ar remote-file +A synonym for +.Ic put . +.It Ic sendport Op Ic on | off +Toggle the use of +.Dv PORT +commands. +By default, +.Nm +will attempt to use a +.Dv PORT +command when establishing +a connection for each data transfer. +The use of +.Dv PORT +commands can prevent delays +when performing multiple file transfers. +If the +.Dv PORT +command fails, +.Nm +will use the default data port. +When the use of +.Dv PORT +commands is disabled, no attempt will be made to use +.Dv PORT +commands for each data transfer. +This is useful for certain FTP implementations which do ignore +.Dv PORT +commands but, incorrectly, indicate they've been accepted. +.It Ic site Ar arg ... +The arguments specified are sent, verbatim, to the remote FTP server as a +.Dv SITE +command. +.It Ic size Ar file +Return size of +.Ar file +on remote machine. +.It Ic status +Show the current status of +.Nm . +.\" .It Ic struct Op Ar struct-name +.\" Set the file transfer +.\" .Ar structure +.\" to +.\" .Ar struct-name . +.\" By default, +.\" .Dq file +.\" structure is used. +.It Ic sunique Op Ic on | off +Toggle storing of files on remote machine under unique file names. +The remote FTP server must support the FTP protocol +.Dv STOU +command for +successful completion. +The remote server will report the unique name. +Default value is off. +.It Ic system +Show the type of operating system running on the remote machine. +.It Ic trace Op Ic on | off +Toggle packet tracing. +.It Ic type Op Ar type-name +Set the file transfer +.Ic type +to +.Ar type-name . +If no type is specified, the current type +is printed. +The default type is +.Dq binary . +.It Ic umask Op Ar newmask +Set the default umask on the remote server to +.Ar newmask . +If +.Ar newmask +is omitted, the current umask is printed. +.It Xo +.Ic user Ar username +.Op Ar password Op Ar account +.Xc +Identify yourself to the remote FTP server. +If the +.Ar password +is not specified and the server requires it, +.Nm +will prompt the user for it (after disabling local echo). +If an +.Ar account +field is not specified, and the FTP server requires it, +the user will be prompted for it. +If an +.Ar account +field is specified, an account command will +be relayed to the remote server after the login sequence +is completed if the remote server did not require it +for logging in. +Unless +.Nm +is invoked with +.Dq auto-login +disabled, this process is done automatically on initial connection to the +FTP server. +.It Ic verbose Op Ic on | off +Toggle verbose mode. +In verbose mode, all responses from +the FTP server are displayed to the user. +In addition, +if verbose is on, when a file transfer completes, statistics +regarding the efficiency of the transfer are reported. +By default, +verbose is on. .El +.Pp +Command arguments which have embedded spaces may be quoted with +quote +.Pq Ql \&" +marks. +.Pp +Commands which toggle settings can take an explicit +.Ic on +or +.Ic off +argument to force the setting appropriately. +.Pp +If +.Nm +receives a +.Dv SIGINFO +(see the +.Dq status +argument of +.Xr stty 1 ) +signal whilst a transfer is in progress, the current transfer rate +statistics will be written to the standard error output, in the +same format as the standard completion message. .Sh AUTO-FETCHING FILES In addition to standard commands, this version of .Nm @@ -332,10 +1324,15 @@ on the command line. .Pp The following formats are valid syntax for an auto-fetch element: .Bl -tag -width Ds +.It Ar host : Ns / Ns Ar file Ns Op / +.Dq Classic +.Nm +format. .Sm off -.It Xo ftp:// +.It Xo +.Pf ftp:// Op Ar user : password No @ .Ar host Op : Ar port -.No / Ar file +.No / Ar file Op / .Xc .Sm on An FTP URL, retrieved using the FTP protocol if @@ -343,8 +1340,20 @@ An FTP URL, retrieved using the FTP protocol if isn't defined. Otherwise, transfer using HTTP via the proxy defined in .Ev ftp_proxy . +If a +.Ar user +and +.Ar password +are given and +.Ev ftp_proxy +isn't defined, +log in as +.Ar user +with a password of +.Ar password . .Sm off -.It Xo http:// +.It Xo +.Pf http:// Op Ar user : password No @ .Ar host Op : Ar port .No / Ar file .Xc @@ -353,8 +1362,21 @@ An HTTP URL, retrieved using the HTTP protocol. If .Ev http_proxy is defined, it is used as a URL to an HTTP proxy server. +If a +.Ar user +and +.Ar password +are given and +.Ev http_proxy +isn't defined, +log in as +.Ar user +with a password of +.Ar password +using Basic authentication. .Sm off -.It Xo https:// +.It Xo +.Pf https:// Op Ar user : password No @ .Ar host Op : Ar port .No / Ar file .Xc @@ -364,19 +1386,367 @@ If .Ev http_proxy is defined, this HTTPS proxy server will be used to fetch the file using the CONNECT method. +If a +.Ar user +and +.Ar password +are given and +.Ev http_proxy +isn't defined, +log in as +.Ar user +with a password of +.Ar password +using Basic authentication. .It Pf file: Ar file .Ar file is retrieved from a mounted file system. .El +.Pp +If a classic format or an FTP URL format has a trailing +.Sq / , +then +.Nm +will connect to the site and +.Ic cd +to the directory given as the path, and leave the user in interactive +mode ready for further input. +.Pp +If +.Ar file +contains a glob character and globbing is enabled +(see +.Ic glob ) , +then the equivalent of +.Ic mget Ar file +is performed. +.Pp +If no +.Fl o +option is specified, and +the directory component of +.Ar file +contains no globbing characters, +then +it is stored in the current directory as the +.Xr basename 1 +of +.Ar file . +If +.Fl o Ar output +is specified, then +.Ar file +is stored as +.Ar output . +Otherwise, the remote name is used as the local name. +.Sh ABORTING A FILE TRANSFER +To abort a file transfer, use the terminal interrupt key +(usually Ctrl-C). +Sending transfers will be immediately halted. +Receiving transfers will be halted by sending an FTP protocol +.Dv ABOR +command to the remote server, and discarding any further data received. +The speed at which this is accomplished depends upon the remote +server's support for +.Dv ABOR +processing. +If the remote server does not support the +.Dv ABOR +command, an +.Ql ftp\*(Gt +prompt will not appear until the remote server has completed +sending the requested file. +.Pp +The terminal interrupt key sequence will be ignored when +.Nm +has completed any local processing and is awaiting a reply +from the remote server. +A long delay in this mode may result from the ABOR processing described +above, or from unexpected behavior by the remote server, including +violations of the FTP protocol. +If the delay results from unexpected remote server behavior, the local +.Nm +program must be killed by hand. +.Sh FILE NAMING CONVENTIONS +Files specified as arguments to +.Nm +commands are processed according to the following rules. +.Bl -enum +.It +If +.Sq - +is specified as a local file name, the standard input (for reading) +or standard output (for writing) +is used. +.It +If the first character of a local file name is +.Sq \&| , +the +remainder of the argument is interpreted as a shell command. +.Nm +then forks a shell, using +.Xr popen 3 +with the argument supplied, and reads (writes) from the standard output +(standard input). +If the shell command includes spaces, the argument +must be quoted; e.g., +.Qq ls -lt . +A particularly +useful example of this mechanism is: +.Qq ls \&. |more . +.It +Failing the above checks, if +.Dq globbing +is enabled, +local file names are expanded +according to the rules used in the +.Xr csh 1 +.Ic glob +command. +If the +.Nm +command expects a single local file (e.g., +.Ic put ) , +only the first filename generated by the +.Dq globbing +operation is used. +.It +For +.Ic mget +commands and +.Ic get +commands with unspecified local file names, the local filename is +the remote filename, which may be altered by a +.Ic case , +.Ic ntrans , +or +.Ic nmap +setting. +The resulting filename may then be altered if +.Ic runique +is on. +.It +For +.Ic mput +commands and +.Ic put +commands with unspecified remote file names, the remote filename is +the local filename, which may be altered by a +.Ic ntrans +or +.Ic nmap +setting. +The resulting filename may then be altered by the remote server if +.Ic sunique +is on. +.El +.Sh FILE TRANSFER PARAMETERS +The FTP specification specifies many parameters which may +affect a file transfer. +The +.Ic type +may be one of +.Dq ascii , +.Dq binary , +or +.Dq image . +.Nm +supports the ASCII and image types of file transfer. +.Pp +.Nm +supports only the default values for the remaining +file transfer parameters: +.Ic mode , +.Ic form , +and +.Ic struct . +.Sh THE .netrc FILE +The +.Pa .netrc +file contains login and initialization information +used by the auto-login process. +It resides in the user's home directory. +The following tokens are recognized; they may be separated by spaces, +tabs, or new-lines: +.Bl -tag -width password +.It Ic machine Ar name +Identify a remote machine +.Ar name . +The auto-login process searches the +.Pa .netrc +file for a +.Ic machine +token that matches the remote machine specified on the +.Nm +command line or as an +.Ic open +command argument. +Once a match is made, the subsequent +.Pa .netrc +tokens are processed, +stopping when the end of file is reached or another +.Ic machine +or a +.Ic default +token is encountered. +.It Ic default +This is the same as +.Ic machine +.Ar name +except that +.Ic default +matches any name. +There can be only one +.Ic default +token, and it must be after all +.Ic machine +tokens. +This is normally used as: +.Pp +.Dl default login anonymous password user@site +.Pp +thereby giving the user +.Ar automatic +anonymous FTP login to +machines not specified in +.Pa .netrc . +This can be overridden +by using the +.Fl n +flag to disable auto-login. +.It Ic login Ar name +Identify a user on the remote machine. +If this token is present, the auto-login process will initiate +a login using the specified +.Ar name . +.It Ic password Ar string +Supply a password. +If this token is present, the auto-login process will supply the +specified string if the remote server requires a password as part +of the login process. +Note that if this token is present in the +.Pa .netrc +file for any user other +than +.Ar anonymous , +.Nm +will abort the auto-login process if the +.Pa .netrc +is readable by +anyone besides the user. +.It Ic account Ar string +Supply an additional account password. +If this token is present, the auto-login process will supply the +specified string if the remote server requires an additional +account password, or the auto-login process will initiate an +.Dv ACCT +command if it does not. +.It Ic macdef Ar name +Define a macro. +This token functions like the +.Nm +.Ic macdef +command functions. +A macro is defined with the specified name; its contents begin with the +next +.Pa .netrc +line and continue until a null line (consecutive new-line +characters) is encountered. +Like the other tokens in the +.Pa .netrc +file, a +.Ic macdef +is applicable only to the +.Ic machine +definition preceding it. +A +.Ic macdef +entry cannot be utilized by multiple +.Ic machine +definitions; rather, it must be defined following each +.Ic machine +it is intended to be used with. +If a macro named +.Ic init +is defined, it is automatically executed as the last step in the +auto-login process. +.El +.Sh COMMAND LINE EDITING +.Nm +supports interactive command line editing, via the +.Xr editline 3 +library. +It is enabled with the +.Ic edit +command, and is enabled by default if input is from a tty. +Previous lines can be recalled and edited with the arrow keys, +and other GNU Emacs-style editing keys may be used as well. +.Pp +The +.Xr editline 3 +library is configured with a +.Pa .editrc +file \- refer to +.Xr editrc 5 +for more information. +.Pp +An extra key binding is available to +.Nm +to provide context sensitive command and filename completion +(including remote file completion). +To use this, bind a key to the +.Xr editline 3 +command +.Ic ftp-complete . +By default, this is bound to the TAB key. .Sh ENVIRONMENT .Nm utilizes the following environment variables: -.Bl -tag -width Ds +.Bl -tag -width "FTPSERVERPORT" +.It Ev FTPMODE +Overrides the default operation mode. +Recognized values are: +.Pp +.Bl -tag -width "passive " -offset indent -compact +.It passive +passive mode FTP only +.It active +active mode FTP only +.It auto +automatic determination of passive or active (this is the default) +.It gate +gate-ftp mode +.El +.It Ev FTPSERVER +Host to use as gate-ftp server when +.Ic gate +is enabled. +.It Ev FTPSERVERPORT +Port to use when connecting to gate-ftp server when +.Ic gate +is enabled. +Default is port returned by a +.Fn getservbyname +lookup of +.Dq ftpgate/tcp . +.It Ev HOME +For default location of a +.Pa .netrc +file, if one exists. +.It Ev PAGER +Used by +.Ic page +to display files. +.It Ev SHELL +For default shell. .It Ev ftp_proxy URL of FTP proxy to use when making FTP URL requests (if not defined, use the standard FTP protocol). .It Ev http_proxy -URL of HTTP proxy to use when making HTTP(S) URL requests. +URL of HTTP proxy to use when making HTTP or HTTPS URL requests. +.It Ev http_cookies +Path of a Netscape-like cookiejar file to use when making +HTTP or HTTPS URL requests. .El .Sh PORT ALLOCATION For active mode data connections, @@ -388,19 +1758,45 @@ variables .Va net.inet.ip.porthifirst and .Va net.inet.ip.porthilast . +.Sh SEE ALSO +.Xr basename 1 , +.Xr csh 1 , +.Xr more 1 , +.Xr stty 1 , +.Xr tar 1 , +.Xr tftp 1 , +.Xr editline 3 , +.Xr getservbyname 3 , +.Xr popen 3 , +.Xr editrc 5 , +.Xr services 5 , +.Xr ftp-proxy 8 , +.Xr ftpd 8 +.Sh STANDARDS +.Rs +.%A J. Postel +.%A J. Reynolds +.%D October 1985 +.%R RFC 959 +.%T FILE TRANSFER PROTOCOL (FTP) +.Re +.Pp +.Rs +.%A P. Hethmon +.%D March 2007 +.%R RFC 3659 +.%T Extensions to FTP +.Re .Sh HISTORY The .Nm -command first appeard in +command appeared in .Bx 4.2 . -A complete rewrite of the -.Nm -command first appeared in -.Ox 6.6 . -.Sh AUTHORS -.An Sunil Nimmagadda Aq Mt sunil@openbsd.org -.Sh CAVEATS -While aborting a data transfer, certain FTP servers violate -the protocol by not responding with a 426 reply first, thereby making -.Nm -wait indefinitely for a correct reply. +.Sh BUGS +Correct execution of many commands depends upon proper behavior +by the remote server. +.Pp +In the recursive mode of +.Ic mget , +files and directories starting with whitespace are ignored +because the list cannot be parsed any other way. diff --git a/usr.bin/ftp/ftp.c b/usr.bin/ftp/ftp.c index a68de0aa52a..cbab7ed0cfb 100644 --- a/usr.bin/ftp/ftp.c +++ b/usr.bin/ftp/ftp.c @@ -1,452 +1,2088 @@ -/* $OpenBSD: ftp.c,v 1.103 2019/05/13 16:04:49 tb Exp $ */ +/* $OpenBSD: ftp.c,v 1.104 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: ftp.c,v 1.27 1997/08/18 10:20:23 lukem Exp $ */ /* - * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org> + * Copyright (C) 1997 and 1998 WIDE Project. + * All rights reserved. * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ +/* + * Copyright (c) 1985, 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/stat.h> #include <sys/socket.h> -#include <arpa/inet.h> #include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +#include <arpa/ftp.h> +#include <arpa/telnet.h> +#include <ctype.h> #include <err.h> #include <errno.h> -#include <libgen.h> -#include <limits.h> #include <netdb.h> +#include <poll.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <utime.h> -#include "ftp.h" -#include "xmalloc.h" +#include "ftp_var.h" -static FILE *ctrl_fp; -static int data_fd; +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; -void -ftp_connect(struct url *url, struct url *proxy, int timeout) -{ - char *buf = NULL; - size_t n = 0; - int sock; +union sockaddr_union myctladdr, hisctladdr, data_addr; - if (proxy) { - http_connect(url, proxy, timeout); - return; - } +int data = -1; +int abrtflag = 0; +jmp_buf ptabort; +int ptabflg; +int ptflag = 0; +off_t restart_point = 0; - if ((sock = tcp_connect(url->host, url->port, timeout)) == -1) - exit(1); - if ((ctrl_fp = fdopen(sock, "r+")) == NULL) - err(1, "%s: fdopen", __func__); +FILE *cin, *cout; - /* greeting */ - if (ftp_getline(&buf, &n, 0, ctrl_fp) != P_OK) { - warnx("Can't connect to host `%s'", url->host); - ftp_command(ctrl_fp, "QUIT"); - exit(1); - } - - free(buf); - log_info("Connected to %s\n", url->host); - if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) { - warnx("Can't login to host `%s'", url->host); - ftp_command(ctrl_fp, "QUIT"); - exit(1); - } -} - -struct url * -ftp_get(struct url *url, struct url *proxy, off_t *offset, off_t *sz) +char * +hookup(char *host, char *port) { - char *buf = NULL, *dir, *file; - - if (proxy) { - url = http_get(url, proxy, offset, sz); - /* this url should now be treated as HTTP */ - url->scheme = S_HTTP; - return url; + int s, tos, error; + static char hostnamebuf[HOST_NAME_MAX+1]; + struct addrinfo hints, *res, *res0; +#ifndef SMALL + struct addrinfo *ares; +#endif + char hbuf[NI_MAXHOST]; + char *cause = "unknown"; + socklen_t namelen; + + epsv4bad = 0; + + memset((char *)&hisctladdr, 0, sizeof (hisctladdr)); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + error = getaddrinfo(host, port, &hints, &res0); + if (error == EAI_SERVICE) { + /* + * If the services file is corrupt/missing, fall back + * on our hard-coded defines. + */ + char pbuf[NI_MAXSERV]; + + pbuf[0] = '\0'; + if (strcmp(port, "ftp") == 0) + snprintf(pbuf, sizeof(pbuf), "%d", FTP_PORT); + else if (strcmp(port, "ftpgate") == 0) + snprintf(pbuf, sizeof(pbuf), "%d", GATE_PORT); + else if (strcmp(port, "http") == 0) + snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT); +#ifndef SMALL + else if (strcmp(port, "https") == 0) + snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT); +#endif /* !SMALL */ + if (pbuf[0]) + error = getaddrinfo(host, pbuf, &hints, &res0); + } + if (error) { + if (error == EAI_SERVICE) + warnx("%s: bad port number `%s'", host, port); + else + warnx("%s: %s", host, gai_strerror(error)); + code = -1; + return (0); } - log_info("Using binary mode to transfer files.\n"); - if (ftp_command(ctrl_fp, "TYPE I") != P_OK) - errx(1, "Failed to set mode to binary"); - - dir = dirname(url->path); - if (ftp_command(ctrl_fp, "CWD %s", dir) != P_OK) - errx(1, "CWD command failed"); - - log_info("Retrieving %s\n", url->path); - file = basename(url->path); - if (strcmp(url->fname, "-")) - log_info("local: %s remote: %s\n", url->fname, file); + if (res0->ai_canonname) + strlcpy(hostnamebuf, res0->ai_canonname, sizeof(hostnamebuf)); else - log_info("remote: %s\n", file); - - if (ftp_size(ctrl_fp, file, sz, &buf) != P_OK) { - fprintf(stderr, "%s", buf); - ftp_command(ctrl_fp, "QUIT"); - exit(1); + strlcpy(hostnamebuf, host, sizeof(hostnamebuf)); + hostname = hostnamebuf; + +#ifndef SMALL + if (srcaddr) { + struct addrinfo ahints; + + memset(&ahints, 0, sizeof(ahints)); + ahints.ai_family = family; + ahints.ai_socktype = SOCK_STREAM; + ahints.ai_flags |= AI_NUMERICHOST; + ahints.ai_protocol = 0; + + error = getaddrinfo(srcaddr, NULL, &ahints, &ares); + if (error) { + warnx("%s: %s", srcaddr, gai_strerror(error)); + code = -1; + return (0); + } } - free(buf); - - if (activemode) - data_fd = ftp_eprt(ctrl_fp); - else if ((data_fd = ftp_epsv(ctrl_fp)) == -1) - data_fd = ftp_eprt(ctrl_fp); - - if (data_fd == -1) - errx(1, "Failed to establish data connection"); - - if (*offset && ftp_command(ctrl_fp, "REST %lld", *offset) != P_INTER) - errx(1, "REST command failed"); +#endif /* !SMALL */ + + s = -1; + for (res = res0; res; res = res->ai_next) { + if (res0->ai_next) /* if we have multiple possibilities */ + { + if (getnameinfo(res->ai_addr, res->ai_addrlen, + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + strlcpy(hbuf, "unknown", sizeof(hbuf)); + if (verbose) + fprintf(ttyout, "Trying %s...\n", hbuf); + } + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s < 0) { + cause = "socket"; + continue; + } +#ifndef SMALL + if (srcaddr) { + if (ares->ai_family != res->ai_family) { + close(s); + s = -1; + errno = EINVAL; + cause = "bind"; + continue; + } + if (bind(s, ares->ai_addr, ares->ai_addrlen) < 0) { + cause = "bind"; + error = errno; + close(s); + errno = error; + s = -1; + continue; + } + } +#endif /* !SMALL */ + for (error = connect(s, res->ai_addr, res->ai_addrlen); + error != 0 && errno == EINTR; error = connect_wait(s)) + continue; + if (error != 0) { + /* this "if" clause is to prevent print warning twice */ + if (verbose && res->ai_next) { + if (getnameinfo(res->ai_addr, res->ai_addrlen, + hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST) != 0) + strlcpy(hbuf, "(unknown)", + sizeof(hbuf)); + warn("connect to address %s", hbuf); + } + cause = "connect"; + error = errno; + close(s); + errno = error; + s = -1; + continue; + } - if (ftp_command(ctrl_fp, "RETR %s", file) != P_PRE) { - ftp_command(ctrl_fp, "QUIT"); - exit(1); + /* finally we got one */ + break; } - - return url; -} - -void -ftp_save(struct url *url, FILE *dst_fp, off_t *offset) -{ - struct sockaddr_storage ss; - FILE *data_fp; - socklen_t len; - int s; - - if (activemode) { - len = sizeof(ss); - if ((s = accept(data_fd, (struct sockaddr *)&ss, &len)) == -1) - err(1, "%s: accept", __func__); - - close(data_fd); - data_fd = s; + if (s < 0) { + warn("%s", cause); + code = -1; + freeaddrinfo(res0); + return 0; + } + memcpy(&hisctladdr, res->ai_addr, res->ai_addrlen); + namelen = res->ai_addrlen; + freeaddrinfo(res0); + res0 = res = NULL; +#ifndef SMALL + if (srcaddr) { + freeaddrinfo(ares); + ares = NULL; + } +#endif /* !SMALL */ + if (getsockname(s, &myctladdr.sa, &namelen) < 0) { + warn("getsockname"); + code = -1; + goto bad; + } + if (hisctladdr.sa.sa_family == AF_INET) { + tos = IPTOS_LOWDELAY; + if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) + warn("setsockopt TOS (ignored)"); + } + cin = fdopen(s, "r"); + cout = fdopen(s, "w"); + if (cin == NULL || cout == NULL) { + warnx("fdopen failed."); + if (cin) + (void)fclose(cin); + if (cout) + (void)fclose(cout); + code = -1; + goto bad; + } + if (verbose) + fprintf(ttyout, "Connected to %s.\n", hostname); + if (getreply(0) > 2) { /* read startup message from server */ + if (cin) + (void)fclose(cin); + if (cout) + (void)fclose(cout); + code = -1; + goto bad; + } + { + int ret, on = 1; + + ret = setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)); +#ifndef SMALL + if (ret < 0 && debug) + warn("setsockopt"); +#endif /* !SMALL */ } - if ((data_fp = fdopen(data_fd, "r")) == NULL) - err(1, "%s: fdopen data_fd", __func__); - - copy_file(dst_fp, data_fp, offset); - fclose(data_fp); + return (hostname); +bad: + (void)close(s); + return (NULL); } +/* ARGSUSED */ void -ftp_quit(struct url *url) +cmdabort(int signo) { - char *buf = NULL; - size_t n = 0; + int save_errno = errno; - if (ftp_getline(&buf, &n, 0, ctrl_fp) != P_OK) - errx(1, "error retrieving file %s", url->fname); + alarmtimer(0); + (void) write(fileno(ttyout), "\n\r", 2); + abrtflag++; - free(buf); - ftp_command(ctrl_fp, "QUIT"); - fclose(ctrl_fp); + errno = save_errno; + if (ptflag) + longjmp(ptabort, 1); } int -ftp_getline(char **lineptr, size_t *n, int suppress_output, FILE *fp) +command(const char *fmt, ...) { - ssize_t len; - char *bufp, code[4]; - const char *errstr; - int lookup[] = { P_PRE, P_OK, P_INTER, N_TRANS, N_PERM }; - - - if ((len = getline(lineptr, n, fp)) == -1) - err(1, "%s: getline", __func__); - - bufp = *lineptr; - if (!suppress_output) - log_info("%s", bufp); - - if (len < 4) - errx(1, "%s: line too short", __func__); - - (void)strlcpy(code, bufp, sizeof code); - if (bufp[3] == ' ') - goto done; + va_list ap; + int r; + sig_t oldintr; + + abrtflag = 0; +#ifndef SMALL + if (debug) { + fputs("---> ", ttyout); + va_start(ap, fmt); + if (strncmp("PASS ", fmt, 5) == 0) + fputs("PASS XXXX", ttyout); + else if (strncmp("ACCT ", fmt, 5) == 0) + fputs("ACCT XXXX", ttyout); + else + vfprintf(ttyout, fmt, ap); + va_end(ap); + putc('\n', ttyout); + (void)fflush(ttyout); + } +#endif /* !SMALL */ + if (cout == NULL) { + warnx("No control connection for command."); + code = -1; + return (0); + } + oldintr = signal(SIGINT, cmdabort); + va_start(ap, fmt); + vfprintf(cout, fmt, ap); + va_end(ap); + fputs("\r\n", cout); + (void)fflush(cout); + cpend = 1; + r = getreply(!strcmp(fmt, "QUIT")); + if (abrtflag && oldintr != SIG_IGN) + (*oldintr)(SIGINT); + (void)signal(SIGINT, oldintr); + return (r); +} - /* multi-line reply */ - while (!(strncmp(code, bufp, 3) == 0 && bufp[3] == ' ')) { - if ((len = getline(lineptr, n, fp)) == -1) - err(1, "%s: getline", __func__); +int keep_alive_timeout = 60; /* 0 -> no timeout */ - bufp = *lineptr; - if (!suppress_output) - log_info("%s", bufp); +static int full_noops_sent = 0; +static time_t last_timestamp = 0; /* 0 -> no measurement yet */ +static char noop[] = "NOOP\r\n"; +#define NOOP_LENGTH (sizeof noop - 1) +static int current_nop_pos = 0; /* 0 -> no noop started */ - if (len < 4) - continue; +/* to achieve keep alive, we send noop one byte at a time */ +static void +send_noop_char(void) +{ +#ifndef SMALL + if (debug) + fprintf(ttyout, "---> %c\n", noop[current_nop_pos]); +#endif /* !SMALL */ + fputc(noop[current_nop_pos++], cout); + (void)fflush(cout); + if (current_nop_pos >= NOOP_LENGTH) { + full_noops_sent++; + current_nop_pos = 0; } - - done: - (void)strtonum(code, 100, 553, &errstr); - if (errstr) - errx(1, "%s: Response code is %s: %s", __func__, errstr, code); - - return lookup[code[0] - '1']; } -int -ftp_command(FILE *fp, const char *fmt, ...) +static void +may_reset_noop_timeout(void) { - va_list ap; - char *buf = NULL, *cmd; - size_t n = 0; - int r; + if (keep_alive_timeout != 0) + last_timestamp = time(NULL); +} - va_start(ap, fmt); - r = vasprintf(&cmd, fmt, ap); - va_end(ap); - if (r < 0) - errx(1, "%s: vasprintf", __func__); +static void +may_receive_noop_ack(void) +{ + int i; - if (io_debug) - fprintf(stderr, ">>> %s\n", cmd); + if (cout == NULL) { + /* Lost connection; so just pretend we're fine. */ + current_nop_pos = full_noops_sent = 0; + return; + } - if (fprintf(fp, "%s\r\n", cmd) < 0) - errx(1, "%s: fprintf", __func__); + /* finish sending last incomplete noop */ + if (current_nop_pos != 0) { + fputs(&(noop[current_nop_pos]), cout); +#ifndef SMALL + if (debug) + fprintf(ttyout, "---> %s\n", &(noop[current_nop_pos])); +#endif /* !SMALL */ + (void)fflush(cout); + current_nop_pos = 0; + full_noops_sent++; + } + /* and get the replies */ + for (i = 0; i < full_noops_sent; i++) + (void)getreply(0); - (void)fflush(fp); - free(cmd); - r = ftp_getline(&buf, &n, 0, fp); - free(buf); - return r; + full_noops_sent = 0; +} +static void +may_send_noop_char(void) +{ + if (keep_alive_timeout != 0) { + if (last_timestamp != 0) { + time_t t = time(NULL); + + if (t - last_timestamp >= keep_alive_timeout) { + last_timestamp = t; + send_noop_char(); + } + } else { + last_timestamp = time(NULL); + } + } } +char reply_string[BUFSIZ]; /* first line of previous reply */ + int -ftp_auth(FILE *fp, const char *user, const char *pass) +getreply(int expecteof) { - char *addr = NULL, hn[HOST_NAME_MAX+1], *un; - int code; + char current_line[BUFSIZ]; /* last line of previous reply */ + int c, n, lineno; + int dig; + int originalcode = 0, continuation = 0; + sig_t oldintr; + int pflag = 0; + char *cp, *pt = pasv; + + memset(current_line, 0, sizeof(current_line)); + oldintr = signal(SIGINT, cmdabort); + for (lineno = 0 ;; lineno++) { + dig = n = code = 0; + cp = current_line; + while ((c = fgetc(cin)) != '\n') { + if (c == IAC) { /* handle telnet commands */ + switch (c = fgetc(cin)) { + case WILL: + case WONT: + c = fgetc(cin); + fprintf(cout, "%c%c%c", IAC, DONT, c); + (void)fflush(cout); + break; + case DO: + case DONT: + c = fgetc(cin); + fprintf(cout, "%c%c%c", IAC, WONT, c); + (void)fflush(cout); + break; + default: + break; + } + continue; + } + dig++; + if (c == EOF) { + if (expecteof) { + (void)signal(SIGINT, oldintr); + code = 221; + return (0); + } + lostpeer(); + if (verbose) { + fputs( +"421 Service not available, remote server has closed connection.\n", ttyout); + (void)fflush(ttyout); + } + code = 421; + return (4); + } + if (c != '\r' && (verbose > 0 || + ((verbose > -1 && n == '5' && dig > 4) && + (((!n && c < '5') || (n && n < '5')) + || !retry_connect)))) { + if (proxflag && + (dig == 1 || (dig == 5 && verbose == 0))) + fprintf(ttyout, "%s:", hostname); + (void)putc(c, ttyout); + } + if (dig < 4 && isdigit(c)) + code = code * 10 + (c - '0'); + if (!pflag && (code == 227 || code == 228)) + pflag = 1; + else if (!pflag && code == 229) + pflag = 100; + if (dig > 4 && pflag == 1 && isdigit(c)) + pflag = 2; + if (pflag == 2) { + if (c != '\r' && c != ')') { + if (pt < &pasv[sizeof(pasv) - 1]) + *pt++ = c; + } else { + *pt = '\0'; + pflag = 3; + } + } + if (pflag == 100 && c == '(') + pflag = 2; + if (dig == 4 && c == '-') { + if (continuation) + code = 0; + continuation++; + } + if (n == 0) + n = c; + if (cp < ¤t_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 */ |