diff options
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.c | 1657 |
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")); +} |