diff options
Diffstat (limited to 'Src')
-rw-r--r-- | Src/Modules/zftp.c | 1472 |
1 files changed, 1070 insertions, 402 deletions
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c index ca0843419..ef85dd330 100644 --- a/Src/Modules/zftp.c +++ b/Src/Modules/zftp.c @@ -29,43 +29,195 @@ /* * TODO: + * should be eight-bit clean, but isn't. + * tracking of logical rather than physical directories, like nochaselinks + * (usually PWD returns physical directory). * can signal handling be improved? - * error messages may need tidying up. * maybe we should block CTRL-c on some more operations, * otherwise you can get the connection closed prematurely. * some way of turning off progress reports when backgrounded * would be nice, but the shell doesn't make it easy to find that out. - * the progress reports 100% a bit prematurely: the data may still - * be in transit, and we are stuck waiting for a message from the - * server. but there's really nothing else to do. it's worst - * with small files. * proxy/gateway connections if i knew what to do * options to specify e.g. a non-standard port - * optimizing things out is hard in general when you don't know what - * the shell's going to want, but they may be places to second guess - * the user. Some of the variables could be made special and so - * only retrieve things like the current directory when necessary. - * But it's much neater to have ordinary variables, which the shell - * can manage without our interference, and getting the directory - * just every time it changes isn't so bad. The user can always - * toggle the `Dumb' preference if it's feeling clever. */ -#include "zftp.mdh" -#include "zftp.pro" +/* needed in prototypes for statics */ +struct hostent; +struct in_addr; +struct sockaddr_in; +struct sockaddr_in6; +union zftp_sockaddr; +struct zftp_session; +typedef struct zftp_session *Zftp_session; + +#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in_systm.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> + +#include "zftp.mdh" +#include "zftp.pro" + /* it's a TELNET based protocol, but don't think I like doing this */ #include <arpa/telnet.h> -/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */ -#ifndef INADDR_NONE -#define INADDR_NONE (in_addr_t)-1 +/* + * We use poll() in preference to select because some subset of manuals says + * that's the thing to do, plus it's a bit less fiddly. I don't actually + * have access to a system with poll but not select, however, though + * both bits of the code have been tested on a machine with both. + */ +#ifdef HAVE_POLL_H +# include <poll.h> #endif +#if defined(HAVE_POLL) && !defined(POLLIN) && !defined(POLLNORM) +# 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 +#endif + +union zftp_sockaddr { + struct sockaddr a; + struct sockaddr_in in; +#ifdef SUPPORT_IPV6 + 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 +#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 @@ -104,7 +256,7 @@ enum { ZFHD_EORB = 128 /* block is end of file */ }; -typedef int (*readwrite_t)(int, char *, size_t, int); +typedef int (*readwrite_t)(int, char *, off_t, int); struct zftpcmd { const char *nam; @@ -124,7 +276,9 @@ enum { ZFTP_HERE = 0x0100, /* here rather than over there */ ZFTP_CDUP = 0x0200, /* CDUP rather than CWD */ ZFTP_REST = 0x0400, /* restart: set point in remote file */ - ZFTP_RECV = 0x0800 /* receive rather than send */ + ZFTP_RECV = 0x0800, /* receive rather than send */ + ZFTP_TEST = 0x1000, /* test command, don't test */ + ZFTP_SESS = 0x2000 /* session command, don't need status */ }; typedef struct zftpcmd *Zftpcmd; @@ -134,6 +288,7 @@ static struct zftpcmd zftpcmdtab[] = { { "params", zftp_params, 0, 4, 0 }, { "login", zftp_login, 0, 3, ZFTP_CONN }, { "user", zftp_login, 0, 3, ZFTP_CONN }, + { "test", zftp_test, 0, 0, ZFTP_TEST }, { "cd", zftp_cd, 1, 1, ZFTP_CONN|ZFTP_LOGI }, { "cdup", zftp_cd, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_CDUP }, { "dir", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI }, @@ -158,7 +313,9 @@ static struct zftpcmd zftpcmdtab[] = { { "site", zftp_quote, 1, -1, ZFTP_CONN|ZFTP_SITE }, { "close", zftp_close, 0, 0, ZFTP_CONN }, { "quit", zftp_close, 0, 0, ZFTP_CONN }, - { 0, 0, 0, 0} + { "session", zftp_session, 0, 1, ZFTP_SESS }, + { "rmsession", zftp_rmsession, 0, 1, ZFTP_SESS }, + { 0, 0, 0, 0, 0 } }; static struct builtin bintab[] = { @@ -180,16 +337,11 @@ static char *zfparams[] = { enum { ZFPM_READONLY = 0x01, /* make parameter readonly */ ZFPM_IFUNSET = 0x02, /* only set if not already set */ - ZFPM_INTEGER = 0x04 /* passed pointer to long */ + ZFPM_INTEGER = 0x04 /* passed pointer to off_t */ }; -/* - * Basic I/O variables for control connection: - * zcfd != -1 is a flag that there is a connection open. - */ -static int zcfd = -1; -static FILE *zcin; -static struct sockaddr_in zsock; +/* Number of connections actually open */ +static int zfnopen; /* * zcfinish = 0 keep going @@ -201,12 +353,6 @@ static int zcfinish; static int zfclosing; /* - * Now stuff for data connection - */ -static int zdfd = -1; -static struct sockaddr_in zdsock; - -/* * Stuff about last message: last line of message and status code. * The reply is also stored in $ZFTP_REPLY; we keep these separate * for convenience. @@ -214,9 +360,6 @@ static struct sockaddr_in zdsock; static char *lastmsg, lastcodestr[4]; static int lastcode; -/* flag for remote system is UNIX --- useful to know as things are simpler */ -static int zfis_unix, zfpassive_conn; - /* remote system has size, mdtm commands */ enum { ZFCP_UNKN = 0, /* dunno if it works on this server */ @@ -224,8 +367,6 @@ enum { ZFCP_NOPE = 2 /* it doesn't */ }; -static int zfhas_size, zfhas_mdtm; - /* * We keep an fd open for communication between the main shell * and forked off bits and pieces. This allows us to know @@ -235,25 +376,26 @@ static int zfhas_size, zfhas_mdtm; * --- we don't try to track it because it's too complicated. */ enum { - ZFST_ASCI = 0x00, /* type for next transfer is ASCII */ - ZFST_IMAG = 0x01, /* type for next transfer is image */ + ZFST_ASCI = 0x0000, /* type for next transfer is ASCII */ + ZFST_IMAG = 0x0001, /* type for next transfer is image */ - ZFST_TMSK = 0x01, /* mask for type flags */ - ZFST_TBIT = 0x01, /* number of bits in type flags */ + ZFST_TMSK = 0x0001, /* mask for type flags */ + ZFST_TBIT = 0x0001, /* number of bits in type flags */ - ZFST_CASC = 0x00, /* current type is ASCII - default */ - ZFST_CIMA = 0x02, /* current type is image */ + ZFST_CASC = 0x0000, /* current type is ASCII - default */ + ZFST_CIMA = 0x0002, /* current type is image */ - ZFST_STRE = 0x00, /* stream mode - default */ - ZFST_BLOC = 0x04, /* block mode */ + ZFST_STRE = 0x0000, /* stream mode - default */ + ZFST_BLOC = 0x0004, /* block mode */ - ZFST_MMSK = 0x04, /* mask for mode flags */ + ZFST_MMSK = 0x0004, /* mask for mode flags */ - ZFST_LOGI = 0x08, /* user logged in */ - ZFST_NOPS = 0x10, /* server doesn't understand PASV */ - ZFST_NOSZ = 0x20, /* server doesn't send `(XXXX bytes)' reply */ - ZFST_TRSZ = 0x40, /* tried getting 'size' from reply */ - ZFST_CLOS = 0x80 /* connection closed */ + ZFST_LOGI = 0x0008, /* user logged in */ + ZFST_SYST = 0x0010, /* done system type check */ + ZFST_NOPS = 0x0020, /* server doesn't understand PASV */ + ZFST_NOSZ = 0x0040, /* server doesn't send `(XXXX bytes)' reply */ + ZFST_TRSZ = 0x0080, /* tried getting 'size' from reply */ + ZFST_CLOS = 0x0100 /* connection closed */ }; #define ZFST_TYPE(x) (x & ZFST_TMSK) /* @@ -263,7 +405,8 @@ enum { #define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK) #define ZFST_MODE(x) (x & ZFST_MMSK) -static int zfstatfd = -1, zfstatus; +/* fd containing status for all sessions and array for internal use */ +static int zfstatfd = -1, *zfstatusp; /* Preferences, read in from the `zftp_prefs' array variable */ enum { @@ -273,11 +416,43 @@ enum { }; /* The flags as stored internally. */ -int zfprefs; +static int zfprefs; + +/* + * Data node for linked list of sessions. + * + * Memory management notes: + * name is permanently allocated and remains for the life of the node. + * userparams is set directly by zftp_params and also freed with the node. + * params and its data are allocated when we need + * to save an existing session, and are freed when we switch back + * to that session. + * The node itself is deleted when we remove it from the list. + */ +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 */ + int dfd; /* data connection */ + int has_size; /* understands SIZE? */ + int has_mdtm; /* understands MDTM? */ +}; + +/* List of active sessions */ +static LinkList zfsessions; +/* Current session */ +static Zftp_session zfsess; -/* zfuserparams is the storage area for zftp_params() */ -char **zfuserparams; +/* Number of current session, corresponding to position in list */ +static int zfsessno; + +/* Total number of sessions */ +static int zfsesscnt; /* * Bits and pieces for dealing with SIGALRM (and SIGPIPE, but that's @@ -379,7 +554,7 @@ zfpipe() /**/ static void -zfunalarm() +zfunalarm(void) { if (oalremain) { /* @@ -446,7 +621,7 @@ zfmovefd(int fd) * set a non-special parameter. * if ZFPM_IFUNSET, don't set if it already exists. * if ZFPM_READONLY, make it readonly, but only when creating it. - * if ZFPM_INTEGER, val pointer is to long (NB not int), don't free. + * if ZFPM_INTEGER, val pointer is to off_t (NB not int), don't free. */ /**/ static void @@ -473,7 +648,7 @@ zfsetparam(char *name, void *val, int flags) return; } if (type == PM_INTEGER) - pm->sets.ifn(pm, *(long *)val); + pm->sets.ifn(pm, *(off_t *)val); else pm->sets.cfn(pm, (char *)val); } @@ -542,7 +717,7 @@ zfgetline(char *ln, int lnsize, int tmout) if (setjmp(zfalrmbuf)) { alarm(0); zwarnnam("zftp", "timeout getting response", NULL, 0); - return 5; + return 6; } zfalarm(tmout); @@ -554,12 +729,12 @@ zfgetline(char *ln, int lnsize, int tmout) * But then we get `frustrated user syndrome'. */ for (;;) { - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); switch(ch) { case EOF: - if (ferror(zcin) && errno == EINTR) { - clearerr(zcin); + if (ferror(zfsess->cin) && errno == EINTR) { + clearerr(zfsess->cin); continue; } zcfinish = 2; @@ -567,7 +742,7 @@ zfgetline(char *ln, int lnsize, int tmout) case '\r': /* always precedes something else */ - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); if (ch == EOF) { zcfinish = 2; break; @@ -594,26 +769,26 @@ zfgetline(char *ln, int lnsize, int tmout) * oh great, now it's sending TELNET commands. try * to persuade it not to. */ - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); switch (ch) { case WILL: case WONT: - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); /* whatever it wants to do, stop it. */ cmdbuf[0] = (char)IAC; cmdbuf[1] = (char)DONT; cmdbuf[2] = ch; - write(zcfd, cmdbuf, 3); + write(zfsess->cfd, cmdbuf, 3); continue; case DO: case DONT: - ch = fgetc(zcin); + ch = fgetc(zfsess->cin); /* well, tough, we're not going to. */ cmdbuf[0] = (char)IAC; cmdbuf[1] = (char)WONT; cmdbuf[2] = ch; - write(zcfd, cmdbuf, 3); + write(zfsess->cfd, cmdbuf, 3); continue; case EOF: @@ -658,13 +833,13 @@ zfgetline(char *ln, int lnsize, int tmout) /**/ static int -zfgetmsg() +zfgetmsg(void) { char line[256], *ptr, *verbose; int stopit, printing = 0, tmout; - if (zcfd == -1) - return 5; + if (zfsess->cfd == -1) + return 6; if (!(verbose = getsparam("ZFTP_VERBOSE"))) verbose = ""; zsfree(lastmsg); @@ -674,12 +849,12 @@ zfgetmsg() zfgetline(line, 256, tmout); ptr = line; - if (zfdrrrring || !isdigit((int)*ptr) || !isdigit((int)ptr[1]) || - !isdigit((int)ptr[2])) { + if (zfdrrrring || !isdigit(STOUC(*ptr)) || !isdigit(STOUC(ptr[1])) || + !isdigit(STOUC(ptr[2]))) { /* timeout, or not talking FTP. not really interested. */ zcfinish = 2; if (!zfclosing) - zfclose(); + zfclose(0); lastmsg = ztrdup(""); strcpy(lastcodestr, "000"); zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY); @@ -748,10 +923,10 @@ zfgetmsg() */ if ((zcfinish == 2 || lastcode == 421) && !zfclosing) { zcfinish = 2; /* don't need to tell server */ - zfclose(); + zfclose(0); /* unexpected, so tell user */ zwarnnam("zftp", "remote server has closed connection", NULL, 0); - return 6; /* pretend it failed, because it did */ + return 6; } if (lastcode == 530) { /* user not logged in */ @@ -789,21 +964,22 @@ zfsendcmd(char *cmd) */ int ret, tmout; - if (zcfd == -1) - return 5; + if (zfsess->cfd == -1) + return 6; tmout = getiparam("ZFTP_TMOUT"); if (setjmp(zfalrmbuf)) { alarm(0); zwarnnam("zftp", "timeout sending message", NULL, 0); - return 5; + return 6; } zfalarm(tmout); - ret = write(zcfd, cmd, strlen(cmd)); + ret = write(zfsess->cfd, cmd, strlen(cmd)); alarm(0); if (ret <= 0) { - zwarnnam("zftp send", "failed sending control message", NULL, 0); - return 5; /* FTP status code */ + zwarnnam("zftp send", "failure sending control message: %e", + NULL, errno); + return 6; } return zfgetmsg(); @@ -814,62 +990,106 @@ zfsendcmd(char *cmd) /**/ static int -zfopendata(char *name) +zfopendata(char *name, union zftp_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; } - zdfd = zfmovefd(socket(AF_INET, SOCK_STREAM, 0)); - if (zdfd < 0) { + zfsess->dfd = socket(AF_INET, SOCK_STREAM, 0); + if (zfsess->dfd < 0) { zwarnnam(name, "can't get data socket: %e", NULL, errno); return 1; } - zdsock = zsock; - zdsock.sin_family = AF_INET; - - if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { - char *ptr; - int i, nums[6], err; - unsigned char iaddr[4], iport[2]; + if (!(zfstatusp[zfsessno] & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { + char *psv_cmd; + int err, salen; - if (zfsendcmd("PASV\r\n") == 6) +#ifdef SUPPORT_IPV6 + if(zfsess->peer.a.sa_family == AF_INET6) + psv_cmd = "EPSV\r\n"; + else +#endif /* SUPPORT_IPV6 */ + psv_cmd = "PASV\r\n"; + if (zfsendcmd(psv_cmd) == 6) return 1; else if (lastcode >= 500 && lastcode <= 504) { /* * Fall back to send port mode. That will * test the preferences for whether that's OK. */ - zfstatus |= ZFST_NOPS; - zfclosedata(); - return zfopendata(name); - } - /* - * OK, now we need to know what port we're looking at, - * which is cunningly concealed in the reply. - * lastmsg already has the reply code expunged. - */ - for (ptr = lastmsg; *ptr; ptr++) - if (isdigit(*ptr)) - break; - if (sscanf(ptr, "%d,%d,%d,%d,%d,%d", - nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) { - zwarnnam(name, "bad response to PASV: %s", lastmsg, 0); + zfstatusp[zfsessno] |= ZFST_NOPS; zfclosedata(); - return 1; + return zfopendata(name, zdsockp, is_passivep); } - for (i = 0; i < 4; i++) - iaddr[i] = STOUC(nums[i]); - iport[0] = STOUC(nums[4]); - iport[1] = STOUC(nums[5]); + zdsockp->a.sa_family = zfsess->peer.a.sa_family; +#ifdef SUPPORT_IPV6 + if(zfsess->peer.a.sa_family == AF_INET6) { + /* see RFC 2428 for explanation */ + char const *ptr, *end; + char delim, portbuf[6], *pbp; + unsigned long portnum; + ptr = strchr(lastmsg, '('); + if(!ptr) { + bad_epsv: + zwarnnam(name, "bad response to EPSV: %s", lastmsg, 0); + zfclosedata(); + return 1; + } + delim = ptr[1]; + if(delim < 33 || delim > 126 || ptr[2] != delim || ptr[3] != delim) + goto bad_epsv; + ptr += 3; + 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))) + 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->in6.sin6_port = htons((unsigned)portnum); + salen = sizeof(struct sockaddr_in6); + } else +#endif /* SUPPORT_IPV6 */ + { + char *ptr; + int i, nums[6]; + unsigned char iaddr[4], iport[2]; - memcpy(&zdsock.sin_addr, iaddr, sizeof(iaddr)); - memcpy(&zdsock.sin_port, iport, sizeof(iport)); + /* + * OK, now we need to know what port we're looking at, + * which is cunningly concealed in the reply. + * lastmsg already has the reply code expunged. + */ + for (ptr = lastmsg; *ptr; ptr++) + if (isdigit(STOUC(*ptr))) + break; + if (sscanf(ptr, "%d,%d,%d,%d,%d,%d", + nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) { + zwarnnam(name, "bad response to PASV: %s", lastmsg, 0); + zfclosedata(); + return 1; + } + for (i = 0; i < 4; i++) + iaddr[i] = STOUC(nums[i]); + iport[0] = STOUC(nums[4]); + iport[1] = STOUC(nums[5]); + + memcpy(&zdsockp->in.sin_addr, iaddr, sizeof(iaddr)); + memcpy(&zdsockp->in.sin_port, iport, sizeof(iport)); + salen = sizeof(struct sockaddr_in); + } /* we should timeout this connect */ do { - err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)); + err = connect(zfsess->dfd, (struct sockaddr *)zdsockp, salen); } while (err && errno == EINTR && !errflag); if (err) { @@ -878,10 +1098,13 @@ zfopendata(char *name) return 1; } - zfpassive_conn = 1; + *is_passivep = 1; } else { +#ifdef SUPPORT_IPV6 + char portcmd[8+INET6_ADDRSTRLEN+9]; +#else char portcmd[40]; - unsigned char *addr, *port; +#endif int ret, len; if (!(zfprefs & ZFPF_SNDP)) { @@ -889,14 +1112,24 @@ zfopendata(char *name) return 1; } - zdsock.sin_port = 0; /* to be set by bind() */ - len = sizeof(zdsock); + *zdsockp = zfsess->sock; +#ifdef SUPPORT_IPV6 + if(zdsockp->a.sa_family == AF_INET6) { + zdsockp->in6.sin6_port = 0; /* to be set by bind() */ + len = sizeof(struct sockaddr_in6); + } else +#endif /* SUPPORT_IPV6 */ + { + zdsockp->in.sin_port = 0; /* to be set by bind() */ + len = sizeof(struct sockaddr_in); + } /* need to do timeout stuff and probably handle EINTR here */ - if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0) + if (bind(zfsess->dfd, (struct sockaddr *)zdsockp, len) < 0) ret = 1; - else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0) + else if (getsockname(zfsess->dfd, (struct sockaddr *)zdsockp, + &len) < 0) ret = 2; - else if (listen(zdfd, 1) < 0) + else if (listen(zfsess->dfd, 1) < 0) ret = 3; else ret = 0; @@ -909,16 +1142,28 @@ zfopendata(char *name) return 1; } - addr = (unsigned char *) &zdsock.sin_addr; - port = (unsigned char *) &zdsock.sin_port; - sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n", - addr[0],addr[1],addr[2],addr[3],port[0],port[1]); +#ifdef SUPPORT_IPV6 + 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); + sprintf(strchr(portcmd, 0), "|%u|\r\n", + (unsigned)ntohs(zdsockp->in6.sin6_port)); + } else +#endif /* SUPPORT_IPV6 */ + { + unsigned char *addr = (unsigned char *) &zdsockp->in.sin_addr; + unsigned char *port = (unsigned char *) &zdsockp->in.sin_port; + sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n", + addr[0],addr[1],addr[2],addr[3],port[0],port[1]); + } if (zfsendcmd(portcmd) >= 5) { zwarnnam(name, "port command failed", NULL, 0); zfclosedata(); return 1; } - zfpassive_conn = 0; + *is_passivep = 0; } return 0; @@ -930,15 +1175,15 @@ zfopendata(char *name) static void zfclosedata(void) { - if (zdfd == -1) + if (zfsess->dfd == -1) return; - close(zdfd); - zdfd = -1; + close(zfsess->dfd); + zfsess->dfd = -1; } /* * Set up a data connection and use cmd to initiate a transfer. - * The actual data fd will be zdfd; the calling routine + * The actual data fd will be zfsess->dfd; the calling routine * must handle the data itself. * rest is a REST command to specify starting somewhere other * then the start of the remote file. @@ -952,9 +1197,10 @@ zfclosedata(void) static int zfgetdata(char *name, char *rest, char *cmd, int getsize) { - int len, newfd; + int len, newfd, is_passive; + union zftp_sockaddr zdsock; - if (zfopendata(name)) + if (zfopendata(name, &zdsock, &is_passive)) return 1; /* @@ -976,7 +1222,8 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) zfclosedata(); return 1; } - if (getsize || (!(zfstatus & ZFST_TRSZ) && !strncmp(cmd, "RETR", 4))) { + if (getsize || (!(zfstatusp[zfsessno] & ZFST_TRSZ) && + !strncmp(cmd, "RETR", 4))) { /* * See if we got something like: * Opening data connection for nortypix.gif (1234567 bytes). @@ -984,25 +1231,25 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) * can avoid sending a special SIZE command beforehand. */ char *ptr = strstr(lastmsg, "bytes"); - zfstatus |= ZFST_NOSZ|ZFST_TRSZ; + zfstatusp[zfsessno] |= ZFST_NOSZ|ZFST_TRSZ; if (ptr) { - while (ptr > lastmsg && !isdigit(*ptr)) + while (ptr > lastmsg && !isdigit(STOUC(*ptr))) ptr--; - while (ptr > lastmsg && isdigit(ptr[-1])) + while (ptr > lastmsg && isdigit(STOUC(ptr[-1]))) ptr--; - if (isdigit(*ptr)) { - zfstatus &= ~ZFST_NOSZ; + if (isdigit(STOUC(*ptr))) { + zfstatusp[zfsessno] &= ~ZFST_NOSZ; if (getsize) { - long sz = zstrtol(ptr, NULL, 10); + off_t sz = zstrtol(ptr, NULL, 10); zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); } } } } - if (!zfpassive_conn) { + if (!is_passive) { /* - * the current zdfd is the socket we opened, but we need + * the current zfsess->dfd is the socket we opened, but we need * to let the server set up a different fd for reading/writing. * then we can close the fd we were listening for a connection on. * don't expect me to understand this, i'm only the programmer. @@ -1010,13 +1257,21 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) /* accept the connection */ len = sizeof(zdsock); - newfd = zfmovefd(accept(zdfd, (struct sockaddr *)&zdsock, &len)); + newfd = zfmovefd(accept(zfsess->dfd, (struct sockaddr *)&zdsock, + &len)); + if (newfd < 0) + zwarnnam(name, "unable to accept data: %e", NULL, errno); zfclosedata(); - if (newfd < 0) { - zwarnnam(name, "unable to accept data.", NULL, 0); + if (newfd < 0) return 1; - } - zdfd = newfd; /* this is now the actual data fd */ + zfsess->dfd = newfd; /* this is now the actual data fd */ + } else { + /* + * We avoided dup'ing zfsess->dfd up to this point, to try to keep + * things simple, so we now need to move it out of the way + * of the user-visible fd's. + */ + zfsess->dfd = zfmovefd(zfsess->dfd); } @@ -1035,20 +1290,21 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) li.l_onoff = 1; li.l_linger = 120; - setsockopt(zdfd, SOL_SOCKET, SO_LINGER, (char *)&li, sizeof(li)); + setsockopt(zfsess->dfd, SOL_SOCKET, SO_LINGER, + (char *)&li, sizeof(li)); } #endif #if defined(IP_TOS) && defined(IPTOS_THROUGHPUT) /* try to get high throughput, snigger */ { int arg = IPTOS_THROUGHPUT; - setsockopt(zdfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg)); + setsockopt(zfsess->dfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg)); } #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(zdfd, F_SETFD, &len); + fcntl(zfsess->dfd, F_SETFD, &len); #endif return 0; @@ -1067,9 +1323,9 @@ zfgetdata(char *name, char *rest, char *cmd, int getsize) /**/ static int -zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) +zfstats(char *fnam, int remote, off_t *retsize, char **retmdtm, int fd) { - long sz = -1; + off_t sz = -1; char *mt = NULL; int ret; @@ -1079,15 +1335,15 @@ zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) *retmdtm = NULL; if (remote) { char *cmd; - if ((zfhas_size == ZFCP_NOPE && retsize) || - (zfhas_mdtm == ZFCP_NOPE && retmdtm)) + if ((zfsess->has_size == ZFCP_NOPE && retsize) || + (zfsess->has_mdtm == ZFCP_NOPE && retmdtm)) return 2; /* * File is coming from over there. * Make sure we get the type right. */ - zfsettype(ZFST_TYPE(zfstatus)); + zfsettype(ZFST_TYPE(zfstatusp[zfsessno])); if (retsize) { cmd = tricat("SIZE ", fnam, "\r\n"); ret = zfsendcmd(cmd); @@ -1096,9 +1352,9 @@ zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) return 1; else if (lastcode < 300) { sz = zstrtol(lastmsg, 0, 10); - zfhas_size = ZFCP_YUPP; + zfsess->has_size = ZFCP_YUPP; } else if (lastcode >= 500 && lastcode <= 504) { - zfhas_size = ZFCP_NOPE; + zfsess->has_size = ZFCP_NOPE; return 2; } else if (lastcode == 550) return 1; @@ -1113,9 +1369,9 @@ zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) return 1; else if (lastcode < 300) { mt = ztrdup(lastmsg); - zfhas_mdtm = ZFCP_YUPP; + zfsess->has_mdtm = ZFCP_YUPP; } else if (lastcode >= 500 && lastcode <= 504) { - zfhas_mdtm = ZFCP_NOPE; + zfsess->has_mdtm = ZFCP_NOPE; return 2; } else if (lastcode == 550) return 1; @@ -1128,7 +1384,7 @@ zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) if ((fd == -1 ? stat(fnam, &statbuf) : fstat(fd, &statbuf)) < 0) return 1; - /* make sure it's long, since this has to be a pointer */ + /* make sure it's off_t, since this has to be a pointer */ sz = statbuf.st_size; if (retmdtm) { @@ -1156,9 +1412,9 @@ zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) /**/ static void -zfstarttrans(char *nam, int recv, long sz) +zfstarttrans(char *nam, int recv, off_t sz) { - long cnt = 0; + off_t cnt = 0; /* * sz = -1 signifies error getting size. don't set ZFTP_SIZE if sz is * zero, either: it probably came from an fstat() on a pipe, so it @@ -1187,7 +1443,7 @@ zfendtrans() /**/ static int -zfread(int fd, char *bf, size_t sz, int tmout) +zfread(int fd, char *bf, off_t sz, int tmout) { int ret; @@ -1212,7 +1468,7 @@ zfread(int fd, char *bf, size_t sz, int tmout) /**/ static int -zfwrite(int fd, char *bf, size_t sz, int tmout) +zfwrite(int fd, char *bf, off_t sz, int tmout) { int ret; @@ -1239,11 +1495,11 @@ static int zfread_eof; /**/ static int -zfread_block(int fd, char *bf, size_t sz, int tmout) +zfread_block(int fd, char *bf, off_t sz, int tmout) { int n; struct zfheader hdr; - size_t blksz, cnt; + off_t blksz, cnt; char *bfptr; do { /* we need the header */ @@ -1251,7 +1507,7 @@ zfread_block(int fd, char *bf, size_t sz, int tmout) n = zfread(fd, (char *)&hdr, sizeof(hdr), tmout); } while (n < 0 && errno == EINTR); if (n != 3 && !zfdrrrring) { - zwarnnam("zftp", "failed to read FTP block header", NULL, 0); + zwarnnam("zftp", "failure reading FTP block header", NULL, 0); return n; } /* size is stored in network byte order */ @@ -1291,11 +1547,11 @@ zfread_block(int fd, char *bf, size_t sz, int tmout) /**/ static int -zfwrite_block(int fd, char *bf, size_t sz, int tmout) +zfwrite_block(int fd, char *bf, off_t sz, int tmout) { int n; struct zfheader hdr; - size_t cnt; + off_t cnt; char *bfptr; /* we need the header */ do { @@ -1305,7 +1561,7 @@ zfwrite_block(int fd, char *bf, size_t sz, int tmout) n = zfwrite(fd, (char *)&hdr, sizeof(hdr), tmout); } while (n < 0 && errno == EINTR); if (n != 3 && !zfdrrrring) { - zwarnnam("zftp", "failed to write FTP block header", NULL, 0); + zwarnnam("zftp", "failure writing FTP block header", NULL, 0); return n; } bfptr = bf; @@ -1336,7 +1592,7 @@ zfwrite_block(int fd, char *bf, size_t sz, int tmout) /**/ static int -zfsenddata(char *name, int recv, int progress, long startat) +zfsenddata(char *name, int recv, int progress, off_t startat) { #define ZF_BUFSIZE 32768 #define ZF_ASCSIZE (ZF_BUFSIZE/2) @@ -1344,35 +1600,39 @@ zfsenddata(char *name, int recv, int progress, long startat) int n, ret = 0, gotack = 0, fdin, fdout, fromasc = 0, toasc = 0; int rtmout = 0, wtmout = 0; char lsbuf[ZF_BUFSIZE], *ascbuf = NULL, *optr; - long sofar = 0, last_sofar = 0; + off_t sofar = 0, last_sofar = 0; readwrite_t read_ptr = zfread, write_ptr = zfwrite; - List l; + Eprog prog; - if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + if (progress && (prog = getshfunc("zftp_progress")) != &dummy_eprog) { /* * progress to set up: ZFTP_COUNT is zero. * We do this here in case we needed to wait for a RETR * command to tell us how many bytes are coming. */ - doshfunc("zftp_progress", l, NULL, 0, 1); + int osc = sfcontext; + + sfcontext = SFC_HOOK; + doshfunc("zftp_progress", prog, NULL, 0, 1); + sfcontext = osc; /* Now add in the bit of the file we've got/sent already */ sofar = last_sofar = startat; } if (recv) { - fdin = zdfd; + fdin = zfsess->dfd; fdout = 1; rtmout = getiparam("ZFTP_TMOUT"); - if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + if (ZFST_CTYP(zfstatusp[zfsessno]) == ZFST_ASCI) fromasc = 1; - if (ZFST_MODE(zfstatus) == ZFST_BLOC) + if (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC) read_ptr = zfread_block; } else { fdin = 0; - fdout = zdfd; + fdout = zfsess->dfd; wtmout = getiparam("ZFTP_TMOUT"); - if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + if (ZFST_CTYP(zfstatusp[zfsessno]) == ZFST_ASCI) toasc = 1; - if (ZFST_MODE(zfstatus) == ZFST_BLOC) + if (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC) write_ptr = zfwrite_block; } @@ -1481,9 +1741,13 @@ zfsenddata(char *name, int recv, int progress, long startat) } else break; if (!ret && sofar != last_sofar && progress && - (l = getshfunc("zftp_progress")) != &dummy_list) { + (prog = getshfunc("zftp_progress")) != &dummy_eprog) { + int osc = sfcontext; + zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER); - doshfunc("zftp_progress", l, NULL, 0, 1); + sfcontext = SFC_HOOK; + doshfunc("zftp_progress", prog, NULL, 0, 1); + sfcontext = osc; last_sofar = sofar; } } @@ -1493,7 +1757,8 @@ zfsenddata(char *name, int recv, int progress, long startat) * so we don't need to force the control connection to close. */ zfdrrrring = 0; - if (!errflag && !ret && !recv && ZFST_MODE(zfstatus) == ZFST_BLOC) { + if (!errflag && !ret && !recv && + ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC) { /* send an end-of-file marker block */ ret = (zfwrite_block(fdout, lsbuf, 0, wtmout) < 0); } @@ -1522,8 +1787,8 @@ zfsenddata(char *name, int recv, int progress, long 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(zcfd, (char *)msg, 3, 0); - send(zcfd, (char *)msg+3, 1, MSG_OOB); + send(zfsess->cfd, (char *)msg, 3, 0); + send(zfsess->cfd, (char *)msg+3, 1, MSG_OOB); zfsendcmd("ABOR\r\n"); if (lastcode == 226) { @@ -1563,16 +1828,16 @@ zfsenddata(char *name, int recv, int progress, long startat) static int zftp_open(char *name, char **args, int flags) { - struct in_addr ipaddr; struct protoent *zprotop; struct servent *zservp; struct hostent *zhostp = NULL; - char **addrp, tbuf[2] = "X", *fname; + char **addrp, *fname; int err, len, tmout; + int herrno, af, salen; if (!*args) { - if (zfuserparams) - args = zfuserparams; + if (zfsess->userparams) + args = zfsess->userparams; else { zwarnnam(name, "no host specified", NULL, 0); return 1; @@ -1584,8 +1849,8 @@ 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 (zcfd != -1) - zfclose(); + if (zfsess->cfd != -1) + zfclose(0); /* this is going to give 0. why bother? */ zprotop = getprotobyname("tcp"); @@ -1611,99 +1876,126 @@ zftp_open(char *name, char **args, int flags) zwarnnam(name, "timeout connecting to %s", hname, 0); else zwarnnam(name, "timeout on host name lookup", NULL, 0); - zfclose(); + zfclose(0); return 1; } zfalarm(tmout); - /* - * Now this is what I like. A library which provides decent higher - * level functions to do things like converting address types. It saves - * so much trouble. Pity about the rest of the network interface, though. - */ - ipaddr.s_addr = inet_addr(args[0]); - if (ipaddr.s_addr != INADDR_NONE) { - /* - * hmmm, we don't necessarily want to do this... maybe the - * user is actively trying to avoid a bad nameserver. - * perhaps better just to set ZFTP_HOST to the dot address, too. - * that way shell functions know how it was opened. - * - * zhostp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET); - * - * or, we could have a `no_lookup' flag. - */ - zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY); - zsock.sin_family = AF_INET; - } else { - zhostp = gethostbyname(args[0]); +#ifdef SUPPORT_IPV6 + for(af=AF_INET6; 1; af = AF_INET) +# define SUCCEEDED() break +# define FAILED() if(af == AF_INET) { } else continue +#else + af = AF_INET; +# define SUCCEEDED() do { } while(0) +# define FAILED() do { } while(0) +#endif + { + zhostp = getipnodebyname(args[0], 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. */ + FAILED(); zwarnnam(name, "host not found: %s", args[0], 0); alarm(0); return 1; } - zsock.sin_family = zhostp->h_addrtype; zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY); - } - zsock.sin_port = ntohs(zservp->s_port); - zcfd = zfmovefd(socket(zsock.sin_family, SOCK_STREAM, 0)); - if (zcfd < 0) { - zwarnnam(name, "socket failed: %e", NULL, errno); - zfunsetparam("ZFTP_HOST"); - alarm(0); - return 1; - } + 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); + } else +#endif /* SUPPORT_IPV6 */ + { + zfsess->peer.in.sin_port = zservp->s_port; + salen = sizeof(struct sockaddr_in); + } -#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(zcfd, F_SETFD, &len); -#endif + zfsess->cfd = socket(af, SOCK_STREAM, 0); + if (zfsess->cfd < 0) { + freehostent(zhostp); + zfunsetparam("ZFTP_HOST"); + FAILED(); + zwarnnam(name, "socket failed: %e", NULL, errno); + alarm(0); + return 1; + } + /* counts as `open' so long as it's not negative */ + zfnopen++; - /* - * now connect the socket. manual pages all say things like `this is all - * explained oh-so-wonderfully in some other manual page'. not. - */ + /* + * now connect the socket. manual pages all say things like `this is + * all explained oh-so-wonderfully in some other manual page'. not. + */ - err = 1; + err = 1; - if (ipaddr.s_addr != INADDR_NONE) { - /* dot address */ - memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr)); - do { - err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); - } while (err && errno == EINTR && !errflag); - } else { - /* host name: try all possible IP's */ - for (addrp = zhostp->h_addr_list; *addrp; addrp++) { - memcpy(&zsock.sin_addr, *addrp, zhostp->h_length); + /* 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); do { - err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + err = connect(zfsess->cfd, (struct sockaddr *)&zfsess->peer, + salen); } while (err && errno == EINTR && !errflag); /* you can check whether it's worth retrying here */ } - } - alarm(0); + if (err) { + freehostent(zhostp); + zfclose(0); + FAILED(); + zwarnnam(name, "connect failed: %e", NULL, errno); + alarm(0); + return 1; + } - if (err < 0) { - zwarnnam(name, "connect failed: %e", NULL, errno); - zfclose(); - return 1; + SUCCEEDED(); } - zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY); + alarm(0); + { +#ifdef SUPPORT_IPV6 + char pbuf[INET6_ADDRSTRLEN]; +#else + char pbuf[INET_ADDRSTRLEN]; +#endif + addrp--; + inet_ntop(af, *addrp, pbuf, sizeof(pbuf)); + zfsetparam("ZFTP_IP", ztrdup(pbuf), ZFPM_READONLY); + } + freehostent(zhostp); /* now we can talk to the control connection */ zcfinish = 0; - len = sizeof(zsock); - if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) { + /* + * 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); + +#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); +#endif + + len = sizeof(zfsess->sock); + if (getsockname(zfsess->cfd, (struct sockaddr *)&zfsess->sock, &len) < 0) { zwarnnam(name, "getsockname failed: %e", NULL, errno); - zfclose(); + zfclose(0); return 1; } /* nice to get some options right, ignore if they don't work */ @@ -1713,89 +2005,75 @@ zftp_open(char *name, char **args, int flags) * do clever things with SIGURG. */ len = 1; - setsockopt(zcfd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len)); + setsockopt(zfsess->cfd, 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(zcfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len)); + setsockopt(zfsess->cfd, 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(). */ - zcin = fdopen(zcfd, "r"); + zfsess->cin = fdopen(zfsess->cfd, "r"); - if (!zcin) { + if (!zfsess->cin) { zwarnnam(name, "file handling error", NULL, 0); - zfclose(); + zfclose(0); return 1; } #ifdef _IONBF - setvbuf(zcin, NULL, _IONBF, 0); + setvbuf(zfsess->cin, NULL, _IONBF, 0); #else - setlinebuf(zcin); + setlinebuf(zfsess->cin); #endif /* * now see what the remote server has to say about that. */ if (zfgetmsg() >= 4) { - zfclose(); + zfclose(0); return 1; } - zfis_unix = 0; - zfhas_size = zfhas_mdtm = ZFCP_UNKN; - zdfd = -1; + zfsess->has_size = zfsess->has_mdtm = ZFCP_UNKN; + zfsess->dfd = -1; /* initial status: open, ASCII data, stream mode 'n' stuff */ - zfstatus = 0; + zfstatusp[zfsessno] = 0; - /* open file for saving the current status */ - fname = gettempname(); - zfstatfd = open(fname, O_RDWR|O_CREAT, 0600); - DPUTS(zfstatfd == -1, "zfstatfd not created"); + /* + * Open file for saving the current status. + * We keep this open at the end of the session because + * it is used to store the status for all sessions. + * However, it is closed whenever there are no connections open. + */ + if (zfstatfd == -1) { + fname = gettempname(); + zfstatfd = open(fname, O_RDWR|O_CREAT, 0600); + 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); + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zfstatfd, F_SETFD, &len); #endif - unlink(fname); - - /* now find out what system we're connected to */ - if (!(zfprefs & ZFPF_DUMB) && zfsendcmd("SYST\r\n") == 2) { - char *ptr = lastmsg, *eptr, *systype; - for (eptr = ptr; *eptr; eptr++) - ; - systype = ztrduppfx(ptr, eptr-ptr); - if (!strncmp(systype, "UNIX Type: L8", 13)) { - /* - * Use binary for transfers. This simple test saves much - * hassle for all concerned, particularly me. - */ - zfstatus |= ZFST_IMAG; - zfis_unix = 1; - } - /* - * we could set zfis_unix based just on the UNIX part, - * but I don't really know the consequences of that. - */ - zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY); - } else if (zcfd == -1) { + unlink(fname); + } + + if (zfsess->cfd == -1) { /* final paranoid check */ return 1; } - tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'; - zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY); /* if remaining arguments, use them to log in. */ - if (zcfd > -1 && *++args) + if (zfsess->cfd > -1 && *++args) return zftp_login(name, args, flags); /* if something wayward happened, connection was already closed */ - return zcfd == -1; + return zfsess->cfd == -1; } /* @@ -1877,8 +2155,8 @@ zftp_params(char *name, char **args, int flags) int i, j, len; if (!*args) { - if (zfuserparams) { - for (aptr = zfuserparams, i = 0; *aptr; aptr++, i++) { + if (zfsess->userparams) { + for (aptr = zfsess->userparams, i = 0; *aptr; aptr++, i++) { if (i == 2) { len = strlen(*aptr); for (j = 0; j < len; j++) @@ -1887,23 +2165,24 @@ zftp_params(char *name, char **args, int flags) } else fprintf(stdout, "%s\n", *aptr); } - } - return 0; + return 0; + } else + return 1; } if (!strcmp(*args, "-")) { - if (zfuserparams) - freearray(zfuserparams); - zfuserparams = 0; + if (zfsess->userparams) + freearray(zfsess->userparams); + zfsess->userparams = 0; return 0; } len = arrlen(args); newarr = (char **)zcalloc((len+1)*sizeof(char *)); for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) { char *str; - if (!strcmp(*aptr, "?")) - str = zfgetinfo(prompts[i], i == 2); + if (**aptr == '?') + str = zfgetinfo((*aptr)[1] ? (*aptr+1) : prompts[i], i == 2); else - str = *aptr; + str = (**aptr == '\\') ? *aptr+1 : *aptr; newarr[i] = ztrdup(str); } if (errflag) { @@ -1913,9 +2192,9 @@ zftp_params(char *name, char **args, int flags) zfree(newarr, len+1); return 1; } - if (zfuserparams) - freearray(zfuserparams); - zfuserparams = newarr; + if (zfsess->userparams) + freearray(zfsess->userparams); + zfsess->userparams = newarr; return 0; } @@ -1926,13 +2205,13 @@ static int zftp_login(char *name, char **args, int flags) { char *ucmd, *passwd = NULL, *acct = NULL; - char *user; + char *user, tbuf[2] = "X"; int stopit; - if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) + if ((zfstatusp[zfsessno] & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) return 1; - zfstatus &= ~ZFST_LOGI; + zfstatusp[zfsessno] &= ~ZFST_LOGI; if (*args) { user = *args++; } else { @@ -1988,7 +2267,7 @@ zftp_login(char *name, char **args, int flags) } zsfree(ucmd); - if (zcfd == -1) + if (zfsess->cfd == -1) return 1; if (stopit == 2 || (lastcode != 230 && lastcode != 202)) { zwarnnam(name, "login failed", NULL, 0); @@ -2001,12 +2280,40 @@ zftp_login(char *name, char **args, int flags) cnt++; zwarnnam(name, "warning: %d comand arguments not used\n", NULL, cnt); } - zfstatus |= ZFST_LOGI; + zfstatusp[zfsessno] |= ZFST_LOGI; zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY); if (acct) zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), ZFPM_READONLY); /* + * Now find out what system we're connected to. Some systems + * won't let us do this until we're logged in; it's fairly safe + * to delay it here for all systems. + */ + if (!(zfprefs & ZFPF_DUMB) && !(zfstatusp[zfsessno] & ZFST_SYST)) { + if (zfsendcmd("SYST\r\n") == 2) { + char *ptr = lastmsg, *eptr, *systype; + for (eptr = ptr; *eptr; eptr++) + ; + systype = ztrduppfx(ptr, eptr-ptr); + if (!strncmp(systype, "UNIX Type: L8", 13)) { + /* + * Use binary for transfers. This simple test saves much + * hassle for all concerned, particularly me. + * + * We could set this based just on the UNIX part, + * but I don't really know the consequences of that. + */ + zfstatusp[zfsessno] |= ZFST_IMAG; + } + zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY); + } + zfstatusp[zfsessno] |= ZFST_SYST; + } + tbuf[0] = (ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI) ? 'A' : 'I'; + zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); + + /* * Get the directory. This is possibly an unnecessary overhead, of * course, but when you're being driven by shell functions there's * just no way of telling. @@ -2014,6 +2321,70 @@ zftp_login(char *name, char **args, int flags) return zfgetcwd(); } +/* + * See if the server wants to tell us something. On a timeout, we usually + * have a `421 Timeout' or something such waiting for us, so we read + * it here. As well as being called explicitly by the user + * (precmd is a very good place for this, it's cheap since it has + * no network overhead), we call it in the bin_zftp front end if we + * have a connection and weren't going to call it anyway. + * + * Poll-free and select-free systems are few and far between these days, + * but I'm willing to consider suggestions. + */ + +/**/ +static int +zftp_test(char *name, char **args, int flags) +{ +#if defined(HAVE_POLL) || defined(HAVE_SELECT) + int ret; +# ifdef HAVE_POLL + struct pollfd pfd; +# else + fd_set f; + struct timeval tv; +# endif /* HAVE_POLL */ + + if (zfsess->cfd == -1) + return 1; + +# ifdef HAVE_POLL +# ifndef POLLIN + /* safety first, though I think POLLIN is more common */ +# define POLLIN POLLNORM +# endif /* HAVE_POLL */ + pfd.fd = zfsess->cfd; + pfd.events = POLLIN; + if ((ret = poll(&pfd, 1, 0)) < 0 && errno != EINTR && errno != EAGAIN) + zfclose(0); + else if (ret > 0 && pfd.revents) { + /* handles 421 (maybe a bit noisily?) */ + zfgetmsg(); + } +# else + FD_ZERO(&f); + FD_SET(zfsess->cfd, &f); + tv.tv_sec = 0; + tv.tv_usec = 0; + if ((ret = select(zfsess->cfd +1, (SELECT_ARG_2_T) &f, + NULL, NULL, &tv)) < 0 + && errno != EINTR) + zfclose(0); + else if (ret > 0) { + /* handles 421 */ + zfgetmsg(); + } +# endif /* HAVE_POLL */ + /* if we now have zfsess->cfd == -1, then we've just been dumped out. */ + return (zfsess->cfd == -1) ? 2 : 0; +#else + zfwarnnam(name, "not supported on this system.", NULL, 0); + return 3; +#endif /* defined(HAVE_POLL) || defined(HAVE_SELECT) */ +} + + /* do ls or dir on the remote directory */ /**/ @@ -2075,7 +2446,7 @@ zfgetcwd(void) { char *ptr, *eptr; int endc; - List l; + Eprog prog; if (zfprefs & ZFPF_DUMB) return 1; @@ -2102,9 +2473,13 @@ zfgetcwd(void) * front end. By putting it here, and in close when ZFTP_PWD is unset, * we at least cover the bases. */ - if ((l = getshfunc("zftp_chpwd")) != &dummy_list) - doshfunc("zftp_chpwd", l, NULL, 0, 1); + if ((prog = getshfunc("zftp_chpwd")) != &dummy_eprog) { + int osc = sfcontext; + sfcontext = SFC_HOOK; + doshfunc("zftp_chpwd", prog, NULL, 0, 1); + sfcontext = osc; + } return 0; } @@ -2117,14 +2492,14 @@ static int zfsettype(int type) { char buf[] = "TYPE X\r\n"; - if (ZFST_TYPE(type) == ZFST_CTYP(zfstatus)) + if (ZFST_TYPE(type) == ZFST_CTYP(zfstatusp[zfsessno])) return 0; buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I'; if (zfsendcmd(buf) > 2) return 1; - zfstatus &= ~(ZFST_TMSK << ZFST_TBIT); + zfstatusp[zfsessno] &= ~(ZFST_TMSK << ZFST_TBIT); /* shift the type left to set the current type bits */; - zfstatus |= type << ZFST_TBIT; + zfstatusp[zfsessno] |= type << ZFST_TBIT; return 0; } @@ -2145,11 +2520,12 @@ zftp_type(char *name, char **args, int flags) * Since this is supposed to be a low-level basis for * an FTP system, just print the single code letter. */ - printf("%c\n", (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'); + printf("%c\n", (ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI) ? + 'A' : 'I'); fflush(stdout); return 0; } else { - nt = toupper(*str); + nt = toupper(STOUC(*str)); /* * RFC959 specifies other types, but these are the only * ones we know what to do with. @@ -2163,8 +2539,8 @@ zftp_type(char *name, char **args, int flags) nt = 'I'; } - zfstatus &= ~ZFST_TMSK; - zfstatus |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI; + zfstatusp[zfsessno] &= ~ZFST_TMSK; + zfstatusp[zfsessno] |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI; tbuf[0] = nt; zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); return 0; @@ -2178,11 +2554,12 @@ zftp_mode(char *name, char **args, int flags) int nt; if (!(str = *args)) { - printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B'); + printf("%c\n", (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_STRE) ? + 'S' : 'B'); fflush(stdout); return 0; } - nt = str[0] = toupper(*str); + nt = str[0] = toupper(STOUC(*str)); if (str[1] || (nt != 'S' && nt != 'B')) { zwarnnam(name, "transfer mode %s not recognised", str, 0); return 1; @@ -2190,8 +2567,8 @@ zftp_mode(char *name, char **args, int flags) cmd[5] = (char) nt; if (zfsendcmd(cmd) > 2) return 1; - zfstatus &= ZFST_MMSK; - zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; + zfstatusp[zfsessno] &= ZFST_MMSK; + zfstatusp[zfsessno] |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY); return 0; } @@ -2202,7 +2579,7 @@ zftp_local(char *name, char **args, int flags) { int more = !!args[1], ret = 0, dofd = !*args; while (*args || dofd) { - long sz; + off_t sz; char *mt; int newret = zfstats(*args, !(flags & ZFTP_HERE), &sz, &mt, dofd ? 0 : -1); @@ -2219,7 +2596,12 @@ zftp_local(char *name, char **args, int flags) fputs(*args, stdout); fputc(' ', stdout); } +#ifdef OFF_T_IS_64_BIT + printf("%s %s\n", output64(sz), mt); +#else + DPUTS(sizeof(sz) > 4, "Shell compiled with wrong off_t size"); printf("%ld %s\n", sz, mt); +#endif zsfree(mt); if (dofd) break; @@ -2249,7 +2631,7 @@ zftp_getput(char *name, char **args, int flags) { int ret = 0, recv = (flags & ZFTP_RECV), getsize = 0, progress = 1; char *cmd = recv ? "RETR " : (flags & ZFTP_APPE) ? "APPE " : "STOR "; - List l; + Eprog prog; /* * At this point I'd like to set progress to 0 if we're @@ -2260,15 +2642,15 @@ zftp_getput(char *name, char **args, int flags) * somewhere or in the background. This seems to me a problem. */ - zfsettype(ZFST_TYPE(zfstatus)); + zfsettype(ZFST_TYPE(zfstatusp[zfsessno])); if (recv) fflush(stdout); /* since we may be using fd 1 */ for (; *args; args++) { char *ln, *rest = NULL; - long startat = 0; - if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { - long sz; + off_t startat = 0; + if (progress && (prog = getshfunc("zftp_progress")) != &dummy_eprog) { + off_t sz; /* * This calls the SIZE command to get the size for remote * files. Some servers send the size with the reply to @@ -2278,7 +2660,7 @@ zftp_getput(char *name, char **args, int flags) * of zftp_progress is delayed until zfsenddata(). */ if ((!(zfprefs & ZFPF_DUMB) && - (zfstatus & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ) + (zfstatusp[zfsessno] & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ) || !recv) { /* the final 0 is a local fd to fstat if recv is zero */ zfstats(*args, recv, &sz, NULL, 0); @@ -2296,16 +2678,27 @@ zftp_getput(char *name, char **args, int flags) } ln = tricat(cmd, *args, "\r\n"); - /* note zdfd doesn't exist till zfgetdata() creates it */ - if (zfgetdata(name, rest, ln, getsize) || - zfsenddata(name, recv, progress, startat)) + /* note zfsess->dfd doesn't exist till zfgetdata() creates it */ + if (zfgetdata(name, rest, ln, getsize)) + ret = 2; + else if (zfsenddata(name, recv, progress, startat)) ret = 1; zsfree(ln); - if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* + * The progress report isn't started till zfsenddata(), where + * it's the first item. Hence we send a final progress report + * if and only if we called zfsenddata(); + */ + if (progress && ret != 2 && + (prog = getshfunc("zftp_progress")) != &dummy_eprog) { /* progress to finish: ZFTP_TRANSFER set to GF or PF */ + int osc = sfcontext; + zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"), ZFPM_READONLY); - doshfunc("zftp_progress", l, NULL, 0, 1); + sfcontext = SFC_HOOK; + doshfunc("zftp_progress", prog, NULL, 0, 1); + sfcontext = osc; } if (rest) { zsfree(rest); @@ -2315,7 +2708,7 @@ zftp_getput(char *name, char **args, int flags) break; } zfendtrans(); - return ret; + return ret != 0; } /* @@ -2393,14 +2786,22 @@ zftp_quote(char *name, char **args, int flags) return ret; } -/* Close the connection, ending the session */ +/* + * Close the connection, ending the session. With leaveparams, + * don't do anything to the external status (parameters, zftp_chpwd), + * probably because this isn't the current session. + */ /**/ -static int -zftp_close(char *name, char **args, int flags) +static void +zfclose(int leaveparams) { char **aptr; - List l; + Eprog prog; + + if (zfsess->cfd == -1) + return; + zfclosing = 1; if (zcfinish != 2) { /* @@ -2410,41 +2811,263 @@ zftp_close(char *name, char **args, int flags) */ zfsendcmd("QUIT\r\n"); } - if (zcin) - fclose(zcin); - zcin = NULL; - close(zcfd); - zcfd = -1; + if (zfsess->cin) { + fclose(zfsess->cin); + zfsess->cin = NULL; + } + if (zfsess->cfd != -1) { + zfnopen--; + close(zfsess->cfd); + zfsess->cfd = -1; + } + + if (zfstatfd != -1) { + zfstatusp[zfsessno] |= ZFST_CLOS; + if (!zfnopen) { + /* Write the final status in case this is a subshell */ + lseek(zfstatfd, zfsessno*sizeof(int), 0); + write(zfstatfd, zfstatusp+zfsessno, sizeof(int)); + + close(zfstatfd); + zfstatfd = -1; + } + } - /* Write the final status in case this is a subshell */ - zfstatus |= ZFST_CLOS; - lseek(zfstatfd, 0, 0); - write(zfstatfd, &zfstatus, sizeof(zfstatus)); - close(zfstatfd); - zfstatfd = -1; + if (!leaveparams) { + /* Unset the non-special parameters */ + for (aptr = zfparams; *aptr; aptr++) + zfunsetparam(*aptr); - /* Unset the non-special parameters */ - for (aptr = zfparams; *aptr; aptr++) - zfunsetparam(*aptr); + /* Now ZFTP_PWD is unset. It's up to zftp_chpwd to notice. */ + if ((prog = getshfunc("zftp_chpwd")) != &dummy_eprog) { + int osc = sfcontext; - /* Now ZFTP_PWD is unset. It's up to zftp_chpwd to notice. */ - if ((l = getshfunc("zftp_chpwd")) != &dummy_list) - doshfunc("zftp_chpwd", l, NULL, 0, 1); + sfcontext = SFC_HOOK; + doshfunc("zftp_chpwd", prog, NULL, 0, 1); + sfcontext = osc; + } + } /* tidy up status variables, because mess is bad */ zfclosing = zfdrrrring = 0; +} + +/* Safe front end to zftp_close() from within the package */ +/**/ +static int +zftp_close(char *name, char **args, int flags) +{ + zfclose(0); return 0; } -/* Safe front end to zftp_close() from within the package */ + +/* + * Session management routines. A session consists of various + * internal variables describing the connection, the set of shell + * parameters --- the same set which is unset by closing a connection --- + * and the set of host/user parameters if set by zftp params. + */ + +/* + * Switch to a new session, creating it if necessary. + * Sets zfsessno, zfsess and $ZFTP_SESSION; updates zfsesscnt and zfstatusp. + */ + +/**/ +static void +newsession(char *nm) +{ + LinkNode nptr; + + for (zfsessno = 0, nptr = firstnode(zfsessions); + nptr; zfsessno++, incnode(nptr)) { + zfsess = (Zftp_session) nptr->dat; + if (!strcmp(zfsess->name, nm)) + break; + } + + if (!nptr) { + zfsess = (Zftp_session) zcalloc(sizeof(struct zftp_session)); + zfsess->name = ztrdup(nm); + zfsess->cfd = zfsess->dfd = -1; + zfsess->params = (char **) zcalloc(sizeof(zfparams)); + zaddlinknode(zfsessions, zfsess); + + zfsesscnt++; + zfstatusp = (int *)zrealloc(zfstatusp, sizeof(int)*zfsesscnt); + zfstatusp[zfsessno] = 0; + } + + zfsetparam("ZFTP_SESSION", ztrdup(zfsess->name), ZFPM_READONLY); +} + +/* Save the existing session: this just means saving the parameters. */ + +static void +savesession() +{ + char **ps, **pd, *val; + + for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) { + if (*pd) + zsfree(*pd); + if ((val = getsparam(*ps))) + *pd = ztrdup(val); + else + *pd = NULL; + } + *pd = NULL; +} + +/* + * Switch to session nm, creating it if necessary. + * Just call newsession, then set up the session-specific parameters. + */ + +/**/ +static void +switchsession(char *nm) +{ + char **ps, **pd; + + newsession(nm); + + for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) { + if (*pd) { + /* Use the permanently allocated string for the parameter */ + zfsetparam(*ps, *pd, ZFPM_READONLY); + *pd = NULL; + } else + zfunsetparam(*ps); + } +} /**/ static void -zfclose(void) +freesession(Zftp_session sptr) { - if (zcfd != -1) - zftp_close("zftp close", NULL, 0); + char **ps, **pd; + zsfree(sptr->name); + for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) + if (*pd) + zsfree(*pd); + zfree(zfsess->params, sizeof(zfparams)); + if (sptr->userparams) + freearray(sptr->userparams); + zfree(sptr, sizeof(struct zftp_session)); +} + +/**/ +static int +zftp_session(char *name, char **args, int flags) +{ + if (!*args) { + LinkNode nptr; + + for (nptr = firstnode(zfsessions); nptr; incnode(nptr)) + printf("%s\n", ((Zftp_session)nptr->dat)->name); + return 0; + } + + /* + * Check if we are already in the required session: if so, + * it's a no-op, not an error. + */ + if (!strcmp(*args, zfsess->name)) + return 0; + + savesession(); + switchsession(*args); + return 0; +} + +/* Remove a session and free it */ + +/**/ +static int +zftp_rmsession(char *name, char **args, int flags) +{ + int no; + LinkNode nptr; + Zftp_session sptr = NULL; + char *newsess = NULL; + + /* Find the session in the list: either the current one, or by name */ + for (no = 0, nptr = firstnode(zfsessions); nptr; no++, incnode(nptr)) { + sptr = (Zftp_session) nptr->dat; + if ((!*args && sptr == zfsess) || + (*args && !strcmp(sptr->name, *args))) + break; + } + if (!nptr) + return 1; + + if (sptr == zfsess) { + /* Freeing current session: make sure it's closed */ + zfclosedata(); + zfclose(0); + + /* + * Choose new session to switch to if any: first in list + * excluding the one just freed. + */ + if (zfsesscnt > 1) { + LinkNode newn = firstnode(zfsessions); + if (newn == nptr) + incnode(newn); + newsess = ((Zftp_session)newn->dat)->name; + } + } else { + Zftp_session oldsess = zfsess; + zfsess = sptr; + /* + * Freeing another session: don't need to switch, just + * tell zfclose() not to delete parameters etc. + */ + zfclosedata(); + zfclose(1); + zfsess = oldsess; + } + remnode(zfsessions, nptr); + freesession(sptr); + + /* + * Fix up array of status pointers. + */ + if (--zfsesscnt) { + /* + * Some remaining, so just shift up + */ + int *newstatusp = (int *)zalloc(sizeof(int)*zfsesscnt); + int *src, *dst, i; + for (i = 0, src = zfstatusp, dst = newstatusp; i < zfsesscnt; + i++, src++, dst++) { + if (i == no) + src++; + *dst = *src; + } + zfree(zfstatusp, sizeof(int)*(zfsesscnt+1)); + zfstatusp = newstatusp; + + /* + * Maybe we need to switch to one of the remaining sessions. + */ + if (newsess) + switchsession(newsess); + } else { + zfree(zfstatusp, sizeof(int)); + zfstatusp = NULL; + + /* + * We've just deleted the last session, so we need to + * start again from scratch. + */ + newsession("default"); + } + + return 0; } /* The builtin command frontend to the rest of the package */ @@ -2453,10 +3076,10 @@ zfclose(void) static int bin_zftp(char *name, char **args, char *ops, int func) { - char fullname[11] = "zftp "; + char fullname[20] = "zftp "; char *cnam = *args++, *prefs, *ptr; Zftpcmd zptr; - int n, ret; + int n, ret = 0; for (zptr = zftpcmdtab; zptr->nam; zptr++) if (!strcmp(zptr->nam, cnam)) @@ -2476,40 +3099,57 @@ bin_zftp(char *name, char **args, char *ops, int func) } strcat(fullname, cnam); - if (zfstatfd != -1) { + if (zfstatfd != -1 && !(zptr->flags & ZFTP_SESS)) { /* Get the status in case it was set by a forked process */ - int oldstatus = zfstatus; + int oldstatus = zfstatusp[zfsessno]; lseek(zfstatfd, 0, 0); - read(zfstatfd, &zfstatus, sizeof(zfstatus)); - if (zcfd != -1 && (zfstatus & ZFST_CLOS)) { + read(zfstatfd, zfstatusp, sizeof(int)*zfsesscnt); + if (zfsess->cfd != -1 && (zfstatusp[zfsessno] & ZFST_CLOS)) { /* got closed in subshell without us knowing */ zcfinish = 2; - zfclose(); + zfclose(0); } else { /* * fix up status types: unfortunately they may already * have been looked at between being changed in the subshell * and now, but we can't help that. */ - if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatus)) + if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatusp[zfsessno])) zfsetparam("ZFTP_TYPE", - ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ? + ztrdup(ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI ? "A" : "I"), ZFPM_READONLY); - if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus)) + if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatusp[zfsessno])) zfsetparam("ZFTP_MODE", - ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ? + ztrdup(ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC ? "B" : "S"), ZFPM_READONLY); } } - if ((zptr->flags & ZFTP_CONN) && zcfd == -1) { - zwarnnam(fullname, "not connected.", NULL, 0); +#if defined(HAVE_SELECT) || defined (HAVE_POLL) + if (zfsess->cfd != -1 && !(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 + * if we are not about to call the test command anyway. + * Not worth it unless we have select() or poll(). + */ + ret = zftp_test("zftp test", NULL, 0); + } +#endif + if ((zptr->flags & ZFTP_CONN) && zfsess->cfd == -1) { + if (ret != 2) { + /* + * with ret == 2, we just got dumped out in the test, + * so enough messages already. + */ + zwarnnam(fullname, "not connected.", NULL, 0); + } return 1; } if ((prefs = getsparam("ZFTP_PREFS"))) { zfprefs = 0; for (ptr = prefs; *ptr; ptr++) { - switch (toupper(*ptr)) { + switch (toupper(STOUC(*ptr))) { case 'S': /* sendport */ zfprefs |= ZFPF_SNDP; @@ -2544,12 +3184,15 @@ bin_zftp(char *name, char **args, char *ops, int func) if (zfdrrrring) { /* had a timeout, close the connection */ zcfinish = 2; /* don't try sending QUIT */ - zfclose(); + zfclose(0); } if (zfstatfd != -1) { - /* Set the status in case another process needs to know */ - lseek(zfstatfd, 0, 0); - write(zfstatfd, &zfstatus, sizeof(zfstatus)); + /* + * Set the status in case another process needs to know, + * but only for the active session. + */ + lseek(zfstatfd, zfsessno*sizeof(int), 0); + write(zfstatfd, zfstatusp+zfsessno, sizeof(int)); } return ret; } @@ -2558,39 +3201,64 @@ bin_zftp(char *name, char **args, char *ops, int func) /**/ int -boot_zftp(Module m) +setup_(Module m) +{ + return 0; +} + +/**/ +int +boot_(Module m) { int ret; if ((ret = addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab))) == 1) { /* if successful, set some default parameters */ - long tmout_def = 60; + off_t tmout_def = 60; zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET); zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER); zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET); /* default preferences if user deletes variable */ zfprefs = ZFPF_SNDP|ZFPF_PASV; + + zfsessions = znewlinklist(); + newsession("default"); } + return !ret; } -#ifdef MODULE - /**/ int -cleanup_zftp(Module m) +cleanup_(Module m) { /* * There are various parameters hanging around, but they're * all non-special so are entirely non-life-threatening. */ - zfclosedata(); - zfclose(); + 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); - if (zfuserparams) - freearray(zfuserparams); + zfunsetparam("ZFTP_SESSION"); + freelinklist(zfsessions, (FreeFunc) freesession); + zfree(zfstatusp, sizeof(int)*zfsesscnt); deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); return 0; } -#endif +/**/ +int +finish_(Module m) +{ + return 0; +} |