=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ftp/util.c,v retrieving revision 1.88 retrieving revision 1.89 diff -u -r1.88 -r1.89 --- src/usr.bin/ftp/util.c 2019/05/12 20:58:19 1.88 +++ src/usr.bin/ftp/util.c 2019/05/16 12:44:18 1.89 @@ -1,229 +1,1099 @@ -/* $OpenBSD: util.c,v 1.88 2019/05/12 20:58:19 jasper Exp $ */ +/* $OpenBSD: util.c,v 1.89 2019/05/16 12:44:18 florian Exp $ */ +/* $NetBSD: util.c,v 1.12 1997/08/18 10:20:27 lukem Exp $ */ +/*- + * Copyright (c) 1997-1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, + * NASA Ames Research Center. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + /* - * Copyright (c) 2015 Sunil Nimmagadda + * Copyright (c) 1985, 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ -#include -#include +/* + * 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 +#include +#include #include -#include "ftp.h" -#include "xmalloc.h" +#include "ftp_var.h" +#include "pathnames.h" -static void tooslow(int); +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) +#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) +static void updateprogressmeter(int); + /* - * Wait for an asynchronous connect(2) attempt to finish. + * Connect to peer server and + * auto-login, if possible. */ -int -connect_wait(int s) +void +setpeer(int argc, char *argv[]) { - struct pollfd pfd[1]; - int error = 0; - socklen_t len = sizeof(error); + char *host, *port; - pfd[0].fd = s; - pfd[0].events = POLLOUT; + if (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 (poll(pfd, 1, -1) == -1) - return -1; - if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) - return -1; - if (error != 0) { - errno = error; - return -1; + if (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; } - return 0; } -static void -tooslow(int signo) -{ - dprintf(STDERR_FILENO, "%s: connect taking too long\n", getprogname()); - _exit(2); -} - +/* + * login to remote host, using given username & password if supplied + */ int -tcp_connect(const char *host, const char *port, int timeout) +ftp_login(const char *host, char *user, char *pass) { - struct addrinfo hints, *res, *res0; - char hbuf[NI_MAXHOST]; - const char *cause = NULL; - int error, s = -1, save_errno; + char tmp[80], *acctname = NULL, host_name[HOST_NAME_MAX+1]; + char anonpass[LOGIN_NAME_MAX + 1 + HOST_NAME_MAX+1]; /* "user@hostname" */ + int n, aflag = 0, retry = 0; + struct passwd *pw; - if (host == NULL) { - warnx("hostname missing"); - return -1; +#ifndef SMALL + if (user == NULL && !anonftp) { + if (ruserpass(host, &user, &pass, &acctname) < 0) { + code = -1; + return (0); + } } +#endif /* !SMALL */ - memset(&hints, 0, sizeof hints); - hints.ai_family = family; - hints.ai_socktype = SOCK_STREAM; - if ((error = getaddrinfo(host, port, &hints, &res0))) { - warnx("%s: %s", host, gai_strerror(error)); - return -1; - } + /* + * Set up arguments for an anonymous FTP session, if necessary. + */ + if ((user == NULL || pass == NULL) && anonftp) { + memset(anonpass, 0, sizeof(anonpass)); + memset(host_name, 0, sizeof(host_name)); - if (timeout) { - (void)signal(SIGALRM, tooslow); - alarm(timeout); + /* + * Set up anonymous login password. + */ + if ((user = getlogin()) == NULL) { + if ((pw = getpwuid(getuid())) == NULL) + user = "anonymous"; + else + user = pw->pw_name; + } + gethostname(host_name, sizeof(host_name)); +#ifndef DONT_CHEAT_ANONPASS + /* + * Every anonymous FTP server I've encountered + * will accept the string "username@", and will + * append the hostname itself. We do this by default + * since many servers are picky about not having + * a FQDN in the anonymous password. - thorpej@netbsd.org + */ + snprintf(anonpass, sizeof(anonpass) - 1, "%s@", + user); +#else + snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s", + user, hp->h_name); +#endif + pass = anonpass; + user = "anonymous"; /* as per RFC 1635 */ } - for (res = res0; res; res = res->ai_next) { - if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, - sizeof hbuf, NULL, 0, NI_NUMERICHOST) != 0) - (void)strlcpy(hbuf, "(unknown)", sizeof hbuf); +tryagain: + if (retry) + user = "ftp"; /* some servers only allow "ftp" */ - log_info("Trying %s...\n", hbuf); - s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (s == -1) { - cause = "socket"; - continue; + while (user == NULL) { + char *myname = getlogin(); + + if (myname == NULL && (pw = getpwuid(getuid())) != NULL) + myname = pw->pw_name; + if (myname) + fprintf(ttyout, "Name (%s:%s): ", host, myname); + else + fprintf(ttyout, "Name (%s): ", host); + user = myname; + if (fgets(tmp, sizeof(tmp), stdin) != NULL) { + tmp[strcspn(tmp, "\n")] = '\0'; + if (tmp[0] != '\0') + user = tmp; } + else + exit(0); + } + n = command("USER %s", user); + if (n == CONTINUE) { + if (pass == NULL) + pass = getpass("Password:"); + n = command("PASS %s", pass); + } + if (n == CONTINUE) { + aflag++; + if (acctname == NULL) + acctname = getpass("Account:"); + n = command("ACCT %s", acctname); + } + if ((n != COMPLETE) || + (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) { + warnx("Login %s failed.", user); + if (retry || !anonftp) + return (0); + else + retry = 1; + goto tryagain; + } + if (proxy) + return (1); + connected = -1; +#ifndef SMALL + for (n = 0; n < macnum; ++n) { + if (!strcmp("init", macros[n].mac_name)) { + (void)strlcpy(line, "$init", sizeof line); + makeargv(); + domacro(margc, margv); + break; + } + } +#endif /* SMALL */ + return (1); +} - for (error = connect(s, res->ai_addr, res->ai_addrlen); - error != 0 && errno == EINTR; error = connect_wait(s)) - continue; +/* + * `another' gets another argument, and stores the new argc and argv. + * It reverts to the top level (via main.c's intr()) on EOF/error. + * + * Returns false if no new arguments have been added. + */ +#ifndef SMALL +int +another(int *pargc, char ***pargv, const char *prompt) +{ + int len = strlen(line), ret; - if (error != 0) { - cause = "connect"; - save_errno = errno; - close(s); - errno = save_errno; - s = -1; - continue; - } + if (len >= sizeof(line) - 3) { + fputs("sorry, arguments too long.\n", ttyout); + intr(); + } + fprintf(ttyout, "(%s) ", prompt); + line[len++] = ' '; + if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) { + clearerr(stdin); + intr(); + } + len += strlen(&line[len]); + if (len > 0 && line[len - 1] == '\n') + line[len - 1] = '\0'; + makeargv(); + ret = margc > *pargc; + *pargc = margc; + *pargv = margv; + return (ret); +} +#endif /* !SMALL */ - break; +/* + * glob files given in argv[] from the remote server. + * if errbuf isn't NULL, store error messages there instead + * of writing to the screen. + * if type isn't NULL, use LIST instead of NLST, and store filetype. + * 'd' means directory, 's' means symbolic link, '-' means plain + * file. + */ +char * +remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type) +{ + char temp[PATH_MAX], *bufp, *cp, *lmode; + static char buf[PATH_MAX], **args; + int oldverbose, oldhash, fd; + + if (!mflag) { + if (!doglob) + args = NULL; + else { + if (*ftemp) { + (void)fclose(*ftemp); + *ftemp = NULL; + } + } + return (NULL); } + if (!doglob) { + if (args == NULL) + args = argv; + if ((cp = *++args) == NULL) + args = NULL; + return (cp); + } + if (*ftemp == NULL) { + int len; - freeaddrinfo(res0); - if (s == -1) { - warn("%s", cause); - return -1; + cp = _PATH_TMP; + len = strlen(cp); + if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) { + warnx("unable to create temporary file: %s", + strerror(ENAMETOOLONG)); + return (NULL); + } + + (void)strlcpy(temp, cp, sizeof temp); + if (temp[len-1] != '/') + temp[len++] = '/'; + (void)strlcpy(&temp[len], TMPFILE, sizeof temp - len); + if ((fd = mkstemp(temp)) < 0) { + warn("unable to create temporary file: %s", temp); + return (NULL); + } + close(fd); + oldverbose = verbose; + verbose = (errbuf != NULL) ? -1 : 0; + oldhash = hash; + hash = 0; + if (doswitch) + pswitch(!proxy); + for (lmode = "w"; *++argv != NULL; lmode = "a") + recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode, + 0, 0); + if ((code / 100) != COMPLETE) { + if (errbuf != NULL) + *errbuf = reply_string; + } + if (doswitch) + pswitch(!proxy); + verbose = oldverbose; + hash = oldhash; + *ftemp = fopen(temp, "r"); + (void)unlink(temp); + if (*ftemp == NULL) { + if (errbuf == NULL) + fputs("can't find list of remote files, oops.\n", + ttyout); + else + *errbuf = + "can't find list of remote files, oops."; + return (NULL); + } } +#ifndef SMALL +again: +#endif + if (fgets(buf, sizeof(buf), *ftemp) == NULL) { + (void)fclose(*ftemp); + *ftemp = NULL; + return (NULL); + } - if (timeout) { - signal(SIGALRM, SIG_DFL); - alarm(0); + 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 s; + return (bufp); } +/* + * wrapper for remglob2 + */ +char * +remglob(char *argv[], int doswitch, char **errbuf) +{ + static FILE *ftemp = NULL; + + return remglob2(argv, doswitch, errbuf, &ftemp, NULL); +} + +#ifndef SMALL int -fd_request(char *path, int flags, off_t *offset) +confirm(const char *cmd, const char *file) { - struct imsg imsg; - off_t *poffset; - int fd, save_errno; + char str[BUFSIZ]; - send_message(&child_ibuf, IMSG_OPEN, flags, path, strlen(path) + 1, -1); - if (read_message(&child_ibuf, &imsg) == 0) - return -1; + if (file && (confirmrest || !interactive)) + return (1); +top: + if (file) + fprintf(ttyout, "%s %s? ", cmd, file); + else + fprintf(ttyout, "Continue with %s? ", cmd); + (void)fflush(ttyout); + if (fgets(str, sizeof(str), stdin) == NULL) + goto quit; + switch (tolower((unsigned char)*str)) { + case '?': + fprintf(ttyout, + "? help\n" + "a answer yes to all\n" + "n answer no\n" + "p turn off prompt mode\n" + "q answer no to all\n" + "y answer yes\n"); + goto top; + case 'a': + confirmrest = 1; + fprintf(ttyout, "Prompting off for duration of %s.\n", + cmd); + break; + case 'n': + return (0); + case 'p': + interactive = 0; + fputs("Interactive mode: off.\n", ttyout); + break; + case 'q': +quit: + mflag = 0; + clearerr(stdin); + return (0); + case 'y': + return(1); + break; + default: + fprintf(ttyout, "?, a, n, p, q, y " + "are the only acceptable commands!\n"); + goto top; + break; + } + return (1); +} +#endif /* !SMALL */ - if (imsg.hdr.type != IMSG_OPEN) - errx(1, "%s: IMSG_OPEN expected", __func__); +/* + * 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; - fd = imsg.fd; - if (offset) { - poffset = imsg.data; - *offset = *poffset; + 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); +} - save_errno = imsg.hdr.peerid; - imsg_free(&imsg); - errno = save_errno; - return fd; +/* + * determine size of remote file + */ +off_t +remotesize(const char *file, int noisy) +{ + int overbose; + off_t size; + + overbose = verbose; + size = -1; +#ifndef SMALL + if (!debug) +#endif /* !SMALL */ + verbose = -1; + if (command("SIZE %s", file) == COMPLETE) { + char *cp, *ep; + + cp = strchr(reply_string, ' '); + if (cp != NULL) { + cp++; + size = strtoll(cp, &ep, 10); + if (*ep != '\0' && !isspace((unsigned char)*ep)) + size = -1; + } + } else if (noisy +#ifndef SMALL + && !debug +#endif /* !SMALL */ + ) { + fputs(reply_string, ttyout); + fputc('\n', ttyout); + } + verbose = overbose; + return (size); } -void -send_message(struct imsgbuf *ibuf, int type, uint32_t peerid, - void *msg, size_t msglen, int fd) +/* + * determine last modification time (in GMT) of remote file + */ +time_t +remotemodtime(const char *file, int noisy) { - if (imsg_compose(ibuf, type, peerid, 0, fd, msg, msglen) != 1) - err(1, "imsg_compose"); + int overbose; + time_t rtime; + int ocode; - if (imsg_flush(ibuf) != 0) - err(1, "imsg_flush"); + overbose = verbose; + ocode = code; + rtime = -1; +#ifndef SMALL + if (!debug) +#endif /* !SMALL */ + verbose = -1; + if (command("MDTM %s", file) == COMPLETE) { + struct tm timebuf; + int yy, mo, day, hour, min, sec; + /* + * time-val = 14DIGIT [ "." 1*DIGIT ] + * YYYYMMDDHHMMSS[.sss] + * mdtm-response = "213" SP time-val CRLF / error-response + */ + /* TODO: parse .sss as well, use timespecs. */ + char *timestr = reply_string; + + /* Repair `19%02d' bug on server side */ + while (!isspace((unsigned char)*timestr)) + timestr++; + while (isspace((unsigned char)*timestr)) + timestr++; + if (strncmp(timestr, "191", 3) == 0) { + fprintf(ttyout, + "Y2K warning! Fixed incorrect time-val received from server.\n"); + timestr[0] = ' '; + timestr[1] = '2'; + timestr[2] = '0'; + } + sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo, + &day, &hour, &min, &sec); + memset(&timebuf, 0, sizeof(timebuf)); + timebuf.tm_sec = sec; + timebuf.tm_min = min; + timebuf.tm_hour = hour; + timebuf.tm_mday = day; + timebuf.tm_mon = mo - 1; + timebuf.tm_year = yy - 1900; + timebuf.tm_isdst = -1; + rtime = mktime(&timebuf); + if (rtime == -1 && (noisy +#ifndef SMALL + || debug +#endif /* !SMALL */ + )) + fprintf(ttyout, "Can't convert %s to a time.\n", reply_string); + else + rtime += timebuf.tm_gmtoff; /* conv. local -> GMT */ + } else if (noisy +#ifndef SMALL + && !debug +#endif /* !SMALL */ + ) { + fputs(reply_string, ttyout); + fputc('\n', ttyout); + } + verbose = overbose; + if (rtime == -1) + code = ocode; + return (rtime); } +/* + * Ensure file is in or under dir. + * Returns 1 if so, 0 if not (or an error occurred). + */ int -read_message(struct imsgbuf *ibuf, struct imsg *imsg) +fileindir(const char *file, const char *dir) { - int n; + char parentdirbuf[PATH_MAX], *parentdir; + char realdir[PATH_MAX]; + size_t dirlen; - if ((n = imsg_read(ibuf)) == -1) - err(1, "%s: imsg_read", __func__); - if (n == 0) - return 0; + /* determine parent directory of file */ + (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf)); + parentdir = dirname(parentdirbuf); + if (strcmp(parentdir, ".") == 0) + return 1; /* current directory is ok */ - if ((n = imsg_get(ibuf, imsg)) == -1) - err(1, "%s: imsg_get", __func__); - if (n == 0) + /* find the directory */ + if (realpath(parentdir, realdir) == NULL) { + warn("Unable to determine real path of `%s'", parentdir); return 0; + } + if (realdir[0] != '/') /* relative result is ok */ + return 1; - return n; + dirlen = strlen(dir); + if (strncmp(realdir, dir, dirlen) == 0 && + (realdir[dirlen] == '/' || realdir[dirlen] == '\0')) + return 1; + return 0; } + +/* + * Returns true if this is the controlling/foreground process, else false. + */ +int +foregroundproc(void) +{ + static pid_t pgrp = -1; + int ctty_pgrp; + + if (pgrp == -1) + pgrp = getpgrp(); + + return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 && + ctty_pgrp == pgrp)); +} + +/* ARGSUSED */ +static void +updateprogressmeter(int signo) +{ + int save_errno = errno; + + /* update progressmeter if foreground process or in -m mode */ + if (foregroundproc() || progress == -1) + progressmeter(0, NULL); + errno = save_errno; +} + +/* + * Display a transfer progress bar if progress is non-zero. + * SIGALRM is hijacked for use by this function. + * - Before the transfer, set filesize to size of file (or -1 if unknown), + * and call with flag = -1. This starts the once per second timer, + * and a call to updateprogressmeter() upon SIGALRM. + * - During the transfer, updateprogressmeter will call progressmeter + * with flag = 0 + * - After the transfer, call with flag = 1 + */ +static struct timespec start; + +char *action; + void -log_info(const char *fmt, ...) +progressmeter(int flag, const char *filename) { - va_list ap; + /* + * List of order of magnitude prefixes. + * The last is `P', as 2^64 = 16384 Petabytes + */ + static const char prefixes[] = " KMGTP"; - if (verbose == 0) + static struct timespec lastupdate; + static off_t lastsize; + static char *title = NULL; + struct timespec now, td, wait; + off_t cursize, abbrevsize; + double elapsed; + int ratio, barlength, i, remaining, overhead = 30; + char buf[512]; + + if (flag == -1) { + clock_gettime(CLOCK_MONOTONIC, &start); + lastupdate = start; + lastsize = restart_point; + } + clock_gettime(CLOCK_MONOTONIC, &now); + if (!progress || filesize < 0) return; + cursize = bytes + restart_point; - va_start(ap, fmt); - vfprintf(msgout, fmt, ap); - va_end(ap); + if (filesize) + ratio = cursize * 100 / filesize; + else + ratio = 100; + ratio = MAXIMUM(ratio, 0); + ratio = MINIMUM(ratio, 100); + if (!verbose && flag == -1) { + filename = basename(filename); + if (filename != NULL) { + free(title); + title = strdup(filename); + } + } + + buf[0] = 0; + if (!verbose && action != NULL) { + int l = strlen(action); + char *dotdot = ""; + + if (l < 7) + l = 7; + else if (l > 12) { + l = 12; + dotdot = "..."; + overhead += 3; + } + snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action, + dotdot); + overhead += l + 1; + } else + snprintf(buf, sizeof(buf), "\r"); + + if (!verbose && title != NULL) { + int l = strlen(title); + char *dotdot = ""; + + if (l < 12) + l = 12; + else if (l > 25) { + l = 22; + dotdot = "..."; + overhead += 3; + } + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%-*.*s%s %3d%% ", l, l, title, + dotdot, ratio); + overhead += l + 1; + } else + snprintf(buf, sizeof(buf), "\r%3d%% ", ratio); + + barlength = ttywidth - overhead; + if (barlength > 0) { + i = barlength * ratio / 100; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "|%.*s%*s|", i, + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************" + "*******************************************************", + barlength - i, ""); + } + + i = 0; + abbrevsize = cursize; + while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) { + i++; + abbrevsize >>= 10; + } + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " %5lld %c%c ", (long long)abbrevsize, prefixes[i], + prefixes[i] == ' ' ? ' ' : 'B'); + + timespecsub(&now, &lastupdate, &wait); + if (cursize > lastsize) { + lastupdate = now; + lastsize = cursize; + if (wait.tv_sec >= STALLTIME) { /* fudge out stalled time */ + start.tv_sec += wait.tv_sec; + start.tv_nsec += wait.tv_nsec; + } + wait.tv_sec = 0; + } + + timespecsub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); + + if (flag == 1) { + i = (int)elapsed / 3600; + if (i) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%2d:", i); + else + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " "); + i = (int)elapsed % 3600; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02d:%02d ", i / 60, i % 60); + } else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " --:-- ETA"); + } else if (wait.tv_sec >= STALLTIME) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " - stalled -"); + } else { + remaining = (int)((filesize - restart_point) / + (bytes / elapsed) - elapsed); + i = remaining / 3600; + if (i) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%2d:", i); + else + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + " "); + i = remaining % 3600; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02d:%02d ETA", i / 60, i % 60); + } + (void)write(fileno(ttyout), buf, strlen(buf)); + + if (flag == -1) { + (void)signal(SIGALRM, updateprogressmeter); + alarmtimer(1); /* set alarm timer for 1 Hz */ + } else if (flag == 1) { + alarmtimer(0); + (void)putc('\n', ttyout); + free(title); + title = NULL; + } + fflush(ttyout); } +/* + * Display transfer statistics. + * Requires start to be initialised by progressmeter(-1), + * direction to be defined by xfer routines, and filesize and bytes + * to be updated by xfer routines + * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR + * instead of TTYOUT. + */ void -copy_file(FILE *dst, FILE *src, off_t *offset) +ptransfer(int siginfo) { - char *tmp_buf; - size_t r; + struct timespec now, td; + double elapsed; + off_t bs; + int meg, remaining, hh; + char buf[100]; - tmp_buf = xmalloc(TMPBUF_LEN); - while ((r = fread(tmp_buf, 1, TMPBUF_LEN, src)) != 0 && !interrupted) { - *offset += r; - if (fwrite(tmp_buf, 1, r, dst) != r) - err(1, "%s: fwrite", __func__); + if (!verbose && !siginfo) + return; + + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &start, &td); + elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0); + bs = bytes / (elapsed == 0.0 ? 1 : elapsed); + meg = 0; + if (bs > (1024 * 1024)) + meg = 1; + + /* XXX floating point printf in signal handler */ + (void)snprintf(buf, sizeof(buf), + "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n", + (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed, + bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K"); + + if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 && + bytes + restart_point <= filesize) { + remaining = (int)((filesize - restart_point) / + (bytes / elapsed) - elapsed); + hh = remaining / 3600; + remaining %= 3600; + + /* "buf+len(buf) -1" to overwrite \n */ + snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf), + " ETA: %02d:%02d:%02d\n", hh, remaining / 60, + remaining % 60); } + (void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf)); +} - if (interrupted) { - free(tmp_buf); - return; +/* + * List words in stringlist, vertically arranged + */ +#ifndef SMALL +void +list_vertical(StringList *sl) +{ + int i, j, w; + int columns, width, lines; + char *p; + + width = 0; + + for (i = 0 ; i < sl->sl_cur ; i++) { + w = strlen(sl->sl_str[i]); + if (w > width) + width = w; } + width = (width + 8) &~ 7; - if (!feof(src)) - errx(1, "%s: fread", __func__); + 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 */ - free(tmp_buf); +/* + * Update the global ttywidth value, using TIOCGWINSZ. + */ +/* ARGSUSED */ +void +setttywidth(int signo) +{ + int save_errno = errno; + struct winsize winsize; + + if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1) + ttywidth = winsize.ws_col ? winsize.ws_col : 80; + else + ttywidth = 80; + errno = save_errno; +} + +/* + * Set the SIGALRM interval timer for wait seconds, 0 to disable. + */ +void +alarmtimer(int wait) +{ + int save_errno = errno; + struct itimerval itv; + + itv.it_value.tv_sec = wait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + setitimer(ITIMER_REAL, &itv, NULL); + errno = save_errno; +} + +/* + * Setup or cleanup EditLine structures + */ +#ifndef SMALL +void +controlediting(void) +{ + HistEvent hev; + + if (editing && el == NULL && hist == NULL) { + el = el_init(__progname, stdin, ttyout, stderr); /* init editline */ + hist = history_init(); /* init the builtin history */ + history(hist, &hev, H_SETSIZE, 100); /* remember 100 events */ + el_set(el, EL_HIST, history, hist); /* use history */ + + el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ + el_set(el, EL_PROMPT, prompt); /* set the prompt function */ + + /* add local file completion, bind to TAB */ + el_set(el, EL_ADDFN, "ftp-complete", + "Context sensitive argument completion", + complete); + el_set(el, EL_BIND, "^I", "ftp-complete", NULL); + + el_source(el, NULL); /* read ~/.editrc */ + el_set(el, EL_SIGNAL, 1); + } else if (!editing) { + if (hist) { + history_end(hist); + hist = NULL; + } + if (el) { + el_end(el); + el = NULL; + } + } +} +#endif /* !SMALL */ + +/* + * Wait for an asynchronous connect(2) attempt to finish. + */ +int +connect_wait(int s) +{ + struct pollfd pfd[1]; + int error = 0; + socklen_t len = sizeof(error); + + pfd[0].fd = s; + pfd[0].events = POLLOUT; + + if (poll(pfd, 1, -1) == -1) + return -1; + if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + return -1; + if (error != 0) { + errno = error; + return -1; + } + return 0; }