ftp.c revision 88769
1223328Sgavin/*-
2223328Sgavin * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3223328Sgavin * All rights reserved.
4223328Sgavin *
5223328Sgavin * Redistribution and use in source and binary forms, with or without
6223328Sgavin * modification, are permitted provided that the following conditions
7223328Sgavin * are met:
8223328Sgavin * 1. Redistributions of source code must retain the above copyright
9223328Sgavin *    notice, this list of conditions and the following disclaimer
10223328Sgavin *    in this position and unchanged.
11223328Sgavin * 2. Redistributions in binary form must reproduce the above copyright
12223328Sgavin *    notice, this list of conditions and the following disclaimer in the
13223328Sgavin *    documentation and/or other materials provided with the distribution.
14223328Sgavin * 3. The name of the author may not be used to endorse or promote products
15223328Sgavin *    derived from this software without specific prior written permission
16223328Sgavin *
17223328Sgavin * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18223328Sgavin * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19223328Sgavin * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20223328Sgavin * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21223328Sgavin * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22223328Sgavin * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23223328Sgavin * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24223328Sgavin * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25223328Sgavin * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26223328Sgavin * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27223328Sgavin */
28223328Sgavin
29223328Sgavin#include <sys/cdefs.h>
30223328Sgavin__FBSDID("$FreeBSD: head/lib/libfetch/ftp.c 88769 2002-01-01 14:48:09Z des $");
31223328Sgavin
32223328Sgavin/*
33223328Sgavin * Portions of this code were taken from or based on ftpio.c:
34223328Sgavin *
35223328Sgavin * ----------------------------------------------------------------------------
36223328Sgavin * "THE BEER-WARE LICENSE" (Revision 42):
37223328Sgavin * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
38223328Sgavin * can do whatever you want with this stuff. If we meet some day, and you think
39223328Sgavin * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
40223328Sgavin * ----------------------------------------------------------------------------
41223328Sgavin *
42223328Sgavin * Major Changelog:
43223328Sgavin *
44223328Sgavin * Dag-Erling Co�dan Sm�rgrav
45223328Sgavin * 9 Jun 1998
46223328Sgavin *
47223328Sgavin * Incorporated into libfetch
48223328Sgavin *
49223328Sgavin * Jordan K. Hubbard
50223328Sgavin * 17 Jan 1996
51223328Sgavin *
52223328Sgavin * Turned inside out. Now returns xfers as new file ids, not as a special
53223328Sgavin * `state' of FTP_t
54223328Sgavin *
55223328Sgavin * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
56223328Sgavin *
57223328Sgavin */
58223328Sgavin
59223328Sgavin#include <sys/param.h>
60223328Sgavin#include <sys/socket.h>
61223328Sgavin#include <netinet/in.h>
62223328Sgavin
63223328Sgavin#include <ctype.h>
64223328Sgavin#include <err.h>
65223328Sgavin#include <errno.h>
66223328Sgavin#include <fcntl.h>
67223328Sgavin#include <netdb.h>
68223328Sgavin#include <stdarg.h>
69223328Sgavin#include <stdio.h>
70223328Sgavin#include <stdlib.h>
71223328Sgavin#include <string.h>
72223328Sgavin#include <time.h>
73223328Sgavin#include <unistd.h>
74223328Sgavin
75223328Sgavin#include "fetch.h"
76223328Sgavin#include "common.h"
77223328Sgavin#include "ftperr.h"
78223328Sgavin
79223328Sgavin#define FTP_ANONYMOUS_USER	"anonymous"
80223328Sgavin
81223328Sgavin#define FTP_CONNECTION_ALREADY_OPEN	125
82223328Sgavin#define FTP_OPEN_DATA_CONNECTION	150
83223328Sgavin#define FTP_OK				200
84223328Sgavin#define FTP_FILE_STATUS			213
85223328Sgavin#define FTP_SERVICE_READY		220
86223328Sgavin#define FTP_TRANSFER_COMPLETE		226
87223328Sgavin#define FTP_PASSIVE_MODE		227
88223328Sgavin#define FTP_LPASSIVE_MODE		228
89223328Sgavin#define FTP_EPASSIVE_MODE		229
90223328Sgavin#define FTP_LOGGED_IN			230
91223328Sgavin#define FTP_FILE_ACTION_OK		250
92223328Sgavin#define FTP_NEED_PASSWORD		331
93223328Sgavin#define FTP_NEED_ACCOUNT		332
94223328Sgavin#define FTP_FILE_OK			350
95223328Sgavin#define FTP_SYNTAX_ERROR		500
96223328Sgavin#define FTP_PROTOCOL_ERROR		999
97223328Sgavin
98223328Sgavinstatic struct url cached_host;
99223328Sgavinstatic int cached_socket;
100223328Sgavin
101223328Sgavinstatic char *last_reply;
102223328Sgavinstatic size_t lr_size, lr_length;
103223328Sgavinstatic int last_code;
104223328Sgavin
105223328Sgavin#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
106223328Sgavin			 && isdigit(foo[2]) \
107223328Sgavin                         && (foo[3] == ' ' || foo[3] == '\0'))
108223328Sgavin#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
109223328Sgavin			&& isdigit(foo[2]) && foo[3] == '-')
110223328Sgavin
111223328Sgavin/* translate IPv4 mapped IPv6 address to IPv4 address */
112223328Sgavinstatic void
113223328Sgavinunmappedaddr(struct sockaddr_in6 *sin6)
114223328Sgavin{
115223328Sgavin    struct sockaddr_in *sin4;
116223328Sgavin    u_int32_t addr;
117223328Sgavin    int port;
118223328Sgavin
119223328Sgavin    if (sin6->sin6_family != AF_INET6 ||
120223328Sgavin	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
121223328Sgavin	return;
122223328Sgavin    sin4 = (struct sockaddr_in *)sin6;
123223328Sgavin    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
124223328Sgavin    port = sin6->sin6_port;
125223328Sgavin    memset(sin4, 0, sizeof(struct sockaddr_in));
126223328Sgavin    sin4->sin_addr.s_addr = addr;
127223328Sgavin    sin4->sin_port = port;
128223328Sgavin    sin4->sin_family = AF_INET;
129223328Sgavin    sin4->sin_len = sizeof(struct sockaddr_in);
130223328Sgavin}
131223328Sgavin
132223328Sgavin/*
133223328Sgavin * Get server response
134223328Sgavin */
135223328Sgavinstatic int
136223328Sgavin_ftp_chkerr(int cd)
137223328Sgavin{
138223328Sgavin    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
139223328Sgavin	_fetch_syserr();
140223328Sgavin	return -1;
141223328Sgavin    }
142223328Sgavin    if (isftpinfo(last_reply)) {
143223328Sgavin	while (lr_length && !isftpreply(last_reply)) {
144223328Sgavin	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
145223328Sgavin		_fetch_syserr();
146223328Sgavin		return -1;
147223328Sgavin	    }
148223328Sgavin	}
149223328Sgavin    }
150223328Sgavin
151223328Sgavin    while (lr_length && isspace(last_reply[lr_length-1]))
152223328Sgavin	lr_length--;
153223328Sgavin    last_reply[lr_length] = 0;
154223328Sgavin
155223328Sgavin    if (!isftpreply(last_reply)) {
156223328Sgavin	_ftp_seterr(FTP_PROTOCOL_ERROR);
157223328Sgavin	return -1;
158223328Sgavin    }
159223328Sgavin
160223328Sgavin    last_code = (last_reply[0] - '0') * 100
161223328Sgavin	+ (last_reply[1] - '0') * 10
162223328Sgavin	+ (last_reply[2] - '0');
163223328Sgavin
164223328Sgavin    return last_code;
165223328Sgavin}
166223328Sgavin
167223328Sgavin/*
168223328Sgavin * Send a command and check reply
169223328Sgavin */
170223328Sgavinstatic int
171223328Sgavin_ftp_cmd(int cd, const char *fmt, ...)
172223328Sgavin{
173223328Sgavin    va_list ap;
174223328Sgavin    size_t len;
175223328Sgavin    char *msg;
176223328Sgavin    int r;
177223328Sgavin
178223328Sgavin    va_start(ap, fmt);
179223328Sgavin    len = vasprintf(&msg, fmt, ap);
180223328Sgavin    va_end(ap);
181223328Sgavin
182223328Sgavin    if (msg == NULL) {
183223328Sgavin	errno = ENOMEM;
184223328Sgavin	_fetch_syserr();
185223328Sgavin	return -1;
186223328Sgavin    }
187223328Sgavin
188223328Sgavin    r = _fetch_putln(cd, msg, len);
189223328Sgavin    free(msg);
190223328Sgavin
191223328Sgavin    if (r == -1) {
192223328Sgavin	_fetch_syserr();
193223328Sgavin	return -1;
194223328Sgavin    }
195223328Sgavin
196223328Sgavin    return _ftp_chkerr(cd);
197223328Sgavin}
198223328Sgavin
199223328Sgavin/*
200223328Sgavin * Return a pointer to the filename part of a path
201223328Sgavin */
202223328Sgavinstatic const char *
203223328Sgavin_ftp_filename(const char *file)
204223328Sgavin{
205223328Sgavin    char *s;
206223328Sgavin
207223328Sgavin    if ((s = strrchr(file, '/')) == NULL)
208223328Sgavin	return file;
209223328Sgavin    else
210223328Sgavin	return s + 1;
211223328Sgavin}
212223328Sgavin
213223328Sgavin/*
214223328Sgavin * Change working directory to the directory that contains the
215223328Sgavin * specified file.
216223328Sgavin */
217223328Sgavinstatic int
218223328Sgavin_ftp_cwd(int cd, const char *file)
219223328Sgavin{
220223328Sgavin    char *s;
221223328Sgavin    int e;
222223328Sgavin
223223328Sgavin    if ((s = strrchr(file, '/')) == NULL || s == file) {
224223328Sgavin	e = _ftp_cmd(cd, "CWD /");
225223328Sgavin    } else {
226223328Sgavin	e = _ftp_cmd(cd, "CWD %.*s", s - file, file);
227223328Sgavin    }
228223328Sgavin    if (e != FTP_FILE_ACTION_OK) {
229223328Sgavin	_ftp_seterr(e);
230223328Sgavin	return -1;
231223328Sgavin    }
232223328Sgavin    return 0;
233223328Sgavin}
234223328Sgavin
235223328Sgavin/*
236223328Sgavin * Request and parse file stats
237223328Sgavin */
238223328Sgavinstatic int
239223328Sgavin_ftp_stat(int cd, const char *file, struct url_stat *us)
240223328Sgavin{
241223328Sgavin    char *ln;
242223328Sgavin    const char *s;
243223328Sgavin    struct tm tm;
244223328Sgavin    time_t t;
245223328Sgavin    int e;
246223328Sgavin
247223328Sgavin    us->size = -1;
248223328Sgavin    us->atime = us->mtime = 0;
249223328Sgavin
250223328Sgavin    if ((s = strrchr(file, '/')) == NULL)
251223328Sgavin	s = file;
252223328Sgavin    else
253223328Sgavin	++s;
254223328Sgavin
255223328Sgavin    if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) {
256223328Sgavin	_ftp_seterr(e);
257223328Sgavin	return -1;
258223328Sgavin    }
259223328Sgavin    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
260223328Sgavin	/* nothing */ ;
261223328Sgavin    for (us->size = 0; *ln && isdigit(*ln); ln++)
262223328Sgavin	us->size = us->size * 10 + *ln - '0';
263223328Sgavin    if (*ln && !isspace(*ln)) {
264223328Sgavin	_ftp_seterr(FTP_PROTOCOL_ERROR);
265223328Sgavin	us->size = -1;
266223328Sgavin	return -1;
267223328Sgavin    }
268223328Sgavin    if (us->size == 0)
269223328Sgavin	us->size = -1;
270223328Sgavin    DEBUG(fprintf(stderr, "size: [%lld]\n", (long long)us->size));
271223328Sgavin
272223328Sgavin    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) {
273223328Sgavin	_ftp_seterr(e);
274223328Sgavin	return -1;
275223328Sgavin    }
276223328Sgavin    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
277223328Sgavin	/* nothing */ ;
278223328Sgavin    switch (strspn(ln, "0123456789")) {
279223328Sgavin    case 14:
280223328Sgavin	break;
281223328Sgavin    case 15:
282223328Sgavin	ln++;
283223328Sgavin	ln[0] = '2';
284223328Sgavin	ln[1] = '0';
285223328Sgavin	break;
286223328Sgavin    default:
287223328Sgavin	_ftp_seterr(FTP_PROTOCOL_ERROR);
288223328Sgavin	return -1;
289223328Sgavin    }
290223328Sgavin    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
291223328Sgavin	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
292223328Sgavin	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
293223328Sgavin	_ftp_seterr(FTP_PROTOCOL_ERROR);
294223328Sgavin	return -1;
295223328Sgavin    }
296223328Sgavin    tm.tm_mon--;
297223328Sgavin    tm.tm_year -= 1900;
298223328Sgavin    tm.tm_isdst = -1;
299223328Sgavin    t = timegm(&tm);
300223328Sgavin    if (t == (time_t)-1)
301223328Sgavin	t = time(NULL);
302223328Sgavin    us->mtime = t;
303223328Sgavin    us->atime = t;
304223328Sgavin    DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n",
305223328Sgavin		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
306223328Sgavin		  tm.tm_hour, tm.tm_min, tm.tm_sec));
307223328Sgavin    return 0;
308223328Sgavin}
309223328Sgavin
310223328Sgavin/*
311223328Sgavin * I/O functions for FTP
312223328Sgavin */
313223328Sgavinstruct ftpio {
314223328Sgavin    int		 csd;		/* Control socket descriptor */
315223328Sgavin    int		 dsd;		/* Data socket descriptor */
316223328Sgavin    int		 dir;		/* Direction */
317223328Sgavin    int		 eof;		/* EOF reached */
318223328Sgavin    int		 err;		/* Error code */
319223328Sgavin};
320223328Sgavin
321223328Sgavinstatic int	_ftp_readfn(void *, char *, int);
322223328Sgavinstatic int	_ftp_writefn(void *, const char *, int);
323223328Sgavinstatic fpos_t	_ftp_seekfn(void *, fpos_t, int);
324223328Sgavinstatic int	_ftp_closefn(void *);
325223328Sgavin
326223328Sgavinstatic int
327223328Sgavin_ftp_readfn(void *v, char *buf, int len)
328223328Sgavin{
329223328Sgavin    struct ftpio *io;
330223328Sgavin    int r;
331223328Sgavin
332223328Sgavin    io = (struct ftpio *)v;
333223328Sgavin    if (io == NULL) {
334223328Sgavin	errno = EBADF;
335223328Sgavin	return -1;
336223328Sgavin    }
337223328Sgavin    if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) {
338223328Sgavin	errno = EBADF;
339223328Sgavin	return -1;
340223328Sgavin    }
341223328Sgavin    if (io->err) {
342223328Sgavin	errno = io->err;
343223328Sgavin	return -1;
344223328Sgavin    }
345223328Sgavin    if (io->eof)
346223328Sgavin	return 0;
347223328Sgavin    r = read(io->dsd, buf, len);
348223328Sgavin    if (r > 0)
349223328Sgavin	return r;
350223328Sgavin    if (r == 0) {
351223328Sgavin	io->eof = 1;
352223328Sgavin	return 0;
353223328Sgavin    }
354223328Sgavin    if (errno != EINTR)
355223328Sgavin	io->err = errno;
356223328Sgavin    return -1;
357223328Sgavin}
358223328Sgavin
359223328Sgavinstatic int
360223328Sgavin_ftp_writefn(void *v, const char *buf, int len)
361223328Sgavin{
362223328Sgavin    struct ftpio *io;
363223328Sgavin    int w;
364223328Sgavin
365223328Sgavin    io = (struct ftpio *)v;
366223328Sgavin    if (io == NULL) {
367223328Sgavin	errno = EBADF;
368223328Sgavin	return -1;
369223328Sgavin    }
370223328Sgavin    if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) {
371223328Sgavin	errno = EBADF;
372223328Sgavin	return -1;
373223328Sgavin    }
374223328Sgavin    if (io->err) {
375223328Sgavin	errno = io->err;
376223328Sgavin	return -1;
377223328Sgavin    }
378223328Sgavin    w = write(io->dsd, buf, len);
379223328Sgavin    if (w >= 0)
380223328Sgavin	return w;
381223328Sgavin    if (errno != EINTR)
382223328Sgavin	io->err = errno;
383223328Sgavin    return -1;
384223328Sgavin}
385223328Sgavin
386223328Sgavinstatic fpos_t
387223328Sgavin_ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused)
388223328Sgavin{
389223328Sgavin    struct ftpio *io;
390223328Sgavin
391223328Sgavin    io = (struct ftpio *)v;
392223328Sgavin    if (io == NULL) {
393223328Sgavin	errno = EBADF;
394223328Sgavin	return -1;
395223328Sgavin    }
396223328Sgavin    errno = ESPIPE;
397223328Sgavin    return -1;
398223328Sgavin}
399223328Sgavin
400223328Sgavinstatic int
401223328Sgavin_ftp_closefn(void *v)
402223328Sgavin{
403223328Sgavin    struct ftpio *io;
404223328Sgavin    int r;
405223328Sgavin
406223328Sgavin    io = (struct ftpio *)v;
407223328Sgavin    if (io == NULL) {
408223328Sgavin	errno = EBADF;
409223328Sgavin	return -1;
410223328Sgavin    }
411223328Sgavin    if (io->dir == -1)
412223328Sgavin	return 0;
413223328Sgavin    if (io->csd == -1 || io->dsd == -1) {
414223328Sgavin	errno = EBADF;
415223328Sgavin	return -1;
416223328Sgavin    }
417223328Sgavin    close(io->dsd);
418223328Sgavin    io->dir = -1;
419223328Sgavin    io->dsd = -1;
420223328Sgavin    DEBUG(fprintf(stderr, "Waiting for final status\n"));
421223328Sgavin    r = _ftp_chkerr(io->csd);
422223328Sgavin    close(io->csd);
423223328Sgavin    free(io);
424223328Sgavin    return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1;
425223328Sgavin}
426223328Sgavin
427223328Sgavinstatic FILE *
428223328Sgavin_ftp_setup(int csd, int dsd, int mode)
429223328Sgavin{
430223328Sgavin    struct ftpio *io;
431223328Sgavin    FILE *f;
432223328Sgavin
433223328Sgavin    if ((io = malloc(sizeof *io)) == NULL)
434223328Sgavin	return NULL;
435223328Sgavin    io->csd = dup(csd);
436223328Sgavin    io->dsd = dsd;
437223328Sgavin    io->dir = mode;
438223328Sgavin    io->eof = io->err = 0;
439223328Sgavin    f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
440223328Sgavin    if (f == NULL)
441223328Sgavin	free(io);
442223328Sgavin    return f;
443223328Sgavin}
444223328Sgavin
445223328Sgavin/*
446223328Sgavin * Transfer file
447223328Sgavin */
448223328Sgavinstatic FILE *
449223328Sgavin_ftp_transfer(int cd, const char *oper, const char *file,
450223328Sgavin	      int mode, off_t offset, const char *flags)
451223328Sgavin{
452223328Sgavin    struct sockaddr_storage sa;
453223328Sgavin    struct sockaddr_in6 *sin6;
454223328Sgavin    struct sockaddr_in *sin4;
455223328Sgavin    int low, pasv, verbose;
456223328Sgavin    int e, sd = -1;
457223328Sgavin    socklen_t l;
458223328Sgavin    char *s;
459223328Sgavin    FILE *df;
460223328Sgavin
461223328Sgavin    /* check flags */
462223328Sgavin    low = CHECK_FLAG('l');
463223328Sgavin    pasv = CHECK_FLAG('p');
464223328Sgavin    verbose = CHECK_FLAG('v');
465223328Sgavin
466223328Sgavin    /* passive mode */
467223328Sgavin    if (!pasv)
468223328Sgavin	pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
469223328Sgavin		strncasecmp(s, "no", 2) != 0);
470223328Sgavin
471223328Sgavin    /* find our own address, bind, and listen */
472223328Sgavin    l = sizeof sa;
473223328Sgavin    if (getsockname(cd, (struct sockaddr *)&sa, &l) == -1)
474223328Sgavin	goto sysouch;
475223328Sgavin    if (sa.ss_family == AF_INET6)
476223328Sgavin	unmappedaddr((struct sockaddr_in6 *)&sa);
477223328Sgavin
478223328Sgavin    /* open data socket */
479223328Sgavin    if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
480223328Sgavin	_fetch_syserr();
481223328Sgavin	return NULL;
482223328Sgavin    }
483223328Sgavin
484223328Sgavin    if (pasv) {
485223328Sgavin	u_char addr[64];
486223328Sgavin	char *ln, *p;
487223328Sgavin	unsigned int i;
488223328Sgavin	int port;
489223328Sgavin
490223328Sgavin	/* send PASV command */
491223328Sgavin	if (verbose)
492223328Sgavin	    _fetch_info("setting passive mode");
493223328Sgavin	switch (sa.ss_family) {
494223328Sgavin	case AF_INET:
495223328Sgavin	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
496223328Sgavin		goto ouch;
497223328Sgavin	    break;
498223328Sgavin	case AF_INET6:
499223328Sgavin	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
500223328Sgavin		if (e == -1)
501223328Sgavin		    goto ouch;
502223328Sgavin		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
503223328Sgavin		    goto ouch;
504223328Sgavin	    }
505223328Sgavin	    break;
506223328Sgavin	default:
507223328Sgavin	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
508223328Sgavin	    goto ouch;
509223328Sgavin	}
510223328Sgavin
511223328Sgavin	/*
512223328Sgavin	 * Find address and port number. The reply to the PASV command
513223328Sgavin         * is IMHO the one and only weak point in the FTP protocol.
514223328Sgavin	 */
515223328Sgavin	ln = last_reply;
516223328Sgavin      	switch (e) {
517223328Sgavin	case FTP_PASSIVE_MODE:
518223328Sgavin	case FTP_LPASSIVE_MODE:
519223328Sgavin	    for (p = ln + 3; *p && !isdigit(*p); p++)
520223328Sgavin		/* nothing */ ;
521223328Sgavin	    if (!*p) {
522223328Sgavin		e = FTP_PROTOCOL_ERROR;
523223328Sgavin		goto ouch;
524223328Sgavin	    }
525223328Sgavin	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
526223328Sgavin	    for (i = 0; *p && i < l; i++, p++)
527223328Sgavin		addr[i] = strtol(p, &p, 10);
528223328Sgavin	    if (i < l) {
529223328Sgavin		e = FTP_PROTOCOL_ERROR;
530223328Sgavin		goto ouch;
531223328Sgavin	    }
532223328Sgavin	    break;
533223328Sgavin	case FTP_EPASSIVE_MODE:
534223328Sgavin	    for (p = ln + 3; *p && *p != '('; p++)
535223328Sgavin		/* nothing */ ;
536223328Sgavin	    if (!*p) {
537223328Sgavin		e = FTP_PROTOCOL_ERROR;
538223328Sgavin		goto ouch;
539223328Sgavin	    }
540223328Sgavin	    ++p;
541223328Sgavin	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
542223328Sgavin		       &port, &addr[3]) != 5 ||
543223328Sgavin		addr[0] != addr[1] ||
544223328Sgavin		addr[0] != addr[2] || addr[0] != addr[3]) {
545223328Sgavin		e = FTP_PROTOCOL_ERROR;
546223328Sgavin		goto ouch;
547223328Sgavin	    }
548223328Sgavin	    break;
549223328Sgavin	}
550223328Sgavin
551223328Sgavin	/* seek to required offset */
552223328Sgavin	if (offset)
553223328Sgavin	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
554223328Sgavin		goto sysouch;
555223328Sgavin
556223328Sgavin	/* construct sockaddr for data socket */
557223328Sgavin	l = sizeof sa;
558223328Sgavin	if (getpeername(cd, (struct sockaddr *)&sa, &l) == -1)
559223328Sgavin	    goto sysouch;
560223328Sgavin	if (sa.ss_family == AF_INET6)
561223328Sgavin	    unmappedaddr((struct sockaddr_in6 *)&sa);
562223328Sgavin	switch (sa.ss_family) {
563223328Sgavin	case AF_INET6:
564223328Sgavin	    sin6 = (struct sockaddr_in6 *)&sa;
565223328Sgavin	    if (e == FTP_EPASSIVE_MODE)
566223328Sgavin		sin6->sin6_port = htons(port);
567223328Sgavin	    else {
568223328Sgavin		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
569223328Sgavin		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
570223328Sgavin	    }
571223328Sgavin	    break;
572223328Sgavin	case AF_INET:
573223328Sgavin	    sin4 = (struct sockaddr_in *)&sa;
574223328Sgavin	    if (e == FTP_EPASSIVE_MODE)
575223328Sgavin		sin4->sin_port = htons(port);
576223328Sgavin	    else {
577223328Sgavin		bcopy(addr, (char *)&sin4->sin_addr, 4);
578223328Sgavin		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
579223328Sgavin	    }
580223328Sgavin	    break;
581223328Sgavin	default:
582223328Sgavin	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
583223328Sgavin	    break;
584223328Sgavin	}
585223328Sgavin
586223328Sgavin	/* connect to data port */
587223328Sgavin	if (verbose)
588223328Sgavin	    _fetch_info("opening data connection");
589223328Sgavin	if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1)
590223328Sgavin	    goto sysouch;
591223328Sgavin
592223328Sgavin	/* make the server initiate the transfer */
593223328Sgavin	if (verbose)
594223328Sgavin	    _fetch_info("initiating transfer");
595223328Sgavin	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
596223328Sgavin	if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
597223328Sgavin	    goto ouch;
598223328Sgavin
599223328Sgavin    } else {
600223328Sgavin	u_int32_t a;
601223328Sgavin	u_short p;
602223328Sgavin	int arg, d;
603223328Sgavin	char *ap;
604223328Sgavin	char hname[INET6_ADDRSTRLEN];
605223328Sgavin
606223328Sgavin	switch (sa.ss_family) {
607223328Sgavin	case AF_INET6:
608223328Sgavin	    ((struct sockaddr_in6 *)&sa)->sin6_port = 0;
609223328Sgavin#ifdef IPV6_PORTRANGE
610223328Sgavin	    arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
611223328Sgavin	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
612223328Sgavin			   (char *)&arg, sizeof(arg)) == -1)
613223328Sgavin		goto sysouch;
614223328Sgavin#endif
615223328Sgavin	    break;
616223328Sgavin	case AF_INET:
617223328Sgavin	    ((struct sockaddr_in *)&sa)->sin_port = 0;
618223328Sgavin	    arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
619223328Sgavin	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
620223328Sgavin			   (char *)&arg, sizeof arg) == -1)
621223328Sgavin		goto sysouch;
622223328Sgavin	    break;
623223328Sgavin	}
624223328Sgavin	if (verbose)
625223328Sgavin	    _fetch_info("binding data socket");
626223328Sgavin	if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1)
627223328Sgavin	    goto sysouch;
628223328Sgavin	if (listen(sd, 1) == -1)
629223328Sgavin	    goto sysouch;
630223328Sgavin
631223328Sgavin	/* find what port we're on and tell the server */
632223328Sgavin	if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1)
633223328Sgavin	    goto sysouch;
634223328Sgavin	switch (sa.ss_family) {
635223328Sgavin	case AF_INET:
636223328Sgavin	    sin4 = (struct sockaddr_in *)&sa;
637223328Sgavin	    a = ntohl(sin4->sin_addr.s_addr);
638223328Sgavin	    p = ntohs(sin4->sin_port);
639223328Sgavin	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
640223328Sgavin			 (a >> 24) & 0xff, (a >> 16) & 0xff,
641223328Sgavin			 (a >> 8) & 0xff, a & 0xff,
642223328Sgavin			 (p >> 8) & 0xff, p & 0xff);
643223328Sgavin	    break;
644223328Sgavin	case AF_INET6:
645223328Sgavin#define UC(b)	(((int)b)&0xff)
646223328Sgavin	    e = -1;
647223328Sgavin	    sin6 = (struct sockaddr_in6 *)&sa;
648223328Sgavin	    if (getnameinfo((struct sockaddr *)&sa, sa.ss_len,
649223328Sgavin			    hname, sizeof(hname),
650223328Sgavin			    NULL, 0, NI_NUMERICHOST) == 0) {
651223328Sgavin		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
652223328Sgavin			     htons(sin6->sin6_port));
653223328Sgavin		if (e == -1)
654223328Sgavin		    goto ouch;
655223328Sgavin	    }
656223328Sgavin	    if (e != FTP_OK) {
657223328Sgavin		ap = (char *)&sin6->sin6_addr;
658223328Sgavin		e = _ftp_cmd(cd,
659223328Sgavin     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
660223328Sgavin			     6, 16,
661223328Sgavin			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
662223328Sgavin			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
663223328Sgavin			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
664223328Sgavin			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
665223328Sgavin			     2,
666223328Sgavin			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
667223328Sgavin			     ntohs(sin6->sin6_port)        & 0xff);
668223328Sgavin	    }
669223328Sgavin	    break;
670223328Sgavin	default:
671223328Sgavin	    e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
672223328Sgavin	    goto ouch;
673223328Sgavin	}
674223328Sgavin	if (e != FTP_OK)
675223328Sgavin	    goto ouch;
676223328Sgavin
677223328Sgavin	/* seek to required offset */
678223328Sgavin	if (offset)
679223328Sgavin	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
680223328Sgavin		goto sysouch;
681223328Sgavin
682223328Sgavin	/* make the server initiate the transfer */
683223328Sgavin	if (verbose)
684223328Sgavin	    _fetch_info("initiating transfer");
685223328Sgavin	e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file));
686223328Sgavin	if (e != FTP_OPEN_DATA_CONNECTION)
687223328Sgavin	    goto ouch;
688223328Sgavin
689223328Sgavin	/* accept the incoming connection and go to town */
690223328Sgavin	if ((d = accept(sd, NULL, NULL)) == -1)
691223328Sgavin	    goto sysouch;
692223328Sgavin	close(sd);
693223328Sgavin	sd = d;
694223328Sgavin    }
695223328Sgavin
696223328Sgavin    if ((df = _ftp_setup(cd, sd, mode)) == NULL)
697223328Sgavin	goto sysouch;
698223328Sgavin    return df;
699223328Sgavin
700223328Sgavinsysouch:
701223328Sgavin    _fetch_syserr();
702223328Sgavin    if (sd >= 0)
703223328Sgavin	close(sd);
704223328Sgavin    return NULL;
705223328Sgavin
706223328Sgavinouch:
707223328Sgavin    if (e != -1)
708223328Sgavin	_ftp_seterr(e);
709223328Sgavin    if (sd >= 0)
710223328Sgavin	close(sd);
71198247Smikeh    return NULL;
71298247Smikeh}
71398247Smikeh
71498247Smikeh/*
71598247Smikeh * Authenticate
71698247Smikeh */
71798247Smikehstatic int
71898247Smikeh_ftp_authenticate(int cd, struct url *url, struct url *purl)
71998247Smikeh{
72098247Smikeh    const char *user, *pwd, *logname;
72198247Smikeh    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
72298247Smikeh    int e, len;
72398247Smikeh
72498247Smikeh    /* XXX FTP_AUTH, and maybe .netrc */
72598247Smikeh
72698247Smikeh    /* send user name and password */
72798247Smikeh    user = url->user;
72898247Smikeh    if (!user || !*user)
72998247Smikeh	user = getenv("FTP_LOGIN");
73098247Smikeh    if (!user || !*user)
73198247Smikeh	user = FTP_ANONYMOUS_USER;
73298247Smikeh    if (purl && url->port == _fetch_default_port(url->scheme))
73398247Smikeh	e = _ftp_cmd(cd, "USER %s@%s", user, url->host);
73498247Smikeh    else if (purl)
73598247Smikeh	e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port);
73698247Smikeh    else
73798247Smikeh	e = _ftp_cmd(cd, "USER %s", user);
73898247Smikeh
73998247Smikeh    /* did the server request a password? */
74098247Smikeh    if (e == FTP_NEED_PASSWORD) {
74198247Smikeh	pwd = url->pwd;
74298247Smikeh	if (!pwd || !*pwd)
74398247Smikeh	    pwd = getenv("FTP_PASSWORD");
74498247Smikeh	if (!pwd || !*pwd) {
74598247Smikeh	    if ((logname = getlogin()) == 0)
74698247Smikeh		logname = FTP_ANONYMOUS_USER;
74798247Smikeh	    if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0)
74898247Smikeh		len = 0;
74998247Smikeh	    else if (len > MAXLOGNAME)
75098247Smikeh	        len = MAXLOGNAME;
75198247Smikeh	    gethostname(pbuf + len, sizeof pbuf - len);
75298247Smikeh	    pwd = pbuf;
75398247Smikeh	}
75498247Smikeh	e = _ftp_cmd(cd, "PASS %s", pwd);
75598247Smikeh    }
75698247Smikeh
75798247Smikeh    return e;
75898247Smikeh}
75998247Smikeh
76098247Smikeh/*
76198247Smikeh * Log on to FTP server
76298247Smikeh */
76398247Smikehstatic int
76498247Smikeh_ftp_connect(struct url *url, struct url *purl, const char *flags)
76598247Smikeh{
76698247Smikeh    int cd, e, direct, verbose;
76798247Smikeh#ifdef INET6
76898247Smikeh    int af = AF_UNSPEC;
76998247Smikeh#else
77098247Smikeh    int af = AF_INET;
77198247Smikeh#endif
77298247Smikeh
77398247Smikeh    direct = CHECK_FLAG('d');
77498247Smikeh    verbose = CHECK_FLAG('v');
77598247Smikeh    if (CHECK_FLAG('4'))
77698247Smikeh	af = AF_INET;
77798247Smikeh    else if (CHECK_FLAG('6'))
77898247Smikeh	af = AF_INET6;
77998247Smikeh
78098247Smikeh    if (direct)
78198247Smikeh	purl = NULL;
78298247Smikeh
78398247Smikeh    /* check for proxy */
78498247Smikeh    if (purl) {
78598247Smikeh	/* XXX proxy authentication! */
78698247Smikeh	cd = _fetch_connect(purl->host, purl->port, af, verbose);
78798247Smikeh    } else {
78898247Smikeh	/* no proxy, go straight to target */
78998247Smikeh	cd = _fetch_connect(url->host, url->port, af, verbose);
79098247Smikeh	purl = NULL;
79198247Smikeh    }
79298247Smikeh
79398247Smikeh    /* check connection */
79498247Smikeh    if (cd == -1) {
79598247Smikeh	_fetch_syserr();
79698247Smikeh	return NULL;
79798247Smikeh    }
79898247Smikeh
79998247Smikeh    /* expect welcome message */
80098247Smikeh    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
80198247Smikeh	goto fouch;
80298247Smikeh
80398247Smikeh    /* authenticate */
80498247Smikeh    if ((e = _ftp_authenticate(cd, url, purl)) != FTP_LOGGED_IN)
80598247Smikeh	goto fouch;
80698247Smikeh
80798247Smikeh    /* might as well select mode and type at once */
80898247Smikeh#ifdef FTP_FORCE_STREAM_MODE
80998247Smikeh    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
81098247Smikeh	goto fouch;
81198247Smikeh#endif
81298247Smikeh    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
81398247Smikeh	goto fouch;
81498247Smikeh
81598247Smikeh    /* done */
81698247Smikeh    return cd;
81798247Smikeh
81898247Smikehfouch:
81998247Smikeh    if (e != -1)
82098247Smikeh	_ftp_seterr(e);
82198247Smikeh    close(cd);
82298247Smikeh    return NULL;
82398247Smikeh}
82498247Smikeh
82598247Smikeh/*
82698247Smikeh * Disconnect from server
82798247Smikeh */
82898247Smikehstatic void
82998247Smikeh_ftp_disconnect(int cd)
83098247Smikeh{
83198247Smikeh    (void)_ftp_cmd(cd, "QUIT");
83298247Smikeh    close(cd);
83398247Smikeh}
83498247Smikeh
83598247Smikeh/*
83698247Smikeh * Check if we're already connected
83798247Smikeh */
83898247Smikehstatic int
83998247Smikeh_ftp_isconnected(struct url *url)
84098247Smikeh{
84198247Smikeh    return (cached_socket
84298247Smikeh	    && (strcmp(url->host, cached_host.host) == 0)
84398247Smikeh	    && (strcmp(url->user, cached_host.user) == 0)
84498247Smikeh	    && (strcmp(url->pwd, cached_host.pwd) == 0)
84598247Smikeh	    && (url->port == cached_host.port));
84698247Smikeh}
84798247Smikeh
84898247Smikeh/*
84998247Smikeh * Check the cache, reconnect if no luck
85098247Smikeh */
85198247Smikehstatic int
85298247Smikeh_ftp_cached_connect(struct url *url, struct url *purl, const char *flags)
85398247Smikeh{
85498247Smikeh    int e, cd;
85598247Smikeh
85698247Smikeh    cd = -1;
85798247Smikeh
85898247Smikeh    /* set default port */
85998247Smikeh    if (!url->port)
86098247Smikeh	url->port = _fetch_default_port(url->scheme);
86198247Smikeh
86298247Smikeh    /* try to use previously cached connection */
86398247Smikeh    if (_ftp_isconnected(url)) {
86498247Smikeh	e = _ftp_cmd(cached_socket, "NOOP");
86598247Smikeh	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
86698247Smikeh	    return cached_socket;
86798247Smikeh    }
86898247Smikeh
86998247Smikeh    /* connect to server */
87098247Smikeh    if ((cd = _ftp_connect(url, purl, flags)) == -1)
87198247Smikeh	return -1;
87298247Smikeh    if (cached_socket)
87398247Smikeh	_ftp_disconnect(cached_socket);
87498247Smikeh    cached_socket = cd;
87598247Smikeh    memcpy(&cached_host, url, sizeof *url);
87698247Smikeh    return cd;
87798247Smikeh}
87898247Smikeh
87998247Smikeh/*
88098247Smikeh * Check the proxy settings
88198247Smikeh */
88298247Smikehstatic struct url *
88398247Smikeh_ftp_get_proxy(void)
88498247Smikeh{
88598247Smikeh    struct url *purl;
88698247Smikeh    char *p;
88798247Smikeh
88898247Smikeh    if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) ||
88998247Smikeh	 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
89098247Smikeh	*p && (purl = fetchParseURL(p)) != NULL) {
89198247Smikeh	if (!*purl->scheme) {
89298247Smikeh	    if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
89398247Smikeh		strcpy(purl->scheme, SCHEME_FTP);
89498247Smikeh	    else
89598247Smikeh		strcpy(purl->scheme, SCHEME_HTTP);
89698247Smikeh	}
89798247Smikeh	if (!purl->port)
89898247Smikeh	    purl->port = _fetch_default_proxy_port(purl->scheme);
89998247Smikeh	if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
90098247Smikeh	    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
90198247Smikeh	    return purl;
90298247Smikeh	fetchFreeURL(purl);
90398247Smikeh    }
90498247Smikeh    return NULL;
90598247Smikeh}
90698247Smikeh
90798247Smikeh/*
90898247Smikeh * Process an FTP request
90998247Smikeh */
91098247SmikehFILE *
91198247Smikeh_ftp_request(struct url *url, const char *op, struct url_stat *us,
91298247Smikeh	     struct url *purl, const char *flags)
91398247Smikeh{
91498247Smikeh    int cd;
91598247Smikeh
91698247Smikeh    /* check if we should use HTTP instead */
91779971Sobrien    if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
91879971Sobrien	if (strcmp(op, "STAT") == 0)
91979971Sobrien	    return _http_request(url, "HEAD", us, purl, flags);
92079971Sobrien	else if (strcmp(op, "RETR") == 0)
92179971Sobrien	    return _http_request(url, "GET", us, purl, flags);
92279971Sobrien	/*
92379971Sobrien	 * Our HTTP code doesn't support PUT requests yet, so try a
92479971Sobrien	 * direct connection.
92579971Sobrien	 */
92679971Sobrien    }
92779971Sobrien
92879971Sobrien    /* connect to server */
92979971Sobrien    cd = _ftp_cached_connect(url, purl, flags);
93079971Sobrien    if (purl)
93179971Sobrien	fetchFreeURL(purl);
93279971Sobrien    if (cd == NULL)
93379971Sobrien	return NULL;
93479971Sobrien
93579971Sobrien    /* change directory */
936223328Sgavin    if (_ftp_cwd(cd, url->doc) == -1)
93779971Sobrien	return NULL;
93879971Sobrien
93979971Sobrien    /* stat file */
94079971Sobrien    if (us && _ftp_stat(cd, url->doc, us) == -1
94179971Sobrien	&& fetchLastErrCode != FETCH_PROTO
94279971Sobrien	&& fetchLastErrCode != FETCH_UNAVAIL)
94379971Sobrien	return NULL;
94479971Sobrien
94579971Sobrien    /* just a stat */
94679971Sobrien    if (strcmp(op, "STAT") == 0)
94779971Sobrien	return (FILE *)1; /* bogus return value */
94879971Sobrien
94979971Sobrien    /* initiate the transfer */
95079971Sobrien    return _ftp_transfer(cd, op, url->doc, O_RDONLY, url->offset, flags);
95179971Sobrien}
95279971Sobrien
95379971Sobrien/*
95479971Sobrien * Get and stat file
95579971Sobrien */
956223328SgavinFILE *
95779971SobrienfetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
95879971Sobrien{
95979971Sobrien    return _ftp_request(url, "RETR", us, _ftp_get_proxy(), flags);
96079971Sobrien}
96179971Sobrien
96279971Sobrien/*
96379971Sobrien * Get file
96479971Sobrien */
96579971SobrienFILE *
96679971SobrienfetchGetFTP(struct url *url, const char *flags)
96779971Sobrien{
96879971Sobrien    return fetchXGetFTP(url, NULL, flags);
96979971Sobrien}
97079971Sobrien
97179971Sobrien/*
97279971Sobrien * Put file
97379971Sobrien */
97479971SobrienFILE *
97579971SobrienfetchPutFTP(struct url *url, const char *flags)
97679971Sobrien{
97779971Sobrien
97879971Sobrien    return _ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL,
97979971Sobrien			_ftp_get_proxy(), flags);
98079971Sobrien}
98179971Sobrien
98279971Sobrien/*
98379971Sobrien * Get file stats
98479971Sobrien */
98579971Sobrienint
98679971SobrienfetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
98779971Sobrien{
98879971Sobrien
98979971Sobrien    if (_ftp_request(url, "STAT", us, _ftp_get_proxy(), flags) == NULL)
99079971Sobrien	return -1;
99179971Sobrien    return 0;
99279971Sobrien}
99379971Sobrien
99479971Sobrien/*
99579971Sobrien * List a directory
99679971Sobrien */
99779971Sobrienstruct url_ent *
99879971SobrienfetchListFTP(struct url *url __unused, const char *flags __unused)
99979971Sobrien{
100079971Sobrien    warnx("fetchListFTP(): not implemented");
100179971Sobrien    return NULL;
100279971Sobrien}
100379971Sobrien