xref: /minix3/external/bsd/fetch/dist/libfetch/ftp.c (revision 9488aa4c0452da1852a4f48bfad3c378d2b3e6cf)
1 /*	$NetBSD: ftp.c,v 1.6 2013/10/19 22:58:40 mrg Exp $	*/
2 /*-
3  * Copyright (c) 1998-2004 Dag-Erling Co�dan Sm�rgrav
4  * Copyright (c) 2008, 2009, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer
12  *    in this position and unchanged.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  * $FreeBSD: ftp.c,v 1.101 2008/01/23 20:57:59 des Exp $
31  */
32 
33 /*
34  * Portions of this code were taken from or based on ftpio.c:
35  *
36  * ----------------------------------------------------------------------------
37  * "THE BEER-WARE LICENSE" (Revision 42):
38  * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
39  * can do whatever you want with this stuff. If we meet some day, and you think
40  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
41  * ----------------------------------------------------------------------------
42  *
43  * Major Changelog:
44  *
45  * Dag-Erling Co�dan Sm�rgrav
46  * 9 Jun 1998
47  *
48  * Incorporated into libfetch
49  *
50  * Jordan K. Hubbard
51  * 17 Jan 1996
52  *
53  * Turned inside out. Now returns xfers as new file ids, not as a special
54  * `state' of FTP_t
55  *
56  * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
57  *
58  */
59 
60 #ifdef __linux__
61 /* Keep this down to Linux, it can create surprises else where. */
62 #define _GNU_SOURCE
63 #endif
64 
65 #if HAVE_CONFIG_H
66 #include "config.h"
67 #endif
68 #ifndef NETBSD
69 #include <nbcompat.h>
70 #endif
71 
72 #include <sys/types.h>
73 #include <sys/socket.h>
74 
75 #include <netinet/in.h>
76 #include <arpa/inet.h>
77 
78 #include <ctype.h>
79 #include <errno.h>
80 #include <fcntl.h>
81 #if defined(HAVE_INTTYPES_H) || defined(NETBSD)
82 #include <inttypes.h>
83 #endif
84 #include <stdarg.h>
85 #ifndef NETBSD
86 #include <nbcompat/netdb.h>
87 #include <nbcompat/stdio.h>
88 #else
89 #include <netdb.h>
90 #include <stdio.h>
91 #endif
92 #include <stdlib.h>
93 #include <string.h>
94 #include <time.h>
95 #include <unistd.h>
96 
97 #include "fetch.h"
98 #include "common.h"
99 #include "ftperr.h"
100 
101 #define FTP_ANONYMOUS_USER	"anonymous"
102 
103 #define FTP_CONNECTION_ALREADY_OPEN	125
104 #define FTP_OPEN_DATA_CONNECTION	150
105 #define FTP_OK				200
106 #define FTP_FILE_STATUS			213
107 #define FTP_SERVICE_READY		220
108 #define FTP_TRANSFER_COMPLETE		226
109 #define FTP_PASSIVE_MODE		227
110 #define FTP_LPASSIVE_MODE		228
111 #define FTP_EPASSIVE_MODE		229
112 #define FTP_LOGGED_IN			230
113 #define FTP_FILE_ACTION_OK		250
114 #define FTP_DIRECTORY_CREATED		257 /* multiple meanings */
115 #define FTP_FILE_CREATED		257 /* multiple meanings */
116 #define FTP_WORKING_DIRECTORY		257 /* multiple meanings */
117 #define FTP_NEED_PASSWORD		331
118 #define FTP_NEED_ACCOUNT		332
119 #define FTP_FILE_OK			350
120 #define FTP_SYNTAX_ERROR		500
121 #define FTP_PROTOCOL_ERROR		999
122 
123 #define isftpreply(foo)				\
124 	(isdigit((unsigned char)foo[0]) &&	\
125 	    isdigit((unsigned char)foo[1]) &&	\
126 	    isdigit((unsigned char)foo[2]) &&	\
127 	    (foo[3] == ' ' || foo[3] == '\0'))
128 #define isftpinfo(foo) \
129 	(isdigit((unsigned char)foo[0]) &&	\
130 	    isdigit((unsigned char)foo[1]) &&	\
131 	    isdigit((unsigned char)foo[2]) &&	\
132 	    foo[3] == '-')
133 
134 /*
135  * Translate IPv4 mapped IPv6 address to IPv4 address
136  */
137 static void
138 unmappedaddr(struct sockaddr_in6 *sin6, socklen_t *len)
139 {
140 	struct sockaddr_in *sin4;
141 	void *addrp;
142 	uint32_t addr;
143 	int port;
144 
145 	if (sin6->sin6_family != AF_INET6 ||
146 	    !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
147 		return;
148 	sin4 = (struct sockaddr_in *)(void *)sin6;
149 	addrp = &sin6->sin6_addr.s6_addr[12];
150 	addr = *(uint32_t *)addrp;
151 	port = sin6->sin6_port;
152 	memset(sin4, 0, sizeof(struct sockaddr_in));
153 	sin4->sin_addr.s_addr = addr;
154 	sin4->sin_port = port;
155 	sin4->sin_family = AF_INET;
156 	*len = sizeof(struct sockaddr_in);
157 #ifdef HAVE_SA_LEN
158 	sin4->sin_len = sizeof(struct sockaddr_in);
159 #endif
160 }
161 
162 /*
163  * Get server response
164  */
165 static int
166 ftp_chkerr(conn_t *conn)
167 {
168 	if (fetch_getln(conn) == -1) {
169 		fetch_syserr();
170 		return (-1);
171 	}
172 	if (isftpinfo(conn->buf)) {
173 		while (conn->buflen && !isftpreply(conn->buf)) {
174 			if (fetch_getln(conn) == -1) {
175 				fetch_syserr();
176 				return (-1);
177 			}
178 		}
179 	}
180 
181 	while (conn->buflen &&
182 	    isspace((unsigned char)conn->buf[conn->buflen - 1]))
183 		conn->buflen--;
184 	conn->buf[conn->buflen] = '\0';
185 
186 	if (!isftpreply(conn->buf)) {
187 		ftp_seterr(FTP_PROTOCOL_ERROR);
188 		return (-1);
189 	}
190 
191 	conn->err = (conn->buf[0] - '0') * 100
192 	    + (conn->buf[1] - '0') * 10
193 	    + (conn->buf[2] - '0');
194 
195 	return (conn->err);
196 }
197 
198 /*
199  * Send a command and check reply
200  */
201 static int
202 ftp_cmd(conn_t *conn, const char *fmt, ...)
203 {
204 	va_list ap;
205 	size_t len;
206 	char *msg;
207 	ssize_t r;
208 
209 	va_start(ap, fmt);
210 	len = vasprintf(&msg, fmt, ap);
211 	va_end(ap);
212 
213 	if (msg == NULL) {
214 		errno = ENOMEM;
215 		fetch_syserr();
216 		return (-1);
217 	}
218 
219 	r = fetch_write(conn, msg, len);
220 	free(msg);
221 
222 	if (r == -1) {
223 		fetch_syserr();
224 		return (-1);
225 	}
226 
227 	return (ftp_chkerr(conn));
228 }
229 
230 /*
231  * Return a pointer to the filename part of a path
232  */
233 static const char *
234 ftp_filename(const char *file, size_t *len, int *type, int subdir)
235 {
236 	const char *s;
237 
238 	if ((s = strrchr(file, '/')) == NULL || subdir)
239 		s = file;
240 	else
241 		s = s + 1;
242 	*len = strlen(s);
243 	if (*len > 7 && strncmp(s + *len - 7, ";type=", 6) == 0) {
244 		*type = s[*len - 1];
245 		*len -= 7;
246 	} else {
247 		*type = '\0';
248 	}
249 	return (s);
250 }
251 
252 /*
253  * Get current working directory from the reply to a CWD, PWD or CDUP
254  * command.
255  */
256 static int
257 ftp_pwd(conn_t *conn, char **pwd)
258 {
259 	char *src, *dst, *end;
260 	int q;
261 	size_t len;
262 
263 	if (conn->err != FTP_WORKING_DIRECTORY &&
264 	    conn->err != FTP_FILE_ACTION_OK)
265 		return (FTP_PROTOCOL_ERROR);
266 	end = conn->buf + conn->buflen;
267 	src = conn->buf + 4;
268 	if (src >= end || *src++ != '"')
269 		return (FTP_PROTOCOL_ERROR);
270 	len = end - src + 1;
271 	*pwd = malloc(len);
272 	if (*pwd == NULL)
273 		return (FTP_PROTOCOL_ERROR);
274 	for (q = 0, dst = *pwd; src < end; ++src) {
275 		if (!q && *src == '"')
276 			q = 1;
277 		else if (q && *src != '"')
278 			break;
279 		else if (q)
280 			*dst++ = '"', q = 0;
281 		else
282 			*dst++ = *src;
283 	}
284 	*dst = '\0';
285 	if (**pwd != '/') {
286 		free(*pwd);
287 		*pwd = NULL;
288 		return (FTP_PROTOCOL_ERROR);
289 	}
290 	return (FTP_OK);
291 }
292 
293 /*
294  * Change working directory to the directory that contains the specified
295  * file.
296  */
297 static int
298 ftp_cwd(conn_t *conn, const char *path, int subdir)
299 {
300 	const char *beg, *end;
301 	char *pwd, *dst;
302 	int e;
303 	size_t i, len;
304 
305 	if (*path != '/') {
306 		ftp_seterr(501);
307 		return (-1);
308 	}
309 	++path;
310 
311 	/* Simple case: still in the home directory and no directory change. */
312 	if (conn->ftp_home == NULL && strchr(path, '/') == NULL &&
313 	    (!subdir || *path == '\0'))
314 		return 0;
315 
316 	if ((e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
317 	    (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
318 		ftp_seterr(e);
319 		return (-1);
320 	}
321 	if (conn->ftp_home == NULL && (conn->ftp_home = strdup(pwd)) == NULL) {
322 		fetch_syserr();
323 		free(pwd);
324 		return (-1);
325 	}
326 	if (*path == '/') {
327 		while (path[1] == '/')
328 			++path;
329 		dst = strdup(path);
330 	} else if (strcmp(conn->ftp_home, "/") == 0) {
331 		dst = strdup(path - 1);
332 	} else {
333 		asprintf(&dst, "%s/%s", conn->ftp_home, path);
334 	}
335 	if (dst == NULL) {
336 		fetch_syserr();
337 		free(pwd);
338 		return (-1);
339 	}
340 
341 	if (subdir)
342 		end = dst + strlen(dst);
343 	else
344 		end = strrchr(dst, '/');
345 
346 	for (;;) {
347 		len = strlen(pwd);
348 
349 		/* Look for a common prefix between PWD and dir to fetch. */
350 		for (i = 0; i <= len && i <= (size_t)(end - dst); ++i)
351 			if (pwd[i] != dst[i])
352 				break;
353 		/* Keep going up a dir until we have a matching prefix. */
354 		if (strcmp(pwd, "/") == 0)
355 			break;
356 		if (pwd[i] == '\0' && (dst[i - 1] == '/' || dst[i] == '/'))
357 			break;
358 		free(pwd);
359 		if ((e = ftp_cmd(conn, "CDUP\r\n")) != FTP_FILE_ACTION_OK ||
360 		    (e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
361 		    (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
362 			ftp_seterr(e);
363 			free(dst);
364 			return (-1);
365 		}
366 	}
367 	free(pwd);
368 
369 #ifdef FTP_COMBINE_CWDS
370 	/* Skip leading slashes, even "////". */
371 	for (beg = dst + i; beg < end && *beg == '/'; ++beg, ++i)
372 		/* nothing */ ;
373 
374 	/* If there is no trailing dir, we're already there. */
375 	if (beg >= end) {
376 		free(dst);
377 		return (0);
378 	}
379 
380 	/* Change to the directory all in one chunk (e.g., foo/bar/baz). */
381 	e = ftp_cmd(conn, "CWD %.*s\r\n", (int)(end - beg), beg);
382 	if (e == FTP_FILE_ACTION_OK) {
383 		free(dst);
384 		return (0);
385 	}
386 #endif /* FTP_COMBINE_CWDS */
387 
388 	/* That didn't work so go back to legacy behavior (multiple CWDs). */
389 	for (beg = dst + i; beg < end; beg = dst + i + 1) {
390 		while (*beg == '/')
391 			++beg, ++i;
392 		for (++i; dst + i < end && dst[i] != '/'; ++i)
393 			/* nothing */ ;
394 		e = ftp_cmd(conn, "CWD %.*s\r\n", dst + i - beg, beg);
395 		if (e != FTP_FILE_ACTION_OK) {
396 			free(dst);
397 			ftp_seterr(e);
398 			return (-1);
399 		}
400 	}
401 	free(dst);
402 	return (0);
403 }
404 
405 /*
406  * Set transfer mode and data type
407  */
408 static int
409 ftp_mode_type(conn_t *conn, int mode, int type)
410 {
411 	int e;
412 
413 	switch (mode) {
414 	case 0:
415 	case 's':
416 		mode = 'S';
417 		/*FALLTHROUGH*/
418 	case 'S':
419 		break;
420 	default:
421 		return (FTP_PROTOCOL_ERROR);
422 	}
423 	if ((e = ftp_cmd(conn, "MODE %c\r\n", mode)) != FTP_OK) {
424 		if (mode == 'S') {
425 			/*
426 			 * Stream mode is supposed to be the default - so
427 			 * much so that some servers not only do not
428 			 * support any other mode, but do not support the
429 			 * MODE command at all.
430 			 *
431 			 * If "MODE S" fails, it is unlikely that we
432 			 * previously succeeded in setting a different
433 			 * mode.  Therefore, we simply hope that the
434 			 * server is already in the correct mode, and
435 			 * silently ignore the failure.
436 			 */
437 		} else {
438 			return (e);
439 		}
440 	}
441 
442 	switch (type) {
443 	case 0:
444 	case 'i':
445 		type = 'I';
446 		/*FALLTHROUGH*/
447 	case 'I':
448 		break;
449 	case 'a':
450 		type = 'A';
451 		/*FALLTHROUGH*/
452 	case 'A':
453 		break;
454 	case 'd':
455 		type = 'D';
456 		/*FALLTHROUGH*/
457 	case 'D':
458 		/* can't handle yet */
459 	default:
460 		return (FTP_PROTOCOL_ERROR);
461 	}
462 	if ((e = ftp_cmd(conn, "TYPE %c\r\n", type)) != FTP_OK)
463 		return (e);
464 
465 	return (FTP_OK);
466 }
467 
468 /*
469  * Request and parse file stats
470  */
471 static int
472 ftp_stat(conn_t *conn, const char *file, struct url_stat *us)
473 {
474 	char *ln;
475 	const char *filename;
476 	size_t filenamelen;
477 	int type;
478 	struct tm tm;
479 	time_t t;
480 	int e;
481 
482 	us->size = -1;
483 	us->atime = us->mtime = 0;
484 
485 	filename = ftp_filename(file, &filenamelen, &type, 0);
486 
487 	if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK) {
488 		ftp_seterr(e);
489 		return (-1);
490 	}
491 
492 	e = ftp_cmd(conn, "SIZE %.*s\r\n", filenamelen, filename);
493 	if (e != FTP_FILE_STATUS) {
494 		ftp_seterr(e);
495 		return (-1);
496 	}
497 	for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
498 		/* nothing */ ;
499 	for (us->size = 0; *ln && isdigit((unsigned char)*ln); ln++)
500 		us->size = us->size * 10 + *ln - '0';
501 	if (*ln && !isspace((unsigned char)*ln)) {
502 		ftp_seterr(FTP_PROTOCOL_ERROR);
503 		us->size = -1;
504 		return (-1);
505 	}
506 	if (us->size == 0)
507 		us->size = -1;
508 
509 	e = ftp_cmd(conn, "MDTM %.*s\r\n", filenamelen, filename);
510 	if (e != FTP_FILE_STATUS) {
511 		ftp_seterr(e);
512 		return (-1);
513 	}
514 	for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
515 		/* nothing */ ;
516 	switch (strspn(ln, "0123456789")) {
517 	case 14:
518 		break;
519 	case 15:
520 		ln++;
521 		ln[0] = '2';
522 		ln[1] = '0';
523 		break;
524 	default:
525 		ftp_seterr(FTP_PROTOCOL_ERROR);
526 		return (-1);
527 	}
528 	if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
529 	    &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
530 	    &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
531 		ftp_seterr(FTP_PROTOCOL_ERROR);
532 		return (-1);
533 	}
534 	tm.tm_mon--;
535 	tm.tm_year -= 1900;
536 	tm.tm_isdst = -1;
537 	t = timegm(&tm);
538 	if (t == (time_t)-1)
539 		t = time(NULL);
540 	us->mtime = t;
541 	us->atime = t;
542 
543 	return (0);
544 }
545 
546 /*
547  * I/O functions for FTP
548  */
549 struct ftpio {
550 	conn_t	*cconn;		/* Control connection */
551 	conn_t	*dconn;		/* Data connection */
552 	int	 dir;		/* Direction */
553 	int	 eof;		/* EOF reached */
554 	int	 err;		/* Error code */
555 };
556 
557 static ssize_t	 ftp_readfn(void *, void *, size_t);
558 static ssize_t	 ftp_writefn(void *, const void *, size_t);
559 static void	 ftp_closefn(void *);
560 
561 static ssize_t
562 ftp_readfn(void *v, void *buf, size_t len)
563 {
564 	struct ftpio *io;
565 	ssize_t r;
566 
567 	io = (struct ftpio *)v;
568 	if (io == NULL) {
569 		errno = EBADF;
570 		return (-1);
571 	}
572 	if (io->cconn == NULL || io->dconn == NULL || io->dir == O_WRONLY) {
573 		errno = EBADF;
574 		return (-1);
575 	}
576 	if (io->err) {
577 		errno = io->err;
578 		return (-1);
579 	}
580 	if (io->eof)
581 		return (0);
582 	r = fetch_read(io->dconn, buf, len);
583 	if (r > 0)
584 		return (r);
585 	if (r == 0) {
586 		io->eof = 1;
587 		return (0);
588 	}
589 	if (errno != EINTR)
590 		io->err = errno;
591 	return (-1);
592 }
593 
594 static ssize_t
595 ftp_writefn(void *v, const void *buf, size_t len)
596 {
597 	struct ftpio *io;
598 	ssize_t w;
599 
600 	io = (struct ftpio *)v;
601 	if (io == NULL) {
602 		errno = EBADF;
603 		return (-1);
604 	}
605 	if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) {
606 		errno = EBADF;
607 		return (-1);
608 	}
609 	if (io->err) {
610 		errno = io->err;
611 		return (-1);
612 	}
613 	w = fetch_write(io->dconn, buf, len);
614 	if (w >= 0)
615 		return (w);
616 	if (errno != EINTR)
617 		io->err = errno;
618 	return (-1);
619 }
620 
621 static int
622 ftp_disconnect(conn_t *conn)
623 {
624 	ftp_cmd(conn, "QUIT\r\n");
625 	return fetch_close(conn);
626 }
627 
628 static void
629 ftp_closefn(void *v)
630 {
631 	struct ftpio *io;
632 
633 	io = (struct ftpio *)v;
634 	if (io == NULL) {
635 		errno = EBADF;
636 		return;
637 	}
638 	if (io->dir == -1)
639 		return;
640 	if (io->cconn == NULL || io->dconn == NULL) {
641 		errno = EBADF;
642 		return;
643 	}
644 	fetch_close(io->dconn);
645 	io->dconn = NULL;
646 	io->dir = -1;
647 	(void)ftp_chkerr(io->cconn);
648 	fetch_cache_put(io->cconn, ftp_disconnect);
649 	free(io);
650 	return;
651 }
652 
653 static fetchIO *
654 ftp_setup(conn_t *cconn, conn_t *dconn, int mode)
655 {
656 	struct ftpio *io;
657 	fetchIO *f;
658 
659 	if (cconn == NULL || dconn == NULL)
660 		return (NULL);
661 	if ((io = malloc(sizeof(*io))) == NULL)
662 		return (NULL);
663 	io->cconn = cconn;
664 	io->dconn = dconn;
665 	io->dir = mode;
666 	io->eof = io->err = 0;
667 	f = fetchIO_unopen(io, ftp_readfn, ftp_writefn, ftp_closefn);
668 	if (f == NULL)
669 		free(io);
670 	return (f);
671 }
672 
673 /*
674  * Transfer file
675  */
676 static fetchIO *
677 ftp_transfer(conn_t *conn, const char *oper, const char *file, const char *op_arg,
678     int mode, off_t offset, const char *flags)
679 {
680 	union anonymous {
681 		struct sockaddr_storage ss;
682 		struct sockaddr sa;
683 		struct sockaddr_in6 sin6;
684 		struct sockaddr_in sin4;
685 	} u;
686 	const char *bindaddr;
687 	const char *filename;
688 	size_t filenamelen;
689 	int type;
690 	int low, pasv, verbose;
691 	int e, sd = -1;
692 	socklen_t l;
693 	char *s;
694 	fetchIO *df;
695 
696 	/* check flags */
697 	low = CHECK_FLAG('l');
698 	pasv = !CHECK_FLAG('a');
699 	verbose = CHECK_FLAG('v');
700 
701 	/* passive mode */
702 	if (!pasv)
703 		pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
704 		    strncasecmp(s, "no", 2) != 0);
705 
706 	/* isolate filename */
707 	filename = ftp_filename(file, &filenamelen, &type, op_arg != NULL);
708 
709 	/* set transfer mode and data type */
710 	if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK)
711 		goto ouch;
712 
713 	/* find our own address, bind, and listen */
714 	l = sizeof(u.ss);
715 	if (getsockname(conn->sd, &u.sa, &l) == -1)
716 		goto sysouch;
717 	if (u.ss.ss_family == AF_INET6)
718 		unmappedaddr(&u.sin6, &l);
719 
720 retry_mode:
721 
722 	/* open data socket */
723 	if ((sd = socket(u.ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
724 		fetch_syserr();
725 		return (NULL);
726 	}
727 
728 	if (pasv) {
729 		unsigned char addr[64];
730 		char *ln, *p;
731 		unsigned int i;
732 		int port;
733 
734 		/* send PASV command */
735 		if (verbose)
736 			fetch_info("setting passive mode");
737 		switch (u.ss.ss_family) {
738 		case AF_INET:
739 			if ((e = ftp_cmd(conn, "PASV\r\n")) != FTP_PASSIVE_MODE)
740 				goto ouch;
741 			break;
742 		case AF_INET6:
743 			if ((e = ftp_cmd(conn, "EPSV\r\n")) != FTP_EPASSIVE_MODE) {
744 				if (e == -1)
745 					goto ouch;
746 				if ((e = ftp_cmd(conn, "LPSV\r\n")) !=
747 				    FTP_LPASSIVE_MODE)
748 					goto ouch;
749 			}
750 			break;
751 		default:
752 			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
753 			goto ouch;
754 		}
755 
756 		/*
757 		 * Find address and port number. The reply to the PASV command
758 		 * is IMHO the one and only weak point in the FTP protocol.
759 		 */
760 		ln = conn->buf;
761 		switch (e) {
762 		case FTP_PASSIVE_MODE:
763 		case FTP_LPASSIVE_MODE:
764 			for (p = ln + 3; *p && !isdigit((unsigned char)*p); p++)
765 				/* nothing */ ;
766 			if (!*p) {
767 				e = FTP_PROTOCOL_ERROR;
768 				goto ouch;
769 			}
770 			l = (e == FTP_PASSIVE_MODE ? 6 : 21);
771 			for (i = 0; *p && i < l; i++, p++)
772 				addr[i] = (unsigned char)strtol(p, &p, 10);
773 			if (i < l) {
774 				e = FTP_PROTOCOL_ERROR;
775 				goto ouch;
776 			}
777 			break;
778 		case FTP_EPASSIVE_MODE:
779 			for (p = ln + 3; *p && *p != '('; p++)
780 				/* nothing */ ;
781 			if (!*p) {
782 				e = FTP_PROTOCOL_ERROR;
783 				goto ouch;
784 			}
785 			++p;
786 			if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
787 				&port, &addr[3]) != 5 ||
788 			    addr[0] != addr[1] ||
789 			    addr[0] != addr[2] || addr[0] != addr[3]) {
790 				e = FTP_PROTOCOL_ERROR;
791 				goto ouch;
792 			}
793 			break;
794 		case FTP_SYNTAX_ERROR:
795 			if (verbose)
796 				fetch_info("passive mode failed");
797 			/* Close socket and retry with passive mode. */
798 			pasv = 0;
799 			close(sd);
800 			sd = -1;
801 			goto retry_mode;
802 		}
803 
804 		/* seek to required offset */
805 		if (offset)
806 			if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
807 				goto sysouch;
808 
809 		/* construct sockaddr for data socket */
810 		l = sizeof(u.ss);
811 		if (getpeername(conn->sd, &u.sa, &l) == -1)
812 			goto sysouch;
813 		if (u.ss.ss_family == AF_INET6)
814 			unmappedaddr(&u.sin6, &l);
815 		switch (u.ss.ss_family) {
816 		case AF_INET6:
817 			if (e == FTP_EPASSIVE_MODE)
818 				u.sin6.sin6_port = htons(port);
819 			else {
820 				memcpy(&u.sin6.sin6_addr, addr + 2, 16);
821 				memcpy(&u.sin6.sin6_port, addr + 19, 2);
822 			}
823 			break;
824 		case AF_INET:
825 			if (e == FTP_EPASSIVE_MODE)
826 				u.sin4.sin_port = htons(port);
827 			else {
828 				memcpy(&u.sin4.sin_addr, addr, 4);
829 				memcpy(&u.sin4.sin_port, addr + 4, 2);
830 			}
831 			break;
832 		default:
833 			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
834 			break;
835 		}
836 
837 		/* connect to data port */
838 		if (verbose)
839 			fetch_info("opening data connection");
840 		bindaddr = getenv("FETCH_BIND_ADDRESS");
841 		if (bindaddr != NULL && *bindaddr != '\0' &&
842 		    fetch_bind(sd, u.ss.ss_family, bindaddr) != 0)
843 			goto sysouch;
844 		if (connect(sd, &u.sa, l) == -1)
845 			goto sysouch;
846 
847 		/* make the server initiate the transfer */
848 		if (verbose)
849 			fetch_info("initiating transfer");
850 		if (op_arg)
851 			e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
852 		else
853 			e = ftp_cmd(conn, "%s %.*s\r\n", oper,
854 			    filenamelen, filename);
855 		if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
856 			goto ouch;
857 
858 	} else {
859 		uint32_t a;
860 		uint16_t p;
861 #if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE)
862 		int arg;
863 #endif
864 		int d;
865 		char hname[INET6_ADDRSTRLEN];
866 
867 		switch (u.ss.ss_family) {
868 		case AF_INET6:
869 			u.sin6.sin6_port = 0;
870 #ifdef IPV6_PORTRANGE
871 			arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
872 			if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
873 				&arg, (socklen_t)sizeof(arg)) == -1)
874 				goto sysouch;
875 #endif
876 			break;
877 		case AF_INET:
878 			u.sin4.sin_port = 0;
879 #ifdef IP_PORTRANGE
880 			arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
881 			if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
882 				&arg, (socklen_t)sizeof(arg)) == -1)
883 				goto sysouch;
884 #endif
885 			break;
886 		}
887 		if (verbose)
888 			fetch_info("binding data socket");
889 		if (bind(sd, &u.sa, l) == -1)
890 			goto sysouch;
891 		if (listen(sd, 1) == -1)
892 			goto sysouch;
893 
894 		/* find what port we're on and tell the server */
895 		if (getsockname(sd, &u.sa, &l) == -1)
896 			goto sysouch;
897 		switch (u.ss.ss_family) {
898 		case AF_INET:
899 			a = ntohl(u.sin4.sin_addr.s_addr);
900 			p = ntohs(u.sin4.sin_port);
901 			e = ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d\r\n",
902 			    (a >> 24) & 0xff, (a >> 16) & 0xff,
903 			    (a >> 8) & 0xff, a & 0xff,
904 			    ((unsigned int)p >> 8) & 0xff, p & 0xff);
905 			break;
906 		case AF_INET6:
907 			e = -1;
908 			u.sin6.sin6_scope_id = 0;
909 			if (getnameinfo(&u.sa, l,
910 				hname, (socklen_t)sizeof(hname),
911 				NULL, 0, NI_NUMERICHOST) == 0) {
912 				e = ftp_cmd(conn, "EPRT |%d|%s|%d|\r\n", 2, hname,
913 				    htons(u.sin6.sin6_port));
914 				if (e == -1)
915 					goto ouch;
916 			}
917 			if (e != FTP_OK) {
918 				uint8_t aa[sizeof(u.sin6.sin6_addr)];
919 				memcpy(aa, &u.sin6.sin6_addr, sizeof(aa));
920 				p = ntohs(u.sin6.sin6_port);
921 				e = ftp_cmd(conn,
922 				    "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\r\n",
923 				    6, 16,
924 				    aa[ 0], aa[ 1], aa[ 2], aa[ 3],
925 				    aa[ 4], aa[ 5], aa[ 6], aa[ 7],
926 				    aa[ 8], aa[ 9], aa[10], aa[11],
927 				    aa[12], aa[13], aa[14], aa[15],
928 				    2,
929 				    ((unsigned int)p >> 8) & 0xff, p & 0xff);
930 			}
931 			break;
932 		default:
933 			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
934 			goto ouch;
935 		}
936 		if (e != FTP_OK)
937 			goto ouch;
938 
939 		/* seek to required offset */
940 		if (offset)
941 			if (ftp_cmd(conn, "REST %llu\r\n", (unsigned long long)offset) != FTP_FILE_OK)
942 				goto sysouch;
943 
944 		/* make the server initiate the transfer */
945 		if (verbose)
946 			fetch_info("initiating transfer");
947 		if (op_arg)
948 			e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
949 		else
950 			e = ftp_cmd(conn, "%s %.*s\r\n", oper,
951 			    filenamelen, filename);
952 		if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
953 			goto ouch;
954 
955 		/* accept the incoming connection and go to town */
956 		if ((d = accept(sd, NULL, NULL)) == -1)
957 			goto sysouch;
958 		close(sd);
959 		sd = d;
960 	}
961 
962 	if ((df = ftp_setup(conn, fetch_reopen(sd), mode)) == NULL)
963 		goto sysouch;
964 	return (df);
965 
966 sysouch:
967 	fetch_syserr();
968 	if (sd >= 0)
969 		close(sd);
970 	return (NULL);
971 
972 ouch:
973 	if (e != -1)
974 		ftp_seterr(e);
975 	if (sd >= 0)
976 		close(sd);
977 	return (NULL);
978 }
979 
980 /*
981  * Authenticate
982  */
983 static int
984 ftp_authenticate(conn_t *conn, struct url *url, struct url *purl)
985 {
986 	const char *user, *pwd, *login_name;
987 	char pbuf[URL_USERLEN + 1 + URL_HOSTLEN + 1];
988 	int e, len;
989 
990 	/* XXX FTP_AUTH, and maybe .netrc */
991 
992 	/* send user name and password */
993 	if (url->user[0] == '\0')
994 		fetch_netrc_auth(url);
995 	user = url->user;
996 	if (*user == '\0')
997 		user = getenv("FTP_LOGIN");
998 	if (user == NULL || *user == '\0')
999 		user = FTP_ANONYMOUS_USER;
1000 	if (purl && url->port == fetch_default_port(url->scheme))
1001 		e = ftp_cmd(conn, "USER %s@%s\r\n", user, url->host);
1002 	else if (purl)
1003 		e = ftp_cmd(conn, "USER %s@%s@%d\r\n", user, url->host, url->port);
1004 	else
1005 		e = ftp_cmd(conn, "USER %s\r\n", user);
1006 
1007 	/* did the server request a password? */
1008 	if (e == FTP_NEED_PASSWORD) {
1009 		pwd = url->pwd;
1010 		if (*pwd == '\0')
1011 			pwd = getenv("FTP_PASSWORD");
1012 		if (pwd == NULL || *pwd == '\0') {
1013 			if ((login_name = getlogin()) == 0)
1014 				login_name = FTP_ANONYMOUS_USER;
1015 			if ((len = snprintf(pbuf, URL_USERLEN + 2, "%s@", login_name)) < 0)
1016 				len = 0;
1017 			else if (len > URL_USERLEN + 1)
1018 				len = URL_USERLEN + 1;
1019 			gethostname(pbuf + len, sizeof(pbuf) - len);
1020 			/* MAXHOSTNAMELEN can differ from URL_HOSTLEN + 1 */
1021 			pbuf[sizeof(pbuf) - 1] = '\0';
1022 			pwd = pbuf;
1023 		}
1024 		e = ftp_cmd(conn, "PASS %s\r\n", pwd);
1025 	}
1026 
1027 	return (e);
1028 }
1029 
1030 /*
1031  * Log on to FTP server
1032  */
1033 static conn_t *
1034 ftp_connect(struct url *url, struct url *purl, const char *flags)
1035 {
1036 	conn_t *conn;
1037 	int e, direct, verbose;
1038 #ifdef INET6
1039 	int af = AF_UNSPEC;
1040 #else
1041 	int af = AF_INET;
1042 #endif
1043 
1044 	direct = CHECK_FLAG('d');
1045 	verbose = CHECK_FLAG('v');
1046 	if (CHECK_FLAG('4'))
1047 		af = AF_INET;
1048 	else if (CHECK_FLAG('6'))
1049 		af = AF_INET6;
1050 
1051 	if (direct)
1052 		purl = NULL;
1053 
1054 	/* check for proxy */
1055 	if (purl) {
1056 		/* XXX proxy authentication! */
1057 		/* XXX connetion caching */
1058 		if (!purl->port)
1059 			purl->port = fetch_default_port(purl->scheme);
1060 
1061 		conn = fetch_connect(purl, af, verbose);
1062 	} else {
1063 		/* no proxy, go straight to target */
1064 		if (!url->port)
1065 			url->port = fetch_default_port(url->scheme);
1066 
1067 		while ((conn = fetch_cache_get(url, af)) != NULL) {
1068 			e = ftp_cmd(conn, "NOOP\r\n");
1069 			if (e == FTP_OK)
1070 				return conn;
1071 			fetch_close(conn);
1072 		}
1073 		conn = fetch_connect(url, af, verbose);
1074 		purl = NULL;
1075 	}
1076 
1077 	/* check connection */
1078 	if (conn == NULL)
1079 		/* fetch_connect() has already set an error code */
1080 		return (NULL);
1081 
1082 	/* expect welcome message */
1083 	if ((e = ftp_chkerr(conn)) != FTP_SERVICE_READY)
1084 		goto fouch;
1085 
1086 	/* authenticate */
1087 	if ((e = ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN)
1088 		goto fouch;
1089 
1090 	/* TODO: Request extended features supported, if any (RFC 3659). */
1091 
1092 	/* done */
1093 	return (conn);
1094 
1095 fouch:
1096 	if (e != -1)
1097 		ftp_seterr(e);
1098 	fetch_close(conn);
1099 	return (NULL);
1100 }
1101 
1102 /*
1103  * Check the proxy settings
1104  */
1105 static struct url *
1106 ftp_get_proxy(struct url * url, const char *flags)
1107 {
1108 	struct url *purl;
1109 	char *p, *fp, *FP, *hp, *HP;
1110 
1111 	if (flags != NULL && strchr(flags, 'd') != NULL)
1112 		return NULL;
1113 	if (fetch_no_proxy_match(url->host))
1114 		return NULL;
1115 
1116 	FP = getenv("FTP_PROXY");
1117 	fp = getenv("ftp_proxy");
1118 	HP = getenv("HTTP_PROXY");
1119 	hp = getenv("http_proxy");
1120 
1121 	if ((((p = FP) || (p = fp) || (p = HP) || (p = hp))) &&
1122 	    *p && (purl = fetchParseURL(p)) != NULL) {
1123 		if (!*purl->scheme) {
1124 			if (fp || FP)
1125 				strcpy(purl->scheme, SCHEME_FTP);
1126 			else
1127 				strcpy(purl->scheme, SCHEME_HTTP);
1128 		}
1129 		if (!purl->port)
1130 			purl->port = fetch_default_proxy_port(purl->scheme);
1131 		if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
1132 		    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
1133 			return purl;
1134 		fetchFreeURL(purl);
1135 	}
1136 	return NULL;
1137 }
1138 
1139 /*
1140  * Process an FTP request
1141  */
1142 fetchIO *
1143 ftp_request(struct url *url, const char *op, const char *op_arg,
1144     struct url_stat *us, struct url *purl, const char *flags)
1145 {
1146 	fetchIO *f;
1147 	char *path;
1148 	conn_t *conn;
1149 	int if_modified_since, oflag;
1150 	struct url_stat local_us;
1151 
1152 	/* check if we should use HTTP instead */
1153 	if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
1154 		if (strcmp(op, "STAT") == 0)
1155 			return (http_request(url, "HEAD", us, purl, flags));
1156 		else if (strcmp(op, "RETR") == 0)
1157 			return (http_request(url, "GET", us, purl, flags));
1158 		/*
1159 		 * Our HTTP code doesn't support PUT requests yet, so try
1160 		 * a direct connection.
1161 		 */
1162 	}
1163 
1164 	/* connect to server */
1165 	conn = ftp_connect(url, purl, flags);
1166 	if (purl)
1167 		fetchFreeURL(purl);
1168 	if (conn == NULL)
1169 		return (NULL);
1170 
1171 	if ((path = fetchUnquotePath(url)) == NULL) {
1172 		fetch_syserr();
1173 		return NULL;
1174 	}
1175 
1176 	/* change directory */
1177 	if (ftp_cwd(conn, path, op_arg != NULL) == -1) {
1178 		free(path);
1179 		return (NULL);
1180 	}
1181 
1182 	if_modified_since = CHECK_FLAG('i');
1183 	if (if_modified_since && us == NULL)
1184 		us = &local_us;
1185 
1186 	/* stat file */
1187 	if (us && ftp_stat(conn, path, us) == -1
1188 	    && fetchLastErrCode != FETCH_PROTO
1189 	    && fetchLastErrCode != FETCH_UNAVAIL) {
1190 		free(path);
1191 		return (NULL);
1192 	}
1193 
1194 	if (if_modified_since && url->last_modified > 0 &&
1195 	    url->last_modified >= us->mtime) {
1196 		free(path);
1197 		fetchLastErrCode = FETCH_UNCHANGED;
1198 		snprintf(fetchLastErrString, MAXERRSTRING, "Unchanged");
1199 		return NULL;
1200 	}
1201 
1202 	/* just a stat */
1203 	if (strcmp(op, "STAT") == 0) {
1204 		free(path);
1205 		return fetchIO_unopen(NULL, NULL, NULL, NULL);
1206 	}
1207 	if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0)
1208 		oflag = O_WRONLY;
1209 	else
1210 		oflag = O_RDONLY;
1211 
1212 	/* initiate the transfer */
1213 	f = (ftp_transfer(conn, op, path, op_arg, oflag, url->offset, flags));
1214 	free(path);
1215 	return f;
1216 }
1217 
1218 /*
1219  * Get and stat file
1220  */
1221 fetchIO *
1222 fetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
1223 {
1224 	return (ftp_request(url, "RETR", NULL, us, ftp_get_proxy(url, flags), flags));
1225 }
1226 
1227 /*
1228  * Get file
1229  */
1230 fetchIO *
1231 fetchGetFTP(struct url *url, const char *flags)
1232 {
1233 	return (fetchXGetFTP(url, NULL, flags));
1234 }
1235 
1236 /*
1237  * Put file
1238  */
1239 fetchIO *
1240 fetchPutFTP(struct url *url, const char *flags)
1241 {
1242 	return (ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, NULL,
1243 	    ftp_get_proxy(url, flags), flags));
1244 }
1245 
1246 /*
1247  * Get file stats
1248  */
1249 int
1250 fetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
1251 {
1252 	fetchIO *f;
1253 
1254 	f = ftp_request(url, "STAT", NULL, us, ftp_get_proxy(url, flags), flags);
1255 	if (f == NULL)
1256 		return (-1);
1257 	fetchIO_close(f);
1258 	return (0);
1259 }
1260 
1261 /*
1262  * List a directory
1263  */
1264 int
1265 fetchListFTP(struct url_list *ue, struct url *url, const char *pattern, const char *flags)
1266 {
1267 	fetchIO *f;
1268 	char buf[2 * PATH_MAX], *eol, *eos;
1269 	ssize_t len;
1270 	size_t cur_off;
1271 	int ret;
1272 
1273 	/* XXX What about proxies? */
1274 	if (pattern == NULL || strcmp(pattern, "*") == 0)
1275 		pattern = "";
1276 	f = ftp_request(url, "NLST", pattern, NULL, ftp_get_proxy(url, flags), flags);
1277 	if (f == NULL)
1278 		return -1;
1279 
1280 	cur_off = 0;
1281 	ret = 0;
1282 
1283 	while ((len = fetchIO_read(f, buf + cur_off, sizeof(buf) - cur_off)) > 0) {
1284 		cur_off += len;
1285 		while ((eol = memchr(buf, '\n', cur_off)) != NULL) {
1286 			if (len == eol - buf)
1287 				break;
1288 			if (eol != buf) {
1289 				if (eol[-1] == '\r')
1290 					eos = eol - 1;
1291 				else
1292 					eos = eol;
1293 				*eos = '\0';
1294 				ret = fetch_add_entry(ue, url, buf, 0);
1295 				if (ret)
1296 					break;
1297 				cur_off -= eol - buf + 1;
1298 				memmove(buf, eol + 1, cur_off);
1299 			}
1300 		}
1301 		if (ret)
1302 			break;
1303 	}
1304 	if (cur_off != 0 || len < 0) {
1305 		/* Not RFC conform, bail out. */
1306 		fetchIO_close(f);
1307 		return -1;
1308 	}
1309 	fetchIO_close(f);
1310 	return ret;
1311 }
1312