xref: /openbsd-src/usr.bin/ftp/util.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: util.c,v 1.66 2014/01/29 16:58:21 dcoppa 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 char *action;
757 
758 void
759 progressmeter(int flag, const char *filename)
760 {
761 	/*
762 	 * List of order of magnitude prefixes.
763 	 * The last is `P', as 2^64 = 16384 Petabytes
764 	 */
765 	static const char prefixes[] = " KMGTP";
766 
767 	static struct timeval lastupdate;
768 	static off_t lastsize;
769 	static char *title = NULL;
770 	struct timeval now, td, wait;
771 	off_t cursize, abbrevsize;
772 	double elapsed;
773 	int ratio, barlength, i, remaining, overhead = 30;
774 	char buf[512];
775 
776 	if (flag == -1) {
777 		(void)gettimeofday(&start, (struct timezone *)0);
778 		lastupdate = start;
779 		lastsize = restart_point;
780 	}
781 	(void)gettimeofday(&now, (struct timezone *)0);
782 	if (!progress || filesize < 0)
783 		return;
784 	cursize = bytes + restart_point;
785 
786 	if (filesize)
787 		ratio = cursize * 100 / filesize;
788 	else
789 		ratio = 100;
790 	ratio = MAX(ratio, 0);
791 	ratio = MIN(ratio, 100);
792 	if (!verbose && flag == -1) {
793 		filename = basename(filename);
794 		if (filename != NULL)
795 			title = strdup(filename);
796 	}
797 
798 	buf[0] = 0;
799 	if (!verbose && action != NULL) {
800 		int l = strlen(action);
801 		char *dotdot = "";
802 
803 		if (l < 7)
804 			l = 7;
805 		else if (l > 12) {
806 			l = 12;
807 			dotdot = "...";
808 			overhead += 3;
809 		}
810 		snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action,
811 		    dotdot);
812 		overhead += l + 1;
813 	} else
814 		snprintf(buf, sizeof(buf), "\r");
815 
816 	if (!verbose && title != NULL) {
817 		int l = strlen(title);
818 		char *dotdot = "";
819 
820 		if (l < 12)
821 			l = 12;
822 		else if (l > 25) {
823 			l = 22;
824 			dotdot = "...";
825 			overhead += 3;
826 		}
827 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
828 		    "%-*.*s%s %3d%% ", l, l, title,
829 		    dotdot, ratio);
830 		overhead += l + 1;
831 	} else
832 		snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
833 
834 	barlength = ttywidth - overhead;
835 	if (barlength > 0) {
836 		i = barlength * ratio / 100;
837 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
838 		    "|%.*s%*s|", i,
839 		    "*******************************************************"
840 		    "*******************************************************"
841 		    "*******************************************************"
842 		    "*******************************************************"
843 		    "*******************************************************"
844 		    "*******************************************************"
845 		    "*******************************************************",
846 		    barlength - i, "");
847 	}
848 
849 	i = 0;
850 	abbrevsize = cursize;
851 	while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) {
852 		i++;
853 		abbrevsize >>= 10;
854 	}
855 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
856 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
857 	    prefixes[i] == ' ' ? ' ' : 'B');
858 
859 	timersub(&now, &lastupdate, &wait);
860 	if (cursize > lastsize) {
861 		lastupdate = now;
862 		lastsize = cursize;
863 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
864 			start.tv_sec += wait.tv_sec;
865 			start.tv_usec += wait.tv_usec;
866 		}
867 		wait.tv_sec = 0;
868 	}
869 
870 	timersub(&now, &start, &td);
871 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
872 
873 	if (flag == 1) {
874 		i = (int)elapsed / 3600;
875 		if (i)
876 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
877 			    "%2d:", i);
878 		else
879 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
880 			    "   ");
881 		i = (int)elapsed % 3600;
882 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
883 		    "%02d:%02d    ", i / 60, i % 60);
884 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
885 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
886 		    "   --:-- ETA");
887 	} else if (wait.tv_sec >= STALLTIME) {
888 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
889 		    " - stalled -");
890 	} else {
891 		remaining = (int)((filesize - restart_point) /
892 				  (bytes / elapsed) - elapsed);
893 		i = remaining / 3600;
894 		if (i)
895 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
896 			    "%2d:", i);
897 		else
898 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
899 			    "   ");
900 		i = remaining % 3600;
901 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
902 		    "%02d:%02d ETA", i / 60, i % 60);
903 	}
904 	(void)write(fileno(ttyout), buf, strlen(buf));
905 
906 	if (flag == -1) {
907 		(void)signal(SIGALRM, updateprogressmeter);
908 		alarmtimer(1);		/* set alarm timer for 1 Hz */
909 	} else if (flag == 1) {
910 		alarmtimer(0);
911 		(void)putc('\n', ttyout);
912 		if (title != NULL) {
913 			free(title);
914 			title = NULL;
915 		}
916 	}
917 	fflush(ttyout);
918 }
919 
920 /*
921  * Display transfer statistics.
922  * Requires start to be initialised by progressmeter(-1),
923  * direction to be defined by xfer routines, and filesize and bytes
924  * to be updated by xfer routines
925  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
926  * instead of TTYOUT.
927  */
928 void
929 ptransfer(int siginfo)
930 {
931 	struct timeval now, td;
932 	double elapsed;
933 	off_t bs;
934 	int meg, remaining, hh;
935 	char buf[100];
936 
937 	if (!verbose && !siginfo)
938 		return;
939 
940 	(void)gettimeofday(&now, (struct timezone *)0);
941 	timersub(&now, &start, &td);
942 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
943 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
944 	meg = 0;
945 	if (bs > (1024 * 1024))
946 		meg = 1;
947 	(void)snprintf(buf, sizeof(buf),
948 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
949 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
950 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
951 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
952 	    && bytes + restart_point <= filesize) {
953 		remaining = (int)((filesize - restart_point) /
954 				  (bytes / elapsed) - elapsed);
955 		hh = remaining / 3600;
956 		remaining %= 3600;
957 			/* "buf+len(buf) -1" to overwrite \n */
958 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
959 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
960 		    remaining % 60);
961 	}
962 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
963 }
964 
965 /*
966  * List words in stringlist, vertically arranged
967  */
968 #ifndef SMALL
969 void
970 list_vertical(StringList *sl)
971 {
972 	int i, j, w;
973 	int columns, width, lines;
974 	char *p;
975 
976 	width = 0;
977 
978 	for (i = 0 ; i < sl->sl_cur ; i++) {
979 		w = strlen(sl->sl_str[i]);
980 		if (w > width)
981 			width = w;
982 	}
983 	width = (width + 8) &~ 7;
984 
985 	columns = ttywidth / width;
986 	if (columns == 0)
987 		columns = 1;
988 	lines = (sl->sl_cur + columns - 1) / columns;
989 	for (i = 0; i < lines; i++) {
990 		for (j = 0; j < columns; j++) {
991 			p = sl->sl_str[j * lines + i];
992 			if (p)
993 				fputs(p, ttyout);
994 			if (j * lines + i + lines >= sl->sl_cur) {
995 				putc('\n', ttyout);
996 				break;
997 			}
998 			w = strlen(p);
999 			while (w < width) {
1000 				w = (w + 8) &~ 7;
1001 				(void)putc('\t', ttyout);
1002 			}
1003 		}
1004 	}
1005 }
1006 #endif /* !SMALL */
1007 
1008 /*
1009  * Update the global ttywidth value, using TIOCGWINSZ.
1010  */
1011 /* ARGSUSED */
1012 void
1013 setttywidth(int signo)
1014 {
1015 	int save_errno = errno;
1016 	struct winsize winsize;
1017 
1018 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
1019 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
1020 	else
1021 		ttywidth = 80;
1022 	errno = save_errno;
1023 }
1024 
1025 /*
1026  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
1027  */
1028 void
1029 alarmtimer(int wait)
1030 {
1031 	struct itimerval itv;
1032 
1033 	itv.it_value.tv_sec = wait;
1034 	itv.it_value.tv_usec = 0;
1035 	itv.it_interval = itv.it_value;
1036 	setitimer(ITIMER_REAL, &itv, NULL);
1037 }
1038 
1039 /*
1040  * Setup or cleanup EditLine structures
1041  */
1042 #ifndef SMALL
1043 void
1044 controlediting(void)
1045 {
1046 	HistEvent hev;
1047 
1048 	if (editing && el == NULL && hist == NULL) {
1049 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1050 		hist = history_init();		/* init the builtin history */
1051 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1052 		el_set(el, EL_HIST, history, hist);	/* use history */
1053 
1054 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1055 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1056 
1057 		/* add local file completion, bind to TAB */
1058 		el_set(el, EL_ADDFN, "ftp-complete",
1059 		    "Context sensitive argument completion",
1060 		    complete);
1061 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1062 
1063 		el_source(el, NULL);	/* read ~/.editrc */
1064 		el_set(el, EL_SIGNAL, 1);
1065 	} else if (!editing) {
1066 		if (hist) {
1067 			history_end(hist);
1068 			hist = NULL;
1069 		}
1070 		if (el) {
1071 			el_end(el);
1072 			el = NULL;
1073 		}
1074 	}
1075 }
1076 #endif /* !SMALL */
1077 
1078