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