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