/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * rexd - a remote execution daemon based on SUN Remote Procedure Calls * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rex.h" #include #include #include /* #include XXX */ #include #include /* #define stderr stdout */ /* XXX */ #define ListnerTimeout 300 /* seconds listner stays alive */ #define WaitLimit 10 /* seconds to wait after io is closed */ #define MOUNTED "/etc/mnttab" #define TempDir "/tmp_rex" /* directory to hold temp mounts */ static char TempName[] = "/tmp_rex/rexdXXXXXX"; /* name template for temp mount points */ #define TempMatch 13 /* unique prefix of above */ SVCXPRT *ListnerTransp; /* non-null means still a listner */ static char **Argv; /* saved argument vector (for ps) */ static char *LastArgv; /* saved end-of-argument vector */ int OutputSocket; /* socket for stop/cont notification */ int MySocket; /* transport socket */ int HasHelper = 0; /* must kill helpers (interactive mode) */ int DesOnly = 0; /* unix credentials too weak */ int confd; /* console fd */ int Debug = 0; pam_handle_t *pamh; /* PAM handle */ time_t time_now; extern int Master; /* half of the pty */ extern char **environ; int child = 0; /* pid of the executed process */ int ChildStatus = 0; /* saved return status of child */ int ChildDied = 0; /* true when above is valid */ char nfsdir[MAXPATHLEN]; /* file system we mounted */ char *tmpdir; /* where above is mounted, NULL if none */ extern void rex_cleanup(void); extern int ValidUser(char *host, uid_t uid, gid_t gid, char *error, char *shell, char *dir, struct rex_start *rst); extern void audit_rexd_fail(char *, char *, char *, uid_t, gid_t, char *, char **); extern void audit_rexd_success(char *, char *, uid_t, gid_t, char *, char **); extern void audit_rexd_setup(); extern int audit_settid(int); /* process rex requests */ void dorex(struct svc_req *rqstp, SVCXPRT *transp); void ListnerTimer(int); /* destroy listener */ void CatchChild(int); /* handle child signals */ void oob(int); /* out of band signals */ void sigwinch(int); /* window change signals -- dummy */ FILE *setmntent(char *fname, char *flag); extern void HelperRead(pollfd_t *fdp, int, int *); int main(int argc, char **argv) { /* * the server is a typical RPC daemon, except that we only * accept TCP connections. */ int pollretval; int npollfds = 0; pollfd_t *pollset = NULL; struct sockaddr_in addr; int maxrecsz = RPC_MAXDATASIZE; audit_rexd_setup(); /* BSM */ /* * Remember the start and extent of argv for setproctitle(). * Open the console for error printouts, but don't let it be * our controlling terminal. */ if (argc > 1) { if (strcmp("-s", argv[1]) == 0) DesOnly = 1; if (strcmp("-d", argv[1]) == 0) Debug = 1; } if (argc > 2) { if (strcmp("-s", argv[2]) == 0) DesOnly = 1; if (strcmp("-d", argv[2]) == 0) Debug = 1; } /* * argv start and extent for setproctitle() */ Argv = argv; if (argc > 0) LastArgv = argv[argc-1] + strlen(argv[argc-1]); else LastArgv = NULL; /* * console open for errors w/o being the controlling terminal */ if ((confd = open("/dev/console", 1)) > 0) { close(1); close(2); confd = dup2(confd, 1); /* console fd copied to stdout */ dup(1); /* console fd copied to stderr */ } setsid(); /* get rid of controlling terminal */ /* * setup signals */ sigset(SIGCHLD, CatchChild); sigset(SIGPIPE, SIG_IGN); sigset(SIGALRM, ListnerTimer); /* * Enable non-blocking mode and maximum record size checks for * connection oriented transports. */ if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrecsz)) { fprintf(stderr, "rexd: unable to set RPC max record size\n"); } /* * determine how we started to see if we are already in the background * and get appropriately registered with rpcbind (portmapper) */ if (isfrominetd(0)) { /* * Started from inetd: use fd 0 as socket */ if (Debug) printf("Started from inetd\n"); if ((ListnerTransp = svctcp_create(0, 0, 0)) == NULL) { fprintf(stderr, "rexd: svctcp_create error\n"); exit(1); } if (!svc_register(ListnerTransp, REXPROG, REXVERS, dorex, 0)) { fprintf(stderr, "rexd: service register error\n"); exit(1); } alarm(ListnerTimeout); } else { if (Debug) printf("started from shell\n"); if (!Debug) { /* * Started from shell, background * thyself and run forever. */ int pid = fork(); if (pid < 0) { /* fork error */ perror("rpc.rexd: can't fork"); exit(1); } if (pid) { /* parent terminates */ exit(0); } } /* * child process continues to establish connections */ if (Debug) printf("before svctcp_create() call\n"); if ((ListnerTransp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL) { fprintf(stderr, "rexd: svctcp_create: error\n"); exit(1); } pmap_unset(REXPROG, REXVERS); if (!svc_register(ListnerTransp, REXPROG, REXVERS, dorex, IPPROTO_TCP)) { fprintf(stderr, "rexd: service rpc register: error\n"); exit(1); } } /* * Create a private temporary directory to hold rexd's mounts */ if (mkdir(TempDir, 0777) < 0) if (errno != EEXIST) { perror("rexd: mkdir"); fprintf(stderr, "rexd: can't create temp directory %s\n", TempDir); exit(1); } if (Debug) printf("created temporary directory\n"); /* * normally we would call svc_run() at this point, but we need to be * informed of when the RPC connection is broken, in case the other * side crashes. */ while (TRUE) { if (Debug) printf("Entered While loop\n"); if (MySocket) { int i; char *waste; /* try to find MySocket in the pollfd set */ for (i = 0; i < svc_max_pollfd; i++) if (svc_pollfd[i].fd == MySocket) break; /* * If we didn't find it, the connection died for * some random reason, e.g. client crashed. */ if (i == svc_max_pollfd) { if (Debug) printf("Connection died\n"); (void) rex_wait(&waste); rex_cleanup(); exit(1); } } /* * Get existing array of pollfd's, should really compress * this but it shouldn't get very large (or sparse). */ if (npollfds != svc_max_pollfd) { pollset = realloc(pollset, sizeof (pollfd_t) * svc_max_pollfd); npollfds = svc_max_pollfd; } if (npollfds == 0) break; /* None waiting, hence return */ (void) memcpy(pollset, svc_pollfd, sizeof (pollfd_t) * svc_max_pollfd); if (Debug) printf("Before select readfds\n"); switch (pollretval = poll(pollset, npollfds, -1)) { case -1: if (Debug) printf("Poll failed\n"); if (errno == EINTR) continue; perror("rexd: poll failed"); exit(1); case 0: if (Debug) printf("Poll returned zero\n"); fprintf(stderr, "rexd: poll returned zero\r\n"); continue; default: if (Debug) printf("Before HelperRead\n"); if (HasHelper) HelperRead(pollset, npollfds, &pollretval); if (Debug) printf("After HelperRead\n"); time_now = time((time_t *)0); if (Debug) printf("before svc_getreq_poll\n"); svc_getreq_poll(pollset, pollretval); } if (Debug) printf("After switch\n"); } return (0); } /* * This function gets called after the listner has timed out waiting * for any new connections coming in. */ void ListnerTimer(int junk) { /* * svc_destroy not done here due to problems with M_ERROR * on stream head and inetd */ exit(0); } struct authunix_parms *authdes_to_unix(des_cred) struct authdes_cred *des_cred; { struct authunix_parms *unix_cred; static struct authunix_parms au; static uint_t stuff[32]; char publickey[HEXKEYBYTES+1]; unix_cred = &au; unix_cred->aup_gids = (gid_t *)stuff; unix_cred->aup_machname = ""; if (getpublickey(des_cred->adc_fullname.name, publickey) == 0) return (NULL); if (netname2user(des_cred->adc_fullname.name, &(unix_cred->aup_uid), &(unix_cred->aup_gid), (int *)&(unix_cred->aup_len), unix_cred->aup_gids) == FALSE) return (NULL); else return (unix_cred); } /* * dorex - handle one of the rex procedure calls, dispatching to the * correct function. */ void dorex(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { struct rex_start *rst; struct rex_result result; struct authunix_parms *unix_cred; struct sockaddr_in *calleraddr; if (ListnerTransp) { /* * First call - fork a server for this connection */ int fd, pid, count; for (count = 0; (pid = fork()) < 0; count++) { if (count > 4) { perror("rexd: cannot fork"); break; } sleep(5); } if (pid != 0) { /* * Parent - return to service loop to accept further * connections. */ alarm(ListnerTimeout); svc_destroy(transp); return; } /* * child - close listner transport to avoid confusion * Also need to close all other service transports * besides the one we are interested in. * Save ours so that we know when it goes away. */ if (Debug) printf("child server process\n"); alarm(0); if (transp != ListnerTransp) { close(ListnerTransp->xp_sock); xprt_unregister(ListnerTransp); } ListnerTransp = NULL; MySocket = transp->xp_sock; /* temp workaround to restore sanity in TLI state */ if (transp->xp_sock != 0) t_close(0); /* opened in parent possibly by inetd */ /* * XXX: svc_pollfd[] is a read-only structure. This * appears to be dead code, which should be removed. * However, until it can be clearly understood, leaving * in. */ for (fd = 1; fd < svc_max_pollfd; fd++) { if (fd != transp->xp_sock && svc_pollfd[fd].fd == fd) { printf("close of fd %d\n", fd); close(fd); svc_pollfd[fd].fd = -1; svc_pollfd[fd].events = 0; svc_pollfd[fd].revents = 0; } } } /* * execute the requested prodcedure */ switch (rqstp->rq_proc) { case NULLPROC: if (Debug) /* XXX */ printf("dorex: call to NULLPROC\n"); if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "rexd: nullproc err"); exit(1); } return; case REXPROC_START: if (Debug) /* XXX */ printf("dorex: call to REXPROC_START\n"); rst = (struct rex_start *)malloc(sizeof (struct rex_start)); memset((char *)rst, '\0', sizeof (*rst)); if (svc_getargs(transp, xdr_rex_start, (char *)rst) == FALSE) { svcerr_decode(transp); exit(1); } if (Debug) printf("svc_getargs: suceeded\n"); if (rqstp->rq_cred.oa_flavor == AUTH_DES) { unix_cred = authdes_to_unix(rqstp->rq_clntcred); } else if (rqstp->rq_cred.oa_flavor == AUTH_UNIX) { if (DesOnly) { fprintf(stderr, "Unix too weak auth(DesOnly)!\n"); unix_cred = NULL; } else unix_cred = (struct authunix_parms *)rqstp->rq_clntcred; } else { fprintf(stderr, "Unknown weak auth!\n"); svcerr_weakauth(transp); sleep(5); exit(1); } if (unix_cred == NULL) { svcerr_weakauth(transp); sleep(5); exit(1); } calleraddr = svc_getcaller(transp); result.rlt_stat = (int)rex_startup(rst, unix_cred, (char **)&result.rlt_message, calleraddr); if (Debug) printf("rex_startup: completed\n"); if (svc_sendreply(transp, xdr_rex_result, (char *)&result) == FALSE) { fprintf(stderr, "rexd: reply failed\n"); rex_cleanup(); exit(1); } if (Debug) printf("svc_sendreply: suceeded\n"); if (result.rlt_stat) { rex_cleanup(); exit(0); } return; case REXPROC_MODES: { struct rex_ttymode mode; if (Debug) /* XXX */ printf("dorex: call to REXPROC_MODES\n"); if (svc_getargs(transp, xdr_rex_ttymode, (char *)&mode) == FALSE) { svcerr_decode(transp); exit(1); } if (Debug) printf("svc_getargs succ REXPROC_MODES call\n"); SetPtyMode(&mode); /* XXX Fix? */ if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "rexd: mode reply failed"); exit(1); } } return; case REXPROC_WINCH: /* XXX Fix? */ { struct rex_ttysize size; if (Debug) /* XXX */ printf("dorex: call to REXPROC_WINCH\n"); if (svc_getargs(transp, xdr_rex_ttysize, (char *)&size) == FALSE) { svcerr_decode(transp); exit(1); } SetPtySize(&size); if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "rexd: window change reply failed"); exit(1); } } return; case REXPROC_SIGNAL: { int sigNumber; if (Debug) /* XXX */ printf("dorex: call to REXPROC_SIGNAL\n"); if (svc_getargs(transp, xdr_int, (char *)&sigNumber) == FALSE) { svcerr_decode(transp); exit(1); } SendSignal(sigNumber); if (svc_sendreply(transp, xdr_void, 0) == FALSE) { fprintf(stderr, "rexd: signal reply failed"); exit(1); } } return; case REXPROC_WAIT: if (Debug) /* XXX */ printf("dorex: call to REXPROC_WAIT\n"); result.rlt_stat = rex_wait(&result.rlt_message); if (svc_sendreply(transp, xdr_rex_result, (char *)&result) == FALSE) { fprintf(stderr, "rexd: reply failed\n"); exit(1); } rex_cleanup(); exit(0); /* NOTREACHED */ default: if (Debug) printf("dorex: call to bad process!\n"); svcerr_noproc(transp); exit(1); } } /* * signal handler for SIGCHLD - called when user process dies or is stopped */ void CatchChild(int junk) { pid_t pid; int status; if (Debug) printf("Enter Catchild\n"); while ((pid = waitpid((pid_t)-1, &status, WNOHANG|WUNTRACED)) > 0) { if (Debug) printf("After waitpid\n"); if (pid == child) { if (Debug) printf("pid==child\n"); if (WIFSTOPPED(status)) { sigset_t nullsigset; if (Debug) printf("WIFSTOPPED\n"); /* tell remote client to stop */ send(OutputSocket, "", 1, MSG_OOB); sigemptyset(&nullsigset); /* port of BSD sigpause(0); */ sigsuspend(&nullsigset); /* restart child */ /* killpg() of SunOS 4.1.1 */ kill((-child), SIGCONT); return; } /* * XXX this probably does not cover all interesting * exit cases hence reread the man page to determine * if we need more data or more test cases */ ChildStatus = status; ChildDied = 1; if (HasHelper && svc_pollfd[Master].fd == -1) { if (Debug) printf("Within If HasHelper\n"); KillHelper(child); HasHelper = 0; } } } } /* * oob -- called when we should restart the stopped child. */ void oob(int junk) { int atmark; char waste[BUFSIZ], mark; for (;;) { if (ioctl(OutputSocket, SIOCATMARK, &atmark) < 0) { perror("ioctl"); break; } if (atmark) break; (void) read(OutputSocket, waste, sizeof (waste)); } (void) recv(OutputSocket, &mark, 1, MSG_OOB); } /* * rex_wait - wait for command to finish, unmount the file system, * and return the exit status. * message gets an optional string error message. */ int rex_wait(message) char **message; { static char error[1024]; int count; *message = error; strcpy(error, ""); if (child == 0) { errprintf(error, "No process to wait for!\n"); rex_cleanup(); return (1); } kill(child, SIGHUP); for (count = 0; !ChildDied && count < WaitLimit; count++) sleep(1); if (ChildStatus & 0xFF) return (ChildStatus); return (ChildStatus >> 8); } /* * cleanup - unmount and remove our temporary directory */ void rex_cleanup() { if (tmpdir) { if (child && !ChildDied) { fprintf(stderr, "rexd: child killed to unmount %s\r\n", nfsdir); kill(child, SIGKILL); } chdir("/"); if (nfsdir[0] && umount_nfs(nfsdir, tmpdir)) fprintf(stderr, "rexd: couldn't umount %s from %s\r\n", nfsdir, tmpdir); if (rmdir(tmpdir) < 0) if (errno != EBUSY) perror("rmdir"); tmpdir = NULL; } if (Debug) printf("rex_cleaup: HasHelper=%d\n", HasHelper); if (HasHelper) KillHelper(child); HasHelper = 0; } /* * This function does the server work to get a command executed * Returns 0 if OK, nonzero if error */ int rex_startup(rst, ucred, message, calleraddr) struct rex_start *rst; struct authunix_parms *ucred; char **message; struct sockaddr_in *calleraddr; { char hostname[255]; char *p, *wdhost, *fsname, *subdir; char dirbuf[1024]; static char error[1024]; char defaultShell[1024]; /* command executed if none given */ char defaultDir[1024]; /* directory used if none given */ int len; int fd0, fd1, fd2; extern pam_handle_t *pamh; char *user = NULL; if (Debug) printf("Beginning of Rex_Startup\n"); if (child) { /* already started */ if (Debug) printf("Killing \"child\" process\n"); kill((-child), SIGKILL); /* killpg() of SunOS 4.1.1 */ return (1); } *message = error; (void) strcpy(error, ""); /* sigset(SIGCHLD, CatchChild); */ if (ValidUser(ucred->aup_machname, (uid_t)ucred->aup_uid, (gid_t)ucred->aup_gid, error, defaultShell, defaultDir, rst)) return (1); if (rst->rst_fsname && strlen(rst->rst_fsname)) { fsname = rst->rst_fsname; subdir = rst->rst_dirwithin; wdhost = rst->rst_host; } else { fsname = defaultDir; subdir = ""; wdhost = hostname; } sysinfo(SI_HOSTNAME, hostname, 255); if (Debug) printf("rexd: errno %d after gethostname\n", errno); if (Debug) { printf("rex_startup on host %s:\nrequests fsname=%s", hostname, fsname); printf("\t\tsubdir=%s\t\twdhost=%s\n", subdir, wdhost); } if (strcmp(wdhost, hostname) == 0) { /* * The requested directory is local to our machine, * so just change to it. */ strcpy(dirbuf, fsname); } else { static char wanted[1024]; static char mountedon[1024]; strcpy(wanted, wdhost); strcat(wanted, ":"); strcat(wanted, fsname); if (AlreadyMounted(wanted, mountedon)) { if (Debug) printf("AlreadyMounted (%d)\n", errno); /* * The requested directory is already mounted. If the * mount is not by another rexd, just change to it. * Otherwise, mount it again. If just changing to * the mounted directy, be careful. It might be mounted * in a different place. * (dirbuf is modified in place!) */ if (strncmp(mountedon, TempName, TempMatch) == 0) { tmpdir = mktemp(TempName); /* * XXX errno is set to ENOENT on success * of mktemp because of accesss checks for file */ if (errno == ENOENT) errno = 0; if (mkdir(tmpdir, 0777)) { perror("Already Mounted"); if (pamh) { pam_end(pamh, PAM_ABORT); pamh = NULL; } return (1); } if (Debug) printf("created %s (%d)\n", tmpdir, errno); strcpy(nfsdir, wanted); if (mount_nfs(wanted, tmpdir, error)) { if (Debug) printf("mount_nfs:error return\n"); if (pamh) { pam_end(pamh, PAM_ABORT); pamh = NULL; } return (1); } if (Debug) printf("mount_nfs: success return\n"); strcpy(dirbuf, tmpdir); } else strcpy(dirbuf, mountedon); } else { if (Debug) printf("not AlreadyMounted (%d)\n", errno); /* * The requested directory is not mounted anywhere, * so try to mount our own copy of it. We set nfsdir * so that it gets unmounted later, and tmpdir so that * it also gets removed when we are done. */ tmpdir = mktemp(TempName); /* * XXX errno is set to ENOENT on success of mktemp * becuase of accesss checks for file */ if (errno == ENOENT) errno = 0; if (mkdir(tmpdir, 0777)) { perror("Not Already Mounted"); if (pamh) { pam_end(pamh, PAM_ABORT); pamh = NULL; } return (1); } if (Debug) printf("created %s (%d)\n", tmpdir, errno); strcpy(nfsdir, wanted); if (mount_nfs(wanted, tmpdir, error)) { if (Debug) printf("mount_nfs:error return\n"); if (pamh) { pam_end(pamh, PAM_ABORT); pamh = NULL; } return (1); } if (Debug) printf("mount_nfs: success return\n"); strcpy(dirbuf, tmpdir); } } /* * "dirbuf" now contains the local mount point, so just tack on * the subdirectory to get the pathname to which we "chdir" */ strcat(dirbuf, subdir); fd0 = socket(AF_INET, SOCK_STREAM, 0); if (Debug) printf("Before doconnect\n"); fd0 = doconnect(calleraddr, rst->rst_port0, fd0); OutputSocket = fd0; /* * Arrange for fd0 to send the SIGURG signal when out-of-band data * arrives, which indicates that we should send the stopped child a * SIGCONT signal so that we can resume work. */ (void) fcntl(fd0, F_SETOWN, getpid()); /* ioctl(fd0, SIOCSPGRP, ?X?); */ sigset(SIGURG, oob); if (Debug) printf("Before \"use same port\"\n"); if (rst->rst_port0 == rst->rst_port1) { /* * use the same connection for both stdin and stdout */ fd1 = fd0; } if (rst->rst_flags & REX_INTERACTIVE) { /* * allocate a pseudo-terminal if necessary */ if (Debug) printf("Before AllocatePty call\n"); /* AllocatePty has grantpt() call which has bug */ /* Hence clear SIGCHLD handler setting */ sigset(SIGCHLD, SIG_DFL); if (AllocatePty(fd0, fd1)) { errprintf(error, "rexd: cannot allocate a pty\n"); if (pamh) { pam_end(pamh, PAM_ABORT); pamh = NULL; } return (1); } HasHelper = 1; } /* * this sigset()call moved to after AllocatePty() call * because a bug in waitpid() inside grantpt() * causes CatchChild() to be invoked. */ sigset(SIGCHLD, CatchChild); if (rst->rst_flags & REX_INTERACTIVE) { sigset(SIGWINCH, sigwinch); /* a dummy signal handler */ /* block the sigpause until signal in */ /* child releases the signal */ sighold(SIGWINCH); } if (Debug) printf("Before a \"child\" fork\n"); child = fork(); if (child < 0) { errprintf(error, "rexd: can't fork\n"); if (pamh) { pam_end(pamh, PAM_ABORT); pamh = NULL; } return (1); } if (child) { /* * parent rexd: close network connections if needed, * then return to the main loop. */ if ((rst->rst_flags & REX_INTERACTIVE) == 0) { close(fd0); close(fd1); } if (Debug) printf("Parent ret to main loop, child does startup\n"); if (pamh) { pam_end(pamh, PAM_SUCCESS); pamh = NULL; } return (0); } /* child rexd */ if (Debug) printf("Child rexd\n"); /* setpgrp(0, 0) */ setsid(); /* make session leader */ if (Debug) printf("After setsid\n"); if (rst->rst_flags & REX_INTERACTIVE) { if (Debug) printf("Before OpenPtySlave\n"); /* reopen slave so that child has controlling tty */ OpenPtySlave(); if (Debug) printf("After OpenPtySlave\n"); } if (rst->rst_port0 != rst->rst_port1) { if (Debug) printf("rst_port0 != rst_port1\n"); /* XXX */ fd1 = socket(AF_INET, SOCK_STREAM, 0); shutdown(fd0, 1); /* 1=>further sends disallowed */ fd1 = doconnect(calleraddr, rst->rst_port1, fd1); shutdown(fd1, 0); /* 0=>further receives disallowed */ } if (rst->rst_port1 == rst->rst_port2) { if (Debug) printf("rst_port1 == rst_port2\n"); /* XXX */ /* * Use the same connection for both stdout and stderr */ fd2 = fd1; } else { if (Debug) printf("rst_port1 != rst_port2\n"); /* XXX */ fd2 = socket(AF_INET, SOCK_STREAM, 0); fd2 = doconnect(calleraddr, rst->rst_port2, fd2); shutdown(fd2, 0); /* 0=>further receives disallowed */ } if (rst->rst_flags & REX_INTERACTIVE) { /* * use ptys instead of sockets in interactive mode */ DoHelper(&fd0, &fd1, &fd2); LoginUser(); } dup2(fd0, 0); dup2(fd1, 1); dup2(fd2, 2); /* setup terminal ID (use read file descriptor) */ if (audit_settid(fd0) != 0) { errprintf("cannot set audit characteristics\n"); return (1); } closefrom(3); if (Debug) printf("After close-all-fds-loop-- errno=%d\n", errno); environ = rst->rst_env; if (pam_get_item(pamh, PAM_USER, (void **)&user) != PAM_SUCCESS) { audit_rexd_fail("user id is not valid", ucred->aup_machname, user, ucred->aup_uid, ucred->aup_gid, defaultShell, rst->rst_cmd); /* BSM */ fprintf(stderr, "rexd: invalid uid/gid.\n"); exit(1); } /* set the real (and effective) GID */ if (setgid(ucred->aup_gid) == -1) { fprintf(stderr, "rexd: invalid gid.\n"); exit(1); } /* Set the supplementary group access list. */ if (setgroups(ucred->aup_len, (gid_t *)ucred->aup_gids) == -1) { fprintf(stderr, "rexd: invalid group list.\n"); exit(1); } if (pam_setcred(pamh, PAM_ESTABLISH_CRED) != PAM_SUCCESS) { audit_rexd_fail("user id is not valid", ucred->aup_machname, user, ucred->aup_uid, ucred->aup_gid, defaultShell, rst->rst_cmd); /* BSM */ fprintf(stderr, "rexd: invalid uid/gid.\n"); exit(1); } audit_rexd_success(ucred->aup_machname, user, ucred->aup_uid, ucred->aup_gid, defaultShell, rst->rst_cmd); /* BSM */ /* set the real (and effective) UID */ if (setuid(ucred->aup_uid) == -1) { fprintf(stderr, "rexd: invalid uid.\n"); exit(1); } if (pamh) { pam_end(pamh, PAM_SUCCESS); pamh = NULL; } if (Debug) /* XXX */ fprintf(stderr, "uid %d gid %d (%d)\n", ucred->aup_uid, ucred->aup_gid, errno); if (chdir(dirbuf)) { fprintf(stderr, "rexd: can't chdir to %s\n", dirbuf); exit(1); } sigset(SIGINT, SIG_DFL); sigset(SIGHUP, SIG_DFL); sigset(SIGQUIT, SIG_DFL); if (rst->rst_flags & REX_INTERACTIVE) { /* pause to sync with first SIGWINCH sent as part of */ sigpause(SIGWINCH); /* protocol and handled by parent doing other rex primitves */ sigrelse(SIGWINCH); sigset(SIGWINCH, SIG_DFL); } if (rst->rst_cmd == (char **)NULL) { /* * Null command means execute the default shell for this user */ char *args[2]; args[0] = defaultShell; args[1] = NULL; execvp(defaultShell, args); fprintf(stderr, "rexd: can't exec shell %s\n", defaultShell); exit(1); } if (Debug) for (len = 0; rst->rst_cmd[len] != (char *)NULL && *rst->rst_cmd[len] != NULL; len++) printf("cmds: %s (%d)\n", rst->rst_cmd[len], errno); /* XXX */ if (Debug) for (len = 0; rst->rst_env[len] != (char *)NULL && *rst->rst_env[len] != NULL; len++) printf("envs: %s\n", rst->rst_env[len]); execvp(rst->rst_cmd[0], rst->rst_cmd); /* XXX get rid of errno in parens */ fprintf(stderr, "rexd: can't exec %s (%d)\n", *rst->rst_cmd, errno); exit(1); } /* * Search the mount table to see if the given file system is already * mounted. If so, return the place that it is mounted on. */ int AlreadyMounted(fsname, mountedon) char *fsname; char *mountedon; { FILE *table; struct mnttab mt; table = setmntent(MOUNTED, "r"); if (table == NULL) return (0); while ((getmntent(table, &mt)) != (-1)) { if (strcmp(mt.mnt_special, fsname) == 0) { strcpy(mountedon, mt.mnt_mountp); endmntent(table); return (1); } } endmntent(table); return (0); } /* * connect to the indicated IP address/port, and return the * resulting file descriptor. */ int doconnect(sin, port, fd) struct sockaddr_in *sin; short port; int fd; { sin->sin_port = ntohs(port); if (connect(fd, (struct sockaddr *)sin, sizeof (*sin))) { perror("rexd: connect"); exit(1); } return (fd); } void sigwinch(int junk) { } /* * SETPROCTITLE -- set the title of this process for "ps" * * Does nothing if there were not enough arguments on the command * line for the information. * * Side Effects: * Clobbers argv[] of our main procedure. */ void setproctitle(user, host) char *user, *host; { register char *tohere; tohere = Argv[0]; if ((int)(LastArgv == NULL) || (int)(strlen(user)+strlen(host)+3) > (int)(LastArgv - tohere)) return; *tohere++ = '-'; /* So ps prints (rpc.rexd) */ sprintf(tohere, "%s@%s", user, host); while (*tohere++) /* Skip to end of printf output */ ; while (tohere < LastArgv) /* Avoid confusing ps */ *tohere++ = ' '; } /* * Determine if started from inetd or not */ int isfrominetd(fd) int fd; { /* * If fd looks like a TLI endpoint, we assume * that we were started by a port monitor. If * t_getstate fails with TBADF, this is not a * TLI endpoint. */ if (t_getstate(0) != -1 || t_errno != TBADF) return (1); return (0); }