/* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. * */ #define _FILE_OFFSET_BITS 64 #include #include #include #include /* just for FIONBIO ... */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* signal disposition - signal handler or SIG_IGN, SIG_ERR, etc. */ typedef void (*sigdisp_t)(int); extern errcode_t profile_get_options_boolean(profile_t, char **, profile_options_boolean *); extern errcode_t profile_get_options_string(profile_t, char **, profile_option_strings *); #define RSH_BUFSIZ (1024 * 50) static char des_inbuf[2 * RSH_BUFSIZ]; /* needs to be > largest read size */ static char des_outbuf[2 * RSH_BUFSIZ]; /* needs to be > largest write size */ static krb5_data desinbuf, desoutbuf; static krb5_encrypt_block eblock; /* eblock for encrypt/decrypt */ static krb5_context bsd_context = NULL; static krb5_auth_context auth_context; static krb5_creds *cred; static krb5_keyblock *session_key; static int encrypt_flag; /* Flag set, when encryption is used */ static int krb5auth_flag; /* Flag set, when KERBEROS is enabled */ static profile_options_boolean autologin_option[] = { { "autologin", &krb5auth_flag, 0 }, { NULL, NULL, 0 } }; static int no_krb5auth_flag = 0; static int fflag; /* Flag set, if creds to be fwd'ed via -f */ static int Fflag; /* Flag set, if fwd'able creds to be fwd'ed via -F */ /* Flag set, if -PN / -PO is specified */ static boolean_t rcmdoption_done; /* Flags set, if corres. cmd line options are turned on */ static boolean_t encrypt_done, fwd_done, fwdable_done; static profile_options_boolean option[] = { { "encrypt", &encrypt_flag, 0 }, { "forward", &fflag, 0 }, { "forwardable", &Fflag, 0 }, { NULL, NULL, 0 } }; static char *rcmdproto; static profile_option_strings rcmdversion[] = { { "rcmd_protocol", &rcmdproto, 0 }, { NULL, NULL, 0 } }; static char *realmdef[] = { "realms", NULL, "rsh", NULL }; static char *appdef[] = { "appdefaults", "rsh", NULL }; static void sendsig(int); static sigdisp_t sigdisp(int); static boolean_t init_service(boolean_t); static int desrshread(int, char *, int); static int desrshwrite(int, char *, int); static int options; static int rfd2; static int portnumber; static const char rlogin_path[] = "/usr/bin/rlogin"; static const char dash_x[] = "-x "; /* Note the blank after -x */ static boolean_t readiv, writeiv; #define set2mask(setp) ((setp)->__sigbits[0]) #define mask2set(mask, setp) \ ((mask) == -1 ? sigfillset(setp) : (set2mask(setp) = (mask))) #ifdef DEBUG #define DEBUGOPTSTRING "D:" #else #define DEBUGOPTSTRING "" #endif /* DEBUG */ static void sigsetmask(int mask) { sigset_t nset; (void) sigprocmask(0, NULL, &nset); mask2set(mask, &nset); (void) sigprocmask(SIG_SETMASK, &nset, NULL); } static int sigblock(int mask) { sigset_t oset; sigset_t nset; (void) sigprocmask(0, NULL, &nset); mask2set(mask, &nset); (void) sigprocmask(SIG_BLOCK, &nset, &oset); return (set2mask(&oset)); } /* * Get signal disposition (or signal handler) for a given signal */ static sigdisp_t sigdisp(int sig) { struct sigaction act; act.sa_handler = NULL; act.sa_flags = 0; (void) sigemptyset(&act.sa_mask); (void) sigaction(sig, NULL, &act); return (act.sa_handler); } static pid_t child_pid = -1; /* * If you do a command like "rsh host output | wc" * and wc terminates, then the parent will receive SIGPIPE * and the child needs to be terminated. */ /* ARGSUSED */ static void sigpipehandler(int signal) { if (child_pid != -1) (void) kill(child_pid, SIGKILL); exit(EXIT_SUCCESS); } #define mask(s) (1 << ((s) - 1)) static void usage(void) { (void) fprintf(stderr, "%s\n%s\n", gettext("usage: rsh [ -PN / -PO ] [ -l login ] [ -n ] " "[ -k realm ] [ -a ] [ -x ] [ -f / -F ] host command"), gettext(" rsh [ -PN / -PO ] [ -l login ] [ -k realm ] " "[ -a ] [ -x ] [ -f / -F ] host")); exit(EXIT_FAILURE); } static void die(const char *message) { (void) fputs(message, stderr); usage(); } static void usage_forward(void) { die(gettext("rsh: Only one of -f and -F allowed.\n")); } /* * rsh - remote shell */ /* VARARGS */ int main(int argc, char **argv) { int c, rem; char *cmd, *cp, **ap, buf[RSH_BUFSIZ], **argv0, *args, *args_no_x; char *host = NULL, *user = NULL; int cc; boolean_t asrsh = B_FALSE; struct passwd *pwd; boolean_t readfrom_rem; boolean_t readfrom_rfd2; int one = 1; int omask; boolean_t nflag = B_FALSE; char *krb_realm = NULL; krb5_flags authopts; krb5_error_code status; enum kcmd_proto kcmd_proto = KCMD_NEW_PROTOCOL; uid_t uid = getuid(); c = (argc + 1) * sizeof (char *); if ((argv0 = malloc(c)) == NULL) { perror("malloc"); return (EXIT_FAILURE); } (void) memcpy(argv0, argv, c); (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* * Determine command name used to invoke to rlogin(1). Users can * create links named by a host pointing to the binary and type * "hostname" to log into that host afterwards. */ cmd = strrchr(argv[0], '/'); cmd = (cmd != NULL) ? (cmd + 1) : argv[0]; /* * Add "remsh" as an alias for "rsh" (System III, V networking * add-ons often used this name for the remote shell since rsh * was already taken for the restricted shell). Note that this * usurps the ability to use "remsh" as the name of a host (by * symlinking it to rsh), so we go one step farther: if the * file "/usr/bin/remsh" does not exist, we behave as if "remsh" * is a host name. If it does exist, we accept "remsh" as an * "rsh" alias. */ if (strcmp(cmd, "remsh") == 0) { struct stat sb; if (stat("/usr/bin/remsh", &sb) < 0) host = cmd; } else if (strcmp(cmd, "rsh") != 0) { host = cmd; } /* Handle legacy synopsis "rsh hostname options [command]". */ if (host == NULL) { if (argc < 2) usage(); if (*argv[1] != '-') { host = argv[1]; argc--; argv[1] = argv[0]; argv++; asrsh = B_TRUE; } } while ((c = getopt(argc, argv, DEBUGOPTSTRING "8AFKLP:ade:fk:l:nwx")) != -1) { switch (c) { #ifdef DEBUG case 'D': portnumber = htons(atoi(optarg)); krb5auth_flag++; break; #endif /* DEBUG */ case 'F': if (fflag) usage_forward(); Fflag = 1; krb5auth_flag++; fwdable_done = B_TRUE; break; case 'f': if (Fflag) usage_forward(); fflag = 1; krb5auth_flag++; fwd_done = B_TRUE; break; case 'P': if (strcmp(optarg, "N") == 0) kcmd_proto = KCMD_NEW_PROTOCOL; else if (strcmp(optarg, "O") == 0) kcmd_proto = KCMD_OLD_PROTOCOL; else die(gettext("rsh: Only -PN or -PO " "allowed.\n")); if (rcmdoption_done) die(gettext("rsh: Only one of -PN and -PO " "allowed.\n")); rcmdoption_done = B_TRUE; krb5auth_flag++; break; case 'a': krb5auth_flag++; break; case 'K': no_krb5auth_flag++; break; case 'd': options |= SO_DEBUG; break; case 'k': krb_realm = optarg; krb5auth_flag++; break; case 'l': user = optarg; break; case 'n': if (!nflag) { if (close(STDIN_FILENO) < 0) { perror("close"); return (EXIT_FAILURE); } /* * "STDION_FILENO" defined to 0 by POSIX * and hence the lowest file descriptor. * So the open(2) below is guaranteed to * reopen it because we closed it above. */ if (open("/dev/null", O_RDONLY) < 0) { perror("open"); return (EXIT_FAILURE); } nflag = B_TRUE; } break; case 'x': encrypt_flag = 1; krb5auth_flag++; encrypt_done = B_TRUE; break; /* * Ignore the -L, -w, -e and -8 flags to allow aliases with * rlogin to work. Actually rlogin(1) doesn't understand * -w either but because "rsh -w hostname command" used * to work we still accept it. */ case '8': case 'L': case 'e': case 'w': /* * On the lines of the -L, -w, -e and -8 options above, we * ignore the -A option too, in order to allow aliases with * rlogin to work. * * Mind you !, the -a option to trigger Kerberos authentication * in rsh, has a totally different usage in rlogin, its the * -A option (in rlogin) which needs to be used to talk * Kerberos. */ case 'A': break; default: usage(); } } argc -= optind; argv += optind; if (host == NULL) { if (argc == 0) usage(); argc--; host = *argv++; asrsh = B_TRUE; } if (argc == 0) { (void) setreuid(uid, uid); if (nflag) usage(); if (asrsh) *argv0 = "rlogin"; (void) execv(rlogin_path, argv0); perror(rlogin_path); (void) fprintf(stderr, gettext("No local rlogin " "program found.\n")); return (EXIT_FAILURE); } if (__init_suid_priv(0, PRIV_NET_PRIVADDR, NULL) == -1) { (void) fprintf(stderr, gettext("Insufficient privileges, " "rsh must be set-uid root\n")); return (EXIT_FAILURE); } pwd = getpwuid(uid); if (pwd == NULL) { (void) fprintf(stderr, gettext("who are you?\n")); return (EXIT_FAILURE); } if (user == NULL) user = pwd->pw_name; /* * if the user disables krb5 on the cmdline (-K), then skip * all krb5 setup. * * if the user does not disable krb5 or enable krb5 on the * cmdline, check krb5.conf to see if it should be enabled. */ if (no_krb5auth_flag) { krb5auth_flag = 0; Fflag = fflag = encrypt_flag = 0; } else if (!krb5auth_flag) { /* is autologin set in krb5.conf? */ status = krb5_init_context(&bsd_context); /* don't sweat failure here */ if (!status) { /* * note that the call to profile_get_options_boolean * with autologin_option can affect value of * krb5auth_flag */ (void) profile_get_options_boolean(bsd_context->profile, appdef, autologin_option); } } if (krb5auth_flag) { if (!bsd_context) { status = krb5_init_context(&bsd_context); if (status) { com_err("rsh", status, "while initializing krb5"); return (EXIT_FAILURE); } } /* * Get our local realm to look up local realm options. */ status = krb5_get_default_realm(bsd_context, &realmdef[1]); if (status) { com_err("rsh", status, gettext("while getting default realm")); return (EXIT_FAILURE); } /* * Check the realms section in krb5.conf for encryption, * forward & forwardable info */ (void) profile_get_options_boolean(bsd_context->profile, realmdef, option); /* * Check the appdefaults section */ (void) profile_get_options_boolean(bsd_context->profile, appdef, option); (void) profile_get_options_string(bsd_context->profile, appdef, rcmdversion); /* * Set the *_flag variables, if the corresponding *_done are * set to 1, because we dont want the config file values * overriding the command line options. */ if (encrypt_done) encrypt_flag = 1; if (fwd_done) { fflag = 1; Fflag = 0; } else if (fwdable_done) { Fflag = 1; fflag = 0; } if (!rcmdoption_done && (rcmdproto != NULL)) { if (strncmp(rcmdproto, "rcmdv2", 6) == 0) { kcmd_proto = KCMD_NEW_PROTOCOL; } else if (strncmp(rcmdproto, "rcmdv1", 6) == 0) { kcmd_proto = KCMD_OLD_PROTOCOL; } else { (void) fprintf(stderr, gettext("Unrecognized " "KCMD protocol (%s)"), rcmdproto); return (EXIT_FAILURE); } } if (encrypt_flag && (!krb5_privacy_allowed())) { (void) fprintf(stderr, gettext("rsh: Encryption not " "supported.\n")); return (EXIT_FAILURE); } } /* * Connect with the service (shell/kshell) on the daemon side */ if (portnumber == 0) { while (!init_service(krb5auth_flag)) { /* * Connecting to the 'kshell' service failed, * fallback to normal rsh; Reset all KRB5 flags * and connect to 'shell' service on the server */ krb5auth_flag = 0; encrypt_flag = fflag = Fflag = 0; } } cc = encrypt_flag ? strlen(dash_x) : 0; for (ap = argv; *ap != NULL; ap++) cc += strlen(*ap) + 1; cp = args = malloc(cc); if (cp == NULL) perror("malloc"); if (encrypt_flag) { int length; length = strlcpy(args, dash_x, cc); cp += length; cc -= length; } args_no_x = args; for (ap = argv; *ap != NULL; ap++) { int length; length = strlcpy(cp, *ap, cc); assert(length < cc); cp += length; cc -= length; if (ap[1] != NULL) { *cp++ = ' '; cc--; } } if (krb5auth_flag) { authopts = AP_OPTS_MUTUAL_REQUIRED; /* * Piggy-back forwarding flags on top of authopts; * they will be reset in kcmd */ if (fflag || Fflag) authopts |= OPTS_FORWARD_CREDS; if (Fflag) authopts |= OPTS_FORWARDABLE_CREDS; status = kcmd(&rem, &host, portnumber, pwd->pw_name, user, args, &rfd2, "host", krb_realm, bsd_context, &auth_context, &cred, NULL, /* No need for sequence number */ NULL, /* No need for server seq # */ authopts, 1, /* Always set anyport */ &kcmd_proto); if (status != 0) { /* * If new protocol requested, we dont fallback to * less secure ones. */ if (kcmd_proto == KCMD_NEW_PROTOCOL) { (void) fprintf(stderr, gettext("rsh: kcmdv2 " "to host %s failed - %s\n" "Fallback to normal rsh denied."), host, error_message(status)); return (EXIT_FAILURE); } /* check NO_TKT_FILE or equivalent... */ if (status != -1) { (void) fprintf(stderr, gettext("rsh: kcmd to host %s failed - %s\n" "trying normal rsh...\n\n"), host, error_message(status)); } else { (void) fprintf(stderr, gettext("trying normal rsh...\n")); } /* * kcmd() failed, so we now fallback to normal rsh, * after resetting the KRB5 flags and the 'args' array */ krb5auth_flag = 0; encrypt_flag = fflag = Fflag = 0; args = args_no_x; (void) init_service(B_FALSE); } else { /* * Set up buffers for desread and deswrite. */ desinbuf.data = des_inbuf; desoutbuf.data = des_outbuf; desinbuf.length = sizeof (des_inbuf); desoutbuf.length = sizeof (des_outbuf); session_key = &cred->keyblock; if (kcmd_proto == KCMD_NEW_PROTOCOL) { status = krb5_auth_con_getlocalsubkey( bsd_context, auth_context, &session_key); if (status) { com_err("rsh", status, "determining subkey for session"); return (EXIT_FAILURE); } if (session_key == NULL) { com_err("rsh", 0, "no subkey " "negotiated for connection"); return (EXIT_FAILURE); } } eblock.crypto_entry = session_key->enctype; eblock.key = (krb5_keyblock *)session_key; init_encrypt(encrypt_flag, bsd_context, kcmd_proto, &desinbuf, &desoutbuf, CLIENT, &eblock); if (encrypt_flag) { char *s = gettext("This rsh session is using " "encryption for all data transmissions."); (void) write(STDERR_FILENO, s, strlen(s)); (void) write(STDERR_FILENO, "\r\n", 2); } } } /* * Don't merge this with the "if" statement above because * "krb5auth_flag" might be set to false inside it. */ if (!krb5auth_flag) { rem = rcmd_af(&host, portnumber, pwd->pw_name, user, args, &rfd2, AF_INET6); if (rem < 0) return (EXIT_FAILURE); } __priv_relinquish(); if (rfd2 < 0) { (void) fprintf(stderr, gettext("rsh: can't establish " "stderr\n")); return (EXIT_FAILURE); } if (options & SO_DEBUG) { if (setsockopt(rem, SOL_SOCKET, SO_DEBUG, (char *)&one, sizeof (one)) < 0) perror("rsh: setsockopt (stdin)"); if (setsockopt(rfd2, SOL_SOCKET, SO_DEBUG, (char *)&one, sizeof (one)) < 0) perror("rsh: setsockopt (stderr)"); } omask = sigblock(mask(SIGINT)|mask(SIGQUIT)|mask(SIGTERM)); if (sigdisp(SIGINT) != SIG_IGN) (void) sigset(SIGINT, sendsig); if (sigdisp(SIGQUIT) != SIG_IGN) (void) sigset(SIGQUIT, sendsig); if (sigdisp(SIGTERM) != SIG_IGN) (void) sigset(SIGTERM, sendsig); if (nflag) { (void) shutdown(rem, SHUT_WR); } else { child_pid = fork(); if (child_pid < 0) { perror("rsh: fork"); return (EXIT_FAILURE); } if (!encrypt_flag) { (void) ioctl(rfd2, FIONBIO, &one); (void) ioctl(rem, FIONBIO, &one); } if (child_pid == 0) { /* Child */ fd_set remset; char *bp; int wc; (void) close(rfd2); reread: errno = 0; cc = read(0, buf, sizeof (buf)); if (cc <= 0) goto done; bp = buf; rewrite: FD_ZERO(&remset); FD_SET(rem, &remset); if (select(rem + 1, NULL, &remset, NULL, NULL) < 0) { if (errno != EINTR) { perror("rsh: select"); return (EXIT_FAILURE); } goto rewrite; } if (!FD_ISSET(rem, &remset)) goto rewrite; writeiv = B_FALSE; wc = desrshwrite(rem, bp, cc); if (wc < 0) { if (errno == EWOULDBLOCK) goto rewrite; goto done; } cc -= wc; bp += wc; if (cc == 0) goto reread; goto rewrite; done: (void) shutdown(rem, SHUT_WR); return (EXIT_SUCCESS); } } #define MAX(a, b) (((a) > (b)) ? (a) : (b)) sigsetmask(omask); readfrom_rem = B_TRUE; readfrom_rfd2 = B_TRUE; (void) sigset(SIGPIPE, sigpipehandler); do { fd_set readyset; FD_ZERO(&readyset); if (readfrom_rem) FD_SET(rem, &readyset); if (readfrom_rfd2) FD_SET(rfd2, &readyset); if (select(MAX(rem, rfd2) + 1, &readyset, NULL, NULL, NULL) < 0) { if (errno != EINTR) { perror("rsh: select"); return (EXIT_FAILURE); } continue; } if (FD_ISSET(rfd2, &readyset)) { errno = 0; readiv = B_TRUE; cc = desrshread(rfd2, buf, sizeof (buf)); if (cc <= 0) { if (errno != EWOULDBLOCK) readfrom_rfd2 = B_FALSE; } else { (void) write(STDERR_FILENO, buf, cc); } } if (FD_ISSET(rem, &readyset)) { errno = 0; readiv = B_FALSE; cc = desrshread(rem, buf, sizeof (buf)); if (cc <= 0) { if (errno != EWOULDBLOCK) readfrom_rem = B_FALSE; } else (void) write(STDOUT_FILENO, buf, cc); } } while (readfrom_rem || readfrom_rfd2); if (!nflag) (void) kill(child_pid, SIGKILL); return (EXIT_SUCCESS); } static void sendsig(int signum) { char buffer; writeiv = B_TRUE; buffer = (char)signum; (void) desrshwrite(rfd2, &buffer, 1); } static boolean_t init_service(boolean_t krb5flag) { struct servent *sp; if (krb5flag) { sp = getservbyname("kshell", "tcp"); if (sp == NULL) { (void) fprintf(stderr, gettext("rsh: kshell/tcp: unknown service.\n" "trying normal shell/tcp service\n")); return (B_FALSE); } } else { sp = getservbyname("shell", "tcp"); if (sp == NULL) { portnumber = htons(IPPORT_CMDSERVER); return (B_TRUE); } } portnumber = sp->s_port; return (B_TRUE); } static int desrshread(int fd, char *buf, int len) { return (desread(fd, buf, len, readiv ? 1 : 0)); } static int desrshwrite(int fd, char *buf, int len) { return (deswrite(fd, buf, len, writeiv ? 1 : 0)); }