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