summaryrefslogtreecommitdiff
path: root/Src/Modules
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>2000-05-09 11:54:43 +0000
committerTanaka Akira <akr@users.sourceforge.net>2000-05-09 11:54:43 +0000
commita65e197d2ab69181e615e32409b69469e509eaa3 (patch)
tree09f988912b193e84686a293c6436d88fdbd37e80 /Src/Modules
parent8c6a5af791ea0ccf44b71577b4cb1a9cec92646c (diff)
downloadzsh-a65e197d2ab69181e615e32409b69469e509eaa3.tar.gz
zsh-a65e197d2ab69181e615e32409b69469e509eaa3.zip
11276: fix build on OpenBSD.
Diffstat (limited to 'Src/Modules')
-rw-r--r--Src/Modules/zftp.c1472
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;
+}