1 /*	$OpenBSD: util.c,v 1.46 2007/06/06 19:15:33 pyr 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  * 3. All advertising materials mentioning features or use of this software
24  *    must display the following acknowledgement:
25  *	This product includes software developed by the NetBSD
26  *	Foundation, Inc. and its contributors.
27  * 4. Neither the name of The NetBSD Foundation nor the names of its
28  *    contributors may be used to endorse or promote products derived
29  *    from this software without specific prior written permission.
30  *
31  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
32  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
33  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
34  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
35  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
36  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
37  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
38  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
39  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
41  * POSSIBILITY OF SUCH DAMAGE.
42  */
43 
44 /*
45  * Copyright (c) 1985, 1989, 1993, 1994
46  *	The Regents of the University of California.  All rights reserved.
47  *
48  * Redistribution and use in source and binary forms, with or without
49  * modification, are permitted provided that the following conditions
50  * are met:
51  * 1. Redistributions of source code must retain the above copyright
52  *    notice, this list of conditions and the following disclaimer.
53  * 2. Redistributions in binary form must reproduce the above copyright
54  *    notice, this list of conditions and the following disclaimer in the
55  *    documentation and/or other materials provided with the distribution.
56  * 3. Neither the name of the University nor the names of its contributors
57  *    may be used to endorse or promote products derived from this software
58  *    without specific prior written permission.
59  *
60  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
61  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
62  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
63  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
64  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
65  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
66  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
67  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
68  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
69  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
70  * SUCH DAMAGE.
71  */
72 
73 /*
74  * FTP User Program -- Misc support routines
75  */
76 #include <sys/ioctl.h>
77 #include <sys/time.h>
78 #include <arpa/ftp.h>
79 
80 #include <ctype.h>
81 #include <err.h>
82 #include <errno.h>
83 #include <fcntl.h>
84 #include <libgen.h>
85 #include <limits.h>
86 #include <glob.h>
87 #include <pwd.h>
88 #include <signal.h>
89 #include <stdio.h>
90 #include <stdlib.h>
91 #include <string.h>
92 #include <time.h>
93 #include <tzfile.h>
94 #include <unistd.h>
95 
96 #include "ftp_var.h"
97 #include "pathnames.h"
98 
99 __RCSID("$MirOS: src/usr.bin/ftp/util.c,v 1.6 2013/09/15 11:01:29 tg Exp $");
100 
101 static void updateprogressmeter(int);
102 
103 /*
104  * Connect to peer server and
105  * auto-login, if possible.
106  */
107 void
setpeer(int argc,char * argv[])108 setpeer(int argc, char *argv[])
109 {
110 	char *host, *port;
111 
112 	if (connected) {
113 		fprintf(ttyout, "Already connected to %s, use close first.\n",
114 		    hostname);
115 		code = -1;
116 		return;
117 	}
118 	if (argc < 2)
119 		(void)another(&argc, &argv, "to");
120 	if (argc < 2 || argc > 3) {
121 		fprintf(ttyout, "usage: %s host-name [port]\n", argv[0]);
122 		code = -1;
123 		return;
124 	}
125 	if (gatemode)
126 		port = gateport;
127 	else
128 		port = ftpport;
129 #if 0
130 	if (argc > 2) {
131 		char *ep;
132 		long nport;
133 
134 		nport = strtol(argv[2], &ep, 10);
135 		if (nport < 1 || nport > USHRT_MAX || *ep != '\0') {
136 			fprintf(ttyout, "%s: bad port number '%s'.\n",
137 			    argv[1], argv[2]);
138 			fprintf(ttyout, "usage: %s host-name [port]\n",
139 			    argv[0]);
140 			code = -1;
141 			return;
142 		}
143 		port = htons((in_port_t)nport);
144 	}
145 #else
146 	if (argc > 2)
147 		port = argv[2];
148 #endif
149 
150 	if (gatemode) {
151 		if (gateserver == NULL || *gateserver == '\0')
152 			errx(1, "gateserver not defined (shouldn't happen)");
153 		host = hookup(gateserver, port);
154 	} else
155 		host = hookup(argv[1], port);
156 
157 	if (host) {
158 		int overbose;
159 
160 		if (gatemode) {
161 			if (command("PASSERVE %s", argv[1]) != COMPLETE)
162 				return;
163 			if (verbose)
164 				fprintf(ttyout,
165 				    "Connected via pass-through server %s\n",
166 				    gateserver);
167 		}
168 
169 		connected = 1;
170 		/*
171 		 * Set up defaults for FTP.
172 		 */
173 		(void)strlcpy(formname, "non-print", sizeof formname);
174 		form = FORM_N;
175 		(void)strlcpy(modename, "stream", sizeof modename);
176 		mode = MODE_S;
177 		(void)strlcpy(structname, "file", sizeof structname);
178 		stru = STRU_F;
179 		(void)strlcpy(bytename, "8", sizeof bytename);
180 		bytesize = 8;
181 
182 		/*
183 		 * Set type to 0 (not specified by user),
184 		 * meaning binary by default, but don't bother
185 		 * telling server.  We can use binary
186 		 * for text files unless changed by the user.
187 		 */
188 		(void)strlcpy(typename, "binary", sizeof typename);
189 		curtype = TYPE_A;
190 		type = 0;
191 		if (autologin)
192 			(void)ftp_login(argv[1], NULL, NULL);
193 
194 #if (defined(unix) || defined(BSD)) && NBBY == 8
195 /*
196  * this ifdef is to keep someone form "porting" this to an incompatible
197  * system and not checking this out. This way they have to think about it.
198  */
199 		overbose = verbose;
200 		if (debug == 0)
201 			verbose = -1;
202 		if (command("SYST") == COMPLETE && overbose) {
203 			char *cp, c;
204 			c = 0;
205 			cp = strchr(reply_string + 4, ' ');
206 			if (cp == NULL)
207 				cp = strchr(reply_string + 4, '\r');
208 			if (cp) {
209 				if (cp[-1] == '.')
210 					cp--;
211 				c = *cp;
212 				*cp = '\0';
213 			}
214 
215 			fprintf(ttyout, "Remote system type is %s.\n", reply_string + 4);
216 			if (cp)
217 				*cp = c;
218 		}
219 		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
220 			if (proxy)
221 				unix_proxy = 1;
222 			else
223 				unix_server = 1;
224 			if (overbose)
225 				fprintf(ttyout, "Using %s mode to transfer files.\n",
226 				    typename);
227 		} else {
228 			if (proxy)
229 				unix_proxy = 0;
230 			else
231 				unix_server = 0;
232 #ifndef SMALL
233 			if (overbose &&
234 			    !strncmp(reply_string, "215 TOPS20", 10))
235 				fputs(
236 "Remember to set tenex mode when transferring binary files from this machine.\n",
237 				    ttyout);
238 #endif /* !SMALL */
239 		}
240 		verbose = overbose;
241 #endif /* unix || BSD */
242 	}
243 }
244 
245 /*
246  * login to remote host, using given username & password if supplied
247  */
248 int
ftp_login(const char * host,char * user,char * pass)249 ftp_login(const char *host, char *user, char *pass)
250 {
251 	char tmp[80], *acctname = NULL, host_name[MAXHOSTNAMELEN];
252 	char anonpass[MAXLOGNAME + 1 + MAXHOSTNAMELEN];	/* "user@hostname" */
253 	int n, aflag = 0, retry = 0;
254 	struct passwd *pw;
255 
256 #ifndef SMALL
257 	if (user == NULL) {
258 		if (ruserpass(host, &user, &pass, &acctname) < 0) {
259 			code = -1;
260 			return (0);
261 		}
262 	}
263 #endif
264 
265 	/*
266 	 * Set up arguments for an anonymous FTP session, if necessary.
267 	 */
268 	if ((user == NULL || pass == NULL) && anonftp) {
269 		memset(anonpass, 0, sizeof(anonpass));
270 		memset(host_name, 0, sizeof(host_name));
271 
272 		/*
273 		 * Set up anonymous login password.
274 		 */
275 		if ((user = getlogin()) == NULL) {
276 			if ((pw = getpwuid(getuid())) == NULL)
277 				user = "anonymous";
278 			else
279 				user = pw->pw_name;
280 		}
281 		gethostname(host_name, sizeof(host_name));
282 #ifndef DONT_CHEAT_ANONPASS
283 		/*
284 		 * Every anonymous FTP server I've encountered
285 		 * will accept the string "username@", and will
286 		 * append the hostname itself.  We do this by default
287 		 * since many servers are picky about not having
288 		 * a FQDN in the anonymous password. - thorpej@netbsd.org
289 		 */
290 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@",
291 		    user);
292 #else
293 		snprintf(anonpass, sizeof(anonpass) - 1, "%s@%s",
294 		    user, hp->h_name);
295 #endif
296 		pass = anonpass;
297 		user = "anonymous";	/* as per RFC 1635 */
298 	}
299 
300 tryagain:
301 	if (retry)
302 		user = "ftp";		/* some servers only allow "ftp" */
303 
304 	while (user == NULL) {
305 		char *myname = getlogin();
306 
307 		if (myname == NULL && (pw = getpwuid(getuid())) != NULL)
308 			myname = pw->pw_name;
309 		if (myname)
310 			fprintf(ttyout, "Name (%s:%s): ", host, myname);
311 		else
312 			fprintf(ttyout, "Name (%s): ", host);
313 		user = myname;
314 		if (fgets(tmp, sizeof(tmp), stdin) != NULL) {
315 			char *p;
316 
317 			if ((p = strchr(tmp, '\n')) != NULL)
318 				*p = '\0';
319 			if (tmp[0] != '\0')
320 				user = tmp;
321 		}
322 	}
323 	n = command("USER %s", user);
324 	if (n == CONTINUE) {
325 		if (pass == NULL)
326 			pass = getpass("Password:");
327 		n = command("PASS %s", pass);
328 	}
329 	if (n == CONTINUE) {
330 		aflag++;
331 		if (acctname == NULL)
332 			acctname = getpass("Account:");
333 		n = command("ACCT %s", acctname);
334 	}
335 	if ((n != COMPLETE) ||
336 	    (!aflag && acctname != NULL && command("ACCT %s", acctname) != COMPLETE)) {
337 		warnx("Login failed.");
338 		if (retry || !anonftp)
339 			return (0);
340 		else
341 			retry = 1;
342 		goto tryagain;
343 	}
344 	if (proxy)
345 		return (1);
346 	connected = -1;
347 	for (n = 0; n < macnum; ++n) {
348 		if (!strcmp("init", macros[n].mac_name)) {
349 			(void)strlcpy(line, "$init", sizeof line);
350 			makeargv();
351 			domacro(margc, margv);
352 			break;
353 		}
354 	}
355 	return (1);
356 }
357 
358 /*
359  * `another' gets another argument, and stores the new argc and argv.
360  * It reverts to the top level (via main.c's intr()) on EOF/error.
361  *
362  * Returns false if no new arguments have been added.
363  */
364 int
another(int * pargc,char *** pargv,const char * prompt)365 another(int *pargc, char ***pargv, const char *prompt)
366 {
367 	int len = strlen(line), ret;
368 
369 	if (len >= sizeof(line) - 3) {
370 		fputs("sorry, arguments too long.\n", ttyout);
371 		intr();
372 	}
373 	fprintf(ttyout, "(%s) ", prompt);
374 	line[len++] = ' ';
375 	if (fgets(&line[len], (int)(sizeof(line) - len), stdin) == NULL)
376 		intr();
377 	len += strlen(&line[len]);
378 	if (len > 0 && line[len - 1] == '\n')
379 		line[len - 1] = '\0';
380 	makeargv();
381 	ret = margc > *pargc;
382 	*pargc = margc;
383 	*pargv = margv;
384 	return (ret);
385 }
386 
387 /*
388  * glob files given in argv[] from the remote server.
389  * if errbuf isn't NULL, store error messages there instead
390  * of writing to the screen.
391  */
392 char *
remglob(char * argv[],int doswitch,char ** errbuf)393 remglob(char *argv[], int doswitch, char **errbuf)
394 {
395 	char temp[MAXPATHLEN], *cp, *lmode;
396 	static char buf[MAXPATHLEN], **args;
397 	static FILE *ftemp = NULL;
398 	int oldverbose, oldhash, fd;
399 
400 	if (!mflag) {
401 		if (!doglob)
402 			args = NULL;
403 		else {
404 			if (ftemp) {
405 				(void)fclose(ftemp);
406 				ftemp = NULL;
407 			}
408 		}
409 		return (NULL);
410 	}
411 	if (!doglob) {
412 		if (args == NULL)
413 			args = argv;
414 		if ((cp = *++args) == NULL)
415 			args = NULL;
416 		return (cp);
417 	}
418 	if (ftemp == NULL) {
419 		int len;
420 
421 		if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0')
422 		    cp = _PATH_TMP;
423 		len = strlen(cp);
424 		if (len + sizeof(TMPFILE) + (cp[len-1] != '/') > sizeof(temp)) {
425 			warnx("unable to create temporary file: %s",
426 			    strerror(ENAMETOOLONG));
427 			return (NULL);
428 		}
429 
430 		(void)strlcpy(temp, cp, sizeof temp);
431 		if (temp[len-1] != '/')
432 			temp[len++] = '/';
433 		(void)strlcpy(&temp[len], TMPFILE, sizeof temp - len);
434 		if ((fd = mkstemp(temp)) < 0) {
435 			warn("unable to create temporary file %s", temp);
436 			return (NULL);
437 		}
438 		close(fd);
439 		oldverbose = verbose;
440 		verbose = (errbuf != NULL) ? -1 : 0;
441 		oldhash = hash;
442 		hash = 0;
443 		if (doswitch)
444 			pswitch(!proxy);
445 		for (lmode = "w"; *++argv != NULL; lmode = "a")
446 			recvrequest("NLST", temp, *argv, lmode, 0, 0);
447 		if ((code / 100) != COMPLETE) {
448 			if (errbuf != NULL)
449 				*errbuf = reply_string;
450 		}
451 		if (doswitch)
452 			pswitch(!proxy);
453 		verbose = oldverbose;
454 		hash = oldhash;
455 		ftemp = fopen(temp, "r");
456 		(void)unlink(temp);
457 		if (ftemp == NULL) {
458 			if (errbuf == NULL)
459 				fputs("can't find list of remote files, oops.\n",
460 				    ttyout);
461 			else
462 				*errbuf =
463 				    "can't find list of remote files, oops.";
464 			return (NULL);
465 		}
466 	}
467 	if (fgets(buf, sizeof(buf), ftemp) == NULL) {
468 		(void)fclose(ftemp);
469 		ftemp = NULL;
470 		return (NULL);
471 	}
472 	if ((cp = strchr(buf, '\n')) != NULL)
473 		*cp = '\0';
474 	return (buf);
475 }
476 
477 int
confirm(const char * cmd,const char * file)478 confirm(const char *cmd, const char *file)
479 {
480 	char str[BUFSIZ];
481 
482 	if (!interactive || confirmrest)
483 		return (1);
484 top:
485 	fprintf(ttyout, "%s %s? ", cmd, file);
486 	(void)fflush(ttyout);
487 	if (fgets(str, sizeof(str), stdin) == NULL)
488 		return (0);
489 	switch (tolower(*str)) {
490 		case 'n':
491 			return (0);
492 		case 'p':
493 			interactive = 0;
494 			fputs("Interactive mode: off.\n", ttyout);
495 			break;
496 		case 'a':
497 			confirmrest = 1;
498 			fprintf(ttyout, "Prompting off for duration of %s.\n", cmd);
499 			break;
500 		case 'y':
501 			return(1);
502 			break;
503 		default:
504 			fprintf(ttyout, "n, y, p, a, are the only acceptable commands!\n");
505 			goto top;
506 			break;
507 	}
508 	return (1);
509 }
510 
511 /*
512  * Glob a local file name specification with
513  * the expectation of a single return value.
514  * Can't control multiple values being expanded
515  * from the expression, we return only the first.
516  */
517 int
globulize(char ** cpp)518 globulize(char **cpp)
519 {
520 	glob_t gl;
521 	int flags;
522 
523 	if (!doglob)
524 		return (1);
525 
526 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
527 	memset(&gl, 0, sizeof(gl));
528 	if (glob(*cpp, flags, NULL, &gl) ||
529 	    gl.gl_pathc == 0) {
530 		warnx("%s: not found", *cpp);
531 		globfree(&gl);
532 		return (0);
533 	}
534 		/* XXX: caller should check if *cpp changed, and
535 		 *	free(*cpp) if that is the case
536 		 */
537 	*cpp = strdup(gl.gl_pathv[0]);
538 	if (*cpp == NULL)
539 		err(1, NULL);
540 	globfree(&gl);
541 	return (1);
542 }
543 
544 /*
545  * determine size of remote file
546  */
547 off_t
remotesize(const char * file,int noisy)548 remotesize(const char *file, int noisy)
549 {
550 	int overbose;
551 	off_t size;
552 
553 	overbose = verbose;
554 	size = -1;
555 	if (debug == 0)
556 		verbose = -1;
557 	if (command("SIZE %s", file) == COMPLETE) {
558 		char *cp, *ep;
559 
560 		cp = strchr(reply_string, ' ');
561 		if (cp != NULL) {
562 			cp++;
563 			size = strtoq(cp, &ep, 10);
564 			if (*ep != '\0' && !isspace(*ep))
565 				size = -1;
566 		}
567 	} else if (noisy && debug == 0) {
568 		fputs(reply_string, ttyout);
569 		fputc('\n', ttyout);
570 	}
571 	verbose = overbose;
572 	return (size);
573 }
574 
575 /*
576  * determine last modification time (in GMT) of remote file
577  */
578 time_t
remotemodtime(const char * file,int noisy)579 remotemodtime(const char *file, int noisy)
580 {
581 	int overbose;
582 	time_t rtime;
583 	int ocode;
584 
585 	overbose = verbose;
586 	ocode = code;
587 	rtime = -1;
588 	if (debug == 0)
589 		verbose = -1;
590 	if (command("MDTM %s", file) == COMPLETE) {
591 		struct tm timebuf;
592 		int yy, mo, day, hour, min, sec;
593  		/*
594  		 * time-val = 14DIGIT [ "." 1*DIGIT ]
595  		 *		YYYYMMDDHHMMSS[.sss]
596  		 * mdtm-response = "213" SP time-val CRLF / error-response
597  		 */
598 		/* TODO: parse .sss as well, use timespecs. */
599 		char *timestr = reply_string;
600 
601 		/* Repair `19%02d' bug on server side */
602 		while (!isspace(*timestr))
603 			timestr++;
604 		while (isspace(*timestr))
605 			timestr++;
606 		if (strncmp(timestr, "191", 3) == 0) {
607  			fprintf(ttyout,
608  	    "Y2K warning! Fixed incorrect time-val received from server.\n");
609 	    		timestr[0] = ' ';
610 			timestr[1] = '2';
611 			timestr[2] = '0';
612 		}
613 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
614 			&day, &hour, &min, &sec);
615 		memset(&timebuf, 0, sizeof(timebuf));
616 		timebuf.tm_sec = sec;
617 		timebuf.tm_min = min;
618 		timebuf.tm_hour = hour;
619 		timebuf.tm_mday = day;
620 		timebuf.tm_mon = mo - 1;
621 		timebuf.tm_year = yy - TM_YEAR_BASE;
622 		timebuf.tm_isdst = -1;
623 		rtime = mktime(&timebuf);
624 		if (rtime == -1 && (noisy || debug != 0))
625 			fprintf(ttyout, "Can't convert %s to a time.\n", reply_string);
626 		else
627 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
628 	} else if (noisy && debug == 0) {
629 		fputs(reply_string, ttyout);
630 		fputc('\n', ttyout);
631 	}
632 	verbose = overbose;
633 	if (rtime == -1)
634 		code = ocode;
635 	return (rtime);
636 }
637 
638 /*
639  * Ensure file is in or under dir.
640  * Returns 1 if so, 0 if not (or an error occurred).
641  */
642 int
fileindir(const char * file,const char * dir)643 fileindir(const char *file, const char *dir)
644 {
645 	char	parentdirbuf[MAXPATHLEN], *parentdir;
646 	char	realdir[MAXPATHLEN];
647 	size_t	dirlen;
648 
649 		 			/* determine parent directory of file */
650 	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
651 	parentdir = dirname(parentdirbuf);
652 	if (strcmp(parentdir, ".") == 0)
653 		return 1;		/* current directory is ok */
654 
655 					/* find the directory */
656 	if (realpath(parentdir, realdir) == NULL) {
657 		warn("Unable to determine real path of `%s'", parentdir);
658 		return 0;
659 	}
660 	if (realdir[0] != '/')		/* relative result is ok */
661 		return 1;
662 
663 	dirlen = strlen(dir);
664 	if (strncmp(realdir, dir, dirlen) == 0 &&
665 	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
666 		return 1;
667 	return 0;
668 }
669 
670 
671 /*
672  * Returns true if this is the controlling/foreground process, else false.
673  */
674 int
foregroundproc(void)675 foregroundproc(void)
676 {
677 	static pid_t pgrp = -1;
678 	int ctty_pgrp;
679 
680 	if (pgrp == -1)
681 		pgrp = getpgrp();
682 
683 	return((ioctl(STDOUT_FILENO, TIOCGPGRP, &ctty_pgrp) != -1 &&
684 	    ctty_pgrp == pgrp));
685 }
686 
687 /* ARGSUSED */
688 static void
updateprogressmeter(int signo)689 updateprogressmeter(int signo)
690 {
691 	int save_errno = errno;
692 
693 	/* update progressmeter if foreground process or in -m mode */
694 	if (foregroundproc() || progress == -1)
695 		progressmeter(0);
696 	errno = save_errno;
697 }
698 
699 /*
700  * Display a transfer progress bar if progress is non-zero.
701  * SIGALRM is hijacked for use by this function.
702  * - Before the transfer, set filesize to size of file (or -1 if unknown),
703  *   and call with flag = -1. This starts the once per second timer,
704  *   and a call to updateprogressmeter() upon SIGALRM.
705  * - During the transfer, updateprogressmeter will call progressmeter
706  *   with flag = 0
707  * - After the transfer, call with flag = 1
708  */
709 static struct timeval start;
710 
711 void
progressmeter(int flag)712 progressmeter(int flag)
713 {
714 	/*
715 	 * List of order of magnitude prefixes.
716 	 * The last is `P', as 2^64 = 16384 Petabytes
717 	 */
718 	static const char prefixes[] = " KMGTP";
719 
720 	static struct timeval lastupdate;
721 	static off_t lastsize;
722 	struct timeval now, td, wait;
723 	off_t cursize, abbrevsize;
724 	double elapsed;
725 	int ratio, barlength, i, remaining;
726 	char buf[512];
727 
728 	if (flag == -1) {
729 		(void)gettimeofday(&start, (struct timezone *)0);
730 		lastupdate = start;
731 		lastsize = restart_point;
732 	}
733 	(void)gettimeofday(&now, (struct timezone *)0);
734 	if (!progress || filesize < 0)
735 		return;
736 	cursize = bytes + restart_point;
737 
738 	if (filesize)
739 		ratio = cursize * 100 / filesize;
740 	else
741 		ratio = 100;
742 	ratio = MAX(ratio, 0);
743 	ratio = MIN(ratio, 100);
744 	snprintf(buf, sizeof(buf), "\r%3d%% ", ratio);
745 
746 	barlength = ttywidth - 30;
747 	if (barlength > 0) {
748 		i = barlength * ratio / 100;
749 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
750 		    "|%.*s%*s|", i,
751 		    "*******************************************************"
752 		    "*******************************************************"
753 		    "*******************************************************"
754 		    "*******************************************************"
755 		    "*******************************************************"
756 		    "*******************************************************"
757 		    "*******************************************************",
758 		    barlength - i, "");
759 	}
760 
761 	i = 0;
762 	abbrevsize = cursize;
763 	while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
764 		i++;
765 		abbrevsize >>= 10;
766 	}
767 	snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
768 	    " %5lld %c%c ", (long long)abbrevsize, prefixes[i],
769 	    prefixes[i] == ' ' ? ' ' : 'B');
770 
771 	timersub(&now, &lastupdate, &wait);
772 	if (cursize > lastsize) {
773 		lastupdate = now;
774 		lastsize = cursize;
775 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
776 			start.tv_sec += wait.tv_sec;
777 			start.tv_usec += wait.tv_usec;
778 		}
779 		wait.tv_sec = 0;
780 	}
781 
782 	timersub(&now, &start, &td);
783 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
784 
785 	if (flag == 1) {
786 		i = (int)elapsed / 3600;
787 		if (i)
788 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
789 			    "%2d:", i);
790 		else
791 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
792 			    "   ");
793 		i = (int)elapsed % 3600;
794 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
795 		    "%02d:%02d    ", i / 60, i % 60);
796 	} else if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
797 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
798 		    "   --:-- ETA");
799 	} else if (wait.tv_sec >= STALLTIME) {
800 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
801 		    " - stalled -");
802 	} else {
803 		remaining = (int)((filesize - restart_point) /
804 				  (bytes / elapsed) - elapsed);
805 		i = remaining / 3600;
806 		if (i)
807 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
808 			    "%2d:", i);
809 		else
810 			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
811 			    "   ");
812 		i = remaining % 3600;
813 		snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
814 		    "%02d:%02d ETA", i / 60, i % 60);
815 	}
816 	(void)write(fileno(ttyout), buf, strlen(buf));
817 
818 	if (flag == -1) {
819 		(void)signal(SIGALRM, updateprogressmeter);
820 		alarmtimer(1);		/* set alarm timer for 1 Hz */
821 	} else if (flag == 1) {
822 		alarmtimer(0);
823 		(void)putc('\n', ttyout);
824 	}
825 	fflush(ttyout);
826 }
827 
828 /*
829  * Display transfer statistics.
830  * Requires start to be initialised by progressmeter(-1),
831  * direction to be defined by xfer routines, and filesize and bytes
832  * to be updated by xfer routines
833  * If siginfo is nonzero, an ETA is displayed, and the output goes to STDERR
834  * instead of TTYOUT.
835  */
836 void
ptransfer(int siginfo)837 ptransfer(int siginfo)
838 {
839 	struct timeval now, td;
840 	double elapsed;
841 	off_t bs;
842 	int meg, remaining, hh;
843 	char buf[100];
844 
845 	if (!verbose && !siginfo)
846 		return;
847 
848 	(void)gettimeofday(&now, (struct timezone *)0);
849 	timersub(&now, &start, &td);
850 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
851 	bs = bytes / (elapsed == 0.0 ? 1 : elapsed);
852 	meg = 0;
853 	if (bs > (1024 * 1024))
854 		meg = 1;
855 	(void)snprintf(buf, sizeof(buf),
856 	    "%lld byte%s %s in %.2f seconds (%.2f %sB/s)\n",
857 	    (long long)bytes, bytes == 1 ? "" : "s", direction, elapsed,
858 	    bs / (1024.0 * (meg ? 1024.0 : 1.0)), meg ? "M" : "K");
859 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
860 	    && bytes + restart_point <= filesize) {
861 		remaining = (int)((filesize - restart_point) /
862 				  (bytes / elapsed) - elapsed);
863 		hh = remaining / 3600;
864 		remaining %= 3600;
865 			/* "buf+len(buf) -1" to overwrite \n */
866 		snprintf(buf + strlen(buf) - 1, sizeof(buf) - strlen(buf),
867 		    "  ETA: %02d:%02d:%02d\n", hh, remaining / 60,
868 		    remaining % 60);
869 	}
870 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, strlen(buf));
871 }
872 
873 /*
874  * List words in stringlist, vertically arranged
875  */
876 void
list_vertical(StringList * sl)877 list_vertical(StringList *sl)
878 {
879 	int i, j, w;
880 	int columns, width, lines;
881 	char *p;
882 
883 	width = 0;
884 
885 	for (i = 0 ; i < sl->sl_cur ; i++) {
886 		w = strlen(sl->sl_str[i]);
887 		if (w > width)
888 			width = w;
889 	}
890 	width = (width + 8) &~ 7;
891 
892 	columns = ttywidth / width;
893 	if (columns == 0)
894 		columns = 1;
895 	lines = (sl->sl_cur + columns - 1) / columns;
896 	for (i = 0; i < lines; i++) {
897 		for (j = 0; j < columns; j++) {
898 			p = sl->sl_str[j * lines + i];
899 			if (p)
900 				fputs(p, ttyout);
901 			if (j * lines + i + lines >= sl->sl_cur) {
902 				putc('\n', ttyout);
903 				break;
904 			}
905 			w = strlen(p);
906 			while (w < width) {
907 				w = (w + 8) &~ 7;
908 				(void)putc('\t', ttyout);
909 			}
910 		}
911 	}
912 }
913 
914 /*
915  * Update the global ttywidth value, using TIOCGWINSZ.
916  */
917 /* ARGSUSED */
918 void
setttywidth(int signo)919 setttywidth(int signo)
920 {
921 	int save_errno = errno;
922 	struct winsize winsize;
923 
924 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1)
925 		ttywidth = winsize.ws_col ? winsize.ws_col : 80;
926 	else
927 		ttywidth = 80;
928 	errno = save_errno;
929 }
930 
931 /*
932  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
933  */
934 void
alarmtimer(int wait)935 alarmtimer(int wait)
936 {
937 	struct itimerval itv;
938 
939 	itv.it_value.tv_sec = wait;
940 	itv.it_value.tv_usec = 0;
941 	itv.it_interval = itv.it_value;
942 	setitimer(ITIMER_REAL, &itv, NULL);
943 }
944 
945 /*
946  * Setup or cleanup EditLine structures
947  */
948 #ifndef SMALL
949 void
controlediting(void)950 controlediting(void)
951 {
952 	HistEvent hev;
953 
954 	if (editing && el == NULL && hist == NULL) {
955 		el = el_init(__progname, stdin, ttyout, stderr); /* init editline */
956 		hist = history_init();		/* init the builtin history */
957 		history(hist, &hev, H_SETSIZE, 100);	/* remember 100 events */
958 		el_set(el, EL_HIST, history, hist);	/* use history */
959 
960 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
961 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
962 
963 		/* add local file completion, bind to TAB */
964 		el_set(el, EL_ADDFN, "ftp-complete",
965 		    "Context sensitive argument completion",
966 		    complete);
967 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
968 
969 		el_source(el, NULL);	/* read ~/.editrc */
970 		el_set(el, EL_SIGNAL, 1);
971 	} else if (!editing) {
972 		if (hist) {
973 			history_end(hist);
974 			hist = NULL;
975 		}
976 		if (el) {
977 			el_end(el);
978 			el = NULL;
979 		}
980 	}
981 }
982 #endif /* !SMALL */
983