xref: /openbsd/usr.bin/ftp/util.c (revision 09467b48)
1 /*	$OpenBSD: util.c,v 1.93 2020/07/06 17:11:29 deraadt 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];
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 		filename = basename(filename);
786 		if (filename != NULL) {
787 			free(title);
788 			title = strdup(filename);
789 		}
790 	}
791 
792 	buf[0] = 0;
793 	if (!verbose && action != NULL) {
794 		int l = strlen(action);
795 		char *dotdot = "";
796 
797 		if (l < 7)
798 			l = 7;
799 		else if (l > 12) {
800 			l = 12;
801 			dotdot = "...";
802 			overhead += 3;
803 		}
804 		snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action,
805 		    dotdot);
806 		overhead += l + 1;
807 	} else
808 		snprintf(buf, sizeof(buf), "\r");
809 
810 	if (!verbose && title != NULL) {
811 		int l = strlen(title);
812 		char *dotdot = "";
813 
814 		if (l < 12)
815 			l = 12;
816 		else if (l > 25) {
817 			l = 22;
818 			dotdot = "...";
819 			overhead += 3;
820 		}
821 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
822 		    "%-*.*s%s %3d%% ", l, l, title,
823 		    dotdot, ratio);
824 		overhead += l + 1;
825 	} else
826 		snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
827 
828 	barlength = ttywidth - overhead;
829 	if (barlength > 0) {
830 		i = barlength * ratio / 100;
831 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
832 		    "|%.*s%*s|", i,
833 		    "*******************************************************"
834 		    "*******************************************************"
835 		    "*******************************************************"
836 		    "*******************************************************"
837 		    "*******************************************************"
838 		    "*******************************************************"
839 		    "*******************************************************",
840 		    barlength - i, "");
841 	}
842 
843 	i = 0;
844 	abbrevsize = cursize;
845 	while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) {
846 		i++;
847 		abbrevsize >>= 10;
848 	}
849 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
850 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
851 	    prefixes[i] == ' ' ? ' ' : 'B');
852 
853 	timespecsub(&now, &lastupdate, &wait);
854 	if (cursize > lastsize) {
855 		lastupdate = now;
856 		lastsize = cursize;
857 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
858 			start.tv_sec += wait.tv_sec;
859 			start.tv_nsec += wait.tv_nsec;
860 		}
861 		wait.tv_sec = 0;
862 	}
863 
864 	timespecsub(&now, &start, &td);
865 	elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0);
866 
867 	if (flag == 1) {
868 		i = (int)elapsed / 3600;
869 		if (i)
870 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
871 			    "%2d:", i);
872 		else
873 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
874 			    "   ");
875 		i = (int)elapsed % 3600;
876 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
877 		    "%02d:%02d    ", i / 60, i % 60);
878 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
879 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
880 		    "   --:-- ETA");
881 	} else if (wait.tv_sec >= STALLTIME) {
882 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
883 		    " - stalled -");
884 	} else {
885 		remaining = (int)((filesize - restart_point) /
886 				  (bytes / elapsed) - elapsed);
887 		i = remaining / 3600;
888 		if (i)
889 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
890 			    "%2d:", i);
891 		else
892 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
893 			    "   ");
894 		i = remaining % 3600;
895 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
896 		    "%02d:%02d ETA", i / 60, i % 60);
897 	}
898 	(void)write(fileno(ttyout), buf, strlen(buf));
899 
900 	if (flag == -1) {
901 		(void)signal(SIGALRM, updateprogressmeter);
902 		alarmtimer(1);		/* set alarm timer for 1 Hz */
903 	} else if (flag == 1) {
904 		alarmtimer(0);
905 		(void)putc('\n', ttyout);
906 		free(title);
907 		title = NULL;
908 	}
909 	fflush(ttyout);
910 }
911 
912 /*
913  * Display transfer statistics.
914  * Requires start to be initialised by progressmeter(-1),
915  * direction to be defined by xfer routines, and filesize and bytes
916  * to be updated by xfer routines
917  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
918  * instead of TTYOUT.
919  */
920 void
921 ptransfer(int siginfo)
922 {
923 	struct timespec now, td;
924 	double elapsed, pace;
925 	off_t bs;
926 	int meg, remaining, hh;
927 	char buf[100];
928 
929 	if (!verbose && !siginfo)
930 		return;
931 
932 	clock_gettime(CLOCK_MONOTONIC, &now);
933 	timespecsub(&now, &start, &td);
934 	elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0);
935 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
936 	meg = 0;
937 	if (bs > (1024 * 1024))
938 		meg = 1;
939 
940 	pace = bs / (1024.0 * (meg ? 1024.0 : 1.0));
941 	(void)snprintf(buf, sizeof(buf),
942 	    "%lld byte%s %s in %lld.%02d seconds (%lld.%02d %sB/s)\n",
943 	    (long long)bytes, bytes == 1 ? "" : "s", direction,
944 	    (long long)elapsed, (int)(elapsed * 100.0) % 100,
945 	    (long long)pace, (int)(pace * 100.0) % 100,
946 	    meg ? "M" : "K");
947 
948 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 &&
949 	    bytes + restart_point <= filesize) {
950 		remaining = (int)((filesize - restart_point) /
951 		    (bytes / elapsed) - elapsed);
952 		hh = remaining / 3600;
953 		remaining %= 3600;
954 
955 		/* "buf+len(buf) -1" to overwrite \n */
956 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
957 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
958 		    remaining % 60);
959 	}
960 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
961 }
962 
963 /*
964  * List words in stringlist, vertically arranged
965  */
966 #ifndef SMALL
967 void
968 list_vertical(StringList *sl)
969 {
970 	int i, j, w;
971 	int columns, width, lines;
972 	char *p;
973 
974 	width = 0;
975 
976 	for (i = 0 ; i < sl->sl_cur ; i++) {
977 		w = strlen(sl->sl_str[i]);
978 		if (w > width)
979 			width = w;
980 	}
981 	width = (width + 8) &~ 7;
982 
983 	columns = ttywidth / width;
984 	if (columns == 0)
985 		columns = 1;
986 	lines = (sl->sl_cur + columns - 1) / columns;
987 	for (i = 0; i < lines; i++) {
988 		for (j = 0; j < columns; j++) {
989 			p = sl->sl_str[j * lines + i];
990 			if (p)
991 				fputs(p, ttyout);
992 			if (j * lines + i + lines >= sl->sl_cur) {
993 				putc('\n', ttyout);
994 				break;
995 			}
996 			w = strlen(p);
997 			while (w < width) {
998 				w = (w + 8) &~ 7;
999 				(void)putc('\t', ttyout);
1000 			}
1001 		}
1002 	}
1003 }
1004 #endif /* !SMALL */
1005 
1006 /*
1007  * Update the global ttywidth value, using TIOCGWINSZ.
1008  */
1009 /* ARGSUSED */
1010 void
1011 setttywidth(int signo)
1012 {
1013 	int save_errno = errno;
1014 	struct winsize winsize;
1015 
1016 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
1017 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
1018 	else
1019 		ttywidth = 80;
1020 	errno = save_errno;
1021 }
1022 
1023 /*
1024  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
1025  */
1026 void
1027 alarmtimer(int wait)
1028 {
1029 	int save_errno = errno;
1030 	struct itimerval itv;
1031 
1032 	itv.it_value.tv_sec = wait;
1033 	itv.it_value.tv_usec = 0;
1034 	itv.it_interval = itv.it_value;
1035 	setitimer(ITIMER_REAL, &itv, NULL);
1036 	errno = save_errno;
1037 }
1038 
1039 /*
1040  * Setup or cleanup EditLine structures
1041  */
1042 #ifndef SMALL
1043 void
1044 controlediting(void)
1045 {
1046 	HistEvent hev;
1047 
1048 	if (editing && el == NULL && hist == NULL) {
1049 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1050 		hist = history_init();		/* init the builtin history */
1051 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1052 		el_set(el, EL_HIST, history, hist);	/* use history */
1053 
1054 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1055 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1056 
1057 		/* add local file completion, bind to TAB */
1058 		el_set(el, EL_ADDFN, "ftp-complete",
1059 		    "Context sensitive argument completion",
1060 		    complete);
1061 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1062 
1063 		el_source(el, NULL);	/* read ~/.editrc */
1064 		el_set(el, EL_SIGNAL, 1);
1065 	} else if (!editing) {
1066 		if (hist) {
1067 			history_end(hist);
1068 			hist = NULL;
1069 		}
1070 		if (el) {
1071 			el_end(el);
1072 			el = NULL;
1073 		}
1074 	}
1075 }
1076 #endif /* !SMALL */
1077 
1078 /*
1079  * Wait for an asynchronous connect(2) attempt to finish.
1080  */
1081 int
1082 connect_wait(int s)
1083 {
1084 	struct pollfd pfd[1];
1085 	int error = 0;
1086 	socklen_t len = sizeof(error);
1087 
1088 	pfd[0].fd = s;
1089 	pfd[0].events = POLLOUT;
1090 
1091 	if (poll(pfd, 1, -1) == -1)
1092 		return -1;
1093 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
1094 		return -1;
1095 	if (error != 0) {
1096 		errno = error;
1097 		return -1;
1098 	}
1099 	return 0;
1100 }
1101