1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-08 08:36:17 +02:00
zsh/Src/Modules/zftp.c
Oliver Kiddle a73c705b0c 51212: remove STOUC() macro
This served as a workaround for ancient compilers where casts to
unsigned char were broken.
2022-12-16 23:28:10 +01:00

3231 lines
82 KiB
C

/*
* zftp.c - builtin FTP client
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1998 Peter Stephenson
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Peter Stephenson or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Peter Stephenson and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Peter Stephenson and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Peter Stephenson and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
/*
* 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?
* 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.
* proxy/gateway connections if i knew what to do
* options to specify e.g. a non-standard port
*/
/* needed in prototypes for statics */
struct hostent;
struct in_addr;
struct sockaddr_in;
struct sockaddr_in6;
struct zftp_session;
typedef struct zftp_session *Zftp_session;
#include "tcp.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>
/*
* 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
#ifdef USE_LOCAL_H_ERRNO
int h_errno;
#endif
union zftp_sockaddr {
struct sockaddr a;
struct sockaddr_in in;
#ifdef SUPPORT_IPV6
struct sockaddr_in6 in6;
#endif
};
#ifdef USE_LOCAL_H_ERRNO
int h_errno;
#endif
/*
* For FTP block mode
*
* The server on our AIX machine here happily accepts block mode, takes the
* first connection, then at the second complains that it's got nowhere
* to send data. The same problem happens with ncftp, it's not just
* me. And a lot of servers don't even support block mode. So I'm not sure
* how widespread the supposed ability to leave open the data fd between
* transfers. Therefore, I've closed all connections after the transfer.
* But then what's the point in block mode? I only implemented it because
* it says in RFC959 that you need it to be able to restart transfers
* later in the file. However, it turns out that's not true for
* most servers --- but our AIX machine happily accepts the REST
* command and then dumps the whole file onto you. Sigh.
*
* Note on block sizes:
* Not quite sure how to optimize this: in principle
* we should handle blocks up to 65535 bytes, which
* is pretty big, and should presumably send blocks
* which are smaller to be on the safe side.
* Currently we send 32768 and use that also as
* the maximum to receive. No-one's complained yet. Of course,
* no-one's *used* it yet apart from me, but even so.
*/
struct zfheader {
char flags;
unsigned char bytes[2];
};
enum {
ZFHD_MARK = 16, /* restart marker */
ZFHD_ERRS = 32, /* suspected errors in block */
ZFHD_EOFB = 64, /* block is end of record */
ZFHD_EORB = 128 /* block is end of file */
};
typedef int (*readwrite_t)(int, char *, off_t, int);
struct zftpcmd {
const char *nam;
int (*fun) _((char *, char **, int));
int min, max, flags;
};
enum {
ZFTP_CONN = 0x0001, /* must be connected */
ZFTP_LOGI = 0x0002, /* must be logged in */
ZFTP_TBIN = 0x0004, /* set transfer type image */
ZFTP_TASC = 0x0008, /* set transfer type ASCII */
ZFTP_NLST = 0x0010, /* use NLST rather than LIST */
ZFTP_DELE = 0x0020, /* a delete rather than a make */
ZFTP_SITE = 0x0040, /* a site rather than a quote */
ZFTP_APPE = 0x0080, /* append rather than overwrite */
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_TEST = 0x1000, /* test command, don't test */
ZFTP_SESS = 0x2000 /* session command, don't need status */
};
typedef struct zftpcmd *Zftpcmd;
static struct zftpcmd zftpcmdtab[] = {
{ "open", zftp_open, 0, 4, 0 },
{ "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 },
{ "ls", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_NLST },
{ "type", zftp_type, 0, 1, ZFTP_CONN|ZFTP_LOGI },
{ "ascii", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TASC },
{ "binary", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TBIN },
{ "mode", zftp_mode, 0, 1, ZFTP_CONN|ZFTP_LOGI },
{ "local", zftp_local, 0, -1, ZFTP_HERE },
{ "remote", zftp_local, 1, -1, ZFTP_CONN|ZFTP_LOGI },
{ "get", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV },
{ "getat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV|ZFTP_REST },
{ "put", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI },
{ "putat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_REST },
{ "append", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE },
{ "appendat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE|ZFTP_REST },
{ "delete", zftp_delete, 1, -1, ZFTP_CONN|ZFTP_LOGI },
{ "mkdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI },
{ "rmdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI|ZFTP_DELE },
{ "rename", zftp_rename, 2, 2, ZFTP_CONN|ZFTP_LOGI },
{ "quote", zftp_quote, 1, -1, ZFTP_CONN },
{ "site", zftp_quote, 1, -1, ZFTP_CONN|ZFTP_SITE },
{ "close", zftp_close, 0, 0, ZFTP_CONN },
{ "quit", zftp_close, 0, 0, ZFTP_CONN },
{ "session", zftp_session, 0, 1, ZFTP_SESS },
{ "rmsession", zftp_rmsession, 0, 1, ZFTP_SESS },
{ 0, 0, 0, 0, 0 }
};
static struct builtin bintab[] = {
BUILTIN("zftp", 0, bin_zftp, 1, -1, 0, NULL, NULL),
};
/*
* these are the non-special params to unset when a connection
* closes. any special params are handled, well, specially.
* currently there aren't any, which is the way I like it.
*/
static char *zfparams[] = {
"ZFTP_HOST", "ZFTP_PORT", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER",
"ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", NULL
};
/* flags for zfsetparam */
enum {
ZFPM_READONLY = 0x01, /* make parameter readonly */
ZFPM_IFUNSET = 0x02, /* only set if not already set */
ZFPM_INTEGER = 0x04 /* passed pointer to off_t */
};
/* Number of connections actually open */
static int zfnopen;
/*
* zcfinish = 0 keep going
* 1 line finished, alles klar
* 2 EOF
*/
static int zcfinish;
/* zfclosing is set if zftp_close() is active */
static int zfclosing;
/*
* 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.
*/
static char *lastmsg, lastcodestr[4];
static int lastcode;
/* remote system has size, mdtm commands */
enum {
ZFCP_UNKN = 0, /* dunno if it works on this server */
ZFCP_YUPP = 1, /* it does */
ZFCP_NOPE = 2 /* it doesn't */
};
/*
* We keep an fd open for communication between the main shell
* and forked off bits and pieces. This allows us to know
* if something happened in a subshell: mode changed, type changed,
* connection was closed. If something too substantial happened
* in a subshell --- connection opened, ZFTP_USER and ZFTP_PWD changed
* --- we don't try to track it because it's too complicated.
*/
enum {
ZFST_ASCI = 0x0000, /* type for next transfer is ASCII */
ZFST_IMAG = 0x0001, /* type for next transfer is image */
ZFST_TMSK = 0x0001, /* mask for type flags */
ZFST_TBIT = 0x0001, /* number of bits in type flags */
ZFST_CASC = 0x0000, /* current type is ASCII - default */
ZFST_CIMA = 0x0002, /* current type is image */
ZFST_STRE = 0x0000, /* stream mode - default */
ZFST_BLOC = 0x0004, /* block mode */
ZFST_MMSK = 0x0004, /* mask for mode flags */
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)
/*
* shift current type flags to match type flags: should be by
* the number of bits in the type flags
*/
#define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK)
#define ZFST_MODE(x) (x & ZFST_MMSK)
/* 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 {
ZFPF_SNDP = 0x01, /* Use send port mode */
ZFPF_PASV = 0x02, /* Try using passive mode */
ZFPF_DUMB = 0x04 /* Don't do clever things with variables */
};
/* The flags as stored internally. */
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 */
FILE *cin; /* control input file */
Tcp_session control; /* 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;
/* 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
* easier). The complication is that SIGALRM may already be handled
* by the user setting TMOUT and possibly setting their own trap --- in
* fact, it's always handled by the shell when it's interactive. It's
* too difficult to use zsh's own signal handler --- either it would
* need rewriting to use a C function as a trap, or we would need a
* hack to make it callback via a hidden builtin from a function --- so
* just install our own, and use settrap() to restore the behaviour
* afterwards if necessary. However, the more that could be done by
* the main shell code, the better I would like it.
*
* Since we don't want to go through the palaver of changing between
* the main zsh signal handler and ours every time we start or stop the
* alarm, we keep the flag zfalarmed set to 1 while zftp is rigged to
* handle alarms. This is tested at the end of bin_zftp(), which is
* the entry point for all functions, and that restores the original
* handler for SIGALRM. To turn off the alarm temporarily in the zftp
* code we then just call alarm(0).
*
* If we could rely on having select() or some replacement, we would
* only need the alarm during zftp_open().
*/
/* flags for alarm set, alarm gone off */
static int zfalarmed, zfdrrrring;
/* remember old alarm status */
static time_t oaltime;
static unsigned int oalremain;
/*
* Where to jump to when the alarm goes off. This is much
* easier than fiddling with error flags at every turn.
* Since we don't expect too many alarm's, the simple setjmp()
* mechanism should be good enough.
*
* gcc -O gives apparently spurious `may be clobbered by longjmp' warnings.
*/
static jmp_buf zfalrmbuf;
/* The signal handler itself */
/**/
static void
zfhandler(int sig)
{
if (sig == SIGALRM) {
zfdrrrring = 1;
#ifdef ETIMEDOUT /* just in case */
errno = ETIMEDOUT;
#else
errno = EIO;
#endif
longjmp(zfalrmbuf, 1);
}
DPUTS(1, "zfhandler caught incorrect signal");
}
/* Set up for an alarm call */
/**/
static void
zfalarm(int tmout)
{
zfdrrrring = 0;
/*
* We need to do this even if tmout is zero, since there may
* be a non-zero timeout set in the main shell which we don't
* want to go off. This could be argued the other way, since
* if we don't get that it's probably harmless. But this looks safer.
*/
if (zfalarmed) {
alarm(tmout);
return;
}
signal(SIGALRM, zfhandler);
oalremain = alarm(tmout);
if (oalremain)
oaltime = time(NULL);
/*
* We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the
* shell's handler doesn't get the signal, they don't matter.
*/
zfalarmed = 1;
}
/* Set up for a broken pipe */
/**/
static void
zfpipe(void)
{
/* Just ignore SIGPIPE and rely on getting EPIPE from the write. */
signal(SIGPIPE, SIG_IGN);
}
/* Unset the alarm, see above */
/**/
static void
zfunalarm(void)
{
if (oalremain) {
/*
* The alarm was previously set, so set it back, adjusting
* for the time used. Mostly the alarm was set to zero
* beforehand, so it would probably be best to reinstall
* the proper signal handler before resetting the alarm.
*
* I love the way alarm() uses unsigned int while time_t
* is probably something completely different.
*/
unsigned int tdiff = time(NULL) - oaltime;
alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
} else
alarm(0);
if (sigtrapped[SIGALRM] || interact) {
if (siglists[SIGALRM] || !sigtrapped[SIGALRM] ||
(sigtrapped[SIGALRM] & ZSIG_FUNC))
install_handler(SIGALRM);
else
signal_ignore(SIGALRM);
} else
signal_default(SIGALRM);
zfalarmed = 0;
}
/* Restore SIGPIPE handling to its usual status */
/**/
static void
zfunpipe(void)
{
if (sigtrapped[SIGPIPE]) {
if (siglists[SIGPIPE] || (sigtrapped[SIGPIPE] & ZSIG_FUNC))
install_handler(SIGPIPE);
else
signal_ignore(SIGPIPE);
} else
signal_default(SIGPIPE);
}
/*
* Same as movefd(), but don't mark the fd in the zsh tables,
* because we only want it closed by zftp. However, we still
* need to shift the fd's out of the way of the user-visible 0-9.
*/
/**/
static int
zfmovefd(int fd)
{
if (fd != -1 && fd < 10) {
#ifdef F_DUPFD
int fe = fcntl(fd, F_DUPFD, 10);
#else
int fe = zfmovefd(dup(fd));
#endif
close(fd);
fd = fe;
}
return 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 off_t (NB not int), don't free.
*/
/**/
static void
zfsetparam(char *name, void *val, int flags)
{
Param pm = NULL;
int type = (flags & ZFPM_INTEGER) ? PM_INTEGER : PM_SCALAR;
if (!(pm = (Param) paramtab->getnode(paramtab, name))
|| (pm->node.flags & PM_UNSET)) {
/*
* just make it readonly when creating, in case user
* *really* knows what they're doing
*/
if ((pm = createparam(name, type)) && (flags & ZFPM_READONLY))
pm->node.flags |= PM_READONLY;
} else if (flags & ZFPM_IFUNSET) {
pm = NULL;
}
if (!pm || PM_TYPE(pm->node.flags) != type) {
/* parameters are funny, you just never know */
if (type == PM_SCALAR)
zsfree((char *)val);
return;
}
if (type == PM_INTEGER)
pm->gsu.i->setfn(pm, *(off_t *)val);
else
pm->gsu.s->setfn(pm, (char *)val);
}
/*
* Unset a ZFTP parameter when the connection is closed.
* We only do this with connection-specific parameters.
*/
/**/
static void
zfunsetparam(char *name)
{
Param pm;
if ((pm = (Param) paramtab->getnode(paramtab, name))) {
pm->node.flags &= ~PM_READONLY;
unsetparam_pm(pm, 0, 1);
}
}
/*
* Join command and arguments to make a proper TELNET command line.
* New line is in permanent storage.
*/
/**/
static char *
zfargstring(char *cmd, char **args)
{
int clen = strlen(cmd) + 3;
char *line, **aptr;
for (aptr = args; *aptr; aptr++)
clen += strlen(*aptr) + 1;
line = zalloc(clen);
strcpy(line, cmd);
for (aptr = args; *aptr; aptr++) {
strcat(line, " ");
strcat(line, *aptr);
}
strcat(line, "\r\n");
return line;
}
/*
* get a line on the control connection according to TELNET rules
* Return status is first digit of FTP reply code
*/
/**/
static int
zfgetline(char *ln, int lnsize, int tmout)
{
int ch, added = 0;
/* current line point */
char *pcur = ln, cmdbuf[3];
zcfinish = 0;
/* leave room for null byte */
lnsize--;
/* in case we return unexpectedly before getting anything */
ln[0] = '\0';
if (setjmp(zfalrmbuf)) {
alarm(0);
zwarnnam("zftp", "timeout getting response");
return 6;
}
zfalarm(tmout);
/*
* We need to be more careful about errors here; we
* should do the stuff with errflag and so forth.
* We should probably holdintr() here, since if we don't
* get the message, the connection is going to be messed up.
* But then we get `frustrated user syndrome'.
*/
for (;;) {
ch = fgetc(zfsess->cin);
switch(ch) {
case EOF:
if (ferror(zfsess->cin) && errno == EINTR) {
clearerr(zfsess->cin);
continue;
}
zcfinish = 2;
break;
case '\r':
/* always precedes something else */
ch = fgetc(zfsess->cin);
if (ch == EOF) {
zcfinish = 2;
break;
}
if (ch == '\n') {
zcfinish = 1;
break;
}
if (ch == '\0') {
ch = '\r';
break;
}
/* not supposed to get here */
ch = '\r';
break;
case '\n':
/* not supposed to get here */
zcfinish = 1;
break;
case IAC:
/*
* oh great, now it's sending TELNET commands. try
* to persuade it not to.
*/
ch = fgetc(zfsess->cin);
switch (ch) {
case WILL:
case WONT:
ch = fgetc(zfsess->cin);
/* whatever it wants to do, stop it. */
cmdbuf[0] = (char)IAC;
cmdbuf[1] = (char)DONT;
cmdbuf[2] = ch;
write_loop(zfsess->control->fd, cmdbuf, 3);
continue;
case DO:
case DONT:
ch = fgetc(zfsess->cin);
/* well, tough, we're not going to. */
cmdbuf[0] = (char)IAC;
cmdbuf[1] = (char)WONT;
cmdbuf[2] = ch;
write_loop(zfsess->control->fd, cmdbuf, 3);
continue;
case EOF:
/* strange machine. */
zcfinish = 2;
break;
default:
break;
}
break;
}
if (zcfinish)
break;
if (added < lnsize) {
*pcur++ = ch;
added++;
}
/* junk it if we don't have room, but go on reading */
}
alarm(0);
*pcur = '\0';
/* if zcfinish == 2, at EOF, return that, else 0 */
return (zcfinish & 2);
}
/*
* Get a whole message from the server. A dash after
* the first line code means keep reading until we get
* a line with the same code followed by a space.
*
* Note that this returns an FTP status code, the first
* digit of the reply. There is also a pseudocode, 6, which
* means `there's no point trying anything, just yet'.
* We return it either if the connection is closed, or if
* we got a 530 (user not logged in), in which case whatever
* you're trying to do isn't going to work.
*/
/**/
static int
zfgetmsg(void)
{
char line[256], *ptr, *verbose;
int stopit, printing = 0, tmout;
if (!zfsess->control)
return 6;
zsfree(lastmsg);
lastmsg = NULL;
tmout = getiparam("ZFTP_TMOUT");
zfgetline(line, 256, tmout);
ptr = line;
if (zfdrrrring || !idigit(*ptr) || !idigit(ptr[1]) || !idigit(ptr[2])) {
/* timeout, or not talking FTP. not really interested. */
zcfinish = 2;
if (!zfclosing)
zfclose(0);
lastmsg = ztrdup("");
strcpy(lastcodestr, "000");
zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY);
return 6;
}
strncpy(lastcodestr, ptr, 3);
ptr += 3;
lastcodestr[3] = '\0';
lastcode = atoi(lastcodestr);
zfsetparam("ZFTP_CODE", ztrdup(lastcodestr), ZFPM_READONLY);
stopit = (*ptr++ != '-');
queue_signals();
if (!(verbose = getsparam_u("ZFTP_VERBOSE")))
verbose = "";
if (strchr(verbose, lastcodestr[0])) {
/* print the whole thing verbatim */
printing = 1;
fputs(line, stderr);
} else if (strchr(verbose, '0') && !stopit) {
/* print multiline parts with the code stripped */
printing = 2;
fputs(ptr, stderr);
}
unqueue_signals();
if (printing)
fputc('\n', stderr);
while (zcfinish != 2 && !stopit) {
zfgetline(line, 256, tmout);
ptr = line;
if (zfdrrrring) {
line[0] = '\0';
break;
}
if (!strncmp(lastcodestr, line, 3)) {
if (line[3] == ' ') {
stopit = 1;
ptr += 4;
} else if (line[3] == '-')
ptr += 4;
} else if (!strncmp(" ", line, 4))
ptr += 4;
if (printing == 2) {
if (!stopit) {
fputs(ptr, stderr);
fputc('\n', stderr);
}
} else if (printing) {
fputs(line, stderr);
fputc('\n', stderr);
}
}
if (printing)
fflush(stderr);
/* The internal message is just the text. */
lastmsg = ztrdup(ptr);
/*
* The parameter is the whole thing, including the code.
*/
zfsetparam("ZFTP_REPLY", ztrdup(line), ZFPM_READONLY);
/*
* close the connection here if zcfinish == 2, i.e. EOF,
* or if we get a 421 (service not available, closing connection),
* but don't do it if it's expected (zfclosing set).
*/
if ((zcfinish == 2 || lastcode == 421) && !zfclosing) {
zcfinish = 2; /* don't need to tell server */
zfclose(0);
/* unexpected, so tell user */
zwarnnam("zftp", "remote server has closed connection");
return 6;
}
if (lastcode == 530) {
/* user not logged in */
return 6;
}
/*
* May as well handle this here, though it's pretty uncommon.
* A 120 is something like "service ready in nnn minutes".
* It means we just hang around waiting for another reply.
*/
if (lastcode == 120) {
zwarnnam("zftp", "delay expected, waiting: %s", lastmsg);
return zfgetmsg();
}
/* first digit of code determines success, failure, not in the mood... */
return lastcodestr[0] - '0';
}
/*
* Send a command and get the reply.
* The command is expected to have the \r\n already tacked on.
* Returns the status code for the reply.
*/
/**/
static int
zfsendcmd(char *cmd)
{
/*
* We use the fd directly; there's no point even using
* stdio with line buffering, since we always send the
* complete line in one string anyway.
*/
int ret, tmout;
if (!zfsess->control)
return 6;
tmout = getiparam("ZFTP_TMOUT");
if (setjmp(zfalrmbuf)) {
alarm(0);
zwarnnam("zftp", "timeout sending message");
return 6;
}
zfalarm(tmout);
ret = write(zfsess->control->fd, cmd, strlen(cmd));
alarm(0);
if (ret <= 0) {
zwarnnam("zftp send", "failure sending control message: %e", errno);
return 6;
}
return zfgetmsg();
}
/* Set up a data connection, return 1 for failure, 0 for success */
/**/
static int
zfopendata(char *name, union tcp_sockaddr *zdsockp, int *is_passivep)
{
if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) {
zwarnnam(name, "Must set preference S or P to transfer data");
return 1;
}
zfsess->dfd = socket(zfsess->control->peer.a.sa_family, SOCK_STREAM, 0);
if (zfsess->dfd < 0) {
zwarnnam(name, "can't get data socket: %e", errno);
return 1;
}
if (!(zfstatusp[zfsessno] & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) {
char *psv_cmd;
int err, salen;
#ifdef SUPPORT_IPV6
if(zfsess->control->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.
*/
zfstatusp[zfsessno] |= ZFST_NOPS;
zfclosedata();
return zfopendata(name, zdsockp, is_passivep);
}
zdsockp->a.sa_family = zfsess->control->peer.a.sa_family;
#ifdef SUPPORT_IPV6
if(zfsess->control->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);
zfclosedata();
return 1;
}
delim = ptr[1];
if(delim < 33 || delim > 126 || ptr[2] != delim || ptr[3] != delim)
goto bad_epsv;
ptr += 4;
end = strchr(ptr, delim);
if(!end || end[1] != ')')
goto bad_epsv;
while(ptr != end && *ptr == '0')
ptr++;
if(ptr == end || (end-ptr) > 5 || !idigit(*ptr))
goto bad_epsv;
memcpy(portbuf, ptr, (end-ptr));
portbuf[end-ptr] = 0;
portnum = strtoul(portbuf, &pbp, 10);
if(*pbp || portnum > 65535UL)
goto bad_epsv;
*zdsockp = zfsess->control->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];
/*
* 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 (idigit(*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);
zfclosedata();
return 1;
}
for (i = 0; i < 4; i++)
iaddr[i] = (unsigned char) nums[i];
iport[0] = (unsigned char) nums[4];
iport[1] = (unsigned char) 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(zfsess->dfd, (struct sockaddr *)zdsockp, salen);
} while (err && errno == EINTR && !errflag);
if (err) {
zwarnnam(name, "connect failed: %e", errno);
zfclosedata();
return 1;
}
*is_passivep = 1;
} else {
#ifdef SUPPORT_IPV6
char portcmd[8+INET6_ADDRSTRLEN+9];
#else
char portcmd[40];
#endif
ZSOCKLEN_T len;
int ret;
if (!(zfprefs & ZFPF_SNDP)) {
zwarnnam(name, "only sendport mode available for data");
return 1;
}
*zdsockp = zfsess->control->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(zfsess->dfd, (struct sockaddr *)zdsockp, len) < 0)
ret = 1;
else if (getsockname(zfsess->dfd, (struct sockaddr *)zdsockp,
&len) < 0)
ret = 2;
else if (listen(zfsess->dfd, 1) < 0)
ret = 3;
else
ret = 0;
if (ret) {
zwarnnam(name, "failure on data socket: %s: %e",
ret == 3 ? "listen" : ret == 2 ? "getsockname" : "bind",
errno);
zfclosedata();
return 1;
}
#ifdef SUPPORT_IPV6
if(zdsockp->a.sa_family == AF_INET6) {
/* see RFC 2428 for explanation */
strcpy(portcmd, "EPRT |2|");
zsh_inet_ntop(AF_INET6, &zdsockp->in6.sin6_addr,
portcmd+8, INET6_ADDRSTRLEN);
sprintf(strchr(portcmd, 0), "|%u|\r\n",
(unsigned)ntohs(zdsockp->in6.sin6_port));
} 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");
zfclosedata();
return 1;
}
*is_passivep = 0;
}
return 0;
}
/* Close the data connection. */
/**/
static void
zfclosedata(void)
{
if (zfsess->dfd == -1)
return;
close(zfsess->dfd);
zfsess->dfd = -1;
}
/*
* Set up a data connection and use cmd to initiate a transfer.
* 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.
* getsize is non-zero if we want to try to find the number
* of bytes in the reply to a RETR command.
*
* Return 0 on success, 1 on failure.
*/
/**/
static int
zfgetdata(char *name, char *rest, char *cmd, int getsize)
{
ZSOCKLEN_T len;
int newfd, is_passive;
union tcp_sockaddr zdsock;
if (zfopendata(name, &zdsock, &is_passive))
return 1;
/*
* Set position in remote file for get/put.
* According to RFC959, the restart command needs something
* called a marker which has previously been put into the data.
* Luckily for the real world, UNIX machines just interpret this
* as an offset into the byte stream.
*
* This has to be sent immediately before the data transfer, i.e.
* after all mucking around with types and sizes and so on.
*/
if (rest && zfsendcmd(rest) > 3) {
zfclosedata();
return 1;
}
if (zfsendcmd(cmd) > 2) {
zfclosedata();
return 1;
}
if (getsize || (!(zfstatusp[zfsessno] & ZFST_TRSZ) &&
!strncmp(cmd, "RETR", 4))) {
/*
* See if we got something like:
* Opening data connection for nortypix.gif (1234567 bytes).
* On the first RETR, always see if this works, Then we
* can avoid sending a special SIZE command beforehand.
*/
char *ptr = strstr(lastmsg, "bytes");
zfstatusp[zfsessno] |= ZFST_NOSZ|ZFST_TRSZ;
if (ptr) {
while (ptr > lastmsg && !idigit(*ptr))
ptr--;
while (ptr > lastmsg && idigit(ptr[-1]))
ptr--;
if (idigit(*ptr)) {
zfstatusp[zfsessno] &= ~ZFST_NOSZ;
if (getsize) {
off_t sz = zstrtol(ptr, NULL, 10);
zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER);
}
}
}
}
if (!is_passive) {
/*
* 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.
*/
/* accept the connection */
len = sizeof(zdsock);
newfd = zfmovefd(accept(zfsess->dfd, (struct sockaddr *)&zdsock,
&len));
if (newfd < 0)
zwarnnam(name, "unable to accept data: %e", errno);
zfclosedata();
if (newfd < 0)
return 1;
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);
}
/* more options, just to look professional */
#ifdef SO_LINGER
/*
* Since data can take arbitrary amounts of time to arrive,
* the socket can be made to hang around until it doesn't think
* anything is arriving.
*
* In BSD 4.3, you could only linger for infinity. Don't
* know if this has changed.
*/
{
struct linger li;
li.l_onoff = 1;
li.l_linger = 120;
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(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. */
fcntl(zfsess->dfd, F_SETFD, FD_CLOEXEC);
#endif
return 0;
}
/*
* Find out about a local or remote file and pass back the information.
*
* We could jigger this to use ls like ncftp does as a backup.
* But if the server is non-standard enough not to have SIZE and MDTM,
* there's a very good chance ls -l isn't going to do great things.
*
* if fd is >= 0, it is used for an fstat when remote is zero:
* this is because on a put we are taking input from fd 0.
*/
/**/
static int
zfstats(char *fnam, int remote, off_t *retsize, char **retmdtm, int fd)
{
off_t sz = -1;
char *mt = NULL;
int ret;
if (retsize)
*retsize = -1;
if (retmdtm)
*retmdtm = NULL;
if (remote) {
char *cmd;
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(zfstatusp[zfsessno]));
if (retsize) {
cmd = tricat("SIZE ", fnam, "\r\n");
ret = zfsendcmd(cmd);
zsfree(cmd);
if (ret == 6)
return 1;
else if (lastcode < 300) {
sz = zstrtol(lastmsg, 0, 10);
zfsess->has_size = ZFCP_YUPP;
} else if (lastcode >= 500 && lastcode <= 504) {
zfsess->has_size = ZFCP_NOPE;
return 2;
} else if (lastcode == 550)
return 1;
/* if we got a 550 from SIZE, the file doesn't exist */
}
if (retmdtm) {
cmd = tricat("MDTM ", fnam, "\r\n");
ret = zfsendcmd(cmd);
zsfree(cmd);
if (ret == 6)
return 1;
else if (lastcode < 300) {
mt = ztrdup(lastmsg);
zfsess->has_mdtm = ZFCP_YUPP;
} else if (lastcode >= 500 && lastcode <= 504) {
zfsess->has_mdtm = ZFCP_NOPE;
return 2;
} else if (lastcode == 550)
return 1;
}
} else {
/* File is over here */
struct stat statbuf;
struct tm *tm;
char tmbuf[20];
if ((fd == -1 ? stat(fnam, &statbuf) : fstat(fd, &statbuf)) < 0)
return 1;
/* make sure it's off_t, since this has to be a pointer */
sz = statbuf.st_size;
if (retmdtm) {
/* use gmtime() rather than localtime() for consistency */
tm = gmtime(&statbuf.st_mtime);
/* FTP format for date is YYYYMMDDHHMMSS */
ztrftime(tmbuf, sizeof(tmbuf), "%Y%m%d%H%M%S", tm, 0L);
mt = ztrdup(tmbuf);
}
}
if (retsize)
*retsize = sz;
if (retmdtm)
*retmdtm = mt;
return 0;
}
/* Set parameters to say what's coming */
/**/
static void
zfstarttrans(char *nam, int recv, off_t sz)
{
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
* means we don't know and shouldn't tell the user porkies.
*/
if (sz > 0)
zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER);
zfsetparam("ZFTP_FILE", ztrdup(nam), ZFPM_READONLY);
zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "G" : "P"), ZFPM_READONLY);
zfsetparam("ZFTP_COUNT", &cnt, ZFPM_READONLY|ZFPM_INTEGER);
}
/* Tidy up afterwards */
/**/
static void
zfendtrans(void)
{
zfunsetparam("ZFTP_SIZE");
zfunsetparam("ZFTP_FILE");
zfunsetparam("ZFTP_TRANSFER");
zfunsetparam("ZFTP_COUNT");
}
/* Read with timeout if recv is set. */
/**/
static int
zfread(int fd, char *bf, off_t sz, int tmout)
{
int ret;
if (!tmout)
return read(fd, bf, sz);
if (setjmp(zfalrmbuf)) {
alarm(0);
zwarnnam("zftp", "timeout on network read");
return -1;
}
zfalarm(tmout);
ret = read(fd, bf, sz);
/* we don't bother turning off the whole alarm mechanism here */
alarm(0);
return ret;
}
/* Write with timeout if recv is not set. */
/**/
static int
zfwrite(int fd, char *bf, off_t sz, int tmout)
{
int ret;
if (!tmout)
return write(fd, bf, sz);
if (setjmp(zfalrmbuf)) {
alarm(0);
zwarnnam("zftp", "timeout on network write");
return -1;
}
zfalarm(tmout);
ret = write(fd, bf, sz);
/* we don't bother turning off the whole alarm mechanism here */
alarm(0);
return ret;
}
static int zfread_eof;
/* Version of zfread when we need to read in block mode. */
/**/
static int
zfread_block(int fd, char *bf, off_t sz, int tmout)
{
int n;
struct zfheader hdr;
off_t blksz, cnt;
char *bfptr;
do {
/* we need the header */
do {
n = zfread(fd, (char *)&hdr, sizeof(hdr), tmout);
} while (n < 0 && errno == EINTR);
if (n != 3 && !zfdrrrring) {
zwarnnam("zftp", "failure reading FTP block header");
return n;
}
/* size is stored in network byte order */
if (hdr.flags & ZFHD_EOFB)
zfread_eof = 1;
blksz = (hdr.bytes[0] << 8) | hdr.bytes[1];
if (blksz > sz) {
/*
* See comments in file headers
*/
zwarnnam("zftp", "block too large to handle");
errno = EIO;
return -1;
}
bfptr = bf;
cnt = blksz;
while (cnt) {
n = zfread(fd, bfptr, cnt, tmout);
if (n > 0) {
bfptr += n;
cnt -= n;
} else if (n < 0 && (errflag || zfdrrrring || errno != EINTR))
return n;
else
break;
}
if (cnt) {
zwarnnam("zftp", "short data block");
errno = EIO;
return -1;
}
} while ((hdr.flags & ZFHD_MARK) && !zfread_eof);
return (hdr.flags & ZFHD_MARK) ? 0 : blksz;
}
/* Version of zfwrite when we need to write in block mode. */
/**/
static int
zfwrite_block(int fd, char *bf, off_t sz, int tmout)
{
int n;
struct zfheader hdr;
off_t cnt;
char *bfptr;
/* we need the header */
do {
hdr.bytes[0] = (sz & 0xff00) >> 8;
hdr.bytes[1] = sz & 0xff;
hdr.flags = sz ? 0 : ZFHD_EOFB;
n = zfwrite(fd, (char *)&hdr, sizeof(hdr), tmout);
} while (n < 0 && errno == EINTR);
if (n != 3 && !zfdrrrring) {
zwarnnam("zftp", "failure writing FTP block header");
return n;
}
bfptr = bf;
cnt = sz;
while (cnt) {
n = zfwrite(fd, bfptr, cnt, tmout);
if (n > 0) {
bfptr += n;
cnt -= n;
} else if (n < 0 && (errflag || zfdrrrring || errno != EINTR))
return n;
}
return sz;
}
/*
* Move stuff from fdin to fdout, tidying up the data connection
* when finished. The data connection could be either input or output:
* recv is 1 for receiving a file, 0 for sending.
*
* progress is 1 to use a progress meter.
* startat says how far in we're starting with a REST command.
*
* Since we're doing some buffering here anyway, we don't bother
* with a stdio layer.
*/
/**/
static int
zfsenddata(char *name, int recv, int progress, off_t startat)
{
#define ZF_BUFSIZE 32768
#define ZF_ASCSIZE (ZF_BUFSIZE/2)
/* ret = 2 signals the local read/write failed, so send abort */
int n, ret = 0, gotack = 0, fdin, fdout, fromasc = 0, toasc = 0;
int rtmout = 0, wtmout = 0;
char lsbuf[ZF_BUFSIZE], *ascbuf = NULL, *optr;
off_t sofar = 0, last_sofar = 0;
readwrite_t read_ptr = zfread, write_ptr = zfwrite;
Shfunc shfunc;
if (progress && (shfunc = getshfunc("zftp_progress"))) {
/*
* 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.
*/
int osc = sfcontext;
sfcontext = SFC_HOOK;
doshfunc(shfunc, NULL, 1);
sfcontext = osc;
/* Now add in the bit of the file we've got/sent already */
sofar = last_sofar = startat;
}
if (recv) {
fdin = zfsess->dfd;
fdout = 1;
rtmout = getiparam("ZFTP_TMOUT");
if (ZFST_CTYP(zfstatusp[zfsessno]) == ZFST_ASCI)
fromasc = 1;
if (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC)
read_ptr = zfread_block;
} else {
fdin = 0;
fdout = zfsess->dfd;
wtmout = getiparam("ZFTP_TMOUT");
if (ZFST_CTYP(zfstatusp[zfsessno]) == ZFST_ASCI)
toasc = 1;
if (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC)
write_ptr = zfwrite_block;
}
if (toasc)
ascbuf = zalloc(ZF_ASCSIZE);
zfpipe();
zfread_eof = 0;
while (!ret && !zfread_eof) {
n = (toasc) ? read_ptr(fdin, ascbuf, ZF_ASCSIZE, rtmout)
: read_ptr(fdin, lsbuf, ZF_BUFSIZE, rtmout);
if (n > 0) {
char *iptr;
if (toasc) {
/* \n -> \r\n it shouldn't happen to a dog. */
char *iptr = ascbuf, *optr = lsbuf;
int cnt = n;
while (cnt--) {
if (*iptr == '\n') {
*optr++ = '\r';
n++;
}
*optr++ = *iptr++;
}
}
if (fromasc && (iptr = memchr(lsbuf, '\r', n))) {
/* \r\n -> \n */
char *optr = iptr;
int cnt = n - (iptr - lsbuf);
while (cnt--) {
if (*iptr != '\r' || iptr[1] != '\n') {
*optr++ = *iptr;
} else
n--;
iptr++;
}
}
optr = lsbuf;
sofar += n;
for (;;) {
/*
* in principle, write can be interrupted after
* safely writing some bytes, and will return the
* number already written, which may not be the
* complete buffer. so make this robust. they call me
* `robustness stephenson'. in my dreams.
*/
int newn = write_ptr(fdout, optr, n, wtmout);
if (newn == n)
break;
if (newn < 0) {
/*
* The somewhat contorted test here (for write)
* and below (for read) means:
* real error if
* - errno is set and it's not just an interrupt, or
* - errflag is set, probably due to CTRL-c, or
* - zfdrrrring is set, due to the alarm going off.
* print an error message if
* - not a timeout, since that was reported, and
* either
* - a non-interactive shell, where we don't
* know what happened otherwise
* - or both of
* - not errflag, i.e. CTRL-c or what have you,
* since the user probably knows about that, and
* - not a SIGPIPE, since usually people are
* silent about those when going to pagers
* (if you quit less or more in the middle
* and see an error message you think `I
* shouldn't have done that').
*
* If we didn't print an error message here,
* and were going to do an abort (ret == 2)
* because the error happened on the local end
* of the connection, set ret to 3 and don't print
* the 'aborting...' either.
*
* There must be a better way of doing this.
*/
if (errno != EINTR || errflag || zfdrrrring) {
if (!zfdrrrring &&
(!interact || (!errflag && errno != EPIPE))) {
ret = recv ? 2 : 1;
zwarnnam(name, "write failed: %e", errno);
} else
ret = recv ? 3 : 1;
break;
}
continue;
}
optr += newn;
n -= newn;
}
} else if (n < 0) {
if (errno != EINTR || errflag || zfdrrrring) {
if (!zfdrrrring &&
(!interact || (!errflag && errno != EPIPE))) {
ret = recv ? 1 : 2;
zwarnnam(name, "read failed: %e", errno);
} else
ret = recv ? 1 : 3;
break;
}
} else
break;
if (!ret && sofar != last_sofar && progress &&
(shfunc = getshfunc("zftp_progress"))) {
int osc = sfcontext;
zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER);
sfcontext = SFC_HOOK;
doshfunc(shfunc, NULL, 1);
sfcontext = osc;
last_sofar = sofar;
}
}
zfunpipe();
/*
* At this point any timeout was on the data connection,
* so we don't need to force the control connection to close.
*/
zfdrrrring = 0;
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);
}
if (errflag || ret > 1) {
/*
* some error occurred, maybe a keyboard interrupt, or
* a local file/pipe handling problem.
* send an abort.
*
* safest to block all signals here? can get frustrating if
* we're waiting for an abort. don't I know. let's start
* off just by blocking SIGINT's.
*
* maybe the timeout for the abort should be shorter than
* for normal commands. and what about aborting after
* we had a timeout on the data connection, is that
* really a good idea?
*/
/* RFC 959 says this is what to send */
unsigned char msg[4] = { IAC, IP, IAC, SYNCH };
if (ret == 2)
zwarnnam(name, "aborting data transfer...");
holdintr();
/* the following is black magic, as far as I'm concerned. */
/* what are we going to do if it fails? not a lot, actually. */
send(zfsess->control->fd, (char *)msg, 3, 0);
send(zfsess->control->fd, (char *)msg+3, 1, MSG_OOB);
zfsendcmd("ABOR\r\n");
if (lastcode == 226) {
/*
* 226 is supposed to mean the transfer got sent OK after
* all, and the abort got ignored, at least that's what
* rfc959 seems to be saying. but in fact what can happen
* is the transfer finishes (at least as far as the
* server's concerned) and it's response is waiting, then
* the abort gets sent, and we need to mop up a response to
* that. so actually in most cases we get two replies
* anyway. we could test if we had select() on all hosts.
*/
/* gotack = 1; */
/*
* we'd better leave errflag, since we don't know
* where it came from. maybe the user wants to abort
* a whole script or function.
*/
} else
ret = 1;
noholdintr();
}
if (toasc)
zfree(ascbuf, ZF_ASCSIZE);
zfclosedata();
if (!gotack && zfgetmsg() > 2)
ret = 1;
return ret != 0;
}
/* Open a new control connection, i.e. start a new FTP session */
/**/
static int
zftp_open(char *name, char **args, int flags)
{
struct protoent *zprotop;
struct servent *zservp;
struct hostent *zhostp;
char **addrp, *fname, *tmpptr, *portnam = "ftp";
char *hostnam, *hostsuffix;
int err, tmout, port = -1;
ZSOCKLEN_T len;
int herrno, af, hlen;
if (!*args) {
if (zfsess->userparams)
args = zfsess->userparams;
else {
zwarnnam(name, "no host specified");
return 1;
}
}
/*
* Close the existing connection if any.
* Probably this is the safest thing to do. It's possible
* a `QUIT' will hang, though.
*/
if (zfsess->control)
zfclose(0);
hostnam = dupstring(args[0]);
/*
* Check for IPv6 address in square brackets (RFC2732).
* We are more lenient and allow any form for the host here.
*/
if (hostnam[0] == '[') {
hostnam++;
hostsuffix = strchr(hostnam, ']');
if (!hostsuffix || (hostsuffix[1] && hostsuffix[1] != ':')) {
zwarnnam(name, "Invalid host format: %s", hostnam);
return 1;
}
*hostsuffix++ = '\0';
}
else
hostsuffix = hostnam;
if ((tmpptr = strchr(hostsuffix, ':'))) {
char *endptr;
*tmpptr++ = '\0';
port = (int)zstrtol(tmpptr, &endptr, 10);
/*
* If the port is not numeric, look it up by name below.
*/
if (*endptr) {
portnam = tmpptr;
port = -1;
}
#if defined(HAVE_NTOHS) && defined(HAVE_HTONS)
else {
port = (int)htons((unsigned short)port);
}
#endif
}
/* this is going to give 0. why bother? */
zprotop = getprotobyname("tcp");
if (!zprotop) {
zwarnnam(name, "Can't find protocol TCP (is your network functional)?");
return 1;
}
if (port < 0)
zservp = getservbyname(portnam, "tcp");
else
zservp = getservbyport(port, "tcp");
if (!zprotop || !zservp) {
zwarnnam(name, "Can't find port for service `%s'", portnam);
return 1;
}
/* don't try talking to server yet */
zcfinish = 2;
/*
* This sets an alarm for the whole process, getting the host name
* as well as connecting. Arguably you could time them out separately.
*/
tmout = getiparam("ZFTP_TMOUT");
if (setjmp(zfalrmbuf)) {
char *hname;
alarm(0);
queue_signals();
if ((hname = getsparam_u("ZFTP_HOST")) && *hname)
zwarnnam(name, "timeout connecting to %s", hname);
else
zwarnnam(name, "timeout on host name lookup");
unqueue_signals();
zfclose(0);
return 1;
}
zfalarm(tmout);
#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
{
off_t tcp_port;
zhostp = zsh_getipnodebyname(hostnam, af, 0, &herrno);
if (!zhostp || errflag) {
/* should use herror() here if available, but maybe
* needs configure test. on AIX it's present but not
* in headers.
*
* on the other hand, herror() is obsolete
*/
FAILED();
zwarnnam(name, "host not found: %s", hostnam);
alarm(0);
return 1;
}
zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY);
/* careful with pointer types */
#if defined(HAVE_NTOHS) && defined(HAVE_HTONS)
tcp_port = (off_t)ntohs((unsigned short)zservp->s_port);
#else
tcp_port = (off_t)zservp->s_port;
#endif
zfsetparam("ZFTP_PORT", &tcp_port, ZFPM_READONLY|ZFPM_INTEGER);
#ifdef SUPPORT_IPV6
if(af == AF_INET6) {
hlen = 16;
} else
#endif /* SUPPORT_IPV6 */
{
hlen = 4;
}
zfsess->control = tcp_socket(af, SOCK_STREAM, 0, ZTCP_ZFTP);
if (!(zfsess->control) || (zfsess->control->fd < 0)) {
if (zfsess->control) {
tcp_close(zfsess->control);
zfsess->control = NULL;
}
freehostent(zhostp);
zfunsetparam("ZFTP_HOST");
zfunsetparam("ZFTP_PORT");
FAILED();
zwarnnam(name, "socket failed: %e", 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.
*/
err = 1;
/* try all possible IP's */
for (addrp = zhostp->h_addr_list; err && *addrp; addrp++) {
if(hlen != zhostp->h_length)
zwarnnam(name, "address length mismatch");
do {
err = tcp_connect(zfsess->control, *addrp, zhostp, zservp->s_port);
} while (err && errno == EINTR && !errflag);
/* you can check whether it's worth retrying here */
}
if (err) {
freehostent(zhostp);
zfclose(0);
FAILED();
zwarnnam(name, "connect failed: %e", errno);
alarm(0);
return 1;
}
SUCCEEDED();
}
alarm(0);
{
#ifdef SUPPORT_IPV6
char pbuf[INET6_ADDRSTRLEN];
#else
char pbuf[INET_ADDRSTRLEN];
#endif
addrp--;
zsh_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;
/*
* Move the fd out of the user-visible range. We need to do
* this after the connect() on some systems.
*/
zfsess->control->fd = zfmovefd(zfsess->control->fd);
#if defined(F_SETFD) && defined(FD_CLOEXEC)
/* If the shell execs a program, we don't want this fd left open. */
fcntl(zfsess->control->fd, F_SETFD, FD_CLOEXEC);
#endif
len = sizeof(zfsess->control->sock);
if (getsockname(zfsess->control->fd, (struct sockaddr *)&zfsess->control->sock, &len) < 0) {
zwarnnam(name, "getsockname failed: %e", errno);
zfclose(0);
return 1;
}
/* nice to get some options right, ignore if they don't work */
#ifdef SO_OOBINLINE
/*
* this says we want messages in line. maybe sophisticated people
* do clever things with SIGURG.
*/
len = 1;
setsockopt(zfsess->control->fd, SOL_SOCKET, SO_OOBINLINE,
(char *)&len, sizeof(len));
#endif
#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
/* for control connection we want low delay. please don't laugh. */
len = IPTOS_LOWDELAY;
setsockopt(zfsess->control->fd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len));
#endif
/*
* We use stdio with line buffering for convenience on input.
* On output, we can just dump a complete message to the fd via write().
*/
zfsess->cin = fdopen(zfsess->control->fd, "r");
if (!zfsess->cin) {
zwarnnam(name, "file handling error");
zfclose(0);
return 1;
}
#ifdef _IONBF
setvbuf(zfsess->cin, NULL, _IONBF, 0);
#else
setlinebuf(zfsess->cin);
#endif
/*
* now see what the remote server has to say about that.
*/
if (zfgetmsg() >= 4) {
zfclose(0);
return 1;
}
zfsess->has_size = zfsess->has_mdtm = ZFCP_UNKN;
zfsess->dfd = -1;
/* initial status: open, ASCII data, stream mode 'n' stuff */
zfstatusp[zfsessno] = 0;
/*
* 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) {
zfstatfd = gettempfile(NULL, 1, &fname);
DPUTS(zfstatfd == -1, "zfstatfd not created");
#if defined(F_SETFD) && defined(FD_CLOEXEC)
/* If the shell execs a program, we don't want this fd left open. */
fcntl(zfstatfd, F_SETFD, FD_CLOEXEC);
#endif
unlink(fname);
}
if (zfsess->control->fd == -1) {
/* final paranoid check */
tcp_close(zfsess->control);
zfsess->control = NULL;
zfnopen--;
} else {
zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY);
/* if remaining arguments, use them to log in. */
if (*++args)
return zftp_login(name, args, flags);
}
/* if something wayward happened, connection was already closed */
return !zfsess->control;
}
/*
* Read a parameter string, with a prompt if reading from stdin.
* The returned string is on the heap.
* If noecho, turn off ECHO mode while reading.
*/
/**/
static char *
zfgetinfo(char *prompt, int noecho)
{
int resettty = 0;
/* 256 characters should be enough, hardly worth allocating
* a password string byte by byte
*/
char instr[256], *strret;
int len;
/*
* Only print the prompt if getting info from a tty. Of
* course, we don't know if stderr has been redirected, but
* that seems a minor point.
*/
if (isatty(0)) {
if (noecho) {
/* hmmm... all this great big shell and we have to read
* something with no echo by ourselves.
* bin_read() is far to complicated for our needs.
* we could use zread(), but that relies on static
* variables, so someone doesn't want that to happen.
*
* this is modified from setcbreak() in utils.c,
* except I don't see any point in using cbreak mode
*/
struct ttyinfo ti;
ti = shttyinfo;
#ifdef HAS_TIO
ti.tio.c_lflag &= ~ECHO;
#else
ti.sgttyb.sg_flags &= ~ECHO;
#endif
settyinfo(&ti);
resettty = 1;
}
fflush(stdin);
fputs(prompt, stderr);
fflush(stderr);
}
if (fgets(instr, 256, stdin) == NULL)
instr[len = 0] = '\0';
else if (instr[len = strlen(instr)-1] == '\n')
instr[len] = '\0';
strret = dupstring(instr);
if (resettty) {
/* '\n' didn't get echoed */
fputc('\n', stdout);
fflush(stdout);
settyinfo(&shttyinfo);
}
return strret;
}
/*
* set params for an open with no arguments.
* this allows easy re-opens.
*/
/**/
static int
zftp_params(UNUSED(char *name), char **args, UNUSED(int flags))
{
char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " };
char **aptr, **newarr;
int i, j, len;
if (!*args) {
if (zfsess->userparams) {
for (aptr = zfsess->userparams, i = 0; *aptr; aptr++, i++) {
if (i == 2) {
len = strlen(*aptr);
for (j = 0; j < len; j++)
fputc('*', stdout);
fputc('\n', stdout);
} else
fprintf(stdout, "%s\n", *aptr);
}
return 0;
} else
return 1;
}
if (!strcmp(*args, "-")) {
if (zfsess->userparams)
freearray(zfsess->userparams);
zfsess->userparams = 0;
return 0;
}
len = arrlen(args);
newarr = (char **)zshcalloc((len+1)*sizeof(char *));
for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) {
char *str;
if (**aptr == '?')
str = zfgetinfo((*aptr)[1] ? (*aptr+1) : prompts[i], i == 2);
else
str = (**aptr == '\\') ? *aptr+1 : *aptr;
newarr[i] = ztrdup(str);
}
if (errflag) {
/* maybe user CTRL-c'd in the middle somewhere */
for (aptr = newarr; *aptr; aptr++)
zsfree(*aptr);
zfree(newarr, len+1);
return 1;
}
if (zfsess->userparams)
freearray(zfsess->userparams);
zfsess->userparams = newarr;
return 0;
}
/* login a user: often called as part of the open sequence */
/**/
static int
zftp_login(char *name, char **args, UNUSED(int flags))
{
char *ucmd, *passwd = NULL, *acct = NULL;
char *user, tbuf[2] = "X";
int stopit;
if ((zfstatusp[zfsessno] & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4)
return 1;
zfstatusp[zfsessno] &= ~ZFST_LOGI;
if (*args) {
user = *args++;
} else {
user = zfgetinfo("User: ", 0);
}
ucmd = tricat("USER ", user, "\r\n");
stopit = 0;
if (zfsendcmd(ucmd) == 6)
stopit = 2;
while (!stopit && !errflag) {
switch (lastcode) {
case 230: /* user logged in */
case 202: /* command not implemented, don't care */
stopit = 1;
break;
case 331: /* need password */
if (*args)
passwd = *args++;
else
passwd = zfgetinfo("Password: ", 1);
zsfree(ucmd);
ucmd = tricat("PASS ", passwd, "\r\n");
if (zfsendcmd(ucmd) == 6)
stopit = 2;
break;
case 332: /* need account */
case 532:
if (*args)
acct = *args++;
else
acct = zfgetinfo("Account: ", 0);
zsfree(ucmd);
ucmd = tricat("ACCT ", acct, "\r\n");
if (zfsendcmd(ucmd) == 6)
stopit = 2;
break;
case 421: /* service not available, so closed anyway */
case 501: /* syntax error */
case 503: /* bad commands */
case 530: /* not logged in */
case 550: /* random can't-do-that */
default: /* whatever, should flag this as bad karma */
/* need more diagnostics here */
stopit = 2;
break;
}
}
zsfree(ucmd);
if (!zfsess->control)
return 1;
if (stopit == 2 || (lastcode != 230 && lastcode != 202)) {
zwarnnam(name, "login failed");
return 1;
}
if (*args) {
int cnt;
for (cnt = 0; *args; args++)
cnt++;
zwarnnam(name, "warning: %d command arguments not used\n", cnt);
}
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.
*/
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(UNUSED(char *name), UNUSED(char **args), UNUSED(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->control)
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->control->fd;
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->control->fd, &f);
tv.tv_sec = 0;
tv.tv_usec = 0;
if ((ret = select(zfsess->control->fd +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 have no zfsess->control, then we've just been dumped out. */
return zfsess->control ? 0 : 2;
#else
zfwarnnam(name, "not supported on this system.");
return 3;
#endif /* defined(HAVE_POLL) || defined(HAVE_SELECT) */
}
/* do ls or dir on the remote directory */
/**/
static int
zftp_dir(char *name, char **args, int flags)
{
/* maybe should be cleverer about handling arguments */
char *cmd;
int ret;
/*
* RFC959 says this must be ASCII or EBCDIC, not image format.
* I rather suspect on a UNIX server we get away handsomely
* with doing everything, including this, as image.
*/
zfsettype(ZFST_ASCI);
cmd = zfargstring((flags & ZFTP_NLST) ? "NLST" : "LIST", args);
ret = zfgetdata(name, NULL, cmd, 0);
zsfree(cmd);
if (ret)
return 1;
fflush(stdout); /* since we're now using fd 1 */
return zfsenddata(name, 1, 0, 0);
}
/* change the remote directory */
/**/
static int
zftp_cd(UNUSED(char *name), char **args, int flags)
{
/* change directory --- enhance to allow 'zftp cdup' */
int ret;
if ((flags & ZFTP_CDUP) || !strcmp(*args, "..") ||
!strcmp(*args, "../")) {
ret = zfsendcmd("CDUP\r\n");
} else {
char *cmd = tricat("CWD ", *args, "\r\n");
ret = zfsendcmd(cmd);
zsfree(cmd);
}
if (ret > 2)
return 1;
/* sometimes the directory may be in the response. usually not. */
if (zfgetcwd())
return 1;
return 0;
}
/* get the remote directory */
/**/
static int
zfgetcwd(void)
{
char *ptr, *eptr;
int endc;
Shfunc shfunc;
if (zfprefs & ZFPF_DUMB)
return 1;
if (zfsendcmd("PWD\r\n") > 2) {
zfunsetparam("ZFTP_PWD");
return 1;
}
ptr = lastmsg;
while (*ptr == ' ')
ptr++;
if (!*ptr) /* ultra safe */
return 1;
if (*ptr == '"') {
ptr++;
endc = '"';
} else
endc = ' ';
for (eptr = ptr; *eptr && *eptr != endc; eptr++)
;
zfsetparam("ZFTP_PWD", ztrduppfx(ptr, eptr-ptr), ZFPM_READONLY);
/*
* This isn't so necessary if we're going to have a shell function
* front end. By putting it here, and in close when ZFTP_PWD is unset,
* we at least cover the bases.
*/
if ((shfunc = getshfunc("zftp_chpwd"))) {
int osc = sfcontext;
sfcontext = SFC_HOOK;
doshfunc(shfunc, NULL, 1);
sfcontext = osc;
}
return 0;
}
/*
* Set the type for the next transfer, usually image (binary) or ASCII.
*/
/**/
static int
zfsettype(int type)
{
char buf[] = "TYPE X\r\n";
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;
zfstatusp[zfsessno] &= ~(ZFST_TMSK << ZFST_TBIT);
/* shift the type left to set the current type bits */;
zfstatusp[zfsessno] |= type << ZFST_TBIT;
return 0;
}
/*
* Print or get a new type for the transfer.
* We don't actually set the type at this point.
*/
/**/
static int
zftp_type(char *name, char **args, int flags)
{
char *str, nt, tbuf[2] = "A";
if (flags & (ZFTP_TBIN|ZFTP_TASC)) {
nt = (flags & ZFTP_TBIN) ? 'I' : 'A';
} else if (!(str = *args)) {
/*
* 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(zfstatusp[zfsessno]) == ZFST_ASCI) ?
'A' : 'I');
fflush(stdout);
return 0;
} else {
nt = toupper((unsigned char) *str);
/*
* RFC959 specifies other types, but these are the only
* ones we know what to do with.
*/
if (str[1] || (nt != 'A' && nt != 'B' && nt != 'I')) {
zwarnnam(name, "transfer type %s not recognised", str);
return 1;
}
if (nt == 'B') /* binary = image */
nt = 'I';
}
zfstatusp[zfsessno] &= ~ZFST_TMSK;
zfstatusp[zfsessno] |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI;
tbuf[0] = nt;
zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY);
return 0;
}
/**/
static int
zftp_mode(char *name, char **args, UNUSED(int flags))
{
char *str, cmd[] = "MODE X\r\n";
int nt;
if (!(str = *args)) {
printf("%c\n", (ZFST_MODE(zfstatusp[zfsessno]) == ZFST_STRE) ?
'S' : 'B');
fflush(stdout);
return 0;
}
nt = str[0] = toupper((unsigned char) *str);
if (str[1] || (nt != 'S' && nt != 'B')) {
zwarnnam(name, "transfer mode %s not recognised", str);
return 1;
}
cmd[5] = (char) nt;
if (zfsendcmd(cmd) > 2)
return 1;
zfstatusp[zfsessno] &= ZFST_MMSK;
zfstatusp[zfsessno] |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC;
zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY);
return 0;
}
/**/
static int
zftp_local(UNUSED(char *name), char **args, int flags)
{
int more = !!args[1], ret = 0, dofd = !*args;
while (*args || dofd) {
off_t sz;
char *mt;
int newret = zfstats(*args, !(flags & ZFTP_HERE), &sz, &mt,
dofd ? 0 : -1);
if (newret == 2) /* at least one is not implemented */
return 2;
else if (newret) {
ret = 1;
if (mt)
zsfree(mt);
args++;
continue;
}
if (more) {
fputs(*args, stdout);
fputc(' ', stdout);
}
#ifdef OFF_T_IS_64_BIT
printf("%s %s\n", output64(sz), mt);
#else
DPUTS(sizeof(sz) > sizeof(long),
"Shell compiled with wrong off_t size");
printf("%ld %s\n", (long)sz, mt);
#endif
zsfree(mt);
if (dofd)
break;
args++;
}
fflush(stdout);
return ret;
}
/*
* Generic transfer for get, put and append.
*
* Get sends all files to stdout, i.e. this is basically cat. It's up to a
* shell function driver to turn this into standard FTP-like commands.
*
* Put/append sends everything from stdin down the drai^H^H^Hata connection.
* Slightly weird with multiple files in that it tries to read
* a separate complete file from stdin each time, which is
* only even potentially useful interactively. But the only
* real alternative is just to allow one file at a time.
*/
/**/
static int
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 ";
Shfunc shfunc;
/*
* At this point I'd like to set progress to 0 if we're
* backgrounded, since it's hard for the user to find out.
* It turns out it's hard enough for us to find out.
* The problem is that zsh clears it's job table, so we
* just don't know if we're some forked shell in a pipeline
* somewhere or in the background. This seems to me a problem.
*/
zfsettype(ZFST_TYPE(zfstatusp[zfsessno]));
if (recv)
fflush(stdout); /* since we may be using fd 1 */
for (; *args; args++) {
char *ln, *rest = NULL;
off_t startat = 0;
if (progress && (shfunc = getshfunc("zftp_progress"))) {
off_t sz = -1;
/*
* This calls the SIZE command to get the size for remote
* files. Some servers send the size with the reply to
* the transfer command (i.e. RETR), in which
* case we note the fact and don't call this
* next time. For that reason, the first call
* of zftp_progress is delayed until zfsenddata().
*/
if ((!(zfprefs & ZFPF_DUMB) &&
(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);
/* even if it doesn't support SIZE, it may tell us */
if (recv && sz == -1)
getsize = 1;
} else
getsize = 1;
zfstarttrans(*args, recv, sz);
}
if (flags & ZFTP_REST) {
startat = zstrtol(args[1], NULL, 10);
rest = tricat("REST ", args[1], "\r\n");
}
ln = tricat(cmd, *args, "\r\n");
/* 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);
/*
* 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 &&
(shfunc = getshfunc("zftp_progress"))) {
/* progress to finish: ZFTP_TRANSFER set to GF or PF */
int osc = sfcontext;
zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"),
ZFPM_READONLY);
sfcontext = SFC_HOOK;
doshfunc(shfunc, NULL, 1);
sfcontext = osc;
}
if (rest) {
zsfree(rest);
args++;
}
if (errflag)
break;
}
zfendtrans();
return ret != 0;
}
/*
* Delete a list of files on the server. We allow a list by analogy with
* `rm'.
*/
/**/
static int
zftp_delete(UNUSED(char *name), char **args, UNUSED(int flags))
{
int ret = 0;
char *cmd, **aptr;
for (aptr = args; *aptr; aptr++) {
cmd = tricat("DELE ", *aptr, "\r\n");
if (zfsendcmd(cmd) > 2)
ret = 1;
zsfree(cmd);
}
return ret;
}
/* Create or remove a directory on the server */
/**/
static int
zftp_mkdir(UNUSED(char *name), char **args, int flags)
{
int ret;
char *cmd = tricat((flags & ZFTP_DELE) ? "RMD " : "MKD ",
*args, "\r\n");
ret = (zfsendcmd(cmd) > 2);
zsfree(cmd);
return ret;
}
/* Rename a file on the server */
/**/
static int
zftp_rename(UNUSED(char *name), char **args, UNUSED(int flags))
{
int ret;
char *cmd;
cmd = tricat("RNFR ", args[0], "\r\n");
ret = 1;
if (zfsendcmd(cmd) == 3) {
zsfree(cmd);
cmd = tricat("RNTO ", args[1], "\r\n");
if (zfsendcmd(cmd) == 2)
ret = 0;
}
zsfree(cmd);
return ret;
}
/*
* Send random commands, either with SITE or literal.
* In the second case, the user better know what they're doing.
*/
/**/
static int
zftp_quote(UNUSED(char *name), char **args, int flags)
{
int ret = 0;
char *cmd;
cmd = (flags & ZFTP_SITE) ? zfargstring("SITE", args)
: zfargstring(args[0], args+1);
ret = (zfsendcmd(cmd) > 2);
zsfree(cmd);
return ret;
}
/*
* 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 void
zfclose(int leaveparams)
{
char **aptr;
Shfunc shfunc;
if (!zfsess->control)
return;
zfclosing = 1;
if (zcfinish != 2) {
/*
* haven't had EOF from server, so send a QUIT and get the response.
* maybe we should set a shorter timeout for this to avoid
* CTRL-c rage.
*/
zfsendcmd("QUIT\r\n");
}
if (zfsess->cin) {
/*
* We fdopen'd the TCP control fd; since we can't fdclose it,
* we need to perform a full fclose, which invalidates the
* TCP fd. We need to do this before closing the FILE, since
* it's not usable afterwards.
*/
if (fileno(zfsess->cin) == zfsess->control->fd)
zfsess->control->fd = -1;
fclose(zfsess->cin);
zfsess->cin = NULL;
}
if (zfsess->control) {
zfnopen--;
tcp_close(zfsess->control);
/* We leak if the above failed */
zfsess->control = NULL;
}
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_loop(zfstatfd, (char *)zfstatusp+zfsessno, sizeof(int));
close(zfstatfd);
zfstatfd = -1;
}
}
if (!leaveparams) {
/* 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 ((shfunc = getshfunc("zftp_chpwd"))) {
int osc = sfcontext;
sfcontext = SFC_HOOK;
doshfunc(shfunc, NULL, 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(UNUSED(char *name), UNUSED(char **args), UNUSED(int flags))
{
zfclose(0);
return 0;
}
/*
* 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) zshcalloc(sizeof(struct zftp_session));
zfsess->name = ztrdup(nm);
zfsess->dfd = -1;
zfsess->params = (char **) zshcalloc(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(void)
{
char **ps, **pd, *val;
for (ps = zfparams, pd = zfsess->params; *ps; ps++, pd++) {
if (*pd)
zsfree(*pd);
queue_signals();
if ((val = getsparam(*ps)))
*pd = ztrdup(val);
else
*pd = NULL;
unqueue_signals();
}
*pd = NULL;
}
/*
* 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
freesession(Zftp_session sptr)
{
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(UNUSED(char *name), char **args, UNUSED(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(UNUSED(char *name), char **args, UNUSED(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 */
/**/
static int
bin_zftp(char *name, char **args, UNUSED(Options ops), UNUSED(int func))
{
char fullname[20] = "zftp ";
char *cnam = *args++, *prefs, *ptr;
Zftpcmd zptr;
int n, ret = 0;
for (zptr = zftpcmdtab; zptr->nam; zptr++)
if (!strcmp(zptr->nam, cnam))
break;
if (!zptr->nam) {
zwarnnam(name, "no such subcommand: %s", cnam);
return 1;
}
/* check number of arguments */
for (n = 0; args[n]; n++)
;
if (n < zptr->min || (zptr->max != -1 && n > zptr->max)) {
zwarnnam(name, "wrong no. of arguments for %s", cnam);
return 1;
}
strcat(fullname, cnam);
if (zfstatfd != -1 && !(zptr->flags & ZFTP_SESS)) {
/* Get the status in case it was set by a forked process */
int oldstatus = zfstatusp[zfsessno];
lseek(zfstatfd, 0, 0);
read_loop(zfstatfd, (char *)zfstatusp, sizeof(int)*zfsesscnt);
if (zfsess->control && (zfstatusp[zfsessno] & ZFST_CLOS)) {
/* got closed in subshell without us knowing */
zcfinish = 2;
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(zfstatusp[zfsessno]))
zfsetparam("ZFTP_TYPE",
ztrdup(ZFST_TYPE(zfstatusp[zfsessno]) == ZFST_ASCI ?
"A" : "I"), ZFPM_READONLY);
if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatusp[zfsessno]))
zfsetparam("ZFTP_MODE",
ztrdup(ZFST_MODE(zfstatusp[zfsessno]) == ZFST_BLOC ?
"B" : "S"), ZFPM_READONLY);
}
}
#if defined(HAVE_SELECT) || defined (HAVE_POLL)
if (zfsess->control && !(zptr->flags & (ZFTP_TEST|ZFTP_SESS))) {
/*
* Test the connection for a bad fd or incoming message, but
* only if the connection was last heard of open, and
* 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->control) {
if (ret != 2) {
/*
* with ret == 2, we just got dumped out in the test,
* so enough messages already.
*/
zwarnnam(fullname, "not connected.");
}
return 1;
}
queue_signals();
if ((prefs = getsparam_u("ZFTP_PREFS"))) {
zfprefs = 0;
for (ptr = prefs; *ptr; ptr++) {
switch (toupper((unsigned char) *ptr)) {
case 'S':
/* sendport */
zfprefs |= ZFPF_SNDP;
break;
case 'P':
/*
* passive
* If we have already been told to use sendport mode,
* we're never going to use passive mode.
*/
if (!(zfprefs & ZFPF_SNDP))
zfprefs |= ZFPF_PASV;
break;
case 'D':
/* dumb */
zfprefs |= ZFPF_DUMB;
break;
default:
zwarnnam(name, "preference %c not recognized", *ptr);
break;
}
}
}
unqueue_signals();
ret = (*zptr->fun)(fullname, args, zptr->flags);
if (zfalarmed)
zfunalarm();
if (zfdrrrring) {
/* had a timeout, close the connection */
zcfinish = 2; /* don't try sending QUIT */
zfclose(0);
}
if (zfstatfd != -1) {
/*
* Set the status in case another process needs to know,
* but only for the active session.
*/
lseek(zfstatfd, zfsessno*sizeof(int), 0);
write_loop(zfstatfd, (char *)zfstatusp+zfsessno, sizeof(int));
}
return ret;
}
static void
zftp_cleanup(void)
{
/*
* There are various parameters hanging around, but they're
* all non-special so are entirely non-life-threatening.
*/
LinkNode nptr;
Zftp_session cursess = zfsess;
for (zfsessno = 0, nptr = firstnode(zfsessions); nptr;
zfsessno++, incnode(nptr)) {
zfsess = (Zftp_session)nptr->dat;
zfclosedata();
/*
* When closing the current session, do the usual unsetting,
* otherwise don't.
*/
zfclose(zfsess != cursess);
}
zsfree(lastmsg);
lastmsg = NULL;
zfunsetparam("ZFTP_SESSION");
freelinklist(zfsessions, (FreeFunc) freesession);
zfree(zfstatusp, sizeof(int)*zfsesscnt);
zfstatusp = NULL;
}
static int
zftpexithook(UNUSED(Hookdef d), UNUSED(void *dummy))
{
zftp_cleanup();
return 0;
}
static struct features module_features = {
bintab, sizeof(bintab)/sizeof(*bintab),
NULL, 0,
NULL, 0,
NULL, 0,
0
};
/* The load/unload routines required by the zsh library interface */
/**/
int
setup_(UNUSED(Module m))
{
return (require_module("zsh/net/tcp", NULL, 0) == 1);
}
/**/
int
features_(Module m, char ***features)
{
*features = featuresarray(m, &module_features);
return 0;
}
/**/
int
enables_(Module m, int **enables)
{
return handlefeatures(m, &module_features, enables);
}
/**/
int
boot_(UNUSED(Module m))
{
/*
* Set some default parameters.
* These aren't special, so aren't associated with features.
*/
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");
addhookfunc("exit", zftpexithook);
return 0;
}
/**/
int
cleanup_(Module m)
{
deletehookfunc("exit", zftpexithook);
zftp_cleanup();
return setfeatureenables(m, &module_features, NULL);
}
/**/
int
finish_(UNUSED(Module m))
{
return 0;
}