xref: /openbsd-src/usr.bin/ftp/util.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: util.c,v 1.56 2009/01/27 22:04:36 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 #if !defined(lint) && !defined(SMALL)
67 static const char rcsid[] = "$OpenBSD: util.c,v 1.56 2009/01/27 22:04:36 martynas 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 		    (bufp[0] == '.' &&	/* LIST defaults to -a on some ftp */
463 		    (bufp[1] == '\0' ||	/* servers.  Ignore '.' and '..'. */
464 		    (bufp[1] == '.' && bufp[2] == '\0'))))
465 			goto again;
466 	}
467 #endif /* !SMALL */
468 
469 	return (bufp);
470 }
471 
472 /*
473  * wrapper for remglob2
474  */
475 char *
476 remglob(char *argv[], int doswitch, char **errbuf)
477 {
478 	static FILE *ftemp = NULL;
479 
480 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
481 }
482 
483 int
484 confirm(const char *cmd, const char *file)
485 {
486 	char str[BUFSIZ];
487 
488 	if (file && (confirmrest || !interactive))
489 		return (1);
490 top:
491 	if (file)
492 		fprintf(ttyout, "%s %s? ", cmd, file);
493 	else
494 		fprintf(ttyout, "Continue with %s? ", cmd);
495 	(void)fflush(ttyout);
496 	if (fgets(str, sizeof(str), stdin) == NULL)
497 		goto quit;
498 	switch (tolower(*str)) {
499 		case '?':
500 			fprintf(ttyout,
501 			    "?	help\n"
502 			    "a	answer yes to all\n"
503 			    "n	answer no\n"
504 			    "p	turn off prompt mode\n"
505 			    "q	answer no to all\n"
506 			    "y	answer yes\n");
507 			goto top;
508 		case 'a':
509 			confirmrest = 1;
510 			fprintf(ttyout, "Prompting off for duration of %s.\n",
511 			    cmd);
512 			break;
513 		case 'n':
514 			return (0);
515 		case 'p':
516 			interactive = 0;
517 			fputs("Interactive mode: off.\n", ttyout);
518 			break;
519 		case 'q':
520 quit:
521 			mflag = 0;
522 			clearerr(stdin);
523 			return (0);
524 		case 'y':
525 			return(1);
526 			break;
527 		default:
528 			fprintf(ttyout, "?, a, n, p, q, y "
529 			    "are the only acceptable commands!\n");
530 			goto top;
531 			break;
532 	}
533 	return (1);
534 }
535 
536 /*
537  * Glob a local file name specification with
538  * the expectation of a single return value.
539  * Can't control multiple values being expanded
540  * from the expression, we return only the first.
541  */
542 int
543 globulize(char **cpp)
544 {
545 	glob_t gl;
546 	int flags;
547 
548 	if (!doglob)
549 		return (1);
550 
551 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
552 	memset(&gl, 0, sizeof(gl));
553 	if (glob(*cpp, flags, NULL, &gl) ||
554 	    gl.gl_pathc == 0) {
555 		warnx("%s: not found", *cpp);
556 		globfree(&gl);
557 		return (0);
558 	}
559 		/* XXX: caller should check if *cpp changed, and
560 		 *	free(*cpp) if that is the case
561 		 */
562 	*cpp = strdup(gl.gl_pathv[0]);
563 	if (*cpp == NULL)
564 		err(1, NULL);
565 	globfree(&gl);
566 	return (1);
567 }
568 
569 /*
570  * determine size of remote file
571  */
572 off_t
573 remotesize(const char *file, int noisy)
574 {
575 	int overbose;
576 	off_t size;
577 
578 	overbose = verbose;
579 	size = -1;
580 #ifndef SMALL
581 	if (!debug)
582 #endif /* !SMALL */
583 		verbose = -1;
584 	if (command("SIZE %s", file) == COMPLETE) {
585 		char *cp, *ep;
586 
587 		cp = strchr(reply_string, ' ');
588 		if (cp != NULL) {
589 			cp++;
590 			size = strtoq(cp, &ep, 10);
591 			if (*ep != '\0' && !isspace(*ep))
592 				size = -1;
593 		}
594 	} else if (noisy
595 #ifndef SMALL
596 	    && !debug
597 #endif /* !SMALL */
598 	    ) {
599 		fputs(reply_string, ttyout);
600 		fputc('\n', ttyout);
601 	}
602 	verbose = overbose;
603 	return (size);
604 }
605 
606 /*
607  * determine last modification time (in GMT) of remote file
608  */
609 time_t
610 remotemodtime(const char *file, int noisy)
611 {
612 	int overbose;
613 	time_t rtime;
614 	int ocode;
615 
616 	overbose = verbose;
617 	ocode = code;
618 	rtime = -1;
619 #ifndef SMALL
620 	if (!debug)
621 #endif /* !SMALL */
622 		verbose = -1;
623 	if (command("MDTM %s", file) == COMPLETE) {
624 		struct tm timebuf;
625 		int yy, mo, day, hour, min, sec;
626  		/*
627  		 * time-val = 14DIGIT [ "." 1*DIGIT ]
628  		 *		YYYYMMDDHHMMSS[.sss]
629  		 * mdtm-response = "213" SP time-val CRLF / error-response
630  		 */
631 		/* TODO: parse .sss as well, use timespecs. */
632 		char *timestr = reply_string;
633 
634 		/* Repair `19%02d' bug on server side */
635 		while (!isspace(*timestr))
636 			timestr++;
637 		while (isspace(*timestr))
638 			timestr++;
639 		if (strncmp(timestr, "191", 3) == 0) {
640  			fprintf(ttyout,
641  	    "Y2K warning! Fixed incorrect time-val received from server.\n");
642 	    		timestr[0] = ' ';
643 			timestr[1] = '2';
644 			timestr[2] = '0';
645 		}
646 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
647 			&day, &hour, &min, &sec);
648 		memset(&timebuf, 0, sizeof(timebuf));
649 		timebuf.tm_sec = sec;
650 		timebuf.tm_min = min;
651 		timebuf.tm_hour = hour;
652 		timebuf.tm_mday = day;
653 		timebuf.tm_mon = mo - 1;
654 		timebuf.tm_year = yy - TM_YEAR_BASE;
655 		timebuf.tm_isdst = -1;
656 		rtime = mktime(&timebuf);
657 		if (rtime == -1 && (noisy
658 #ifndef SMALL
659 		    || debug
660 #endif /* !SMALL */
661 		    ))
662 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
663 		else
664 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
665 	} else if (noisy
666 #ifndef SMALL
667 	    && !debug
668 #endif /* !SMALL */
669 	    ) {
670 		fputs(reply_string, ttyout);
671 		fputc('\n', ttyout);
672 	}
673 	verbose = overbose;
674 	if (rtime == -1)
675 		code = ocode;
676 	return (rtime);
677 }
678 
679 /*
680  * Ensure file is in or under dir.
681  * Returns 1 if so, 0 if not (or an error occurred).
682  */
683 int
684 fileindir(const char *file, const char *dir)
685 {
686 	char	parentdirbuf[MAXPATHLEN], *parentdir;
687 	char	realdir[MAXPATHLEN];
688 	size_t	dirlen;
689 
690 		 			/* determine parent directory of file */
691 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
692 	parentdir = dirname(parentdirbuf);
693 	if (strcmp(parentdir, ".") == 0)
694 		return 1;		/* current directory is ok */
695 
696 					/* find the directory */
697 	if (realpath(parentdir, realdir) == NULL) {
698 		warn("Unable to determine real path of `%s'", parentdir);
699 		return 0;
700 	}
701 	if (realdir[0] != '/')		/* relative result is ok */
702 		return 1;
703 
704 	dirlen = strlen(dir);
705 	if (strncmp(realdir, dir, dirlen) == 0 &&
706 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
707 		return 1;
708 	return 0;
709 }
710 
711 
712 /*
713  * Returns true if this is the controlling/foreground process, else false.
714  */
715 int
716 foregroundproc(void)
717 {
718 	static pid_t pgrp = -1;
719 	int ctty_pgrp;
720 
721 	if (pgrp == -1)
722 		pgrp = getpgrp();
723 
724 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
725 	    ctty_pgrp == pgrp));
726 }
727 
728 /* ARGSUSED */
729 static void
730 updateprogressmeter(int signo)
731 {
732 	int save_errno = errno;
733 
734 	/* update progressmeter if foreground process or in -m mode */
735 	if (foregroundproc() || progress == -1)
736 		progressmeter(0);
737 	errno = save_errno;
738 }
739 
740 /*
741  * Display a transfer progress bar if progress is non-zero.
742  * SIGALRM is hijacked for use by this function.
743  * - Before the transfer, set filesize to size of file (or -1 if unknown),
744  *   and call with flag = -1. This starts the once per second timer,
745  *   and a call to updateprogressmeter() upon SIGALRM.
746  * - During the transfer, updateprogressmeter will call progressmeter
747  *   with flag = 0
748  * - After the transfer, call with flag = 1
749  */
750 static struct timeval start;
751 
752 void
753 progressmeter(int flag)
754 {
755 	/*
756 	 * List of order of magnitude prefixes.
757 	 * The last is `P', as 2^64 = 16384 Petabytes
758 	 */
759 	static const char prefixes[] = " KMGTP";
760 
761 	static struct timeval lastupdate;
762 	static off_t lastsize;
763 	struct timeval now, td, wait;
764 	off_t cursize, abbrevsize;
765 	double elapsed;
766 	int ratio, barlength, i, remaining;
767 	char buf[512];
768 
769 	if (flag == -1) {
770 		(void)gettimeofday(&start, (struct timezone *)0);
771 		lastupdate = start;
772 		lastsize = restart_point;
773 	}
774 	(void)gettimeofday(&now, (struct timezone *)0);
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 = MAX(ratio, 0);
784 	ratio = MIN(ratio, 100);
785 	snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
786 
787 	barlength = ttywidth - 30;
788 	if (barlength > 0) {
789 		i = barlength * ratio / 100;
790 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
791 		    "|%.*s%*s|", i,
792 		    "*******************************************************"
793 		    "*******************************************************"
794 		    "*******************************************************"
795 		    "*******************************************************"
796 		    "*******************************************************"
797 		    "*******************************************************"
798 		    "*******************************************************",
799 		    barlength - i, "");
800 	}
801 
802 	i = 0;
803 	abbrevsize = cursize;
804 	while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
805 		i++;
806 		abbrevsize >>= 10;
807 	}
808 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
809 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
810 	    prefixes[i] == ' ' ? ' ' : 'B');
811 
812 	timersub(&now, &lastupdate, &wait);
813 	if (cursize > lastsize) {
814 		lastupdate = now;
815 		lastsize = cursize;
816 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
817 			start.tv_sec += wait.tv_sec;
818 			start.tv_usec += wait.tv_usec;
819 		}
820 		wait.tv_sec = 0;
821 	}
822 
823 	timersub(&now, &start, &td);
824 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
825 
826 	if (flag == 1) {
827 		i = (int)elapsed / 3600;
828 		if (i)
829 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
830 			    "%2d:", i);
831 		else
832 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
833 			    "   ");
834 		i = (int)elapsed % 3600;
835 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
836 		    "%02d:%02d    ", i / 60, i % 60);
837 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
838 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
839 		    "   --:-- ETA");
840 	} else if (wait.tv_sec >= STALLTIME) {
841 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
842 		    " - stalled -");
843 	} else {
844 		remaining = (int)((filesize - restart_point) /
845 				  (bytes / elapsed) - elapsed);
846 		i = remaining / 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 = remaining % 3600;
854 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
855 		    "%02d:%02d ETA", i / 60, i % 60);
856 	}
857 	(void)write(fileno(ttyout), buf, strlen(buf));
858 
859 	if (flag == -1) {
860 		(void)signal(SIGALRM, updateprogressmeter);
861 		alarmtimer(1);		/* set alarm timer for 1 Hz */
862 	} else if (flag == 1) {
863 		alarmtimer(0);
864 		(void)putc('\n', ttyout);
865 	}
866 	fflush(ttyout);
867 }
868 
869 /*
870  * Display transfer statistics.
871  * Requires start to be initialised by progressmeter(-1),
872  * direction to be defined by xfer routines, and filesize and bytes
873  * to be updated by xfer routines
874  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
875  * instead of TTYOUT.
876  */
877 void
878 ptransfer(int siginfo)
879 {
880 	struct timeval now, td;
881 	double elapsed;
882 	off_t bs;
883 	int meg, remaining, hh;
884 	char buf[100];
885 
886 	if (!verbose && !siginfo)
887 		return;
888 
889 	(void)gettimeofday(&now, (struct timezone *)0);
890 	timersub(&now, &start, &td);
891 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
892 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
893 	meg = 0;
894 	if (bs > (1024 * 1024))
895 		meg = 1;
896 	(void)snprintf(buf, sizeof(buf),
897 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
898 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
899 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
900 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
901 	    && bytes + restart_point <= filesize) {
902 		remaining = (int)((filesize - restart_point) /
903 				  (bytes / elapsed) - elapsed);
904 		hh = remaining / 3600;
905 		remaining %= 3600;
906 			/* "buf+len(buf) -1" to overwrite \n */
907 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
908 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
909 		    remaining % 60);
910 	}
911 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
912 }
913 
914 /*
915  * List words in stringlist, vertically arranged
916  */
917 void
918 list_vertical(StringList *sl)
919 {
920 	int i, j, w;
921 	int columns, width, lines;
922 	char *p;
923 
924 	width = 0;
925 
926 	for (i = 0 ; i < sl->sl_cur ; i++) {
927 		w = strlen(sl->sl_str[i]);
928 		if (w > width)
929 			width = w;
930 	}
931 	width = (width + 8) &~ 7;
932 
933 	columns = ttywidth / width;
934 	if (columns == 0)
935 		columns = 1;
936 	lines = (sl->sl_cur + columns - 1) / columns;
937 	for (i = 0; i < lines; i++) {
938 		for (j = 0; j < columns; j++) {
939 			p = sl->sl_str[j * lines + i];
940 			if (p)
941 				fputs(p, ttyout);
942 			if (j * lines + i + lines >= sl->sl_cur) {
943 				putc('\n', ttyout);
944 				break;
945 			}
946 			w = strlen(p);
947 			while (w < width) {
948 				w = (w + 8) &~ 7;
949 				(void)putc('\t', ttyout);
950 			}
951 		}
952 	}
953 }
954 
955 /*
956  * Update the global ttywidth value, using TIOCGWINSZ.
957  */
958 /* ARGSUSED */
959 void
960 setttywidth(int signo)
961 {
962 	int save_errno = errno;
963 	struct winsize winsize;
964 
965 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
966 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
967 	else
968 		ttywidth = 80;
969 	errno = save_errno;
970 }
971 
972 /*
973  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
974  */
975 void
976 alarmtimer(int wait)
977 {
978 	struct itimerval itv;
979 
980 	itv.it_value.tv_sec = wait;
981 	itv.it_value.tv_usec = 0;
982 	itv.it_interval = itv.it_value;
983 	setitimer(ITIMER_REAL, &itv, NULL);
984 }
985 
986 /*
987  * Setup or cleanup EditLine structures
988  */
989 #ifndef SMALL
990 void
991 controlediting(void)
992 {
993 	HistEvent hev;
994 
995 	if (editing && el == NULL && hist == NULL) {
996 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
997 		hist = history_init();		/* init the builtin history */
998 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
999 		el_set(el, EL_HIST, history, hist);	/* use history */
1000 
1001 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1002 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1003 
1004 		/* add local file completion, bind to TAB */
1005 		el_set(el, EL_ADDFN, "ftp-complete",
1006 		    "Context sensitive argument completion",
1007 		    complete);
1008 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1009 
1010 		el_source(el, NULL);	/* read ~/.editrc */
1011 		el_set(el, EL_SIGNAL, 1);
1012 	} else if (!editing) {
1013 		if (hist) {
1014 			history_end(hist);
1015 			hist = NULL;
1016 		}
1017 		if (el) {
1018 			el_end(el);
1019 			el = NULL;
1020 		}
1021 	}
1022 }
1023 #endif /* !SMALL */
1024