summaryrefslogtreecommitdiff
path: root/sys/src/cmd/ssh/scp.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /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-xsys/src/cmd/ssh/scp.c799
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);
+}