xref: /openbsd-src/usr.bin/ftp/util.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: util.c,v 1.61 2009/04/27 22:51:51 martynas 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/time.h>
71 #include <arpa/ftp.h>
72 
73 #include <ctype.h>
74 #include <err.h>
75 #include <errno.h>
76 #include <fcntl.h>
77 #include <libgen.h>
78 #include <limits.h>
79 #include <glob.h>
80 #include <pwd.h>
81 #include <signal.h>
82 #include <stdio.h>
83 #include <stdlib.h>
84 #include <string.h>
85 #include <time.h>
86 #include <tzfile.h>
87 #include <unistd.h>
88 
89 #include "ftp_var.h"
90 #include "pathnames.h"
91 
92 static void updateprogressmeter(int);
93 
94 /*
95  * Connect to peer server and
96  * auto-login, if possible.
97  */
98 void
99 setpeer(int argc, char *argv[])
100 {
101 	char *host, *port;
102 
103 	if (connected) {
104 		fprintf(ttyout, "Already connected to %s, use close first.\n",
105 		    hostname);
106 		code = -1;
107 		return;
108 	}
109 	if (argc < 2)
110 		(void)another(&argc, &argv, "to");
111 	if (argc < 2 || argc > 3) {
112 		fprintf(ttyout, "usage: %s host [port]\n", argv[0]);
113 		code = -1;
114 		return;
115 	}
116 	if (gatemode)
117 		port = gateport;
118 	else
119 		port = ftpport;
120 	if (argc > 2)
121 		port = argv[2];
122 
123 	if (gatemode) {
124 		if (gateserver == NULL || *gateserver == '\0')
125 			errx(1, "gateserver not defined (shouldn't happen)");
126 		host = hookup(gateserver, port);
127 	} else
128 		host = hookup(argv[1], port);
129 
130 	if (host) {
131 		int overbose;
132 
133 		if (gatemode) {
134 			if (command("PASSERVE %s", argv[1]) != COMPLETE)
135 				return;
136 			if (verbose)
137 				fprintf(ttyout,
138 				    "Connected via pass-through server %s\n",
139 				    gateserver);
140 		}
141 
142 		connected = 1;
143 		/*
144 		 * Set up defaults for FTP.
145 		 */
146 		(void)strlcpy(formname, "non-print", sizeof formname);
147 		form = FORM_N;
148 		(void)strlcpy(modename, "stream", sizeof modename);
149 		mode = MODE_S;
150 		(void)strlcpy(structname, "file", sizeof structname);
151 		stru = STRU_F;
152 		(void)strlcpy(bytename, "8", sizeof bytename);
153 		bytesize = 8;
154 
155 		/*
156 		 * Set type to 0 (not specified by user),
157 		 * meaning binary by default, but don't bother
158 		 * telling server.  We can use binary
159 		 * for text files unless changed by the user.
160 		 */
161 		(void)strlcpy(typename, "binary", sizeof typename);
162 		curtype = TYPE_A;
163 		type = 0;
164 		if (autologin)
165 			(void)ftp_login(argv[1], NULL, NULL);
166 
167 #if (defined(unix) || defined(BSD)) && NBBY == 8
168 /*
169  * this ifdef is to keep someone form "porting" this to an incompatible
170  * system and not checking this out. This way they have to think about it.
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 			if (overbose &&
208 			    !strncmp(reply_string, "215 TOPS20", 10))
209 				fputs(
210 "Remember to set tenex mode when transferring binary files from this machine.\n",
211 				    ttyout);
212 		}
213 		verbose = overbose;
214 #endif /* unix || BSD */
215 	}
216 }
217 
218 /*
219  * login to remote host, using given username & password if supplied
220  */
221 int
222 ftp_login(const char *host, char *user, char *pass)
223 {
224 	char tmp[80], *acctname = NULL, host_name[MAXHOSTNAMELEN];
225 	char anonpass[MAXLOGNAME + 1 + MAXHOSTNAMELEN];	/* "user@hostname" */
226 	int n, aflag = 0, retry = 0;
227 	struct passwd *pw;
228 
229 #ifndef SMALL
230 	if (user == NULL) {
231 		if (ruserpass(host, &user, &pass, &acctname) < 0) {
232 			code = -1;
233 			return (0);
234 		}
235 	}
236 #endif /* !SMALL */
237 
238 	/*
239 	 * Set up arguments for an anonymous FTP session, if necessary.
240 	 */
241 	if ((user == NULL || pass == NULL) && anonftp) {
242 		memset(anonpass, 0, sizeof(anonpass));
243 		memset(host_name, 0, sizeof(host_name));
244 
245 		/*
246 		 * Set up anonymous login password.
247 		 */
248 		if ((user = getlogin()) == NULL) {
249 			if ((pw = getpwuid(getuid())) == NULL)
250 				user = "anonymous";
251 			else
252 				user = pw->pw_name;
253 		}
254 		gethostname(host_name, sizeof(host_name));
255 #ifndef DONT_CHEAT_ANONPASS
256 		/*
257 		 * Every anonymous FTP server I've encountered
258 		 * will accept the string "username@", and will
259 		 * append the hostname itself.  We do this by default
260 		 * since many servers are picky about not having
261 		 * a FQDN in the anonymous password. - thorpej@netbsd.org
262 		 */
263 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@",
264 		    user);
265 #else
266 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s",
267 		    user, hp->h_name);
268 #endif
269 		pass = anonpass;
270 		user = "anonymous";	/* as per RFC 1635 */
271 	}
272 
273 tryagain:
274 	if (retry)
275 		user = "ftp";		/* some servers only allow "ftp" */
276 
277 	while (user == NULL) {
278 		char *myname = getlogin();
279 
280 		if (myname == NULL && (pw = getpwuid(getuid())) != NULL)
281 			myname = pw->pw_name;
282 		if (myname)
283 			fprintf(ttyout, "Name (%s:%s): ", host, myname);
284 		else
285 			fprintf(ttyout, "Name (%s): ", host);
286 		user = myname;
287 		if (fgets(tmp, sizeof(tmp), stdin) != NULL) {
288 			tmp[strcspn(tmp, "\n")] = '\0';
289 			if (tmp[0] != '\0')
290 				user = tmp;
291 		}
292 		else
293 			exit(0);
294 	}
295 	n = command("USER %s", user);
296 	if (n == CONTINUE) {
297 		if (pass == NULL)
298 			pass = getpass("Password:");
299 		n = command("PASS %s", pass);
300 	}
301 	if (n == CONTINUE) {
302 		aflag++;
303 		if (acctname == NULL)
304 			acctname = getpass("Account:");
305 		n = command("ACCT %s", acctname);
306 	}
307 	if ((n != COMPLETE) ||
308 	    (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
309 		warnx("Login failed.");
310 		if (retry || !anonftp)
311 			return (0);
312 		else
313 			retry = 1;
314 		goto tryagain;
315 	}
316 	if (proxy)
317 		return (1);
318 	connected = -1;
319 #ifndef SMALL
320 	for (n = 0; n < macnum; ++n) {
321 		if (!strcmp("init", macros[n].mac_name)) {
322 			(void)strlcpy(line, "$init", sizeof line);
323 			makeargv();
324 			domacro(margc, margv);
325 			break;
326 		}
327 	}
328 #endif /* SMALL */
329 	return (1);
330 }
331 
332 /*
333  * `another' gets another argument, and stores the new argc and argv.
334  * It reverts to the top level (via main.c's intr()) on EOF/error.
335  *
336  * Returns false if no new arguments have been added.
337  */
338 int
339 another(int *pargc, char ***pargv, const char *prompt)
340 {
341 	int len = strlen(line), ret;
342 
343 	if (len >= sizeof(line) - 3) {
344 		fputs("sorry, arguments too long.\n", ttyout);
345 		intr();
346 	}
347 	fprintf(ttyout, "(%s) ", prompt);
348 	line[len++] = ' ';
349 	if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) {
350 		clearerr(stdin);
351 		intr();
352 	}
353 	len += strlen(&line[len]);
354 	if (len > 0 && line[len - 1] == '\n')
355 		line[len - 1] = '\0';
356 	makeargv();
357 	ret = margc > *pargc;
358 	*pargc = margc;
359 	*pargv = margv;
360 	return (ret);
361 }
362 
363 /*
364  * glob files given in argv[] from the remote server.
365  * if errbuf isn't NULL, store error messages there instead
366  * of writing to the screen.
367  * if type isn't NULL, use LIST instead of NLST, and store filetype.
368  * 'd' means directory, 's' means symbolic link, '-' means plain
369  * file.
370  */
371 char *
372 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type)
373 {
374 	char temp[MAXPATHLEN], *bufp, *cp, *lmode;
375 	static char buf[MAXPATHLEN], **args;
376 	int oldverbose, oldhash, fd;
377 
378 	if (!mflag) {
379 		if (!doglob)
380 			args = NULL;
381 		else {
382 			if (*ftemp) {
383 				(void)fclose(*ftemp);
384 				*ftemp = NULL;
385 			}
386 		}
387 		return (NULL);
388 	}
389 	if (!doglob) {
390 		if (args == NULL)
391 			args = argv;
392 		if ((cp = *++args) == NULL)
393 			args = NULL;
394 		return (cp);
395 	}
396 	if (*ftemp == NULL) {
397 		int len;
398 
399 		if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0')
400 		    cp = _PATH_TMP;
401 		len = strlen(cp);
402 		if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
403 			warnx("unable to create temporary file: %s",
404 			    strerror(ENAMETOOLONG));
405 			return (NULL);
406 		}
407 
408 		(void)strlcpy(temp, cp, sizeof temp);
409 		if (temp[len-1] != '/')
410 			temp[len++] = '/';
411 		(void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
412 		if ((fd = mkstemp(temp)) < 0) {
413 			warn("unable to create temporary file: %s", temp);
414 			return (NULL);
415 		}
416 		close(fd);
417 		oldverbose = verbose;
418 		verbose = (errbuf != NULL) ? -1 : 0;
419 		oldhash = hash;
420 		hash = 0;
421 		if (doswitch)
422 			pswitch(!proxy);
423 		for (lmode = "w"; *++argv != NULL; lmode = "a")
424 			recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode,
425 			    0, 0);
426 		if ((code / 100) != COMPLETE) {
427 			if (errbuf != NULL)
428 				*errbuf = reply_string;
429 		}
430 		if (doswitch)
431 			pswitch(!proxy);
432 		verbose = oldverbose;
433 		hash = oldhash;
434 		*ftemp = fopen(temp, "r");
435 		(void)unlink(temp);
436 		if (*ftemp == NULL) {
437 			if (errbuf == NULL)
438 				fputs("can't find list of remote files, oops.\n",
439 				    ttyout);
440 			else
441 				*errbuf =
442 				    "can't find list of remote files, oops.";
443 			return (NULL);
444 		}
445 	}
446 again:
447 	if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
448 		(void)fclose(*ftemp);
449 		*ftemp = NULL;
450 		return (NULL);
451 	}
452 
453 	buf[strcspn(buf, "\n")] = '\0';
454 	bufp = buf;
455 
456 #ifndef SMALL
457 	if (type) {
458 		parse_list(&bufp, type);
459 		if (!bufp ||
460 		    (bufp[0] == '.' &&	/* LIST defaults to -a on some ftp */
461 		    (bufp[1] == '\0' ||	/* servers.  Ignore '.' and '..'. */
462 		    (bufp[1] == '.' && bufp[2] == '\0'))))
463 			goto again;
464 	}
465 #endif /* !SMALL */
466 
467 	return (bufp);
468 }
469 
470 /*
471  * wrapper for remglob2
472  */
473 char *
474 remglob(char *argv[], int doswitch, char **errbuf)
475 {
476 	static FILE *ftemp = NULL;
477 
478 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
479 }
480 
481 int
482 confirm(const char *cmd, const char *file)
483 {
484 	char str[BUFSIZ];
485 
486 	if (file && (confirmrest || !interactive))
487 		return (1);
488 top:
489 	if (file)
490 		fprintf(ttyout, "%s %s? ", cmd, file);
491 	else
492 		fprintf(ttyout, "Continue with %s? ", cmd);
493 	(void)fflush(ttyout);
494 	if (fgets(str, sizeof(str), stdin) == NULL)
495 		goto quit;
496 	switch (tolower(*str)) {
497 		case '?':
498 			fprintf(ttyout,
499 			    "?	help\n"
500 			    "a	answer yes to all\n"
501 			    "n	answer no\n"
502 			    "p	turn off prompt mode\n"
503 			    "q	answer no to all\n"
504 			    "y	answer yes\n");
505 			goto top;
506 		case 'a':
507 			confirmrest = 1;
508 			fprintf(ttyout, "Prompting off for duration of %s.\n",
509 			    cmd);
510 			break;
511 		case 'n':
512 			return (0);
513 		case 'p':
514 			interactive = 0;
515 			fputs("Interactive mode: off.\n", ttyout);
516 			break;
517 		case 'q':
518 quit:
519 			mflag = 0;
520 			clearerr(stdin);
521 			return (0);
522 		case 'y':
523 			return(1);
524 			break;
525 		default:
526 			fprintf(ttyout, "?, a, n, p, q, y "
527 			    "are the only acceptable commands!\n");
528 			goto top;
529 			break;
530 	}
531 	return (1);
532 }
533 
534 /*
535  * Glob a local file name specification with
536  * the expectation of a single return value.
537  * Can't control multiple values being expanded
538  * from the expression, we return only the first.
539  */
540 int
541 globulize(char **cpp)
542 {
543 	glob_t gl;
544 	int flags;
545 
546 	if (!doglob)
547 		return (1);
548 
549 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
550 	memset(&gl, 0, sizeof(gl));
551 	if (glob(*cpp, flags, NULL, &gl) ||
552 	    gl.gl_pathc == 0) {
553 		warnx("%s: not found", *cpp);
554 		globfree(&gl);
555 		return (0);
556 	}
557 		/* XXX: caller should check if *cpp changed, and
558 		 *	free(*cpp) if that is the case
559 		 */
560 	*cpp = strdup(gl.gl_pathv[0]);
561 	if (*cpp == NULL)
562 		err(1, NULL);
563 	globfree(&gl);
564 	return (1);
565 }
566 
567 /*
568  * determine size of remote file
569  */
570 off_t
571 remotesize(const char *file, int noisy)
572 {
573 	int overbose;
574 	off_t size;
575 
576 	overbose = verbose;
577 	size = -1;
578 #ifndef SMALL
579 	if (!debug)
580 #endif /* !SMALL */
581 		verbose = -1;
582 	if (command("SIZE %s", file) == COMPLETE) {
583 		char *cp, *ep;
584 
585 		cp = strchr(reply_string, ' ');
586 		if (cp != NULL) {
587 			cp++;
588 			size = strtoq(cp, &ep, 10);
589 			if (*ep != '\0' && !isspace(*ep))
590 				size = -1;
591 		}
592 	} else if (noisy
593 #ifndef SMALL
594 	    && !debug
595 #endif /* !SMALL */
596 	    ) {
597 		fputs(reply_string, ttyout);
598 		fputc('\n', ttyout);
599 	}
600 	verbose = overbose;
601 	return (size);
602 }
603 
604 /*
605  * determine last modification time (in GMT) of remote file
606  */
607 time_t
608 remotemodtime(const char *file, int noisy)
609 {
610 	int overbose;
611 	time_t rtime;
612 	int ocode;
613 
614 	overbose = verbose;
615 	ocode = code;
616 	rtime = -1;
617 #ifndef SMALL
618 	if (!debug)
619 #endif /* !SMALL */
620 		verbose = -1;
621 	if (command("MDTM %s", file) == COMPLETE) {
622 		struct tm timebuf;
623 		int yy, mo, day, hour, min, sec;
624  		/*
625  		 * time-val = 14DIGIT [ "." 1*DIGIT ]
626  		 *		YYYYMMDDHHMMSS[.sss]
627  		 * mdtm-response = "213" SP time-val CRLF / error-response
628  		 */
629 		/* TODO: parse .sss as well, use timespecs. */
630 		char *timestr = reply_string;
631 
632 		/* Repair `19%02d' bug on server side */
633 		while (!isspace(*timestr))
634 			timestr++;
635 		while (isspace(*timestr))
636 			timestr++;
637 		if (strncmp(timestr, "191", 3) == 0) {
638  			fprintf(ttyout,
639  	    "Y2K warning! Fixed incorrect time-val received from server.\n");
640 	    		timestr[0] = ' ';
641 			timestr[1] = '2';
642 			timestr[2] = '0';
643 		}
644 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
645 			&day, &hour, &min, &sec);
646 		memset(&timebuf, 0, sizeof(timebuf));
647 		timebuf.tm_sec = sec;
648 		timebuf.tm_min = min;
649 		timebuf.tm_hour = hour;
650 		timebuf.tm_mday = day;
651 		timebuf.tm_mon = mo - 1;
652 		timebuf.tm_year = yy - TM_YEAR_BASE;
653 		timebuf.tm_isdst = -1;
654 		rtime = mktime(&timebuf);
655 		if (rtime == -1 && (noisy
656 #ifndef SMALL
657 		    || debug
658 #endif /* !SMALL */
659 		    ))
660 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
661 		else
662 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
663 	} else if (noisy
664 #ifndef SMALL
665 	    && !debug
666 #endif /* !SMALL */
667 	    ) {
668 		fputs(reply_string, ttyout);
669 		fputc('\n', ttyout);
670 	}
671 	verbose = overbose;
672 	if (rtime == -1)
673 		code = ocode;
674 	return (rtime);
675 }
676 
677 /*
678  * Ensure file is in or under dir.
679  * Returns 1 if so, 0 if not (or an error occurred).
680  */
681 int
682 fileindir(const char *file, const char *dir)
683 {
684 	char	parentdirbuf[MAXPATHLEN], *parentdir;
685 	char	realdir[MAXPATHLEN];
686 	size_t	dirlen;
687 
688 		 			/* determine parent directory of file */
689 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
690 	parentdir = dirname(parentdirbuf);
691 	if (strcmp(parentdir, ".") == 0)
692 		return 1;		/* current directory is ok */
693 
694 					/* find the directory */
695 	if (realpath(parentdir, realdir) == NULL) {
696 		warn("Unable to determine real path of `%s'", parentdir);
697 		return 0;
698 	}
699 	if (realdir[0] != '/')		/* relative result is ok */
700 		return 1;
701 
702 	dirlen = strlen(dir);
703 	if (strncmp(realdir, dir, dirlen) == 0 &&
704 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
705 		return 1;
706 	return 0;
707 }
708 
709 
710 /*
711  * Returns true if this is the controlling/foreground process, else false.
712  */
713 int
714 foregroundproc(void)
715 {
716 	static pid_t pgrp = -1;
717 	int ctty_pgrp;
718 
719 	if (pgrp == -1)
720 		pgrp = getpgrp();
721 
722 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
723 	    ctty_pgrp == pgrp));
724 }
725 
726 /* ARGSUSED */
727 static void
728 updateprogressmeter(int signo)
729 {
730 	int save_errno = errno;
731 
732 	/* update progressmeter if foreground process or in -m mode */
733 	if (foregroundproc() || progress == -1)
734 		progressmeter(0, NULL);
735 	errno = save_errno;
736 }
737 
738 /*
739  * Display a transfer progress bar if progress is non-zero.
740  * SIGALRM is hijacked for use by this function.
741  * - Before the transfer, set filesize to size of file (or -1 if unknown),
742  *   and call with flag = -1. This starts the once per second timer,
743  *   and a call to updateprogressmeter() upon SIGALRM.
744  * - During the transfer, updateprogressmeter will call progressmeter
745  *   with flag = 0
746  * - After the transfer, call with flag = 1
747  */
748 static struct timeval start;
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 timeval lastupdate;
760 	static off_t lastsize;
761 	static char *title = NULL;
762 	struct timeval 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 		(void)gettimeofday(&start, (struct timezone *)0);
770 		lastupdate = start;
771 		lastsize = restart_point;
772 	}
773 	(void)gettimeofday(&now, (struct timezone *)0);
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 = MAX(ratio, 0);
783 	ratio = MIN(ratio, 100);
784 	if (!verbose && flag == -1) {
785 		filename = basename(filename);
786 		if (filename != NULL)
787 			title = strdup(filename);
788 	}
789 	if (!verbose && title != NULL) {
790 		int l = strlen(title);
791 		char *dotdot = "";
792 
793 		if (l < 12)
794 			l = 12;
795 		else if (l > 25) {
796 			l = 22;
797 			dotdot = "...";
798 			overhead += 3;
799 		}
800 		snprintf(buf, sizeof(buf), "\r%-*.*s%s %3d%% ", l, l, title,
801 		    dotdot, ratio);
802 		overhead += l + 1;
803 	} else
804 		snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
805 
806 	barlength = ttywidth - overhead;
807 	if (barlength > 0) {
808 		i = barlength * ratio / 100;
809 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
810 		    "|%.*s%*s|", i,
811 		    "*******************************************************"
812 		    "*******************************************************"
813 		    "*******************************************************"
814 		    "*******************************************************"
815 		    "*******************************************************"
816 		    "*******************************************************"
817 		    "*******************************************************",
818 		    barlength - i, "");
819 	}
820 
821 	i = 0;
822 	abbrevsize = cursize;
823 	while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
824 		i++;
825 		abbrevsize >>= 10;
826 	}
827 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
828 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
829 	    prefixes[i] == ' ' ? ' ' : 'B');
830 
831 	timersub(&now, &lastupdate, &wait);
832 	if (cursize > lastsize) {
833 		lastupdate = now;
834 		lastsize = cursize;
835 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
836 			start.tv_sec += wait.tv_sec;
837 			start.tv_usec += wait.tv_usec;
838 		}
839 		wait.tv_sec = 0;
840 	}
841 
842 	timersub(&now, &start, &td);
843 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
844 
845 	if (flag == 1) {
846 		i = (int)elapsed / 3600;
847 		if (i)
848 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
849 			    "%2d:", i);
850 		else
851 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
852 			    "   ");
853 		i = (int)elapsed % 3600;
854 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
855 		    "%02d:%02d    ", i / 60, i % 60);
856 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
857 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
858 		    "   --:-- ETA");
859 	} else if (wait.tv_sec >= STALLTIME) {
860 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
861 		    " - stalled -");
862 	} else {
863 		remaining = (int)((filesize - restart_point) /
864 				  (bytes / elapsed) - elapsed);
865 		i = remaining / 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 = remaining % 3600;
873 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
874 		    "%02d:%02d ETA", i / 60, i % 60);
875 	}
876 	(void)write(fileno(ttyout), buf, strlen(buf));
877 
878 	if (flag == -1) {
879 		(void)signal(SIGALRM, updateprogressmeter);
880 		alarmtimer(1);		/* set alarm timer for 1 Hz */
881 	} else if (flag == 1) {
882 		alarmtimer(0);
883 		(void)putc('\n', ttyout);
884 		if (title != NULL) {
885 			free(title);
886 			title = NULL;
887 		}
888 	}
889 	fflush(ttyout);
890 }
891 
892 /*
893  * Display transfer statistics.
894  * Requires start to be initialised by progressmeter(-1),
895  * direction to be defined by xfer routines, and filesize and bytes
896  * to be updated by xfer routines
897  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
898  * instead of TTYOUT.
899  */
900 void
901 ptransfer(int siginfo)
902 {
903 	struct timeval now, td;
904 	double elapsed;
905 	off_t bs;
906 	int meg, remaining, hh;
907 	char buf[100];
908 
909 	if (!verbose && !siginfo)
910 		return;
911 
912 	(void)gettimeofday(&now, (struct timezone *)0);
913 	timersub(&now, &start, &td);
914 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
915 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
916 	meg = 0;
917 	if (bs > (1024 * 1024))
918 		meg = 1;
919 	(void)snprintf(buf, sizeof(buf),
920 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
921 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
922 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
923 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
924 	    && bytes + restart_point <= filesize) {
925 		remaining = (int)((filesize - restart_point) /
926 				  (bytes / elapsed) - elapsed);
927 		hh = remaining / 3600;
928 		remaining %= 3600;
929 			/* "buf+len(buf) -1" to overwrite \n */
930 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
931 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
932 		    remaining % 60);
933 	}
934 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
935 }
936 
937 /*
938  * List words in stringlist, vertically arranged
939  */
940 void
941 list_vertical(StringList *sl)
942 {
943 	int i, j, w;
944 	int columns, width, lines;
945 	char *p;
946 
947 	width = 0;
948 
949 	for (i = 0 ; i < sl->sl_cur ; i++) {
950 		w = strlen(sl->sl_str[i]);
951 		if (w > width)
952 			width = w;
953 	}
954 	width = (width + 8) &~ 7;
955 
956 	columns = ttywidth / width;
957 	if (columns == 0)
958 		columns = 1;
959 	lines = (sl->sl_cur + columns - 1) / columns;
960 	for (i = 0; i < lines; i++) {
961 		for (j = 0; j < columns; j++) {
962 			p = sl->sl_str[j * lines + i];
963 			if (p)
964 				fputs(p, ttyout);
965 			if (j * lines + i + lines >= sl->sl_cur) {
966 				putc('\n', ttyout);
967 				break;
968 			}
969 			w = strlen(p);
970 			while (w < width) {
971 				w = (w + 8) &~ 7;
972 				(void)putc('\t', ttyout);
973 			}
974 		}
975 	}
976 }
977 
978 /*
979  * Update the global ttywidth value, using TIOCGWINSZ.
980  */
981 /* ARGSUSED */
982 void
983 setttywidth(int signo)
984 {
985 	int save_errno = errno;
986 	struct winsize winsize;
987 
988 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
989 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
990 	else
991 		ttywidth = 80;
992 	errno = save_errno;
993 }
994 
995 /*
996  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
997  */
998 void
999 alarmtimer(int wait)
1000 {
1001 	struct itimerval itv;
1002 
1003 	itv.it_value.tv_sec = wait;
1004 	itv.it_value.tv_usec = 0;
1005 	itv.it_interval = itv.it_value;
1006 	setitimer(ITIMER_REAL, &itv, NULL);
1007 }
1008 
1009 /*
1010  * Setup or cleanup EditLine structures
1011  */
1012 #ifndef SMALL
1013 void
1014 controlediting(void)
1015 {
1016 	HistEvent hev;
1017 
1018 	if (editing && el == NULL && hist == NULL) {
1019 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1020 		hist = history_init();		/* init the builtin history */
1021 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1022 		el_set(el, EL_HIST, history, hist);	/* use history */
1023 
1024 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1025 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1026 
1027 		/* add local file completion, bind to TAB */
1028 		el_set(el, EL_ADDFN, "ftp-complete",
1029 		    "Context sensitive argument completion",
1030 		    complete);
1031 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1032 
1033 		el_source(el, NULL);	/* read ~/.editrc */
1034 		el_set(el, EL_SIGNAL, 1);
1035 	} else if (!editing) {
1036 		if (hist) {
1037 			history_end(hist);
1038 			hist = NULL;
1039 		}
1040 		if (el) {
1041 			el_end(el);
1042 			el = NULL;
1043 		}
1044 	}
1045 }
1046 #endif /* !SMALL */
1047