1 /*$Id: jftp.c,v 1.64 2000/05/27 13:47:43 jens Exp $*/
2 /*
3  * Copyright (c) 1997, 1998, 1999, 2000
4  *      Jens A. Nilsson, jnilsson@ludd.luth.se. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #ifdef MTYPES
29 #include "missing/defs.h"
30 #endif
31 
32 #ifdef MEMDEBUG
33 #include <memdebug.h>
34 #endif
35 
36 #include <sys/param.h>
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 
43 #include <ctype.h>
44 #include <setjmp.h>
45 #include <signal.h>
46 #include <stdio.h>
47 #include <errno.h>
48 #include <netdb.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <stdarg.h>
52 #include <fcntl.h>
53 #include <stdlib.h>
54 
55 #ifdef NO_STRLCPY
56 #include "strlcpy.h"
57 #endif
58 
59 #include "jftp.h"
60 #include "e_err.h"
61 
62 #ifdef lint
63 #undef va_start
64 #define va_start(x, y) { x = x ;}
65 #endif
66 
67 /* Older versions of NetBSD than 1.3K doesn't have socklen_t defined */
68 #ifdef __NetBSD__
69 #ifdef __NetBSD_Version
70 #if __NetBSD_Version__ < 103110000
71 #define socklen_t int
72 #endif
73 #else
74 #define socklen_t int
75 #endif /* __NetBSD_Version__ */
76 #endif /* __NetBSD__ */
77 
78 /* FreeBSD doesn't have socklen_t defined */
79 #ifdef __FreeBSD__
80 #define socklen_t int
81 #endif
82 
83 #ifdef NOPROTOS
84 int	socket(int, int, int);
85 int     connect(int, const struct sockaddr *, int);
86 int	setsockopt(int s, int level, int optname, const void *optval, int len);
87 int	select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
88 		struct timeval *timeout);
89 void    bzero(void *, size_t);
90 int	vsnprintf(char *str, size_t size, const char *format, va_list ap);
91 char	*strdup(const char *);
92 int     gethostname(char *, int);
93 int     bind(int, const struct sockaddr *, int);
94 int     getsockname(int, struct sockaddr *, int *);
95 int     listen(int, int);
96 int     accept(int, struct sockaddr * addr, int *);
97 int     mkstemp(char *);
98 #endif
99 
100 int ftp_set_sock_opts(struct ftp_con *c, int fd);
101 char *ftp_dir2(struct ftp_con * c, const char *flags, const char *dir);
102 
103 #define FD_CLOSE(fd)		\
104 	if ((fd) != -1) {		\
105 		(void) close(fd);	\
106 		(fd) = -1; 			\
107 	}
108 
109 #if defined(lint)
110 #define __FUNCTION__ "__FUNCTION__"
111 #endif
112 
113 #define E_LOGX(level, fmt) \
114 	e_logx(level, "ftp: %s: " fmt, __FUNCTION__);
115 #define E_LOGX_1(level, fmt, a1) \
116 	e_logx(level, "ftp: %s: " fmt, __FUNCTION__, a1);
117 #define E_LOGX_2(level, fmt, a1, a2) \
118 	e_logx(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2);
119 #define E_LOGX_3(level, fmt, a1, a2, a3) \
120 	e_logx(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2, a3);
121 #define E_LOGX_4(level, fmt, a1, a2, a3, a4) \
122 	e_logx(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2, a3, a4);
123 
124 #define E_LOG(level, fmt) \
125 	e_log(level, "ftp: %s: " fmt, __FUNCTION__);
126 #define E_LOG_1(level, fmt, a1) \
127 	e_log(level, "ftp: %s: " fmt, __FUNCTION__, a1);
128 #define E_LOG_2(level, fmt, a1, a2) \
129 	e_log(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2);
130 #define E_LOG_3(level, fmt, a1, a2, a3) \
131 	e_log(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2, a3);
132 #define E_LOG_4(level, fmt, a1, a2, a3, a4) \
133 	e_log(level, "ftp: %s: " fmt, __FUNCTION__, a1, a2, a3, a4);
134 
135 #ifdef INET6
136 /* translate IPv4 mapped IPv6 address to IPv4 address */
137 static void
unmappedaddr(struct sockaddr_in6 * sin6,int * len)138 unmappedaddr(struct sockaddr_in6 *sin6, int *len)
139 {
140 	struct sockaddr_in *sin4;
141 	u_int32_t addr;
142 	int port;
143 
144 	if (sin6->sin6_family != AF_INET6 ||
145 	    !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
146 		return;
147 	sin4 = (struct sockaddr_in *)sin6;
148 	addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
149 	port = sin6->sin6_port;
150 	memset(sin4, 0, sizeof(struct sockaddr_in));
151 	sin4->sin_addr.s_addr = addr;
152 	sin4->sin_port = port;
153 	sin4->sin_family = AF_INET;
154 #ifdef SIN6_LEN
155 	sin4->sin_len = sizeof(struct sockaddr_in);
156 #endif
157 	*len = sizeof(struct sockaddr_in);
158 }
159 #endif
160 
161 void
ftp_set_timeout_val(struct ftp_con * c,int n)162 ftp_set_timeout_val(struct ftp_con *c, int n)
163 {
164 	c->ftp_timeout = n < 0 ? 150 : n;
165 }
166 
167 void
ftp_set_passive(struct ftp_con * c,int passive)168 ftp_set_passive(struct ftp_con *c, int passive)
169 {
170 	c->ftp_passive = passive;
171 }
172 
173 void
ftp_set_tempdir(struct ftp_con * c,char * tempdir)174 ftp_set_tempdir(struct ftp_con *c, char *tempdir)
175 {
176 	c->ftp_tempdir = tempdir;
177 }
178 
179 static ssize_t
ftp_write(struct ftp_con * c,int fd,void * buf,size_t nbytes)180 ftp_write(struct ftp_con *c, int fd, void *buf, size_t nbytes)
181 {
182 	ssize_t	res;
183 #ifndef SO_RCVTIMEO
184 	struct	timeval tv;
185 	fd_set	fdset;
186 
187 	tv.tv_sec = c->ftp_timeout;
188 	tv.tv_usec = 0;
189 	FD_ZERO(&fdset);
190 	FD_SET(fd, &fdset);
191 	do
192 		res = select(fd + 1, NULL, &fdset, &tv);
193 	while (res < 0 && errno == EINTR);
194 	if (res < 0)
195 		return res;
196 	if (res == 0) {
197 		c->ftp_timed_out = 1;
198 		return -1;
199 	}
200 	do
201 		res = write(fd, buf, nbytes);
202 	while (res < 0 && errno == EINTR);
203 #endif
204 	do
205 		res = write(fd, buf, nbytes);
206 	while (res < 0 && errno == EINTR);
207 	c->ftp_timed_out = (res < 0 && errno == EWOULDBLOCK);
208 	return res;
209 }
210 
211 static ssize_t
ftp_read(struct ftp_con * c,int fd,void * buf,size_t nbytes)212 ftp_read(struct ftp_con *c, int fd, void *buf, size_t nbytes)
213 {
214 	ssize_t	res;
215 #ifndef SO_RCVTIMEO
216 	struct	timeval tv;
217 	fd_set	fdset;
218 
219 	tv.tv_sec = c->ftp_timeout;
220 	tv.tv_usec = 0;
221 	FD_ZERO(&fdset);
222 	FD_SET(fd, &fdset);
223 	do
224 		res = select(fd + 1, &fdset, NULL, &tv);
225 	while (res < 0 && errno == EINTR);
226 	if (res < 0)
227 		return res;
228 	if (res == 0) {
229 		c->ftp_timed_out = 1;
230 		return -1;
231 	}
232 	do
233 		res = read(fd, buf, nbytes);
234 	while (res < 0 && errno == EINTR);
235 #endif
236 	do
237 		res = read(fd, buf, nbytes);
238 	while (res < 0 && errno == EINTR);
239 	c->ftp_timed_out = (res < 0 && errno == EWOULDBLOCK);
240 	return res;
241 }
242 
243 int
ftp_req(struct ftp_con * c,const char * fmt,...)244 ftp_req(struct ftp_con *c, const char *fmt, ...)
245 {
246 	va_list	ap;
247 	int     ftp_status, eol, i, islongtext;
248 	ssize_t	done, res, size, pos;
249 	char	*nfmt;
250 	char	rescode[5];
251 
252 	if (*fmt != ' ') {
253 		if ((nfmt = alloca(strlen(fmt) + 3)) == NULL) {
254 			c->ftp_resp = JFTP_ERRNO;
255 			return -1;
256 		}
257 		(void)strcpy(nfmt, fmt);
258 		(void)strcat(nfmt, "\r\n");
259 		va_start(ap, fmt);
260 		(void)vsnprintf(c->ftp_buf, sizeof(c->ftp_buf), nfmt, ap);
261 		va_end(ap);
262 		E_LOGX_2(2, "command: %s %d", c->ftp_buf, 3);
263 		res = ftp_write(c, c->ftp_com, c->ftp_buf, strlen(c->ftp_buf));
264 
265 		/* sometimes it's possible to get res = 0 under Solaris */
266 		if (res <= 0 || c->ftp_timed_out)
267 			goto io_err;
268 
269 		c->ftp_sent += res;
270 	}
271 	(void)memset(c->ftp_buf, 0, sizeof(c->ftp_buf));
272 	size = sizeof(c->ftp_buf);
273 	pos = 0;
274 	eol = 0;
275 	islongtext = 0;
276 	ftp_status = 0;
277 	do {
278 		done = ftp_read(c, c->ftp_com, c->ftp_buf + pos, (size_t)1);
279 
280 		/* We shouldn't get done == 0 since we
281 		 * are expecting a newline
282 		 */
283 		if (done <= 0 || c->ftp_timed_out)
284 			goto io_err;
285 
286 		c->ftp_recd += done;
287 		for (i = (int)pos; i < (pos + done); i++) {
288 			if (c->ftp_buf[i] == '\n') {
289 				eol = 1;
290 				c->ftp_buf[i] = 0;
291 				break;
292 			}
293 		}
294 		size -= done;
295 		pos += done;
296 		if(eol) {
297 			ftp_status=0;
298 
299 			/* We are interested in the first 4 bytes */
300 			(void)memset(rescode, '\0', sizeof(rescode));
301 			(void)memcpy(rescode, c->ftp_buf, sizeof(rescode) - 1);
302 
303 			if (isdigit(rescode[0])
304 			    && isdigit(rescode[1])
305 			    && isdigit(rescode[2])) {
306 				res = sscanf(rescode, "%3d", &ftp_status);
307 
308 				E_LOGX_1(3, "ftp_status: %d", ftp_status);
309 				E_LOGX_1(3, "c->ftp_buf: %s", c->ftp_buf);
310 
311 				if ((rescode[3] == '-' || rescode[3] == ' ')
312 				    && (res == 1)) {
313 					islongtext = (rescode[3] == '-');
314 					if (ftp_status < 100) {
315 						E_LOGX_1(1, "%s", c->ftp_buf);
316 						c->ftp_resp = JFTP_ERR;
317 						return -1;
318 					}
319 				}
320 			}
321 			if(islongtext) {
322 				size = sizeof(c->ftp_buf);
323 				pos = 0;
324 				eol = 0;
325 			}
326 		}
327 	} while (size && !eol);
328 	if ((!eol) || (!ftp_status)) {
329 		E_LOGX_1(1, "%s", c->ftp_buf);
330 		c->ftp_resp = JFTP_ERR;
331 		return -1;
332 	}
333 	c->ftp_resp = ftp_status;
334 	return 0;
335 
336 io_err:
337 	if (c->ftp_timed_out) {
338 		E_LOGX(0, "timeout");
339 		c->ftp_resp = JFTP_TIMEOUT;
340 	} else {
341 		E_LOGX(0, "command connection broken");
342 		c->ftp_resp = JFTP_BROKEN;
343 	}
344 	FD_CLOSE(c->ftp_com);
345 	FD_CLOSE(c->ftp_listen);
346 	FD_CLOSE(c->ftp_data);
347 	return -1;
348 }
349 
350 struct ftp_con *
ftp_login(char * host,int port,int family,char * username,char * password,FILE * logfile,int verbose)351 ftp_login(char *host, int port, int family, char *username,
352 	char *password, FILE * logfile, int verbose)
353 {
354 	struct	ftp_con *c;
355 
356 	c = calloc((size_t)1, sizeof(*c));
357 	if ((c->ftp_remote_host = strdup(host)) == NULL)
358 		goto ret_bad;
359 	if ((c->ftp_user_name = strdup(username)) == NULL)
360 		goto ret_bad;
361 	if ((c->ftp_password = strdup(password)) == NULL)
362 		goto ret_bad;
363 	c->ftp_logfile = logfile;
364 	c->ftp_verbose = verbose;
365 	e_set_level(verbose); /* XXX - global */
366 	c->ftp_com = -1;
367 	c->ftp_listen = -1;
368 	c->ftp_data = -1;
369 	c->ftp_port = port;
370 	c->ftp_family = family;
371 	c->ftp_retries = 20;
372 	c->ftp_timeout = JFTP_TIMEOUT_VAL;
373 	c->ftp_relogins = -1;
374 	if ((c->ftp_tempdir = strdup(JFTP_DIR)) == NULL)
375 		goto ret_bad;
376 
377 	if (ftp_relogin(c) < 0)
378 		goto ret_bad;
379 
380 	return c;
381 
382 ret_bad:
383 	ftp_unalloc(c);
384 	return NULL;
385 }
386 
387 void
ftp_unalloc(struct ftp_con * c)388 ftp_unalloc(struct ftp_con *c)
389 {
390 #define FC_FREE(var) if (c->var) free(c->var)
391 
392 	if (c == NULL)
393 		return;
394 	FC_FREE(ftp_remote_host);
395 	FC_FREE(ftp_user_name);
396 	FC_FREE(ftp_password);
397 	FC_FREE(ftp_tempdir);
398 	free(c);
399 }
400 
401 int
ftp_relogin(struct ftp_con * c)402 ftp_relogin(struct ftp_con *c)
403 {
404 #ifdef INET6
405 	struct	addrinfo hints, *res0 = NULL, *res;
406 	char	str_port[NI_MAXSERV];
407 #else
408 	struct	sockaddr_in server;
409 	struct	hostent *hp;
410 #endif
411 
412 	c->ftp_relogins++;
413 	FD_CLOSE(c->ftp_com);
414 	FD_CLOSE(c->ftp_listen);
415 	FD_CLOSE(c->ftp_data);
416 #ifdef INET6
417 	snprintf(str_port, sizeof(str_port), "%d", c->ftp_port);
418 	memset(&hints, 0, sizeof(hints));
419 	hints.ai_family = c->ftp_family;
420 	hints.ai_socktype = SOCK_STREAM;
421 	hints.ai_flags = AI_CANONNAME;
422 	if (getaddrinfo(c->ftp_remote_host, str_port, &hints, &res0) != 0) {
423 		E_LOGX_1(0, "getaddrinfo(%s): failed", c->ftp_remote_host);
424 		c->ftp_resp = JFTP_ERR;
425 		return -1;
426 	}
427 	c->ftp_com = -1;
428 	for (res = res0; res; res = res->ai_next) {
429 		c->ftp_com = socket(res->ai_family, res->ai_socktype,
430 				    res->ai_protocol);
431 		if (c->ftp_com < 0)
432 			continue;
433 		if (connect(c->ftp_com, res->ai_addr, res->ai_addrlen) >= 0)
434 			break;
435 		FD_CLOSE(c->ftp_com);
436 		c->ftp_com = -1;
437 	}
438 	freeaddrinfo(res0);
439 	if (c->ftp_com < 0) {
440 		E_LOG(0, "connect");
441 		c->ftp_resp = JFTP_ERR;
442 		return -1;
443 	}
444 #else
445 	c->ftp_com = socket(AF_INET, SOCK_STREAM, 0);
446 	if (c->ftp_com < 0) {
447 		E_LOG(0, "socket");
448 		c->ftp_resp = JFTP_ERR;
449 		return -1;
450 	}
451 	server.sin_family = AF_INET;
452 	hp = gethostbyname(c->ftp_remote_host);
453 	if (hp == NULL) {
454 		E_LOGX_1(0, "gethostbyname(%s): failed", c->ftp_remote_host);
455 		FD_CLOSE(c->ftp_com);
456 		c->ftp_resp = JFTP_ERR;
457 		return -1;
458 	}
459 	(void) memcpy(&server.sin_addr, hp->h_addr, sizeof(server.sin_addr));
460 	server.sin_port = htons(c->ftp_port);
461 
462 	/* NOSTRICT server */
463 	if (connect(c->ftp_com, (struct sockaddr *) & server, sizeof(server)) < 0) {
464 		E_LOG(0, "connect");
465 		FD_CLOSE(c->ftp_com);
466 		c->ftp_resp = JFTP_ERR;
467 		return -1;
468 	}
469 #endif
470 	if (ftp_req(c, JFTP_RESPONSE) < 0 || c->ftp_resp != 220) {
471 		E_LOGX_1(0, "unexpected greeting from server: %s", c->ftp_buf);
472 		FD_CLOSE(c->ftp_com);
473 		c->ftp_resp = JFTP_ERR;
474 		return -1;
475 	}
476 	if (ftp_req(c, "user %s", c->ftp_user_name) < 0
477 		|| (c->ftp_resp != 331 && c->ftp_resp != 230)) {
478 		E_LOGX_1(0, "Username %s: failed", c->ftp_user_name);
479 		FD_CLOSE(c->ftp_com);
480 		c->ftp_resp = JFTP_ERR;
481 		return -1;
482 	}
483 	/* USER command can respond 230 immediately in some cases */
484 	if (c->ftp_resp != 230) {
485 		if (ftp_req(c, "pass %s", c->ftp_password) < 0
486 			|| c->ftp_resp != 230) {
487 			E_LOGX(0, "Password xxxxx: failed");
488 			FD_CLOSE(c->ftp_com);
489 			c->ftp_resp = JFTP_ERR;
490 			return -1;
491 		}
492 	}
493 	c->ftp_resp = 0;
494 	if (ftp_req(c, "TYPE I") < 0 || c->ftp_resp != 200) {
495 		E_LOGX(0, "Setting BIN type: failed");
496 		FD_CLOSE(c->ftp_com);
497 		c->ftp_resp = JFTP_ERR;
498 		return -1;
499 	}
500 	if (c->ftp_remote_dir != NULL && strcmp(c->ftp_remote_dir, "/") != 0)
501 		return ftp_cd(c, c->ftp_remote_dir);
502 	return 0;
503 }
504 
505 
506 int
ftp_cd(struct ftp_con * c,const char * dir)507 ftp_cd(struct ftp_con *c, const char *dir)
508 {
509 	char	*p;
510 	int		res;
511 
512 	if ((p = strdup(dir)) == NULL) { /* Thank You John Polstra */
513 		c->ftp_resp = JFTP_ERRNO;
514 		return -1;
515 	}
516 
517 	/* Save new directory on server */
518 	if (c->ftp_remote_dir != NULL)
519 		free(c->ftp_remote_dir);
520 	c->ftp_remote_dir = p;
521 
522 	/* try to change directory on server */
523 	res = ftp_req(c, "CWD %s", c->ftp_remote_dir);
524 
525 	/* req failed, ftp_resp has the error */
526 	if (res < 0)
527 		return res;
528 
529 	/* our request wasn't sucessful */
530 	if (c->ftp_resp != 250) {
531 		c->ftp_resp = JFTP_ERR;
532 		return -1;
533 	}
534 	return 0;
535 }
536 
537 /* ARGSUSED */
538 int
ftp_set_sock_opts(struct ftp_con * c,int fd)539 ftp_set_sock_opts(struct ftp_con *c, int fd)
540 {
541 #if !defined(ULTRIX)
542 	int		buf_size = 65536;	/* 64k, this can speed up if
543 								 * delay x bandwidth is large
544 								 */
545 	static	int failed = 0;
546 
547 	c = NULL; /* quiet gcc */
548 
549 	/* Don't make the same misstake again */
550 	if (failed)
551 		return -1;
552 	if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf_size,
553 		(socklen_t)sizeof(buf_size)) < 0) {
554 
555 		E_LOG_1(1, "setsockopt: SO_RCVBUF = %d", buf_size);
556 		failed = 1;
557 		return -1;
558 	}
559 #endif
560 	return 0;
561 }
562 
563 int
ftp_port(struct ftp_con * c)564 ftp_port(struct ftp_con *c)
565 {
566 	char	*p, *a;
567 #ifdef INET6
568 	struct	sockaddr_storage sin2;
569 	struct	sockaddr_in6 *sin6;
570 	char	hname[INET6_ADDRSTRLEN];
571 	u_char	addr[4];
572 	int	port;
573 #else
574 	struct	sockaddr_in sin2;
575 #endif
576 	struct	sockaddr_in *sin4;
577 	int	arg;
578 	int     len, res;
579 	int		a0, a1, a2, a3, p0, p1;
580 
581 	c->ftp_downloads++;
582 
583 	len = sizeof(sin2);
584 	if (getsockname(c->ftp_com, (struct sockaddr *)&sin2, &len) == -1) {
585 		E_LOG(0, "getsockname");
586 		FD_CLOSE(c->ftp_com);
587 		c->ftp_resp = JFTP_BROKEN;
588 		return -1;
589 	}
590 	switch (((struct sockaddr *)&sin2)->sa_family) {
591 	case AF_INET:
592 		break;
593 #ifdef INET6
594 	case AF_INET6:
595 		unmappedaddr((struct sockaddr_in6 *)&sin2, &len);
596 		break;
597 #endif
598 	default:
599 		E_LOG(0, "getsockname");
600 		FD_CLOSE(c->ftp_com);
601 		c->ftp_resp = JFTP_BROKEN;
602 		return -1;
603 	}
604 
605 	/* doing active ftp */
606 	if (!c->ftp_passive) {
607 		/* sanity check */
608 		if (c->ftp_listen != -1) {
609 			FD_CLOSE(c->ftp_listen);
610 			E_LOGX(1, "ERROR This shouldn't happen, "
611 				"data connection wasn't closed");
612 		}
613 
614 		/* create listen socket */
615 		c->ftp_listen = socket(((struct sockaddr *)&sin2)->sa_family,
616 				       SOCK_STREAM, 0);
617 		if (c->ftp_listen < 0) {
618 			E_LOGX(0, "socket");
619 			c->ftp_listen = -1;
620 			c->ftp_resp = JFTP_BROKEN;
621 			return -1;
622 		}
623 
624 		/* bind socket */
625 		switch (((struct sockaddr *)&sin2)->sa_family) {
626 		case AF_INET:
627 			((struct sockaddr_in *)&sin2)->sin_port = 0;
628 #ifdef IP_PORTRANGE
629 			arg = IP_PORTRANGE_HIGH;
630 			setsockopt(c->ftp_listen, IPPROTO_IP, IP_PORTRANGE,
631 				   (char *)&arg, sizeof(arg));
632 #endif
633 			break;
634 #ifdef INET6
635 		case AF_INET6:
636 			((struct sockaddr_in6 *)&sin2)->sin6_port = 0;
637 #ifdef IPV6_PORTRANGE
638 			arg = IPV6_PORTRANGE_HIGH;
639 			setsockopt(c->ftp_listen, IPPROTO_IPV6, IPV6_PORTRANGE,
640 				   (char *)&arg, sizeof(arg));
641 #endif
642 			break;
643 #endif
644 		}
645 		if (bind(c->ftp_listen, (struct sockaddr *) &sin2, len) < 0) {
646 			E_LOG(0, "bind");
647 			c->ftp_resp = JFTP_BROKEN;
648 			FD_CLOSE(c->ftp_listen);
649 			FD_CLOSE(c->ftp_com);
650 			return -1;
651 		}
652 
653 		/* get the port we are going to listen on */
654 		len = sizeof(sin2);
655 		/* NOSTRICT sin2 */
656 		if (getsockname(c->ftp_listen, (struct sockaddr *) &sin2, &len) < 0) {
657 			E_LOG(0, "getsockname");
658 			FD_CLOSE(c->ftp_listen);
659 			FD_CLOSE(c->ftp_com);
660 			c->ftp_resp = JFTP_BROKEN;
661 			return -1;
662 		}
663 
664 		(void) ftp_set_sock_opts(c, c->ftp_listen);
665 		if (listen(c->ftp_listen, 1) < 0) {
666 			E_LOG(0, "listen");
667 			FD_CLOSE(c->ftp_listen);
668 			FD_CLOSE(c->ftp_com);
669 			c->ftp_resp = JFTP_BROKEN;
670 			return -1;
671 		}
672 
673 		/* do the port command */
674 		switch (((struct sockaddr *)&sin2)->sa_family) {
675 		case AF_INET:
676 			sin4 = (struct sockaddr_in *)&sin2;
677 			a = (char *)&sin4->sin_addr.s_addr;
678 			p = (char *)&sin4->sin_port;
679 			res = ftp_req(c, "PORT %d,%d,%d,%d,%d,%d",
680 				      a[0] & 0xff, a[1] & 0xff,
681 				      a[2] & 0xff, a[3] & 0xff,
682 				      p[0] & 0xff, p[1] & 0xff);
683 			break;
684 #ifdef INET6
685 		case AF_INET6:
686 			sin6 = (struct sockaddr_in6 *)&sin2;
687 			if (getnameinfo((struct sockaddr *)&sin2, sin2.ss_len,
688 					hname, sizeof(hname),
689 					NULL, 0, NI_NUMERICHOST) != 0) {
690 				E_LOG(0, "getnameinfo");
691 				FD_CLOSE(c->ftp_listen);
692 				FD_CLOSE(c->ftp_com);
693 				c->ftp_resp = JFTP_BROKEN;
694 				return -1;
695 			}
696 			res = ftp_req(c, "EPRT |%d|%s|%d|", 2, hname,
697 				      htons(sin6->sin6_port));
698 			break;
699 #endif
700 		}
701 		if (res < 0 || c->ftp_resp != 200) {
702 			if (res >= 0)
703 				c->ftp_resp = JFTP_ERR;
704 			FD_CLOSE(c->ftp_listen);
705 			return -1;
706 		}
707 		return 0;
708 	} else { /* passive ftp */
709 		switch (((struct sockaddr *)&sin2)->sa_family) {
710 		case AF_INET:
711 			res = ftp_req(c, "PASV");
712 			if (res < 0 || c->ftp_resp != 227) {
713 				if (res >= 0)
714 					c->ftp_resp = JFTP_ERR;
715 				return -1;
716 			}
717 			for (p = c->ftp_buf; *p != '\0' && *p != '('; p++);
718 			res = sscanf(p, "(%d,%d,%d,%d,%d,%d",
719 				     &a0, &a1, &a2, &a3, &p0, &p1);
720 			if (res != 6) {
721 				c->ftp_resp = JFTP_ERR;
722 				return -1;
723 			}
724 			sin4 = (struct sockaddr_in *)&sin2;
725 			/* NOSTRICT a */
726 			a = (char *)&sin4->sin_addr.s_addr;
727 			a[0] = a0 & 0xff;
728 			a[1] = a1 & 0xff;
729 			a[2] = a2 & 0xff;
730 			a[3] = a3 & 0xff;
731 			/* NOSTRICT p */
732 			p = (char *)&sin4->sin_port;
733 			p[0] = p0 & 0xff;
734 			p[1] = p1 & 0xff;
735 			sin4->sin_family = AF_INET;
736 			break;
737 #ifdef INET6
738 		case AF_INET6:
739 			res = ftp_req(c, "EPSV");
740 			if (res < 0 || c->ftp_resp != 229) {
741 				if (res >= 0)
742 					c->ftp_resp = JFTP_ERR;
743 				return -1;
744 			}
745 			for (p = c->ftp_buf; *p != '\0' && *p != '('; p++);
746 			if (*p == '\0') {
747 				c->ftp_resp = JFTP_ERR;
748 				return -1;
749 			}
750 			++p;
751 			res = sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1],
752 				     &addr[2], &port, &addr[3]);
753 			if (res != 5 || addr[0] != addr[1] ||
754 			    addr[0] != addr[2] || addr[0] != addr[3]) {
755 				c->ftp_resp = JFTP_ERR;
756 				return -1;
757 			}
758 			len = sizeof(sin2);
759 			res = getpeername(c->ftp_com,
760 					  (struct sockaddr *)&sin2, &len);
761 			if (res == -1 || sin2.ss_family != AF_INET6) {
762 				E_LOG(0, "getpeername");
763 				c->ftp_resp = JFTP_BROKEN;
764 				return -1;
765 			}
766 			sin6 = (struct sockaddr_in6 *)&sin2;
767 			sin6->sin6_port = htons(port);
768 			break;
769 #endif
770 		}
771 
772 		/* sanity check */
773 		if (c->ftp_data != -1) {
774 			FD_CLOSE(c->ftp_data);
775 			E_LOGX(1, "ERROR1 This shouldn't happen, "
776 				"data connection wasn't closed");
777 		}
778 		/* connect to server */
779 		c->ftp_data = socket(((struct sockaddr *)&sin2)->sa_family,
780 				     SOCK_STREAM, 0);
781 		if (c->ftp_data < 0) {
782 			E_LOG(0, "socket");
783 			c->ftp_resp = JFTP_ERR;
784 			return -1;
785 		}
786 		(void)ftp_set_sock_opts(c, c->ftp_data);
787 		if (connect(c->ftp_data,
788 				/* NOSTRICT sin2 */
789 				(struct sockaddr *)&sin2, len) < 0) {
790 			E_LOG(0, "connect");
791 			FD_CLOSE(c->ftp_data);
792 			c->ftp_resp = JFTP_ERR;
793 			return -1;
794 		}
795 		return 0;
796 	}
797 	/* NOT REACHED */
798 }
799 
800 int
ftp_read_data(struct ftp_con * c,char * buf,size_t size)801 ftp_read_data(struct ftp_con *c, char *buf, size_t size)
802 {
803 	int     res;
804 	ssize_t	done = 0;
805 	struct	timeval tv;
806 	fd_set	fdset;
807 #ifndef SO_RCVTIMEO
808 	int		flags;
809 #endif
810 
811 	if ((c->ftp_data == -1) && (c->ftp_listen != -1)) {
812 		tv.tv_sec = c->ftp_timeout;
813 		tv.tv_usec = 0;
814 		FD_ZERO(&fdset);
815 		/* LINTED fdset */
816 		FD_SET(c->ftp_listen, &fdset);
817 		do
818 			res = select(c->ftp_listen + 1, &fdset, NULL, NULL, &tv);
819 		while (res < 0 && errno == EINTR);
820 		if (res < 0) {
821 			E_LOG(0, "select");
822 			c->ftp_resp = JFTP_ERR;
823 			return -1;
824 		}
825 		if (res == 0) {
826 			FD_CLOSE(c->ftp_listen);
827 			c->ftp_resp = JFTP_TIMEOUT;
828 			c->ftp_timeouts++;
829 			return -1;
830 		}
831 
832 		c->ftp_data = accept(c->ftp_listen, 0, 0);
833 		FD_CLOSE(c->ftp_listen);
834 		if (c->ftp_data < 0) {
835 			E_LOG(0, "accept");
836 			c->ftp_data = -1;
837 			c->ftp_resp = JFTP_BROKEN;
838 			return -1;
839 		}
840 #ifdef SO_RCVTIMEO
841 		if (setsockopt(c->ftp_data, SOL_SOCKET, SO_RCVTIMEO,
842 /* LINTED */
843 			(char *) &tv, (size_t) sizeof(tv)) < 0) {
844 			E_LOG(0, "setsockopt");
845 			FD_CLOSE(c->ftp_data);
846 			c->ftp_resp = JFTP_ERR;
847 			return -1;
848 		}
849 #else
850 		if ((flags = fcntl(c->ftp_data, F_GETFL, 0)) < 0) {
851 			E_LOG(0, "fcntl F_GETFL");
852 			FD_CLOSE(c->ftp_data);
853 			c->ftp_resp = JFTP_ERR;
854 			return -1;
855 		}
856 		flags |= O_NONBLOCK;
857 		if (fcntl(c->ftp_data, F_SETFS, flags) < 0) {
858 			E_LOG(0, "fcntl F_SETFS");
859 			FD_CLOSE(c->ftp_data);
860 			c->ftp_resp = JFTP_ERR;
861 			return -1;
862 		}
863 #endif
864 	}
865 	done = ftp_read(c, c->ftp_data, buf, size);
866 	if (done < 0 || c->ftp_timed_out) {
867 		FD_CLOSE(c->ftp_data);
868 		if (c->ftp_timed_out)
869 			c->ftp_resp = JFTP_TIMEOUT;
870 		else
871 			c->ftp_resp = JFTP_BROKEN;
872 		return -1;
873 	}
874 	c->ftp_recd += done;
875 	c->ftp_resp = (int)done;
876 	return 0;
877 }
878 
879 int
ftp_get(struct ftp_con * c,char * local_file,char * remote_file,size_t seekto)880 ftp_get(struct ftp_con *c, char *local_file, char *remote_file, size_t seekto)
881 {
882 	FILE   *fp;
883 	int     done, recieved;
884 	ssize_t	res;
885 
886 	if (seekto > 0) {
887 		fp = fopen(local_file, "ab");
888 		if (fp == NULL) {
889 			E_LOG_1(0, "opening of %s for writing failed", local_file);
890 			c->ftp_resp = JFTP_WRITEERR;
891 			return -1;
892 		}
893 		if (fseek(fp, (long) seekto, SEEK_SET) < 0) {
894 			E_LOG_2(0, "fseek to pos %d in %s failed",
895 				(unsigned) seekto, local_file);
896 			c->ftp_resp = JFTP_ERRNO;
897 			return -1;
898 		}
899 	} else {
900 		fp = fopen(local_file, "wb");
901 		if (fp == NULL) {
902 			E_LOG_1(0, "creation of %s failed", local_file);
903 			c->ftp_resp = JFTP_WRITEERR;
904 			return -1;
905 		}
906 	}
907 	if (ftp_port(c) < 0) {
908 		E_LOGX(0, "PORT command failed");
909 		if (c->ftp_resp != JFTP_BROKEN && c->ftp_resp != JFTP_TIMEOUT)
910 			c->ftp_resp = JFTP_ERR;
911 		goto ret_bad;
912 		return -1;
913 	}
914 	if (seekto > 0) {
915 		res = ftp_req(c, "REST %lu", (unsigned long) seekto);
916 		if (res < 0 || c->ftp_resp != 350) {
917 			E_LOGX(0, "REST command failed");
918 			if (res >= 0)
919 				c->ftp_resp = JFTP_ERR;
920 			goto ret_bad;
921 		}
922 	}
923 	res = ftp_req(c, "RETR %s", remote_file);
924 	if (res < 0 || c->ftp_resp != 150) {
925 		E_LOGX(0, "RETR command failed");
926 		if (res >= 0)
927 			c->ftp_resp = JFTP_ERR;
928 		goto ret_bad;
929 	}
930 	recieved = 0;
931 	if (ftp_read_data(c, c->ftp_buf, sizeof(c->ftp_buf)) < 0)
932 		goto ret_bad;
933 	done = c->ftp_resp;
934 	while (done > 0) {
935 		recieved += done;
936 		res = fwrite(c->ftp_buf, (size_t)done, (size_t)1, fp);
937 		if (res < 0) {
938 			E_LOG(0, "write");
939 				/* XXX can't report error if this failes */
940 			(void) ftp_req(c, JFTP_RESPONSE);
941 			c->ftp_resp = JFTP_WRITEERR;
942 			goto ret_bad;
943 		}
944 		if (ftp_read_data(c, c->ftp_buf, sizeof(c->ftp_buf)) < 0)
945 			goto ret_bad;
946 		done = c->ftp_resp;
947 	}
948 
949 	(void)fclose(fp);
950 	FD_CLOSE(c->ftp_listen);
951 	FD_CLOSE(c->ftp_data);
952 	res = ftp_req(c, JFTP_RESPONSE);
953 	if (res < 0) {
954 		E_LOGX(0, "broken connection");
955 		FD_CLOSE(c->ftp_com);
956 		c->ftp_resp = JFTP_BROKEN;
957 		goto ret_bad;
958 	}
959 	if (c->ftp_resp != 226) {
960 		E_LOGX_1(0, "retrieve of %s was aborted by server", remote_file);
961 		c->ftp_resp = JFTP_ERR;
962 		goto ret_bad;
963 	}
964 	return 0;
965 
966 ret_bad:
967 	FD_CLOSE(c->ftp_listen);
968 	FD_CLOSE(c->ftp_data);
969 	(void)fclose(fp);
970 	return -1;
971 }
972 
973 char *
ftp_dir2(struct ftp_con * c,const char * flags,const char * dir)974 ftp_dir2(struct ftp_con * c, const char *flags, const char *dir)
975 {
976 	int     done, recieved;
977 	ssize_t	res;
978 	char	*tmp;
979 	int     fd;
980 
981 	tmp = malloc(strlen(c->ftp_tempdir) + strlen(JFTP_TEMPFILE) + 2);
982 	if (tmp == NULL) {
983 		c->ftp_resp = JFTP_ERRNO;
984 		return NULL;
985 	}
986 	(void) sprintf(tmp, "%s/%s", c->ftp_tempdir, JFTP_TEMPFILE);
987 
988 	fd = mkstemp(tmp);
989 	if (fd < 0) {
990 		c->ftp_resp = JFTP_WRITEERR;
991 		E_LOG_1(0, "mkstemp(%s)", tmp);
992 		return NULL;
993 	}
994 	if (ftp_port(c) < 0) {
995 		E_LOGX(0, "PORT command failed");
996 		FD_CLOSE(c->ftp_listen);
997 		FD_CLOSE(c->ftp_data);
998 		(void) close(fd);
999 		(void) unlink(tmp);
1000 		return NULL;
1001 	}
1002 
1003 	/* Late versions of wu-ftpd does some kind of recursive
1004 	 * listing if only a '.' is given as directory.
1005 	 */
1006 	if ((strcmp(dir, ".") == 0) || !*dir)
1007 		res = ftp_req(c, "list %s", flags);
1008 	else
1009 		res = ftp_req(c, "list %s %s", flags, dir);
1010 
1011 	if (res < 0 || c->ftp_resp != 150) {
1012 		FD_CLOSE(c->ftp_listen);
1013 		FD_CLOSE(c->ftp_data);
1014 		E_LOGX_1(0, "LIST command for %s failed", dir);
1015 		if (res >= 0)
1016 			c->ftp_resp = JFTP_ERR;
1017 		(void) close(fd);
1018 		(void) unlink(tmp);
1019 		return NULL;
1020 	}
1021 	recieved = 0;
1022 	if (ftp_read_data(c, c->ftp_buf, sizeof(c->ftp_buf)) < 0) {
1023 		(void) close(fd);
1024 		(void) unlink(tmp);
1025 		return NULL;
1026 	}
1027 	done = c->ftp_resp;
1028 	while (done > 0) {
1029 		recieved += done;
1030 		while((res = write(fd, c->ftp_buf, (size_t)done)) < 0 &&
1031 			errno == EINTR);
1032 		if (res < 0) {
1033 			E_LOG_1(0, "write(%s)", tmp);
1034 			c->ftp_resp = JFTP_WRITEERR;
1035 			FD_CLOSE(c->ftp_listen);
1036 			FD_CLOSE(c->ftp_data);
1037 			(void) close(fd);
1038 			(void) unlink(tmp);
1039 			(void) ftp_req(c, JFTP_RESPONSE);
1040 			return NULL;
1041 		}
1042 		if (ftp_read_data(c, c->ftp_buf, sizeof(c->ftp_buf)) < 0) {
1043 			FD_CLOSE(c->ftp_listen);
1044 			FD_CLOSE(c->ftp_data);
1045 			(void) close(fd);
1046 			(void) unlink(tmp);
1047 			return NULL;
1048 		}
1049 		done = c->ftp_resp;
1050 	}
1051 
1052 	(void) close(fd);
1053 	FD_CLOSE(c->ftp_listen);
1054 	FD_CLOSE(c->ftp_data);
1055 	res = ftp_req(c, JFTP_RESPONSE);
1056 	if (res < 0) {
1057 		E_LOGX(0, "broken connection");
1058 		FD_CLOSE(c->ftp_com);
1059 		c->ftp_resp = JFTP_BROKEN;
1060 		(void) unlink(tmp);
1061 		return NULL;
1062 	}
1063 	if (c->ftp_resp != 226) {
1064 		E_LOGX_1(0, "retrieve of dir %s was aborted by server", dir);
1065 		(void) unlink(tmp);
1066 		return NULL;
1067 	}
1068 	return tmp;
1069 }
1070 
1071 char *
ftp_dir(struct ftp_con * c,const char * dir)1072 ftp_dir(struct ftp_con *c, const char *dir)
1073 {
1074 	char	buf[PATH_MAX];
1075 	char	*res;
1076 
1077 	strlcpy(buf, c->ftp_remote_dir, PATH_MAX);
1078 	if (ftp_cd(c, dir) < 0)
1079 		return NULL;
1080 	res = ftp_dir2(c, "-lag", "");
1081 	if (ftp_cd(c, buf) < 0)
1082 		return NULL;	/* XXX - maybe abort here */
1083 	return res;
1084 }
1085 
1086 #if NOTYET
1087 char *
ftp_dir_recurs(struct ftp_con * c,char * tempfile)1088 ftp_dir_recurs(struct ftp_con *c, char *tempfile)
1089 {
1090 	return ftp_dir2(c, "-lagR", tempfile);
1091 }
1092 #endif
1093 
1094 
1095 int
ftp_bye(struct ftp_con * c)1096 ftp_bye(struct ftp_con * c)
1097 {
1098 	int     res;
1099 
1100 	res = ftp_req(c, "quit");
1101 	FD_CLOSE(c->ftp_com);
1102 	FD_CLOSE(c->ftp_listen);
1103 	FD_CLOSE(c->ftp_data);
1104 	if (res >= 0 && c->ftp_resp == 221)
1105 		return 0;
1106 	return -1;
1107 }
1108