ftp.c revision 60791
137535Sdes/*-
237535Sdes * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
337535Sdes * All rights reserved.
437535Sdes *
537535Sdes * Redistribution and use in source and binary forms, with or without
637535Sdes * modification, are permitted provided that the following conditions
737535Sdes * are met:
837535Sdes * 1. Redistributions of source code must retain the above copyright
937535Sdes *    notice, this list of conditions and the following disclaimer
1037535Sdes *    in this position and unchanged.
1137535Sdes * 2. Redistributions in binary form must reproduce the above copyright
1237535Sdes *    notice, this list of conditions and the following disclaimer in the
1337535Sdes *    documentation and/or other materials provided with the distribution.
1437535Sdes * 3. The name of the author may not be used to endorse or promote products
1537535Sdes *    derived from this software without specific prior written permission
1637535Sdes *
1737535Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1837535Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1937535Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2037535Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2137535Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2237535Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2337535Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2437535Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2537535Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2637535Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2737535Sdes *
2850476Speter * $FreeBSD: head/lib/libfetch/ftp.c 60791 2000-05-22 13:01:13Z ume $
2937535Sdes */
3037535Sdes
3137535Sdes/*
3237571Sdes * Portions of this code were taken from or based on ftpio.c:
3337535Sdes *
3437535Sdes * ----------------------------------------------------------------------------
3537535Sdes * "THE BEER-WARE LICENSE" (Revision 42):
3637535Sdes * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
3737535Sdes * can do whatever you want with this stuff. If we meet some day, and you think
3837535Sdes * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
3937535Sdes * ----------------------------------------------------------------------------
4037535Sdes *
4137535Sdes * Major Changelog:
4237535Sdes *
4337535Sdes * Dag-Erling Co�dan Sm�rgrav
4437535Sdes * 9 Jun 1998
4537535Sdes *
4637535Sdes * Incorporated into libfetch
4737535Sdes *
4837535Sdes * Jordan K. Hubbard
4937535Sdes * 17 Jan 1996
5037535Sdes *
5137535Sdes * Turned inside out. Now returns xfers as new file ids, not as a special
5237535Sdes * `state' of FTP_t
5337535Sdes *
5437535Sdes * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
5537535Sdes *
5637535Sdes */
5737535Sdes
5841862Sdes#include <sys/param.h>
5937535Sdes#include <sys/socket.h>
6055557Sdes#include <sys/uio.h>
6137535Sdes#include <netinet/in.h>
6237535Sdes
6337535Sdes#include <ctype.h>
6455557Sdes#include <errno.h>
6560188Sdes#include <netdb.h>
6637573Sdes#include <stdarg.h>
6737535Sdes#include <stdio.h>
6837571Sdes#include <stdlib.h>
6937535Sdes#include <string.h>
7041869Sdes#include <time.h>
7137571Sdes#include <unistd.h>
7237535Sdes
7337535Sdes#include "fetch.h"
7440939Sdes#include "common.h"
7541862Sdes#include "ftperr.h"
7637535Sdes
7737535Sdes#define FTP_ANONYMOUS_USER	"ftp"
7837535Sdes#define FTP_ANONYMOUS_PASSWORD	"ftp"
7937573Sdes#define FTP_DEFAULT_PORT 21
8037535Sdes
8137573Sdes#define FTP_OPEN_DATA_CONNECTION	150
8237573Sdes#define FTP_OK				200
8341869Sdes#define FTP_FILE_STATUS			213
8441863Sdes#define FTP_SERVICE_READY		220
8537573Sdes#define FTP_PASSIVE_MODE		227
8660737Sume#define FTP_LPASSIVE_MODE		228
8760737Sume#define FTP_EPASSIVE_MODE		229
8837573Sdes#define FTP_LOGGED_IN			230
8937573Sdes#define FTP_FILE_ACTION_OK		250
9037573Sdes#define FTP_NEED_PASSWORD		331
9137573Sdes#define FTP_NEED_ACCOUNT		332
9260188Sdes#define FTP_FILE_OK			350
9355557Sdes#define FTP_SYNTAX_ERROR		500
9437573Sdes
9555557Sdesstatic char ENDL[2] = "\r\n";
9637571Sdes
9740975Sdesstatic struct url cached_host;
9855557Sdesstatic int cached_socket;
9937535Sdes
10055557Sdesstatic char *last_reply;
10155557Sdesstatic size_t lr_size, lr_length;
10255557Sdesstatic int last_code;
10337571Sdes
10455557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10560707Sdes			 && isdigit(foo[2]) \
10660707Sdes                         && (foo[3] == ' ' || foo[3] == '\0'))
10755557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10855557Sdes			&& isdigit(foo[2]) && foo[3] == '-')
10955557Sdes
11060737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */
11160737Sumestatic void
11260737Sumeunmappedaddr(struct sockaddr_in6 *sin6)
11360737Sume{
11460737Sume    struct sockaddr_in *sin4;
11560737Sume    u_int32_t addr;
11660737Sume    int port;
11760737Sume
11860737Sume    if (sin6->sin6_family != AF_INET6 ||
11960737Sume	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
12060737Sume	return;
12160737Sume    sin4 = (struct sockaddr_in *)sin6;
12260737Sume    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
12360737Sume    port = sin6->sin6_port;
12460737Sume    memset(sin4, 0, sizeof(struct sockaddr_in));
12560737Sume    sin4->sin_addr.s_addr = addr;
12660737Sume    sin4->sin_port = port;
12760737Sume    sin4->sin_family = AF_INET;
12860737Sume    sin4->sin_len = sizeof(struct sockaddr_in);
12960737Sume}
13060737Sume
13137571Sdes/*
13255557Sdes * Get server response
13337535Sdes */
13437535Sdesstatic int
13555557Sdes_ftp_chkerr(int cd)
13637535Sdes{
13737535Sdes    do {
13855557Sdes	if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
13940939Sdes	    _fetch_syserr();
14037535Sdes	    return -1;
14137571Sdes	}
14237573Sdes#ifndef NDEBUG
14355557Sdes	_fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
14437573Sdes#endif
14555557Sdes    } while (isftpinfo(last_reply));
14655557Sdes
14755557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
14855557Sdes	lr_length--;
14955557Sdes    last_reply[lr_length] = 0;
15037573Sdes
15155557Sdes    if (!isftpreply(last_reply)) {
15255557Sdes	_ftp_seterr(999);
15337535Sdes	return -1;
15437571Sdes    }
15537535Sdes
15655557Sdes    last_code = (last_reply[0] - '0') * 100
15755557Sdes	+ (last_reply[1] - '0') * 10
15855557Sdes	+ (last_reply[2] - '0');
15955557Sdes
16055557Sdes    return last_code;
16137535Sdes}
16237535Sdes
16337535Sdes/*
16437573Sdes * Send a command and check reply
16537535Sdes */
16637535Sdesstatic int
16755557Sdes_ftp_cmd(int cd, char *fmt, ...)
16837535Sdes{
16937573Sdes    va_list ap;
17055557Sdes    struct iovec iov[2];
17155557Sdes    char *msg;
17255557Sdes    int r;
17337573Sdes
17437573Sdes    va_start(ap, fmt);
17555557Sdes    vasprintf(&msg, fmt, ap);
17655557Sdes    va_end(ap);
17755557Sdes
17855557Sdes    if (msg == NULL) {
17955557Sdes	errno = ENOMEM;
18055557Sdes	_fetch_syserr();
18155557Sdes	return -1;
18255557Sdes    }
18337573Sdes#ifndef NDEBUG
18455557Sdes    _fetch_info("sending '%s'", msg);
18537573Sdes#endif
18655557Sdes    iov[0].iov_base = msg;
18755557Sdes    iov[0].iov_len = strlen(msg);
18855557Sdes    iov[1].iov_base = ENDL;
18960188Sdes    iov[1].iov_len = sizeof ENDL;
19055557Sdes    r = writev(cd, iov, 2);
19155557Sdes    free(msg);
19255557Sdes    if (r == -1) {
19355557Sdes	_fetch_syserr();
19455557Sdes	return -1;
19555557Sdes    }
19637571Sdes
19755557Sdes    return _ftp_chkerr(cd);
19837535Sdes}
19937535Sdes
20037535Sdes/*
20137608Sdes * Transfer file
20237535Sdes */
20337535Sdesstatic FILE *
20460188Sdes_ftp_transfer(int cd, char *oper, char *file,
20560188Sdes	      char *mode, off_t offset, char *flags)
20637535Sdes{
20760737Sume    struct sockaddr_storage sin;
20860737Sume    struct sockaddr_in6 *sin6;
20960737Sume    struct sockaddr_in *sin4;
21055544Sdes    int pasv, high, verbose;
21155544Sdes    int e, sd = -1;
21255544Sdes    socklen_t l;
21337573Sdes    char *s;
21437573Sdes    FILE *df;
21555544Sdes
21655544Sdes    /* check flags */
21755544Sdes    pasv = (flags && strchr(flags, 'p'));
21855544Sdes    high = (flags && strchr(flags, 'h'));
21955544Sdes    verbose = (flags && strchr(flags, 'v'));
22055544Sdes
22137535Sdes    /* change directory */
22237573Sdes    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
22337573Sdes	*s = 0;
22455544Sdes	if (verbose)
22555544Sdes	    _fetch_info("changing directory to %s", file);
22655557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
22737573Sdes	    *s = '/';
22855557Sdes	    if (e != -1)
22955557Sdes		_ftp_seterr(e);
23037535Sdes	    return NULL;
23137535Sdes	}
23237573Sdes	*s++ = '/';
23337535Sdes    } else {
23455544Sdes	if (verbose)
23555544Sdes	    _fetch_info("changing directory to /");
23655557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
23755557Sdes	    if (e != -1)
23855557Sdes		_ftp_seterr(e);
23937535Sdes	    return NULL;
24041869Sdes	}
24137535Sdes    }
24237535Sdes
24337573Sdes    /* s now points to file name */
24437573Sdes
24560737Sume    /* find our own address, bind, and listen */
24660737Sume    l = sizeof sin;
24760737Sume    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
24860737Sume	goto sysouch;
24960737Sume    if (sin.ss_family == AF_INET6)
25060737Sume	unmappedaddr((struct sockaddr_in6 *)&sin);
25160737Sume
25237573Sdes    /* open data socket */
25360737Sume    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
25440939Sdes	_fetch_syserr();
25537573Sdes	return NULL;
25637573Sdes    }
25737573Sdes
25837573Sdes    if (pasv) {
25960737Sume	u_char addr[64];
26037573Sdes	char *ln, *p;
26137573Sdes	int i;
26260737Sume	int port;
26337573Sdes
26437573Sdes	/* send PASV command */
26555544Sdes	if (verbose)
26655544Sdes	    _fetch_info("setting passive mode");
26760737Sume	switch (sin.ss_family) {
26860737Sume	case AF_INET:
26960737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
27060737Sume		goto ouch;
27160737Sume	    break;
27260737Sume	case AF_INET6:
27360737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
27460737Sume		if (e == -1)
27560737Sume		    goto ouch;
27660737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
27760737Sume		    goto ouch;
27860737Sume	    }
27960737Sume	    break;
28060737Sume	default:
28160737Sume	    e = 999;		/* XXX: error code should be prepared */
28237573Sdes	    goto ouch;
28360737Sume	}
28437573Sdes
28555544Sdes	/*
28655544Sdes	 * Find address and port number. The reply to the PASV command
28755544Sdes         * is IMHO the one and only weak point in the FTP protocol.
28855544Sdes	 */
28955557Sdes	ln = last_reply;
29060737Sume	for (p = ln + 3; *p && *p != '('; p++)
29137573Sdes	    /* nothing */ ;
29260737Sume	if (!*p) {
29360707Sdes	    e = 999;
29460707Sdes	    goto ouch;
29537573Sdes	}
29660737Sume	p++;
29760737Sume	switch (e) {
29860737Sume	case FTP_PASSIVE_MODE:
29960737Sume	case FTP_LPASSIVE_MODE:
30060737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
30160737Sume	    for (i = 0; *p && i < l; i++, p++)
30260737Sume		addr[i] = strtol(p, &p, 10);
30360737Sume	    if (i < l) {
30460737Sume		e = 999;
30560737Sume		goto ouch;
30660737Sume	    }
30760737Sume	    break;
30860737Sume	case FTP_EPASSIVE_MODE:
30960737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
31060737Sume		       &port, &addr[3]) != 5 ||
31160737Sume		addr[0] != addr[1] ||
31260737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
31360737Sume		e = 999;
31460737Sume		goto ouch;
31560737Sume	    }
31660737Sume	    break;
31760737Sume	}
31837573Sdes
31960188Sdes	/* seek to required offset */
32060188Sdes	if (offset)
32160188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
32260188Sdes		goto sysouch;
32360188Sdes
32437573Sdes	/* construct sockaddr for data socket */
32560188Sdes	l = sizeof sin;
32655557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
32737573Sdes	    goto sysouch;
32860737Sume	if (sin.ss_family == AF_INET6)
32960737Sume	    unmappedaddr((struct sockaddr_in6 *)&sin);
33060737Sume	switch (sin.ss_family) {
33160737Sume	case AF_INET6:
33260737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
33360737Sume	    if (e == FTP_EPASSIVE_MODE)
33460737Sume		sin6->sin6_port = htons(port);
33560737Sume	    else {
33660737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
33760737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
33860737Sume	    }
33960737Sume	    break;
34060737Sume	case AF_INET:
34160737Sume	    sin4 = (struct sockaddr_in *)&sin;
34260737Sume	    if (e == FTP_EPASSIVE_MODE)
34360737Sume		sin4->sin_port = htons(port);
34460737Sume	    else {
34560737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
34660737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
34760737Sume	    }
34860737Sume	    break;
34960737Sume	default:
35060737Sume	    e = 999;		/* XXX: error code should be prepared */
35160737Sume	    break;
35260737Sume	}
35337573Sdes
35437573Sdes	/* connect to data port */
35555544Sdes	if (verbose)
35655544Sdes	    _fetch_info("opening data connection");
35760737Sume	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
35837573Sdes	    goto sysouch;
35960188Sdes
36037573Sdes	/* make the server initiate the transfer */
36155544Sdes	if (verbose)
36255544Sdes	    _fetch_info("initiating transfer");
36355557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
36441869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
36537573Sdes	    goto ouch;
36637573Sdes
36737573Sdes    } else {
36837573Sdes	u_int32_t a;
36937573Sdes	u_short p;
37055544Sdes	int arg, d;
37160737Sume	char *ap;
37260737Sume	char hname[INET6_ADDRSTRLEN];
37337573Sdes
37460737Sume	switch (sin.ss_family) {
37560737Sume	case AF_INET6:
37660737Sume	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
37760737Sume#ifdef IPV6_PORTRANGE
37860737Sume	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
37960737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
38060737Sume			   (char *)&arg, sizeof(arg)) == -1)
38160737Sume		goto sysouch;
38260737Sume#endif
38360737Sume	    break;
38460737Sume	case AF_INET:
38560737Sume	    ((struct sockaddr_in *)&sin)->sin_port = 0;
38660737Sume	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
38760737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
38860737Sume			   (char *)&arg, sizeof arg) == -1)
38960737Sume		goto sysouch;
39060737Sume	    break;
39160737Sume	}
39255544Sdes	if (verbose)
39355544Sdes	    _fetch_info("binding data socket");
39460737Sume	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
39537573Sdes	    goto sysouch;
39638394Sdes	if (listen(sd, 1) == -1)
39737573Sdes	    goto sysouch;
39837573Sdes
39937573Sdes	/* find what port we're on and tell the server */
40038394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
40137573Sdes	    goto sysouch;
40260737Sume	switch (sin.ss_family) {
40360737Sume	case AF_INET:
40460737Sume	    sin4 = (struct sockaddr_in *)&sin;
40560737Sume	    a = ntohl(sin4->sin_addr.s_addr);
40660737Sume	    p = ntohs(sin4->sin_port);
40760737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
40860737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
40960737Sume			 (a >> 8) & 0xff, a & 0xff,
41060737Sume			 (p >> 8) & 0xff, p & 0xff);
41160737Sume	    break;
41260737Sume	case AF_INET6:
41360737Sume#define UC(b)	(((int)b)&0xff)
41460737Sume	    e = -1;
41560737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
41660737Sume	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
41760737Sume			    hname, sizeof(hname),
41860737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
41960737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
42060737Sume			     htons(sin6->sin6_port));
42160737Sume		if (e == -1)
42260737Sume		    goto ouch;
42360737Sume	    }
42460737Sume	    if (e != FTP_OK) {
42560737Sume		ap = (char *)&sin6->sin6_addr;
42660737Sume		e = _ftp_cmd(cd,
42760737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
42860737Sume			     6, 16,
42960737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
43060737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
43160737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
43260737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
43360737Sume			     2,
43460737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
43560737Sume			     ntohs(sin6->sin6_port)        & 0xff);
43660737Sume	    }
43760737Sume	    break;
43860737Sume	default:
43960737Sume	    e = 999;		/* XXX: error code should be prepared */
44060737Sume	    goto ouch;
44160737Sume	}
44241869Sdes	if (e != FTP_OK)
44337573Sdes	    goto ouch;
44437573Sdes
44537573Sdes	/* make the server initiate the transfer */
44655544Sdes	if (verbose)
44755544Sdes	    _fetch_info("initiating transfer");
44855557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
44941869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
45037573Sdes	    goto ouch;
45137573Sdes
45237573Sdes	/* accept the incoming connection and go to town */
45338394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
45437573Sdes	    goto sysouch;
45537573Sdes	close(sd);
45637573Sdes	sd = d;
45737573Sdes    }
45837573Sdes
45937608Sdes    if ((df = fdopen(sd, mode)) == NULL)
46037573Sdes	goto sysouch;
46137573Sdes    return df;
46237573Sdes
46337573Sdessysouch:
46440939Sdes    _fetch_syserr();
46560737Sume    if (sd >= 0)
46660737Sume	close(sd);
46741869Sdes    return NULL;
46841869Sdes
46937573Sdesouch:
47055557Sdes    if (e != -1)
47155557Sdes	_ftp_seterr(e);
47260737Sume    if (sd >= 0)
47360737Sume	close(sd);
47437535Sdes    return NULL;
47537535Sdes}
47637535Sdes
47737571Sdes/*
47837571Sdes * Log on to FTP server
47937535Sdes */
48055557Sdesstatic int
48155544Sdes_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
48237571Sdes{
48360188Sdes    int cd, e, pp = 0, direct, verbose;
48460737Sume#ifdef INET6
48560737Sume    int af = AF_UNSPEC;
48660737Sume#else
48760737Sume    int af = AF_INET;
48860737Sume#endif
48937608Sdes    char *p, *q;
49060791Sume    const char *logname;
49160791Sume    char localhost[MAXHOSTNAMELEN];
49260791Sume    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
49337571Sdes
49455544Sdes    direct = (flags && strchr(flags, 'd'));
49555544Sdes    verbose = (flags && strchr(flags, 'v'));
49660737Sume    if ((flags && strchr(flags, '4')))
49760737Sume	af = AF_INET;
49860737Sume    else if ((flags && strchr(flags, '6')))
49960737Sume	af = AF_INET6;
50060737Sume
50137608Sdes    /* check for proxy */
50255544Sdes    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
50360737Sume	char c = 0;
50460737Sume
50560737Sume#ifdef INET6
50660737Sume	if (*p != '[' || (q = strchr(p + 1, ']')) == NULL ||
50760737Sume	    (*++q != '\0' && *q != ':'))
50860737Sume#endif
50960737Sume	    q = strchr(p, ':');
51060737Sume	if (q != NULL && *q == ':') {
51160188Sdes	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
51260188Sdes		/* XXX we should emit some kind of warning */
51360188Sdes	    }
51437608Sdes	    pp = atoi(q+1);
51560188Sdes	    if (pp < 1 || pp > 65535) {
51660188Sdes		/* XXX we should emit some kind of warning */
51760188Sdes	    }
51837608Sdes	}
51960188Sdes	if (!pp) {
52060188Sdes	    struct servent *se;
52160188Sdes
52260188Sdes	    if ((se = getservbyname("ftp", "tcp")) != NULL)
52360188Sdes		pp = ntohs(se->s_port);
52460188Sdes	    else
52560188Sdes		pp = FTP_DEFAULT_PORT;
52660188Sdes	}
52760737Sume	if (q) {
52860737Sume#ifdef INET6
52960737Sume	    if (q > p && *p == '[' && *(q - 1) == ']') {
53060737Sume		p++;
53160737Sume		q--;
53260737Sume	    }
53360737Sume#endif
53460737Sume	    c = *q;
53537608Sdes	    *q = 0;
53660737Sume	}
53760737Sume	cd = _fetch_connect(p, pp, af, verbose);
53837608Sdes	if (q)
53960737Sume	    *q = c;
54037608Sdes    } else {
54137608Sdes	/* no proxy, go straight to target */
54260737Sume	cd = _fetch_connect(host, port, af, verbose);
54355544Sdes	p = NULL;
54437608Sdes    }
54537608Sdes
54637608Sdes    /* check connection */
54755557Sdes    if (cd == -1) {
54840939Sdes	_fetch_syserr();
54937571Sdes	return NULL;
55037571Sdes    }
55137608Sdes
55237571Sdes    /* expect welcome message */
55355557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
55437571Sdes	goto fouch;
55537571Sdes
55637571Sdes    /* send user name and password */
55737608Sdes    if (!user || !*user)
55837608Sdes	user = FTP_ANONYMOUS_USER;
55955557Sdes    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
56055557Sdes	  : _ftp_cmd(cd, "USER %s", user);
56137608Sdes
56237608Sdes    /* did the server request a password? */
56337608Sdes    if (e == FTP_NEED_PASSWORD) {
56437608Sdes	if (!pwd || !*pwd)
56560791Sume	    pwd = getenv("FTP_PASSWORD");
56660791Sume	if (!pwd || !*pwd) {
56760791Sume	    if ((logname = getlogin()) == 0)
56860791Sume		logname = FTP_ANONYMOUS_PASSWORD;
56960791Sume	    gethostname(localhost, sizeof localhost);
57060791Sume	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
57160791Sume	    pwd = pbuf;
57260791Sume	}
57355557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
57437608Sdes    }
57537608Sdes
57637608Sdes    /* did the server request an account? */
57741869Sdes    if (e == FTP_NEED_ACCOUNT)
57841863Sdes	goto fouch;
57937608Sdes
58037608Sdes    /* we should be done by now */
58141869Sdes    if (e != FTP_LOGGED_IN)
58237571Sdes	goto fouch;
58337571Sdes
58437571Sdes    /* might as well select mode and type at once */
58537571Sdes#ifdef FTP_FORCE_STREAM_MODE
58655557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
58741869Sdes	goto fouch;
58837571Sdes#endif
58955557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
59041869Sdes	goto fouch;
59137571Sdes
59237571Sdes    /* done */
59355557Sdes    return cd;
59437571Sdes
59537571Sdesfouch:
59655557Sdes    if (e != -1)
59755557Sdes	_ftp_seterr(e);
59855557Sdes    close(cd);
59937571Sdes    return NULL;
60037571Sdes}
60137571Sdes
60237571Sdes/*
60337571Sdes * Disconnect from server
60437571Sdes */
60537571Sdesstatic void
60655557Sdes_ftp_disconnect(int cd)
60737571Sdes{
60855557Sdes    (void)_ftp_cmd(cd, "QUIT");
60955557Sdes    close(cd);
61037571Sdes}
61137571Sdes
61237571Sdes/*
61337571Sdes * Check if we're already connected
61437571Sdes */
61537571Sdesstatic int
61640975Sdes_ftp_isconnected(struct url *url)
61737571Sdes{
61837571Sdes    return (cached_socket
61937571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
62037571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
62137571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
62237571Sdes	    && (url->port == cached_host.port));
62337571Sdes}
62437571Sdes
62537608Sdes/*
62641869Sdes * Check the cache, reconnect if no luck
62737608Sdes */
62855557Sdesstatic int
62941869Sdes_ftp_cached_connect(struct url *url, char *flags)
63037535Sdes{
63155557Sdes    int e, cd;
63237535Sdes
63355557Sdes    cd = -1;
63441869Sdes
63537571Sdes    /* set default port */
63660188Sdes    if (!url->port) {
63760188Sdes	struct servent *se;
63860188Sdes
63960188Sdes	if ((se = getservbyname("ftp", "tcp")) != NULL)
64060188Sdes	    url->port = ntohs(se->s_port);
64160188Sdes	else
64260188Sdes	    url->port = FTP_DEFAULT_PORT;
64360188Sdes    }
64437535Sdes
64541863Sdes    /* try to use previously cached connection */
64655557Sdes    if (_ftp_isconnected(url)) {
64755557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
64855557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
64955557Sdes	    cd = cached_socket;
65055557Sdes    }
65137571Sdes
65237571Sdes    /* connect to server */
65355557Sdes    if (cd == -1) {
65455557Sdes	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
65555557Sdes	if (cd == -1)
65655557Sdes	    return -1;
65737571Sdes	if (cached_socket)
65837571Sdes	    _ftp_disconnect(cached_socket);
65955557Sdes	cached_socket = cd;
66060188Sdes	memcpy(&cached_host, url, sizeof *url);
66137535Sdes    }
66237571Sdes
66355557Sdes    return cd;
66437535Sdes}
66537535Sdes
66637571Sdes/*
66741869Sdes * Get file
66837571Sdes */
66937535SdesFILE *
67040975SdesfetchGetFTP(struct url *url, char *flags)
67137608Sdes{
67255557Sdes    int cd;
67355557Sdes
67441869Sdes    /* connect to server */
67555557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
67641869Sdes	return NULL;
67741869Sdes
67841869Sdes    /* initiate the transfer */
67960188Sdes    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
68037608Sdes}
68137608Sdes
68241869Sdes/*
68341869Sdes * Put file
68441869Sdes */
68537608SdesFILE *
68640975SdesfetchPutFTP(struct url *url, char *flags)
68737535Sdes{
68855557Sdes    int cd;
68941869Sdes
69041869Sdes    /* connect to server */
69155557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
69241869Sdes	return NULL;
69341869Sdes
69441869Sdes    /* initiate the transfer */
69555557Sdes    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
69660188Sdes			 url->doc, "w", url->offset, flags);
69737535Sdes}
69840975Sdes
69941869Sdes/*
70041869Sdes * Get file stats
70141869Sdes */
70240975Sdesint
70340975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
70440975Sdes{
70541869Sdes    char *ln, *s;
70641869Sdes    struct tm tm;
70741869Sdes    time_t t;
70855557Sdes    int e, cd;
70941869Sdes
71060582Sdes    us->size = -1;
71160582Sdes    us->atime = us->mtime = 0;
71260582Sdes
71341869Sdes    /* connect to server */
71455557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
71541869Sdes	return -1;
71641869Sdes
71741869Sdes    /* change directory */
71841869Sdes    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
71941869Sdes	*s = 0;
72055557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
72141869Sdes	    *s = '/';
72241869Sdes	    goto ouch;
72341869Sdes	}
72441869Sdes	*s++ = '/';
72541869Sdes    } else {
72655557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
72741869Sdes	    goto ouch;
72841869Sdes    }
72941869Sdes
73041869Sdes    /* s now points to file name */
73141869Sdes
73255557Sdes    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
73341869Sdes	goto ouch;
73455557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
73541869Sdes	/* nothing */ ;
73641869Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
73741869Sdes	us->size = us->size * 10 + *ln - '0';
73841869Sdes    if (*ln && !isspace(*ln)) {
73955557Sdes	_ftp_seterr(999);
74041869Sdes	return -1;
74141869Sdes    }
74260383Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
74341869Sdes
74455557Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
74541869Sdes	goto ouch;
74655557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
74741869Sdes	/* nothing */ ;
74860383Sdes    e = 999;
74960383Sdes    switch (strspn(ln, "0123456789")) {
75060383Sdes    case 14:
75160383Sdes	break;
75260383Sdes    case 15:
75360383Sdes	ln++;
75460383Sdes	ln[0] = '2';
75560383Sdes	ln[1] = '0';
75660383Sdes	break;
75760383Sdes    default:
75860383Sdes	goto ouch;
75960383Sdes    }
76060383Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
76160383Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
76260383Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
76360383Sdes	goto ouch;
76441869Sdes    tm.tm_mon--;
76541869Sdes    tm.tm_year -= 1900;
76641869Sdes    tm.tm_isdst = -1;
76760383Sdes    t = timegm(&tm);
76856635Sdes    if (t == (time_t)-1)
76956635Sdes	t = time(NULL);
77056635Sdes    us->mtime = t;
77156635Sdes    us->atime = t;
77260383Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
77360383Sdes		  "%02d:%02d:%02d\033[m]\n",
77460383Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
77560383Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
77641869Sdes    return 0;
77741869Sdes
77841869Sdesouch:
77955557Sdes    if (e != -1)
78055557Sdes	_ftp_seterr(e);
78140975Sdes    return -1;
78240975Sdes}
78341989Sdes
78441989Sdes/*
78541989Sdes * List a directory
78641989Sdes */
78741989Sdesextern void warnx(char *, ...);
78841989Sdesstruct url_ent *
78941989SdesfetchListFTP(struct url *url, char *flags)
79041989Sdes{
79141989Sdes    warnx("fetchListFTP(): not implemented");
79241989Sdes    return NULL;
79341989Sdes}
794