root/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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
 */

/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1983-1989 AT&T    */
/*        All Rights Reserved   */

/*
 * Portions of this source code were derived from Berkeley 4.3 BSD
 * under license from the Regents of the University of California.
 */

#define _FILE_OFFSET_BITS 64

/*
 * remote shell server:
 *      remuser\0
 *      locuser\0
 *      command\0
 *      data
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/telioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/select.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <netdb.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <locale.h>

#include <sys/resource.h>
#include <sys/filio.h>
#include <shadow.h>
#include <stdlib.h>

#include <security/pam_appl.h>
#include <deflt.h>

#include <k5-int.h>
#include <krb5_repository.h>
#include <com_err.h>
#include <kcmd.h>

#include <addr_match.h>
#include <store_forw_creds.h>

#ifndef NCARGS
#define NCARGS  5120
#endif /* !NCARGS */

static void error(char *, ...);
static void doit(int, struct sockaddr_storage *, char **);
static void getstr(int, char *, int, char *);

static int legalenvvar(char *);
static void add_to_envinit(char *);
static int locale_envmatch(char *, char *);

/* Function decls. for functions not in any header file.  (Grrrr.) */
extern int audit_rshd_setup(void);
extern int audit_rshd_success(char *, char *, char *, char *);
extern int audit_rshd_fail(char *, char *, char *, char *, char *);
extern int audit_settid(int);

static int do_encrypt = 0;
static pam_handle_t *pamh;

/*
 * This is the shell/kshell daemon. The very basic protocol for checking
 * authentication and authorization is:
 * 1) Check authentication.
 * 2) Check authorization via the access-control files:
 *    ~/.k5login (using krb5_kuserok) and/or
 * Execute command if configured authoriztion checks pass, else deny
 * permission.
 *
 * The configuration is done either by command-line arguments passed by inetd,
 * or by the name of the daemon. If command-line arguments are present, they
 * take priority. The options are:
 * -k allow kerberos authentication (krb5 only; krb4 support is not provided)
 * -5 same as `-k', mainly for compatability with MIT
 * -e allow encrypted session
 * -c demand authenticator checksum
 * -i ignore authenticator checksum
 * -U Refuse connections that cannot be mapped to a name via `gethostbyname'
 * -s <tos>     Set the IP TOS option
 * -S <keytab>  Set the keytab file to use
 * -M <realm>   Set the Kerberos realm to use
 */

#define ARGSTR  "ek5ciUD:M:S:L:?:"
#define RSHD_BUFSIZ     (50 * 1024)

static krb5_context bsd_context;
static krb5_keytab keytab = NULL;
static krb5_ccache ccache = NULL;
static krb5_keyblock *sessionkey = NULL;

static int require_encrypt = 0;
static int resolve_hostname = 0;
static int krb5auth_flag = 0;   /* Flag set, when KERBEROS is enabled */
static enum kcmd_proto kcmd_protocol;

#ifdef DEBUG
static int debug_port = 0;
#endif /* DEBUG */

/*
 * There are two authentication related masks:
 * auth_ok and auth_sent.
 * The auth_ok mask is the or'ing of authentication
 * systems any one of which can be used.
 * The auth_sent mask is the or'ing of one or more authentication/authorization
 * systems that succeeded.  If the and'ing
 * of these two masks is true, then authorization is successful.
 */

#define AUTH_KRB5       (0x2)
static int auth_ok = 0;
static int auth_sent = 0;
static int checksum_required = 0;
static int checksum_ignored = 0;

/*
 * Leave room for 4 environment variables to be passed.
 * The "-L env_var" option has been added primarily to
 * maintain compatability with MIT.
 */
#define MAXENV  4
static char *save_env[MAXENV];
static int num_env = 0;

static void usage(void);
static krb5_error_code recvauth(int, int *);

/*ARGSUSED*/
int
main(int argc, char **argv, char **renvp)
{
        struct linger linger;
        int on = 1, fromlen;
        struct sockaddr_storage from;
        int fd = 0;

        extern int opterr, optind;
        extern char *optarg;
        int ch;
        int tos = -1;
        krb5_error_code status;

        openlog("rsh", LOG_PID | LOG_ODELAY, LOG_DAEMON);
        (void) audit_rshd_setup();      /* BSM */
        fromlen = sizeof (from);

        (void) setlocale(LC_ALL, "");

        /*
         * Analyze parameters.
         */
        opterr = 0;
        while ((ch = getopt(argc, argv, ARGSTR)) != EOF)
                switch (ch) {
                case '5':
                case 'k':
                        auth_ok |= AUTH_KRB5;
                        krb5auth_flag++;
                        break;

                case 'c':
                        checksum_required = 1;
                        krb5auth_flag++;
                        break;
                case 'i':
                        checksum_ignored = 1;
                        krb5auth_flag++;
                        break;

                case 'e':
                        require_encrypt = 1;
                        krb5auth_flag++;
                        break;
#ifdef DEBUG
                case 'D':
                        debug_port = atoi(optarg);
                        break;
#endif /* DEBUG */
                case 'U':
                        resolve_hostname = 1;
                        break;

                case 'M':
                        (void) krb5_set_default_realm(bsd_context, optarg);
                        krb5auth_flag++;
                        break;

                case 'S':
                        if ((status = krb5_kt_resolve(bsd_context, optarg,
                                &keytab))) {
                                com_err("rsh", status,
                                        gettext("while resolving "
                                                "srvtab file %s"), optarg);
                                exit(2);
                        }
                        krb5auth_flag++;
                        break;

                case 's':
                        if (optarg == NULL || ((tos = atoi(optarg)) < 0) ||
                                (tos > 255)) {
                                syslog(LOG_ERR, "rshd: illegal tos value: "
                                    "%s\n", optarg);
                        }
                        break;

                case 'L':
                        if (num_env < MAXENV) {
                                save_env[num_env] = strdup(optarg);
                                if (!save_env[num_env++]) {
                                        com_err("rsh", ENOMEM,
                                                gettext("in saving env"));
                                        exit(2);
                                }
                        } else {
                                (void) fprintf(stderr, gettext("rshd: Only %d"
                                                " -L arguments allowed\n"),
                                                MAXENV);
                                exit(2);
                        }
                        break;

                case '?':
                default:
                        usage();
                        exit(1);
                        break;
                }

        if (optind == 0) {
                usage();
                exit(1);
        }
        argc -= optind;
        argv += optind;

        if (krb5auth_flag > 0) {
                status = krb5_init_context(&bsd_context);
                if (status) {
                        syslog(LOG_ERR, "Error initializing krb5: %s",
                            error_message(status));
                        exit(1);
                }
        }

        if (!checksum_required && !checksum_ignored)
                checksum_ignored = 1;

        if (checksum_required && checksum_ignored) {
                syslog(LOG_CRIT, gettext("Checksums are required and ignored."
                "These options are mutually exclusive"
                "--check the documentation."));
                error("Configuration error: mutually exclusive "
                                "options specified.\n");
                exit(1);
        }

#ifdef DEBUG
        if (debug_port) {
                int s;
                struct sockaddr_in sin;

                if ((s = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) {
                        fprintf(stderr, gettext("Error in socket: %s\n"),
                                        strerror(errno));
                        exit(2);
                }
                (void) memset((char *)&sin, 0, sizeof (sin));
                sin.sin_family = AF_INET;
                sin.sin_port = htons(debug_port);
                sin.sin_addr.s_addr = INADDR_ANY;

                (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                        (char *)&on, sizeof (on));

                if ((bind(s, (struct sockaddr *)&sin, sizeof (sin))) < 0) {
                        (void) fprintf(stderr, gettext("Error in bind: %s\n"),
                                        strerror(errno));
                        exit(2);
                }
                if ((listen(s, 5)) < 0) {
                        (void) fprintf(stderr, gettext("Error in listen: %s\n"),
                                        strerror(errno));
                        exit(2);
                }
                if ((fd = accept(s, (struct sockaddr *)&from,
                                        &fromlen)) < 0) {
                        (void) fprintf(stderr, gettext("Error in accept: %s\n"),
                                        strerror(errno));
                        exit(2);
                }
                (void) close(s);
        }
        else
#endif /* DEBUG */
        {
                if (getpeername(STDIN_FILENO, (struct sockaddr *)&from,
                                (socklen_t *)&fromlen) < 0) {
                        (void) fprintf(stderr, "rshd: ");
                        perror("getpeername");
                        _exit(1);
                }
                fd = STDIN_FILENO;
        }

        if (audit_settid(fd) != 0) {
                perror("settid");
                exit(1);
        }

        if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&on,
            sizeof (on)) < 0)
                syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
        linger.l_onoff = 1;
        linger.l_linger = 60;                   /* XXX */
        if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&linger,
            sizeof (linger)) < 0)
                syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m");

        if ((tos != -1) && (setsockopt(fd, IPPROTO_IP, IP_TOS, (char *)&tos,
                                sizeof (tos)) < 0) &&
                                (errno != ENOPROTOOPT)) {
                syslog(LOG_ERR, "setsockopt (IP_TOS %d): %m");
        }

        doit(dup(fd), &from, renvp);
        return (0);
}

/*
 * locale environments to be passed to shells.
 */
static char *localeenv[] = {
        "LANG",
        "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
        "LC_MONETARY", "LC_MESSAGES", "LC_ALL", NULL};

/*
 * The following is for the environment variable list
 * used in the call to execle().  envinit is declared here,
 * but populated after the call to getpwnam().
 */
static char     *homedir;       /* "HOME=" */
static char     *shell;         /* "SHELL=" */
static char     *username;      /* "USER=" */
static char     *tz;            /* "TZ=" */

static char     homestr[] = "HOME=";
static char     shellstr[] = "SHELL=";
static char     userstr[] = "USER=";
static char     tzstr[] = "TZ=";

static char     **envinit;
#define PAM_ENV_ELIM    16      /* allow 16 PAM environment variables */
#define USERNAME_LEN    16      /* maximum number of characters in user name */

/*
 *      See PSARC opinion 1992/025
 */
static char     userpath[] = "PATH=/usr/bin:";
static char     rootpath[] = "PATH=/usr/sbin:/usr/bin";

static char cmdbuf[NCARGS+1];
static char hostname [MAXHOSTNAMELEN + 1];
static char locuser[USERNAME_LEN + 1];
static char remuser[USERNAME_LEN + 1];

#define KRB5_RECVAUTH_V5        5
#define SIZEOF_INADDR sizeof    (struct in_addr)

#define MAX_REPOSITORY_LEN      255
static char repository[MAX_REPOSITORY_LEN];

static char *kremuser;
static krb5_principal client = NULL;

static char     remote_addr[64];
static char     local_addr[64];

#define _PATH_DEFAULT_LOGIN "/etc/default/login"

static void
doit(int f, struct sockaddr_storage *fromp, char **renvp)
{
        char *cp;

        struct passwd *pwd;
        char *path;
        char *tzenv;
        struct spwd *shpwd;
        struct stat statb;
        char **lenvp;

        krb5_error_code status;
        int valid_checksum;
        int cnt;
        int sin_len;
        struct sockaddr_in localaddr;

        int s;
        in_port_t port;
        pid_t pid;
        int pv[2], pw[2], px[2], cc;
        char buf[RSHD_BUFSIZ];
        char sig;
        int one = 1;
        int v = 0;
        int err = 0;
        int idx = 0;
        char **pam_env;
        char abuf[INET6_ADDRSTRLEN];
        struct sockaddr_in *sin;
        struct sockaddr_in6 *sin6;
        int fromplen;
        int homedir_len, shell_len, username_len, tz_len;
        int no_name;
        boolean_t bad_port;
        int netf = 0;

        (void) signal(SIGINT, SIG_DFL);
        (void) signal(SIGQUIT, SIG_DFL);
        (void) signal(SIGTERM, SIG_DFL);
        (void) signal(SIGXCPU, SIG_DFL);
        (void) signal(SIGXFSZ, SIG_DFL);
        (void) sigset(SIGCHLD, SIG_IGN);
        (void) signal(SIGPIPE, SIG_DFL);
        (void) signal(SIGHUP, SIG_DFL);

#ifdef DEBUG
        {
            int t = open("/dev/tty", 2);

            if (t >= 0) {
                (void) setsid();
                (void) close(t);
            }
        }
#endif
        if (fromp->ss_family == AF_INET) {
                sin = (struct sockaddr_in *)fromp;
                port = ntohs((ushort_t)sin->sin_port);
                fromplen = sizeof (struct sockaddr_in);
        } else if (fromp->ss_family == AF_INET6) {
                sin6 = (struct sockaddr_in6 *)fromp;
                port = ntohs((ushort_t)sin6->sin6_port);
                fromplen = sizeof (struct sockaddr_in6);
        } else {
                syslog(LOG_ERR, "wrong address family\n");
                exit(1);
        }

        if (fromp->ss_family == AF_INET6) {
                if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
                        struct in_addr ipv4_addr;

                        IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &ipv4_addr);
                        (void) inet_ntop(AF_INET, &ipv4_addr, abuf,
                            sizeof (abuf));
                } else {
                        (void) inet_ntop(AF_INET6, &sin6->sin6_addr, abuf,
                            sizeof (abuf));
                }
        } else if (fromp->ss_family == AF_INET) {
                (void) inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof (abuf));
        }

        sin_len = sizeof (struct sockaddr_in);
        if (getsockname(f, (struct sockaddr *)&localaddr, &sin_len) < 0) {
                perror("getsockname");
                exit(1);
        }

        netf = f;

        bad_port = (port >= IPPORT_RESERVED ||
                port < (uint_t)(IPPORT_RESERVED/2));

        /* Get the name of the client side host to use later */
        no_name = (getnameinfo((const struct sockaddr *) fromp, fromplen,
                hostname, sizeof (hostname), NULL, 0, 0) != 0);

        if (bad_port || no_name != 0) {
                /*
                 * If there is no host name available then use the
                 * IP address to identify the host in the PAM call
                 * below.  Do the same if a bad port was used, to
                 * prevent untrustworthy authentication.
                 */
                (void) strlcpy(hostname, abuf, sizeof (hostname));
        }

        if (no_name != 0) {
                /*
                 * If the '-U' option was given on the cmd line,
                 * we must be able to lookup the hostname
                 */
                if (resolve_hostname) {
                        syslog(LOG_ERR, "rshd: Couldn't resolve your "
                            "address into a host name.\r\n Please "
                            "contact your net administrator");
                        exit(1);
                }
        } else {
                /*
                 * Even if getnameinfo() succeeded, we still have to check
                 * for spoofing.
                 */
                check_address("rshd", fromp, sin, sin6, abuf, hostname,
                    sizeof (hostname));
        }

        if (!krb5auth_flag && bad_port) {
                if (no_name)
                        syslog(LOG_NOTICE, "connection from %s - "
                            "bad port\n", abuf);
                else
                        syslog(LOG_NOTICE, "connection from %s (%s) - "
                            "bad port\n", hostname, abuf);
                exit(1);
        }

        (void) alarm(60);
        port = 0;
        for (;;) {
                char c;
                if ((cc = read(f, &c, 1)) != 1) {
                        if (cc < 0)
                                syslog(LOG_NOTICE, "read: %m");
                        (void) shutdown(f, 1+1);
                        exit(1);
                }
                if (c == 0)
                        break;
                port = port * 10 + c - '0';
        }
        (void) alarm(0);
        if (port != 0) {
                int lport = 0;
                struct sockaddr_storage ctl_addr;
                int addrlen;

                (void) memset(&ctl_addr, 0, sizeof (ctl_addr));
                addrlen = sizeof (ctl_addr);
                if (getsockname(f, (struct sockaddr *)&ctl_addr,
                        &addrlen) < 0) {
                        syslog(LOG_ERR, "getsockname: %m");
                        exit(1);
                }
get_port:
                /*
                 * 0 means that rresvport_addr() will bind to a port in
                 * the anonymous priviledged port range.
                 */
                if (krb5auth_flag) {
                        /*
                         * Kerberos does not support IPv6 yet.
                         */
                        lport = IPPORT_RESERVED - 1;
                }
                s = rresvport_addr(&lport, &ctl_addr);

                if (s < 0) {
                        syslog(LOG_ERR, "can't get stderr port: %m");
                        exit(1);
                }
                if (!krb5auth_flag && (port >= IPPORT_RESERVED)) {
                        syslog(LOG_ERR, "2nd port not reserved\n");
                        exit(1);
                }
                if (fromp->ss_family == AF_INET) {
                        sin->sin_port = htons((ushort_t)port);
                } else if (fromp->ss_family == AF_INET6) {
                        sin6->sin6_port = htons((ushort_t)port);
                }
                if (connect(s, (struct sockaddr *)fromp, fromplen) < 0) {
                        if (errno == EADDRINUSE) {
                                (void) close(s);
                                goto get_port;
                        }
                        syslog(LOG_INFO, "connect second port: %m");
                        exit(1);
                }
        }
        (void) dup2(f, 0);
        (void) dup2(f, 1);
        (void) dup2(f, 2);

#ifdef DEBUG
        syslog(LOG_NOTICE, "rshd: Client hostname = %s", hostname);
        if (debug_port)
                syslog(LOG_NOTICE, "rshd: Debug port is %d", debug_port);
        if (krb5auth_flag > 0)
                syslog(LOG_NOTICE, "rshd: Kerberos mode is ON");
        else
                syslog(LOG_NOTICE, "rshd: Kerberos mode is OFF");
#endif /* DEBUG */

        if (krb5auth_flag > 0) {
                if ((status = recvauth(f, &valid_checksum))) {
                        syslog(LOG_ERR, gettext("Kerberos Authentication "
                                        "failed \n"));
                        error("Authentication failed: %s\n",
                                        error_message(status));
                        (void) audit_rshd_fail("Kerberos Authentication "
                                "failed", hostname, remuser, locuser, cmdbuf);
                        exit(1);
                }

                if (checksum_required && !valid_checksum &&
                        kcmd_protocol == KCMD_OLD_PROTOCOL) {
                        syslog(LOG_WARNING, "Client did not supply required"
                                        " checksum--connection rejected.");
                        error("Client did not supply required"
                                "checksum--connection rejected.\n");
                        (void) audit_rshd_fail("Client did not supply required"
                                " checksum--connection rejected.", hostname,
                                remuser, locuser, cmdbuf);      /* BSM */
                        goto signout;
                }

                /*
                 * Authentication has succeeded, we now need
                 * to check authorization.
                 *
                 * krb5_kuserok returns 1 if OK.
                 */
                if (client && krb5_kuserok(bsd_context, client, locuser)) {
                        auth_sent |= AUTH_KRB5;
                } else {
                        syslog(LOG_ERR, "Principal %s (%s@%s) for local user "
                                "%s failed krb5_kuserok.\n",
                                kremuser, remuser, hostname, locuser);
                }
        } else {
                getstr(netf, remuser, sizeof (remuser), "remuser");
                getstr(netf, locuser, sizeof (locuser), "locuser");
                getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
        }

#ifdef DEBUG
        syslog(LOG_NOTICE, "rshd: locuser = %s, remuser = %s, cmdbuf = %s",
                        locuser, remuser, cmdbuf);
#endif /* DEBUG */

        /*
         * Note that there is no rsh conv functions at present.
         */
        if (krb5auth_flag > 0) {
                if ((err = pam_start("krsh", locuser, NULL, &pamh))
                                != PAM_SUCCESS) {
                        syslog(LOG_ERR, "pam_start() failed: %s\n",
                                pam_strerror(0, err));
                        exit(1);
                }
        }
        else
        {
                if ((err = pam_start("rsh", locuser, NULL, &pamh))
                                != PAM_SUCCESS) {
                        syslog(LOG_ERR, "pam_start() failed: %s\n",
                                pam_strerror(0, err));
                        exit(1);
                }
        }
        if ((err = pam_set_item(pamh, PAM_RHOST, hostname)) != PAM_SUCCESS) {
                syslog(LOG_ERR, "pam_set_item() failed: %s\n",
                        pam_strerror(pamh, err));
                exit(1);
        }
        if ((err = pam_set_item(pamh, PAM_RUSER, remuser)) != PAM_SUCCESS) {
                syslog(LOG_ERR, "pam_set_item() failed: %s\n",
                        pam_strerror(pamh, err));
                exit(1);
        }

        pwd = getpwnam(locuser);
        shpwd = getspnam(locuser);
        if ((pwd == NULL) || (shpwd == NULL)) {
                if (krb5auth_flag > 0)
                        syslog(LOG_ERR, "Principal %s (%s@%s) for local user "
                                "%s has no account.\n", kremuser, remuser,
                                                        hostname, locuser);
                error("permission denied.\n");
                (void) audit_rshd_fail("Login incorrect", hostname,
                        remuser, locuser, cmdbuf);      /* BSM */
                exit(1);
        }

        if (krb5auth_flag > 0) {
                (void) snprintf(repository, sizeof (repository),
                                        KRB5_REPOSITORY_NAME);
                /*
                 * We currently only support special handling of the
                 * KRB5 PAM repository
                 */
                if (strlen(locuser) != 0) {
                        krb5_repository_data_t krb5_data;
                        pam_repository_t pam_rep_data;

                        krb5_data.principal = locuser;
                        krb5_data.flags = SUNW_PAM_KRB5_ALREADY_AUTHENTICATED;

                        pam_rep_data.type = repository;
                        pam_rep_data.scope = (void *)&krb5_data;
                        pam_rep_data.scope_len = sizeof (krb5_data);

                        (void) pam_set_item(pamh, PAM_REPOSITORY,
                                        (void *)&pam_rep_data);
                }
        }

        if (shpwd->sp_pwdp != 0) {
                if (*shpwd->sp_pwdp != '\0') {
                        if ((v = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
                                error("permission denied\n");
                                (void) audit_rshd_fail("Permission denied",
                                    hostname, remuser, locuser, cmdbuf);
                                (void) pam_end(pamh, v);
                                exit(1);
                        }
                } else {
                        int flags;
                        char *p;
                        /*
                         * maintain 2.1 and 4.* and BSD semantics with
                         * anonymous rshd unless PASSREQ is set to YES in
                         * /etc/default/login: then we deny logins with empty
                         * passwords.
                         */
                        if (defopen(_PATH_DEFAULT_LOGIN) == 0) {
                                flags = defcntl(DC_GETFLAGS, 0);
                                TURNOFF(flags, DC_CASE);
                                (void) defcntl(DC_SETFLAGS, flags);

                                if ((p = defread("PASSREQ=")) != NULL &&
                                    strcasecmp(p, "YES") == 0) {
                                        error("permission denied\n");
                                        (void) audit_rshd_fail(
                                            "Permission denied", hostname,
                                            remuser, locuser, cmdbuf);
                                        (void) pam_end(pamh, PAM_ABORT);
                                        (void) defopen(NULL);
                                        syslog(LOG_AUTH|LOG_NOTICE,
                                            "empty password not allowed for "
                                            "%s from %s.", locuser, hostname);
                                        exit(1);
                                }
                                (void) defopen(NULL);
                        }
                        /*
                         * /etc/default/login not found or PASSREQ not set
                         * to YES. Allow logins without passwords.
                         */
                }
        }

        if (krb5auth_flag > 0) {
                if (require_encrypt && (!do_encrypt)) {
                        error("You must use encryption.\n");
                        (void) audit_rshd_fail("You must use encryption.",
                                hostname, remuser, locuser, cmdbuf); /* BSM */
                        goto signout;
                }

                if (!(auth_ok & auth_sent)) {
                        if (auth_sent) {
                                error("Another authentication mechanism "
                                    "must be used to access this host.\n");
                                (void) audit_rshd_fail("Another authentication"
                                        " mechanism must be used to access"
                                        " this host.\n", hostname, remuser,
                                        locuser, cmdbuf); /* BSM */
                                goto signout;
                        } else {
                                error("Permission denied.\n");
                                (void) audit_rshd_fail("Permission denied.",
                                        hostname, remuser, locuser, cmdbuf);
                                        /* BSM */
                                goto signout;
                        }
                }


                if (pwd->pw_uid && !access("/etc/nologin", F_OK)) {
                        error("Logins currently disabled.\n");
                        (void) audit_rshd_fail("Logins currently disabled.",
                                hostname, remuser, locuser, cmdbuf);
                        goto signout;
                }

                /* Log access to account */
                if (pwd && (pwd->pw_uid == 0)) {
                        syslog(LOG_NOTICE, "Executing %s for user %s (%s@%s)"
                            " as ROOT", cmdbuf,
                            kremuser, remuser, hostname);
                }
        }

        if ((v = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
                switch (v) {
                case PAM_NEW_AUTHTOK_REQD:
                        error("password expired\n");
                        (void) audit_rshd_fail("Password expired", hostname,
                                remuser, locuser, cmdbuf); /* BSM */
                        break;
                case PAM_PERM_DENIED:
                        error("account expired\n");
                        (void) audit_rshd_fail("Account expired", hostname,
                                remuser, locuser, cmdbuf); /* BSM */
                        break;
                case PAM_AUTHTOK_EXPIRED:
                        error("password expired\n");
                        (void) audit_rshd_fail("Password expired", hostname,
                                remuser, locuser, cmdbuf); /* BSM */
                        break;
                default:
                        error("login incorrect\n");
                        (void) audit_rshd_fail("Permission denied", hostname,
                                remuser, locuser, cmdbuf); /* BSM */
                        break;
                }
                (void) pam_end(pamh, PAM_ABORT);
                exit(1);
        }

        if (chdir(pwd->pw_dir) < 0) {
                (void) chdir("/");
#ifdef notdef
                error("No remote directory.\n");

                exit(1);
#endif
        }

        /*
         * XXX There is no session management currently being done
         */

        (void) write(STDERR_FILENO, "\0", 1);
        if (port || do_encrypt) {
                if ((pipe(pv) < 0)) {
                        error("Can't make pipe.\n");
                        (void) pam_end(pamh, PAM_ABORT);
                        exit(1);
                }
                if (do_encrypt) {
                        if (pipe(pw) < 0) {
                                error("Can't make pipe 2.\n");
                                (void) pam_end(pamh, PAM_ABORT);
                                exit(1);
                        }
                        if (pipe(px) < 0) {
                                error("Can't make pipe 3.\n");
                                (void) pam_end(pamh, PAM_ABORT);
                                exit(1);
                        }
                }
                pid = fork();
                if (pid == (pid_t)-1)  {
                        error("Fork (to start shell) failed on server.  "
                                "Please try again later.\n");
                        (void) pam_end(pamh, PAM_ABORT);
                        exit(1);
                }
                if (pid) {
                        fd_set ready;
                        fd_set readfrom;

                        (void) close(STDIN_FILENO);
                        (void) close(STDOUT_FILENO);
                        (void) close(STDERR_FILENO);
                        (void) close(pv[1]);
                        if (do_encrypt) {
                                (void) close(pw[1]);
                                (void) close(px[0]);
                        } else {
                                (void) close(f);
                        }

                        (void) FD_ZERO(&readfrom);

                        FD_SET(pv[0], &readfrom);
                        if (do_encrypt) {
                                FD_SET(pw[0], &readfrom);
                                FD_SET(f, &readfrom);
                        }
                        if (port)
                                FD_SET(s, &readfrom);

                        /* read f (net), write to px[1] (child stdin) */
                        /* read pw[0] (child stdout), write to f (net) */
                        /* read s (alt. channel), signal child */
                        /* read pv[0] (child stderr), write to s */
                        if (ioctl(pv[0], FIONBIO, (char *)&one) == -1)
                                syslog(LOG_INFO, "ioctl FIONBIO: %m");
                        if (do_encrypt &&
                                ioctl(pw[0], FIONBIO, (char *)&one) == -1)
                                syslog(LOG_INFO, "ioctl FIONBIO: %m");
                        do {
                                ready = readfrom;
                                if (select(FD_SETSIZE, &ready, NULL,
                                        NULL, NULL) < 0) {
                                        if (errno == EINTR) {
                                                continue;
                                        } else {
                                                break;
                                        }
                                }
                                /*
                                 * Read from child stderr, write to net
                                 */
                                if (port && FD_ISSET(pv[0], &ready)) {
                                        errno = 0;
                                        cc = read(pv[0], buf, sizeof (buf));
                                        if (cc <= 0) {
                                                (void) shutdown(s, 2);
                                                FD_CLR(pv[0], &readfrom);
                                        } else {
                                                (void) deswrite(s, buf, cc, 1);
                                        }
                                }
                                /*
                                 * Read from alternate channel, signal child
                                 */
                                if (port && FD_ISSET(s, &ready)) {
                                        if ((int)desread(s, &sig, 1, 1) <= 0)
                                                FD_CLR(s, &readfrom);
                                        else
                                                (void) killpg(pid, sig);
                                }
                                /*
                                 * Read from child stdout, write to net
                                 */
                                if (do_encrypt && FD_ISSET(pw[0], &ready)) {
                                        errno = 0;
                                        cc = read(pw[0], buf, sizeof (buf));
                                        if (cc <= 0) {
                                                (void) shutdown(f, 2);
                                                FD_CLR(pw[0], &readfrom);
                                        } else {
                                                (void) deswrite(f, buf, cc, 0);
                                        }
                                }
                                /*
                                 * Read from the net, write to child stdin
                                 */
                                if (do_encrypt && FD_ISSET(f, &ready)) {
                                        errno = 0;
                                        cc = desread(f, buf, sizeof (buf), 0);
                                        if (cc <= 0) {
                                                (void) close(px[1]);
                                                FD_CLR(f, &readfrom);
                                        } else {
                                                int wcc;
                                                wcc = write(px[1], buf, cc);
                                                if (wcc == -1) {
                                                        /*
                                                         * pipe closed,
                                                         * don't read any
                                                         * more
                                                         *
                                                         * might check for
                                                         * EPIPE
                                                         */
                                                    (void) close(px[1]);
                                                    FD_CLR(f, &readfrom);
                                                } else if (wcc != cc) {
                                                    /* CSTYLED */
                                                    syslog(LOG_INFO, gettext("only wrote %d/%d to child"),
                                                    wcc, cc);
                                                }
                                        }
                                }
                        } while ((port && FD_ISSET(s, &readfrom)) ||
                                (port && FD_ISSET(pv[0], &readfrom)) ||
                                (do_encrypt && FD_ISSET(f, &readfrom)) ||
                                (do_encrypt && FD_ISSET(pw[0], &readfrom)));
#ifdef DEBUG
                        syslog(LOG_INFO, "Shell process completed.");
#endif /* DEBUG */
                        if (ccache)
                                (void) pam_close_session(pamh, 0);
                        (void) pam_end(pamh, PAM_SUCCESS);

                        exit(0);
                } /* End of Parent block */

                (void) setsid();        /* Should be the same as above. */
                (void) close(pv[0]);
                (void) dup2(pv[1], 2);
                (void) close(pv[1]);
                if (port)
                        (void) close(s);
                if (do_encrypt) {
                        (void) close(f);
                        (void) close(pw[0]);
                        (void) close(px[1]);

                        (void) dup2(px[0], 0);
                        (void) dup2(pw[1], 1);

                        (void) close(px[0]);
                        (void) close(pw[1]);
                }
        }

        if (*pwd->pw_shell == '\0')
                pwd->pw_shell = "/bin/sh";
        if (!do_encrypt)
                (void) close(f);
        /*
         * write audit record before making uid switch
         */
        (void) audit_rshd_success(hostname, remuser, locuser, cmdbuf); /* BSM */

        /* set the real (and effective) GID */
        if (setgid(pwd->pw_gid) == -1) {
                error("Invalid gid.\n");
                (void) pam_end(pamh, PAM_ABORT);
                exit(1);
        }

        /*
         * Initialize the supplementary group access list.
         */
        if (strlen(locuser) == 0) {
                error("No local user.\n");
                (void) pam_end(pamh, PAM_ABORT);
                exit(1);
        }
        if (initgroups(locuser, pwd->pw_gid) == -1) {
                error("Initgroup failed.\n");
                (void) pam_end(pamh, PAM_ABORT);
                exit(1);
        }

        if ((v = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
                error("Insufficient credentials.\n");
                (void) pam_end(pamh, v);
                exit(1);
        }

        /* set the real (and effective) UID */
        if (setuid(pwd->pw_uid) == -1) {
                error("Invalid uid.\n");
                (void) pam_end(pamh, PAM_ABORT);
                exit(1);
        }

        /* Change directory only after becoming the appropriate user. */
        if (chdir(pwd->pw_dir) < 0) {
                (void) chdir("/");
                if (krb5auth_flag > 0) {
                        syslog(LOG_ERR, "Principal %s  (%s@%s) for local user"
                                " %s has no home directory.",
                                kremuser, remuser, hostname, locuser);
                        error("No remote directory.\n");
                        goto signout;
                }
#ifdef notdef
                error("No remote directory.\n");
                exit(1);
#endif
        }

        path = (pwd->pw_uid == 0) ? rootpath : userpath;

        /*
         * Space for the following environment variables are dynamically
         * allocated because their lengths are not known before calling
         * getpwnam().
         */
        homedir_len = strlen(pwd->pw_dir) + strlen(homestr) + 1;
        shell_len = strlen(pwd->pw_shell) + strlen(shellstr) + 1;
        username_len = strlen(pwd->pw_name) + strlen(userstr) + 1;
        homedir = (char *)malloc(homedir_len);
        shell = (char *)malloc(shell_len);
        username = (char *)malloc(username_len);
        if (homedir == NULL || shell == NULL || username == NULL) {
                perror("malloc");
                exit(1);
        }
        (void) snprintf(homedir, homedir_len, "%s%s", homestr, pwd->pw_dir);
        (void) snprintf(shell, shell_len, "%s%s", shellstr, pwd->pw_shell);
        (void) snprintf(username, username_len, "%s%s", userstr, pwd->pw_name);

        /* Pass timezone to executed command. */
        if (tzenv = getenv("TZ")) {
                tz_len = strlen(tzenv) + strlen(tzstr) + 1;
                tz = malloc(tz_len);
                if (tz != NULL)
                        (void) snprintf(tz, tz_len, "%s%s", tzstr, tzenv);
        }

        add_to_envinit(homedir);
        add_to_envinit(shell);
        add_to_envinit(path);
        add_to_envinit(username);
        add_to_envinit(tz);

        if (krb5auth_flag > 0) {
                int length;
                char *buffer;

                /*
                 * If we have KRB5CCNAME set, then copy into the child's
                 * environment.  This can't really have a fixed position
                 * because `tz' may or may not be set.
                 */
                if (getenv("KRB5CCNAME")) {
                        length = (int)strlen(getenv("KRB5CCNAME")) +
                                        (int)strlen("KRB5CCNAME=") + 1;
                        buffer = (char *)malloc(length);

                        if (buffer) {
                                (void) snprintf(buffer, length, "KRB5CCNAME=%s",
                                                getenv("KRB5CCNAME"));
                                add_to_envinit(buffer);
                        }
                } {
                        /* These two are covered by ADDRPAD */
                        length = strlen(inet_ntoa(localaddr.sin_addr)) + 1 +
                                        strlen("KRB5LOCALADDR=");
                        (void) snprintf(local_addr, length, "KRB5LOCALADDR=%s",
                                inet_ntoa(localaddr.sin_addr));
                        add_to_envinit(local_addr);

                        length = strlen(inet_ntoa(sin->sin_addr)) + 1 +
                                        strlen("KRB5REMOTEADDR=");
                        (void) snprintf(remote_addr, length,
                                "KRB5REMOTEADDR=%s", inet_ntoa(sin->sin_addr));
                        add_to_envinit(remote_addr);
                }

                /*
                 * If we do anything else, make sure there is
                 * space in the array.
                 */
                for (cnt = 0; cnt < num_env; cnt++) {
                        char *buf;

                        if (getenv(save_env[cnt])) {
                                length = (int)strlen(getenv(save_env[cnt])) +
                                        (int)strlen(save_env[cnt]) + 2;

                                buf = (char *)malloc(length);
                                if (buf) {
                                        (void) snprintf(buf, length, "%s=%s",
                                                save_env[cnt],
                                                getenv(save_env[cnt]));
                                        add_to_envinit(buf);
                                }
                        }
                }

        }

        /*
         * add PAM environment variables set by modules
         * -- only allowed 16 (PAM_ENV_ELIM)
         * -- check to see if the environment variable is legal
         */
        if ((pam_env = pam_getenvlist(pamh)) != 0) {
                while (pam_env[idx] != 0) {
                        if (idx < PAM_ENV_ELIM &&
                            legalenvvar(pam_env[idx])) {
                                add_to_envinit(pam_env[idx]);
                        }
                        idx++;
                }
        }

        (void) pam_end(pamh, PAM_SUCCESS);

        /*
         * Pick up locale environment variables, if any.
         */
        lenvp = renvp;
        while (*lenvp != NULL) {
                int     index;

                for (index = 0; localeenv[index] != NULL; index++)
                        /*
                         * locale_envmatch() returns 1 if
                         * *lenvp is localenev[index] and valid.
                         */
                        if (locale_envmatch(localeenv[index], *lenvp)) {
                                add_to_envinit(*lenvp);
                                break;
                        }

                lenvp++;
        }

        cp = strrchr(pwd->pw_shell, '/');
        if (cp != NULL)
                cp++;
        else
                cp = pwd->pw_shell;
        /*
         * rdist has been moved to /usr/bin, so /usr/ucb/rdist might not
         * be present on a system.  So if it doesn't exist we fall back
         * and try for it in /usr/bin.  We take care to match the space
         * after the name because the only purpose of this is to protect
         * the internal call from old rdist's, not humans who type
         * "rsh foo /usr/ucb/rdist".
         */
#define RDIST_PROG_NAME "/usr/ucb/rdist -Server"
        if (strncmp(cmdbuf, RDIST_PROG_NAME, strlen(RDIST_PROG_NAME)) == 0) {
                if (stat("/usr/ucb/rdist", &statb) != 0) {
                        (void) strncpy(cmdbuf + 5, "bin", 3);
                }
        }

#ifdef DEBUG
        syslog(LOG_NOTICE, "rshd: cmdbuf = %s", cmdbuf);
        if (do_encrypt)
                syslog(LOG_NOTICE, "rshd: cmd to be exec'ed = %s",
                        ((char *)cmdbuf + 3));
#endif /* DEBUG */

        if (do_encrypt && (strncmp(cmdbuf, "-x ", 3) == 0)) {
                (void) execle(pwd->pw_shell, cp, "-c", (char *)cmdbuf + 3,
                                NULL, envinit);
        } else {
                (void) execle(pwd->pw_shell, cp, "-c", cmdbuf, NULL,
                                envinit);
        }

        perror(pwd->pw_shell);
        exit(1);

signout:
        if (ccache)
                (void) pam_close_session(pamh, 0);
        ccache = NULL;
        (void) pam_end(pamh, PAM_ABORT);
        exit(1);
}

static void
getstr(fd, buf, cnt, err)
        int fd;
        char *buf;
        int cnt;
        char *err;
{
        char c;

        do {
                if (read(fd, &c, 1) != 1)
                        exit(1);
                if (cnt-- == 0) {
                        error("%s too long\n", err);
                        exit(1);
                }
                *buf++ = c;
        } while (c != 0);
}

/*PRINTFLIKE1*/
static void
error(char *fmt, ...)
{
        va_list ap;
        char buf[RSHD_BUFSIZ];

        buf[0] = 1;
        va_start(ap, fmt);
        (void) vsnprintf(&buf[1], sizeof (buf) - 1, fmt, ap);
        va_end(ap);
        (void) write(STDERR_FILENO, buf, strlen(buf));
}

static char *illegal[] = {
        "SHELL=",
        "HOME=",
        "LOGNAME=",
#ifndef NO_MAIL
        "MAIL=",
#endif
        "CDPATH=",
        "IFS=",
        "PATH=",
        "USER=",
        "TZ=",
        0
};

/*
 * legalenvvar - can PAM modules insert this environmental variable?
 */

static int
legalenvvar(char *s)
{
        register char **p;

        for (p = illegal; *p; p++)
                if (strncmp(s, *p, strlen(*p)) == 0)
                        return (0);

        if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
                return (0);

        return (1);
}

/*
 * Add a string to the environment of the new process.
 */

static void
add_to_envinit(char *string)
{
        /*
         * Reserve space for 2 * 8 = 16 environment entries initially which
         * should be enough to avoid reallocation of "envinit" in most cases.
         */
        static int      size = 8;
        static int      index = 0;

        if (string == NULL)
                return;

        if ((envinit == NULL) || (index == size)) {
                size *= 2;
                envinit = realloc(envinit, (size + 1) * sizeof (char *));
                if (envinit == NULL) {
                        perror("malloc");
                        exit(1);
                }
        }

        envinit[index++] = string;
        envinit[index] = NULL;
}

/*
 * Check if lenv and penv matches or not.
 */
static int
locale_envmatch(char *lenv, char *penv)
{
        while ((*lenv == *penv) && (*lenv != '\0') && (*penv != '=')) {
                lenv++;
                penv++;
        }

        /*
         * '/' is eliminated for security reason.
         */
        return ((*lenv == '\0' && *penv == '=' && *(penv + 1) != '/'));
}

#ifndef KRB_SENDAUTH_VLEN
#define KRB_SENDAUTH_VLEN       8       /* length for version strings */
#endif

/* MUST be KRB_SENDAUTH_VLEN chars */
#define KRB_SENDAUTH_VERS       "AUTHV0.1"
#define SIZEOF_INADDR sizeof (struct in_addr)

static krb5_error_code
recvauth(int netf, int *valid_checksum)
{
        krb5_auth_context auth_context = NULL;
        krb5_error_code status;
        struct sockaddr_in laddr;
        int len;
        krb5_data inbuf;
        krb5_authenticator *authenticator;
        krb5_ticket *ticket;
        krb5_rcache rcache;
        krb5_data version;
        krb5_encrypt_block eblock;      /* eblock for encrypt/decrypt */
        krb5_data desinbuf;
        krb5_data desoutbuf;
        char des_inbuf[2 * RSHD_BUFSIZ];
                        /* needs to be > largest read size */
        char des_outbuf[2 * RSHD_BUFSIZ + 4];
                        /* needs to be > largest write size */

        *valid_checksum = 0;
        len = sizeof (laddr);

        if (getsockname(netf, (struct sockaddr *)&laddr, &len)) {
                exit(1);
        }

        if (status = krb5_auth_con_init(bsd_context, &auth_context))
                return (status);

        if (status = krb5_auth_con_genaddrs(bsd_context, auth_context, netf,
                KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR))
                return (status);

        status = krb5_auth_con_getrcache(bsd_context, auth_context, &rcache);
        if (status)
                return (status);

        if (!rcache) {
                krb5_principal server;

                status = krb5_sname_to_principal(bsd_context, 0, 0,
                        KRB5_NT_SRV_HST, &server);
                if (status)
                        return (status);

                status = krb5_get_server_rcache(bsd_context,
                        krb5_princ_component(bsd_context, server, 0),
                        &rcache);
                krb5_free_principal(bsd_context, server);
                if (status)
                        return (status);

                status = krb5_auth_con_setrcache(bsd_context, auth_context,
                                                        rcache);
                if (status)
                        return (status);
        }

        status = krb5_recvauth_version(bsd_context, &auth_context, &netf,
                NULL,           /* Specify daemon principal */
                0,              /* no flags */
                keytab,         /* normally NULL to use v5srvtab */
                &ticket,        /* return ticket */
                &version);      /* application version string */


        if (status) {
                getstr(netf, locuser, sizeof (locuser), "locuser");
                getstr(netf, cmdbuf, sizeof (cmdbuf), "command");
                getstr(netf, remuser, sizeof (locuser), "remuser");
                return (status);
        }
        getstr(netf, locuser, sizeof (locuser), "locuser");
        getstr(netf, cmdbuf, sizeof (cmdbuf), "command");

        /* Must be V5  */

        kcmd_protocol = KCMD_UNKNOWN_PROTOCOL;
        if (version.length != 9 || version.data == NULL) {
                syslog(LOG_ERR, "bad application version length");
                error(gettext("bad application version length\n"));
                exit(1);
        }
        if (strncmp(version.data, "KCMDV0.1", 9) == 0) {
                kcmd_protocol = KCMD_OLD_PROTOCOL;
        } else if (strncmp(version.data, "KCMDV0.2", 9) == 0) {
                kcmd_protocol = KCMD_NEW_PROTOCOL;
        } else {
                syslog(LOG_ERR, "Unrecognized KCMD protocol (%s)",
                        (char *)version.data);
                error(gettext("Unrecognized KCMD protocol (%s)"),
                        (char *)version.data);
                exit(1);
        }
        getstr(netf, remuser, sizeof (locuser), "remuser");

        if ((status = krb5_unparse_name(bsd_context, ticket->enc_part2->client,
                        &kremuser)))
                return (status);

        if ((status = krb5_copy_principal(bsd_context,
                                ticket->enc_part2->client, &client)))
                return (status);


        if (checksum_required && (kcmd_protocol == KCMD_OLD_PROTOCOL)) {
                if ((status = krb5_auth_con_getauthenticator(bsd_context,
                        auth_context, &authenticator)))
                        return (status);

                if (authenticator->checksum && checksum_required) {
                        struct sockaddr_in adr;
                        int adr_length = sizeof (adr);
                        int chksumsize = strlen(cmdbuf) + strlen(locuser) + 32;
                        krb5_data input;
                        krb5_keyblock key;

                        char *chksumbuf = (char *)malloc(chksumsize);

                        if (chksumbuf == 0)
                                goto error_cleanup;
                        if (getsockname(netf, (struct sockaddr *)&adr,
                                        &adr_length) != 0)
                                goto error_cleanup;

                        (void) snprintf(chksumbuf, chksumsize, "%u:",
                                        ntohs(adr.sin_port));
                        if (strlcat(chksumbuf, cmdbuf,
                                        chksumsize) >= chksumsize) {
                                syslog(LOG_ERR, "cmd buffer too long.");
                                free(chksumbuf);
                                return (-1);
                        }
                        if (strlcat(chksumbuf, locuser,
                                        chksumsize) >= chksumsize) {
                                syslog(LOG_ERR, "locuser too long.");
                                free(chksumbuf);
                                return (-1);
                        }

                        input.data = chksumbuf;
                        input.length = strlen(chksumbuf);
                        key.magic = ticket->enc_part2->session->magic;
                        key.enctype = ticket->enc_part2->session->enctype;
                        key.contents = ticket->enc_part2->session->contents;
                        key.length = ticket->enc_part2->session->length;

                        status = krb5_c_verify_checksum(bsd_context,
                            &key, 0, &input, authenticator->checksum,
                            (unsigned int *)valid_checksum);

                        if (status == 0 && *valid_checksum == 0)
                            status = KRB5KRB_AP_ERR_BAD_INTEGRITY;
error_cleanup:
                        if (chksumbuf)
                                krb5_xfree(chksumbuf);
                        if (status) {
                                krb5_free_authenticator(bsd_context,
                                                authenticator);
                                return (status);
                        }
                }
                krb5_free_authenticator(bsd_context, authenticator);
        }


        if ((strncmp(cmdbuf, "-x ", 3) == 0)) {
                if (krb5_privacy_allowed()) {
                        do_encrypt = 1;
                } else {
                        syslog(LOG_ERR, "rshd: Encryption not supported");
                        error("rshd: Encryption not supported. \n");
                        exit(2);
                }

                status = krb5_auth_con_getremotesubkey(bsd_context,
                                                    auth_context,
                                                    &sessionkey);
                if (status) {
                        syslog(LOG_ERR, "Error getting KRB5 session subkey");
                        error(gettext("Error getting KRB5 session subkey"));
                        exit(1);
                }
                /*
                 * The "new" protocol requires that a subkey be sent.
                 */
                if (sessionkey == NULL && kcmd_protocol == KCMD_NEW_PROTOCOL) {
                        syslog(LOG_ERR, "No KRB5 session subkey sent");
                        error(gettext("No KRB5 session subkey sent"));
                        exit(1);
                }
                /*
                 * The "old" protocol does not permit an authenticator subkey.
                 * The key is taken from the ticket instead (see below).
                 */
                if (sessionkey != NULL && kcmd_protocol == KCMD_OLD_PROTOCOL) {
                        syslog(LOG_ERR, "KRB5 session subkey not permitted "
                                "with old KCMD protocol");
                        error(gettext("KRB5 session subkey not permitted "
                                "with old KCMD protocol"));
                        exit(1);
                }
                /*
                 * If no key at this point, use the session key from
                 * the ticket.
                 */
                if (sessionkey == NULL) {
                        /*
                         * Save the session key so we can configure the crypto
                         * module later.
                         */
                        status = krb5_copy_keyblock(bsd_context,
                                                ticket->enc_part2->session,
                                                &sessionkey);
                        if (status) {
                                syslog(LOG_ERR, "krb5_copy_keyblock failed");
                                error(gettext("krb5_copy_keyblock failed"));
                                exit(1);
                        }
                }
                /*
                 * If session key still cannot be found, we must
                 * exit because encryption is required here
                 * when encr_flag (-x) is set.
                 */
                if (sessionkey == NULL) {
                        syslog(LOG_ERR, "Could not find an encryption key");
                        error(gettext("Could not find an encryption key"));
                        exit(1);
                }

                /*
                 * Initialize parameters/buffers for desread & deswrite here.
                 */
                desinbuf.data = des_inbuf;
                desoutbuf.data = des_outbuf;
                desinbuf.length = sizeof (des_inbuf);
                desoutbuf.length = sizeof (des_outbuf);

                eblock.crypto_entry = sessionkey->enctype;
                eblock.key = (krb5_keyblock *)sessionkey;

                init_encrypt(do_encrypt, bsd_context, kcmd_protocol,
                                &desinbuf, &desoutbuf, SERVER, &eblock);
        }

        ticket->enc_part2->session = 0;

        if ((status = krb5_read_message(bsd_context, (krb5_pointer) & netf,
                                &inbuf))) {
                error(gettext("Error reading message: %s\n"),
                                error_message(status));
                exit(1);
        }

        if (inbuf.length) {
                krb5_creds **creds = NULL;

                /* Forwarding being done, read creds */
                if ((status = krb5_rd_cred(bsd_context,
                                            auth_context, &inbuf, &creds,
                                            NULL))) {
                        error("Can't get forwarded credentials: %s\n",
                                error_message(status));
                        exit(1);
                }

                /* Store the forwarded creds in the ccache */
                if ((status = store_forw_creds(bsd_context,
                                            creds, ticket, locuser,
                                            &ccache))) {
                        error("Can't store forwarded credentials: %s\n",
                                error_message(status));
                        exit(1);
                }
                krb5_free_creds(bsd_context, *creds);
        }

        krb5_free_ticket(bsd_context, ticket);
        return (0);
}

static void
usage(void)
{
        (void) fprintf(stderr, gettext("%s: rshd [-k5eciU] "
                        "[-P path] [-M realm] [-s tos] "
#ifdef DEBUG
                        "[-D port] "
#endif /* DEBUG */
                        "[-S keytab]"), gettext("usage"));

        syslog(LOG_ERR, "%s: rshd [-k5eciU] [-P path] [-M realm] [-s tos] "
#ifdef DEBUG
                        "[-D port] "
#endif /* DEBUG */
                        "[-S keytab]", gettext("usage"));
}