ftp.c revision 60707
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 60707 2000-05-19 09:45:42Z 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
8637573Sdes#define FTP_LOGGED_IN			230
8737573Sdes#define FTP_FILE_ACTION_OK		250
8837573Sdes#define FTP_NEED_PASSWORD		331
8937573Sdes#define FTP_NEED_ACCOUNT		332
9060188Sdes#define FTP_FILE_OK			350
9155557Sdes#define FTP_SYNTAX_ERROR		500
9237573Sdes
9355557Sdesstatic char ENDL[2] = "\r\n";
9437571Sdes
9540975Sdesstatic struct url cached_host;
9655557Sdesstatic int cached_socket;
9737535Sdes
9855557Sdesstatic char *last_reply;
9955557Sdesstatic size_t lr_size, lr_length;
10055557Sdesstatic int last_code;
10137571Sdes
10255557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10360707Sdes			 && isdigit(foo[2]) \
10460707Sdes                         && (foo[3] == ' ' || foo[3] == '\0'))
10555557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10655557Sdes			&& isdigit(foo[2]) && foo[3] == '-')
10755557Sdes
10837571Sdes/*
10955557Sdes * Get server response
11037535Sdes */
11137535Sdesstatic int
11255557Sdes_ftp_chkerr(int cd)
11337535Sdes{
11437535Sdes    do {
11555557Sdes	if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
11640939Sdes	    _fetch_syserr();
11737535Sdes	    return -1;
11837571Sdes	}
11937573Sdes#ifndef NDEBUG
12055557Sdes	_fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
12137573Sdes#endif
12255557Sdes    } while (isftpinfo(last_reply));
12355557Sdes
12455557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
12555557Sdes	lr_length--;
12655557Sdes    last_reply[lr_length] = 0;
12737573Sdes
12855557Sdes    if (!isftpreply(last_reply)) {
12955557Sdes	_ftp_seterr(999);
13037535Sdes	return -1;
13137571Sdes    }
13237535Sdes
13355557Sdes    last_code = (last_reply[0] - '0') * 100
13455557Sdes	+ (last_reply[1] - '0') * 10
13555557Sdes	+ (last_reply[2] - '0');
13655557Sdes
13755557Sdes    return last_code;
13837535Sdes}
13937535Sdes
14037535Sdes/*
14137573Sdes * Send a command and check reply
14237535Sdes */
14337535Sdesstatic int
14455557Sdes_ftp_cmd(int cd, char *fmt, ...)
14537535Sdes{
14637573Sdes    va_list ap;
14755557Sdes    struct iovec iov[2];
14855557Sdes    char *msg;
14955557Sdes    int r;
15037573Sdes
15137573Sdes    va_start(ap, fmt);
15255557Sdes    vasprintf(&msg, fmt, ap);
15355557Sdes    va_end(ap);
15455557Sdes
15555557Sdes    if (msg == NULL) {
15655557Sdes	errno = ENOMEM;
15755557Sdes	_fetch_syserr();
15855557Sdes	return -1;
15955557Sdes    }
16037573Sdes#ifndef NDEBUG
16155557Sdes    _fetch_info("sending '%s'", msg);
16237573Sdes#endif
16355557Sdes    iov[0].iov_base = msg;
16455557Sdes    iov[0].iov_len = strlen(msg);
16555557Sdes    iov[1].iov_base = ENDL;
16660188Sdes    iov[1].iov_len = sizeof ENDL;
16755557Sdes    r = writev(cd, iov, 2);
16855557Sdes    free(msg);
16955557Sdes    if (r == -1) {
17055557Sdes	_fetch_syserr();
17155557Sdes	return -1;
17255557Sdes    }
17337571Sdes
17455557Sdes    return _ftp_chkerr(cd);
17537535Sdes}
17637535Sdes
17737535Sdes/*
17837608Sdes * Transfer file
17937535Sdes */
18037535Sdesstatic FILE *
18160188Sdes_ftp_transfer(int cd, char *oper, char *file,
18260188Sdes	      char *mode, off_t offset, char *flags)
18337535Sdes{
18437573Sdes    struct sockaddr_in sin;
18555544Sdes    int pasv, high, verbose;
18655544Sdes    int e, sd = -1;
18755544Sdes    socklen_t l;
18837573Sdes    char *s;
18937573Sdes    FILE *df;
19055544Sdes
19155544Sdes    /* check flags */
19255544Sdes    pasv = (flags && strchr(flags, 'p'));
19355544Sdes    high = (flags && strchr(flags, 'h'));
19455544Sdes    verbose = (flags && strchr(flags, 'v'));
19555544Sdes
19637535Sdes    /* change directory */
19737573Sdes    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
19837573Sdes	*s = 0;
19955544Sdes	if (verbose)
20055544Sdes	    _fetch_info("changing directory to %s", file);
20155557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
20237573Sdes	    *s = '/';
20355557Sdes	    if (e != -1)
20455557Sdes		_ftp_seterr(e);
20537535Sdes	    return NULL;
20637535Sdes	}
20737573Sdes	*s++ = '/';
20837535Sdes    } else {
20955544Sdes	if (verbose)
21055544Sdes	    _fetch_info("changing directory to /");
21155557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
21255557Sdes	    if (e != -1)
21355557Sdes		_ftp_seterr(e);
21437535Sdes	    return NULL;
21541869Sdes	}
21637535Sdes    }
21737535Sdes
21837573Sdes    /* s now points to file name */
21937573Sdes
22037573Sdes    /* open data socket */
22138394Sdes    if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
22240939Sdes	_fetch_syserr();
22337573Sdes	return NULL;
22437573Sdes    }
22537573Sdes
22637573Sdes    if (pasv) {
22737573Sdes	u_char addr[6];
22837573Sdes	char *ln, *p;
22937573Sdes	int i;
23037573Sdes
23137573Sdes	/* send PASV command */
23255544Sdes	if (verbose)
23355544Sdes	    _fetch_info("setting passive mode");
23455557Sdes	if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
23537573Sdes	    goto ouch;
23637573Sdes
23755544Sdes	/*
23855544Sdes	 * Find address and port number. The reply to the PASV command
23955544Sdes         * is IMHO the one and only weak point in the FTP protocol.
24055544Sdes	 */
24155557Sdes	ln = last_reply;
24260707Sdes	for (p = ln + 3; *p && !isdigit(*p); p++)
24337573Sdes	    /* nothing */ ;
24460707Sdes	for (i = 0; *p, i < 6; i++, p++)
24537573Sdes	    addr[i] = strtol(p, &p, 10);
24660707Sdes	if (i < 6) {
24760707Sdes	    e = 999;
24860707Sdes	    goto ouch;
24937573Sdes	}
25037573Sdes
25160188Sdes	/* seek to required offset */
25260188Sdes	if (offset)
25360188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
25460188Sdes		goto sysouch;
25560188Sdes
25637573Sdes	/* construct sockaddr for data socket */
25760188Sdes	l = sizeof sin;
25855557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
25937573Sdes	    goto sysouch;
26037573Sdes	bcopy(addr, (char *)&sin.sin_addr, 4);
26137573Sdes	bcopy(addr + 4, (char *)&sin.sin_port, 2);
26237573Sdes
26337573Sdes	/* connect to data port */
26455544Sdes	if (verbose)
26555544Sdes	    _fetch_info("opening data connection");
26660188Sdes	if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1)
26737573Sdes	    goto sysouch;
26860188Sdes
26937573Sdes	/* make the server initiate the transfer */
27055544Sdes	if (verbose)
27155544Sdes	    _fetch_info("initiating transfer");
27255557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
27341869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
27437573Sdes	    goto ouch;
27537573Sdes
27637573Sdes    } else {
27737573Sdes	u_int32_t a;
27837573Sdes	u_short p;
27955544Sdes	int arg, d;
28037573Sdes
28137573Sdes	/* find our own address, bind, and listen */
28260188Sdes	l = sizeof sin;
28355557Sdes	if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
28437573Sdes	    goto sysouch;
28537573Sdes	sin.sin_port = 0;
28655544Sdes	arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
28755544Sdes	if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
28860188Sdes		       (char *)&arg, sizeof arg) == -1)
28955544Sdes	    goto sysouch;
29055544Sdes	if (verbose)
29155544Sdes	    _fetch_info("binding data socket");
29238394Sdes	if (bind(sd, (struct sockaddr *)&sin, l) == -1)
29337573Sdes	    goto sysouch;
29438394Sdes	if (listen(sd, 1) == -1)
29537573Sdes	    goto sysouch;
29637573Sdes
29737573Sdes	/* find what port we're on and tell the server */
29838394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
29937573Sdes	    goto sysouch;
30037573Sdes	a = ntohl(sin.sin_addr.s_addr);
30137573Sdes	p = ntohs(sin.sin_port);
30255557Sdes	e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
30341869Sdes		     (a >> 24) & 0xff, (a >> 16) & 0xff,
30441869Sdes		     (a >> 8) & 0xff, a & 0xff,
30541869Sdes		     (p >> 8) & 0xff, p & 0xff);
30641869Sdes	if (e != FTP_OK)
30737573Sdes	    goto ouch;
30837573Sdes
30937573Sdes	/* make the server initiate the transfer */
31055544Sdes	if (verbose)
31155544Sdes	    _fetch_info("initiating transfer");
31255557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
31341869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
31437573Sdes	    goto ouch;
31537573Sdes
31637573Sdes	/* accept the incoming connection and go to town */
31738394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
31837573Sdes	    goto sysouch;
31937573Sdes	close(sd);
32037573Sdes	sd = d;
32137573Sdes    }
32237573Sdes
32337608Sdes    if ((df = fdopen(sd, mode)) == NULL)
32437573Sdes	goto sysouch;
32537573Sdes    return df;
32637573Sdes
32737573Sdessysouch:
32840939Sdes    _fetch_syserr();
32941869Sdes    close(sd);
33041869Sdes    return NULL;
33141869Sdes
33237573Sdesouch:
33355557Sdes    if (e != -1)
33455557Sdes	_ftp_seterr(e);
33537573Sdes    close(sd);
33637535Sdes    return NULL;
33737535Sdes}
33837535Sdes
33937571Sdes/*
34037571Sdes * Log on to FTP server
34137535Sdes */
34255557Sdesstatic int
34355544Sdes_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
34437571Sdes{
34560188Sdes    int cd, e, pp = 0, direct, verbose;
34637608Sdes    char *p, *q;
34737571Sdes
34855544Sdes    direct = (flags && strchr(flags, 'd'));
34955544Sdes    verbose = (flags && strchr(flags, 'v'));
35055544Sdes
35137608Sdes    /* check for proxy */
35255544Sdes    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
35337608Sdes	if ((q = strchr(p, ':')) != NULL) {
35460188Sdes	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
35560188Sdes		/* XXX we should emit some kind of warning */
35660188Sdes	    }
35737608Sdes	    pp = atoi(q+1);
35860188Sdes	    if (pp < 1 || pp > 65535) {
35960188Sdes		/* XXX we should emit some kind of warning */
36060188Sdes	    }
36137608Sdes	}
36260188Sdes	if (!pp) {
36360188Sdes	    struct servent *se;
36460188Sdes
36560188Sdes	    if ((se = getservbyname("ftp", "tcp")) != NULL)
36660188Sdes		pp = ntohs(se->s_port);
36760188Sdes	    else
36860188Sdes		pp = FTP_DEFAULT_PORT;
36960188Sdes	}
37037608Sdes	if (q)
37137608Sdes	    *q = 0;
37255557Sdes	cd = _fetch_connect(p, pp, verbose);
37337608Sdes	if (q)
37437608Sdes	    *q = ':';
37537608Sdes    } else {
37637608Sdes	/* no proxy, go straight to target */
37755557Sdes	cd = _fetch_connect(host, port, verbose);
37855544Sdes	p = NULL;
37937608Sdes    }
38037608Sdes
38137608Sdes    /* check connection */
38255557Sdes    if (cd == -1) {
38340939Sdes	_fetch_syserr();
38437571Sdes	return NULL;
38537571Sdes    }
38637608Sdes
38737571Sdes    /* expect welcome message */
38855557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
38937571Sdes	goto fouch;
39037571Sdes
39137571Sdes    /* send user name and password */
39237608Sdes    if (!user || !*user)
39337608Sdes	user = FTP_ANONYMOUS_USER;
39455557Sdes    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
39555557Sdes	  : _ftp_cmd(cd, "USER %s", user);
39637608Sdes
39737608Sdes    /* did the server request a password? */
39837608Sdes    if (e == FTP_NEED_PASSWORD) {
39937608Sdes	if (!pwd || !*pwd)
40037608Sdes	    pwd = FTP_ANONYMOUS_PASSWORD;
40155557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
40237608Sdes    }
40337608Sdes
40437608Sdes    /* did the server request an account? */
40541869Sdes    if (e == FTP_NEED_ACCOUNT)
40641863Sdes	goto fouch;
40737608Sdes
40837608Sdes    /* we should be done by now */
40941869Sdes    if (e != FTP_LOGGED_IN)
41037571Sdes	goto fouch;
41137571Sdes
41237571Sdes    /* might as well select mode and type at once */
41337571Sdes#ifdef FTP_FORCE_STREAM_MODE
41455557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
41541869Sdes	goto fouch;
41637571Sdes#endif
41755557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
41841869Sdes	goto fouch;
41937571Sdes
42037571Sdes    /* done */
42155557Sdes    return cd;
42237571Sdes
42337571Sdesfouch:
42455557Sdes    if (e != -1)
42555557Sdes	_ftp_seterr(e);
42655557Sdes    close(cd);
42737571Sdes    return NULL;
42837571Sdes}
42937571Sdes
43037571Sdes/*
43137571Sdes * Disconnect from server
43237571Sdes */
43337571Sdesstatic void
43455557Sdes_ftp_disconnect(int cd)
43537571Sdes{
43655557Sdes    (void)_ftp_cmd(cd, "QUIT");
43755557Sdes    close(cd);
43837571Sdes}
43937571Sdes
44037571Sdes/*
44137571Sdes * Check if we're already connected
44237571Sdes */
44337571Sdesstatic int
44440975Sdes_ftp_isconnected(struct url *url)
44537571Sdes{
44637571Sdes    return (cached_socket
44737571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
44837571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
44937571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
45037571Sdes	    && (url->port == cached_host.port));
45137571Sdes}
45237571Sdes
45337608Sdes/*
45441869Sdes * Check the cache, reconnect if no luck
45537608Sdes */
45655557Sdesstatic int
45741869Sdes_ftp_cached_connect(struct url *url, char *flags)
45837535Sdes{
45955557Sdes    int e, cd;
46037535Sdes
46155557Sdes    cd = -1;
46241869Sdes
46337571Sdes    /* set default port */
46460188Sdes    if (!url->port) {
46560188Sdes	struct servent *se;
46660188Sdes
46760188Sdes	if ((se = getservbyname("ftp", "tcp")) != NULL)
46860188Sdes	    url->port = ntohs(se->s_port);
46960188Sdes	else
47060188Sdes	    url->port = FTP_DEFAULT_PORT;
47160188Sdes    }
47237535Sdes
47341863Sdes    /* try to use previously cached connection */
47455557Sdes    if (_ftp_isconnected(url)) {
47555557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
47655557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
47755557Sdes	    cd = cached_socket;
47855557Sdes    }
47937571Sdes
48037571Sdes    /* connect to server */
48155557Sdes    if (cd == -1) {
48255557Sdes	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
48355557Sdes	if (cd == -1)
48455557Sdes	    return -1;
48537571Sdes	if (cached_socket)
48637571Sdes	    _ftp_disconnect(cached_socket);
48755557Sdes	cached_socket = cd;
48860188Sdes	memcpy(&cached_host, url, sizeof *url);
48937535Sdes    }
49037571Sdes
49155557Sdes    return cd;
49237535Sdes}
49337535Sdes
49437571Sdes/*
49541869Sdes * Get file
49637571Sdes */
49737535SdesFILE *
49840975SdesfetchGetFTP(struct url *url, char *flags)
49937608Sdes{
50055557Sdes    int cd;
50155557Sdes
50241869Sdes    /* connect to server */
50355557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
50441869Sdes	return NULL;
50541869Sdes
50641869Sdes    /* initiate the transfer */
50760188Sdes    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
50837608Sdes}
50937608Sdes
51041869Sdes/*
51141869Sdes * Put file
51241869Sdes */
51337608SdesFILE *
51440975SdesfetchPutFTP(struct url *url, char *flags)
51537535Sdes{
51655557Sdes    int cd;
51741869Sdes
51841869Sdes    /* connect to server */
51955557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
52041869Sdes	return NULL;
52141869Sdes
52241869Sdes    /* initiate the transfer */
52355557Sdes    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
52460188Sdes			 url->doc, "w", url->offset, flags);
52537535Sdes}
52640975Sdes
52741869Sdes/*
52841869Sdes * Get file stats
52941869Sdes */
53040975Sdesint
53140975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
53240975Sdes{
53341869Sdes    char *ln, *s;
53441869Sdes    struct tm tm;
53541869Sdes    time_t t;
53655557Sdes    int e, cd;
53741869Sdes
53860582Sdes    us->size = -1;
53960582Sdes    us->atime = us->mtime = 0;
54060582Sdes
54141869Sdes    /* connect to server */
54255557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
54341869Sdes	return -1;
54441869Sdes
54541869Sdes    /* change directory */
54641869Sdes    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
54741869Sdes	*s = 0;
54855557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
54941869Sdes	    *s = '/';
55041869Sdes	    goto ouch;
55141869Sdes	}
55241869Sdes	*s++ = '/';
55341869Sdes    } else {
55455557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
55541869Sdes	    goto ouch;
55641869Sdes    }
55741869Sdes
55841869Sdes    /* s now points to file name */
55941869Sdes
56055557Sdes    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
56141869Sdes	goto ouch;
56255557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
56341869Sdes	/* nothing */ ;
56441869Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
56541869Sdes	us->size = us->size * 10 + *ln - '0';
56641869Sdes    if (*ln && !isspace(*ln)) {
56755557Sdes	_ftp_seterr(999);
56841869Sdes	return -1;
56941869Sdes    }
57060383Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
57141869Sdes
57255557Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
57341869Sdes	goto ouch;
57455557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
57541869Sdes	/* nothing */ ;
57660383Sdes    e = 999;
57760383Sdes    switch (strspn(ln, "0123456789")) {
57860383Sdes    case 14:
57960383Sdes	break;
58060383Sdes    case 15:
58160383Sdes	ln++;
58260383Sdes	ln[0] = '2';
58360383Sdes	ln[1] = '0';
58460383Sdes	break;
58560383Sdes    default:
58660383Sdes	goto ouch;
58760383Sdes    }
58860383Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
58960383Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
59060383Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
59160383Sdes	goto ouch;
59241869Sdes    tm.tm_mon--;
59341869Sdes    tm.tm_year -= 1900;
59441869Sdes    tm.tm_isdst = -1;
59560383Sdes    t = timegm(&tm);
59656635Sdes    if (t == (time_t)-1)
59756635Sdes	t = time(NULL);
59856635Sdes    us->mtime = t;
59956635Sdes    us->atime = t;
60060383Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
60160383Sdes		  "%02d:%02d:%02d\033[m]\n",
60260383Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
60360383Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
60441869Sdes    return 0;
60541869Sdes
60641869Sdesouch:
60755557Sdes    if (e != -1)
60855557Sdes	_ftp_seterr(e);
60940975Sdes    return -1;
61040975Sdes}
61141989Sdes
61241989Sdes/*
61341989Sdes * List a directory
61441989Sdes */
61541989Sdesextern void warnx(char *, ...);
61641989Sdesstruct url_ent *
61741989SdesfetchListFTP(struct url *url, char *flags)
61841989Sdes{
61941989Sdes    warnx("fetchListFTP(): not implemented");
62041989Sdes    return NULL;
62141989Sdes}
622