ftp.c revision 67890
1272343Sngie/*-
2272343Sngie * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3272343Sngie * All rights reserved.
4272343Sngie *
5272343Sngie * Redistribution and use in source and binary forms, with or without
6272343Sngie * modification, are permitted provided that the following conditions
7272343Sngie * are met:
8272343Sngie * 1. Redistributions of source code must retain the above copyright
9272343Sngie *    notice, this list of conditions and the following disclaimer
10272343Sngie *    in this position and unchanged.
11272343Sngie * 2. Redistributions in binary form must reproduce the above copyright
12272343Sngie *    notice, this list of conditions and the following disclaimer in the
13272343Sngie *    documentation and/or other materials provided with the distribution.
14272343Sngie * 3. The name of the author may not be used to endorse or promote products
15272343Sngie *    derived from this software without specific prior written permission
16272343Sngie *
17272343Sngie * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18272343Sngie * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19272343Sngie * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20272343Sngie * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21272343Sngie * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22272343Sngie * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23272343Sngie * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24272343Sngie * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25272343Sngie * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26272343Sngie * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27272343Sngie *
28272343Sngie * $FreeBSD: head/lib/libfetch/ftp.c 67890 2000-10-29 15:52:05Z des $
29272343Sngie */
30272343Sngie
31272343Sngie/*
32272343Sngie * Portions of this code were taken from or based on ftpio.c:
33272343Sngie *
34272343Sngie * ----------------------------------------------------------------------------
35272343Sngie * "THE BEER-WARE LICENSE" (Revision 42):
36272343Sngie * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
37272343Sngie * can do whatever you want with this stuff. If we meet some day, and you think
38272343Sngie * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
39272343Sngie * ----------------------------------------------------------------------------
40272343Sngie *
41272343Sngie * Major Changelog:
42272343Sngie *
43272343Sngie * Dag-Erling Co�dan Sm�rgrav
44272343Sngie * 9 Jun 1998
45272343Sngie *
46272343Sngie * Incorporated into libfetch
47272343Sngie *
48272343Sngie * Jordan K. Hubbard
49272343Sngie * 17 Jan 1996
50272343Sngie *
51272343Sngie * Turned inside out. Now returns xfers as new file ids, not as a special
52272343Sngie * `state' of FTP_t
53272343Sngie *
54272343Sngie * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
55272343Sngie *
56272343Sngie */
57272343Sngie
58272343Sngie#include <sys/param.h>
59272343Sngie#include <sys/socket.h>
60272343Sngie#include <netinet/in.h>
61272343Sngie
62272343Sngie#include <ctype.h>
63272343Sngie#include <errno.h>
64272343Sngie#include <fcntl.h>
65272343Sngie#include <netdb.h>
66272343Sngie#include <stdarg.h>
67272343Sngie#include <stdio.h>
68272343Sngie#include <stdlib.h>
69272343Sngie#include <string.h>
70272343Sngie#include <time.h>
71272343Sngie#include <unistd.h>
72272343Sngie
73272343Sngie#include "fetch.h"
74272343Sngie#include "common.h"
75272343Sngie#include "ftperr.h"
76272343Sngie
77272343Sngie#define FTP_ANONYMOUS_USER	"ftp"
78272343Sngie#define FTP_ANONYMOUS_PASSWORD	"ftp"
79272343Sngie
80272343Sngie#define FTP_CONNECTION_ALREADY_OPEN	125
81272343Sngie#define FTP_OPEN_DATA_CONNECTION	150
82272343Sngie#define FTP_OK				200
83272343Sngie#define FTP_FILE_STATUS			213
84272343Sngie#define FTP_SERVICE_READY		220
85272343Sngie#define FTP_TRANSFER_COMPLETE		226
86272343Sngie#define FTP_PASSIVE_MODE		227
87272343Sngie#define FTP_LPASSIVE_MODE		228
88272343Sngie#define FTP_EPASSIVE_MODE		229
89272343Sngie#define FTP_LOGGED_IN			230
90272343Sngie#define FTP_FILE_ACTION_OK		250
91272343Sngie#define FTP_NEED_PASSWORD		331
92272343Sngie#define FTP_NEED_ACCOUNT		332
93272343Sngie#define FTP_FILE_OK			350
94272343Sngie#define FTP_SYNTAX_ERROR		500
95272343Sngie#define FTP_PROTOCOL_ERROR		999
96272343Sngie
97272343Sngiestatic struct url cached_host;
98272343Sngiestatic int cached_socket;
99272343Sngie
100272343Sngiestatic char *last_reply;
101272343Sngiestatic size_t lr_size, lr_length;
102272343Sngiestatic int last_code;
103272343Sngie
104272343Sngie#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105272343Sngie			 && isdigit(foo[2]) \
106272343Sngie                         && (foo[3] == ' ' || foo[3] == '\0'))
107272343Sngie#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
108272343Sngie			&& isdigit(foo[2]) && foo[3] == '-')
109272343Sngie
110272343Sngie/* translate IPv4 mapped IPv6 address to IPv4 address */
111272343Sngiestatic void
112272343Sngieunmappedaddr(struct sockaddr_in6 *sin6)
113272343Sngie{
114272343Sngie    struct sockaddr_in *sin4;
115272343Sngie    u_int32_t addr;
116272343Sngie    int port;
117272343Sngie
118272343Sngie    if (sin6->sin6_family != AF_INET6 ||
119272343Sngie	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
120272343Sngie	return;
121272343Sngie    sin4 = (struct sockaddr_in *)sin6;
122272343Sngie    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
123272343Sngie    port = sin6->sin6_port;
124272343Sngie    memset(sin4, 0, sizeof(struct sockaddr_in));
125272343Sngie    sin4->sin_addr.s_addr = addr;
126272343Sngie    sin4->sin_port = port;
127272343Sngie    sin4->sin_family = AF_INET;
128272343Sngie    sin4->sin_len = sizeof(struct sockaddr_in);
129272343Sngie}
130272343Sngie
131272343Sngie/*
132272343Sngie * Get server response
133272343Sngie */
134272343Sngiestatic int
135272343Sngie_ftp_chkerr(int cd)
136272343Sngie{
137272343Sngie    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
138272343Sngie	_fetch_syserr();
139272343Sngie	return -1;
140272343Sngie    }
141272343Sngie    if (isftpinfo(last_reply)) {
142272343Sngie	while (!isftpreply(last_reply)) {
143272343Sngie	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
144272343Sngie		_fetch_syserr();
145272343Sngie		return -1;
146272343Sngie	    }
147272343Sngie	}
148272343Sngie    }
149272343Sngie
150272343Sngie    while (lr_length && isspace(last_reply[lr_length-1]))
151272343Sngie	lr_length--;
152272343Sngie    last_reply[lr_length] = 0;
153272343Sngie
154272343Sngie    if (!isftpreply(last_reply)) {
155272343Sngie	_ftp_seterr(FTP_PROTOCOL_ERROR);
156272343Sngie	return -1;
157272343Sngie    }
158272343Sngie
159272343Sngie    last_code = (last_reply[0] - '0') * 100
160272343Sngie	+ (last_reply[1] - '0') * 10
161272343Sngie	+ (last_reply[2] - '0');
162272343Sngie
163272343Sngie    return last_code;
164272343Sngie}
165272343Sngie
166272343Sngie/*
167272343Sngie * Send a command and check reply
168272343Sngie */
169272343Sngiestatic int
170272343Sngie_ftp_cmd(int cd, char *fmt, ...)
171272343Sngie{
172272343Sngie    va_list ap;
173272343Sngie    size_t len;
174272343Sngie    char *msg;
175272343Sngie    int r;
176272343Sngie
177272343Sngie    va_start(ap, fmt);
178272343Sngie    len = vasprintf(&msg, fmt, ap);
179272343Sngie    va_end(ap);
180272343Sngie
181272343Sngie    if (msg == NULL) {
182272343Sngie	errno = ENOMEM;
183272343Sngie	_fetch_syserr();
184272343Sngie	return -1;
185272343Sngie    }
186272343Sngie
187272343Sngie    r = _fetch_putln(cd, msg, len);
188272343Sngie    free(msg);
189272343Sngie
190272343Sngie    if (r == -1) {
191272343Sngie	_fetch_syserr();
192272343Sngie	return -1;
193272343Sngie    }
194272343Sngie
195272343Sngie    return _ftp_chkerr(cd);
196272343Sngie}
197272343Sngie
198272343Sngie/*
199272343Sngie * Return a pointer to the filename part of a path
200272343Sngie */
201272343Sngiestatic char *
202272343Sngie_ftp_filename(char *file)
203272343Sngie{
204272343Sngie    char *s;
205272343Sngie
206272343Sngie    if ((s = strrchr(file, '/')) == NULL)
207272343Sngie	return file;
208272343Sngie    else
209272343Sngie	return s + 1;
210272343Sngie}
211272343Sngie
212272343Sngie/*
213272343Sngie * Change working directory to the directory that contains the
214272343Sngie * specified file.
215272343Sngie */
216272343Sngiestatic int
217272343Sngie_ftp_cwd(int cd, char *file)
218272343Sngie{
219272343Sngie    char *s;
220272343Sngie    int e;
221272343Sngie
222272343Sngie    if ((s = strrchr(file, '/')) == NULL || s == file) {
223272343Sngie	e = _ftp_cmd(cd, "CWD /");
224272343Sngie    } else {
225272343Sngie	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
226272343Sngie    }
227272343Sngie    if (e != FTP_FILE_ACTION_OK) {
228272343Sngie	_ftp_seterr(e);
229272343Sngie	return -1;
230272343Sngie    }
231272343Sngie    return 0;
232272343Sngie}
233272343Sngie
234272343Sngie/*
235272343Sngie * Request and parse file stats
236272343Sngie */
237272343Sngiestatic int
238272343Sngie_ftp_stat(int cd, char *file, struct url_stat *us)
239272343Sngie{
240272343Sngie    char *ln, *s;
241272343Sngie    struct tm tm;
242272343Sngie    time_t t;
243272343Sngie    int e;
244272343Sngie
245272343Sngie    us->size = -1;
246272343Sngie    us->atime = us->mtime = 0;
247272343Sngie
248272343Sngie    if ((s = strrchr(file, '/')) == NULL)
249272343Sngie	s = file;
250272343Sngie    else
251272343Sngie	++s;
252272343Sngie
253272343Sngie    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
254272343Sngie	_ftp_seterr(e);
255272343Sngie	return -1;
256272343Sngie    }
257272343Sngie    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
258272343Sngie	/* nothing */ ;
259272343Sngie    for (us->size = 0; *ln && isdigit(*ln); ln++)
260272343Sngie	us->size = us->size * 10 + *ln - '0';
261272343Sngie    if (*ln && !isspace(*ln)) {
262272343Sngie	_ftp_seterr(FTP_PROTOCOL_ERROR);
263272343Sngie	return -1;
264272343Sngie    }
265272343Sngie    if (us->size == 0)
266272343Sngie	us->size = -1;
267272343Sngie    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
268272343Sngie
269272343Sngie    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
270272343Sngie	_ftp_seterr(e);
271272343Sngie	return -1;
272272343Sngie    }
273272343Sngie    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
274272343Sngie	/* nothing */ ;
275272343Sngie    switch (strspn(ln, "0123456789")) {
276272343Sngie    case 14:
277272343Sngie	break;
278272343Sngie    case 15:
279272343Sngie	ln++;
280272343Sngie	ln[0] = '2';
281272343Sngie	ln[1] = '0';
282272343Sngie	break;
283272343Sngie    default:
284272343Sngie	_ftp_seterr(FTP_PROTOCOL_ERROR);
285272343Sngie	return -1;
286272343Sngie    }
287272343Sngie    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
288272343Sngie	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
289272343Sngie	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
290272343Sngie	_ftp_seterr(FTP_PROTOCOL_ERROR);
291272343Sngie	return -1;
292272343Sngie    }
293272343Sngie    tm.tm_mon--;
294272343Sngie    tm.tm_year -= 1900;
295272343Sngie    tm.tm_isdst = -1;
296272343Sngie    t = timegm(&tm);
297272343Sngie    if (t == (time_t)-1)
298272343Sngie	t = time(NULL);
299272343Sngie    us->mtime = t;
300272343Sngie    us->atime = t;
301272343Sngie    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
302272343Sngie		  "%02d:%02d:%02d\033[m]\n",
303272343Sngie		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
304272343Sngie		  tm.tm_hour, tm.tm_min, tm.tm_sec));
305272343Sngie    return 0;
306272343Sngie}
307272343Sngie
308272343Sngie/*
309272343Sngie * I/O functions for FTP
310272343Sngie */
311272343Sngiestruct ftpio {
312272343Sngie    int		 csd;		/* Control socket descriptor */
313272343Sngie    int		 dsd;		/* Data socket descriptor */
314272343Sngie    int		 dir;		/* Direction */
315272343Sngie    int		 eof;		/* EOF reached */
316272343Sngie    int		 err;		/* Error code */
317272343Sngie};
318272343Sngie
319272343Sngiestatic int	_ftp_readfn(void *, char *, int);
320272343Sngiestatic int	_ftp_writefn(void *, const char *, int);
321272343Sngiestatic fpos_t	_ftp_seekfn(void *, fpos_t, int);
322272343Sngiestatic int	_ftp_closefn(void *);
323272343Sngie
324272343Sngiestatic int
325272343Sngie_ftp_readfn(void *v, char *buf, int len)
326272343Sngie{
327272343Sngie    struct ftpio *io;
328272343Sngie    int r;
329272343Sngie
330272343Sngie    io = (struct ftpio *)v;
331272343Sngie    if (io == NULL) {
332272343Sngie	errno = EBADF;
333272343Sngie	return -1;
334272343Sngie    }
335272343Sngie    if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
336272343Sngie	errno = EBADF;
337272343Sngie	return -1;
338272343Sngie    }
339272343Sngie    if (io->err) {
340272343Sngie	errno = io->err;
341272343Sngie	return -1;
342272343Sngie    }
343272343Sngie    if (io->eof)
344272343Sngie	return 0;
345272343Sngie    r = read(io->dsd, buf, len);
346272343Sngie    if (r > 0)
347272343Sngie	return r;
348272343Sngie    if (r == 0) {
349272343Sngie	io->eof = 1;
350272343Sngie	return _ftp_closefn(v);
351272343Sngie    }
352272343Sngie    io->err = errno;
353272343Sngie    return -1;
354272343Sngie}
355272343Sngie
356static 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
382static 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
396static 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
426static 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 */
447static 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 = (flags && strchr(flags, 'p'));
462    high = (flags && strchr(flags, 'h'));
463    verbose = (flags && strchr(flags, '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
699sysouch:
700    _fetch_syserr();
701    if (sd >= 0)
702	close(sd);
703    return NULL;
704
705ouch:
706    if (e != -1)
707	_ftp_seterr(e);
708    if (sd >= 0)
709	close(sd);
710    return NULL;
711}
712
713/*
714 * Return default port
715 */
716static int
717_ftp_default_port(void)
718{
719    struct servent *se;
720
721    if ((se = getservbyname(SCHEME_FTP, "tcp")) != NULL)
722	return ntohs(se->s_port);
723    return FTP_DEFAULT_PORT;
724}
725
726/*
727 * Log on to FTP server
728 */
729static int
730_ftp_connect(struct url *url, struct url *purl, char *flags)
731{
732    int cd, e, direct, verbose;
733#ifdef INET6
734    int af = AF_UNSPEC;
735#else
736    int af = AF_INET;
737#endif
738    const char *logname;
739    char *user, *pwd;
740    char localhost[MAXHOSTNAMELEN];
741    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
742
743    direct = (flags && strchr(flags, 'd'));
744    verbose = (flags && strchr(flags, 'v'));
745    if ((flags && strchr(flags, '4')))
746	af = AF_INET;
747    else if ((flags && strchr(flags, '6')))
748	af = AF_INET6;
749
750    if (direct)
751	purl = NULL;
752
753    /* check for proxy */
754    if (purl) {
755	/* XXX proxy authentication! */
756	cd = _fetch_connect(purl->host, purl->port, af, verbose);
757    } else {
758	/* no proxy, go straight to target */
759	cd = _fetch_connect(url->host, url->port, af, verbose);
760	purl = NULL;
761    }
762
763    /* check connection */
764    if (cd == -1) {
765	_fetch_syserr();
766	return NULL;
767    }
768
769    /* expect welcome message */
770    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
771	goto fouch;
772
773    /* XXX FTP_AUTH, and maybe .netrc */
774
775    /* send user name and password */
776    user = url->user;
777    if (!user || !*user)
778	user = FTP_ANONYMOUS_USER;
779    if (purl && url->port == FTP_DEFAULT_PORT)
780	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
781    else if (purl)
782	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
783    else
784	e = _ftp_cmd(cd, "USER %s", user);
785
786    /* did the server request a password? */
787    if (e == FTP_NEED_PASSWORD) {
788	pwd = url->pwd;
789	if (!pwd || !*pwd)
790	    pwd = getenv("FTP_PASSWORD");
791	if (!pwd || !*pwd) {
792	    if ((logname = getlogin()) == 0)
793		logname = FTP_ANONYMOUS_PASSWORD;
794	    gethostname(localhost, sizeof localhost);
795	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
796	    pwd = pbuf;
797	}
798	e = _ftp_cmd(cd, "PASS %s", pwd);
799    }
800
801    /* did the server request an account? */
802    if (e == FTP_NEED_ACCOUNT)
803	goto fouch;
804
805    /* we should be done by now */
806    if (e != FTP_LOGGED_IN)
807	goto fouch;
808
809    /* might as well select mode and type at once */
810#ifdef FTP_FORCE_STREAM_MODE
811    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
812	goto fouch;
813#endif
814    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
815	goto fouch;
816
817    /* done */
818    return cd;
819
820fouch:
821    if (e != -1)
822	_ftp_seterr(e);
823    close(cd);
824    return NULL;
825}
826
827/*
828 * Disconnect from server
829 */
830static void
831_ftp_disconnect(int cd)
832{
833    (void)_ftp_cmd(cd, "QUIT");
834    close(cd);
835}
836
837/*
838 * Check if we're already connected
839 */
840static int
841_ftp_isconnected(struct url *url)
842{
843    return (cached_socket
844	    && (strcmp(url->host, cached_host.host) == 0)
845	    && (strcmp(url->user, cached_host.user) == 0)
846	    && (strcmp(url->pwd, cached_host.pwd) == 0)
847	    && (url->port == cached_host.port));
848}
849
850/*
851 * Check the cache, reconnect if no luck
852 */
853static int
854_ftp_cached_connect(struct url *url, struct url *purl, char *flags)
855{
856    int e, cd;
857
858    cd = -1;
859
860    /* set default port */
861    if (!url->port)
862	url->port = _ftp_default_port();
863
864    /* try to use previously cached connection */
865    if (_ftp_isconnected(url)) {
866	e = _ftp_cmd(cached_socket, "NOOP");
867	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
868	    return cached_socket;
869    }
870
871    /* connect to server */
872    if ((cd = _ftp_connect(url, purl, flags)) == -1)
873	return -1;
874    if (cached_socket)
875	_ftp_disconnect(cached_socket);
876    cached_socket = cd;
877    memcpy(&cached_host, url, sizeof *url);
878    return cd;
879}
880
881/*
882 * Check the proxy settings
883 */
884static struct url *
885_ftp_get_proxy(void)
886{
887    struct url *purl;
888    char *p;
889
890    if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) &&
891	*p && (purl = fetchParseURL(p)) != NULL) {
892	if (!*purl->scheme)
893	    strcpy(purl->scheme, SCHEME_FTP);
894	if (!purl->port)
895	    purl->port = _ftp_default_port();
896	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
897	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
898	    return purl;
899	fetchFreeURL(purl);
900    }
901    return NULL;
902}
903
904/*
905 * Get and stat file
906 */
907FILE *
908fetchXGetFTP(struct url *url, struct url_stat *us, char *flags)
909{
910    struct url *purl;
911    int cd;
912
913    /* get the proxy URL, and check if we should use HTTP instead */
914    if (!(flags && strchr(flags, 'd')) && (purl = _ftp_get_proxy()) != NULL) {
915	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
916	    return _http_request(url, "GET", us, purl, flags);
917    } else {
918	purl = NULL;
919    }
920
921    /* connect to server */
922    cd = _ftp_cached_connect(url, purl, flags);
923    if (purl)
924	fetchFreeURL(purl);
925    if (cd == NULL)
926	return NULL;
927
928    /* change directory */
929    if (_ftp_cwd(cd, url->doc) == -1)
930	return NULL;
931
932    /* stat file */
933    if (us && _ftp_stat(cd, url->doc, us) == -1
934	&& fetchLastErrCode != FETCH_PROTO
935	&& fetchLastErrCode != FETCH_UNAVAIL)
936	return NULL;
937
938    /* initiate the transfer */
939    return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags);
940}
941
942/*
943 * Get file
944 */
945FILE *
946fetchGetFTP(struct url *url, char *flags)
947{
948    return fetchXGetFTP(url, NULL, flags);
949}
950
951/*
952 * Put file
953 */
954FILE *
955fetchPutFTP(struct url *url, char *flags)
956{
957    struct url *purl;
958    int cd;
959
960    /* get the proxy URL, and check if we should use HTTP instead */
961    if (!(flags && strchr(flags, 'd')) && (purl = _ftp_get_proxy()) != NULL) {
962	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
963	    /* XXX HTTP PUT is not implemented, so try without the proxy */
964	    purl = NULL;
965    } else {
966	purl = NULL;
967    }
968
969    /* connect to server */
970    cd = _ftp_cached_connect(url, purl, flags);
971    if (purl)
972	fetchFreeURL(purl);
973    if (cd == NULL)
974	return NULL;
975
976    /* change directory */
977    if (_ftp_cwd(cd, url->doc) == -1)
978	return NULL;
979
980    /* initiate the transfer */
981    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
982			 url->doc, O_WRONLY, url->offset, flags);
983}
984
985/*
986 * Get file stats
987 */
988int
989fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
990{
991    struct url *purl;
992    int cd;
993
994    /* get the proxy URL, and check if we should use HTTP instead */
995    if (!(flags && strchr(flags, 'd')) && (purl = _ftp_get_proxy()) != NULL) {
996	if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
997	    FILE *f;
998
999	    if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL)
1000		return -1;
1001	    fclose(f);
1002	    return 0;
1003	}
1004    } else {
1005	purl = NULL;
1006    }
1007
1008    /* connect to server */
1009    cd = _ftp_cached_connect(url, purl, flags);
1010    if (purl)
1011	fetchFreeURL(purl);
1012    if (cd == NULL)
1013	return NULL;
1014
1015    /* change directory */
1016    if (_ftp_cwd(cd, url->doc) == -1)
1017	return -1;
1018
1019    /* stat file */
1020    return _ftp_stat(cd, url->doc, us);
1021}
1022
1023/*
1024 * List a directory
1025 */
1026extern void warnx(char *, ...);
1027struct url_ent *
1028fetchListFTP(struct url *url, char *flags)
1029{
1030    warnx("fetchListFTP(): not implemented");
1031    return NULL;
1032}
1033