mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-11-19 13:33:52 +01:00
05b06b1c08
(now traplists); improve trap tests
3215 lines
82 KiB
C
3215 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 happend 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 */
|
|
int zfalarmed, zfdrrrring;
|
|
/* remember old alarm status */
|
|
time_t oaltime;
|
|
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.
|
|
*/
|
|
jmp_buf zfalrmbuf;
|
|
|
|
/* The signal handler itself */
|
|
|
|
/**/
|
|
static RETSIGTYPE
|
|
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()
|
|
{
|
|
/* 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()
|
|
{
|
|
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->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->flags |= PM_READONLY;
|
|
} else if (flags & ZFPM_IFUNSET) {
|
|
pm = NULL;
|
|
}
|
|
if (!pm || PM_TYPE(pm->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->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", NULL, 0);
|
|
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(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(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 || !isdigit(STOUC(*ptr)) || !isdigit(STOUC(ptr[1])) ||
|
|
!isdigit(STOUC(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("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", NULL, 0);
|
|
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, 0);
|
|
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", NULL, 0);
|
|
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",
|
|
NULL, 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", NULL, 0);
|
|
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", NULL, 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, 0);
|
|
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 || !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->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 (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(zfsess->dfd, (struct sockaddr *)zdsockp, salen);
|
|
} while (err && errno == EINTR && !errflag);
|
|
|
|
if (err) {
|
|
zwarnnam(name, "connect failed: %e", NULL, 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", NULL, 0);
|
|
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", NULL, 0);
|
|
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 && !isdigit(STOUC(*ptr)))
|
|
ptr--;
|
|
while (ptr > lastmsg && isdigit(STOUC(ptr[-1])))
|
|
ptr--;
|
|
if (isdigit(STOUC(*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", NULL, 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 data is YYYYMMDDHHMMSS
|
|
* Using tm directly is easier than worrying about
|
|
* incompatible strftime()'s.
|
|
*/
|
|
sprintf(tmbuf, "%04d%02d%02d%02d%02d%02d",
|
|
tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday,
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
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()
|
|
{
|
|
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", NULL, 0);
|
|
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", NULL, 0);
|
|
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", NULL, 0);
|
|
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", NULL, 0);
|
|
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", NULL, 0);
|
|
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", NULL, 0);
|
|
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;
|
|
Eprog prog;
|
|
|
|
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.
|
|
*/
|
|
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 = 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", NULL, 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", NULL, errno);
|
|
} else
|
|
ret = recv ? 1 : 3;
|
|
break;
|
|
}
|
|
} else
|
|
break;
|
|
if (!ret && sofar != last_sofar && progress &&
|
|
(prog = getshfunc("zftp_progress")) != &dummy_eprog) {
|
|
int osc = sfcontext;
|
|
|
|
zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER);
|
|
sfcontext = SFC_HOOK;
|
|
doshfunc("zftp_progress", prog, NULL, 0, 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...", NULL, 0);
|
|
|
|
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 = NULL;
|
|
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", NULL, 0);
|
|
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, 0);
|
|
return 1;
|
|
}
|
|
*hostsuffix++ = '\0';
|
|
}
|
|
else
|
|
hostsuffix = hostnam;
|
|
|
|
if ((tmpptr = strchr(hostsuffix, ':'))) {
|
|
char *endptr;
|
|
|
|
*tmpptr++ = '\0';
|
|
port = (int)zstrtol(tmpptr, &endptr, 10);
|
|
/*
|
|
* If the port is not numeric, look it up by name below.
|
|
*/
|
|
if (*endptr) {
|
|
portnam = tmpptr;
|
|
port = -1;
|
|
}
|
|
#if defined(HAVE_NTOHS) && defined(HAVE_HTONS)
|
|
else {
|
|
port = (int)htons((unsigned short)port);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* this is going to give 0. why bother? */
|
|
zprotop = getprotobyname("tcp");
|
|
if (!zprotop) {
|
|
zwarnnam(name, "Can't find protocol TCP (is your network functional)?",
|
|
NULL, 0);
|
|
return 1;
|
|
}
|
|
if (port < 0)
|
|
zservp = getservbyname(portnam, "tcp");
|
|
else
|
|
zservp = getservbyport(port, "tcp");
|
|
|
|
if (!zprotop || !zservp) {
|
|
zwarnnam(name, "Can't find port for service `%s'", portnam, 0);
|
|
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("ZFTP_HOST")) && *hname)
|
|
zwarnnam(name, "timeout connecting to %s", hname, 0);
|
|
else
|
|
zwarnnam(name, "timeout on host name lookup", NULL, 0);
|
|
unqueue_signals();
|
|
zfclose(0);
|
|
return 1;
|
|
}
|
|
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, 0);
|
|
alarm(0);
|
|
return 1;
|
|
}
|
|
zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY);
|
|
/* careful with pointer types */
|
|
#if defined(HAVE_NTOHS) && defined(HAVE_HTONS)
|
|
tcp_port = (off_t)ntohs((unsigned short)zservp->s_port);
|
|
#else
|
|
tcp_port = (off_t)zservp->s_port;
|
|
#endif
|
|
zfsetparam("ZFTP_PORT", &tcp_port, ZFPM_READONLY|ZFPM_INTEGER);
|
|
|
|
#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", 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.
|
|
*/
|
|
|
|
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", NULL, 0);
|
|
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", NULL, 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", NULL, 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", NULL, 0);
|
|
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);
|
|
}
|
|
|
|
fgets(instr, 256, stdin);
|
|
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 ", passwd, "\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", NULL, 0);
|
|
return 1;
|
|
}
|
|
|
|
if (*args) {
|
|
int cnt;
|
|
for (cnt = 0; *args; args++)
|
|
cnt++;
|
|
zwarnnam(name, "warning: %d comand arguments not used\n", NULL, 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.", NULL, 0);
|
|
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;
|
|
Eprog prog;
|
|
|
|
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 ((prog = getshfunc("zftp_chpwd")) != &dummy_eprog) {
|
|
int osc = sfcontext;
|
|
|
|
sfcontext = SFC_HOOK;
|
|
doshfunc("zftp_chpwd", prog, NULL, 0, 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(STOUC(*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, 0);
|
|
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(STOUC(*str));
|
|
if (str[1] || (nt != 'S' && nt != 'B')) {
|
|
zwarnnam(name, "transfer mode %s not recognised", str, 0);
|
|
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) > 4, "Shell compiled with wrong off_t size");
|
|
printf("%ld %s\n", 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 ";
|
|
Eprog prog;
|
|
|
|
/*
|
|
* 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 && (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
|
|
* 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 &&
|
|
(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);
|
|
sfcontext = SFC_HOOK;
|
|
doshfunc("zftp_progress", prog, NULL, 0, 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;
|
|
Eprog prog;
|
|
|
|
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(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 ((prog = getshfunc("zftp_chpwd")) != &dummy_eprog) {
|
|
int osc = sfcontext;
|
|
|
|
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(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()
|
|
{
|
|
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, 0);
|
|
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, 0);
|
|
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(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.", NULL, 0);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
queue_signals();
|
|
if ((prefs = getsparam("ZFTP_PREFS"))) {
|
|
zfprefs = 0;
|
|
for (ptr = prefs; *ptr; ptr++) {
|
|
switch (toupper(STOUC(*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", NULL, *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(zfstatfd, (char *)zfstatusp+zfsessno, sizeof(int));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
zftp_cleanup(void)
|
|
{
|
|
/*
|
|
* There are various parameters hanging around, but they're
|
|
* all non-special so are entirely non-life-threatening.
|
|
*/
|
|
LinkNode nptr;
|
|
Zftp_session cursess = zfsess;
|
|
for (zfsessno = 0, nptr = firstnode(zfsessions); nptr;
|
|
zfsessno++, incnode(nptr)) {
|
|
zfsess = (Zftp_session)nptr->dat;
|
|
zfclosedata();
|
|
/*
|
|
* When closing the current session, do the usual unsetting,
|
|
* otherwise don't.
|
|
*/
|
|
zfclose(zfsess != cursess);
|
|
}
|
|
zsfree(lastmsg);
|
|
zfunsetparam("ZFTP_SESSION");
|
|
freelinklist(zfsessions, (FreeFunc) freesession);
|
|
zfree(zfstatusp, sizeof(int)*zfsesscnt);
|
|
deletebuiltins("zftp", bintab, sizeof(bintab)/sizeof(*bintab));
|
|
}
|
|
|
|
static int
|
|
zftpexithook(UNUSED(Hookdef d), UNUSED(void *dummy))
|
|
{
|
|
zftp_cleanup();
|
|
return 0;
|
|
}
|
|
|
|
/* The load/unload routines required by the zsh library interface */
|
|
|
|
/**/
|
|
int
|
|
setup_(UNUSED(Module m))
|
|
{
|
|
/* setup_ returns 0 for success. require_module returns 1 for success. */
|
|
return !require_module("", "zsh/net/tcp", 0, 0);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
boot_(UNUSED(Module m))
|
|
{
|
|
int ret;
|
|
if ((ret = addbuiltins("zftp", bintab,
|
|
sizeof(bintab)/sizeof(*bintab))) == 1) {
|
|
/* if successful, set some default parameters */
|
|
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 !ret;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
cleanup_(UNUSED(Module m))
|
|
{
|
|
deletehookfunc("exit", zftpexithook);
|
|
zftp_cleanup();
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
finish_(UNUSED(Module m))
|
|
{
|
|
return 0;
|
|
}
|