xref: /netbsd-src/usr.bin/ftp/util.c (revision 93f9db1b75d415b78f73ed629beeb86235153473)
1 /*	$NetBSD: util.c,v 1.35 1998/11/06 16:53:29 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1998 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9  * NASA Ames Research Center.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the NetBSD
22  *	Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 /*
41  * Copyright (c) 1985, 1989, 1993, 1994
42  *	The Regents of the University of California.  All rights reserved.
43  *
44  * Redistribution and use in source and binary forms, with or without
45  * modification, are permitted provided that the following conditions
46  * are met:
47  * 1. Redistributions of source code must retain the above copyright
48  *    notice, this list of conditions and the following disclaimer.
49  * 2. Redistributions in binary form must reproduce the above copyright
50  *    notice, this list of conditions and the following disclaimer in the
51  *    documentation and/or other materials provided with the distribution.
52  * 3. All advertising materials mentioning features or use of this software
53  *    must display the following acknowledgement:
54  *	This product includes software developed by the University of
55  *	California, Berkeley and its contributors.
56  * 4. 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 #include <sys/cdefs.h>
74 #ifndef lint
75 __RCSID("$NetBSD: util.c,v 1.35 1998/11/06 16:53:29 christos Exp $");
76 #endif /* not lint */
77 
78 /*
79  * FTP User Program -- Misc support routines
80  */
81 #include <sys/types.h>
82 #include <sys/socket.h>
83 #include <sys/ioctl.h>
84 #include <sys/time.h>
85 #include <arpa/ftp.h>
86 
87 #include <ctype.h>
88 #include <err.h>
89 #include <fcntl.h>
90 #include <glob.h>
91 #include <termios.h>
92 #include <signal.h>
93 #include <limits.h>
94 #include <pwd.h>
95 #include <stdio.h>
96 #include <stdlib.h>
97 #include <string.h>
98 #include <time.h>
99 #include <tzfile.h>
100 #include <unistd.h>
101 
102 #include "ftp_var.h"
103 #include "pathnames.h"
104 
105 /*
106  * Connect to peer server and
107  * auto-login, if possible.
108  */
109 void
110 setpeer(argc, argv)
111 	int argc;
112 	char *argv[];
113 {
114 	char *host;
115 	in_port_t port;
116 
117 	if (connected) {
118 		fprintf(ttyout, "Already connected to %s, use close first.\n",
119 		    hostname);
120 		code = -1;
121 		return;
122 	}
123 	if (argc < 2)
124 		(void)another(&argc, &argv, "to");
125 	if (argc < 2 || argc > 3) {
126 		fprintf(ttyout, "usage: %s host-name [port]\n", argv[0]);
127 		code = -1;
128 		return;
129 	}
130 	if (gatemode)
131 		port = gateport;
132 	else
133 		port = ftpport;
134 	if (argc > 2) {
135 		char *ep;
136 		long nport;
137 
138 		nport = strtol(argv[2], &ep, 10);
139 		if (nport < 1 || nport > MAX_IN_PORT_T || *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 
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)strcpy(typename, "ascii"), type = TYPE_A;
174 		curtype = TYPE_A;
175 		(void)strcpy(formname, "non-print"), form = FORM_N;
176 		(void)strcpy(modename, "stream"), mode = MODE_S;
177 		(void)strcpy(structname, "file"), stru = STRU_F;
178 		(void)strcpy(bytename, "8"), bytesize = 8;
179 		if (autologin)
180 			(void)ftp_login(argv[1], NULL, NULL);
181 
182 		overbose = verbose;
183 		if (debug == 0)
184 			verbose = -1;
185 		if (command("SYST") == COMPLETE && overbose) {
186 			char *cp, c;
187 			c = 0;
188 			cp = strchr(reply_string + 4, ' ');
189 			if (cp == NULL)
190 				cp = strchr(reply_string + 4, '\r');
191 			if (cp) {
192 				if (cp[-1] == '.')
193 					cp--;
194 				c = *cp;
195 				*cp = '\0';
196 			}
197 
198 			fprintf(ttyout, "Remote system type is %s.\n",
199 			    reply_string + 4);
200 			if (cp)
201 				*cp = c;
202 		}
203 		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
204 			if (proxy)
205 				unix_proxy = 1;
206 			else
207 				unix_server = 1;
208 			/*
209 			 * Set type to 0 (not specified by user),
210 			 * meaning binary by default, but don't bother
211 			 * telling server.  We can use binary
212 			 * for text files unless changed by the user.
213 			 */
214 			type = 0;
215 			(void)strcpy(typename, "binary");
216 			if (overbose)
217 			    fprintf(ttyout,
218 				"Using %s mode to transfer files.\n",
219 				typename);
220 		} else {
221 			if (proxy)
222 				unix_proxy = 0;
223 			else
224 				unix_server = 0;
225 			if (overbose &&
226 			    !strncmp(reply_string, "215 TOPS20", 10))
227 				fputs(
228 "Remember to set tenex mode when transferring binary files from this machine.\n",
229 				    ttyout);
230 		}
231 		verbose = overbose;
232 	}
233 }
234 
235 /*
236  * login to remote host, using given username & password if supplied
237  */
238 int
239 ftp_login(host, user, pass)
240 	const char *host;
241 	const char *user, *pass;
242 {
243 	char tmp[80];
244 	const char *acct;
245 	char anonpass[MAXLOGNAME + 2]; /* "user@" */
246 	struct passwd *pw;
247 	int n, aflag = 0;
248 
249 	acct = NULL;
250 	if (user == NULL) {
251 		if (ruserpass(host, &user, &pass, &acct) < 0) {
252 			code = -1;
253 			return (0);
254 		}
255 	}
256 
257 	/*
258 	 * Set up arguments for an anonymous FTP session, if necessary.
259 	 */
260 	if ((user == NULL || pass == NULL) && anonftp) {
261 		memset(anonpass, 0, sizeof(anonpass));
262 
263 		/*
264 		 * Set up anonymous login password.
265 		 */
266 		if ((pass = getenv("FTPANONPASS")) == NULL) {
267 			if ((pass = getlogin()) == NULL) {
268 				if ((pw = getpwuid(getuid())) == NULL)
269 					pass = "anonymous";
270 				else
271 					pass = pw->pw_name;
272 			}
273 			/*
274 			 * Every anonymous FTP server I've encountered
275 			 * will accept the string "username@", and will
276 			 * append the hostname itself.  We do this by default
277 			 * since many servers are picky about not having
278 			 * a FQDN in the anonymous password.
279 			 * - thorpej@netbsd.org
280 			 */
281 			snprintf(anonpass, sizeof(anonpass) - 1, "%s@", pass);
282 			pass = anonpass;
283 		}
284 		user = "anonymous";	/* as per RFC 1635 */
285 	}
286 
287 	while (user == NULL) {
288 		const char *myname = getlogin();
289 
290 		if (myname == NULL && (pw = getpwuid(getuid())) != NULL)
291 			myname = pw->pw_name;
292 		if (myname)
293 			fprintf(ttyout, "Name (%s:%s): ", host, myname);
294 		else
295 			fprintf(ttyout, "Name (%s): ", host);
296 		*tmp = '\0';
297 		(void)fgets(tmp, sizeof(tmp) - 1, stdin);
298 		tmp[strlen(tmp) - 1] = '\0';
299 		if (*tmp == '\0')
300 			user = myname;
301 		else
302 			user = tmp;
303 	}
304 	n = command("USER %s", user);
305 	if (n == CONTINUE) {
306 		if (pass == NULL)
307 			pass = getpass("Password:");
308 		n = command("PASS %s", pass);
309 	}
310 	if (n == CONTINUE) {
311 		aflag++;
312 		if (acct == NULL)
313 			acct = getpass("Account:");
314 		n = command("ACCT %s", acct);
315 	}
316 	if ((n != COMPLETE) ||
317 	    (!aflag && acct != NULL && command("ACCT %s", acct) != COMPLETE)) {
318 		warnx("Login failed.");
319 		return (0);
320 	}
321 	if (proxy)
322 		return (1);
323 	connected = -1;
324 	for (n = 0; n < macnum; ++n) {
325 		if (!strcmp("init", macros[n].mac_name)) {
326 			(void)strcpy(line, "$init");
327 			makeargv();
328 			domacro(margc, margv);
329 			break;
330 		}
331 	}
332 	return (1);
333 }
334 
335 /*
336  * `another' gets another argument, and stores the new argc and argv.
337  * It reverts to the top level (via main.c's intr()) on EOF/error.
338  *
339  * Returns false if no new arguments have been added.
340  */
341 int
342 another(pargc, pargv, prompt)
343 	int *pargc;
344 	char ***pargv;
345 	const char *prompt;
346 {
347 	int len = strlen(line), ret;
348 
349 	if (len >= sizeof(line) - 3) {
350 		fputs("sorry, arguments too long.\n", ttyout);
351 		intr();
352 	}
353 	fprintf(ttyout, "(%s) ", prompt);
354 	line[len++] = ' ';
355 	if (fgets(&line[len], sizeof(line) - len, stdin) == NULL)
356 		intr();
357 	len += strlen(&line[len]);
358 	if (len > 0 && line[len - 1] == '\n')
359 		line[len - 1] = '\0';
360 	makeargv();
361 	ret = margc > *pargc;
362 	*pargc = margc;
363 	*pargv = margv;
364 	return (ret);
365 }
366 
367 /*
368  * glob files given in argv[] from the remote server.
369  * if errbuf isn't NULL, store error messages there instead
370  * of writing to the screen.
371  */
372 char *
373 remglob(argv, doswitch, errbuf)
374         char *argv[];
375         int doswitch;
376 	char **errbuf;
377 {
378         char temp[MAXPATHLEN];
379         static char buf[MAXPATHLEN];
380         static FILE *ftemp = NULL;
381         static char **args;
382         int oldverbose, oldhash, fd;
383         char *cp, *mode;
384 
385         if (!mflag) {
386                 if (!doglob)
387                         args = NULL;
388                 else {
389                         if (ftemp) {
390                                 (void)fclose(ftemp);
391                                 ftemp = NULL;
392                         }
393                 }
394                 return (NULL);
395         }
396         if (!doglob) {
397                 if (args == NULL)
398                         args = argv;
399                 if ((cp = *++args) == NULL)
400                         args = NULL;
401                 return (cp);
402         }
403         if (ftemp == NULL) {
404                 (void)snprintf(temp, sizeof(temp), "%s/%s", tmpdir, TMPFILE);
405                 if ((fd = mkstemp(temp)) < 0) {
406                         warn("unable to create temporary file %s", temp);
407                         return (NULL);
408                 }
409                 close(fd);
410                 oldverbose = verbose;
411 		verbose = (errbuf != NULL) ? -1 : 0;
412                 oldhash = hash;
413                 hash = 0;
414                 if (doswitch)
415                         pswitch(!proxy);
416                 for (mode = "w"; *++argv != NULL; mode = "a")
417                         recvrequest("NLST", temp, *argv, mode, 0, 0);
418 		if ((code / 100) != COMPLETE) {
419 			if (errbuf != NULL)
420 				*errbuf = reply_string;
421 		}
422                 if (doswitch)
423                         pswitch(!proxy);
424                 verbose = oldverbose;
425 		hash = oldhash;
426                 ftemp = fopen(temp, "r");
427                 (void)unlink(temp);
428                 if (ftemp == NULL) {
429 			if (errbuf == NULL)
430 				fputs(
431 				    "can't find list of remote files, oops.\n",
432 				    ttyout);
433 			else
434 				*errbuf =
435 				    "can't find list of remote files, oops.";
436                         return (NULL);
437                 }
438         }
439         if (fgets(buf, sizeof(buf), ftemp) == NULL) {
440                 (void)fclose(ftemp);
441 		ftemp = NULL;
442                 return (NULL);
443         }
444         if ((cp = strchr(buf, '\n')) != NULL)
445                 *cp = '\0';
446         return (buf);
447 }
448 
449 int
450 confirm(cmd, file)
451 	const char *cmd, *file;
452 {
453 	char line[BUFSIZ];
454 
455 	if (!interactive || confirmrest)
456 		return (1);
457 	fprintf(ttyout, "%s %s? ", cmd, file);
458 	(void)fflush(ttyout);
459 	if (fgets(line, sizeof(line), stdin) == NULL)
460 		return (0);
461 	switch (tolower(*line)) {
462 		case 'n':
463 			return (0);
464 		case 'p':
465 			interactive = 0;
466 			fputs("Interactive mode: off.\n", ttyout);
467 			break;
468 		case 'a':
469 			confirmrest = 1;
470 			fprintf(ttyout, "Prompting off for duration of %s.\n",
471 			    cmd);
472 			break;
473 	}
474 	return (1);
475 }
476 
477 /*
478  * Glob a local file name specification with
479  * the expectation of a single return value.
480  * Can't control multiple values being expanded
481  * from the expression, we return only the first.
482  */
483 int
484 globulize(cpp)
485 	char **cpp;
486 {
487 	glob_t gl;
488 	int flags;
489 
490 	if (!doglob)
491 		return (1);
492 
493 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
494 	memset(&gl, 0, sizeof(gl));
495 	if (glob(*cpp, flags, NULL, &gl) ||
496 	    gl.gl_pathc == 0) {
497 		warnx("%s: not found", *cpp);
498 		globfree(&gl);
499 		return (0);
500 	}
501 		/* XXX: caller should check if *cpp changed, and
502 		 *	free(*cpp) if that is the case
503 		 */
504 	*cpp = xstrdup(gl.gl_pathv[0]);
505 	globfree(&gl);
506 	return (1);
507 }
508 
509 /*
510  * determine size of remote file
511  */
512 off_t
513 remotesize(file, noisy)
514 	const char *file;
515 	int noisy;
516 {
517 	int overbose;
518 	off_t size;
519 
520 	overbose = verbose;
521 	size = -1;
522 	if (debug == 0)
523 		verbose = -1;
524 	if (command("SIZE %s", file) == COMPLETE) {
525 		char *cp, *ep;
526 
527 		cp = strchr(reply_string, ' ');
528 		if (cp != NULL) {
529 			cp++;
530 #ifndef NO_QUAD
531 			size = strtoq(cp, &ep, 10);
532 #else
533 			size = strtol(cp, &ep, 10);
534 #endif
535 			if (*ep != '\0' && !isspace((unsigned char)*ep))
536 				size = -1;
537 		}
538 	} else if (noisy && debug == 0) {
539 		fputs(reply_string, ttyout);
540 		putc('\n', ttyout);
541 	}
542 	verbose = overbose;
543 	return (size);
544 }
545 
546 /*
547  * determine last modification time (in GMT) of remote file
548  */
549 time_t
550 remotemodtime(file, noisy)
551 	const char *file;
552 	int noisy;
553 {
554 	int overbose;
555 	time_t rtime;
556 	int ocode;
557 
558 	overbose = verbose;
559 	ocode = code;
560 	rtime = -1;
561 	if (debug == 0)
562 		verbose = -1;
563 	if (command("MDTM %s", file) == COMPLETE) {
564 		struct tm timebuf;
565 		int yy, mo, day, hour, min, sec;
566 		sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo,
567 			&day, &hour, &min, &sec);
568 		memset(&timebuf, 0, sizeof(timebuf));
569 		timebuf.tm_sec = sec;
570 		timebuf.tm_min = min;
571 		timebuf.tm_hour = hour;
572 		timebuf.tm_mday = day;
573 		timebuf.tm_mon = mo - 1;
574 		timebuf.tm_year = yy - TM_YEAR_BASE;
575 		timebuf.tm_isdst = -1;
576 		rtime = mktime(&timebuf);
577 		if (rtime == -1 && (noisy || debug != 0))
578 			fprintf(ttyout, "Can't convert %s to a time.\n",
579 			    reply_string);
580 		else
581 #ifndef __SVR4
582 			rtime += timebuf.tm_gmtoff;	/* conv. local -> GMT */
583 #else
584 			rtime -= timezone;
585 #endif
586 	} else if (noisy && debug == 0) {
587 		fputs(reply_string, ttyout);
588 		putc('\n', ttyout);
589 	}
590 	verbose = overbose;
591 	if (rtime == -1)
592 		code = ocode;
593 	return (rtime);
594 }
595 
596 #ifndef	SMALL
597 
598 /*
599  * return non-zero if we're the current foreground process
600  */
601 int
602 foregroundproc()
603 {
604 	static pid_t pgrp = -1;
605 	int ctty_pgrp;
606 
607 	if (pgrp == -1)
608 		pgrp = getpgrp();
609 
610 	return ((ioctl(fileno(ttyout), TIOCGPGRP, &ctty_pgrp) != -1 &&
611 	    ctty_pgrp == (int)pgrp));
612 }
613 
614 
615 static void updateprogressmeter __P((int));
616 
617 static void
618 updateprogressmeter(dummy)
619 	int dummy;
620 {
621 
622 	/*
623 	 * print progress bar only if we are foreground process.
624 	 */
625 	if (foregroundproc())
626 		progressmeter(0);
627 }
628 #endif	/* SMALL */
629 
630 /*
631  * Display a transfer progress bar if progress is non-zero.
632  * SIGALRM is hijacked for use by this function.
633  * - Before the transfer, set filesize to size of file (or -1 if unknown),
634  *   and call with flag = -1. This starts the once per second timer,
635  *   and a call to updateprogressmeter() upon SIGALRM.
636  * - During the transfer, updateprogressmeter will call progressmeter
637  *   with flag = 0
638  * - After the transfer, call with flag = 1
639  */
640 static struct timeval start;
641 static struct timeval lastupdate;
642 
643 void
644 progressmeter(flag)
645 	int flag;
646 {
647 #ifndef	SMALL
648 	/*
649 	 * List of order of magnitude prefixes.
650 	 * The last is `P', as 2^64 = 16384 Petabytes
651 	 */
652 	static const char prefixes[] = "BKMGTP";
653 
654 	static off_t lastsize;
655 	struct timeval now, td, wait;
656 	off_t cursize, abbrevsize, bytespersec;
657 	double elapsed;
658 	int ratio, barlength, i, len, remaining;
659 	char buf[256];
660 
661 	len = 0;
662 
663 	if (flag == -1) {
664 		(void)gettimeofday(&start, NULL);
665 		lastupdate = start;
666 		lastsize = restart_point;
667 	}
668 	(void)gettimeofday(&now, NULL);
669 	if (!progress || filesize <= 0)
670 		return;
671 	cursize = bytes + restart_point;
672 
673 	ratio = (int)((double)cursize * 100.0 / (double)filesize);
674 	ratio = MAX(ratio, 0);
675 	ratio = MIN(ratio, 100);
676 	len += snprintf(buf + len, sizeof(buf) - len, "\r%3d%% ", ratio);
677 
678 	barlength = ttywidth - 42;
679 	if (barlength > 0) {
680 		i = barlength * ratio / 100;
681 		len += snprintf(buf + len, sizeof(buf) - len,
682 		    "|%.*s%*s|", i,
683 "*****************************************************************************"
684 "*****************************************************************************",
685 		    barlength - i, "");
686 	}
687 
688 	abbrevsize = cursize;
689 	for (i = 0; abbrevsize >= 100000 && i < sizeof(prefixes); i++)
690 		abbrevsize >>= 10;
691 	len += snprintf(buf + len, sizeof(buf) - len,
692 #ifndef NO_QUAD
693 	    " %5qd %c%c ", (long long)abbrevsize,
694 #else
695 	    " %5ld %c%c ", (long)abbrevsize,
696 #endif
697 	    i == 0 ? ' ' : prefixes[i],
698 	    i == 0 ? ' ' : 'B');
699 
700 	timersub(&now, &lastupdate, &wait);
701 	if (cursize > lastsize) {
702 		lastupdate = now;
703 		lastsize = cursize;
704 		if (wait.tv_sec >= STALLTIME) {	/* fudge out stalled time */
705 			start.tv_sec += wait.tv_sec;
706 			start.tv_usec += wait.tv_usec;
707 		}
708 		wait.tv_sec = 0;
709 	}
710 
711 	timersub(&now, &start, &td);
712 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
713 
714 	bytespersec = 0;
715 	if (bytes > 0) {
716 		bytespersec = bytes;
717 		if (elapsed > 0.0)
718 			bytespersec /= elapsed;
719 	}
720 	for (i = 0; bytespersec >= 100000 && i < sizeof(prefixes); i++)
721 		bytespersec >>= 10;
722 	len += snprintf(buf + len, sizeof(buf) - len,
723 #ifndef NO_QUAD
724 	    " %5qd %c%s ", (long long)bytespersec,
725 #else
726 	    " %5ld %c%s ", (long)bytespersec,
727 #endif
728 	    prefixes[i],
729 	    i == 0 ? "/s " : "B/s");
730 
731 	if (bytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
732 		len += snprintf(buf + len, sizeof(buf) - len,
733 		    "   --:-- ETA");
734 	} else if (wait.tv_sec >= STALLTIME) {
735 		len += snprintf(buf + len, sizeof(buf) - len,
736 		    " - stalled -");
737 	} else {
738 		remaining = (int)
739 		    ((filesize - restart_point) / (bytes / elapsed) - elapsed);
740 		if (remaining >= 100 * SECSPERHOUR)
741 			len += snprintf(buf + len, sizeof(buf) - len,
742 			    "   --:-- ETA");
743 		else {
744 			i = remaining / SECSPERHOUR;
745 			if (i)
746 				len += snprintf(buf + len, sizeof(buf) - len,
747 				    "%2d:", i);
748 			else
749 				len += snprintf(buf + len, sizeof(buf) - len,
750 				    "   ");
751 			i = remaining % SECSPERHOUR;
752 			len += snprintf(buf + len, sizeof(buf) - len,
753 			    "%02d:%02d ETA", i / 60, i % 60);
754 		}
755 	}
756 	(void)write(fileno(ttyout), buf, len);
757 
758 	if (flag == -1) {
759 		(void)xsignal(SIGALRM, updateprogressmeter);
760 		alarmtimer(1);		/* set alarm timer for 1 Hz */
761 	} else if (flag == 1) {
762 		(void)xsignal(SIGALRM, SIG_DFL);
763 		alarmtimer(0);
764 		(void)putc('\n', ttyout);
765 	}
766 	fflush(ttyout);
767 #endif	/* SMALL */
768 }
769 
770 /*
771  * Display transfer statistics.
772  * Requires start to be initialised by progressmeter(-1),
773  * direction to be defined by xfer routines, and filesize and bytes
774  * to be updated by xfer routines
775  * If siginfo is nonzero, an ETA is displayed, and the output goes to stderr
776  * instead of ttyout.
777  */
778 void
779 ptransfer(siginfo)
780 	int siginfo;
781 {
782 #ifndef	SMALL
783 	struct timeval now, td, wait;
784 	double elapsed;
785 	off_t bytespersec;
786 	int meg, remaining, hh, len;
787 	char buf[100];
788 
789 	if (!verbose && !siginfo)
790 		return;
791 
792 	(void)gettimeofday(&now, NULL);
793 	timersub(&now, &start, &td);
794 	elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
795 	bytespersec = 0;
796 	meg = 0;
797 	if (bytes > 0) {
798 		bytespersec = bytes;
799 		if (elapsed > 0.0)
800 			bytespersec /= elapsed;
801 		if (bytespersec > (1024 * 1024))
802 			meg = 1;
803 	}
804 	len = 0;
805 	len += snprintf(buf + len, sizeof(buf) - len,
806 #ifndef NO_QUAD
807 	    "%qd byte%s %s in ", (long long)bytes,
808 #else
809 	    "%ld byte%s %s in ", (long)bytes,
810 #endif
811 	    bytes == 1 ? "" : "s", direction);
812 	remaining = (int)elapsed;
813 	if (remaining > SECSPERDAY) {
814 		int days;
815 
816 		days = remaining / SECSPERDAY;
817 		remaining %= SECSPERDAY;
818 		len += snprintf(buf + len, sizeof(buf) - len,
819 		    "%d day%s ", days, days == 1 ? "" : "s");
820 	}
821 	hh = remaining / SECSPERHOUR;
822 	remaining %= SECSPERHOUR;
823 	if (hh)
824 		len += snprintf(buf + len, sizeof(buf) - len, "%2d:", hh);
825 	len += snprintf(buf + len, sizeof(buf) - len,
826 	    "%02d:%02d (%.2f %sB/s)", remaining / 60, remaining % 60,
827 	    bytespersec / (1024.0 * (meg ? 1024.0 : 1.0)),
828 	    meg ? "M" : "K");
829 
830 	if (siginfo && bytes > 0 && elapsed > 0.0 && filesize >= 0
831 	    && bytes + restart_point <= filesize) {
832 		remaining = (int)((filesize - restart_point) /
833 				  (bytes / elapsed) - elapsed);
834 		hh = remaining / SECSPERHOUR;
835 		remaining %= SECSPERHOUR;
836 		len += snprintf(buf + len, sizeof(buf) - len, "  ETA: ");
837 		if (hh)
838 			len += snprintf(buf + len, sizeof(buf) - len, "%2d:",
839 			    hh);
840 		len += snprintf(buf + len, sizeof(buf) - len,
841 		    "%02d:%02d", remaining / 60, remaining % 60);
842 		timersub(&now, &lastupdate, &wait);
843 		if (wait.tv_sec >= STALLTIME)
844 			len += snprintf(buf + len, sizeof(buf) - len,
845 			    "  (stalled)");
846 	}
847 	len += snprintf(buf + len, sizeof(buf) - len, "\n");
848 	(void)write(siginfo ? STDERR_FILENO : fileno(ttyout), buf, len);
849 #endif	/* SMALL */
850 }
851 
852 /*
853  * List words in stringlist, vertically arranged
854  */
855 void
856 list_vertical(sl)
857 	StringList *sl;
858 {
859 	int i, j, w;
860 	int columns, width, lines, items;
861 	char *p;
862 
863 	width = items = 0;
864 
865 	for (i = 0 ; i < sl->sl_cur ; i++) {
866 		w = strlen(sl->sl_str[i]);
867 		if (w > width)
868 			width = w;
869 	}
870 	width = (width + 8) &~ 7;
871 
872 	columns = ttywidth / width;
873 	if (columns == 0)
874 		columns = 1;
875 	lines = (sl->sl_cur + columns - 1) / columns;
876 	for (i = 0; i < lines; i++) {
877 		for (j = 0; j < columns; j++) {
878 			p = sl->sl_str[j * lines + i];
879 			if (p)
880 				fputs(p, ttyout);
881 			if (j * lines + i + lines >= sl->sl_cur) {
882 				putc('\n', ttyout);
883 				break;
884 			}
885 			w = strlen(p);
886 			while (w < width) {
887 				w = (w + 8) &~ 7;
888 				(void)putc('\t', ttyout);
889 			}
890 		}
891 	}
892 }
893 
894 /*
895  * Update the global ttywidth value, using TIOCGWINSZ.
896  */
897 void
898 setttywidth(a)
899 	int a;
900 {
901 	struct winsize winsize;
902 
903 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1 &&
904 	    winsize.ws_col != 0)
905 		ttywidth = winsize.ws_col;
906 	else
907 		ttywidth = 80;
908 }
909 
910 /*
911  * Set the SIGALRM interval timer for wait seconds, 0 to disable.
912  */
913 void
914 alarmtimer(wait)
915 	int wait;
916 {
917 	struct itimerval itv;
918 
919 	itv.it_value.tv_sec = wait;
920 	itv.it_value.tv_usec = 0;
921 	itv.it_interval = itv.it_value;
922 	setitimer(ITIMER_REAL, &itv, NULL);
923 }
924 
925 /*
926  * Setup or cleanup EditLine structures
927  */
928 #ifndef SMALL
929 void
930 controlediting()
931 {
932 	if (editing && el == NULL && hist == NULL) {
933 		HistEvent ev;
934 		int editmode;
935 
936 		el = el_init(__progname, stdin, ttyout, stderr);
937 		/* init editline */
938 		hist = history_init();		/* init the builtin history */
939 		history(hist, &ev, H_SETSIZE, 100);/* remember 100 events */
940 		el_set(el, EL_HIST, history, hist);	/* use history */
941 
942 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
943 		el_set(el, EL_PROMPT, prompt);	/* set the prompt function */
944 
945 		/* add local file completion, bind to TAB */
946 		el_set(el, EL_ADDFN, "ftp-complete",
947 		    "Context sensitive argument completion",
948 		    complete);
949 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
950 		el_source(el, NULL);	/* read ~/.editrc */
951 		if ((el_get(el, EL_EDITMODE, &editmode) != -1) && editmode == 0)
952 			editing = 0;	/* the user doesn't want editing,
953 					 * so disable, and let statement
954 					 * below cleanup */
955 		else
956 			el_set(el, EL_SIGNAL, 1);
957 	}
958 	if (!editing) {
959 		if (hist) {
960 			history_end(hist);
961 			hist = NULL;
962 		}
963 		if (el) {
964 			el_end(el);
965 			el = NULL;
966 		}
967 	}
968 }
969 #endif /* !SMALL */
970 
971 /*
972  * Parse the specified socket buffer size.
973  */
974 int
975 getsockbufsize(arg)
976 	const char *arg;
977 {
978 	char *cp;
979 	int val;
980 
981 	if (!isdigit((unsigned char)arg[0]))
982 		return (-1);
983 
984 	val = strtol(arg, &cp, 10);
985 	if (cp != NULL) {
986 		if (cp[1] != '\0')
987 			 return (-1);
988 		if (cp[0] == 'k')
989 			val *= 1024;
990 		if (cp[0] == 'm')
991 			val *= 1024 * 1024;
992 	}
993 
994 	if (val < 0)
995 		return (-1);
996 
997 	return (val);
998 }
999 
1000 /*
1001  * Set up socket buffer sizes before a connection is made.
1002  */
1003 void
1004 setupsockbufsize(sock)
1005 	int sock;
1006 {
1007 	static int sndbuf_default, rcvbuf_default;
1008 	int len, size;
1009 
1010 	/*
1011 	 * Get the default socket buffer sizes if we don't already
1012 	 * have them.  It doesn't matter which socket we do this
1013 	 * to, because on the first call no socket buffer sizes
1014 	 * will have been modified, so we are guaranteed to get
1015 	 * the system defaults.
1016 	 */
1017 	if (sndbuf_default == 0) {
1018 		len = sizeof(sndbuf_default);
1019 		if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndbuf_default,
1020 		    &len) < 0)
1021 			err(1, "unable to get default sndbuf size");
1022 		len = sizeof(rcvbuf_default);
1023 		if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf_default,
1024 		    &len) < 0)
1025 			err(1, "unable to get default rcvbuf size");
1026 
1027 	}
1028 
1029 	size = sndbuf_size ? sndbuf_size : sndbuf_default;
1030 	if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) < 0)
1031 		warn("unable to set sndbuf size %d", size);
1032 
1033 	size = rcvbuf_size ? rcvbuf_size : rcvbuf_default;
1034 	if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) < 0)
1035 		warn("unable to set rcvbuf size %d", size);
1036 }
1037 
1038 /*
1039  * If the socket buffer sizes were not set manually (i.e. came from a
1040  * configuration file), reset them so the right thing will happen on
1041  * subsequent connections.
1042  */
1043 void
1044 resetsockbufsize()
1045 {
1046 
1047 	if (sndbuf_manual == 0)
1048 		sndbuf_size = 0;
1049 	if (rcvbuf_manual == 0)
1050 		rcvbuf_size = 0;
1051 }
1052 
1053 /*
1054  * Internal version of connect(2); sets socket buffer sizes first.
1055  */
1056 int
1057 xconnect(sock, name, namelen)
1058 	int sock;
1059 	const struct sockaddr *name;
1060 	int namelen;
1061 {
1062 
1063 	setupsockbufsize(sock);
1064 	return (connect(sock, name, namelen));
1065 }
1066 
1067 /*
1068  * Internal version of listen(2); sets socket buffer sizes first.
1069  */
1070 int
1071 xlisten(sock, backlog)
1072 	int sock, backlog;
1073 {
1074 
1075 	setupsockbufsize(sock);
1076 	return (listen(sock, backlog));
1077 }
1078 
1079 void *
1080 xmalloc(size)
1081 	size_t size;
1082 {
1083 	void *p;
1084 
1085 	p = malloc(size);
1086 	if (p == NULL)
1087 		err(1, "Unable to allocate %ld bytes of memory", (long)size);
1088 	return (p);
1089 }
1090 
1091 char *
1092 xstrdup(str)
1093 	const char *str;
1094 {
1095 	char *s;
1096 
1097 	if (str == NULL)
1098 		errx(1, "xstrdup() called with NULL argument");
1099 	s = strdup(str);
1100 	if (s == NULL)
1101 		err(1, "Unable to allocate memory for string copy");
1102 	return (s);
1103 }
1104 
1105 sig_t
1106 xsignal(sig, func)
1107 	int sig;
1108 	void (*func) __P((int));
1109 {
1110 	struct sigaction act, oact;
1111 
1112 	act.sa_handler = func;
1113 	sigemptyset(&act.sa_mask);
1114 	act.sa_flags = SA_RESTART;
1115 	if (sigaction(sig, &act, &oact) < 0)
1116 		return (SIG_ERR);
1117 	return (oact.sa_handler);
1118 }
1119