1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * on - user interface program for remote execution service 24 * 25 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 26 * Use is subject to license terms. 27 */ 28 29 #pragma ident "%Z%%M% %I% %E% SMI" 30 31 #define BSD_COMP 32 33 #include <ctype.h> 34 #include <errno.h> 35 #include <netdb.h> 36 #include <signal.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #include <netinet/in.h> 43 #include <rpc/rpc.h> 44 #include <rpc/clnt_soc.h> 45 #include <rpc/key_prot.h> 46 #include <sys/fcntl.h> 47 #include <sys/ioctl.h> 48 #include <sys/param.h> 49 #include <sys/socket.h> 50 #include <sys/sockio.h> 51 #include <sys/stat.h> 52 #include <sys/time.h> 53 54 55 #include <sys/ttold.h> 56 57 58 #include "rex.h" 59 60 #include <stropts.h> 61 #include <sys/stream.h> 62 #include <sys/ttcompat.h> 63 64 65 #define bcmp(b1, b2, len) memcmp(b1, b2, len) 66 #define bzero(b, len) memset(b, '\0', len) 67 #define bcopy(b1, b2, len) memcpy(b2, b1, len) 68 69 #define CommandName "on" /* given as argv[0] */ 70 #define AltCommandName "dbon" 71 72 extern int errno; 73 74 /* 75 * Note - the following must be long enough for at least two portmap 76 * timeouts on the other side. 77 */ 78 struct timeval LongTimeout = { 123, 0 }; 79 struct timeval testtimeout = { 5, 0 }; 80 81 int Debug = 0; /* print extra debugging information */ 82 int Only2 = 0; /* stdout and stderr are the same */ 83 int Interactive = 0; /* use a pty on server */ 84 int NoInput = 0; /* don't read standard input */ 85 int child = 0; /* pid of the executed process */ 86 int ChildDied = 0; /* true when above is valid */ 87 int HasHelper = 0; /* must kill helpers (interactive mode) */ 88 89 int InOut; /* socket for stdin/stdout */ 90 int Err; /* socket for stderr */ 91 92 struct sgttyb OldFlags; /* saved tty flags */ 93 struct sgttyb NewFlags; /* for stop/continue job control */ 94 CLIENT *Client; /* RPC client handle */ 95 struct rex_ttysize WindowSize; /* saved window size */ 96 97 static int Argc; 98 static char **Argv; /* saved argument vector (for ps) */ 99 static char *LastArgv; /* saved end-of-argument vector */ 100 101 void usage(void); 102 void Die(int stat); 103 void doaccept(int *fdp); 104 u_short makeport(int *fdp); 105 106 107 /* 108 * window change handler - propagate to remote server 109 */ 110 void 111 sigwinch(int junk) 112 { 113 struct winsize newsize; /* the modern way to get row and col */ 114 struct rex_ttysize size; /* the old way no body */ 115 /* bothered to change */ 116 enum clnt_stat clstat; 117 118 ioctl(0, TIOCGWINSZ, &newsize); 119 120 /* 121 * compensate for the struct change 122 */ 123 size.ts_lines = (int)newsize.ws_row; /* typecast important! */ 124 size.ts_cols = (int)newsize.ws_col; 125 126 if (bcmp(&size, &WindowSize, sizeof (size)) == 0) 127 return; 128 129 WindowSize = size; 130 if (clstat = clnt_call(Client, REXPROC_WINCH, 131 xdr_rex_ttysize, (caddr_t)&size, xdr_void, 132 NULL, LongTimeout)) { 133 fprintf(stderr, "on (size): "); 134 clnt_perrno(clstat); 135 fprintf(stderr, "\r\n"); 136 } 137 } 138 139 /* 140 * signal handler - propagate to remote server 141 */ 142 void 143 sendsig(int sig) 144 { 145 enum clnt_stat clstat; 146 147 if (clstat = clnt_call(Client, REXPROC_SIGNAL, 148 xdr_int, (caddr_t) &sig, xdr_void, 149 NULL, LongTimeout)) { 150 fprintf(stderr, "on (signal): "); 151 clnt_perrno(clstat); 152 fprintf(stderr, "\r\n"); 153 } 154 } 155 156 157 void 158 cont(int junk) 159 { 160 /* 161 * Put tty modes back the way they were and tell the rexd server 162 * to send the command a SIGCONT signal. 163 */ 164 if (Interactive) { 165 ioctl(0, TIOCSETN, &NewFlags); 166 (void) send(InOut, "", 1, MSG_OOB); 167 } 168 } 169 170 /* 171 * oob -- called when the command invoked by the rexd server is stopped 172 * with a SIGTSTP or SIGSTOP signal. 173 */ 174 void 175 oob(int junk) 176 { 177 int atmark; 178 char waste[BUFSIZ], mark; 179 180 for (;;) { 181 if (ioctl(InOut, SIOCATMARK, &atmark) < 0) { 182 perror("ioctl"); 183 break; 184 } 185 if (atmark) 186 break; 187 (void) read(InOut, waste, sizeof (waste)); 188 } 189 (void) recv(InOut, &mark, 1, MSG_OOB); 190 /* 191 * Reset tty modes to something sane and stop myself 192 */ 193 if (Interactive) { 194 ioctl(0, TIOCSETN, &OldFlags); 195 printf("\r\n"); 196 } 197 kill(getpid(), SIGSTOP); 198 } 199 200 201 202 int 203 main(int argc, char **argv) 204 { 205 struct winsize newsize; /* the modern way to get row and col */ 206 char *rhost, **cmdp; 207 char curdir[MAXPATHLEN]; 208 char wdhost[MAXHOSTNAMELEN]; 209 char fsname[MAXPATHLEN]; 210 char dirwithin[MAXPATHLEN]; 211 struct rex_start rst; 212 struct rex_result result; 213 extern char **environ; 214 enum clnt_stat clstat; 215 struct hostent *hp; 216 struct sockaddr_in server_addr; 217 int sock = RPC_ANYSOCK; 218 fd_set selmask, zmask, remmask; 219 int nfds, cc; 220 char *chi, *cho; 221 int trying_authdes; 222 char netname[MAXNETNAMELEN+1]; 223 char hostname[MAXHOSTNAMELEN+1]; 224 char publickey[HEXKEYBYTES+1]; 225 int i; 226 char *domain; 227 static char buf[4096]; 228 229 /* 230 * we check the invoked command name to see if it should 231 * really be a host name. 232 */ 233 if ((rhost = strrchr(argv[0], '/')) == NULL) { 234 rhost = argv[0]; 235 } else { 236 rhost++; 237 } 238 239 /* 240 * argv start and extent for setproctitle() 241 */ 242 Argc = argc; 243 Argv = argv; 244 if (argc > 0) 245 LastArgv = argv[argc-1] + strlen(argv[argc-1]); 246 else 247 LastArgv = NULL; 248 249 while (argc > 1 && argv[1][0] == '-') { 250 switch (argv[1][1]) { 251 case 'd': Debug = 1; 252 break; 253 case 'i': Interactive = 1; 254 break; 255 case 'n': NoInput = 1; 256 break; 257 default: 258 printf("Unknown option %s\n", argv[1]); 259 } 260 argv++; 261 argc--; 262 } 263 264 if (strcmp(rhost, CommandName) && strcmp(rhost, AltCommandName)) { 265 cmdp = &argv[1]; 266 Interactive = 1; 267 } else { 268 if (argc < 2) 269 usage(); 270 rhost = argv[1]; 271 cmdp = &argv[2]; 272 } 273 274 /* 275 * Can only have one of these 276 */ 277 if (Interactive && NoInput) 278 usage(); 279 280 if ((hp = gethostbyname(rhost)) == NULL) { 281 fprintf(stderr, "on: unknown host %s\n", rhost); 282 exit(1); 283 } 284 285 bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, hp->h_length); 286 server_addr.sin_family = AF_INET; 287 server_addr.sin_port = 0; /* use pmapper */ 288 289 if (Debug) 290 printf("Got the host named %s (%s)\n", 291 rhost, inet_ntoa(server_addr.sin_addr)); 292 trying_authdes = 1; 293 294 try_auth_unix: 295 sock = RPC_ANYSOCK; 296 297 if (Debug) 298 printf("clnt_create: Server_Addr %u Prog %d Vers %d Sock %d\n", 299 &server_addr, REXPROG, REXVERS, sock); 300 301 if ((Client = clnttcp_create(&server_addr, REXPROG, REXVERS, &sock, 302 0, 0)) == NULL) { 303 fprintf(stderr, "on: cannot connect to server on %s\n", 304 rhost); 305 clnt_pcreateerror("on:"); 306 exit(1); 307 } 308 309 if (Debug) 310 printf("TCP RPC connection created\n"); 311 312 if (trying_authdes) { 313 yp_get_default_domain(&domain); 314 315 cho = hostname; 316 *cho = 0; 317 chi = hp->h_name; 318 319 for (i = 0; (*chi && (i < MAXHOSTNAMELEN)); i++) 320 { 321 if (isupper(*chi)) 322 *cho = tolower(*chi); 323 else 324 *cho = *chi; 325 cho++; 326 chi++; 327 } 328 *cho = 0; 329 330 if (domain != NULL) { 331 if (host2netname(netname, hostname, domain) == 0) { 332 trying_authdes = 0; 333 if (Debug) 334 printf("host2netname failed %s\n", 335 hp->h_name); 336 } 337 /* #ifdef NOWAY */ 338 else { 339 340 if (getpublickey(netname, publickey) == 0) { 341 trying_authdes = 0; 342 cho = strchr(hostname, '.'); 343 344 if (cho) { 345 *cho = 0; 346 347 if (!host2netname(netname, 348 hostname, 349 domain)) { 350 if (Debug) 351 printf("host2netname failed %s\n", hp->h_name); 352 } else { 353 if (getpublickey( 354 netname, 355 publickey) != 0) 356 trying_authdes = 1; 357 } 358 } 359 } 360 } 361 } else { 362 trying_authdes = 0; 363 if (Debug) 364 printf("yp_get_default_domain failed \n"); 365 } 366 } 367 368 if (trying_authdes) { 369 Client->cl_auth = (AUTH *)authdes_create(netname, 60*60, 370 &server_addr, NULL); 371 372 if (Client->cl_auth == NULL) { 373 374 if (Debug) 375 printf("authdes_create failed %s\n", netname); 376 trying_authdes = 0; 377 } 378 } 379 380 381 if (trying_authdes == 0) 382 if ((Client->cl_auth = authsys_create_default()) == NULL) { 383 clnt_destroy(Client); 384 fprintf(stderr,"on: can't create authunix structure.\n"); 385 exit(1); 386 } 387 388 389 /* 390 * Now that we have created the TCP connection, we do some 391 * work while the server daemon is being swapped in. 392 */ 393 if (getcwd(curdir, MAXPATHLEN) == (char *)NULL) { 394 fprintf(stderr, "on: can't find . (%s)\n", curdir); 395 exit(1); 396 } 397 398 if (findmount(curdir, wdhost, fsname, dirwithin) == 0) { 399 400 if (Debug) { 401 fprintf(stderr, 402 "findmount failed: curdir %s\twdhost %s\t", 403 curdir, wdhost); 404 fprintf(stderr, "fsname %s\tdirwithin %s\n", 405 fsname, dirwithin); 406 } 407 408 fprintf(stderr, "on: can't locate mount point for %s (%s)\n", 409 curdir, dirwithin); 410 exit(1); 411 } 412 413 if (Debug) { 414 printf("findmount suceeds: cwd= %s, wd host %s, fs %s,", 415 curdir, wdhost, fsname); 416 printf("dir within %s\n", dirwithin); 417 } 418 419 Only2 = samefd(1, 2); 420 421 rst.rst_cmd = (void *)(cmdp); 422 rst.rst_host = (void *)wdhost; 423 rst.rst_fsname = (void *)fsname; 424 rst.rst_dirwithin = (void *)dirwithin; 425 rst.rst_env = (void *)environ; 426 rst.rst_port0 = makeport(&InOut); 427 rst.rst_port1 = rst.rst_port0; /* same port for stdin */ 428 rst.rst_flags = 0; 429 430 if (Debug) 431 printf("before Interactive flags\n"); 432 433 if (Interactive) { 434 rst.rst_flags |= REX_INTERACTIVE; 435 ioctl(0, TIOCGETP, &OldFlags); 436 NewFlags = OldFlags; 437 NewFlags.sg_flags |= (u_int)RAW; 438 NewFlags.sg_flags &= (u_int)~ECHO; 439 ioctl(0, TIOCSETN, &NewFlags); 440 } 441 442 if (Only2) { 443 rst.rst_port2 = rst.rst_port1; 444 } else { 445 rst.rst_port2 = makeport(&Err); 446 } 447 448 if (Debug) 449 printf("before client call REXPROC_START\n"); 450 451 (void) memset(&result, '\0', sizeof(result)); 452 453 if (clstat = clnt_call(Client, REXPROC_START, 454 xdr_rex_start, (caddr_t)&rst, 455 xdr_rex_result, (caddr_t)&result, LongTimeout)) { 456 457 if (Debug) 458 printf("Client call failed for REXPROC_START\r\n"); 459 460 if (trying_authdes) { 461 auth_destroy(Client->cl_auth); 462 clnt_destroy(Client); 463 trying_authdes = 0; 464 if (Interactive) 465 ioctl(0, TIOCSETN, &OldFlags); 466 goto try_auth_unix; 467 } else { 468 fprintf(stderr, "on %s: ", rhost); 469 clnt_perrno(clstat); 470 fprintf(stderr, "\n"); 471 Die(1); 472 } 473 } 474 475 if (result.rlt_stat != 0) { 476 fprintf(stderr, "on %s: %s\n\r", rhost, result.rlt_message); 477 Die(1); 478 } 479 480 clnt_freeres(Client, xdr_rex_result, (caddr_t)&result); 481 482 if (Debug) 483 printf("Client call suceeded for REXPROC_START\r\n"); 484 485 if (Interactive) { 486 /* 487 * Pass the tty modes along to the server 488 */ 489 struct rex_ttymode mode; 490 int err; 491 492 mode.basic.sg_ispeed = OldFlags.sg_ispeed; 493 mode.basic.sg_ospeed = OldFlags.sg_ospeed; 494 mode.basic.sg_erase = OldFlags.sg_erase; 495 mode.basic.sg_kill = OldFlags.sg_kill; 496 mode.basic.sg_flags = (short) (OldFlags.sg_flags & 0xFFFF); 497 err = (ioctl(0, TIOCGETC, &mode.more) < 0 || 498 ioctl(0, TIOCGLTC, &mode.yetmore) < 0 || 499 ioctl(0, TIOCLGET, &mode.andmore) < 0); 500 if (Debug) 501 printf("Before clnt_call(REXPROC_MODES) err=%d\n", err); 502 503 if (!err && (clstat = clnt_call(Client, REXPROC_MODES, 504 xdr_rex_ttymode, (caddr_t)&mode, 505 xdr_void, NULL, LongTimeout))) { 506 507 fprintf(stderr, "on (modes) %s: ", rhost); 508 clnt_perrno(clstat); 509 fprintf(stderr, "\r\n"); 510 } 511 512 err = ioctl(0, TIOCGWINSZ, &newsize) < 0; 513 /* typecast important in following lines */ 514 WindowSize.ts_lines = (int)newsize.ws_row; 515 WindowSize.ts_cols = (int)newsize.ws_col; 516 517 if (Debug) 518 printf("Before client call REXPROC_WINCH\n"); 519 520 if (!err && (clstat = clnt_call(Client, REXPROC_WINCH, 521 xdr_rex_ttysize, (caddr_t)&WindowSize, 522 xdr_void, NULL, LongTimeout))) { 523 524 fprintf(stderr, "on (size) %s: ", rhost); 525 clnt_perrno(clstat); 526 fprintf(stderr, "\r\n"); 527 } 528 529 sigset(SIGWINCH, sigwinch); 530 sigset(SIGINT, sendsig); 531 sigset(SIGQUIT, sendsig); 532 sigset(SIGTERM, sendsig); 533 } 534 sigset(SIGCONT, cont); 535 sigset(SIGURG, oob); 536 doaccept(&InOut); 537 (void) fcntl(InOut, F_SETOWN, getpid()); 538 FD_ZERO(&remmask); 539 FD_SET(InOut, &remmask); 540 if (Debug) 541 printf("accept on stdout\r\n"); 542 543 if (!Only2) { 544 545 doaccept(&Err); 546 shutdown(Err, 1); /* 1=> further sends disallowed */ 547 if (Debug) 548 printf("accept on stderr\r\n"); 549 FD_SET(Err, &remmask); 550 } 551 552 FD_ZERO(&zmask); 553 if (NoInput) { 554 555 /* 556 * no input - simulate end-of-file instead 557 */ 558 shutdown(InOut, 1); /* 1=> further sends disallowed */ 559 } else { 560 /* 561 * set up to read standard input, send to remote 562 */ 563 FD_SET(0, &zmask); 564 } 565 566 FD_ZERO(&selmask); 567 while (FD_ISSET(InOut, &remmask) || FD_ISSET(Err, &remmask)) { 568 if (FD_ISSET(InOut, &remmask)) 569 FD_SET(InOut, &selmask); 570 else 571 FD_CLR(InOut, &selmask); 572 if (FD_ISSET(Err, &remmask)) 573 FD_SET(Err, &selmask); 574 else 575 FD_CLR(Err, &selmask); 576 if (FD_ISSET(0, &zmask)) 577 FD_SET(0, &selmask); 578 else 579 FD_CLR(0, &selmask); 580 nfds = select(FD_SETSIZE, &selmask, (fd_set *) 0, (fd_set *) 0, 581 (struct timeval *) 0); 582 583 584 if (nfds <= 0) { 585 if (errno == EINTR) continue; 586 perror("on: select"); 587 Die(1); 588 } 589 if (FD_ISSET(InOut, &selmask)) { 590 591 cc = read(InOut, buf, sizeof buf); 592 if (cc > 0) 593 write(1, buf, cc); 594 else 595 FD_CLR(InOut, &remmask); 596 } 597 598 if (!Only2 && FD_ISSET(Err, &selmask)) { 599 600 cc = read(Err, buf, sizeof buf); 601 if (cc > 0) 602 write(2, buf, cc); 603 else 604 FD_CLR(Err, &remmask); 605 } 606 607 if (!NoInput && FD_ISSET(0, &selmask)) { 608 609 cc = read(0, buf, sizeof buf); 610 if (cc > 0) 611 write(InOut, buf, cc); 612 else { 613 /* 614 * End of standard input - shutdown outgoing 615 * direction of the TCP connection. 616 */ 617 if (Debug) 618 printf("Got EOF - shutting down connection\n"); 619 FD_CLR(0, &zmask); 620 shutdown(InOut, 1); /* further sends disallowed */ 621 } 622 } 623 } 624 625 close(InOut); 626 if (!Only2) 627 close(Err); 628 629 (void) memset(&result, '\0', sizeof(result)); 630 631 if (clstat = clnt_call(Client, REXPROC_WAIT, 632 xdr_void, 0, xdr_rex_result, (caddr_t)&result, 633 LongTimeout)) { 634 635 fprintf(stderr, "on: "); 636 clnt_perrno(clstat); 637 fprintf(stderr, "\r\n"); 638 Die(1); 639 } 640 Die(result.rlt_stat); 641 return (0); /* Should never get here. */ 642 } 643 644 /* 645 * like exit, but resets the terminal state first 646 */ 647 void 648 Die(int stat) 649 650 { 651 if (Interactive) { 652 ioctl(0, TIOCSETN, &OldFlags); 653 printf("\r\n"); 654 } 655 exit(stat); 656 } 657 658 659 void 660 remstop() 661 662 { 663 Die(23); 664 } 665 666 /* 667 * returns true if we can safely say that the two file descriptors 668 * are the "same" (both are same file). 669 */ 670 int 671 samefd(a, b) 672 { 673 struct stat astat, bstat; 674 675 if (fstat(a, &astat) || fstat(b, &bstat)) 676 return (0); 677 if (astat.st_ino == 0 || bstat.st_ino == 0) 678 return (0); 679 return (!bcmp(&astat, &bstat, sizeof (astat))); 680 } 681 682 683 /* 684 * accept the incoming connection on the given 685 * file descriptor, and return the new file descritpor 686 */ 687 void 688 doaccept(fdp) 689 int *fdp; 690 { 691 int fd; 692 693 fd = accept(*fdp, 0, 0); 694 695 if (fd < 0) { 696 perror("accept"); 697 remstop(); 698 } 699 close(*fdp); 700 *fdp = fd; 701 } 702 703 /* 704 * create a socket, and return its the port number. 705 */ 706 u_short 707 makeport(fdp) 708 int *fdp; 709 { 710 struct sockaddr_in sin; 711 socklen_t len = (socklen_t)sizeof (sin); 712 int fd; 713 714 fd = socket(AF_INET, SOCK_STREAM, 0); 715 716 if (fd < 0) { 717 perror("socket"); 718 exit(1); 719 } 720 721 bzero((char *)&sin, sizeof (sin)); 722 sin.sin_family = AF_INET; 723 bind(fd, (struct sockaddr *)&sin, sizeof (sin)); 724 getsockname(fd, (struct sockaddr *)&sin, &len); 725 listen(fd, 1); 726 *fdp = fd; 727 return (htons(sin.sin_port)); 728 } 729 730 void 731 usage(void) 732 { 733 fprintf(stderr, "Usage: on [-i|-n] [-d] machine cmd [args]...\n"); 734 exit(1); 735 } 736 737 /* 738 * SETPROCTITLE -- set the title of this process for "ps" 739 * 740 * Does nothing if there were not enough arguments on the command 741 * line for the information. 742 * 743 * Side Effects: 744 * Clobbers argv[] of our main procedure. 745 */ 746 void 747 setproctitle(user, host) 748 char *user, *host; 749 { 750 register char *tohere; 751 752 tohere = Argv[0]; 753 if ((int)LastArgv == (int)((char *)NULL) || 754 (int)(strlen(user) + strlen(host)+3) > (int)(LastArgv - tohere)) 755 return; 756 *tohere++ = '-'; /* So ps prints (rpc.rexd) */ 757 sprintf(tohere, "%s@%s", user, host); 758 while (*tohere++) /* Skip to end of printf output */ 759 ; 760 while (tohere < LastArgv) /* Avoid confusing ps */ 761 *tohere++ = ' '; 762 } 763