ftp.c revision 61838
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 61838 2000-06-20 05:32:41Z wes $
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 <sys/uio.h>
61#include <netinet/in.h>
62
63#include <ctype.h>
64#include <errno.h>
65#include <netdb.h>
66#include <stdarg.h>
67#include <stdio.h>
68#include <stdlib.h>
69#include <string.h>
70#include <time.h>
71#include <unistd.h>
72
73#include "fetch.h"
74#include "common.h"
75#include "ftperr.h"
76
77#define FTP_ANONYMOUS_USER	"ftp"
78#define FTP_ANONYMOUS_PASSWORD	"ftp"
79#define FTP_DEFAULT_PORT 21
80
81#define FTP_OPEN_DATA_CONNECTION	150
82#define FTP_OK				200
83#define FTP_FILE_STATUS			213
84#define FTP_SERVICE_READY		220
85#define FTP_SUCCESSFUL			226
86#define FTP_PASSIVE_MODE		227
87#define FTP_LPASSIVE_MODE		228
88#define FTP_EPASSIVE_MODE		229
89#define FTP_LOGGED_IN			230
90#define FTP_FILE_ACTION_OK		250
91#define FTP_NEED_PASSWORD		331
92#define FTP_NEED_ACCOUNT		332
93#define FTP_FILE_OK			350
94#define FTP_NO_DATA_CONNECTION		425
95#define FTP_SYNTAX_ERROR		500
96
97static char ENDL[2] = "\r\n";
98
99static struct url cached_host;
100static int cached_socket;
101
102static char *last_reply;
103static size_t lr_size, lr_length;
104static int last_code;
105
106#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
107			 && isdigit(foo[2]) \
108                         && (foo[3] == ' ' || foo[3] == '\0'))
109#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
110			&& isdigit(foo[2]) && foo[3] == '-')
111
112/* translate IPv4 mapped IPv6 address to IPv4 address */
113static void
114unmappedaddr(struct sockaddr_in6 *sin6)
115{
116    struct sockaddr_in *sin4;
117    u_int32_t addr;
118    int port;
119
120    if (sin6->sin6_family != AF_INET6 ||
121	!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
122	return;
123    sin4 = (struct sockaddr_in *)sin6;
124    addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
125    port = sin6->sin6_port;
126    memset(sin4, 0, sizeof(struct sockaddr_in));
127    sin4->sin_addr.s_addr = addr;
128    sin4->sin_port = port;
129    sin4->sin_family = AF_INET;
130    sin4->sin_len = sizeof(struct sockaddr_in);
131}
132
133/*
134 * Get server response
135 */
136static int
137_ftp_chkerr(int cd)
138{
139    do {
140	if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
141	    _fetch_syserr();
142	    return -1;
143	}
144#ifndef NDEBUG
145	_fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
146#endif
147    } while (isftpinfo(last_reply));
148
149    while (lr_length && isspace(last_reply[lr_length-1]))
150	lr_length--;
151    last_reply[lr_length] = 0;
152
153    if (!isftpreply(last_reply)) {
154	_ftp_seterr(999);
155	return -1;
156    }
157
158    last_code = (last_reply[0] - '0') * 100
159	+ (last_reply[1] - '0') * 10
160	+ (last_reply[2] - '0');
161
162    return last_code;
163}
164
165/*
166 * Send a command and check reply
167 */
168static int
169_ftp_cmd(int cd, char *fmt, ...)
170{
171    va_list ap;
172    struct iovec iov[2];
173    char *msg;
174    int r;
175
176    va_start(ap, fmt);
177    vasprintf(&msg, fmt, ap);
178    va_end(ap);
179
180    if (msg == NULL) {
181	errno = ENOMEM;
182	_fetch_syserr();
183	return -1;
184    }
185#ifndef NDEBUG
186    _fetch_info("sending '%s'", msg);
187#endif
188    iov[0].iov_base = msg;
189    iov[0].iov_len = strlen(msg);
190    iov[1].iov_base = ENDL;
191    iov[1].iov_len = sizeof ENDL;
192    r = writev(cd, iov, 2);
193    free(msg);
194    if (r == -1) {
195	_fetch_syserr();
196	return -1;
197    }
198
199    return _ftp_chkerr(cd);
200}
201
202/*
203 * Transfer file
204 */
205static FILE *
206_ftp_transfer(int cd, char *oper, char *file,
207	      char *mode, off_t offset, char *flags)
208{
209    struct sockaddr_storage sin;
210    struct sockaddr_in6 *sin6;
211    struct sockaddr_in *sin4;
212    int pasv, high, verbose;
213    int e, sd = -1;
214    socklen_t l;
215    char *s;
216    FILE *df;
217
218    /* check flags */
219    pasv = (flags && strchr(flags, 'p'));
220    high = (flags && strchr(flags, 'h'));
221    verbose = (flags && strchr(flags, 'v'));
222
223    /* passive mode */
224    if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL)
225	pasv = (strncasecmp(s, "no", 2) != 0);
226
227    /* change directory */
228    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
229	*s = 0;
230	if (verbose)
231	    _fetch_info("changing directory to %s", file);
232	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
233	    *s = '/';
234	    if (e != -1)
235		_ftp_seterr(e);
236	    return NULL;
237	}
238	*s++ = '/';
239    } else {
240	if (verbose)
241	    _fetch_info("changing directory to /");
242	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
243	    if (e != -1)
244		_ftp_seterr(e);
245	    return NULL;
246	}
247    }
248
249    /* s now points to file name */
250
251    /* find our own address, bind, and listen */
252    l = sizeof sin;
253    if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
254	goto sysouch;
255    if (sin.ss_family == AF_INET6)
256	unmappedaddr((struct sockaddr_in6 *)&sin);
257
258    /* open data socket */
259    if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
260	_fetch_syserr();
261	return NULL;
262    }
263
264    if (pasv) {
265	u_char addr[64];
266	char *ln, *p;
267	int i;
268	int port;
269
270	/* send PASV command */
271	if (verbose)
272	    _fetch_info("setting passive mode");
273	switch (sin.ss_family) {
274	case AF_INET:
275	    if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
276		goto ouch;
277	    break;
278	case AF_INET6:
279	    if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) {
280		if (e == -1)
281		    goto ouch;
282		if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE)
283		    goto ouch;
284	    }
285	    break;
286	default:
287	    e = 999;		/* XXX: error code should be prepared */
288	    goto ouch;
289	}
290
291	/*
292	 * Find address and port number. The reply to the PASV command
293         * is IMHO the one and only weak point in the FTP protocol.
294	 */
295	ln = last_reply;
296	for (p = ln + 3; *p && *p != '('; p++)
297	    /* nothing */ ;
298	if (!*p) {
299	    e = 999;
300	    goto ouch;
301	}
302	p++;
303	switch (e) {
304	case FTP_PASSIVE_MODE:
305	case FTP_LPASSIVE_MODE:
306	    l = (e == FTP_PASSIVE_MODE ? 6 : 21);
307	    for (i = 0; *p && i < l; i++, p++)
308		addr[i] = strtol(p, &p, 10);
309	    if (i < l) {
310		e = 999;
311		goto ouch;
312	    }
313	    break;
314	case FTP_EPASSIVE_MODE:
315	    if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
316		       &port, &addr[3]) != 5 ||
317		addr[0] != addr[1] ||
318		addr[0] != addr[2] || addr[0] != addr[3]) {
319		e = 999;
320		goto ouch;
321	    }
322	    break;
323	}
324
325	/* seek to required offset */
326	if (offset)
327	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
328		goto sysouch;
329
330	/* construct sockaddr for data socket */
331	l = sizeof sin;
332	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
333	    goto sysouch;
334	if (sin.ss_family == AF_INET6)
335	    unmappedaddr((struct sockaddr_in6 *)&sin);
336	switch (sin.ss_family) {
337	case AF_INET6:
338	    sin6 = (struct sockaddr_in6 *)&sin;
339	    if (e == FTP_EPASSIVE_MODE)
340		sin6->sin6_port = htons(port);
341	    else {
342		bcopy(addr + 2, (char *)&sin6->sin6_addr, 16);
343		bcopy(addr + 19, (char *)&sin6->sin6_port, 2);
344	    }
345	    break;
346	case AF_INET:
347	    sin4 = (struct sockaddr_in *)&sin;
348	    if (e == FTP_EPASSIVE_MODE)
349		sin4->sin_port = htons(port);
350	    else {
351		bcopy(addr, (char *)&sin4->sin_addr, 4);
352		bcopy(addr + 4, (char *)&sin4->sin_port, 2);
353	    }
354	    break;
355	default:
356	    e = 999;		/* XXX: error code should be prepared */
357	    break;
358	}
359
360	/* connect to data port */
361	if (verbose)
362	    _fetch_info("opening data connection");
363	if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
364	    goto sysouch;
365
366	/* make the server initiate the transfer */
367	if (s && *s == '\0') {
368	    if (verbose)
369		_fetch_info("initiating %s transfer", oper);
370	    e = _ftp_cmd(cd, oper);
371	} else {
372	    if (verbose)
373		_fetch_info("initiating %s \"%s\" transfer", oper, s);
374	    e = _ftp_cmd(cd, "%s %s", oper, s);
375	}
376	if (e != FTP_OPEN_DATA_CONNECTION)
377	    goto ouch;
378
379    } else {
380	u_int32_t a;
381	u_short p;
382	int arg, d;
383	char *ap;
384	char hname[INET6_ADDRSTRLEN];
385
386	switch (sin.ss_family) {
387	case AF_INET6:
388	    ((struct sockaddr_in6 *)&sin)->sin6_port = 0;
389#ifdef IPV6_PORTRANGE
390	    arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT;
391	    if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
392			   (char *)&arg, sizeof(arg)) == -1)
393		goto sysouch;
394#endif
395	    break;
396	case AF_INET:
397	    ((struct sockaddr_in *)&sin)->sin_port = 0;
398	    arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
399	    if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
400			   (char *)&arg, sizeof arg) == -1)
401		goto sysouch;
402	    break;
403	}
404	if (verbose)
405	    _fetch_info("binding data socket");
406	if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1)
407	    goto sysouch;
408	if (listen(sd, 1) == -1)
409	    goto sysouch;
410
411	/* find what port we're on and tell the server */
412	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
413	    goto sysouch;
414	switch (sin.ss_family) {
415	case AF_INET:
416	    sin4 = (struct sockaddr_in *)&sin;
417	    a = ntohl(sin4->sin_addr.s_addr);
418	    p = ntohs(sin4->sin_port);
419	    e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
420			 (a >> 24) & 0xff, (a >> 16) & 0xff,
421			 (a >> 8) & 0xff, a & 0xff,
422			 (p >> 8) & 0xff, p & 0xff);
423	    break;
424	case AF_INET6:
425#define UC(b)	(((int)b)&0xff)
426	    e = -1;
427	    sin6 = (struct sockaddr_in6 *)&sin;
428	    if (getnameinfo((struct sockaddr *)&sin, sin.ss_len,
429			    hname, sizeof(hname),
430			    NULL, 0, NI_NUMERICHOST) == 0) {
431		e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname,
432			     htons(sin6->sin6_port));
433		if (e == -1)
434		    goto ouch;
435	    }
436	    if (e != FTP_OK) {
437		ap = (char *)&sin6->sin6_addr;
438		e = _ftp_cmd(cd,
439     "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
440			     6, 16,
441			     UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]),
442			     UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]),
443			     UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]),
444			     UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]),
445			     2,
446			     (ntohs(sin6->sin6_port) >> 8) & 0xff,
447			     ntohs(sin6->sin6_port)        & 0xff);
448	    }
449	    break;
450	default:
451	    e = 999;		/* XXX: error code should be prepared */
452	    goto ouch;
453	}
454	if (e != FTP_OK)
455	    goto ouch;
456
457	/* make the server initiate the transfer */
458	if (verbose)
459	    _fetch_info("initiating transfer");
460	e = _ftp_cmd(cd, "%s %s", oper, s);
461	if (e != FTP_OPEN_DATA_CONNECTION)
462	    goto ouch;
463
464	/* accept the incoming connection and go to town */
465	if ((d = accept(sd, NULL, NULL)) == -1)
466	    goto sysouch;
467	close(sd);
468	sd = d;
469    }
470
471    if ((df = fdopen(sd, mode)) == NULL)
472	goto sysouch;
473    return df;
474
475sysouch:
476    _fetch_syserr();
477    if (sd >= 0)
478	close(sd);
479    return NULL;
480
481ouch:
482    if (e != -1)
483	_ftp_seterr(e);
484    if (sd >= 0)
485	close(sd);
486    return NULL;
487}
488
489/*
490 * Log on to FTP server
491 */
492static int
493_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
494{
495    int cd, e, pp = 0, direct, verbose;
496#ifdef INET6
497    int af = AF_UNSPEC;
498#else
499    int af = AF_INET;
500#endif
501    char *p, *q;
502    const char *logname;
503    char localhost[MAXHOSTNAMELEN];
504    char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1];
505
506    direct = (flags && strchr(flags, 'd'));
507    verbose = (flags && strchr(flags, 'v'));
508    if ((flags && strchr(flags, '4')))
509	af = AF_INET;
510    else if ((flags && strchr(flags, '6')))
511	af = AF_INET6;
512
513    /* check for proxy */
514    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
515	char c = 0;
516
517#ifdef INET6
518	if (*p != '[' || (q = strchr(p + 1, ']')) == NULL ||
519	    (*++q != '\0' && *q != ':'))
520#endif
521	    q = strchr(p, ':');
522	if (q != NULL && *q == ':') {
523	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
524		/* XXX we should emit some kind of warning */
525	    }
526	    pp = atoi(q+1);
527	    if (pp < 1 || pp > 65535) {
528		/* XXX we should emit some kind of warning */
529	    }
530	}
531	if (!pp) {
532	    struct servent *se;
533
534	    if ((se = getservbyname("ftp", "tcp")) != NULL)
535		pp = ntohs(se->s_port);
536	    else
537		pp = FTP_DEFAULT_PORT;
538	}
539	if (q) {
540#ifdef INET6
541	    if (q > p && *p == '[' && *(q - 1) == ']') {
542		p++;
543		q--;
544	    }
545#endif
546	    c = *q;
547	    *q = 0;
548	}
549	cd = _fetch_connect(p, pp, af, verbose);
550	if (q)
551	    *q = c;
552    } else {
553	/* no proxy, go straight to target */
554	cd = _fetch_connect(host, port, af, verbose);
555	p = NULL;
556    }
557
558    /* check connection */
559    if (cd == -1) {
560	_fetch_syserr();
561	return NULL;
562    }
563
564    /* expect welcome message */
565    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
566	goto fouch;
567
568    /* send user name and password */
569    if (!user || !*user)
570	user = FTP_ANONYMOUS_USER;
571    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
572	  : _ftp_cmd(cd, "USER %s", user);
573
574    /* did the server request a password? */
575    if (e == FTP_NEED_PASSWORD) {
576	if (!pwd || !*pwd)
577	    pwd = getenv("FTP_PASSWORD");
578	if (!pwd || !*pwd) {
579	    if ((logname = getlogin()) == 0)
580		logname = FTP_ANONYMOUS_PASSWORD;
581	    gethostname(localhost, sizeof localhost);
582	    snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost);
583	    pwd = pbuf;
584	}
585	e = _ftp_cmd(cd, "PASS %s", pwd);
586    }
587
588    /* did the server request an account? */
589    if (e == FTP_NEED_ACCOUNT)
590	goto fouch;
591
592    /* we should be done by now */
593    if (e != FTP_LOGGED_IN)
594	goto fouch;
595
596    /* might as well select mode and type at once */
597#ifdef FTP_FORCE_STREAM_MODE
598    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
599	goto fouch;
600#endif
601    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
602	goto fouch;
603
604    /* done */
605    return cd;
606
607fouch:
608    if (e != -1)
609	_ftp_seterr(e);
610    close(cd);
611    return NULL;
612}
613
614/*
615 * Disconnect from server
616 */
617static void
618_ftp_disconnect(int cd)
619{
620    (void)_ftp_cmd(cd, "QUIT");
621    close(cd);
622}
623
624/*
625 * Check if we're already connected
626 */
627static int
628_ftp_isconnected(struct url *url)
629{
630    return (cached_socket
631	    && (strcmp(url->host, cached_host.host) == 0)
632	    && (strcmp(url->user, cached_host.user) == 0)
633	    && (strcmp(url->pwd, cached_host.pwd) == 0)
634	    && (url->port == cached_host.port));
635}
636
637/*
638 * Check the cache, reconnect if no luck
639 */
640static int
641_ftp_cached_connect(struct url *url, char *flags)
642{
643    int e, cd;
644
645    cd = -1;
646
647    /* set default port */
648    if (!url->port) {
649	struct servent *se;
650
651	if ((se = getservbyname("ftp", "tcp")) != NULL)
652	    url->port = ntohs(se->s_port);
653	else
654	    url->port = FTP_DEFAULT_PORT;
655    }
656
657    /* try to use previously cached connection */
658    if (_ftp_isconnected(url)) {
659	e = _ftp_cmd(cached_socket, "NOOP");
660	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
661	    cd = cached_socket;
662    }
663
664    /* connect to server */
665    if (cd == -1) {
666	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
667	if (cd == -1)
668	    return -1;
669	if (cached_socket)
670	    _ftp_disconnect(cached_socket);
671	cached_socket = cd;
672	memcpy(&cached_host, url, sizeof *url);
673    }
674
675    return cd;
676}
677
678/*
679 * Get file
680 */
681FILE *
682fetchGetFTP(struct url *url, char *flags)
683{
684    int cd;
685
686    /* connect to server */
687    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
688	return NULL;
689
690    /* initiate the transfer */
691    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
692}
693
694/*
695 * Put file
696 */
697FILE *
698fetchPutFTP(struct url *url, char *flags)
699{
700    int cd;
701
702    /* connect to server */
703    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
704	return NULL;
705
706    /* initiate the transfer */
707    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
708			 url->doc, "w", url->offset, flags);
709}
710
711/*
712 * Get file stats
713 */
714int
715fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
716{
717    char *ln, *s;
718    struct tm tm;
719    time_t t;
720    int e, cd;
721
722    us->size = -1;
723    us->atime = us->mtime = 0;
724
725    /* connect to server */
726    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
727	return -1;
728
729    /* change directory */
730    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
731	*s = 0;
732	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
733	    *s = '/';
734	    goto ouch;
735	}
736	*s++ = '/';
737    } else {
738	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
739	    goto ouch;
740    }
741
742    /* s now points to file name */
743
744    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
745	goto ouch;
746    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
747	/* nothing */ ;
748    for (us->size = 0; *ln && isdigit(*ln); ln++)
749	us->size = us->size * 10 + *ln - '0';
750    if (*ln && !isspace(*ln)) {
751	_ftp_seterr(999);
752	return -1;
753    }
754    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
755
756    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
757	goto ouch;
758    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
759	/* nothing */ ;
760    e = 999;
761    switch (strspn(ln, "0123456789")) {
762    case 14:
763	break;
764    case 15:
765	ln++;
766	ln[0] = '2';
767	ln[1] = '0';
768	break;
769    default:
770	goto ouch;
771    }
772    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
773	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
774	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
775	goto ouch;
776    tm.tm_mon--;
777    tm.tm_year -= 1900;
778    tm.tm_isdst = -1;
779    t = timegm(&tm);
780    if (t == (time_t)-1)
781	t = time(NULL);
782    us->mtime = t;
783    us->atime = t;
784    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
785		  "%02d:%02d:%02d\033[m]\n",
786		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
787		  tm.tm_hour, tm.tm_min, tm.tm_sec));
788    return 0;
789
790ouch:
791    if (e != -1)
792	_ftp_seterr(e);
793    return -1;
794}
795
796/*
797 * List a directory
798 */
799extern void warnx(char *, ...);
800struct url_ent *
801fetchListFTP(struct url *url, char *flags)
802{
803    int cd, n, e, status;
804    int size, len;
805    struct tm tm;
806    time_t t, now;
807    off_t s;
808    FILE * df;
809    char * filename;
810    struct url_stat thistat;
811    struct url_ent * answer = NULL;
812    size_t lastbuf = 0, thisbuf = 1024;
813    char * bp, * cp, * buffer = NULL, * ln;
814
815    int verbose = (flags && strchr(flags, 'v'));
816
817    /* Connect to server. */
818    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
819	return NULL;
820
821    /* Initiate the transfer. */
822    df = _ftp_transfer(cd, "NLST", url->doc, "r", (off_t) 0, flags);
823    if (df == NULL)
824	return NULL;
825
826    /* Get initial buffer for filenames. */
827    if ((buffer = malloc(thisbuf)) == NULL)
828	return NULL;
829
830    /*
831     * Read the list of filenames in this directory from the ftp connection.
832     * Once the list is complete, scan the list and STAT each file, adding
833     * them to the list.
834     */
835    while (1) {
836	n = fread(buffer + lastbuf, 1, thisbuf - lastbuf, df);
837	if (n < (thisbuf - lastbuf))
838	    break;
839
840	/*
841	 * That didn't get it all, so expand the buffer and read
842	 * some more.
843	 */
844	lastbuf = thisbuf;
845	thisbuf *= 2;
846	if ((buffer = realloc(buffer, thisbuf)) == NULL)
847	    return NULL;
848    }
849
850    /* Expect server reply, punt if nothing happened. */
851    if (((e = _ftp_chkerr(cd)) != FTP_SUCCESSFUL) || (n < 0))
852	return NULL;
853
854    /*
855     * We have all the filenames in the buffer.  The end of the buffer is at
856     * buffer + lastbuf + n, so we'll point lastbuf at it for convenience.
857     * Parse and stat each filename so we can fill in the URL entries.
858     */
859    lastbuf += n;
860    buffer[lastbuf++] = '\0';
861    now = time(0);
862
863    for (n = 0, bp = buffer; (cp = strsep(&bp, "\r\n")) != NULL; n++) {
864	if (*cp == '\0')
865	    continue;
866
867	/* Copy the "doc" member so we can modify it. */
868
869	filename = malloc(MAXPATHLEN + 1);
870	strlcpy(filename, url->doc, MAXPATHLEN);
871	strlcat(filename, cp, MAXPATHLEN);
872
873	t = now;
874	s = (off_t) 0;
875
876	/* Get file modification time from the server. */
877
878	status = _ftp_cmd(cd, "MDTM %s", filename);
879
880	if (status == FTP_FILE_STATUS) {
881	    /* MDTM succeeded, get file time */
882
883	    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
884		/* nothing */ ;
885
886	    switch ((e = strspn(ln, "0123456789"))) {
887	    case 14:
888		break;
889	    case 15:
890		ln++;
891		ln[0] = '2';
892		ln[1] = '0';
893		break;
894	    default:
895		goto ouch;
896	    }
897	    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
898		       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
899		       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
900		tm.tm_mon--;
901		tm.tm_year -= 1900;
902		tm.tm_isdst = -1;
903		t = timegm(&tm);
904		if (t == (time_t)-1)
905		    t = now;
906	    }
907
908	ouch:
909	    /* If MDTM succeeded it is a file, so get the size too. */
910	    if ((e = _ftp_cmd(cd, "SIZE %s", filename)) == FTP_FILE_STATUS) {
911		for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
912		    /* nothing */ ;
913		sscanf(ln, "%qu", &s);
914	    }
915	}
916
917	thistat.mtime = thistat.atime = t;
918	thistat.size = s;
919
920	if (verbose) {
921	    printf("%2d (%3d): %qu %s %s",
922		   n, status,
923		   thistat.size, filename, ctime(&thistat.mtime));
924	}
925
926	_fetch_add_entry(&answer, &size, &len, filename, &thistat);
927
928	free(filename);
929    }
930
931    fclose(df);
932    free(buffer);
933
934    return answer;
935}
936