xref: /openbsd/usr.bin/ftp/util.c (revision 73471bf0)
1 /*	$OpenBSD: util.c,v 1.95 2021/02/02 12:58:42 robert Exp $	*/
2 /*	$NetBSD: util.c,v 1.12 1997/08/18 10:20:27 lukem Exp $	*/
3 
4 /*-
5  * Copyright (c) 1997-1999 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Luke Mewburn.
10  *
11  * This code is derived from software contributed to The NetBSD Foundation
12  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
13  * NASA Ames Research Center.
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions
17  * are met:
18  * 1. Redistributions of source code must retain the above copyright
19  *    notice, this list of conditions and the following disclaimer.
20  * 2. Redistributions in binary form must reproduce the above copyright
21  *    notice, this list of conditions and the following disclaimer in the
22  *    documentation and/or other materials provided with the distribution.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 /*
38  * Copyright (c) 1985, 1989, 1993, 1994
39  *	The Regents of the University of California.  All rights reserved.
40  *
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  *    notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  *    notice, this list of conditions and the following disclaimer in the
48  *    documentation and/or other materials provided with the distribution.
49  * 3. Neither the name of the University nor the names of its contributors
50  *    may be used to endorse or promote products derived from this software
51  *    without specific prior written permission.
52  *
53  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
54  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
55  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
56  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
57  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
58  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
59  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
60  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
61  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
62  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
63  * SUCH DAMAGE.
64  */
65 
66 /*
67  * FTP User Program -- Misc support routines
68  */
69 #include <sys/ioctl.h>
70 #include <sys/socket.h>
71 #include <sys/time.h>
72 #include <arpa/ftp.h>
73 
74 #include <ctype.h>
75 #include <err.h>
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <libgen.h>
79 #include <glob.h>
80 #include <poll.h>
81 #include <pwd.h>
82 #include <signal.h>
83 #include <stdio.h>
84 #include <stdlib.h>
85 #include <string.h>
86 #include <time.h>
87 #include <unistd.h>
88 
89 #include "ftp_var.h"
90 #include "pathnames.h"
91 
92 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
93 #define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
94 
95 static void updateprogressmeter(int);
96 
97 /*
98  * Connect to peer server and
99  * auto-login, if possible.
100  */
101 void
102 setpeer(int argc, char *argv[])
103 {
104 	char *host, *port;
105 
106 	if (connected) {
107 		fprintf(ttyout, "Already connected to %s, use close first.\n",
108 		    hostname);
109 		code = -1;
110 		return;
111 	}
112 #ifndef SMALL
113 	if (argc < 2)
114 		(void)another(&argc, &argv, "to");
115 	if (argc < 2 || argc > 3) {
116 		fprintf(ttyout, "usage: %s host [port]\n", argv[0]);
117 		code = -1;
118 		return;
119 	}
120 #endif /* !SMALL */
121 	if (gatemode)
122 		port = gateport;
123 	else
124 		port = ftpport;
125 	if (argc > 2)
126 		port = argv[2];
127 
128 	if (gatemode) {
129 		if (gateserver == NULL || *gateserver == '\0')
130 			errx(1, "gateserver not defined (shouldn't happen)");
131 		host = hookup(gateserver, port);
132 	} else
133 		host = hookup(argv[1], port);
134 
135 	if (host) {
136 		int overbose;
137 
138 		if (gatemode) {
139 			if (command("PASSERVE %s", argv[1]) != COMPLETE)
140 				return;
141 			if (verbose)
142 				fprintf(ttyout,
143 				    "Connected via pass-through server %s\n",
144 				    gateserver);
145 		}
146 
147 		connected = 1;
148 		/*
149 		 * Set up defaults for FTP.
150 		 */
151 		(void)strlcpy(formname, "non-print", sizeof formname);
152 		form = FORM_N;
153 		(void)strlcpy(modename, "stream", sizeof modename);
154 		mode = MODE_S;
155 		(void)strlcpy(structname, "file", sizeof structname);
156 		stru = STRU_F;
157 		(void)strlcpy(bytename, "8", sizeof bytename);
158 		bytesize = 8;
159 
160 		/*
161 		 * Set type to 0 (not specified by user),
162 		 * meaning binary by default, but don't bother
163 		 * telling server.  We can use binary
164 		 * for text files unless changed by the user.
165 		 */
166 		(void)strlcpy(typename, "binary", sizeof typename);
167 		curtype = TYPE_A;
168 		type = 0;
169 		if (autologin)
170 			(void)ftp_login(argv[1], NULL, NULL);
171 
172 		overbose = verbose;
173 #ifndef SMALL
174 		if (!debug)
175 #endif /* !SMALL */
176 			verbose = -1;
177 		if (command("SYST") == COMPLETE && overbose) {
178 			char *cp, c;
179 			c = 0;
180 			cp = strchr(reply_string + 4, ' ');
181 			if (cp == NULL)
182 				cp = strchr(reply_string + 4, '\r');
183 			if (cp) {
184 				if (cp[-1] == '.')
185 					cp--;
186 				c = *cp;
187 				*cp = '\0';
188 			}
189 
190 			fprintf(ttyout, "Remote system type is %s.\n", reply_string + 4);
191 			if (cp)
192 				*cp = c;
193 		}
194 		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
195 			if (proxy)
196 				unix_proxy = 1;
197 			else
198 				unix_server = 1;
199 			if (overbose)
200 				fprintf(ttyout, "Using %s mode to transfer files.\n",
201 				    typename);
202 		} else {
203 			if (proxy)
204 				unix_proxy = 0;
205 			else
206 				unix_server = 0;
207 		}
208 		verbose = overbose;
209 	}
210 }
211 
212 /*
213  * login to remote host, using given username & password if supplied
214  */
215 int
216 ftp_login(const char *host, char *user, char *pass)
217 {
218 	char tmp[80], *acctname = NULL, host_name[HOST_NAME_MAX+1];
219 	char anonpass[LOGIN_NAME_MAX + 1 + HOST_NAME_MAX+1];	/* "user@hostname" */
220 	int n, aflag = 0, retry = 0;
221 	struct passwd *pw;
222 
223 #ifndef SMALL
224 	if (user == NULL && !anonftp) {
225 		if (ruserpass(host, &user, &pass, &acctname) < 0) {
226 			code = -1;
227 			return (0);
228 		}
229 	}
230 #endif /* !SMALL */
231 
232 	/*
233 	 * Set up arguments for an anonymous FTP session, if necessary.
234 	 */
235 	if ((user == NULL || pass == NULL) && anonftp) {
236 		memset(anonpass, 0, sizeof(anonpass));
237 		memset(host_name, 0, sizeof(host_name));
238 
239 		/*
240 		 * Set up anonymous login password.
241 		 */
242 		if ((user = getlogin()) == NULL) {
243 			if ((pw = getpwuid(getuid())) == NULL)
244 				user = "anonymous";
245 			else
246 				user = pw->pw_name;
247 		}
248 		gethostname(host_name, sizeof(host_name));
249 #ifndef DONT_CHEAT_ANONPASS
250 		/*
251 		 * Every anonymous FTP server I've encountered
252 		 * will accept the string "username@", and will
253 		 * append the hostname itself.  We do this by default
254 		 * since many servers are picky about not having
255 		 * a FQDN in the anonymous password. - thorpej@netbsd.org
256 		 */
257 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@",
258 		    user);
259 #else
260 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s",
261 		    user, hp->h_name);
262 #endif
263 		pass = anonpass;
264 		user = "anonymous";	/* as per RFC 1635 */
265 	}
266 
267 tryagain:
268 	if (retry)
269 		user = "ftp";		/* some servers only allow "ftp" */
270 
271 	while (user == NULL) {
272 		char *myname = getlogin();
273 
274 		if (myname == NULL && (pw = getpwuid(getuid())) != NULL)
275 			myname = pw->pw_name;
276 		if (myname)
277 			fprintf(ttyout, "Name (%s:%s): ", host, myname);
278 		else
279 			fprintf(ttyout, "Name (%s): ", host);
280 		user = myname;
281 		if (fgets(tmp, sizeof(tmp), stdin) != NULL) {
282 			tmp[strcspn(tmp, "\n")] = '\0';
283 			if (tmp[0] != '\0')
284 				user = tmp;
285 		} else
286 			exit(0);
287 	}
288 	n = command("USER %s", user);
289 	if (n == CONTINUE) {
290 		if (pass == NULL)
291 			pass = getpass("Password:");
292 		n = command("PASS %s", pass);
293 	}
294 	if (n == CONTINUE) {
295 		aflag++;
296 		if (acctname == NULL)
297 			acctname = getpass("Account:");
298 		n = command("ACCT %s", acctname);
299 	}
300 	if ((n != COMPLETE) ||
301 	    (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
302 		warnx("Login %s failed.", user);
303 		if (retry || !anonftp)
304 			return (0);
305 		else
306 			retry = 1;
307 		goto tryagain;
308 	}
309 	if (proxy)
310 		return (1);
311 	connected = -1;
312 #ifndef SMALL
313 	for (n = 0; n < macnum; ++n) {
314 		if (!strcmp("init", macros[n].mac_name)) {
315 			(void)strlcpy(line, "$init", sizeof line);
316 			makeargv();
317 			domacro(margc, margv);
318 			break;
319 		}
320 	}
321 #endif /* SMALL */
322 	return (1);
323 }
324 
325 /*
326  * `another' gets another argument, and stores the new argc and argv.
327  * It reverts to the top level (via main.c's intr()) on EOF/error.
328  *
329  * Returns false if no new arguments have been added.
330  */
331 #ifndef SMALL
332 int
333 another(int *pargc, char ***pargv, const char *prompt)
334 {
335 	int len = strlen(line), ret;
336 
337 	if (len >= sizeof(line) - 3) {
338 		fputs("sorry, arguments too long.\n", ttyout);
339 		intr();
340 	}
341 	fprintf(ttyout, "(%s) ", prompt);
342 	line[len++] = ' ';
343 	if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) {
344 		clearerr(stdin);
345 		intr();
346 	}
347 	len += strlen(&line[len]);
348 	if (len > 0 && line[len - 1] == '\n')
349 		line[len - 1] = '\0';
350 	makeargv();
351 	ret = margc > *pargc;
352 	*pargc = margc;
353 	*pargv = margv;
354 	return (ret);
355 }
356 #endif /* !SMALL */
357 
358 /*
359  * glob files given in argv[] from the remote server.
360  * if errbuf isn't NULL, store error messages there instead
361  * of writing to the screen.
362  * if type isn't NULL, use LIST instead of NLST, and store filetype.
363  * 'd' means directory, 's' means symbolic link, '-' means plain
364  * file.
365  */
366 char *
367 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type)
368 {
369 	char temp[PATH_MAX], *bufp, *cp, *lmode;
370 	static char buf[PATH_MAX], **args;
371 	int oldverbose, oldhash, fd;
372 
373 	if (!mflag) {
374 		if (!doglob)
375 			args = NULL;
376 		else {
377 			if (*ftemp) {
378 				(void)fclose(*ftemp);
379 				*ftemp = NULL;
380 			}
381 		}
382 		return (NULL);
383 	}
384 	if (!doglob) {
385 		if (args == NULL)
386 			args = argv;
387 		if ((cp = *++args) == NULL)
388 			args = NULL;
389 		return (cp);
390 	}
391 	if (*ftemp == NULL) {
392 		int len;
393 
394 		cp = _PATH_TMP;
395 		len = strlen(cp);
396 		if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
397 			warnx("unable to create temporary file: %s",
398 			    strerror(ENAMETOOLONG));
399 			return (NULL);
400 		}
401 
402 		(void)strlcpy(temp, cp, sizeof temp);
403 		if (temp[len-1] != '/')
404 			temp[len++] = '/';
405 		(void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
406 		if ((fd = mkstemp(temp)) == -1) {
407 			warn("unable to create temporary file: %s", temp);
408 			return (NULL);
409 		}
410 		close(fd);
411 		oldverbose = verbose;
412 		verbose = (errbuf != NULL) ? -1 : 0;
413 		oldhash = hash;
414 		hash = 0;
415 		if (doswitch)
416 			pswitch(!proxy);
417 		for (lmode = "w"; *++argv != NULL; lmode = "a")
418 			recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode,
419 			    0, 0);
420 		if ((code / 100) != COMPLETE) {
421 			if (errbuf != NULL)
422 				*errbuf = reply_string;
423 		}
424 		if (doswitch)
425 			pswitch(!proxy);
426 		verbose = oldverbose;
427 		hash = oldhash;
428 		*ftemp = fopen(temp, "r");
429 		(void)unlink(temp);
430 		if (*ftemp == NULL) {
431 			if (errbuf == NULL)
432 				fputs("can't find list of remote files, oops.\n",
433 				    ttyout);
434 			else
435 				*errbuf =
436 				    "can't find list of remote files, oops.";
437 			return (NULL);
438 		}
439 	}
440 #ifndef SMALL
441 again:
442 #endif
443 	if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
444 		(void)fclose(*ftemp);
445 		*ftemp = NULL;
446 		return (NULL);
447 	}
448 
449 	buf[strcspn(buf, "\n")] = '\0';
450 	bufp = buf;
451 
452 #ifndef SMALL
453 	if (type) {
454 		parse_list(&bufp, type);
455 		if (!bufp ||
456 		    (bufp[0] == '.' &&	/* LIST defaults to -a on some ftp */
457 		    (bufp[1] == '\0' ||	/* servers.  Ignore '.' and '..'. */
458 		    (bufp[1] == '.' && bufp[2] == '\0'))))
459 			goto again;
460 	}
461 #endif /* !SMALL */
462 
463 	return (bufp);
464 }
465 
466 /*
467  * wrapper for remglob2
468  */
469 char *
470 remglob(char *argv[], int doswitch, char **errbuf)
471 {
472 	static FILE *ftemp = NULL;
473 
474 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
475 }
476 
477 #ifndef SMALL
478 int
479 confirm(const char *cmd, const char *file)
480 {
481 	char str[BUFSIZ];
482 
483 	if (file && (confirmrest || !interactive))
484 		return (1);
485 top:
486 	if (file)
487 		fprintf(ttyout, "%s %s? ", cmd, file);
488 	else
489 		fprintf(ttyout, "Continue with %s? ", cmd);
490 	(void)fflush(ttyout);
491 	if (fgets(str, sizeof(str), stdin) == NULL)
492 		goto quit;
493 	switch (tolower((unsigned char)*str)) {
494 		case '?':
495 			fprintf(ttyout,
496 			    "?	help\n"
497 			    "a	answer yes to all\n"
498 			    "n	answer no\n"
499 			    "p	turn off prompt mode\n"
500 			    "q	answer no to all\n"
501 			    "y	answer yes\n");
502 			goto top;
503 		case 'a':
504 			confirmrest = 1;
505 			fprintf(ttyout, "Prompting off for duration of %s.\n",
506 			    cmd);
507 			break;
508 		case 'n':
509 			return (0);
510 		case 'p':
511 			interactive = 0;
512 			fputs("Interactive mode: off.\n", ttyout);
513 			break;
514 		case 'q':
515 quit:
516 			mflag = 0;
517 			clearerr(stdin);
518 			return (0);
519 		case 'y':
520 			return(1);
521 			break;
522 		default:
523 			fprintf(ttyout, "?, a, n, p, q, y "
524 			    "are the only acceptable commands!\n");
525 			goto top;
526 			break;
527 	}
528 	return (1);
529 }
530 #endif /* !SMALL */
531 
532 /*
533  * Glob a local file name specification with
534  * the expectation of a single return value.
535  * Can't control multiple values being expanded
536  * from the expression, we return only the first.
537  */
538 int
539 globulize(char **cpp)
540 {
541 	glob_t gl;
542 	int flags;
543 
544 	if (!doglob)
545 		return (1);
546 
547 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
548 	memset(&gl, 0, sizeof(gl));
549 	if (glob(*cpp, flags, NULL, &gl) ||
550 	    gl.gl_pathc == 0) {
551 		warnx("%s: not found", *cpp);
552 		globfree(&gl);
553 		return (0);
554 	}
555 		/* XXX: caller should check if *cpp changed, and
556 		 *	free(*cpp) if that is the case
557 		 */
558 	*cpp = strdup(gl.gl_pathv[0]);
559 	if (*cpp == NULL)
560 		err(1, NULL);
561 	globfree(&gl);
562 	return (1);
563 }
564 
565 /*
566  * determine size of remote file
567  */
568 off_t
569 remotesize(const char *file, int noisy)
570 {
571 	int overbose;
572 	off_t size;
573 
574 	overbose = verbose;
575 	size = -1;
576 #ifndef SMALL
577 	if (!debug)
578 #endif /* !SMALL */
579 		verbose = -1;
580 	if (command("SIZE %s", file) == COMPLETE) {
581 		char *cp, *ep;
582 
583 		cp = strchr(reply_string, ' ');
584 		if (cp != NULL) {
585 			cp++;
586 			size = strtoll(cp, &ep, 10);
587 			if (*ep != '\0' && !isspace((unsigned char)*ep))
588 				size = -1;
589 		}
590 	} else if (noisy
591 #ifndef SMALL
592 	    && !debug
593 #endif /* !SMALL */
594 	    ) {
595 		fputs(reply_string, ttyout);
596 		fputc('\n', ttyout);
597 	}
598 	verbose = overbose;
599 	return (size);
600 }
601 
602 /*
603  * determine last modification time (in GMT) of remote file
604  */
605 time_t
606 remotemodtime(const char *file, int noisy)
607 {
608 	int overbose;
609 	time_t rtime;
610 	int ocode;
611 
612 	overbose = verbose;
613 	ocode = code;
614 	rtime = -1;
615 #ifndef SMALL
616 	if (!debug)
617 #endif /* !SMALL */
618 		verbose = -1;
619 	if (command("MDTM %s", file) == COMPLETE) {
620 		struct tm timebuf;
621 		int yy, mo, day, hour, min, sec;
622 		/*
623 		 * time-val = 14DIGIT [ "." 1*DIGIT ]
624 		 *		YYYYMMDDHHMMSS[.sss]
625 		 * mdtm-response = "213" SP time-val CRLF / error-response
626 		 */
627 		/* TODO: parse .sss as well, use timespecs. */
628 		char *timestr = reply_string;
629 
630 		/* Repair `19%02d' bug on server side */
631 		while (!isspace((unsigned char)*timestr))
632 			timestr++;
633 		while (isspace((unsigned char)*timestr))
634 			timestr++;
635 		if (strncmp(timestr, "191", 3) == 0) {
636 			fprintf(ttyout,
637 	    "Y2K warning! Fixed incorrect time-val received from server.\n");
638 			timestr[0] = ' ';
639 			timestr[1] = '2';
640 			timestr[2] = '0';
641 		}
642 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
643 			&day, &hour, &min, &sec);
644 		memset(&timebuf, 0, sizeof(timebuf));
645 		timebuf.tm_sec = sec;
646 		timebuf.tm_min = min;
647 		timebuf.tm_hour = hour;
648 		timebuf.tm_mday = day;
649 		timebuf.tm_mon = mo - 1;
650 		timebuf.tm_year = yy - 1900;
651 		timebuf.tm_isdst = -1;
652 		rtime = mktime(&timebuf);
653 		if (rtime == -1 && (noisy
654 #ifndef SMALL
655 		    || debug
656 #endif /* !SMALL */
657 		    ))
658 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
659 		else
660 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
661 	} else if (noisy
662 #ifndef SMALL
663 	    && !debug
664 #endif /* !SMALL */
665 	    ) {
666 		fputs(reply_string, ttyout);
667 		fputc('\n', ttyout);
668 	}
669 	verbose = overbose;
670 	if (rtime == -1)
671 		code = ocode;
672 	return (rtime);
673 }
674 
675 /*
676  * Ensure file is in or under dir.
677  * Returns 1 if so, 0 if not (or an error occurred).
678  */
679 int
680 fileindir(const char *file, const char *dir)
681 {
682 	char	parentdirbuf[PATH_MAX], *parentdir;
683 	char	realdir[PATH_MAX];
684 	size_t	dirlen;
685 
686 					/* determine parent directory of file */
687 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
688 	parentdir = dirname(parentdirbuf);
689 	if (strcmp(parentdir, ".") == 0)
690 		return 1;		/* current directory is ok */
691 
692 					/* find the directory */
693 	if (realpath(parentdir, realdir) == NULL) {
694 		warn("Unable to determine real path of `%s'", parentdir);
695 		return 0;
696 	}
697 	if (realdir[0] != '/')		/* relative result is ok */
698 		return 1;
699 
700 	dirlen = strlen(dir);
701 	if (strncmp(realdir, dir, dirlen) == 0 &&
702 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
703 		return 1;
704 	return 0;
705 }
706 
707 
708 /*
709  * Returns true if this is the controlling/foreground process, else false.
710  */
711 int
712 foregroundproc(void)
713 {
714 	static pid_t pgrp = -1;
715 	int ctty_pgrp;
716 
717 	if (pgrp == -1)
718 		pgrp = getpgrp();
719 
720 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
721 	    ctty_pgrp == pgrp));
722 }
723 
724 /* ARGSUSED */
725 static void
726 updateprogressmeter(int signo)
727 {
728 	int save_errno = errno;
729 
730 	/* update progressmeter if foreground process or in -m mode */
731 	if (foregroundproc() || progress == -1)
732 		progressmeter(0, NULL);
733 	errno = save_errno;
734 }
735 
736 /*
737  * Display a transfer progress bar if progress is non-zero.
738  * SIGALRM is hijacked for use by this function.
739  * - Before the transfer, set filesize to size of file (or -1 if unknown),
740  *   and call with flag = -1. This starts the once per second timer,
741  *   and a call to updateprogressmeter() upon SIGALRM.
742  * - During the transfer, updateprogressmeter will call progressmeter
743  *   with flag = 0
744  * - After the transfer, call with flag = 1
745  */
746 static struct timespec start;
747 
748 char *action;
749 
750 void
751 progressmeter(int flag, const char *filename)
752 {
753 	/*
754 	 * List of order of magnitude prefixes.
755 	 * The last is `P', as 2^64 = 16384 Petabytes
756 	 */
757 	static const char prefixes[] = " KMGTP";
758 
759 	static struct timespec lastupdate;
760 	static off_t lastsize;
761 	static char *title = NULL;
762 	struct timespec now, td, wait;
763 	off_t cursize, abbrevsize;
764 	double elapsed;
765 	int ratio, barlength, i, remaining, overhead = 30;
766 	char buf[512], *filenamebuf;
767 
768 	if (flag == -1) {
769 		clock_gettime(CLOCK_MONOTONIC, &start);
770 		lastupdate = start;
771 		lastsize = restart_point;
772 	}
773 	clock_gettime(CLOCK_MONOTONIC, &now);
774 	if (!progress || filesize < 0)
775 		return;
776 	cursize = bytes + restart_point;
777 
778 	if (filesize)
779 		ratio = cursize * 100 / filesize;
780 	else
781 		ratio = 100;
782 	ratio = MAXIMUM(ratio, 0);
783 	ratio = MINIMUM(ratio, 100);
784 	if (!verbose && flag == -1) {
785 		if ((filenamebuf = strdup(filename)) != NULL &&
786 		    (filename = basename(filenamebuf)) != NULL) {
787 			free(title);
788 			title = strdup(filename);
789 		}
790 		free(filenamebuf);
791 	}
792 
793 	buf[0] = 0;
794 	if (!verbose && action != NULL) {
795 		int l = strlen(action);
796 		char *dotdot = "";
797 
798 		if (l < 7)
799 			l = 7;
800 		else if (l > 12) {
801 			l = 12;
802 			dotdot = "...";
803 			overhead += 3;
804 		}
805 		snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action,
806 		    dotdot);
807 		overhead += l + 1;
808 	} else
809 		snprintf(buf, sizeof(buf), "\r");
810 
811 	if (!verbose && title != NULL) {
812 		int l = strlen(title);
813 		char *dotdot = "";
814 
815 		if (l < 12)
816 			l = 12;
817 		else if (l > 25) {
818 			l = 22;
819 			dotdot = "...";
820 			overhead += 3;
821 		}
822 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
823 		    "%-*.*s%s %3d%% ", l, l, title,
824 		    dotdot, ratio);
825 		overhead += l + 1;
826 	} else
827 		snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
828 
829 	barlength = ttywidth - overhead;
830 	if (barlength > 0) {
831 		i = barlength * ratio / 100;
832 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
833 		    "|%.*s%*s|", i,
834 		    "*******************************************************"
835 		    "*******************************************************"
836 		    "*******************************************************"
837 		    "*******************************************************"
838 		    "*******************************************************"
839 		    "*******************************************************"
840 		    "*******************************************************",
841 		    barlength - i, "");
842 	}
843 
844 	i = 0;
845 	abbrevsize = cursize;
846 	while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) {
847 		i++;
848 		abbrevsize >>= 10;
849 	}
850 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
851 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
852 	    prefixes[i] == ' ' ? ' ' : 'B');
853 
854 	timespecsub(&now, &lastupdate, &wait);
855 	if (cursize > lastsize) {
856 		lastupdate = now;
857 		lastsize = cursize;
858 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
859 			start.tv_sec += wait.tv_sec;
860 			start.tv_nsec += wait.tv_nsec;
861 		}
862 		wait.tv_sec = 0;
863 	}
864 
865 	timespecsub(&now, &start, &td);
866 	elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0);
867 
868 	if (flag == 1) {
869 		i = (int)elapsed / 3600;
870 		if (i)
871 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
872 			    "%2d:", i);
873 		else
874 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
875 			    "   ");
876 		i = (int)elapsed % 3600;
877 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
878 		    "%02d:%02d    ", i / 60, i % 60);
879 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
880 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
881 		    "   --:-- ETA");
882 	} else if (wait.tv_sec >= STALLTIME) {
883 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
884 		    " - stalled -");
885 	} else {
886 		remaining = (int)((filesize - restart_point) /
887 				  (bytes / elapsed) - elapsed);
888 		i = remaining / 3600;
889 		if (i)
890 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
891 			    "%2d:", i);
892 		else
893 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
894 			    "   ");
895 		i = remaining % 3600;
896 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
897 		    "%02d:%02d ETA", i / 60, i % 60);
898 	}
899 	(void)write(fileno(ttyout), buf, strlen(buf));
900 
901 	if (flag == -1) {
902 		(void)signal(SIGALRM, updateprogressmeter);
903 		alarmtimer(1);		/* set alarm timer for 1 Hz */
904 	} else if (flag == 1) {
905 		alarmtimer(0);
906 		(void)putc('\n', ttyout);
907 		free(title);
908 		title = NULL;
909 	}
910 	fflush(ttyout);
911 }
912 
913 /*
914  * Display transfer statistics.
915  * Requires start to be initialised by progressmeter(-1),
916  * direction to be defined by xfer routines, and filesize and bytes
917  * to be updated by xfer routines
918  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
919  * instead of TTYOUT.
920  */
921 void
922 ptransfer(int siginfo)
923 {
924 	struct timespec now, td;
925 	double elapsed, pace;
926 	off_t bs;
927 	int meg, remaining, hh;
928 	char buf[100];
929 
930 	if (!verbose && !siginfo)
931 		return;
932 
933 	clock_gettime(CLOCK_MONOTONIC, &now);
934 	timespecsub(&now, &start, &td);
935 	elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0);
936 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
937 	meg = 0;
938 	if (bs > (1024 * 1024))
939 		meg = 1;
940 
941 	pace = bs / (1024.0 * (meg ? 1024.0 : 1.0));
942 	(void)snprintf(buf, sizeof(buf),
943 	    "%lld byte%s %s in %lld.%02d seconds (%lld.%02d %sB/s)\n",
944 	    (long long)bytes, bytes == 1 ? "" : "s", direction,
945 	    (long long)elapsed, (int)(elapsed * 100.0) % 100,
946 	    (long long)pace, (int)(pace * 100.0) % 100,
947 	    meg ? "M" : "K");
948 
949 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 &&
950 	    bytes + restart_point <= filesize) {
951 		remaining = (int)((filesize - restart_point) /
952 		    (bytes / elapsed) - elapsed);
953 		hh = remaining / 3600;
954 		remaining %= 3600;
955 
956 		/* "buf+len(buf) -1" to overwrite \n */
957 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
958 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
959 		    remaining % 60);
960 	}
961 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
962 }
963 
964 /*
965  * List words in stringlist, vertically arranged
966  */
967 #ifndef SMALL
968 void
969 list_vertical(StringList *sl)
970 {
971 	int i, j, w;
972 	int columns, width, lines;
973 	char *p;
974 
975 	width = 0;
976 
977 	for (i = 0 ; i < sl->sl_cur ; i++) {
978 		w = strlen(sl->sl_str[i]);
979 		if (w > width)
980 			width = w;
981 	}
982 	width = (width + 8) &~ 7;
983 
984 	columns = ttywidth / width;
985 	if (columns == 0)
986 		columns = 1;
987 	lines = (sl->sl_cur + columns - 1) / columns;
988 	for (i = 0; i < lines; i++) {
989 		for (j = 0; j < columns; j++) {
990 			p = sl->sl_str[j * lines + i];
991 			if (p)
992 				fputs(p, ttyout);
993 			if (j * lines + i + lines >= sl->sl_cur) {
994 				putc('\n', ttyout);
995 				break;
996 			}
997 			w = strlen(p);
998 			while (w < width) {
999 				w = (w + 8) &~ 7;
1000 				(void)putc('\t', ttyout);
1001 			}
1002 		}
1003 	}
1004 }
1005 #endif /* !SMALL */
1006 
1007 /*
1008  * Update the global ttywidth value, using TIOCGWINSZ.
1009  */
1010 /* ARGSUSED */
1011 void
1012 setttywidth(int signo)
1013 {
1014 	int save_errno = errno;
1015 	struct winsize winsize;
1016 
1017 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
1018 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
1019 	else
1020 		ttywidth = 80;
1021 	errno = save_errno;
1022 }
1023 
1024 /*
1025  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
1026  */
1027 void
1028 alarmtimer(int wait)
1029 {
1030 	int save_errno = errno;
1031 	struct itimerval itv;
1032 
1033 	itv.it_value.tv_sec = wait;
1034 	itv.it_value.tv_usec = 0;
1035 	itv.it_interval = itv.it_value;
1036 	setitimer(ITIMER_REAL, &itv, NULL);
1037 	errno = save_errno;
1038 }
1039 
1040 /*
1041  * Setup or cleanup EditLine structures
1042  */
1043 #ifndef SMALL
1044 void
1045 controlediting(void)
1046 {
1047 	HistEvent hev;
1048 
1049 	if (editing && el == NULL && hist == NULL) {
1050 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1051 		hist = history_init();		/* init the builtin history */
1052 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1053 		el_set(el, EL_HIST, history, hist);	/* use history */
1054 
1055 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1056 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1057 
1058 		/* add local file completion, bind to TAB */
1059 		el_set(el, EL_ADDFN, "ftp-complete",
1060 		    "Context sensitive argument completion",
1061 		    complete);
1062 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1063 
1064 		el_source(el, NULL);	/* read ~/.editrc */
1065 		el_set(el, EL_SIGNAL, 1);
1066 	} else if (!editing) {
1067 		if (hist) {
1068 			history_end(hist);
1069 			hist = NULL;
1070 		}
1071 		if (el) {
1072 			el_end(el);
1073 			el = NULL;
1074 		}
1075 	}
1076 }
1077 #endif /* !SMALL */
1078 
1079 /*
1080  * Wait for an asynchronous connect(2) attempt to finish.
1081  */
1082 int
1083 connect_wait(int s)
1084 {
1085 	struct pollfd pfd[1];
1086 	int error = 0;
1087 	socklen_t len = sizeof(error);
1088 
1089 	pfd[0].fd = s;
1090 	pfd[0].events = POLLOUT;
1091 
1092 	if (poll(pfd, 1, -1) == -1)
1093 		return -1;
1094 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
1095 		return -1;
1096 	if (error != 0) {
1097 		errno = error;
1098 		return -1;
1099 	}
1100 	return 0;
1101 }
1102 
1103 #ifndef SMALL
1104 ssize_t
1105 http_time(time_t t, char *tmbuf, size_t len)
1106 {
1107 	struct tm tm;
1108 
1109 	/* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
1110 	if (gmtime_r(&t, &tm) == NULL)
1111 		return 0;
1112 	else
1113 		return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
1114 }
1115 #endif /* !SMALL */
1116