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