ftp.c revision 68551
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: head/lib/libfetch/ftp.c 68551 2000-11-10 08:43:40Z des $
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
97static struct url cached_host;
98static int cached_socket;
99
100static char *last_reply;
101static size_t lr_size, lr_length;
102static 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 */
111static void
112unmappedaddr(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 */
134static 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 (!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 */
169static 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 */
201static 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 */
216static 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 */
237static 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 */
311struct 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
319static int	_ftp_readfn(void *, char *, int);
320static int	_ftp_writefn(void *, const char *, int);
321static fpos_t	_ftp_seekfn(void *, fpos_t, int);
322static int	_ftp_closefn(void *);
323
324static 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
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 = 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
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 * Log on to FTP server
715 */
716static 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
807fouch:
808    if (e != -1)
809	_ftp_seterr(e);
810    close(cd);
811    return NULL;
812}
813
814/*
815 * Disconnect from server
816 */
817static 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 */
827static 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 */
840static 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 */
871static 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 */
894FILE *
895fetchXGetFTP(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 */
932FILE *
933fetchGetFTP(struct url *url, char *flags)
934{
935    return fetchXGetFTP(url, NULL, flags);
936}
937
938/*
939 * Put file
940 */
941FILE *
942fetchPutFTP(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 */
975int
976fetchStatFTP(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 */
1013extern void warnx(char *, ...);
1014struct url_ent *
1015fetchListFTP(struct url *url, char *flags)
1016{
1017    warnx("fetchListFTP(): not implemented");
1018    return NULL;
1019}
1020