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
sigwinch(int junk)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
sendsig(int sig)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
cont(int junk)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
oob(int junk)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
main(int argc,char ** argv)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
Die(int stat)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
remstop()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
samefd(a,b)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
doaccept(fdp)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
makeport(fdp)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
usage(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
setproctitle(user,host)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