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