1 /* 2 * ssh - remote login via SSH v2 3 * /net/ssh does most of the work; we copy bytes back and forth 4 */ 5 #include <u.h> 6 #include <libc.h> 7 #include <auth.h> 8 #include "ssh2.h" 9 10 int doauth(int, char *); 11 int isatty(int); 12 13 char *user, *remote; 14 char *netdir = "/net"; 15 int debug = 0; 16 17 static int stripcr = 0; 18 static int mflag = 0; 19 static int iflag = -1; 20 static int nopw = 0, nopka = 0; 21 static int chpid; 22 static int reqfd, dfd1, cfd1, dfd2, cfd2, consfd, kconsfd, cctlfd, notefd, keyfd; 23 24 void 25 usage(void) 26 { 27 fprint(2, "usage: %s [-dkKmr] [-l user] [-n dir] [-z attr=val] addr " 28 "[cmd [args]]\n", argv0); 29 exits("usage"); 30 } 31 32 /* 33 * this is probably overkill except writing "kill" to notefd; 34 * file descriptors are closed by the kernel upon exit. 35 */ 36 static void 37 shutdown(void) 38 { 39 if (cctlfd > 0) { 40 fprint(cctlfd, "rawoff"); 41 close(cctlfd); 42 } 43 if (consfd > 0) 44 close(consfd); 45 if (reqfd > 0) { 46 fprint(reqfd, "close"); 47 close(reqfd); 48 } 49 close(dfd2); 50 close(dfd1); 51 close(cfd2); 52 close(cfd1); 53 54 fprint(notefd, "kill"); 55 close(notefd); 56 } 57 58 static void 59 bail(char *sts) 60 { 61 shutdown(); 62 exits(sts); 63 } 64 65 int 66 handler(void *, char *s) 67 { 68 char *nf; 69 int fd; 70 71 if (strstr(s, "alarm") != nil) 72 return 0; 73 if (chpid) { 74 nf = esmprint("/proc/%d/note", chpid); 75 fd = open(nf, OWRITE); 76 fprint(fd, "interrupt"); 77 close(fd); 78 free(nf); 79 } 80 shutdown(); 81 return 1; 82 } 83 84 static void 85 parseargs(void) 86 { 87 int n; 88 char *p, *q; 89 90 q = strchr(remote, '@'); 91 if (q != nil) { 92 user = remote; 93 *q++ = 0; 94 remote = q; 95 } 96 97 q = strchr(remote, '!'); 98 if (q) { 99 n = q - remote; 100 netdir = malloc(n+1); 101 if (netdir == nil) 102 sysfatal("out of memory"); 103 strncpy(netdir, remote, n+1); 104 netdir[n] = '\0'; 105 106 p = strrchr(netdir, '/'); 107 if (p == nil) { 108 free(netdir); 109 netdir = "/net"; 110 } else if (strcmp(p+1, "ssh") == 0) 111 *p = '\0'; 112 else 113 remote = esmprint("%s/ssh", netdir); 114 } 115 116 } 117 118 static int 119 catcher(void *, char *s) 120 { 121 return strstr(s, "alarm") != nil; 122 } 123 124 static int 125 timedmount(int fd, int afd, char *mntpt, int flag, char *aname) 126 { 127 int oalarm, ret; 128 129 atnotify(catcher, 1); 130 oalarm = alarm(5*1000); /* don't get stuck here */ 131 ret = mount(fd, afd, mntpt, flag, aname); 132 alarm(oalarm); 133 atnotify(catcher, 0); 134 return ret; 135 } 136 137 static void 138 mounttunnel(char *srv) 139 { 140 int fd; 141 142 if (debug) 143 fprint(2, "%s: mounting %s on /net\n", argv0, srv); 144 fd = open(srv, OREAD); 145 if (fd < 0) { 146 if (debug) 147 fprint(2, "%s: can't open %s: %r\n", argv0, srv); 148 } else if (timedmount(fd, -1, netdir, MBEFORE, "") < 0) { 149 fprint(2, "can't mount %s on %s: %r\n", srv, netdir); 150 close(fd); 151 } 152 } 153 154 static void 155 newtunnel(char *myname) 156 { 157 int kid, pid; 158 159 if(debug) 160 fprint(2, "%s: starting new netssh for key access\n", argv0); 161 kid = rfork(RFPROC|RFNOTEG|RFENVG /* |RFFDG */); 162 if (kid == 0) { 163 // for (fd = 3; fd < 40; fd++) 164 // close(fd); 165 execl("/bin/netssh", "netssh", "-m", netdir, "-s", myname, nil); 166 sysfatal("no /bin/netssh: %r"); 167 } else if (kid < 0) 168 sysfatal("fork failed: %r"); 169 while ((pid = waitpid()) != kid && pid >= 0) 170 ; 171 } 172 173 static void 174 starttunnel(void) 175 { 176 char *keys, *mysrv, *myname; 177 178 keys = esmprint("%s/ssh/keys", netdir); 179 myname = esmprint("ssh.%s", getuser()); 180 mysrv = esmprint("/srv/%s", myname); 181 182 if (access(keys, ORDWR) < 0) 183 mounttunnel("/srv/netssh"); /* old name */ 184 if (access(keys, ORDWR) < 0) 185 mounttunnel("/srv/ssh"); 186 if (access(keys, ORDWR) < 0) 187 mounttunnel(mysrv); 188 if (access(keys, ORDWR) < 0) 189 newtunnel(myname); 190 if (access(keys, ORDWR) < 0) 191 mounttunnel(mysrv); 192 193 /* if we *still* can't see our own tunnel, throw a tantrum. */ 194 if (access(keys, ORDWR) < 0) 195 sysfatal("%s inaccessible: %r", keys); /* WTF? */ 196 197 free(myname); 198 free(mysrv); 199 free(keys); 200 } 201 202 int 203 cmdmode(void) 204 { 205 int n, m; 206 char buf[Arbbufsz]; 207 208 for(;;) { 209 reprompt: 210 print("\n>>> "); 211 n = 0; 212 do { 213 m = read(0, buf + n, sizeof buf - n - 1); 214 if (m <= 0) 215 return 1; 216 write(1, buf + n, m); 217 n += m; 218 buf[n] = '\0'; 219 if (buf[n-1] == ('u' & 037)) 220 goto reprompt; 221 } while (buf[n-1] != '\n' && buf[n-1] != '\r'); 222 switch (buf[0]) { 223 case '\n': 224 case '\r': 225 break; 226 case 'q': 227 return 1; 228 case 'c': 229 return 0; 230 case 'r': 231 stripcr = !stripcr; 232 return 0; 233 case 'h': 234 print("c - continue\n"); 235 print("h - help\n"); 236 print("q - quit\n"); 237 print("r - toggle carriage return stripping\n"); 238 break; 239 default: 240 print("unknown command\n"); 241 break; 242 } 243 } 244 } 245 246 static void 247 keyprompt(char *buf, int size, int n) 248 { 249 if (*buf == 'c') { 250 fprint(kconsfd, "The following key has been offered by the server:\n"); 251 write(kconsfd, buf+5, n); 252 fprint(kconsfd, "\n\n"); 253 fprint(kconsfd, "Add this key? (yes, no, session) "); 254 } else { 255 fprint(kconsfd, "The following key does NOT match the known " 256 "key(s) for the server:\n"); 257 write(kconsfd, buf+5, n); 258 fprint(kconsfd, "\n\n"); 259 fprint(kconsfd, "Add this key? (yes, no, session, replace) "); 260 } 261 n = read(kconsfd, buf, size - 1); 262 if (n <= 0) 263 return; 264 write(keyfd, buf, n); /* user's response -> /net/ssh/keys */ 265 seek(keyfd, 0, 2); 266 if (readn(keyfd, buf, 5) <= 0) 267 return; 268 buf[5] = 0; 269 n = strtol(buf+1, nil, 10); 270 n = readn(keyfd, buf+5, n); 271 if (n <= 0) 272 return; 273 buf[n+5] = 0; 274 275 switch (*buf) { 276 case 'b': 277 case 'f': 278 fprint(kconsfd, "%s\n", buf+5); 279 case 'o': 280 close(keyfd); 281 close(kconsfd); 282 } 283 } 284 285 /* talk the undocumented /net/ssh/keys protocol */ 286 static void 287 keyproc(char *buf, int size) 288 { 289 int n; 290 char *p; 291 292 if (size < 6) 293 exits("keyproc buffer too small"); 294 p = esmprint("%s/ssh/keys", netdir); 295 keyfd = open(p, ORDWR); 296 if (keyfd < 0) { 297 chpid = 0; 298 sysfatal("failed to open ssh keys in %s: %r", p); 299 } 300 301 kconsfd = open("/dev/cons", ORDWR); 302 if (kconsfd < 0) 303 nopw = 1; 304 305 buf[0] = 0; 306 n = read(keyfd, buf, 5); /* reading /net/ssh/keys */ 307 if (n < 0) 308 sysfatal("%s read: %r", p); 309 buf[5] = 0; 310 n = strtol(buf+1, nil, 10); 311 n = readn(keyfd, buf+5, n); 312 buf[n < 0? 5: n+5] = 0; 313 free(p); 314 315 switch (*buf) { 316 case 'f': 317 if (kconsfd >= 0) 318 fprint(kconsfd, "%s\n", buf+5); 319 /* fall through */ 320 case 'o': 321 close(keyfd); 322 if (kconsfd >= 0) 323 close(kconsfd); 324 break; 325 default: 326 if (kconsfd >= 0) 327 keyprompt(buf, size, n); 328 else { 329 fprint(keyfd, "n"); 330 close(keyfd); 331 } 332 break; 333 } 334 chpid = 0; 335 exits(nil); 336 } 337 338 /* 339 * start a subproc to copy from network to stdout 340 * while we copy from stdin to network. 341 */ 342 static void 343 bidircopy(char *buf, int size) 344 { 345 int i, n, lstart; 346 char *path, *p, *q; 347 348 rfork(RFNOTEG); 349 path = esmprint("/proc/%d/notepg", getpid()); 350 notefd = open(path, OWRITE); 351 352 switch (rfork(RFPROC|RFMEM|RFNOWAIT)) { 353 case 0: 354 while ((n = read(dfd2, buf, size - 1)) > 0) { 355 if (!stripcr) 356 p = buf + n; 357 else 358 for (i = 0, p = buf, q = buf; i < n; ++i, ++q) 359 if (*q != '\r') 360 *p++ = *q; 361 if (p != buf) 362 write(1, buf, p-buf); 363 } 364 /* 365 * don't bother; it will be obvious when the user's prompt 366 * changes. 367 * 368 * fprint(2, "%s: Connection closed by server\n", argv0); 369 */ 370 break; 371 default: 372 lstart = 1; 373 while ((n = read(0, buf, size - 1)) > 0) { 374 if (!mflag && lstart && buf[0] == 0x1c) 375 if (cmdmode()) 376 break; 377 else 378 continue; 379 lstart = (buf[n-1] == '\n' || buf[n-1] == '\r'); 380 write(dfd2, buf, n); 381 } 382 /* 383 * don't bother; it will be obvious when the user's prompt 384 * changes. 385 * 386 * fprint(2, "%s: EOF on client side\n", argv0); 387 */ 388 break; 389 case -1: 390 fprint(2, "%s: fork error: %r\n", argv0); 391 break; 392 } 393 394 bail(nil); 395 } 396 397 static int 398 connect(char *buf, int size) 399 { 400 int nfd, n; 401 char *dir, *ds, *nf; 402 403 dir = esmprint("%s/ssh", netdir); 404 ds = netmkaddr(remote, dir, "22"); /* tcp port 22 is ssh */ 405 free(dir); 406 407 dfd1 = dial(ds, nil, nil, &cfd1); 408 if (dfd1 < 0) { 409 fprint(2, "%s: dial conn %s: %r\n", argv0, ds); 410 if (chpid) { 411 nf = esmprint("/proc/%d/note", chpid); 412 nfd = open(nf, OWRITE); 413 fprint(nfd, "interrupt"); 414 close(nfd); 415 } 416 exits("can't dial"); 417 } 418 419 seek(cfd1, 0, 0); 420 n = read(cfd1, buf, size - 1); 421 buf[n >= 0? n: 0] = 0; 422 return atoi(buf); 423 } 424 425 static int 426 chanconnect(int conn, char *buf, int size) 427 { 428 int n; 429 char *path; 430 431 path = esmprint("%s/ssh/%d!session", netdir, conn); 432 dfd2 = dial(path, nil, nil, &cfd2); 433 if (dfd2 < 0) { 434 fprint(2, "%s: dial chan %s: %r\n", argv0, path); 435 bail("dial"); 436 } 437 free(path); 438 439 n = read(cfd2, buf, size - 1); 440 buf[n >= 0? n: 0] = 0; 441 return atoi(buf); 442 } 443 444 static void 445 remotecmd(int argc, char *argv[], int conn, int chan, char *buf, int size) 446 { 447 int i; 448 char *path, *q, *ep; 449 450 path = esmprint("%s/ssh/%d/%d/request", netdir, conn, chan); 451 reqfd = open(path, OWRITE); 452 if (reqfd < 0) 453 bail("can't open request chan"); 454 if (argc == 0) 455 if (readfile("/env/TERM", buf, size) < 0) 456 fprint(reqfd, "shell"); 457 else 458 fprint(reqfd, "shell %s", buf); 459 else { 460 assert(size >= Bigbufsz); 461 ep = buf + Bigbufsz; 462 q = seprint(buf, ep, "exec"); 463 for (i = 0; i < argc; ++i) 464 q = seprint(q, ep, " %q", argv[i]); 465 if (q >= ep) { 466 fprint(2, "%s: command too long\n", argv0); 467 fprint(reqfd, "close"); 468 bail("cmd too long"); 469 } 470 write(reqfd, buf, q - buf); 471 } 472 } 473 474 void 475 main(int argc, char *argv[]) 476 { 477 char *whichkey; 478 int conn, chan, n; 479 char buf[Copybufsz]; 480 481 quotefmtinstall(); 482 reqfd = dfd1 = cfd1 = dfd2 = cfd2 = consfd = kconsfd = cctlfd = 483 notefd = keyfd = -1; 484 whichkey = nil; 485 ARGBEGIN { 486 case 'A': /* auth protos */ 487 case 'c': /* ciphers */ 488 fprint(2, "%s: sorry, -%c is not supported\n", argv0, ARGC()); 489 break; 490 case 'a': /* compat? */ 491 case 'C': /* cooked mode */ 492 case 'f': /* agent forwarding */ 493 case 'p': /* force pty */ 494 case 'P': /* force no pty */ 495 case 'R': /* force raw mode on pty */ 496 case 'v': /* scp compat */ 497 case 'w': /* send window-size changes */ 498 case 'x': /* unix compat: no x11 forwarding */ 499 break; 500 case 'd': 501 debug++; 502 break; 503 case 'I': /* non-interactive */ 504 iflag = 0; 505 break; 506 case 'i': /* interactive: scp & rx do it */ 507 iflag = 1; 508 break; 509 case 'l': 510 case 'u': 511 user = EARGF(usage()); 512 break; 513 case 'k': 514 nopka = 1; 515 break; 516 case 'K': 517 nopw = 1; 518 break; 519 case 'm': 520 mflag = 1; 521 break; 522 case 'n': 523 netdir = EARGF(usage()); 524 break; 525 case 'r': 526 stripcr = 1; 527 break; 528 case 'z': 529 whichkey = EARGF(usage()); 530 break; 531 default: 532 usage(); 533 } ARGEND; 534 if (argc == 0) 535 usage(); 536 537 if (iflag == -1) 538 iflag = isatty(0); 539 remote = *argv++; 540 --argc; 541 542 parseargs(); 543 544 if (!user) 545 user = getuser(); 546 if (user == nil || remote == nil) 547 sysfatal("out of memory"); 548 549 starttunnel(); 550 551 /* fork subproc to handle keys; don't wait for it */ 552 if ((n = rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)) == 0) 553 keyproc(buf, sizeof buf); 554 chpid = n; 555 atnotify(handler, 1); 556 557 /* connect and learn connection number */ 558 conn = connect(buf, sizeof buf); 559 560 consfd = open("/dev/cons", ORDWR); 561 cctlfd = open("/dev/consctl", OWRITE); 562 fprint(cctlfd, "rawon"); 563 if (doauth(cfd1, whichkey) < 0) 564 bail("doauth"); 565 566 /* connect a channel of conn and learn channel number */ 567 chan = chanconnect(conn, buf, sizeof buf); 568 569 /* open request channel, request shell or command execution */ 570 remotecmd(argc, argv, conn, chan, buf, sizeof buf); 571 572 bidircopy(buf, sizeof buf); 573 } 574 575 int 576 isatty(int fd) 577 { 578 char buf[64]; 579 580 buf[0] = '\0'; 581 fd2path(fd, buf, sizeof buf); 582 return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0; 583 } 584 585 int 586 doauth(int cfd1, char *whichkey) 587 { 588 UserPasswd *up; 589 int n; 590 char path[Arbpathlen]; 591 592 if (!nopka) { 593 if (whichkey) 594 n = fprint(cfd1, "ssh-userauth K %q %q", user, whichkey); 595 else 596 n = fprint(cfd1, "ssh-userauth K %q", user); 597 if (n >= 0) 598 return 0; 599 } 600 if (nopw) 601 return -1; 602 up = auth_getuserpasswd(iflag? auth_getkey: nil, 603 "proto=pass service=ssh server=%q user=%q", remote, user); 604 if (up == nil) { 605 fprint(2, "%s: didn't get password: %r\n", argv0); 606 return -1; 607 } 608 n = fprint(cfd1, "ssh-userauth k %q %q", user, up->passwd); 609 if (n >= 0) 610 return 0; 611 612 path[0] = '\0'; 613 fd2path(cfd1, path, sizeof path); 614 fprint(2, "%s: auth ctl msg `ssh-userauth k %q <password>' for %q: %r\n", 615 argv0, user, path); 616 return -1; 617 } 618