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