xref: /openbsd-src/usr.bin/ftp/util.c (revision 5b133f3f277e80f096764111e64f3a1284acb179)
1 /*	$OpenBSD: util.c,v 1.98 2023/03/08 04:43:11 guenther 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
setpeer(int argc,char * argv[])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
ftp_login(const char * host,char * user,char * pass)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 		} else
286 			exit(0);
287 	}
288 	n = command("USER %s", user);
289 	if (n == CONTINUE) {
290 		if (pass == NULL)
291 			pass = getpass("Password:");
292 		n = command("PASS %s", pass);
293 	}
294 	if (n == CONTINUE) {
295 		aflag++;
296 		if (acctname == NULL)
297 			acctname = getpass("Account:");
298 		n = command("ACCT %s", acctname);
299 	}
300 	if ((n != COMPLETE) ||
301 	    (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
302 		warnx("Login %s failed.", user);
303 		if (retry || !anonftp)
304 			return (0);
305 		else
306 			retry = 1;
307 		goto tryagain;
308 	}
309 	if (proxy)
310 		return (1);
311 	connected = -1;
312 #ifndef SMALL
313 	for (n = 0; n < macnum; ++n) {
314 		if (!strcmp("init", macros[n].mac_name)) {
315 			(void)strlcpy(line, "$init", sizeof line);
316 			makeargv();
317 			domacro(margc, margv);
318 			break;
319 		}
320 	}
321 #endif /* SMALL */
322 	return (1);
323 }
324 
325 /*
326  * `another' gets another argument, and stores the new argc and argv.
327  * It reverts to the top level (via main.c's intr()) on EOF/error.
328  *
329  * Returns false if no new arguments have been added.
330  */
331 #ifndef SMALL
332 int
another(int * pargc,char *** pargv,const char * prompt)333 another(int *pargc, char ***pargv, const char *prompt)
334 {
335 	int len = strlen(line), ret;
336 
337 	if (len >= sizeof(line) - 3) {
338 		fputs("sorry, arguments too long.\n", ttyout);
339 		intr();
340 	}
341 	fprintf(ttyout, "(%s) ", prompt);
342 	line[len++] = ' ';
343 	if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL) {
344 		clearerr(stdin);
345 		intr();
346 	}
347 	len += strlen(&line[len]);
348 	if (len > 0 && line[len - 1] == '\n')
349 		line[len - 1] = '\0';
350 	makeargv();
351 	ret = margc > *pargc;
352 	*pargc = margc;
353 	*pargv = margv;
354 	return (ret);
355 }
356 #endif /* !SMALL */
357 
358 /*
359  * glob files given in argv[] from the remote server.
360  * if errbuf isn't NULL, store error messages there instead
361  * of writing to the screen.
362  * if type isn't NULL, use LIST instead of NLST, and store filetype.
363  * 'd' means directory, 's' means symbolic link, '-' means plain
364  * file.
365  */
366 char *
remglob2(char * argv[],int doswitch,char ** errbuf,FILE ** ftemp,char * type)367 remglob2(char *argv[], int doswitch, char **errbuf, FILE **ftemp, char *type)
368 {
369 	char temp[PATH_MAX], *bufp, *cp, *lmode;
370 	static char buf[PATH_MAX], **args;
371 	int oldverbose, oldhash, fd;
372 
373 	if (!mflag) {
374 		if (!doglob)
375 			args = NULL;
376 		else {
377 			if (*ftemp) {
378 				(void)fclose(*ftemp);
379 				*ftemp = NULL;
380 			}
381 		}
382 		return (NULL);
383 	}
384 	if (!doglob) {
385 		if (args == NULL)
386 			args = argv;
387 		if ((cp = *++args) == NULL)
388 			args = NULL;
389 		return (cp);
390 	}
391 	if (*ftemp == NULL) {
392 		int len;
393 
394 		cp = _PATH_TMP;
395 		len = strlen(cp);
396 		if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
397 			warnx("unable to create temporary file: %s",
398 			    strerror(ENAMETOOLONG));
399 			return (NULL);
400 		}
401 
402 		(void)strlcpy(temp, cp, sizeof temp);
403 		if (temp[len-1] != '/')
404 			temp[len++] = '/';
405 		(void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
406 		if ((fd = mkstemp(temp)) == -1) {
407 			warn("unable to create temporary file: %s", temp);
408 			return (NULL);
409 		}
410 		close(fd);
411 		oldverbose = verbose;
412 		verbose = (errbuf != NULL) ? -1 : 0;
413 		oldhash = hash;
414 		hash = 0;
415 		if (doswitch)
416 			pswitch(!proxy);
417 		for (lmode = "w"; *++argv != NULL; lmode = "a")
418 			recvrequest(type ? "LIST" : "NLST", temp, *argv, lmode,
419 			    0, 0);
420 		if ((code / 100) != COMPLETE) {
421 			if (errbuf != NULL)
422 				*errbuf = reply_string;
423 		}
424 		if (doswitch)
425 			pswitch(!proxy);
426 		verbose = oldverbose;
427 		hash = oldhash;
428 		*ftemp = fopen(temp, "r");
429 		(void)unlink(temp);
430 		if (*ftemp == NULL) {
431 			if (errbuf == NULL)
432 				fputs("can't find list of remote files, oops.\n",
433 				    ttyout);
434 			else
435 				*errbuf =
436 				    "can't find list of remote files, oops.";
437 			return (NULL);
438 		}
439 	}
440 #ifndef SMALL
441 again:
442 #endif
443 	if (fgets(buf, sizeof(buf), *ftemp) == NULL) {
444 		(void)fclose(*ftemp);
445 		*ftemp = NULL;
446 		return (NULL);
447 	}
448 
449 	buf[strcspn(buf, "\n")] = '\0';
450 	bufp = buf;
451 
452 #ifndef SMALL
453 	if (type) {
454 		parse_list(&bufp, type);
455 		if (!bufp ||
456 		    (bufp[0] == '.' &&	/* LIST defaults to -a on some ftp */
457 		    (bufp[1] == '\0' ||	/* servers.  Ignore '.' and '..'. */
458 		    (bufp[1] == '.' && bufp[2] == '\0'))))
459 			goto again;
460 	}
461 #endif /* !SMALL */
462 
463 	return (bufp);
464 }
465 
466 /*
467  * wrapper for remglob2
468  */
469 char *
remglob(char * argv[],int doswitch,char ** errbuf)470 remglob(char *argv[], int doswitch, char **errbuf)
471 {
472 	static FILE *ftemp = NULL;
473 
474 	return remglob2(argv, doswitch, errbuf, &ftemp, NULL);
475 }
476 
477 #ifndef SMALL
478 int
confirm(const char * cmd,const char * file)479 confirm(const char *cmd, const char *file)
480 {
481 	char str[BUFSIZ];
482 
483 	if (file && (confirmrest || !interactive))
484 		return (1);
485 top:
486 	if (file)
487 		fprintf(ttyout, "%s %s? ", cmd, file);
488 	else
489 		fprintf(ttyout, "Continue with %s? ", cmd);
490 	(void)fflush(ttyout);
491 	if (fgets(str, sizeof(str), stdin) == NULL)
492 		goto quit;
493 	switch (tolower((unsigned char)*str)) {
494 		case '?':
495 			fprintf(ttyout,
496 			    "?	help\n"
497 			    "a	answer yes to all\n"
498 			    "n	answer no\n"
499 			    "p	turn off prompt mode\n"
500 			    "q	answer no to all\n"
501 			    "y	answer yes\n");
502 			goto top;
503 		case 'a':
504 			confirmrest = 1;
505 			fprintf(ttyout, "Prompting off for duration of %s.\n",
506 			    cmd);
507 			break;
508 		case 'n':
509 			return (0);
510 		case 'p':
511 			interactive = 0;
512 			fputs("Interactive mode: off.\n", ttyout);
513 			break;
514 		case 'q':
515 quit:
516 			mflag = 0;
517 			clearerr(stdin);
518 			return (0);
519 		case 'y':
520 			return(1);
521 			break;
522 		default:
523 			fprintf(ttyout, "?, a, n, p, q, y "
524 			    "are the only acceptable commands!\n");
525 			goto top;
526 			break;
527 	}
528 	return (1);
529 }
530 #endif /* !SMALL */
531 
532 /*
533  * Glob a local file name specification with
534  * the expectation of a single return value.
535  * Can't control multiple values being expanded
536  * from the expression, we return only the first.
537  */
538 int
globulize(char ** cpp)539 globulize(char **cpp)
540 {
541 	glob_t gl;
542 	int flags;
543 
544 	if (!doglob)
545 		return (1);
546 
547 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
548 	memset(&gl, 0, sizeof(gl));
549 	if (glob(*cpp, flags, NULL, &gl) ||
550 	    gl.gl_pathc == 0) {
551 		warnx("%s: not found", *cpp);
552 		globfree(&gl);
553 		return (0);
554 	}
555 		/* XXX: caller should check if *cpp changed, and
556 		 *	free(*cpp) if that is the case
557 		 */
558 	*cpp = strdup(gl.gl_pathv[0]);
559 	if (*cpp == NULL)
560 		err(1, NULL);
561 	globfree(&gl);
562 	return (1);
563 }
564 
565 /*
566  * determine size of remote file
567  */
568 off_t
remotesize(const char * file,int noisy)569 remotesize(const char *file, int noisy)
570 {
571 	int overbose;
572 	off_t size;
573 
574 	overbose = verbose;
575 	size = -1;
576 #ifndef SMALL
577 	if (!debug)
578 #endif /* !SMALL */
579 		verbose = -1;
580 	if (command("SIZE %s", file) == COMPLETE) {
581 		char *cp, *ep;
582 
583 		cp = strchr(reply_string, ' ');
584 		if (cp != NULL) {
585 			cp++;
586 			size = strtoll(cp, &ep, 10);
587 			if (*ep != '\0' && !isspace((unsigned char)*ep))
588 				size = -1;
589 		}
590 	} else if (noisy
591 #ifndef SMALL
592 	    && !debug
593 #endif /* !SMALL */
594 	    ) {
595 		fputs(reply_string, ttyout);
596 		fputc('\n', ttyout);
597 	}
598 	verbose = overbose;
599 	return (size);
600 }
601 
602 /*
603  * determine last modification time (in GMT) of remote file
604  */
605 time_t
remotemodtime(const char * file,int noisy)606 remotemodtime(const char *file, int noisy)
607 {
608 	int overbose;
609 	time_t rtime;
610 	int ocode;
611 
612 	overbose = verbose;
613 	ocode = code;
614 	rtime = -1;
615 #ifndef SMALL
616 	if (!debug)
617 #endif /* !SMALL */
618 		verbose = -1;
619 	if (command("MDTM %s", file) == COMPLETE) {
620 		struct tm timebuf;
621 		int yy, mo, day, hour, min, sec;
622 		/*
623 		 * time-val = 14DIGIT [ "." 1*DIGIT ]
624 		 *		YYYYMMDDHHMMSS[.sss]
625 		 * mdtm-response = "213" SP time-val CRLF / error-response
626 		 */
627 		/* TODO: parse .sss as well, use timespecs. */
628 		char *timestr = reply_string;
629 
630 		/* Repair `19%02d' bug on server side */
631 		while (!isspace((unsigned char)*timestr))
632 			timestr++;
633 		while (isspace((unsigned char)*timestr))
634 			timestr++;
635 		if (strncmp(timestr, "191", 3) == 0) {
636 			fprintf(ttyout,
637 	    "Y2K warning! Fixed incorrect time-val received from server.\n");
638 			timestr[0] = ' ';
639 			timestr[1] = '2';
640 			timestr[2] = '0';
641 		}
642 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
643 			&day, &hour, &min, &sec);
644 		memset(&timebuf, 0, sizeof(timebuf));
645 		timebuf.tm_sec = sec;
646 		timebuf.tm_min = min;
647 		timebuf.tm_hour = hour;
648 		timebuf.tm_mday = day;
649 		timebuf.tm_mon = mo - 1;
650 		timebuf.tm_year = yy - 1900;
651 		timebuf.tm_isdst = -1;
652 		rtime = mktime(&timebuf);
653 		if (rtime == -1 && (noisy
654 #ifndef SMALL
655 		    || debug
656 #endif /* !SMALL */
657 		    ))
658 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
659 		else
660 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
661 	} else if (noisy
662 #ifndef SMALL
663 	    && !debug
664 #endif /* !SMALL */
665 	    ) {
666 		fputs(reply_string, ttyout);
667 		fputc('\n', ttyout);
668 	}
669 	verbose = overbose;
670 	if (rtime == -1)
671 		code = ocode;
672 	return (rtime);
673 }
674 
675 /*
676  * Ensure file is in or under dir.
677  * Returns 1 if so, 0 if not (or an error occurred).
678  */
679 int
fileindir(const char * file,const char * dir)680 fileindir(const char *file, const char *dir)
681 {
682 	char	parentdirbuf[PATH_MAX], *parentdir;
683 	char	realdir[PATH_MAX];
684 	size_t	dirlen;
685 
686 					/* determine parent directory of file */
687 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
688 	parentdir = dirname(parentdirbuf);
689 	if (strcmp(parentdir, ".") == 0)
690 		return 1;		/* current directory is ok */
691 
692 					/* find the directory */
693 	if (realpath(parentdir, realdir) == NULL) {
694 		warn("Unable to determine real path of `%s'", parentdir);
695 		return 0;
696 	}
697 	if (realdir[0] != '/')		/* relative result is ok */
698 		return 1;
699 
700 	dirlen = strlen(dir);
701 	if (strncmp(realdir, dir, dirlen) == 0 &&
702 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
703 		return 1;
704 	return 0;
705 }
706 
707 
708 /*
709  * Returns true if this is the controlling/foreground process, else false.
710  */
711 int
foregroundproc(void)712 foregroundproc(void)
713 {
714 	static pid_t pgrp = -1;
715 	int ctty_pgrp;
716 
717 	if (pgrp == -1)
718 		pgrp = getpgrp();
719 
720 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
721 	    ctty_pgrp == pgrp));
722 }
723 
724 static void
updateprogressmeter(int signo)725 updateprogressmeter(int signo)
726 {
727 	int save_errno = errno;
728 
729 	/* update progressmeter if foreground process or in -m mode */
730 	if (foregroundproc() || progress == -1)
731 		progressmeter(0, NULL);
732 	errno = save_errno;
733 }
734 
735 /*
736  * Display a transfer progress bar if progress is non-zero.
737  * SIGALRM is hijacked for use by this function.
738  * - Before the transfer, set filesize to size of file (or -1 if unknown),
739  *   and call with flag = -1. This starts the once per second timer,
740  *   and a call to updateprogressmeter() upon SIGALRM.
741  * - During the transfer, updateprogressmeter will call progressmeter
742  *   with flag = 0
743  * - After the transfer, call with flag = 1
744  */
745 static struct timespec start;
746 
747 char *action;
748 
749 void
progressmeter(int flag,const char * filename)750 progressmeter(int flag, const char *filename)
751 {
752 	/*
753 	 * List of order of magnitude prefixes.
754 	 * The last is `P', as 2^64 = 16384 Petabytes
755 	 */
756 	static const char prefixes[] = " KMGTP";
757 
758 	static struct timespec lastupdate;
759 	static off_t lastsize;
760 	static char *title = NULL;
761 	struct timespec now, td, wait;
762 	off_t cursize, abbrevsize;
763 	double elapsed;
764 	int ratio, barlength, i, remaining, overhead = 30;
765 	char buf[512], *filenamebuf;
766 
767 	if (flag == -1) {
768 		clock_gettime(CLOCK_MONOTONIC, &start);
769 		lastupdate = start;
770 		lastsize = restart_point;
771 	}
772 	clock_gettime(CLOCK_MONOTONIC, &now);
773 	if (!progress || filesize < 0)
774 		return;
775 	cursize = bytes + restart_point;
776 
777 	if (filesize)
778 		ratio = cursize * 100 / filesize;
779 	else
780 		ratio = 100;
781 	ratio = MAXIMUM(ratio, 0);
782 	ratio = MINIMUM(ratio, 100);
783 	if (!verbose && flag == -1) {
784 		if ((filenamebuf = strdup(filename)) != NULL &&
785 		    (filename = basename(filenamebuf)) != NULL) {
786 			free(title);
787 			title = strdup(filename);
788 		}
789 		free(filenamebuf);
790 	}
791 
792 	buf[0] = 0;
793 	if (!verbose && action != NULL) {
794 		int l = strlen(action);
795 		char *dotdot = "";
796 
797 		if (l < 7)
798 			l = 7;
799 		else if (l > 12) {
800 			l = 12;
801 			dotdot = "...";
802 			overhead += 3;
803 		}
804 		snprintf(buf, sizeof(buf), "\r%-*.*s%s ", l, l, action,
805 		    dotdot);
806 		overhead += l + 1;
807 	} else
808 		snprintf(buf, sizeof(buf), "\r");
809 
810 	if (!verbose && title != NULL) {
811 		int l = strlen(title);
812 		char *dotdot = "";
813 
814 		if (l < 12)
815 			l = 12;
816 		else if (l > 25) {
817 			l = 22;
818 			dotdot = "...";
819 			overhead += 3;
820 		}
821 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
822 		    "%-*.*s%s %3d%% ", l, l, title,
823 		    dotdot, ratio);
824 		overhead += l + 1;
825 	} else
826 		snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
827 
828 	barlength = ttywidth - overhead;
829 	if (barlength > 0) {
830 		i = barlength * ratio / 100;
831 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
832 		    "|%.*s%*s|", i,
833 		    "*******************************************************"
834 		    "*******************************************************"
835 		    "*******************************************************"
836 		    "*******************************************************"
837 		    "*******************************************************"
838 		    "*******************************************************"
839 		    "*******************************************************",
840 		    barlength - i, "");
841 	}
842 
843 	i = 0;
844 	abbrevsize = cursize;
845 	while (abbrevsize >= 100000 && i < sizeof(prefixes)-1) {
846 		i++;
847 		abbrevsize >>= 10;
848 	}
849 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
850 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
851 	    prefixes[i] == ' ' ? ' ' : 'B');
852 
853 	timespecsub(&now, &lastupdate, &wait);
854 	if (cursize > lastsize) {
855 		lastupdate = now;
856 		lastsize = cursize;
857 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
858 			start.tv_sec += wait.tv_sec;
859 			start.tv_nsec += wait.tv_nsec;
860 		}
861 		wait.tv_sec = 0;
862 	}
863 
864 	timespecsub(&now, &start, &td);
865 	elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0);
866 
867 	if (flag == 1) {
868 		i = (int)elapsed / 3600;
869 		if (i)
870 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
871 			    "%2d:", i);
872 		else
873 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
874 			    "   ");
875 		i = (int)elapsed % 3600;
876 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
877 		    "%02d:%02d    ", i / 60, i % 60);
878 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
879 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
880 		    "   --:-- ETA");
881 	} else if (wait.tv_sec >= STALLTIME) {
882 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
883 		    " - stalled -");
884 	} else {
885 		remaining = (int)((filesize - restart_point) /
886 				  (bytes / elapsed) - elapsed);
887 		i = remaining / 3600;
888 		if (i)
889 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
890 			    "%2d:", i);
891 		else
892 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
893 			    "   ");
894 		i = remaining % 3600;
895 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
896 		    "%02d:%02d ETA", i / 60, i % 60);
897 	}
898 	(void)write(fileno(ttyout), buf, strlen(buf));
899 
900 	if (flag == -1) {
901 		(void)signal(SIGALRM, updateprogressmeter);
902 		alarmtimer(1);		/* set alarm timer for 1 Hz */
903 	} else if (flag == 1) {
904 		alarmtimer(0);
905 		(void)putc('\n', ttyout);
906 		free(title);
907 		title = NULL;
908 	}
909 	fflush(ttyout);
910 }
911 
912 /*
913  * Display transfer statistics.
914  * Requires start to be initialised by progressmeter(-1),
915  * direction to be defined by xfer routines, and filesize and bytes
916  * to be updated by xfer routines
917  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
918  * instead of TTYOUT.
919  */
920 void
ptransfer(int siginfo)921 ptransfer(int siginfo)
922 {
923 	struct timespec now, td;
924 	double elapsed, pace;
925 	off_t bs;
926 	int meg, remaining, hh;
927 	char buf[100];
928 
929 	if (!verbose && !siginfo)
930 		return;
931 
932 	clock_gettime(CLOCK_MONOTONIC, &now);
933 	timespecsub(&now, &start, &td);
934 	elapsed = td.tv_sec + (td.tv_nsec / 1000000000.0);
935 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
936 	meg = 0;
937 	if (bs > (1024 * 1024))
938 		meg = 1;
939 
940 	pace = bs / (1024.0 * (meg ? 1024.0 : 1.0));
941 	(void)snprintf(buf, sizeof(buf),
942 	    "%lld byte%s %s in %lld.%02d seconds (%lld.%02d %sB/s)\n",
943 	    (long long)bytes, bytes == 1 ? "" : "s", direction,
944 	    (long long)elapsed, (int)(elapsed * 100.0) % 100,
945 	    (long long)pace, (int)(pace * 100.0) % 100,
946 	    meg ? "M" : "K");
947 
948 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0 &&
949 	    bytes + restart_point <= filesize) {
950 		remaining = (int)((filesize - restart_point) /
951 		    (bytes / elapsed) - elapsed);
952 		hh = remaining / 3600;
953 		remaining %= 3600;
954 
955 		/* "buf+len(buf) -1" to overwrite \n */
956 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
957 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
958 		    remaining % 60);
959 	}
960 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
961 }
962 
963 /*
964  * List words in stringlist, vertically arranged
965  */
966 #ifndef SMALL
967 void
list_vertical(StringList * sl)968 list_vertical(StringList *sl)
969 {
970 	int i, j, w;
971 	int columns, width, lines;
972 	char *p;
973 
974 	width = 0;
975 
976 	for (i = 0 ; i < sl->sl_cur ; i++) {
977 		w = strlen(sl->sl_str[i]);
978 		if (w > width)
979 			width = w;
980 	}
981 	width = (width + 8) &~ 7;
982 
983 	columns = ttywidth / width;
984 	if (columns == 0)
985 		columns = 1;
986 	lines = (sl->sl_cur + columns - 1) / columns;
987 	for (i = 0; i < lines; i++) {
988 		for (j = 0; j < columns; j++) {
989 			p = sl->sl_str[j * lines + i];
990 			if (p)
991 				fputs(p, ttyout);
992 			if (j * lines + i + lines >= sl->sl_cur) {
993 				putc('\n', ttyout);
994 				break;
995 			}
996 			w = strlen(p);
997 			while (w < width) {
998 				w = (w + 8) &~ 7;
999 				(void)putc('\t', ttyout);
1000 			}
1001 		}
1002 	}
1003 }
1004 #endif /* !SMALL */
1005 
1006 /*
1007  * Update the global ttywidth value, using TIOCGWINSZ.
1008  */
1009 void
setttywidth(int signo)1010 setttywidth(int signo)
1011 {
1012 	int save_errno = errno;
1013 	struct winsize winsize;
1014 
1015 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
1016 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
1017 	else
1018 		ttywidth = 80;
1019 	errno = save_errno;
1020 }
1021 
1022 /*
1023  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
1024  */
1025 void
alarmtimer(int wait)1026 alarmtimer(int wait)
1027 {
1028 	int save_errno = errno;
1029 	struct itimerval itv;
1030 
1031 	itv.it_value.tv_sec = wait;
1032 	itv.it_value.tv_usec = 0;
1033 	itv.it_interval = itv.it_value;
1034 	setitimer(ITIMER_REAL, &itv, NULL);
1035 	errno = save_errno;
1036 }
1037 
1038 /*
1039  * Setup or cleanup EditLine structures
1040  */
1041 #ifndef SMALL
1042 void
controlediting(void)1043 controlediting(void)
1044 {
1045 	HistEvent hev;
1046 
1047 	if (editing && el == NULL && hist == NULL) {
1048 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
1049 		hist = history_init();		/* init the builtin history */
1050 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
1051 		el_set(el, EL_HIST, history, hist);	/* use history */
1052 
1053 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
1054 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
1055 
1056 		/* add local file completion, bind to TAB */
1057 		el_set(el, EL_ADDFN, "ftp-complete",
1058 		    "Context sensitive argument completion",
1059 		    complete);
1060 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1061 
1062 		el_source(el, NULL);	/* read ~/.editrc */
1063 		el_set(el, EL_SIGNAL, 1);
1064 	} else if (!editing) {
1065 		if (hist) {
1066 			history_end(hist);
1067 			hist = NULL;
1068 		}
1069 		if (el) {
1070 			el_end(el);
1071 			el = NULL;
1072 		}
1073 	}
1074 }
1075 #endif /* !SMALL */
1076 
1077 /*
1078  * connect(2) with an optional timeout if secs > 0.
1079  */
1080 int
timed_connect(int s,const struct sockaddr * name,socklen_t namelen,int secs)1081 timed_connect(int s, const struct sockaddr *name, socklen_t namelen, int secs)
1082 {
1083 	struct timespec now, target, timebuf, *timeout = NULL;
1084 	int flags, nready, optval, ret = -1;
1085 	socklen_t optlen;
1086 	struct pollfd pfd;
1087 
1088 	if (secs > 0) {
1089 		timebuf.tv_sec = secs;
1090 		timebuf.tv_nsec = 0;
1091 		timeout = &timebuf;
1092 		clock_gettime(CLOCK_MONOTONIC, &target);
1093 		timespecadd(&target, timeout, &target);
1094 	}
1095 
1096 	flags = fcntl(s, F_GETFL, 0);
1097 	if (flags == -1) {
1098 		warn("fcntl(F_GETFL)");
1099 		return -1;
1100 	}
1101 	if (!(flags & O_NONBLOCK)) {
1102 		if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) {
1103 			warn("fcntl(F_SETFL)");
1104 			return -1;
1105 		}
1106 	}
1107 
1108 	ret = connect(s, name, namelen);
1109 	if (ret == 0 || errno != EINPROGRESS)
1110 		goto done;
1111 
1112 	for (;;) {
1113 		pfd.fd = s;
1114 		pfd.events = POLLOUT;
1115 		nready = ppoll(&pfd, 1, timeout, NULL);
1116 		switch (nready) {
1117 		case -1:
1118 			if (errno != EINTR && errno != EAGAIN) {
1119 				warn("ppoll");
1120 				goto done;
1121 			}
1122 			if (timeout == NULL)
1123 				continue;
1124 			clock_gettime(CLOCK_MONOTONIC, &now);
1125 			timespecsub(&now, &target, timeout);
1126 			if (timeout->tv_sec >= 0)
1127 				continue;
1128 			/* FALLTHROUGH */
1129 		case 0:
1130 			errno = ETIMEDOUT;
1131 			goto done;
1132 		default:
1133 			optlen = sizeof(optval);
1134 			ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &optval,
1135 			    &optlen);
1136 			if (ret == 0 && optval != 0) {
1137 				ret = -1;
1138 				errno = optval;
1139 			}
1140 			goto done;
1141 		}
1142 	}
1143 
1144 done:
1145 	if (!(flags & O_NONBLOCK)) {
1146 		if (fcntl(s, F_SETFL, flags) == -1) {
1147 			warn("fcntl(F_SETFL)");
1148 			ret = -1;
1149 		}
1150 	}
1151 
1152 	return ret;
1153 }
1154 
1155 #ifndef SMALL
1156 ssize_t
http_time(time_t t,char * tmbuf,size_t len)1157 http_time(time_t t, char *tmbuf, size_t len)
1158 {
1159 	struct tm tm;
1160 
1161 	/* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
1162 	if (gmtime_r(&t, &tm) == NULL)
1163 		return 0;
1164 	else
1165 		return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
1166 }
1167 #endif /* !SMALL */
1168