1223328Sgavin/*	$NetBSD: util.c,v 1.21 2009/11/15 10:12:37 lukem Exp $	*/
2223328Sgavin/*	from	NetBSD: util.c,v 1.152 2009/07/13 19:05:41 roy Exp	*/
379971Sobrien
479971Sobrien/*-
5223328Sgavin * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
679971Sobrien * All rights reserved.
779971Sobrien *
879971Sobrien * This code is derived from software contributed to The NetBSD Foundation
979971Sobrien * by Luke Mewburn.
1079971Sobrien *
1179971Sobrien * This code is derived from software contributed to The NetBSD Foundation
1279971Sobrien * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
1379971Sobrien * NASA Ames Research Center.
1479971Sobrien *
1579971Sobrien * Redistribution and use in source and binary forms, with or without
1679971Sobrien * modification, are permitted provided that the following conditions
1779971Sobrien * are met:
1879971Sobrien * 1. Redistributions of source code must retain the above copyright
1979971Sobrien *    notice, this list of conditions and the following disclaimer.
2079971Sobrien * 2. Redistributions in binary form must reproduce the above copyright
2179971Sobrien *    notice, this list of conditions and the following disclaimer in the
2279971Sobrien *    documentation and/or other materials provided with the distribution.
2379971Sobrien *
2479971Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2579971Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2679971Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2779971Sobrien * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2879971Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2979971Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3079971Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3179971Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3279971Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3379971Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3479971Sobrien * POSSIBILITY OF SUCH DAMAGE.
3579971Sobrien */
3679971Sobrien
3779971Sobrien/*
3879971Sobrien * Copyright (c) 1985, 1989, 1993, 1994
3979971Sobrien *	The Regents of the University of California.  All rights reserved.
4079971Sobrien *
4179971Sobrien * Redistribution and use in source and binary forms, with or without
4279971Sobrien * modification, are permitted provided that the following conditions
4379971Sobrien * are met:
4479971Sobrien * 1. Redistributions of source code must retain the above copyright
4579971Sobrien *    notice, this list of conditions and the following disclaimer.
4679971Sobrien * 2. Redistributions in binary form must reproduce the above copyright
4779971Sobrien *    notice, this list of conditions and the following disclaimer in the
4879971Sobrien *    documentation and/or other materials provided with the distribution.
49121966Smikeh * 3. Neither the name of the University nor the names of its contributors
5079971Sobrien *    may be used to endorse or promote products derived from this software
5179971Sobrien *    without specific prior written permission.
5279971Sobrien *
5379971Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
5479971Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
5579971Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
5679971Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
5779971Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
5879971Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
5979971Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
6079971Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
6179971Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6279971Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6379971Sobrien * SUCH DAMAGE.
6479971Sobrien */
6579971Sobrien
66223328Sgavin#include "tnftp.h"
67223328Sgavin
68223328Sgavin#if 0	/* tnftp */
69223328Sgavin
70116424Smikeh#include <sys/cdefs.h>
71116424Smikeh#ifndef lint
72223328Sgavin__RCSID(" NetBSD: util.c,v 1.152 2009/07/13 19:05:41 roy Exp  ");
73116424Smikeh#endif /* not lint */
74116424Smikeh
7579971Sobrien/*
7679971Sobrien * FTP User Program -- Misc support routines
7779971Sobrien */
78142129Smikeh#include <sys/param.h>
79116424Smikeh#include <sys/socket.h>
80116424Smikeh#include <sys/ioctl.h>
81116424Smikeh#include <sys/time.h>
82116424Smikeh#include <netinet/in.h>
83116424Smikeh#include <arpa/ftp.h>
8479971Sobrien
85116424Smikeh#include <ctype.h>
86116424Smikeh#include <err.h>
87116424Smikeh#include <errno.h>
88116424Smikeh#include <fcntl.h>
89116424Smikeh#include <glob.h>
90116424Smikeh#include <signal.h>
91223328Sgavin#include <libgen.h>
92116424Smikeh#include <limits.h>
93116424Smikeh#include <netdb.h>
94116424Smikeh#include <stdio.h>
95116424Smikeh#include <stdlib.h>
96116424Smikeh#include <string.h>
97116424Smikeh#include <termios.h>
98116424Smikeh#include <time.h>
99223328Sgavin#include <tzfile.h>
100116424Smikeh#include <unistd.h>
10179971Sobrien
102223328Sgavin#endif	/* tnftp */
103223328Sgavin
10479971Sobrien#include "ftp_var.h"
10579971Sobrien
10679971Sobrien/*
10779971Sobrien * Connect to peer server and auto-login, if possible.
10879971Sobrien */
10979971Sobrienvoid
11079971Sobriensetpeer(int argc, char *argv[])
11179971Sobrien{
11279971Sobrien	char *host;
113223328Sgavin	const char *port;
11479971Sobrien
11579971Sobrien	if (argc == 0)
11679971Sobrien		goto usage;
11779971Sobrien	if (connected) {
11879971Sobrien		fprintf(ttyout, "Already connected to %s, use close first.\n",
11979971Sobrien		    hostname);
12079971Sobrien		code = -1;
12179971Sobrien		return;
12279971Sobrien	}
12379971Sobrien	if (argc < 2)
12479971Sobrien		(void)another(&argc, &argv, "to");
12579971Sobrien	if (argc < 2 || argc > 3) {
12679971Sobrien usage:
127223328Sgavin		UPRINTF("usage: %s host-name [port]\n", argv[0]);
12879971Sobrien		code = -1;
12979971Sobrien		return;
13079971Sobrien	}
13179971Sobrien	if (gatemode)
13279971Sobrien		port = gateport;
13379971Sobrien	else
13479971Sobrien		port = ftpport;
13579971Sobrien	if (argc > 2)
13679971Sobrien		port = argv[2];
13779971Sobrien
13879971Sobrien	if (gatemode) {
13979971Sobrien		if (gateserver == NULL || *gateserver == '\0')
140223328Sgavin			errx(1, "main: gateserver not defined");
14179971Sobrien		host = hookup(gateserver, port);
14279971Sobrien	} else
14379971Sobrien		host = hookup(argv[1], port);
14479971Sobrien
14579971Sobrien	if (host) {
14679971Sobrien		if (gatemode && verbose) {
14779971Sobrien			fprintf(ttyout,
14879971Sobrien			    "Connecting via pass-through server %s\n",
14979971Sobrien			    gateserver);
15079971Sobrien		}
15179971Sobrien
15279971Sobrien		connected = 1;
15379971Sobrien		/*
15479971Sobrien		 * Set up defaults for FTP.
15579971Sobrien		 */
15679971Sobrien		(void)strlcpy(typename, "ascii", sizeof(typename));
15779971Sobrien		type = TYPE_A;
15879971Sobrien		curtype = TYPE_A;
15979971Sobrien		(void)strlcpy(formname, "non-print", sizeof(formname));
16079971Sobrien		form = FORM_N;
16179971Sobrien		(void)strlcpy(modename, "stream", sizeof(modename));
16279971Sobrien		mode = MODE_S;
16379971Sobrien		(void)strlcpy(structname, "file", sizeof(structname));
16479971Sobrien		stru = STRU_F;
16579971Sobrien		(void)strlcpy(bytename, "8", sizeof(bytename));
16679971Sobrien		bytesize = 8;
16779971Sobrien		if (autologin)
16879971Sobrien			(void)ftp_login(argv[1], NULL, NULL);
16979971Sobrien	}
17079971Sobrien}
17179971Sobrien
17279971Sobrienstatic void
173223328Sgavinparse_feat(const char *fline)
17479971Sobrien{
17579971Sobrien
176121966Smikeh			/*
177121966Smikeh			 * work-around broken ProFTPd servers that can't
178223328Sgavin			 * even obey RFC2389.
179121966Smikeh			 */
180223328Sgavin	while (*fline && isspace((int)*fline))
181223328Sgavin		fline++;
182121966Smikeh
183223328Sgavin	if (strcasecmp(fline, "MDTM") == 0)
18479971Sobrien		features[FEAT_MDTM] = 1;
185223328Sgavin	else if (strncasecmp(fline, "MLST", sizeof("MLST") - 1) == 0) {
18679971Sobrien		features[FEAT_MLST] = 1;
187223328Sgavin	} else if (strcasecmp(fline, "REST STREAM") == 0)
18879971Sobrien		features[FEAT_REST_STREAM] = 1;
189223328Sgavin	else if (strcasecmp(fline, "SIZE") == 0)
19079971Sobrien		features[FEAT_SIZE] = 1;
191223328Sgavin	else if (strcasecmp(fline, "TVFS") == 0)
19279971Sobrien		features[FEAT_TVFS] = 1;
19379971Sobrien}
19479971Sobrien
19579971Sobrien/*
19679971Sobrien * Determine the remote system type (SYST) and features (FEAT).
19779971Sobrien * Call after a successful login (i.e, connected = -1)
19879971Sobrien */
19979971Sobrienvoid
20079971Sobriengetremoteinfo(void)
20179971Sobrien{
20279971Sobrien	int overbose, i;
20379971Sobrien
20479971Sobrien	overbose = verbose;
205223328Sgavin	if (ftp_debug == 0)
20679971Sobrien		verbose = -1;
20779971Sobrien
20879971Sobrien			/* determine remote system type */
20979971Sobrien	if (command("SYST") == COMPLETE) {
21079971Sobrien		if (overbose) {
21179971Sobrien			char *cp, c;
21279971Sobrien
21379971Sobrien			c = 0;
21479971Sobrien			cp = strchr(reply_string + 4, ' ');
21579971Sobrien			if (cp == NULL)
21679971Sobrien				cp = strchr(reply_string + 4, '\r');
21779971Sobrien			if (cp) {
21879971Sobrien				if (cp[-1] == '.')
21979971Sobrien					cp--;
22079971Sobrien				c = *cp;
22179971Sobrien				*cp = '\0';
22279971Sobrien			}
22379971Sobrien
22479971Sobrien			fprintf(ttyout, "Remote system type is %s.\n",
22579971Sobrien			    reply_string + 4);
22679971Sobrien			if (cp)
22779971Sobrien				*cp = c;
22879971Sobrien		}
22979971Sobrien		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
23079971Sobrien			if (proxy)
23179971Sobrien				unix_proxy = 1;
23279971Sobrien			else
23379971Sobrien				unix_server = 1;
23479971Sobrien			/*
23579971Sobrien			 * Set type to 0 (not specified by user),
23679971Sobrien			 * meaning binary by default, but don't bother
23779971Sobrien			 * telling server.  We can use binary
23879971Sobrien			 * for text files unless changed by the user.
23979971Sobrien			 */
24079971Sobrien			type = 0;
24179971Sobrien			(void)strlcpy(typename, "binary", sizeof(typename));
24279971Sobrien			if (overbose)
24379971Sobrien			    fprintf(ttyout,
24479971Sobrien				"Using %s mode to transfer files.\n",
24579971Sobrien				typename);
24679971Sobrien		} else {
24779971Sobrien			if (proxy)
24879971Sobrien				unix_proxy = 0;
24979971Sobrien			else
25079971Sobrien				unix_server = 0;
25179971Sobrien			if (overbose &&
25279971Sobrien			    !strncmp(reply_string, "215 TOPS20", 10))
25379971Sobrien				fputs(
25479971Sobrien"Remember to set tenex mode when transferring binary files from this machine.\n",
25579971Sobrien				    ttyout);
25679971Sobrien		}
25779971Sobrien	}
25879971Sobrien
25979971Sobrien			/* determine features (if any) */
26079971Sobrien	for (i = 0; i < FEAT_max; i++)
26179971Sobrien		features[i] = -1;
26279971Sobrien	reply_callback = parse_feat;
26379971Sobrien	if (command("FEAT") == COMPLETE) {
26479971Sobrien		for (i = 0; i < FEAT_max; i++) {
26579971Sobrien			if (features[i] == -1)
26679971Sobrien				features[i] = 0;
26779971Sobrien		}
26879971Sobrien		features[FEAT_FEAT] = 1;
26979971Sobrien	} else
27079971Sobrien		features[FEAT_FEAT] = 0;
271223328Sgavin#ifndef NO_DEBUG
272223328Sgavin	if (ftp_debug) {
273121966Smikeh#define DEBUG_FEAT(x) fprintf(ttyout, "features[" #x "] = %d\n", features[(x)])
274121966Smikeh		DEBUG_FEAT(FEAT_FEAT);
275121966Smikeh		DEBUG_FEAT(FEAT_MDTM);
276121966Smikeh		DEBUG_FEAT(FEAT_MLST);
277121966Smikeh		DEBUG_FEAT(FEAT_REST_STREAM);
278121966Smikeh		DEBUG_FEAT(FEAT_SIZE);
279121966Smikeh		DEBUG_FEAT(FEAT_TVFS);
280121966Smikeh#undef DEBUG_FEAT
281121966Smikeh	}
282223328Sgavin#endif
28379971Sobrien	reply_callback = NULL;
28479971Sobrien
28579971Sobrien	verbose = overbose;
28679971Sobrien}
28779971Sobrien
28879971Sobrien/*
28979971Sobrien * Reset the various variables that indicate connection state back to
29079971Sobrien * disconnected settings.
29179971Sobrien * The caller is responsible for issuing any commands to the remote server
29279971Sobrien * to perform a clean shutdown before this is invoked.
29379971Sobrien */
29479971Sobrienvoid
29579971Sobriencleanuppeer(void)
29679971Sobrien{
29779971Sobrien
29879971Sobrien	if (cout)
29979971Sobrien		(void)fclose(cout);
30079971Sobrien	cout = NULL;
30179971Sobrien	connected = 0;
30279971Sobrien	unix_server = 0;
30379971Sobrien	unix_proxy = 0;
30479971Sobrien			/*
30579971Sobrien			 * determine if anonftp was specifically set with -a
30679971Sobrien			 * (1), or implicitly set by auto_fetch() (2). in the
30779971Sobrien			 * latter case, disable after the current xfer
30879971Sobrien			 */
30979971Sobrien	if (anonftp == 2)
31079971Sobrien		anonftp = 0;
31179971Sobrien	data = -1;
31279971Sobrien	epsv4bad = 0;
313223328Sgavin	epsv6bad = 0;
31479971Sobrien	if (username)
31579971Sobrien		free(username);
31679971Sobrien	username = NULL;
31779971Sobrien	if (!proxy)
31879971Sobrien		macnum = 0;
31979971Sobrien}
32079971Sobrien
32179971Sobrien/*
32279971Sobrien * Top-level signal handler for interrupted commands.
32379971Sobrien */
32479971Sobrienvoid
325142129Smikehintr(int signo)
32679971Sobrien{
32779971Sobrien
328142129Smikeh	sigint_raised = 1;
32979971Sobrien	alarmtimer(0);
33079971Sobrien	if (fromatty)
33179971Sobrien		write(fileno(ttyout), "\n", 1);
33279971Sobrien	siglongjmp(toplevel, 1);
33379971Sobrien}
33479971Sobrien
33579971Sobrien/*
33679971Sobrien * Signal handler for lost connections; cleanup various elements of
33779971Sobrien * the connection state, and call cleanuppeer() to finish it off.
33879971Sobrien */
33979971Sobrienvoid
34079971Sobrienlostpeer(int dummy)
34179971Sobrien{
34279971Sobrien	int oerrno = errno;
34379971Sobrien
34479971Sobrien	alarmtimer(0);
34579971Sobrien	if (connected) {
34679971Sobrien		if (cout != NULL) {
34779971Sobrien			(void)shutdown(fileno(cout), 1+1);
34879971Sobrien			(void)fclose(cout);
34979971Sobrien			cout = NULL;
35079971Sobrien		}
35179971Sobrien		if (data >= 0) {
35279971Sobrien			(void)shutdown(data, 1+1);
35379971Sobrien			(void)close(data);
35479971Sobrien			data = -1;
35579971Sobrien		}
35679971Sobrien		connected = 0;
35779971Sobrien	}
35879971Sobrien	pswitch(1);
35979971Sobrien	if (connected) {
36079971Sobrien		if (cout != NULL) {
36179971Sobrien			(void)shutdown(fileno(cout), 1+1);
36279971Sobrien			(void)fclose(cout);
36379971Sobrien			cout = NULL;
36479971Sobrien		}
36579971Sobrien		connected = 0;
36679971Sobrien	}
36779971Sobrien	proxflag = 0;
36879971Sobrien	pswitch(0);
36979971Sobrien	cleanuppeer();
37079971Sobrien	errno = oerrno;
37179971Sobrien}
37279971Sobrien
37379971Sobrien
37479971Sobrien/*
37579971Sobrien * Login to remote host, using given username & password if supplied.
37679971Sobrien * Return non-zero if successful.
37779971Sobrien */
37879971Sobrienint
379223328Sgavinftp_login(const char *host, const char *luser, const char *lpass)
38079971Sobrien{
38179971Sobrien	char tmp[80];
382223328Sgavin	char *fuser, *pass, *facct, *p;
383223328Sgavin	char emptypass[] = "";
384223328Sgavin	const char *errormsg;
385223328Sgavin	int n, aflag, rval, nlen;
38679971Sobrien
387223328Sgavin	aflag = rval = 0;
388223328Sgavin	fuser = pass = facct = NULL;
389223328Sgavin	if (luser)
390223328Sgavin		fuser = ftp_strdup(luser);
391223328Sgavin	if (lpass)
392223328Sgavin		pass = ftp_strdup(lpass);
39379971Sobrien
394223328Sgavin	DPRINTF("ftp_login: user `%s' pass `%s' host `%s'\n",
395223328Sgavin	    STRorNULL(fuser), STRorNULL(pass), STRorNULL(host));
39679971Sobrien
39779971Sobrien	/*
39879971Sobrien	 * Set up arguments for an anonymous FTP session, if necessary.
39979971Sobrien	 */
40079971Sobrien	if (anonftp) {
401223328Sgavin		FREEPTR(fuser);
402223328Sgavin		fuser = ftp_strdup("anonymous");	/* as per RFC1635 */
403223328Sgavin		FREEPTR(pass);
404223328Sgavin		pass = ftp_strdup(getoptionvalue("anonpass"));
40579971Sobrien	}
40679971Sobrien
407223328Sgavin	if (ruserpass(host, &fuser, &pass, &facct) < 0) {
40879971Sobrien		code = -1;
40979971Sobrien		goto cleanup_ftp_login;
41079971Sobrien	}
41179971Sobrien
412223328Sgavin	while (fuser == NULL) {
41398247Smikeh		if (localname)
41498247Smikeh			fprintf(ttyout, "Name (%s:%s): ", host, localname);
41579971Sobrien		else
41679971Sobrien			fprintf(ttyout, "Name (%s): ", host);
417223328Sgavin		errormsg = NULL;
418223328Sgavin		nlen = get_line(stdin, tmp, sizeof(tmp), &errormsg);
419223328Sgavin		if (nlen < 0) {
420223328Sgavin			fprintf(ttyout, "%s; %s aborted.\n", errormsg, "login");
42179971Sobrien			code = -1;
42279971Sobrien			goto cleanup_ftp_login;
423223328Sgavin		} else if (nlen == 0) {
424223328Sgavin			fuser = ftp_strdup(localname);
425223328Sgavin		} else {
426223328Sgavin			fuser = ftp_strdup(tmp);
42779971Sobrien		}
42879971Sobrien	}
42979971Sobrien
43079971Sobrien	if (gatemode) {
43179971Sobrien		char *nuser;
432223328Sgavin		size_t len;
43379971Sobrien
434223328Sgavin		len = strlen(fuser) + 1 + strlen(host) + 1;
435223328Sgavin		nuser = ftp_malloc(len);
436223328Sgavin		(void)strlcpy(nuser, fuser, len);
43779971Sobrien		(void)strlcat(nuser, "@",  len);
43879971Sobrien		(void)strlcat(nuser, host, len);
439223328Sgavin		FREEPTR(fuser);
440223328Sgavin		fuser = nuser;
44179971Sobrien	}
44279971Sobrien
443223328Sgavin	n = command("USER %s", fuser);
44479971Sobrien	if (n == CONTINUE) {
44579971Sobrien		if (pass == NULL) {
446223328Sgavin			p = getpass("Password: ");
447223328Sgavin			if (p == NULL)
448223328Sgavin				p = emptypass;
449223328Sgavin			pass = ftp_strdup(p);
450223328Sgavin			memset(p, 0, strlen(p));
45179971Sobrien		}
45279971Sobrien		n = command("PASS %s", pass);
453223328Sgavin		memset(pass, 0, strlen(pass));
45479971Sobrien	}
45579971Sobrien	if (n == CONTINUE) {
45679971Sobrien		aflag++;
457223328Sgavin		if (facct == NULL) {
458223328Sgavin			p = getpass("Account: ");
459223328Sgavin			if (p == NULL)
460223328Sgavin				p = emptypass;
461223328Sgavin			facct = ftp_strdup(p);
462223328Sgavin			memset(p, 0, strlen(p));
46379971Sobrien		}
464223328Sgavin		if (facct[0] == '\0') {
465223328Sgavin			warnx("Login failed");
46679971Sobrien			goto cleanup_ftp_login;
46779971Sobrien		}
468223328Sgavin		n = command("ACCT %s", facct);
469223328Sgavin		memset(facct, 0, strlen(facct));
47079971Sobrien	}
47179971Sobrien	if ((n != COMPLETE) ||
472223328Sgavin	    (!aflag && facct != NULL && command("ACCT %s", facct) != COMPLETE)) {
473223328Sgavin		warnx("Login failed");
47479971Sobrien		goto cleanup_ftp_login;
47579971Sobrien	}
47679971Sobrien	rval = 1;
477223328Sgavin	username = ftp_strdup(fuser);
47879971Sobrien	if (proxy)
47979971Sobrien		goto cleanup_ftp_login;
48079971Sobrien
48179971Sobrien	connected = -1;
48279971Sobrien	getremoteinfo();
48379971Sobrien	for (n = 0; n < macnum; ++n) {
48479971Sobrien		if (!strcmp("init", macros[n].mac_name)) {
48579971Sobrien			(void)strlcpy(line, "$init", sizeof(line));
48679971Sobrien			makeargv();
48779971Sobrien			domacro(margc, margv);
48879971Sobrien			break;
48979971Sobrien		}
49079971Sobrien	}
491142129Smikeh	updatelocalcwd();
492142129Smikeh	updateremotecwd();
49379971Sobrien
49479971Sobrien cleanup_ftp_login:
495223328Sgavin	FREEPTR(fuser);
496223328Sgavin	if (pass != NULL)
497223328Sgavin		memset(pass, 0, strlen(pass));
498223328Sgavin	FREEPTR(pass);
499223328Sgavin	if (facct != NULL)
500223328Sgavin		memset(facct, 0, strlen(facct));
501223328Sgavin	FREEPTR(facct);
50279971Sobrien	return (rval);
50379971Sobrien}
50479971Sobrien
50579971Sobrien/*
50679971Sobrien * `another' gets another argument, and stores the new argc and argv.
50779971Sobrien * It reverts to the top level (via intr()) on EOF/error.
50879971Sobrien *
50979971Sobrien * Returns false if no new arguments have been added.
51079971Sobrien */
51179971Sobrienint
512223328Sgavinanother(int *pargc, char ***pargv, const char *aprompt)
51379971Sobrien{
514223328Sgavin	const char	*errormsg;
515223328Sgavin	int		ret, nlen;
516223328Sgavin	size_t		len;
51779971Sobrien
518223328Sgavin	len = strlen(line);
51979971Sobrien	if (len >= sizeof(line) - 3) {
520223328Sgavin		fputs("Sorry, arguments too long.\n", ttyout);
52179971Sobrien		intr(0);
52279971Sobrien	}
523223328Sgavin	fprintf(ttyout, "(%s) ", aprompt);
52479971Sobrien	line[len++] = ' ';
525223328Sgavin	errormsg = NULL;
526223328Sgavin	nlen = get_line(stdin, line + len, sizeof(line)-len, &errormsg);
527223328Sgavin	if (nlen < 0) {
528223328Sgavin		fprintf(ttyout, "%s; %s aborted.\n", errormsg, "operation");
52979971Sobrien		intr(0);
53079971Sobrien	}
531223328Sgavin	len += nlen;
53279971Sobrien	makeargv();
53379971Sobrien	ret = margc > *pargc;
53479971Sobrien	*pargc = margc;
53579971Sobrien	*pargv = margv;
53679971Sobrien	return (ret);
53779971Sobrien}
53879971Sobrien
53979971Sobrien/*
54079971Sobrien * glob files given in argv[] from the remote server.
54179971Sobrien * if errbuf isn't NULL, store error messages there instead
54279971Sobrien * of writing to the screen.
54379971Sobrien */
54479971Sobrienchar *
545223328Sgavinremglob(char *argv[], int doswitch, const char **errbuf)
54679971Sobrien{
547223328Sgavin	static char buf[MAXPATHLEN];
548223328Sgavin	static FILE *ftemp = NULL;
549223328Sgavin	static char **args;
550223328Sgavin	char temp[MAXPATHLEN];
551223328Sgavin	int oldverbose, oldhash, oldprogress, fd;
552223328Sgavin	char *cp;
553223328Sgavin	const char *rmode;
554223328Sgavin	size_t len;
55579971Sobrien
556223328Sgavin	if (!mflag || !connected) {
557223328Sgavin		if (!doglob)
558223328Sgavin			args = NULL;
559223328Sgavin		else {
560223328Sgavin			if (ftemp) {
561223328Sgavin				(void)fclose(ftemp);
562223328Sgavin				ftemp = NULL;
563223328Sgavin			}
564223328Sgavin		}
565223328Sgavin		return (NULL);
566223328Sgavin	}
567223328Sgavin	if (!doglob) {
568223328Sgavin		if (args == NULL)
569223328Sgavin			args = argv;
570223328Sgavin		if ((cp = *++args) == NULL)
571223328Sgavin			args = NULL;
572223328Sgavin		return (cp);
573223328Sgavin	}
574223328Sgavin	if (ftemp == NULL) {
57579971Sobrien		len = strlcpy(temp, tmpdir, sizeof(temp));
57679971Sobrien		if (temp[len - 1] != '/')
57779971Sobrien			(void)strlcat(temp, "/", sizeof(temp));
57879971Sobrien		(void)strlcat(temp, TMPFILE, sizeof(temp));
579223328Sgavin		if ((fd = mkstemp(temp)) < 0) {
580223328Sgavin			warn("Unable to create temporary file `%s'", temp);
581223328Sgavin			return (NULL);
582223328Sgavin		}
583223328Sgavin		close(fd);
584223328Sgavin		oldverbose = verbose;
58579971Sobrien		verbose = (errbuf != NULL) ? -1 : 0;
586223328Sgavin		oldhash = hash;
58798247Smikeh		oldprogress = progress;
588223328Sgavin		hash = 0;
58998247Smikeh		progress = 0;
590223328Sgavin		if (doswitch)
591223328Sgavin			pswitch(!proxy);
592223328Sgavin		for (rmode = "w"; *++argv != NULL; rmode = "a")
593223328Sgavin			recvrequest("NLST", temp, *argv, rmode, 0, 0);
59479971Sobrien		if ((code / 100) != COMPLETE) {
59579971Sobrien			if (errbuf != NULL)
59679971Sobrien				*errbuf = reply_string;
59779971Sobrien		}
598223328Sgavin		if (doswitch)
599223328Sgavin			pswitch(!proxy);
600223328Sgavin		verbose = oldverbose;
60179971Sobrien		hash = oldhash;
60298247Smikeh		progress = oldprogress;
603223328Sgavin		ftemp = fopen(temp, "r");
604223328Sgavin		(void)unlink(temp);
605223328Sgavin		if (ftemp == NULL) {
60679971Sobrien			if (errbuf == NULL)
607223328Sgavin				warnx("Can't find list of remote files");
60879971Sobrien			else
60979971Sobrien				*errbuf =
610223328Sgavin				    "Can't find list of remote files";
611223328Sgavin			return (NULL);
612223328Sgavin		}
613223328Sgavin	}
614223328Sgavin	if (fgets(buf, sizeof(buf), ftemp) == NULL) {
615223328Sgavin		(void)fclose(ftemp);
61679971Sobrien		ftemp = NULL;
617223328Sgavin		return (NULL);
618223328Sgavin	}
619223328Sgavin	if ((cp = strchr(buf, '\n')) != NULL)
620223328Sgavin		*cp = '\0';
621223328Sgavin	return (buf);
62279971Sobrien}
62379971Sobrien
62479971Sobrien/*
62579971Sobrien * Glob a local file name specification with the expectation of a single
62679971Sobrien * return value. Can't control multiple values being expanded from the
62779971Sobrien * expression, we return only the first.
62879971Sobrien * Returns NULL on error, or a pointer to a buffer containing the filename
62979971Sobrien * that's the caller's responsiblity to free(3) when finished with.
63079971Sobrien */
63179971Sobrienchar *
63279971Sobrienglobulize(const char *pattern)
63379971Sobrien{
63479971Sobrien	glob_t gl;
63579971Sobrien	int flags;
63679971Sobrien	char *p;
63779971Sobrien
63879971Sobrien	if (!doglob)
639223328Sgavin		return (ftp_strdup(pattern));
64079971Sobrien
64179971Sobrien	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
64279971Sobrien	memset(&gl, 0, sizeof(gl));
64379971Sobrien	if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
644223328Sgavin		warnx("Glob pattern `%s' not found", pattern);
64579971Sobrien		globfree(&gl);
64679971Sobrien		return (NULL);
64779971Sobrien	}
648223328Sgavin	p = ftp_strdup(gl.gl_pathv[0]);
64979971Sobrien	globfree(&gl);
65079971Sobrien	return (p);
65179971Sobrien}
65279971Sobrien
65379971Sobrien/*
65479971Sobrien * determine size of remote file
65579971Sobrien */
65679971Sobrienoff_t
65779971Sobrienremotesize(const char *file, int noisy)
65879971Sobrien{
65979971Sobrien	int overbose, r;
66079971Sobrien	off_t size;
66179971Sobrien
66279971Sobrien	overbose = verbose;
66379971Sobrien	size = -1;
664223328Sgavin	if (ftp_debug == 0)
66579971Sobrien		verbose = -1;
66679971Sobrien	if (! features[FEAT_SIZE]) {
66779971Sobrien		if (noisy)
66879971Sobrien			fprintf(ttyout,
66979971Sobrien			    "SIZE is not supported by remote server.\n");
67079971Sobrien		goto cleanup_remotesize;
67179971Sobrien	}
67279971Sobrien	r = command("SIZE %s", file);
67379971Sobrien	if (r == COMPLETE) {
67479971Sobrien		char *cp, *ep;
67579971Sobrien
67679971Sobrien		cp = strchr(reply_string, ' ');
67779971Sobrien		if (cp != NULL) {
67879971Sobrien			cp++;
67979971Sobrien			size = STRTOLL(cp, &ep, 10);
68079971Sobrien			if (*ep != '\0' && !isspace((unsigned char)*ep))
68179971Sobrien				size = -1;
68279971Sobrien		}
68379971Sobrien	} else {
68479971Sobrien		if (r == ERROR && code == 500 && features[FEAT_SIZE] == -1)
68579971Sobrien			features[FEAT_SIZE] = 0;
686223328Sgavin		if (noisy && ftp_debug == 0) {
68779971Sobrien			fputs(reply_string, ttyout);
68879971Sobrien			putc('\n', ttyout);
68979971Sobrien		}
69079971Sobrien	}
69179971Sobrien cleanup_remotesize:
69279971Sobrien	verbose = overbose;
69379971Sobrien	return (size);
69479971Sobrien}
69579971Sobrien
69679971Sobrien/*
69779971Sobrien * determine last modification time (in GMT) of remote file
69879971Sobrien */
69979971Sobrientime_t
70079971Sobrienremotemodtime(const char *file, int noisy)
70179971Sobrien{
70279971Sobrien	int	overbose, ocode, r;
70379971Sobrien	time_t	rtime;
70479971Sobrien
70579971Sobrien	overbose = verbose;
70679971Sobrien	ocode = code;
70779971Sobrien	rtime = -1;
708223328Sgavin	if (ftp_debug == 0)
70979971Sobrien		verbose = -1;
71079971Sobrien	if (! features[FEAT_MDTM]) {
71179971Sobrien		if (noisy)
71279971Sobrien			fprintf(ttyout,
71379971Sobrien			    "MDTM is not supported by remote server.\n");
71479971Sobrien		goto cleanup_parse_time;
71579971Sobrien	}
71679971Sobrien	r = command("MDTM %s", file);
71779971Sobrien	if (r == COMPLETE) {
71879971Sobrien		struct tm timebuf;
71979971Sobrien		char *timestr, *frac;
72079971Sobrien
72179971Sobrien		/*
72279971Sobrien		 * time-val = 14DIGIT [ "." 1*DIGIT ]
72379971Sobrien		 *		YYYYMMDDHHMMSS[.sss]
72479971Sobrien		 * mdtm-response = "213" SP time-val CRLF / error-response
72579971Sobrien		 */
72679971Sobrien		timestr = reply_string + 4;
72779971Sobrien
72879971Sobrien					/*
72979971Sobrien					 * parse fraction.
73079971Sobrien					 * XXX: ignored for now
73179971Sobrien					 */
73279971Sobrien		frac = strchr(timestr, '\r');
73379971Sobrien		if (frac != NULL)
73479971Sobrien			*frac = '\0';
73579971Sobrien		frac = strchr(timestr, '.');
73679971Sobrien		if (frac != NULL)
73779971Sobrien			*frac++ = '\0';
73879971Sobrien		if (strlen(timestr) == 15 && strncmp(timestr, "191", 3) == 0) {
73979971Sobrien			/*
74079971Sobrien			 * XXX:	Workaround for lame ftpd's that return
74179971Sobrien			 *	`19100' instead of `2000'
74279971Sobrien			 */
74379971Sobrien			fprintf(ttyout,
74479971Sobrien	    "Y2K warning! Incorrect time-val `%s' received from server.\n",
74579971Sobrien			    timestr);
74679971Sobrien			timestr++;
74779971Sobrien			timestr[0] = '2';
74879971Sobrien			timestr[1] = '0';
74979971Sobrien			fprintf(ttyout, "Converted to `%s'\n", timestr);
75079971Sobrien		}
751223328Sgavin		memset(&timebuf, 0, sizeof(timebuf));
75279971Sobrien		if (strlen(timestr) != 14 ||
753223328Sgavin		    (strptime(timestr, "%Y%m%d%H%M%S", &timebuf) == NULL)) {
75479971Sobrien bad_parse_time:
75579971Sobrien			fprintf(ttyout, "Can't parse time `%s'.\n", timestr);
75679971Sobrien			goto cleanup_parse_time;
75779971Sobrien		}
75879971Sobrien		timebuf.tm_isdst = -1;
75979971Sobrien		rtime = timegm(&timebuf);
76079971Sobrien		if (rtime == -1) {
761223328Sgavin			if (noisy || ftp_debug != 0)
76279971Sobrien				goto bad_parse_time;
76379971Sobrien			else
76479971Sobrien				goto cleanup_parse_time;
765223328Sgavin		} else {
766223328Sgavin			DPRINTF("remotemodtime: parsed date `%s' as " LLF
767223328Sgavin			    ", %s",
768223328Sgavin			    timestr, (LLT)rtime,
769223328Sgavin			    rfc2822time(localtime(&rtime)));
770223328Sgavin		}
77179971Sobrien	} else {
77279971Sobrien		if (r == ERROR && code == 500 && features[FEAT_MDTM] == -1)
77379971Sobrien			features[FEAT_MDTM] = 0;
774223328Sgavin		if (noisy && ftp_debug == 0) {
77579971Sobrien			fputs(reply_string, ttyout);
77679971Sobrien			putc('\n', ttyout);
77779971Sobrien		}
77879971Sobrien	}
77979971Sobrien cleanup_parse_time:
78079971Sobrien	verbose = overbose;
78179971Sobrien	if (rtime == -1)
78279971Sobrien		code = ocode;
78379971Sobrien	return (rtime);
78479971Sobrien}
78579971Sobrien
78679971Sobrien/*
787223328Sgavin * Format tm in an RFC2822 compatible manner, with a trailing \n.
788223328Sgavin * Returns a pointer to a static string containing the result.
789223328Sgavin */
790223328Sgavinconst char *
791223328Sgavinrfc2822time(const struct tm *tm)
792223328Sgavin{
793223328Sgavin	static char result[50];
794223328Sgavin
795223328Sgavin	if (strftime(result, sizeof(result),
796223328Sgavin	    "%a, %d %b %Y %H:%M:%S %z\n", tm) == 0)
797223328Sgavin		errx(1, "Can't convert RFC2822 time: buffer too small");
798223328Sgavin	return result;
799223328Sgavin}
800223328Sgavin
801223328Sgavin/*
802142129Smikeh * Update global `localcwd', which contains the state of the local cwd
80379971Sobrien */
80479971Sobrienvoid
805142129Smikehupdatelocalcwd(void)
80679971Sobrien{
807142129Smikeh
808142129Smikeh	if (getcwd(localcwd, sizeof(localcwd)) == NULL)
809142129Smikeh		localcwd[0] = '\0';
810223328Sgavin	DPRINTF("updatelocalcwd: got `%s'\n", localcwd);
811142129Smikeh}
812142129Smikeh
813142129Smikeh/*
814142129Smikeh * Update global `remotecwd', which contains the state of the remote cwd
815142129Smikeh */
816142129Smikehvoid
817142129Smikehupdateremotecwd(void)
818142129Smikeh{
819223328Sgavin	int	 overbose, ocode;
820223328Sgavin	size_t	 i;
82179971Sobrien	char	*cp;
82279971Sobrien
82379971Sobrien	overbose = verbose;
82479971Sobrien	ocode = code;
825223328Sgavin	if (ftp_debug == 0)
82679971Sobrien		verbose = -1;
82779971Sobrien	if (command("PWD") != COMPLETE)
828142129Smikeh		goto badremotecwd;
82979971Sobrien	cp = strchr(reply_string, ' ');
83079971Sobrien	if (cp == NULL || cp[0] == '\0' || cp[1] != '"')
831142129Smikeh		goto badremotecwd;
83279971Sobrien	cp += 2;
833142129Smikeh	for (i = 0; *cp && i < sizeof(remotecwd) - 1; i++, cp++) {
83479971Sobrien		if (cp[0] == '"') {
83579971Sobrien			if (cp[1] == '"')
83679971Sobrien				cp++;
83779971Sobrien			else
83879971Sobrien				break;
83979971Sobrien		}
840142129Smikeh		remotecwd[i] = *cp;
84179971Sobrien	}
842142129Smikeh	remotecwd[i] = '\0';
843223328Sgavin	DPRINTF("updateremotecwd: got `%s'\n", remotecwd);
844142129Smikeh	goto cleanupremotecwd;
845142129Smikeh badremotecwd:
846142129Smikeh	remotecwd[0]='\0';
847142129Smikeh cleanupremotecwd:
84879971Sobrien	verbose = overbose;
84979971Sobrien	code = ocode;
85079971Sobrien}
85179971Sobrien
852142129Smikeh/*
853142129Smikeh * Ensure file is in or under dir.
854142129Smikeh * Returns 1 if so, 0 if not (or an error occurred).
855142129Smikeh */
856142129Smikehint
857142129Smikehfileindir(const char *file, const char *dir)
858142129Smikeh{
859223328Sgavin	char	parentdirbuf[PATH_MAX+1], *parentdir;
860223328Sgavin	char	realdir[PATH_MAX+1];
861142129Smikeh	size_t	dirlen;
86279971Sobrien
863223328Sgavin					/* determine parent directory of file */
864223328Sgavin	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
865223328Sgavin	parentdir = dirname(parentdirbuf);
866223328Sgavin	if (strcmp(parentdir, ".") == 0)
867223328Sgavin		return 1;		/* current directory is ok */
868223328Sgavin
869223328Sgavin					/* find the directory */
870223328Sgavin	if (realpath(parentdir, realdir) == NULL) {
871223328Sgavin		warn("Unable to determine real path of `%s'", parentdir);
872142129Smikeh		return 0;
873142129Smikeh	}
874223328Sgavin	if (realdir[0] != '/')		/* relative result is ok */
875142129Smikeh		return 1;
876142129Smikeh	dirlen = strlen(dir);
877223328Sgavin	if (strncmp(realdir, dir, dirlen) == 0 &&
878223328Sgavin	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
879142129Smikeh		return 1;
880142129Smikeh	return 0;
881142129Smikeh}
882142129Smikeh
88379971Sobrien/*
88479971Sobrien * List words in stringlist, vertically arranged
88579971Sobrien */
88679971Sobrienvoid
88779971Sobrienlist_vertical(StringList *sl)
88879971Sobrien{
889223328Sgavin	size_t i, j;
890223328Sgavin	size_t columns, lines;
89179971Sobrien	char *p;
892223328Sgavin	size_t w, width;
89379971Sobrien
89479971Sobrien	width = 0;
89579971Sobrien
89679971Sobrien	for (i = 0 ; i < sl->sl_cur ; i++) {
89779971Sobrien		w = strlen(sl->sl_str[i]);
89879971Sobrien		if (w > width)
89979971Sobrien			width = w;
90079971Sobrien	}
90179971Sobrien	width = (width + 8) &~ 7;
90279971Sobrien
90379971Sobrien	columns = ttywidth / width;
90479971Sobrien	if (columns == 0)
90579971Sobrien		columns = 1;
90679971Sobrien	lines = (sl->sl_cur + columns - 1) / columns;
90779971Sobrien	for (i = 0; i < lines; i++) {
90879971Sobrien		for (j = 0; j < columns; j++) {
90979971Sobrien			p = sl->sl_str[j * lines + i];
91079971Sobrien			if (p)
91179971Sobrien				fputs(p, ttyout);
91279971Sobrien			if (j * lines + i + lines >= sl->sl_cur) {
91379971Sobrien				putc('\n', ttyout);
91479971Sobrien				break;
91579971Sobrien			}
916223328Sgavin			if (p) {
917223328Sgavin				w = strlen(p);
918223328Sgavin				while (w < width) {
919223328Sgavin					w = (w + 8) &~ 7;
920223328Sgavin					(void)putc('\t', ttyout);
921223328Sgavin				}
92279971Sobrien			}
92379971Sobrien		}
92479971Sobrien	}
92579971Sobrien}
92679971Sobrien
92779971Sobrien/*
92879971Sobrien * Update the global ttywidth value, using TIOCGWINSZ.
92979971Sobrien */
93079971Sobrienvoid
93179971Sobriensetttywidth(int a)
93279971Sobrien{
93379971Sobrien	struct winsize winsize;
93479971Sobrien	int oerrno = errno;
93579971Sobrien
93679971Sobrien	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1 &&
93779971Sobrien	    winsize.ws_col != 0)
93879971Sobrien		ttywidth = winsize.ws_col;
93979971Sobrien	else
94079971Sobrien		ttywidth = 80;
94179971Sobrien	errno = oerrno;
94279971Sobrien}
94379971Sobrien
94479971Sobrien/*
94579971Sobrien * Change the rate limit up (SIGUSR1) or down (SIGUSR2)
94679971Sobrien */
94779971Sobrienvoid
94879971Sobriencrankrate(int sig)
94979971Sobrien{
95079971Sobrien
95179971Sobrien	switch (sig) {
95279971Sobrien	case SIGUSR1:
95379971Sobrien		if (rate_get)
95479971Sobrien			rate_get += rate_get_incr;
95579971Sobrien		if (rate_put)
95679971Sobrien			rate_put += rate_put_incr;
95779971Sobrien		break;
95879971Sobrien	case SIGUSR2:
95979971Sobrien		if (rate_get && rate_get > rate_get_incr)
96079971Sobrien			rate_get -= rate_get_incr;
96179971Sobrien		if (rate_put && rate_put > rate_put_incr)
96279971Sobrien			rate_put -= rate_put_incr;
96379971Sobrien		break;
96479971Sobrien	default:
96579971Sobrien		err(1, "crankrate invoked with unknown signal: %d", sig);
96679971Sobrien	}
96779971Sobrien}
96879971Sobrien
96979971Sobrien
97079971Sobrien/*
97179971Sobrien * Setup or cleanup EditLine structures
97279971Sobrien */
97379971Sobrien#ifndef NO_EDITCOMPLETE
97479971Sobrienvoid
97579971Sobriencontrolediting(void)
97679971Sobrien{
97779971Sobrien	if (editing && el == NULL && hist == NULL) {
97879971Sobrien		HistEvent ev;
97979971Sobrien		int editmode;
98079971Sobrien
98198247Smikeh		el = el_init(getprogname(), stdin, ttyout, stderr);
98279971Sobrien		/* init editline */
98379971Sobrien		hist = history_init();		/* init the builtin history */
98479971Sobrien		history(hist, &ev, H_SETSIZE, 100);/* remember 100 events */
98579971Sobrien		el_set(el, EL_HIST, history, hist);	/* use history */
98679971Sobrien
98779971Sobrien		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
98879971Sobrien		el_set(el, EL_PROMPT, prompt);	/* set the prompt functions */
98979971Sobrien		el_set(el, EL_RPROMPT, rprompt);
99079971Sobrien
99179971Sobrien		/* add local file completion, bind to TAB */
99279971Sobrien		el_set(el, EL_ADDFN, "ftp-complete",
99379971Sobrien		    "Context sensitive argument completion",
99479971Sobrien		    complete);
99579971Sobrien		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
99679971Sobrien		el_source(el, NULL);	/* read ~/.editrc */
99779971Sobrien		if ((el_get(el, EL_EDITMODE, &editmode) != -1) && editmode == 0)
99879971Sobrien			editing = 0;	/* the user doesn't want editing,
99979971Sobrien					 * so disable, and let statement
100079971Sobrien					 * below cleanup */
100179971Sobrien		else
100279971Sobrien			el_set(el, EL_SIGNAL, 1);
100379971Sobrien	}
100479971Sobrien	if (!editing) {
100579971Sobrien		if (hist) {
100679971Sobrien			history_end(hist);
100779971Sobrien			hist = NULL;
100879971Sobrien		}
100979971Sobrien		if (el) {
101079971Sobrien			el_end(el);
101179971Sobrien			el = NULL;
101279971Sobrien		}
101379971Sobrien	}
101479971Sobrien}
101579971Sobrien#endif /* !NO_EDITCOMPLETE */
101679971Sobrien
101779971Sobrien/*
101879971Sobrien * Convert the string `arg' to an int, which may have an optional SI suffix
101979971Sobrien * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
102079971Sobrien */
102179971Sobrienint
102279971Sobrienstrsuftoi(const char *arg)
102379971Sobrien{
102479971Sobrien	char *cp;
102579971Sobrien	long val;
102679971Sobrien
102779971Sobrien	if (!isdigit((unsigned char)arg[0]))
102879971Sobrien		return (-1);
102979971Sobrien
103079971Sobrien	val = strtol(arg, &cp, 10);
103179971Sobrien	if (cp != NULL) {
103279971Sobrien		if (cp[0] != '\0' && cp[1] != '\0')
103379971Sobrien			 return (-1);
103479971Sobrien		switch (tolower((unsigned char)cp[0])) {
103579971Sobrien		case '\0':
103679971Sobrien		case 'b':
103779971Sobrien			break;
103879971Sobrien		case 'k':
103979971Sobrien			val <<= 10;
104079971Sobrien			break;
104179971Sobrien		case 'm':
104279971Sobrien			val <<= 20;
104379971Sobrien			break;
104479971Sobrien		case 'g':
104579971Sobrien			val <<= 30;
104679971Sobrien			break;
104779971Sobrien		default:
104879971Sobrien			return (-1);
104979971Sobrien		}
105079971Sobrien	}
105179971Sobrien	if (val < 0 || val > INT_MAX)
105279971Sobrien		return (-1);
105379971Sobrien
105479971Sobrien	return (val);
105579971Sobrien}
105679971Sobrien
105779971Sobrien/*
105879971Sobrien * Set up socket buffer sizes before a connection is made.
105979971Sobrien */
106079971Sobrienvoid
106179971Sobriensetupsockbufsize(int sock)
106279971Sobrien{
1063232779Sgavin	socklen_t slen;
106479971Sobrien
1065232779Sgavin	if (0 == rcvbuf_size) {
1066232779Sgavin		slen = sizeof(rcvbuf_size);
1067232779Sgavin		if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF,
1068232779Sgavin		    (void *)&rcvbuf_size, &slen) == -1)
1069232779Sgavin			err(1, "Unable to determine rcvbuf size");
1070232779Sgavin		if (rcvbuf_size <= 0)
1071232779Sgavin			rcvbuf_size = 8 * 1024;
1072232779Sgavin		if (rcvbuf_size > 8 * 1024 * 1024)
1073232779Sgavin			rcvbuf_size = 8 * 1024 * 1024;
1074232779Sgavin		DPRINTF("setupsockbufsize: rcvbuf_size determined as %d\n",
1075232779Sgavin		    rcvbuf_size);
1076232779Sgavin	}
1077232779Sgavin	if (0 == sndbuf_size) {
1078232779Sgavin		slen = sizeof(sndbuf_size);
1079232779Sgavin		if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF,
1080232779Sgavin		    (void *)&sndbuf_size, &slen) == -1)
1081232779Sgavin			err(1, "Unable to determine sndbuf size");
1082232779Sgavin		if (sndbuf_size <= 0)
1083232779Sgavin			sndbuf_size = 8 * 1024;
1084232779Sgavin		if (sndbuf_size > 8 * 1024 * 1024)
1085232779Sgavin			sndbuf_size = 8 * 1024 * 1024;
1086232779Sgavin		DPRINTF("setupsockbufsize: sndbuf_size determined as %d\n",
1087232779Sgavin		    sndbuf_size);
1088232779Sgavin	}
1089232779Sgavin
1090146309Smikeh	if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
1091146309Smikeh	    (void *)&sndbuf_size, sizeof(sndbuf_size)) == -1)
1092223328Sgavin		warn("Unable to set sndbuf size %d", sndbuf_size);
109379971Sobrien
1094146309Smikeh	if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
1095146309Smikeh	    (void *)&rcvbuf_size, sizeof(rcvbuf_size)) == -1)
1096223328Sgavin		warn("Unable to set rcvbuf size %d", rcvbuf_size);
109779971Sobrien}
109879971Sobrien
109979971Sobrien/*
110079971Sobrien * Copy characters from src into dst, \ quoting characters that require it
110179971Sobrien */
110279971Sobrienvoid
110379971Sobrienftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
110479971Sobrien{
1105223328Sgavin	size_t	di, si;
110679971Sobrien
110779971Sobrien	for (di = si = 0;
110879971Sobrien	    src[si] != '\0' && di < dstlen && si < srclen;
110979971Sobrien	    di++, si++) {
111079971Sobrien		switch (src[si]) {
111179971Sobrien		case '\\':
111279971Sobrien		case ' ':
111379971Sobrien		case '\t':
111479971Sobrien		case '\r':
111579971Sobrien		case '\n':
111679971Sobrien		case '"':
111779971Sobrien			dst[di++] = '\\';
111879971Sobrien			if (di >= dstlen)
111979971Sobrien				break;
112079971Sobrien			/* FALLTHROUGH */
112179971Sobrien		default:
112279971Sobrien			dst[di] = src[si];
112379971Sobrien		}
112479971Sobrien	}
112579971Sobrien	dst[di] = '\0';
112679971Sobrien}
112779971Sobrien
112879971Sobrien/*
112979971Sobrien * Copy src into buf (which is len bytes long), expanding % sequences.
113079971Sobrien */
113179971Sobrienvoid
113279971Sobrienformatbuf(char *buf, size_t len, const char *src)
113379971Sobrien{
1134223328Sgavin	const char	*p, *p2, *q;
1135223328Sgavin	size_t		 i;
1136223328Sgavin	int		 op, updirs, pdirs;
113779971Sobrien
113879971Sobrien#define ADDBUF(x) do { \
113979971Sobrien		if (i >= len - 1) \
114079971Sobrien			goto endbuf; \
114179971Sobrien		buf[i++] = (x); \
114279971Sobrien	} while (0)
114379971Sobrien
114479971Sobrien	p = src;
114579971Sobrien	for (i = 0; *p; p++) {
114679971Sobrien		if (*p != '%') {
114779971Sobrien			ADDBUF(*p);
114879971Sobrien			continue;
114979971Sobrien		}
115079971Sobrien		p++;
115179971Sobrien
115279971Sobrien		switch (op = *p) {
115379971Sobrien
115479971Sobrien		case '/':
115579971Sobrien		case '.':
115679971Sobrien		case 'c':
1157142129Smikeh			p2 = connected ? remotecwd : "";
115879971Sobrien			updirs = pdirs = 0;
115979971Sobrien
116079971Sobrien			/* option to determine fixed # of dirs from path */
116179971Sobrien			if (op == '.' || op == 'c') {
116279971Sobrien				int skip;
116379971Sobrien
116479971Sobrien				q = p2;
116579971Sobrien				while (*p2)		/* calc # of /'s */
116679971Sobrien					if (*p2++ == '/')
116779971Sobrien						updirs++;
116879971Sobrien				if (p[1] == '0') {	/* print <x> or ... */
116979971Sobrien					pdirs = 1;
117079971Sobrien					p++;
117179971Sobrien				}
117279971Sobrien				if (p[1] >= '1' && p[1] <= '9') {
117379971Sobrien							/* calc # to skip  */
117479971Sobrien					skip = p[1] - '0';
117579971Sobrien					p++;
117679971Sobrien				} else
117779971Sobrien					skip = 1;
117879971Sobrien
117979971Sobrien				updirs -= skip;
118079971Sobrien				while (skip-- > 0) {
118179971Sobrien					while ((p2 > q) && (*p2 != '/'))
118279971Sobrien						p2--;	/* back up */
118379971Sobrien					if (skip && p2 > q)
118479971Sobrien						p2--;
118579971Sobrien				}
118679971Sobrien				if (*p2 == '/' && p2 != q)
118779971Sobrien					p2++;
118879971Sobrien			}
118979971Sobrien
119079971Sobrien			if (updirs > 0 && pdirs) {
119179971Sobrien				if (i >= len - 5)
119279971Sobrien					break;
119379971Sobrien				if (op == '.') {
119479971Sobrien					ADDBUF('.');
119579971Sobrien					ADDBUF('.');
119679971Sobrien					ADDBUF('.');
119779971Sobrien				} else {
119879971Sobrien					ADDBUF('/');
119979971Sobrien					ADDBUF('<');
120079971Sobrien					if (updirs > 9) {
120179971Sobrien						ADDBUF('9');
120279971Sobrien						ADDBUF('+');
120379971Sobrien					} else
120479971Sobrien						ADDBUF('0' + updirs);
120579971Sobrien					ADDBUF('>');
120679971Sobrien				}
120779971Sobrien			}
120879971Sobrien			for (; *p2; p2++)
120979971Sobrien				ADDBUF(*p2);
121079971Sobrien			break;
121179971Sobrien
121279971Sobrien		case 'M':
121379971Sobrien		case 'm':
1214223328Sgavin			for (p2 = connected && hostname ? hostname : "-";
1215116424Smikeh			    *p2 ; p2++) {
121679971Sobrien				if (op == 'm' && *p2 == '.')
121779971Sobrien					break;
121879971Sobrien				ADDBUF(*p2);
121979971Sobrien			}
122079971Sobrien			break;
122179971Sobrien
122279971Sobrien		case 'n':
122379971Sobrien			for (p2 = connected ? username : "-"; *p2 ; p2++)
122479971Sobrien				ADDBUF(*p2);
122579971Sobrien			break;
122679971Sobrien
122779971Sobrien		case '%':
122879971Sobrien			ADDBUF('%');
122979971Sobrien			break;
123079971Sobrien
123179971Sobrien		default:		/* display unknown codes literally */
123279971Sobrien			ADDBUF('%');
123379971Sobrien			ADDBUF(op);
123479971Sobrien			break;
123579971Sobrien
123679971Sobrien		}
123779971Sobrien	}
123879971Sobrien endbuf:
123979971Sobrien	buf[i] = '\0';
124079971Sobrien}
124179971Sobrien
124279971Sobrien/*
124379971Sobrien * Determine if given string is an IPv6 address or not.
124479971Sobrien * Return 1 for yes, 0 for no
124579971Sobrien */
124679971Sobrienint
124779971Sobrienisipv6addr(const char *addr)
124879971Sobrien{
124979971Sobrien	int rv = 0;
125079971Sobrien#ifdef INET6
125179971Sobrien	struct addrinfo hints, *res;
125279971Sobrien
125379971Sobrien	memset(&hints, 0, sizeof(hints));
1254223328Sgavin	hints.ai_family = AF_INET6;
125579971Sobrien	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
125679971Sobrien	hints.ai_flags = AI_NUMERICHOST;
125779971Sobrien	if (getaddrinfo(addr, "0", &hints, &res) != 0)
125879971Sobrien		rv = 0;
125979971Sobrien	else {
126079971Sobrien		rv = 1;
126179971Sobrien		freeaddrinfo(res);
126279971Sobrien	}
1263223328Sgavin	DPRINTF("isipv6addr: got %d for %s\n", rv, addr);
126479971Sobrien#endif
126579971Sobrien	return (rv == 1) ? 1 : 0;
126679971Sobrien}
126779971Sobrien
1268223328Sgavin/*
1269223328Sgavin * Read a line from the FILE stream into buf/buflen using fgets(), so up
1270223328Sgavin * to buflen-1 chars will be read and the result will be NUL terminated.
1271223328Sgavin * If the line has a trailing newline it will be removed.
1272223328Sgavin * If the line is too long, excess characters will be read until
1273223328Sgavin * newline/EOF/error.
1274223328Sgavin * If EOF/error occurs or a too-long line is encountered and errormsg
1275223328Sgavin * isn't NULL, it will be changed to a description of the problem.
1276223328Sgavin * (The EOF message has a leading \n for cosmetic purposes).
1277223328Sgavin * Returns:
1278223328Sgavin *	>=0	length of line (excluding trailing newline) if all ok
1279223328Sgavin *	-1	error occurred
1280223328Sgavin *	-2	EOF encountered
1281223328Sgavin *	-3	line was too long
1282223328Sgavin */
1283223328Sgavinint
1284223328Sgavinget_line(FILE *stream, char *buf, size_t buflen, const char **errormsg)
1285223328Sgavin{
1286223328Sgavin	int	rv, ch;
1287223328Sgavin	size_t	len;
128879971Sobrien
1289223328Sgavin	if (fgets(buf, buflen, stream) == NULL) {
1290223328Sgavin		if (feof(stream)) {	/* EOF */
1291223328Sgavin			rv = -2;
1292223328Sgavin			if (errormsg)
1293223328Sgavin				*errormsg = "\nEOF received";
1294223328Sgavin		} else  {		/* error */
1295223328Sgavin			rv = -1;
1296223328Sgavin			if (errormsg)
1297223328Sgavin				*errormsg = "Error encountered";
1298223328Sgavin		}
1299223328Sgavin		clearerr(stream);
1300223328Sgavin		return rv;
1301223328Sgavin	}
1302223328Sgavin	len = strlen(buf);
1303223328Sgavin	if (buf[len-1] == '\n') {	/* clear any trailing newline */
1304223328Sgavin		buf[--len] = '\0';
1305223328Sgavin	} else if (len == buflen-1) {	/* line too long */
1306223328Sgavin		while ((ch = getchar()) != '\n' && ch != EOF)
1307223328Sgavin			continue;
1308223328Sgavin		if (errormsg)
1309223328Sgavin			*errormsg = "Input line is too long";
1310223328Sgavin		clearerr(stream);
1311223328Sgavin		return -3;
1312223328Sgavin	}
1313223328Sgavin	if (errormsg)
1314223328Sgavin		*errormsg = NULL;
1315223328Sgavin	return len;
1316223328Sgavin}
1317223328Sgavin
131879971Sobrien/*
1319223328Sgavin * Internal version of connect(2); sets socket buffer sizes,
1320223328Sgavin * binds to a specific local address (if set), and
1321146309Smikeh * supports a connection timeout using a non-blocking connect(2) with
1322146309Smikeh * a poll(2).
1323146309Smikeh * Socket fcntl flags are temporarily updated to include O_NONBLOCK;
1324146309Smikeh * these will not be reverted on connection failure.
1325223328Sgavin * Returns 0 on success, or -1 upon failure (with an appropriate
1326223328Sgavin * error message displayed.)
132779971Sobrien */
132879971Sobrienint
1329223328Sgavinftp_connect(int sock, const struct sockaddr *name, socklen_t namelen)
133079971Sobrien{
1331146309Smikeh	int		flags, rv, timeout, error;
1332146309Smikeh	socklen_t	slen;
1333146309Smikeh	struct timeval	endtime, now, td;
1334146309Smikeh	struct pollfd	pfd[1];
1335223328Sgavin	char		hname[NI_MAXHOST];
1336223328Sgavin	char		sname[NI_MAXSERV];
133779971Sobrien
133879971Sobrien	setupsockbufsize(sock);
1339223328Sgavin	if (getnameinfo(name, namelen,
1340223328Sgavin	    hname, sizeof(hname), sname, sizeof(sname),
1341223328Sgavin	    NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
1342223328Sgavin		strlcpy(hname, "?", sizeof(hname));
1343223328Sgavin		strlcpy(sname, "?", sizeof(sname));
1344223328Sgavin	}
1345128671Smikeh
1346223328Sgavin	if (bindai != NULL) {			/* bind to specific addr */
1347223328Sgavin		struct addrinfo *ai;
1348146309Smikeh
1349223328Sgavin		for (ai = bindai; ai != NULL; ai = ai->ai_next) {
1350223328Sgavin			if (ai->ai_family == name->sa_family)
1351223328Sgavin				break;
1352223328Sgavin		}
1353223328Sgavin		if (ai == NULL)
1354223328Sgavin			ai = bindai;
1355223328Sgavin		if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
1356223328Sgavin			char	bname[NI_MAXHOST];
1357223328Sgavin			int	saveerr;
1358223328Sgavin
1359223328Sgavin			saveerr = errno;
1360223328Sgavin			if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
1361223328Sgavin			    bname, sizeof(bname), NULL, 0, NI_NUMERICHOST) != 0)
1362223328Sgavin				strlcpy(bname, "?", sizeof(bname));
1363223328Sgavin			errno = saveerr;
1364223328Sgavin			warn("Can't bind to `%s'", bname);
1365223328Sgavin			return -1;
1366223328Sgavin		}
1367223328Sgavin	}
1368223328Sgavin
1369223328Sgavin						/* save current socket flags */
1370223328Sgavin	if ((flags = fcntl(sock, F_GETFL, 0)) == -1) {
1371223328Sgavin		warn("Can't %s socket flags for connect to `%s:%s'",
1372223328Sgavin		    "save", hname, sname);
1373223328Sgavin		return -1;
1374223328Sgavin	}
1375223328Sgavin						/* set non-blocking connect */
1376223328Sgavin	if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
1377223328Sgavin		warn("Can't set socket non-blocking for connect to `%s:%s'",
1378223328Sgavin		    hname, sname);
1379223328Sgavin		return -1;
1380223328Sgavin	}
1381223328Sgavin
1382146309Smikeh	/* NOTE: we now must restore socket flags on successful exit */
1383146309Smikeh
1384146309Smikeh	pfd[0].fd = sock;
1385146309Smikeh	pfd[0].events = POLLIN|POLLOUT;
1386146309Smikeh
1387146309Smikeh	if (quit_time > 0) {			/* want a non default timeout */
1388146309Smikeh		(void)gettimeofday(&endtime, NULL);
1389146309Smikeh		endtime.tv_sec += quit_time;	/* determine end time */
1390146309Smikeh	}
1391146309Smikeh
1392146309Smikeh	rv = connect(sock, name, namelen);	/* inititate the connection */
1393146309Smikeh	if (rv == -1) {				/* connection error */
1394223328Sgavin		if (errno != EINPROGRESS) {	/* error isn't "please wait" */
1395223328Sgavin connecterror:
1396223328Sgavin			warn("Can't connect to `%s:%s'", hname, sname);
1397146309Smikeh			return -1;
1398223328Sgavin		}
1399146309Smikeh
1400146309Smikeh						/* connect EINPROGRESS; wait */
1401128671Smikeh		do {
1402146309Smikeh			if (quit_time > 0) {	/* determine timeout */
1403146309Smikeh				(void)gettimeofday(&now, NULL);
1404146309Smikeh				timersub(&endtime, &now, &td);
1405146309Smikeh				timeout = td.tv_sec * 1000 + td.tv_usec/1000;
1406146309Smikeh				if (timeout < 0)
1407146309Smikeh					timeout = 0;
1408146309Smikeh			} else {
1409146309Smikeh				timeout = INFTIM;
1410146309Smikeh			}
1411146309Smikeh			pfd[0].revents = 0;
1412223328Sgavin			rv = ftp_poll(pfd, 1, timeout);
1413146309Smikeh						/* loop until poll ! EINTR */
1414128671Smikeh		} while (rv == -1 && errno == EINTR);
1415146309Smikeh
1416146309Smikeh		if (rv == 0) {			/* poll (connect) timed out */
1417146309Smikeh			errno = ETIMEDOUT;
1418223328Sgavin			goto connecterror;
1419146309Smikeh		}
1420146309Smikeh
1421146309Smikeh		if (rv == -1) {			/* poll error */
1422223328Sgavin			goto connecterror;
1423146309Smikeh		} else if (pfd[0].revents & (POLLIN|POLLOUT)) {
1424146309Smikeh			slen = sizeof(error);	/* OK, or pending error */
1425146309Smikeh			if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
1426223328Sgavin			    &error, &slen) == -1) {
1427223328Sgavin						/* Solaris pending error */
1428223328Sgavin				goto connecterror;
1429223328Sgavin			} else if (error != 0) {
1430146309Smikeh				errno = error;	/* BSD pending error */
1431223328Sgavin				goto connecterror;
1432146309Smikeh			}
1433146309Smikeh		} else {
1434146309Smikeh			errno = EBADF;		/* this shouldn't happen ... */
1435223328Sgavin			goto connecterror;
1436146309Smikeh		}
1437128671Smikeh	}
1438146309Smikeh
1439223328Sgavin	if (fcntl(sock, F_SETFL, flags) == -1) {
1440223328Sgavin						/* restore socket flags */
1441223328Sgavin		warn("Can't %s socket flags for connect to `%s:%s'",
1442223328Sgavin		    "restore", hname, sname);
1443146309Smikeh		return -1;
1444223328Sgavin	}
1445146309Smikeh	return 0;
144679971Sobrien}
144779971Sobrien
144879971Sobrien/*
144979971Sobrien * Internal version of listen(2); sets socket buffer sizes first.
145079971Sobrien */
145179971Sobrienint
1452223328Sgavinftp_listen(int sock, int backlog)
145379971Sobrien{
145479971Sobrien
145579971Sobrien	setupsockbufsize(sock);
145679971Sobrien	return (listen(sock, backlog));
145779971Sobrien}
145879971Sobrien
145979971Sobrien/*
1460146309Smikeh * Internal version of poll(2), to allow reimplementation by select(2)
1461146309Smikeh * on platforms without the former.
1462146309Smikeh */
1463146309Smikehint
1464223328Sgavinftp_poll(struct pollfd *fds, int nfds, int timeout)
1465146309Smikeh{
1466223328Sgavin#if defined(HAVE_POLL)
1467146309Smikeh	return poll(fds, nfds, timeout);
1468223328Sgavin
1469223328Sgavin#elif defined(HAVE_SELECT)
1470223328Sgavin		/* implement poll(2) using select(2) */
1471223328Sgavin	fd_set		rset, wset, xset;
1472223328Sgavin	const int	rsetflags = POLLIN | POLLRDNORM;
1473223328Sgavin	const int	wsetflags = POLLOUT | POLLWRNORM;
1474223328Sgavin	const int	xsetflags = POLLRDBAND;
1475223328Sgavin	struct timeval	tv, *ptv;
1476223328Sgavin	int		i, max, rv;
1477223328Sgavin
1478223328Sgavin	FD_ZERO(&rset);			/* build list of read & write events */
1479223328Sgavin	FD_ZERO(&wset);
1480223328Sgavin	FD_ZERO(&xset);
1481223328Sgavin	max = 0;
1482223328Sgavin	for (i = 0; i < nfds; i++) {
1483223328Sgavin		if (fds[i].fd > FD_SETSIZE) {
1484223328Sgavin			warnx("can't select fd %d", fds[i].fd);
1485223328Sgavin			errno = EINVAL;
1486223328Sgavin			return -1;
1487223328Sgavin		} else if (fds[i].fd > max)
1488223328Sgavin			max = fds[i].fd;
1489223328Sgavin		if (fds[i].events & rsetflags)
1490223328Sgavin			FD_SET(fds[i].fd, &rset);
1491223328Sgavin		if (fds[i].events & wsetflags)
1492223328Sgavin			FD_SET(fds[i].fd, &wset);
1493223328Sgavin		if (fds[i].events & xsetflags)
1494223328Sgavin			FD_SET(fds[i].fd, &xset);
1495223328Sgavin	}
1496223328Sgavin
1497223328Sgavin	ptv = &tv;			/* determine timeout */
1498223328Sgavin	if (timeout == -1) {		/* wait forever */
1499223328Sgavin		ptv = NULL;
1500223328Sgavin	} else if (timeout == 0) {	/* poll once */
1501223328Sgavin		ptv->tv_sec = 0;
1502223328Sgavin		ptv->tv_usec = 0;
1503223328Sgavin	}
1504223328Sgavin	else if (timeout != 0) {	/* wait timeout milliseconds */
1505223328Sgavin		ptv->tv_sec = timeout / 1000;
1506223328Sgavin		ptv->tv_usec = (timeout % 1000) * 1000;
1507223328Sgavin	}
1508223328Sgavin	rv = select(max + 1, &rset, &wset, &xset, ptv);
1509223328Sgavin	if (rv <= 0)			/* -1 == error, 0 == timeout */
1510223328Sgavin		return rv;
1511223328Sgavin
1512223328Sgavin	for (i = 0; i < nfds; i++) {	/* determine results */
1513223328Sgavin		if (FD_ISSET(fds[i].fd, &rset))
1514223328Sgavin			fds[i].revents |= (fds[i].events & rsetflags);
1515223328Sgavin		if (FD_ISSET(fds[i].fd, &wset))
1516223328Sgavin			fds[i].revents |= (fds[i].events & wsetflags);
1517223328Sgavin		if (FD_ISSET(fds[i].fd, &xset))
1518223328Sgavin			fds[i].revents |= (fds[i].events & xsetflags);
1519223328Sgavin	}
1520223328Sgavin	return rv;
1521223328Sgavin
1522223328Sgavin#else
1523223328Sgavin# error no way to implement xpoll
1524223328Sgavin#endif
1525146309Smikeh}
1526146309Smikeh
1527146309Smikeh/*
152879971Sobrien * malloc() with inbuilt error checking
152979971Sobrien */
153079971Sobrienvoid *
1531223328Sgavinftp_malloc(size_t size)
153279971Sobrien{
153379971Sobrien	void *p;
153479971Sobrien
153579971Sobrien	p = malloc(size);
153679971Sobrien	if (p == NULL)
153779971Sobrien		err(1, "Unable to allocate %ld bytes of memory", (long)size);
153879971Sobrien	return (p);
153979971Sobrien}
154079971Sobrien
154179971Sobrien/*
154279971Sobrien * sl_init() with inbuilt error checking
154379971Sobrien */
154479971SobrienStringList *
1545223328Sgavinftp_sl_init(void)
154679971Sobrien{
154779971Sobrien	StringList *p;
154879971Sobrien
154979971Sobrien	p = sl_init();
155079971Sobrien	if (p == NULL)
155179971Sobrien		err(1, "Unable to allocate memory for stringlist");
155279971Sobrien	return (p);
155379971Sobrien}
155479971Sobrien
155579971Sobrien/*
155679971Sobrien * sl_add() with inbuilt error checking
155779971Sobrien */
155879971Sobrienvoid
1559223328Sgavinftp_sl_add(StringList *sl, char *i)
156079971Sobrien{
156179971Sobrien
156279971Sobrien	if (sl_add(sl, i) == -1)
156379971Sobrien		err(1, "Unable to add `%s' to stringlist", i);
156479971Sobrien}
156579971Sobrien
156679971Sobrien/*
156779971Sobrien * strdup() with inbuilt error checking
156879971Sobrien */
156979971Sobrienchar *
1570223328Sgavinftp_strdup(const char *str)
157179971Sobrien{
157279971Sobrien	char *s;
157379971Sobrien
157479971Sobrien	if (str == NULL)
1575223328Sgavin		errx(1, "ftp_strdup: called with NULL argument");
157679971Sobrien	s = strdup(str);
157779971Sobrien	if (s == NULL)
157879971Sobrien		err(1, "Unable to allocate memory for string copy");
157979971Sobrien	return (s);
158079971Sobrien}
1581