lobase

Linux port of OpenBSDs userland.
Log | Files | Refs | README

commit 53f9101a113ec1693a519594a2ccaab32efc7c62
parent 31f0970ad6c2a7f1b3ba6e0493ff2df44888f5fb
Author: Duncaen <mail@duncano.de>
Date:   Sun, 11 Jun 2017 19:18:44 +0200

usr.bin/ftp: import

Diffstat:
usr.bin/Makefile | 2+-
usr.bin/ftp/Makefile | 17+++++++++++++++++
usr.bin/ftp/cmds.c | 1688+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/cmds.h | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/cmdtab.c | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/complete.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/cookie.c | 231+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/domacro.c | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/extern.h | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/fetch.c | 1602+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/ftp.1 | 1792+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/ftp.c | 2095+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/ftp_var.h | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/list.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/main.c | 921+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/pathnames.h | 37+++++++++++++++++++++++++++++++++++++
usr.bin/ftp/ruserpass.c | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/small.c | 728+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/small.h | 35+++++++++++++++++++++++++++++++++++
usr.bin/ftp/stringlist.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/stringlist.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/ftp/util.c | 1071+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 11980 insertions(+), 1 deletion(-)

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