diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ssh/scp.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ssh/scp.c')
-rwxr-xr-x | sys/src/cmd/ssh/scp.c | 799 |
1 files changed, 799 insertions, 0 deletions
diff --git a/sys/src/cmd/ssh/scp.c b/sys/src/cmd/ssh/scp.c new file mode 100755 index 000000000..bab66754a --- /dev/null +++ b/sys/src/cmd/ssh/scp.c @@ -0,0 +1,799 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> + +int +isatty(int fd) +{ + char buf[64]; + + buf[0] = '\0'; + fd2path(fd, buf, sizeof buf); + if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0) + return 1; + return 0; +} + +#define OK 0x00 +#define ERROR 0x01 +#define FATAL 0x02 + +char *progname; + +int dflag; +int fflag; +int iflag; +int pflag; +int rflag; +int tflag; +int vflag; + +int remote; + +char *exitflag = nil; + +void scperror(int, char*, ...); +void mustbedir(char*); +void receive(char*); +char *fileaftercolon(char*); +void destislocal(char *cmd, int argc, char *argv[], char *dest); +void destisremote(char *cmd, int argc, char *argv[], char *host, char *dest); +int remotessh(char *host, char *cmd); +void send(char*); +void senddir(char*, int, Dir*); +int getresponse(void); + +char theuser[32]; + +char ssh[] = "/bin/ssh"; + +int remotefd0; +int remotefd1; + +int +runcommand(char *cmd) +{ + Waitmsg *w; + int pid; + char *argv[4]; + + if (cmd == nil) + return -1; + switch(pid = fork()){ + case -1: + return -1; + case 0: + argv[0] = "rc"; + argv[1] = "-c"; + argv[2] = cmd; + argv[3] = nil; + exec("/bin/rc", argv); + exits("exec failed"); + } + for(;;){ + w = wait(); + if(w == nil) + return -1; + if(w->pid == pid) + break; + free(w); + } + if(w->msg[0]){ + free(w); + return -1; + } + free(w); + return 1; +} + +void +vprint(char *fmt, ...) +{ + char buf[1024]; + va_list arg; + static char *name; + + if(vflag == 0) + return; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + + if(name == nil){ + name = sysname(); + if(name == nil) + name = "<unknown>"; + } + fprint(2, "%s: %s\n", name, buf); +} + +void +usage(void) +{ + fprint(2, "Usage: scp [-Iidfprtv] source ... destination\n"); + exits("usage"); +} + + +#pragma varargck type "F" int +#pragma varargck type "V" char* +static int flag; + +/* flag: if integer flag, take following char *value */ +int +flagfmt(Fmt *f) +{ + flag = va_arg(f->args, int); + return 0; +} + +/* flag: if previous integer flag, take char *value */ +int +valfmt(Fmt *f) +{ + char *value; + + value = va_arg(f->args, char*); + if(flag) + return fmtprint(f, " %s", value); + return 0; +} + +void +sendokresponse(void) +{ + char ok = OK; + + write(remotefd1, &ok, 1); +} + +void +main(int argc, char *argv[]) +{ + int i, fd; + char cmd[32]; + char *p; + + progname = argv[0]; + fmtinstall('F', flagfmt); + fmtinstall('V', valfmt); + iflag = -1; + + ARGBEGIN { + case 'I': + iflag = 0; + break; + case 'i': + iflag = 1; + break; + case 'd': + dflag++; + break; + case 'f': + fflag++; + remote++; + break; + case 'p': + pflag++; + break; + case 'r': + rflag++; + break; + case 't': + tflag++; + remote++; + break; + case 'v': + vflag++; + break; + default: + scperror(1, "unknown option %c", ARGC()); + } ARGEND + + if(iflag == -1) + iflag = isatty(0); + + remotefd0 = 0; + remotefd1 = 1; + + if(fflag){ + getresponse(); + for(i=0; i<argc; i++) + send(argv[i]); + exits(0); + } + if(tflag){ + if(argc != 1) + usage(); + receive(argv[0]); + exits(0); + } + + if (argc < 2) + usage(); + if (argc > 2) + dflag = 1; + + i = 0; + fd = open("/dev/user", OREAD); + if(fd >= 0){ + i = read(fd, theuser, sizeof theuser - 1); + close(fd); + } + if(i <= 0) + scperror(1, "can't read /dev/user: %r"); + + remotefd0 = -1; + remotefd1 = -1; + + snprint(cmd, sizeof cmd, "scp%F%V%F%V%F%V%F%V", + dflag, "-d", + pflag, "-p", + rflag, "-r", + vflag, "-v"); + + p = fileaftercolon(argv[argc-1]); + if(p != nil) /* send to remote machine. */ + destisremote(cmd, argc-1, argv, argv[argc-1], p); + else{ + if(dflag) + mustbedir(argv[argc-1]); + destislocal(cmd, argc-1, argv, argv[argc-1]); + } + + exits(exitflag); +} + +void +destislocal(char *cmd, int argc, char *argv[], char *dst) +{ + int i; + char *src; + char buf[4096]; + + for(i = 0; i<argc; i++){ + src = fileaftercolon(argv[i]); + if(src == nil){ + /* local file; no network */ + snprint(buf, sizeof buf, "exec cp%F%V%F%V %s %s", + rflag, "-r", + pflag, "-p", + argv[i], dst); + vprint("remotetolocal: %s", buf); + if(runcommand(buf) < 0) + exitflag = "local cp exec"; + }else{ + /* remote file; use network */ + snprint(buf, sizeof buf, "%s -f %s", cmd, src); + if(remotessh(argv[i], buf) < 0) + exitflag = "remote ssh exec"; + else{ + receive(dst); + close(remotefd0); + remotefd0 = -1; + remotefd1 = -1; + } + } + } +} + +void +destisremote(char *cmd, int argc, char *argv[], char *host, char *dest) +{ + int i; + char *src; + char buf[4096]; + + for(i = 0; i < argc; i++){ + vprint("remote destination: send %s to %s:%s", argv[i], host, dest); + /* destination is remote, but source may be local */ + src = fileaftercolon(argv[i]); + if(src != nil){ + /* remote to remote */ + snprint(buf, sizeof buf, "exec %s%F%V%F%V %s %s %s '%s:%s'", + ssh, + iflag, " -i", + vflag, "-v", + argv[i], cmd, src, + host, dest); + vprint("localtoremote: %s", buf); + runcommand(buf); + }else{ + /* local to remote */ + if(remotefd0 == -1){ + snprint(buf, sizeof buf, "%s -t %s", cmd, dest); + if(remotessh(host, buf) < 0) + exits("remotessh"); + if(getresponse() < 0) + exits("bad response"); + } + send(argv[i]); + } + } +} + +void +readhdr(char *p, int n) +{ + int i; + + for(i=0; i<n; i++){ + if(read(remotefd0, &p[i], 1) != 1) + break; + if(p[i] == '\n'){ + p[i] = '\0'; + return; + } + } + /* if at beginning, this is regular EOF */ + if(i == 0) + exits(nil); + scperror(1, "read error on receive header: %r"); +} + +Dir * +receivedir(char *dir, int exists, Dir *d, int settimes, ulong atime, ulong mtime, ulong mode) +{ + Dir nd; + int setmodes; + int fd; + + setmodes = pflag; + if(exists){ + if(!(d->qid.type & QTDIR)) { + scperror(0, "%s: protocol botch: directory requrest for non-directory", dir); + return d; + } + }else{ + /* create it writeable; will fix later */ + setmodes = 1; + fd = create(dir, OREAD, DMDIR|mode|0700); + if (fd < 0){ + scperror(0, "%s: can't create: %r", dir); + return d; + } + d = dirfstat(fd); + close(fd); + if(d == nil){ + scperror(0, "%s: can't stat: %r", dir); + return d; + } + } + receive(dir); + if(settimes || setmodes){ + nulldir(&nd); + if(settimes){ + nd.atime = atime; + nd.mtime = mtime; + d->atime = nd.atime; + d->mtime = nd.mtime; + } + if(setmodes){ + nd.mode = DMDIR | (mode & 0777); + d->mode = nd.mode; + } + if(dirwstat(dir, &nd) < 0){ + scperror(0, "can't wstat %s: %r", dir); + free(d); + return nil; + } + } + return d; +} + +void +receive(char *dest) +{ + int isdir, settimes, mode; + int exists, n, i, fd, m; + int errors; + ulong atime, mtime, size; + char buf[8192], *p; + char name[1024]; + Dir *d; + Dir nd; + + mtime = 0L; + atime = 0L; + settimes = 0; + isdir = 0; + if ((d = dirstat(dest)) && (d->qid.type & QTDIR)) { + isdir = 1; + } + if(dflag && !isdir) + scperror(1, "%s: not a directory: %r", dest); + + sendokresponse(); + + for (;;) { + readhdr(buf, sizeof buf); + + switch(buf[0]){ + case ERROR: + case FATAL: + if(!remote) + fprint(2, "%s\n", buf+1); + exitflag = "bad receive"; + if(buf[0] == FATAL) + exits(exitflag); + continue; + + case 'E': + sendokresponse(); + return; + + case 'T': + settimes = 1; + p = buf + 1; + mtime = strtol(p, &p, 10); + if(*p++ != ' '){ + Badtime: + scperror(1, "bad time format: %s", buf+1); + } + strtol(p, &p, 10); + if(*p++ != ' ') + goto Badtime; + atime = strtol(p, &p, 10); + if(*p++ != ' ') + goto Badtime; + strtol(p, &p, 10); + if(*p++ != 0) + goto Badtime; + + sendokresponse(); + continue; + + case 'D': + case 'C': + p = buf + 1; + mode = strtol(p, &p, 8); + if (*p++ != ' '){ + Badmode: + scperror(1, "bad mode/size format: %s", buf+1); + } + size = strtoll(p, &p, 10); + if(*p++ != ' ') + goto Badmode; + + if(isdir){ + if(dest[0] == '\0') + snprint(name, sizeof name, "%s", p); + else + snprint(name, sizeof name, "%s/%s", dest, p); + }else + snprint(name, sizeof name, "%s", dest); + if(strlen(name) > sizeof name-UTFmax) + scperror(1, "file name too long: %s", dest); + + exists = 1; + free(d); + if((d = dirstat(name)) == nil) + exists = 0; + + if(buf[0] == 'D'){ + vprint("receive directory %s", name); + d = receivedir(name, exists, d, settimes, atime, mtime, mode); + settimes = 0; + continue; + } + + vprint("receive file %s by %s", name, getuser()); + fd = create(name, OWRITE, mode); + if(fd < 0){ + scperror(0, "can't create %s: %r", name); + continue; + } + sendokresponse(); + + /* + * Committed to receive size bytes + */ + errors = 0; + for(i = 0; i < size; i += m){ + n = sizeof buf; + if(n > size - i) + n = size - i; + m = readn(remotefd0, buf, n); + if(m <= 0) + scperror(1, "read error on connection: %r"); + if(errors == 0){ + n = write(fd, buf, m); + if(n != m) + errors = 1; + } + } + + /* if file exists, modes could be wrong */ + if(errors) + scperror(0, "%s: write error: %r", name); + else if(settimes || (exists && (d->mode&0777) != (mode&0777))){ + nulldir(&nd); + if(settimes){ + settimes = 0; + nd.atime = atime; + nd.mtime = mtime; + } + if(exists && (d->mode&0777) != (mode&0777)) + nd.mode = (d->mode & ~0777) | (mode&0777); + if(dirwstat(name, &nd) < 0) + scperror(0, "can't wstat %s: %r", name); + } + free(d); + d = nil; + close(fd); + getresponse(); + if(errors) + exits("write error"); + sendokresponse(); + break; + + default: + scperror(0, "unrecognized header type char %c", buf[0]); + scperror(1, "input line: %s", buf); + } + } +} + +/* + * Lastelem is called when we have a Dir with the final element, but if the file + * has been bound, we want the original name that was used rather than + * the contents of the stat buffer, so do this lexically. + */ +char* +lastelem(char *file) +{ + char *elem; + + elem = strrchr(file, '/'); + if(elem == nil) + return file; + return elem+1; +} + +void +send(char *file) +{ + Dir *d; + ulong i; + int m, n, fd; + char buf[8192]; + + if((fd = open(file, OREAD)) < 0){ + scperror(0, "can't open %s: %r", file); + return; + } + if((d = dirfstat(fd)) == nil){ + scperror(0, "can't fstat %s: %r", file); + goto Return; + } + + if(d->qid.type & QTDIR){ + if(rflag) + senddir(file, fd, d); + else + scperror(0, "%s: is a directory", file); + goto Return; + } + + if(pflag){ + fprint(remotefd1, "T%lud 0 %lud 0\n", d->mtime, d->atime); + if(getresponse() < 0) + goto Return; + } + + fprint(remotefd1, "C%.4luo %lld %s\n", d->mode&0777, d->length, lastelem(file)); + if(getresponse() < 0) + goto Return; + + /* + * We are now committed to send d.length bytes, regardless + */ + for(i=0; i<d->length; i+=m){ + n = sizeof buf; + if(n > d->length - i) + n = d->length - i; + m = readn(fd, buf, n); + if(m <= 0) + break; + write(remotefd1, buf, m); + } + + if(i == d->length) + sendokresponse(); + else{ + /* continue to send gibberish up to d.length */ + for(; i<d->length; i+=n){ + n = sizeof buf; + if(n > d->length - i) + n = d->length - i; + write(remotefd1, buf, n); + } + scperror(0, "%s: %r", file); + } + + getresponse(); + + Return: + free(d); + close(fd); +} + +int +getresponse(void) +{ + uchar first, byte, buf[256]; + int i; + + if (read(remotefd0, &first, 1) != 1) + scperror(1, "lost connection"); + + if(first == 0) + return 0; + + i = 0; + if(first > FATAL){ + fprint(2, "scp: unexpected response character 0x%.2ux\n", first); + buf[i++] = first; + } + + /* read error message up to newline */ + for(;;){ + if(read(remotefd0, &byte, 1) != 1) + scperror(1, "response: dropped connection"); + if(byte == '\n') + break; + if(i < sizeof buf) + buf[i++] = byte; + } + + exitflag = "bad response"; + if(!remote) + fprint(2, "%.*s\n", utfnlen((char*)buf, i), (char*)buf); + + if (first == ERROR) + return -1; + exits(exitflag); + return 0; /* not reached */ +} + +void +senddir(char *name, int fd, Dir *dirp) +{ + Dir *d, *dir; + int n; + char file[256]; + + if(pflag){ + fprint(remotefd1, "T%lud 0 %lud 0\n", dirp->mtime, dirp->atime); + if(getresponse() < 0) + return; + } + + vprint("directory %s mode: D%.4lo %d %.1024s", name, dirp->mode&0777, 0, lastelem(name)); + + fprint(remotefd1, "D%.4lo %d %.1024s\n", dirp->mode&0777, 0, dirp->name); + if(getresponse() < 0) + return; + + n = dirreadall(fd, &dir); + for(d = dir; d < &dir[n]; d++){ + /* shouldn't happen with plan 9, but worth checking anyway */ + if(strcmp(d->name, ".")==0 || strcmp(d->name, "..")==0) + continue; + if(snprint(file, sizeof file, "%s/%s", name, d->name) > sizeof file-UTFmax){ + scperror(0, "%.20s.../%s: name too long; skipping file", file, d->name); + continue; + } + send(file); + } + free(dir); + fprint(remotefd1, "E\n"); + getresponse(); +} + +int +remotessh(char *host, char *cmd) +{ + int i, p[2]; + char *arg[32]; + + vprint("remotessh: %s: %s", host, cmd); + + if(pipe(p) < 0) + scperror(1, "pipe: %r"); + + switch(fork()){ + case -1: + scperror(1, "fork: %r"); + + case 0: + /* child */ + close(p[0]); + dup(p[1], 0); + dup(p[1], 1); + for (i = 3; i < 100; i++) + close(i); + + i = 0; + arg[i++] = ssh; + arg[i++] = "-x"; + arg[i++] = "-a"; + arg[i++] = "-m"; + if(iflag) + arg[i++] = "-i"; + if(vflag) + arg[i++] = "-v"; + arg[i++] = host; + arg[i++] = cmd; + arg[i] = nil; + + exec(ssh, arg); + exits("exec failed"); + + default: + /* parent */ + close(p[1]); + remotefd0 = p[0]; + remotefd1 = p[0]; + } + return 0; +} + +void +scperror(int exit, char *fmt, ...) +{ + char buf[2048]; + va_list arg; + + + va_start(arg, fmt); + vseprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + + fprint(remotefd1, "%cscp: %s\n", ERROR, buf); + + if (!remote) + fprint(2, "scp: %s\n", buf); + exitflag = buf; + if(exit) + exits(exitflag); +} + +char * +fileaftercolon(char *file) +{ + char *c, *s; + + c = utfrune(file, ':'); + if(c == nil) + return nil; + + /* colon must be in middle of name to be a separator */ + if(c == file) + return nil; + + /* does slash occur before colon? */ + s = utfrune(file, '/'); + if(s != nil && s < c) + return nil; + + *c++ = '\0'; + if(*c == '\0') + return "."; + return c; +} + +void +mustbedir(char *file) +{ + Dir *d; + + if((d = dirstat(file)) == nil){ + scperror(1, "%s: %r", file); + return; + } + if(!(d->qid.type & QTDIR)) + scperror(1, "%s: Not a directory", file); + free(d); +} |