diff options
Diffstat (limited to 'Src/Modules')
-rw-r--r-- | Src/Modules/.cvsignore | 10 | ||||
-rw-r--r-- | Src/Modules/.distfiles | 8 | ||||
-rw-r--r-- | Src/Modules/.exrc | 2 | ||||
-rw-r--r-- | Src/Modules/cap.c | 141 | ||||
-rw-r--r-- | Src/Modules/cap.mdd | 3 | ||||
-rw-r--r-- | Src/Modules/clone.c | 115 | ||||
-rw-r--r-- | Src/Modules/clone.mdd | 3 | ||||
-rw-r--r-- | Src/Modules/example.c | 76 | ||||
-rw-r--r-- | Src/Modules/example.mdd | 3 | ||||
-rw-r--r-- | Src/Modules/files.c | 528 | ||||
-rw-r--r-- | Src/Modules/files.mdd | 3 | ||||
-rw-r--r-- | Src/Modules/stat.c | 535 | ||||
-rw-r--r-- | Src/Modules/stat.mdd | 3 | ||||
-rw-r--r-- | Src/Modules/zftp.c | 2596 | ||||
-rw-r--r-- | Src/Modules/zftp.mdd | 3 |
15 files changed, 4029 insertions, 0 deletions
diff --git a/Src/Modules/.cvsignore b/Src/Modules/.cvsignore new file mode 100644 index 000000000..169be5ef9 --- /dev/null +++ b/Src/Modules/.cvsignore @@ -0,0 +1,10 @@ +Makefile +Makefile.in +*.pro +*.o +*.o.c +*.so +*.mdh +*.mdhi +*.mdhs +*.mdh.tmp diff --git a/Src/Modules/.distfiles b/Src/Modules/.distfiles new file mode 100644 index 000000000..4c98f97ea --- /dev/null +++ b/Src/Modules/.distfiles @@ -0,0 +1,8 @@ +DISTFILES_SRC=' + .cvsignore .distfiles .exrc + cap.mdd cap.c + clone.mdd clone.c + example.mdd example.c + files.mdd files.c + stat.mdd stat.c +' diff --git a/Src/Modules/.exrc b/Src/Modules/.exrc new file mode 100644 index 000000000..91d0b39ef --- /dev/null +++ b/Src/Modules/.exrc @@ -0,0 +1,2 @@ +set ai +set sw=4 diff --git a/Src/Modules/cap.c b/Src/Modules/cap.c new file mode 100644 index 000000000..008b6932d --- /dev/null +++ b/Src/Modules/cap.c @@ -0,0 +1,141 @@ +/* + * cap.c - POSIX.1e (POSIX.6) capability set manipulation + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1997 Andrew Main + * 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 Andrew Main 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 Andrew Main and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Andrew Main 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 Andrew Main and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "cap.mdh" +#include "cap.pro" + +#ifdef HAVE_CAP_GET_PROC + +static int +bin_cap(char *nam, char **argv, char *ops, int func) +{ + int ret = 0; + cap_t caps; + if(*argv) { + caps = cap_from_text(*argv); + if(!caps) { + zwarnnam(nam, "invalid capability string", NULL, 0); + return 1; + } + if(cap_set_proc(caps)) { + zwarnnam(nam, "can't change capabilites: %e", NULL, errno); + ret = 1; + } + } else { + char *result; + ssize_t length; + caps = cap_get_proc(); + if(caps) + result = cap_to_text(caps, &length); + if(!caps || !result) { + zwarnnam(nam, "can't get capabilites: %e", NULL, errno); + ret = 1; + } else + puts(result); + } + cap_free(&caps); + return ret; +} + +static int +bin_getcap(char *nam, char **argv, char *ops, int func) +{ + int ret = 0; + + do { + char *result; + ssize_t length; + cap_t caps = cap_get_file(*argv); + if(caps) + result = cap_to_text(caps, &length); + if (!caps || !result) { + zwarnnam(nam, "%s: %e", *argv, errno); + ret = 1; + } else + printf("%s %s\n", *argv, result); + cap_free(&caps); + } while(*++argv); + return ret; +} + +static int +bin_setcap(char *nam, char **argv, char *ops, int func) +{ + cap_t caps; + int ret = 0; + + caps = cap_from_text(*argv++); + if(!caps) { + zwarnnam(nam, "invalid capability string", NULL, 0); + return 1; + } + + do { + if(cap_set_file(*argv, caps)) { + zwarnnam(nam, "%s: %e", *argv, errno); + ret = 1; + } + } while(*++argv); + cap_free(&caps); + return ret; +} + +#else /* !HAVE_CAP_GET_PROC */ + +# define bin_cap bin_notavail +# define bin_getcap bin_notavail +# define bin_setcap bin_notavail + +#endif /* !HAVE_CAP_GET_PROC */ + +/* module paraphernalia */ + +static struct builtin bintab[] = { + BUILTIN("cap", 0, bin_cap, 0, 1, 0, NULL, NULL), + BUILTIN("getcap", 0, bin_getcap, 1, -1, 0, NULL, NULL), + BUILTIN("setcap", 0, bin_setcap, 2, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_cap(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_cap(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/cap.mdd b/Src/Modules/cap.mdd new file mode 100644 index 000000000..97f377e9d --- /dev/null +++ b/Src/Modules/cap.mdd @@ -0,0 +1,3 @@ +autobins="cap getcap setcap" + +objects="cap.o" diff --git a/Src/Modules/clone.c b/Src/Modules/clone.c new file mode 100644 index 000000000..11387fc90 --- /dev/null +++ b/Src/Modules/clone.c @@ -0,0 +1,115 @@ +/* + * clone.c - start a forked instance of the current shell on a new terminal + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1997 Zoltán Hidvégi + * 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 Zoltán Hidvégi 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 Zoltán Hidvégi and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Zoltán Hidvégi 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 Zoltán Hidvégi and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +/* + * The clone builtin can be used to start a forked instance of the current + * shell on a new terminal. The only argument to the builtin is the name + * of the new terminal. In the new shell the PID, PPID and TTY parameters + * are changed appropriately. $! is set to zero in the new instance of the + * shell and to the pid of the new instance in the original shell. + * + */ + +#include "clone.mdh" +#include "clone.pro" + +/**/ +static int +bin_clone(char *nam, char **args, char *ops, int func) +{ + int ttyfd, pid; + + unmetafy(*args, NULL); + ttyfd = open(*args, O_RDWR|O_NOCTTY); + if (ttyfd < 0) { + zwarnnam(nam, "%s: %e", *args, errno); + return 1; + } + pid = fork(); + if (!pid) { + clearjobtab(); + ppid = getppid(); + mypid = getpid(); +#ifdef HAVE_SETSID + if (setsid() != mypid) { + zwarnnam(nam, "failed to create new session: %e", NULL, errno); +#endif +#ifdef TIOCNOTTY + if (ioctl(SHTTY, TIOCNOTTY)) + zwarnnam(nam, "%e", NULL, errno); + setpgrp(0L, mypid); +#endif +#ifdef HAVE_SETSID + } +#endif + if (ttyfd) { + close(0); + dup(ttyfd); + } else + ttyfd = -1; + close(1); + close(2); + dup(0); + dup(0); + closem(0); + close(coprocin); + close(coprocout); + init_io(); + setsparam("TTY", ztrdup(ttystrname)); + } + close(ttyfd); + if (pid < 0) { + zerrnam(nam, "fork failed: %e", NULL, errno); + return 1; + } + lastpid = pid; + return 0; +} + +static struct builtin bintab[] = { + BUILTIN("clone", 0, bin_clone, 1, 1, 0, NULL, NULL), +}; + +/**/ +int +boot_clone(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_clone(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/clone.mdd b/Src/Modules/clone.mdd new file mode 100644 index 000000000..5277d3151 --- /dev/null +++ b/Src/Modules/clone.mdd @@ -0,0 +1,3 @@ +autobins="clone" + +objects="clone.o" diff --git a/Src/Modules/example.c b/Src/Modules/example.c new file mode 100644 index 000000000..45ef3c28f --- /dev/null +++ b/Src/Modules/example.c @@ -0,0 +1,76 @@ +/* + * example.c - an example module for zsh + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Zoltán Hidvégi + * 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 Zoltán Hidvégi 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 Zoltán Hidvégi and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Zoltán Hidvégi 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 Zoltán Hidvégi and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "example.mdh" +#include "example.pro" + +/**/ +static int +bin_example(char *nam, char **args, char *ops, int func) +{ + unsigned char c; + + printf("Options: "); + for (c = 32; ++c < 128;) + if (ops[c]) + putchar(c); + printf("\nArguments:"); + for (; *args; args++) { + putchar(' '); + fputs(*args, stdout); + } + printf("\nName: %s\n", nam); + return 0; +} + +/* + * boot_example is executed when the module is loaded. + */ + +static struct builtin bintab[] = { + BUILTIN("example", 0, bin_example, 0, -1, 0, "flags", NULL), +}; + +/**/ +int +boot_example(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_example(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/example.mdd b/Src/Modules/example.mdd new file mode 100644 index 000000000..89f12097c --- /dev/null +++ b/Src/Modules/example.mdd @@ -0,0 +1,3 @@ +autobins="example" + +objects="example.o" diff --git a/Src/Modules/files.c b/Src/Modules/files.c new file mode 100644 index 000000000..6127c5524 --- /dev/null +++ b/Src/Modules/files.c @@ -0,0 +1,528 @@ +/* + * files.c - file operation builtins + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Andrew Main + * 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 Andrew Main 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 Andrew Main and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Andrew Main 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 Andrew Main and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "files.mdh" + +typedef int (*MoveFunc) _((char const *, char const *)); + +#ifndef STDC_HEADERS +extern int link _((const char *, const char *)); +extern int symlink _((const char *, const char *)); +extern int rename _((const char *, const char *)); +#endif + +#include "files.pro" + +/**/ +static int +ask(void) +{ + int a = getchar(), c; + for(c = a; c != EOF && c != '\n'; ) + c = getchar(); + return a == 'y' || a == 'Y'; +} + +/* sync builtin */ + +/**/ +static int +bin_sync(char *nam, char **args, char *ops, int func) +{ + sync(); + return 0; +} + +/* mkdir builtin */ + +/**/ +static int +bin_mkdir(char *nam, char **args, char *ops, int func) +{ + mode_t oumask = umask(0); + mode_t mode = 0777 & ~oumask; + int err = 0; + + umask(oumask); + if(ops['m']) { + char *str = *args++, *ptr; + + if(!*args) { + zwarnnam(nam, "not enough arguments", NULL, 0); + return 1; + } + mode = zstrtol(str, &ptr, 8); + if(!*str || *ptr) { + zwarnnam(nam, "invalid mode `%s'", str, 0); + return 1; + } + } + for(; *args; args++) { + char *ptr = strchr(*args, 0); + + while(ptr > *args + (**args == '/') && *--ptr == '/') + *ptr = 0; + if(ztrlen(*args) > PATH_MAX - 1) { + zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG); + err = 1; + continue; + } + if(ops['p']) { + char *ptr = *args; + + for(;;) { + while(*ptr == '/') + ptr++; + while(*ptr && *ptr != '/') + ptr++; + if(!*ptr) { + err |= domkdir(nam, *args, mode, 1); + break; + } else { + int e; + + *ptr = 0; + e = domkdir(nam, *args, mode | 0300, 1); + if(e) { + err = 1; + break; + } + *ptr = '/'; + } + } + } else + err |= domkdir(nam, *args, mode, 0); + } + return err; +} + +/**/ +static int +domkdir(char *nam, char *path, mode_t mode, int p) +{ + int err; + mode_t oumask; + char const *rpath = unmeta(path); + + if(p) { + struct stat st; + + if(!lstat(rpath, &st) && S_ISDIR(st.st_mode)) + return 0; + } + oumask = umask(0); + err = mkdir(path, mode) ? errno : 0; + umask(oumask); + if(!err) + return 0; + zwarnnam(nam, "cannot make directory `%s': %e", path, err); + return 1; +} + +/* rmdir builtin */ + +/**/ +static int +bin_rmdir(char *nam, char **args, char *ops, int func) +{ + int err = 0; + + for(; *args; args++) { + char *rpath = unmeta(*args); + + if(!rpath) { + zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG); + err = 1; + } else if(rmdir(rpath)) { + zwarnnam(nam, "cannot remove directory `%s': %e", *args, errno); + err = 1; + } + } + return err; +} + +/* ln and mv builtins */ + +#define BIN_LN 0 +#define BIN_MV 1 + +#define MV_NODIRS (1<<0) +#define MV_FORCE (1<<1) +#define MV_INTER (1<<2) +#define MV_ASKNW (1<<3) +#define MV_ATOMIC (1<<4) + +/* bin_ln actually does three related jobs: hard linking, symbolic * + * linking, and renaming. If called as mv it renames, otherwise * + * it looks at the -s option. If hard linking, it will refuse to * + * attempt linking to a directory unless the -d option is given. */ + +/**/ +static int +bin_ln(char *nam, char **args, char *ops, int func) +{ + MoveFunc move; + int flags, space, err = 0; + char **a, *ptr, *rp; + struct stat st; + char buf[PATH_MAX * 2 + 1]; + + + if(func == BIN_MV) { + move = rename; + flags = ops['f'] ? 0 : MV_ASKNW; + flags |= MV_ATOMIC; + } else { + flags = ops['f'] ? MV_FORCE : 0; +#ifdef HAVE_LSTAT + if(ops['s']) + move = symlink; + else +#endif + { + move = link; + if(!ops['d']) + flags |= MV_NODIRS; + } + } + if(ops['i'] && !ops['f']) + flags |= MV_INTER; + for(a = args; a[1]; a++) ; + if(a != args) { + rp = unmeta(*a); + if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode)) + goto havedir; + } + if(a > args+1) { + zwarnnam(nam, "last of many arguments must be a directory", NULL, 0); + return 1; + } + if(!args[1]) { + ptr = strrchr(args[0], '/'); + if(ptr) + args[1] = ptr+1; + else + args[1] = args[0]; + } + return domove(nam, move, args[0], args[1], flags); + havedir: + strcpy(buf, *a); + *a = NULL; + space = PATH_MAX - 1 - ztrlen(buf); + rp = strchr(buf, 0); + *rp++ = '/'; + for(; *args; args++) { + if(ztrlen(*args) > PATH_MAX) { + zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG); + err = 1; + continue; + } + ptr = strrchr(*args, '/'); + if(ptr) + ptr++; + else + ptr = *args; + if(ztrlen(ptr) > space) { + zwarnnam(nam, "%s: %e", ptr, ENAMETOOLONG); + err = 1; + continue; + } + strcpy(rp, ptr); + err |= domove(nam, move, *args, buf, flags); + } + return err; +} + +/**/ +static int +domove(char *nam, MoveFunc move, char *p, char *q, int flags) +{ + struct stat st; + char *qbuf; + char pbuf[PATH_MAX + 1]; + strcpy(pbuf, unmeta(p)); + qbuf = unmeta(q); + if(flags & MV_NODIRS) { + errno = EISDIR; + if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) { + zwarnnam(nam, "%s: %e", p, errno); + return 1; + } + } + if(!lstat(qbuf, &st)) { + int doit = flags & MV_FORCE; + if(S_ISDIR(st.st_mode)) { + zwarnnam(nam, "%s: cannot overwrite directory", q, 0); + return 1; + } else if(flags & MV_INTER) { + nicezputs(nam, stderr); + fputs(": replace `", stderr); + nicezputs(q, stderr); + fputs("'? ", stderr); + fflush(stderr); + if(!ask()) + return 0; + doit = 1; + } else if((flags & MV_ASKNW) && + !S_ISLNK(st.st_mode) && + access(qbuf, W_OK)) { + nicezputs(nam, stderr); + fputs(": replace `", stderr); + nicezputs(q, stderr); + fprintf(stderr, "', overriding mode %04o? ", + mode_to_octal(st.st_mode)); + fflush(stderr); + if(!ask()) + return 0; + doit = 1; + } + if(doit && !(flags & MV_ATOMIC)) + unlink(qbuf); + } + if(move(pbuf, qbuf)) { + zwarnnam(nam, "%s: %e", p, errno); + return 1; + } + return 0; +} + +/* rm builtin */ + +/**/ +static int +bin_rm(char *nam, char **args, char *ops, int func) +{ + int err = 0, len; + char *rp, *s; + struct dirsav ds; + + ds.ino = ds.dev = 0; + ds.dirname = NULL; + ds.dirfd = ds.level = -1; + if (ops['r'] || ops['s']) { + if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 && + zgetdir(&ds) && *ds.dirname != '/') + ds.dirfd = open("..", O_RDONLY|O_NOCTTY); + } + for(; !errflag && !(err & 2) && *args; args++) { + rp = ztrdup(*args); + unmetafy(rp, &len); + if (ops['s']) { + s = strrchr(rp, '/'); + if (s && !s[1]) { + while (*s == '/' && s > rp) + *s-- = '\0'; + while (*s != '/' && s > rp) + s--; + } + if (s && s[1]) { + int e; + + *s = '\0'; + e = lchdir(s > rp ? rp : "/", &ds, 1); + err |= -e; + if (!e) { + struct dirsav d; + + d.ino = d.dev = 0; + d.dirname = NULL; + d.dirfd = d.level = -1; + err |= dorm(nam, *args, s + 1, ops, &d, 0); + zsfree(d.dirname); + if (restoredir(&ds)) + err |= 2; + } else + zwarnnam(nam, "%s: %e", *args, errno); + } else + err |= dorm(nam, *args, rp, ops, &ds, 0); + } else + err |= dorm(nam, *args, rp, ops, &ds, 1); + zfree(rp, len + 1); + } + if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) { + zsfree(pwd); + pwd = ztrdup("/"); + chdir(pwd); + } + if (ds.dirfd >= 0) + close(ds.dirfd); + zsfree(ds.dirname); + return ops['f'] ? 0 : !!err; +} + +/**/ +static int +dorm(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first) +{ + struct stat st; + + if((!ops['d'] || !ops['f']) && !lstat(rp, &st)) { + if(!ops['d'] && S_ISDIR(st.st_mode)) { + if(ops['r']) + return dormr(nam, arg, rp, ops, ds, first); + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, EISDIR); + return 1; + } + if(!ops['f'] && ops['i']) { + nicezputs(nam, stderr); + fputs(": remove `", stderr); + nicezputs(arg, stderr); + fputs("'? ", stderr); + fflush(stderr); + if(!ask()) + return 0; + } else if(!ops['f'] && + !S_ISLNK(st.st_mode) && + access(rp, W_OK)) { + nicezputs(nam, stderr); + fputs(": remove `", stderr); + nicezputs(arg, stderr); + fprintf(stderr, "', overriding mode %04o? ", + mode_to_octal(st.st_mode)); + fflush(stderr); + if(!ask()) + return 0; + } + } + if(!unlink(rp)) + return 0; + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + return 1; +} + +/**/ +static int +dormr(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first) +{ + char *fn; + DIR *d; + int err; + struct dirsav dsav; + char *files = NULL; + int fileslen = 0; + + err = -lchdir(rp, ds, !first); + if (err) { + if (!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + return err; + } + + dsav.ino = dsav.dev = 0; + dsav.dirname = NULL; + dsav.dirfd = dsav.level = -1; + d = opendir("."); + if(!d) { + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + err = 1; + } else { + int arglen = strlen(arg) + 1; + + while (!errflag && (fn = zreaddir(d, 1))) { + int l = strlen(fn) + 1; + files = hrealloc(files, fileslen, fileslen + l); + strcpy(files + fileslen, fn); + fileslen += l; + } + closedir(d); + for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) { + int l = strlen(fn) + 1; + VARARR(char, narg, arglen + l); + + strcpy(narg,arg); + narg[arglen-1] = '/'; + strcpy(narg + arglen, fn); + unmetafy(fn, NULL); + err |= dorm(nam, narg, fn, ops, &dsav, 0); + fn += l; + } + hrealloc(files, fileslen, 0); + } + zsfree(dsav.dirname); + if (err & 2) + return 2; + if (restoredir(ds)) { + if(!ops['f']) + zwarnnam(nam, "failed to return to previous directory: %e", + NULL, errno); + return 2; + } + if(!ops['f'] && ops['i']) { + nicezputs(nam, stderr); + fputs(": remove `", stderr); + nicezputs(arg, stderr); + fputs("'? ", stderr); + fflush(stderr); + if(!ask()) + return err; + } + if(!rmdir(rp)) + return err; + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + return 1; +} + +/* module paraphernalia */ + +#ifdef HAVE_LSTAT +# define LN_OPTS "dfis" +#else +# define LN_OPTS "dfi" +#endif + +static struct builtin bintab[] = { + BUILTIN("ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL), + BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0, "pm", NULL), + BUILTIN("mv", 0, bin_ln, 2, -1, BIN_MV, "fi", NULL), + BUILTIN("rm", 0, bin_rm, 1, -1, 0, "dfirs", NULL), + BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0, NULL, NULL), + BUILTIN("sync", 0, bin_sync, 0, 0, 0, NULL, NULL), +}; + +/**/ +int +boot_files(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_files(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/files.mdd b/Src/Modules/files.mdd new file mode 100644 index 000000000..236ca2d5a --- /dev/null +++ b/Src/Modules/files.mdd @@ -0,0 +1,3 @@ +autobins="ln mkdir mv rm rmdir sync" + +objects="files.o" diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c new file mode 100644 index 000000000..09245b52f --- /dev/null +++ b/Src/Modules/stat.c @@ -0,0 +1,535 @@ +/* + * stat.c - stat builtin interface to system call + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 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. + * + */ + +#include "stat.mdh" +#include "stat.pro" + +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 }; +static char *statelts[] = { "device", "inode", "mode", "nlink", + "uid", "gid", "rdev", "size", "atime", + "mtime", "ctime", "blksize", "blocks", + "link", NULL }; + +/**/ +static void +statmodeprint(mode_t mode, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%lu", (unsigned long)mode); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { + static const char *modes = "?rwxrwxrwx"; + static const mode_t mflags[] = { S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IROTH, S_IWOTH, S_IXOTH }; + const mode_t *mfp = mflags; + char pm[11]; + int i; + + if (S_ISBLK(mode)) + *pm = 'b'; + else if (S_ISCHR(mode)) + *pm = 'c'; + else if (S_ISDIR(mode)) + *pm = 'd'; + else if (S_ISFIFO(mode)) + *pm = 'p'; + else if (S_ISLNK(mode)) + *pm = 'l'; + else if (S_ISMPC(mode)) + *pm = 'm'; + else if (S_ISNWK(mode)) + *pm = 'n'; + else if (S_ISOFD(mode)) + *pm = 'M'; + else if (S_ISOFL(mode)) + *pm = 'M'; + else if (S_ISREG(mode)) + *pm = '-'; + else if (S_ISSOCK(mode)) + *pm = 's'; + else + *pm = '?'; + + for (i = 1; i <= 9; i++) + pm[i] = (mode & *mfp++) ? modes[i] : '-'; + + if (mode & S_ISUID) + pm[3] = (mode & S_IXUSR) ? 's' : 'S'; + if (mode & S_ISGID) + pm[6] = (mode & S_IXGRP) ? 's' : 'S'; + if (mode & S_ISVTX) + pm[9] = (mode & S_IXOTH) ? 't' : 'T'; + + pm[10] = 0; + strcat(outbuf, pm); + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + + +/**/ +static void +statuidprint(uid_t uid, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%lu", (unsigned long)uid); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { +#ifdef HAVE_GETPWUID + struct passwd *pwd; + pwd = getpwuid(uid); + strcat(outbuf, pwd ? pwd->pw_name : "???"); +#else /* !HAVE_GETPWUID */ + strcat(outbuf, "???"); +#endif /* !HAVE_GETPWUID */ + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + + +/**/ +static void +statgidprint(gid_t gid, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%lu", (unsigned long)gid); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { +#ifdef HAVE_GETGRGID + struct group *gr; + gr = getgrgid(gid); + strcat(outbuf, gr ? gr->gr_name : "???"); +#else /* !HAVE_GETGRGID */ + strcat(outbuf, "???"); +#endif /* !HAVE_GETGRGID */ + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + +static char *timefmt; + +/**/ +static void +stattimeprint(time_t tim, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%ld", (unsigned long)tim); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { + char *oend = outbuf + strlen(outbuf); + ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) : + localtime(&tim)); + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + + +/**/ +static void +statulprint(unsigned long num, char *outbuf) +{ + sprintf(outbuf, "%lu", num); +} + + +/**/ +static void +statlinkprint(struct stat *sbuf, char *outbuf, char *fname) +{ + int num; + + /* fname is NULL if we are looking at an fd */ + if (fname && S_ISLNK(sbuf->st_mode) && + (num = readlink(fname, outbuf, PATH_MAX)) > 0) { + /* readlink doesn't terminate the buffer itself */ + outbuf[num] = '\0'; + } +} + + +/**/ +static void +statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags) +{ + char *optr = outbuf; + + if (flags & STF_NAME) { + sprintf(outbuf, (flags & (STF_PICK|STF_ARRAY)) ? + "%s " : "%-8s", statelts[iwhich]); + optr += strlen(outbuf); + } + *optr = '\0'; + + /* cast values to unsigned long as safest bet */ + switch (iwhich) { + case ST_DEV: + statulprint((unsigned long)sbuf->st_dev, optr); + break; + + case ST_INO: + statulprint((unsigned long)sbuf->st_ino, optr); + break; + + case ST_MODE: + statmodeprint(sbuf->st_mode, optr, flags); + break; + + case ST_NLINK: + statulprint((unsigned long)sbuf->st_nlink, optr); + break; + + case ST_UID: + statuidprint(sbuf->st_uid, optr, flags); + break; + + case ST_GID: + statgidprint(sbuf->st_gid, optr, flags); + break; + + case ST_RDEV: + statulprint((unsigned long)sbuf->st_rdev, optr); + break; + + case ST_SIZE: + statulprint((unsigned long)sbuf->st_size, optr); + break; + + case ST_ATIM: + stattimeprint(sbuf->st_atime, optr, flags); + break; + + case ST_MTIM: + stattimeprint(sbuf->st_mtime, optr, flags); + break; + + case ST_CTIM: + stattimeprint(sbuf->st_ctime, optr, flags); + break; + + case ST_BLKSIZE: + statulprint((unsigned long)sbuf->st_blksize, optr); + break; + + case ST_BLOCKS: + statulprint((unsigned long)sbuf->st_blocks, optr); + break; + + case ST_READLINK: + statlinkprint(sbuf, optr, fname); + break; + + case ST_COUNT: /* keep some compilers happy */ + break; + } +} + + +/* + * + * Options: + * -f fd: stat fd instead of file + * -g: use GMT rather than local time for time strings (forces -s on). + * -n: always print file name of file being statted + * -N: never print file name + * -l: list stat types + * -L: do lstat (else links are implicitly dereferenced by stat) + * -t: always print name of stat type + * -T: never print name of stat type + * -r: print raw alongside string data + * -s: string, print mode, times, uid, gid as appropriate strings: + * harmless but unnecessary when combined with -r. + * -A array: assign results to given array, one stat result per element. + * File names and type names are only added if explicitly requested: + * 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. + * -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. + * + * +type selects just element type of stat buffer (-l gives list): + * type can be shortened to unambiguous string. only one type is + * allowed. The extra type, +link, reads the target of a symbolic + * link; it is empty if the stat was not an lstat or if + * a file descriptor was stat'd, if the stat'd file is + * not a symbolic link, or if symbolic links are not + * supported. If +link is explicitly requested, the -L (lstat) + * option is set automatically. + */ +/**/ +static int +bin_stat(char *name, char **args, char *ops, int func) +{ + char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL; + int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0; + struct stat statbuf; + int found = 0, nargs; + + timefmt = "%a %b %e %k:%M:%S"; + + for (; *args && (**args == '+' || **args == '-'); args++) { + char *arg = *args+1; + if (!*arg || *arg == '-' || *arg == '+') { + args++; + break; + } + + if (**args == '+') { + if (found) + break; + len = strlen(arg); + for (aptr = statelts; *aptr; aptr++) + if (!strncmp(*aptr, arg, len)) { + found++; + iwhich = aptr - statelts; + } + if (found > 1) { + zerrnam(name, "%s: ambiguous stat element", arg, 0); + return 1; + } else if (found == 0) { + zerrnam(name, "%s: no such stat element", arg, 0); + return 1; + } + /* if name of link requested, turn on lstat */ + if (iwhich == ST_READLINK) + ops['L'] = 1; + flags |= STF_PICK; + } else { + for (; *arg; arg++) { + if (strchr("glLnNrstT", *arg)) + ops[*arg] = 1; + else if (*arg == 'A') { + if (arg[1]) { + arrnam = arg+1; + } else if (!(arrnam = *++args)) { + zerrnam(name, "missing parameter name\n", + NULL, 0); + return 1; + } + flags |= STF_ARRAY; + break; + } else if (*arg == 'f') { + char *sfd; + ops['f'] = 1; + if (arg[1]) { + sfd = arg+1; + } else if (!(sfd = *++args)) { + zerrnam(name, "missing file descriptor\n", NULL, 0); + return 1; + } + fd = zstrtol(sfd, &sfd, 10); + if (*sfd) { + zerrnam(name, "bad file descriptor\n", NULL, 0); + return 1; + } + break; + } else if (*arg == 'F') { + if (arg[1]) { + timefmt = arg+1; + } else if (!(timefmt = *++args)) { + zerrnam(name, "missing time format\n", NULL, 0); + return 1; + } + /* force string format in order to use time format */ + ops['s'] = 1; + break; + } else { + zerrnam(name, "bad option: -%c", NULL, *arg); + return 1; + } + } + } + } + + if (ops['l']) { + /* list types and return: can also list to array */ + if (arrnam) { + arrptr = array = (char **)zalloc((ST_COUNT+1)*sizeof(char *)); + array[ST_COUNT] = NULL; + } + for (aptr = statelts; *aptr; aptr++) { + if (arrnam) { + *arrptr++ = ztrdup(*aptr); + } else { + printf("%s", *aptr); + if (aptr[1]) + putchar(' '); + } + } + if (arrnam) { + setaparam(arrnam, array); + if (errflag) + return 1; + } else + putchar('\n'); + return 0; + } + + if (!*args && !ops['f']) { + zwarnnam(name, "no files given", NULL, 0); + return 1; + } else if (*args && ops['f']) { + zwarnnam(name, "no files allowed with -f", NULL, 0); + return 1; + } + + nargs = 0; + if (ops['f']) + nargs = 1; + else + for (aptr = args; *aptr; aptr++) + nargs++; + + if (ops['s'] || ops['r']) + flags |= STF_STRING; + if (ops['r'] || !ops['s']) + flags |= STF_RAW; + if (ops['n']) + flags |= STF_FILE; + if (ops['t']) + flags |= STF_NAME; + if (ops['g']) + flags |= STF_GMT; + + if (!arrnam) { + if (nargs > 1) + flags |= STF_FILE; + if (!(flags & STF_PICK)) + flags |= STF_NAME; + } + + if (ops['N'] || ops['f']) + flags &= ~STF_FILE; + if (ops['T']) + flags &= ~STF_NAME; + + if (arrnam) { + arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; + if (flags & STF_FILE) + arrsize++; + arrsize *= nargs; + arrptr = array = (char **)zcalloc((arrsize+1)*sizeof(char *)); + } + + for (; ops['f'] || *args; args++) { + char outbuf[PATH_MAX + 9]; /* "link " + link name + NULL */ + int rval = ops['f'] ? fstat(fd, &statbuf) : + ops['L'] ? lstat(*args, &statbuf) : stat(*args, &statbuf); + if (rval) { + if (ops['f']) + sprintf(outbuf, "%d", fd); + zwarnnam(name, "%s: %e", ops['f'] ? outbuf : *args, errno); + ret = 1; + if (ops['f'] || arrnam) + break; + else + continue; + } + + if (flags & STF_FILE) + if (arrnam) + *arrptr++ = 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 + printf("%s\n", outbuf); + } else { + int i; + for (i = 0; i < ST_COUNT; i++) { + statprint(&statbuf, outbuf, *args, i, flags); + if (arrnam) + *arrptr++= ztrdup(outbuf); + else + printf("%s\n", outbuf); + } + } + if (ops['f']) + break; + + if (!arrnam && args[1] && !(flags & STF_PICK)) + putchar('\n'); + } + + if (arrnam) + if (ret) { + for (aptr = array; *aptr; aptr++) + zsfree(*aptr); + zfree(array, arrsize+1); + } else { + setaparam(arrnam, array); + if (errflag) + return 1; + } + + return ret; +} + +static struct builtin bintab[] = { + BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_stat(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_stat(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#endif diff --git a/Src/Modules/stat.mdd b/Src/Modules/stat.mdd new file mode 100644 index 000000000..b775fda09 --- /dev/null +++ b/Src/Modules/stat.mdd @@ -0,0 +1,3 @@ +autobins="stat" + +objects="stat.o" 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 <sys/socket.h> +#include <netdb.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +/* it's a TELNET based protocol, but don't think I like doing this */ +#include <arpa/telnet.h> + +/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */ +#ifndef INADDR_NONE +#define INADDR_NONE (in_addr_t)-1 +#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" |