xref: /openbsd-src/usr.bin/ftp/util.c (revision 7350f337b9e3eb4461d99580e625c7ef148d107c)
1 /*	$OpenBSD: util.c,v 1.89 2019/05/16 12:44:18 florian 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 		}
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 #ifndef SMALL
442 again:
443 #endif
444 	if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
445 		(void)fclose(*ftemp);
446 		*ftemp = NULL;
447 		return (NULL);
448 	}
449 
450 	buf[strcspn(buf, "\n")] = '\0';
451 	bufp = buf;
452 
453 #ifndef SMALL
454 	if (type) {
455 		parse_list(&bufp, type);
456 		if (!bufp ||
457 		    (bufp[0] == '.' &&	/* LIST defaults to -a on some ftp */
458 		    (bufp[1] == '\0' ||	/* servers.  Ignore '.' and '..'. */
459 		    (bufp[1] == '.' && bufp[2] == '\0'))))
460 			goto again;
461 	}
462 #endif /* !SMALL */
463 
464 	return (bufp);
465 }
466 
467 /*
468  * wrapper for remglob2
469  */
470 char *
471 remglob(char *argv[], int doswitch, char **errbuf)
472 {
473 	static FILE *ftemp = NULL;
474 
475 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
476 }
477 
478 #ifndef SMALL
479 int
480 confirm(const char *cmd, const char *file)
481 {
482 	char str[BUFSIZ];
483 
484 	if (file && (confirmrest || !interactive))
485 		return (1);
486 top:
487 	if (file)
488 		fprintf(ttyout, "%s %s? ", cmd, file);
489 	else
490 		fprintf(ttyout, "Continue with %s? ", cmd);
491 	(void)fflush(ttyout);
492 	if (fgets(str, sizeof(str), stdin) == NULL)
493 		goto quit;
494 	switch (tolower((unsigned char)*str)) {
495 		case '?':
496 			fprintf(ttyout,
497 			    "?	help\n"
498 			    "a	answer yes to all\n"
499 			    "n	answer no\n"
500 			    "p	turn off prompt mode\n"
501 			    "q	answer no to all\n"
502 			    "y	answer yes\n");
503 			goto top;
504 		case 'a':
505 			confirmrest = 1;
506 			fprintf(ttyout, "Prompting off for duration of %s.\n",
507 			    cmd);
508 			break;
509 		case 'n':
510 			return (0);
511 		case 'p':
512 			interactive = 0;
513 			fputs("Interactive mode: off.\n", ttyout);
514 			break;
515 		case 'q':
516 quit:
517 			mflag = 0;
518 			clearerr(stdin);
519 			return (0);
520 		case 'y':
521 			return(1);
522 			break;
523 		default:
524 			fprintf(ttyout, "?, a, n, p, q, y "
525 			    "are the only acceptable commands!\n");
526 			goto top;
527 			break;
528 	}
529 	return (1);
530 }
531 #endif /* !SMALL */
532 
533 /*
534  * Glob a local file name specification with
535  * the expectation of a single return value.
536  * Can't control multiple values being expanded
537  * from the expression, we return only the first.
538  */
539 int
540 globulize(char **cpp)
541 {
542 	glob_t gl;
543 	int flags;
544 
545 	if (!doglob)
546 		return (1);
547 
548 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
549 	memset(&gl, 0, sizeof(gl));
550 	if (glob(*cpp, flags, NULL, &gl) ||
551 	    gl.gl_pathc == 0) {
552 		warnx("%s: not found", *cpp);
553 		globfree(&gl);
554 		return (0);
555 	}
556 		/* XXX: caller should check if *cpp changed, and
557 		 *	free(*cpp) if that is the case
558 		 */
559 	*cpp = strdup(gl.gl_pathv[0]);
560 	if (*cpp == NULL)
561 		err(1, NULL);
562 	globfree(&gl);
563 	return (1);
564 }
565 
566 /*
567  * determine size of remote file
568  */
569 off_t
570 remotesize(const char *file, int noisy)
571 {
572 	int overbose;
573 	off_t size;
574 
575 	overbose = verbose;
576 	size = -1;
577 #ifndef SMALL
578 	if (!debug)
579 #endif /* !SMALL */
580 		verbose = -1;
581 	if (command("SIZE %s", file) == COMPLETE) {
582 		char *cp, *ep;
583 
584 		cp = strchr(reply_string, ' ');
585 		if (cp != NULL) {
586 			cp++;
587 			size = strtoll(cp, &ep, 10);
588 			if (*ep != '\0' && !isspace((unsigned char)*ep))
589 				size = -1;
590 		}
591 	} else if (noisy
592 #ifndef SMALL
593 	    && !debug
594 #endif /* !SMALL */
595 	    ) {
596 		fputs(reply_string, ttyout);
597 		fputc('\n', ttyout);
598 	}
599 	verbose = overbose;
600 	return (size);
601 }
602 
603 /*
604  * determine last modification time (in GMT) of remote file
605  */
606 time_t
607 remotemodtime(const char *file, int noisy)
608 {
609 	int overbose;
610 	time_t rtime;
611 	int ocode;
612 
613 	overbose = verbose;
614 	ocode = code;
615 	rtime = -1;
616 #ifndef SMALL
617 	if (!debug)
618 #endif /* !SMALL */
619 		verbose = -1;
620 	if (command("MDTM %s", file) == COMPLETE) {
621 		struct tm timebuf;
622 		int yy, mo, day, hour, min, sec;
623 		/*
624 		 * time-val = 14DIGIT [ "." 1*DIGIT ]
625 		 *		YYYYMMDDHHMMSS[.sss]
626 		 * mdtm-response = "213" SP time-val CRLF / error-response
627 		 */
628 		/* TODO: parse .sss as well, use timespecs. */
629 		char *timestr = reply_string;
630 
631 		/* Repair `19%02d' bug on server side */
632 		while (!isspace((unsigned char)*timestr))
633 			timestr++;
634 		while (isspace((unsigned char)*timestr))
635 			timestr++;
636 		if (strncmp(timestr, "191", 3) == 0) {
637 			fprintf(ttyout,
638 	    "Y2K warning! Fixed incorrect time-val received from server.\n");
639 			timestr[0] = ' ';
640 			timestr[1] = '2';
641 			timestr[2] = '0';
642 		}
643 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
644 			&day, &hour, &min, &sec);
645 		memset(&timebuf, 0, sizeof(timebuf));
646 		timebuf.tm_sec = sec;
647 		timebuf.tm_min = min;
648 		timebuf.tm_hour = hour;
649 		timebuf.tm_mday = day;
650 		timebuf.tm_mon = mo - 1;
651 		timebuf.tm_year = yy - 1900;
652 		timebuf.tm_isdst = -1;
653 		rtime = mktime(&timebuf);
654 		if (rtime == -1 && (noisy
655 #ifndef SMALL
656 		    || debug
657 #endif /* !SMALL */
658 		    ))
659 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
660 		else
661 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
662 	} else if (noisy
663 #ifndef SMALL
664 	    && !debug
665 #endif /* !SMALL */
666 	    ) {
667 		fputs(reply_string, ttyout);
668 		fputc('\n', ttyout);
669 	}
670 	verbose = overbose;
671 	if (rtime == -1)
672 		code = ocode;
673 	return (rtime);
674 }
675 
676 /*
677  * Ensure file is in or under dir.
678  * Returns 1 if so, 0 if not (or an error occurred).
679  */
680 int
681 fileindir(const char *file, const char *dir)
682 {
683 	char	parentdirbuf[PATH_MAX], *parentdir;
684 	char	realdir[PATH_MAX];
685 	size_t	dirlen;
686 
687 					/* determine parent directory of file */
688 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
689 	parentdir = dirname(parentdirbuf);
690 	if (strcmp(parentdir, ".") == 0)
691 		return 1;		/* current directory is ok */
692 
693 					/* find the directory */
694 	if (realpath(parentdir, realdir) == NULL) {
695 		warn("Unable to determine real path of `%s'", parentdir);
696 		return 0;
697 	}
698 	if (realdir[0] != '/')		/* relative result is ok */
699 		return 1;
700 
701 	dirlen = strlen(dir);
702 	if (strncmp(realdir, dir, dirlen) == 0 &&
703 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
704 		return 1;
705 	return 0;
706 }
707 
708 
709 /*
710  * Returns true if this is the controlling/foreground process, else false.
711  */
712 int
713 foregroundproc(void)
714 {
715 	static pid_t pgrp = -1;
716 	int ctty_pgrp;
717 
718 	if (pgrp == -1)
719 		pgrp = getpgrp();
720 
721 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
722 	    ctty_pgrp == pgrp));
723 }
724 
725 /* ARGSUSED */
726 static void
727 updateprogressmeter(int signo)
728 {
729 	int save_errno = errno;
730 
731 	/* update progressmeter if foreground process or in -m mode */
732 	if (foregroundproc() || progress == -1)
733 		progressmeter(0, NULL);
734 	errno = save_errno;
735 }
736 
737 /*
738  * Display a transfer progress bar if progress is non-zero.
739  * SIGALRM is hijacked for use by this function.
740  * - Before the transfer, set filesize to size of file (or -1 if unknown),
741  *   and call with flag = -1. This starts the once per second timer,
742  *   and a call to updateprogressmeter() upon SIGALRM.
743  * - During the transfer, updateprogressmeter will call progressmeter
744  *   with flag = 0
745  * - After the transfer, call with flag = 1
746  */
747 static struct timespec start;
748 
749 char *action;
750 
751 void
752 progressmeter(int flag, const char *filename)
753 {
754 	/*
755 	 * List of order of magnitude prefixes.
756 	 * The last is `P', as 2^64 = 16384 Petabytes
757 	 */
758 	static const char prefixes[] = " KMGTP";
759 
760 	static struct timespec lastupdate;
761 	static off_t lastsize;
762 	static char *title = NULL;
763 	struct timespec now, td, wait;
764 	off_t cursize, abbrevsize;
765 	double elapsed;
766 	int ratio, barlength, i, remaining, overhead = 30;
767 	char buf[512];
768 
769 	if (flag == -1) {
770 		clock_gettime(CLOCK_MONOTONIC, &start);
771 		lastupdate = start;
772 		lastsize = restart_point;
773 	}
774 	clock_gettime(CLOCK_MONOTONIC, &now);
775 	if (!progress || filesize < 0)
776 		return;
777 	cursize = bytes + restart_point;
778 
779 	if (filesize)
780 		ratio = cursize * 100 / filesize;
781 	else
782 		ratio = 100;
783 	ratio = MAXIMUM(ratio, 0);
784 	ratio = MINIMUM(ratio, 100);
785 	if (!verbose && flag == -1) {
786 		filename = basename(filename);
787 		if (filename != NULL) {
788 			free(title);
789 			title = strdup(filename);
790 		}
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;
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 	/* XXX floating point printf in signal handler */
942 	(void)snprintf(buf, sizeof(buf),
943 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
944 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
945 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
946 
947 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 &&
948 	    bytes + restart_point <= filesize) {
949 		remaining = (int)((filesize - restart_point) /
950 		    (bytes / elapsed) - elapsed);
951 		hh = remaining / 3600;
952 		remaining %= 3600;
953 
954 		/* "buf+len(buf) -1" to overwrite \n */
955 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
956 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
957 		    remaining % 60);
958 	}
959 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
960 }
961 
962 /*
963  * List words in stringlist, vertically arranged
964  */
965 #ifndef SMALL
966 void
967 list_vertical(StringList *sl)
968 {
969 	int i, j, w;
970 	int columns, width, lines;
971 	char *p;
972 
973 	width = 0;
974 
975 	for (i = 0 ; i < sl->sl_cur ; i++) {
976 		w = strlen(sl->sl_str[i]);
977 		if (w > width)
978 			width = w;
979 	}
980 	width = (width + 8) &~ 7;
981 
982 	columns = ttywidth / width;
983 	if (columns == 0)
984 		columns = 1;
985 	lines = (sl->sl_cur + columns - 1) / columns;
986 	for (i = 0; i < lines; i++) {
987 		for (j = 0; j < columns; j++) {
988 			p = sl->sl_str[j * lines + i];
989 			if (p)
990 				fputs(p, ttyout);
991 			if (j * lines + i + lines >= sl->sl_cur) {
992 				putc('\n', ttyout);
993 				break;
994 			}
995 			w = strlen(p);
996 			while (w < width) {
997 				w = (w + 8) &~ 7;
998 				(void)putc('\t', ttyout);
999 			}
1000 		}
1001 	}
1002 }
1003 #endif /* !SMALL */
1004 
1005 /*
1006  * Update the global ttywidth value, using TIOCGWINSZ.
1007  */
1008 /* ARGSUSED */
1009 void
1010 setttywidth(int signo)
1011 {
1012 	int save_errno = errno;
1013 	struct winsize winsize;
1014 
1015 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
1016 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
1017 	else
1018 		ttywidth = 80;
1019 	errno = save_errno;
1020 }
1021 
1022 /*
1023  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
1024  */
1025 void
1026 alarmtimer(int wait)
1027 {
1028 	int save_errno = errno;
1029 	struct itimerval itv;
1030 
1031 	itv.it_value.tv_sec = wait;
1032 	itv.it_value.tv_usec = 0;
1033 	itv.it_interval = itv.it_value;
1034 	setitimer(ITIMER_REAL, &itv, NULL);
1035 	errno = save_errno;
1036 }
1037 
1038 /*
1039  * Setup or cleanup EditLine structures
1040  */
1041 #ifndef SMALL
1042 void
1043 controlediting(void)
1044 {
1045 	HistEvent hev;
1046 
1047 	if (editing && el == NULL && hist == NULL) {
1048 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1049 		hist = history_init();		/* init the builtin history */
1050 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1051 		el_set(el, EL_HIST, history, hist);	/* use history */
1052 
1053 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1054 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1055 
1056 		/* add local file completion, bind to TAB */
1057 		el_set(el, EL_ADDFN, "ftp-complete",
1058 		    "Context sensitive argument completion",
1059 		    complete);
1060 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1061 
1062 		el_source(el, NULL);	/* read ~/.editrc */
1063 		el_set(el, EL_SIGNAL, 1);
1064 	} else if (!editing) {
1065 		if (hist) {
1066 			history_end(hist);
1067 			hist = NULL;
1068 		}
1069 		if (el) {
1070 			el_end(el);
1071 			el = NULL;
1072 		}
1073 	}
1074 }
1075 #endif /* !SMALL */
1076 
1077 /*
1078  * Wait for an asynchronous connect(2) attempt to finish.
1079  */
1080 int
1081 connect_wait(int s)
1082 {
1083 	struct pollfd pfd[1];
1084 	int error = 0;
1085 	socklen_t len = sizeof(error);
1086 
1087 	pfd[0].fd = s;
1088 	pfd[0].events = POLLOUT;
1089 
1090 	if (poll(pfd, 1, -1) == -1)
1091 		return -1;
1092 	if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
1093 		return -1;
1094 	if (error != 0) {
1095 		errno = error;
1096 		return -1;
1097 	}
1098 	return 0;
1099 }
1100