summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c')
-rw-r--r--usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c1657
1 files changed, 1657 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c b/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c
new file mode 100644
index 0000000000..d5ffbcffc5
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/usr.sbin/in.rshd.c
@@ -0,0 +1,1657 @@
+/*
+ * 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
+ */
+/*
+ * Copyright 2004 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#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 <k5-int.h>
+#include <krb5_repository.h>
+#include <com_err.h>
+#include <kcmd.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*/
+void
+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':
+ 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);
+ /* NOTREACHED */
+}
+
+/*
+ * 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];
+
+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;
+ int 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);
+ }
+
+ 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));
+
+ no_name = (getnameinfo((const struct sockaddr *) fromp, fromplen,
+ hostname, sizeof (hostname), NULL, 0, 0) != 0);
+
+ /* Get the name of the client side host to use later */
+ if (no_name == 1 || bad_port == 1) {
+ 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 {
+ /*
+ * If there is no host name available and the
+ * -U option hasnt been used on the cmd line,
+ * use the IP address to identify the
+ * host in the pam call below.
+ */
+ (void) strncpy(hostname, abuf,
+ sizeof (hostname));
+ }
+ }
+
+ 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));
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /*
+ * maintain 2.1 and 4.* and BSD semantics with anonymous rshd
+ */
+ if (shpwd->sp_pwdp != 0 && *shpwd->sp_pwdp != '\0' &&
+ (v = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
+ error("permission denied\n");
+ (void) audit_rshd_fail("Permission denied", hostname,
+ remuser, locuser, cmdbuf); /* BSM */
+ (void) pam_end(pamh, v);
+ exit(1);
+ }
+
+ 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) {
+ /* Forwarding being done, read creds */
+ if ((status = rd_and_store_for_creds(bsd_context,
+ auth_context, &inbuf, ticket, locuser,
+ &ccache))) {
+ error("Can't get forwarded credentials: %s\n",
+ error_message(status));
+ exit(1);
+ }
+
+ }
+ 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"));
+}