xref: /openbsd-src/usr.bin/ftp/util.c (revision 850e275390052b330d93020bf619a739a3c277ac)
1 /*	$OpenBSD: util.c,v 1.55 2008/08/22 08:52:35 sobrado 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 #if !defined(lint) && !defined(SMALL)
67 static const char rcsid[] = "$OpenBSD: util.c,v 1.55 2008/08/22 08:52:35 sobrado Exp $";
68 #endif /* not lint and not SMALL */
69 
70 /*
71  * FTP User Program -- Misc support routines
72  */
73 #include <sys/ioctl.h>
74 #include <sys/time.h>
75 #include <arpa/ftp.h>
76 
77 #include <ctype.h>
78 #include <err.h>
79 #include <errno.h>
80 #include <fcntl.h>
81 #include <libgen.h>
82 #include <limits.h>
83 #include <glob.h>
84 #include <pwd.h>
85 #include <signal.h>
86 #include <stdio.h>
87 #include <stdlib.h>
88 #include <string.h>
89 #include <time.h>
90 #include <tzfile.h>
91 #include <unistd.h>
92 
93 #include "ftp_var.h"
94 #include "pathnames.h"
95 
96 static void updateprogressmeter(int);
97 
98 /*
99  * Connect to peer server and
100  * auto-login, if possible.
101  */
102 void
103 setpeer(int argc, char *argv[])
104 {
105 	char *host, *port;
106 
107 	if (connected) {
108 		fprintf(ttyout, "Already connected to %s, use close first.\n",
109 		    hostname);
110 		code = -1;
111 		return;
112 	}
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 	if (gatemode)
121 		port = gateport;
122 	else
123 		port = ftpport;
124 	if (argc > 2)
125 		port = argv[2];
126 
127 	if (gatemode) {
128 		if (gateserver == NULL || *gateserver == '\0')
129 			errx(1, "gateserver not defined (shouldn't happen)");
130 		host = hookup(gateserver, port);
131 	} else
132 		host = hookup(argv[1], port);
133 
134 	if (host) {
135 		int overbose;
136 
137 		if (gatemode) {
138 			if (command("PASSERVE %s", argv[1]) != COMPLETE)
139 				return;
140 			if (verbose)
141 				fprintf(ttyout,
142 				    "Connected via pass-through server %s\n",
143 				    gateserver);
144 		}
145 
146 		connected = 1;
147 		/*
148 		 * Set up defaults for FTP.
149 		 */
150 		(void)strlcpy(formname, "non-print", sizeof formname);
151 		form = FORM_N;
152 		(void)strlcpy(modename, "stream", sizeof modename);
153 		mode = MODE_S;
154 		(void)strlcpy(structname, "file", sizeof structname);
155 		stru = STRU_F;
156 		(void)strlcpy(bytename, "8", sizeof bytename);
157 		bytesize = 8;
158 
159 		/*
160 		 * Set type to 0 (not specified by user),
161 		 * meaning binary by default, but don't bother
162 		 * telling server.  We can use binary
163 		 * for text files unless changed by the user.
164 		 */
165 		(void)strlcpy(typename, "binary", sizeof typename);
166 		curtype = TYPE_A;
167 		type = 0;
168 		if (autologin)
169 			(void)ftp_login(argv[1], NULL, NULL);
170 
171 #if (defined(unix) || defined(BSD)) && NBBY == 8
172 /*
173  * this ifdef is to keep someone form "porting" this to an incompatible
174  * system and not checking this out. This way they have to think about it.
175  */
176 		overbose = verbose;
177 #ifndef SMALL
178 		if (!debug)
179 #endif /* !SMALL */
180 			verbose = -1;
181 		if (command("SYST") == COMPLETE && overbose) {
182 			char *cp, c;
183 			c = 0;
184 			cp = strchr(reply_string + 4, ' ');
185 			if (cp == NULL)
186 				cp = strchr(reply_string + 4, '\r');
187 			if (cp) {
188 				if (cp[-1] == '.')
189 					cp--;
190 				c = *cp;
191 				*cp = '\0';
192 			}
193 
194 			fprintf(ttyout, "Remote system type is %s.\n", reply_string + 4);
195 			if (cp)
196 				*cp = c;
197 		}
198 		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
199 			if (proxy)
200 				unix_proxy = 1;
201 			else
202 				unix_server = 1;
203 			if (overbose)
204 				fprintf(ttyout, "Using %s mode to transfer files.\n",
205 				    typename);
206 		} else {
207 			if (proxy)
208 				unix_proxy = 0;
209 			else
210 				unix_server = 0;
211 			if (overbose &&
212 			    !strncmp(reply_string, "215 TOPS20", 10))
213 				fputs(
214 "Remember to set tenex mode when transferring binary files from this machine.\n",
215 				    ttyout);
216 		}
217 		verbose = overbose;
218 #endif /* unix || BSD */
219 	}
220 }
221 
222 /*
223  * login to remote host, using given username & password if supplied
224  */
225 int
226 ftp_login(const char *host, char *user, char *pass)
227 {
228 	char tmp[80], *acctname = NULL, host_name[MAXHOSTNAMELEN];
229 	char anonpass[MAXLOGNAME + 1 + MAXHOSTNAMELEN];	/* "user@hostname" */
230 	int n, aflag = 0, retry = 0;
231 	struct passwd *pw;
232 
233 #ifndef SMALL
234 	if (user == NULL) {
235 		if (ruserpass(host, &user, &pass, &acctname) < 0) {
236 			code = -1;
237 			return (0);
238 		}
239 	}
240 #endif /* !SMALL */
241 
242 	/*
243 	 * Set up arguments for an anonymous FTP session, if necessary.
244 	 */
245 	if ((user == NULL || pass == NULL) && anonftp) {
246 		memset(anonpass, 0, sizeof(anonpass));
247 		memset(host_name, 0, sizeof(host_name));
248 
249 		/*
250 		 * Set up anonymous login password.
251 		 */
252 		if ((user = getlogin()) == NULL) {
253 			if ((pw = getpwuid(getuid())) == NULL)
254 				user = "anonymous";
255 			else
256 				user = pw->pw_name;
257 		}
258 		gethostname(host_name, sizeof(host_name));
259 #ifndef DONT_CHEAT_ANONPASS
260 		/*
261 		 * Every anonymous FTP server I've encountered
262 		 * will accept the string "username@", and will
263 		 * append the hostname itself.  We do this by default
264 		 * since many servers are picky about not having
265 		 * a FQDN in the anonymous password. - thorpej@netbsd.org
266 		 */
267 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@",
268 		    user);
269 #else
270 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s",
271 		    user, hp->h_name);
272 #endif
273 		pass = anonpass;
274 		user = "anonymous";	/* as per RFC 1635 */
275 	}
276 
277 tryagain:
278 	if (retry)
279 		user = "ftp";		/* some servers only allow "ftp" */
280 
281 	while (user == NULL) {
282 		char *myname = getlogin();
283 
284 		if (myname == NULL && (pw = getpwuid(getuid())) != NULL)
285 			myname = pw->pw_name;
286 		if (myname)
287 			fprintf(ttyout, "Name (%s:%s): ", host, myname);
288 		else
289 			fprintf(ttyout, "Name (%s): ", host);
290 		user = myname;
291 		if (fgets(tmp, sizeof(tmp), stdin) != NULL) {
292 			tmp[strcspn(tmp, "\n")] = '\0';
293 			if (tmp[0] != '\0')
294 				user = tmp;
295 		}
296 		else
297 			exit(0);
298 	}
299 	n = command("USER %s", user);
300 	if (n == CONTINUE) {
301 		if (pass == NULL)
302 			pass = getpass("Password:");
303 		n = command("PASS %s", pass);
304 	}
305 	if (n == CONTINUE) {
306 		aflag++;
307 		if (acctname == NULL)
308 			acctname = getpass("Account:");
309 		n = command("ACCT %s", acctname);
310 	}
311 	if ((n != COMPLETE) ||
312 	    (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
313 		warnx("Login failed.");
314 		if (retry || !anonftp)
315 			return (0);
316 		else
317 			retry = 1;
318 		goto tryagain;
319 	}
320 	if (proxy)
321 		return (1);
322 	connected = -1;
323 	for (n = 0; n < macnum; ++n) {
324 		if (!strcmp("init", macros[n].mac_name)) {
325 			(void)strlcpy(line, "$init", sizeof line);
326 			makeargv();
327 			domacro(margc, margv);
328 			break;
329 		}
330 	}
331 	return (1);
332 }
333 
334 /*
335  * `another' gets another argument, and stores the new argc and argv.
336  * It reverts to the top level (via main.c's intr()) on EOF/error.
337  *
338  * Returns false if no new arguments have been added.
339  */
340 int
341 another(int *pargc, char ***pargv, const char *prompt)
342 {
343 	int len = strlen(line), ret;
344 
345 	if (len >= sizeof(line) - 3) {
346 		fputs("sorry, arguments too long.\n", ttyout);
347 		intr();
348 	}
349 	fprintf(ttyout, "(%s) ", prompt);
350 	line[len++] = ' ';
351 	if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) {
352 		clearerr(stdin);
353 		intr();
354 	}
355 	len += strlen(&line[len]);
356 	if (len > 0 && line[len - 1] == '\n')
357 		line[len - 1] = '\0';
358 	makeargv();
359 	ret = margc > *pargc;
360 	*pargc = margc;
361 	*pargv = margv;
362 	return (ret);
363 }
364 
365 /*
366  * glob files given in argv[] from the remote server.
367  * if errbuf isn't NULL, store error messages there instead
368  * of writing to the screen.
369  * if type isn't NULL, use LIST instead of NLST, and store filetype.
370  * 'd' means directory, 's' means symbolic link, '-' means plain
371  * file.
372  */
373 char *
374 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type)
375 {
376 	char temp[MAXPATHLEN], *bufp, *cp, *lmode;
377 	static char buf[MAXPATHLEN], **args;
378 	int oldverbose, oldhash, fd;
379 
380 	if (!mflag) {
381 		if (!doglob)
382 			args = NULL;
383 		else {
384 			if (*ftemp) {
385 				(void)fclose(*ftemp);
386 				*ftemp = NULL;
387 			}
388 		}
389 		return (NULL);
390 	}
391 	if (!doglob) {
392 		if (args == NULL)
393 			args = argv;
394 		if ((cp = *++args) == NULL)
395 			args = NULL;
396 		return (cp);
397 	}
398 	if (*ftemp == NULL) {
399 		int len;
400 
401 		if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0')
402 		    cp = _PATH_TMP;
403 		len = strlen(cp);
404 		if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
405 			warnx("unable to create temporary file: %s",
406 			    strerror(ENAMETOOLONG));
407 			return (NULL);
408 		}
409 
410 		(void)strlcpy(temp, cp, sizeof temp);
411 		if (temp[len-1] != '/')
412 			temp[len++] = '/';
413 		(void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
414 		if ((fd = mkstemp(temp)) < 0) {
415 			warn("unable to create temporary file %s", temp);
416 			return (NULL);
417 		}
418 		close(fd);
419 		oldverbose = verbose;
420 		verbose = (errbuf != NULL) ? -1 : 0;
421 		oldhash = hash;
422 		hash = 0;
423 		if (doswitch)
424 			pswitch(!proxy);
425 		for (lmode = "w"; *++argv != NULL; lmode = "a")
426 			recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode,
427 			    0, 0);
428 		if ((code / 100) != COMPLETE) {
429 			if (errbuf != NULL)
430 				*errbuf = reply_string;
431 		}
432 		if (doswitch)
433 			pswitch(!proxy);
434 		verbose = oldverbose;
435 		hash = oldhash;
436 		*ftemp = fopen(temp, "r");
437 		(void)unlink(temp);
438 		if (*ftemp == NULL) {
439 			if (errbuf == NULL)
440 				fputs("can't find list of remote files, oops.\n",
441 				    ttyout);
442 			else
443 				*errbuf =
444 				    "can't find list of remote files, oops.";
445 			return (NULL);
446 		}
447 	}
448 again:
449 	if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
450 		(void)fclose(*ftemp);
451 		*ftemp = NULL;
452 		return (NULL);
453 	}
454 
455 	buf[strcspn(buf, "\n")] = '\0';
456 	bufp = buf;
457 
458 #ifndef SMALL
459 	if (type) {
460 		parse_list(&bufp, type);
461 		if (!bufp)
462 			goto again;
463 	}
464 #endif /* !SMALL */
465 
466 	return (bufp);
467 }
468 
469 /*
470  * wrapper for remglob2
471  */
472 char *
473 remglob(char *argv[], int doswitch, char **errbuf)
474 {
475 	static FILE *ftemp = NULL;
476 
477 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
478 }
479 
480 int
481 confirm(const char *cmd, const char *file)
482 {
483 	char str[BUFSIZ];
484 
485 	if (file && (confirmrest || !interactive))
486 		return (1);
487 top:
488 	if (file)
489 		fprintf(ttyout, "%s %s? ", cmd, file);
490 	else
491 		fprintf(ttyout, "Continue with %s? ", cmd);
492 	(void)fflush(ttyout);
493 	if (fgets(str, sizeof(str), stdin) == NULL)
494 		goto quit;
495 	switch (tolower(*str)) {
496 		case '?':
497 			fprintf(ttyout,
498 			    "?	help\n"
499 			    "a	answer yes to all\n"
500 			    "n	answer no\n"
501 			    "p	turn off prompt mode\n"
502 			    "q	answer no to all\n"
503 			    "y	answer yes\n");
504 			goto top;
505 		case 'a':
506 			confirmrest = 1;
507 			fprintf(ttyout, "Prompting off for duration of %s.\n",
508 			    cmd);
509 			break;
510 		case 'n':
511 			return (0);
512 		case 'p':
513 			interactive = 0;
514 			fputs("Interactive mode: off.\n", ttyout);
515 			break;
516 		case 'q':
517 quit:
518 			mflag = 0;
519 			clearerr(stdin);
520 			return (0);
521 		case 'y':
522 			return(1);
523 			break;
524 		default:
525 			fprintf(ttyout, "?, a, n, p, q, y "
526 			    "are the only acceptable commands!\n");
527 			goto top;
528 			break;
529 	}
530 	return (1);
531 }
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 = strtoq(cp, &ep, 10);
588 			if (*ep != '\0' && !isspace(*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(*timestr))
633 			timestr++;
634 		while (isspace(*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 - TM_YEAR_BASE;
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[MAXPATHLEN], *parentdir;
684 	char	realdir[MAXPATHLEN];
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);
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 timeval start;
748 
749 void
750 progressmeter(int flag)
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 	struct timeval now, td, wait;
761 	off_t cursize, abbrevsize;
762 	double elapsed;
763 	int ratio, barlength, i, remaining;
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 = MAX(ratio, 0);
781 	ratio = MIN(ratio, 100);
782 	snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
783 
784 	barlength = ttywidth - 30;
785 	if (barlength > 0) {
786 		i = barlength * ratio / 100;
787 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
788 		    "|%.*s%*s|", i,
789 		    "*******************************************************"
790 		    "*******************************************************"
791 		    "*******************************************************"
792 		    "*******************************************************"
793 		    "*******************************************************"
794 		    "*******************************************************"
795 		    "*******************************************************",
796 		    barlength - i, "");
797 	}
798 
799 	i = 0;
800 	abbrevsize = cursize;
801 	while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
802 		i++;
803 		abbrevsize >>= 10;
804 	}
805 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
806 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
807 	    prefixes[i] == ' ' ? ' ' : 'B');
808 
809 	timersub(&now, &lastupdate, &wait);
810 	if (cursize > lastsize) {
811 		lastupdate = now;
812 		lastsize = cursize;
813 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
814 			start.tv_sec += wait.tv_sec;
815 			start.tv_usec += wait.tv_usec;
816 		}
817 		wait.tv_sec = 0;
818 	}
819 
820 	timersub(&now, &start, &td);
821 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
822 
823 	if (flag == 1) {
824 		i = (int)elapsed / 3600;
825 		if (i)
826 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
827 			    "%2d:", i);
828 		else
829 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
830 			    "   ");
831 		i = (int)elapsed % 3600;
832 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
833 		    "%02d:%02d    ", i / 60, i % 60);
834 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
835 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
836 		    "   --:-- ETA");
837 	} else if (wait.tv_sec >= STALLTIME) {
838 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
839 		    " - stalled -");
840 	} else {
841 		remaining = (int)((filesize - restart_point) /
842 				  (bytes / elapsed) - elapsed);
843 		i = remaining / 3600;
844 		if (i)
845 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
846 			    "%2d:", i);
847 		else
848 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
849 			    "   ");
850 		i = remaining % 3600;
851 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
852 		    "%02d:%02d ETA", i / 60, i % 60);
853 	}
854 	(void)write(fileno(ttyout), buf, strlen(buf));
855 
856 	if (flag == -1) {
857 		(void)signal(SIGALRM, updateprogressmeter);
858 		alarmtimer(1);		/* set alarm timer for 1 Hz */
859 	} else if (flag == 1) {
860 		alarmtimer(0);
861 		(void)putc('\n', ttyout);
862 	}
863 	fflush(ttyout);
864 }
865 
866 /*
867  * Display transfer statistics.
868  * Requires start to be initialised by progressmeter(-1),
869  * direction to be defined by xfer routines, and filesize and bytes
870  * to be updated by xfer routines
871  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
872  * instead of TTYOUT.
873  */
874 void
875 ptransfer(int siginfo)
876 {
877 	struct timeval now, td;
878 	double elapsed;
879 	off_t bs;
880 	int meg, remaining, hh;
881 	char buf[100];
882 
883 	if (!verbose && !siginfo)
884 		return;
885 
886 	(void)gettimeofday(&now, (struct timezone *)0);
887 	timersub(&now, &start, &td);
888 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
889 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
890 	meg = 0;
891 	if (bs > (1024 * 1024))
892 		meg = 1;
893 	(void)snprintf(buf, sizeof(buf),
894 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
895 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
896 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
897 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
898 	    && bytes + restart_point <= filesize) {
899 		remaining = (int)((filesize - restart_point) /
900 				  (bytes / elapsed) - elapsed);
901 		hh = remaining / 3600;
902 		remaining %= 3600;
903 			/* "buf+len(buf) -1" to overwrite \n */
904 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
905 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
906 		    remaining % 60);
907 	}
908 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
909 }
910 
911 /*
912  * List words in stringlist, vertically arranged
913  */
914 void
915 list_vertical(StringList *sl)
916 {
917 	int i, j, w;
918 	int columns, width, lines;
919 	char *p;
920 
921 	width = 0;
922 
923 	for (i = 0 ; i < sl->sl_cur ; i++) {
924 		w = strlen(sl->sl_str[i]);
925 		if (w > width)
926 			width = w;
927 	}
928 	width = (width + 8) &~ 7;
929 
930 	columns = ttywidth / width;
931 	if (columns == 0)
932 		columns = 1;
933 	lines = (sl->sl_cur + columns - 1) / columns;
934 	for (i = 0; i < lines; i++) {
935 		for (j = 0; j < columns; j++) {
936 			p = sl->sl_str[j * lines + i];
937 			if (p)
938 				fputs(p, ttyout);
939 			if (j * lines + i + lines >= sl->sl_cur) {
940 				putc('\n', ttyout);
941 				break;
942 			}
943 			w = strlen(p);
944 			while (w < width) {
945 				w = (w + 8) &~ 7;
946 				(void)putc('\t', ttyout);
947 			}
948 		}
949 	}
950 }
951 
952 /*
953  * Update the global ttywidth value, using TIOCGWINSZ.
954  */
955 /* ARGSUSED */
956 void
957 setttywidth(int signo)
958 {
959 	int save_errno = errno;
960 	struct winsize winsize;
961 
962 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
963 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
964 	else
965 		ttywidth = 80;
966 	errno = save_errno;
967 }
968 
969 /*
970  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
971  */
972 void
973 alarmtimer(int wait)
974 {
975 	struct itimerval itv;
976 
977 	itv.it_value.tv_sec = wait;
978 	itv.it_value.tv_usec = 0;
979 	itv.it_interval = itv.it_value;
980 	setitimer(ITIMER_REAL, &itv, NULL);
981 }
982 
983 /*
984  * Setup or cleanup EditLine structures
985  */
986 #ifndef SMALL
987 void
988 controlediting(void)
989 {
990 	HistEvent hev;
991 
992 	if (editing && el == NULL && hist == NULL) {
993 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
994 		hist = history_init();		/* init the builtin history */
995 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
996 		el_set(el, EL_HIST, history, hist);	/* use history */
997 
998 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
999 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1000 
1001 		/* add local file completion, bind to TAB */
1002 		el_set(el, EL_ADDFN, "ftp-complete",
1003 		    "Context sensitive argument completion",
1004 		    complete);
1005 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1006 
1007 		el_source(el, NULL);	/* read ~/.editrc */
1008 		el_set(el, EL_SIGNAL, 1);
1009 	} else if (!editing) {
1010 		if (hist) {
1011 			history_end(hist);
1012 			hist = NULL;
1013 		}
1014 		if (el) {
1015 			el_end(el);
1016 			el = NULL;
1017 		}
1018 	}
1019 }
1020 #endif /* !SMALL */
1021