NetBSD Problem Report #47920

From www@NetBSD.org  Wed Jun 12 12:01:29 2013
Return-Path: <www@NetBSD.org>
Received: from mail.netbsd.org (mail.netbsd.org [149.20.53.66])
	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
	(Client CN "mail.NetBSD.org", Issuer "Postmaster NetBSD.org" (verified OK))
	by mollari.NetBSD.org (Postfix) with ESMTPS id ED8477069D
	for <gnats-bugs@gnats.NetBSD.org>; Wed, 12 Jun 2013 12:01:29 +0000 (UTC)
Message-Id: <20130612120123.0D96D706B1@mollari.NetBSD.org>
Date: Wed, 12 Jun 2013 12:01:23 +0000 (UTC)
From: nonakap@gmail.com
Reply-To: nonakap@gmail.com
To: gnats-bugs@NetBSD.org
Subject: ftp(1): FTPS support
X-Send-Pr-Version: www-1.0

>Number:         47920
>Category:       bin
>Synopsis:       ftp(1): FTPS support
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Wed Jun 12 12:05:00 +0000 2013
>Originator:     NONAKA Kimihiro
>Release:        6.99.21
>Organization:
>Environment:
NetBSD koharu.myhome.local 6.99.21 NetBSD 6.99.21 (KOHARU) #1985: Thu Jun  6 22:05:15 JST 2013  nonaka@koharu.myhome.local:/usr/obj.amd64/sys/arch/amd64/compile/KOHARU amd64
>Description:
ftp(1): FTPS support
>How-To-Repeat:

>Fix:
Apply following patch.

diff --git a/extern.h b/extern.h
index e856ef4..41e899b 100644
--- a/extern.h
+++ b/extern.h
@@ -89,11 +89,13 @@
  * SUCH DAMAGE.
  */

+#include "ssl.h"
+
 struct sockaddr;
 struct tm;
 struct addrinfo;

-void	abort_remote(FILE *);
+void	abort_remote(FETCH *);
 void	account(int, char **);
 void	ai_unmapped(struct addrinfo *);
 int	another(int *, char ***, const char *);
@@ -114,7 +116,7 @@ unsigned char complete(EditLine *, int);
 void	controlediting(void);
 #endif /* !NO_EDITCOMPLETE */
 void	crankrate(int);
-FILE   *dataconn(const char *);
+FETCH   *dataconn(const char *);
 void	delete(int, char **);
 void	disconnect(int, char **);
 void	do_chmod(int, char **);
diff --git a/fetch.c b/fetch.c
index e837d8f..d0b2ed2 100644
--- a/fetch.c
+++ b/fetch.c
@@ -64,7 +64,6 @@ __RCSID("$NetBSD: fetch.c,v 1.202 2013/02/23 13:47:36 christos Exp $");
 #include <unistd.h>
 #include <time.h>

-#include "ssl.h"
 #include "ftp_var.h"
 #include "version.h"

@@ -75,6 +74,10 @@ typedef enum {
 	HTTPS_URL_T,
 #endif
 	FTP_URL_T,
+#ifdef WITH_SSL
+	FTPS_URL_T,
+	FTPES_URL_T,
+#endif
 	FILE_URL_T,
 	CLASSIC_URL_T
 } url_t;
@@ -105,14 +108,18 @@ static int	redirect_loop;
 #define	FTP_URL		"ftp://"	/* ftp URL prefix */
 #define	HTTP_URL	"http://"	/* http URL prefix */
 #ifdef WITH_SSL
+#define	FTPS_URL	"ftps://"	/* ftps URL prefix */
+#define	FTPES_URL	"ftpes://"	/* ftpes URL prefix */
 #define	HTTPS_URL	"https://"	/* https URL prefix */

-#define	IS_HTTP_TYPE(urltype) \
-	(((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T))
-#else
-#define	IS_HTTP_TYPE(urltype) \
-	((urltype) == HTTP_URL_T)
-#endif
+#define	IS_FTP_TYPE(t) \
+	(((t) == FTP_URL_T) || ((t) == FTPS_URL_T) || ((t) == FTPES_URL_T))
+#define	IS_HTTP_TYPE(t) \
+	(((t) == HTTP_URL_T) || ((t) == HTTPS_URL_T))
+#else	/* !WITH_SSL */
+#define	IS_FTP_TYPE(t)	((t) == FTP_URL_T)
+#define	IS_HTTP_TYPE(t)	((t) == HTTP_URL_T)
+#endif	/* WITH_SSL */

 /*
  * Determine if token is the next word in buf (case insensitive).
@@ -364,6 +371,20 @@ parse_url(const char *url, const char *desc, url_t *utype,
 		*utype = HTTPS_URL_T;
 		*portnum = HTTPS_PORT;
 		tport = httpsport;
+	} else if (STRNEQUAL(url, FTPS_URL)) {
+		url += sizeof(FTPS_URL) - 1;
+		*utype = FTPS_URL_T;
+		*portnum = FTPS_PORT;
+		tport = ftpsport;
+		ftpssl = 1;
+		ftps_explicit = 0;
+	} else if (STRNEQUAL(url, FTPES_URL)) {
+		url += sizeof(FTPES_URL) - 1;
+		*utype = FTPES_URL_T;
+		*portnum = FTPS_PORT;
+		tport = ftpsport;
+		ftpssl = 1;
+		ftps_explicit = 1;
 #endif
 	} else {
 		warnx("Invalid %s `%s'", desc, url);
@@ -389,14 +410,14 @@ parse_url(const char *url, const char *desc, url_t *utype,
 		len = ep - url;
 		thost = (char *)ftp_malloc(len + 1);
 		(void)strlcpy(thost, url, len + 1);
-		if (*utype == FTP_URL_T)	/* skip first / for ftp URLs */
+		if (IS_FTP_TYPE(*utype))	/* skip first / for ftp URLs */
 			ep++;
 		*path = ftp_strdup(ep);
 	}

 	cp = strchr(thost, '@');	/* look for user[:pass]@ in URLs */
 	if (cp != NULL) {
-		if (*utype == FTP_URL_T)
+		if (IS_FTP_TYPE(*utype))
 			anonftp = 0;	/* disable anonftp */
 		*uuser = thost;
 		*cp = '\0';
@@ -463,7 +484,7 @@ parse_url(const char *url, const char *desc, url_t *utype,
 		*port = ftp_strdup(tport);
 	if (*path == NULL) {
 		const char *emptypath = "/";
-		if (*utype == FTP_URL_T)	/* skip first / for ftp URLs */
+		if (IS_FTP_TYPE(*utype))	/* skip first / for ftp URLs */
 			emptypath++;
 		*path = ftp_strdup(emptypath);
 	}
@@ -523,7 +544,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 	url_t			urltype;
 	in_port_t		portnum;
 #ifdef WITH_SSL
-	void			*ssl;
+	struct fetch_ssl	*volatile ssl;
 #endif

 	DPRINTF("fetch_url: `%s' proxyenv `%s'\n", url, STRorNULL(proxyenv));
@@ -550,7 +571,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 	}

 	if (EMPTYSTRING(path)) {
-		if (urltype == FTP_URL_T) {
+		if (IS_FTP_TYPE(urltype)) {
 			rval = fetch_ftp(url);
 			goto cleanup_fetch_url;
 		}
@@ -574,7 +595,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 	}
 	DPRINTF("fetch_url: savefile `%s'\n", savefile);
 	if (EMPTYSTRING(savefile)) {
-		if (urltype == FTP_URL_T) {
+		if (IS_FTP_TYPE(urltype)) {
 			rval = fetch_ftp(url);
 			goto cleanup_fetch_url;
 		}
@@ -631,7 +652,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 #endif
 			if (proxyenv == NULL && IS_HTTP_TYPE(urltype))
 				proxyenv = getoptionvalue("http_proxy");
-			else if (urltype == FTP_URL_T)
+			else if (IS_FTP_TYPE(urltype))
 				proxyenv = getoptionvalue("ftp_proxy");
 		}
 		direction = "retrieved";
@@ -673,7 +694,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 					}
 				}
 				FREEPTR(np_copy);
-				if (isproxy == 0 && urltype == FTP_URL_T) {
+				if (isproxy == 0 && IS_FTP_TYPE(urltype)) {
 					rval = fetch_ftp(url);
 					goto cleanup_fetch_url;
 				}
@@ -691,7 +712,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 					goto cleanup_fetch_url;

 				if ((!IS_HTTP_TYPE(purltype)
-				     && purltype != FTP_URL_T) ||
+				     && !IS_FTP_TYPE(purltype)) ||
 				    EMPTYSTRING(phost) ||
 				    (! EMPTYSTRING(ppath)
 				     && strcmp(ppath, "/") != 0)) {
@@ -791,11 +812,26 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)

 		if (s < 0) {
 			warnx("Can't connect to `%s:%s'", host, port);
+#ifdef WITH_SSL
+			fetch_stop_ssl(ssl);
+#endif
 			goto cleanup_fetch_url;
 		}

 		fin = fetch_fdopen(s, "r+");
-		fetch_set_ssl(fin, ssl);
+		if (fin == NULL) {
+			warnx("fdopen failed");
+#ifdef WITH_SSL
+			fetch_stop_ssl(ssl);
+#endif
+			goto cleanup_fetch_url;
+		}
+#ifdef WITH_SSL
+		if (urltype == HTTPS_URL_T) {
+			fetch_set_ssl(fin, ssl);
+			ssl = NULL;
+		}
+#endif

 		/*
 		 * Construct and send the request.
@@ -1254,7 +1290,7 @@ fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
 			while (bufrem > 0) {
 				flen = fetch_read(xferbuf, sizeof(char),
 				    MIN((off_t)bufsize, bufrem), fin);
-				if (flen <= 0)
+				if (flen == 0)
 					goto chunkdone;
 				bytes += flen;
 				bufrem -= flen;
@@ -1414,7 +1450,12 @@ fetch_ftp(const char *url)
 	rval = 1;
 	transtype = TYPE_I;

-	if (STRNEQUAL(url, FTP_URL)) {
+	if (STRNEQUAL(url, FTP_URL)
+#ifdef WITH_SSL
+	    || STRNEQUAL(url, FTPS_URL)
+	    || STRNEQUAL(url, FTPES_URL)
+#endif
+	   ) {
 		if ((parse_url(url, "URL", &urltype, &uuser, &pass,
 		    &host, &port, &portnum, &path) == -1) ||
 		    (uuser != NULL && *uuser == '\0') ||
@@ -1502,7 +1543,7 @@ fetch_ftp(const char *url)
 		}
 	} else
 		dir = NULL;
-	if (urltype == FTP_URL_T && file != NULL) {
+	if (IS_FTP_TYPE(urltype) && file != NULL) {
 		url_decode(file);
 		/* but still don't url_decode(dir) */
 	}
@@ -1628,7 +1669,7 @@ fetch_ftp(const char *url)
 		 * Note that we don't need `dir' after this point.
 		 */
 		do {
-			if (urltype == FTP_URL_T) {
+			if (IS_FTP_TYPE(urltype)) {
 				nextpart = strchr(dir, '/');
 				if (nextpart) {
 					*nextpart = '\0';
@@ -1639,7 +1680,7 @@ fetch_ftp(const char *url)
 				nextpart = NULL;
 			DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n",
 			    STRorNULL(dir), STRorNULL(nextpart));
-			if (urltype == FTP_URL_T || *dir != '\0') {
+			if (IS_FTP_TYPE(urltype) || *dir != '\0') {
 				(void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf));
 				xargv[0] = cmdbuf;
 				xargv[1] = dir;
@@ -1796,7 +1837,11 @@ go_fetch(const char *url)
 	 * part before the colon is a host name, not an URL scheme,
 	 * so we don't try to match that here.
 	 */
-	if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL))
+	if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL)
+#ifdef WITH_SSL
+	    && ! STRNEQUAL(url, FTPS_URL) && ! STRNEQUAL(url, FTPES_URL)
+#endif
+	   )
 		errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url);

 	/*
@@ -1805,7 +1850,11 @@ go_fetch(const char *url)
 	 * Othewise, use fetch_ftp().
 	 */
 	proxyenv = getoptionvalue("ftp_proxy");
-	if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL))
+	if (!EMPTYSTRING(proxyenv) && (STRNEQUAL(url, FTP_URL)
+#ifdef WITH_SSL
+	    || STRNEQUAL(url, FTPS_URL) || STRNEQUAL(url, FTPES_URL)
+#endif
+	    ))
 		return (fetch_url(url, NULL, NULL, NULL));

 	return (fetch_ftp(url));
diff --git a/ftp.c b/ftp.c
index d1a8785..d43a35b 100644
--- a/ftp.c
+++ b/ftp.c
@@ -131,7 +131,7 @@ int	ptabflg;
 int	ptflag = 0;
 char	pasv[BUFSIZ];	/* passive port for proxy data connection */

-static int empty(FILE *, FILE *, int);
+static int empty(FETCH *, FETCH *, int);
 __dead static void abort_squared(int);

 struct sockinet {
@@ -164,6 +164,9 @@ hookup(const char *host, const char *port)
 	static char hostnamebuf[MAXHOSTNAMELEN];
 	socklen_t len;
 	int on = 1;
+#ifdef WITH_SSL
+	struct fetch_ssl *ssl = NULL;
+#endif

 	memset((char *)&hisctladdr, 0, sizeof (hisctladdr));
 	memset((char *)&myctladdr, 0, sizeof (myctladdr));
@@ -202,6 +205,9 @@ hookup(const char *host, const char *port)
 				/* if we have multiple possibilities */
 			fprintf(ttyout, "Trying %s:%s ...\n", hname, sname);
 		}
+#ifdef WITH_SSL
+ retry:
+#endif
 		s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol);
 		if (s < 0) {
 			warn("Can't create socket for connection to `%s:%s'",
@@ -215,6 +221,21 @@ hookup(const char *host, const char *port)
 			continue;
 		}

+#ifdef WITH_SSL
+		if (ftpssl && !ftps_explicit) {
+			if ((ssl = fetch_start_ssl(s)) == NULL) {
+				close(s);
+				s = -1;
+				if (ftps_fallback) {
+					ftps_explicit = 1;
+					goto retry;
+				} else {
+					continue;
+				}
+			}
+		}
+#endif
+
 		/* finally we got one */
 		break;
 	}
@@ -248,24 +269,31 @@ hookup(const char *host, const char *port)
 		}
 	}
 #endif
-	cin = fdopen(s, "r");
-	cout = fdopen(s, "w");
+	cin = fetch_fdopen(s, "r");
+	cout = fetch_fdopen(s, "w");
 	if (cin == NULL || cout == NULL) {
 		warnx("Can't fdopen socket");
 		if (cin)
-			(void)fclose(cin);
+			(void)fetch_close(cin);
 		if (cout)
-			(void)fclose(cout);
+			(void)fetch_close(cout);
 		code = -1;
 		goto bad;
 	}
+#ifdef WITH_SSL
+	if (ftpssl && !ftps_explicit) {
+		fetch_set_ssl(cin, ssl);
+		fetch_set_ssl(cout, ssl);
+		ssl = NULL;
+	}
+#endif
 	if (verbose)
 		fprintf(ttyout, "Connected to %s.\n", hostname);
 	if (getreply(0) > 2) {	/* read startup message from server */
 		if (cin)
-			(void)fclose(cin);
+			(void)fetch_close(cin);
 		if (cout)
-			(void)fclose(cout);
+			(void)fetch_close(cout);
 		code = -1;
 		goto bad;
 	}
@@ -277,6 +305,9 @@ hookup(const char *host, const char *port)

 	return (hostname);
  bad:
+#ifdef WITH_SSL
+	fetch_stop_ssl(ssl);
+#endif
 	(void)close(s);
 	return (NULL);
 }
@@ -343,10 +374,10 @@ command(const char *fmt, ...)
 	oldsigint = xsignal(SIGINT, cmdabort);

 	va_start(ap, fmt);
-	vfprintf(cout, fmt, ap);
+	fetch_vprintf(cout, fmt, ap);
 	va_end(ap);
-	fputs("\r\n", cout);
-	(void)fflush(cout);
+	fetch_printf(cout, "%s", "\r\n");
+	(void)fetch_flush(cout);
 	cpend = 1;
 	r = getreply(!strcmp(fmt, "QUIT"));
 	if (abrtflag && oldsigint != SIG_IGN)
@@ -382,20 +413,22 @@ getreply(int expecteof)
 		dig = n = code = 0;
 		cp = current_line;
 		while (alarmtimer(quit_time ? quit_time : 60),
-		       ((c = getc(cin)) != '\n')) {
+		       ((c = fetch_getc(cin)) != '\n')) {
 			if (c == IAC) {     /* handle telnet commands */
-				switch (c = getc(cin)) {
+				switch (c = fetch_getc(cin)) {
 				case WILL:
 				case WONT:
-					c = getc(cin);
-					fprintf(cout, "%c%c%c", IAC, DONT, c);
-					(void)fflush(cout);
+					c = fetch_getc(cin);
+					fetch_printf(cout, "%c%c%c", IAC, DONT,
+					    c);
+					(void)fetch_flush(cout);
 					break;
 				case DO:
 				case DONT:
-					c = getc(cin);
-					fprintf(cout, "%c%c%c", IAC, WONT, c);
-					(void)fflush(cout);
+					c = fetch_getc(cin);
+					fetch_printf(cout, "%c%c%c", IAC, WONT,
+					    c);
+					(void)fetch_flush(cout);
 					break;
 				default:
 					break;
@@ -412,7 +445,7 @@ getreply(int expecteof)
 				int reply_abrtflag = abrtflag;

 				alarmtimer(0);
-				if (expecteof && feof(cin)) {
+				if (expecteof && fetch_eof(cin)) {
 					(void)xsignal(SIGINT, oldsigint);
 					(void)xsignal(SIGALRM, oldsigalrm);
 					code = 221;
@@ -510,19 +543,19 @@ getreply(int expecteof)
 }

 static int
-empty(FILE *ecin, FILE *din, int sec)
+empty(FETCH *ecin, FETCH *din, int sec)
 {
 	int		nr, nfd;
 	struct pollfd	pfd[2];

 	nfd = 0;
 	if (ecin) {
-		pfd[nfd].fd = fileno(ecin);
+		pfd[nfd].fd = fetch_fileno(ecin);
 		pfd[nfd++].events = POLLIN;
 	}

 	if (din) {
-		pfd[nfd].fd = fileno(din);
+		pfd[nfd].fd = fetch_fileno(din);
 		pfd[nfd++].events = POLLIN;
 	}

@@ -576,15 +609,16 @@ abortxfer(int notused)
  * In the case of error, errno contains the appropriate error code.
  */
 static int
-copy_bytes(int infd, int outfd, char *buf, size_t bufsize,
+copy_bytes(FETCH *infd, FETCH *outfd, char *buf, size_t bufsize,
 	int rate_limit, int hash_interval)
 {
 	volatile off_t	hashc;
-	ssize_t		inc, outc;
+	size_t		inc, outc;
 	char		*bufp;
 	struct timeval	tvthen, tvnow, tvdiff;
 	off_t		bufrem, bufchunk;
 	int		serr;
+	int		rv = 0;

 	hashc = hash_interval;
 	if (rate_limit)
@@ -601,16 +635,23 @@ copy_bytes(int infd, int outfd, char *buf, size_t bufsize,
 					/* copy bufchunk at a time */
 		bufrem = bufchunk;
 		while (bufrem > 0) {
-			inc = read(infd, buf, MIN((off_t)bufsize, bufrem));
-			if (inc <= 0)
+			inc = fetch_read(buf, 1, MIN((off_t)bufsize, bufrem),
+			    infd);
+			if (inc == 0) {
+				if (!fetch_eof(infd))
+					rv = 1;
 				goto copy_done;
+			}
 			bytes += inc;
 			bufrem -= inc;
 			bufp = buf;
 			while (inc > 0) {
-				outc = write(outfd, bufp, inc);
-				if (outc < 0)
+				outc = fetch_write(bufp, 1, inc, outfd);
+				if (outc == 0) {
+					if (fetch_error(outfd))
+						rv = 2;
 					goto copy_done;
+				}
 				inc -= outc;
 				bufp += outc;
 			}
@@ -642,12 +683,7 @@ copy_bytes(int infd, int outfd, char *buf, size_t bufsize,
 		(void)fflush(ttyout);
 	}
 	errno = serr;
-	if (inc == -1)
-		return 1;
-	if (outc == -1)
-		return 2;
-
-	return 0;
+	return rv;
 }

 void
@@ -657,7 +693,7 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 	struct stat st;
 	int c;
 	FILE *volatile fin;
-	FILE *volatile dout;
+	FETCH *volatile dout;
 	int (*volatile closefunc)(FILE *);
 	sigfunc volatile oldintr;
 	sigfunc volatile oldintp;
@@ -667,6 +703,10 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 	static size_t bufsize;
 	static char *buf;
 	int oprogress;
+#ifdef WITH_SSL
+	FETCH *volatile fin2;
+	int fdin;
+#endif

 	hashbytes = mark;
 	direction = "sent";
@@ -783,8 +823,22 @@ sendrequest(const char *cmd, const char *local, const char *remote,

 	case TYPE_I:
 	case TYPE_L:
-		c = copy_bytes(fileno(fin), fileno(dout), buf, bufsize,
-			       rate_put, hash_interval);
+#ifdef WITH_SSL
+		c = 1;
+		fdin = dup(fileno(fin));
+		if (fdin >= 0) {
+			fin2 = fetch_fdopen(fdin, "r");
+			if (fin2 != NULL) {
+				c = copy_bytes(fin2, dout, buf, bufsize,
+				    rate_put, hash_interval);
+				fetch_close(fin2);
+			} else
+				close(fdin);
+		}
+#else /* !WITH_SSL */
+		c = copy_bytes(fin, dout, buf, bufsize, rate_put,
+		    hash_interval);
+#endif /* WITH_SSL */
 		if (c == 1) {
 			warn("Reading `%s'", local);
 		} else if (c == 2) {
@@ -802,16 +856,16 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 					(void)fflush(ttyout);
 					hashbytes += mark;
 				}
-				if (ferror(dout))
+				if (fetch_error(dout))
 					break;
-				(void)putc('\r', dout);
+				(void)fetch_putc('\r', dout);
 				bytes++;
 			}
-			(void)putc(c, dout);
+			(void)fetch_putc(c, dout);
 			bytes++;
 #if 0	/* this violates RFC 959 */
 			if (c == '\r') {
-				(void)putc('\0', dout);
+				(void)fetch_putc('\0', dout);
 				bytes++;
 			}
 #endif
@@ -823,7 +877,7 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 		}
 		if (ferror(fin))
 			warn("Reading `%s'", local);
-		if (ferror(dout)) {
+		if (fetch_error(dout)) {
 			if (errno != EPIPE)
 				warn("Writing to network");
 			bytes = -1;
@@ -836,7 +890,7 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 		(*closefunc)(fin);
 		fin = NULL;
 	}
-	(void)fclose(dout);
+	(void)fetch_close(dout);
 	dout = NULL;
 	(void)getreply(0);
 	if (bytes > 0)
@@ -855,7 +909,7 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 		data = -1;
 	}
 	if (dout) {
-		(void)fclose(dout);
+		(void)fetch_close(dout);
 		dout = NULL;
 	}
 	(void)getreply(0);
@@ -875,7 +929,7 @@ sendrequest(const char *cmd, const char *local, const char *remote,
 	if (closefunc != NULL && fin != NULL)
 		(*closefunc)(fin);
 	if (dout)
-		(void)fclose(dout);
+		(void)fetch_close(dout);
 	progress = oprogress;
 	restart_point = 0;
 	bytes = 0;
@@ -886,7 +940,7 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 	    const char *lmode, int printnames, int ignorespecial)
 {
 	FILE *volatile fout;
-	FILE *volatile din;
+	FETCH *volatile din;
 	int (*volatile closefunc)(FILE *);
 	sigfunc volatile oldintr;
 	sigfunc volatile oldintp;
@@ -903,6 +957,11 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 	struct timeval tval[2];
 	int oprogress;
 	int opreserve;
+#ifdef WITH_SSL
+	const char *fmode;
+	FETCH *volatile fout2;
+	int fdout;
+#endif

 	fout = NULL;
 	din = NULL;
@@ -1003,11 +1062,17 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 	if (din == NULL)
 		goto abort;
 	if (!ignorespecial && strcmp(local, "-") == 0) {
+#ifdef WITH_SSL
+		fmode = "w";
+#endif
 		fout = stdout;
 		progress = 0;
 		preserve = 0;
 	} else if (!ignorespecial && *local == '|') {
 		oldintp = xsignal(SIGPIPE, SIG_IGN);
+#ifdef WITH_SSL
+		fmode = "w";
+#endif
 		fout = popen(local + 1, "w");
 		if (fout == NULL) {
 			warn("Can't execute `%s'", local+1);
@@ -1017,6 +1082,9 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 		preserve = 0;
 		closefunc = pclose;
 	} else {
+#ifdef WITH_SSL
+		fmode = lmode;
+#endif
 		fout = fopen(local, lmode);
 		if (fout == NULL) {
 			warn("Can't open `%s'", local);
@@ -1049,8 +1117,22 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 			warn("Can't seek to restart `%s'", local);
 			goto cleanuprecv;
 		}
-		c = copy_bytes(fileno(din), fileno(fout), buf, bufsize,
-			       rate_get, hash_interval);
+#ifdef WITH_SSL
+		c = 2;
+		fdout = dup(fileno(fout));
+		if (fdout >= 0) {
+			fout2 = fetch_fdopen(fdout, fmode);
+			if (fout2 != NULL) {
+				c = copy_bytes(din, fout2, buf, bufsize,
+				    rate_get, hash_interval);
+				fetch_close(fout2);
+			} else
+				close(fdout);
+		}
+#else /* !WITH_SSL */
+		c = copy_bytes(din, fout, buf, bufsize, rate_get,
+		    hash_interval);
+#endif /* WITH_SSL */
 		if (c == 1) {
 			if (errno != EPIPE)
 				warn("Reading from network");
@@ -1079,7 +1161,7 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 				goto cleanuprecv;
 			}
 		}
-		while ((c = getc(din)) != EOF) {
+		while ((c = fetch_getc(din)) != EOF) {
 			if (c == '\n')
 				bare_lfs++;
 			while (c == '\r') {
@@ -1089,7 +1171,7 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 					hashbytes += mark;
 				}
 				bytes++;
-				if ((c = getc(din)) != '\n' || tcrflag) {
+				if ((c = fetch_getc(din)) != '\n' || tcrflag) {
 					if (ferror(fout))
 						goto break2;
 					(void)putc('\r', fout);
@@ -1111,7 +1193,7 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 				(void)putc('#', ttyout);
 			(void)putc('\n', ttyout);
 		}
-		if (ferror(din)) {
+		if (fetch_error(din)) {
 			if (errno != EPIPE)
 				warn("Reading from network");
 			bytes = -1;
@@ -1126,7 +1208,7 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 		(*closefunc)(fout);
 		fout = NULL;
 	}
-	(void)fclose(din);
+	(void)fetch_close(din);
 	din = NULL;
 	(void)getreply(0);
 	if (bare_lfs) {
@@ -1184,7 +1266,7 @@ recvrequest(const char *cmd, const char *volatile local, const char *remote,
 	if (closefunc != NULL && fout != NULL)
 		(*closefunc)(fout);
 	if (din)
-		(void)fclose(din);
+		(void)fetch_close(din);
 	progress = oprogress;
 	preserve = opreserve;
 	bytes = 0;
@@ -1650,7 +1732,7 @@ initconn(void)
 	return (1);
 }

-FILE *
+FETCH *
 dataconn(const char *lmode)
 {
 	struct sockinet	from;
@@ -1658,9 +1740,13 @@ dataconn(const char *lmode)
 	struct timeval	endtime, now, td;
 	struct pollfd	pfd[1];
 	socklen_t	fromlen;
+	FETCH		*conn;
+#ifdef WITH_SSL
+	struct fetch_ssl *ssl;
+#endif

 	if (passivemode)	/* passive data connection */
-		return (fdopen(data, lmode));
+		goto dataconn_open;

 				/* active mode data connection */

@@ -1718,7 +1804,27 @@ dataconn(const char *lmode)
 		}
 	}
 #endif
-	return (fdopen(data, lmode));
+ dataconn_open:
+#ifdef WITH_SSL
+	ssl = NULL;
+	if (ftpssl) {
+		if ((ssl = fetch_start_ssl(data)) == NULL) {
+			warn("SSL negotiation failed on data connection");
+			goto dataconn_failed;
+		}
+	}
+#endif
+	conn = fetch_fdopen(data, lmode);
+#ifdef WITH_SSL
+	if (ftpssl) {
+		if (conn == NULL) {
+			fetch_stop_ssl(ssl);
+			goto dataconn_failed;
+		}
+		fetch_set_ssl(conn, ssl);
+	}
+#endif
+	return conn;

  dataconn_failed:
 	(void)close(data);
@@ -1746,8 +1852,8 @@ pswitch(int flag)
 		char name[MAXHOSTNAMELEN];
 		struct sockinet mctl;
 		struct sockinet hctl;
-		FILE *in;
-		FILE *out;
+		FETCH *in;
+		FETCH *out;
 		int tpe;
 		int curtpe;
 		int cpnd;
@@ -2052,7 +2158,7 @@ abort_squared(int dummy)
 }

 void
-abort_remote(FILE *din)
+abort_remote(FETCH *din)
 {
 	char buf[BUFSIZ];
 	int nfnd;
@@ -2071,10 +2177,10 @@ abort_remote(FILE *din)
 	buf[0] = IAC;
 	buf[1] = IP;
 	buf[2] = IAC;
-	if (send(fileno(cout), buf, 3, MSG_OOB) != 3)
+	if (fetch_send(cout, buf, 3, MSG_OOB) != 3)
 		warn("Can't send abort message");
-	fprintf(cout, "%cABOR\r\n", DM);
-	(void)fflush(cout);
+	fetch_printf(cout, "%cABOR\r\n", DM);
+	(void)fetch_flush(cout);
 	if ((nfnd = empty(cin, din, 10)) <= 0) {
 		if (nfnd < 0)
 			warn("Can't send abort message");
@@ -2083,7 +2189,7 @@ abort_remote(FILE *din)
 		lostpeer(0);
 	}
 	if (din && (nfnd & 2)) {
-		while (read(fileno(din), buf, BUFSIZ) > 0)
+		while (fetch_read(buf, 1, BUFSIZ, din) > 0)
 			continue;
 	}
 	if (getreply(0) == ERROR && code == 552) {
diff --git a/ftp_var.h b/ftp_var.h
index 7e5040a..7db055e 100644
--- a/ftp_var.h
+++ b/ftp_var.h
@@ -116,6 +116,7 @@

 #include "extern.h"
 #include "progressbar.h"
+#include "ssl.h"

 /*
  * Format of command table.
@@ -162,6 +163,9 @@ enum {
 	FEAT_REST_STREAM,	/* RESTart STREAM */
 	FEAT_SIZE,		/* SIZE */
 	FEAT_TVFS,		/* TVFS (not used) */
+	FEAT_PBSZ,		/* PBSZ */
+	FEAT_PROT,		/* PROT */
+	FEAT_AUTH_TLS,		/* AUTH TLS */
 	FEAT_max
 };

@@ -176,6 +180,7 @@ enum {
 #define	DEFAULTINCR	1024	/* default increment for `rate' command */

 #define	FTP_PORT	21	/* default if ! getservbyname("ftp/tcp") */
+#define	FTPS_PORT	990	/* default if ! getservbyname("ftps/tcp") */
 #define	HTTP_PORT	80	/* default if ! getservbyname("http/tcp") */
 #define	HTTPS_PORT	443	/* default if ! getservbyname("https/tcp") */
 #ifndef	GATE_PORT
@@ -255,6 +260,11 @@ GLOBAL	int	epsv6;		/* use EPSV/EPRT on IPv6 connections */
 GLOBAL	int	epsv6bad;	/* EPSV doesn't work on the current server */
 GLOBAL	int	editing;	/* command line editing enabled */
 GLOBAL	int	features[FEAT_max];	/* remote FEATures supported */
+#ifdef WITH_SSL
+GLOBAL	int	ftpssl;		/* use FTPS */
+GLOBAL	int	ftps_explicit;	/* FTPS explicit mode */
+GLOBAL	int	ftps_fallback;	/* explicit mode from implicit mode */
+#endif

 #ifndef NO_EDITCOMPLETE
 GLOBAL	EditLine *el;		/* editline(3) status structure */
@@ -275,6 +285,7 @@ GLOBAL	sa_family_t family;	/* address family to use for connections */
 GLOBAL	const char *ftpport;	/* port number to use for FTP connections */
 GLOBAL	const char *httpport;	/* port number to use for HTTP connections */
 #ifdef WITH_SSL
+GLOBAL	const char *ftpsport;	/* port number to use for FTPS connections */
 GLOBAL	const char *httpsport;	/* port number to use for HTTPS connections */
 #endif
 GLOBAL	const char *gateport;	/* port number to use for gateftp connections */
@@ -315,8 +326,8 @@ GLOBAL	void	(*reply_callback)(const char *);

 GLOBAL	volatile sig_atomic_t	sigint_raised;

-GLOBAL	FILE	*cin;
-GLOBAL	FILE	*cout;
+GLOBAL	FETCH	*cin;
+GLOBAL	FETCH	*cout;
 GLOBAL	int	 data;

 extern	struct cmd	cmdtab[];
diff --git a/main.c b/main.c
index c84364d..438fc2a 100644
--- a/main.c
+++ b/main.c
@@ -152,6 +152,7 @@ main(int volatile argc, char **volatile argv)
 	ftpport = "ftp";
 	httpport = "http";
 #ifdef WITH_SSL
+	ftpsport = "ftps";
 	httpsport = "https";
 #endif
 	gateport = NULL;
@@ -201,6 +202,11 @@ main(int volatile argc, char **volatile argv)
 #else
 	family = AF_INET;	/* force AF_INET if no INET6 support */
 #endif
+#ifdef WITH_SSL
+	ftpssl = 0;
+	ftps_explicit = 0;
+	ftps_fallback = 1;
+#endif

 	netrc[0] = '\0';
 	cp = getenv("NETRC");
@@ -335,6 +341,9 @@ main(int volatile argc, char **volatile argv)
 			break;

 		case 'P':
+#ifdef WITH_SSL
+			ftpsport =
+#endif
 			ftpport = optarg;
 			break;

@@ -1048,6 +1057,10 @@ usage(void)
 "           [-r retry] [-s srcaddr] [-T dir,max[,inc]]\n"
 "           [[user@]host [port]] [host:path[/]] [file:///file]\n"
 "           [ftp://[user[:pass]@]host[:port]/path[/]]\n"
+#ifdef WITH_SSL
+"           [ftps://[user[:pass]@]host[:port]/path[/]]\n" /* Implicit */
+"           [ftpes://[user[:pass]@]host[:port]/path[/]]\n" /* Explicit */
+#endif
 "           [http://[user[:pass]@]host[:port]/path] [...]\n"
 #ifdef WITH_SSL
 "           [https://[user[:pass]@]host[:port]/path] [...]\n"
diff --git a/ssl.c b/ssl.c
index b34c864..1439af3 100644
--- a/ssl.c
+++ b/ssl.c
@@ -42,6 +42,7 @@ __RCSID("$NetBSD: ssl.c,v 1.2 2012/12/24 22:12:28 christos Exp $");

 #include <sys/param.h>
 #include <sys/select.h>
+#include <sys/stat.h>
 #include <sys/uio.h>

 #include <netinet/tcp.h>
@@ -57,6 +58,11 @@ __RCSID("$NetBSD: ssl.c,v 1.2 2012/12/24 22:12:28 christos Exp $");
 extern int quit_time, verbose, ftp_debug;
 extern FILE *ttyout;

+struct fetch_ssl {
+	SSL	*ssl;
+	int	refcnt;
+};
+
 struct fetch_connect {
 	int			 sd;		/* file/socket descriptor */
 	char			*buf;		/* buffer */
@@ -73,7 +79,7 @@ struct fetch_connect {
 	int 			 issock;
 	int			 iserr;
 	int			 iseof;
-	SSL			*ssl;		/* SSL handle */
+	struct fetch_ssl	*ssl;		/* SSL handle */
 };

 /*
@@ -119,7 +125,8 @@ fetch_writev(struct fetch_connect *conn, struct iovec *iov, int iovcnt)
 		}
 		errno = 0;
 		if (conn->ssl != NULL)
-			len = SSL_write(conn->ssl, iov->iov_base, iov->iov_len);
+			len = SSL_write(conn->ssl->ssl, iov->iov_base,
+			    iov->iov_len);
 		else
 			len = writev(conn->sd, iov, iovcnt);
 		if (len == 0) {
@@ -150,14 +157,23 @@ fetch_writev(struct fetch_connect *conn, struct iovec *iov, int iovcnt)
 /*
  * Write to a connection w/ timeout
  */
-static int
-fetch_write(struct fetch_connect *conn, const char *str, size_t len)
+size_t
+fetch_write(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
 {
 	struct iovec iov[1];
+	int rv;

-	iov[0].iov_base = (char *)__UNCONST(str);
-	iov[0].iov_len = len;
-	return fetch_writev(conn, iov, 1);
+	if (size == 0 || nmemb == 0)
+		return 0;
+
+	iov[0].iov_base = ptr;
+	iov[0].iov_len = size * nmemb;
+	rv = fetch_writev(conn, iov, 1);
+	if (rv < 0) {
+		conn->iserr = 1;
+		return 0;
+	}
+	return rv;
 }

 /*
@@ -167,24 +183,35 @@ int
 fetch_printf(struct fetch_connect *conn, const char *fmt, ...)
 {
 	va_list ap;
+	int r;
+
+	va_start(ap, fmt);
+	r = fetch_vprintf(conn, fmt, ap);
+	va_end(ap);
+
+	return r;
+}
+
+int
+fetch_vprintf(struct fetch_connect *conn, const char *fmt, va_list ap)
+{
 	size_t len;
 	char *msg;
 	int r;

-	va_start(ap, fmt);
 	len = vasprintf(&msg, fmt, ap);
-	va_end(ap);

 	if (msg == NULL) {
 		errno = ENOMEM;
 		return -1;
 	}

-	r = fetch_write(conn, msg, len);
+	r = fetch_write(msg, 1, len, conn);
 	free(msg);
 	return r;
 }

+
 int
 fetch_fileno(struct fetch_connect *conn)
 {
@@ -207,6 +234,13 @@ fetch_clearerr(struct fetch_connect *conn)
 }

 int
+fetch_eof(struct fetch_connect *conn)
+{
+
+	return conn->iseof;
+}
+
+int
 fetch_flush(struct fetch_connect *conn)
 {
 	int v;
@@ -227,9 +261,47 @@ struct fetch_connect *
 fetch_open(const char *fname, const char *fmode)
 {
 	struct fetch_connect *conn;
+	int mode, option;
 	int fd;

-	fd = open(fname, O_RDONLY); /* XXX: fmode */
+        switch (*fmode++) {
+	default: /* illegal mode */
+		return NULL;
+	case 'r': /* open for reading */
+		mode = O_RDONLY;
+		option = 0;
+		break;
+	case 'w': /* open for writing */
+		mode = O_WRONLY;
+		option = O_CREAT | O_TRUNC;
+		break;
+	case 'a': /* open for appending */
+		mode = O_WRONLY;
+		option = O_CREAT | O_APPEND;
+		break;
+        }
+	for (; *fmode != '\0'; fmode++) {
+		switch (*fmode) {
+		case '+':
+			mode = O_RDWR;
+			break;
+		case 'f':
+			option |= O_NONBLOCK;
+			break;
+		case 'e':
+			option |= O_CLOEXEC;
+			break;
+		case 'x':
+			option |= O_EXCL;
+			break;
+		case 'b':
+			break;
+		default:	/* We could produce a warning here */
+			break;
+		}
+	}
+
+	fd = open(fname, mode | option);
 	if (fd < 0)
 		return NULL;

@@ -248,22 +320,28 @@ struct fetch_connect *
 fetch_fdopen(int sd, const char *fmode)
 {
 	struct fetch_connect *conn;
+	struct stat sb;
 #if defined(SO_NOSIGPIPE) || defined(TCP_NOPUSH)
 	int opt = 1;
 #endif

+	if (fstat(sd, &sb) < 0)
+		return NULL;
+
 	if ((conn = calloc(1, sizeof(*conn))) == NULL)
 		return NULL;

 	conn->sd = sd;
-	conn->issock = 1;
-	fcntl(sd, F_SETFD, FD_CLOEXEC);
+	if (S_ISSOCK(sb.st_mode)) {
+		conn->issock = 1;
+		fcntl(sd, F_SETFD, FD_CLOEXEC);
 #ifdef SO_NOSIGPIPE
-	setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
+		setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
 #endif
 #ifdef TCP_NOPUSH
-	setsockopt(sd, IPPROTO_TCP, TCP_NOPUSH, &opt, sizeof(opt));
+		setsockopt(sd, IPPROTO_TCP, TCP_NOPUSH, &opt, sizeof(opt));
 #endif
+	}
 	return conn;
 }

@@ -274,7 +352,7 @@ fetch_close(struct fetch_connect *conn)

 	if (conn != NULL) {
 		fetch_flush(conn);
-		SSL_free(conn->ssl);
+		fetch_free_ssl(conn);
 		rv = close(conn->sd);
 		if (rv < 0) {
 			errno = rv;
@@ -287,8 +365,9 @@ fetch_close(struct fetch_connect *conn)
 	return rv;
 }

-#define FETCH_READ_WAIT		-2
-#define FETCH_READ_ERROR	-1
+#define FETCH_READ_INTR		-4
+#define FETCH_READ_WAIT		-3
+#define FETCH_READ_ERROR	-2

 static ssize_t
 fetch_ssl_read(SSL *ssl, void *buf, size_t len)
@@ -299,6 +378,9 @@ fetch_ssl_read(SSL *ssl, void *buf, size_t len)
 	rlen = SSL_read(ssl, buf, len);
 	if (rlen < 0) {
 		ssl_err = SSL_get_error(ssl, rlen);
+		if (ssl_err == SSL_ERROR_SYSCALL && errno == EINTR) {
+			return FETCH_READ_INTR;
+		}
 		if (ssl_err == SSL_ERROR_WANT_READ ||
 		    ssl_err == SSL_ERROR_WANT_WRITE) {
 			return FETCH_READ_WAIT;
@@ -316,7 +398,9 @@ fetch_nonssl_read(int sd, void *buf, size_t len)

 	rlen = read(sd, buf, len);
 	if (rlen < 0) {
-		if (errno == EAGAIN || errno == EINTR)
+		if (errno == EINTR)
+			return FETCH_READ_INTR;
+		if (errno == EAGAIN)
 			return FETCH_READ_WAIT;
 		return FETCH_READ_ERROR;
 	}
@@ -346,8 +430,8 @@ fetch_cache_data(struct fetch_connect *conn, char *src, size_t nbytes)
 	return 0;
 }

-ssize_t
-fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
+static ssize_t
+fetch_read1(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
 {
 	struct timeval now, timeout, delta;
 	fd_set readfds;
@@ -402,7 +486,7 @@ fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
 		 * slightly) when reading small amounts of data.
 		 */
 		if (conn->ssl != NULL)
-			rlen = fetch_ssl_read(conn->ssl, buf, len);
+			rlen = fetch_ssl_read(conn->ssl->ssl, buf, len);
 		else
 			rlen = fetch_nonssl_read(conn->sd, buf, len);
 		if (rlen == 0) {
@@ -412,9 +496,10 @@ fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
 			buf += rlen;
 			total += rlen;
 			continue;
+		} else if (rlen == FETCH_READ_INTR) {
+			fetch_cache_data(conn, start, total);
+			return -1;
 		} else if (rlen == FETCH_READ_ERROR) {
-			if (errno == EINTR)
-				fetch_cache_data(conn, start, total);
 			return -1;
 		}
 		FD_ZERO(&readfds);
@@ -440,6 +525,23 @@ fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
 	return total;
 }

+size_t
+fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn)
+{
+	ssize_t rlen;
+
+	rlen = fetch_read1(ptr, size, nmemb, conn);
+	if (rlen == -1) {
+		conn->iserr = 1;
+		return 0;
+	}
+	if (rlen == 0) {
+		conn->iseof = 1;
+		return 0;
+	}
+	return rlen;
+}
+
 #define MIN_BUF_SIZE 1024

 /*
@@ -449,8 +551,7 @@ char *
 fetch_getln(char *str, int size, struct fetch_connect *conn)
 {
 	size_t tmpsize;
-	ssize_t len;
-	char c;
+	int c;

 	if (conn->buf == NULL) {
 		if ((conn->buf = malloc(MIN_BUF_SIZE)) == NULL) {
@@ -471,14 +572,11 @@ fetch_getln(char *str, int size, struct fetch_connect *conn)
 	conn->bufpos = 0;
 	conn->buflen = 0;
 	do {
-		len = fetch_read(&c, sizeof(c), 1, conn);
-		if (len == -1) {
-			conn->iserr = 1;
-			return NULL;
-		}
-		if (len == 0) {
-			conn->iseof = 1;
-			break;
+		c = fetch_getc(conn);
+		if (c == EOF) {
+			if (fetch_error(conn))
+				return NULL;
+			break;	/* EOF */
 		}
 		conn->buf[conn->buflen++] = c;
 		if (conn->buflen == conn->bufsize) {
@@ -530,7 +628,7 @@ fetch_getline(struct fetch_connect *conn, char *buf, size_t buflen,
 	} else if (len == buflen - 1) {	/* line too long */
 		while (1) {
 			char c;
-			ssize_t rlen = fetch_read(&c, sizeof(c), 1, conn);
+			ssize_t rlen = fetch_read1(&c, 1, sizeof(c), conn);
 			if (rlen <= 0 || c == '\n')
 				break;
 		}
@@ -544,9 +642,54 @@ fetch_getline(struct fetch_connect *conn, char *buf, size_t buflen,
 	return len;
 }

-void *
+int
+fetch_getc(struct fetch_connect *conn)
+{
+	ssize_t rlen;
+	char c;
+
+	rlen = fetch_read1(&c, 1, sizeof(c), conn);
+	if (rlen == -1) {
+		conn->iserr = 1;
+		return EOF;
+	}
+	if (rlen == 0) {
+		conn->iseof = 1;
+		return EOF;
+	}
+	return c;
+}
+
+int
+fetch_putc(int c, struct fetch_connect *conn)
+{
+	char buf[1];
+	int r;
+
+	buf[0] = c;
+	r = fetch_write(buf, 1, 1, conn);
+	if (r < 0)
+		return EOF;
+	return c;
+}
+
+ssize_t
+fetch_send(struct fetch_connect *conn, const void *msg, size_t len, int flags)
+{
+	struct iovec iov[1];
+
+	if (conn->ssl == NULL)
+		return send(fetch_fileno(conn), msg, len, flags);
+
+	iov[0].iov_base = __UNCONST(msg);
+	iov[0].iov_len = len;
+	return fetch_writev(conn, iov, 1);
+}
+
+struct fetch_ssl *
 fetch_start_ssl(int sock)
 {
+	struct fetch_ssl *fssl;
 	SSL *ssl;
 	SSL_CTX *ctx;
 	int ret, ssl_err;
@@ -562,9 +705,17 @@ fetch_start_ssl(int sock)
 	ctx = SSL_CTX_new(SSLv23_client_method());
 	SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);

+	fssl = calloc(1, sizeof(*fssl));
+	if (fssl == NULL) {
+		fprintf(ttyout, "SSL memory allocation failed\n");
+		SSL_CTX_free(ctx);
+		return NULL;
+	}
+
 	ssl = SSL_new(ctx);
 	if (ssl == NULL){
 		fprintf(ttyout, "SSL context creation failed\n");
+		free(fssl);
 		SSL_CTX_free(ctx);
 		return NULL;
 	}
@@ -575,6 +726,7 @@ fetch_start_ssl(int sock)
 		    ssl_err != SSL_ERROR_WANT_WRITE) {
 			ERR_print_errors_fp(ttyout);
 			SSL_free(ssl);
+			free(fssl);
 			return NULL;
 		}
 	}
@@ -597,12 +749,39 @@ fetch_start_ssl(int sock)
 		free(str);
 	}

-	return ssl;
+	fssl->ssl = ssl;
+	fssl->refcnt = 0;
+	return fssl;
+}
+
+void
+fetch_stop_ssl(struct fetch_ssl *ssl)
+{
+
+	if (ssl != NULL) {
+		SSL_free(ssl->ssl);
+		free(ssl);
+	}
 }

+void
+fetch_set_ssl(struct fetch_connect *conn, struct fetch_ssl *ssl)
+{
+
+	if (ssl != NULL) {
+		ssl->refcnt++;
+		conn->ssl = ssl;
+	}
+}

 void
-fetch_set_ssl(struct fetch_connect *conn, void *ssl)
+fetch_free_ssl(struct fetch_connect *conn)
 {
-	conn->ssl = ssl;
+
+	if (conn != NULL && conn->ssl != NULL) {
+		if (--conn->ssl->refcnt <= 0) {
+			fetch_stop_ssl(conn->ssl);
+			conn->ssl = NULL;
+		}
+	}
 }
diff --git a/ssl.h b/ssl.h
index 9ecc759..fc872a5 100644
--- a/ssl.h
+++ b/ssl.h
@@ -25,38 +25,60 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */
+
+#ifndef	FTP_SSL_H
+#define	FTP_SSL_H
+
 #ifdef WITH_SSL

+#include <stdarg.h>
+
 #define FETCH struct fetch_connect
 struct fetch_connect;
+struct fetch_ssl;

-int fetch_printf(struct fetch_connect *, const char *fmt, ...);
+int fetch_printf(struct fetch_connect *, const char *, ...);
+int fetch_vprintf(struct fetch_connect *, const char *, va_list);
 int fetch_fileno(struct fetch_connect *);
 int fetch_error(struct fetch_connect *);
+int fetch_eof(struct fetch_connect *);
 int fetch_flush(struct fetch_connect *);
 struct fetch_connect *fetch_open(const char *, const char *);
 struct fetch_connect *fetch_fdopen(int, const char *);
 int fetch_close(struct fetch_connect *);
-ssize_t fetch_read(void *, size_t, size_t, struct fetch_connect *);
+size_t fetch_write(void *, size_t, size_t, struct fetch_connect *);
+size_t fetch_read(void *, size_t, size_t, struct fetch_connect *);
 char *fetch_getln(char *, int, struct fetch_connect *);
 int fetch_getline(struct fetch_connect *, char *, size_t, const char **);
-void fetch_set_ssl(struct fetch_connect *, void *);
-void *fetch_start_ssl(int);
+int fetch_getc(struct fetch_connect *);
+int fetch_putc(int, struct fetch_connect *);
+ssize_t fetch_send(struct fetch_connect *, const void *, size_t, int);
+void fetch_set_ssl(struct fetch_connect *, struct fetch_ssl *);
+void fetch_free_ssl(struct fetch_connect *);
+struct fetch_ssl *fetch_start_ssl(int);
+void fetch_stop_ssl(struct fetch_ssl *);

 #else	/* !WITH_SSL */

 #define FETCH FILE

-#define	fetch_printf	fprintf
-#define	fetch_fileno	fileno
-#define	fetch_error	ferror
-#define	fetch_flush	fflush
-#define	fetch_open	fopen
-#define	fetch_fdopen	fdopen
-#define	fetch_close	fclose
-#define	fetch_read	fread
-#define	fetch_getln	fgets
-#define	fetch_getline	get_line
-#define	fetch_set_ssl(a, b)
-
-#endif	/* !WITH_SSL */
+#define	fetch_printf		fprintf
+#define	fetch_vprintf		vfprintf
+#define	fetch_fileno		fileno
+#define	fetch_error		ferror
+#define	fetch_eof		feof
+#define	fetch_flush		fflush
+#define	fetch_open		fopen
+#define	fetch_fdopen		fdopen
+#define	fetch_close		fclose
+#define	fetch_write		fwrite
+#define	fetch_read		fread
+#define	fetch_getln		fgets
+#define	fetch_getline		get_line
+#define	fetch_getc		getc
+#define	fetch_putc		putc
+#define	fetch_send(f,m,l,fl)	send(fileno((f)),(m),(l),(fl))
+
+#endif	/* WITH_SSL */
+
+#endif	/* FTP_SSL_H */
diff --git a/util.c b/util.c
index ee610c6..976ca3b 100644
--- a/util.c
+++ b/util.c
@@ -124,6 +124,10 @@ setpeer(int argc, char *argv[])
 	}
 	if (gatemode)
 		port = gateport;
+#ifdef WITH_SSL
+	else if (ftpssl)
+		port = ftpsport;
+#endif
 	else
 		port = ftpport;
 	if (argc > 2)
@@ -184,6 +188,14 @@ parse_feat(const char *fline)
 		features[FEAT_SIZE] = 1;
 	else if (strcasecmp(fline, "TVFS") == 0)
 		features[FEAT_TVFS] = 1;
+#ifdef WITH_SSL
+	else if (strcasecmp(fline, "PBSZ") == 0)
+		features[FEAT_PBSZ] = 1;
+	else if (strcasecmp(fline, "PROT") == 0)
+		features[FEAT_PROT] = 1;
+	else if (strcasecmp(fline, "AUTH TLS") == 0)
+		features[FEAT_AUTH_TLS] = 1;
+#endif
 }

 /*
@@ -266,6 +278,11 @@ getremoteinfo(void)
 		DEBUG_FEAT(FEAT_REST_STREAM);
 		DEBUG_FEAT(FEAT_SIZE);
 		DEBUG_FEAT(FEAT_TVFS);
+#ifdef WITH_SSL
+		DEBUG_FEAT(FEAT_PBSZ);
+		DEBUG_FEAT(FEAT_PROT);
+		DEBUG_FEAT(FEAT_AUTH_TLS);
+#endif
 #undef DEBUG_FEAT
 	}
 #endif
@@ -285,7 +302,7 @@ cleanuppeer(void)
 {

 	if (cout)
-		(void)fclose(cout);
+		(void)fetch_close(cout);
 	cout = NULL;
 	connected = 0;
 	unix_server = 0;
@@ -333,8 +350,8 @@ lostpeer(int dummy)
 	alarmtimer(0);
 	if (connected) {
 		if (cout != NULL) {
-			(void)shutdown(fileno(cout), 1+1);
-			(void)fclose(cout);
+			(void)shutdown(fetch_fileno(cout), 1+1);
+			(void)fetch_close(cout);
 			cout = NULL;
 		}
 		if (data >= 0) {
@@ -347,8 +364,8 @@ lostpeer(int dummy)
 	pswitch(1);
 	if (connected) {
 		if (cout != NULL) {
-			(void)shutdown(fileno(cout), 1+1);
-			(void)fclose(cout);
+			(void)shutdown(fetch_fileno(cout), 1+1);
+			(void)fetch_close(cout);
 			cout = NULL;
 		}
 		connected = 0;
@@ -372,6 +389,11 @@ ftp_login(const char *host, const char *luser, const char *lpass)
 	char emptypass[] = "";
 	const char *errormsg;
 	int n, aflag, rval, nlen;
+#ifdef WITH_SSL
+	static const char *sslprot[] = { "TLS", "SSL" };
+	struct fetch_ssl *ssl;
+	size_t i;
+#endif

 	aflag = rval = 0;
 	fuser = pass = facct = NULL;
@@ -429,7 +451,34 @@ ftp_login(const char *host, const char *luser, const char *lpass)
 		fuser = nuser;
 	}

-	n = command("USER %s", fuser);
+	n = COMPLETE;
+#ifdef WITH_SSL
+	if (ftpssl && ftps_explicit) {
+		for (i = 0; i < sizeof(sslprot) / sizeof(sslprot[0]); i++) {
+			n = command("AUTH %s", sslprot[i]);
+			if (n == COMPLETE) {
+				ssl = fetch_start_ssl(fetch_fileno(cout));
+				if (ssl != NULL) {
+					fetch_set_ssl(cin, ssl);
+					fetch_set_ssl(cout, ssl);
+				} else
+					n = ERROR;
+				break;
+			} else if (n != TRANSIENT && n != ERROR) {
+				n = ERROR;
+				break;
+			}
+		}
+		if (n == COMPLETE) {
+			n = command("PBSZ 0");
+			if (n == COMPLETE) {
+				n = command("PROT P");
+			}
+		}
+	}
+#endif
+	if (n == COMPLETE)
+		n = command("USER %s", fuser);
 	if (n == CONTINUE) {
 		if (pass == NULL) {
 			p = getpass("Password: ");
-- 
1.8.2.3

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.39 2013/11/01 18:47:49 spz Exp $
$NetBSD: gnats_config.sh,v 1.8 2006/05/07 09:23:38 tsutsui Exp $
Copyright © 1994-2007 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.