xref: /freebsd/lib/libfetch/ftp.c (revision e137bceb8f1d8c853830c52bf0bf5382343c5807)
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$
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 
94 static struct url cached_host;
95 static int cached_socket;
96 
97 static char *last_reply;
98 static size_t lr_size, lr_length;
99 static 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 */
108 static void
109 unmappedaddr(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  */
131 static 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  */
166 static 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  */
198 static 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 
473 sysouch:
474     _fetch_syserr();
475     if (sd >= 0)
476 	close(sd);
477     return NULL;
478 
479 ouch:
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  */
490 static 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 
605 fouch:
606     if (e != -1)
607 	_ftp_seterr(e);
608     close(cd);
609     return NULL;
610 }
611 
612 /*
613  * Disconnect from server
614  */
615 static 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  */
625 static 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  */
638 static 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  */
679 FILE *
680 fetchGetFTP(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  */
695 FILE *
696 fetchPutFTP(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  */
712 int
713 fetchStatFTP(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 
788 ouch:
789     if (e != -1)
790 	_ftp_seterr(e);
791     return -1;
792 }
793 
794 /*
795  * List a directory
796  */
797 extern void warnx(char *, ...);
798 struct url_ent *
799 fetchListFTP(struct url *url, char *flags)
800 {
801     warnx("fetchListFTP(): not implemented");
802     return NULL;
803 }
804