xref: /minix3/external/bsd/fetch/dist/libfetch/ftp.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1*0a6a1f1dSLionel Sambuc /*	$NetBSD: ftp.c,v 1.7 2014/01/07 02:13:00 joerg Exp $	*/
2040ec644SDavid van Moolenbroek /*-
3040ec644SDavid van Moolenbroek  * Copyright (c) 1998-2004 Dag-Erling Co�dan Sm�rgrav
4040ec644SDavid van Moolenbroek  * Copyright (c) 2008, 2009, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
5040ec644SDavid van Moolenbroek  * All rights reserved.
6040ec644SDavid van Moolenbroek  *
7040ec644SDavid van Moolenbroek  * Redistribution and use in source and binary forms, with or without
8040ec644SDavid van Moolenbroek  * modification, are permitted provided that the following conditions
9040ec644SDavid van Moolenbroek  * are met:
10040ec644SDavid van Moolenbroek  * 1. Redistributions of source code must retain the above copyright
11040ec644SDavid van Moolenbroek  *    notice, this list of conditions and the following disclaimer
12040ec644SDavid van Moolenbroek  *    in this position and unchanged.
13040ec644SDavid van Moolenbroek  * 2. Redistributions in binary form must reproduce the above copyright
14040ec644SDavid van Moolenbroek  *    notice, this list of conditions and the following disclaimer in the
15040ec644SDavid van Moolenbroek  *    documentation and/or other materials provided with the distribution.
16040ec644SDavid van Moolenbroek  * 3. The name of the author may not be used to endorse or promote products
17040ec644SDavid van Moolenbroek  *    derived from this software without specific prior written permission
18040ec644SDavid van Moolenbroek  *
19040ec644SDavid van Moolenbroek  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20040ec644SDavid van Moolenbroek  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21040ec644SDavid van Moolenbroek  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22040ec644SDavid van Moolenbroek  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23040ec644SDavid van Moolenbroek  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24040ec644SDavid van Moolenbroek  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25040ec644SDavid van Moolenbroek  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26040ec644SDavid van Moolenbroek  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27040ec644SDavid van Moolenbroek  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28040ec644SDavid van Moolenbroek  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29040ec644SDavid van Moolenbroek  *
30040ec644SDavid van Moolenbroek  * $FreeBSD: ftp.c,v 1.101 2008/01/23 20:57:59 des Exp $
31040ec644SDavid van Moolenbroek  */
32040ec644SDavid van Moolenbroek 
33040ec644SDavid van Moolenbroek /*
34040ec644SDavid van Moolenbroek  * Portions of this code were taken from or based on ftpio.c:
35040ec644SDavid van Moolenbroek  *
36040ec644SDavid van Moolenbroek  * ----------------------------------------------------------------------------
37040ec644SDavid van Moolenbroek  * "THE BEER-WARE LICENSE" (Revision 42):
38040ec644SDavid van Moolenbroek  * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
39040ec644SDavid van Moolenbroek  * can do whatever you want with this stuff. If we meet some day, and you think
40040ec644SDavid van Moolenbroek  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
41040ec644SDavid van Moolenbroek  * ----------------------------------------------------------------------------
42040ec644SDavid van Moolenbroek  *
43040ec644SDavid van Moolenbroek  * Major Changelog:
44040ec644SDavid van Moolenbroek  *
45040ec644SDavid van Moolenbroek  * Dag-Erling Co�dan Sm�rgrav
46040ec644SDavid van Moolenbroek  * 9 Jun 1998
47040ec644SDavid van Moolenbroek  *
48040ec644SDavid van Moolenbroek  * Incorporated into libfetch
49040ec644SDavid van Moolenbroek  *
50040ec644SDavid van Moolenbroek  * Jordan K. Hubbard
51040ec644SDavid van Moolenbroek  * 17 Jan 1996
52040ec644SDavid van Moolenbroek  *
53040ec644SDavid van Moolenbroek  * Turned inside out. Now returns xfers as new file ids, not as a special
54040ec644SDavid van Moolenbroek  * `state' of FTP_t
55040ec644SDavid van Moolenbroek  *
56040ec644SDavid van Moolenbroek  * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
57040ec644SDavid van Moolenbroek  *
58040ec644SDavid van Moolenbroek  */
59040ec644SDavid van Moolenbroek 
60040ec644SDavid van Moolenbroek #ifdef __linux__
61040ec644SDavid van Moolenbroek /* Keep this down to Linux, it can create surprises else where. */
62040ec644SDavid van Moolenbroek #define _GNU_SOURCE
63040ec644SDavid van Moolenbroek #endif
64040ec644SDavid van Moolenbroek 
65040ec644SDavid van Moolenbroek #if HAVE_CONFIG_H
66040ec644SDavid van Moolenbroek #include "config.h"
67040ec644SDavid van Moolenbroek #endif
68040ec644SDavid van Moolenbroek #ifndef NETBSD
69040ec644SDavid van Moolenbroek #include <nbcompat.h>
70040ec644SDavid van Moolenbroek #endif
71040ec644SDavid van Moolenbroek 
72040ec644SDavid van Moolenbroek #include <sys/types.h>
73040ec644SDavid van Moolenbroek #include <sys/socket.h>
74040ec644SDavid van Moolenbroek 
75040ec644SDavid van Moolenbroek #include <netinet/in.h>
76040ec644SDavid van Moolenbroek #include <arpa/inet.h>
77040ec644SDavid van Moolenbroek 
78040ec644SDavid van Moolenbroek #include <ctype.h>
79040ec644SDavid van Moolenbroek #include <errno.h>
80040ec644SDavid van Moolenbroek #include <fcntl.h>
81040ec644SDavid van Moolenbroek #if defined(HAVE_INTTYPES_H) || defined(NETBSD)
82040ec644SDavid van Moolenbroek #include <inttypes.h>
83040ec644SDavid van Moolenbroek #endif
84040ec644SDavid van Moolenbroek #include <stdarg.h>
85040ec644SDavid van Moolenbroek #ifndef NETBSD
86040ec644SDavid van Moolenbroek #include <nbcompat/netdb.h>
87040ec644SDavid van Moolenbroek #include <nbcompat/stdio.h>
88040ec644SDavid van Moolenbroek #else
89040ec644SDavid van Moolenbroek #include <netdb.h>
90040ec644SDavid van Moolenbroek #include <stdio.h>
91040ec644SDavid van Moolenbroek #endif
92040ec644SDavid van Moolenbroek #include <stdlib.h>
93040ec644SDavid van Moolenbroek #include <string.h>
94040ec644SDavid van Moolenbroek #include <time.h>
95040ec644SDavid van Moolenbroek #include <unistd.h>
96040ec644SDavid van Moolenbroek 
97040ec644SDavid van Moolenbroek #include "fetch.h"
98040ec644SDavid van Moolenbroek #include "common.h"
99040ec644SDavid van Moolenbroek #include "ftperr.h"
100040ec644SDavid van Moolenbroek 
101040ec644SDavid van Moolenbroek #define FTP_ANONYMOUS_USER	"anonymous"
102040ec644SDavid van Moolenbroek 
103040ec644SDavid van Moolenbroek #define FTP_CONNECTION_ALREADY_OPEN	125
104040ec644SDavid van Moolenbroek #define FTP_OPEN_DATA_CONNECTION	150
105040ec644SDavid van Moolenbroek #define FTP_OK				200
106040ec644SDavid van Moolenbroek #define FTP_FILE_STATUS			213
107040ec644SDavid van Moolenbroek #define FTP_SERVICE_READY		220
108040ec644SDavid van Moolenbroek #define FTP_TRANSFER_COMPLETE		226
109040ec644SDavid van Moolenbroek #define FTP_PASSIVE_MODE		227
110040ec644SDavid van Moolenbroek #define FTP_LPASSIVE_MODE		228
111040ec644SDavid van Moolenbroek #define FTP_EPASSIVE_MODE		229
112040ec644SDavid van Moolenbroek #define FTP_LOGGED_IN			230
113040ec644SDavid van Moolenbroek #define FTP_FILE_ACTION_OK		250
114040ec644SDavid van Moolenbroek #define FTP_DIRECTORY_CREATED		257 /* multiple meanings */
115040ec644SDavid van Moolenbroek #define FTP_FILE_CREATED		257 /* multiple meanings */
116040ec644SDavid van Moolenbroek #define FTP_WORKING_DIRECTORY		257 /* multiple meanings */
117040ec644SDavid van Moolenbroek #define FTP_NEED_PASSWORD		331
118040ec644SDavid van Moolenbroek #define FTP_NEED_ACCOUNT		332
119040ec644SDavid van Moolenbroek #define FTP_FILE_OK			350
120040ec644SDavid van Moolenbroek #define FTP_SYNTAX_ERROR		500
121040ec644SDavid van Moolenbroek #define FTP_PROTOCOL_ERROR		999
122040ec644SDavid van Moolenbroek 
123040ec644SDavid van Moolenbroek #define isftpreply(foo)				\
124040ec644SDavid van Moolenbroek 	(isdigit((unsigned char)foo[0]) &&	\
125040ec644SDavid van Moolenbroek 	    isdigit((unsigned char)foo[1]) &&	\
126040ec644SDavid van Moolenbroek 	    isdigit((unsigned char)foo[2]) &&	\
127040ec644SDavid van Moolenbroek 	    (foo[3] == ' ' || foo[3] == '\0'))
128040ec644SDavid van Moolenbroek #define isftpinfo(foo) \
129040ec644SDavid van Moolenbroek 	(isdigit((unsigned char)foo[0]) &&	\
130040ec644SDavid van Moolenbroek 	    isdigit((unsigned char)foo[1]) &&	\
131040ec644SDavid van Moolenbroek 	    isdigit((unsigned char)foo[2]) &&	\
132040ec644SDavid van Moolenbroek 	    foo[3] == '-')
133040ec644SDavid van Moolenbroek 
134040ec644SDavid van Moolenbroek /*
135040ec644SDavid van Moolenbroek  * Translate IPv4 mapped IPv6 address to IPv4 address
136040ec644SDavid van Moolenbroek  */
137040ec644SDavid van Moolenbroek static void
unmappedaddr(struct sockaddr_in6 * sin6,socklen_t * len)138040ec644SDavid van Moolenbroek unmappedaddr(struct sockaddr_in6 *sin6, socklen_t *len)
139040ec644SDavid van Moolenbroek {
140040ec644SDavid van Moolenbroek 	struct sockaddr_in *sin4;
141040ec644SDavid van Moolenbroek 	void *addrp;
142040ec644SDavid van Moolenbroek 	uint32_t addr;
143040ec644SDavid van Moolenbroek 	int port;
144040ec644SDavid van Moolenbroek 
145040ec644SDavid van Moolenbroek 	if (sin6->sin6_family != AF_INET6 ||
146040ec644SDavid van Moolenbroek 	    !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
147040ec644SDavid van Moolenbroek 		return;
148040ec644SDavid van Moolenbroek 	sin4 = (struct sockaddr_in *)(void *)sin6;
149040ec644SDavid van Moolenbroek 	addrp = &sin6->sin6_addr.s6_addr[12];
150040ec644SDavid van Moolenbroek 	addr = *(uint32_t *)addrp;
151040ec644SDavid van Moolenbroek 	port = sin6->sin6_port;
152040ec644SDavid van Moolenbroek 	memset(sin4, 0, sizeof(struct sockaddr_in));
153040ec644SDavid van Moolenbroek 	sin4->sin_addr.s_addr = addr;
154040ec644SDavid van Moolenbroek 	sin4->sin_port = port;
155040ec644SDavid van Moolenbroek 	sin4->sin_family = AF_INET;
156040ec644SDavid van Moolenbroek 	*len = sizeof(struct sockaddr_in);
157040ec644SDavid van Moolenbroek #ifdef HAVE_SA_LEN
158040ec644SDavid van Moolenbroek 	sin4->sin_len = sizeof(struct sockaddr_in);
159040ec644SDavid van Moolenbroek #endif
160040ec644SDavid van Moolenbroek }
161040ec644SDavid van Moolenbroek 
162040ec644SDavid van Moolenbroek /*
163040ec644SDavid van Moolenbroek  * Get server response
164040ec644SDavid van Moolenbroek  */
165040ec644SDavid van Moolenbroek static int
ftp_chkerr(conn_t * conn)166040ec644SDavid van Moolenbroek ftp_chkerr(conn_t *conn)
167040ec644SDavid van Moolenbroek {
168040ec644SDavid van Moolenbroek 	if (fetch_getln(conn) == -1) {
169040ec644SDavid van Moolenbroek 		fetch_syserr();
170040ec644SDavid van Moolenbroek 		return (-1);
171040ec644SDavid van Moolenbroek 	}
172040ec644SDavid van Moolenbroek 	if (isftpinfo(conn->buf)) {
173040ec644SDavid van Moolenbroek 		while (conn->buflen && !isftpreply(conn->buf)) {
174040ec644SDavid van Moolenbroek 			if (fetch_getln(conn) == -1) {
175040ec644SDavid van Moolenbroek 				fetch_syserr();
176040ec644SDavid van Moolenbroek 				return (-1);
177040ec644SDavid van Moolenbroek 			}
178040ec644SDavid van Moolenbroek 		}
179040ec644SDavid van Moolenbroek 	}
180040ec644SDavid van Moolenbroek 
181040ec644SDavid van Moolenbroek 	while (conn->buflen &&
182040ec644SDavid van Moolenbroek 	    isspace((unsigned char)conn->buf[conn->buflen - 1]))
183040ec644SDavid van Moolenbroek 		conn->buflen--;
184040ec644SDavid van Moolenbroek 	conn->buf[conn->buflen] = '\0';
185040ec644SDavid van Moolenbroek 
186040ec644SDavid van Moolenbroek 	if (!isftpreply(conn->buf)) {
187040ec644SDavid van Moolenbroek 		ftp_seterr(FTP_PROTOCOL_ERROR);
188040ec644SDavid van Moolenbroek 		return (-1);
189040ec644SDavid van Moolenbroek 	}
190040ec644SDavid van Moolenbroek 
191040ec644SDavid van Moolenbroek 	conn->err = (conn->buf[0] - '0') * 100
192040ec644SDavid van Moolenbroek 	    + (conn->buf[1] - '0') * 10
193040ec644SDavid van Moolenbroek 	    + (conn->buf[2] - '0');
194040ec644SDavid van Moolenbroek 
195040ec644SDavid van Moolenbroek 	return (conn->err);
196040ec644SDavid van Moolenbroek }
197040ec644SDavid van Moolenbroek 
198040ec644SDavid van Moolenbroek /*
199040ec644SDavid van Moolenbroek  * Send a command and check reply
200040ec644SDavid van Moolenbroek  */
201*0a6a1f1dSLionel Sambuc __printflike(2, 3)
202040ec644SDavid van Moolenbroek static int
ftp_cmd(conn_t * conn,const char * fmt,...)203040ec644SDavid van Moolenbroek ftp_cmd(conn_t *conn, const char *fmt, ...)
204040ec644SDavid van Moolenbroek {
205040ec644SDavid van Moolenbroek 	va_list ap;
206040ec644SDavid van Moolenbroek 	size_t len;
207040ec644SDavid van Moolenbroek 	char *msg;
208040ec644SDavid van Moolenbroek 	ssize_t r;
209040ec644SDavid van Moolenbroek 
210040ec644SDavid van Moolenbroek 	va_start(ap, fmt);
211040ec644SDavid van Moolenbroek 	len = vasprintf(&msg, fmt, ap);
212040ec644SDavid van Moolenbroek 	va_end(ap);
213040ec644SDavid van Moolenbroek 
214040ec644SDavid van Moolenbroek 	if (msg == NULL) {
215040ec644SDavid van Moolenbroek 		errno = ENOMEM;
216040ec644SDavid van Moolenbroek 		fetch_syserr();
217040ec644SDavid van Moolenbroek 		return (-1);
218040ec644SDavid van Moolenbroek 	}
219040ec644SDavid van Moolenbroek 
220040ec644SDavid van Moolenbroek 	r = fetch_write(conn, msg, len);
221040ec644SDavid van Moolenbroek 	free(msg);
222040ec644SDavid van Moolenbroek 
223040ec644SDavid van Moolenbroek 	if (r == -1) {
224040ec644SDavid van Moolenbroek 		fetch_syserr();
225040ec644SDavid van Moolenbroek 		return (-1);
226040ec644SDavid van Moolenbroek 	}
227040ec644SDavid van Moolenbroek 
228040ec644SDavid van Moolenbroek 	return (ftp_chkerr(conn));
229040ec644SDavid van Moolenbroek }
230040ec644SDavid van Moolenbroek 
231040ec644SDavid van Moolenbroek /*
232040ec644SDavid van Moolenbroek  * Return a pointer to the filename part of a path
233040ec644SDavid van Moolenbroek  */
234040ec644SDavid van Moolenbroek static const char *
ftp_filename(const char * file,size_t * len,int * type,int subdir)235040ec644SDavid van Moolenbroek ftp_filename(const char *file, size_t *len, int *type, int subdir)
236040ec644SDavid van Moolenbroek {
237040ec644SDavid van Moolenbroek 	const char *s;
238040ec644SDavid van Moolenbroek 
239040ec644SDavid van Moolenbroek 	if ((s = strrchr(file, '/')) == NULL || subdir)
240040ec644SDavid van Moolenbroek 		s = file;
241040ec644SDavid van Moolenbroek 	else
242040ec644SDavid van Moolenbroek 		s = s + 1;
243040ec644SDavid van Moolenbroek 	*len = strlen(s);
244040ec644SDavid van Moolenbroek 	if (*len > 7 && strncmp(s + *len - 7, ";type=", 6) == 0) {
245040ec644SDavid van Moolenbroek 		*type = s[*len - 1];
246040ec644SDavid van Moolenbroek 		*len -= 7;
247040ec644SDavid van Moolenbroek 	} else {
248040ec644SDavid van Moolenbroek 		*type = '\0';
249040ec644SDavid van Moolenbroek 	}
250040ec644SDavid van Moolenbroek 	return (s);
251040ec644SDavid van Moolenbroek }
252040ec644SDavid van Moolenbroek 
253040ec644SDavid van Moolenbroek /*
254040ec644SDavid van Moolenbroek  * Get current working directory from the reply to a CWD, PWD or CDUP
255040ec644SDavid van Moolenbroek  * command.
256040ec644SDavid van Moolenbroek  */
257040ec644SDavid van Moolenbroek static int
ftp_pwd(conn_t * conn,char ** pwd)258040ec644SDavid van Moolenbroek ftp_pwd(conn_t *conn, char **pwd)
259040ec644SDavid van Moolenbroek {
260040ec644SDavid van Moolenbroek 	char *src, *dst, *end;
261040ec644SDavid van Moolenbroek 	int q;
262040ec644SDavid van Moolenbroek 	size_t len;
263040ec644SDavid van Moolenbroek 
264040ec644SDavid van Moolenbroek 	if (conn->err != FTP_WORKING_DIRECTORY &&
265040ec644SDavid van Moolenbroek 	    conn->err != FTP_FILE_ACTION_OK)
266040ec644SDavid van Moolenbroek 		return (FTP_PROTOCOL_ERROR);
267040ec644SDavid van Moolenbroek 	end = conn->buf + conn->buflen;
268040ec644SDavid van Moolenbroek 	src = conn->buf + 4;
269040ec644SDavid van Moolenbroek 	if (src >= end || *src++ != '"')
270040ec644SDavid van Moolenbroek 		return (FTP_PROTOCOL_ERROR);
271040ec644SDavid van Moolenbroek 	len = end - src + 1;
272040ec644SDavid van Moolenbroek 	*pwd = malloc(len);
273040ec644SDavid van Moolenbroek 	if (*pwd == NULL)
274040ec644SDavid van Moolenbroek 		return (FTP_PROTOCOL_ERROR);
275040ec644SDavid van Moolenbroek 	for (q = 0, dst = *pwd; src < end; ++src) {
276040ec644SDavid van Moolenbroek 		if (!q && *src == '"')
277040ec644SDavid van Moolenbroek 			q = 1;
278040ec644SDavid van Moolenbroek 		else if (q && *src != '"')
279040ec644SDavid van Moolenbroek 			break;
280040ec644SDavid van Moolenbroek 		else if (q)
281040ec644SDavid van Moolenbroek 			*dst++ = '"', q = 0;
282040ec644SDavid van Moolenbroek 		else
283040ec644SDavid van Moolenbroek 			*dst++ = *src;
284040ec644SDavid van Moolenbroek 	}
285040ec644SDavid van Moolenbroek 	*dst = '\0';
286040ec644SDavid van Moolenbroek 	if (**pwd != '/') {
287040ec644SDavid van Moolenbroek 		free(*pwd);
288040ec644SDavid van Moolenbroek 		*pwd = NULL;
289040ec644SDavid van Moolenbroek 		return (FTP_PROTOCOL_ERROR);
290040ec644SDavid van Moolenbroek 	}
291040ec644SDavid van Moolenbroek 	return (FTP_OK);
292040ec644SDavid van Moolenbroek }
293040ec644SDavid van Moolenbroek 
294040ec644SDavid van Moolenbroek /*
295040ec644SDavid van Moolenbroek  * Change working directory to the directory that contains the specified
296040ec644SDavid van Moolenbroek  * file.
297040ec644SDavid van Moolenbroek  */
298040ec644SDavid van Moolenbroek static int
ftp_cwd(conn_t * conn,const char * path,int subdir)299040ec644SDavid van Moolenbroek ftp_cwd(conn_t *conn, const char *path, int subdir)
300040ec644SDavid van Moolenbroek {
301040ec644SDavid van Moolenbroek 	const char *beg, *end;
302040ec644SDavid van Moolenbroek 	char *pwd, *dst;
303040ec644SDavid van Moolenbroek 	int e;
304040ec644SDavid van Moolenbroek 	size_t i, len;
305040ec644SDavid van Moolenbroek 
306040ec644SDavid van Moolenbroek 	if (*path != '/') {
307040ec644SDavid van Moolenbroek 		ftp_seterr(501);
308040ec644SDavid van Moolenbroek 		return (-1);
309040ec644SDavid van Moolenbroek 	}
310040ec644SDavid van Moolenbroek 	++path;
311040ec644SDavid van Moolenbroek 
312040ec644SDavid van Moolenbroek 	/* Simple case: still in the home directory and no directory change. */
313040ec644SDavid van Moolenbroek 	if (conn->ftp_home == NULL && strchr(path, '/') == NULL &&
314040ec644SDavid van Moolenbroek 	    (!subdir || *path == '\0'))
315040ec644SDavid van Moolenbroek 		return 0;
316040ec644SDavid van Moolenbroek 
317040ec644SDavid van Moolenbroek 	if ((e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
318040ec644SDavid van Moolenbroek 	    (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
319040ec644SDavid van Moolenbroek 		ftp_seterr(e);
320040ec644SDavid van Moolenbroek 		return (-1);
321040ec644SDavid van Moolenbroek 	}
322040ec644SDavid van Moolenbroek 	if (conn->ftp_home == NULL && (conn->ftp_home = strdup(pwd)) == NULL) {
323040ec644SDavid van Moolenbroek 		fetch_syserr();
324040ec644SDavid van Moolenbroek 		free(pwd);
325040ec644SDavid van Moolenbroek 		return (-1);
326040ec644SDavid van Moolenbroek 	}
327040ec644SDavid van Moolenbroek 	if (*path == '/') {
328040ec644SDavid van Moolenbroek 		while (path[1] == '/')
329040ec644SDavid van Moolenbroek 			++path;
330040ec644SDavid van Moolenbroek 		dst = strdup(path);
331040ec644SDavid van Moolenbroek 	} else if (strcmp(conn->ftp_home, "/") == 0) {
332040ec644SDavid van Moolenbroek 		dst = strdup(path - 1);
333040ec644SDavid van Moolenbroek 	} else {
334040ec644SDavid van Moolenbroek 		asprintf(&dst, "%s/%s", conn->ftp_home, path);
335040ec644SDavid van Moolenbroek 	}
336040ec644SDavid van Moolenbroek 	if (dst == NULL) {
337040ec644SDavid van Moolenbroek 		fetch_syserr();
338040ec644SDavid van Moolenbroek 		free(pwd);
339040ec644SDavid van Moolenbroek 		return (-1);
340040ec644SDavid van Moolenbroek 	}
341040ec644SDavid van Moolenbroek 
342040ec644SDavid van Moolenbroek 	if (subdir)
343040ec644SDavid van Moolenbroek 		end = dst + strlen(dst);
344040ec644SDavid van Moolenbroek 	else
345040ec644SDavid van Moolenbroek 		end = strrchr(dst, '/');
346040ec644SDavid van Moolenbroek 
347040ec644SDavid van Moolenbroek 	for (;;) {
348040ec644SDavid van Moolenbroek 		len = strlen(pwd);
349040ec644SDavid van Moolenbroek 
350040ec644SDavid van Moolenbroek 		/* Look for a common prefix between PWD and dir to fetch. */
351040ec644SDavid van Moolenbroek 		for (i = 0; i <= len && i <= (size_t)(end - dst); ++i)
352040ec644SDavid van Moolenbroek 			if (pwd[i] != dst[i])
353040ec644SDavid van Moolenbroek 				break;
354040ec644SDavid van Moolenbroek 		/* Keep going up a dir until we have a matching prefix. */
355040ec644SDavid van Moolenbroek 		if (strcmp(pwd, "/") == 0)
356040ec644SDavid van Moolenbroek 			break;
357040ec644SDavid van Moolenbroek 		if (pwd[i] == '\0' && (dst[i - 1] == '/' || dst[i] == '/'))
358040ec644SDavid van Moolenbroek 			break;
359040ec644SDavid van Moolenbroek 		free(pwd);
360040ec644SDavid van Moolenbroek 		if ((e = ftp_cmd(conn, "CDUP\r\n")) != FTP_FILE_ACTION_OK ||
361040ec644SDavid van Moolenbroek 		    (e = ftp_cmd(conn, "PWD\r\n")) != FTP_WORKING_DIRECTORY ||
362040ec644SDavid van Moolenbroek 		    (e = ftp_pwd(conn, &pwd)) != FTP_OK) {
363040ec644SDavid van Moolenbroek 			ftp_seterr(e);
364040ec644SDavid van Moolenbroek 			free(dst);
365040ec644SDavid van Moolenbroek 			return (-1);
366040ec644SDavid van Moolenbroek 		}
367040ec644SDavid van Moolenbroek 	}
368040ec644SDavid van Moolenbroek 	free(pwd);
369040ec644SDavid van Moolenbroek 
370040ec644SDavid van Moolenbroek #ifdef FTP_COMBINE_CWDS
371040ec644SDavid van Moolenbroek 	/* Skip leading slashes, even "////". */
372040ec644SDavid van Moolenbroek 	for (beg = dst + i; beg < end && *beg == '/'; ++beg, ++i)
373040ec644SDavid van Moolenbroek 		/* nothing */ ;
374040ec644SDavid van Moolenbroek 
375040ec644SDavid van Moolenbroek 	/* If there is no trailing dir, we're already there. */
376040ec644SDavid van Moolenbroek 	if (beg >= end) {
377040ec644SDavid van Moolenbroek 		free(dst);
378040ec644SDavid van Moolenbroek 		return (0);
379040ec644SDavid van Moolenbroek 	}
380040ec644SDavid van Moolenbroek 
381040ec644SDavid van Moolenbroek 	/* Change to the directory all in one chunk (e.g., foo/bar/baz). */
382040ec644SDavid van Moolenbroek 	e = ftp_cmd(conn, "CWD %.*s\r\n", (int)(end - beg), beg);
383040ec644SDavid van Moolenbroek 	if (e == FTP_FILE_ACTION_OK) {
384040ec644SDavid van Moolenbroek 		free(dst);
385040ec644SDavid van Moolenbroek 		return (0);
386040ec644SDavid van Moolenbroek 	}
387040ec644SDavid van Moolenbroek #endif /* FTP_COMBINE_CWDS */
388040ec644SDavid van Moolenbroek 
389040ec644SDavid van Moolenbroek 	/* That didn't work so go back to legacy behavior (multiple CWDs). */
390040ec644SDavid van Moolenbroek 	for (beg = dst + i; beg < end; beg = dst + i + 1) {
391040ec644SDavid van Moolenbroek 		while (*beg == '/')
392040ec644SDavid van Moolenbroek 			++beg, ++i;
393040ec644SDavid van Moolenbroek 		for (++i; dst + i < end && dst[i] != '/'; ++i)
394040ec644SDavid van Moolenbroek 			/* nothing */ ;
395*0a6a1f1dSLionel Sambuc 		e = ftp_cmd(conn, "CWD %.*s\r\n", (int)(dst + i - beg), beg);
396040ec644SDavid van Moolenbroek 		if (e != FTP_FILE_ACTION_OK) {
397040ec644SDavid van Moolenbroek 			free(dst);
398040ec644SDavid van Moolenbroek 			ftp_seterr(e);
399040ec644SDavid van Moolenbroek 			return (-1);
400040ec644SDavid van Moolenbroek 		}
401040ec644SDavid van Moolenbroek 	}
402040ec644SDavid van Moolenbroek 	free(dst);
403040ec644SDavid van Moolenbroek 	return (0);
404040ec644SDavid van Moolenbroek }
405040ec644SDavid van Moolenbroek 
406040ec644SDavid van Moolenbroek /*
407040ec644SDavid van Moolenbroek  * Set transfer mode and data type
408040ec644SDavid van Moolenbroek  */
409040ec644SDavid van Moolenbroek static int
ftp_mode_type(conn_t * conn,int mode,int type)410040ec644SDavid van Moolenbroek ftp_mode_type(conn_t *conn, int mode, int type)
411040ec644SDavid van Moolenbroek {
412040ec644SDavid van Moolenbroek 	int e;
413040ec644SDavid van Moolenbroek 
414040ec644SDavid van Moolenbroek 	switch (mode) {
415040ec644SDavid van Moolenbroek 	case 0:
416040ec644SDavid van Moolenbroek 	case 's':
417040ec644SDavid van Moolenbroek 		mode = 'S';
418040ec644SDavid van Moolenbroek 		/*FALLTHROUGH*/
419040ec644SDavid van Moolenbroek 	case 'S':
420040ec644SDavid van Moolenbroek 		break;
421040ec644SDavid van Moolenbroek 	default:
422040ec644SDavid van Moolenbroek 		return (FTP_PROTOCOL_ERROR);
423040ec644SDavid van Moolenbroek 	}
424040ec644SDavid van Moolenbroek 	if ((e = ftp_cmd(conn, "MODE %c\r\n", mode)) != FTP_OK) {
425040ec644SDavid van Moolenbroek 		if (mode == 'S') {
426040ec644SDavid van Moolenbroek 			/*
427040ec644SDavid van Moolenbroek 			 * Stream mode is supposed to be the default - so
428040ec644SDavid van Moolenbroek 			 * much so that some servers not only do not
429040ec644SDavid van Moolenbroek 			 * support any other mode, but do not support the
430040ec644SDavid van Moolenbroek 			 * MODE command at all.
431040ec644SDavid van Moolenbroek 			 *
432040ec644SDavid van Moolenbroek 			 * If "MODE S" fails, it is unlikely that we
433040ec644SDavid van Moolenbroek 			 * previously succeeded in setting a different
434040ec644SDavid van Moolenbroek 			 * mode.  Therefore, we simply hope that the
435040ec644SDavid van Moolenbroek 			 * server is already in the correct mode, and
436040ec644SDavid van Moolenbroek 			 * silently ignore the failure.
437040ec644SDavid van Moolenbroek 			 */
438040ec644SDavid van Moolenbroek 		} else {
439040ec644SDavid van Moolenbroek 			return (e);
440040ec644SDavid van Moolenbroek 		}
441040ec644SDavid van Moolenbroek 	}
442040ec644SDavid van Moolenbroek 
443040ec644SDavid van Moolenbroek 	switch (type) {
444040ec644SDavid van Moolenbroek 	case 0:
445040ec644SDavid van Moolenbroek 	case 'i':
446040ec644SDavid van Moolenbroek 		type = 'I';
447040ec644SDavid van Moolenbroek 		/*FALLTHROUGH*/
448040ec644SDavid van Moolenbroek 	case 'I':
449040ec644SDavid van Moolenbroek 		break;
450040ec644SDavid van Moolenbroek 	case 'a':
451040ec644SDavid van Moolenbroek 		type = 'A';
452040ec644SDavid van Moolenbroek 		/*FALLTHROUGH*/
453040ec644SDavid van Moolenbroek 	case 'A':
454040ec644SDavid van Moolenbroek 		break;
455040ec644SDavid van Moolenbroek 	case 'd':
456040ec644SDavid van Moolenbroek 		type = 'D';
457040ec644SDavid van Moolenbroek 		/*FALLTHROUGH*/
458040ec644SDavid van Moolenbroek 	case 'D':
459040ec644SDavid van Moolenbroek 		/* can't handle yet */
460040ec644SDavid van Moolenbroek 	default:
461040ec644SDavid van Moolenbroek 		return (FTP_PROTOCOL_ERROR);
462040ec644SDavid van Moolenbroek 	}
463040ec644SDavid van Moolenbroek 	if ((e = ftp_cmd(conn, "TYPE %c\r\n", type)) != FTP_OK)
464040ec644SDavid van Moolenbroek 		return (e);
465040ec644SDavid van Moolenbroek 
466040ec644SDavid van Moolenbroek 	return (FTP_OK);
467040ec644SDavid van Moolenbroek }
468040ec644SDavid van Moolenbroek 
469040ec644SDavid van Moolenbroek /*
470040ec644SDavid van Moolenbroek  * Request and parse file stats
471040ec644SDavid van Moolenbroek  */
472040ec644SDavid van Moolenbroek static int
ftp_stat(conn_t * conn,const char * file,struct url_stat * us)473040ec644SDavid van Moolenbroek ftp_stat(conn_t *conn, const char *file, struct url_stat *us)
474040ec644SDavid van Moolenbroek {
475040ec644SDavid van Moolenbroek 	char *ln;
476040ec644SDavid van Moolenbroek 	const char *filename;
477040ec644SDavid van Moolenbroek 	size_t filenamelen;
478040ec644SDavid van Moolenbroek 	int type;
479040ec644SDavid van Moolenbroek 	struct tm tm;
480040ec644SDavid van Moolenbroek 	time_t t;
481040ec644SDavid van Moolenbroek 	int e;
482040ec644SDavid van Moolenbroek 
483040ec644SDavid van Moolenbroek 	us->size = -1;
484040ec644SDavid van Moolenbroek 	us->atime = us->mtime = 0;
485040ec644SDavid van Moolenbroek 
486040ec644SDavid van Moolenbroek 	filename = ftp_filename(file, &filenamelen, &type, 0);
487040ec644SDavid van Moolenbroek 
488040ec644SDavid van Moolenbroek 	if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK) {
489040ec644SDavid van Moolenbroek 		ftp_seterr(e);
490040ec644SDavid van Moolenbroek 		return (-1);
491040ec644SDavid van Moolenbroek 	}
492040ec644SDavid van Moolenbroek 
493*0a6a1f1dSLionel Sambuc 	e = ftp_cmd(conn, "SIZE %.*s\r\n", (int)filenamelen, filename);
494040ec644SDavid van Moolenbroek 	if (e != FTP_FILE_STATUS) {
495040ec644SDavid van Moolenbroek 		ftp_seterr(e);
496040ec644SDavid van Moolenbroek 		return (-1);
497040ec644SDavid van Moolenbroek 	}
498040ec644SDavid van Moolenbroek 	for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
499040ec644SDavid van Moolenbroek 		/* nothing */ ;
500040ec644SDavid van Moolenbroek 	for (us->size = 0; *ln && isdigit((unsigned char)*ln); ln++)
501040ec644SDavid van Moolenbroek 		us->size = us->size * 10 + *ln - '0';
502040ec644SDavid van Moolenbroek 	if (*ln && !isspace((unsigned char)*ln)) {
503040ec644SDavid van Moolenbroek 		ftp_seterr(FTP_PROTOCOL_ERROR);
504040ec644SDavid van Moolenbroek 		us->size = -1;
505040ec644SDavid van Moolenbroek 		return (-1);
506040ec644SDavid van Moolenbroek 	}
507040ec644SDavid van Moolenbroek 	if (us->size == 0)
508040ec644SDavid van Moolenbroek 		us->size = -1;
509040ec644SDavid van Moolenbroek 
510*0a6a1f1dSLionel Sambuc 	e = ftp_cmd(conn, "MDTM %.*s\r\n", (int)filenamelen, filename);
511040ec644SDavid van Moolenbroek 	if (e != FTP_FILE_STATUS) {
512040ec644SDavid van Moolenbroek 		ftp_seterr(e);
513040ec644SDavid van Moolenbroek 		return (-1);
514040ec644SDavid van Moolenbroek 	}
515040ec644SDavid van Moolenbroek 	for (ln = conn->buf + 4; *ln && isspace((unsigned char)*ln); ln++)
516040ec644SDavid van Moolenbroek 		/* nothing */ ;
517040ec644SDavid van Moolenbroek 	switch (strspn(ln, "0123456789")) {
518040ec644SDavid van Moolenbroek 	case 14:
519040ec644SDavid van Moolenbroek 		break;
520040ec644SDavid van Moolenbroek 	case 15:
521040ec644SDavid van Moolenbroek 		ln++;
522040ec644SDavid van Moolenbroek 		ln[0] = '2';
523040ec644SDavid van Moolenbroek 		ln[1] = '0';
524040ec644SDavid van Moolenbroek 		break;
525040ec644SDavid van Moolenbroek 	default:
526040ec644SDavid van Moolenbroek 		ftp_seterr(FTP_PROTOCOL_ERROR);
527040ec644SDavid van Moolenbroek 		return (-1);
528040ec644SDavid van Moolenbroek 	}
529040ec644SDavid van Moolenbroek 	if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
530040ec644SDavid van Moolenbroek 	    &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
531040ec644SDavid van Moolenbroek 	    &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
532040ec644SDavid van Moolenbroek 		ftp_seterr(FTP_PROTOCOL_ERROR);
533040ec644SDavid van Moolenbroek 		return (-1);
534040ec644SDavid van Moolenbroek 	}
535040ec644SDavid van Moolenbroek 	tm.tm_mon--;
536040ec644SDavid van Moolenbroek 	tm.tm_year -= 1900;
537040ec644SDavid van Moolenbroek 	tm.tm_isdst = -1;
538040ec644SDavid van Moolenbroek 	t = timegm(&tm);
539040ec644SDavid van Moolenbroek 	if (t == (time_t)-1)
540040ec644SDavid van Moolenbroek 		t = time(NULL);
541040ec644SDavid van Moolenbroek 	us->mtime = t;
542040ec644SDavid van Moolenbroek 	us->atime = t;
543040ec644SDavid van Moolenbroek 
544040ec644SDavid van Moolenbroek 	return (0);
545040ec644SDavid van Moolenbroek }
546040ec644SDavid van Moolenbroek 
547040ec644SDavid van Moolenbroek /*
548040ec644SDavid van Moolenbroek  * I/O functions for FTP
549040ec644SDavid van Moolenbroek  */
550040ec644SDavid van Moolenbroek struct ftpio {
551040ec644SDavid van Moolenbroek 	conn_t	*cconn;		/* Control connection */
552040ec644SDavid van Moolenbroek 	conn_t	*dconn;		/* Data connection */
553040ec644SDavid van Moolenbroek 	int	 dir;		/* Direction */
554040ec644SDavid van Moolenbroek 	int	 eof;		/* EOF reached */
555040ec644SDavid van Moolenbroek 	int	 err;		/* Error code */
556040ec644SDavid van Moolenbroek };
557040ec644SDavid van Moolenbroek 
558040ec644SDavid van Moolenbroek static ssize_t	 ftp_readfn(void *, void *, size_t);
559040ec644SDavid van Moolenbroek static ssize_t	 ftp_writefn(void *, const void *, size_t);
560040ec644SDavid van Moolenbroek static void	 ftp_closefn(void *);
561040ec644SDavid van Moolenbroek 
562040ec644SDavid van Moolenbroek static ssize_t
ftp_readfn(void * v,void * buf,size_t len)563040ec644SDavid van Moolenbroek ftp_readfn(void *v, void *buf, size_t len)
564040ec644SDavid van Moolenbroek {
565040ec644SDavid van Moolenbroek 	struct ftpio *io;
566040ec644SDavid van Moolenbroek 	ssize_t r;
567040ec644SDavid van Moolenbroek 
568040ec644SDavid van Moolenbroek 	io = (struct ftpio *)v;
569040ec644SDavid van Moolenbroek 	if (io == NULL) {
570040ec644SDavid van Moolenbroek 		errno = EBADF;
571040ec644SDavid van Moolenbroek 		return (-1);
572040ec644SDavid van Moolenbroek 	}
573040ec644SDavid van Moolenbroek 	if (io->cconn == NULL || io->dconn == NULL || io->dir == O_WRONLY) {
574040ec644SDavid van Moolenbroek 		errno = EBADF;
575040ec644SDavid van Moolenbroek 		return (-1);
576040ec644SDavid van Moolenbroek 	}
577040ec644SDavid van Moolenbroek 	if (io->err) {
578040ec644SDavid van Moolenbroek 		errno = io->err;
579040ec644SDavid van Moolenbroek 		return (-1);
580040ec644SDavid van Moolenbroek 	}
581040ec644SDavid van Moolenbroek 	if (io->eof)
582040ec644SDavid van Moolenbroek 		return (0);
583040ec644SDavid van Moolenbroek 	r = fetch_read(io->dconn, buf, len);
584040ec644SDavid van Moolenbroek 	if (r > 0)
585040ec644SDavid van Moolenbroek 		return (r);
586040ec644SDavid van Moolenbroek 	if (r == 0) {
587040ec644SDavid van Moolenbroek 		io->eof = 1;
588040ec644SDavid van Moolenbroek 		return (0);
589040ec644SDavid van Moolenbroek 	}
590040ec644SDavid van Moolenbroek 	if (errno != EINTR)
591040ec644SDavid van Moolenbroek 		io->err = errno;
592040ec644SDavid van Moolenbroek 	return (-1);
593040ec644SDavid van Moolenbroek }
594040ec644SDavid van Moolenbroek 
595040ec644SDavid van Moolenbroek static ssize_t
ftp_writefn(void * v,const void * buf,size_t len)596040ec644SDavid van Moolenbroek ftp_writefn(void *v, const void *buf, size_t len)
597040ec644SDavid van Moolenbroek {
598040ec644SDavid van Moolenbroek 	struct ftpio *io;
599040ec644SDavid van Moolenbroek 	ssize_t w;
600040ec644SDavid van Moolenbroek 
601040ec644SDavid van Moolenbroek 	io = (struct ftpio *)v;
602040ec644SDavid van Moolenbroek 	if (io == NULL) {
603040ec644SDavid van Moolenbroek 		errno = EBADF;
604040ec644SDavid van Moolenbroek 		return (-1);
605040ec644SDavid van Moolenbroek 	}
606040ec644SDavid van Moolenbroek 	if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) {
607040ec644SDavid van Moolenbroek 		errno = EBADF;
608040ec644SDavid van Moolenbroek 		return (-1);
609040ec644SDavid van Moolenbroek 	}
610040ec644SDavid van Moolenbroek 	if (io->err) {
611040ec644SDavid van Moolenbroek 		errno = io->err;
612040ec644SDavid van Moolenbroek 		return (-1);
613040ec644SDavid van Moolenbroek 	}
614040ec644SDavid van Moolenbroek 	w = fetch_write(io->dconn, buf, len);
615040ec644SDavid van Moolenbroek 	if (w >= 0)
616040ec644SDavid van Moolenbroek 		return (w);
617040ec644SDavid van Moolenbroek 	if (errno != EINTR)
618040ec644SDavid van Moolenbroek 		io->err = errno;
619040ec644SDavid van Moolenbroek 	return (-1);
620040ec644SDavid van Moolenbroek }
621040ec644SDavid van Moolenbroek 
622040ec644SDavid van Moolenbroek static int
ftp_disconnect(conn_t * conn)623040ec644SDavid van Moolenbroek ftp_disconnect(conn_t *conn)
624040ec644SDavid van Moolenbroek {
625040ec644SDavid van Moolenbroek 	ftp_cmd(conn, "QUIT\r\n");
626040ec644SDavid van Moolenbroek 	return fetch_close(conn);
627040ec644SDavid van Moolenbroek }
628040ec644SDavid van Moolenbroek 
629040ec644SDavid van Moolenbroek static void
ftp_closefn(void * v)630040ec644SDavid van Moolenbroek ftp_closefn(void *v)
631040ec644SDavid van Moolenbroek {
632040ec644SDavid van Moolenbroek 	struct ftpio *io;
633040ec644SDavid van Moolenbroek 
634040ec644SDavid van Moolenbroek 	io = (struct ftpio *)v;
635040ec644SDavid van Moolenbroek 	if (io == NULL) {
636040ec644SDavid van Moolenbroek 		errno = EBADF;
637040ec644SDavid van Moolenbroek 		return;
638040ec644SDavid van Moolenbroek 	}
639040ec644SDavid van Moolenbroek 	if (io->dir == -1)
640040ec644SDavid van Moolenbroek 		return;
641040ec644SDavid van Moolenbroek 	if (io->cconn == NULL || io->dconn == NULL) {
642040ec644SDavid van Moolenbroek 		errno = EBADF;
643040ec644SDavid van Moolenbroek 		return;
644040ec644SDavid van Moolenbroek 	}
645040ec644SDavid van Moolenbroek 	fetch_close(io->dconn);
646040ec644SDavid van Moolenbroek 	io->dconn = NULL;
647040ec644SDavid van Moolenbroek 	io->dir = -1;
648040ec644SDavid van Moolenbroek 	(void)ftp_chkerr(io->cconn);
649040ec644SDavid van Moolenbroek 	fetch_cache_put(io->cconn, ftp_disconnect);
650040ec644SDavid van Moolenbroek 	free(io);
651040ec644SDavid van Moolenbroek 	return;
652040ec644SDavid van Moolenbroek }
653040ec644SDavid van Moolenbroek 
654040ec644SDavid van Moolenbroek static fetchIO *
ftp_setup(conn_t * cconn,conn_t * dconn,int mode)655040ec644SDavid van Moolenbroek ftp_setup(conn_t *cconn, conn_t *dconn, int mode)
656040ec644SDavid van Moolenbroek {
657040ec644SDavid van Moolenbroek 	struct ftpio *io;
658040ec644SDavid van Moolenbroek 	fetchIO *f;
659040ec644SDavid van Moolenbroek 
660040ec644SDavid van Moolenbroek 	if (cconn == NULL || dconn == NULL)
661040ec644SDavid van Moolenbroek 		return (NULL);
662040ec644SDavid van Moolenbroek 	if ((io = malloc(sizeof(*io))) == NULL)
663040ec644SDavid van Moolenbroek 		return (NULL);
664040ec644SDavid van Moolenbroek 	io->cconn = cconn;
665040ec644SDavid van Moolenbroek 	io->dconn = dconn;
666040ec644SDavid van Moolenbroek 	io->dir = mode;
667040ec644SDavid van Moolenbroek 	io->eof = io->err = 0;
668040ec644SDavid van Moolenbroek 	f = fetchIO_unopen(io, ftp_readfn, ftp_writefn, ftp_closefn);
669040ec644SDavid van Moolenbroek 	if (f == NULL)
670040ec644SDavid van Moolenbroek 		free(io);
671040ec644SDavid van Moolenbroek 	return (f);
672040ec644SDavid van Moolenbroek }
673040ec644SDavid van Moolenbroek 
674040ec644SDavid van Moolenbroek /*
675040ec644SDavid van Moolenbroek  * Transfer file
676040ec644SDavid van Moolenbroek  */
677040ec644SDavid van Moolenbroek static fetchIO *
ftp_transfer(conn_t * conn,const char * oper,const char * file,const char * op_arg,int mode,off_t offset,const char * flags)678040ec644SDavid van Moolenbroek ftp_transfer(conn_t *conn, const char *oper, const char *file, const char *op_arg,
679040ec644SDavid van Moolenbroek     int mode, off_t offset, const char *flags)
680040ec644SDavid van Moolenbroek {
681040ec644SDavid van Moolenbroek 	union anonymous {
682040ec644SDavid van Moolenbroek 		struct sockaddr_storage ss;
683040ec644SDavid van Moolenbroek 		struct sockaddr sa;
684040ec644SDavid van Moolenbroek 		struct sockaddr_in6 sin6;
685040ec644SDavid van Moolenbroek 		struct sockaddr_in sin4;
686040ec644SDavid van Moolenbroek 	} u;
687040ec644SDavid van Moolenbroek 	const char *bindaddr;
688040ec644SDavid van Moolenbroek 	const char *filename;
689040ec644SDavid van Moolenbroek 	size_t filenamelen;
690040ec644SDavid van Moolenbroek 	int type;
691040ec644SDavid van Moolenbroek 	int low, pasv, verbose;
692040ec644SDavid van Moolenbroek 	int e, sd = -1;
693040ec644SDavid van Moolenbroek 	socklen_t l;
694040ec644SDavid van Moolenbroek 	char *s;
695040ec644SDavid van Moolenbroek 	fetchIO *df;
696040ec644SDavid van Moolenbroek 
697040ec644SDavid van Moolenbroek 	/* check flags */
698040ec644SDavid van Moolenbroek 	low = CHECK_FLAG('l');
699040ec644SDavid van Moolenbroek 	pasv = !CHECK_FLAG('a');
700040ec644SDavid van Moolenbroek 	verbose = CHECK_FLAG('v');
701040ec644SDavid van Moolenbroek 
702040ec644SDavid van Moolenbroek 	/* passive mode */
703040ec644SDavid van Moolenbroek 	if (!pasv)
704040ec644SDavid van Moolenbroek 		pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL &&
705040ec644SDavid van Moolenbroek 		    strncasecmp(s, "no", 2) != 0);
706040ec644SDavid van Moolenbroek 
707040ec644SDavid van Moolenbroek 	/* isolate filename */
708040ec644SDavid van Moolenbroek 	filename = ftp_filename(file, &filenamelen, &type, op_arg != NULL);
709040ec644SDavid van Moolenbroek 
710040ec644SDavid van Moolenbroek 	/* set transfer mode and data type */
711040ec644SDavid van Moolenbroek 	if ((e = ftp_mode_type(conn, 0, type)) != FTP_OK)
712040ec644SDavid van Moolenbroek 		goto ouch;
713040ec644SDavid van Moolenbroek 
714040ec644SDavid van Moolenbroek 	/* find our own address, bind, and listen */
715040ec644SDavid van Moolenbroek 	l = sizeof(u.ss);
716040ec644SDavid van Moolenbroek 	if (getsockname(conn->sd, &u.sa, &l) == -1)
717040ec644SDavid van Moolenbroek 		goto sysouch;
718040ec644SDavid van Moolenbroek 	if (u.ss.ss_family == AF_INET6)
719040ec644SDavid van Moolenbroek 		unmappedaddr(&u.sin6, &l);
720040ec644SDavid van Moolenbroek 
721040ec644SDavid van Moolenbroek retry_mode:
722040ec644SDavid van Moolenbroek 
723040ec644SDavid van Moolenbroek 	/* open data socket */
724040ec644SDavid van Moolenbroek 	if ((sd = socket(u.ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
725040ec644SDavid van Moolenbroek 		fetch_syserr();
726040ec644SDavid van Moolenbroek 		return (NULL);
727040ec644SDavid van Moolenbroek 	}
728040ec644SDavid van Moolenbroek 
729040ec644SDavid van Moolenbroek 	if (pasv) {
730040ec644SDavid van Moolenbroek 		unsigned char addr[64];
731040ec644SDavid van Moolenbroek 		char *ln, *p;
732040ec644SDavid van Moolenbroek 		unsigned int i;
733040ec644SDavid van Moolenbroek 		int port;
734040ec644SDavid van Moolenbroek 
735040ec644SDavid van Moolenbroek 		/* send PASV command */
736040ec644SDavid van Moolenbroek 		if (verbose)
737040ec644SDavid van Moolenbroek 			fetch_info("setting passive mode");
738040ec644SDavid van Moolenbroek 		switch (u.ss.ss_family) {
739040ec644SDavid van Moolenbroek 		case AF_INET:
740040ec644SDavid van Moolenbroek 			if ((e = ftp_cmd(conn, "PASV\r\n")) != FTP_PASSIVE_MODE)
741040ec644SDavid van Moolenbroek 				goto ouch;
742040ec644SDavid van Moolenbroek 			break;
743040ec644SDavid van Moolenbroek 		case AF_INET6:
744040ec644SDavid van Moolenbroek 			if ((e = ftp_cmd(conn, "EPSV\r\n")) != FTP_EPASSIVE_MODE) {
745040ec644SDavid van Moolenbroek 				if (e == -1)
746040ec644SDavid van Moolenbroek 					goto ouch;
747040ec644SDavid van Moolenbroek 				if ((e = ftp_cmd(conn, "LPSV\r\n")) !=
748040ec644SDavid van Moolenbroek 				    FTP_LPASSIVE_MODE)
749040ec644SDavid van Moolenbroek 					goto ouch;
750040ec644SDavid van Moolenbroek 			}
751040ec644SDavid van Moolenbroek 			break;
752040ec644SDavid van Moolenbroek 		default:
753040ec644SDavid van Moolenbroek 			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
754040ec644SDavid van Moolenbroek 			goto ouch;
755040ec644SDavid van Moolenbroek 		}
756040ec644SDavid van Moolenbroek 
757040ec644SDavid van Moolenbroek 		/*
758040ec644SDavid van Moolenbroek 		 * Find address and port number. The reply to the PASV command
759040ec644SDavid van Moolenbroek 		 * is IMHO the one and only weak point in the FTP protocol.
760040ec644SDavid van Moolenbroek 		 */
761040ec644SDavid van Moolenbroek 		ln = conn->buf;
762040ec644SDavid van Moolenbroek 		switch (e) {
763040ec644SDavid van Moolenbroek 		case FTP_PASSIVE_MODE:
764040ec644SDavid van Moolenbroek 		case FTP_LPASSIVE_MODE:
765040ec644SDavid van Moolenbroek 			for (p = ln + 3; *p && !isdigit((unsigned char)*p); p++)
766040ec644SDavid van Moolenbroek 				/* nothing */ ;
767040ec644SDavid van Moolenbroek 			if (!*p) {
768040ec644SDavid van Moolenbroek 				e = FTP_PROTOCOL_ERROR;
769040ec644SDavid van Moolenbroek 				goto ouch;
770040ec644SDavid van Moolenbroek 			}
771040ec644SDavid van Moolenbroek 			l = (e == FTP_PASSIVE_MODE ? 6 : 21);
772040ec644SDavid van Moolenbroek 			for (i = 0; *p && i < l; i++, p++)
773040ec644SDavid van Moolenbroek 				addr[i] = (unsigned char)strtol(p, &p, 10);
774040ec644SDavid van Moolenbroek 			if (i < l) {
775040ec644SDavid van Moolenbroek 				e = FTP_PROTOCOL_ERROR;
776040ec644SDavid van Moolenbroek 				goto ouch;
777040ec644SDavid van Moolenbroek 			}
778040ec644SDavid van Moolenbroek 			break;
779040ec644SDavid van Moolenbroek 		case FTP_EPASSIVE_MODE:
780040ec644SDavid van Moolenbroek 			for (p = ln + 3; *p && *p != '('; p++)
781040ec644SDavid van Moolenbroek 				/* nothing */ ;
782040ec644SDavid van Moolenbroek 			if (!*p) {
783040ec644SDavid van Moolenbroek 				e = FTP_PROTOCOL_ERROR;
784040ec644SDavid van Moolenbroek 				goto ouch;
785040ec644SDavid van Moolenbroek 			}
786040ec644SDavid van Moolenbroek 			++p;
787040ec644SDavid van Moolenbroek 			if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
788040ec644SDavid van Moolenbroek 				&port, &addr[3]) != 5 ||
789040ec644SDavid van Moolenbroek 			    addr[0] != addr[1] ||
790040ec644SDavid van Moolenbroek 			    addr[0] != addr[2] || addr[0] != addr[3]) {
791040ec644SDavid van Moolenbroek 				e = FTP_PROTOCOL_ERROR;
792040ec644SDavid van Moolenbroek 				goto ouch;
793040ec644SDavid van Moolenbroek 			}
794040ec644SDavid van Moolenbroek 			break;
795040ec644SDavid van Moolenbroek 		case FTP_SYNTAX_ERROR:
796040ec644SDavid van Moolenbroek 			if (verbose)
797040ec644SDavid van Moolenbroek 				fetch_info("passive mode failed");
798040ec644SDavid van Moolenbroek 			/* Close socket and retry with passive mode. */
799040ec644SDavid van Moolenbroek 			pasv = 0;
800040ec644SDavid van Moolenbroek 			close(sd);
801040ec644SDavid van Moolenbroek 			sd = -1;
802040ec644SDavid van Moolenbroek 			goto retry_mode;
803040ec644SDavid van Moolenbroek 		}
804040ec644SDavid van Moolenbroek 
805040ec644SDavid van Moolenbroek 		/* seek to required offset */
806040ec644SDavid van Moolenbroek 		if (offset)
807040ec644SDavid van Moolenbroek 			if (ftp_cmd(conn, "REST %lu\r\n", (unsigned long)offset) != FTP_FILE_OK)
808040ec644SDavid van Moolenbroek 				goto sysouch;
809040ec644SDavid van Moolenbroek 
810040ec644SDavid van Moolenbroek 		/* construct sockaddr for data socket */
811040ec644SDavid van Moolenbroek 		l = sizeof(u.ss);
812040ec644SDavid van Moolenbroek 		if (getpeername(conn->sd, &u.sa, &l) == -1)
813040ec644SDavid van Moolenbroek 			goto sysouch;
814040ec644SDavid van Moolenbroek 		if (u.ss.ss_family == AF_INET6)
815040ec644SDavid van Moolenbroek 			unmappedaddr(&u.sin6, &l);
816040ec644SDavid van Moolenbroek 		switch (u.ss.ss_family) {
817040ec644SDavid van Moolenbroek 		case AF_INET6:
818040ec644SDavid van Moolenbroek 			if (e == FTP_EPASSIVE_MODE)
819040ec644SDavid van Moolenbroek 				u.sin6.sin6_port = htons(port);
820040ec644SDavid van Moolenbroek 			else {
821040ec644SDavid van Moolenbroek 				memcpy(&u.sin6.sin6_addr, addr + 2, 16);
822040ec644SDavid van Moolenbroek 				memcpy(&u.sin6.sin6_port, addr + 19, 2);
823040ec644SDavid van Moolenbroek 			}
824040ec644SDavid van Moolenbroek 			break;
825040ec644SDavid van Moolenbroek 		case AF_INET:
826040ec644SDavid van Moolenbroek 			if (e == FTP_EPASSIVE_MODE)
827040ec644SDavid van Moolenbroek 				u.sin4.sin_port = htons(port);
828040ec644SDavid van Moolenbroek 			else {
829040ec644SDavid van Moolenbroek 				memcpy(&u.sin4.sin_addr, addr, 4);
830040ec644SDavid van Moolenbroek 				memcpy(&u.sin4.sin_port, addr + 4, 2);
831040ec644SDavid van Moolenbroek 			}
832040ec644SDavid van Moolenbroek 			break;
833040ec644SDavid van Moolenbroek 		default:
834040ec644SDavid van Moolenbroek 			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
835040ec644SDavid van Moolenbroek 			break;
836040ec644SDavid van Moolenbroek 		}
837040ec644SDavid van Moolenbroek 
838040ec644SDavid van Moolenbroek 		/* connect to data port */
839040ec644SDavid van Moolenbroek 		if (verbose)
840040ec644SDavid van Moolenbroek 			fetch_info("opening data connection");
841040ec644SDavid van Moolenbroek 		bindaddr = getenv("FETCH_BIND_ADDRESS");
842040ec644SDavid van Moolenbroek 		if (bindaddr != NULL && *bindaddr != '\0' &&
843040ec644SDavid van Moolenbroek 		    fetch_bind(sd, u.ss.ss_family, bindaddr) != 0)
844040ec644SDavid van Moolenbroek 			goto sysouch;
845040ec644SDavid van Moolenbroek 		if (connect(sd, &u.sa, l) == -1)
846040ec644SDavid van Moolenbroek 			goto sysouch;
847040ec644SDavid van Moolenbroek 
848040ec644SDavid van Moolenbroek 		/* make the server initiate the transfer */
849040ec644SDavid van Moolenbroek 		if (verbose)
850040ec644SDavid van Moolenbroek 			fetch_info("initiating transfer");
851040ec644SDavid van Moolenbroek 		if (op_arg)
852040ec644SDavid van Moolenbroek 			e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
853040ec644SDavid van Moolenbroek 		else
854040ec644SDavid van Moolenbroek 			e = ftp_cmd(conn, "%s %.*s\r\n", oper,
855*0a6a1f1dSLionel Sambuc 			    (int)filenamelen, filename);
856040ec644SDavid van Moolenbroek 		if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
857040ec644SDavid van Moolenbroek 			goto ouch;
858040ec644SDavid van Moolenbroek 
859040ec644SDavid van Moolenbroek 	} else {
860040ec644SDavid van Moolenbroek 		uint32_t a;
861040ec644SDavid van Moolenbroek 		uint16_t p;
862040ec644SDavid van Moolenbroek #if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE)
863040ec644SDavid van Moolenbroek 		int arg;
864040ec644SDavid van Moolenbroek #endif
865040ec644SDavid van Moolenbroek 		int d;
866040ec644SDavid van Moolenbroek 		char hname[INET6_ADDRSTRLEN];
867040ec644SDavid van Moolenbroek 
868040ec644SDavid van Moolenbroek 		switch (u.ss.ss_family) {
869040ec644SDavid van Moolenbroek 		case AF_INET6:
870040ec644SDavid van Moolenbroek 			u.sin6.sin6_port = 0;
871040ec644SDavid van Moolenbroek #ifdef IPV6_PORTRANGE
872040ec644SDavid van Moolenbroek 			arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH;
873040ec644SDavid van Moolenbroek 			if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE,
874040ec644SDavid van Moolenbroek 				&arg, (socklen_t)sizeof(arg)) == -1)
875040ec644SDavid van Moolenbroek 				goto sysouch;
876040ec644SDavid van Moolenbroek #endif
877040ec644SDavid van Moolenbroek 			break;
878040ec644SDavid van Moolenbroek 		case AF_INET:
879040ec644SDavid van Moolenbroek 			u.sin4.sin_port = 0;
880040ec644SDavid van Moolenbroek #ifdef IP_PORTRANGE
881040ec644SDavid van Moolenbroek 			arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH;
882040ec644SDavid van Moolenbroek 			if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
883040ec644SDavid van Moolenbroek 				&arg, (socklen_t)sizeof(arg)) == -1)
884040ec644SDavid van Moolenbroek 				goto sysouch;
885040ec644SDavid van Moolenbroek #endif
886040ec644SDavid van Moolenbroek 			break;
887040ec644SDavid van Moolenbroek 		}
888040ec644SDavid van Moolenbroek 		if (verbose)
889040ec644SDavid van Moolenbroek 			fetch_info("binding data socket");
890040ec644SDavid van Moolenbroek 		if (bind(sd, &u.sa, l) == -1)
891040ec644SDavid van Moolenbroek 			goto sysouch;
892040ec644SDavid van Moolenbroek 		if (listen(sd, 1) == -1)
893040ec644SDavid van Moolenbroek 			goto sysouch;
894040ec644SDavid van Moolenbroek 
895040ec644SDavid van Moolenbroek 		/* find what port we're on and tell the server */
896040ec644SDavid van Moolenbroek 		if (getsockname(sd, &u.sa, &l) == -1)
897040ec644SDavid van Moolenbroek 			goto sysouch;
898040ec644SDavid van Moolenbroek 		switch (u.ss.ss_family) {
899040ec644SDavid van Moolenbroek 		case AF_INET:
900040ec644SDavid van Moolenbroek 			a = ntohl(u.sin4.sin_addr.s_addr);
901040ec644SDavid van Moolenbroek 			p = ntohs(u.sin4.sin_port);
902040ec644SDavid van Moolenbroek 			e = ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d\r\n",
903040ec644SDavid van Moolenbroek 			    (a >> 24) & 0xff, (a >> 16) & 0xff,
904040ec644SDavid van Moolenbroek 			    (a >> 8) & 0xff, a & 0xff,
905040ec644SDavid van Moolenbroek 			    ((unsigned int)p >> 8) & 0xff, p & 0xff);
906040ec644SDavid van Moolenbroek 			break;
907040ec644SDavid van Moolenbroek 		case AF_INET6:
908040ec644SDavid van Moolenbroek 			e = -1;
909040ec644SDavid van Moolenbroek 			u.sin6.sin6_scope_id = 0;
910040ec644SDavid van Moolenbroek 			if (getnameinfo(&u.sa, l,
911040ec644SDavid van Moolenbroek 				hname, (socklen_t)sizeof(hname),
912040ec644SDavid van Moolenbroek 				NULL, 0, NI_NUMERICHOST) == 0) {
913040ec644SDavid van Moolenbroek 				e = ftp_cmd(conn, "EPRT |%d|%s|%d|\r\n", 2, hname,
914040ec644SDavid van Moolenbroek 				    htons(u.sin6.sin6_port));
915040ec644SDavid van Moolenbroek 				if (e == -1)
916040ec644SDavid van Moolenbroek 					goto ouch;
917040ec644SDavid van Moolenbroek 			}
918040ec644SDavid van Moolenbroek 			if (e != FTP_OK) {
919040ec644SDavid van Moolenbroek 				uint8_t aa[sizeof(u.sin6.sin6_addr)];
920040ec644SDavid van Moolenbroek 				memcpy(aa, &u.sin6.sin6_addr, sizeof(aa));
921040ec644SDavid van Moolenbroek 				p = ntohs(u.sin6.sin6_port);
922040ec644SDavid van Moolenbroek 				e = ftp_cmd(conn,
923040ec644SDavid van Moolenbroek 				    "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\r\n",
924040ec644SDavid van Moolenbroek 				    6, 16,
925040ec644SDavid van Moolenbroek 				    aa[ 0], aa[ 1], aa[ 2], aa[ 3],
926040ec644SDavid van Moolenbroek 				    aa[ 4], aa[ 5], aa[ 6], aa[ 7],
927040ec644SDavid van Moolenbroek 				    aa[ 8], aa[ 9], aa[10], aa[11],
928040ec644SDavid van Moolenbroek 				    aa[12], aa[13], aa[14], aa[15],
929040ec644SDavid van Moolenbroek 				    2,
930040ec644SDavid van Moolenbroek 				    ((unsigned int)p >> 8) & 0xff, p & 0xff);
931040ec644SDavid van Moolenbroek 			}
932040ec644SDavid van Moolenbroek 			break;
933040ec644SDavid van Moolenbroek 		default:
934040ec644SDavid van Moolenbroek 			e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */
935040ec644SDavid van Moolenbroek 			goto ouch;
936040ec644SDavid van Moolenbroek 		}
937040ec644SDavid van Moolenbroek 		if (e != FTP_OK)
938040ec644SDavid van Moolenbroek 			goto ouch;
939040ec644SDavid van Moolenbroek 
940040ec644SDavid van Moolenbroek 		/* seek to required offset */
941040ec644SDavid van Moolenbroek 		if (offset)
942040ec644SDavid van Moolenbroek 			if (ftp_cmd(conn, "REST %llu\r\n", (unsigned long long)offset) != FTP_FILE_OK)
943040ec644SDavid van Moolenbroek 				goto sysouch;
944040ec644SDavid van Moolenbroek 
945040ec644SDavid van Moolenbroek 		/* make the server initiate the transfer */
946040ec644SDavid van Moolenbroek 		if (verbose)
947040ec644SDavid van Moolenbroek 			fetch_info("initiating transfer");
948040ec644SDavid van Moolenbroek 		if (op_arg)
949040ec644SDavid van Moolenbroek 			e = ftp_cmd(conn, "%s%s%s\r\n", oper, *op_arg ? " " : "", op_arg);
950040ec644SDavid van Moolenbroek 		else
951040ec644SDavid van Moolenbroek 			e = ftp_cmd(conn, "%s %.*s\r\n", oper,
952*0a6a1f1dSLionel Sambuc 			    (int)filenamelen, filename);
953040ec644SDavid van Moolenbroek 		if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION)
954040ec644SDavid van Moolenbroek 			goto ouch;
955040ec644SDavid van Moolenbroek 
956040ec644SDavid van Moolenbroek 		/* accept the incoming connection and go to town */
957040ec644SDavid van Moolenbroek 		if ((d = accept(sd, NULL, NULL)) == -1)
958040ec644SDavid van Moolenbroek 			goto sysouch;
959040ec644SDavid van Moolenbroek 		close(sd);
960040ec644SDavid van Moolenbroek 		sd = d;
961040ec644SDavid van Moolenbroek 	}
962040ec644SDavid van Moolenbroek 
963040ec644SDavid van Moolenbroek 	if ((df = ftp_setup(conn, fetch_reopen(sd), mode)) == NULL)
964040ec644SDavid van Moolenbroek 		goto sysouch;
965040ec644SDavid van Moolenbroek 	return (df);
966040ec644SDavid van Moolenbroek 
967040ec644SDavid van Moolenbroek sysouch:
968040ec644SDavid van Moolenbroek 	fetch_syserr();
969040ec644SDavid van Moolenbroek 	if (sd >= 0)
970040ec644SDavid van Moolenbroek 		close(sd);
971040ec644SDavid van Moolenbroek 	return (NULL);
972040ec644SDavid van Moolenbroek 
973040ec644SDavid van Moolenbroek ouch:
974040ec644SDavid van Moolenbroek 	if (e != -1)
975040ec644SDavid van Moolenbroek 		ftp_seterr(e);
976040ec644SDavid van Moolenbroek 	if (sd >= 0)
977040ec644SDavid van Moolenbroek 		close(sd);
978040ec644SDavid van Moolenbroek 	return (NULL);
979040ec644SDavid van Moolenbroek }
980040ec644SDavid van Moolenbroek 
981040ec644SDavid van Moolenbroek /*
982040ec644SDavid van Moolenbroek  * Authenticate
983040ec644SDavid van Moolenbroek  */
984040ec644SDavid van Moolenbroek static int
ftp_authenticate(conn_t * conn,struct url * url,struct url * purl)985040ec644SDavid van Moolenbroek ftp_authenticate(conn_t *conn, struct url *url, struct url *purl)
986040ec644SDavid van Moolenbroek {
987040ec644SDavid van Moolenbroek 	const char *user, *pwd, *login_name;
988040ec644SDavid van Moolenbroek 	char pbuf[URL_USERLEN + 1 + URL_HOSTLEN + 1];
989040ec644SDavid van Moolenbroek 	int e, len;
990040ec644SDavid van Moolenbroek 
991040ec644SDavid van Moolenbroek 	/* XXX FTP_AUTH, and maybe .netrc */
992040ec644SDavid van Moolenbroek 
993040ec644SDavid van Moolenbroek 	/* send user name and password */
994040ec644SDavid van Moolenbroek 	if (url->user[0] == '\0')
995040ec644SDavid van Moolenbroek 		fetch_netrc_auth(url);
996040ec644SDavid van Moolenbroek 	user = url->user;
997040ec644SDavid van Moolenbroek 	if (*user == '\0')
998040ec644SDavid van Moolenbroek 		user = getenv("FTP_LOGIN");
999040ec644SDavid van Moolenbroek 	if (user == NULL || *user == '\0')
1000040ec644SDavid van Moolenbroek 		user = FTP_ANONYMOUS_USER;
1001040ec644SDavid van Moolenbroek 	if (purl && url->port == fetch_default_port(url->scheme))
1002040ec644SDavid van Moolenbroek 		e = ftp_cmd(conn, "USER %s@%s\r\n", user, url->host);
1003040ec644SDavid van Moolenbroek 	else if (purl)
1004040ec644SDavid van Moolenbroek 		e = ftp_cmd(conn, "USER %s@%s@%d\r\n", user, url->host, url->port);
1005040ec644SDavid van Moolenbroek 	else
1006040ec644SDavid van Moolenbroek 		e = ftp_cmd(conn, "USER %s\r\n", user);
1007040ec644SDavid van Moolenbroek 
1008040ec644SDavid van Moolenbroek 	/* did the server request a password? */
1009040ec644SDavid van Moolenbroek 	if (e == FTP_NEED_PASSWORD) {
1010040ec644SDavid van Moolenbroek 		pwd = url->pwd;
1011040ec644SDavid van Moolenbroek 		if (*pwd == '\0')
1012040ec644SDavid van Moolenbroek 			pwd = getenv("FTP_PASSWORD");
1013040ec644SDavid van Moolenbroek 		if (pwd == NULL || *pwd == '\0') {
1014040ec644SDavid van Moolenbroek 			if ((login_name = getlogin()) == 0)
1015040ec644SDavid van Moolenbroek 				login_name = FTP_ANONYMOUS_USER;
1016040ec644SDavid van Moolenbroek 			if ((len = snprintf(pbuf, URL_USERLEN + 2, "%s@", login_name)) < 0)
1017040ec644SDavid van Moolenbroek 				len = 0;
1018040ec644SDavid van Moolenbroek 			else if (len > URL_USERLEN + 1)
1019040ec644SDavid van Moolenbroek 				len = URL_USERLEN + 1;
1020040ec644SDavid van Moolenbroek 			gethostname(pbuf + len, sizeof(pbuf) - len);
1021040ec644SDavid van Moolenbroek 			/* MAXHOSTNAMELEN can differ from URL_HOSTLEN + 1 */
1022040ec644SDavid van Moolenbroek 			pbuf[sizeof(pbuf) - 1] = '\0';
1023040ec644SDavid van Moolenbroek 			pwd = pbuf;
1024040ec644SDavid van Moolenbroek 		}
1025040ec644SDavid van Moolenbroek 		e = ftp_cmd(conn, "PASS %s\r\n", pwd);
1026040ec644SDavid van Moolenbroek 	}
1027040ec644SDavid van Moolenbroek 
1028040ec644SDavid van Moolenbroek 	return (e);
1029040ec644SDavid van Moolenbroek }
1030040ec644SDavid van Moolenbroek 
1031040ec644SDavid van Moolenbroek /*
1032040ec644SDavid van Moolenbroek  * Log on to FTP server
1033040ec644SDavid van Moolenbroek  */
1034040ec644SDavid van Moolenbroek static conn_t *
ftp_connect(struct url * url,struct url * purl,const char * flags)1035040ec644SDavid van Moolenbroek ftp_connect(struct url *url, struct url *purl, const char *flags)
1036040ec644SDavid van Moolenbroek {
1037040ec644SDavid van Moolenbroek 	conn_t *conn;
1038040ec644SDavid van Moolenbroek 	int e, direct, verbose;
1039040ec644SDavid van Moolenbroek #ifdef INET6
1040040ec644SDavid van Moolenbroek 	int af = AF_UNSPEC;
1041040ec644SDavid van Moolenbroek #else
1042040ec644SDavid van Moolenbroek 	int af = AF_INET;
1043040ec644SDavid van Moolenbroek #endif
1044040ec644SDavid van Moolenbroek 
1045040ec644SDavid van Moolenbroek 	direct = CHECK_FLAG('d');
1046040ec644SDavid van Moolenbroek 	verbose = CHECK_FLAG('v');
1047040ec644SDavid van Moolenbroek 	if (CHECK_FLAG('4'))
1048040ec644SDavid van Moolenbroek 		af = AF_INET;
1049040ec644SDavid van Moolenbroek 	else if (CHECK_FLAG('6'))
1050040ec644SDavid van Moolenbroek 		af = AF_INET6;
1051040ec644SDavid van Moolenbroek 
1052040ec644SDavid van Moolenbroek 	if (direct)
1053040ec644SDavid van Moolenbroek 		purl = NULL;
1054040ec644SDavid van Moolenbroek 
1055040ec644SDavid van Moolenbroek 	/* check for proxy */
1056040ec644SDavid van Moolenbroek 	if (purl) {
1057040ec644SDavid van Moolenbroek 		/* XXX proxy authentication! */
1058040ec644SDavid van Moolenbroek 		/* XXX connetion caching */
1059040ec644SDavid van Moolenbroek 		if (!purl->port)
1060040ec644SDavid van Moolenbroek 			purl->port = fetch_default_port(purl->scheme);
1061040ec644SDavid van Moolenbroek 
1062040ec644SDavid van Moolenbroek 		conn = fetch_connect(purl, af, verbose);
1063040ec644SDavid van Moolenbroek 	} else {
1064040ec644SDavid van Moolenbroek 		/* no proxy, go straight to target */
1065040ec644SDavid van Moolenbroek 		if (!url->port)
1066040ec644SDavid van Moolenbroek 			url->port = fetch_default_port(url->scheme);
1067040ec644SDavid van Moolenbroek 
1068040ec644SDavid van Moolenbroek 		while ((conn = fetch_cache_get(url, af)) != NULL) {
1069040ec644SDavid van Moolenbroek 			e = ftp_cmd(conn, "NOOP\r\n");
1070040ec644SDavid van Moolenbroek 			if (e == FTP_OK)
1071040ec644SDavid van Moolenbroek 				return conn;
1072040ec644SDavid van Moolenbroek 			fetch_close(conn);
1073040ec644SDavid van Moolenbroek 		}
1074040ec644SDavid van Moolenbroek 		conn = fetch_connect(url, af, verbose);
1075040ec644SDavid van Moolenbroek 		purl = NULL;
1076040ec644SDavid van Moolenbroek 	}
1077040ec644SDavid van Moolenbroek 
1078040ec644SDavid van Moolenbroek 	/* check connection */
1079040ec644SDavid van Moolenbroek 	if (conn == NULL)
1080040ec644SDavid van Moolenbroek 		/* fetch_connect() has already set an error code */
1081040ec644SDavid van Moolenbroek 		return (NULL);
1082040ec644SDavid van Moolenbroek 
1083040ec644SDavid van Moolenbroek 	/* expect welcome message */
1084040ec644SDavid van Moolenbroek 	if ((e = ftp_chkerr(conn)) != FTP_SERVICE_READY)
1085040ec644SDavid van Moolenbroek 		goto fouch;
1086040ec644SDavid van Moolenbroek 
1087040ec644SDavid van Moolenbroek 	/* authenticate */
1088040ec644SDavid van Moolenbroek 	if ((e = ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN)
1089040ec644SDavid van Moolenbroek 		goto fouch;
1090040ec644SDavid van Moolenbroek 
1091040ec644SDavid van Moolenbroek 	/* TODO: Request extended features supported, if any (RFC 3659). */
1092040ec644SDavid van Moolenbroek 
1093040ec644SDavid van Moolenbroek 	/* done */
1094040ec644SDavid van Moolenbroek 	return (conn);
1095040ec644SDavid van Moolenbroek 
1096040ec644SDavid van Moolenbroek fouch:
1097040ec644SDavid van Moolenbroek 	if (e != -1)
1098040ec644SDavid van Moolenbroek 		ftp_seterr(e);
1099040ec644SDavid van Moolenbroek 	fetch_close(conn);
1100040ec644SDavid van Moolenbroek 	return (NULL);
1101040ec644SDavid van Moolenbroek }
1102040ec644SDavid van Moolenbroek 
1103040ec644SDavid van Moolenbroek /*
1104040ec644SDavid van Moolenbroek  * Check the proxy settings
1105040ec644SDavid van Moolenbroek  */
1106040ec644SDavid van Moolenbroek static struct url *
ftp_get_proxy(struct url * url,const char * flags)1107040ec644SDavid van Moolenbroek ftp_get_proxy(struct url * url, const char *flags)
1108040ec644SDavid van Moolenbroek {
1109040ec644SDavid van Moolenbroek 	struct url *purl;
1110040ec644SDavid van Moolenbroek 	char *p, *fp, *FP, *hp, *HP;
1111040ec644SDavid van Moolenbroek 
1112040ec644SDavid van Moolenbroek 	if (flags != NULL && strchr(flags, 'd') != NULL)
1113040ec644SDavid van Moolenbroek 		return NULL;
1114040ec644SDavid van Moolenbroek 	if (fetch_no_proxy_match(url->host))
1115040ec644SDavid van Moolenbroek 		return NULL;
1116040ec644SDavid van Moolenbroek 
1117040ec644SDavid van Moolenbroek 	FP = getenv("FTP_PROXY");
1118040ec644SDavid van Moolenbroek 	fp = getenv("ftp_proxy");
1119040ec644SDavid van Moolenbroek 	HP = getenv("HTTP_PROXY");
1120040ec644SDavid van Moolenbroek 	hp = getenv("http_proxy");
1121040ec644SDavid van Moolenbroek 
1122040ec644SDavid van Moolenbroek 	if ((((p = FP) || (p = fp) || (p = HP) || (p = hp))) &&
1123040ec644SDavid van Moolenbroek 	    *p && (purl = fetchParseURL(p)) != NULL) {
1124040ec644SDavid van Moolenbroek 		if (!*purl->scheme) {
1125040ec644SDavid van Moolenbroek 			if (fp || FP)
1126040ec644SDavid van Moolenbroek 				strcpy(purl->scheme, SCHEME_FTP);
1127040ec644SDavid van Moolenbroek 			else
1128040ec644SDavid van Moolenbroek 				strcpy(purl->scheme, SCHEME_HTTP);
1129040ec644SDavid van Moolenbroek 		}
1130040ec644SDavid van Moolenbroek 		if (!purl->port)
1131040ec644SDavid van Moolenbroek 			purl->port = fetch_default_proxy_port(purl->scheme);
1132040ec644SDavid van Moolenbroek 		if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 ||
1133040ec644SDavid van Moolenbroek 		    strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
1134040ec644SDavid van Moolenbroek 			return purl;
1135040ec644SDavid van Moolenbroek 		fetchFreeURL(purl);
1136040ec644SDavid van Moolenbroek 	}
1137040ec644SDavid van Moolenbroek 	return NULL;
1138040ec644SDavid van Moolenbroek }
1139040ec644SDavid van Moolenbroek 
1140040ec644SDavid van Moolenbroek /*
1141040ec644SDavid van Moolenbroek  * Process an FTP request
1142040ec644SDavid van Moolenbroek  */
1143040ec644SDavid van Moolenbroek fetchIO *
ftp_request(struct url * url,const char * op,const char * op_arg,struct url_stat * us,struct url * purl,const char * flags)1144040ec644SDavid van Moolenbroek ftp_request(struct url *url, const char *op, const char *op_arg,
1145040ec644SDavid van Moolenbroek     struct url_stat *us, struct url *purl, const char *flags)
1146040ec644SDavid van Moolenbroek {
1147040ec644SDavid van Moolenbroek 	fetchIO *f;
1148040ec644SDavid van Moolenbroek 	char *path;
1149040ec644SDavid van Moolenbroek 	conn_t *conn;
1150040ec644SDavid van Moolenbroek 	int if_modified_since, oflag;
1151040ec644SDavid van Moolenbroek 	struct url_stat local_us;
1152040ec644SDavid van Moolenbroek 
1153040ec644SDavid van Moolenbroek 	/* check if we should use HTTP instead */
1154040ec644SDavid van Moolenbroek 	if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) {
1155040ec644SDavid van Moolenbroek 		if (strcmp(op, "STAT") == 0)
1156040ec644SDavid van Moolenbroek 			return (http_request(url, "HEAD", us, purl, flags));
1157040ec644SDavid van Moolenbroek 		else if (strcmp(op, "RETR") == 0)
1158040ec644SDavid van Moolenbroek 			return (http_request(url, "GET", us, purl, flags));
1159040ec644SDavid van Moolenbroek 		/*
1160040ec644SDavid van Moolenbroek 		 * Our HTTP code doesn't support PUT requests yet, so try
1161040ec644SDavid van Moolenbroek 		 * a direct connection.
1162040ec644SDavid van Moolenbroek 		 */
1163040ec644SDavid van Moolenbroek 	}
1164040ec644SDavid van Moolenbroek 
1165040ec644SDavid van Moolenbroek 	/* connect to server */
1166040ec644SDavid van Moolenbroek 	conn = ftp_connect(url, purl, flags);
1167040ec644SDavid van Moolenbroek 	if (purl)
1168040ec644SDavid van Moolenbroek 		fetchFreeURL(purl);
1169040ec644SDavid van Moolenbroek 	if (conn == NULL)
1170040ec644SDavid van Moolenbroek 		return (NULL);
1171040ec644SDavid van Moolenbroek 
1172040ec644SDavid van Moolenbroek 	if ((path = fetchUnquotePath(url)) == NULL) {
1173040ec644SDavid van Moolenbroek 		fetch_syserr();
1174040ec644SDavid van Moolenbroek 		return NULL;
1175040ec644SDavid van Moolenbroek 	}
1176040ec644SDavid van Moolenbroek 
1177040ec644SDavid van Moolenbroek 	/* change directory */
1178040ec644SDavid van Moolenbroek 	if (ftp_cwd(conn, path, op_arg != NULL) == -1) {
1179040ec644SDavid van Moolenbroek 		free(path);
1180040ec644SDavid van Moolenbroek 		return (NULL);
1181040ec644SDavid van Moolenbroek 	}
1182040ec644SDavid van Moolenbroek 
1183040ec644SDavid van Moolenbroek 	if_modified_since = CHECK_FLAG('i');
1184040ec644SDavid van Moolenbroek 	if (if_modified_since && us == NULL)
1185040ec644SDavid van Moolenbroek 		us = &local_us;
1186040ec644SDavid van Moolenbroek 
1187040ec644SDavid van Moolenbroek 	/* stat file */
1188040ec644SDavid van Moolenbroek 	if (us && ftp_stat(conn, path, us) == -1
1189040ec644SDavid van Moolenbroek 	    && fetchLastErrCode != FETCH_PROTO
1190040ec644SDavid van Moolenbroek 	    && fetchLastErrCode != FETCH_UNAVAIL) {
1191040ec644SDavid van Moolenbroek 		free(path);
1192040ec644SDavid van Moolenbroek 		return (NULL);
1193040ec644SDavid van Moolenbroek 	}
1194040ec644SDavid van Moolenbroek 
1195040ec644SDavid van Moolenbroek 	if (if_modified_since && url->last_modified > 0 &&
1196040ec644SDavid van Moolenbroek 	    url->last_modified >= us->mtime) {
1197040ec644SDavid van Moolenbroek 		free(path);
1198040ec644SDavid van Moolenbroek 		fetchLastErrCode = FETCH_UNCHANGED;
1199040ec644SDavid van Moolenbroek 		snprintf(fetchLastErrString, MAXERRSTRING, "Unchanged");
1200040ec644SDavid van Moolenbroek 		return NULL;
1201040ec644SDavid van Moolenbroek 	}
1202040ec644SDavid van Moolenbroek 
1203040ec644SDavid van Moolenbroek 	/* just a stat */
1204040ec644SDavid van Moolenbroek 	if (strcmp(op, "STAT") == 0) {
1205040ec644SDavid van Moolenbroek 		free(path);
1206040ec644SDavid van Moolenbroek 		return fetchIO_unopen(NULL, NULL, NULL, NULL);
1207040ec644SDavid van Moolenbroek 	}
1208040ec644SDavid van Moolenbroek 	if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0)
1209040ec644SDavid van Moolenbroek 		oflag = O_WRONLY;
1210040ec644SDavid van Moolenbroek 	else
1211040ec644SDavid van Moolenbroek 		oflag = O_RDONLY;
1212040ec644SDavid van Moolenbroek 
1213040ec644SDavid van Moolenbroek 	/* initiate the transfer */
1214040ec644SDavid van Moolenbroek 	f = (ftp_transfer(conn, op, path, op_arg, oflag, url->offset, flags));
1215040ec644SDavid van Moolenbroek 	free(path);
1216040ec644SDavid van Moolenbroek 	return f;
1217040ec644SDavid van Moolenbroek }
1218040ec644SDavid van Moolenbroek 
1219040ec644SDavid van Moolenbroek /*
1220040ec644SDavid van Moolenbroek  * Get and stat file
1221040ec644SDavid van Moolenbroek  */
1222040ec644SDavid van Moolenbroek fetchIO *
fetchXGetFTP(struct url * url,struct url_stat * us,const char * flags)1223040ec644SDavid van Moolenbroek fetchXGetFTP(struct url *url, struct url_stat *us, const char *flags)
1224040ec644SDavid van Moolenbroek {
1225040ec644SDavid van Moolenbroek 	return (ftp_request(url, "RETR", NULL, us, ftp_get_proxy(url, flags), flags));
1226040ec644SDavid van Moolenbroek }
1227040ec644SDavid van Moolenbroek 
1228040ec644SDavid van Moolenbroek /*
1229040ec644SDavid van Moolenbroek  * Get file
1230040ec644SDavid van Moolenbroek  */
1231040ec644SDavid van Moolenbroek fetchIO *
fetchGetFTP(struct url * url,const char * flags)1232040ec644SDavid van Moolenbroek fetchGetFTP(struct url *url, const char *flags)
1233040ec644SDavid van Moolenbroek {
1234040ec644SDavid van Moolenbroek 	return (fetchXGetFTP(url, NULL, flags));
1235040ec644SDavid van Moolenbroek }
1236040ec644SDavid van Moolenbroek 
1237040ec644SDavid van Moolenbroek /*
1238040ec644SDavid van Moolenbroek  * Put file
1239040ec644SDavid van Moolenbroek  */
1240040ec644SDavid van Moolenbroek fetchIO *
fetchPutFTP(struct url * url,const char * flags)1241040ec644SDavid van Moolenbroek fetchPutFTP(struct url *url, const char *flags)
1242040ec644SDavid van Moolenbroek {
1243040ec644SDavid van Moolenbroek 	return (ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, NULL,
1244040ec644SDavid van Moolenbroek 	    ftp_get_proxy(url, flags), flags));
1245040ec644SDavid van Moolenbroek }
1246040ec644SDavid van Moolenbroek 
1247040ec644SDavid van Moolenbroek /*
1248040ec644SDavid van Moolenbroek  * Get file stats
1249040ec644SDavid van Moolenbroek  */
1250040ec644SDavid van Moolenbroek int
fetchStatFTP(struct url * url,struct url_stat * us,const char * flags)1251040ec644SDavid van Moolenbroek fetchStatFTP(struct url *url, struct url_stat *us, const char *flags)
1252040ec644SDavid van Moolenbroek {
1253040ec644SDavid van Moolenbroek 	fetchIO *f;
1254040ec644SDavid van Moolenbroek 
1255040ec644SDavid van Moolenbroek 	f = ftp_request(url, "STAT", NULL, us, ftp_get_proxy(url, flags), flags);
1256040ec644SDavid van Moolenbroek 	if (f == NULL)
1257040ec644SDavid van Moolenbroek 		return (-1);
1258040ec644SDavid van Moolenbroek 	fetchIO_close(f);
1259040ec644SDavid van Moolenbroek 	return (0);
1260040ec644SDavid van Moolenbroek }
1261040ec644SDavid van Moolenbroek 
1262040ec644SDavid van Moolenbroek /*
1263040ec644SDavid van Moolenbroek  * List a directory
1264040ec644SDavid van Moolenbroek  */
1265040ec644SDavid van Moolenbroek int
fetchListFTP(struct url_list * ue,struct url * url,const char * pattern,const char * flags)1266040ec644SDavid van Moolenbroek fetchListFTP(struct url_list *ue, struct url *url, const char *pattern, const char *flags)
1267040ec644SDavid van Moolenbroek {
1268040ec644SDavid van Moolenbroek 	fetchIO *f;
1269040ec644SDavid van Moolenbroek 	char buf[2 * PATH_MAX], *eol, *eos;
1270040ec644SDavid van Moolenbroek 	ssize_t len;
1271040ec644SDavid van Moolenbroek 	size_t cur_off;
1272040ec644SDavid van Moolenbroek 	int ret;
1273040ec644SDavid van Moolenbroek 
1274040ec644SDavid van Moolenbroek 	/* XXX What about proxies? */
1275040ec644SDavid van Moolenbroek 	if (pattern == NULL || strcmp(pattern, "*") == 0)
1276040ec644SDavid van Moolenbroek 		pattern = "";
1277040ec644SDavid van Moolenbroek 	f = ftp_request(url, "NLST", pattern, NULL, ftp_get_proxy(url, flags), flags);
1278040ec644SDavid van Moolenbroek 	if (f == NULL)
1279040ec644SDavid van Moolenbroek 		return -1;
1280040ec644SDavid van Moolenbroek 
1281040ec644SDavid van Moolenbroek 	cur_off = 0;
1282040ec644SDavid van Moolenbroek 	ret = 0;
1283040ec644SDavid van Moolenbroek 
1284040ec644SDavid van Moolenbroek 	while ((len = fetchIO_read(f, buf + cur_off, sizeof(buf) - cur_off)) > 0) {
1285040ec644SDavid van Moolenbroek 		cur_off += len;
1286040ec644SDavid van Moolenbroek 		while ((eol = memchr(buf, '\n', cur_off)) != NULL) {
1287040ec644SDavid van Moolenbroek 			if (len == eol - buf)
1288040ec644SDavid van Moolenbroek 				break;
1289040ec644SDavid van Moolenbroek 			if (eol != buf) {
1290040ec644SDavid van Moolenbroek 				if (eol[-1] == '\r')
1291040ec644SDavid van Moolenbroek 					eos = eol - 1;
1292040ec644SDavid van Moolenbroek 				else
1293040ec644SDavid van Moolenbroek 					eos = eol;
1294040ec644SDavid van Moolenbroek 				*eos = '\0';
1295040ec644SDavid van Moolenbroek 				ret = fetch_add_entry(ue, url, buf, 0);
1296040ec644SDavid van Moolenbroek 				if (ret)
1297040ec644SDavid van Moolenbroek 					break;
1298040ec644SDavid van Moolenbroek 				cur_off -= eol - buf + 1;
1299040ec644SDavid van Moolenbroek 				memmove(buf, eol + 1, cur_off);
1300040ec644SDavid van Moolenbroek 			}
1301040ec644SDavid van Moolenbroek 		}
1302040ec644SDavid van Moolenbroek 		if (ret)
1303040ec644SDavid van Moolenbroek 			break;
1304040ec644SDavid van Moolenbroek 	}
1305040ec644SDavid van Moolenbroek 	if (cur_off != 0 || len < 0) {
1306040ec644SDavid van Moolenbroek 		/* Not RFC conform, bail out. */
1307040ec644SDavid van Moolenbroek 		fetchIO_close(f);
1308040ec644SDavid van Moolenbroek 		return -1;
1309040ec644SDavid van Moolenbroek 	}
1310040ec644SDavid van Moolenbroek 	fetchIO_close(f);
1311040ec644SDavid van Moolenbroek 	return ret;
1312040ec644SDavid van Moolenbroek }
1313