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* 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* 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 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 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 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* 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 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 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 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 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 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 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 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 502 wasintr(void) 503 { 504 char err[64]; 505 506 rerrstr(err, sizeof err); 507 return strstr(err, "interrupt") != 0; 508 } 509 510 void 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 atexitdont(atexitkiller); 567 exits(nil); 568 } 569 570 void 571 winchanges(Conn *c) 572 { 573 int nrow, ncol, width, height; 574 int pid; 575 576 switch(pid = rfork(RFMEM|RFPROC|RFNOWAIT)){ 577 case -1: 578 error("fork: %r"); 579 case 0: 580 break; 581 default: 582 atexitkill(pid); 583 return; 584 } 585 586 for(;;){ 587 if(readgeom(&nrow, &ncol, &width, &height) < 0) 588 break; 589 sendwindowsize(c, nrow, ncol, width, height); 590 } 591 exits(nil); 592 } 593