Module Name:    src
Committed By:   martin
Date:           Tue May 16 16:26:03 UTC 2023

Modified Files:
        src/usr.bin/ftp [netbsd-10]: ftp.c ssl.c util.c version.h

Log Message:
Pull up following revision(s) (requested by lukem in ticket #171):

        usr.bin/ftp/ssl.c: revision 1.15
        usr.bin/ftp/util.c: revision 1.167
        usr.bin/ftp/ftp.c: revision 1.175
        usr.bin/ftp/version.h: revision 1.97

add timeout for ssl connect

Implement a timeout for SSL connection setup, using -q QUITTIME,
defaulting to 60 seconds.

SSL_connect(3) (unlike connect(2)) doesn't timeout by default.
Adapt ssl error messages destination: if unexpected error
from local API, use warn()/warnx() to stderr;
if expected error from a network operation (e.g., timeouts),
use fprintf to ttyout (which might be stdout).

Consistently use ftp_poll() instead of select();
ssl.c (using select()) was added 7 years after the
previous uses of select() were converted to poll().

Check EAGAIN as well as existing EINTR error from ftp_poll(),
for portability.


To generate a diff of this commit:
cvs rdiff -u -r1.174 -r1.174.2.1 src/usr.bin/ftp/ftp.c
cvs rdiff -u -r1.12.2.2 -r1.12.2.3 src/usr.bin/ftp/ssl.c
cvs rdiff -u -r1.164.2.1 -r1.164.2.2 src/usr.bin/ftp/util.c
cvs rdiff -u -r1.95.2.1 -r1.95.2.2 src/usr.bin/ftp/version.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.bin/ftp/ftp.c
diff -u src/usr.bin/ftp/ftp.c:1.174 src/usr.bin/ftp/ftp.c:1.174.2.1
--- src/usr.bin/ftp/ftp.c:1.174	Thu Aug 26 06:23:24 2021
+++ src/usr.bin/ftp/ftp.c	Tue May 16 16:26:03 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: ftp.c,v 1.174 2021/08/26 06:23:24 lukem Exp $	*/
+/*	$NetBSD: ftp.c,v 1.174.2.1 2023/05/16 16:26:03 martin Exp $	*/
 
 /*-
  * Copyright (c) 1996-2021 The NetBSD Foundation, Inc.
@@ -92,7 +92,7 @@
 #if 0
 static char sccsid[] = "@(#)ftp.c	8.6 (Berkeley) 10/27/94";
 #else
-__RCSID("$NetBSD: ftp.c,v 1.174 2021/08/26 06:23:24 lukem Exp $");
+__RCSID("$NetBSD: ftp.c,v 1.174.2.1 2023/05/16 16:26:03 martin Exp $");
 #endif
 #endif /* not lint */
 
@@ -1733,7 +1733,8 @@ dataconn(const char *lmode)
 		if (timeout < 0)
 			timeout = 0;
 		rv = ftp_poll(pfd, 1, timeout);
-	} while (rv == -1 && errno == EINTR);	/* loop until poll ! EINTR */
+			/* loop until poll !EINTR && !EAGAIN */
+	} while (rv == -1 && (errno == EINTR || errno == EAGAIN));
 	if (rv == -1) {
 		warn("Can't poll waiting before accept");
 		goto dataconn_failed;
@@ -1747,7 +1748,8 @@ dataconn(const char *lmode)
 	fromlen = myctladdr.su_len;
 	do {
 		s = accept(data, (struct sockaddr *) &from.si_su, &fromlen);
-	} while (s == -1 && errno == EINTR);	/* loop until accept ! EINTR */
+			/* loop until accept !EINTR && !EAGAIN */
+	} while (s == -1 && (errno == EINTR || errno == EAGAIN));
 	if (s == -1) {
 		warn("Can't accept data connection");
 		goto dataconn_failed;

Index: src/usr.bin/ftp/ssl.c
diff -u src/usr.bin/ftp/ssl.c:1.12.2.2 src/usr.bin/ftp/ssl.c:1.12.2.3
--- src/usr.bin/ftp/ssl.c:1.12.2.2	Tue May 16 16:23:45 2023
+++ src/usr.bin/ftp/ssl.c	Tue May 16 16:26:03 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: ssl.c,v 1.12.2.2 2023/05/16 16:23:45 martin Exp $	*/
+/*	$NetBSD: ssl.c,v 1.12.2.3 2023/05/16 16:26:03 martin Exp $	*/
 
 /*-
  * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
@@ -35,9 +35,10 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__RCSID("$NetBSD: ssl.c,v 1.12.2.2 2023/05/16 16:23:45 martin Exp $");
+__RCSID("$NetBSD: ssl.c,v 1.12.2.3 2023/05/16 16:26:03 martin Exp $");
 #endif
 
+#include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdarg.h>
@@ -48,7 +49,6 @@ __RCSID("$NetBSD: ssl.c,v 1.12.2.2 2023/
 #include <unistd.h>
 
 #include <sys/param.h>
-#include <sys/select.h>
 #include <sys/uio.h>
 
 #include <netinet/tcp.h>
@@ -96,38 +96,33 @@ struct fetch_connect {
 static ssize_t
 fetch_writev(struct fetch_connect *conn, struct iovec *iov, int iovcnt)
 {
-	struct timeval now, timeout, delta;
-	fd_set writefds;
+	struct timeval timeout, now, delta;
 	ssize_t len, total;
 	int fd = conn->sd;
-	int r;
+	int rv, timeout_secs;
+	struct pollfd pfd[1];
 
-	if (quit_time > 0) {
-		FD_ZERO(&writefds);
-		gettimeofday(&timeout, NULL);
-		timeout.tv_sec += quit_time;
-	}
+	pfd[0].fd = fd;
+	pfd[0].events = POLLOUT;
+	gettimeofday(&timeout, NULL);
+	timeout.tv_sec += quit_time;
 
 	total = 0;
 	while (iovcnt > 0) {
-		while (quit_time > 0 && !FD_ISSET(fd, &writefds)) {
-			FD_SET(fd, &writefds);
-			gettimeofday(&now, NULL);
-			delta.tv_sec = timeout.tv_sec - now.tv_sec;
-			delta.tv_usec = timeout.tv_usec - now.tv_usec;
-			if (delta.tv_usec < 0) {
-				delta.tv_usec += 1000000;
-				delta.tv_sec--;
-			}
-			if (delta.tv_sec < 0) {
-				errno = ETIMEDOUT;
+		if (quit_time > 0) {	/* enforce timeout */
+			do {
+				(void)gettimeofday(&now, NULL);
+				timersub(&timeout, &now, &delta);
+				timeout_secs = delta.tv_sec * 1000 + delta.tv_usec/1000;
+				if (timeout_secs < 0)
+					timeout_secs = 0;
+				rv = ftp_poll(pfd, 1, timeout_secs);
+					/* loop until poll !EINTR && !EAGAIN */
+			} while (rv == -1 && (errno == EINTR || errno == EAGAIN));
+			if (rv == -1)
 				return -1;
-			}
-			errno = 0;
-			r = select(fd + 1, NULL, &writefds, NULL, &delta);
-			if (r == -1) {
-				if (errno == EINTR)
-					continue;
+			if (rv == 0) {
+				errno = ETIMEDOUT;
 				return -1;
 			}
 		}
@@ -330,7 +325,7 @@ fetch_nonssl_read(int sd, void *buf, siz
 
 	rlen = read(sd, buf, len);
 	if (rlen == -1) {
-		if (errno == EAGAIN || errno == EINTR)
+		if (errno == EINTR || errno == EAGAIN)
 			return FETCH_READ_WAIT;
 		return FETCH_READ_ERROR;
 	}
@@ -365,33 +360,43 @@ fetch_wait(struct fetch_connect *conn, s
 {
 	struct timeval now, delta;
 	int fd = conn->sd;
-	fd_set fds;
+	int rv, timeout_secs;
+	struct pollfd pfd[1];
+
+	pfd[0].fd = fd;
+	if (rlen == FETCH_READ_WAIT) {
+		pfd[0].events = POLLIN;
+	} else if (rlen == FETCH_WRITE_WAIT) {
+		pfd[0].events = POLLOUT;
+	} else {
+		pfd[0].events = 0;
+	}
 
-	FD_ZERO(&fds);
-	while (!FD_ISSET(fd, &fds)) {
-		FD_SET(fd, &fds);
+	do {
 		if (quit_time > 0) {
 			gettimeofday(&now, NULL);
-			if (!timercmp(timeout, &now, >)) {
-				fprintf(ttyout, "\r\n%s: transfer aborted"
-				    " because stalled for %lu sec.\r\n",
-				    getprogname(), (unsigned long)quit_time);
-				errno = ETIMEDOUT;
-				conn->iserr = ETIMEDOUT;
-				return -1;
-			}
 			timersub(timeout, &now, &delta);
+			timeout_secs = delta.tv_sec * 1000 + delta.tv_usec/1000;
+			if (timeout_secs < 0)
+				timeout_secs = 0;
+		} else {
+			timeout_secs = INFTIM;
 		}
 		errno = 0;
-		if (select(fd + 1,
-			rlen == FETCH_READ_WAIT ? &fds : NULL,
-			rlen == FETCH_WRITE_WAIT ? &fds : NULL,
-			NULL, quit_time > 0 ? &delta : NULL) < 0) {
-			if (errno == EINTR)
-				continue;
-			conn->iserr = errno;
-			return -1;
-		}
+		rv = ftp_poll(pfd, 1, timeout_secs);
+				/* loop until poll !EINTR && !EAGAIN */
+	} while (rv == -1 && (errno == EINTR || errno == EAGAIN));
+	if (rv == 0) {		/* poll timeout */
+		fprintf(ttyout, "\r\n%s: transfer aborted"
+		    " because stalled for %lu sec.\r\n",
+		    getprogname(), (unsigned long)quit_time);
+		errno = ETIMEDOUT;
+		conn->iserr = ETIMEDOUT;
+		return -1;
+	}
+	if (rv == -1) {		/* poll error */
+		conn->iserr = errno;
+		return -1;
 	}
 	return 0;
 }
@@ -432,7 +437,7 @@ fetch_read(void *ptr, size_t size, size_
 	while (len > 0) {
 		/*
 		 * The socket is non-blocking.  Instead of the canonical
-		 * select() -> read(), we do the following:
+		 * poll() -> read(), we do the following:
 		 *
 		 * 1) call read() or SSL_read().
 		 * 2) if an error occurred, return -1.
@@ -440,7 +445,7 @@ fetch_read(void *ptr, size_t size, size_
 		 *    update our counters and loop.
 		 * 4) if read() or SSL_read() signaled EOF, return.
 		 * 5) if we did not receive any data but we're not at EOF,
-		 *    call select().
+		 *    call poll().
 		 *
 		 * In the SSL case, this is necessary because if we
 		 * receive a close notification, we have to call
@@ -462,7 +467,7 @@ fetch_read(void *ptr, size_t size, size_
 			return total;
 		case FETCH_READ_ERROR:
 			conn->iserr = errno;
-			if (errno == EINTR)
+			if (errno == EINTR || errno == EAGAIN)
 				fetch_cache_data(conn, start, total);
 			return 0;
 		case FETCH_READ_WAIT:
@@ -584,19 +589,28 @@ fetch_getline(struct fetch_connect *conn
 }
 
 #ifdef WITH_SSL
+/*
+ * Start the SSL/TLS negotiation.
+ * Socket fcntl flags are temporarily updated to include O_NONBLOCK;
+ * these will not be reverted on connection failure.
+ * Returns pointer to allocated SSL structure on success,
+ * or NULL upon failure.
+ */
 void *
 fetch_start_ssl(int sock, const char *servername)
 {
-	SSL *ssl;
-	SSL_CTX *ctx;
+	SSL *ssl = NULL;
+	SSL_CTX *ctx = NULL;
 	X509_VERIFY_PARAM *param;
-	int ret, ssl_err;
+	int ret, ssl_err, flags, rv, timeout_secs;
 	int verify = !ftp_truthy("sslnoverify", getoptionvalue("sslnoverify"), 0);
+	struct timeval timeout, now, delta;
+	struct pollfd pfd[1];
 
 	/* Init the SSL library and context */
 	if (!SSL_library_init()){
-		fprintf(ttyout, "SSL library init failed\n");
-		return NULL;
+		warnx("SSL library init failed");
+		goto cleanup_start_ssl;
 	}
 
 	SSL_load_error_strings();
@@ -610,43 +624,87 @@ fetch_start_ssl(int sock, const char *se
 
 	ssl = SSL_new(ctx);
 	if (ssl == NULL){
-		fprintf(ttyout, "SSL context creation failed\n");
-		SSL_CTX_free(ctx);
-		return NULL;
+		warnx("SSL context creation failed");
+		goto cleanup_start_ssl;
 	}
 
 	if (verify) {
 		param = SSL_get0_param(ssl);
 		if (!X509_VERIFY_PARAM_set1_host(param, servername,
 		    strlen(servername))) {
-			fprintf(ttyout, "SSL verification setup failed\n");
-			SSL_free(ssl);
-			SSL_CTX_free(ctx);
-			return NULL;
+			warnx("SSL verification setup failed");
+			goto cleanup_start_ssl;
 		}
 
 		/* Enable peer verification, (using the default callback) */
 		SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL);
 	}
 
+						/* save current socket flags */
+	if ((flags = fcntl(sock, F_GETFL, 0)) == -1) {
+		warn("Can't %s socket flags for SSL connect to `%s'",
+		    "save", servername);
+		goto cleanup_start_ssl;
+	}
+						/* set non-blocking connect */
+	if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
+		warn("Can't set socket non-blocking for SSL connect to `%s'",
+		    servername);
+		goto cleanup_start_ssl;
+	}
+
+	/* NOTE: we now must restore socket flags on successful connection */
+
+	(void)gettimeofday(&timeout, NULL);	/* setup SSL_connect() timeout */
+	timeout.tv_sec += (quit_time > 0) ? quit_time: 60;
+						/* without -q, default to 60s */
+
 	SSL_set_fd(ssl, sock);
 	if (!SSL_set_tlsext_host_name(ssl, __UNCONST(servername))) {
-		fprintf(ttyout, "SSL hostname setting failed\n");
-		SSL_free(ssl);
-		SSL_CTX_free(ctx);
-		return NULL;
+		warnx("SSL hostname setting failed");
+		goto cleanup_start_ssl;
 	}
-	while ((ret = SSL_connect(ssl)) == -1) {
+	pfd[0].fd = sock;
+	pfd[0].events = 0;
+	while ((ret = SSL_connect(ssl)) <= 0) {
 		ssl_err = SSL_get_error(ssl, ret);
-		if (ssl_err != SSL_ERROR_WANT_READ &&
-		    ssl_err != SSL_ERROR_WANT_WRITE) {
+		DPRINTF("%s: SSL_connect() ret=%d ssl_err=%d\n",
+		    __func__, ret, ssl_err);
+		if (ret == 0) { /* unsuccessful handshake */
 			ERR_print_errors_fp(ttyout);
-			SSL_free(ssl);
-			SSL_CTX_free(ctx);
-			return NULL;
+			goto cleanup_start_ssl;
+		}
+		if (ssl_err == SSL_ERROR_WANT_READ) {
+			pfd[0].events = POLLIN;
+		} else if (ssl_err == SSL_ERROR_WANT_WRITE) {
+			pfd[0].events = POLLOUT;
+		} else {
+			ERR_print_errors_fp(ttyout);
+			goto cleanup_start_ssl;
+		}
+		(void)gettimeofday(&now, NULL);
+		timersub(&timeout, &now, &delta);
+		timeout_secs = delta.tv_sec * 1000 + delta.tv_usec/1000;
+		if (timeout_secs < 0)
+			timeout_secs = 0;
+		rv = ftp_poll(pfd, 1, timeout_secs);
+		if (rv == 0) {		/* poll for SSL_connect() timed out */
+			fprintf(ttyout, "Timeout establishing SSL connection to `%s'\n",
+			    servername);
+			goto cleanup_start_ssl;
+		} else if (rv == -1 && errno != EINTR && errno != EAGAIN) {
+			warn("Error polling for SSL connect to `%s'", servername);
+			goto cleanup_start_ssl;
 		}
 	}
 
+	if (fcntl(sock, F_SETFL, flags) == -1) {
+						/* restore socket flags */
+		warn("Can't %s socket flags for SSL connect to `%s'",
+		    "restore", servername);
+		goto cleanup_start_ssl;
+	}
+
 	if (ftp_debug && verbose) {
 		X509 *cert;
 		X509_NAME *name;
@@ -666,6 +724,13 @@ fetch_start_ssl(int sock, const char *se
 	}
 
 	return ssl;
+
+ cleanup_start_ssl:
+	if (ssl)
+		SSL_free(ssl);
+	if (ctx)
+		SSL_CTX_free(ctx);
+	return NULL;
 }
 #endif /* WITH_SSL */
 

Index: src/usr.bin/ftp/util.c
diff -u src/usr.bin/ftp/util.c:1.164.2.1 src/usr.bin/ftp/util.c:1.164.2.2
--- src/usr.bin/ftp/util.c:1.164.2.1	Tue May 16 16:16:00 2023
+++ src/usr.bin/ftp/util.c	Tue May 16 16:26:03 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: util.c,v 1.164.2.1 2023/05/16 16:16:00 martin Exp $	*/
+/*	$NetBSD: util.c,v 1.164.2.2 2023/05/16 16:26:03 martin Exp $	*/
 
 /*-
  * Copyright (c) 1997-2023 The NetBSD Foundation, Inc.
@@ -64,7 +64,7 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__RCSID("$NetBSD: util.c,v 1.164.2.1 2023/05/16 16:16:00 martin Exp $");
+__RCSID("$NetBSD: util.c,v 1.164.2.2 2023/05/16 16:26:03 martin Exp $");
 #endif /* not lint */
 
 /*
@@ -1439,8 +1439,8 @@ ftp_connect(int sock, const struct socka
 			}
 			pfd[0].revents = 0;
 			rv = ftp_poll(pfd, 1, timeout);
-						/* loop until poll ! EINTR */
-		} while (rv == -1 && errno == EINTR);
+					/* loop until poll !EINTR && !EAGAIN */
+		} while (rv == -1 && (errno == EINTR || errno == EAGAIN));
 
 		if (rv == 0) {			/* poll (connect) timed out */
 			errno = ETIMEDOUT;

Index: src/usr.bin/ftp/version.h
diff -u src/usr.bin/ftp/version.h:1.95.2.1 src/usr.bin/ftp/version.h:1.95.2.2
--- src/usr.bin/ftp/version.h:1.95.2.1	Tue May 16 16:16:00 2023
+++ src/usr.bin/ftp/version.h	Tue May 16 16:26:03 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: version.h,v 1.95.2.1 2023/05/16 16:16:00 martin Exp $	*/
+/*	$NetBSD: version.h,v 1.95.2.2 2023/05/16 16:26:03 martin Exp $	*/
 
 /*-
  * Copyright (c) 1999-2023 The NetBSD Foundation, Inc.
@@ -34,5 +34,5 @@
 #endif
 
 #ifndef FTP_VERSION
-#define	FTP_VERSION	"20230225"
+#define	FTP_VERSION	"20230505"
 #endif

Reply via email to