1/*	$NetBSD: util.c,v 1.18 2007/08/06 04:33:24 lukem Exp $	*/
2/*	from	NetBSD: util.c,v 1.143 2007/05/24 05:05:19 lukem Exp	*/
3
4/*-
5 * Copyright (c) 1997-2007 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Luke Mewburn.
10 *
11 * This code is derived from software contributed to The NetBSD Foundation
12 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
13 * NASA Ames Research Center.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 *    notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 *    notice, this list of conditions and the following disclaimer in the
22 *    documentation and/or other materials provided with the distribution.
23 * 3. All advertising materials mentioning features or use of this software
24 *    must display the following acknowledgement:
25 *	This product includes software developed by the NetBSD
26 *	Foundation, Inc. and its contributors.
27 * 4. Neither the name of The NetBSD Foundation nor the names of its
28 *    contributors may be used to endorse or promote products derived
29 *    from this software without specific prior written permission.
30 *
31 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
32 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
33 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
34 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
35 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
36 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
37 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
38 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
39 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
41 * POSSIBILITY OF SUCH DAMAGE.
42 */
43
44/*
45 * Copyright (c) 1985, 1989, 1993, 1994
46 *	The Regents of the University of California.  All rights reserved.
47 *
48 * Redistribution and use in source and binary forms, with or without
49 * modification, are permitted provided that the following conditions
50 * are met:
51 * 1. Redistributions of source code must retain the above copyright
52 *    notice, this list of conditions and the following disclaimer.
53 * 2. Redistributions in binary form must reproduce the above copyright
54 *    notice, this list of conditions and the following disclaimer in the
55 *    documentation and/or other materials provided with the distribution.
56 * 3. Neither the name of the University nor the names of its contributors
57 *    may be used to endorse or promote products derived from this software
58 *    without specific prior written permission.
59 *
60 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
61 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
62 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
63 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
64 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
65 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
66 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
67 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
68 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
69 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
70 * SUCH DAMAGE.
71 */
72
73#include "tnftp.h"
74
75#if 0	/* tnftp */
76
77#include <sys/cdefs.h>
78#ifndef lint
79__RCSID(" NetBSD: util.c,v 1.143 2007/05/24 05:05:19 lukem Exp  ");
80#endif /* not lint */
81
82/*
83 * FTP User Program -- Misc support routines
84 */
85#include <sys/param.h>
86#include <sys/socket.h>
87#include <sys/ioctl.h>
88#include <sys/time.h>
89#include <netinet/in.h>
90#include <arpa/ftp.h>
91
92#include <ctype.h>
93#include <err.h>
94#include <errno.h>
95#include <fcntl.h>
96#include <glob.h>
97#include <signal.h>
98#include <libgen.h>
99#include <limits.h>
100#include <netdb.h>
101#include <stdio.h>
102#include <stdlib.h>
103#include <string.h>
104#include <termios.h>
105#include <time.h>
106#include <tzfile.h>
107#include <unistd.h>
108
109#endif	/* tnftp */
110
111#include "ftp_var.h"
112
113/*
114 * Connect to peer server and auto-login, if possible.
115 */
116void
117setpeer(int argc, char *argv[])
118{
119	char *host;
120	char *port;
121
122	if (argc == 0)
123		goto usage;
124	if (connected) {
125		fprintf(ttyout, "Already connected to %s, use close first.\n",
126		    hostname);
127		code = -1;
128		return;
129	}
130	if (argc < 2)
131		(void)another(&argc, &argv, "to");
132	if (argc < 2 || argc > 3) {
133 usage:
134		UPRINTF("usage: %s host-name [port]\n", argv[0]);
135		code = -1;
136		return;
137	}
138	if (gatemode)
139		port = gateport;
140	else
141		port = ftpport;
142	if (argc > 2)
143		port = argv[2];
144
145	if (gatemode) {
146		if (gateserver == NULL || *gateserver == '\0')
147			errx(1, "main: gateserver not defined");
148		host = hookup(gateserver, port);
149	} else
150		host = hookup(argv[1], port);
151
152	if (host) {
153		if (gatemode && verbose) {
154			fprintf(ttyout,
155			    "Connecting via pass-through server %s\n",
156			    gateserver);
157		}
158
159		connected = 1;
160		/*
161		 * Set up defaults for FTP.
162		 */
163		(void)strlcpy(typename, "ascii", sizeof(typename));
164		type = TYPE_A;
165		curtype = TYPE_A;
166		(void)strlcpy(formname, "non-print", sizeof(formname));
167		form = FORM_N;
168		(void)strlcpy(modename, "stream", sizeof(modename));
169		mode = MODE_S;
170		(void)strlcpy(structname, "file", sizeof(structname));
171		stru = STRU_F;
172		(void)strlcpy(bytename, "8", sizeof(bytename));
173		bytesize = 8;
174		if (autologin)
175			(void)ftp_login(argv[1], NULL, NULL);
176	}
177}
178
179static void
180parse_feat(const char *line)
181{
182
183			/*
184			 * work-around broken ProFTPd servers that can't
185			 * even obey RFC2389.
186			 */
187	while (*line && isspace((int)*line))
188		line++;
189
190	if (strcasecmp(line, "MDTM") == 0)
191		features[FEAT_MDTM] = 1;
192	else if (strncasecmp(line, "MLST", sizeof("MLST") - 1) == 0) {
193		features[FEAT_MLST] = 1;
194	} else if (strcasecmp(line, "REST STREAM") == 0)
195		features[FEAT_REST_STREAM] = 1;
196	else if (strcasecmp(line, "SIZE") == 0)
197		features[FEAT_SIZE] = 1;
198	else if (strcasecmp(line, "TVFS") == 0)
199		features[FEAT_TVFS] = 1;
200}
201
202/*
203 * Determine the remote system type (SYST) and features (FEAT).
204 * Call after a successful login (i.e, connected = -1)
205 */
206void
207getremoteinfo(void)
208{
209	int overbose, i;
210
211	overbose = verbose;
212	if (ftp_debug == 0)
213		verbose = -1;
214
215			/* determine remote system type */
216	if (command("SYST") == COMPLETE) {
217		if (overbose) {
218			char *cp, c;
219
220			c = 0;
221			cp = strchr(reply_string + 4, ' ');
222			if (cp == NULL)
223				cp = strchr(reply_string + 4, '\r');
224			if (cp) {
225				if (cp[-1] == '.')
226					cp--;
227				c = *cp;
228				*cp = '\0';
229			}
230
231			fprintf(ttyout, "Remote system type is %s.\n",
232			    reply_string + 4);
233			if (cp)
234				*cp = c;
235		}
236		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
237			if (proxy)
238				unix_proxy = 1;
239			else
240				unix_server = 1;
241			/*
242			 * Set type to 0 (not specified by user),
243			 * meaning binary by default, but don't bother
244			 * telling server.  We can use binary
245			 * for text files unless changed by the user.
246			 */
247			type = 0;
248			(void)strlcpy(typename, "binary", sizeof(typename));
249			if (overbose)
250			    fprintf(ttyout,
251				"Using %s mode to transfer files.\n",
252				typename);
253		} else {
254			if (proxy)
255				unix_proxy = 0;
256			else
257				unix_server = 0;
258			if (overbose &&
259			    !strncmp(reply_string, "215 TOPS20", 10))
260				fputs(
261"Remember to set tenex mode when transferring binary files from this machine.\n",
262				    ttyout);
263		}
264	}
265
266			/* determine features (if any) */
267	for (i = 0; i < FEAT_max; i++)
268		features[i] = -1;
269	reply_callback = parse_feat;
270	if (command("FEAT") == COMPLETE) {
271		for (i = 0; i < FEAT_max; i++) {
272			if (features[i] == -1)
273				features[i] = 0;
274		}
275		features[FEAT_FEAT] = 1;
276	} else
277		features[FEAT_FEAT] = 0;
278#ifndef NO_DEBUG
279	if (ftp_debug) {
280#define DEBUG_FEAT(x) fprintf(ttyout, "features[" #x "] = %d\n", features[(x)])
281		DEBUG_FEAT(FEAT_FEAT);
282		DEBUG_FEAT(FEAT_MDTM);
283		DEBUG_FEAT(FEAT_MLST);
284		DEBUG_FEAT(FEAT_REST_STREAM);
285		DEBUG_FEAT(FEAT_SIZE);
286		DEBUG_FEAT(FEAT_TVFS);
287#undef DEBUG_FEAT
288	}
289#endif
290	reply_callback = NULL;
291
292	verbose = overbose;
293}
294
295/*
296 * Reset the various variables that indicate connection state back to
297 * disconnected settings.
298 * The caller is responsible for issuing any commands to the remote server
299 * to perform a clean shutdown before this is invoked.
300 */
301void
302cleanuppeer(void)
303{
304
305	if (cout)
306		(void)fclose(cout);
307	cout = NULL;
308	connected = 0;
309	unix_server = 0;
310	unix_proxy = 0;
311			/*
312			 * determine if anonftp was specifically set with -a
313			 * (1), or implicitly set by auto_fetch() (2). in the
314			 * latter case, disable after the current xfer
315			 */
316	if (anonftp == 2)
317		anonftp = 0;
318	data = -1;
319	epsv4bad = 0;
320	if (username)
321		free(username);
322	username = NULL;
323	if (!proxy)
324		macnum = 0;
325}
326
327/*
328 * Top-level signal handler for interrupted commands.
329 */
330void
331intr(int signo)
332{
333
334	sigint_raised = 1;
335	alarmtimer(0);
336	if (fromatty)
337		write(fileno(ttyout), "\n", 1);
338	siglongjmp(toplevel, 1);
339}
340
341/*
342 * Signal handler for lost connections; cleanup various elements of
343 * the connection state, and call cleanuppeer() to finish it off.
344 */
345void
346lostpeer(int dummy)
347{
348	int oerrno = errno;
349
350	alarmtimer(0);
351	if (connected) {
352		if (cout != NULL) {
353			(void)shutdown(fileno(cout), 1+1);
354			(void)fclose(cout);
355			cout = NULL;
356		}
357		if (data >= 0) {
358			(void)shutdown(data, 1+1);
359			(void)close(data);
360			data = -1;
361		}
362		connected = 0;
363	}
364	pswitch(1);
365	if (connected) {
366		if (cout != NULL) {
367			(void)shutdown(fileno(cout), 1+1);
368			(void)fclose(cout);
369			cout = NULL;
370		}
371		connected = 0;
372	}
373	proxflag = 0;
374	pswitch(0);
375	cleanuppeer();
376	errno = oerrno;
377}
378
379
380/*
381 * Login to remote host, using given username & password if supplied.
382 * Return non-zero if successful.
383 */
384int
385ftp_login(const char *host, const char *luser, const char *lpass)
386{
387	char tmp[80];
388	char *user, *pass, *acct, *p;
389	char emptypass[] = "";
390	const char *errormsg;
391	int n, aflag, rval, nlen;
392
393	aflag = rval = 0;
394	user = pass = acct = NULL;
395	if (luser)
396		user = ftp_strdup(luser);
397	if (lpass)
398		pass = ftp_strdup(lpass);
399
400	DPRINTF("ftp_login: user `%s' pass `%s' host `%s'\n",
401	    user ? user : "<null>", pass ? pass : "<null>",
402	    host ? host : "<null>");
403
404	/*
405	 * Set up arguments for an anonymous FTP session, if necessary.
406	 */
407	if (anonftp) {
408		FREEPTR(user);
409		user = ftp_strdup("anonymous");	/* as per RFC1635 */
410		FREEPTR(pass);
411		pass = ftp_strdup(getoptionvalue("anonpass"));
412	}
413
414	if (ruserpass(host, &user, &pass, &acct) < 0) {
415		code = -1;
416		goto cleanup_ftp_login;
417	}
418
419	while (user == NULL) {
420		if (localname)
421			fprintf(ttyout, "Name (%s:%s): ", host, localname);
422		else
423			fprintf(ttyout, "Name (%s): ", host);
424		errormsg = NULL;
425		nlen = get_line(stdin, tmp, sizeof(tmp), &errormsg);
426		if (nlen < 0) {
427			fprintf(ttyout, "%s; %s aborted.\n", errormsg, "login");
428			code = -1;
429			goto cleanup_ftp_login;
430		} else if (nlen == 0) {
431			user = ftp_strdup(localname);
432		} else {
433			user = ftp_strdup(tmp);
434		}
435	}
436
437	if (gatemode) {
438		char *nuser;
439		size_t len;
440
441		len = strlen(user) + 1 + strlen(host) + 1;
442		nuser = ftp_malloc(len);
443		(void)strlcpy(nuser, user, len);
444		(void)strlcat(nuser, "@",  len);
445		(void)strlcat(nuser, host, len);
446		FREEPTR(user);
447		user = nuser;
448	}
449
450	n = command("USER %s", user);
451	if (n == CONTINUE) {
452		if (pass == NULL) {
453			p = getpass("Password: ");
454			if (p == NULL)
455				p = emptypass;
456			pass = ftp_strdup(p);
457			memset(p, 0, strlen(p));
458		}
459		n = command("PASS %s", pass);
460		memset(pass, 0, strlen(pass));
461	}
462	if (n == CONTINUE) {
463		aflag++;
464		if (acct == NULL) {
465			p = getpass("Account: ");
466			if (p == NULL)
467				p = emptypass;
468			acct = ftp_strdup(p);
469			memset(p, 0, strlen(p));
470		}
471		if (acct[0] == '\0') {
472			warnx("Login failed");
473			goto cleanup_ftp_login;
474		}
475		n = command("ACCT %s", acct);
476		memset(acct, 0, strlen(acct));
477	}
478	if ((n != COMPLETE) ||
479	    (!aflag && acct != NULL && command("ACCT %s", acct) != COMPLETE)) {
480		warnx("Login failed");
481		goto cleanup_ftp_login;
482	}
483	rval = 1;
484	username = ftp_strdup(user);
485	if (proxy)
486		goto cleanup_ftp_login;
487
488	connected = -1;
489	getremoteinfo();
490	for (n = 0; n < macnum; ++n) {
491		if (!strcmp("init", macros[n].mac_name)) {
492			(void)strlcpy(line, "$init", sizeof(line));
493			makeargv();
494			domacro(margc, margv);
495			break;
496		}
497	}
498	updatelocalcwd();
499	updateremotecwd();
500
501 cleanup_ftp_login:
502	FREEPTR(user);
503	if (pass != NULL)
504		memset(pass, 0, strlen(pass));
505	FREEPTR(pass);
506	if (acct != NULL)
507		memset(acct, 0, strlen(acct));
508	FREEPTR(acct);
509	return (rval);
510}
511
512/*
513 * `another' gets another argument, and stores the new argc and argv.
514 * It reverts to the top level (via intr()) on EOF/error.
515 *
516 * Returns false if no new arguments have been added.
517 */
518int
519another(int *pargc, char ***pargv, const char *prompt)
520{
521	const char	*errormsg;
522	int		ret, nlen;
523	size_t		len;
524
525	len = strlen(line);
526	if (len >= sizeof(line) - 3) {
527		fputs("Sorry, arguments too long.\n", ttyout);
528		intr(0);
529	}
530	fprintf(ttyout, "(%s) ", prompt);
531	line[len++] = ' ';
532	errormsg = NULL;
533	nlen = get_line(stdin, line + len, sizeof(line)-len, &errormsg);
534	if (nlen < 0) {
535		fprintf(ttyout, "%s; %s aborted.\n", errormsg, "operation");
536		intr(0);
537	}
538	len += nlen;
539	makeargv();
540	ret = margc > *pargc;
541	*pargc = margc;
542	*pargv = margv;
543	return (ret);
544}
545
546/*
547 * glob files given in argv[] from the remote server.
548 * if errbuf isn't NULL, store error messages there instead
549 * of writing to the screen.
550 */
551char *
552remglob(char *argv[], int doswitch, const char **errbuf)
553{
554	static char buf[MAXPATHLEN];
555	static FILE *ftemp = NULL;
556	static char **args;
557	char temp[MAXPATHLEN];
558	int oldverbose, oldhash, oldprogress, fd;
559	char *cp;
560	const char *mode;
561	size_t len;
562
563	if (!mflag || !connected) {
564		if (!doglob)
565			args = NULL;
566		else {
567			if (ftemp) {
568				(void)fclose(ftemp);
569				ftemp = NULL;
570			}
571		}
572		return (NULL);
573	}
574	if (!doglob) {
575		if (args == NULL)
576			args = argv;
577		if ((cp = *++args) == NULL)
578			args = NULL;
579		return (cp);
580	}
581	if (ftemp == NULL) {
582		len = strlcpy(temp, tmpdir, sizeof(temp));
583		if (temp[len - 1] != '/')
584			(void)strlcat(temp, "/", sizeof(temp));
585		(void)strlcat(temp, TMPFILE, sizeof(temp));
586		if ((fd = mkstemp(temp)) < 0) {
587			warn("Unable to create temporary file `%s'", temp);
588			return (NULL);
589		}
590		close(fd);
591		oldverbose = verbose;
592		verbose = (errbuf != NULL) ? -1 : 0;
593		oldhash = hash;
594		oldprogress = progress;
595		hash = 0;
596		progress = 0;
597		if (doswitch)
598			pswitch(!proxy);
599		for (mode = "w"; *++argv != NULL; mode = "a")
600			recvrequest("NLST", temp, *argv, mode, 0, 0);
601		if ((code / 100) != COMPLETE) {
602			if (errbuf != NULL)
603				*errbuf = reply_string;
604		}
605		if (doswitch)
606			pswitch(!proxy);
607		verbose = oldverbose;
608		hash = oldhash;
609		progress = oldprogress;
610		ftemp = fopen(temp, "r");
611		(void)unlink(temp);
612		if (ftemp == NULL) {
613			if (errbuf == NULL)
614				warnx("Can't find list of remote files");
615			else
616				*errbuf =
617				    "Can't find list of remote files";
618			return (NULL);
619		}
620	}
621	if (fgets(buf, sizeof(buf), ftemp) == NULL) {
622		(void)fclose(ftemp);
623		ftemp = NULL;
624		return (NULL);
625	}
626	if ((cp = strchr(buf, '\n')) != NULL)
627		*cp = '\0';
628	return (buf);
629}
630
631/*
632 * Glob a local file name specification with the expectation of a single
633 * return value. Can't control multiple values being expanded from the
634 * expression, we return only the first.
635 * Returns NULL on error, or a pointer to a buffer containing the filename
636 * that's the caller's responsiblity to free(3) when finished with.
637 */
638char *
639globulize(const char *pattern)
640{
641	glob_t gl;
642	int flags;
643	char *p;
644
645	if (!doglob)
646		return (ftp_strdup(pattern));
647
648	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
649	memset(&gl, 0, sizeof(gl));
650	if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
651		warnx("Glob pattern `%s' not found", pattern);
652		globfree(&gl);
653		return (NULL);
654	}
655	p = ftp_strdup(gl.gl_pathv[0]);
656	globfree(&gl);
657	return (p);
658}
659
660/*
661 * determine size of remote file
662 */
663off_t
664remotesize(const char *file, int noisy)
665{
666	int overbose, r;
667	off_t size;
668
669	overbose = verbose;
670	size = -1;
671	if (ftp_debug == 0)
672		verbose = -1;
673	if (! features[FEAT_SIZE]) {
674		if (noisy)
675			fprintf(ttyout,
676			    "SIZE is not supported by remote server.\n");
677		goto cleanup_remotesize;
678	}
679	r = command("SIZE %s", file);
680	if (r == COMPLETE) {
681		char *cp, *ep;
682
683		cp = strchr(reply_string, ' ');
684		if (cp != NULL) {
685			cp++;
686			size = STRTOLL(cp, &ep, 10);
687			if (*ep != '\0' && !isspace((unsigned char)*ep))
688				size = -1;
689		}
690	} else {
691		if (r == ERROR && code == 500 && features[FEAT_SIZE] == -1)
692			features[FEAT_SIZE] = 0;
693		if (noisy && ftp_debug == 0) {
694			fputs(reply_string, ttyout);
695			putc('\n', ttyout);
696		}
697	}
698 cleanup_remotesize:
699	verbose = overbose;
700	return (size);
701}
702
703/*
704 * determine last modification time (in GMT) of remote file
705 */
706time_t
707remotemodtime(const char *file, int noisy)
708{
709	int	overbose, ocode, r;
710	time_t	rtime;
711
712	overbose = verbose;
713	ocode = code;
714	rtime = -1;
715	if (ftp_debug == 0)
716		verbose = -1;
717	if (! features[FEAT_MDTM]) {
718		if (noisy)
719			fprintf(ttyout,
720			    "MDTM is not supported by remote server.\n");
721		goto cleanup_parse_time;
722	}
723	r = command("MDTM %s", file);
724	if (r == COMPLETE) {
725		struct tm timebuf;
726		char *timestr, *frac;
727
728		/*
729		 * time-val = 14DIGIT [ "." 1*DIGIT ]
730		 *		YYYYMMDDHHMMSS[.sss]
731		 * mdtm-response = "213" SP time-val CRLF / error-response
732		 */
733		timestr = reply_string + 4;
734
735					/*
736					 * parse fraction.
737					 * XXX: ignored for now
738					 */
739		frac = strchr(timestr, '\r');
740		if (frac != NULL)
741			*frac = '\0';
742		frac = strchr(timestr, '.');
743		if (frac != NULL)
744			*frac++ = '\0';
745		if (strlen(timestr) == 15 && strncmp(timestr, "191", 3) == 0) {
746			/*
747			 * XXX:	Workaround for lame ftpd's that return
748			 *	`19100' instead of `2000'
749			 */
750			fprintf(ttyout,
751	    "Y2K warning! Incorrect time-val `%s' received from server.\n",
752			    timestr);
753			timestr++;
754			timestr[0] = '2';
755			timestr[1] = '0';
756			fprintf(ttyout, "Converted to `%s'\n", timestr);
757		}
758		memset(&timebuf, 0, sizeof(timebuf));
759		if (strlen(timestr) != 14 ||
760		    (strptime(timestr, "%Y%m%d%H%M%S", &timebuf) == NULL)) {
761 bad_parse_time:
762			fprintf(ttyout, "Can't parse time `%s'.\n", timestr);
763			goto cleanup_parse_time;
764		}
765		timebuf.tm_isdst = -1;
766		rtime = timegm(&timebuf);
767		if (rtime == -1) {
768			if (noisy || ftp_debug != 0)
769				goto bad_parse_time;
770			else
771				goto cleanup_parse_time;
772		} else
773			DPRINTF("parsed date `%s' as " LLF ", %s",
774			    timestr, (LLT)rtime,
775			    rfc2822time(localtime(&rtime)));
776	} else {
777		if (r == ERROR && code == 500 && features[FEAT_MDTM] == -1)
778			features[FEAT_MDTM] = 0;
779		if (noisy && ftp_debug == 0) {
780			fputs(reply_string, ttyout);
781			putc('\n', ttyout);
782		}
783	}
784 cleanup_parse_time:
785	verbose = overbose;
786	if (rtime == -1)
787		code = ocode;
788	return (rtime);
789}
790
791/*
792 * Format tm in an RFC2822 compatible manner, with a trailing \n.
793 * Returns a pointer to a static string containing the result.
794 */
795const char *
796rfc2822time(const struct tm *tm)
797{
798	static char result[50];
799
800	if (strftime(result, sizeof(result),
801	    "%a, %d %b %Y %H:%M:%S %z\n", tm) == 0)
802		errx(1, "Can't convert RFC2822 time: buffer too small");
803	return result;
804}
805
806/*
807 * Update global `localcwd', which contains the state of the local cwd
808 */
809void
810updatelocalcwd(void)
811{
812
813	if (getcwd(localcwd, sizeof(localcwd)) == NULL)
814		localcwd[0] = '\0';
815	DPRINTF("got localcwd as `%s'\n", localcwd);
816}
817
818/*
819 * Update global `remotecwd', which contains the state of the remote cwd
820 */
821void
822updateremotecwd(void)
823{
824	int	 overbose, ocode, i;
825	char	*cp;
826
827	overbose = verbose;
828	ocode = code;
829	if (ftp_debug == 0)
830		verbose = -1;
831	if (command("PWD") != COMPLETE)
832		goto badremotecwd;
833	cp = strchr(reply_string, ' ');
834	if (cp == NULL || cp[0] == '\0' || cp[1] != '"')
835		goto badremotecwd;
836	cp += 2;
837	for (i = 0; *cp && i < sizeof(remotecwd) - 1; i++, cp++) {
838		if (cp[0] == '"') {
839			if (cp[1] == '"')
840				cp++;
841			else
842				break;
843		}
844		remotecwd[i] = *cp;
845	}
846	remotecwd[i] = '\0';
847	DPRINTF("got remotecwd as `%s'\n", remotecwd);
848	goto cleanupremotecwd;
849 badremotecwd:
850	remotecwd[0]='\0';
851 cleanupremotecwd:
852	verbose = overbose;
853	code = ocode;
854}
855
856/*
857 * Ensure file is in or under dir.
858 * Returns 1 if so, 0 if not (or an error occurred).
859 */
860int
861fileindir(const char *file, const char *dir)
862{
863	char	parentdirbuf[PATH_MAX+1], *parentdir;
864	char	realdir[PATH_MAX+1];
865	size_t	dirlen;
866
867					/* determine parent directory of file */
868	(void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf));
869	parentdir = dirname(parentdirbuf);
870	if (strcmp(parentdir, ".") == 0)
871		return 1;		/* current directory is ok */
872
873					/* find the directory */
874	if (realpath(parentdir, realdir) == NULL) {
875		warn("Unable to determine real path of `%s'", parentdir);
876		return 0;
877	}
878	if (realdir[0] != '/')		/* relative result is ok */
879		return 1;
880	dirlen = strlen(dir);
881#if 0
882printf("file %s parent %s realdir %s dir %s [%d]\n",
883    file, parentdir, realdir, dir, dirlen);
884#endif
885	if (strncmp(realdir, dir, dirlen) == 0 &&
886	    (realdir[dirlen] == '/' || realdir[dirlen] == '\0'))
887		return 1;
888	return 0;
889}
890
891/*
892 * List words in stringlist, vertically arranged
893 */
894void
895list_vertical(StringList *sl)
896{
897	int i, j;
898	int columns, lines;
899	char *p;
900	size_t w, width;
901
902	width = 0;
903
904	for (i = 0 ; i < sl->sl_cur ; i++) {
905		w = strlen(sl->sl_str[i]);
906		if (w > width)
907			width = w;
908	}
909	width = (width + 8) &~ 7;
910
911	columns = ttywidth / width;
912	if (columns == 0)
913		columns = 1;
914	lines = (sl->sl_cur + columns - 1) / columns;
915	for (i = 0; i < lines; i++) {
916		for (j = 0; j < columns; j++) {
917			p = sl->sl_str[j * lines + i];
918			if (p)
919				fputs(p, ttyout);
920			if (j * lines + i + lines >= sl->sl_cur) {
921				putc('\n', ttyout);
922				break;
923			}
924			if (p) {
925				w = strlen(p);
926				while (w < width) {
927					w = (w + 8) &~ 7;
928					(void)putc('\t', ttyout);
929				}
930			}
931		}
932	}
933}
934
935/*
936 * Update the global ttywidth value, using TIOCGWINSZ.
937 */
938void
939setttywidth(int a)
940{
941	struct winsize winsize;
942	int oerrno = errno;
943
944	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1 &&
945	    winsize.ws_col != 0)
946		ttywidth = winsize.ws_col;
947	else
948		ttywidth = 80;
949	errno = oerrno;
950}
951
952/*
953 * Change the rate limit up (SIGUSR1) or down (SIGUSR2)
954 */
955void
956crankrate(int sig)
957{
958
959	switch (sig) {
960	case SIGUSR1:
961		if (rate_get)
962			rate_get += rate_get_incr;
963		if (rate_put)
964			rate_put += rate_put_incr;
965		break;
966	case SIGUSR2:
967		if (rate_get && rate_get > rate_get_incr)
968			rate_get -= rate_get_incr;
969		if (rate_put && rate_put > rate_put_incr)
970			rate_put -= rate_put_incr;
971		break;
972	default:
973		err(1, "crankrate invoked with unknown signal: %d", sig);
974	}
975}
976
977
978/*
979 * Setup or cleanup EditLine structures
980 */
981#ifndef NO_EDITCOMPLETE
982void
983controlediting(void)
984{
985	if (editing && el == NULL && hist == NULL) {
986		HistEvent ev;
987		int editmode;
988
989		el = el_init(getprogname(), stdin, ttyout, stderr);
990		/* init editline */
991		hist = history_init();		/* init the builtin history */
992		history(hist, &ev, H_SETSIZE, 100);/* remember 100 events */
993		el_set(el, EL_HIST, history, hist);	/* use history */
994
995		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
996		el_set(el, EL_PROMPT, prompt);	/* set the prompt functions */
997		el_set(el, EL_RPROMPT, rprompt);
998
999		/* add local file completion, bind to TAB */
1000		el_set(el, EL_ADDFN, "ftp-complete",
1001		    "Context sensitive argument completion",
1002		    complete);
1003		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1004		el_source(el, NULL);	/* read ~/.editrc */
1005		if ((el_get(el, EL_EDITMODE, &editmode) != -1) && editmode == 0)
1006			editing = 0;	/* the user doesn't want editing,
1007					 * so disable, and let statement
1008					 * below cleanup */
1009		else
1010			el_set(el, EL_SIGNAL, 1);
1011	}
1012	if (!editing) {
1013		if (hist) {
1014			history_end(hist);
1015			hist = NULL;
1016		}
1017		if (el) {
1018			el_end(el);
1019			el = NULL;
1020		}
1021	}
1022}
1023#endif /* !NO_EDITCOMPLETE */
1024
1025/*
1026 * Convert the string `arg' to an int, which may have an optional SI suffix
1027 * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
1028 */
1029int
1030strsuftoi(const char *arg)
1031{
1032	char *cp;
1033	long val;
1034
1035	if (!isdigit((unsigned char)arg[0]))
1036		return (-1);
1037
1038	val = strtol(arg, &cp, 10);
1039	if (cp != NULL) {
1040		if (cp[0] != '\0' && cp[1] != '\0')
1041			 return (-1);
1042		switch (tolower((unsigned char)cp[0])) {
1043		case '\0':
1044		case 'b':
1045			break;
1046		case 'k':
1047			val <<= 10;
1048			break;
1049		case 'm':
1050			val <<= 20;
1051			break;
1052		case 'g':
1053			val <<= 30;
1054			break;
1055		default:
1056			return (-1);
1057		}
1058	}
1059	if (val < 0 || val > INT_MAX)
1060		return (-1);
1061
1062	return (val);
1063}
1064
1065/*
1066 * Set up socket buffer sizes before a connection is made.
1067 */
1068void
1069setupsockbufsize(int sock)
1070{
1071
1072	if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
1073	    (void *)&sndbuf_size, sizeof(sndbuf_size)) == -1)
1074		warn("Unable to set sndbuf size %d", sndbuf_size);
1075
1076	if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
1077	    (void *)&rcvbuf_size, sizeof(rcvbuf_size)) == -1)
1078		warn("Unable to set rcvbuf size %d", rcvbuf_size);
1079}
1080
1081/*
1082 * Copy characters from src into dst, \ quoting characters that require it
1083 */
1084void
1085ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
1086{
1087	int	di, si;
1088
1089	for (di = si = 0;
1090	    src[si] != '\0' && di < dstlen && si < srclen;
1091	    di++, si++) {
1092		switch (src[si]) {
1093		case '\\':
1094		case ' ':
1095		case '\t':
1096		case '\r':
1097		case '\n':
1098		case '"':
1099			dst[di++] = '\\';
1100			if (di >= dstlen)
1101				break;
1102			/* FALLTHROUGH */
1103		default:
1104			dst[di] = src[si];
1105		}
1106	}
1107	dst[di] = '\0';
1108}
1109
1110/*
1111 * Copy src into buf (which is len bytes long), expanding % sequences.
1112 */
1113void
1114formatbuf(char *buf, size_t len, const char *src)
1115{
1116	const char	*p, *p2, *q;
1117	int		 i, op, updirs, pdirs;
1118
1119#define ADDBUF(x) do { \
1120		if (i >= len - 1) \
1121			goto endbuf; \
1122		buf[i++] = (x); \
1123	} while (0)
1124
1125	p = src;
1126	for (i = 0; *p; p++) {
1127		if (*p != '%') {
1128			ADDBUF(*p);
1129			continue;
1130		}
1131		p++;
1132
1133		switch (op = *p) {
1134
1135		case '/':
1136		case '.':
1137		case 'c':
1138			p2 = connected ? remotecwd : "";
1139			updirs = pdirs = 0;
1140
1141			/* option to determine fixed # of dirs from path */
1142			if (op == '.' || op == 'c') {
1143				int skip;
1144
1145				q = p2;
1146				while (*p2)		/* calc # of /'s */
1147					if (*p2++ == '/')
1148						updirs++;
1149				if (p[1] == '0') {	/* print <x> or ... */
1150					pdirs = 1;
1151					p++;
1152				}
1153				if (p[1] >= '1' && p[1] <= '9') {
1154							/* calc # to skip  */
1155					skip = p[1] - '0';
1156					p++;
1157				} else
1158					skip = 1;
1159
1160				updirs -= skip;
1161				while (skip-- > 0) {
1162					while ((p2 > q) && (*p2 != '/'))
1163						p2--;	/* back up */
1164					if (skip && p2 > q)
1165						p2--;
1166				}
1167				if (*p2 == '/' && p2 != q)
1168					p2++;
1169			}
1170
1171			if (updirs > 0 && pdirs) {
1172				if (i >= len - 5)
1173					break;
1174				if (op == '.') {
1175					ADDBUF('.');
1176					ADDBUF('.');
1177					ADDBUF('.');
1178				} else {
1179					ADDBUF('/');
1180					ADDBUF('<');
1181					if (updirs > 9) {
1182						ADDBUF('9');
1183						ADDBUF('+');
1184					} else
1185						ADDBUF('0' + updirs);
1186					ADDBUF('>');
1187				}
1188			}
1189			for (; *p2; p2++)
1190				ADDBUF(*p2);
1191			break;
1192
1193		case 'M':
1194		case 'm':
1195			for (p2 = connected && hostname ? hostname : "-";
1196			    *p2 ; p2++) {
1197				if (op == 'm' && *p2 == '.')
1198					break;
1199				ADDBUF(*p2);
1200			}
1201			break;
1202
1203		case 'n':
1204			for (p2 = connected ? username : "-"; *p2 ; p2++)
1205				ADDBUF(*p2);
1206			break;
1207
1208		case '%':
1209			ADDBUF('%');
1210			break;
1211
1212		default:		/* display unknown codes literally */
1213			ADDBUF('%');
1214			ADDBUF(op);
1215			break;
1216
1217		}
1218	}
1219 endbuf:
1220	buf[i] = '\0';
1221}
1222
1223/*
1224 * Parse `port' into a TCP port number, defaulting to `defport' if `port' is
1225 * an unknown service name. If defport != -1, print a warning upon bad parse.
1226 */
1227int
1228parseport(const char *port, int defport)
1229{
1230	int	 rv;
1231	long	 nport;
1232	char	*p, *ep;
1233
1234	p = ftp_strdup(port);
1235	nport = strtol(p, &ep, 10);
1236	if (*ep != '\0' && ep == p) {
1237		struct servent	*svp;
1238
1239		svp = getservbyname(port, "tcp");
1240		if (svp == NULL) {
1241 badparseport:
1242			if (defport != -1)
1243				warnx("Unknown port `%s', using port %d",
1244				    port, defport);
1245			rv = defport;
1246		} else
1247			rv = ntohs(svp->s_port);
1248	} else if (nport < 1 || nport > MAX_IN_PORT_T || *ep != '\0')
1249		goto badparseport;
1250	else
1251		rv = nport;
1252	free(p);
1253	return (rv);
1254}
1255
1256/*
1257 * Determine if given string is an IPv6 address or not.
1258 * Return 1 for yes, 0 for no
1259 */
1260int
1261isipv6addr(const char *addr)
1262{
1263	int rv = 0;
1264#ifdef INET6
1265	struct addrinfo hints, *res;
1266
1267	memset(&hints, 0, sizeof(hints));
1268	hints.ai_family = PF_INET6;
1269	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
1270	hints.ai_flags = AI_NUMERICHOST;
1271	if (getaddrinfo(addr, "0", &hints, &res) != 0)
1272		rv = 0;
1273	else {
1274		rv = 1;
1275		freeaddrinfo(res);
1276	}
1277	DPRINTF("isipv6addr: got %d for %s\n", rv, addr);
1278#endif
1279	return (rv == 1) ? 1 : 0;
1280}
1281
1282/*
1283 * Read a line from the FILE stream into buf/buflen using fgets(), so up
1284 * to buflen-1 chars will be read and the result will be NUL terminated.
1285 * If the line has a trailing newline it will be removed.
1286 * If the line is too long, excess characters will be read until
1287 * newline/EOF/error.
1288 * If EOF/error occurs or a too-long line is encountered and errormsg
1289 * isn't NULL, it will be changed to a description of the problem.
1290 * (The EOF message has a leading \n for cosmetic purposes).
1291 * Returns:
1292 *	>=0	length of line (excluding trailing newline) if all ok
1293 *	-1	error occurred
1294 *	-2	EOF encountered
1295 *	-3	line was too long
1296 */
1297int
1298get_line(FILE *stream, char *buf, size_t buflen, const char **errormsg)
1299{
1300	int	rv, ch;
1301	size_t	len;
1302
1303	if (fgets(buf, buflen, stream) == NULL) {
1304		if (feof(stream)) {	/* EOF */
1305			rv = -2;
1306			if (errormsg)
1307				*errormsg = "\nEOF received";
1308		} else  {		/* error */
1309			rv = -1;
1310			if (errormsg)
1311				*errormsg = "Error encountered";
1312		}
1313		clearerr(stream);
1314		return rv;
1315	}
1316	len = strlen(buf);
1317	if (buf[len-1] == '\n') {	/* clear any trailing newline */
1318		buf[--len] = '\0';
1319	} else if (len == buflen-1) {	/* line too long */
1320		while ((ch = getchar()) != '\n' && ch != EOF)
1321			continue;
1322		if (errormsg)
1323			*errormsg = "Input line is too long";
1324		clearerr(stream);
1325		return -3;
1326	}
1327	if (errormsg)
1328		*errormsg = NULL;
1329	return len;
1330}
1331
1332/*
1333 * Internal version of connect(2); sets socket buffer sizes,
1334 * binds to a specific local address (if set), and
1335 * supports a connection timeout using a non-blocking connect(2) with
1336 * a poll(2).
1337 * Socket fcntl flags are temporarily updated to include O_NONBLOCK;
1338 * these will not be reverted on connection failure.
1339 * Returns 0 on success, or -1 upon failure (with an appropriate
1340 * error message displayed.)
1341 */
1342int
1343ftp_connect(int sock, const struct sockaddr *name, socklen_t namelen)
1344{
1345	int		flags, rv, timeout, error;
1346	socklen_t	slen;
1347	struct timeval	endtime, now, td;
1348	struct pollfd	pfd[1];
1349	char		hname[NI_MAXHOST];
1350
1351	setupsockbufsize(sock);
1352	if (getnameinfo(name, namelen,
1353	    hname, sizeof(hname), NULL, 0, NI_NUMERICHOST) != 0)
1354		strlcpy(hname, "?", sizeof(hname));
1355
1356	if (bindai != NULL) {			/* bind to specific addr */
1357		struct addrinfo *ai;
1358
1359		for (ai = bindai; ai != NULL; ai = ai->ai_next) {
1360			if (ai->ai_family == name->sa_family)
1361				break;
1362		}
1363		if (ai == NULL)
1364			ai = bindai;
1365		if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
1366			char	bname[NI_MAXHOST];
1367			int	saveerr;
1368
1369			saveerr = errno;
1370			if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
1371			    bname, sizeof(bname), NULL, 0, NI_NUMERICHOST) != 0)
1372				strlcpy(bname, "?", sizeof(bname));
1373			errno = saveerr;
1374			warn("Can't bind to `%s'", bname);
1375			return -1;
1376		}
1377	}
1378
1379						/* save current socket flags */
1380	if ((flags = fcntl(sock, F_GETFL, 0)) == -1) {
1381		warn("Can't %s socket flags for connect to `%s'",
1382		    "save", hname);
1383		return -1;
1384	}
1385						/* set non-blocking connect */
1386	if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
1387		warn("Can't set socket non-blocking for connect to `%s'",
1388		    hname);
1389		return -1;
1390	}
1391
1392	/* NOTE: we now must restore socket flags on successful exit */
1393
1394	pfd[0].fd = sock;
1395	pfd[0].events = POLLIN|POLLOUT;
1396
1397	if (quit_time > 0) {			/* want a non default timeout */
1398		(void)gettimeofday(&endtime, NULL);
1399		endtime.tv_sec += quit_time;	/* determine end time */
1400	}
1401
1402	rv = connect(sock, name, namelen);	/* inititate the connection */
1403	if (rv == -1) {				/* connection error */
1404		if (errno != EINPROGRESS) {	/* error isn't "please wait" */
1405 connecterror:
1406			warn("Can't connect to `%s'", hname);
1407			return -1;
1408		}
1409
1410						/* connect EINPROGRESS; wait */
1411		do {
1412			if (quit_time > 0) {	/* determine timeout */
1413				(void)gettimeofday(&now, NULL);
1414				timersub(&endtime, &now, &td);
1415				timeout = td.tv_sec * 1000 + td.tv_usec/1000;
1416				if (timeout < 0)
1417					timeout = 0;
1418			} else {
1419				timeout = INFTIM;
1420			}
1421			pfd[0].revents = 0;
1422			rv = ftp_poll(pfd, 1, timeout);
1423						/* loop until poll ! EINTR */
1424		} while (rv == -1 && errno == EINTR);
1425
1426		if (rv == 0) {			/* poll (connect) timed out */
1427			errno = ETIMEDOUT;
1428			goto connecterror;
1429		}
1430
1431		if (rv == -1) {			/* poll error */
1432			goto connecterror;
1433		} else if (pfd[0].revents & (POLLIN|POLLOUT)) {
1434			slen = sizeof(error);	/* OK, or pending error */
1435			if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
1436			    &error, &slen) == -1) {
1437						/* Solaris pending error */
1438				goto connecterror;
1439			} else if (error != 0) {
1440				errno = error;	/* BSD pending error */
1441				goto connecterror;
1442			}
1443		} else {
1444			errno = EBADF;		/* this shouldn't happen ... */
1445			goto connecterror;
1446		}
1447	}
1448
1449	if (fcntl(sock, F_SETFL, flags) == -1) {
1450						/* restore socket flags */
1451		warn("Can't %s socket flags for connect to `%s'",
1452		    "restore", hname);
1453		return -1;
1454	}
1455	return 0;
1456}
1457
1458/*
1459 * Internal version of listen(2); sets socket buffer sizes first.
1460 */
1461int
1462ftp_listen(int sock, int backlog)
1463{
1464
1465	setupsockbufsize(sock);
1466	return (listen(sock, backlog));
1467}
1468
1469/*
1470 * Internal version of poll(2), to allow reimplementation by select(2)
1471 * on platforms without the former.
1472 */
1473int
1474ftp_poll(struct pollfd *fds, int nfds, int timeout)
1475{
1476#if defined(HAVE_POLL)
1477	return poll(fds, nfds, timeout);
1478
1479#elif defined(HAVE_SELECT)
1480		/* implement poll(2) using select(2) */
1481	fd_set		rset, wset, xset;
1482	const int	rsetflags = POLLIN | POLLRDNORM;
1483	const int	wsetflags = POLLOUT | POLLWRNORM;
1484	const int	xsetflags = POLLRDBAND;
1485	struct timeval	tv, *ptv;
1486	int		i, max, rv;
1487
1488	FD_ZERO(&rset);			/* build list of read & write events */
1489	FD_ZERO(&wset);
1490	FD_ZERO(&xset);
1491	max = 0;
1492	for (i = 0; i < nfds; i++) {
1493		if (fds[i].fd > FD_SETSIZE) {
1494			warnx("can't select fd %d", fds[i].fd);
1495			errno = EINVAL;
1496			return -1;
1497		} else if (fds[i].fd > max)
1498			max = fds[i].fd;
1499		if (fds[i].events & rsetflags)
1500			FD_SET(fds[i].fd, &rset);
1501		if (fds[i].events & wsetflags)
1502			FD_SET(fds[i].fd, &wset);
1503		if (fds[i].events & xsetflags)
1504			FD_SET(fds[i].fd, &xset);
1505	}
1506
1507	ptv = &tv;			/* determine timeout */
1508	if (timeout == -1) {		/* wait forever */
1509		ptv = NULL;
1510	} else if (timeout == 0) {	/* poll once */
1511		ptv->tv_sec = 0;
1512		ptv->tv_usec = 0;
1513	}
1514	else if (timeout != 0) {	/* wait timeout milliseconds */
1515		ptv->tv_sec = timeout / 1000;
1516		ptv->tv_usec = (timeout % 1000) * 1000;
1517	}
1518	rv = select(max + 1, &rset, &wset, &xset, ptv);
1519	if (rv <= 0)			/* -1 == error, 0 == timeout */
1520		return rv;
1521
1522	for (i = 0; i < nfds; i++) {	/* determine results */
1523		if (FD_ISSET(fds[i].fd, &rset))
1524			fds[i].revents |= (fds[i].events & rsetflags);
1525		if (FD_ISSET(fds[i].fd, &wset))
1526			fds[i].revents |= (fds[i].events & wsetflags);
1527		if (FD_ISSET(fds[i].fd, &xset))
1528			fds[i].revents |= (fds[i].events & xsetflags);
1529	}
1530	return rv;
1531
1532#else
1533# error no way to implement xpoll
1534#endif
1535}
1536
1537/*
1538 * malloc() with inbuilt error checking
1539 */
1540void *
1541ftp_malloc(size_t size)
1542{
1543	void *p;
1544
1545	p = malloc(size);
1546	if (p == NULL)
1547		err(1, "Unable to allocate %ld bytes of memory", (long)size);
1548	return (p);
1549}
1550
1551/*
1552 * sl_init() with inbuilt error checking
1553 */
1554StringList *
1555ftp_sl_init(void)
1556{
1557	StringList *p;
1558
1559	p = sl_init();
1560	if (p == NULL)
1561		err(1, "Unable to allocate memory for stringlist");
1562	return (p);
1563}
1564
1565/*
1566 * sl_add() with inbuilt error checking
1567 */
1568void
1569ftp_sl_add(StringList *sl, char *i)
1570{
1571
1572	if (sl_add(sl, i) == -1)
1573		err(1, "Unable to add `%s' to stringlist", i);
1574}
1575
1576/*
1577 * strdup() with inbuilt error checking
1578 */
1579char *
1580ftp_strdup(const char *str)
1581{
1582	char *s;
1583
1584	if (str == NULL)
1585		errx(1, "ftp_strdup: called with NULL argument");
1586	s = strdup(str);
1587	if (s == NULL)
1588		err(1, "Unable to allocate memory for string copy");
1589	return (s);
1590}
1591