xref: /freebsd/lib/libfetch/ftp.c (revision d1ba25f456132eabc6f1244e4bbbf3d19e8f3a31)
1 /*-
2  * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * $FreeBSD$
29  */
30 
31 /*
32  * Portions of this code were taken from or based on ftpio.c:
33  *
34  * ----------------------------------------------------------------------------
35  * "THE BEER-WARE LICENSE" (Revision 42):
36  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
37  * can do whatever you want with this stuff. If we meet some day, and you think
38  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
39  * ----------------------------------------------------------------------------
40  *
41  * Major Changelog:
42  *
43  * Dag-Erling Co�dan Sm�rgrav
44  * 9 Jun 1998
45  *
46  * Incorporated into libfetch
47  *
48  * Jordan K. Hubbard
49  * 17 Jan 1996
50  *
51  * Turned inside out. Now returns xfers as new file ids, not as a special
52  * `state' of FTP_t
53  *
54  * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
55  *
56  */
57 
58 #include <sys/param.h>
59 #include <sys/socket.h>
60 #include <netinet/in.h>
61 
62 #include <ctype.h>
63 #include <errno.h>
64 #include <fcntl.h>
65 #include <netdb.h>
66 #include <stdarg.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <time.h>
71 #include <unistd.h>
72 
73 #include "fetch.h"
74 #include "common.h"
75 #include "ftperr.h"
76 
77 #define FTP_ANONYMOUS_USER	"ftp"
78 #define FTP_ANONYMOUS_PASSWORD	"ftp"
79 
80 #define FTP_CONNECTION_ALREADY_OPEN	125
81 #define FTP_OPEN_DATA_CONNECTION	150
82 #define FTP_OK				200
83 #define FTP_FILE_STATUS			213
84 #define FTP_SERVICE_READY		220
85 #define FTP_PASSIVE_MODE		227
86 #define FTP_LPASSIVE_MODE		228
87 #define FTP_EPASSIVE_MODE		229
88 #define FTP_LOGGED_IN			230
89 #define FTP_FILE_ACTION_OK		250
90 #define FTP_NEED_PASSWORD		331
91 #define FTP_NEED_ACCOUNT		332
92 #define FTP_FILE_OK			350
93 #define FTP_SYNTAX_ERROR		500
94 #define FTP_PROTOCOL_ERROR		999
95 
96 static struct url cached_host;
97 static int cached_socket;
98 
99 static char *last_reply;
100 static size_t lr_size, lr_length;
101 static int last_code;
102 
103 #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
104 			 && isdigit(foo[2]) \
105                          && (foo[3] == ' ' || foo[3] == '\0'))
106 #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
107 			&& isdigit(foo[2]) && foo[3] == '-')
108 
109 /* translate IPv4 mapped IPv6 address to IPv4 address */
110 static void
111 unmappedaddr(struct sockaddr_in6 *sin6)
112 {
113     struct sockaddr_in *sin4;
114     u_int32_t addr;
115     int port;
116 
117     if (sin6->sin6_family != AF_INET6 ||
118 	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
119 	return;
120     sin4 = (struct sockaddr_in *)sin6;
121     addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
122     port = sin6->sin6_port;
123     memset(sin4, 0, sizeof(struct sockaddr_in));
124     sin4->sin_addr.s_addr = addr;
125     sin4->sin_port = port;
126     sin4->sin_family = AF_INET;
127     sin4->sin_len = sizeof(struct sockaddr_in);
128 }
129 
130 /*
131  * Get server response
132  */
133 static int
134 _ftp_chkerr(int cd)
135 {
136     if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
137 	_fetch_syserr();
138 	return -1;
139     }
140     if (isftpinfo(last_reply)) {
141 	while (!isftpreply(last_reply)) {
142 	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
143 		_fetch_syserr();
144 		return -1;
145 	    }
146 	}
147     }
148 
149     while (lr_length && isspace(last_reply[lr_length-1]))
150 	lr_length--;
151     last_reply[lr_length] = 0;
152 
153     if (!isftpreply(last_reply)) {
154 	_ftp_seterr(FTP_PROTOCOL_ERROR);
155 	return -1;
156     }
157 
158     last_code = (last_reply[0] - '0') * 100
159 	+ (last_reply[1] - '0') * 10
160 	+ (last_reply[2] - '0');
161 
162     return last_code;
163 }
164 
165 /*
166  * Send a command and check reply
167  */
168 static int
169 _ftp_cmd(int cd, char *fmt, ...)
170 {
171     va_list ap;
172     size_t len;
173     char *msg;
174     int r;
175 
176     va_start(ap, fmt);
177     len = vasprintf(&msg, fmt, ap);
178     va_end(ap);
179 
180     if (msg == NULL) {
181 	errno = ENOMEM;
182 	_fetch_syserr();
183 	return -1;
184     }
185 
186     r = _fetch_putln(cd, msg, len);
187     free(msg);
188 
189     if (r == -1) {
190 	_fetch_syserr();
191 	return -1;
192     }
193 
194     return _ftp_chkerr(cd);
195 }
196 
197 /*
198  * Return a pointer to the filename part of a path
199  */
200 static char *
201 _ftp_filename(char *file)
202 {
203     char *s;
204 
205     if ((s = strrchr(file, '/')) == NULL)
206 	return file;
207     else
208 	return s + 1;
209 }
210 
211 /*
212  * Change working directory to the directory that contains the
213  * specified file.
214  */
215 static int
216 _ftp_cwd(int cd, char *file)
217 {
218     char *s;
219     int e;
220 
221     if ((s = strrchr(file, '/')) == NULL || s == file) {
222 	e = _ftp_cmd(cd, "CWD /");
223     } else {
224 	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
225     }
226     if (e != FTP_FILE_ACTION_OK) {
227 	_ftp_seterr(e);
228 	return -1;
229     }
230     return 0;
231 }
232 
233 /*
234  * Request and parse file stats
235  */
236 static int
237 _ftp_stat(int cd, char *file, struct url_stat *us)
238 {
239     char *ln, *s;
240     struct tm tm;
241     time_t t;
242     int e;
243 
244     us->size = -1;
245     us->atime = us->mtime = 0;
246 
247     if ((s = strrchr(file, '/')) == NULL)
248 	s = file;
249     else
250 	++s;
251 
252     if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
253 	_ftp_seterr(e);
254 	return -1;
255     }
256     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
257 	/* nothing */ ;
258     for (us->size = 0; *ln && isdigit(*ln); ln++)
259 	us->size = us->size * 10 + *ln - '0';
260     if (*ln && !isspace(*ln)) {
261 	_ftp_seterr(FTP_PROTOCOL_ERROR);
262 	return -1;
263     }
264     if (us->size == 0)
265 	us->size = -1;
266     DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
267 
268     if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
269 	_ftp_seterr(e);
270 	return -1;
271     }
272     for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
273 	/* nothing */ ;
274     switch (strspn(ln, "0123456789")) {
275     case 14:
276 	break;
277     case 15:
278 	ln++;
279 	ln[0] = '2';
280 	ln[1] = '0';
281 	break;
282     default:
283 	_ftp_seterr(FTP_PROTOCOL_ERROR);
284 	return -1;
285     }
286     if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
287 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
288 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
289 	_ftp_seterr(FTP_PROTOCOL_ERROR);
290 	return -1;
291     }
292     tm.tm_mon--;
293     tm.tm_year -= 1900;
294     tm.tm_isdst = -1;
295     t = timegm(&tm);
296     if (t == (time_t)-1)
297 	t = time(NULL);
298     us->mtime = t;
299     us->atime = t;
300     DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
301 		  "%02d:%02d:%02d\033[m]\n",
302 		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
303 		  tm.tm_hour, tm.tm_min, tm.tm_sec));
304     return 0;
305 }
306 
307 /*
308  * I/O functions for FTP
309  */
310 struct ftpio {
311     int		 csd;		/* Control socket descriptor */
312     int		 dsd;		/* Data socket descriptor */
313     int		 dir;		/* Direction */
314     int		 eof;		/* EOF reached */
315     int		 err;		/* Error code */
316 };
317 
318 static int	_ftp_readfn(void *, char *, int);
319 static int	_ftp_writefn(void *, const char *, int);
320 static fpos_t	_ftp_seekfn(void *, fpos_t, int);
321 static int	_ftp_closefn(void *);
322 
323 static int
324 _ftp_readfn(void *v, char *buf, int len)
325 {
326     struct ftpio *io;
327     int r;
328 
329     io = (struct ftpio *)v;
330     if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
331 	errno = EBADF;
332 	return -1;
333     }
334     if (io->err) {
335 	errno = io->err;
336 	return -1;
337     }
338     if (io->eof)
339 	return 0;
340     r = read(io->dsd, buf, len);
341     if (r > 0)
342 	return r;
343     if (r == 0) {
344 	io->eof = 1;
345 	return _ftp_closefn(v);
346     }
347     io->err = errno;
348     return -1;
349 }
350 
351 static int
352 _ftp_writefn(void *v, const char *buf, int len)
353 {
354     struct ftpio *io;
355     int w;
356 
357     io = (struct ftpio *)v;
358     if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) {
359 	errno = EBADF;
360 	return -1;
361     }
362     if (io->err) {
363 	errno = io->err;
364 	return -1;
365     }
366     w = write(io->dsd, buf, len);
367     if (w >= 0)
368 	return w;
369     io->err = errno;
370     return -1;
371 }
372 
373 static fpos_t
374 _ftp_seekfn(void *v, fpos_t pos, int whence)
375 {
376     errno = ESPIPE;
377     return -1;
378 }
379 
380 static int
381 _ftp_closefn(void *v)
382 {
383     struct ftpio *io;
384 
385     io = (struct ftpio *)v;
386     if (io->dir == -1)
387 	return 0;
388     if (io->csd == -1 || io->dsd == -1) {
389 	errno = EBADF;
390 	return -1;
391     }
392     io->err = _ftp_chkerr(io->csd);
393     io->dir = -1;
394     if (close(io->dsd) == -1)
395 	return -1;
396     io->dsd = -1;
397     close(io->csd);
398     io->csd = -1;
399     return io->err ? -1 : 0;
400 }
401 
402 static FILE *
403 _ftp_setup(int csd, int dsd, int mode)
404 {
405     struct ftpio *io;
406     FILE *f;
407 
408     if ((io = malloc(sizeof *io)) == NULL)
409 	return NULL;
410     io->csd = dup(csd);
411     io->dsd = dsd;
412     io->dir = mode;
413     io->eof = io->err = 0;
414     f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
415     if (f == NULL)
416 	free(io);
417     return f;
418 }
419 
420 /*
421  * Transfer file
422  */
423 static FILE *
424 _ftp_transfer(int cd, char *oper, char *file,
425 	      int mode, off_t offset, char *flags)
426 {
427     struct sockaddr_storage sin;
428     struct sockaddr_in6 *sin6;
429     struct sockaddr_in *sin4;
430     int pasv, high, verbose;
431     int e, sd = -1;
432     socklen_t l;
433     char *s;
434     FILE *df;
435 
436     /* check flags */
437     pasv = (flags && strchr(flags, 'p'));
438     high = (flags && strchr(flags, 'h'));
439     verbose = (flags && strchr(flags, 'v'));
440 
441     /* passive mode */
442     if (!pasv)
443 	pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL ||
444 		strncasecmp(s, "no", 2) != 0);
445 
446     /* find our own address, bind, and listen */
447     l = sizeof sin;
448     if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
449 	goto sysouch;
450     if (sin.ss_family == AF_INET6)
451 	unmappedaddr((struct sockaddr_in6 *)&sin);
452 
453     /* open data socket */
454     if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
455 	_fetch_syserr();
456 	return NULL;
457     }
458 
459     if (pasv) {
460 	u_char addr[64];
461 	char *ln, *p;
462 	int i;
463 	int port;
464 
465 	/* send PASV command */
466 	if (verbose)
467 	    _fetch_info("setting passive mode");
468 	switch (sin.ss_family) {
469 	case AF_INET:
470 	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
471 		goto ouch;
472 	    break;
473 	case AF_INET6:
474 	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
475 		if (e == -1)
476 		    goto ouch;
477 		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
478 		    goto ouch;
479 	    }
480 	    break;
481 	default:
482 	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
483 	    goto ouch;
484 	}
485 
486 	/*
487 	 * Find address and port number. The reply to the PASV command
488          * is IMHO the one and only weak point in the FTP protocol.
489 	 */
490 	ln = last_reply;
491       	switch (e) {
492 	case FTP_PASSIVE_MODE:
493 	case FTP_LPASSIVE_MODE:
494 	    for (p = ln + 3; *p && !isdigit(*p); p++)
495 		/* nothing */ ;
496 	    if (!*p) {
497 		e = FTP_PROTOCOL_ERROR;
498 		goto ouch;
499 	    }
500 	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
501 	    for (i = 0; *p && i < l; i++, p++)
502 		addr[i] = strtol(p, &p, 10);
503 	    if (i < l) {
504 		e = FTP_PROTOCOL_ERROR;
505 		goto ouch;
506 	    }
507 	    break;
508 	case FTP_EPASSIVE_MODE:
509 	    for (p = ln + 3; *p && *p != '('; p++)
510 		/* nothing */ ;
511 	    if (!*p) {
512 		e = FTP_PROTOCOL_ERROR;
513 		goto ouch;
514 	    }
515 	    ++p;
516 	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
517 		       &port, &addr[3]) != 5 ||
518 		addr[0] != addr[1] ||
519 		addr[0] != addr[2] || addr[0] != addr[3]) {
520 		e = FTP_PROTOCOL_ERROR;
521 		goto ouch;
522 	    }
523 	    break;
524 	}
525 
526 	/* seek to required offset */
527 	if (offset)
528 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
529 		goto sysouch;
530 
531 	/* construct sockaddr for data socket */
532 	l = sizeof sin;
533 	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
534 	    goto sysouch;
535 	if (sin.ss_family == AF_INET6)
536 	    unmappedaddr((struct sockaddr_in6 *)&sin);
537 	switch (sin.ss_family) {
538 	case AF_INET6:
539 	    sin6 = (struct sockaddr_in6 *)&sin;
540 	    if (e == FTP_EPASSIVE_MODE)
541 		sin6->sin6_port = htons(port);
542 	    else {
543 		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
544 		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
545 	    }
546 	    break;
547 	case AF_INET:
548 	    sin4 = (struct sockaddr_in *)&sin;
549 	    if (e == FTP_EPASSIVE_MODE)
550 		sin4->sin_port = htons(port);
551 	    else {
552 		bcopy(addr, (char *)&sin4->sin_addr, 4);
553 		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
554 	    }
555 	    break;
556 	default:
557 	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
558 	    break;
559 	}
560 
561 	/* connect to data port */
562 	if (verbose)
563 	    _fetch_info("opening data connection");
564 	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
565 	    goto sysouch;
566 
567 	/* make the server initiate the transfer */
568 	if (verbose)
569 	    _fetch_info("initiating transfer");
570 	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
571 	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
572 	    goto ouch;
573 
574     } else {
575 	u_int32_t a;
576 	u_short p;
577 	int arg, d;
578 	char *ap;
579 	char hname[INET6_ADDRSTRLEN];
580 
581 	switch (sin.ss_family) {
582 	case AF_INET6:
583 	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
584 #ifdef IPV6_PORTRANGE
585 	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
586 	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
587 			   (char *)&arg, sizeof(arg)) == -1)
588 		goto sysouch;
589 #endif
590 	    break;
591 	case AF_INET:
592 	    ((struct sockaddr_in *)&sin)->sin_port = 0;
593 	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
594 	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
595 			   (char *)&arg, sizeof arg) == -1)
596 		goto sysouch;
597 	    break;
598 	}
599 	if (verbose)
600 	    _fetch_info("binding data socket");
601 	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
602 	    goto sysouch;
603 	if (listen(sd, 1) == -1)
604 	    goto sysouch;
605 
606 	/* find what port we're on and tell the server */
607 	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
608 	    goto sysouch;
609 	switch (sin.ss_family) {
610 	case AF_INET:
611 	    sin4 = (struct sockaddr_in *)&sin;
612 	    a = ntohl(sin4->sin_addr.s_addr);
613 	    p = ntohs(sin4->sin_port);
614 	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
615 			 (a >> 24) & 0xff, (a >> 16) & 0xff,
616 			 (a >> 8) & 0xff, a & 0xff,
617 			 (p >> 8) & 0xff, p & 0xff);
618 	    break;
619 	case AF_INET6:
620 #define UC(b)	(((int)b)&0xff)
621 	    e = -1;
622 	    sin6 = (struct sockaddr_in6 *)&sin;
623 	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
624 			    hname, sizeof(hname),
625 			    NULL, 0, NI_NUMERICHOST) == 0) {
626 		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
627 			     htons(sin6->sin6_port));
628 		if (e == -1)
629 		    goto ouch;
630 	    }
631 	    if (e != FTP_OK) {
632 		ap = (char *)&sin6->sin6_addr;
633 		e = _ftp_cmd(cd,
634      "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
635 			     6, 16,
636 			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
637 			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
638 			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
639 			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
640 			     2,
641 			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
642 			     ntohs(sin6->sin6_port)        & 0xff);
643 	    }
644 	    break;
645 	default:
646 	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
647 	    goto ouch;
648 	}
649 	if (e != FTP_OK)
650 	    goto ouch;
651 
652 	/* seek to required offset */
653 	if (offset)
654 	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
655 		goto sysouch;
656 
657 	/* make the server initiate the transfer */
658 	if (verbose)
659 	    _fetch_info("initiating transfer");
660 	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
661 	if (e != FTP_OPEN_DATA_CONNECTION)
662 	    goto ouch;
663 
664 	/* accept the incoming connection and go to town */
665 	if ((d = accept(sd, NULL, NULL)) == -1)
666 	    goto sysouch;
667 	close(sd);
668 	sd = d;
669     }
670 
671     if ((df = _ftp_setup(cd, sd, mode)) == NULL)
672 	goto sysouch;
673     return df;
674 
675 sysouch:
676     _fetch_syserr();
677     if (sd >= 0)
678 	close(sd);
679     return NULL;
680 
681 ouch:
682     if (e != -1)
683 	_ftp_seterr(e);
684     if (sd >= 0)
685 	close(sd);
686     return NULL;
687 }
688 
689 /*
690  * Return default port
691  */
692 static int
693 _ftp_default_port(void)
694 {
695     struct servent *se;
696 
697     if ((se = getservbyname(SCHEME_FTP, "tcp")) != NULL)
698 	return ntohs(se->s_port);
699     return FTP_DEFAULT_PORT;
700 }
701 
702 /*
703  * Log on to FTP server
704  */
705 static int
706 _ftp_connect(struct url *url, struct url *purl, char *flags)
707 {
708     int cd, e, direct, verbose;
709 #ifdef INET6
710     int af = AF_UNSPEC;
711 #else
712     int af = AF_INET;
713 #endif
714     const char *logname;
715     char *user, *pwd;
716     char localhost[MAXHOSTNAMELEN];
717     char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
718 
719     direct = (flags && strchr(flags, 'd'));
720     verbose = (flags && strchr(flags, 'v'));
721     if ((flags && strchr(flags, '4')))
722 	af = AF_INET;
723     else if ((flags && strchr(flags, '6')))
724 	af = AF_INET6;
725 
726     if (direct)
727 	purl = NULL;
728 
729     /* check for proxy */
730     if (purl) {
731 	/* XXX proxy authentication! */
732 	cd = _fetch_connect(purl->host, purl->port, af, verbose);
733     } else {
734 	/* no proxy, go straight to target */
735 	cd = _fetch_connect(url->host, url->port, af, verbose);
736 	purl = NULL;
737     }
738 
739     /* check connection */
740     if (cd == -1) {
741 	_fetch_syserr();
742 	return NULL;
743     }
744 
745     /* expect welcome message */
746     if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
747 	goto fouch;
748 
749     /* XXX FTP_AUTH, and maybe .netrc */
750 
751     /* send user name and password */
752     user = url->user;
753     if (!user || !*user)
754 	user = FTP_ANONYMOUS_USER;
755     if (purl && url->port == FTP_DEFAULT_PORT)
756 	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
757     else if (purl)
758 	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
759     else
760 	e = _ftp_cmd(cd, "USER %s", user);
761 
762     /* did the server request a password? */
763     if (e == FTP_NEED_PASSWORD) {
764 	pwd = url->pwd;
765 	if (!pwd || !*pwd)
766 	    pwd = getenv("FTP_PASSWORD");
767 	if (!pwd || !*pwd) {
768 	    if ((logname = getlogin()) == 0)
769 		logname = FTP_ANONYMOUS_PASSWORD;
770 	    gethostname(localhost, sizeof localhost);
771 	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
772 	    pwd = pbuf;
773 	}
774 	e = _ftp_cmd(cd, "PASS %s", pwd);
775     }
776 
777     /* did the server request an account? */
778     if (e == FTP_NEED_ACCOUNT)
779 	goto fouch;
780 
781     /* we should be done by now */
782     if (e != FTP_LOGGED_IN)
783 	goto fouch;
784 
785     /* might as well select mode and type at once */
786 #ifdef FTP_FORCE_STREAM_MODE
787     if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
788 	goto fouch;
789 #endif
790     if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
791 	goto fouch;
792 
793     /* done */
794     return cd;
795 
796 fouch:
797     if (e != -1)
798 	_ftp_seterr(e);
799     close(cd);
800     return NULL;
801 }
802 
803 /*
804  * Disconnect from server
805  */
806 static void
807 _ftp_disconnect(int cd)
808 {
809     (void)_ftp_cmd(cd, "QUIT");
810     close(cd);
811 }
812 
813 /*
814  * Check if we're already connected
815  */
816 static int
817 _ftp_isconnected(struct url *url)
818 {
819     return (cached_socket
820 	    && (strcmp(url->host, cached_host.host) == 0)
821 	    && (strcmp(url->user, cached_host.user) == 0)
822 	    && (strcmp(url->pwd, cached_host.pwd) == 0)
823 	    && (url->port == cached_host.port));
824 }
825 
826 /*
827  * Check the cache, reconnect if no luck
828  */
829 static int
830 _ftp_cached_connect(struct url *url, struct url *purl, char *flags)
831 {
832     int e, cd;
833 
834     cd = -1;
835 
836     /* set default port */
837     if (!url->port)
838 	url->port = _ftp_default_port();
839 
840     /* try to use previously cached connection */
841     if (_ftp_isconnected(url)) {
842 	e = _ftp_cmd(cached_socket, "NOOP");
843 	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
844 	    return cached_socket;
845     }
846 
847     /* connect to server */
848     if ((cd = _ftp_connect(url, purl, flags)) == -1)
849 	return -1;
850     if (cached_socket)
851 	_ftp_disconnect(cached_socket);
852     cached_socket = cd;
853     memcpy(&cached_host, url, sizeof *url);
854     return cd;
855 }
856 
857 /*
858  * Check the proxy settings
859  */
860 static struct url *
861 _ftp_get_proxy(void)
862 {
863     struct url *purl;
864     char *p;
865 
866     if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) &&
867 	*p && (purl = fetchParseURL(p)) != NULL) {
868 	if (!*purl->scheme)
869 	    strcpy(purl->scheme, SCHEME_FTP);
870 	if (!purl->port)
871 	    purl->port = _ftp_default_port();
872 	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
873 	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
874 	    return purl;
875 	fetchFreeURL(purl);
876     }
877     return NULL;
878 }
879 
880 /*
881  * Get and stat file
882  */
883 FILE *
884 fetchXGetFTP(struct url *url, struct url_stat *us, char *flags)
885 {
886     struct url *purl;
887     int cd;
888 
889     /* get the proxy URL, and check if we should use HTTP instead */
890     if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) {
891 	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
892 	    return _http_request(url, "GET", us, purl, flags);
893     } else {
894 	purl = NULL;
895     }
896 
897     /* connect to server */
898     cd = _ftp_cached_connect(url, purl, flags);
899     if (purl)
900 	fetchFreeURL(purl);
901     if (cd == NULL)
902 	return NULL;
903 
904     /* change directory */
905     if (_ftp_cwd(cd, url->doc) == -1)
906 	return NULL;
907 
908     /* stat file */
909     if (us && _ftp_stat(cd, url->doc, us) == -1
910 	&& fetchLastErrCode != FETCH_PROTO
911 	&& fetchLastErrCode != FETCH_UNAVAIL)
912 	return NULL;
913 
914     /* initiate the transfer */
915     return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
916 }
917 
918 /*
919  * Get file
920  */
921 FILE *
922 fetchGetFTP(struct url *url, char *flags)
923 {
924     return fetchXGetFTP(url, NULL, flags);
925 }
926 
927 /*
928  * Put file
929  */
930 FILE *
931 fetchPutFTP(struct url *url, char *flags)
932 {
933     struct url *purl;
934     int cd;
935 
936     /* get the proxy URL, and check if we should use HTTP instead */
937     if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) {
938 	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
939 	    /* XXX HTTP PUT is not implemented, so try without the proxy */
940 	    purl = NULL;
941     } else {
942 	purl = NULL;
943     }
944 
945     /* connect to server */
946     cd = _ftp_cached_connect(url, purl, flags);
947     if (purl)
948 	fetchFreeURL(purl);
949     if (cd == NULL)
950 	return NULL;
951 
952     /* change directory */
953     if (_ftp_cwd(cd, url->doc) == -1)
954 	return NULL;
955 
956     /* initiate the transfer */
957     return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
958 			 url->doc, O_WRONLY, url->offset, flags);
959 }
960 
961 /*
962  * Get file stats
963  */
964 int
965 fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
966 {
967     struct url *purl;
968     int cd;
969 
970     /* get the proxy URL, and check if we should use HTTP instead */
971     if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) {
972 	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
973 	    FILE *f;
974 
975 	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
976 		return -1;
977 	    fclose(f);
978 	    return 0;
979 	}
980     } else {
981 	purl = NULL;
982     }
983 
984     /* connect to server */
985     cd = _ftp_cached_connect(url, purl, flags);
986     if (purl)
987 	fetchFreeURL(purl);
988     if (cd == NULL)
989 	return NULL;
990 
991     /* change directory */
992     if (_ftp_cwd(cd, url->doc) == -1)
993 	return -1;
994 
995     /* stat file */
996     return _ftp_stat(cd, url->doc, us);
997 }
998 
999 /*
1000  * List a directory
1001  */
1002 extern void warnx(char *, ...);
1003 struct url_ent *
1004 fetchListFTP(struct url *url, char *flags)
1005 {
1006     warnx("fetchListFTP(): not implemented");
1007     return NULL;
1008 }
1009