ftp.c revision 62982
1/*-
2 * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer
10 *    in this position and unchanged.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * $FreeBSD: head/lib/libfetch/ftp.c 62982 2000-07-11 23:50:22Z des $
29 */
30
31/*
32 * Portions of this code were taken from or based on ftpio.c:
33 *
34 * ----------------------------------------------------------------------------
35 * "THE BEER-WARE LICENSE" (Revision 42):
36 * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
37 * can do whatever you want with this stuff. If we meet some day, and you think
38 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
39 * ----------------------------------------------------------------------------
40 *
41 * Major Changelog:
42 *
43 * Dag-Erling Co�dan Sm�rgrav
44 * 9 Jun 1998
45 *
46 * Incorporated into libfetch
47 *
48 * Jordan K. Hubbard
49 * 17 Jan 1996
50 *
51 * Turned inside out. Now returns xfers as new file ids, not as a special
52 * `state' of FTP_t
53 *
54 * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
55 *
56 */
57
58#include <sys/param.h>
59#include <sys/socket.h>
60#include <netinet/in.h>
61
62#include <ctype.h>
63#include <errno.h>
64#include <netdb.h>
65#include <stdarg.h>
66#include <stdio.h>
67#include <stdlib.h>
68#include <string.h>
69#include <time.h>
70#include <unistd.h>
71
72#include "fetch.h"
73#include "common.h"
74#include "ftperr.h"
75
76#define FTP_ANONYMOUS_USER	"ftp"
77#define FTP_ANONYMOUS_PASSWORD	"ftp"
78#define FTP_DEFAULT_PORT 21
79
80#define FTP_OPEN_DATA_CONNECTION	150
81#define FTP_OK				200
82#define FTP_FILE_STATUS			213
83#define FTP_SERVICE_READY		220
84#define FTP_PASSIVE_MODE		227
85#define FTP_LPASSIVE_MODE		228
86#define FTP_EPASSIVE_MODE		229
87#define FTP_LOGGED_IN			230
88#define FTP_FILE_ACTION_OK		250
89#define FTP_NEED_PASSWORD		331
90#define FTP_NEED_ACCOUNT		332
91#define FTP_FILE_OK			350
92#define FTP_SYNTAX_ERROR		500
93
94static struct url cached_host;
95static int cached_socket;
96
97static char *last_reply;
98static size_t lr_size, lr_length;
99static int last_code;
100
101#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
102			 && isdigit(foo[2]) \
103                         && (foo[3] == ' ' || foo[3] == '\0'))
104#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105			&& isdigit(foo[2]) && foo[3] == '-')
106
107/* translate IPv4 mapped IPv6 address to IPv4 address */
108static void
109unmappedaddr(struct sockaddr_in6 *sin6)
110{
111    struct sockaddr_in *sin4;
112    u_int32_t addr;
113    int port;
114
115    if (sin6->sin6_family != AF_INET6 ||
116	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
117	return;
118    sin4 = (struct sockaddr_in *)sin6;
119    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
120    port = sin6->sin6_port;
121    memset(sin4, 0, sizeof(struct sockaddr_in));
122    sin4->sin_addr.s_addr = addr;
123    sin4->sin_port = port;
124    sin4->sin_family = AF_INET;
125    sin4->sin_len = sizeof(struct sockaddr_in);
126}
127
128/*
129 * Get server response
130 */
131static int
132_ftp_chkerr(int cd)
133{
134    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
135	_fetch_syserr();
136	return -1;
137    }
138    if (isftpinfo(last_reply)) {
139	while (!isftpreply(last_reply)) {
140	    if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
141		_fetch_syserr();
142		return -1;
143	    }
144	}
145    }
146
147    while (lr_length && isspace(last_reply[lr_length-1]))
148	lr_length--;
149    last_reply[lr_length] = 0;
150
151    if (!isftpreply(last_reply)) {
152	_ftp_seterr(999);
153	return -1;
154    }
155
156    last_code = (last_reply[0] - '0') * 100
157	+ (last_reply[1] - '0') * 10
158	+ (last_reply[2] - '0');
159
160    return last_code;
161}
162
163/*
164 * Send a command and check reply
165 */
166static int
167_ftp_cmd(int cd, char *fmt, ...)
168{
169    va_list ap;
170    size_t len;
171    char *msg;
172    int r;
173
174    va_start(ap, fmt);
175    len = vasprintf(&msg, fmt, ap);
176    va_end(ap);
177
178    if (msg == NULL) {
179	errno = ENOMEM;
180	_fetch_syserr();
181	return -1;
182    }
183
184    r = _fetch_putln(cd, msg, len);
185    free(msg);
186
187    if (r == -1) {
188	_fetch_syserr();
189	return -1;
190    }
191
192    return _ftp_chkerr(cd);
193}
194
195/*
196 * Transfer file
197 */
198static FILE *
199_ftp_transfer(int cd, char *oper, char *file,
200	      char *mode, off_t offset, char *flags)
201{
202    struct sockaddr_storage sin;
203    struct sockaddr_in6 *sin6;
204    struct sockaddr_in *sin4;
205    int pasv, high, verbose;
206    int e, sd = -1;
207    socklen_t l;
208    char *s;
209    FILE *df;
210
211    /* check flags */
212    pasv = (flags && strchr(flags, 'p'));
213    high = (flags && strchr(flags, 'h'));
214    verbose = (flags && strchr(flags, 'v'));
215
216    /* passive mode */
217    if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL)
218	pasv = (strncasecmp(s, "no", 2) != 0);
219
220    /* change directory */
221    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
222	*s = 0;
223	if (verbose)
224	    _fetch_info("changing directory to %s", file);
225	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
226	    *s = '/';
227	    if (e != -1)
228		_ftp_seterr(e);
229	    return NULL;
230	}
231	*s++ = '/';
232    } else {
233	if (verbose)
234	    _fetch_info("changing directory to /");
235	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
236	    if (e != -1)
237		_ftp_seterr(e);
238	    return NULL;
239	}
240    }
241
242    /* s now points to file name */
243
244    /* find our own address, bind, and listen */
245    l = sizeof sin;
246    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
247	goto sysouch;
248    if (sin.ss_family == AF_INET6)
249	unmappedaddr((struct sockaddr_in6 *)&sin);
250
251    /* open data socket */
252    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
253	_fetch_syserr();
254	return NULL;
255    }
256
257    if (pasv) {
258	u_char addr[64];
259	char *ln, *p;
260	int i;
261	int port;
262
263	/* send PASV command */
264	if (verbose)
265	    _fetch_info("setting passive mode");
266	switch (sin.ss_family) {
267	case AF_INET:
268	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
269		goto ouch;
270	    break;
271	case AF_INET6:
272	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
273		if (e == -1)
274		    goto ouch;
275		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
276		    goto ouch;
277	    }
278	    break;
279	default:
280	    e = 999;		/* XXX: error code should be prepared */
281	    goto ouch;
282	}
283
284	/*
285	 * Find address and port number. The reply to the PASV command
286         * is IMHO the one and only weak point in the FTP protocol.
287	 */
288	ln = last_reply;
289      	switch (e) {
290	case FTP_PASSIVE_MODE:
291	case FTP_LPASSIVE_MODE:
292	    for (p = ln + 3; *p && !isdigit(*p); p++)
293		/* nothing */ ;
294	    if (!*p) {
295		e = 999;
296		goto ouch;
297	    }
298	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
299	    for (i = 0; *p && i < l; i++, p++)
300		addr[i] = strtol(p, &p, 10);
301	    if (i < l) {
302		e = 999;
303		goto ouch;
304	    }
305	    break;
306	case FTP_EPASSIVE_MODE:
307	    for (p = ln + 3; *p && *p != '('; p++)
308		/* nothing */ ;
309	    if (!*p) {
310		e = 999;
311		goto ouch;
312	    }
313	    ++p;
314	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
315		       &port, &addr[3]) != 5 ||
316		addr[0] != addr[1] ||
317		addr[0] != addr[2] || addr[0] != addr[3]) {
318		e = 999;
319		goto ouch;
320	    }
321	    break;
322	}
323
324	/* seek to required offset */
325	if (offset)
326	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
327		goto sysouch;
328
329	/* construct sockaddr for data socket */
330	l = sizeof sin;
331	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
332	    goto sysouch;
333	if (sin.ss_family == AF_INET6)
334	    unmappedaddr((struct sockaddr_in6 *)&sin);
335	switch (sin.ss_family) {
336	case AF_INET6:
337	    sin6 = (struct sockaddr_in6 *)&sin;
338	    if (e == FTP_EPASSIVE_MODE)
339		sin6->sin6_port = htons(port);
340	    else {
341		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
342		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
343	    }
344	    break;
345	case AF_INET:
346	    sin4 = (struct sockaddr_in *)&sin;
347	    if (e == FTP_EPASSIVE_MODE)
348		sin4->sin_port = htons(port);
349	    else {
350		bcopy(addr, (char *)&sin4->sin_addr, 4);
351		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
352	    }
353	    break;
354	default:
355	    e = 999;		/* XXX: error code should be prepared */
356	    break;
357	}
358
359	/* connect to data port */
360	if (verbose)
361	    _fetch_info("opening data connection");
362	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
363	    goto sysouch;
364
365	/* make the server initiate the transfer */
366	if (verbose)
367	    _fetch_info("initiating transfer");
368	e = _ftp_cmd(cd, "%s %s", oper, s);
369	if (e != FTP_OPEN_DATA_CONNECTION)
370	    goto ouch;
371
372    } else {
373	u_int32_t a;
374	u_short p;
375	int arg, d;
376	char *ap;
377	char hname[INET6_ADDRSTRLEN];
378
379	switch (sin.ss_family) {
380	case AF_INET6:
381	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
382#ifdef IPV6_PORTRANGE
383	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
384	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
385			   (char *)&arg, sizeof(arg)) == -1)
386		goto sysouch;
387#endif
388	    break;
389	case AF_INET:
390	    ((struct sockaddr_in *)&sin)->sin_port = 0;
391	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
392	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
393			   (char *)&arg, sizeof arg) == -1)
394		goto sysouch;
395	    break;
396	}
397	if (verbose)
398	    _fetch_info("binding data socket");
399	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
400	    goto sysouch;
401	if (listen(sd, 1) == -1)
402	    goto sysouch;
403
404	/* find what port we're on and tell the server */
405	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
406	    goto sysouch;
407	switch (sin.ss_family) {
408	case AF_INET:
409	    sin4 = (struct sockaddr_in *)&sin;
410	    a = ntohl(sin4->sin_addr.s_addr);
411	    p = ntohs(sin4->sin_port);
412	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
413			 (a >> 24) & 0xff, (a >> 16) & 0xff,
414			 (a >> 8) & 0xff, a & 0xff,
415			 (p >> 8) & 0xff, p & 0xff);
416	    break;
417	case AF_INET6:
418#define UC(b)	(((int)b)&0xff)
419	    e = -1;
420	    sin6 = (struct sockaddr_in6 *)&sin;
421	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
422			    hname, sizeof(hname),
423			    NULL, 0, NI_NUMERICHOST) == 0) {
424		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
425			     htons(sin6->sin6_port));
426		if (e == -1)
427		    goto ouch;
428	    }
429	    if (e != FTP_OK) {
430		ap = (char *)&sin6->sin6_addr;
431		e = _ftp_cmd(cd,
432     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
433			     6, 16,
434			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
435			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
436			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
437			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
438			     2,
439			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
440			     ntohs(sin6->sin6_port)        & 0xff);
441	    }
442	    break;
443	default:
444	    e = 999;		/* XXX: error code should be prepared */
445	    goto ouch;
446	}
447	if (e != FTP_OK)
448	    goto ouch;
449
450	/* seek to required offset */
451	if (offset)
452	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
453		goto sysouch;
454
455	/* make the server initiate the transfer */
456	if (verbose)
457	    _fetch_info("initiating transfer");
458	e = _ftp_cmd(cd, "%s %s", oper, s);
459	if (e != FTP_OPEN_DATA_CONNECTION)
460	    goto ouch;
461
462	/* accept the incoming connection and go to town */
463	if ((d = accept(sd, NULL, NULL)) == -1)
464	    goto sysouch;
465	close(sd);
466	sd = d;
467    }
468
469    if ((df = fdopen(sd, mode)) == NULL)
470	goto sysouch;
471    return df;
472
473sysouch:
474    _fetch_syserr();
475    if (sd >= 0)
476	close(sd);
477    return NULL;
478
479ouch:
480    if (e != -1)
481	_ftp_seterr(e);
482    if (sd >= 0)
483	close(sd);
484    return NULL;
485}
486
487/*
488 * Log on to FTP server
489 */
490static int
491_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
492{
493    int cd, e, pp = 0, direct, verbose;
494#ifdef INET6
495    int af = AF_UNSPEC;
496#else
497    int af = AF_INET;
498#endif
499    char *p, *q;
500    const char *logname;
501    char localhost[MAXHOSTNAMELEN];
502    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
503
504    direct = (flags && strchr(flags, 'd'));
505    verbose = (flags && strchr(flags, 'v'));
506    if ((flags && strchr(flags, '4')))
507	af = AF_INET;
508    else if ((flags && strchr(flags, '6')))
509	af = AF_INET6;
510
511    /* check for proxy */
512    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
513	char c = 0;
514
515#ifdef INET6
516	if (*p != '[' || (q = strchr(p + 1, ']')) == NULL ||
517	    (*++q != '\0' && *q != ':'))
518#endif
519	    q = strchr(p, ':');
520	if (q != NULL && *q == ':') {
521	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
522		/* XXX we should emit some kind of warning */
523	    }
524	    pp = atoi(q+1);
525	    if (pp < 1 || pp > 65535) {
526		/* XXX we should emit some kind of warning */
527	    }
528	}
529	if (!pp) {
530	    struct servent *se;
531
532	    if ((se = getservbyname("ftp", "tcp")) != NULL)
533		pp = ntohs(se->s_port);
534	    else
535		pp = FTP_DEFAULT_PORT;
536	}
537	if (q) {
538#ifdef INET6
539	    if (q > p && *p == '[' && *(q - 1) == ']') {
540		p++;
541		q--;
542	    }
543#endif
544	    c = *q;
545	    *q = 0;
546	}
547	cd = _fetch_connect(p, pp, af, verbose);
548	if (q)
549	    *q = c;
550    } else {
551	/* no proxy, go straight to target */
552	cd = _fetch_connect(host, port, af, verbose);
553	p = NULL;
554    }
555
556    /* check connection */
557    if (cd == -1) {
558	_fetch_syserr();
559	return NULL;
560    }
561
562    /* expect welcome message */
563    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
564	goto fouch;
565
566    /* send user name and password */
567    if (!user || !*user)
568	user = FTP_ANONYMOUS_USER;
569    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
570	  : _ftp_cmd(cd, "USER %s", user);
571
572    /* did the server request a password? */
573    if (e == FTP_NEED_PASSWORD) {
574	if (!pwd || !*pwd)
575	    pwd = getenv("FTP_PASSWORD");
576	if (!pwd || !*pwd) {
577	    if ((logname = getlogin()) == 0)
578		logname = FTP_ANONYMOUS_PASSWORD;
579	    gethostname(localhost, sizeof localhost);
580	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
581	    pwd = pbuf;
582	}
583	e = _ftp_cmd(cd, "PASS %s", pwd);
584    }
585
586    /* did the server request an account? */
587    if (e == FTP_NEED_ACCOUNT)
588	goto fouch;
589
590    /* we should be done by now */
591    if (e != FTP_LOGGED_IN)
592	goto fouch;
593
594    /* might as well select mode and type at once */
595#ifdef FTP_FORCE_STREAM_MODE
596    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
597	goto fouch;
598#endif
599    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
600	goto fouch;
601
602    /* done */
603    return cd;
604
605fouch:
606    if (e != -1)
607	_ftp_seterr(e);
608    close(cd);
609    return NULL;
610}
611
612/*
613 * Disconnect from server
614 */
615static void
616_ftp_disconnect(int cd)
617{
618    (void)_ftp_cmd(cd, "QUIT");
619    close(cd);
620}
621
622/*
623 * Check if we're already connected
624 */
625static int
626_ftp_isconnected(struct url *url)
627{
628    return (cached_socket
629	    && (strcmp(url->host, cached_host.host) == 0)
630	    && (strcmp(url->user, cached_host.user) == 0)
631	    && (strcmp(url->pwd, cached_host.pwd) == 0)
632	    && (url->port == cached_host.port));
633}
634
635/*
636 * Check the cache, reconnect if no luck
637 */
638static int
639_ftp_cached_connect(struct url *url, char *flags)
640{
641    int e, cd;
642
643    cd = -1;
644
645    /* set default port */
646    if (!url->port) {
647	struct servent *se;
648
649	if ((se = getservbyname("ftp", "tcp")) != NULL)
650	    url->port = ntohs(se->s_port);
651	else
652	    url->port = FTP_DEFAULT_PORT;
653    }
654
655    /* try to use previously cached connection */
656    if (_ftp_isconnected(url)) {
657	e = _ftp_cmd(cached_socket, "NOOP");
658	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
659	    cd = cached_socket;
660    }
661
662    /* connect to server */
663    if (cd == -1) {
664	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
665	if (cd == -1)
666	    return -1;
667	if (cached_socket)
668	    _ftp_disconnect(cached_socket);
669	cached_socket = cd;
670	memcpy(&cached_host, url, sizeof *url);
671    }
672
673    return cd;
674}
675
676/*
677 * Get file
678 */
679FILE *
680fetchGetFTP(struct url *url, char *flags)
681{
682    int cd;
683
684    /* connect to server */
685    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
686	return NULL;
687
688    /* initiate the transfer */
689    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
690}
691
692/*
693 * Put file
694 */
695FILE *
696fetchPutFTP(struct url *url, char *flags)
697{
698    int cd;
699
700    /* connect to server */
701    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
702	return NULL;
703
704    /* initiate the transfer */
705    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
706			 url->doc, "w", url->offset, flags);
707}
708
709/*
710 * Get file stats
711 */
712int
713fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
714{
715    char *ln, *s;
716    struct tm tm;
717    time_t t;
718    int e, cd;
719
720    us->size = -1;
721    us->atime = us->mtime = 0;
722
723    /* connect to server */
724    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
725	return -1;
726
727    /* change directory */
728    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
729	*s = 0;
730	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
731	    *s = '/';
732	    goto ouch;
733	}
734	*s++ = '/';
735    } else {
736	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
737	    goto ouch;
738    }
739
740    /* s now points to file name */
741
742    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
743	goto ouch;
744    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
745	/* nothing */ ;
746    for (us->size = 0; *ln && isdigit(*ln); ln++)
747	us->size = us->size * 10 + *ln - '0';
748    if (*ln && !isspace(*ln)) {
749	_ftp_seterr(999);
750	return -1;
751    }
752    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
753
754    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
755	goto ouch;
756    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
757	/* nothing */ ;
758    e = 999;
759    switch (strspn(ln, "0123456789")) {
760    case 14:
761	break;
762    case 15:
763	ln++;
764	ln[0] = '2';
765	ln[1] = '0';
766	break;
767    default:
768	goto ouch;
769    }
770    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
771	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
772	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
773	goto ouch;
774    tm.tm_mon--;
775    tm.tm_year -= 1900;
776    tm.tm_isdst = -1;
777    t = timegm(&tm);
778    if (t == (time_t)-1)
779	t = time(NULL);
780    us->mtime = t;
781    us->atime = t;
782    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
783		  "%02d:%02d:%02d\033[m]\n",
784		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
785		  tm.tm_hour, tm.tm_min, tm.tm_sec));
786    return 0;
787
788ouch:
789    if (e != -1)
790	_ftp_seterr(e);
791    return -1;
792}
793
794/*
795 * List a directory
796 */
797extern void warnx(char *, ...);
798struct url_ent *
799fetchListFTP(struct url *url, char *flags)
800{
801    warnx("fetchListFTP(): not implemented");
802    return NULL;
803}
804