From 9003d99d16c46b5679da7fcf1f2a41adef495ff9 Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Thu, 15 Apr 1999 18:09:05 +0000 Subject: zsh-3.1.5-pws-3 --- Src/Modules/example.c | 62 +- Src/Modules/stat.c | 77 +- Src/Modules/zftp.c | 2596 +++++++++++++++++++++++++++++++++++++++++++++++++ Src/Modules/zftp.mdd | 3 + 4 files changed, 2725 insertions(+), 13 deletions(-) create mode 100644 Src/Modules/zftp.c create mode 100644 Src/Modules/zftp.mdd (limited to 'Src/Modules') diff --git a/Src/Modules/example.c b/Src/Modules/example.c index 45ef3c28f..a71806c3a 100644 --- a/Src/Modules/example.c +++ b/Src/Modules/example.c @@ -49,6 +49,53 @@ bin_example(char *nam, char **args, char *ops, int func) return 0; } +/**/ +static int +cond_p_len(Conddef c, char **a) +{ + char *s1 = a[0], *s2 = a[1]; + + singsub(&s1); + untokenize(s1); + if (s2) { + singsub(&s2); + untokenize(s2); + return strlen(s1) == matheval(s2); + } else { + return !s1[0]; + } +} + +/**/ +static int +cond_i_ex(Conddef c, char **a) +{ + char *s1 = a[0], *s2 = a[1]; + + singsub(&s1); + untokenize(s1); + singsub(&s2); + untokenize(s2); + return !strcmp("example", dyncat(s1, s2)); +} + +/**/ +static int +ex_wrapper(List list, FuncWrap w, char *name) +{ + if (strncmp(name, "example", 7)) + return 1; + else { + int ogd = opts[GLOBDOTS]; + + opts[GLOBDOTS] = 1; + runshfunc(list, w, name); + opts[GLOBDOTS] = ogd; + + return 0; + } +} + /* * boot_example is executed when the module is loaded. */ @@ -57,11 +104,22 @@ static struct builtin bintab[] = { BUILTIN("example", 0, bin_example, 0, -1, 0, "flags", NULL), }; +static struct conddef cotab[] = { + CONDDEF("len", 0, 1, 2, cond_p_len), + CONDDEF("ex", CONDF_INFIX, 0, 0, cond_i_ex), +}; + +static struct funcwrap wrapper[] = { + WRAPDEF(ex_wrapper), +}; + /**/ int boot_example(Module m) { - return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return !(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) | + addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)) | + !addwrapper(m, wrapper)); } #ifdef MODULE @@ -71,6 +129,8 @@ int cleanup_example(Module m) { deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)); + deletewrapper(m, wrapper); return 0; } #endif diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c index 64664abaf..769b42b1a 100644 --- a/Src/Modules/stat.c +++ b/Src/Modules/stat.c @@ -34,11 +34,13 @@ enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID, ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM, ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT }; enum statflags { STF_NAME = 1, STF_FILE = 2, STF_STRING = 4, STF_RAW = 8, - STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64 }; + STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64, + STF_HASH = 128 }; static char *statelts[] = { "device", "inode", "mode", "nlink", "uid", "gid", "rdev", "size", "atime", "mtime", "ctime", "blksize", "blocks", "link", NULL }; +#define HNAMEKEY "name" /**/ static void @@ -287,6 +289,8 @@ statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags) * file names are returned as a separate array element, type names as * prefix to element. Note the formatting deliberately contains * fewer frills when -A is used. + * -H hash: as for -A array, but returns a hash with the keys being those + * from stat -l * -F fmt: specify a $TIME-like format for printing times; the default * is the (CTIME-like) "%a %b %e %k:%M:%S". This option implies * -s as it is not useful for numerical times. @@ -305,6 +309,7 @@ static int bin_stat(char *name, char **args, char *ops, int func) { char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL; + char *hashnam = NULL, **hash = NULL, **hashptr = NULL; int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0; struct stat statbuf; int found = 0, nargs; @@ -352,6 +357,16 @@ bin_stat(char *name, char **args, char *ops, int func) } flags |= STF_ARRAY; break; + } else if (*arg == 'H') { + if (arg[1]) { + hashnam = arg+1; + } else if (!(hashnam = *++args)) { + zerrnam(name, "missing parameter name\n", + NULL, 0); + return 1; + } + flags |= STF_HASH; + break; } else if (*arg == 'f') { char *sfd; ops['f'] = 1; @@ -385,6 +400,15 @@ bin_stat(char *name, char **args, char *ops, int func) } } + if ((flags & STF_ARRAY) && (flags & STF_HASH)) { + /* We don't implement setting multiple variables at once */ + zwarnnam(name, "both array and hash requested", NULL, 0); + return 1; + /* Alternate method would be to make -H blank arrnam etc etc * + * and so get 'silent loss' of earlier choice, which would * + * be similar to stat -A foo -A bar filename */ + } + if (ops['l']) { /* list types and return: can also list to array */ if (arrnam) { @@ -435,7 +459,7 @@ bin_stat(char *name, char **args, char *ops, int func) if (ops['g']) flags |= STF_GMT; - if (!arrnam) { + if (!(arrnam || hashnam)) { if (nargs > 1) flags |= STF_FILE; if (!(flags & STF_PICK)) @@ -444,9 +468,20 @@ bin_stat(char *name, char **args, char *ops, int func) if (ops['N'] || ops['f']) flags &= ~STF_FILE; - if (ops['T']) + if (ops['T'] || ops['H']) flags &= ~STF_NAME; + if (hashnam) { + if (nargs > 1) { + zwarnnam(name, "only one file allowed with -H", NULL, 0); + return 1; + } + arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; + if (flags & STF_FILE) + arrsize++; + hashptr = hash = (char **)zcalloc((arrsize+1)*2*sizeof(char *)); + } + if (arrnam) { arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; if (flags & STF_FILE) @@ -473,13 +508,20 @@ bin_stat(char *name, char **args, char *ops, int func) if (flags & STF_FILE) if (arrnam) *arrptr++ = ztrdup(*args); - else + else if (hashnam) { + *hashptr++ = ztrdup(HNAMEKEY); + *hashptr++ = ztrdup(*args); + } else printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n"); if (iwhich > -1) { statprint(&statbuf, outbuf, *args, iwhich, flags); if (arrnam) *arrptr++ = ztrdup(outbuf); - else + else if (hashnam) { + /* STF_NAME explicitly turned off for ops['H'] above */ + *hashptr++ = ztrdup(statelts[iwhich]); + *hashptr++ = ztrdup(outbuf); + } else printf("%s\n", outbuf); } else { int i; @@ -487,28 +529,39 @@ bin_stat(char *name, char **args, char *ops, int func) statprint(&statbuf, outbuf, *args, i, flags); if (arrnam) *arrptr++= ztrdup(outbuf); - else + else if (hashnam) { + /* STF_NAME explicitly turned off for ops['H'] above */ + *hashptr++ = ztrdup(statelts[i]); + *hashptr++ = ztrdup(outbuf); + } else printf("%s\n", outbuf); } } if (ops['f']) break; - if (!arrnam && args[1] && !(flags & STF_PICK)) + if (!arrnam && !hashnam && args[1] && !(flags & STF_PICK)) putchar('\n'); } if (arrnam) - if (ret) { - for (aptr = array; *aptr; aptr++) - zsfree(*aptr); - zfree(array, arrsize+1); - } else { + if (ret) + freearray(array); + else { setaparam(arrnam, array); if (errflag) return 1; } + if (hashnam) + if (ret) + freearray(hash); + else { + sethparam(hashnam, hash); + if (errflag) + return 1; + } + return ret; } diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c new file mode 100644 index 000000000..ca0843419 --- /dev/null +++ b/Src/Modules/zftp.c @@ -0,0 +1,2596 @@ +/* + * 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: + * can signal handling be improved? + * error messages may need tidying up. + * maybe we should block CTRL-c on some more operations, + * otherwise you can get the connection closed prematurely. + * some way of turning off progress reports when backgrounded + * would be nice, but the shell doesn't make it easy to find that out. + * the progress reports 100% a bit prematurely: the data may still + * be in transit, and we are stuck waiting for a message from the + * server. but there's really nothing else to do. it's worst + * with small files. + * proxy/gateway connections if i knew what to do + * options to specify e.g. a non-standard port + * optimizing things out is hard in general when you don't know what + * the shell's going to want, but they may be places to second guess + * the user. Some of the variables could be made special and so + * only retrieve things like the current directory when necessary. + * But it's much neater to have ordinary variables, which the shell + * can manage without our interference, and getting the directory + * just every time it changes isn't so bad. The user can always + * toggle the `Dumb' preference if it's feeling clever. + */ +#include "zftp.mdh" +#include "zftp.pro" + +#include +#include +#include +#include +#include +#include +/* it's a TELNET based protocol, but don't think I like doing this */ +#include + +/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */ +#ifndef INADDR_NONE +#define INADDR_NONE (in_addr_t)-1 +#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 *, size_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 */ +}; + +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 }, + { "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 }, + { 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_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 long */ +}; + +/* + * Basic I/O variables for control connection: + * zcfd != -1 is a flag that there is a connection open. + */ +static int zcfd = -1; +static FILE *zcin; +static struct sockaddr_in zsock; + +/* + * 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; + +/* + * Now stuff for data connection + */ +static int zdfd = -1; +static struct sockaddr_in zdsock; + +/* + * Stuff about last message: last line of message and status code. + * The reply is also stored in $ZFTP_REPLY; we keep these separate + * for convenience. + */ +static char *lastmsg, lastcodestr[4]; +static int lastcode; + +/* flag for remote system is UNIX --- useful to know as things are simpler */ +static int zfis_unix, zfpassive_conn; + +/* remote system has size, mdtm commands */ +enum { + ZFCP_UNKN = 0, /* dunno if it works on this server */ + ZFCP_YUPP = 1, /* it does */ + ZFCP_NOPE = 2 /* it doesn't */ +}; + +static int zfhas_size, zfhas_mdtm; + +/* + * We keep an fd open for communication between the main shell + * and forked off bits and pieces. This allows us to know + * 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 = 0x00, /* type for next transfer is ASCII */ + ZFST_IMAG = 0x01, /* type for next transfer is image */ + + ZFST_TMSK = 0x01, /* mask for type flags */ + ZFST_TBIT = 0x01, /* number of bits in type flags */ + + ZFST_CASC = 0x00, /* current type is ASCII - default */ + ZFST_CIMA = 0x02, /* current type is image */ + + ZFST_STRE = 0x00, /* stream mode - default */ + ZFST_BLOC = 0x04, /* block mode */ + + ZFST_MMSK = 0x04, /* mask for mode flags */ + + ZFST_LOGI = 0x08, /* user logged in */ + ZFST_NOPS = 0x10, /* server doesn't understand PASV */ + ZFST_NOSZ = 0x20, /* server doesn't send `(XXXX bytes)' reply */ + ZFST_TRSZ = 0x40, /* tried getting 'size' from reply */ + ZFST_CLOS = 0x80 /* connection closed */ +}; +#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) + +static int zfstatfd = -1, zfstatus; + +/* 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. */ +int zfprefs; + + +/* zfuserparams is the storage area for zftp_params() */ +char **zfuserparams; + +/* + * 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() +{ + 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. + */ + time_t tdiff = time(NULL) - oaltime; + alarm(oalremain < tdiff ? 1 : oalremain - tdiff); + } else + alarm(0); + if (sigtrapped[SIGALRM] || interact) { + if (sigfuncs[SIGALRM] || !sigtrapped[SIGALRM]) + 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 (sigfuncs[SIGPIPE]) + 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 long (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->sets.ifn(pm, *(long *)val); + else + pm->sets.cfn(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 5; + } + 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(zcin); + + switch(ch) { + case EOF: + if (ferror(zcin) && errno == EINTR) { + clearerr(zcin); + continue; + } + zcfinish = 2; + break; + + case '\r': + /* always precedes something else */ + ch = fgetc(zcin); + 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(zcin); + switch (ch) { + case WILL: + case WONT: + ch = fgetc(zcin); + /* whatever it wants to do, stop it. */ + cmdbuf[0] = (char)IAC; + cmdbuf[1] = (char)DONT; + cmdbuf[2] = ch; + write(zcfd, cmdbuf, 3); + continue; + + case DO: + case DONT: + ch = fgetc(zcin); + /* well, tough, we're not going to. */ + cmdbuf[0] = (char)IAC; + cmdbuf[1] = (char)WONT; + cmdbuf[2] = ch; + write(zcfd, 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() +{ + char line[256], *ptr, *verbose; + int stopit, printing = 0, tmout; + + if (zcfd == -1) + return 5; + if (!(verbose = getsparam("ZFTP_VERBOSE"))) + verbose = ""; + zsfree(lastmsg); + lastmsg = NULL; + + tmout = getiparam("ZFTP_TMOUT"); + + zfgetline(line, 256, tmout); + ptr = line; + if (zfdrrrring || !isdigit((int)*ptr) || !isdigit((int)ptr[1]) || + !isdigit((int)ptr[2])) { + /* timeout, or not talking FTP. not really interested. */ + zcfinish = 2; + if (!zfclosing) + zfclose(); + 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++ != '-'); + + 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); + } + 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(); + /* unexpected, so tell user */ + zwarnnam("zftp", "remote server has closed connection", NULL, 0); + return 6; /* pretend it failed, because it did */ + } + 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 (zcfd == -1) + return 5; + tmout = getiparam("ZFTP_TMOUT"); + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout sending message", NULL, 0); + return 5; + } + zfalarm(tmout); + ret = write(zcfd, cmd, strlen(cmd)); + alarm(0); + + if (ret <= 0) { + zwarnnam("zftp send", "failed sending control message", NULL, 0); + return 5; /* FTP status code */ + } + + return zfgetmsg(); +} + + +/* Set up a data connection, return 1 for failure, 0 for success */ + +/**/ +static int +zfopendata(char *name) +{ + if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) { + zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0); + return 1; + } + zdfd = zfmovefd(socket(AF_INET, SOCK_STREAM, 0)); + if (zdfd < 0) { + zwarnnam(name, "can't get data socket: %e", NULL, errno); + return 1; + } + + zdsock = zsock; + zdsock.sin_family = AF_INET; + + if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { + char *ptr; + int i, nums[6], err; + unsigned char iaddr[4], iport[2]; + + if (zfsendcmd("PASV\r\n") == 6) + return 1; + else if (lastcode >= 500 && lastcode <= 504) { + /* + * Fall back to send port mode. That will + * test the preferences for whether that's OK. + */ + zfstatus |= ZFST_NOPS; + zfclosedata(); + return zfopendata(name); + } + /* + * OK, now we need to know what port we're looking at, + * which is cunningly concealed in the reply. + * lastmsg already has the reply code expunged. + */ + for (ptr = lastmsg; *ptr; ptr++) + if (isdigit(*ptr)) + break; + if (sscanf(ptr, "%d,%d,%d,%d,%d,%d", + nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) { + zwarnnam(name, "bad response to PASV: %s", lastmsg, 0); + 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(&zdsock.sin_addr, iaddr, sizeof(iaddr)); + memcpy(&zdsock.sin_port, iport, sizeof(iport)); + + /* we should timeout this connect */ + do { + err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)); + } while (err && errno == EINTR && !errflag); + + if (err) { + zwarnnam(name, "connect failed: %e", NULL, errno); + zfclosedata(); + return 1; + } + + zfpassive_conn = 1; + } else { + char portcmd[40]; + unsigned char *addr, *port; + int ret, len; + + if (!(zfprefs & ZFPF_SNDP)) { + zwarnnam(name, "only sendport mode available for data", NULL, 0); + return 1; + } + + zdsock.sin_port = 0; /* to be set by bind() */ + len = sizeof(zdsock); + /* need to do timeout stuff and probably handle EINTR here */ + if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0) + ret = 1; + else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0) + ret = 2; + else if (listen(zdfd, 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; + } + + addr = (unsigned char *) &zdsock.sin_addr; + port = (unsigned char *) &zdsock.sin_port; + sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n", + addr[0],addr[1],addr[2],addr[3],port[0],port[1]); + if (zfsendcmd(portcmd) >= 5) { + zwarnnam(name, "port command failed", NULL, 0); + zfclosedata(); + return 1; + } + zfpassive_conn = 0; + } + + return 0; +} + +/* Close the data connection. */ + +/**/ +static void +zfclosedata(void) +{ + if (zdfd == -1) + return; + close(zdfd); + zdfd = -1; +} + +/* + * Set up a data connection and use cmd to initiate a transfer. + * The actual data fd will be zdfd; 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) +{ + int len, newfd; + + if (zfopendata(name)) + 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 || (!(zfstatus & 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"); + zfstatus |= ZFST_NOSZ|ZFST_TRSZ; + if (ptr) { + while (ptr > lastmsg && !isdigit(*ptr)) + ptr--; + while (ptr > lastmsg && isdigit(ptr[-1])) + ptr--; + if (isdigit(*ptr)) { + zfstatus &= ~ZFST_NOSZ; + if (getsize) { + long sz = zstrtol(ptr, NULL, 10); + zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); + } + } + } + } + + if (!zfpassive_conn) { + /* + * the current zdfd 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(zdfd, (struct sockaddr *)&zdsock, &len)); + zfclosedata(); + if (newfd < 0) { + zwarnnam(name, "unable to accept data.", NULL, 0); + return 1; + } + zdfd = newfd; /* this is now the actual data fd */ + } + + + /* 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(zdfd, SOL_SOCKET, SO_LINGER, (char *)&li, sizeof(li)); + } +#endif +#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT) + /* try to get high throughput, snigger */ + { + int arg = IPTOS_THROUGHPUT; + setsockopt(zdfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg)); + } +#endif +#if defined(F_SETFD) && defined(FD_CLOEXEC) + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zdfd, F_SETFD, &len); +#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, long *retsize, char **retmdtm, int fd) +{ + long sz = -1; + char *mt = NULL; + int ret; + + if (retsize) + *retsize = -1; + if (retmdtm) + *retmdtm = NULL; + if (remote) { + char *cmd; + if ((zfhas_size == ZFCP_NOPE && retsize) || + (zfhas_mdtm == ZFCP_NOPE && retmdtm)) + return 2; + + /* + * File is coming from over there. + * Make sure we get the type right. + */ + zfsettype(ZFST_TYPE(zfstatus)); + 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); + zfhas_size = ZFCP_YUPP; + } else if (lastcode >= 500 && lastcode <= 504) { + zfhas_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); + zfhas_mdtm = ZFCP_YUPP; + } else if (lastcode >= 500 && lastcode <= 504) { + zfhas_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 long, 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, long sz) +{ + long 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, size_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, size_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, size_t sz, int tmout) +{ + int n; + struct zfheader hdr; + size_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", "failed to read 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, size_t sz, int tmout) +{ + int n; + struct zfheader hdr; + size_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", "failed to write 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, long 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; + long sofar = 0, last_sofar = 0; + readwrite_t read_ptr = zfread, write_ptr = zfwrite; + List l; + + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* + * progress to set up: ZFTP_COUNT is zero. + * We do this here in case we needed to wait for a RETR + * command to tell us how many bytes are coming. + */ + doshfunc("zftp_progress", l, NULL, 0, 1); + /* Now add in the bit of the file we've got/sent already */ + sofar = last_sofar = startat; + } + if (recv) { + fdin = zdfd; + fdout = 1; + rtmout = getiparam("ZFTP_TMOUT"); + if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + fromasc = 1; + if (ZFST_MODE(zfstatus) == ZFST_BLOC) + read_ptr = zfread_block; + } else { + fdin = 0; + fdout = zdfd; + wtmout = getiparam("ZFTP_TMOUT"); + if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + toasc = 1; + if (ZFST_MODE(zfstatus) == 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 && + (l = getshfunc("zftp_progress")) != &dummy_list) { + zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER); + doshfunc("zftp_progress", l, NULL, 0, 1); + 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(zfstatus) == 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(zcfd, (char *)msg, 3, 0); + send(zcfd, (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 in_addr ipaddr; + struct protoent *zprotop; + struct servent *zservp; + struct hostent *zhostp = NULL; + char **addrp, tbuf[2] = "X", *fname; + int err, len, tmout; + + if (!*args) { + if (zfuserparams) + args = zfuserparams; + 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 (zcfd != -1) + zfclose(); + + /* this is going to give 0. why bother? */ + zprotop = getprotobyname("tcp"); + zservp = getservbyname("ftp", "tcp"); + + if (!zprotop || !zservp) { + zwarnnam(name, "Somebody stole FTP!", NULL, 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); + if ((hname = getsparam("ZFTP_HOST")) && *hname) + zwarnnam(name, "timeout connecting to %s", hname, 0); + else + zwarnnam(name, "timeout on host name lookup", NULL, 0); + zfclose(); + return 1; + } + zfalarm(tmout); + + /* + * Now this is what I like. A library which provides decent higher + * level functions to do things like converting address types. It saves + * so much trouble. Pity about the rest of the network interface, though. + */ + ipaddr.s_addr = inet_addr(args[0]); + if (ipaddr.s_addr != INADDR_NONE) { + /* + * hmmm, we don't necessarily want to do this... maybe the + * user is actively trying to avoid a bad nameserver. + * perhaps better just to set ZFTP_HOST to the dot address, too. + * that way shell functions know how it was opened. + * + * zhostp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET); + * + * or, we could have a `no_lookup' flag. + */ + zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY); + zsock.sin_family = AF_INET; + } else { + zhostp = gethostbyname(args[0]); + if (!zhostp || errflag) { + /* should use herror() here if available, but maybe + * needs configure test. on AIX it's present but not + * in headers. + */ + zwarnnam(name, "host not found: %s", args[0], 0); + alarm(0); + return 1; + } + zsock.sin_family = zhostp->h_addrtype; + zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY); + } + + zsock.sin_port = ntohs(zservp->s_port); + zcfd = zfmovefd(socket(zsock.sin_family, SOCK_STREAM, 0)); + if (zcfd < 0) { + zwarnnam(name, "socket failed: %e", NULL, errno); + zfunsetparam("ZFTP_HOST"); + alarm(0); + return 1; + } + +#if defined(F_SETFD) && defined(FD_CLOEXEC) + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zcfd, F_SETFD, &len); +#endif + + /* + * 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; + + if (ipaddr.s_addr != INADDR_NONE) { + /* dot address */ + memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr)); + do { + err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + } while (err && errno == EINTR && !errflag); + } else { + /* host name: try all possible IP's */ + for (addrp = zhostp->h_addr_list; *addrp; addrp++) { + memcpy(&zsock.sin_addr, *addrp, zhostp->h_length); + do { + err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + } while (err && errno == EINTR && !errflag); + /* you can check whether it's worth retrying here */ + } + } + + alarm(0); + + if (err < 0) { + zwarnnam(name, "connect failed: %e", NULL, errno); + zfclose(); + return 1; + } + zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY); + /* now we can talk to the control connection */ + zcfinish = 0; + + len = sizeof(zsock); + if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) { + zwarnnam(name, "getsockname failed: %e", NULL, errno); + zfclose(); + 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(zcfd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len)); +#endif +#if defined(IP_TOS) && defined(IPTOS_LOWDELAY) + /* for control connection we want low delay. please don't laugh. */ + len = IPTOS_LOWDELAY; + setsockopt(zcfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len)); +#endif + + /* + * We use stdio with line buffering for convenience on input. + * On output, we can just dump a complete message to the fd via write(). + */ + zcin = fdopen(zcfd, "r"); + + if (!zcin) { + zwarnnam(name, "file handling error", NULL, 0); + zfclose(); + return 1; + } + +#ifdef _IONBF + setvbuf(zcin, NULL, _IONBF, 0); +#else + setlinebuf(zcin); +#endif + + /* + * now see what the remote server has to say about that. + */ + if (zfgetmsg() >= 4) { + zfclose(); + return 1; + } + + zfis_unix = 0; + zfhas_size = zfhas_mdtm = ZFCP_UNKN; + zdfd = -1; + /* initial status: open, ASCII data, stream mode 'n' stuff */ + zfstatus = 0; + + /* open file for saving the current status */ + fname = gettempname(); + zfstatfd = open(fname, O_RDWR|O_CREAT, 0600); + DPUTS(zfstatfd == -1, "zfstatfd not created"); +#if defined(F_SETFD) && defined(FD_CLOEXEC) + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zfstatfd, F_SETFD, &len); +#endif + unlink(fname); + + /* now find out what system we're connected to */ + if (!(zfprefs & ZFPF_DUMB) && zfsendcmd("SYST\r\n") == 2) { + char *ptr = lastmsg, *eptr, *systype; + for (eptr = ptr; *eptr; eptr++) + ; + systype = ztrduppfx(ptr, eptr-ptr); + if (!strncmp(systype, "UNIX Type: L8", 13)) { + /* + * Use binary for transfers. This simple test saves much + * hassle for all concerned, particularly me. + */ + zfstatus |= ZFST_IMAG; + zfis_unix = 1; + } + /* + * we could set zfis_unix based just on the UNIX part, + * but I don't really know the consequences of that. + */ + zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY); + } else if (zcfd == -1) { + /* final paranoid check */ + return 1; + } + + tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'; + zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); + zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY); + /* if remaining arguments, use them to log in. */ + if (zcfd > -1 && *++args) + return zftp_login(name, args, flags); + /* if something wayward happened, connection was already closed */ + return zcfd == -1; +} + +/* + * 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(char *name, char **args, int flags) +{ + char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " }; + char **aptr, **newarr; + int i, j, len; + + if (!*args) { + if (zfuserparams) { + for (aptr = zfuserparams, 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; + } + if (!strcmp(*args, "-")) { + if (zfuserparams) + freearray(zfuserparams); + zfuserparams = 0; + return 0; + } + len = arrlen(args); + newarr = (char **)zcalloc((len+1)*sizeof(char *)); + for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) { + char *str; + if (!strcmp(*aptr, "?")) + str = zfgetinfo(prompts[i], i == 2); + else + str = *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 (zfuserparams) + freearray(zfuserparams); + zfuserparams = newarr; + return 0; +} + +/* login a user: often called as part of the open sequence */ + +/**/ +static int +zftp_login(char *name, char **args, int flags) +{ + char *ucmd, *passwd = NULL, *acct = NULL; + char *user; + int stopit; + + if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) + return 1; + + zfstatus &= ~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 (zcfd == -1) + 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); + } + zfstatus |= ZFST_LOGI; + zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY); + if (acct) + zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), 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(); +} + +/* 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(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; + List l; + + 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 ((l = getshfunc("zftp_chpwd")) != &dummy_list) + doshfunc("zftp_chpwd", l, NULL, 0, 1); + + 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(zfstatus)) + return 0; + buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I'; + if (zfsendcmd(buf) > 2) + return 1; + zfstatus &= ~(ZFST_TMSK << ZFST_TBIT); + /* shift the type left to set the current type bits */; + zfstatus |= 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(zfstatus) == ZFST_ASCI) ? 'A' : 'I'); + fflush(stdout); + return 0; + } else { + nt = toupper(*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'; + } + + zfstatus &= ~ZFST_TMSK; + zfstatus |= (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, int flags) +{ + char *str, cmd[] = "MODE X\r\n"; + int nt; + + if (!(str = *args)) { + printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B'); + fflush(stdout); + return 0; + } + nt = str[0] = toupper(*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; + zfstatus &= ZFST_MMSK; + zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; + zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY); + return 0; +} + +/**/ +static int +zftp_local(char *name, char **args, int flags) +{ + int more = !!args[1], ret = 0, dofd = !*args; + while (*args || dofd) { + long 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); + } + printf("%ld %s\n", sz, mt); + 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 "; + List l; + + /* + * 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(zfstatus)); + + if (recv) + fflush(stdout); /* since we may be using fd 1 */ + for (; *args; args++) { + char *ln, *rest = NULL; + long startat = 0; + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + long sz; + /* + * 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) && + (zfstatus & (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 zdfd doesn't exist till zfgetdata() creates it */ + if (zfgetdata(name, rest, ln, getsize) || + zfsenddata(name, recv, progress, startat)) + ret = 1; + zsfree(ln); + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* progress to finish: ZFTP_TRANSFER set to GF or PF */ + zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"), + ZFPM_READONLY); + doshfunc("zftp_progress", l, NULL, 0, 1); + } + if (rest) { + zsfree(rest); + args++; + } + if (errflag) + break; + } + zfendtrans(); + return ret; +} + +/* + * Delete a list of files on the server. We allow a list by analogy with + * `rm'. + */ + +/**/ +static int +zftp_delete(char *name, char **args, 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(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(char *name, char **args, 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(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 */ + +/**/ +static int +zftp_close(char *name, char **args, int flags) +{ + char **aptr; + List l; + 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 (zcin) + fclose(zcin); + zcin = NULL; + close(zcfd); + zcfd = -1; + + /* Write the final status in case this is a subshell */ + zfstatus |= ZFST_CLOS; + lseek(zfstatfd, 0, 0); + write(zfstatfd, &zfstatus, sizeof(zfstatus)); + close(zfstatfd); + zfstatfd = -1; + + /* 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 ((l = getshfunc("zftp_chpwd")) != &dummy_list) + doshfunc("zftp_chpwd", l, NULL, 0, 1); + + /* tidy up status variables, because mess is bad */ + zfclosing = zfdrrrring = 0; + + return 0; +} + +/* Safe front end to zftp_close() from within the package */ + +/**/ +static void +zfclose(void) +{ + if (zcfd != -1) + zftp_close("zftp close", NULL, 0); +} + +/* The builtin command frontend to the rest of the package */ + +/**/ +static int +bin_zftp(char *name, char **args, char *ops, int func) +{ + char fullname[11] = "zftp "; + char *cnam = *args++, *prefs, *ptr; + Zftpcmd zptr; + int n, ret; + + 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) { + /* Get the status in case it was set by a forked process */ + int oldstatus = zfstatus; + lseek(zfstatfd, 0, 0); + read(zfstatfd, &zfstatus, sizeof(zfstatus)); + if (zcfd != -1 && (zfstatus & ZFST_CLOS)) { + /* got closed in subshell without us knowing */ + zcfinish = 2; + zfclose(); + } else { + /* + * fix up status types: unfortunately they may already + * have been looked at between being changed in the subshell + * and now, but we can't help that. + */ + if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatus)) + zfsetparam("ZFTP_TYPE", + ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ? + "A" : "I"), ZFPM_READONLY); + if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus)) + zfsetparam("ZFTP_MODE", + ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ? + "B" : "S"), ZFPM_READONLY); + } + } + if ((zptr->flags & ZFTP_CONN) && zcfd == -1) { + zwarnnam(fullname, "not connected.", NULL, 0); + return 1; + } + + if ((prefs = getsparam("ZFTP_PREFS"))) { + zfprefs = 0; + for (ptr = prefs; *ptr; ptr++) { + switch (toupper(*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; + } + } + } + + 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(); + } + if (zfstatfd != -1) { + /* Set the status in case another process needs to know */ + lseek(zfstatfd, 0, 0); + write(zfstatfd, &zfstatus, sizeof(zfstatus)); + } + return ret; +} + +/* The load/unload routines required by the zsh library interface */ + +/**/ +int +boot_zftp(Module m) +{ + int ret; + if ((ret = addbuiltins(m->nam, bintab, + sizeof(bintab)/sizeof(*bintab))) == 1) { + /* if successful, set some default parameters */ + long tmout_def = 60; + 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; + } + return !ret; +} + +#ifdef MODULE + +/**/ +int +cleanup_zftp(Module m) +{ + /* + * There are various parameters hanging around, but they're + * all non-special so are entirely non-life-threatening. + */ + zfclosedata(); + zfclose(); + zsfree(lastmsg); + if (zfuserparams) + freearray(zfuserparams); + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#endif diff --git a/Src/Modules/zftp.mdd b/Src/Modules/zftp.mdd new file mode 100644 index 000000000..83051ae54 --- /dev/null +++ b/Src/Modules/zftp.mdd @@ -0,0 +1,3 @@ +autobins="zftp" + +objects="zftp.o" -- cgit 1.4.1