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
95static void updateprogressmeter(int);
96
97/*
98 * Connect to peer server and
99 * auto-login, if possible.
100 */
101void
102setpeer(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 */
215int
216ftp_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
267tryagain:
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
332int
333another(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 */
366char *
367remglob2(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
441again:
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 */
469char *
470remglob(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
478int
479confirm(const char *cmd, const char *file)
480{
481	char str[BUFSIZ];
482
483	if (file && (confirmrest || !interactive))
484		return (1);
485top:
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':
515quit:
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 */
538int
539globulize(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 */
568off_t
569remotesize(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 */
605time_t
606remotemodtime(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 */
679int
680fileindir(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 */
711int
712foregroundproc(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
724static void
725updateprogressmeter(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 */
745static struct timespec start;
746
747char *action;
748
749void
750progressmeter(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 */
920void
921ptransfer(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
967void
968list_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 */
1009void
1010setttywidth(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 */
1025void
1026alarmtimer(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
1042void
1043controlediting(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 */
1080int
1081timed_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
1144done:
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
1156ssize_t
1157http_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