xref: /plan9/sys/src/cmd/ssh1/scp.c (revision 63afb9a5d3f910047231762bcce0ee49fed3d07c)
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 
5 int
isatty(int fd)6 isatty(int fd)
7 {
8 	char buf[64];
9 
10 	buf[0] = '\0';
11 	fd2path(fd, buf, sizeof buf);
12 	if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0)
13 		return 1;
14 	return 0;
15 }
16 
17 #define	OK	0x00
18 #define	ERROR	0x01
19 #define	FATAL	0x02
20 
21 char	*progname;
22 
23 int	dflag;
24 int	fflag;
25 int	iflag;
26 int	pflag;
27 int	rflag;
28 int	tflag;
29 int	vflag;
30 
31 int	remote;
32 
33 char	*exitflag = nil;
34 
35 void	scperror(int, char*, ...);
36 void	mustbedir(char*);
37 void	receive(char*);
38 char	*fileaftercolon(char*);
39 void	destislocal(char *cmd, int argc, char *argv[], char *dest);
40 void	destisremote(char *cmd, int argc, char *argv[], char *host, char *dest);
41 int	remotessh(char *host, char *cmd);
42 void	send(char*);
43 void	senddir(char*, int, Dir*);
44 int 	getresponse(void);
45 
46 char	theuser[32];
47 
48 char	ssh[] = "/bin/ssh";
49 
50 int	remotefd0;
51 int	remotefd1;
52 
53 int
runcommand(char * cmd)54 runcommand(char *cmd)
55 {
56 	Waitmsg *w;
57 	int pid;
58 	char *argv[4];
59 
60 	if (cmd == nil)
61 		return -1;
62 	switch(pid = fork()){
63 	case -1:
64 		return -1;
65 	case 0:
66 		argv[0] = "rc";
67 		argv[1] = "-c";
68 		argv[2] = cmd;
69 		argv[3] = nil;
70 		exec("/bin/rc", argv);
71 		exits("exec failed");
72 	}
73 	for(;;){
74 		w = wait();
75 		if(w == nil)
76 			return -1;
77 		if(w->pid == pid)
78 			break;
79 		free(w);
80 	}
81 	if(w->msg[0]){
82 		free(w);
83 		return -1;
84 	}
85 	free(w);
86 	return 1;
87 }
88 
89 void
vprint(char * fmt,...)90 vprint(char *fmt, ...)
91 {
92 	char buf[1024];
93 	va_list arg;
94 	static char *name;
95 
96 	if(vflag == 0)
97 		return;
98 
99 	va_start(arg, fmt);
100 	vseprint(buf, buf+sizeof(buf), fmt, arg);
101 	va_end(arg);
102 
103 	if(name == nil){
104 		name = sysname();
105 		if(name == nil)
106 			name = "<unknown>";
107 	}
108 	fprint(2, "%s: %s\n", name, buf);
109 }
110 
111 void
usage(void)112 usage(void)
113 {
114 	fprint(2, "Usage: scp [-Iidfprtv] source ... destination\n");
115 	exits("usage");
116 }
117 
118 
119 #pragma	varargck	type	"F"	int
120 #pragma	varargck	type	"V"	char*
121 static int flag;
122 
123 /* flag: if integer flag, take following char *value */
124 int
flagfmt(Fmt * f)125 flagfmt(Fmt *f)
126 {
127 	flag = va_arg(f->args, int);
128 	return 0;
129 }
130 
131 /* flag: if previous integer flag, take char *value */
132 int
valfmt(Fmt * f)133 valfmt(Fmt *f)
134 {
135 	char *value;
136 
137 	value = va_arg(f->args, char*);
138 	if(flag)
139 		return fmtprint(f, " %s", value);
140 	return 0;
141 }
142 
143 void
sendokresponse(void)144 sendokresponse(void)
145 {
146 	char ok = OK;
147 
148 	write(remotefd1, &ok, 1);
149 }
150 
151 void
main(int argc,char * argv[])152 main(int argc, char *argv[])
153 {
154 	int i, fd;
155 	char cmd[32];
156 	char *p;
157 
158 	progname = argv[0];
159 	fmtinstall('F', flagfmt);
160 	fmtinstall('V', valfmt);
161 	iflag = -1;
162 
163 	ARGBEGIN {
164 	case 'I':
165 		iflag = 0;
166 		break;
167 	case 'i':
168 		iflag = 1;
169 		break;
170 	case 'd':
171 		dflag++;
172 		break;
173 	case 'f':
174 		fflag++;
175 		remote++;
176 		break;
177 	case 'p':
178 		pflag++;
179 		break;
180 	case 'r':
181 		rflag++;
182 		break;
183 	case 't':
184 		tflag++;
185 		remote++;
186 		break;
187 	case 'v':
188 		vflag++;
189 		break;
190 	default:
191 		scperror(1, "unknown option %c", ARGC());
192 	} ARGEND
193 
194 	if(iflag == -1)
195 		iflag = isatty(0);
196 
197 	remotefd0 = 0;
198 	remotefd1 = 1;
199 
200 	if(fflag){
201 		getresponse();
202 		for(i=0; i<argc; i++)
203 			send(argv[i]);
204 		exits(0);
205 	}
206 	if(tflag){
207 		if(argc != 1)
208 			usage();
209 		receive(argv[0]);
210 		exits(0);
211 	}
212 
213 	if (argc < 2)
214 		usage();
215 	if (argc > 2)
216 		dflag = 1;
217 
218 	i = 0;
219 	fd = open("/dev/user", OREAD);
220 	if(fd >= 0){
221 		i = read(fd, theuser, sizeof theuser - 1);
222 		close(fd);
223 	}
224 	if(i <= 0)
225 		scperror(1, "can't read /dev/user: %r");
226 
227 	remotefd0 = -1;
228 	remotefd1 = -1;
229 
230 	snprint(cmd, sizeof cmd, "scp%F%V%F%V%F%V%F%V",
231 		dflag, "-d",
232 		pflag, "-p",
233 		rflag, "-r",
234 		vflag, "-v");
235 
236 	p = fileaftercolon(argv[argc-1]);
237 	if(p != nil)	/* send to remote machine. */
238 		destisremote(cmd, argc-1, argv, argv[argc-1], p);
239 	else{
240 		if(dflag)
241 			mustbedir(argv[argc-1]);
242 		destislocal(cmd, argc-1, argv, argv[argc-1]);
243 	}
244 
245 	exits(exitflag);
246 }
247 
248 void
destislocal(char * cmd,int argc,char * argv[],char * dst)249 destislocal(char *cmd, int argc, char *argv[], char *dst)
250 {
251 	int i;
252 	char *src;
253 	char buf[4096];
254 
255 	for(i = 0; i<argc; i++){
256 		src = fileaftercolon(argv[i]);
257 		if(src == nil){
258 			/* local file; no network */
259 			snprint(buf, sizeof buf, "exec cp%F%V%F%V %s %s",
260 				rflag, "-r",
261 				pflag, "-p",
262 				argv[i], dst);
263 	  		vprint("remotetolocal: %s", buf);
264 			if(runcommand(buf) < 0)
265 				exitflag = "local cp exec";
266 		}else{
267 			/* remote file; use network */
268 			snprint(buf, sizeof buf, "%s -f %s", cmd, src);
269 		  	if(remotessh(argv[i], buf) < 0)
270 				exitflag = "remote ssh exec";
271 			else{
272 				receive(dst);
273 				close(remotefd0);
274 				remotefd0 = -1;
275 				remotefd1 = -1;
276 			}
277 		}
278 	}
279 }
280 
281 void
destisremote(char * cmd,int argc,char * argv[],char * host,char * dest)282 destisremote(char *cmd, int argc, char *argv[], char *host, char *dest)
283 {
284 	int i;
285 	char *src;
286 	char buf[4096];
287 
288 	for(i = 0; i < argc; i++){
289 		vprint("remote destination: send %s to %s:%s", argv[i], host, dest);
290 		/* destination is remote, but source may be local */
291 		src = fileaftercolon(argv[i]);
292 		if(src != nil){
293 			/* remote to remote */
294 			snprint(buf, sizeof buf, "exec %s%F%V%F%V %s %s %s '%s:%s'",
295 				ssh,
296 				iflag, " -i",
297 				vflag, "-v",
298 				argv[i], cmd, src,
299 				host, dest);
300 			vprint("localtoremote: %s", buf);
301 			runcommand(buf);
302 		}else{
303 			/* local to remote */
304 			if(remotefd0 == -1){
305 				snprint(buf, sizeof buf, "%s -t %s", cmd, dest);
306 				if(remotessh(host, buf) < 0)
307 					exits("remotessh");
308 				if(getresponse() < 0)
309 					exits("bad response");
310 			}
311 			send(argv[i]);
312 		}
313 	}
314 }
315 
316 void
readhdr(char * p,int n)317 readhdr(char *p, int n)
318 {
319 	int i;
320 
321 	for(i=0; i<n; i++){
322 		if(read(remotefd0, &p[i], 1) != 1)
323 			break;
324 		if(p[i] == '\n'){
325 			p[i] = '\0';
326 			return;
327 		}
328 	}
329 	/* if at beginning, this is regular EOF */
330 	if(i == 0)
331 		exits(nil);
332 	scperror(1, "read error on receive header: %r");
333 }
334 
335 Dir *
receivedir(char * dir,int exists,Dir * d,int settimes,ulong atime,ulong mtime,ulong mode)336 receivedir(char *dir, int exists, Dir *d, int settimes, ulong atime, ulong mtime, ulong mode)
337 {
338 	Dir nd;
339 	int setmodes;
340 	int fd;
341 
342 	setmodes = pflag;
343 	if(exists){
344 		if(!(d->qid.type & QTDIR)) {
345 			scperror(0, "%s: protocol botch: directory requrest for non-directory", dir);
346 			return d;
347 		}
348 	}else{
349 		/* create it writeable; will fix later */
350 		setmodes = 1;
351 		fd = create(dir, OREAD, DMDIR|mode|0700);
352 		if (fd < 0){
353 			scperror(0, "%s: can't create: %r", dir);
354 			return d;
355 		}
356 		d = dirfstat(fd);
357 		close(fd);
358 		if(d == nil){
359 			scperror(0, "%s: can't stat: %r", dir);
360 			return d;
361 		}
362 	}
363 	receive(dir);
364 	if(settimes || setmodes){
365 		nulldir(&nd);
366 		if(settimes){
367 			nd.atime = atime;
368 			nd.mtime = mtime;
369 			d->atime = nd.atime;
370 			d->mtime = nd.mtime;
371 		}
372 		if(setmodes){
373 			nd.mode = DMDIR | (mode & 0777);
374 			d->mode = nd.mode;
375 		}
376 		if(dirwstat(dir, &nd) < 0){
377 			scperror(0, "can't wstat %s: %r", dir);
378 			free(d);
379 			return nil;
380 		}
381 	}
382 	return d;
383 }
384 
385 void
receive(char * dest)386 receive(char *dest)
387 {
388 	int isdir, settimes, mode;
389 	int exists, n, i, fd, m;
390 	int errors;
391 	ulong atime, mtime, size;
392 	char buf[8192], *p;
393 	char name[1024];
394 	Dir *d;
395 	Dir nd;
396 
397 	mtime = 0L;
398 	atime = 0L;
399 	settimes = 0;
400 	isdir = 0;
401 	if ((d = dirstat(dest)) && (d->qid.type & QTDIR)) {
402 		isdir = 1;
403 	}
404 	if(dflag && !isdir)
405 		scperror(1, "%s: not a directory: %r", dest);
406 
407 	sendokresponse();
408 
409 	for (;;) {
410 		readhdr(buf, sizeof buf);
411 
412 		switch(buf[0]){
413 		case ERROR:
414 		case FATAL:
415 			if(!remote)
416 				fprint(2, "%s\n", buf+1);
417 			exitflag = "bad receive";
418 			if(buf[0] == FATAL)
419 				exits(exitflag);
420 			continue;
421 
422 		case 'E':
423 			sendokresponse();
424 			return;
425 
426 		case 'T':
427 			settimes = 1;
428 			p = buf + 1;
429 			mtime = strtol(p, &p, 10);
430 			if(*p++ != ' '){
431 		Badtime:
432 				scperror(1, "bad time format: %s", buf+1);
433 			}
434 			strtol(p, &p, 10);
435 			if(*p++ != ' ')
436 				goto Badtime;
437 			atime = strtol(p, &p, 10);
438 			if(*p++ != ' ')
439 				goto Badtime;
440 			strtol(p, &p, 10);
441 			if(*p++ != 0)
442 				goto Badtime;
443 
444 			sendokresponse();
445 			continue;
446 
447 		case 'D':
448 		case 'C':
449 			p = buf + 1;
450 			mode = strtol(p, &p, 8);
451 			if (*p++ != ' '){
452 		Badmode:
453 				scperror(1, "bad mode/size format: %s", buf+1);
454 			}
455 			size = strtoll(p, &p, 10);
456 			if(*p++ != ' ')
457 				goto Badmode;
458 
459 			if(isdir){
460 				if(dest[0] == '\0')
461 					snprint(name, sizeof name, "%s", p);
462 				else
463 					snprint(name, sizeof name, "%s/%s", dest, p);
464 			}else
465 				snprint(name, sizeof name, "%s", dest);
466 			if(strlen(name) > sizeof name-UTFmax)
467 				scperror(1, "file name too long: %s", dest);
468 
469 			exists = 1;
470 			free(d);
471 			if((d = dirstat(name)) == nil)
472 				exists = 0;
473 
474 			if(buf[0] == 'D'){
475 				vprint("receive directory %s", name);
476 				d = receivedir(name, exists, d, settimes, atime, mtime, mode);
477 				settimes = 0;
478 				continue;
479 			}
480 
481 			vprint("receive file %s by %s", name, getuser());
482 			fd = create(name, OWRITE, mode);
483 			if(fd < 0){
484 				scperror(0, "can't create %s: %r", name);
485 				continue;
486 			}
487 			sendokresponse();
488 
489 			/*
490 			 * Committed to receive size bytes
491 			 */
492 			errors = 0;
493 			for(i = 0; i < size; i += m){
494 				n = sizeof buf;
495 				if(n > size - i)
496 					n = size - i;
497 				m = readn(remotefd0, buf, n);
498 				if(m <= 0)
499 					scperror(1, "read error on connection: %r");
500 				if(errors == 0){
501 					n = write(fd, buf, m);
502 					if(n != m)
503 						errors = 1;
504 				}
505 			}
506 
507 			/* if file exists, modes could be wrong */
508 			if(errors)
509 				scperror(0, "%s: write error: %r", name);
510 			else if(settimes || (exists && (d->mode&0777) != (mode&0777))){
511 				nulldir(&nd);
512 				if(settimes){
513 					settimes = 0;
514 					nd.atime = atime;
515 					nd.mtime = mtime;
516 				}
517 				if(exists && (d->mode&0777) != (mode&0777))
518 					nd.mode = (d->mode & ~0777) | (mode&0777);
519 				if(dirwstat(name, &nd) < 0)
520 					scperror(0, "can't wstat %s: %r", name);
521 			}
522 			free(d);
523 			d = nil;
524 			close(fd);
525 			getresponse();
526 			if(errors)
527 				exits("write error");
528 			sendokresponse();
529 			break;
530 
531 		default:
532 			scperror(0, "unrecognized header type char %c", buf[0]);
533 			scperror(1, "input line: %s", buf);
534 		}
535 	}
536 }
537 
538 /*
539  * Lastelem is called when we have a Dir with the final element, but if the file
540  * has been bound, we want the original name that was used rather than
541  * the contents of the stat buffer, so do this lexically.
542  */
543 char*
lastelem(char * file)544 lastelem(char *file)
545 {
546 	char *elem;
547 
548 	elem = strrchr(file, '/');
549 	if(elem == nil)
550 		return file;
551 	return elem+1;
552 }
553 
554 void
send(char * file)555 send(char *file)
556 {
557 	Dir *d;
558 	ulong i;
559 	int m, n, fd;
560 	char buf[8192];
561 
562 	if((fd = open(file, OREAD)) < 0){
563 		scperror(0, "can't open %s: %r", file);
564 		return;
565 	}
566 	if((d = dirfstat(fd)) == nil){
567 		scperror(0, "can't fstat %s: %r", file);
568 		goto Return;
569 	}
570 
571 	if(d->qid.type & QTDIR){
572 		if(rflag)
573 			senddir(file, fd, d);
574 		else
575 			scperror(0, "%s: is a directory", file);
576 		goto Return;
577 	}
578 
579 	if(pflag){
580 		fprint(remotefd1, "T%lud 0 %lud 0\n", d->mtime, d->atime);
581 		if(getresponse() < 0)
582 			goto Return;
583 	}
584 
585 	fprint(remotefd1, "C%.4luo %lld %s\n", d->mode&0777, d->length, lastelem(file));
586 	if(getresponse() < 0)
587 		goto Return;
588 
589 	/*
590 	 * We are now committed to send d.length bytes, regardless
591 	 */
592 	for(i=0; i<d->length; i+=m){
593 		n = sizeof buf;
594 		if(n > d->length - i)
595 			n = d->length - i;
596 		m = readn(fd, buf, n);
597 		if(m <= 0)
598 			break;
599 		write(remotefd1, buf, m);
600 	}
601 
602 	if(i == d->length)
603 		sendokresponse();
604 	else{
605 		/* continue to send gibberish up to d.length */
606 		for(; i<d->length; i+=n){
607 			n = sizeof buf;
608 			if(n > d->length - i)
609 				n = d->length - i;
610 			write(remotefd1, buf, n);
611 		}
612 		scperror(0, "%s: %r", file);
613 	}
614 
615 	getresponse();
616 
617     Return:
618 	free(d);
619 	close(fd);
620 }
621 
622 int
getresponse(void)623 getresponse(void)
624 {
625 	uchar first, byte, buf[256];
626 	int i;
627 
628 	if (read(remotefd0, &first, 1) != 1)
629 		scperror(1, "lost connection");
630 
631 	if(first == 0)
632 		return 0;
633 
634 	i = 0;
635 	if(first > FATAL){
636 		fprint(2, "scp: unexpected response character 0x%.2ux\n", first);
637 		buf[i++] = first;
638 	}
639 
640 	/* read error message up to newline */
641 	for(;;){
642 		if(read(remotefd0, &byte, 1) != 1)
643 			scperror(1, "response: dropped connection");
644 		if(byte == '\n')
645 			break;
646 		if(i < sizeof buf)
647 			buf[i++] = byte;
648 	}
649 
650 	exitflag = "bad response";
651 	if(!remote)
652 		fprint(2, "%.*s\n", utfnlen((char*)buf, i), (char*)buf);
653 
654 	if (first == ERROR)
655 		return -1;
656 	exits(exitflag);
657 	return 0;	/* not reached */
658 }
659 
660 void
senddir(char * name,int fd,Dir * dirp)661 senddir(char *name, int fd, Dir *dirp)
662 {
663 	Dir *d, *dir;
664 	int n;
665 	char file[256];
666 
667 	if(pflag){
668 		fprint(remotefd1, "T%lud 0 %lud 0\n", dirp->mtime, dirp->atime);
669 		if(getresponse() < 0)
670 			return;
671 	}
672 
673 	vprint("directory %s mode: D%.4lo %d %.1024s", name, dirp->mode&0777, 0, lastelem(name));
674 
675 	fprint(remotefd1, "D%.4lo %d %.1024s\n", dirp->mode&0777, 0, dirp->name);
676 	if(getresponse() < 0)
677 		return;
678 
679 	n = dirreadall(fd, &dir);
680 	for(d = dir; d < &dir[n]; d++){
681 		/* shouldn't happen with plan 9, but worth checking anyway */
682 		if(strcmp(d->name, ".")==0 || strcmp(d->name, "..")==0)
683 			continue;
684 		if(snprint(file, sizeof file, "%s/%s", name, d->name) > sizeof file-UTFmax){
685 			scperror(0, "%.20s.../%s: name too long; skipping file", file, d->name);
686 			continue;
687 		}
688 		send(file);
689 	}
690 	free(dir);
691 	fprint(remotefd1, "E\n");
692 	getresponse();
693 }
694 
695 int
remotessh(char * host,char * cmd)696 remotessh(char *host, char *cmd)
697 {
698 	int i, p[2];
699 	char *arg[32];
700 
701 	vprint("remotessh: %s: %s", host, cmd);
702 
703 	if(pipe(p) < 0)
704 		scperror(1, "pipe: %r");
705 
706 	switch(fork()){
707 	case -1:
708 		scperror(1, "fork: %r");
709 
710 	case 0:
711 		/* child */
712 		close(p[0]);
713 		dup(p[1], 0);
714 		dup(p[1], 1);
715 		for (i = 3; i < 100; i++)
716 			close(i);
717 
718 		i = 0;
719 		arg[i++] = ssh;
720 		arg[i++] = "-x";
721 		arg[i++] = "-a";
722 		arg[i++] = "-m";
723 		if(iflag)
724 			arg[i++] = "-i";
725 		if(vflag)
726 			arg[i++] = "-v";
727 		arg[i++] = host;
728 		arg[i++] = cmd;
729 		arg[i] = nil;
730 
731 		exec(ssh, arg);
732 		exits("exec failed");
733 
734 	default:
735 		/* parent */
736 		close(p[1]);
737 		remotefd0 = p[0];
738 		remotefd1 = p[0];
739 	}
740 	return 0;
741 }
742 
743 void
scperror(int exit,char * fmt,...)744 scperror(int exit, char *fmt, ...)
745 {
746 	char buf[2048];
747 	va_list arg;
748 
749 
750 	va_start(arg, fmt);
751 	vseprint(buf, buf+sizeof(buf), fmt, arg);
752 	va_end(arg);
753 
754 	fprint(remotefd1, "%cscp: %s\n", ERROR, buf);
755 
756 	if (!remote)
757 		fprint(2, "scp: %s\n", buf);
758 	exitflag = buf;
759 	if(exit)
760 		exits(exitflag);
761 }
762 
763 char *
fileaftercolon(char * file)764 fileaftercolon(char *file)
765 {
766 	char *c, *s;
767 
768 	c = utfrune(file, ':');
769 	if(c == nil)
770 		return nil;
771 
772 	/* colon must be in middle of name to be a separator */
773 	if(c == file)
774 		return nil;
775 
776 	/* does slash occur before colon? */
777 	s = utfrune(file, '/');
778 	if(s != nil && s < c)
779 		return nil;
780 
781 	*c++ = '\0';
782 	if(*c == '\0')
783 		return ".";
784 	return c;
785 }
786 
787 void
mustbedir(char * file)788 mustbedir(char *file)
789 {
790 	Dir *d;
791 
792 	if((d = dirstat(file)) == nil){
793 		scperror(1, "%s: %r", file);
794 		return;
795 	}
796 	if(!(d->qid.type & QTDIR))
797 		scperror(1, "%s: Not a directory", file);
798 	free(d);
799 }
800