From 5b209a46175d2276ef206e760f075ecbc59e8749 Mon Sep 17 00:00:00 2001 From: Lain Iwakura Date: Mon, 29 Dec 2025 23:28:43 +0300 Subject: feat(main): yes, works! --- .gitignore | 2 + Makefile | 50 ++ cmds.c | 1663 ++++++++++++++++++++++++++++++++++++++++++++++ cmds.h | 83 +++ cmdtab.c | 215 ++++++ compat.c | 330 ++++++++++ compat.h | 115 ++++ complete.c | 381 +++++++++++ cookie.c | 221 +++++++ domacro.c | 146 +++++ extern.h | 152 +++++ fetch.c | 1789 ++++++++++++++++++++++++++++++++++++++++++++++++++ ftp.1 | 1821 ++++++++++++++++++++++++++++++++++++++++++++++++++ ftp.c | 2080 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ftp_var.h | 241 +++++++ list.c | 86 +++ main.c | 1093 ++++++++++++++++++++++++++++++ pathnames.h | 37 ++ ruserpass.c | 317 +++++++++ small.c | 721 ++++++++++++++++++++ small.h | 35 + stringlist.c | 97 +++ stringlist.h | 55 ++ tls_compat.c | 283 ++++++++ tls_compat.h | 60 ++ util.c | 1167 ++++++++++++++++++++++++++++++++ 26 files changed, 13240 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cmds.c create mode 100644 cmds.h create mode 100644 cmdtab.c create mode 100644 compat.c create mode 100644 compat.h create mode 100644 complete.c create mode 100644 cookie.c create mode 100644 domacro.c create mode 100644 extern.h create mode 100644 fetch.c create mode 100644 ftp.1 create mode 100644 ftp.c create mode 100644 ftp_var.h create mode 100644 list.c create mode 100644 main.c create mode 100644 pathnames.h create mode 100644 ruserpass.c create mode 100644 small.c create mode 100644 small.h create mode 100644 stringlist.c create mode 100644 stringlist.h create mode 100644 tls_compat.c create mode 100644 tls_compat.h create mode 100644 util.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02d0ba9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +ftp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3210093 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +CC ?= cc +CFLAGS ?= -O2 -g +CFLAGS += -Wall +LDFLAGS ?= + +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 compat.c + +ifndef NOSSL +SRCS += tls_compat.c +CFLAGS += -DNOSSL=0 +LDFLAGS += -lssl -lcrypto +else +CFLAGS += -DNOSSL=1 +endif + +ifndef SMALL +CFLAGS += -DSMALL=0 +ifneq ($(shell pkg-config --exists libedit && echo yes),) +CFLAGS += $(shell pkg-config --cflags libedit) +LDFLAGS += $(shell pkg-config --libs libedit) +else +LDFLAGS += -ledit +endif +ifneq ($(shell uname),Darwin) +LDFLAGS += -lcurses +endif +else +CFLAGS += -DSMALL=1 +endif + +OBJS = $(SRCS:.c=.o) + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $(LDFLAGS) -o $(PROG) $(OBJS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(PROG) + +install: $(PROG) + install -d $(DESTDIR)/usr/bin + install -m 755 $(PROG) $(DESTDIR)/usr/bin/ + +.PHONY: all clean install \ No newline at end of file diff --git a/cmds.c b/cmds.c new file mode 100644 index 0000000..9daedea --- /dev/null +++ b/cmds.c @@ -0,0 +1,1663 @@ +/* $OpenBSD: cmds.c,v 1.85 2023/03/08 04:43:11 guenther 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftp_var.h" +#include "pathnames.h" +#include "cmds.h" + +/* + * Set ascii transfer type. + */ +void +setascii(int argc, char *argv[]) +{ + + stype[1] = "ascii"; + settype(2, stype); +} + +/* + * Set file transfer mode. + */ +void +setftmode(int argc, char *argv[]) +{ + + fprintf(ttyout, "We only support %s mode, sorry.\n", modename); + code = -1; +} + +/* + * Set file transfer format. + */ +void +setform(int argc, char *argv[]) +{ + + fprintf(ttyout, "We only support %s format, sorry.\n", formname); + code = -1; +} + +/* + * Set file transfer structure. + */ +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. + */ +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 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. + */ +void +setglob(int argc, char *argv[]) +{ + + code = togglevar(argc, argv, &doglob, "Globbing"); +} + +/* + * Toggle preserving modification times on retrieved files. + */ +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. + */ +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]) == -1) { + 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 + */ +void +shell(int argc, char *argv[]) +{ + pid_t pid; + sig_t old1, old2; + char shellnam[PATH_MAX], *shellp, *namep; + int wait_status; + + old1 = signal (SIGINT, SIG_IGN); + old2 = signal (SIGQUIT, SIG_IGN); + if ((pid = fork()) == 0) { + (void)closefrom(3); + (void)signal(SIGINT, SIG_DFL); + (void)signal(SIGQUIT, SIG_DFL); + shellp = getenv("SHELL"); + if (shellp == NULL || *shellp == '\0') + shellp = _PATH_BSHELL; + namep = strrchr(shellp, '/'); + if (namep == NULL) + namep = shellp; + shellnam[0] = '-'; + (void)strlcpy(shellnam + 1, ++namep, sizeof(shellnam) - 1); + if (strcmp(namep, "sh") != 0) + shellnam[0] = '+'; + if (debug) { + fputs(shellp, ttyout); + fputc('\n', ttyout); + (void)fflush(ttyout); + } + if (argc > 1) { + execl(shellp, shellnam, "-c", altarg, (char *)NULL); + } + else { + execl(shellp, shellnam, (char *)NULL); + } + warn("%s", shellp); + code = -1; + exit(1); + } + if (pid > 0) + while (wait(&wait_status) != pid) + ; + (void)signal(SIGINT, old1); + (void)signal(SIGQUIT, old2); + if (pid == -1) { + warn("Try again later"); + code = -1; + } + else { + code = 0; + } +} + +/* + * Send new user information (re-login) + */ +void +user(int argc, char *argv[]) +{ + char acctname[80]; + int n, aflag = 0; + + if (argc < 2) + (void)another(&argc, &argv, "username"); + if (argc < 2 || argc > 4) { + fprintf(ttyout, "usage: %s username [password [account]]\n", + argv[0]); + code = -1; + return; + } + n = command("USER %s", argv[1]); + if (n == CONTINUE) { + if (argc < 3 ) + argv[2] = getpass("Password:"), argc++; + n = command("PASS %s", argv[2]); + } + if (n == CONTINUE) { + if (argc < 4) { + (void)fputs("Account: ", ttyout); + (void)fflush(ttyout); + if (fgets(acctname, sizeof(acctname), stdin) == NULL) { + clearerr(stdin); + goto fail; + } + + acctname[strcspn(acctname, "\n")] = '\0'; + + argv[3] = acctname; + argc++; + } + n = command("ACCT %s", argv[3]); + aflag++; + } + if (n != COMPLETE) { + fail: + fputs("Login failed.\n", ttyout); + return; + } + if (!aflag && argc == 4) { + (void)command("ACCT %s", argv[3]); + } + connected = -1; +} + +/* + * Print working directory on remote machine. + */ +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. + */ +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. + */ +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; + +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 */ +void +cdup(int argc, char *argv[]) +{ + int r; + + r = command("CDUP"); + if (r == ERROR && code == 500) { + if (verbose) + fputs("CDUP command not recognized, trying XCUP.\n", ttyout); + r = command("XCUP"); + } + if (r == COMPLETE) + dirchange = 1; +} + +/* + * Restart transfer at specific point + */ +void +restart(int argc, char *argv[]) +{ + off_t nrestart_point; + char *ep; + + if (argc != 2) + fputs("restart: offset not specified.\n", ttyout); + else { + nrestart_point = strtoll(argv[1], &ep, 10); + if (nrestart_point == LLONG_MAX || *ep != '\0') + fputs("restart: invalid offset.\n", ttyout); + else { + fprintf(ttyout, "Restarting at %lld. Execute get, put " + "or append to initiate transfer\n", + (long long)nrestart_point); + restart_point = nrestart_point; + } + } +} + +/* + * Show remote system type + */ +void +syst(int argc, char *argv[]) +{ + + (void)command("SYST"); +} + +void +macdef(int argc, char *argv[]) +{ + char *tmp; + int c; + + if (macnum == 16) { + fputs("Limit of 16 macros have already been defined.\n", ttyout); + code = -1; + return; + } + if ((argc < 2 && !another(&argc, &argv, "macro-name")) || argc > 2) { + fprintf(ttyout, "usage: %s macro-name\n", argv[0]); + code = -1; + return; + } + if (interactive) + fputs( +"Enter macro line by line, terminating it with a null line.\n", ttyout); + (void)strlcpy(macros[macnum].mac_name, argv[1], + sizeof(macros[macnum].mac_name)); + if (macnum == 0) + macros[macnum].mac_start = macbuf; + else + macros[macnum].mac_start = macros[macnum - 1].mac_end + 1; + tmp = macros[macnum].mac_start; + while (tmp != macbuf+4096) { + if ((c = getchar()) == EOF) { + fputs("macdef: end of file encountered.\n", ttyout); + code = -1; + return; + } + if ((*tmp = c) == '\n') { + if (tmp == macros[macnum].mac_start) { + macros[macnum++].mac_end = tmp; + code = 0; + return; + } + if (*(tmp-1) == '\0') { + macros[macnum++].mac_end = tmp - 1; + code = 0; + return; + } + *tmp = '\0'; + } + tmp++; + } + while (1) { + while ((c = getchar()) != '\n' && c != EOF) + /* LOOP */; + if (c == EOF || getchar() == '\n') { + fputs("Macro not defined - 4K buffer exceeded.\n", ttyout); + code = -1; + return; + } + } +} + +/* + * Get size of file on remote machine + */ +void +sizecmd(int argc, char *argv[]) +{ + off_t size; + + if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) { + fprintf(ttyout, "usage: %s file\n", argv[0]); + code = -1; + return; + } + size = remotesize(argv[1], 1); + if (size != -1) + fprintf(ttyout, "%s\t%lld\n", argv[1], (long long)size); + code = size; +} + +/* + * Get last modification time of file on remote machine + */ +void +modtime(int argc, char *argv[]) +{ + time_t mtime; + + if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) { + fprintf(ttyout, "usage: %s file\n", argv[0]); + code = -1; + return; + } + mtime = remotemodtime(argv[1], 1); + if (mtime != -1) + fprintf(ttyout, "%s\t%s", argv[1], asctime(localtime(&mtime))); + code = mtime; +} + +/* + * Show status on remote machine + */ +void +rmtstatus(int argc, char *argv[]) +{ + + (void)command(argc > 1 ? "STAT %s" : "STAT" , argv[1]); +} + +/* + * Get file if modtime is more recent than current file + */ +void +newer(int argc, char *argv[]) +{ + + (void)getit(argc, argv, -1, "w"); +} + +/* + * Display one file through $PAGER (defaults to "more"). + */ +void +page(int argc, char *argv[]) +{ + off_t orestart_point; + int ohash, overbose; + char *p, *pager, *oldargv1; + + if ((argc < 2 && !another(&argc, &argv, "file")) || argc > 2) { + fprintf(ttyout, "usage: %s file\n", argv[0]); + code = -1; + return; + } + oldargv1 = argv[1]; + if (!globulize(&argv[1])) { + code = -1; + return; + } + p = getenv("PAGER"); + if (p == NULL || (*p == '\0')) + p = PAGER; + if (asprintf(&pager, "|%s", p) == -1) + errx(1, "Can't allocate memory for $PAGER"); + + orestart_point = restart_point; + ohash = hash; + overbose = verbose; + restart_point = hash = verbose = 0; + recvrequest("RETR", pager, argv[1], "r+w", 1, 0); + (void)free(pager); + restart_point = orestart_point; + hash = ohash; + verbose = overbose; + if (oldargv1 != argv[1]) /* free up after globulize() */ + free(argv[1]); +} + +#endif /* !SMALL */ + diff --git a/cmds.h b/cmds.h new file mode 100644 index 0000000..ca01845 --- /dev/null +++ b/cmds.h @@ -0,0 +1,83 @@ +/* $OpenBSD: cmds.h,v 1.4 2019/05/16 12:44:17 florian Exp $ */ + +/* + * Copyright (c) 2009 Martynas Venckus + * + * 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/cmdtab.c b/cmdtab.c new file mode 100644 index 0000000..5b871dc --- /dev/null +++ b/cmdtab.c @@ -0,0 +1,215 @@ +/* $OpenBSD: cmdtab.c,v 1.31 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: cmdtab.c,v 1.17 1997/08/18 10:20:17 lukem Exp $ */ + +/* + * Copyright (c) 1985, 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SMALL + +#include +#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/compat.c b/compat.c new file mode 100644 index 0000000..b578962 --- /dev/null +++ b/compat.c @@ -0,0 +1,330 @@ +#define COMPAT_C_IMPL +#include "compat.h" + +#ifndef __OpenBSD__ + +#include +#include +#include +#include +#include +#include + +#ifndef program_invocation_short_name +extern char *__progname; +#define program_invocation_short_name (__progname ? __progname : "ftp") +#endif + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + int error = 0; + char *ep; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "too large", ERANGE }, + { "too small", ERANGE }, + { "invalid", EINVAL }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = 3; + ev[error].err = EINVAL; + ev[error].errstr = "invalid"; + } else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = 3; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = 1; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = 2; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} + +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + if (n == 0) { + if (siz != 0) + *d = '\0'; + while (*s++) + ; + } + + return(s - src - 1); +} + +void +err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (fmt != NULL) { + fprintf(stderr, "%s: ", program_invocation_short_name); + vfprintf(stderr, fmt, ap); + va_end(ap); + if (errno != 0) + fprintf(stderr, ": %s", strerror(errno)); + fprintf(stderr, "\n"); + } else { + if (errno != 0) + fprintf(stderr, "%s: %s\n", program_invocation_short_name, + strerror(errno)); + va_end(ap); + } + exit(eval); +} + +void +errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s: ", program_invocation_short_name); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(eval); +} + +void +warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (fmt != NULL) { + fprintf(stderr, "%s: ", program_invocation_short_name); + vfprintf(stderr, fmt, ap); + va_end(ap); + if (errno != 0) + fprintf(stderr, ": %s", strerror(errno)); + fprintf(stderr, "\n"); + } else { + if (errno != 0) + fprintf(stderr, "%s: %s\n", program_invocation_short_name, + strerror(errno)); + va_end(ap); + } + errno = 0; +} + +void +warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s: ", program_invocation_short_name); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +#if defined(__linux__) && !defined(HAVE_FUNOPEN) +#define _GNU_SOURCE +#include + +struct funopen_cookie { + const void *cookie; + int (*readfn)(void *, char *, int); + int (*writefn)(void *, const char *, int); + fpos_t (*seekfn)(void *, fpos_t, int); + int (*closefn)(void *); +}; + +static ssize_t +funopen_read(void *cookie, char *buf, size_t size) +{ + struct funopen_cookie *c = cookie; + if (c->readfn) + return c->readfn((void *)c->cookie, buf, (int)size); + return -1; +} + +static ssize_t +funopen_write(void *cookie, const char *buf, size_t size) +{ + struct funopen_cookie *c = cookie; + if (c->writefn) + return c->writefn((void *)c->cookie, buf, (int)size); + return -1; +} + +static int +funopen_seek(void *cookie, off64_t *offset, int whence) +{ + struct funopen_cookie *c = cookie; + fpos_t pos = (fpos_t)*offset; + int ret; + if (c->seekfn) { + ret = c->seekfn((void *)c->cookie, pos, whence); + if (ret == 0) + *offset = (off64_t)pos; + return ret; + } + return -1; +} + +static int +funopen_close(void *cookie) +{ + struct funopen_cookie *c = cookie; + if (c->closefn) + return c->closefn((void *)c->cookie); + free(c); + return 0; +} + +FILE * +funopen(const void *cookie, + int (*readfn)(void *, char *, int), + int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), + int (*closefn)(void *)) +{ + struct funopen_cookie *c = malloc(sizeof(*c)); + if (c == NULL) + return NULL; + c->cookie = cookie; + c->readfn = readfn; + c->writefn = writefn; + c->seekfn = seekfn; + c->closefn = closefn; + + cookie_io_functions_t funcs = { + .read = funopen_read, + .write = funopen_write, + .seek = funopen_seek, + .close = funopen_close + }; + + return fopencookie(c, "r+", funcs); +} +#endif + +#if defined(__APPLE__) +#include + +int +strnvis_openbsd(char *dst, const char *src, size_t dlen, int flags) +{ + return strnvis(dst, dlen, src, flags); +} +#endif + +#ifndef __OpenBSD__ +#include +#ifndef u_char +#define u_char unsigned char +#endif + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +int +b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) +{ + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + u_int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + if (srclength != 0) { + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; + return (datalength); +} +#endif + +#if !defined(__OpenBSD__) +#include +#include + +int +ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, + const sigset_t *sigmask) +{ + int timeout_ms; + + (void)sigmask; /* Ignoring signal mask for simplicity */ + + if (timeout_ts == NULL) { + timeout_ms = -1; + } else { + /* Convert timespec to milliseconds */ + timeout_ms = timeout_ts->tv_sec * 1000 + timeout_ts->tv_nsec / 1000000; + } + + return poll(fds, nfds, timeout_ms); +} +#endif + +#endif \ No newline at end of file diff --git a/compat.h b/compat.h new file mode 100644 index 0000000..ad0b45d --- /dev/null +++ b/compat.h @@ -0,0 +1,115 @@ +#ifndef COMPAT_H +#define COMPAT_H + +#include +#include +#include +#include +#include +#include +#include + +#ifndef __OpenBSD__ +#ifndef HOST_NAME_MAX +#ifdef _POSIX_HOST_NAME_MAX +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#else +#define HOST_NAME_MAX 255 +#endif +#endif + +#ifndef LOGIN_NAME_MAX +#ifdef _POSIX_LOGIN_NAME_MAX +#define LOGIN_NAME_MAX _POSIX_LOGIN_NAME_MAX +#else +#define LOGIN_NAME_MAX 256 +#endif +#endif + +#ifndef HAVE_STRTONUM +#define HAVE_STRTONUM +long long strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp); +#endif + +#ifndef HAVE_STRLCPY +#define HAVE_STRLCPY +size_t strlcpy(char *dst, const char *src, size_t siz); +#endif + +#ifndef HAVE_ERR +#define HAVE_ERR +void err(int eval, const char *fmt, ...); +void errx(int eval, const char *fmt, ...); +void warn(const char *fmt, ...); +void warnx(const char *fmt, ...); +#endif + +#ifndef HAVE_TIMESPECADD +#define HAVE_TIMESPECADD +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + +#ifndef HAVE_PLEDGE +#define HAVE_PLEDGE +static inline int pledge(const char *promises, const char *execpromises) { + (void)promises; + (void)execpromises; + return 0; +} +#endif + +#if !defined(__OpenBSD__) +#include +#include +#include +int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, + const sigset_t *sigmask); +#endif + +#if defined(__APPLE__) && !defined(HAVE_STRNVIS) +#define HAVE_STRNVIS +#ifndef COMPAT_C_IMPL +int strnvis_openbsd(char *dst, const char *src, size_t dlen, int flags); +#define strnvis strnvis_openbsd +#endif +#endif + +#ifndef HAVE_B64_NTOP +#define HAVE_B64_NTOP +#ifndef u_char +#define u_char unsigned char +#endif +int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); +#endif +#endif + +#if defined(__linux__) && !defined(HAVE_FUNOPEN) +#define HAVE_FUNOPEN +#define _GNU_SOURCE +#include +FILE *funopen(const void *cookie, + int (*readfn)(void *, char *, int), + int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), + int (*closefn)(void *)); +#endif + +#endif \ No newline at end of file diff --git a/complete.c b/complete.c new file mode 100644 index 0000000..ef2478f --- /dev/null +++ b/complete.c @@ -0,0 +1,381 @@ +/* $OpenBSD: complete.c,v 1.33 2019/05/16 12:44:17 florian Exp $ */ +/* $NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $ */ + +/*- + * Copyright (c) 1997 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SMALL + +/* + * FTP user program - command and file completion routines + */ + +#include +#include +#include +#include +#include +#include + +#include "ftp_var.h" + +static int comparstr(const void *, const void *); +static unsigned char complete_ambiguous(char *, int, StringList *); +static unsigned char complete_command(char *, int); +static unsigned char complete_local(char *, int); +static unsigned char complete_remote(char *, int); +static void ftpvis(char *, size_t, const char *, size_t); + +static int +comparstr(const void *a, const void *b) +{ + return (strcmp(*(char **)a, *(char **)b)); +} + +/* + * Determine if complete is ambiguous. If unique, insert. + * If no choices, error. If unambiguous prefix, insert that. + * Otherwise, list choices. words is assumed to be filtered + * to only contain possible choices. + * Args: + * word word which started the match + * list list by default + * words stringlist containing possible matches + */ +static unsigned char +complete_ambiguous(char *word, int list, StringList *words) +{ + char insertstr[PATH_MAX * 2]; + char *lastmatch; + int i, j; + size_t matchlen, wordlen; + + wordlen = strlen(word); + if (words->sl_cur == 0) + return (CC_ERROR); /* no choices available */ + + if (words->sl_cur == 1) { /* only once choice available */ + char *p = words->sl_str[0] + wordlen; + ftpvis(insertstr, sizeof(insertstr), p, strlen(p)); + if (el_insertstr(el, insertstr) == -1) + return (CC_ERROR); + else + return (CC_REFRESH); + } + + if (!list) { + lastmatch = words->sl_str[0]; + matchlen = strlen(lastmatch); + for (i = 1 ; i < words->sl_cur ; i++) { + for (j = wordlen ; j < strlen(words->sl_str[i]); j++) + if (lastmatch[j] != words->sl_str[i][j]) + break; + if (j < matchlen) + matchlen = j; + } + if (matchlen > wordlen) { + ftpvis(insertstr, sizeof(insertstr), + lastmatch + wordlen, matchlen - wordlen); + if (el_insertstr(el, insertstr) == -1) + return (CC_ERROR); + else + /* + * XXX: really want CC_REFRESH_BEEP + */ + return (CC_REFRESH); + } + } + + putc('\n', ttyout); + qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr); + list_vertical(words); + return (CC_REDISPLAY); +} + +/* + * Complete a command + */ +static unsigned char +complete_command(char *word, int list) +{ + struct cmd *c; + StringList *words; + size_t wordlen; + unsigned char rv; + + words = sl_init(); + wordlen = strlen(word); + + for (c = cmdtab; c->c_name != NULL; c++) { + if (wordlen > strlen(c->c_name)) + continue; + if (strncmp(word, c->c_name, wordlen) == 0) + sl_add(words, c->c_name); + } + + rv = complete_ambiguous(word, list, words); + sl_free(words, 0); + return (rv); +} + +/* + * Complete a local file + */ +static unsigned char +complete_local(char *word, int list) +{ + StringList *words; + char dir[PATH_MAX]; + char *file; + DIR *dd; + struct dirent *dp; + unsigned char rv; + + if ((file = strrchr(word, '/')) == NULL) { + dir[0] = '.'; + dir[1] = '\0'; + file = word; + } else { + if (file == word) { + dir[0] = '/'; + dir[1] = '\0'; + } else { + (void)strlcpy(dir, word, (size_t)(file - word) + 1); + } + file++; + } + + if ((dd = opendir(dir)) == NULL) + return (CC_ERROR); + + words = sl_init(); + + for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (strlen(file) > dp->d_namlen) + continue; + if (strncmp(file, dp->d_name, strlen(file)) == 0) { + char *tcp; + + tcp = strdup(dp->d_name); + if (tcp == NULL) + errx(1, "Can't allocate memory for local dir"); + sl_add(words, tcp); + } + } + closedir(dd); + + rv = complete_ambiguous(file, list, words); + sl_free(words, 1); + return (rv); +} + +/* + * Complete a remote file + */ +static unsigned char +complete_remote(char *word, int list) +{ + static StringList *dirlist; + static char lastdir[PATH_MAX]; + StringList *words; + char dir[PATH_MAX]; + char *file, *cp; + int i; + unsigned char rv; + + char *dummyargv[] = { "complete", dir, NULL }; + + if ((file = strrchr(word, '/')) == NULL) { + dir[0] = '.'; + dir[1] = '\0'; + file = word; + } else { + cp = file; + while (*cp == '/' && cp > word) + cp--; + (void)strlcpy(dir, word, (size_t)(cp - word + 2)); + file++; + } + + if (dirchange || strcmp(dir, lastdir) != 0) { /* dir not cached */ + char *emesg; + + sl_free(dirlist, 1); + dirlist = sl_init(); + + mflag = 1; + emesg = NULL; + if (debug) + (void)putc('\n', ttyout); + while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) { + char *tcp; + + if (!mflag) + continue; + if (*cp == '\0') { + mflag = 0; + continue; + } + tcp = strrchr(cp, '/'); + if (tcp) + tcp++; + else + tcp = cp; + tcp = strdup(tcp); + if (tcp == NULL) + errx(1, "Can't allocate memory for remote dir"); + sl_add(dirlist, tcp); + } + if (emesg != NULL) { + fprintf(ttyout, "\n%s\n", emesg); + return (CC_REDISPLAY); + } + (void)strlcpy(lastdir, dir, sizeof lastdir); + dirchange = 0; + } + + words = sl_init(); + for (i = 0; i < dirlist->sl_cur; i++) { + cp = dirlist->sl_str[i]; + if (strlen(file) > strlen(cp)) + continue; + if (strncmp(file, cp, strlen(file)) == 0) + sl_add(words, cp); + } + rv = complete_ambiguous(file, list, words); + sl_free(words, 0); + return (rv); +} + +/* + * Generic complete routine + */ +unsigned char +complete(EditLine *el, int ch) +{ + static char word[FTPBUFLEN]; + static int lastc_argc, lastc_argo; + struct cmd *c; + const LineInfo *lf; + int celems, dolist; + size_t len; + + lf = el_line(el); + len = lf->lastchar - lf->buffer; + if (len >= sizeof(line)) + return (CC_ERROR); + (void)memcpy(line, lf->buffer, len); + line[len] = '\0'; + cursor_pos = line + (lf->cursor - lf->buffer); + lastc_argc = cursor_argc; /* remember last cursor pos */ + lastc_argo = cursor_argo; + makeargv(); /* build argc/argv of current line */ + + if (cursor_argo >= sizeof(word)) + return (CC_ERROR); + + dolist = 0; + /* if cursor and word is same, list alternatives */ + if (lastc_argc == cursor_argc && lastc_argo == cursor_argo + && strncmp(word, margv[cursor_argc], cursor_argo) == 0) + dolist = 1; + else if (cursor_argo) + memcpy(word, margv[cursor_argc], cursor_argo); + word[cursor_argo] = '\0'; + + if (cursor_argc == 0) + return (complete_command(word, dolist)); + + c = getcmd(margv[0]); + if (c == (struct cmd *)-1 || c == 0) + return (CC_ERROR); + celems = strlen(c->c_complete); + + /* check for 'continuation' completes (which are uppercase) */ + if ((cursor_argc > celems) && (celems > 0) + && isupper((unsigned char)c->c_complete[celems - 1])) + cursor_argc = celems; + + if (cursor_argc > celems) + return (CC_ERROR); + + switch (c->c_complete[cursor_argc - 1]) { + case 'l': /* local complete */ + case 'L': + return (complete_local(word, dolist)); + case 'r': /* remote complete */ + case 'R': + if (connected != -1) { + fputs("\nMust be logged in to complete.\n", ttyout); + return (CC_REDISPLAY); + } + return (complete_remote(word, dolist)); + case 'c': /* command complete */ + case 'C': + return (complete_command(word, dolist)); + case 'n': /* no complete */ + return (CC_ERROR); + } + + return (CC_ERROR); +} + +/* + * Copy characters from src into dst, \ quoting characters that require it. + */ +static void +ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen) +{ + size_t di, si; + + di = si = 0; + while (di + 1 < dstlen && si < srclen && src[si] != '\0') { + switch (src[si]) { + case '\\': + case ' ': + case '\t': + case '\r': + case '\n': + case '"': + /* Need room for two characters and NUL, avoiding + * incomplete escape sequences at end of dst. */ + if (di + 3 >= dstlen) + break; + dst[di++] = '\\'; + /* FALLTHROUGH */ + default: + dst[di++] = src[si++]; + } + } + if (dstlen != 0) + dst[di] = '\0'; +} +#endif /* !SMALL */ diff --git a/cookie.c b/cookie.c new file mode 100644 index 0000000..b5f5beb --- /dev/null +++ b/cookie.c @@ -0,0 +1,221 @@ +/* $OpenBSD: cookie.c,v 1.10 2021/02/16 16:27:34 naddy Exp $ */ + +/* + * Copyright (c) 2007 Pierre-Yves Ritschard + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef NOSSL + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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; + time_t date; + char *line; + char *lbuf = NULL; + size_t lbufsize = 0; + 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); + while (getline(&lbuf, &lbufsize, fp) != -1) { + line = lbuf; + line[strcspn(line, "\r\n")] = '\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/domacro.c b/domacro.c new file mode 100644 index 0000000..cba0f9f --- /dev/null +++ b/domacro.c @@ -0,0 +1,146 @@ +/* $OpenBSD: domacro.c,v 1.22 2019/11/18 04:37:35 deraadt 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 +#include +#include +#include + +#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/extern.h b/extern.h new file mode 100644 index 0000000..e8a8b3b --- /dev/null +++ b/extern.h @@ -0,0 +1,152 @@ +/* $OpenBSD: extern.h,v 1.54 2024/05/21 05:00:48 jsg 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 +#include + +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 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 **); +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); +int timed_connect(int s, const struct sockaddr *, socklen_t, int); + +__dead void usage(void); + +void cookie_get(const char *, const char *, int, char **); +void cookie_load(void); + +#ifndef SMALL +void abortsend(int); +unsigned char complete(EditLine *, int); +void controlediting(void); +void domacro(int, char **); +void list_vertical(StringList *); +void parse_list(char **, char *); +char *remglob2(char **, int, char **, FILE **ftemp, char *type); +int ruserpass(const char *, char **, char **, char **); +void sendrequest(const char *, const char *, const char *, int); +ssize_t http_time(time_t, char *, size_t); +#endif /* !SMALL */ + +extern jmp_buf abortprox; +extern int abrtflag; +extern FILE *cout; +extern int data; +extern char *home; +extern jmp_buf jabort; +extern int family; +extern int proxy; +extern char reply_string[]; +extern off_t restart_point; +extern int keep_alive_timeout; +extern int connect_timeout; +extern int pipeout; +extern char *action; + +#ifndef SMALL +extern int NCMDS; +extern int server_timestamps; +#endif /* !SMALL */ + +extern char *__progname; /* from crt0.o */ + diff --git a/fetch.c b/fetch.c new file mode 100644 index 0000000..47161b0 --- /dev/null +++ b/fetch.c @@ -0,0 +1,1789 @@ +/* $OpenBSD: fetch.c,v 1.221 2025/11/20 11:15:59 tb 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 +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#endif +#include + +#include "compat.h" + +#if !defined(NOSSL) || !NOSSL +#include "tls_compat.h" +#else +struct tls; +typedef struct tls *tls_t; +#endif + +#include "ftp_var.h" +#include "cmds.h" + +static int file_get(const char *, const char *); +static int url_get(const char *, const char *, const char *, int); +static int save_chunked(FILE *, tls_t, int , char *, size_t); +static void aborthttp(int); +static char hextochar(const char *); +static char *urldecode(const char *); +static char *recode_credentials(const char *_userinfo); +static void ftp_close(FILE **, tls_t *, int *); +static const char *sockerror(tls_t); +#ifdef SMALL +#define ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__) +#else +static int ftp_printf(FILE *, const char *, ...); +#endif /* SMALL */ +#ifndef NOSSL +static int proxy_connect(int, char *, char *); +static int stdio_tls_write_wrapper(void *, const char *, int); +static int stdio_tls_read_wrapper(void *, char *, int); +#endif /* !NOSSL */ + +#define FTP_URL "ftp://" /* ftp URL prefix */ +#define HTTP_URL "http://" /* http URL prefix */ +#define HTTPS_URL "https://" /* https URL prefix */ +#define FILE_URL "file:" /* file URL prefix */ +#define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */ +#define HTTP_PROXY "http_proxy" /* env var with http proxy location */ + +#define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0')) + +static const char at_encoding_warning[] = + "Extra `@' characters in usernames and passwords should be encoded as %%40"; + +static jmp_buf httpabort; + +static int redirect_loop; +static int retried; + +/* + * Determine whether the character needs encoding, per RFC2396. + */ +static int +to_encode(const char *c0) +{ + /* 2.4.3. Excluded US-ASCII Characters */ + const char *excluded_chars = + " " /* space */ + "<>#\"" /* delims (modulo "%", see below) */ + "{}|\\^[]`" /* unwise */ + ; + 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) || + + /* + * '%' is also reserved, if is not followed by two + * hexadecimal digits. + */ + strchr(excluded_chars, *c) != NULL || + (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2])))); +} + +/* + * Encode given URL, per RFC2396. + * 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 characters to encode and determine length of the final URL. + */ + for (i = 0; i < length; i++) + if (to_encode(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 (to_encode(path + i)) { + snprintf(epathp, 4, "%%" "%02x", + (unsigned char)path[i]); + epathp += 3; + } else + *(epathp++) = path[i]; + + *epathp = '\0'; + return (epath); +} + +/* + * Copy a local file (used by the OpenBSD installer). + * Returns -1 on failure, 0 on success + */ +static int +file_get(const char *path, const char *outfile) +{ + struct stat st; + int fd, out = -1, rval = -1, save_errno; + volatile sig_t oldintr, oldinti; + const char *savefile; + char *buf = NULL, *cp, *pathbuf = NULL; + const size_t buflen = 128 * 1024; + off_t hashbytes; + ssize_t len, wlen; + + direction = "received"; + + fd = open(path, O_RDONLY); + if (fd == -1) { + warn("Can't open file %s", path); + return -1; + } + + if (fstat(fd, &st) == -1) + filesize = -1; + else + filesize = st.st_size; + + if (outfile != NULL) + savefile = outfile; + else { + if (path[strlen(path) - 1] == '/') /* Consider no file */ + savefile = NULL; /* after dir invalid. */ + else { + pathbuf = strdup(path); + if (pathbuf == NULL) + errx(1, "Can't allocate memory for filename"); + savefile = basename(pathbuf); + } + } + + if (EMPTYSTRING(savefile)) { + warnx("No filename after directory (use -o): %s", path); + goto cleanup_copy; + } + + /* Open the output file. */ + if (!pipeout) { + out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (out == -1) { + warn("Can't open %s", savefile); + goto cleanup_copy; + } + } else + out = fileno(stdout); + + if ((buf = malloc(buflen)) == NULL) + errx(1, "Can't allocate memory for transfer buffer"); + + /* Trap signals */ + oldintr = NULL; + oldinti = NULL; + if (setjmp(httpabort)) { + if (oldintr) + (void)signal(SIGINT, oldintr); + if (oldinti) + (void)signal(SIGINFO, oldinti); + goto cleanup_copy; + } + oldintr = signal(SIGINT, aborthttp); + + bytes = 0; + hashbytes = mark; + progressmeter(-1, path); + + /* Finally, suck down the file. */ + oldinti = signal(SIGINFO, psummary); + while ((len = read(fd, buf, buflen)) > 0) { + bytes += len; + for (cp = buf; len > 0; len -= wlen, cp += wlen) { + if ((wlen = write(out, cp, len)) == -1) { + warn("Writing %s", savefile); + signal(SIGINT, oldintr); + signal(SIGINFO, oldinti); + goto cleanup_copy; + } + } + if (hash && !progress) { + while (bytes >= hashbytes) { + (void)putc('#', ttyout); + hashbytes += mark; + } + (void)fflush(ttyout); + } + } + save_errno = errno; + signal(SIGINT, oldintr); + signal(SIGINFO, oldinti); + if (hash && !progress && bytes > 0) { + if (bytes < mark) + (void)putc('#', ttyout); + (void)putc('\n', ttyout); + (void)fflush(ttyout); + } + if (len == -1) { + warnc(save_errno, "Reading from file"); + goto cleanup_copy; + } + progressmeter(1, NULL); + if (verbose) + ptransfer(0); + + rval = 0; + +cleanup_copy: + free(buf); + free(pathbuf); + if (out >= 0 && out != fileno(stdout)) + close(out); + close(fd); + + return rval; +} + +/* + * Retrieve URL, via the proxy in $proxyvar if necessary. + * Returns -1 on failure, 0 on success + */ +static int +url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile) +{ + char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4]; + char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; + char *epath, *redirurl, *loctail, *h, *p, gerror[200]; + int error, isftpurl = 0, isredirect = 0, rval = -1; + int isunavail = 0, retryafter = -1; + struct addrinfo hints, *res0, *res; + const char *savefile; + char *pathbuf = NULL; + char *proxyurl = NULL; + char *credentials = NULL, *proxy_credentials = NULL; + int fd = -1, out = -1; + volatile sig_t oldintr, oldinti; + FILE *fin = NULL; + off_t hashbytes; + const char *errstr; + ssize_t len, wlen; + size_t bufsize; + char *proxyhost = NULL; +#ifndef NOSSL + char *sslpath = NULL, *sslhost = NULL; + int ishttpsurl = 0; +#endif /* !NOSSL */ +#ifndef SMALL + char *full_host = NULL; + const char *scheme; + char *locbase; + struct addrinfo *ares = NULL; + char tmbuf[32]; + time_t mtime = 0; + struct stat stbuf; + struct tm lmt = { 0 }; + struct timespec ts[2]; +#endif /* !SMALL */ + tls_t tls = NULL; + int status; + int save_errno; + const size_t buflen = 128 * 1024; + int chunked = 0; + + 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 + 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, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) { +#ifndef NOSSL + host = newline + sizeof(HTTPS_URL) - 1; + ishttpsurl = 1; +#else + errx(1, "%s: No HTTPS support", newline); +#endif /* !NOSSL */ +#ifndef SMALL + scheme = HTTPS_URL; +#endif /* !SMALL */ + } else + errx(1, "%s: URL not permitted", newline); + + path = strchr(host, '/'); /* Find path */ + + /* + * Look for auth header in host. + * Basic auth from RFC 2617, valid characters for path are in + * RFC 3986 section 3.3. + */ + if (!isftpurl) { + p = strchr(host, '@'); + if (p != NULL && (path == NULL || p < path)) { + *p++ = '\0'; + credentials = recode_credentials(host); + + /* Overwrite userinfo */ + memmove(host, p, strlen(p) + 1); + path = strchr(host, '/'); + } + } + + 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: + if (outfile) + savefile = outfile; + else { + if (path[strlen(path) - 1] == '/') /* Consider no file */ + savefile = NULL; /* after dir invalid. */ + else { + pathbuf = strdup(path); + if (pathbuf == NULL) + errx(1, "Can't allocate memory for filename"); + savefile = basename(pathbuf); + } + } + + 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 (proxyenv != NULL) { /* use proxy */ +#ifndef NOSSL + if (ishttpsurl) { + sslpath = strdup(path); + sslhost = strdup(host); + if (! sslpath || ! sslhost) + errx(1, "Can't allocate memory for https path/host."); + } +#endif /* !NOSSL */ + proxyhost = strdup(host); + if (proxyhost == NULL) + errx(1, "Can't allocate memory for proxy host."); + proxyurl = strdup(proxyenv); + if (proxyurl == NULL) + errx(1, "Can't allocate memory for proxy URL."); + if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) + host = proxyurl + sizeof(HTTP_URL) - 1; + else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0) + host = proxyurl + sizeof(FTP_URL) - 1; + else { + warnx("Malformed proxy URL: %s", proxyenv); + goto cleanup_url_get; + } + if (EMPTYSTRING(host)) { + warnx("Malformed proxy URL: %s", proxyenv); + goto cleanup_url_get; + } + if (*--path == '\0') + *path = '/'; /* add / back to real path */ + path = strchr(host, '/'); /* remove trailing / on host */ + if (!EMPTYSTRING(path)) + *path++ = '\0'; /* i guess this ++ is useless */ + + path = strchr(host, '@'); /* look for credentials in proxy */ + if (!EMPTYSTRING(path)) { + *path = '\0'; + if (strchr(host, ':') == NULL) { + warnx("Malformed proxy URL: %s", proxyenv); + goto cleanup_url_get; + } + proxy_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 (*host == '[' && (hosttail = strrchr(host, ']')) != NULL && + (hosttail[1] == '\0' || hosttail[1] == ':')) { + host++; + *hosttail++ = '\0'; +#ifndef SMALL + if (asprintf(&full_host, "[%s]", host) == -1) + errx(1, "Cannot allocate memory for hostname"); +#endif /* !SMALL */ + } else + hosttail = host; + + portnum = strrchr(hosttail, ':'); /* find portnum */ + if (portnum != NULL) + *portnum++ = '\0'; +#ifndef NOSSL + port = portnum ? portnum : (ishttpsurl ? httpsport : httpport); +#else /* !NOSSL */ + port = portnum ? portnum : httpport; +#endif /* !NOSSL */ + +#ifndef SMALL + if (full_host == NULL) + if ((full_host = strdup(host)) == NULL) + errx(1, "Cannot allocate memory for hostname"); + if (debug) + fprintf(ttyout, "host %s, port %s, path %s, " + "save as %s, auth %s.\n", host, port, path, + savefile, credentials ? credentials : "none"); +#endif /* !SMALL */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + /* + * If the services file is corrupt/missing, fall back + * on our hard-coded defines. + */ + if (error == EAI_SERVICE && port == httpport) { + snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT); + error = getaddrinfo(host, pbuf, &hints, &res0); +#ifndef NOSSL + } else if (error == EAI_SERVICE && port == httpsport) { + snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT); + error = getaddrinfo(host, pbuf, &hints, &res0); +#endif /* !NOSSL */ + } + if (error) { + warnx("%s: %s", host, gai_strerror(error)); + goto cleanup_url_get; + } + +#ifndef SMALL + if (srcaddr) { + hints.ai_flags |= AI_NUMERICHOST; + error = getaddrinfo(srcaddr, NULL, &hints, &ares); + if (error) { + warnx("%s: %s", srcaddr, gai_strerror(error)); + goto cleanup_url_get; + } + } +#endif /* !SMALL */ + + /* ensure consistent order of the output */ + if (verbose) + setvbuf(ttyout, NULL, _IOLBF, 0); + + fd = -1; + for (res = res0; res; res = res->ai_next) { + if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + strlcpy(hbuf, "(unknown)", sizeof(hbuf)); + if (verbose) + fprintf(ttyout, "Trying %s...\n", hbuf); + + fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd == -1) { + cause = "socket"; + continue; + } + +#ifndef SMALL + if (srcaddr) { + if (ares->ai_family != res->ai_family) { + close(fd); + fd = -1; + errno = EINVAL; + cause = "bind"; + continue; + } + if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) { + save_errno = errno; + close(fd); + errno = save_errno; + fd = -1; + cause = "bind"; + continue; + } + } +#endif /* !SMALL */ + + error = timed_connect(fd, res->ai_addr, res->ai_addrlen, + connect_timeout); + if (error != 0) { + save_errno = errno; + close(fd); + errno = save_errno; + fd = -1; + cause = "connect"; + continue; + } + + /* get port in numeric */ + if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0, + pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0) + port = pbuf; + else + port = NULL; + +#ifndef NOSSL + if (proxyenv && sslhost) + proxy_connect(fd, sslhost, proxy_credentials); +#endif /* !NOSSL */ + break; + } + freeaddrinfo(res0); +#ifndef SMALL + if (srcaddr) + freeaddrinfo(ares); +#endif /* !SMALL */ + if (fd < 0) { + warn("%s", cause); + goto cleanup_url_get; + } + +#ifndef NOSSL + if (ishttpsurl) { + ssize_t ret; + 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, "TLS configuration failure: %s\n", + tls_error(tls)); + goto cleanup_url_get; + } + if (tls_connect_socket(tls, fd, sslhost) != 0) { + fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls)); + goto cleanup_url_get; + } + do { + ret = tls_handshake(tls); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + if (ret != 0) { + fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls)); + goto cleanup_url_get; + } + fin = funopen(tls, stdio_tls_read_wrapper, + stdio_tls_write_wrapper, NULL, NULL); + } else { + fin = fdopen(fd, "r+"); + fd = -1; + } +#else /* !NOSSL */ + fin = fdopen(fd, "r+"); + fd = -1; +#endif /* !NOSSL */ + +#ifdef SMALL + if (lastfile) { + if (pipeout) { + if (pledge("stdio rpath inet dns tty", NULL) == -1) + err(1, "pledge"); + } else { + if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1) + err(1, "pledge"); + } + } +#endif + + /* + * Construct and send the request. Proxy requests don't want leading /. + */ +#ifndef NOSSL + cookie_get(host, path, ishttpsurl, &buf); +#endif /* !NOSSL */ + + epath = url_encode(path); + if (proxyurl) { + if (verbose) { + fprintf(ttyout, "Requesting %s (via %s)\n", + origline, proxyurl); + } + /* + * Host: directive must use the destination host address for + * the original URI (path). + */ + ftp_printf(fin, "GET %s HTTP/1.1\r\n" + "Connection: close\r\n" + "Host: %s\r\n%s%s\r\n" + "Accept: */*\r\n", + epath, proxyhost, buf ? buf : "", httpuseragent); + if (credentials) + ftp_printf(fin, "Authorization: Basic %s\r\n", + credentials); + if (proxy_credentials) + ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n", + proxy_credentials); + ftp_printf(fin, "\r\n"); + } else { + if (verbose) + fprintf(ttyout, "Requesting %s\n", origline); +#ifndef SMALL + if (resume || timestamp) { + if (stat(savefile, &stbuf) == 0) { + if (resume) + restart_point = stbuf.st_size; + if (timestamp) + mtime = stbuf.st_mtime; + } else { + restart_point = 0; + mtime = 0; + } + } +#endif /* SMALL */ + ftp_printf(fin, + "GET /%s HTTP/1.1\r\n" + "Connection: close\r\n" + "Host: ", epath); + if (proxyhost) { + ftp_printf(fin, "%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, "[%s]", h); + free(h); + } else + ftp_printf(fin, "%s", host); + + /* + * Send port number only if it's specified and does not equal + * 80. Some broken HTTP servers get confused if you explicitly + * send them the port number. + */ +#ifndef NOSSL + if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0) + ftp_printf(fin, ":%s", port); + if (restart_point) + ftp_printf(fin, "\r\nRange: bytes=%lld-", + (long long)restart_point); +#else /* !NOSSL */ + if (port && strcmp(port, "80") != 0) + ftp_printf(fin, ":%s", port); +#endif /* !NOSSL */ + +#ifndef SMALL + if (mtime && (http_time(mtime, tmbuf, sizeof(tmbuf)) != 0)) + ftp_printf(fin, "\r\nIf-Modified-Since: %s", tmbuf); +#endif /* SMALL */ + + ftp_printf(fin, "\r\n%s%s\r\n", buf ? buf : "", httpuseragent); + ftp_printf(fin, "Accept: */*\r\n"); + if (credentials) + ftp_printf(fin, "Authorization: Basic %s\r\n", + credentials); + ftp_printf(fin, "\r\n"); + } + free(epath); + +#ifndef NOSSL + free(buf); +#endif /* !NOSSL */ + buf = NULL; + bufsize = 0; + + if (fflush(fin) == EOF) { + warnx("Writing HTTP request: %s", sockerror(tls)); + goto cleanup_url_get; + } + if ((len = getline(&buf, &bufsize, fin)) == -1) { + warnx("Receiving HTTP reply: %s", sockerror(tls)); + 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, 503, &errstr); + if (errstr) { + strnvis(gerror, cp, sizeof gerror, VIS_SAFE); + warnx("Error retrieving %s: %s", origline, gerror); + 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 */ + case 308: /* Permanent Redirect (RFC 7538) */ + isredirect++; + if (redirect_loop++ > 10) { + warnx("Too many redirections requested"); + goto cleanup_url_get; + } + break; +#ifndef SMALL + case 304: /* Not Modified */ + warnx("File is not modified on the server"); + goto cleanup_url_get; + case 416: /* Requested Range Not Satisfiable */ + warnx("File is already fully retrieved."); + goto cleanup_url_get; +#endif /* !SMALL */ + case 503: + isunavail = 1; + break; + default: + strnvis(gerror, cp, sizeof gerror, VIS_SAFE); + warnx("Error retrieving %s: %s", origline, gerror); + goto cleanup_url_get; + } + + /* + * Read the rest of the header. + */ + filesize = -1; + + for (;;) { + if ((len = getline(&buf, &bufsize, fin)) == -1) { + warnx("Receiving HTTP reply: %s", sockerror(tls)); + goto cleanup_url_get; + } + + while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n' || + buf[len-1] == ' ' || buf[len-1] == '\t')) + 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) { + cp += sizeof(CONTENTLEN) - 1; + cp += strspn(cp, " \t"); + 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; + cp += strspn(cp, " \t"); + /* + * 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'; + } + /* Construct 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) { + char *visbuf; + if (stravis(&visbuf, redirurl, VIS_SAFE) == -1) + err(1, "Cannot vis redirect URL"); + fprintf(ttyout, "Redirected to %s\n", visbuf); + free(visbuf); + } + ftp_close(&fin, &tls, &fd); + rval = url_get(redirurl, proxyenv, savefile, lastfile); + free(redirurl); + goto cleanup_url_get; +#define RETRYAFTER "Retry-After:" + } else if (isunavail && + strncasecmp(cp, RETRYAFTER, sizeof(RETRYAFTER) - 1) == 0) { + cp += sizeof(RETRYAFTER) - 1; + cp += strspn(cp, " \t"); + retryafter = strtonum(cp, 0, 0, &errstr); + if (errstr != NULL) + retryafter = -1; +#define TRANSFER_ENCODING "Transfer-Encoding:" + } else if (strncasecmp(cp, TRANSFER_ENCODING, + sizeof(TRANSFER_ENCODING) - 1) == 0) { + cp += sizeof(TRANSFER_ENCODING) - 1; + cp += strspn(cp, " \t"); + if (strcasecmp(cp, "chunked") == 0) + chunked = 1; +#ifndef SMALL +#define LAST_MODIFIED "Last-Modified:" + } else if (strncasecmp(cp, LAST_MODIFIED, + sizeof(LAST_MODIFIED) - 1) == 0) { + cp += sizeof(LAST_MODIFIED) - 1; + cp += strspn(cp, " \t"); + if (strptime(cp, "%a, %d %h %Y %T %Z", &lmt) == NULL) + server_timestamps = 0; +#endif /* !SMALL */ + } + } + free(buf); + buf = NULL; + + /* Content-Length should be ignored for Transfer-Encoding: chunked */ + if (chunked) + filesize = -1; + + if (isunavail) { + if (retried || retryafter != 0) + warnx("Error retrieving %s: 503 Service Unavailable", + origline); + else { + if (verbose) + fprintf(ttyout, "Retrying %s\n", origline); + retried = 1; + ftp_close(&fin, &tls, &fd); + rval = url_get(origline, proxyenv, savefile, lastfile); + } + goto cleanup_url_get; + } + + /* 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 == -1) { + warn("Can't open %s", savefile); + goto cleanup_url_get; + } + } else { + out = fileno(stdout); +#ifdef SMALL + if (lastfile) { + if (pledge("stdio tty", NULL) == -1) + err(1, "pledge"); + } +#endif + } + + if ((buf = malloc(buflen)) == NULL) + errx(1, "Can't allocate memory for transfer buffer"); + + /* 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); + + /* Finally, suck down the file. */ + oldinti = signal(SIGINFO, psummary); + if (chunked) { + error = save_chunked(fin, tls, out, buf, buflen); + signal(SIGINT, oldintr); + signal(SIGINFO, oldinti); + if (error == -1) + goto cleanup_url_get; + } else { + while ((len = fread(buf, 1, buflen, fin)) > 0) { + bytes += len; + for (cp = buf; len > 0; len -= wlen, cp += wlen) { + if ((wlen = write(out, cp, len)) == -1) { + warn("Writing %s", savefile); + signal(SIGINT, oldintr); + signal(SIGINFO, oldinti); + goto cleanup_url_get; + } + } + if (hash && !progress) { + while (bytes >= hashbytes) { + (void)putc('#', ttyout); + hashbytes += mark; + } + (void)fflush(ttyout); + } + } + save_errno = errno; + signal(SIGINT, oldintr); + signal(SIGINFO, oldinti); + if (hash && !progress && bytes > 0) { + if (bytes < mark) + (void)putc('#', ttyout); + (void)putc('\n', ttyout); + (void)fflush(ttyout); + } + if (len == 0 && ferror(fin)) { + errno = save_errno; + warnx("Reading from socket: %s", sockerror(tls)); + 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); + + 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 + free(full_host); +#endif /* !SMALL */ +#ifndef NOSSL + free(sslhost); +#endif /* !NOSSL */ + ftp_close(&fin, &tls, &fd); + if (out >= 0 && out != fileno(stdout)) { +#ifndef SMALL + if (server_timestamps && lmt.tm_zone != NULL && + fstat(out, &stbuf) == 0 && S_ISREG(stbuf.st_mode) != 0) { + ts[0].tv_nsec = UTIME_NOW; + ts[1].tv_nsec = 0; + setenv("TZ", lmt.tm_zone, 1); + if (((ts[1].tv_sec = mktime(&lmt)) != -1) && + (futimens(out, ts) == -1)) + warnx("Unable to set file modification time"); + } +#endif /* !SMALL */ + close(out); + } + free(buf); + free(pathbuf); + free(proxyhost); + free(proxyurl); + free(newline); + free(credentials); + free(proxy_credentials); + return (rval); +} + +static int +save_chunked(FILE *fin, tls_t tls, int out, char *buf, size_t buflen) +{ + + char *header = NULL, *end, *cp; + unsigned long chunksize; + size_t hsize = 0, rlen, wlen; + ssize_t written; + char cr, lf; + + for (;;) { + if (getline(&header, &hsize, fin) == -1) + break; + /* strip CRLF and any optional chunk extension */ + header[strcspn(header, "; \t\r\n")] = '\0'; + errno = 0; + chunksize = strtoul(header, &end, 16); + if (errno || header[0] == '\0' || *end != '\0' || + chunksize > INT_MAX) { + warnx("Invalid chunk size '%s'", header); + free(header); + return -1; + } + + if (chunksize == 0) { + /* We're done. Ignore optional trailer. */ + free(header); + return 0; + } + + for (written = 0; chunksize != 0; chunksize -= rlen) { + rlen = (chunksize < buflen) ? chunksize : buflen; + rlen = fread(buf, 1, rlen, fin); + if (rlen == 0) + break; + bytes += rlen; + for (cp = buf, wlen = rlen; wlen > 0; + wlen -= written, cp += written) { + if ((written = write(out, cp, wlen)) == -1) { + warn("Writing output file"); + free(header); + return -1; + } + } + } + + if (rlen == 0 || + fread(&cr, 1, 1, fin) != 1 || + fread(&lf, 1, 1, fin) != 1) + break; + + if (cr != '\r' || lf != '\n') { + warnx("Invalid chunked encoding"); + free(header); + return -1; + } + } + free(header); + + if (ferror(fin)) + warnx("Error while reading from socket: %s", sockerror(tls)); + else + warnx("Invalid chunked encoding: short read"); + + return -1; +} + +/* + * Abort a http retrieval + */ +static void +aborthttp(int signo) +{ + const char errmsg[] = "\nfetch aborted.\n"; + + write(fileno(ttyout), errmsg, sizeof(errmsg) - 1); + longjmp(httpabort, 1); +} + +/* + * Retrieve multiple files from the command line, transferring + * files of the form "host:path", "ftp://host/path" using the + * ftp protocol, and files of the form "http://host/path" using + * the http protocol. + * If path has a trailing "/", then return (-1); + * the path will be cd-ed into and the connection remains open, + * and the function will return -1 (to indicate the connection + * is alive). + * If an error occurs the return value will be the offset+1 in + * argv[] of the file that caused a problem (i.e, argv[x] + * returns x+1) + * Otherwise, 0 is returned if all files retrieved successfully. + */ +int +auto_fetch(int argc, char *argv[], char *outfile) +{ + char *xargv[5]; + char *cp, *url, *host, *dir, *file, *portnum; + char *username, *pass, *pathstart; + char *ftpproxy, *httpproxy; + int rval, xargc, lastfile; + volatile int argpos; + int dirhasglob, filehasglob, oautologin; + char rempath[PATH_MAX]; + + argpos = 0; + + if (setjmp(toplevel)) { + if (connected) + disconnect(0, NULL); + return (argpos + 1); + } + (void)signal(SIGINT, (sig_t)intr); + (void)signal(SIGPIPE, (sig_t)lostpeer); + + if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0') + ftpproxy = NULL; + if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0') + httpproxy = NULL; + + /* + * Loop through as long as there's files to fetch. + */ + url = username = pass = NULL; + for (rval = 0; (rval == 0) && (argpos < argc); argpos++) { + if (strchr(argv[argpos], ':') == NULL) { + warnx("No colon in URL: %s", argv[argpos]); + rval = argpos + 1; + continue; + } + + free(url); + free(username); + free(pass); + url = username = pass = host = portnum = dir = file = NULL; + + lastfile = (argv[argpos+1] == NULL); + + /* + * We muck with the string, so we make a copy. + */ + url = strdup(argv[argpos]); + if (url == NULL) + errx(1, "Can't allocate memory for auto-fetch."); + + if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) { + if (file_get(url + sizeof(FILE_URL) - 1, outfile) == -1) + rval = argpos + 1; + continue; + } + + /* + * Try HTTP URL-style arguments next. + */ + if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || + strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) { + redirect_loop = 0; + retried = 0; + if (url_get(url, httpproxy, outfile, lastfile) == -1) + rval = argpos + 1; + continue; + } + + /* + * Try FTP URL-style arguments next. If ftpproxy is + * set, use url_get() instead of standard ftp. + * Finally, try host:file. + */ + host = url; + if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) { + char *passend, *passagain, *userend; + + if (ftpproxy) { + if (url_get(url, ftpproxy, outfile, lastfile) == -1) + rval = argpos + 1; + continue; + } + host += sizeof(FTP_URL) - 1; + dir = strchr(host, '/'); + + /* Look for [user:pass@]host[:port] */ + + /* check if we have "user:pass@" */ + userend = strchr(host, ':'); + passend = strchr(host, '@'); + if (passend && userend && userend < passend && + (!dir || passend < dir)) { + username = host; + pass = userend + 1; + host = passend + 1; + *userend = *passend = '\0'; + passagain = strchr(host, '@'); + if (strchr(pass, '@') != NULL || + (passagain != NULL && passagain < dir)) { + warnx(at_encoding_warning); + username = pass = NULL; + goto bad_ftp_url; + } + + if (EMPTYSTRING(username)) { +bad_ftp_url: + warnx("Invalid URL: %s", argv[argpos]); + rval = argpos + 1; + username = pass = NULL; + continue; + } + username = urldecode(username); + pass = urldecode(pass); + } + + /* check [host]:port, or [host] */ + if (host[0] == '[') { + cp = strchr(host, ']'); + if (cp && (!dir || cp < dir)) { + if (cp + 1 == dir || cp[1] == ':') { + host++; + *cp++ = '\0'; + } else + cp = NULL; + } else + cp = host; + } else + cp = host; + + /* split off host[:port] if there is */ + if (cp) { + portnum = strchr(cp, ':'); + pathstart = strchr(cp, '/'); + /* : in path is not a port # indicator */ + if (portnum && pathstart && + pathstart < portnum) + portnum = NULL; + + if (!portnum) + ; + else { + if (!dir) + ; + else if (portnum + 1 < dir) { + *portnum++ = '\0'; + /* + * XXX should check if portnum + * is decimal number + */ + } else { + /* empty portnum */ + goto bad_ftp_url; + } + } + } else + portnum = NULL; + } else { /* classic style `host:file' */ + dir = strchr(host, ':'); + } + + /* + * If dir is NULL, the file wasn't specified + * (URL looked something like ftp://host) + */ + if (dir != NULL) + *dir++ = '\0'; + + if (EMPTYSTRING(host)) { + warnx("No host name in URL: %s", argv[argpos]); + rval = argpos + 1; + continue; + } + + /* + * 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; + } + free(url); + free(username); + free(pass); + 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; +} + +static 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((u_char const *)ui, ulen, creds, credsize) == -1) + errx(1, "error in base64 encoding"); + free(ui); + return (creds); +} + +static char +hextochar(const char *str) +{ + unsigned char c, ret; + + c = str[0]; + ret = c; + if (isalpha(c)) + ret -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + ret -= '0'; + ret *= 16; + + c = str[1]; + ret += c; + if (isalpha(c)) + ret -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + ret -= '0'; + return ret; +} + +int +isurl(const char *p) +{ + + if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 || + strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 || +#ifndef NOSSL + strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 || +#endif /* !NOSSL */ + strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 || + strstr(p, ":/")) + return (1); + return (0); +} + +#ifndef SMALL +static int +ftp_printf(FILE *fp, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vfprintf(fp, fmt, ap); + va_end(ap); + + if (debug) { + va_start(ap, fmt); + vfprintf(ttyout, fmt, ap); + va_end(ap); + } + + return ret; +} +#endif /* !SMALL */ + +static void +ftp_close(FILE **fin, tls_t *tls, int *fd) +{ +#ifndef NOSSL + int ret; + + if (*tls != NULL) { + if (tls_session_fd != -1) + dprintf(STDERR_FILENO, "tls session resumed: %s\n", + tls_conn_session_resumed(*tls) ? "yes" : "no"); + do { + ret = tls_close(*tls); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + tls_free(*tls); + *tls = NULL; + } + if (*fd != -1) { + close(*fd); + *fd = -1; + } +#endif + if (*fin != NULL) { + fclose(*fin); + *fin = NULL; + } +} + +static const char * +sockerror(tls_t tls) +{ + int save_errno = errno; +#ifndef NOSSL + if (tls != NULL) { + const char *tlserr = tls_error(tls); + if (tlserr != NULL) + return tlserr; + } +#endif + return strerror(save_errno); +} + +#ifndef NOSSL +static 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" + "Host: %s:%s\r\n" + "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n", + host, port, host, port, cookie, HTTP_USER_AGENT); + } else { + l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n" + "Host: %s:%s\r\n%s\r\n\r\n", + host, port, 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); +} + +static int +stdio_tls_write_wrapper(void *arg, const char *buf, int len) +{ + tls_t tls = arg; + ssize_t ret; + + do { + ret = tls_write(tls, buf, len); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + return ret; +} + +static int +stdio_tls_read_wrapper(void *arg, char *buf, int len) +{ + tls_t tls = arg; + ssize_t ret; + + do { + ret = tls_read(tls, buf, len); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + return ret; +} +#endif /* !NOSSL */ diff --git a/ftp.1 b/ftp.1 new file mode 100644 index 0000000..c5dde04 --- /dev/null +++ b/ftp.1 @@ -0,0 +1,1821 @@ +.\" $OpenBSD: ftp.1,v 1.124 2022/09/15 12:47:10 millert 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: September 15 2022 $ +.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 sourceaddr +.Op Ar host Op Ar port +.Nm ftp +.Op Fl C +.Op Fl N Ar name +.Op Fl o Ar output +.Op Fl s Ar sourceaddr +.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 CTu +.Op Fl c Ar cookie +.Op Fl N Ar name +.Op Fl o Ar output +.Op Fl S Ar ssl_options +.Op Fl s Ar sourceaddr +.Op Fl U Ar useragent +.Op Fl w Ar seconds +.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 N Ar name +.Op Fl o Ar output +.Op Fl s Ar sourceaddr +.Pf file: Ar +.Nm ftp +.Op Fl C +.Op Fl N Ar name +.Op Fl o Ar output +.Op Fl s Ar sourceaddr +.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 Ar name +Use this alternative name instead of +.Nm +in some error reports. +.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. +.It Cm muststaple +Require the server to present a valid OCSP stapling in the TLS handshake. +.It Cm noverifytime +Disable validation of certificate times and OCSP validation. +.It Cm protocols Ns = Ns Ar protocol_list +Specify the TLS protocols that will be supported by +.Nm +(see +.Xr tls_config_parse_protocols 3 +for details). +.It Cm session Ns = Ns Ar /path/to/session +Specify a file to use for TLS session data. +If this file has a non-zero length, the session data will be read from this file +and the client will attempt to resume the TLS session with the server. +Upon completion of a successful TLS handshake this file will be updated +with new session data, if available. +This file will be created if it does not already exist. +.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 sourceaddr +Set the source address for connections, which is useful on machines +with multiple interfaces. +.It Fl T +Send an +.Dq If-Modified-Since +header to the remote to determine if the remote file's timestamp +has changed. +.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 u +Disable setting the local file's timestamps based +on the +.Dq Last-Modified +header. +By default the local file's timestamps are set to match those +from the remote. +.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. +.It Fl w Ar seconds +Wait for +.Ar seconds +for the remote server to connect before giving up. +.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 Ds +.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 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 +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 +.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 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/ftp.c b/ftp.c new file mode 100644 index 0000000..807a4e9 --- /dev/null +++ b/ftp.c @@ -0,0 +1,2080 @@ +/* $OpenBSD: ftp.c,v 1.109 2023/03/08 04:43:11 guenther 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftp_var.h" + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +union sockaddr_union 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 == -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) == -1) { + cause = "bind"; + error = errno; + close(s); + errno = error; + s = -1; + continue; + } + } +#endif /* !SMALL */ + error = timed_connect(s, res->ai_addr, res->ai_addrlen, + connect_timeout); + if (error != 0) { + /* this "if" clause is to prevent print warning twice */ + if (verbose && res->ai_next) { + if (getnameinfo(res->ai_addr, res->ai_addrlen, + hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST) != 0) + strlcpy(hbuf, "(unknown)", + sizeof(hbuf)); + warn("connect to address %s", hbuf); + } + cause = "connect"; + error = errno; + close(s); + errno = error; + s = -1; + continue; + } + + /* finally we got one */ + break; + } + if (s == -1) { + warn("%s", cause); + code = -1; + freeaddrinfo(res0); + return 0; + } + memcpy(&hisctladdr, res->ai_addr, res->ai_addrlen); + namelen = res->ai_addrlen; + freeaddrinfo(res0); + res0 = res = NULL; +#ifndef SMALL + if (srcaddr) { + freeaddrinfo(ares); + ares = NULL; + } +#endif /* !SMALL */ + if (getsockname(s, &myctladdr.sa, &namelen) == -1) { + warn("getsockname"); + code = -1; + goto bad; + } + if (hisctladdr.sa.sa_family == AF_INET) { + tos = IPTOS_LOWDELAY; + if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) == -1) + 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 == -1 && debug) + warn("setsockopt"); +#else + (void)ret; +#endif /* !SMALL */ + } + + return (hostname); +bad: + (void)close(s); + return (NULL); +} + +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 < ¤t_line[sizeof(current_line) - 1]) + *cp++ = c; + } + if (verbose > 0 || ((verbose > -1 && n == '5') && + (n < '5' || !retry_connect))) { + (void)putc(c, ttyout); + (void)fflush (ttyout); + } + if (lineno == 0) { + size_t len = cp - current_line; + + if (len > sizeof(reply_string)) + len = sizeof(reply_string); + + (void)strlcpy(reply_string, current_line, len); + } + if (continuation && code != originalcode) { + if (originalcode == 0) + originalcode = code; + continue; + } + *cp = '\0'; + if (n != '1') + cpend = 0; + (void)signal(SIGINT, oldintr); + if (code == 421 || originalcode == 421) + lostpeer(); + if (abrtflag && oldintr != cmdabort && oldintr != SIG_IGN) + (*oldintr)(SIGINT); + return (n - '0'); + } +} + +#ifndef SMALL +jmp_buf sendabort; + +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) == -1 || + (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; + +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) == -1) { + 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 == -1) { + 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)) == -1) { + 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) == -1 || 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) == -1) { + 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) == -1) + 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) == -1) { +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 timespec times[2]; + + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = mtime; + times[1].tv_nsec = 0; + if (utimensat(AT_FDCWD, local, times, 0) == -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.sa.sa_family == AF_INET6 + && (IN6_IS_ADDR_LINKLOCAL(&myctladdr.sin6.sin6_addr) + || IN6_IS_ADDR_SITELOCAL(&myctladdr.sin6.sin6_addr))) { + warnx("use of scoped address can be troublesome"); + } +#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.sa.sa_family, SOCK_STREAM, 0); + if (data == -1) { + warn("socket"); + return (1); + } +#ifndef SMALL + if (srcaddr) { + if (bind(data, ares->ai_addr, ares->ai_addrlen) == -1) { + warn("bind"); + close(data); + return (1); + } + } + if ((options & SO_DEBUG) && + setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)) == -1) + warn("setsockopt (ignored)"); +#endif /* !SMALL */ + switch (data_addr.sa.sa_family) { + case AF_INET: + if (epsv4 && !epsv4bad) { + int ov; + /* shut this command up in case it fails */ + ov = verbose; + verbose = -1; + result = command(pasvcmd = "EPSV"); + /* + * now back to whatever verbosity we had before + * and we can try PASV + */ + verbose = ov; + if (code / 10 == 22 && code != 229) { + fputs( +"wrong server: return code must be 229\n", + ttyout); + result = COMPLETE + 1; + } + if (result != COMPLETE) { + epsv4bad = 1; +#ifndef SMALL + if (debug) { + fputs( +"disabling epsv4 for this connection\n", + ttyout); + } +#endif /* !SMALL */ + } + } + if (result != COMPLETE) + result = command(pasvcmd = "PASV"); + break; + case AF_INET6: + result = command(pasvcmd = "EPSV"); + if (code / 10 == 22 && code != 229) { + fputs( +"wrong server: return code must be 229\n", + ttyout); + result = COMPLETE + 1; + } + if (result != COMPLETE) + result = command(pasvcmd = "LPSV"); + break; + default: + result = COMPLETE + 1; + break; + } + if (result != COMPLETE) { + if (activefallback) { + (void)close(data); + data = -1; + passivemode = 0; + activefallback = 0; + goto reinit; + } + fputs("Passive mode refused.\n", ttyout); + goto bad; + } + +#define pack2(var, off) \ + (((var[(off) + 0] & 0xff) << 8) | ((var[(off) + 1] & 0xff) << 0)) +#define pack4(var, off) \ + (((var[(off) + 0] & 0xff) << 24) | ((var[(off) + 1] & 0xff) << 16) | \ + ((var[(off) + 2] & 0xff) << 8) | ((var[(off) + 3] & 0xff) << 0)) + + /* + * What we've got at this point is a string of comma separated + * one-byte unsigned integer values, separated by commas. + */ + if (!pasvcmd) + goto bad; + if (strcmp(pasvcmd, "PASV") == 0) { + if (data_addr.sa.sa_family != AF_INET) { + fputs( +"Passive mode AF mismatch. Shouldn't happen!\n", ttyout); + goto bad; + } + if (code / 10 == 22 && code != 227) { + fputs("wrong server: return code must be 227\n", + ttyout); + goto bad; + } + error = sscanf(pasv, "%u,%u,%u,%u,%u,%u", + &addr[0], &addr[1], &addr[2], &addr[3], + &port[0], &port[1]); + if (error != 6) { + fputs( +"Passive mode address scan failure. Shouldn't happen!\n", ttyout); + goto bad; + } + memset(&data_addr, 0, sizeof(data_addr)); + data_addr.sin.sin_family = AF_INET; + data_addr.sin.sin_len = sizeof(struct sockaddr_in); + data_addr.sin.sin_addr.s_addr = + htonl(pack4(addr, 0)); + data_addr.sin.sin_port = htons(pack2(port, 0)); + } else if (strcmp(pasvcmd, "LPSV") == 0) { + if (code / 10 == 22 && code != 228) { + fputs("wrong server: return code must be 228\n", + ttyout); + goto bad; + } + switch (data_addr.sa.sa_family) { + case AF_INET: + error = sscanf(pasv, +"%u,%u,%u,%u,%u,%u,%u,%u,%u", + &af, &hal, + &addr[0], &addr[1], &addr[2], &addr[3], + &pal, &port[0], &port[1]); + if (error != 9) { + fputs( +"Passive mode address scan failure. Shouldn't happen!\n", ttyout); + goto bad; + } + if (af != 4 || hal != 4 || pal != 2) { + fputs( +"Passive mode AF mismatch. Shouldn't happen!\n", ttyout); + error = 1; + goto bad; + } + + memset(&data_addr, 0, sizeof(data_addr)); + data_addr.sin.sin_family = AF_INET; + data_addr.sin.sin_len = sizeof(struct sockaddr_in); + data_addr.sin.sin_addr.s_addr = + htonl(pack4(addr, 0)); + data_addr.sin.sin_port = htons(pack2(port, 0)); + break; + case AF_INET6: + error = sscanf(pasv, +"%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u", + &af, &hal, + &addr[0], &addr[1], &addr[2], &addr[3], + &addr[4], &addr[5], &addr[6], &addr[7], + &addr[8], &addr[9], &addr[10], + &addr[11], &addr[12], &addr[13], + &addr[14], &addr[15], + &pal, &port[0], &port[1]); + if (error != 21) { + fputs( +"Passive mode address scan failure. Shouldn't happen!\n", ttyout); + goto bad; + } + if (af != 6 || hal != 16 || pal != 2) { + fputs( +"Passive mode AF mismatch. Shouldn't happen!\n", ttyout); + goto bad; + } + + memset(&data_addr, 0, sizeof(data_addr)); + data_addr.sin6.sin6_family = AF_INET6; + data_addr.sin6.sin6_len = sizeof(struct sockaddr_in6); + { + u_int32_t *p32; + p32 = (u_int32_t *)&data_addr.sin6.sin6_addr; + p32[0] = htonl(pack4(addr, 0)); + p32[1] = htonl(pack4(addr, 4)); + p32[2] = htonl(pack4(addr, 8)); + p32[3] = htonl(pack4(addr, 12)); + } + data_addr.sin6.sin6_port = htons(pack2(port, 0)); + break; + default: + fputs("Bad family!\n", ttyout); + goto bad; + } + } else if (strcmp(pasvcmd, "EPSV") == 0) { + char delim[4]; + + port[0] = 0; + if (code / 10 == 22 && code != 229) { + fputs("wrong server: return code must be 229\n", + ttyout); + goto bad; + } + if (sscanf(pasv, "%c%c%c%d%c", &delim[0], + &delim[1], &delim[2], &port[1], + &delim[3]) != 5) { + fputs("parse error!\n", ttyout); + goto bad; + } + if (delim[0] != delim[1] || delim[0] != delim[2] + || delim[0] != delim[3]) { + fputs("parse error!\n", ttyout); + goto bad; + } + data_addr = hisctladdr; + data_addr.sin.sin_port = htons(port[1]); + } else + goto bad; + + error = timed_connect(data, &data_addr.sa, data_addr.sa.sa_len, + connect_timeout); + if (error != 0) { + if (activefallback) { + (void)close(data); + data = -1; + passivemode = 0; + activefallback = 0; + goto reinit; + } + warn("connect"); + goto bad; + } + if (data_addr.sa.sa_family == AF_INET) { + on = IPTOS_THROUGHPUT; + if (setsockopt(data, IPPROTO_IP, IP_TOS, (char *)&on, + sizeof(int)) == -1) + warn("setsockopt TOS (ignored)"); + } + return (0); + } + +noport: + data_addr = myctladdr; + if (sendport) + data_addr.sin.sin_port = 0; /* let system pick one */ + if (data != -1) + (void)close(data); + data = socket(data_addr.sa.sa_family, SOCK_STREAM, 0); + if (data == -1) { + warn("socket"); + if (tmpno) + sendport = 1; + return (1); + } + if (!sendport) + if (setsockopt(data, SOL_SOCKET, SO_REUSEADDR, (char *)&on, + sizeof(on)) == -1) { + warn("setsockopt (reuse address)"); + goto bad; + } + switch (data_addr.sa.sa_family) { + case AF_INET: + on = IP_PORTRANGE_HIGH; + if (setsockopt(data, IPPROTO_IP, IP_PORTRANGE, + (char *)&on, sizeof(on)) == -1) + warn("setsockopt IP_PORTRANGE (ignored)"); + break; + case AF_INET6: + on = IPV6_PORTRANGE_HIGH; + if (setsockopt(data, IPPROTO_IPV6, IPV6_PORTRANGE, + (char *)&on, sizeof(on)) == -1) + warn("setsockopt IPV6_PORTRANGE (ignored)"); + break; + } + if (bind(data, &data_addr.sa, data_addr.sa.sa_len) == -1) { + warn("bind"); + goto bad; + } +#ifndef SMALL + if (options & SO_DEBUG && + setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)) == -1) + warn("setsockopt (ignored)"); +#endif /* !SMALL */ + namelen = sizeof(data_addr); + if (getsockname(data, &data_addr.sa, &namelen) == -1) { + warn("getsockname"); + goto bad; + } + if (listen(data, 1) == -1) + warn("listen"); + +#define UC(b) (((int)b)&0xff) + + if (sendport) { + char hname[NI_MAXHOST], pbuf[NI_MAXSERV]; + int af_tmp; + union sockaddr_union tmp; + + tmp = data_addr; + switch (tmp.sa.sa_family) { + case AF_INET: + if (!epsv4 || epsv4bad) { + result = COMPLETE +1; + break; + } + /*FALLTHROUGH*/ + case AF_INET6: + if (tmp.sa.sa_family == AF_INET6) + tmp.sin6.sin6_scope_id = 0; + af_tmp = (tmp.sa.sa_family == AF_INET) ? 1 : 2; + if (getnameinfo(&tmp.sa, tmp.sa.sa_len, hname, + sizeof(hname), pbuf, sizeof(pbuf), + NI_NUMERICHOST | NI_NUMERICSERV)) { + result = ERROR; + } else { + result = command("EPRT |%d|%s|%s|", + af_tmp, hname, pbuf); + if (result != COMPLETE) { + epsv4bad = 1; +#ifndef SMALL + if (debug) { + fputs( +"disabling epsv4 for this connection\n", + ttyout); + } +#endif /* !SMALL */ + } + } + break; + default: + result = COMPLETE + 1; + break; + } + if (result == COMPLETE) + goto skip_port; + + switch (data_addr.sa.sa_family) { + case AF_INET: + a = (char *)&data_addr.sin.sin_addr; + p = (char *)&data_addr.sin.sin_port; + result = command("PORT %d,%d,%d,%d,%d,%d", + UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(p[0]), UC(p[1])); + break; + case AF_INET6: + a = (char *)&data_addr.sin6.sin6_addr; + p = (char *)&data_addr.sin6.sin6_port; + result = command( +"LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + 6, 16, + UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]), + UC(a[4]),UC(a[5]),UC(a[6]),UC(a[7]), + UC(a[8]),UC(a[9]),UC(a[10]),UC(a[11]), + UC(a[12]),UC(a[13]),UC(a[14]),UC(a[15]), + 2, UC(p[0]), UC(p[1])); + break; + default: + result = COMPLETE + 1; /* xxx */ + } + skip_port: + + if (result == ERROR && sendport == -1) { + sendport = 0; + tmpno = 1; + goto noport; + } + return (result != COMPLETE); + } + if (tmpno) + sendport = 1; + if (data_addr.sa.sa_family == AF_INET) { + on = IPTOS_THROUGHPUT; + if (setsockopt(data, IPPROTO_IP, IP_TOS, (char *)&on, + sizeof(int)) == -1) + warn("setsockopt TOS (ignored)"); + } + return (0); +bad: + (void)close(data), data = -1; + if (tmpno) + sendport = 1; + return (1); +} + +FILE * +dataconn(const char *lmode) +{ + union sockaddr_union from; + socklen_t fromlen = myctladdr.sa.sa_len; + int s; + + if (passivemode) + return (fdopen(data, lmode)); + + s = accept(data, &from.sa, &fromlen); + if (s == -1) { + warn("accept"); + (void)close(data), data = -1; + return (NULL); + } + (void)close(data); + data = s; + if (from.sa.sa_family == AF_INET) { + int tos = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, + sizeof(int)) == -1) { + warn("setsockopt TOS (ignored)"); + } + } + return (fdopen(data, lmode)); +} + +void +psummary(int signo) +{ + int save_errno = errno; + + if (bytes > 0) + ptransfer(1); + errno = save_errno; +} + +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 sockaddr_union mctl; + union sockaddr_union hctl; + FILE *in; + FILE *out; + int tpe; + int curtpe; + int cpnd; + int sunqe; + int runqe; + int mcse; + int ntflg; + char nti[17]; + char nto[17]; + int mapflg; + char mi[PATH_MAX]; + char mo[PATH_MAX]; + } proxstruct, tmpstruct; + struct comvars *ip, *op; + + abrtflag = 0; + oldintr = signal(SIGINT, psabort); + if (flag) { + if (proxy) + return; + ip = &tmpstruct; + op = &proxstruct; + proxy++; + } else { + if (!proxy) + return; + ip = &proxstruct; + op = &tmpstruct; + proxy = 0; + } + ip->connect = connected; + connected = op->connect; + if (hostname) { + (void)strlcpy(ip->name, hostname, sizeof(ip->name)); + } else + ip->name[0] = '\0'; + hostname = op->name; + ip->hctl = hisctladdr; + hisctladdr = op->hctl; + ip->mctl = myctladdr; + myctladdr = op->mctl; + ip->in = cin; + cin = op->in; + ip->out = cout; + cout = op->out; + ip->tpe = type; + type = op->tpe; + ip->curtpe = curtype; + curtype = op->curtpe; + ip->cpnd = cpend; + cpend = op->cpnd; + ip->sunqe = sunique; + sunique = op->sunqe; + ip->runqe = runique; + runique = op->runqe; + ip->mcse = mcase; + mcase = op->mcse; + ip->ntflg = ntflag; + ntflag = op->ntflg; + (void)strlcpy(ip->nti, ntin, sizeof(ip->nti)); + (void)strlcpy(ntin, op->nti, sizeof ntin); + (void)strlcpy(ip->nto, ntout, sizeof(ip->nto)); + (void)strlcpy(ntout, op->nto, sizeof ntout); + ip->mapflg = mapflag; + mapflag = op->mapflg; + (void)strlcpy(ip->mi, mapin, sizeof(ip->mi)); + (void)strlcpy(mapin, op->mi, sizeof mapin); + (void)strlcpy(ip->mo, mapout, sizeof(ip->mo)); + (void)strlcpy(mapout, op->mo, sizeof mapout); + (void)signal(SIGINT, oldintr); + if (abrtflag) { + abrtflag = 0; + (*oldintr)(SIGINT); + } +} + +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 == -1) + 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 +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)) == -1) { + 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 == -1) { + 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)) == -1) + break; + if (ext != '0') + cp--; + else if (*(cp - 2) == '.') + *(cp - 1) = '1'; + else { + *(cp - 2) = *(cp - 2) + 1; + cp--; + } + } + return (new); +} + +jmp_buf forceabort; + +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 == -1) + 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/ftp_var.h b/ftp_var.h new file mode 100644 index 0000000..878ae4d --- /dev/null +++ b/ftp_var.h @@ -0,0 +1,241 @@ +/* $OpenBSD: ftp_var.h,v 1.46 2021/02/02 12:58:42 robert 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 +#include +#include + +#include "compat.h" + +#ifndef SMALL +#include +#endif /* !SMALL */ + +#ifndef NOSSL +#ifdef __OpenBSD__ +#include +#else +#include "tls_compat.h" +#endif +#endif + +#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. + */ +extern int trace; /* trace packets exchanged */ +extern int hash; /* print # for each buffer transferred */ +extern int mark; /* number of bytes between hashes */ +extern int sendport; /* use PORT/LPRT cmd for each data connection */ +extern int verbose; /* print messages coming back from server */ +extern int connected; /* 1 = connected to server, -1 = logged in */ +extern int fromatty; /* input is from a terminal */ +extern int interactive; /* interactively prompt on m* cmds */ +#ifndef SMALL +extern int confirmrest; /* confirm rest of current m* cmd */ +extern int debug; /* debugging level */ +extern int bell; /* ring bell on cmd completion */ +extern char *altarg; /* argv[1] with no shell-like preprocessing */ +#endif /* !SMALL */ +extern int doglob; /* glob local file names */ +extern int autologin; /* establish user account on connection */ +extern int proxy; /* proxy server connection active */ +extern int proxflag; /* proxy connection exists */ +extern int gatemode; /* use gate-ftp */ +extern char *gateserver; /* server to use for gate-ftp */ +extern int sunique; /* store files on server with unique name */ +extern int runique; /* store local files with unique name */ +extern int mcase; /* map upper to lower case for mget names */ +extern int ntflag; /* use ntin ntout tables for name translation */ +extern int mapflag; /* use mapin mapout templates on file names */ +extern int preserve; /* preserve modification time on files */ +extern int progress; /* display transfer progress bar */ +extern int code; /* return/reply code for ftp command */ +extern int crflag; /* if 1, strip car. rets. on ascii gets */ +extern char pasv[BUFSIZ]; /* passive port for proxy data connection */ +extern int passivemode; /* passive mode enabled */ +extern int activefallback; /* fall back to active mode if passive fails */ +extern char ntin[17]; /* input translation table */ +extern char ntout[17]; /* output translation table */ +extern char mapin[PATH_MAX]; /* input map template */ +extern char mapout[PATH_MAX]; /* output map template */ +extern char typename[32]; /* name of file transfer type */ +extern int type; /* requested file transfer type */ +extern int curtype; /* current file transfer type */ +extern char structname[32]; /* name of file transfer structure */ +extern int stru; /* file transfer structure */ +extern char formname[32]; /* name of file transfer format */ +extern int form; /* file transfer format */ +extern char modename[32]; /* name of file transfer mode */ +extern int mode; /* file transfer mode */ +extern char bytename[32]; /* local byte size in ascii */ +extern int bytesize; /* local byte size in binary */ +extern int anonftp; /* automatic anonymous login */ +extern int dirchange; /* remote directory changed by cd command */ +extern unsigned int retry_connect; /* retry connect if failed */ +extern int ttywidth; /* width of tty */ +extern int epsv4; /* use EPSV/EPRT on IPv4 connections */ +extern int epsv4bad; /* EPSV doesn't work on the current server */ + +#ifndef SMALL +extern int editing; /* command line editing enabled */ +extern EditLine *el; /* editline(3) status structure */ +extern History *hist; /* editline(3) history structure */ +extern char *cursor_pos; /* cursor position we're looking for */ +extern size_t cursor_argc; /* location of cursor in margv */ +extern size_t cursor_argo; /* offset of cursor in margv[cursor_argc] */ +extern int resume; /* continue transfer */ +extern int timestamp; /* send an If-Modified-Since header */ +extern char *srcaddr; /* source address to bind to */ +#endif /* !SMALL */ + +extern char *cookiefile; /* cookie jar to use */ + +extern off_t bytes; /* current # of bytes read */ +extern off_t filesize; /* size of file being transferred */ +extern char *direction; /* direction transfer is occurring */ + +extern char *hostname; /* name of host connected to */ +extern int unix_server; /* server is unix, can use binary for ascii */ +extern int unix_proxy; /* proxy is unix, can use binary for ascii */ + +extern char *ftpport; /* port number to use for ftp connections */ +extern char *httpport; /* port number to use for http connections */ +#ifndef NOSSL +extern char *httpsport; /* port number to use for https connections */ +#endif /* !SMALL */ +extern char *httpuseragent; /* user agent for http(s) connections */ +extern char *gateport; /* port number to use for gateftp connections */ + +extern jmp_buf toplevel; /* non-local goto stuff for cmd scanner */ + +#ifndef SMALL +extern char line[FTPBUFLEN]; /* input line buffer */ +extern char *argbase; /* current storage point in arg buffer */ +extern char *stringbase; /* current scan point in line buffer */ +extern char argbuf[FTPBUFLEN]; /* argument storage buffer */ +extern StringList *marg_sl; /* stringlist containing margv */ +extern int margc; /* count of arguments on input line */ +extern int options; /* used during socket creation */ +#endif /* !SMALL */ + +#define margv (marg_sl->sl_str) /* args parsed from input line */ +extern int cpend; /* flag: if != 0, then pending server reply */ +extern 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 +extern int macnum; /* number of defined macros */ +extern struct macel macros[16]; +extern char macbuf[4096]; +#endif /* !SMALL */ + +extern FILE *ttyout; /* stdout or stderr, depending on interactive */ + +extern struct cmd cmdtab[]; + +#ifndef NOSSL +#include "tls_compat.h" +extern tls_config_t tls_config; +extern int tls_session_fd; +#endif /* !NOSSL */ diff --git a/list.c b/list.c new file mode 100644 index 0000000..d95945e --- /dev/null +++ b/list.c @@ -0,0 +1,86 @@ +/* $OpenBSD: list.c,v 1.9 2019/05/16 12:44:18 florian Exp $ */ + +/* + * Copyright (c) 2008 Martynas Venckus + * + * 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 + +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, "") == 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/main.c b/main.c new file mode 100644 index 0000000..0124714 --- /dev/null +++ b/main.c @@ -0,0 +1,1093 @@ +/* $OpenBSD: main.c,v 1.146 2023/12/23 23:03:00 kn 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NOSSL +#include "tls_compat.h" +#endif + +#include "cmds.h" +#include "ftp_var.h" + +int trace; +int hash; +int mark; +int sendport; +int verbose; +int connected; +int fromatty; +int interactive; +#ifndef SMALL +int confirmrest; +int debug; +int bell; +char *altarg; +#endif /* !SMALL */ +int doglob; +int autologin; +int proxy; +int proxflag; +int gatemode; +char *gateserver; +int sunique; +int runique; +int mcase; +int ntflag; +int mapflag; +int preserve; +int progress; +int code; +int crflag; +char pasv[BUFSIZ]; +int passivemode; +int activefallback; +char ntin[17]; +char ntout[17]; +char mapin[PATH_MAX]; +char mapout[PATH_MAX]; +char typename[32]; +int type; +int curtype; +char structname[32]; +int stru; +char formname[32]; +int form; +char modename[32]; +int mode; +char bytename[32]; +int bytesize; +int anonftp; +int dirchange; +unsigned int retry_connect; +int ttywidth; +int epsv4; +int epsv4bad; + +#ifndef SMALL +int editing; +EditLine *el; +History *hist; +char *cursor_pos; +size_t cursor_argc; +size_t cursor_argo; +int resume; +char *srcaddr; +int timestamp; +#endif /* !SMALL */ + +char *cookiefile; + +off_t bytes; +off_t filesize; +char *direction; + +char *hostname; +int unix_server; +int unix_proxy; + +char *ftpport; +char *httpport; +#ifndef NOSSL +char *httpsport; +#endif /* !SMALL */ +char *httpuseragent; +char *gateport; + +jmp_buf toplevel; + +#ifndef SMALL +char line[FTPBUFLEN]; +char *argbase; +char *stringbase; +char argbuf[FTPBUFLEN]; +StringList *marg_sl; +int margc; +int options; +#endif /* !SMALL */ + +int cpend; +int mflag; + +#ifndef SMALL +int macnum; +struct macel macros[16]; +char macbuf[4096]; +#endif /* !SMALL */ + +FILE *ttyout; + +int connect_timeout; + +#ifndef SMALL +/* enable using server timestamps by default */ +int server_timestamps = 1; +#endif + +#ifndef NOSSL +char * const ssl_verify_opts[] = { +#define SSL_CAFILE 0 + "cafile", +#define SSL_CAPATH 1 + "capath", +#define SSL_CIPHERS 2 + "ciphers", +#define SSL_DONTVERIFY 3 + "dont", +#define SSL_DOVERIFY 4 + "do", +#define SSL_VERIFYDEPTH 5 + "depth", +#define SSL_MUSTSTAPLE 6 + "muststaple", +#define SSL_NOVERIFYTIME 7 + "noverifytime", +#define SSL_SESSION 8 + "session", +#define SSL_PROTOCOLS 9 + "protocols", + NULL +}; + +tls_config_t tls_config; +int tls_session_fd = -1; + +static void +process_ssl_options(char *cp) +{ + const char *errstr; + char *str; + int depth; + uint32_t protocols; + + while (*cp) { + switch (getsubopt(&cp, ssl_verify_opts, &str)) { + case SSL_CAFILE: + if (str == NULL) + errx(1, "missing CA file"); + if (tls_config_set_ca_file(tls_config, str) != 0) + errx(1, "tls ca file failed: %s", + tls_config_error(tls_config)); + break; + case SSL_CAPATH: + if (str == NULL) + errx(1, "missing CA directory path"); + if (tls_config_set_ca_path(tls_config, str) != 0) + errx(1, "tls ca path failed: %s", + tls_config_error(tls_config)); + break; + case SSL_CIPHERS: + if (str == NULL) + errx(1, "missing cipher list"); + if (tls_config_set_ciphers(tls_config, str) != 0) + errx(1, "tls ciphers failed: %s", + tls_config_error(tls_config)); + break; + case SSL_DONTVERIFY: + tls_config_insecure_noverifycert(tls_config); + tls_config_insecure_noverifyname(tls_config); + break; + case SSL_DOVERIFY: + tls_config_verify(tls_config); + break; + case SSL_VERIFYDEPTH: + if (str == NULL) + errx(1, "missing depth"); + depth = strtonum(str, 0, INT_MAX, &errstr); + if (errstr) + errx(1, "certificate validation depth is %s", + errstr); + tls_config_set_verify_depth(tls_config, depth); + break; + case SSL_MUSTSTAPLE: + tls_config_ocsp_require_stapling(tls_config); + break; + case SSL_NOVERIFYTIME: + tls_config_insecure_noverifytime(tls_config); + break; + case SSL_SESSION: + if (str == NULL) + errx(1, "missing session file"); + if ((tls_session_fd = open(str, O_RDWR|O_CREAT, + 0600)) == -1) + err(1, "failed to open or create session file " + "'%s'", str); + if (tls_config_set_session_fd(tls_config, + tls_session_fd) == -1) + errx(1, "failed to set session: %s", + tls_config_error(tls_config)); + break; + case SSL_PROTOCOLS: + if (str == NULL) + errx(1, "missing protocol name"); + if (tls_config_parse_protocols(&protocols, str) != 0) + errx(1, "failed to parse TLS protocols"); + if (tls_config_set_protocols(tls_config, protocols) != 0) + errx(1, "failed to set TLS protocols: %s", + tls_config_error(tls_config)); + break; + default: + errx(1, "unknown -S suboption `%s'", + suboptarg ? suboptarg : ""); + /* NOTREACHED */ + } + } +} +#endif /* !NOSSL */ + +int family = PF_UNSPEC; +int pipeout; + +int +main(volatile int argc, char *argv[]) +{ + int ch, rval; +#ifndef SMALL + int top; +#endif + struct passwd *pw = NULL; + char *cp, homedir[PATH_MAX]; + char *outfile = NULL; + const char *errstr; + int dumb_terminal = 0; + + ftpport = "ftp"; + httpport = "http"; +#ifndef NOSSL + httpsport = "https"; +#endif /* !NOSSL */ + gateport = getenv("FTPSERVERPORT"); + if (gateport == NULL || *gateport == '\0') + gateport = "ftpgate"; + doglob = 1; + interactive = 1; + autologin = 1; + passivemode = 1; + activefallback = 1; + preserve = 1; + verbose = 0; + progress = 0; + gatemode = 0; +#ifndef NOSSL + cookiefile = NULL; +#endif /* NOSSL */ +#ifndef SMALL + editing = 0; + el = NULL; + hist = NULL; + resume = 0; + timestamp = 0; + srcaddr = NULL; + marg_sl = sl_init(); +#endif /* !SMALL */ + mark = HASHBYTES; + epsv4 = 1; + epsv4bad = 0; + + /* Set default operation mode based on FTPMODE environment variable */ + if ((cp = getenv("FTPMODE")) != NULL && *cp != '\0') { + if (strcmp(cp, "passive") == 0) { + passivemode = 1; + activefallback = 0; + } else if (strcmp(cp, "active") == 0) { + passivemode = 0; + activefallback = 0; + } else if (strcmp(cp, "gate") == 0) { + gatemode = 1; + } else if (strcmp(cp, "auto") == 0) { + passivemode = 1; + activefallback = 1; + } else + warnx("unknown FTPMODE: %s. Using defaults", cp); + } + + if (strcmp(__progname, "gate-ftp") == 0) + gatemode = 1; + gateserver = getenv("FTPSERVER"); + if (gateserver == NULL) + gateserver = ""; + if (gatemode) { + if (*gateserver == '\0') { + warnx( +"Neither $FTPSERVER nor $GATE_SERVER is defined; disabling gate-ftp"); + gatemode = 0; + } + } + + cp = getenv("TERM"); + dumb_terminal = (cp == NULL || *cp == '\0' || !strcmp(cp, "dumb") || + !strcmp(cp, "emacs") || !strcmp(cp, "su")); + fromatty = isatty(fileno(stdin)); + if (fromatty) { + verbose = 1; /* verbose if from a tty */ +#ifndef SMALL + if (!dumb_terminal) + editing = 1; /* editing mode on if tty is usable */ +#endif /* !SMALL */ + } + + ttyout = stdout; + if (isatty(fileno(ttyout)) && !dumb_terminal && foregroundproc()) + progress = 1; /* progress bar on if tty is usable */ + +#ifndef NOSSL + cookiefile = getenv("http_cookies"); + if (tls_config == NULL) { + tls_config = tls_config_new(); + if (tls_config == NULL) + errx(1, "tls config failed"); + if (tls_config_set_protocols(tls_config, + TLS_PROTOCOLS_ALL) != 0) + errx(1, "tls set protocols failed: %s", + tls_config_error(tls_config)); + if (tls_config_set_ciphers(tls_config, "DEFAULT") != 0) + errx(1, "tls set ciphers failed: %s", + tls_config_error(tls_config)); + } +#endif /* !NOSSL */ + + httpuseragent = NULL; + + while ((ch = getopt(argc, argv, + "46AaCc:dD:EeN:gik:Mmno:pP:r:S:s:TtU:uvVw:")) != -1) { + switch (ch) { + case '4': + family = PF_INET; + break; + case '6': + family = PF_INET6; + break; + case 'A': + activefallback = 0; + passivemode = 0; + break; + + case 'N': + setprogname(optarg); + 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; + 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 NOSSL + process_ssl_options(optarg); +#endif /* !NOSSL */ + break; + + case 's': +#ifndef SMALL + srcaddr = optarg; +#endif /* !SMALL */ + break; + +#ifndef SMALL + case 'T': + timestamp = 1; + break; +#endif /* !SMALL */ + 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; + case 'u': + server_timestamps = 0; + break; +#endif /* !SMALL */ + + case 'v': + verbose = 1; + break; + + case 'V': + verbose = 0; + break; + + case 'w': + connect_timeout = strtonum(optarg, 0, 200, &errstr); + if (errstr) + errx(1, "-w: %s", errstr); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + +#ifndef NOSSL + cookie_load(); +#endif /* !NOSSL */ + if (httpuseragent == NULL) + httpuseragent = HTTP_USER_AGENT; + + cpend = 0; /* no pending replies */ + proxy = 0; /* proxy not active */ + crflag = 1; /* strip c.r. on ascii gets */ + sendport = -1; /* not using ports */ + /* + * Set up the home directory in case we're globbing. + */ + cp = getlogin(); + if (cp != NULL) { + pw = getpwnam(cp); + } + if (pw == NULL) + pw = getpwuid(getuid()); + if (pw != NULL) { + (void)strlcpy(homedir, pw->pw_dir, sizeof homedir); + home = homedir; + } + + setttywidth(0); + (void)signal(SIGWINCH, setttywidth); + + if (argc > 0) { + if (isurl(argv[0])) { + if (pipeout) { + if (pledge("stdio rpath dns tty inet", + NULL) == -1) + err(1, "pledge"); + } else { +#ifndef SMALL + if (outfile == NULL) { + if (pledge("stdio rpath wpath cpath dns tty inet proc exec fattr", + NULL) == -1) + err(1, "pledge"); + } else +#endif /* !SMALL */ + if (pledge("stdio rpath wpath cpath dns tty inet fattr", + NULL) == -1) + err(1, "pledge"); + } + + rval = auto_fetch(argc, argv, outfile); + /* -1 == connected and cd-ed */ + if (rval >= 0 || outfile != NULL) + 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 */ + +__dead void +usage(void) +{ + fprintf(stderr, "usage: " +#ifndef SMALL + "ftp [-46AadEegiMmnptVv] [-D title] [-k seconds] [-P port] " + "[-r seconds]\n" + " [-s sourceaddr] [host [port]]\n" + " ftp [-C] [-N name] [-o output] [-s sourceaddr]\n" + " ftp://[user:password@]host[:port]/file[/] ...\n" + " ftp [-CTu] [-c cookie] [-N name] [-o output] [-S ssl_options] " + "[-s sourceaddr]\n" + " [-U useragent] [-w seconds] " + "http[s]://[user:password@]host[:port]/file ...\n" + " ftp [-C] [-N name] [-o output] [-s sourceaddr] file:file ...\n" + " ftp [-C] [-N name] [-o output] [-s sourceaddr] host:/file[/] ...\n" +#else /* !SMALL */ + "ftp [-N name] [-o output] " + "ftp://[user:password@]host[:port]/file[/] ...\n" +#ifndef NOSSL + " ftp [-N name] [-o output] [-S ssl_options] [-w seconds] " + "http[s]://[user:password@]host[:port]/file ...\n" +#else + " ftp [-N name] [-o output] [-w seconds] http://host[:port]/file ...\n" +#endif /* NOSSL */ + " ftp [-N name] [-o output] file:file ...\n" + " ftp [-N name] [-o output] host:/file[/] ...\n" +#endif /* !SMALL */ + ); + exit(1); +} diff --git a/pathnames.h b/pathnames.h new file mode 100644 index 0000000..7524460 --- /dev/null +++ b/pathnames.h @@ -0,0 +1,37 @@ +/* $OpenBSD: pathnames.h,v 1.9 2019/05/16 12:44:18 florian Exp $ */ +/* $NetBSD: pathnames.h,v 1.7 1997/01/09 20:19:40 tls Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + */ + +#include + +#define TMPFILE "ftpXXXXXXXXXX" diff --git a/ruserpass.c b/ruserpass.c new file mode 100644 index 0000000..6e5adb5 --- /dev/null +++ b/ruserpass.c @@ -0,0 +1,317 @@ +/* $OpenBSD: ruserpass.c,v 1.33 2019/06/28 13:35:01 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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)) == -1) + 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/small.c b/small.c new file mode 100644 index 0000000..484d78f --- /dev/null +++ b/small.c @@ -0,0 +1,721 @@ +/* $OpenBSD: small.c,v 1.13 2023/03/08 04:43:11 guenther 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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. + */ +void +setbinary(int argc, char *argv[]) +{ + + stype[1] = "binary"; + settype(2, stype); +} + +void +get(int argc, char *argv[]) +{ + + (void)getit(argc, argv, 0, restart_point ? "a+w" : "w" ); +} + +/* + * Receive one file. + */ +int +getit(int argc, char *argv[], int restartit, const char *mode) +{ + int loc = 0; + int rval = 0; + char *oldargv1, *oldargv2, *globargv2; + + if (argc == 2) { + argc++; + argv[2] = argv[1]; + loc++; + } +#ifndef SMALL + if (argc < 2 && !another(&argc, &argv, "remote-file")) + goto usage; + if ((argc < 3 && !another(&argc, &argv, "local-file")) || argc > 3) { +usage: + fprintf(ttyout, "usage: %s remote-file [local-file]\n", + argv[0]); + code = -1; + return (0); + } +#endif /* !SMALL */ + oldargv1 = argv[1]; + oldargv2 = argv[2]; + if (!globulize(&argv[2])) { + code = -1; + return (0); + } + globargv2 = argv[2]; + if (loc && mcase) { + char *tp = argv[1], *tp2, tmpbuf[PATH_MAX]; + + while (*tp && !islower((unsigned char)*tp)) { + tp++; + } + if (!*tp) { + tp = argv[2]; + tp2 = tmpbuf; + while ((*tp2 = *tp) != '\0') { + if (isupper((unsigned char)*tp2)) { + *tp2 = tolower((unsigned char)*tp2); + } + tp++; + tp2++; + } + argv[2] = tmpbuf; + } + } + if (loc && ntflag) + argv[2] = dotrans(argv[2]); + if (loc && mapflag) + argv[2] = domap(argv[2]); +#ifndef SMALL + if (restartit) { + struct stat stbuf; + int ret; + + ret = stat(argv[2], &stbuf); + if (restartit == 1) { + restart_point = (ret < 0) ? 0 : stbuf.st_size; + } else { + if (ret == 0) { + time_t mtime; + + mtime = remotemodtime(argv[1], 0); + if (mtime == -1) + goto freegetit; + if (stbuf.st_mtime >= mtime) { + rval = 1; + fprintf(ttyout, + "Local file \"%s\" is newer "\ + "than remote file \"%s\".\n", + argv[2], argv[1]); + goto freegetit; + } + } + } + } +#endif /* !SMALL */ + + recvrequest("RETR", argv[2], argv[1], mode, + argv[1] != oldargv1 || argv[2] != oldargv2 || !interactive, loc); + restart_point = 0; +#ifndef SMALL +freegetit: +#endif + if (oldargv2 != globargv2) /* free up after globulize() */ + free(globargv2); + return (rval); +} + +/* XXX - Signal race. */ +void +mabort(int signo) +{ + int save_errno = errno; + + alarmtimer(0); + (void) write(fileno(ttyout), "\n\r", 2); +#ifndef SMALL + if (mflag && fromatty) { + /* XXX signal race, crazy unbelievable stdio misuse */ + if (confirm(mname, NULL)) { + errno = save_errno; + longjmp(jabort, 1); + } + } +#endif /* !SMALL */ + mflag = 0; + errno = save_errno; + longjmp(jabort, 1); +} + +/* + * Get multiple files. + */ +void +mget(int argc, char *argv[]) +{ + extern int optind, optreset; + sig_t oldintr; + int xargc = 2; + char *cp, localcwd[PATH_MAX], *xargv[] = { argv[0], NULL, NULL }; + static int restartit = 0; +#ifndef SMALL + extern char *optarg; + const char *errstr; + int ch, i = 1; + char type = 0, *dummyargv[] = { argv[0], ".", NULL }; + FILE *ftemp = NULL; + static int depth = 0, max_depth = 0; + + optind = optreset = 1; + + if (depth) + depth++; + + while ((ch = getopt(argc, argv, "cd:nr")) != -1) { + switch(ch) { + case 'c': + restartit = 1; + break; + case 'd': + max_depth = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) { + fprintf(ttyout, "bad depth value, %s: %s\n", + errstr, optarg); + code = -1; + return; + } + break; + case 'n': + restartit = -1; + break; + case 'r': + depth = 1; + break; + default: + goto usage; + } + } + + if (argc - optind < 1 && !another(&argc, &argv, "remote-files")) { +usage: + fprintf(ttyout, "usage: %s [-cnr] [-d depth] remote-files\n", + argv[0]); + code = -1; + return; + } + + argv[optind - 1] = argv[0]; + argc -= optind - 1; + argv += optind - 1; +#endif /* !SMALL */ + + mname = argv[0]; + mflag = 1; + if (getcwd(localcwd, sizeof(localcwd)) == NULL) + err(1, "can't get cwd"); + + oldintr = signal(SIGINT, mabort); + (void)setjmp(jabort); + while ((cp = +#ifdef SMALL + remglob(argv, proxy, NULL)) != NULL + ) { +#else /* SMALL */ + depth ? remglob2(dummyargv, proxy, NULL, &ftemp, &type) : + remglob(argv, proxy, NULL)) != NULL + || (mflag && depth && ++i < argc) + ) { + if (cp == NULL) + continue; +#endif /* SMALL */ + if (*cp == '\0') { + mflag = 0; + continue; + } + if (!mflag) + continue; +#ifndef SMALL + if (depth && fnmatch(argv[i], cp, FNM_PATHNAME) != 0) + continue; +#endif /* !SMALL */ + if (!fileindir(cp, localcwd)) { + fprintf(ttyout, "Skipping non-relative filename `%s'\n", + cp); + continue; + } +#ifndef SMALL + if (type == 'd' && depth == max_depth) + continue; + if (!confirm(argv[0], cp)) + continue; + if (type == 'd') { + mkdir(cp, 0755); + if (chdir(cp) != 0) { + warn("local: %s", cp); + continue; + } + + xargv[1] = cp; + cd(xargc, xargv); + if (dirchange != 1) + goto out; + + xargv[1] = "*"; + mget(xargc, xargv); + + xargv[1] = ".."; + cd(xargc, xargv); + if (dirchange != 1) { + mflag = 0; + goto out; + } + +out: + if (chdir("..") != 0) { + warn("local: %s", cp); + mflag = 0; + } + continue; + } + if (type == 's') + /* Currently ignored. */ + continue; +#endif /* !SMALL */ + xargv[1] = cp; + (void)getit(xargc, xargv, restartit, + (restartit == 1 || restart_point) ? "a+w" : "w"); +#ifndef SMALL + if (!mflag && fromatty) { + if (confirm(argv[0], NULL)) + mflag = 1; + } +#endif /* !SMALL */ + } + (void)signal(SIGINT, oldintr); +#ifndef SMALL + if (depth) + depth--; + if (depth == 0 || mflag == 0) + depth = max_depth = mflag = restartit = 0; +#else /* !SMALL */ + mflag = 0; +#endif /* !SMALL */ +} + +/* + * Set current working directory on remote machine. + */ +void +cd(int argc, char *argv[]) +{ + int r; + +#ifndef SMALL + if ((argc < 2 && !another(&argc, &argv, "remote-directory")) || + argc > 2) { + fprintf(ttyout, "usage: %s remote-directory\n", argv[0]); + code = -1; + return; + } +#endif /* !SMALL */ + r = command("CWD %s", argv[1]); + if (r == ERROR && code == 500) { + if (verbose) + fputs("CWD command not recognized, trying XCWD.\n", ttyout); + r = command("XCWD %s", argv[1]); + } + if (r == ERROR && code == 550) { + dirchange = 0; + return; + } + if (r == COMPLETE) + dirchange = 1; +} + +/* + * Terminate session, but don't exit. + */ +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/small.h b/small.h new file mode 100644 index 0000000..6dc289f --- /dev/null +++ b/small.h @@ -0,0 +1,35 @@ +/* $OpenBSD: small.h,v 1.3 2019/05/16 12:44:18 florian Exp $ */ + +/* + * Copyright (c) 2009 Martynas Venckus + * + * 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/stringlist.c b/stringlist.c new file mode 100644 index 0000000..00eb711 --- /dev/null +++ b/stringlist.c @@ -0,0 +1,97 @@ +/* $OpenBSD: stringlist.c,v 1.14 2019/05/16 12:44:18 florian Exp $ */ +/* $NetBSD: stringlist.c,v 1.2 1997/01/17 07:26:20 lukem Exp $ */ + +/* + * Copyright (c) 1994 Christos Zoulas + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef SMALL + +#include +#include +#include +#include + +#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/stringlist.h b/stringlist.h new file mode 100644 index 0000000..5e9e090 --- /dev/null +++ b/stringlist.h @@ -0,0 +1,55 @@ +/* $OpenBSD: stringlist.h,v 1.9 2024/05/21 05:00:48 jsg 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 + +/* + * 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); +__END_DECLS + +#endif /* _STRINGLIST_H */ + +#endif /* !SMALL */ + diff --git a/tls_compat.c b/tls_compat.c new file mode 100644 index 0000000..cde80a9 --- /dev/null +++ b/tls_compat.c @@ -0,0 +1,283 @@ +#include "tls_compat.h" + +#ifndef NOSSL + +#include +#include +#include +#include +#include +#include +#include +#include + +static char tls_error_buf[256]; +static tls_config_t global_tls_config = NULL; + +tls_config_t +tls_config_new(void) +{ + tls_config_t config; + SSL_CTX *ctx; + + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + + ctx = SSL_CTX_new(TLS_client_method()); + if (ctx == NULL) + return NULL; + + config = calloc(1, sizeof(*config)); + if (config == NULL) { + SSL_CTX_free(ctx); + return NULL; + } + + config->ctx = ctx; + config->verify_depth = 100; + config->noverifycert = 0; + config->noverifyname = 0; + config->noverifytime = 0; + config->muststaple = 0; + config->session_fd = -1; + + SSL_CTX_set_options(ctx, SSL_OP_LEGACY_SERVER_CONNECT); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + return config; +} + +void +tls_config_free(tls_config_t config) +{ + if (config) { + if (config->ctx) + SSL_CTX_free(config->ctx); + free(config); + } +} + +int +tls_config_set_ca_file(tls_config_t config, const char *ca_file) +{ + if (SSL_CTX_load_verify_locations(config->ctx, ca_file, NULL) != 1) + return -1; + return 0; +} + +int +tls_config_set_ca_path(tls_config_t config, const char *ca_path) +{ + if (SSL_CTX_load_verify_locations(config->ctx, NULL, ca_path) != 1) + return -1; + return 0; +} + +int +tls_config_set_ciphers(tls_config_t config, const char *ciphers) +{ + if (SSL_CTX_set_cipher_list(config->ctx, ciphers) != 1) + return -1; + return 0; +} + +void +tls_config_insecure_noverifycert(tls_config_t config) +{ + config->noverifycert = 1; + SSL_CTX_set_verify(config->ctx, SSL_VERIFY_NONE, NULL); +} + +void +tls_config_insecure_noverifyname(tls_config_t config) +{ + config->noverifyname = 1; +} + +void +tls_config_verify(tls_config_t config) +{ + config->noverifycert = 0; + config->noverifyname = 0; + SSL_CTX_set_verify(config->ctx, SSL_VERIFY_PEER, NULL); +} + +int +tls_config_set_verify_depth(tls_config_t config, int depth) +{ + config->verify_depth = depth; + SSL_CTX_set_verify_depth(config->ctx, depth); + return 0; +} + +void +tls_config_ocsp_require_stapling(tls_config_t config) +{ + config->muststaple = 1; +} + +void +tls_config_insecure_noverifytime(tls_config_t config) +{ + config->noverifytime = 1; + X509_STORE *store = SSL_CTX_get_cert_store(config->ctx); + if (store) + X509_STORE_set_flags(store, X509_V_FLAG_NO_CHECK_TIME); +} + +int +tls_config_set_session_fd(tls_config_t config, int fd) +{ + config->session_fd = fd; + return 0; +} + +int +tls_config_parse_protocols(uint32_t *protocols, const char *protostr) +{ + *protocols = 0; + if (strstr(protostr, "TLSv1.3") || strstr(protostr, "tlsv1.3")) + *protocols |= (1 << 0); + if (strstr(protostr, "TLSv1.2") || strstr(protostr, "tlsv1.2")) + *protocols |= (1 << 1); + if (strstr(protostr, "TLSv1.1") || strstr(protostr, "tlsv1.1")) + *protocols |= (1 << 2); + if (strstr(protostr, "TLSv1.0") || strstr(protostr, "tlsv1.0") || strstr(protostr, "TLSv1") || strstr(protostr, "tlsv1")) + *protocols |= (1 << 3); + if (*protocols == 0) + return -1; + return 0; +} + +int +tls_config_set_protocols(tls_config_t config, uint32_t protocols) +{ + long options = 0; + if (!(protocols & (1 << 3))) + options |= SSL_OP_NO_TLSv1; + if (!(protocols & (1 << 2))) + options |= SSL_OP_NO_TLSv1_1; + if (!(protocols & (1 << 1))) + options |= SSL_OP_NO_TLSv1_2; + if (!(protocols & (1 << 0))) + options |= SSL_OP_NO_TLSv1_3; + SSL_CTX_set_options(config->ctx, options); + return 0; +} + +const char * +tls_config_error(tls_config_t config) +{ + unsigned long err = ERR_get_error(); + if (err) + ERR_error_string_n(err, tls_error_buf, sizeof(tls_error_buf)); + else + strcpy(tls_error_buf, "unknown error"); + return tls_error_buf; +} + +tls_t +tls_client(void) +{ + tls_t tls = calloc(1, sizeof(*tls)); + if (tls == NULL) + return NULL; + return tls; +} + +int +tls_configure(tls_t ctx, tls_config_t config) +{ + ctx->ctx = config->ctx; + global_tls_config = config; + return 0; +} + +int +tls_connect_socket(tls_t ctx, int s, const char *servername) +{ + ctx->fd = s; + ctx->ssl = SSL_new(ctx->ctx); + if (ctx->ssl == NULL) + return -1; + if (SSL_set_fd(ctx->ssl, s) != 1) + return -1; + if (servername && SSL_set_tlsext_host_name(ctx->ssl, servername) != 1) + return -1; + return 0; +} + +int +tls_handshake(tls_t ctx) +{ + int ret = SSL_connect(ctx->ssl); + if (ret <= 0) { + int err = SSL_get_error(ctx->ssl, ret); + if (err == SSL_ERROR_WANT_READ) + return TLS_WANT_POLLIN; + if (err == SSL_ERROR_WANT_WRITE) + return TLS_WANT_POLLOUT; + return -1; + } + return 0; +} + +int +tls_read(tls_t ctx, void *buf, size_t buflen) +{ + return SSL_read(ctx->ssl, buf, buflen); +} + +int +tls_write(tls_t ctx, const void *buf, size_t buflen) +{ + return SSL_write(ctx->ssl, buf, buflen); +} + +int +tls_close(tls_t ctx) +{ + if (ctx && ctx->ssl) { + int ret = SSL_shutdown(ctx->ssl); + if (ret < 0) { + int err = SSL_get_error(ctx->ssl, ret); + if (err == SSL_ERROR_WANT_READ) + return TLS_WANT_POLLIN; + if (err == SSL_ERROR_WANT_WRITE) + return TLS_WANT_POLLOUT; + } + SSL_free(ctx->ssl); + ctx->ssl = NULL; + } + return 0; +} + +void +tls_free(tls_t ctx) +{ + if (ctx) { + if (ctx->ssl) + SSL_free(ctx->ssl); + free(ctx); + } +} + +const char * +tls_error(tls_t ctx) +{ + unsigned long err = ERR_get_error(); + if (err) + ERR_error_string_n(err, tls_error_buf, sizeof(tls_error_buf)); + else + strcpy(tls_error_buf, "unknown error"); + return tls_error_buf; +} + +int +tls_conn_session_resumed(tls_t ctx) +{ + return SSL_session_reused(ctx->ssl); +} + +#endif \ No newline at end of file diff --git a/tls_compat.h b/tls_compat.h new file mode 100644 index 0000000..3283fa4 --- /dev/null +++ b/tls_compat.h @@ -0,0 +1,60 @@ +#ifndef TLS_COMPAT_H +#define TLS_COMPAT_H + +#if !defined(NOSSL) || !NOSSL + +#include +#include +#include + +#define TLS_PROTOCOLS_ALL 0xffffffff + +typedef struct tls { + SSL *ssl; + SSL_CTX *ctx; + int fd; +} *tls_t; + +typedef struct tls_config { + SSL_CTX *ctx; + int verify_depth; + int noverifycert; + int noverifyname; + int noverifytime; + int muststaple; + int session_fd; +} *tls_config_t; + +#define TLS_WANT_POLLIN SSL_ERROR_WANT_READ +#define TLS_WANT_POLLOUT SSL_ERROR_WANT_WRITE + +tls_config_t tls_config_new(void); +void tls_config_free(tls_config_t config); +int tls_config_set_ca_file(tls_config_t config, const char *ca_file); +int tls_config_set_ca_path(tls_config_t config, const char *ca_path); +int tls_config_set_ciphers(tls_config_t config, const char *ciphers); +void tls_config_insecure_noverifycert(tls_config_t config); +void tls_config_insecure_noverifyname(tls_config_t config); +void tls_config_verify(tls_config_t config); +int tls_config_set_verify_depth(tls_config_t config, int depth); +void tls_config_ocsp_require_stapling(tls_config_t config); +void tls_config_insecure_noverifytime(tls_config_t config); +int tls_config_set_session_fd(tls_config_t config, int fd); +int tls_config_parse_protocols(uint32_t *protocols, const char *protostr); +int tls_config_set_protocols(tls_config_t config, uint32_t protocols); +const char *tls_config_error(tls_config_t config); + +tls_t tls_client(void); +int tls_configure(tls_t ctx, tls_config_t config); +int tls_connect_socket(tls_t ctx, int s, const char *servername); +int tls_handshake(tls_t ctx); +int tls_read(tls_t ctx, void *buf, size_t buflen); +int tls_write(tls_t ctx, const void *buf, size_t buflen); +int tls_close(tls_t ctx); +void tls_free(tls_t ctx); +const char *tls_error(tls_t ctx); +int tls_conn_session_resumed(tls_t ctx); + +#endif + +#endif \ No newline at end of file diff --git a/util.c b/util.c new file mode 100644 index 0000000..91c689b --- /dev/null +++ b/util.c @@ -0,0 +1,1167 @@ +/* $OpenBSD: util.c,v 1.99 2025/12/21 07:29:03 tb 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 && !anonftp) { + 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; + + 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)) == -1) { + warn("unable to create temporary file: %s", temp); + return (NULL); + } + close(fd); + oldverbose = verbose; + verbose = (errbuf != NULL) ? -1 : 0; + oldhash = hash; + hash = 0; + if (doswitch) + pswitch(!proxy); + for (lmode = "w"; *++argv != NULL; lmode = "a") + recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode, + 0, 0); + if ((code / 100) != COMPLETE) { + if (errbuf != NULL) + *errbuf = reply_string; + } + if (doswitch) + pswitch(!proxy); + verbose = oldverbose; + hash = oldhash; + *ftemp = fopen(temp, "r"); + (void)unlink(temp); + if (*ftemp == NULL) { + if (errbuf == NULL) + fputs("can't find list of remote files, oops.\n", + ttyout); + else + *errbuf = + "can't find list of remote files, oops."; + return (NULL); + } + } +#ifndef SMALL +again: +#endif + if (fgets(buf, sizeof(buf), *ftemp) == NULL) { + (void)fclose(*ftemp); + *ftemp = NULL; + return (NULL); + } + + buf[strcspn(buf, "\n")] = '\0'; + bufp = buf; + +#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 = strtoll(cp, &ep, 10); + if (*ep != '\0' && !isspace((unsigned char)*ep)) + size = -1; + } + } else if (noisy +#ifndef SMALL + && !debug +#endif /* !SMALL */ + ) { + fputs(reply_string, ttyout); + fputc('\n', ttyout); + } + verbose = overbose; + return (size); +} + +/* + * 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)); +} + +static void +updateprogressmeter(int signo) +{ + int save_errno = errno; + + /* update progressmeter if foreground process or in -m mode */ + if (foregroundproc() || progress == -1) + progressmeter(0, NULL); + errno = save_errno; +} + +/* + * Display a transfer progress bar if progress is non-zero. + * SIGALRM is hijacked for use by this function. + * - Before the transfer, set filesize to size of file (or -1 if unknown), + * and call with flag = -1. This starts the once per second timer, + * and a call to updateprogressmeter() upon SIGALRM. + * - During the transfer, updateprogressmeter will call progressmeter + * with flag = 0 + * - After the transfer, call with flag = 1 + */ +static struct timespec start; + +char *action; + +void +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 timespec lastupdate; + static off_t lastsize; + static char *title = NULL; + struct timespec now, td, wait; + off_t cursize, abbrevsize; + double elapsed; + int ratio, barlength, i, remaining, overhead = 30; + char buf[512], *filenamebuf; + + if (flag == -1) { + clock_gettime(CLOCK_MONOTONIC, &start); + lastupdate = start; + lastsize = restart_point; + } + clock_gettime(CLOCK_MONOTONIC, &now); + if (!progress || filesize < 0) + return; + cursize = bytes + restart_point; + + if (filesize) + ratio = cursize * 100 / filesize; + else + ratio = 100; + ratio = MAXIMUM(ratio, 0); + ratio = MINIMUM(ratio, 100); + if (!verbose && flag == -1) { + if ((filenamebuf = strdup(filename)) != NULL && + (filename = basename(filenamebuf)) != NULL) { + free(title); + title = strdup(filename); + } + free(filenamebuf); + } + + buf[0] = 0; + if (!verbose && action != NULL) { + int l = strlen(action); + char *dotdot = ""; + + if (l < 7) + l = 7; + else if (l > 12) { + l = 12; + dotdot = "..."; + overhead += 3; + } + snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action, + dotdot); + overhead += l + 1; + } else + snprintf(buf, sizeof(buf), "\r"); + + if (!verbose && title != NULL) { + int l = strlen(title); + char *dotdot = ""; + + if (l < 12) + l = 12; + else if (l > 25) { + l = 22; + dotdot = "..."; + overhead += 3; + } + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%-*.*s%s %3d%% ", l, l, title, + dotdot, ratio); + overhead += l + 1; + } else + snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); + + barlength = ttywidth - overhead; + if (barlength > 0) { + i = barlength * ratio / 100; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "|%.*s%*s|", i, + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************", + barlength - i, ""); + } + + i = 0; + abbrevsize = cursize; + while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) { + i++; + abbrevsize >>= 10; + } + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " %5lld %c%c ", (long long)abbrevsize, prefixes[i], + prefixes[i] == ' ' ? ' ' : 'B'); + + timespecsub(&now, &lastupdate, &wait); + if (cursize > lastsize) { + lastupdate = now; + lastsize = cursize; + if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ + start.tv_sec += wait.tv_sec; + start.tv_nsec += wait.tv_nsec; + } + wait.tv_sec = 0; + } + + timespecsub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); + + if (flag == 1) { + i = (int)elapsed / 3600; + if (i) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%2d:", i); + else + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " "); + i = (int)elapsed % 3600; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02d:%02d ", i / 60, i % 60); + } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " --:-- ETA"); + } else if (wait.tv_sec >= STALLTIME) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " - stalled -"); + } else { + remaining = (int)((filesize - restart_point) / + (bytes / elapsed) - elapsed); + i = remaining / 3600; + if (i) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%2d:", i); + else + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " "); + i = remaining % 3600; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02d:%02d ETA", i / 60, i % 60); + } + (void)write(fileno(ttyout), buf, strlen(buf)); + + if (flag == -1) { + (void)signal(SIGALRM, updateprogressmeter); + alarmtimer(1); /* set alarm timer for 1 Hz */ + } else if (flag == 1) { + alarmtimer(0); + (void)putc('\n', ttyout); + free(title); + title = NULL; + } + fflush(ttyout); +} + +/* + * Display transfer statistics. + * Requires start to be initialised by progressmeter(-1), + * direction to be defined by xfer routines, and filesize and bytes + * to be updated by xfer routines + * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR + * instead of TTYOUT. + */ +void +ptransfer(int siginfo) +{ + struct timespec now, td; + double elapsed, pace; + off_t bs; + int meg, remaining, hh; + char buf[100]; + + if (!verbose && !siginfo) + return; + + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); + bs = bytes / (elapsed == 0.0 ? 1 : elapsed); + meg = 0; + if (bs > (1024 * 1024)) + meg = 1; + + pace = bs / (1024.0 * (meg ? 1024.0 : 1.0)); + (void)snprintf(buf, sizeof(buf), + "%lld byte%s %s in %lld.%02d seconds (%lld.%02d %sB/s)\n", + (long long)bytes, bytes == 1 ? "" : "s", direction, + (long long)elapsed, (int)(elapsed * 100.0) % 100, + (long long)pace, (int)(pace * 100.0) % 100, + 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. + */ +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 */ + +/* + * connect(2) with an optional timeout if secs > 0. + */ +int +timed_connect(int s, const struct sockaddr *name, socklen_t namelen, int secs) +{ + struct timespec now, target, timebuf, *timeout = NULL; + int flags, nready, optval, ret = -1; + socklen_t optlen; + struct pollfd pfd; + + if (secs > 0) { + timebuf.tv_sec = secs; + timebuf.tv_nsec = 0; + timeout = &timebuf; + clock_gettime(CLOCK_MONOTONIC, &target); + timespecadd(&target, timeout, &target); + } + + flags = fcntl(s, F_GETFL, 0); + if (flags == -1) { + warn("fcntl(F_GETFL)"); + return -1; + } + if (!(flags & O_NONBLOCK)) { + if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) { + warn("fcntl(F_SETFL)"); + return -1; + } + } + + ret = connect(s, name, namelen); + if (ret == 0 || errno != EINPROGRESS) + goto done; + + for (;;) { + pfd.fd = s; + pfd.events = POLLOUT; + nready = ppoll(&pfd, 1, timeout, NULL); + switch (nready) { + case -1: + if (errno != EINTR && errno != EAGAIN) { + warn("ppoll"); + goto done; + } + if (timeout == NULL) + continue; + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &target, timeout); + if (timeout->tv_sec >= 0) + continue; + /* FALLTHROUGH */ + case 0: + errno = ETIMEDOUT; + goto done; + default: + optlen = sizeof(optval); + ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &optval, + &optlen); + if (ret == 0 && optval != 0) { + ret = -1; + errno = optval; + } + goto done; + } + } + +done: + if (!(flags & O_NONBLOCK)) { + if (fcntl(s, F_SETFL, flags) == -1) { + warn("fcntl(F_SETFL)"); + ret = -1; + } + } + + return ret; +} + +#ifndef SMALL +ssize_t +http_time(time_t t, char *tmbuf, size_t len) +{ + struct tm tm; + + /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */ + if (gmtime_r(&t, &tm) == NULL) + return 0; + else + return (strftime(tmbuf, len, "%a, %d %h %Y %T GMT", &tm)); +} +#endif /* !SMALL */ -- cgit 1.4.1