xref: /openbsd/usr.bin/ftp/util.c (revision 9b7c3dbb)
1 /*	$OpenBSD: util.c,v 1.81 2016/08/20 20:18:42 millert 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) {
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 		}
286 		else
287 			exit(0);
288 	}
289 	n = command("USER %s", user);
290 	if (n == CONTINUE) {
291 		if (pass == NULL)
292 			pass = getpass("Password:");
293 		n = command("PASS %s", pass);
294 	}
295 	if (n == CONTINUE) {
296 		aflag++;
297 		if (acctname == NULL)
298 			acctname = getpass("Account:");
299 		n = command("ACCT %s", acctname);
300 	}
301 	if ((n != COMPLETE) ||
302 	    (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
303 		warnx("Login %s failed.", user);
304 		if (retry || !anonftp)
305 			return (0);
306 		else
307 			retry = 1;
308 		goto tryagain;
309 	}
310 	if (proxy)
311 		return (1);
312 	connected = -1;
313 #ifndef SMALL
314 	for (n = 0; n < macnum; ++n) {
315 		if (!strcmp("init", macros[n].mac_name)) {
316 			(void)strlcpy(line, "$init", sizeof line);
317 			makeargv();
318 			domacro(margc, margv);
319 			break;
320 		}
321 	}
322 #endif /* SMALL */
323 	return (1);
324 }
325 
326 /*
327  * `another' gets another argument, and stores the new argc and argv.
328  * It reverts to the top level (via main.c's intr()) on EOF/error.
329  *
330  * Returns false if no new arguments have been added.
331  */
332 #ifndef SMALL
333 int
334 another(int *pargc, char ***pargv, const char *prompt)
335 {
336 	int len = strlen(line), ret;
337 
338 	if (len >= sizeof(line) - 3) {
339 		fputs("sorry, arguments too long.\n", ttyout);
340 		intr();
341 	}
342 	fprintf(ttyout, "(%s) ", prompt);
343 	line[len++] = ' ';
344 	if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) {
345 		clearerr(stdin);
346 		intr();
347 	}
348 	len += strlen(&line[len]);
349 	if (len > 0 && line[len - 1] == '\n')
350 		line[len - 1] = '\0';
351 	makeargv();
352 	ret = margc > *pargc;
353 	*pargc = margc;
354 	*pargv = margv;
355 	return (ret);
356 }
357 #endif /* !SMALL */
358 
359 /*
360  * glob files given in argv[] from the remote server.
361  * if errbuf isn't NULL, store error messages there instead
362  * of writing to the screen.
363  * if type isn't NULL, use LIST instead of NLST, and store filetype.
364  * 'd' means directory, 's' means symbolic link, '-' means plain
365  * file.
366  */
367 char *
368 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type)
369 {
370 	char temp[PATH_MAX], *bufp, *cp, *lmode;
371 	static char buf[PATH_MAX], **args;
372 	int oldverbose, oldhash, fd;
373 
374 	if (!mflag) {
375 		if (!doglob)
376 			args = NULL;
377 		else {
378 			if (*ftemp) {
379 				(void)fclose(*ftemp);
380 				*ftemp = NULL;
381 			}
382 		}
383 		return (NULL);
384 	}
385 	if (!doglob) {
386 		if (args == NULL)
387 			args = argv;
388 		if ((cp = *++args) == NULL)
389 			args = NULL;
390 		return (cp);
391 	}
392 	if (*ftemp == NULL) {
393 		int len;
394 
395 		cp = _PATH_TMP;
396 		len = strlen(cp);
397 		if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
398 			warnx("unable to create temporary file: %s",
399 			    strerror(ENAMETOOLONG));
400 			return (NULL);
401 		}
402 
403 		(void)strlcpy(temp, cp, sizeof temp);
404 		if (temp[len-1] != '/')
405 			temp[len++] = '/';
406 		(void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
407 		if ((fd = mkstemp(temp)) < 0) {
408 			warn("unable to create temporary file: %s", temp);
409 			return (NULL);
410 		}
411 		close(fd);
412 		oldverbose = verbose;
413 		verbose = (errbuf != NULL) ? -1 : 0;
414 		oldhash = hash;
415 		hash = 0;
416 		if (doswitch)
417 			pswitch(!proxy);
418 		for (lmode = "w"; *++argv != NULL; lmode = "a")
419 			recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode,
420 			    0, 0);
421 		if ((code / 100) != COMPLETE) {
422 			if (errbuf != NULL)
423 				*errbuf = reply_string;
424 		}
425 		if (doswitch)
426 			pswitch(!proxy);
427 		verbose = oldverbose;
428 		hash = oldhash;
429 		*ftemp = fopen(temp, "r");
430 		(void)unlink(temp);
431 		if (*ftemp == NULL) {
432 			if (errbuf == NULL)
433 				fputs("can't find list of remote files, oops.\n",
434 				    ttyout);
435 			else
436 				*errbuf =
437 				    "can't find list of remote files, oops.";
438 			return (NULL);
439 		}
440 	}
441 again:
442 	if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
443 		(void)fclose(*ftemp);
444 		*ftemp = NULL;
445 		return (NULL);
446 	}
447 
448 	buf[strcspn(buf, "\n")] = '\0';
449 	bufp = buf;
450 
451 #ifndef SMALL
452 	if (type) {
453 		parse_list(&bufp, type);
454 		if (!bufp ||
455 		    (bufp[0] == '.' &&	/* LIST defaults to -a on some ftp */
456 		    (bufp[1] == '\0' ||	/* servers.  Ignore '.' and '..'. */
457 		    (bufp[1] == '.' && bufp[2] == '\0'))))
458 			goto again;
459 	}
460 #endif /* !SMALL */
461 
462 	return (bufp);
463 }
464 
465 /*
466  * wrapper for remglob2
467  */
468 char *
469 remglob(char *argv[], int doswitch, char **errbuf)
470 {
471 	static FILE *ftemp = NULL;
472 
473 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
474 }
475 
476 #ifndef SMALL
477 int
478 confirm(const char *cmd, const char *file)
479 {
480 	char str[BUFSIZ];
481 
482 	if (file && (confirmrest || !interactive))
483 		return (1);
484 top:
485 	if (file)
486 		fprintf(ttyout, "%s %s? ", cmd, file);
487 	else
488 		fprintf(ttyout, "Continue with %s? ", cmd);
489 	(void)fflush(ttyout);
490 	if (fgets(str, sizeof(str), stdin) == NULL)
491 		goto quit;
492 	switch (tolower((unsigned char)*str)) {
493 		case '?':
494 			fprintf(ttyout,
495 			    "?	help\n"
496 			    "a	answer yes to all\n"
497 			    "n	answer no\n"
498 			    "p	turn off prompt mode\n"
499 			    "q	answer no to all\n"
500 			    "y	answer yes\n");
501 			goto top;
502 		case 'a':
503 			confirmrest = 1;
504 			fprintf(ttyout, "Prompting off for duration of %s.\n",
505 			    cmd);
506 			break;
507 		case 'n':
508 			return (0);
509 		case 'p':
510 			interactive = 0;
511 			fputs("Interactive mode: off.\n", ttyout);
512 			break;
513 		case 'q':
514 quit:
515 			mflag = 0;
516 			clearerr(stdin);
517 			return (0);
518 		case 'y':
519 			return(1);
520 			break;
521 		default:
522 			fprintf(ttyout, "?, a, n, p, q, y "
523 			    "are the only acceptable commands!\n");
524 			goto top;
525 			break;
526 	}
527 	return (1);
528 }
529 #endif /* !SMALL */
530 
531 /*
532  * Glob a local file name specification with
533  * the expectation of a single return value.
534  * Can't control multiple values being expanded
535  * from the expression, we return only the first.
536  */
537 int
538 globulize(char **cpp)
539 {
540 	glob_t gl;
541 	int flags;
542 
543 	if (!doglob)
544 		return (1);
545 
546 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
547 	memset(&gl, 0, sizeof(gl));
548 	if (glob(*cpp, flags, NULL, &gl) ||
549 	    gl.gl_pathc == 0) {
550 		warnx("%s: not found", *cpp);
551 		globfree(&gl);
552 		return (0);
553 	}
554 		/* XXX: caller should check if *cpp changed, and
555 		 *	free(*cpp) if that is the case
556 		 */
557 	*cpp = strdup(gl.gl_pathv[0]);
558 	if (*cpp == NULL)
559 		err(1, NULL);
560 	globfree(&gl);
561 	return (1);
562 }
563 
564 /*
565  * determine size of remote file
566  */
567 off_t
568 remotesize(const char *file, int noisy)
569 {
570 	int overbose;
571 	off_t size;
572 
573 	overbose = verbose;
574 	size = -1;
575 #ifndef SMALL
576 	if (!debug)
577 #endif /* !SMALL */
578 		verbose = -1;
579 	if (command("SIZE %s", file) == COMPLETE) {
580 		char *cp, *ep;
581 
582 		cp = strchr(reply_string, ' ');
583 		if (cp != NULL) {
584 			cp++;
585 			size = strtoll(cp, &ep, 10);
586 			if (*ep != '\0' && !isspace((unsigned char)*ep))
587 				size = -1;
588 		}
589 	} else if (noisy
590 #ifndef SMALL
591 	    && !debug
592 #endif /* !SMALL */
593 	    ) {
594 		fputs(reply_string, ttyout);
595 		fputc('\n', ttyout);
596 	}
597 	verbose = overbose;
598 	return (size);
599 }
600 
601 /*
602  * determine last modification time (in GMT) of remote file
603  */
604 time_t
605 remotemodtime(const char *file, int noisy)
606 {
607 	int overbose;
608 	time_t rtime;
609 	int ocode;
610 
611 	overbose = verbose;
612 	ocode = code;
613 	rtime = -1;
614 #ifndef SMALL
615 	if (!debug)
616 #endif /* !SMALL */
617 		verbose = -1;
618 	if (command("MDTM %s", file) == COMPLETE) {
619 		struct tm timebuf;
620 		int yy, mo, day, hour, min, sec;
621  		/*
622  		 * time-val = 14DIGIT [ "." 1*DIGIT ]
623  		 *		YYYYMMDDHHMMSS[.sss]
624  		 * mdtm-response = "213" SP time-val CRLF / error-response
625  		 */
626 		/* TODO: parse .sss as well, use timespecs. */
627 		char *timestr = reply_string;
628 
629 		/* Repair `19%02d' bug on server side */
630 		while (!isspace((unsigned char)*timestr))
631 			timestr++;
632 		while (isspace((unsigned char)*timestr))
633 			timestr++;
634 		if (strncmp(timestr, "191", 3) == 0) {
635  			fprintf(ttyout,
636  	    "Y2K warning! Fixed incorrect time-val received from server.\n");
637 	    		timestr[0] = ' ';
638 			timestr[1] = '2';
639 			timestr[2] = '0';
640 		}
641 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
642 			&day, &hour, &min, &sec);
643 		memset(&timebuf, 0, sizeof(timebuf));
644 		timebuf.tm_sec = sec;
645 		timebuf.tm_min = min;
646 		timebuf.tm_hour = hour;
647 		timebuf.tm_mday = day;
648 		timebuf.tm_mon = mo - 1;
649 		timebuf.tm_year = yy - 1900;
650 		timebuf.tm_isdst = -1;
651 		rtime = mktime(&timebuf);
652 		if (rtime == -1 && (noisy
653 #ifndef SMALL
654 		    || debug
655 #endif /* !SMALL */
656 		    ))
657 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
658 		else
659 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
660 	} else if (noisy
661 #ifndef SMALL
662 	    && !debug
663 #endif /* !SMALL */
664 	    ) {
665 		fputs(reply_string, ttyout);
666 		fputc('\n', ttyout);
667 	}
668 	verbose = overbose;
669 	if (rtime == -1)
670 		code = ocode;
671 	return (rtime);
672 }
673 
674 /*
675  * Ensure file is in or under dir.
676  * Returns 1 if so, 0 if not (or an error occurred).
677  */
678 int
679 fileindir(const char *file, const char *dir)
680 {
681 	char	parentdirbuf[PATH_MAX], *parentdir;
682 	char	realdir[PATH_MAX];
683 	size_t	dirlen;
684 
685 		 			/* determine parent directory of file */
686 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
687 	parentdir = dirname(parentdirbuf);
688 	if (strcmp(parentdir, ".") == 0)
689 		return 1;		/* current directory is ok */
690 
691 					/* find the directory */
692 	if (realpath(parentdir, realdir) == NULL) {
693 		warn("Unable to determine real path of `%s'", parentdir);
694 		return 0;
695 	}
696 	if (realdir[0] != '/')		/* relative result is ok */
697 		return 1;
698 
699 	dirlen = strlen(dir);
700 	if (strncmp(realdir, dir, dirlen) == 0 &&
701 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
702 		return 1;
703 	return 0;
704 }
705 
706 
707 /*
708  * Returns true if this is the controlling/foreground process, else false.
709  */
710 int
711 foregroundproc(void)
712 {
713 	static pid_t pgrp = -1;
714 	int ctty_pgrp;
715 
716 	if (pgrp == -1)
717 		pgrp = getpgrp();
718 
719 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
720 	    ctty_pgrp == pgrp));
721 }
722 
723 /* ARGSUSED */
724 static void
725 updateprogressmeter(int signo)
726 {
727 	int save_errno = errno;
728 
729 	/* update progressmeter if foreground process or in -m mode */
730 	if (foregroundproc() || progress == -1)
731 		progressmeter(0, NULL);
732 	errno = save_errno;
733 }
734 
735 /*
736  * Display a transfer progress bar if progress is non-zero.
737  * SIGALRM is hijacked for use by this function.
738  * - Before the transfer, set filesize to size of file (or -1 if unknown),
739  *   and call with flag = -1. This starts the once per second timer,
740  *   and a call to updateprogressmeter() upon SIGALRM.
741  * - During the transfer, updateprogressmeter will call progressmeter
742  *   with flag = 0
743  * - After the transfer, call with flag = 1
744  */
745 static struct timeval start;
746 
747 char *action;
748 
749 void
750 progressmeter(int flag, const char *filename)
751 {
752 	/*
753 	 * List of order of magnitude prefixes.
754 	 * The last is `P', as 2^64 = 16384 Petabytes
755 	 */
756 	static const char prefixes[] = " KMGTP";
757 
758 	static struct timeval lastupdate;
759 	static off_t lastsize;
760 	static char *title = NULL;
761 	struct timeval now, td, wait;
762 	off_t cursize, abbrevsize;
763 	double elapsed;
764 	int ratio, barlength, i, remaining, overhead = 30;
765 	char buf[512];
766 
767 	if (flag == -1) {
768 		(void)gettimeofday(&start, NULL);
769 		lastupdate = start;
770 		lastsize = restart_point;
771 	}
772 	(void)gettimeofday(&now, NULL);
773 	if (!progress || filesize < 0)
774 		return;
775 	cursize = bytes + restart_point;
776 
777 	if (filesize)
778 		ratio = cursize * 100 / filesize;
779 	else
780 		ratio = 100;
781 	ratio = MAXIMUM(ratio, 0);
782 	ratio = MINIMUM(ratio, 100);
783 	if (!verbose && flag == -1) {
784 		filename = basename(filename);
785 		if (filename != NULL)
786 			title = strdup(filename);
787 	}
788 
789 	buf[0] = 0;
790 	if (!verbose && action != NULL) {
791 		int l = strlen(action);
792 		char *dotdot = "";
793 
794 		if (l < 7)
795 			l = 7;
796 		else if (l > 12) {
797 			l = 12;
798 			dotdot = "...";
799 			overhead += 3;
800 		}
801 		snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action,
802 		    dotdot);
803 		overhead += l + 1;
804 	} else
805 		snprintf(buf, sizeof(buf), "\r");
806 
807 	if (!verbose && title != NULL) {
808 		int l = strlen(title);
809 		char *dotdot = "";
810 
811 		if (l < 12)
812 			l = 12;
813 		else if (l > 25) {
814 			l = 22;
815 			dotdot = "...";
816 			overhead += 3;
817 		}
818 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
819 		    "%-*.*s%s %3d%% ", l, l, title,
820 		    dotdot, ratio);
821 		overhead += l + 1;
822 	} else
823 		snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
824 
825 	barlength = ttywidth - overhead;
826 	if (barlength > 0) {
827 		i = barlength * ratio / 100;
828 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
829 		    "|%.*s%*s|", i,
830 		    "*******************************************************"
831 		    "*******************************************************"
832 		    "*******************************************************"
833 		    "*******************************************************"
834 		    "*******************************************************"
835 		    "*******************************************************"
836 		    "*******************************************************",
837 		    barlength - i, "");
838 	}
839 
840 	i = 0;
841 	abbrevsize = cursize;
842 	while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) {
843 		i++;
844 		abbrevsize >>= 10;
845 	}
846 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
847 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
848 	    prefixes[i] == ' ' ? ' ' : 'B');
849 
850 	timersub(&now, &lastupdate, &wait);
851 	if (cursize > lastsize) {
852 		lastupdate = now;
853 		lastsize = cursize;
854 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
855 			start.tv_sec += wait.tv_sec;
856 			start.tv_usec += wait.tv_usec;
857 		}
858 		wait.tv_sec = 0;
859 	}
860 
861 	timersub(&now, &start, &td);
862 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
863 
864 	if (flag == 1) {
865 		i = (int)elapsed / 3600;
866 		if (i)
867 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
868 			    "%2d:", i);
869 		else
870 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
871 			    "   ");
872 		i = (int)elapsed % 3600;
873 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
874 		    "%02d:%02d    ", i / 60, i % 60);
875 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
876 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
877 		    "   --:-- ETA");
878 	} else if (wait.tv_sec >= STALLTIME) {
879 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
880 		    " - stalled -");
881 	} else {
882 		remaining = (int)((filesize - restart_point) /
883 				  (bytes / elapsed) - elapsed);
884 		i = remaining / 3600;
885 		if (i)
886 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
887 			    "%2d:", i);
888 		else
889 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
890 			    "   ");
891 		i = remaining % 3600;
892 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
893 		    "%02d:%02d ETA", i / 60, i % 60);
894 	}
895 	(void)write(fileno(ttyout), buf, strlen(buf));
896 
897 	if (flag == -1) {
898 		(void)signal(SIGALRM, updateprogressmeter);
899 		alarmtimer(1);		/* set alarm timer for 1 Hz */
900 	} else if (flag == 1) {
901 		alarmtimer(0);
902 		(void)putc('\n', ttyout);
903 		free(title);
904 		title = NULL;
905 	}
906 	fflush(ttyout);
907 }
908 
909 /*
910  * Display transfer statistics.
911  * Requires start to be initialised by progressmeter(-1),
912  * direction to be defined by xfer routines, and filesize and bytes
913  * to be updated by xfer routines
914  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
915  * instead of TTYOUT.
916  */
917 void
918 ptransfer(int siginfo)
919 {
920 	struct timeval now, td;
921 	double elapsed;
922 	off_t bs;
923 	int meg, remaining, hh;
924 	char buf[100];
925 
926 	if (!verbose && !siginfo)
927 		return;
928 
929 	(void)gettimeofday(&now, NULL);
930 	timersub(&now, &start, &td);
931 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
932 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
933 	meg = 0;
934 	if (bs > (1024 * 1024))
935 		meg = 1;
936 
937 	/* XXX floating point printf in signal handler */
938 	(void)snprintf(buf, sizeof(buf),
939 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
940 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
941 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
942 
943 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 &&
944 	    bytes + restart_point <= filesize) {
945 		remaining = (int)((filesize - restart_point) /
946 		    (bytes / elapsed) - elapsed);
947 		hh = remaining / 3600;
948 		remaining %= 3600;
949 
950 		/* "buf+len(buf) -1" to overwrite \n */
951 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
952 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
953 		    remaining % 60);
954 	}
955 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
956 }
957 
958 /*
959  * List words in stringlist, vertically arranged
960  */
961 #ifndef SMALL
962 void
963 list_vertical(StringList *sl)
964 {
965 	int i, j, w;
966 	int columns, width, lines;
967 	char *p;
968 
969 	width = 0;
970 
971 	for (i = 0 ; i < sl->sl_cur ; i++) {
972 		w = strlen(sl->sl_str[i]);
973 		if (w > width)
974 			width = w;
975 	}
976 	width = (width + 8) &~ 7;
977 
978 	columns = ttywidth / width;
979 	if (columns == 0)
980 		columns = 1;
981 	lines = (sl->sl_cur + columns - 1) / columns;
982 	for (i = 0; i < lines; i++) {
983 		for (j = 0; j < columns; j++) {
984 			p = sl->sl_str[j * lines + i];
985 			if (p)
986 				fputs(p, ttyout);
987 			if (j * lines + i + lines >= sl->sl_cur) {
988 				putc('\n', ttyout);
989 				break;
990 			}
991 			w = strlen(p);
992 			while (w < width) {
993 				w = (w + 8) &~ 7;
994 				(void)putc('\t', ttyout);
995 			}
996 		}
997 	}
998 }
999 #endif /* !SMALL */
1000 
1001 /*
1002  * Update the global ttywidth value, using TIOCGWINSZ.
1003  */
1004 /* ARGSUSED */
1005 void
1006 setttywidth(int signo)
1007 {
1008 	int save_errno = errno;
1009 	struct winsize winsize;
1010 
1011 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
1012 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
1013 	else
1014 		ttywidth = 80;
1015 	errno = save_errno;
1016 }
1017 
1018 /*
1019  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
1020  */
1021 void
1022 alarmtimer(int wait)
1023 {
1024 	int save_errno = errno;
1025 	struct itimerval itv;
1026 
1027 	itv.it_value.tv_sec = wait;
1028 	itv.it_value.tv_usec = 0;
1029 	itv.it_interval = itv.it_value;
1030 	setitimer(ITIMER_REAL, &itv, NULL);
1031 	errno = save_errno;
1032 }
1033 
1034 /*
1035  * Setup or cleanup EditLine structures
1036  */
1037 #ifndef SMALL
1038 void
1039 controlediting(void)
1040 {
1041 	HistEvent hev;
1042 
1043 	if (editing && el == NULL && hist == NULL) {
1044 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1045 		hist = history_init();		/* init the builtin history */
1046 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1047 		el_set(el, EL_HIST, history, hist);	/* use history */
1048 
1049 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1050 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1051 
1052 		/* add local file completion, bind to TAB */
1053 		el_set(el, EL_ADDFN, "ftp-complete",
1054 		    "Context sensitive argument completion",
1055 		    complete);
1056 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1057 
1058 		el_source(el, NULL);	/* read ~/.editrc */
1059 		el_set(el, EL_SIGNAL, 1);
1060 	} else if (!editing) {
1061 		if (hist) {
1062 			history_end(hist);
1063 			hist = NULL;
1064 		}
1065 		if (el) {
1066 			el_end(el);
1067 			el = NULL;
1068 		}
1069 	}
1070 }
1071 #endif /* !SMALL */
1072 
1073 /*
1074  * Wait for an asynchronous connect(2) attempt to finish.
1075  */
1076 int
1077 connect_wait(int s)
1078 {
1079 	struct pollfd pfd[1];
1080 	int error = 0;
1081 	socklen_t len = sizeof(error);
1082 
1083 	pfd[0].fd = s;
1084 	pfd[0].events = POLLOUT;
1085 
1086 	if (poll(pfd, 1, -1) == -1)
1087 		return -1;
1088 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
1089 		return -1;
1090 	if (error != 0) {
1091 		errno = error;
1092 		return -1;
1093 	}
1094 	return 0;
1095 }
1096