ftp.c revision 60383
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 60383 2000-05-11 16:01:03Z 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]) \
10355557Sdes			 && isdigit(foo[2]) && foo[3] == ' ')
10455557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
10555557Sdes			&& isdigit(foo[2]) && foo[3] == '-')
10655557Sdes
10737571Sdes/*
10855557Sdes * Get server response
10937535Sdes */
11037535Sdesstatic int
11155557Sdes_ftp_chkerr(int cd)
11237535Sdes{
11337535Sdes    do {
11455557Sdes	if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
11540939Sdes	    _fetch_syserr();
11637535Sdes	    return -1;
11737571Sdes	}
11837573Sdes#ifndef NDEBUG
11955557Sdes	_fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
12037573Sdes#endif
12155557Sdes    } while (isftpinfo(last_reply));
12255557Sdes
12355557Sdes    while (lr_length && isspace(last_reply[lr_length-1]))
12455557Sdes	lr_length--;
12555557Sdes    last_reply[lr_length] = 0;
12637573Sdes
12755557Sdes    if (!isftpreply(last_reply)) {
12855557Sdes	_ftp_seterr(999);
12937535Sdes	return -1;
13037571Sdes    }
13137535Sdes
13255557Sdes    last_code = (last_reply[0] - '0') * 100
13355557Sdes	+ (last_reply[1] - '0') * 10
13455557Sdes	+ (last_reply[2] - '0');
13555557Sdes
13655557Sdes    return last_code;
13737535Sdes}
13837535Sdes
13937535Sdes/*
14037573Sdes * Send a command and check reply
14137535Sdes */
14237535Sdesstatic int
14355557Sdes_ftp_cmd(int cd, char *fmt, ...)
14437535Sdes{
14537573Sdes    va_list ap;
14655557Sdes    struct iovec iov[2];
14755557Sdes    char *msg;
14855557Sdes    int r;
14937573Sdes
15037573Sdes    va_start(ap, fmt);
15155557Sdes    vasprintf(&msg, fmt, ap);
15255557Sdes    va_end(ap);
15355557Sdes
15455557Sdes    if (msg == NULL) {
15555557Sdes	errno = ENOMEM;
15655557Sdes	_fetch_syserr();
15755557Sdes	return -1;
15855557Sdes    }
15937573Sdes#ifndef NDEBUG
16055557Sdes    _fetch_info("sending '%s'", msg);
16137573Sdes#endif
16255557Sdes    iov[0].iov_base = msg;
16355557Sdes    iov[0].iov_len = strlen(msg);
16455557Sdes    iov[1].iov_base = ENDL;
16560188Sdes    iov[1].iov_len = sizeof ENDL;
16655557Sdes    r = writev(cd, iov, 2);
16755557Sdes    free(msg);
16855557Sdes    if (r == -1) {
16955557Sdes	_fetch_syserr();
17055557Sdes	return -1;
17155557Sdes    }
17237571Sdes
17355557Sdes    return _ftp_chkerr(cd);
17437535Sdes}
17537535Sdes
17637535Sdes/*
17737608Sdes * Transfer file
17837535Sdes */
17937535Sdesstatic FILE *
18060188Sdes_ftp_transfer(int cd, char *oper, char *file,
18160188Sdes	      char *mode, off_t offset, char *flags)
18237535Sdes{
18337573Sdes    struct sockaddr_in sin;
18455544Sdes    int pasv, high, verbose;
18555544Sdes    int e, sd = -1;
18655544Sdes    socklen_t l;
18737573Sdes    char *s;
18837573Sdes    FILE *df;
18955544Sdes
19055544Sdes    /* check flags */
19155544Sdes    pasv = (flags && strchr(flags, 'p'));
19255544Sdes    high = (flags && strchr(flags, 'h'));
19355544Sdes    verbose = (flags && strchr(flags, 'v'));
19455544Sdes
19537535Sdes    /* change directory */
19637573Sdes    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
19737573Sdes	*s = 0;
19855544Sdes	if (verbose)
19955544Sdes	    _fetch_info("changing directory to %s", file);
20055557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
20137573Sdes	    *s = '/';
20255557Sdes	    if (e != -1)
20355557Sdes		_ftp_seterr(e);
20437535Sdes	    return NULL;
20537535Sdes	}
20637573Sdes	*s++ = '/';
20737535Sdes    } else {
20855544Sdes	if (verbose)
20955544Sdes	    _fetch_info("changing directory to /");
21055557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
21155557Sdes	    if (e != -1)
21255557Sdes		_ftp_seterr(e);
21337535Sdes	    return NULL;
21441869Sdes	}
21537535Sdes    }
21637535Sdes
21737573Sdes    /* s now points to file name */
21837573Sdes
21937573Sdes    /* open data socket */
22038394Sdes    if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
22140939Sdes	_fetch_syserr();
22237573Sdes	return NULL;
22337573Sdes    }
22437573Sdes
22537573Sdes    if (pasv) {
22637573Sdes	u_char addr[6];
22737573Sdes	char *ln, *p;
22837573Sdes	int i;
22937573Sdes
23037573Sdes	/* send PASV command */
23155544Sdes	if (verbose)
23255544Sdes	    _fetch_info("setting passive mode");
23355557Sdes	if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
23437573Sdes	    goto ouch;
23537573Sdes
23655544Sdes	/*
23755544Sdes	 * Find address and port number. The reply to the PASV command
23855544Sdes         * is IMHO the one and only weak point in the FTP protocol.
23955544Sdes	 */
24055557Sdes	ln = last_reply;
24137573Sdes	for (p = ln + 3; !isdigit(*p); p++)
24237573Sdes	    /* nothing */ ;
24337573Sdes	for (p--, i = 0; i < 6; i++) {
24437573Sdes	    p++; /* skip the comma */
24537573Sdes	    addr[i] = strtol(p, &p, 10);
24637573Sdes	}
24737573Sdes
24860188Sdes	/* seek to required offset */
24960188Sdes	if (offset)
25060188Sdes	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
25160188Sdes		goto sysouch;
25260188Sdes
25337573Sdes	/* construct sockaddr for data socket */
25460188Sdes	l = sizeof sin;
25555557Sdes	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
25637573Sdes	    goto sysouch;
25737573Sdes	bcopy(addr, (char *)&sin.sin_addr, 4);
25837573Sdes	bcopy(addr + 4, (char *)&sin.sin_port, 2);
25937573Sdes
26037573Sdes	/* connect to data port */
26155544Sdes	if (verbose)
26255544Sdes	    _fetch_info("opening data connection");
26360188Sdes	if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1)
26437573Sdes	    goto sysouch;
26560188Sdes
26637573Sdes	/* make the server initiate the transfer */
26755544Sdes	if (verbose)
26855544Sdes	    _fetch_info("initiating transfer");
26955557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
27041869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
27137573Sdes	    goto ouch;
27237573Sdes
27337573Sdes    } else {
27437573Sdes	u_int32_t a;
27537573Sdes	u_short p;
27655544Sdes	int arg, d;
27737573Sdes
27837573Sdes	/* find our own address, bind, and listen */
27960188Sdes	l = sizeof sin;
28055557Sdes	if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
28137573Sdes	    goto sysouch;
28237573Sdes	sin.sin_port = 0;
28355544Sdes	arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
28455544Sdes	if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
28560188Sdes		       (char *)&arg, sizeof arg) == -1)
28655544Sdes	    goto sysouch;
28755544Sdes	if (verbose)
28855544Sdes	    _fetch_info("binding data socket");
28938394Sdes	if (bind(sd, (struct sockaddr *)&sin, l) == -1)
29037573Sdes	    goto sysouch;
29138394Sdes	if (listen(sd, 1) == -1)
29237573Sdes	    goto sysouch;
29337573Sdes
29437573Sdes	/* find what port we're on and tell the server */
29538394Sdes	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
29637573Sdes	    goto sysouch;
29737573Sdes	a = ntohl(sin.sin_addr.s_addr);
29837573Sdes	p = ntohs(sin.sin_port);
29955557Sdes	e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
30041869Sdes		     (a >> 24) & 0xff, (a >> 16) & 0xff,
30141869Sdes		     (a >> 8) & 0xff, a & 0xff,
30241869Sdes		     (p >> 8) & 0xff, p & 0xff);
30341869Sdes	if (e != FTP_OK)
30437573Sdes	    goto ouch;
30537573Sdes
30637573Sdes	/* make the server initiate the transfer */
30755544Sdes	if (verbose)
30855544Sdes	    _fetch_info("initiating transfer");
30955557Sdes	e = _ftp_cmd(cd, "%s %s", oper, s);
31041869Sdes	if (e != FTP_OPEN_DATA_CONNECTION)
31137573Sdes	    goto ouch;
31237573Sdes
31337573Sdes	/* accept the incoming connection and go to town */
31438394Sdes	if ((d = accept(sd, NULL, NULL)) == -1)
31537573Sdes	    goto sysouch;
31637573Sdes	close(sd);
31737573Sdes	sd = d;
31837573Sdes    }
31937573Sdes
32037608Sdes    if ((df = fdopen(sd, mode)) == NULL)
32137573Sdes	goto sysouch;
32237573Sdes    return df;
32337573Sdes
32437573Sdessysouch:
32540939Sdes    _fetch_syserr();
32641869Sdes    close(sd);
32741869Sdes    return NULL;
32841869Sdes
32937573Sdesouch:
33055557Sdes    if (e != -1)
33155557Sdes	_ftp_seterr(e);
33237573Sdes    close(sd);
33337535Sdes    return NULL;
33437535Sdes}
33537535Sdes
33637571Sdes/*
33737571Sdes * Log on to FTP server
33837535Sdes */
33955557Sdesstatic int
34055544Sdes_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
34137571Sdes{
34260188Sdes    int cd, e, pp = 0, direct, verbose;
34337608Sdes    char *p, *q;
34437571Sdes
34555544Sdes    direct = (flags && strchr(flags, 'd'));
34655544Sdes    verbose = (flags && strchr(flags, 'v'));
34755544Sdes
34837608Sdes    /* check for proxy */
34955544Sdes    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
35037608Sdes	if ((q = strchr(p, ':')) != NULL) {
35160188Sdes	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
35260188Sdes		/* XXX we should emit some kind of warning */
35360188Sdes	    }
35437608Sdes	    pp = atoi(q+1);
35560188Sdes	    if (pp < 1 || pp > 65535) {
35660188Sdes		/* XXX we should emit some kind of warning */
35760188Sdes	    }
35837608Sdes	}
35960188Sdes	if (!pp) {
36060188Sdes	    struct servent *se;
36160188Sdes
36260188Sdes	    if ((se = getservbyname("ftp", "tcp")) != NULL)
36360188Sdes		pp = ntohs(se->s_port);
36460188Sdes	    else
36560188Sdes		pp = FTP_DEFAULT_PORT;
36660188Sdes	}
36737608Sdes	if (q)
36837608Sdes	    *q = 0;
36955557Sdes	cd = _fetch_connect(p, pp, verbose);
37037608Sdes	if (q)
37137608Sdes	    *q = ':';
37237608Sdes    } else {
37337608Sdes	/* no proxy, go straight to target */
37455557Sdes	cd = _fetch_connect(host, port, verbose);
37555544Sdes	p = NULL;
37637608Sdes    }
37737608Sdes
37837608Sdes    /* check connection */
37955557Sdes    if (cd == -1) {
38040939Sdes	_fetch_syserr();
38137571Sdes	return NULL;
38237571Sdes    }
38337608Sdes
38437571Sdes    /* expect welcome message */
38555557Sdes    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
38637571Sdes	goto fouch;
38737571Sdes
38837571Sdes    /* send user name and password */
38937608Sdes    if (!user || !*user)
39037608Sdes	user = FTP_ANONYMOUS_USER;
39155557Sdes    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
39255557Sdes	  : _ftp_cmd(cd, "USER %s", user);
39337608Sdes
39437608Sdes    /* did the server request a password? */
39537608Sdes    if (e == FTP_NEED_PASSWORD) {
39637608Sdes	if (!pwd || !*pwd)
39737608Sdes	    pwd = FTP_ANONYMOUS_PASSWORD;
39855557Sdes	e = _ftp_cmd(cd, "PASS %s", pwd);
39937608Sdes    }
40037608Sdes
40137608Sdes    /* did the server request an account? */
40241869Sdes    if (e == FTP_NEED_ACCOUNT)
40341863Sdes	goto fouch;
40437608Sdes
40537608Sdes    /* we should be done by now */
40641869Sdes    if (e != FTP_LOGGED_IN)
40737571Sdes	goto fouch;
40837571Sdes
40937571Sdes    /* might as well select mode and type at once */
41037571Sdes#ifdef FTP_FORCE_STREAM_MODE
41155557Sdes    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
41241869Sdes	goto fouch;
41337571Sdes#endif
41455557Sdes    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
41541869Sdes	goto fouch;
41637571Sdes
41737571Sdes    /* done */
41855557Sdes    return cd;
41937571Sdes
42037571Sdesfouch:
42155557Sdes    if (e != -1)
42255557Sdes	_ftp_seterr(e);
42355557Sdes    close(cd);
42437571Sdes    return NULL;
42537571Sdes}
42637571Sdes
42737571Sdes/*
42837571Sdes * Disconnect from server
42937571Sdes */
43037571Sdesstatic void
43155557Sdes_ftp_disconnect(int cd)
43237571Sdes{
43355557Sdes    (void)_ftp_cmd(cd, "QUIT");
43455557Sdes    close(cd);
43537571Sdes}
43637571Sdes
43737571Sdes/*
43837571Sdes * Check if we're already connected
43937571Sdes */
44037571Sdesstatic int
44140975Sdes_ftp_isconnected(struct url *url)
44237571Sdes{
44337571Sdes    return (cached_socket
44437571Sdes	    && (strcmp(url->host, cached_host.host) == 0)
44537571Sdes	    && (strcmp(url->user, cached_host.user) == 0)
44637571Sdes	    && (strcmp(url->pwd, cached_host.pwd) == 0)
44737571Sdes	    && (url->port == cached_host.port));
44837571Sdes}
44937571Sdes
45037608Sdes/*
45141869Sdes * Check the cache, reconnect if no luck
45237608Sdes */
45355557Sdesstatic int
45441869Sdes_ftp_cached_connect(struct url *url, char *flags)
45537535Sdes{
45655557Sdes    int e, cd;
45737535Sdes
45855557Sdes    cd = -1;
45941869Sdes
46037571Sdes    /* set default port */
46160188Sdes    if (!url->port) {
46260188Sdes	struct servent *se;
46360188Sdes
46460188Sdes	if ((se = getservbyname("ftp", "tcp")) != NULL)
46560188Sdes	    url->port = ntohs(se->s_port);
46660188Sdes	else
46760188Sdes	    url->port = FTP_DEFAULT_PORT;
46860188Sdes    }
46937535Sdes
47041863Sdes    /* try to use previously cached connection */
47155557Sdes    if (_ftp_isconnected(url)) {
47255557Sdes	e = _ftp_cmd(cached_socket, "NOOP");
47355557Sdes	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
47455557Sdes	    cd = cached_socket;
47555557Sdes    }
47637571Sdes
47737571Sdes    /* connect to server */
47855557Sdes    if (cd == -1) {
47955557Sdes	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
48055557Sdes	if (cd == -1)
48155557Sdes	    return -1;
48237571Sdes	if (cached_socket)
48337571Sdes	    _ftp_disconnect(cached_socket);
48455557Sdes	cached_socket = cd;
48560188Sdes	memcpy(&cached_host, url, sizeof *url);
48637535Sdes    }
48737571Sdes
48855557Sdes    return cd;
48937535Sdes}
49037535Sdes
49137571Sdes/*
49241869Sdes * Get file
49337571Sdes */
49437535SdesFILE *
49540975SdesfetchGetFTP(struct url *url, char *flags)
49637608Sdes{
49755557Sdes    int cd;
49855557Sdes
49941869Sdes    /* connect to server */
50055557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
50141869Sdes	return NULL;
50241869Sdes
50341869Sdes    /* initiate the transfer */
50460188Sdes    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
50537608Sdes}
50637608Sdes
50741869Sdes/*
50841869Sdes * Put file
50941869Sdes */
51037608SdesFILE *
51140975SdesfetchPutFTP(struct url *url, char *flags)
51237535Sdes{
51355557Sdes    int cd;
51441869Sdes
51541869Sdes    /* connect to server */
51655557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
51741869Sdes	return NULL;
51841869Sdes
51941869Sdes    /* initiate the transfer */
52055557Sdes    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
52160188Sdes			 url->doc, "w", url->offset, flags);
52237535Sdes}
52340975Sdes
52441869Sdes/*
52541869Sdes * Get file stats
52641869Sdes */
52740975Sdesint
52840975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
52940975Sdes{
53041869Sdes    char *ln, *s;
53141869Sdes    struct tm tm;
53241869Sdes    time_t t;
53355557Sdes    int e, cd;
53441869Sdes
53541869Sdes    /* connect to server */
53655557Sdes    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
53741869Sdes	return -1;
53841869Sdes
53941869Sdes    /* change directory */
54041869Sdes    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
54141869Sdes	*s = 0;
54255557Sdes	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
54341869Sdes	    *s = '/';
54441869Sdes	    goto ouch;
54541869Sdes	}
54641869Sdes	*s++ = '/';
54741869Sdes    } else {
54855557Sdes	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
54941869Sdes	    goto ouch;
55041869Sdes    }
55141869Sdes
55241869Sdes    /* s now points to file name */
55341869Sdes
55455557Sdes    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
55541869Sdes	goto ouch;
55655557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
55741869Sdes	/* nothing */ ;
55841869Sdes    for (us->size = 0; *ln && isdigit(*ln); ln++)
55941869Sdes	us->size = us->size * 10 + *ln - '0';
56041869Sdes    if (*ln && !isspace(*ln)) {
56155557Sdes	_ftp_seterr(999);
56241869Sdes	return -1;
56341869Sdes    }
56460383Sdes    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
56541869Sdes
56655557Sdes    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
56741869Sdes	goto ouch;
56855557Sdes    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
56941869Sdes	/* nothing */ ;
57060383Sdes    e = 999;
57160383Sdes    switch (strspn(ln, "0123456789")) {
57260383Sdes    case 14:
57360383Sdes	break;
57460383Sdes    case 15:
57560383Sdes	ln++;
57660383Sdes	ln[0] = '2';
57760383Sdes	ln[1] = '0';
57860383Sdes	break;
57960383Sdes    default:
58060383Sdes	goto ouch;
58160383Sdes    }
58260383Sdes    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
58360383Sdes	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
58460383Sdes	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
58560383Sdes	goto ouch;
58641869Sdes    tm.tm_mon--;
58741869Sdes    tm.tm_year -= 1900;
58841869Sdes    tm.tm_isdst = -1;
58960383Sdes    t = timegm(&tm);
59056635Sdes    if (t == (time_t)-1)
59156635Sdes	t = time(NULL);
59256635Sdes    us->mtime = t;
59356635Sdes    us->atime = t;
59460383Sdes    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
59560383Sdes		  "%02d:%02d:%02d\033[m]\n",
59660383Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
59760383Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
59841869Sdes    return 0;
59941869Sdes
60041869Sdesouch:
60155557Sdes    if (e != -1)
60255557Sdes	_ftp_seterr(e);
60340975Sdes    return -1;
60440975Sdes}
60541989Sdes
60641989Sdes/*
60741989Sdes * List a directory
60841989Sdes */
60941989Sdesextern void warnx(char *, ...);
61041989Sdesstruct url_ent *
61141989SdesfetchListFTP(struct url *url, char *flags)
61241989Sdes{
61341989Sdes    warnx("fetchListFTP(): not implemented");
61441989Sdes    return NULL;
61541989Sdes}
616