in.rshd.c revision 57c407852ad197a758d9fc3212bd9484cacf2a69
/*
* 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
* 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 2008 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/telioctl.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 <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;
/*
* authentication and authorization is:
* 1) Check authentication.
* 2) Check authorization via the access-control files:
* 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:?:"
static krb5_context bsd_context;
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 int num_env = 0;
static void usage(void);
static krb5_error_code recvauth(int, int *);
/*ARGSUSED*/
int
{
struct sockaddr_storage from;
int fd = 0;
extern char *optarg;
int ch;
int tos = -1;
(void) audit_rshd_setup(); /* BSM */
/*
* Analyze parameters.
*/
opterr = 0;
switch (ch) {
case '5':
case 'k':
break;
case 'c':
checksum_required = 1;
break;
case 'i':
checksum_ignored = 1;
break;
case 'e':
require_encrypt = 1;
break;
#ifdef DEBUG
case 'D':
break;
#endif /* DEBUG */
case 'U':
resolve_hostname = 1;
break;
case 'M':
break;
case 'S':
&keytab))) {
gettext("while resolving "
"srvtab file %s"), optarg);
exit(2);
}
break;
case 's':
(tos > 255)) {
"%s\n", optarg);
}
break;
case 'L':
gettext("in saving env"));
exit(2);
}
} else {
" -L arguments allowed\n"),
MAXENV);
exit(2);
}
break;
case '?':
default:
usage();
exit(1);
break;
}
if (optind == 0) {
usage();
exit(1);
}
if (krb5auth_flag > 0) {
if (status) {
exit(1);
}
}
if (!checksum_required && !checksum_ignored)
checksum_ignored = 1;
if (checksum_required && checksum_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;
exit(2);
}
exit(2);
}
if ((listen(s, 5)) < 0) {
exit(2);
}
&fromlen)) < 0) {
exit(2);
}
(void) close(s);
}
else
#endif /* DEBUG */
{
perror("getpeername");
_exit(1);
}
fd = STDIN_FILENO;
}
if (audit_settid(fd) != 0) {
perror("settid");
exit(1);
}
sizeof (on)) < 0)
sizeof (linger)) < 0)
sizeof (tos)) < 0) &&
(errno != ENOPROTOOPT)) {
}
return (0);
}
/*
* locale environments to be passed to shells.
*/
static char *localeenv[] = {
"LANG",
"LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
/*
* 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;
/*
* See PSARC opinion 1992/025
*/
#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 char remote_addr[64];
static char local_addr[64];
#define _PATH_DEFAULT_LOGIN "/etc/default/login"
static void
{
char *cp;
char *path;
char *tzenv;
char **lenvp;
int valid_checksum;
int cnt;
int sin_len;
struct sockaddr_in localaddr;
int s;
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 no_name;
int netf = 0;
#ifdef DEBUG
if (t >= 0) {
(void) setsid();
(void) close(t);
}
}
#endif
fromplen = sizeof (struct sockaddr_in);
fromplen = sizeof (struct sockaddr_in6);
} else {
exit(1);
}
sizeof (abuf));
} else {
sizeof (abuf));
}
}
sin_len = sizeof (struct sockaddr_in);
perror("getsockname");
exit(1);
}
netf = f;
/* Get the name of the client side host to use later */
/*
* 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.
*/
}
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) {
"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.
*/
sizeof (hostname));
}
if (!krb5auth_flag && bad_port) {
if (no_name)
"bad port\n", abuf);
else
exit(1);
}
(void) alarm(60);
port = 0;
for (;;) {
char c;
if (cc < 0)
exit(1);
}
if (c == 0)
break;
}
(void) alarm(0);
if (port != 0) {
int lport = 0;
struct sockaddr_storage ctl_addr;
int addrlen;
&addrlen) < 0) {
exit(1);
}
/*
* 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.
*/
}
if (s < 0) {
exit(1);
}
exit(1);
}
}
if (errno == EADDRINUSE) {
(void) close(s);
goto get_port;
}
exit(1);
}
}
(void) dup2(f, 0);
(void) dup2(f, 1);
(void) dup2(f, 2);
#ifdef DEBUG
if (debug_port)
if (krb5auth_flag > 0)
else
#endif /* DEBUG */
if (krb5auth_flag > 0) {
"failed \n"));
error("Authentication failed: %s\n",
(void) audit_rshd_fail("Kerberos Authentication "
exit(1);
}
if (checksum_required && !valid_checksum &&
" 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,
goto signout;
}
/*
* Authentication has succeeded, we now need
* to check authorization.
*
* krb5_kuserok returns 1 if OK.
*/
} else {
"%s failed krb5_kuserok.\n",
}
} else {
}
#ifdef DEBUG
#endif /* DEBUG */
/*
* Note that there is no rsh conv functions at present.
*/
if (krb5auth_flag > 0) {
!= PAM_SUCCESS) {
pam_strerror(0, err));
exit(1);
}
}
else
{
!= PAM_SUCCESS) {
pam_strerror(0, err));
exit(1);
}
}
exit(1);
}
exit(1);
}
if (krb5auth_flag > 0)
error("permission denied.\n");
exit(1);
}
if (krb5auth_flag > 0) {
/*
* We currently only support special handling of the
* KRB5 PAM repository
*/
(void *)&pam_rep_data);
}
}
error("permission denied\n");
(void) audit_rshd_fail("Permission denied",
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
* passwords.
*/
if (defopen(_PATH_DEFAULT_LOGIN) == 0) {
strcasecmp(p, "YES") == 0) {
error("permission denied\n");
(void) audit_rshd_fail(
"Permission denied", hostname,
"empty password not allowed for "
exit(1);
}
}
/*
* 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.",
goto signout;
}
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"
goto signout;
} else {
error("Permission denied.\n");
(void) audit_rshd_fail("Permission denied.",
/* BSM */
goto signout;
}
}
error("Logins currently disabled.\n");
(void) audit_rshd_fail("Logins currently disabled.",
goto signout;
}
/* Log access to account */
" as ROOT", cmdbuf,
}
}
switch (v) {
case PAM_NEW_AUTHTOK_REQD:
error("password expired\n");
break;
case PAM_PERM_DENIED:
error("account expired\n");
break;
case PAM_AUTHTOK_EXPIRED:
error("password expired\n");
break;
default:
error("login incorrect\n");
break;
}
exit(1);
}
(void) chdir("/");
#ifdef notdef
error("No remote directory.\n");
exit(1);
#endif
}
/*
* XXX There is no session management currently being done
*/
if (port || do_encrypt) {
error("Can't make pipe.\n");
exit(1);
}
if (do_encrypt) {
error("Can't make pipe 2.\n");
exit(1);
}
error("Can't make pipe 3.\n");
exit(1);
}
}
error("Fork (to start shell) failed on server. "
"Please try again later.\n");
exit(1);
}
if (pid) {
(void) close(STDIN_FILENO);
(void) close(STDOUT_FILENO);
(void) close(STDERR_FILENO);
if (do_encrypt) {
} else {
(void) close(f);
}
if (do_encrypt) {
}
if (port)
/* 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 (do_encrypt &&
do {
continue;
} else {
break;
}
}
/*
* Read from child stderr, write to net
*/
errno = 0;
if (cc <= 0) {
(void) shutdown(s, 2);
} else {
}
}
/*
* Read from alternate channel, signal child
*/
else
}
/*
* Read from child stdout, write to net
*/
errno = 0;
if (cc <= 0) {
(void) shutdown(f, 2);
} else {
}
}
/*
* Read from the net, write to child stdin
*/
errno = 0;
if (cc <= 0) {
} else {
int wcc;
if (wcc == -1) {
/*
* pipe closed,
* don't read any
* more
*
* might check for
* EPIPE
*/
/* CSTYLED */
}
}
}
#ifdef DEBUG
#endif /* DEBUG */
if (ccache)
(void) pam_close_session(pamh, 0);
exit(0);
} /* End of Parent block */
(void) setsid(); /* Should be the same as above. */
if (port)
(void) close(s);
if (do_encrypt) {
(void) close(f);
}
}
if (!do_encrypt)
(void) close(f);
/*
* write audit record before making uid switch
*/
/* set the real (and effective) GID */
error("Invalid gid.\n");
exit(1);
}
/*
* Initialize the supplementary group access list.
*/
error("No local user.\n");
exit(1);
}
error("Initgroup failed.\n");
exit(1);
}
error("Insufficient credentials.\n");
exit(1);
}
/* set the real (and effective) UID */
error("Invalid uid.\n");
exit(1);
}
/* Change directory only after becoming the appropriate user. */
(void) chdir("/");
if (krb5auth_flag > 0) {
" %s has no home directory.",
error("No remote directory.\n");
goto signout;
}
#ifdef notdef
error("No remote directory.\n");
exit(1);
#endif
}
/*
* Space for the following environment variables are dynamically
* allocated because their lengths are not known before calling
* getpwnam().
*/
perror("malloc");
exit(1);
}
/* Pass timezone to executed command. */
}
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")) {
if (buffer) {
getenv("KRB5CCNAME"));
}
} {
/* These two are covered by ADDRPAD */
strlen("KRB5LOCALADDR=");
strlen("KRB5REMOTEADDR=");
}
/*
* If we do anything else, make sure there is
* space in the array.
*/
char *buf;
if (buf) {
}
}
}
}
/*
* add PAM environment variables set by modules
* -- only allowed 16 (PAM_ENV_ELIM)
* -- check to see if the environment variable is legal
*/
if (idx < PAM_ENV_ELIM &&
}
idx++;
}
}
/*
* Pick up locale environment variables, if any.
*/
int index;
/*
* locale_envmatch() returns 1 if
* *lenvp is localenev[index] and valid.
*/
break;
}
lenvp++;
}
cp++;
else
/*
* be present on a system. So if it doesn't exist we fall back
* after the name because the only purpose of this is to protect
* the internal call from old rdist's, not humans who type
*/
#define RDIST_PROG_NAME "/usr/ucb/rdist -Server"
}
}
#ifdef DEBUG
if (do_encrypt)
((char *)cmdbuf + 3));
#endif /* DEBUG */
} else {
envinit);
}
exit(1);
if (ccache)
(void) pam_close_session(pamh, 0);
exit(1);
}
static void
int fd;
char *buf;
int cnt;
char *err;
{
char c;
do {
exit(1);
if (cnt-- == 0) {
exit(1);
}
*buf++ = c;
} while (c != 0);
}
/*PRINTFLIKE1*/
static void
{
char buf[RSHD_BUFSIZ];
buf[0] = 1;
}
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++)
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;
return;
size *= 2;
perror("malloc");
exit(1);
}
}
}
/*
* Check if lenv and penv matches or not.
*/
static int
{
lenv++;
penv++;
}
/*
* '/' is eliminated for security reason.
*/
}
#ifndef KRB_SENDAUTH_VLEN
#endif
/* MUST be KRB_SENDAUTH_VLEN chars */
#define KRB_SENDAUTH_VERS "AUTHV0.1"
#define SIZEOF_INADDR sizeof (struct in_addr)
static krb5_error_code
{
struct sockaddr_in laddr;
int len;
/* needs to be > largest read size */
/* needs to be > largest write size */
*valid_checksum = 0;
exit(1);
}
return (status);
return (status);
if (status)
return (status);
if (!rcache) {
if (status)
return (status);
&rcache);
if (status)
return (status);
rcache);
if (status)
return (status);
}
NULL, /* Specify daemon principal */
0, /* no flags */
keytab, /* normally NULL to use v5srvtab */
&ticket, /* return ticket */
&version); /* application version string */
if (status) {
return (status);
}
/* Must be V5 */
exit(1);
}
} else {
exit(1);
}
&kremuser)))
return (status);
return (status);
return (status);
struct sockaddr_in adr;
int adr_length = sizeof (adr);
if (chksumbuf == 0)
goto error_cleanup;
&adr_length) != 0)
goto error_cleanup;
chksumsize) >= chksumsize) {
return (-1);
}
chksumsize) >= chksumsize) {
return (-1);
}
(unsigned int *)valid_checksum);
if (status == 0 && *valid_checksum == 0)
if (chksumbuf)
if (status) {
return (status);
}
}
}
if (krb5_privacy_allowed()) {
do_encrypt = 1;
} else {
error("rshd: Encryption not supported. \n");
exit(2);
}
&sessionkey);
if (status) {
exit(1);
}
/*
* The "new" protocol requires that a subkey be sent.
*/
exit(1);
}
/*
* The "old" protocol does not permit an authenticator subkey.
* The key is taken from the ticket instead (see below).
*/
"with old KCMD protocol");
"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.
*/
&sessionkey);
if (status) {
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) {
exit(1);
}
/*
* Initialize parameters/buffers for desread & deswrite here.
*/
}
&inbuf))) {
exit(1);
}
/* Forwarding being done, read creds */
NULL))) {
error("Can't get forwarded credentials: %s\n",
exit(1);
}
/* Store the forwarded creds in the ccache */
&ccache))) {
error("Can't store forwarded credentials: %s\n",
exit(1);
}
}
return (0);
}
static void
usage(void)
{
"[-P path] [-M realm] [-s tos] "
#ifdef DEBUG
"[-D port] "
#endif /* DEBUG */
#ifdef DEBUG
"[-D port] "
#endif /* DEBUG */
}