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