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