xref: /plan9/sys/src/cmd/ssh1/ssh1.c (revision 58da3067adcdccaaa043d0bfde28ba83b7ced07d)
1 /* remote login via ssh v1 */
2 #include "ssh.h"
3 
4 int cooked = 0;		/* user wants cooked mode */
5 int raw = 0;		/* console is in raw mode */
6 int crstrip;
7 int interactive = -1;
8 int usemenu = 1;
9 int isatty(int);
10 int rawhack;
11 int forwardagent = 0;
12 char *buildcmd(int, char**);
13 void fromnet(Conn*);
14 void fromstdin(Conn*);
15 void winchanges(Conn*);
16 static void	sendwritemsg(Conn *c, char *buf, int n);
17 
18 /*
19  * Lifted from telnet.c, con.c
20  */
21 static int consctl = -1;
22 static int outfd = 1;			/* changed during system */
23 static void system(Conn*, char*);
24 
25 Cipher *allcipher[] = {
26 	&cipherrc4,
27 	&cipherblowfish,
28 	&cipher3des,
29 	&cipherdes,
30 	&ciphernone,
31 	&ciphertwiddle,
32 };
33 
34 Auth *allauth[] = {
35 	&authpassword,
36 	&authrsa,
37 	&authtis,
38 };
39 
40 char *cipherlist = "blowfish rc4 3des";
41 char *authlist = "rsa password tis";
42 
43 Cipher*
findcipher(char * name,Cipher ** list,int nlist)44 findcipher(char *name, Cipher **list, int nlist)
45 {
46 	int i;
47 
48 	for(i=0; i<nlist; i++)
49 		if(strcmp(name, list[i]->name) == 0)
50 			return list[i];
51 	error("unknown cipher %s", name);
52 	return nil;
53 }
54 
55 Auth*
findauth(char * name,Auth ** list,int nlist)56 findauth(char *name, Auth **list, int nlist)
57 {
58 	int i;
59 
60 	for(i=0; i<nlist; i++)
61 		if(strcmp(name, list[i]->name) == 0)
62 			return list[i];
63 	error("unknown auth %s", name);
64 	return nil;
65 }
66 
67 void
usage(void)68 usage(void)
69 {
70 	fprint(2, "usage: ssh [-CiImPpRr] [-A authlist] [-c cipherlist] [user@]hostname [cmd [args]]\n");
71 	exits("usage");
72 }
73 
74 void
main(int argc,char ** argv)75 main(int argc, char **argv)
76 {
77 	int i, dowinchange, fd, usepty;
78 	char *host, *cmd, *user, *p;
79 	char *f[16];
80 	Conn c;
81 	Msg *m;
82 
83 	fmtinstall('B', mpfmt);
84 	fmtinstall('H', encodefmt);
85 	atexit(atexitkiller);
86 	atexitkill(getpid());
87 
88 	dowinchange = 0;
89 	if(getenv("LINES"))
90 		dowinchange = 1;
91 	usepty = -1;
92 	user = nil;
93 	ARGBEGIN{
94 	case 'B':	/* undocumented, debugging */
95 		doabort = 1;
96 		break;
97 	case 'D':	/* undocumented, debugging */
98 		debuglevel = strtol(EARGF(usage()), nil, 0);
99 		break;
100 	case 'l':	/* deprecated */
101 	case 'u':
102 		user = EARGF(usage());
103 		break;
104 	case 'a':	/* used by Unix scp implementations; we must ignore them. */
105 	case 'x':
106 		break;
107 
108 	case 'A':
109 		authlist = EARGF(usage());
110 		break;
111 	case 'C':
112 		cooked = 1;
113 		break;
114 	case 'c':
115 		cipherlist = EARGF(usage());
116 		break;
117 	case 'f':
118 		forwardagent = 1;
119 		break;
120 	case 'I':
121 		interactive = 0;
122 		break;
123 	case 'i':
124 		interactive = 1;
125 		break;
126 	case 'm':
127 		usemenu = 0;
128 		break;
129 	case 'P':
130 		usepty = 0;
131 		break;
132 	case 'p':
133 		usepty = 1;
134 		break;
135 	case 'R':
136 		rawhack = 1;
137 		break;
138 	case 'r':
139 		crstrip = 1;
140 		break;
141 	default:
142 		usage();
143 	}ARGEND
144 
145 	if(argc < 1)
146 		usage();
147 
148 	host = argv[0];
149 
150 	cmd = nil;
151 	if(argc > 1)
152 		cmd = buildcmd(argc-1, argv+1);
153 
154 	if((p = strchr(host, '@')) != nil){
155 		*p++ = '\0';
156 		user = host;
157 		host = p;
158 	}
159 	if(user == nil)
160 		user = getenv("user");
161 	if(user == nil)
162 		sysfatal("cannot find user name");
163 
164 	privatefactotum();
165 	if(interactive==-1)
166 		interactive = isatty(0);
167 
168 	if((fd = dial(netmkaddr(host, "tcp", "ssh"), nil, nil, nil)) < 0)
169 		sysfatal("dialing %s: %r", host);
170 
171 	memset(&c, 0, sizeof c);
172 	c.interactive = interactive;
173 	c.fd[0] = c.fd[1] = fd;
174 	c.user = user;
175 	c.host = host;
176 	setaliases(&c, host);
177 
178 	c.nokcipher = getfields(cipherlist, f, nelem(f), 1, ", ");
179 	c.okcipher = emalloc(sizeof(Cipher*)*c.nokcipher);
180 	for(i=0; i<c.nokcipher; i++)
181 		c.okcipher[i] = findcipher(f[i], allcipher, nelem(allcipher));
182 
183 	c.nokauth = getfields(authlist, f, nelem(f), 1, ", ");
184 	c.okauth = emalloc(sizeof(Auth*)*c.nokauth);
185 	for(i=0; i<c.nokauth; i++)
186 		c.okauth[i] = findauth(f[i], allauth, nelem(allauth));
187 
188 	sshclienthandshake(&c);
189 
190 	if(forwardagent){
191 		if(startagent(&c) < 0)
192 			forwardagent = 0;
193 	}
194 	if(usepty == -1)
195 		usepty = cmd==nil;
196 	if(usepty)
197 		requestpty(&c);
198 	if(cmd){
199 		m = allocmsg(&c, SSH_CMSG_EXEC_CMD, 4+strlen(cmd));
200 		putstring(m, cmd);
201 	}else
202 		m = allocmsg(&c, SSH_CMSG_EXEC_SHELL, 0);
203 	sendmsg(m);
204 
205 	fromstdin(&c);
206 	rfork(RFNOTEG);	/* only fromstdin gets notes */
207 	if(dowinchange)
208 		winchanges(&c);
209 	fromnet(&c);
210 	exits(0);
211 }
212 
213 int
isatty(int fd)214 isatty(int fd)
215 {
216 	char buf[64];
217 
218 	buf[0] = '\0';
219 	fd2path(fd, buf, sizeof buf);
220 	if(strlen(buf)>=9 && strcmp(buf+strlen(buf)-9, "/dev/cons")==0)
221 		return 1;
222 	return 0;
223 }
224 
225 char*
buildcmd(int argc,char ** argv)226 buildcmd(int argc, char **argv)
227 {
228 	int i, len;
229 	char *s, *t;
230 
231 	len = argc-1;
232 	for(i=0; i<argc; i++)
233 		len += strlen(argv[i]);
234 	s = emalloc(len+1);
235 	t = s;
236 	for(i=0; i<argc; i++){
237 		if(i)
238 			*t++ = ' ';
239 		strcpy(t, argv[i]);
240 		t += strlen(t);
241 	}
242 	return s;
243 }
244 
245 
246 void
fromnet(Conn * c)247 fromnet(Conn *c)
248 {
249 	int fd, len;
250 	char *s, *es, *r, *w;
251 	ulong ex;
252 	char buf[64];
253 	Msg *m;
254 
255 	for(;;){
256 		m = recvmsg(c, -1);
257 		if(m == nil)
258 			break;
259 		switch(m->type){
260 		default:
261 			badmsg(m, 0);
262 
263 		case SSH_SMSG_EXITSTATUS:
264 			ex = getlong(m);
265 			if(ex==0)
266 				exits(0);
267 			sprint(buf, "%lud", ex);
268 			exits(buf);
269 
270 		case SSH_MSG_DISCONNECT:
271 			s = getstring(m);
272 			error("disconnect: %s", s);
273 
274 		/*
275 		 * If we ever add reverse port forwarding, we'll have to
276 		 * revisit this.  It assumes that the agent connections are
277 		 * the only ones.
278 		 */
279 		case SSH_SMSG_AGENT_OPEN:
280 			if(!forwardagent)
281 				error("server tried to use agent forwarding");
282 			handleagentopen(m);
283 			break;
284 		case SSH_MSG_CHANNEL_INPUT_EOF:
285 			if(!forwardagent)
286 				error("server tried to use agent forwarding");
287 			handleagentieof(m);
288 			break;
289 		case SSH_MSG_CHANNEL_OUTPUT_CLOSED:
290 			if(!forwardagent)
291 				error("server tried to use agent forwarding");
292 			handleagentoclose(m);
293 			break;
294 		case SSH_MSG_CHANNEL_DATA:
295 			if(!forwardagent)
296 				error("server tried to use agent forwarding");
297 			handleagentmsg(m);
298 			break;
299 
300 		case SSH_SMSG_STDOUT_DATA:
301 			fd = outfd;
302 			goto Dataout;
303 		case SSH_SMSG_STDERR_DATA:
304 			fd = 2;
305 			goto Dataout;
306 		Dataout:
307 			len = getlong(m);
308 			s = (char*)getbytes(m, len);
309 			if(crstrip){
310 				es = s+len;
311 				for(r=w=s; r<es; r++)
312 					if(*r != '\r')
313 						*w++ = *r;
314 				len = w-s;
315 			}
316 			write(fd, s, len);
317 			break;
318 		}
319 		free(m);
320 	}
321 }
322 
323 /*
324  *  turn keyboard raw mode on
325  */
326 static void
rawon(void)327 rawon(void)
328 {
329 	if(raw)
330 		return;
331 	if(cooked)
332 		return;
333 	if(consctl < 0)
334 		consctl = open("/dev/consctl", OWRITE);
335 	if(consctl < 0)
336 		return;
337 	if(write(consctl, "rawon", 5) != 5)
338 		return;
339 	raw = 1;
340 }
341 
342 /*
343  *  turn keyboard raw mode off
344  */
345 static void
rawoff(void)346 rawoff(void)
347 {
348 	if(raw == 0)
349 		return;
350 	if(consctl < 0)
351 		return;
352 	if(write(consctl, "rawoff", 6) != 6)
353 		return;
354 	close(consctl);
355 	consctl = -1;
356 	raw = 0;
357 }
358 
359 /*
360  *  control menu
361  */
362 #define STDHELP	"\t(q)uit, (i)nterrupt, toggle printing (r)eturns, (.)continue, (!cmd)\n"
363 
364 static int
menu(Conn * c)365 menu(Conn *c)
366 {
367 	char buf[1024];
368 	long n;
369 	int done;
370 	int wasraw;
371 
372 	wasraw = raw;
373 	if(wasraw)
374 		rawoff();
375 
376 	buf[0] = '?';
377 	fprint(2, ">>> ");
378 	for(done = 0; !done; ){
379 		n = read(0, buf, sizeof(buf)-1);
380 		if(n <= 0)
381 			return -1;
382 		buf[n] = 0;
383 		switch(buf[0]){
384 		case '!':
385 			print(buf);
386 			system(c, buf+1);
387 			print("!\n");
388 			done = 1;
389 			break;
390 		case 'i':
391 			buf[0] = 0x1c;
392 			sendwritemsg(c, buf, 1);
393 			done = 1;
394 			break;
395 		case '.':
396 		case 'q':
397 			done = 1;
398 			break;
399 		case 'r':
400 			crstrip = 1-crstrip;
401 			done = 1;
402 			break;
403 		default:
404 			fprint(2, STDHELP);
405 			break;
406 		}
407 		if(!done)
408 			fprint(2, ">>> ");
409 	}
410 
411 	if(wasraw)
412 		rawon();
413 	else
414 		rawoff();
415 	return buf[0];
416 }
417 
418 static void
sendwritemsg(Conn * c,char * buf,int n)419 sendwritemsg(Conn *c, char *buf, int n)
420 {
421 	Msg *m;
422 
423 	if(n==0)
424 		m = allocmsg(c, SSH_CMSG_EOF, 0);
425 	else{
426 		m = allocmsg(c, SSH_CMSG_STDIN_DATA, 4+n);
427 		putlong(m, n);
428 		putbytes(m, buf, n);
429 	}
430 	sendmsg(m);
431 }
432 
433 /*
434  *  run a command with the network connection as standard IO
435  */
436 static void
system(Conn * c,char * cmd)437 system(Conn *c, char *cmd)
438 {
439 	int pid;
440 	int p;
441 	int pfd[2];
442 	int n;
443 	int wasconsctl;
444 	char buf[4096];
445 
446 	if(pipe(pfd) < 0){
447 		perror("pipe");
448 		return;
449 	}
450 	outfd = pfd[1];
451 
452 	wasconsctl = consctl;
453 	close(consctl);
454 	consctl = -1;
455 	switch(pid = fork()){
456 	case -1:
457 		perror("con");
458 		return;
459 	case 0:
460 		close(pfd[1]);
461 		dup(pfd[0], 0);
462 		dup(pfd[0], 1);
463 		close(c->fd[0]);	/* same as c->fd[1] */
464 		close(pfd[0]);
465 		if(*cmd)
466 			execl("/bin/rc", "rc", "-c", cmd, nil);
467 		else
468 			execl("/bin/rc", "rc", nil);
469 		perror("con");
470 		exits("exec");
471 		break;
472 	default:
473 		close(pfd[0]);
474 		while((n = read(pfd[1], buf, sizeof(buf))) > 0)
475 			sendwritemsg(c, buf, n);
476 		p = waitpid();
477 		outfd = 1;
478 		close(pfd[1]);
479 		if(p < 0 || p != pid)
480 			return;
481 		break;
482 	}
483 	if(wasconsctl >= 0){
484 		consctl = open("/dev/consctl", OWRITE);
485 		if(consctl < 0)
486 			error("cannot open consctl");
487 	}
488 }
489 
490 static void
cookedcatchint(void *,char * msg)491 cookedcatchint(void*, char *msg)
492 {
493 	if(strstr(msg, "interrupt"))
494 		noted(NCONT);
495 	else if(strstr(msg, "kill"))
496 		noted(NDFLT);
497 	else
498 		noted(NCONT);
499 }
500 
501 static int
wasintr(void)502 wasintr(void)
503 {
504 	char err[64];
505 
506 	rerrstr(err, sizeof err);
507 	return strstr(err, "interrupt") != 0;
508 }
509 
510 void
fromstdin(Conn * c)511 fromstdin(Conn *c)
512 {
513 	int n;
514 	char buf[1024];
515 	int pid;
516 	int eofs;
517 
518 	switch(pid = rfork(RFMEM|RFPROC|RFNOWAIT)){
519 	case -1:
520 		error("fork: %r");
521 	case 0:
522 		break;
523 	default:
524 		atexitkill(pid);
525 		return;
526 	}
527 
528 	atexit(atexitkiller);
529 	if(interactive)
530 		rawon();
531 
532 	notify(cookedcatchint);
533 
534 	eofs = 0;
535 	for(;;){
536 		n = read(0, buf, sizeof(buf));
537 		if(n < 0){
538 			if(wasintr()){
539 				if(!raw){
540 					buf[0] = 0x7f;
541 					n = 1;
542 				}else
543 					continue;
544 			}else
545 				break;
546 		}
547 		if(n == 0){
548 			if(!c->interactive || ++eofs > 32)
549 				break;
550 		}else
551 			eofs = 0;
552 		if(interactive && usemenu && n && memchr(buf, 0x1c, n)) {
553 			if(menu(c)=='q'){
554 				sendwritemsg(c, "", 0);
555 				exits("quit");
556 			}
557 			continue;
558 		}
559 		if(!raw && n==0){
560 			buf[0] = 0x4;
561 			n = 1;
562 		}
563 		sendwritemsg(c, buf, n);
564 	}
565 	sendwritemsg(c, "", 0);
566 	if(n >= 0)				/* weren't hung up upon? */
567 		atexitdont(atexitkiller);
568 	exits(nil);
569 }
570 
571 void
winchanges(Conn * c)572 winchanges(Conn *c)
573 {
574 	int nrow, ncol, width, height;
575 	int pid;
576 
577 	switch(pid = rfork(RFMEM|RFPROC|RFNOWAIT)){
578 	case -1:
579 		error("fork: %r");
580 	case 0:
581 		break;
582 	default:
583 		atexitkill(pid);
584 		return;
585 	}
586 
587 	for(;;){
588 		if(readgeom(&nrow, &ncol, &width, &height) < 0)
589 			break;
590 		sendwindowsize(c, nrow, ncol, width, height);
591 	}
592 	exits(nil);
593 }
594