about summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
authorPaul Ackersviller <packersv@users.sourceforge.net>2007-05-29 02:40:42 +0000
committerPaul Ackersviller <packersv@users.sourceforge.net>2007-05-29 02:40:42 +0000
commit8b8f780cb22fbb616720897120c2ff76c28479e7 (patch)
tree133e7469ac4eccc4e988f01cd2a737afc58b4587 /Src
parent5828c247a5bbd4472c62a49f96a8fa24e41ecd20 (diff)
downloadzsh-8b8f780cb22fbb616720897120c2ff76c28479e7.tar.gz
zsh-8b8f780cb22fbb616720897120c2ff76c28479e7.tar.xz
zsh-8b8f780cb22fbb616720897120c2ff76c28479e7.zip
Merge of unposted revision 1.38.
Diffstat (limited to 'Src')
-rw-r--r--Src/Modules/zftp.c534
1 files changed, 242 insertions, 292 deletions
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index 64565c9e1..769032f65 100644
--- a/Src/Modules/zftp.c
+++ b/Src/Modules/zftp.c
@@ -46,19 +46,13 @@ struct hostent;
 struct in_addr;
 struct sockaddr_in;
 struct sockaddr_in6;
-union zftp_sockaddr;
 struct zftp_session;
 typedef struct zftp_session *Zftp_session;
 
+#include "tcp.h"
 #include "zftp.mdh"
 #include "zftp.pro"
 
-#include <sys/socket.h>
-#include <netdb.h>
-#include <netinet/in_systm.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <arpa/inet.h>
 /* it's a TELNET based protocol, but don't think I like doing this */
 #include <arpa/telnet.h>
 
@@ -75,148 +69,23 @@ typedef struct zftp_session *Zftp_session;
 # undef HAVE_POLL
 #endif
 
-/* Is IPv6 supported by the library? */
 
-#if defined(AF_INET6) && defined(IN6ADDR_LOOPBACK_INIT) \
-	&& defined(HAVE_INET_NTOP) && defined(HAVE_INET_PTON)
-# define SUPPORT_IPV6 1
+#ifdef USE_LOCAL_H_ERRNO
+int h_errno;
 #endif
 
 union zftp_sockaddr {
-	struct sockaddr a;
-	struct sockaddr_in in;
+    struct sockaddr a;
+    struct sockaddr_in in;
 #ifdef SUPPORT_IPV6
-	struct sockaddr_in6 in6;
+    struct sockaddr_in6 in6;
 #endif
 };
 
-/* We use the RFC 2553 interfaces.  If the functions don't exist in the library,
-   simulate them. */
-
-#ifndef INET_ADDRSTRLEN
-# define INET_ADDRSTRLEN 16
-#endif
-
-#ifndef INET6_ADDRSTRLEN
-# define INET6_ADDRSTRLEN 46
+#ifdef USE_LOCAL_H_ERRNO
+int h_errno;
 #endif
 
-/**/  
-#ifndef HAVE_INET_NTOP
-
-/**/    
-static char const *
-inet_ntop(int af, void const *cp, char *buf, size_t len)
-{       
-        if(af != AF_INET) {
-                errno = EAFNOSUPPORT;
-                return NULL;
-        } 
-        if(len < INET_ADDRSTRLEN) {
-                errno = ENOSPC;
-                return NULL;
-        }
-        strcpy(buf, inet_ntoa(*(struct in_addr *)cp));
-        return buf;
-}
-
-/**/  
-#endif /* !HAVE_INET_NTOP */
-
-/**/
-#ifndef HAVE_INET_PTON
-
-/**/
-# ifndef HAVE_INET_ATON
-
-#  ifndef INADDR_NONE
-#   define INADDR_NONE 0xffffffffUL
-#  endif
-
-/**/
-static int inet_aton(char const *src, struct in_addr *dst)
-{
-    return (dst->s_addr = inet_addr(src)) != INADDR_NONE;
-}
-
-/**/
-# endif /* !HAVE_INET_ATON */
-
-/**/
-static int
-inet_pton(int af, char const *src, void *dst)
-{
-        if(af != AF_INET) {
-                errno = EAFNOSUPPORT;
-                return -1;
-        }
-        return !!inet_aton(src, dst);
-}
-
-/**/
-#endif /* !HAVE_INET_PTON */
-
-/**/
-#ifndef HAVE_GETIPNODEBYNAME
-
-/* note: this is not a complete implementation.  If ignores the flags,
-   and does not provide the memory allocation of the standard interface.
-   Each returned structure will overwrite the previous one. */
-
-/**/
-static struct hostent *
-getipnodebyname(char const *name, int af, int flags, int *errorp)
-{
-	static struct hostent ahe;
-	static char nbuf[16];
-	static char *addrlist[] = { nbuf, NULL };
-# ifdef SUPPORT_IPV6
-	static char pbuf[INET6_ADDRSTRLEN];
-# else
-	static char pbuf[INET_ADDRSTRLEN];
-# endif
-	struct hostent *he;
-	if(inet_pton(af, name, nbuf) == 1) {
-		inet_ntop(af, nbuf, pbuf, sizeof(pbuf));
-		ahe.h_name = pbuf;
-		ahe.h_aliases = addrlist+1;
-		ahe.h_addrtype = af;
-		ahe.h_length = (af == AF_INET) ? 4 : 16;
-		ahe.h_addr_list = addrlist;
-		return &ahe;
-	}
-	he = gethostbyname2(name, af);
-	if(!he)
-		*errorp = h_errno;
-	return he;
-}
-
-/**/
-# ifndef HAVE_GETHOSTBYNAME2
-
-/**/
-static struct hostent *
-gethostbyname2(char const *name, int af)
-{
-	if(af != AF_INET) {
-		h_errno = NO_RECOVERY;
-		return NULL;
-	}
-	return gethostbyname(name);
-}
-
-/**/
-# endif /* !HAVE_GETHOSTBYNAME2 */
-
-/**/
-static void
-freehostent(struct hostent *ptr)
-{
-}
-
-/**/
-#endif /* !HAVE_GETIPNODEBYNAME */
-
 /*
  * For FTP block mode
  *
@@ -326,7 +195,7 @@ static struct builtin bintab[] = {
  * currently there aren't any, which is the way I like it.
  */
 static char *zfparams[] = {
-    "ZFTP_HOST", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER",
+    "ZFTP_HOST", "ZFTP_PORT", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER",
     "ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", NULL
 };
 
@@ -431,10 +300,8 @@ struct zftp_session {
     char *name;			/* name of session */
     char **params;		/* parameters ordered as in zfparams */
     char **userparams;		/* user parameters set by zftp_params */
-    int cfd;			/* control file descriptor */
     FILE *cin;			/* control input file */
-    union zftp_sockaddr sock;	/* this end of the control connection */
-    union zftp_sockaddr peer;	/* far end of the control connection */
+    Tcp_session control;	/* the control connection */
     int dfd;			/* data connection */
     int has_size;		/* understands SIZE? */
     int has_mdtm;		/* understands MDTM? */
@@ -564,7 +431,7 @@ zfunalarm(void)
 	 * I love the way alarm() uses unsigned int while time_t
 	 * is probably something completely different.
 	 */
-	time_t tdiff = time(NULL) - oaltime;
+	unsigned int tdiff = time(NULL) - oaltime;
 	alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
     } else
 	alarm(0);
@@ -646,9 +513,9 @@ zfsetparam(char *name, void *val, int flags)
 	return;
     }
     if (type == PM_INTEGER)
-	pm->sets.ifn(pm, *(off_t *)val);
+	pm->gsu.i->setfn(pm, *(off_t *)val);
     else
-	pm->sets.cfn(pm, (char *)val);
+	pm->gsu.s->setfn(pm, (char *)val);
 }
 
 /*
@@ -776,7 +643,7 @@ zfgetline(char *ln, int lnsize, int tmout)
 		cmdbuf[0] = (char)IAC;
 		cmdbuf[1] = (char)DONT;
 		cmdbuf[2] = ch;
-		write(zfsess->cfd, cmdbuf, 3);
+		write(zfsess->control->fd, cmdbuf, 3);
 		continue;
 
 	    case DO:
@@ -786,7 +653,7 @@ zfgetline(char *ln, int lnsize, int tmout)
 		cmdbuf[0] = (char)IAC;
 		cmdbuf[1] = (char)WONT;
 		cmdbuf[2] = ch;
-		write(zfsess->cfd, cmdbuf, 3);
+		write(zfsess->control->fd, cmdbuf, 3);
 		continue;
 
 	    case EOF:
@@ -836,10 +703,8 @@ zfgetmsg(void)
     char line[256], *ptr, *verbose;
     int stopit, printing = 0, tmout;
 
-    if (zfsess->cfd == -1)
+    if (!zfsess->control)
 	return 6;
-    if (!(verbose = getsparam("ZFTP_VERBOSE")))
-	verbose = "";
     zsfree(lastmsg);
     lastmsg = NULL;
 
@@ -847,8 +712,7 @@ zfgetmsg(void)
 
     zfgetline(line, 256, tmout);
     ptr = line;
-    if (zfdrrrring || !isdigit(STOUC(*ptr)) || !isdigit(STOUC(ptr[1])) || 
-	!isdigit(STOUC(ptr[2]))) {
+    if (zfdrrrring || !idigit(*ptr) || !idigit(ptr[1]) || !idigit(ptr[2])) {
 	/* timeout, or not talking FTP.  not really interested. */
 	zcfinish = 2;
 	if (!zfclosing)
@@ -865,6 +729,9 @@ zfgetmsg(void)
     zfsetparam("ZFTP_CODE", ztrdup(lastcodestr), ZFPM_READONLY);
     stopit = (*ptr++ != '-');
 
+    queue_signals();
+    if (!(verbose = getsparam("ZFTP_VERBOSE")))
+	verbose = "";
     if (strchr(verbose, lastcodestr[0])) {
 	/* print the whole thing verbatim */
 	printing = 1;
@@ -874,6 +741,7 @@ zfgetmsg(void)
 	printing = 2;
 	fputs(ptr, stderr);
     }
+    unqueue_signals();
     if (printing)
 	fputc('\n', stderr);
 
@@ -962,7 +830,7 @@ zfsendcmd(char *cmd)
      */
     int ret, tmout;
 
-    if (zfsess->cfd == -1)
+    if (!zfsess->control)
 	return 6;
     tmout = getiparam("ZFTP_TMOUT");
     if (setjmp(zfalrmbuf)) {
@@ -971,7 +839,7 @@ zfsendcmd(char *cmd)
 	return 6;
     }
     zfalarm(tmout);
-    ret = write(zfsess->cfd, cmd, strlen(cmd));
+    ret = write(zfsess->control->fd, cmd, strlen(cmd));
     alarm(0);
 
     if (ret <= 0) {
@@ -988,13 +856,13 @@ zfsendcmd(char *cmd)
 
 /**/
 static int
-zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
+zfopendata(char *name, union tcp_sockaddr *zdsockp, int *is_passivep)
 {
     if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) {
 	zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0);
 	return 1;
     }
-    zfsess->dfd = socket(AF_INET, SOCK_STREAM, 0);
+    zfsess->dfd = socket(zfsess->control->peer.a.sa_family, SOCK_STREAM, 0);
     if (zfsess->dfd < 0) {
 	zwarnnam(name, "can't get data socket: %e", NULL, errno);
 	return 1;
@@ -1005,7 +873,7 @@ zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
 	int err, salen;
 
 #ifdef SUPPORT_IPV6
-	if(zfsess->peer.a.sa_family == AF_INET6)
+	if(zfsess->control->peer.a.sa_family == AF_INET6)
 	    psv_cmd = "EPSV\r\n";
 	else
 #endif /* SUPPORT_IPV6 */
@@ -1021,9 +889,9 @@ zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
 	    zfclosedata();
 	    return zfopendata(name, zdsockp, is_passivep);
 	}
-	zdsockp->a.sa_family = zfsess->peer.a.sa_family;
+	zdsockp->a.sa_family = zfsess->control->peer.a.sa_family;
 #ifdef SUPPORT_IPV6
-	if(zfsess->peer.a.sa_family == AF_INET6) {
+	if(zfsess->control->peer.a.sa_family == AF_INET6) {
 	    /* see RFC 2428 for explanation */
 	    char const *ptr, *end;
 	    char delim, portbuf[6], *pbp;
@@ -1038,20 +906,20 @@ zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
 	    delim = ptr[1];
 	    if(delim < 33 || delim > 126 || ptr[2] != delim || ptr[3] != delim)
 		goto bad_epsv;
-	    ptr += 3;
+	    ptr += 4;
 	    end = strchr(ptr, delim);
 	    if(!end || end[1] != ')')
 		goto bad_epsv;
 	    while(ptr != end && *ptr == '0')
 		ptr++;
-	    if(ptr == end || (end-ptr) > 5 || !isdigit(STOUC(*ptr)))
+	    if(ptr == end || (end-ptr) > 5 || !idigit(*ptr))
 		goto bad_epsv;
 	    memcpy(portbuf, ptr, (end-ptr));
 	    portbuf[end-ptr] = 0;
 	    portnum = strtoul(portbuf, &pbp, 10);
 	    if(*pbp || portnum > 65535UL)
 		goto bad_epsv;
-	    *zdsockp = zfsess->peer;
+	    *zdsockp = zfsess->control->peer;
 	    zdsockp->in6.sin6_port = htons((unsigned)portnum);
 	    salen = sizeof(struct sockaddr_in6);
 	} else
@@ -1067,7 +935,7 @@ zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
 	     * lastmsg already has the reply code expunged.
 	     */
 	    for (ptr = lastmsg; *ptr; ptr++)
-		if (isdigit(STOUC(*ptr)))
+		if (idigit(*ptr))
 		    break;
 	    if (sscanf(ptr, "%d,%d,%d,%d,%d,%d",
 		       nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) {
@@ -1103,14 +971,15 @@ zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
 #else
 	char portcmd[40];
 #endif
-	int ret, len;
+	ZSOCKLEN_T len;
+	int ret;
 
 	if (!(zfprefs & ZFPF_SNDP)) {
 	    zwarnnam(name, "only sendport mode available for data", NULL, 0);
 	    return 1;
 	}
 
-	*zdsockp = zfsess->sock;
+	*zdsockp = zfsess->control->sock;
 #ifdef SUPPORT_IPV6
 	if(zdsockp->a.sa_family == AF_INET6) {
 	    zdsockp->in6.sin6_port = 0;	/* to be set by bind() */
@@ -1144,10 +1013,10 @@ zfopendata(char *name, union zftp_sockaddr *zdsockp, int *is_passivep)
 	if(zdsockp->a.sa_family == AF_INET6) {
 	    /* see RFC 2428 for explanation */
 	    strcpy(portcmd, "EPRT |2|");
-	    inet_ntop(AF_INET6, &zdsockp->in6.sin6_addr,
-		portcmd+8, INET6_ADDRSTRLEN);
+	    zsh_inet_ntop(AF_INET6, &zdsockp->in6.sin6_addr,
+			  portcmd+8, INET6_ADDRSTRLEN);
 	    sprintf(strchr(portcmd, 0), "|%u|\r\n",
-		(unsigned)ntohs(zdsockp->in6.sin6_port));
+		    (unsigned)ntohs(zdsockp->in6.sin6_port));
 	} else
 #endif /* SUPPORT_IPV6 */
 	{
@@ -1195,8 +1064,9 @@ zfclosedata(void)
 static int
 zfgetdata(char *name, char *rest, char *cmd, int getsize)
 {
-    int len, newfd, is_passive;
-    union zftp_sockaddr zdsock;
+    ZSOCKLEN_T len;
+    int newfd, is_passive;
+    union tcp_sockaddr zdsock;
 
     if (zfopendata(name, &zdsock, &is_passive))
 	return 1;
@@ -1231,11 +1101,11 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize)
 	char *ptr = strstr(lastmsg, "bytes");
 	zfstatusp[zfsessno] |= ZFST_NOSZ|ZFST_TRSZ;
 	if (ptr) {
-	    while (ptr > lastmsg && !isdigit(STOUC(*ptr)))
+	    while (ptr > lastmsg && !idigit(*ptr))
 		ptr--;
-	    while (ptr > lastmsg && isdigit(STOUC(ptr[-1])))
+	    while (ptr > lastmsg && idigit(ptr[-1]))
 		ptr--;
-	    if (isdigit(STOUC(*ptr))) {
+	    if (idigit(*ptr)) {
 		zfstatusp[zfsessno] &= ~ZFST_NOSZ;
 		if (getsize) {
 		    off_t sz = zstrtol(ptr, NULL, 10);
@@ -1300,9 +1170,8 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize)
     }
 #endif
 #if defined(F_SETFD) && defined(FD_CLOEXEC)
-	/* If the shell execs a program, we don't want this fd left open. */
-	len = FD_CLOEXEC;
-	fcntl(zfsess->dfd, F_SETFD, &len);
+    /* If the shell execs a program, we don't want this fd left open. */
+    fcntl(zfsess->dfd, F_SETFD, FD_CLOEXEC);
 #endif
 
     return 0;
@@ -1785,8 +1654,8 @@ zfsenddata(char *name, int recv, int progress, off_t startat)
 
 	/* the following is black magic, as far as I'm concerned. */
 	/* what are we going to do if it fails?  not a lot, actually. */
-	send(zfsess->cfd, (char *)msg, 3, 0);
-	send(zfsess->cfd, (char *)msg+3, 1, MSG_OOB);
+	send(zfsess->control->fd, (char *)msg, 3, 0);
+	send(zfsess->control->fd, (char *)msg+3, 1, MSG_OOB);
 
 	zfsendcmd("ABOR\r\n");
 	if (lastcode == 226) {
@@ -1829,9 +1698,11 @@ zftp_open(char *name, char **args, int flags)
     struct protoent *zprotop;
     struct servent *zservp;
     struct hostent *zhostp = NULL;
-    char **addrp, *fname;
-    int err, len, tmout;
-    int herrno, af, salen;
+    char **addrp, *fname, *tmpptr, *portnam = "ftp";
+    char *hostnam, *hostsuffix;
+    int err, tmout, port = -1;
+    ZSOCKLEN_T  len;
+    int herrno, af, hlen;
 
     if (!*args) {
 	if (zfsess->userparams)
@@ -1847,15 +1718,59 @@ zftp_open(char *name, char **args, int flags)
      * Probably this is the safest thing to do.  It's possible
      * a `QUIT' will hang, though.
      */
-    if (zfsess->cfd != -1)
+    if (zfsess->control)
 	zfclose(0);
 
+    hostnam = dupstring(args[0]);
+    /*
+     * Check for IPv6 address in square brackets (RFC2732).
+     * We are more lenient and allow any form for the host here.
+     */
+    if (hostnam[0] == '[') {
+	hostnam++;
+	hostsuffix = strchr(hostnam, ']');
+	if (!hostsuffix || (hostsuffix[1] && hostsuffix[1] != ':')) {
+	    zwarnnam(name, "Invalid host format: %s", hostnam, 0);
+	    return 1;
+	}
+	*hostsuffix++ = '\0';
+    }
+    else
+	hostsuffix = hostnam;
+
+    if ((tmpptr = strchr(hostsuffix, ':'))) {
+	char *endptr;
+
+	*tmpptr++ = '\0';
+	port = (int)zstrtol(tmpptr, &endptr, 10);
+	/*
+	 * If the port is not numeric, look it up by name below.
+	 */
+	if (*endptr) {
+	    portnam = tmpptr;
+	    port = -1;
+	}
+#if defined(HAVE_NTOHS) && defined(HAVE_HTONS)
+	else {
+	    port = (int)htons((unsigned short)port);
+	}
+#endif
+    }
+
     /* this is going to give 0.  why bother? */
     zprotop = getprotobyname("tcp");
-    zservp = getservbyname("ftp", "tcp");
+    if (!zprotop) {
+	zwarnnam(name, "Can't find protocol TCP (is your network functional)?",
+		 NULL, 0);
+	return 1;
+    }
+    if (port < 0)
+	zservp = getservbyname(portnam, "tcp");
+    else
+	zservp = getservbyport(port, "tcp");
 
     if (!zprotop || !zservp) {
-	zwarnnam(name, "Somebody stole FTP!", NULL, 0);
+	zwarnnam(name, "Can't find port for service `%s'", portnam, 0);
 	return 1;
     }
 
@@ -1870,10 +1785,12 @@ zftp_open(char *name, char **args, int flags)
     if (setjmp(zfalrmbuf)) {
 	char *hname;
 	alarm(0);
+	queue_signals();
 	if ((hname = getsparam("ZFTP_HOST")) && *hname) 
 	    zwarnnam(name, "timeout connecting to %s", hname, 0);
 	else
 	    zwarnnam(name, "timeout on host name lookup", NULL, 0);
+	unqueue_signals();
 	zfclose(0);
 	return 1;
     }
@@ -1884,44 +1801,54 @@ zftp_open(char *name, char **args, int flags)
 # define SUCCEEDED() break
 # define FAILED() if(af == AF_INET) { } else continue
 #else
-    af = AF_INET;
+	af = AF_INET;
 # define SUCCEEDED() do { } while(0)
 # define FAILED() do { } while(0)
 #endif
     {
-    	zhostp = getipnodebyname(args[0], af, 0, &herrno);
+	off_t tcp_port;
+
+	zhostp = zsh_getipnodebyname(hostnam, af, 0, &herrno);
 	if (!zhostp || errflag) {
 	    /* should use herror() here if available, but maybe
 	     * needs configure test. on AIX it's present but not
 	     * in headers.
+	     * 
+	     * on the other hand, herror() is obsolete
 	     */
 	    FAILED();
-	    zwarnnam(name, "host not found: %s", args[0], 0);
+	    zwarnnam(name, "host not found: %s", hostnam, 0);
 	    alarm(0);
 	    return 1;
 	}
 	zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY);
+	/* careful with pointer types */
+#if defined(HAVE_NTOHS) && defined(HAVE_HTONS)
+	tcp_port = (off_t)ntohs((unsigned short)zservp->s_port);
+#else
+	tcp_port = (off_t)zservp->s_port;
+#endif
+	zfsetparam("ZFTP_PORT", &tcp_port, ZFPM_READONLY|ZFPM_INTEGER);
 
-	zfsess->peer.a.sa_family = af;
 #ifdef SUPPORT_IPV6
 	if(af == AF_INET6) {
-	    zfsess->peer.in6.sin6_port = zservp->s_port;
-	    zfsess->peer.in6.sin6_flowinfo = 0;
-# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
-	    zfsess->peer.in6.sin6_scope_id = 0;
-# endif
-	    salen = sizeof(struct sockaddr_in6);
+	    hlen = 16;
 	} else
 #endif /* SUPPORT_IPV6 */
 	{
-	    zfsess->peer.in.sin_port = zservp->s_port;
-	    salen = sizeof(struct sockaddr_in);
+	    hlen = 4;
 	}
 
-	zfsess->cfd = socket(af, SOCK_STREAM, 0);
-	if (zfsess->cfd < 0) {
+	zfsess->control = tcp_socket(af, SOCK_STREAM, 0, ZTCP_ZFTP);
+
+	if (!(zfsess->control) || (zfsess->control->fd < 0)) {
+	    if (zfsess->control) {
+		tcp_close(zfsess->control);
+		zfsess->control = NULL;
+	    }
 	    freehostent(zhostp);
 	    zfunsetparam("ZFTP_HOST");
+	    zfunsetparam("ZFTP_PORT");
 	    FAILED();
 	    zwarnnam(name, "socket failed: %e", NULL, errno);
 	    alarm(0);
@@ -1939,16 +1866,10 @@ zftp_open(char *name, char **args, int flags)
 
 	/* try all possible IP's */
 	for (addrp = zhostp->h_addr_list; err && *addrp; addrp++) {
-#ifdef SUPPORT_IPV6
-	    if(af == AF_INET6)
-		memcpy(&zfsess->peer.in6.sin6_addr, *addrp, zhostp->h_length);
-	    else
-#endif /* SUPPORT_IPV6 */
-		memcpy(&zfsess->peer.in.sin_addr, *addrp, zhostp->h_length);
+	    if(hlen != zhostp->h_length)
+		zwarnnam(name, "address length mismatch", NULL, 0);
 	    do {
-		err = connect(zfsess->cfd, (struct sockaddr *)&zfsess->peer,
-			      salen);
-	    } while (err && errno == EINTR && !errflag);
+		err = tcp_connect(zfsess->control, *addrp, zhostp, zservp->s_port);	    } while (err && errno == EINTR && !errflag);
 	    /* you can check whether it's worth retrying here */
 	}
 
@@ -1971,7 +1892,7 @@ zftp_open(char *name, char **args, int flags)
 	char pbuf[INET_ADDRSTRLEN];
 #endif
 	addrp--;
-	inet_ntop(af, *addrp, pbuf, sizeof(pbuf));
+	zsh_inet_ntop(af, *addrp, pbuf, sizeof(pbuf));
 	zfsetparam("ZFTP_IP", ztrdup(pbuf), ZFPM_READONLY);
     }
     freehostent(zhostp);
@@ -1982,16 +1903,15 @@ zftp_open(char *name, char **args, int flags)
      * Move the fd out of the user-visible range.  We need to do
      * this after the connect() on some systems.
      */
-    zfsess->cfd = zfmovefd(zfsess->cfd);
+    zfsess->control->fd = zfmovefd(zfsess->control->fd);
 
 #if defined(F_SETFD) && defined(FD_CLOEXEC)
     /* If the shell execs a program, we don't want this fd left open. */
-    len = FD_CLOEXEC;
-    fcntl(zfsess->cfd, F_SETFD, &len);
+    fcntl(zfsess->control->fd, F_SETFD, FD_CLOEXEC);
 #endif
 
-    len = sizeof(zfsess->sock);
-    if (getsockname(zfsess->cfd, (struct sockaddr *)&zfsess->sock, &len) < 0) {
+    len = sizeof(zfsess->control->sock);
+    if (getsockname(zfsess->control->fd, (struct sockaddr *)&zfsess->control->sock, &len) < 0) {
 	zwarnnam(name, "getsockname failed: %e", NULL, errno);
 	zfclose(0);
 	return 1;
@@ -2003,20 +1923,20 @@ zftp_open(char *name, char **args, int flags)
      * do clever things with SIGURG.
      */
     len = 1;
-    setsockopt(zfsess->cfd, SOL_SOCKET, SO_OOBINLINE,
+    setsockopt(zfsess->control->fd, SOL_SOCKET, SO_OOBINLINE,
 	       (char *)&len, sizeof(len));
 #endif
 #if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
     /* for control connection we want low delay.  please don't laugh. */
     len = IPTOS_LOWDELAY;
-    setsockopt(zfsess->cfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len));
+    setsockopt(zfsess->control->fd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len));
 #endif
 
     /*
      * We use stdio with line buffering for convenience on input.
      * On output, we can just dump a complete message to the fd via write().
      */
-    zfsess->cin = fdopen(zfsess->cfd, "r");
+    zfsess->cin = fdopen(zfsess->control->fd, "r");
 
     if (!zfsess->cin) {
 	zwarnnam(name, "file handling error", NULL, 0);
@@ -2050,28 +1970,28 @@ zftp_open(char *name, char **args, int flags)
      * However, it is closed whenever there are no connections open.
      */
     if (zfstatfd == -1) {
-	fname = gettempname();
-	zfstatfd = open(fname, O_RDWR|O_CREAT, 0600);
+	zfstatfd = gettempfile(NULL, 1, &fname);
 	DPUTS(zfstatfd == -1, "zfstatfd not created");
 #if defined(F_SETFD) && defined(FD_CLOEXEC)
 	/* If the shell execs a program, we don't want this fd left open. */
-	len = FD_CLOEXEC;
-	fcntl(zfstatfd, F_SETFD, &len);
+	fcntl(zfstatfd, F_SETFD, FD_CLOEXEC);
 #endif
 	unlink(fname);
     }
 
-    if (zfsess->cfd == -1) {
+    if (zfsess->control->fd == -1) {
 	/* final paranoid check */
-	return 1;
+	tcp_close(zfsess->control);
+	zfsess->control = NULL;
+	zfnopen--;
+    } else {
+	zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY);
+	/* if remaining arguments, use them to log in. */
+	if (*++args)
+	    return zftp_login(name, args, flags);
     }
-	
-    zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY);
-    /* if remaining arguments, use them to log in. */
-    if (zfsess->cfd > -1 && *++args)
-	return zftp_login(name, args, flags);
     /* if something wayward happened, connection was already closed */
-    return zfsess->cfd == -1;
+    return !zfsess->control;
 }
 
 /*
@@ -2133,7 +2053,7 @@ zfgetinfo(char *prompt, int noecho)
 	/* '\n' didn't get echoed */
 	fputc('\n', stdout);
 	fflush(stdout);
-    	settyinfo(&shttyinfo);
+	settyinfo(&shttyinfo);
     }
 
     return strret;
@@ -2146,7 +2066,7 @@ zfgetinfo(char *prompt, int noecho)
 
 /**/
 static int
-zftp_params(char *name, char **args, int flags)
+zftp_params(UNUSED(char *name), char **args, UNUSED(int flags))
 {
     char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " };
     char **aptr, **newarr;
@@ -2174,7 +2094,7 @@ zftp_params(char *name, char **args, int flags)
 	return 0;
     }
     len = arrlen(args);
-    newarr = (char **)zcalloc((len+1)*sizeof(char *));
+    newarr = (char **)zshcalloc((len+1)*sizeof(char *));
     for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) {
 	char *str;
 	if (**aptr == '?')
@@ -2200,7 +2120,7 @@ zftp_params(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_login(char *name, char **args, int flags)
+zftp_login(char *name, char **args, UNUSED(int flags))
 {
     char *ucmd, *passwd = NULL, *acct = NULL;
     char *user, tbuf[2] = "X";
@@ -2265,7 +2185,7 @@ zftp_login(char *name, char **args, int flags)
     }
 
     zsfree(ucmd);
-    if (zfsess->cfd == -1)
+    if (!zfsess->control)
 	return 1;
     if (stopit == 2 || (lastcode != 230 && lastcode != 202)) {
 	zwarnnam(name, "login failed", NULL, 0);
@@ -2333,7 +2253,7 @@ zftp_login(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_test(char *name, char **args, int flags)
+zftp_test(UNUSED(char *name), UNUSED(char **args), UNUSED(int flags))
 {
 #if defined(HAVE_POLL) || defined(HAVE_SELECT)
     int ret;
@@ -2344,7 +2264,7 @@ zftp_test(char *name, char **args, int flags)
     struct timeval tv;
 # endif /* HAVE_POLL */
 
-    if (zfsess->cfd == -1)
+    if (!zfsess->control)
 	return 1;
 
 # ifdef HAVE_POLL
@@ -2352,7 +2272,7 @@ zftp_test(char *name, char **args, int flags)
     /* safety first, though I think POLLIN is more common */
 #   define POLLIN POLLNORM
 #  endif /* HAVE_POLL */
-    pfd.fd = zfsess->cfd;
+    pfd.fd = zfsess->control->fd;
     pfd.events = POLLIN;
     if ((ret = poll(&pfd, 1, 0)) < 0 && errno != EINTR && errno != EAGAIN)
 	zfclose(0);
@@ -2362,10 +2282,10 @@ zftp_test(char *name, char **args, int flags)
     }
 # else
     FD_ZERO(&f);
-    FD_SET(zfsess->cfd, &f);
+    FD_SET(zfsess->control->fd, &f);
     tv.tv_sec = 0;
     tv.tv_usec = 0;
-    if ((ret = select(zfsess->cfd +1, (SELECT_ARG_2_T) &f,
+    if ((ret = select(zfsess->control->fd +1, (SELECT_ARG_2_T) &f,
 		      NULL, NULL, &tv)) < 0
 	&& errno != EINTR)
 	zfclose(0);
@@ -2374,8 +2294,8 @@ zftp_test(char *name, char **args, int flags)
 	zfgetmsg();
     }
 # endif /* HAVE_POLL */
-    /* if we now have zfsess->cfd == -1, then we've just been dumped out. */
-    return (zfsess->cfd == -1) ? 2 : 0;
+    /* if we have no zfsess->control, then we've just been dumped out. */
+    return zfsess->control ? 0 : 2;
 #else
     zfwarnnam(name, "not supported on this system.", NULL, 0);
     return 3;
@@ -2414,7 +2334,7 @@ zftp_dir(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_cd(char *name, char **args, int flags)
+zftp_cd(UNUSED(char *name), char **args, int flags)
 {
     /* change directory --- enhance to allow 'zftp cdup' */
     int ret;
@@ -2546,7 +2466,7 @@ zftp_type(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_mode(char *name, char **args, int flags)
+zftp_mode(char *name, char **args, UNUSED(int flags))
 {
     char *str, cmd[] = "MODE X\r\n";
     int nt;
@@ -2573,7 +2493,7 @@ zftp_mode(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_local(char *name, char **args, int flags)
+zftp_local(UNUSED(char *name), char **args, int flags)
 {
     int more = !!args[1], ret = 0, dofd = !*args;
     while (*args || dofd) {
@@ -2716,7 +2636,7 @@ zftp_getput(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_delete(char *name, char **args, int flags)
+zftp_delete(UNUSED(char *name), char **args, UNUSED(int flags))
 {
     int ret = 0;
     char *cmd, **aptr;
@@ -2733,7 +2653,7 @@ zftp_delete(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_mkdir(char *name, char **args, int flags)
+zftp_mkdir(UNUSED(char *name), char **args, int flags)
 {
     int ret;
     char *cmd = tricat((flags & ZFTP_DELE) ? "RMD " : "MKD ",
@@ -2747,7 +2667,7 @@ zftp_mkdir(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_rename(char *name, char **args, int flags)
+zftp_rename(UNUSED(char *name), char **args, UNUSED(int flags))
 {
     int ret;
     char *cmd;
@@ -2771,7 +2691,7 @@ zftp_rename(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_quote(char *name, char **args, int flags)
+zftp_quote(UNUSED(char *name), char **args, int flags)
 {
     int ret = 0;
     char *cmd;
@@ -2797,7 +2717,7 @@ zfclose(int leaveparams)
     char **aptr;
     Eprog prog;
 
-    if (zfsess->cfd == -1)
+    if (!zfsess->control)
 	return;
 
     zfclosing = 1;
@@ -2810,13 +2730,22 @@ zfclose(int leaveparams)
 	zfsendcmd("QUIT\r\n");
     }
     if (zfsess->cin) {
+	/*
+	 * We fdopen'd the TCP control fd; since we can't fdclose it,
+	 * we need to perform a full fclose, which invalidates the
+	 * TCP fd.  We need to do this before closing the FILE, since
+	 * it's not usable afterwards.
+	 */
+	if (fileno(zfsess->cin) == zfsess->control->fd)
+	    zfsess->control->fd = -1;
 	fclose(zfsess->cin);
 	zfsess->cin = NULL;
     }
-    if (zfsess->cfd != -1) {
+    if (zfsess->control) {
 	zfnopen--;
-	close(zfsess->cfd);
-	zfsess->cfd = -1;
+	tcp_close(zfsess->control);
+	/* We leak if the above failed */
+	zfsess->control = NULL;
     }
 
     if (zfstatfd != -1) {
@@ -2824,7 +2753,7 @@ zfclose(int leaveparams)
 	if (!zfnopen) {
 	    /* Write the final status in case this is a subshell */
 	    lseek(zfstatfd, zfsessno*sizeof(int), 0);
-	    write(zfstatfd, zfstatusp+zfsessno, sizeof(int));
+	    write(zfstatfd, (char *)zfstatusp+zfsessno, sizeof(int));
 
 	    close(zfstatfd);
 	    zfstatfd = -1;
@@ -2854,7 +2783,7 @@ zfclose(int leaveparams)
 
 /**/
 static int
-zftp_close(char *name, char **args, int flags)
+zftp_close(UNUSED(char *name), UNUSED(char **args), UNUSED(int flags))
 {
     zfclose(0);
     return 0;
@@ -2887,10 +2816,10 @@ newsession(char *nm)
     }
 
     if (!nptr) {
-	zfsess = (Zftp_session) zcalloc(sizeof(struct zftp_session));
+	zfsess = (Zftp_session) zshcalloc(sizeof(struct zftp_session));
 	zfsess->name = ztrdup(nm);
-	zfsess->cfd = zfsess->dfd = -1;
-	zfsess->params = (char **) zcalloc(sizeof(zfparams));
+	zfsess->dfd = -1;
+	zfsess->params = (char **) zshcalloc(sizeof(zfparams));
 	zaddlinknode(zfsessions, zfsess);
 
 	zfsesscnt++;
@@ -2911,10 +2840,12 @@ savesession()
     for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) {
 	if (*pd)
 	    zsfree(*pd);
+	queue_signals();
 	if ((val = getsparam(*ps)))
 	    *pd = ztrdup(val);
 	else
 	    *pd = NULL;
+	unqueue_signals();
     }
     *pd = NULL;
 }
@@ -2959,7 +2890,7 @@ freesession(Zftp_session sptr)
 
 /**/
 static int
-zftp_session(char *name, char **args, int flags)
+zftp_session(UNUSED(char *name), char **args, UNUSED(int flags))
 {
     if (!*args) {
 	LinkNode nptr;
@@ -2985,7 +2916,7 @@ zftp_session(char *name, char **args, int flags)
 
 /**/
 static int
-zftp_rmsession(char *name, char **args, int flags)
+zftp_rmsession(UNUSED(char *name), char **args, UNUSED(int flags))
 {
     int no;
     LinkNode nptr;
@@ -3072,7 +3003,7 @@ zftp_rmsession(char *name, char **args, int flags)
 
 /**/
 static int
-bin_zftp(char *name, char **args, char *ops, int func)
+bin_zftp(char *name, char **args, UNUSED(Options ops), UNUSED(int func))
 {
     char fullname[20] = "zftp ";
     char *cnam = *args++, *prefs, *ptr;
@@ -3101,8 +3032,8 @@ bin_zftp(char *name, char **args, char *ops, int func)
 	/* Get the status in case it was set by a forked process */
 	int oldstatus = zfstatusp[zfsessno];
 	lseek(zfstatfd, 0, 0);
-	read(zfstatfd, zfstatusp, sizeof(int)*zfsesscnt);
-	if (zfsess->cfd != -1 && (zfstatusp[zfsessno] & ZFST_CLOS)) {
+	read(zfstatfd, (char *)zfstatusp, sizeof(int)*zfsesscnt);
+	if (zfsess->control && (zfstatusp[zfsessno] & ZFST_CLOS)) {
 	    /* got closed in subshell without us knowing */
 	    zcfinish = 2;
 	    zfclose(0);
@@ -3123,7 +3054,7 @@ bin_zftp(char *name, char **args, char *ops, int func)
 	}
     }
 #if defined(HAVE_SELECT) || defined (HAVE_POLL)
-    if (zfsess->cfd != -1 && !(zptr->flags & (ZFTP_TEST|ZFTP_SESS))) {
+    if (zfsess->control && !(zptr->flags & (ZFTP_TEST|ZFTP_SESS))) {
 	/*
 	 * Test the connection for a bad fd or incoming message, but
 	 * only if the connection was last heard of open, and
@@ -3133,7 +3064,7 @@ bin_zftp(char *name, char **args, char *ops, int func)
 	ret = zftp_test("zftp test", NULL, 0);
     }
 #endif
-    if ((zptr->flags & ZFTP_CONN) && zfsess->cfd == -1) {
+    if ((zptr->flags & ZFTP_CONN) && !zfsess->control) {
 	if (ret != 2) {
 	    /*
 	     * with ret == 2, we just got dumped out in the test,
@@ -3144,6 +3075,7 @@ bin_zftp(char *name, char **args, char *ops, int func)
 	return 1;
     }
 
+    queue_signals();
     if ((prefs = getsparam("ZFTP_PREFS"))) {
 	zfprefs = 0;
 	for (ptr = prefs; *ptr; ptr++) {
@@ -3174,6 +3106,7 @@ bin_zftp(char *name, char **args, char *ops, int func)
 	    }
 	}
     }
+    unqueue_signals();
 
     ret = (*zptr->fun)(fullname, args, zptr->flags);
 
@@ -3190,26 +3123,60 @@ bin_zftp(char *name, char **args, char *ops, int func)
 	 * but only for the active session.
 	 */
 	lseek(zfstatfd, zfsessno*sizeof(int), 0);
-	write(zfstatfd, zfstatusp+zfsessno, sizeof(int));
+	write(zfstatfd, (char *)zfstatusp+zfsessno, sizeof(int));
     }
     return ret;
 }
 
+static void
+zftp_cleanup(void)
+{
+    /*
+     * There are various parameters hanging around, but they're
+     * all non-special so are entirely non-life-threatening.
+     */
+    LinkNode nptr;
+    Zftp_session cursess = zfsess;
+    for (zfsessno = 0, nptr = firstnode(zfsessions); nptr;
+	 zfsessno++, incnode(nptr)) {
+	zfsess = (Zftp_session)nptr->dat;
+	zfclosedata();
+	/*
+	 * When closing the current session, do the usual unsetting,
+	 * otherwise don't.
+	 */
+	zfclose(zfsess != cursess);
+    }
+    zsfree(lastmsg);
+    zfunsetparam("ZFTP_SESSION");
+    freelinklist(zfsessions, (FreeFunc) freesession);
+    zfree(zfstatusp, sizeof(int)*zfsesscnt);
+    deletebuiltins("zftp", bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+static int
+zftpexithook(UNUSED(Hookdef d), UNUSED(void *dummy))
+{
+    zftp_cleanup();
+    return 0;
+}
+
 /* The load/unload routines required by the zsh library interface */
 
 /**/
 int
-setup_(Module m)
+setup_(UNUSED(Module m))
 {
-    return 0;
+    /* setup_ returns 0 for success. require_module returns 1 for success. */
+    return !require_module("", "zsh/net/tcp", 0, 0);
 }
 
 /**/
 int
-boot_(Module m)
+boot_(UNUSED(Module m))
 {
     int ret;
-    if ((ret = addbuiltins(m->nam, bintab,
+    if ((ret = addbuiltins("zftp", bintab,
 			   sizeof(bintab)/sizeof(*bintab))) == 1) {
 	/* if successful, set some default parameters */
 	off_t tmout_def = 60;
@@ -3221,6 +3188,8 @@ boot_(Module m)
     
 	zfsessions = znewlinklist();
 	newsession("default");
+
+	addhookfunc("exit", zftpexithook);
     }
 
     return !ret;
@@ -3228,35 +3197,16 @@ boot_(Module m)
 
 /**/
 int
-cleanup_(Module m)
+cleanup_(UNUSED(Module m))
 {
-    /*
-     * There are various parameters hanging around, but they're
-     * all non-special so are entirely non-life-threatening.
-     */
-    LinkNode nptr;
-    Zftp_session cursess = zfsess;
-    for (zfsessno = 0, nptr = firstnode(zfsessions); nptr;
-	 zfsessno++, incnode(nptr)) {
-	zfsess = (Zftp_session)nptr->dat;
-	zfclosedata();
-	/*
-	 * When closing the current session, do the usual unsetting,
-	 * otherwise don't.
-	 */
-	zfclose(zfsess != cursess);
-    }
-    zsfree(lastmsg);
-    zfunsetparam("ZFTP_SESSION");
-    freelinklist(zfsessions, (FreeFunc) freesession);
-    zfree(zfstatusp, sizeof(int)*zfsesscnt);
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    deletehookfunc("exit", zftpexithook);
+    zftp_cleanup();
     return 0;
 }
 
 /**/
 int
-finish_(Module m)
+finish_(UNUSED(Module m))
 {
     return 0;
 }