ftp.c revision 62256
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 62256 2000-06-29 10:44:10Z des $
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{
13762215Sdes    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
13862215Sdes	_fetch_syserr();
13962215Sdes	return -1;
14062215Sdes    }
14137573Sdes#ifndef NDEBUG
14262215Sdes    _fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
14337573Sdes#endif
14462215Sdes    if (isftpinfo(last_reply)) {
14562215Sdes	while (!isftpreply(last_reply)) {
14662215Sdes	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
14762215Sdes		_fetch_syserr();
14862215Sdes		return -1;
14962215Sdes	    }
15062215Sdes#ifndef NDEBUG
15162215Sdes	    _fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
15262215Sdes#endif
15362215Sdes	}
15462215Sdes    }
15555557Sdes
15655557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
15755557Sdes	lr_length--;
15855557Sdes    last_reply[lr_length] = 0;
15937573Sdes
16055557Sdes    if (!isftpreply(last_reply)) {
16155557Sdes	_ftp_seterr(999);
16237535Sdes	return -1;
16337571Sdes    }
16437535Sdes
16555557Sdes    last_code = (last_reply[0] - '0') * 100
16655557Sdes	+ (last_reply[1] - '0') * 10
16755557Sdes	+ (last_reply[2] - '0');
16855557Sdes
16955557Sdes    return last_code;
17037535Sdes}
17137535Sdes
17237535Sdes/*
17337573Sdes * Send a command and check reply
17437535Sdes */
17537535Sdesstatic int
17655557Sdes_ftp_cmd(int cd, char *fmt, ...)
17737535Sdes{
17837573Sdes    va_list ap;
17955557Sdes    struct iovec iov[2];
18055557Sdes    char *msg;
18155557Sdes    int r;
18237573Sdes
18337573Sdes    va_start(ap, fmt);
18455557Sdes    vasprintf(&msg, fmt, ap);
18555557Sdes    va_end(ap);
18655557Sdes
18755557Sdes    if (msg == NULL) {
18855557Sdes	errno = ENOMEM;
18955557Sdes	_fetch_syserr();
19055557Sdes	return -1;
19155557Sdes    }
19237573Sdes#ifndef NDEBUG
19355557Sdes    _fetch_info("sending '%s'", msg);
19437573Sdes#endif
19555557Sdes    iov[0].iov_base = msg;
19655557Sdes    iov[0].iov_len = strlen(msg);
19755557Sdes    iov[1].iov_base = ENDL;
19860188Sdes    iov[1].iov_len = sizeof ENDL;
19955557Sdes    r = writev(cd, iov, 2);
20055557Sdes    free(msg);
20155557Sdes    if (r == -1) {
20255557Sdes	_fetch_syserr();
20355557Sdes	return -1;
20455557Sdes    }
20537571Sdes
20655557Sdes    return _ftp_chkerr(cd);
20737535Sdes}
20837535Sdes
20937535Sdes/*
21037608Sdes * Transfer file
21137535Sdes */
21237535Sdesstatic FILE *
21360188Sdes_ftp_transfer(int cd, char *oper, char *file,
21460188Sdes	      char *mode, off_t offset, char *flags)
21537535Sdes{
21660737Sume    struct sockaddr_storage sin;
21760737Sume    struct sockaddr_in6 *sin6;
21860737Sume    struct sockaddr_in *sin4;
21955544Sdes    int pasv, high, verbose;
22055544Sdes    int e, sd = -1;
22155544Sdes    socklen_t l;
22237573Sdes    char *s;
22337573Sdes    FILE *df;
22455544Sdes
22555544Sdes    /* check flags */
22655544Sdes    pasv = (flags && strchr(flags, 'p'));
22755544Sdes    high = (flags && strchr(flags, 'h'));
22855544Sdes    verbose = (flags && strchr(flags, 'v'));
22955544Sdes
23060951Sdes    /* passive mode */
23160951Sdes    if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL)
23260951Sdes	pasv = (strncasecmp(s, "no", 2) != 0);
23360951Sdes
23437535Sdes    /* change directory */
23537573Sdes    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
23637573Sdes	*s = 0;
23755544Sdes	if (verbose)
23855544Sdes	    _fetch_info("changing directory to %s", file);
23955557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
24037573Sdes	    *s = '/';
24155557Sdes	    if (e != -1)
24255557Sdes		_ftp_seterr(e);
24337535Sdes	    return NULL;
24437535Sdes	}
24537573Sdes	*s++ = '/';
24637535Sdes    } else {
24755544Sdes	if (verbose)
24855544Sdes	    _fetch_info("changing directory to /");
24955557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
25055557Sdes	    if (e != -1)
25155557Sdes		_ftp_seterr(e);
25237535Sdes	    return NULL;
25341869Sdes	}
25437535Sdes    }
25537535Sdes
25637573Sdes    /* s now points to file name */
25737573Sdes
25860737Sume    /* find our own address, bind, and listen */
25960737Sume    l = sizeof sin;
26060737Sume    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
26160737Sume	goto sysouch;
26260737Sume    if (sin.ss_family == AF_INET6)
26360737Sume	unmappedaddr((struct sockaddr_in6 *)&sin);
26460737Sume
26537573Sdes    /* open data socket */
26660737Sume    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
26740939Sdes	_fetch_syserr();
26837573Sdes	return NULL;
26937573Sdes    }
27037573Sdes
27137573Sdes    if (pasv) {
27260737Sume	u_char addr[64];
27337573Sdes	char *ln, *p;
27437573Sdes	int i;
27560737Sume	int port;
27637573Sdes
27737573Sdes	/* send PASV command */
27855544Sdes	if (verbose)
27955544Sdes	    _fetch_info("setting passive mode");
28060737Sume	switch (sin.ss_family) {
28160737Sume	case AF_INET:
28260737Sume	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
28360737Sume		goto ouch;
28460737Sume	    break;
28560737Sume	case AF_INET6:
28660737Sume	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
28760737Sume		if (e == -1)
28860737Sume		    goto ouch;
28960737Sume		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
29060737Sume		    goto ouch;
29160737Sume	    }
29260737Sume	    break;
29360737Sume	default:
29460737Sume	    e = 999;		/* XXX: error code should be prepared */
29537573Sdes	    goto ouch;
29660737Sume	}
29737573Sdes
29855544Sdes	/*
29955544Sdes	 * Find address and port number. The reply to the PASV command
30055544Sdes         * is IMHO the one and only weak point in the FTP protocol.
30155544Sdes	 */
30255557Sdes	ln = last_reply;
30360737Sume	for (p = ln + 3; *p && *p != '('; p++)
30437573Sdes	    /* nothing */ ;
30560737Sume	if (!*p) {
30660707Sdes	    e = 999;
30760707Sdes	    goto ouch;
30837573Sdes	}
30960737Sume	p++;
31060737Sume	switch (e) {
31160737Sume	case FTP_PASSIVE_MODE:
31260737Sume	case FTP_LPASSIVE_MODE:
31360737Sume	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
31460737Sume	    for (i = 0; *p && i < l; i++, p++)
31560737Sume		addr[i] = strtol(p, &p, 10);
31660737Sume	    if (i < l) {
31760737Sume		e = 999;
31860737Sume		goto ouch;
31960737Sume	    }
32060737Sume	    break;
32160737Sume	case FTP_EPASSIVE_MODE:
32260737Sume	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
32360737Sume		       &port, &addr[3]) != 5 ||
32460737Sume		addr[0] != addr[1] ||
32560737Sume		addr[0] != addr[2] || addr[0] != addr[3]) {
32660737Sume		e = 999;
32760737Sume		goto ouch;
32860737Sume	    }
32960737Sume	    break;
33060737Sume	}
33137573Sdes
33260188Sdes	/* seek to required offset */
33360188Sdes	if (offset)
33460188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
33560188Sdes		goto sysouch;
33660188Sdes
33737573Sdes	/* construct sockaddr for data socket */
33860188Sdes	l = sizeof sin;
33955557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
34037573Sdes	    goto sysouch;
34160737Sume	if (sin.ss_family == AF_INET6)
34260737Sume	    unmappedaddr((struct sockaddr_in6 *)&sin);
34360737Sume	switch (sin.ss_family) {
34460737Sume	case AF_INET6:
34560737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
34660737Sume	    if (e == FTP_EPASSIVE_MODE)
34760737Sume		sin6->sin6_port = htons(port);
34860737Sume	    else {
34960737Sume		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
35060737Sume		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
35160737Sume	    }
35260737Sume	    break;
35360737Sume	case AF_INET:
35460737Sume	    sin4 = (struct sockaddr_in *)&sin;
35560737Sume	    if (e == FTP_EPASSIVE_MODE)
35660737Sume		sin4->sin_port = htons(port);
35760737Sume	    else {
35860737Sume		bcopy(addr, (char *)&sin4->sin_addr, 4);
35960737Sume		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
36060737Sume	    }
36160737Sume	    break;
36260737Sume	default:
36360737Sume	    e = 999;		/* XXX: error code should be prepared */
36460737Sume	    break;
36560737Sume	}
36637573Sdes
36737573Sdes	/* connect to data port */
36855544Sdes	if (verbose)
36955544Sdes	    _fetch_info("opening data connection");
37060737Sume	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
37137573Sdes	    goto sysouch;
37260188Sdes
37337573Sdes	/* make the server initiate the transfer */
37461866Sdes	if (verbose)
37561866Sdes	    _fetch_info("initiating transfer");
37661866Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
37741869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
37837573Sdes	    goto ouch;
37937573Sdes
38037573Sdes    } else {
38137573Sdes	u_int32_t a;
38237573Sdes	u_short p;
38355544Sdes	int arg, d;
38460737Sume	char *ap;
38560737Sume	char hname[INET6_ADDRSTRLEN];
38637573Sdes
38760737Sume	switch (sin.ss_family) {
38860737Sume	case AF_INET6:
38960737Sume	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
39060737Sume#ifdef IPV6_PORTRANGE
39160737Sume	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
39260737Sume	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
39360737Sume			   (char *)&arg, sizeof(arg)) == -1)
39460737Sume		goto sysouch;
39560737Sume#endif
39660737Sume	    break;
39760737Sume	case AF_INET:
39860737Sume	    ((struct sockaddr_in *)&sin)->sin_port = 0;
39960737Sume	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
40060737Sume	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
40160737Sume			   (char *)&arg, sizeof arg) == -1)
40260737Sume		goto sysouch;
40360737Sume	    break;
40460737Sume	}
40555544Sdes	if (verbose)
40655544Sdes	    _fetch_info("binding data socket");
40760737Sume	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
40837573Sdes	    goto sysouch;
40938394Sdes	if (listen(sd, 1) == -1)
41037573Sdes	    goto sysouch;
41137573Sdes
41237573Sdes	/* find what port we're on and tell the server */
41338394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
41437573Sdes	    goto sysouch;
41560737Sume	switch (sin.ss_family) {
41660737Sume	case AF_INET:
41760737Sume	    sin4 = (struct sockaddr_in *)&sin;
41860737Sume	    a = ntohl(sin4->sin_addr.s_addr);
41960737Sume	    p = ntohs(sin4->sin_port);
42060737Sume	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
42160737Sume			 (a >> 24) & 0xff, (a >> 16) & 0xff,
42260737Sume			 (a >> 8) & 0xff, a & 0xff,
42360737Sume			 (p >> 8) & 0xff, p & 0xff);
42460737Sume	    break;
42560737Sume	case AF_INET6:
42660737Sume#define UC(b)	(((int)b)&0xff)
42760737Sume	    e = -1;
42860737Sume	    sin6 = (struct sockaddr_in6 *)&sin;
42960737Sume	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
43060737Sume			    hname, sizeof(hname),
43160737Sume			    NULL, 0, NI_NUMERICHOST) == 0) {
43260737Sume		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
43360737Sume			     htons(sin6->sin6_port));
43460737Sume		if (e == -1)
43560737Sume		    goto ouch;
43660737Sume	    }
43760737Sume	    if (e != FTP_OK) {
43860737Sume		ap = (char *)&sin6->sin6_addr;
43960737Sume		e = _ftp_cmd(cd,
44060737Sume     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
44160737Sume			     6, 16,
44260737Sume			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
44360737Sume			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
44460737Sume			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
44560737Sume			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
44660737Sume			     2,
44760737Sume			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
44860737Sume			     ntohs(sin6->sin6_port)        & 0xff);
44960737Sume	    }
45060737Sume	    break;
45160737Sume	default:
45260737Sume	    e = 999;		/* XXX: error code should be prepared */
45360737Sume	    goto ouch;
45460737Sume	}
45541869Sdes	if (e != FTP_OK)
45637573Sdes	    goto ouch;
45737573Sdes
45862256Sdes	/* seek to required offset */
45962256Sdes	if (offset)
46062256Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
46162256Sdes		goto sysouch;
46262256Sdes
46337573Sdes	/* make the server initiate the transfer */
46455544Sdes	if (verbose)
46555544Sdes	    _fetch_info("initiating transfer");
46655557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
46741869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
46837573Sdes	    goto ouch;
46937573Sdes
47037573Sdes	/* accept the incoming connection and go to town */
47138394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
47237573Sdes	    goto sysouch;
47337573Sdes	close(sd);
47437573Sdes	sd = d;
47537573Sdes    }
47637573Sdes
47737608Sdes    if ((df = fdopen(sd, mode)) == NULL)
47837573Sdes	goto sysouch;
47937573Sdes    return df;
48037573Sdes
48137573Sdessysouch:
48240939Sdes    _fetch_syserr();
48360737Sume    if (sd >= 0)
48460737Sume	close(sd);
48541869Sdes    return NULL;
48641869Sdes
48737573Sdesouch:
48855557Sdes    if (e != -1)
48955557Sdes	_ftp_seterr(e);
49060737Sume    if (sd >= 0)
49160737Sume	close(sd);
49237535Sdes    return NULL;
49337535Sdes}
49437535Sdes
49537571Sdes/*
49637571Sdes * Log on to FTP server
49737535Sdes */
49855557Sdesstatic int
49955544Sdes_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
50037571Sdes{
50160188Sdes    int cd, e, pp = 0, direct, verbose;
50260737Sume#ifdef INET6
50360737Sume    int af = AF_UNSPEC;
50460737Sume#else
50560737Sume    int af = AF_INET;
50660737Sume#endif
50737608Sdes    char *p, *q;
50860791Sume    const char *logname;
50960791Sume    char localhost[MAXHOSTNAMELEN];
51060791Sume    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
51137571Sdes
51255544Sdes    direct = (flags && strchr(flags, 'd'));
51355544Sdes    verbose = (flags && strchr(flags, 'v'));
51460737Sume    if ((flags && strchr(flags, '4')))
51560737Sume	af = AF_INET;
51660737Sume    else if ((flags && strchr(flags, '6')))
51760737Sume	af = AF_INET6;
51860737Sume
51937608Sdes    /* check for proxy */
52055544Sdes    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
52160737Sume	char c = 0;
52260737Sume
52360737Sume#ifdef INET6
52460737Sume	if (*p != '[' || (q = strchr(p + 1, ']')) == NULL ||
52560737Sume	    (*++q != '\0' && *q != ':'))
52660737Sume#endif
52760737Sume	    q = strchr(p, ':');
52860737Sume	if (q != NULL && *q == ':') {
52960188Sdes	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
53060188Sdes		/* XXX we should emit some kind of warning */
53160188Sdes	    }
53237608Sdes	    pp = atoi(q+1);
53360188Sdes	    if (pp < 1 || pp > 65535) {
53460188Sdes		/* XXX we should emit some kind of warning */
53560188Sdes	    }
53637608Sdes	}
53760188Sdes	if (!pp) {
53860188Sdes	    struct servent *se;
53960188Sdes
54060188Sdes	    if ((se = getservbyname("ftp", "tcp")) != NULL)
54160188Sdes		pp = ntohs(se->s_port);
54260188Sdes	    else
54360188Sdes		pp = FTP_DEFAULT_PORT;
54460188Sdes	}
54560737Sume	if (q) {
54660737Sume#ifdef INET6
54760737Sume	    if (q > p && *p == '[' && *(q - 1) == ']') {
54860737Sume		p++;
54960737Sume		q--;
55060737Sume	    }
55160737Sume#endif
55260737Sume	    c = *q;
55337608Sdes	    *q = 0;
55460737Sume	}
55560737Sume	cd = _fetch_connect(p, pp, af, verbose);
55637608Sdes	if (q)
55760737Sume	    *q = c;
55837608Sdes    } else {
55937608Sdes	/* no proxy, go straight to target */
56060737Sume	cd = _fetch_connect(host, port, af, verbose);
56155544Sdes	p = NULL;
56237608Sdes    }
56337608Sdes
56437608Sdes    /* check connection */
56555557Sdes    if (cd == -1) {
56640939Sdes	_fetch_syserr();
56737571Sdes	return NULL;
56837571Sdes    }
56937608Sdes
57037571Sdes    /* expect welcome message */
57155557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
57237571Sdes	goto fouch;
57337571Sdes
57437571Sdes    /* send user name and password */
57537608Sdes    if (!user || !*user)
57637608Sdes	user = FTP_ANONYMOUS_USER;
57755557Sdes    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
57855557Sdes	  : _ftp_cmd(cd, "USER %s", user);
57937608Sdes
58037608Sdes    /* did the server request a password? */
58137608Sdes    if (e == FTP_NEED_PASSWORD) {
58237608Sdes	if (!pwd || !*pwd)
58360791Sume	    pwd = getenv("FTP_PASSWORD");
58460791Sume	if (!pwd || !*pwd) {
58560791Sume	    if ((logname = getlogin()) == 0)
58660791Sume		logname = FTP_ANONYMOUS_PASSWORD;
58760791Sume	    gethostname(localhost, sizeof localhost);
58860791Sume	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
58960791Sume	    pwd = pbuf;
59060791Sume	}
59155557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
59237608Sdes    }
59337608Sdes
59437608Sdes    /* did the server request an account? */
59541869Sdes    if (e == FTP_NEED_ACCOUNT)
59641863Sdes	goto fouch;
59737608Sdes
59837608Sdes    /* we should be done by now */
59941869Sdes    if (e != FTP_LOGGED_IN)
60037571Sdes	goto fouch;
60137571Sdes
60237571Sdes    /* might as well select mode and type at once */
60337571Sdes#ifdef FTP_FORCE_STREAM_MODE
60455557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
60541869Sdes	goto fouch;
60637571Sdes#endif
60755557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
60841869Sdes	goto fouch;
60937571Sdes
61037571Sdes    /* done */
61155557Sdes    return cd;
61237571Sdes
61337571Sdesfouch:
61455557Sdes    if (e != -1)
61555557Sdes	_ftp_seterr(e);
61655557Sdes    close(cd);
61737571Sdes    return NULL;
61837571Sdes}
61937571Sdes
62037571Sdes/*
62137571Sdes * Disconnect from server
62237571Sdes */
62337571Sdesstatic void
62455557Sdes_ftp_disconnect(int cd)
62537571Sdes{
62655557Sdes    (void)_ftp_cmd(cd, "QUIT");
62755557Sdes    close(cd);
62837571Sdes}
62937571Sdes
63037571Sdes/*
63137571Sdes * Check if we're already connected
63237571Sdes */
63337571Sdesstatic int
63440975Sdes_ftp_isconnected(struct url *url)
63537571Sdes{
63637571Sdes    return (cached_socket
63737571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
63837571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
63937571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
64037571Sdes	    && (url->port == cached_host.port));
64137571Sdes}
64237571Sdes
64337608Sdes/*
64441869Sdes * Check the cache, reconnect if no luck
64537608Sdes */
64655557Sdesstatic int
64741869Sdes_ftp_cached_connect(struct url *url, char *flags)
64837535Sdes{
64955557Sdes    int e, cd;
65037535Sdes
65155557Sdes    cd = -1;
65241869Sdes
65337571Sdes    /* set default port */
65460188Sdes    if (!url->port) {
65560188Sdes	struct servent *se;
65660188Sdes
65760188Sdes	if ((se = getservbyname("ftp", "tcp")) != NULL)
65860188Sdes	    url->port = ntohs(se->s_port);
65960188Sdes	else
66060188Sdes	    url->port = FTP_DEFAULT_PORT;
66160188Sdes    }
66237535Sdes
66341863Sdes    /* try to use previously cached connection */
66455557Sdes    if (_ftp_isconnected(url)) {
66555557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
66655557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
66755557Sdes	    cd = cached_socket;
66855557Sdes    }
66937571Sdes
67037571Sdes    /* connect to server */
67155557Sdes    if (cd == -1) {
67255557Sdes	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
67355557Sdes	if (cd == -1)
67455557Sdes	    return -1;
67537571Sdes	if (cached_socket)
67637571Sdes	    _ftp_disconnect(cached_socket);
67755557Sdes	cached_socket = cd;
67860188Sdes	memcpy(&cached_host, url, sizeof *url);
67937535Sdes    }
68037571Sdes
68155557Sdes    return cd;
68237535Sdes}
68337535Sdes
68437571Sdes/*
68541869Sdes * Get file
68637571Sdes */
68737535SdesFILE *
68840975SdesfetchGetFTP(struct url *url, char *flags)
68937608Sdes{
69055557Sdes    int cd;
69155557Sdes
69241869Sdes    /* connect to server */
69355557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
69441869Sdes	return NULL;
69541869Sdes
69641869Sdes    /* initiate the transfer */
69760188Sdes    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
69837608Sdes}
69937608Sdes
70041869Sdes/*
70141869Sdes * Put file
70241869Sdes */
70337608SdesFILE *
70440975SdesfetchPutFTP(struct url *url, char *flags)
70537535Sdes{
70655557Sdes    int cd;
70741869Sdes
70841869Sdes    /* connect to server */
70955557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
71041869Sdes	return NULL;
71141869Sdes
71241869Sdes    /* initiate the transfer */
71355557Sdes    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
71460188Sdes			 url->doc, "w", url->offset, flags);
71537535Sdes}
71640975Sdes
71741869Sdes/*
71841869Sdes * Get file stats
71941869Sdes */
72040975Sdesint
72140975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
72240975Sdes{
72341869Sdes    char *ln, *s;
72441869Sdes    struct tm tm;
72541869Sdes    time_t t;
72655557Sdes    int e, cd;
72741869Sdes
72860582Sdes    us->size = -1;
72960582Sdes    us->atime = us->mtime = 0;
73060582Sdes
73141869Sdes    /* connect to server */
73255557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
73341869Sdes	return -1;
73441869Sdes
73541869Sdes    /* change directory */
73641869Sdes    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
73741869Sdes	*s = 0;
73855557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
73941869Sdes	    *s = '/';
74041869Sdes	    goto ouch;
74141869Sdes	}
74241869Sdes	*s++ = '/';
74341869Sdes    } else {
74455557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
74541869Sdes	    goto ouch;
74641869Sdes    }
74741869Sdes
74841869Sdes    /* s now points to file name */
74941869Sdes
75055557Sdes    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
75141869Sdes	goto ouch;
75255557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
75341869Sdes	/* nothing */ ;
75441869Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
75541869Sdes	us->size = us->size * 10 + *ln - '0';
75641869Sdes    if (*ln && !isspace(*ln)) {
75755557Sdes	_ftp_seterr(999);
75841869Sdes	return -1;
75941869Sdes    }
76060383Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
76141869Sdes
76255557Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
76341869Sdes	goto ouch;
76455557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
76541869Sdes	/* nothing */ ;
76660383Sdes    e = 999;
76760383Sdes    switch (strspn(ln, "0123456789")) {
76860383Sdes    case 14:
76960383Sdes	break;
77060383Sdes    case 15:
77160383Sdes	ln++;
77260383Sdes	ln[0] = '2';
77360383Sdes	ln[1] = '0';
77460383Sdes	break;
77560383Sdes    default:
77660383Sdes	goto ouch;
77760383Sdes    }
77860383Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
77960383Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
78060383Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
78160383Sdes	goto ouch;
78241869Sdes    tm.tm_mon--;
78341869Sdes    tm.tm_year -= 1900;
78441869Sdes    tm.tm_isdst = -1;
78560383Sdes    t = timegm(&tm);
78656635Sdes    if (t == (time_t)-1)
78756635Sdes	t = time(NULL);
78856635Sdes    us->mtime = t;
78956635Sdes    us->atime = t;
79060383Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
79160383Sdes		  "%02d:%02d:%02d\033[m]\n",
79260383Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
79360383Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
79441869Sdes    return 0;
79541869Sdes
79641869Sdesouch:
79755557Sdes    if (e != -1)
79855557Sdes	_ftp_seterr(e);
79940975Sdes    return -1;
80040975Sdes}
80141989Sdes
80241989Sdes/*
80341989Sdes * List a directory
80441989Sdes */
80541989Sdesextern void warnx(char *, ...);
80641989Sdesstruct url_ent *
80741989SdesfetchListFTP(struct url *url, char *flags)
80841989Sdes{
80961866Sdes    warnx("fetchListFTP(): not implemented");
81061866Sdes    return NULL;
81141989Sdes}
812