in.rlogind.c revision 7243fb49732852c0e9ce39939a905b2a40f2ddeb
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright(c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* Copyright (c) 1983 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* remote login server:
* remuser\0
* locuser\0
* terminal info\0
* data
*/
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <syslog.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <alloca.h>
#include <stropts.h>
#include <sac.h> /* for SC_WILDC */
#include <utmpx.h>
#include <sys/logindmux.h>
#include <security/pam_appl.h>
#include <strings.h>
#include <com_err.h>
#include <k5-int.h>
#include <kcmd.h>
#include <krb5_repository.h>
#include <sys/cryptmod.h>
#include <addr_match.h>
#define KRB5_RECVAUTH_V5 5
static char *krusername = NULL;
static char term[64];
static int chksum_flag = 0;
static int use_auth = 0;
static enum kcmd_proto kcmd_protocol;
#ifdef ALLOW_KCMD_V2
#endif /* ALLOW_KCMD_V2 */
#define CHKSUM_REQUIRED 0x01
#define CHKSUM_IGNORED 0x02
#define VALID_CHKSUM(x) ((x) == 0 || (x) == CHKSUM_REQUIRED ||\
(x) == CHKSUM_IGNORED)
#define PWD_IF_FAIL 0x01
#define PWD_REQUIRED 0x02
#define AUTH_NONE 0x00
#define ARGSTR "k5exEXciM:s:S:D:"
#define DEFAULT_TOS 16
#define KRB5_PROG_NAME "krlogin"
#define SECURE_MSG "This rlogin session is using encryption " \
"for all data transmissions.\r\n"
#define KRB_V5_SENDAUTH_VERS "KRB5_SENDAUTH_V1.0"
#define KRB5_RECVAUTH_V5 5
krb5_data *);
#define LOGIN_PROGRAM "/bin/login"
#define DEFAULT_PROG_NAME "rlogin"
static const char *pam_prog_name = DEFAULT_PROG_NAME;
static void rmut(void);
static void protocol(int, int, int);
static int readstream(int, char *, int);
static void fatal(int, const char *);
static void fatalperror(int, const char *);
static int
{
return (0);
}
/*
* audit_rlogin_settid stores the terminal id while it is still
* available. Subsequent calls to adt_load_hostname() return
* the id which is stored here.
*/
static int
audit_rlogin_settid(int fd) {
int rc;
ADT_NO_AUDIT, 0, ADT_NO_AUDIT,
termid, ADT_SETTID)) == 0)
(void) adt_set_proc(ah);
}
(void) adt_end_session(ah);
}
return (rc);
}
/* ARGSUSED */
int
{
int on = 1;
struct sockaddr_storage from;
int fd = -1;
extern char *optarg;
char c;
int tos = -1;
char *keytab_file = NULL;
int encr_flag = 0;
struct sockaddr_storage ouraddr;
#ifdef DEBUG
int debug_port = 0;
#endif /* DEBUG */
switch (c) {
case 'k':
case '5':
break;
case 'e':
case 'E':
case 'x':
case 'X':
encr_flag = 1;
break;
case 'M':
break;
case 'S':
break;
case 'c':
break;
case 'i':
break;
case 's':
tos > 255) {
} else {
if (tos < 0)
tos = DEFAULT_TOS;
}
break;
#ifdef DEBUG
case 'D':
break;
#endif /* DEBUG */
default:
}
}
if (use_auth == KRB5_RECVAUTH_V5) {
if (status) {
}
if (keytab_file != NULL) {
&keytab))) {
"while resolving srvtab file %s",
}
}
}
#ifdef DEBUG
if (debug_port) {
int s;
struct sockaddr_in sin;
}
}
if ((listen(s, 5)) < 0) {
}
}
(void) close(s);
} else
#endif /* DEBUG */
{
if (!issock(STDIN_FILENO))
"stdin is not a socket file descriptor");
fd = STDIN_FILENO;
}
sizeof (on)) < 0)
if (!VALID_CHKSUM(chksum_flag)) {
"options specified (-c and -i)");
"these options are mutually exclusive - check "
"the documentation.");
}
}
if (tos != -1 &&
sizeof (tos)) < 0 &&
errno != ENOPROTOOPT) {
}
return (0);
}
static void cleanup(int);
static int nsize = 0; /* bytes read prior to pushing rlmod */
static char *rlbuf; /* buffer where nbytes are read to */
static char *line;
static void
{
char c;
do {
}
*buf++ = c;
} while (c != '\0');
}
static krb5_error_code
recvauth(int f,
unsigned int *valid_checksum,
int *auth_type,
int encr_flag,
{
krb5_error_code status = 0;
*valid_checksum = 0;
return (status);
/* Only need remote address for rd_cred() to verify client */
return (status);
if (status)
return (status);
if (!rcache) {
if (status)
return (status);
&rcache);
if (status)
return (status);
rcache);
if (status)
return (status);
}
&f,
NULL, /* Specify daemon principal */
0, /* no flags */
keytab, /* NULL to use v5srvtab */
ticket, /* return ticket */
auth_type, /* authentication system */
&auth_version))) {
if (*auth_type == KRB5_RECVAUTH_V5) {
/*
* clean up before exiting
*/
}
return (status);
}
"KRB5 exchange, exiting");
fatal(f, "Bad application version length, exiting.");
}
/*
* Determine which Kerberos CMD protocol was used.
*/
} else {
(char *)auth_version.data);
fatal(f, "Unrecognized KCMD protocol, exiting");
}
&authenticator)))
return (status);
if (authenticator->checksum) {
struct sockaddr_storage adr;
int adr_length = sizeof (adr);
int buflen;
char *chksumbuf;
/*
* Define the lenght of the chksum buffer.
* chksum string = "[portnum]:termstr:username"
* The extra 32 is to hold a integer string for
* the portnumber.
*/
if (chksumbuf == 0) {
fatal(f, "Out of memory error");
}
&adr_length) != 0) {
fatal(f, "getsockname error");
}
"%u:%s%s",
&key, 0,
&input,
if (status == 0 && *valid_checksum == 0)
if (chksumbuf)
if (status) {
return (status);
}
}
}
client)))
return (status);
/* Get the Unix username of the remote user */
/* Get the Kerberos principal name string of the remote user */
return (status);
#ifdef DEBUG
#endif
if (encr_flag) {
&session_key);
if (status) {
"subkey, exiting");
fatal(f, "Error getting KRB5 session subkey, exiting");
}
/*
* The "new" protocol requires that a subkey be sent.
*/
if (session_key == NULL &&
fatal(f, "No KRB5 session subkey sent, exiting");
}
/*
* The "old" protocol does not permit an authenticator subkey.
* The key is taken from the ticket instead (see below).
*/
if (session_key != NULL &&
"with old KCMD protocol, exiting");
fatal(f, "KRB5 session subkey not permitted "
"with old KCMD protocol, exiting");
}
/*
* If no key at this point, use the session key from
* the ticket.
*/
if (session_key == NULL) {
/*
* Save the session key so we can configure the crypto
* module later.
*/
&session_key);
if (status) {
fatal(f, "krb5_copy_keyblock failed");
}
}
/*
* If session key still cannot be found, we must
* exit because encryption is required here
* when encr_flag (-x) is set.
*/
if (session_key == NULL) {
"exiting");
fatal(f, "Encryption required but key not found, "
"exiting");
}
}
/*
* Use krb5_read_message to read the principal stuff.
*/
&inbuf)))
fatal(f, "Error reading krb5 message");
&ccache))) {
if (rcache)
fatal(f, "Can't get forwarded credentials");
}
if (rcache)
return (status);
}
static void
{
int auth_sys = 0;
int auth_sent = 0;
if (getuid())
fatal(f, "Error authorizing KRB5 connection, "
"server lacks privilege");
if (status) {
if (ticket)
if (status != 255)
"Authentication failed from %s(%s): %s\n",
fatal(f, "Kerberos authentication failed, exiting");
}
if (auth_sys != KRB5_RECVAUTH_V5) {
fatal(f, "This server only supports Kerberos V5");
} else {
/*
* Authenticated OK, now check authorization.
*/
}
if (auth_sent == KRB5_RECVAUTH_V5 &&
"connection rejected.");
fatal(f, "Client did not supply required checksum, "
"connection rejected.");
}
int msgsize = 0;
if (ticket)
if (krusername != NULL) {
/*
* msgsize must be enough to hold
* krusername, lusername and a brief
* message describing the failure.
*/
}
"specified account");
fatal(f, "User is not authorized to login to "
"specified account");
}
if (auth_sent != 0)
"Access denied because of improper "
"KRB5 credentials");
else
"User %s is not authorized to login "
"to account %s",
}
}
/*
* stop_stream
*
* Utility routine to send a CRYPTIOCSTOP ioctl to the
* crypto module(cryptmod).
*/
static void
{
}
/*
* start_stream
*
* Utility routine to send a CRYPTIOCSTART ioctl to the
* crypto module(cryptmod). This routine may contain optional
* payload data that the cryptmod will interpret as bytes that
* need to be decrypted and sent back up to the application
* via the data stream.
*/
static void
{
if (dir == CRYPT_DECRYPT) {
/* Look for data not yet processed */
datalen = 0;
} else {
if (datalen > 0) {
} else {
"malloc error (%d bytes)",
datalen);
datalen = 0;
}
} else {
datalen = 0;
}
}
} else {
}
}
static int
{
struct cr_info_t setup_info;
int retval = 0;
case ENCTYPE_DES_CBC_CRC:
break;
case ENCTYPE_DES_CBC_MD5:
break;
case ENCTYPE_DES_CBC_RAW:
break;
case ENCTYPE_DES3_CBC_SHA1:
break;
case ENCTYPE_ARCFOUR_HMAC:
break;
case ENCTYPE_ARCFOUR_HMAC_EXP:
break;
break;
break;
default:
"is not supported by crypto module(%d)",
return (-1);
}
/* Kerberos IVs are 8 bytes long for DES keys */
else
setup_info.iveclen = 0;
} else {
}
/*
* R* commands get special handling by crypto module -
* 4 byte length field is used before each encrypted block
* of data.
*/
retval = -1;
}
return (retval);
}
static krb5_error_code
{
char *buf;
/*
* Assume that we're talking to a V5 recvauth; read in the
* the version string, and make sure it matches.
*/
return (KRB5_SENDAUTH_BADAUTHVERS);
return (ENOMEM);
}
return (KRB5_SENDAUTH_BADAUTHVERS);
}
return (retval);
}
static void
doit(int f,
struct sockaddr_storage *fromp,
int encr_flag,
{
int p, t, on = 1;
char c;
char abuf[INET6_ADDRSTRLEN];
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
int fromplen;
char rhost_addra[INET6_ADDRSTRLEN];
}
(void) alarm(60);
}
(void) alarm(0);
fromplen = sizeof (struct sockaddr_in);
rhost_addra, sizeof (rhost_addra)))
goto badconversion;
fromplen = sizeof (struct sockaddr_in6);
&ipv4_addr);
sizeof (rhost_addra)))
goto badconversion;
} else {
rhost_addra, sizeof (rhost_addra)))
goto badconversion;
}
} else {
fatal(f, "Permission denied");
}
/*
* Allow connections only from the "ephemeral" reserved
* ports(ports 512 - 1023) by checking the remote port
* because other utilities(e.g. in.ftpd) can be used to
* allow a unprivileged user to originate a connection
* from a privileged port and provide untrustworthy
* authentication.
*/
NULL, 0, 0) != 0;
/* If no host name, use IP address for name later on. */
if (no_name)
}
if (!no_name) {
/*
* Even if getnameinfo() succeeded, we still have to check
* for spoofing.
*/
}
if (bad_port) {
if (no_name)
"connection from %s - bad port\n",
abuf);
else
"connection from %s(%s) - bad port\n",
fatal(f, "Permission denied");
}
if (use_auth == KRB5_RECVAUTH_V5) {
/*
* Kerberos Authentication succeeded,
* so set the proper program name to use
* with pam (important during 'cleanup'
* routine later).
*/
}
}
"send of the zero byte(to %s) failed:"
" cannot start data transfer mode\n",
}
fatalperror(f, "cannot open /dev/ptmx");
if (grantpt(p) == -1)
fatal(f, "could not grant slave pty");
if (unlockpt(p) == -1)
fatal(f, "could not unlock slave pty");
fatal(f, "could not enable slave pty");
fatal(f, "could not open slave pty");
fatalperror(f, "ioctl I_PUSH ptem");
fatalperror(f, "ioctl I_PUSH ldterm");
fatalperror(f, "ioctl I_PUSH ttcompat");
/*
* POP the sockmod and push the rlmod module.
*
* Note that sockmod has to be removed since readstream assumes
* a "raw" TPI endpoint(e.g. it uses getmsg).
*/
if (removemod(f, "sockmod") < 0)
fatalperror(f, "couldn't remove sockmod");
if (encr_flag) {
fatalperror(f, "ioctl I_PUSH rlmod");
}
fatalperror(f, "ioctl I_PUSH rlmod");
if (encr_flag) {
/*
* Make sure rlmod will pass unrecognized IOCTLs to cryptmod
*/
fatal(f, "ioctl CRYPTPASSTHRU failed\n");
}
/*
* readstream will do a getmsg till it receives
* M_PROTO type T_DATA_REQ from rloginmodopen()
* indicating all data on the stream prior to pushing rlmod has
* been drained at the stream head.
*/
fatalperror(f, "readstream failed");
/*
* Make sure the pty doesn't modify the strings passed
* to login as part of the "rlogin protocol." The login
* program should set these flags to apropriate values
* after it has read the strings.
*/
fatalperror(f, "ioctl TCGETS");
fatalperror(f, "ioctl TCSETS");
/*
* System V ptys allow the TIOC{SG}WINSZ ioctl to be
* issued on the master side of the pty. Luckily, that's
* the only tty ioctl we need to do do, so we can close the
* slave side in the parent process after the fork.
*/
if (pid < 0)
fatalperror(f, "fork");
if (pid == 0) {
int tt;
/* System V login expects a utmp entry to already be there */
/* controlling tty */
if (setsid() == -1)
fatalperror(f, "setsid");
fatalperror(f, "could not re-open slave pty");
if (close(p) == -1)
fatalperror(f, "error closing pty master");
if (close(t) == -1)
fatalperror(f, "error closing pty slave"
" opened before session established");
/*
* If this fails we may or may not be able to output an
* error message.
*/
if (close(f) == -1)
fatalperror(f, "error closing deamon stdout");
if (use_auth == KRB5_RECVAUTH_V5 &&
"-d", line,
"-r", hostname,
"-s", pam_prog_name,
"-R", KRB5_REPOSITORY_NAME,
lusername, /* local user */
NULL);
} else {
"-d", line,
"-r", hostname,
NULL);
}
/*NOTREACHED*/
}
(void) close(t);
/*
* Must ignore SIGTTOU, otherwise we'll stop
* when we try and set slave pty's window shape
* (our controlling tty is the master pty).
* Likewise, we don't want any of the tty-generated
* signals from chars passing through.
*/
(void) setpgrp();
if (encr_flag) {
/*
* Configure the STREAMS crypto module. For now,
* don't use any IV parameter. KCMDV0.2 support
* will require the use of Initialization Vectors
* for both encrypt and decrypt modes.
*/
if (kcmd_protocol == KCMD_OLD_PROTOCOL) {
/*
* This is gross but necessary for MIT compat.
*/
} else {
}
/*
* configure both sides of stream together
* since they share the same IV.
* This is what makes the OLD KCMD protocol
* less secure than the newer one - Bad ivecs.
*/
if (configure_stream(f, session_key,
ivptr, ivec_usage) != 0)
fatal(f, "Cannot initialize encryption -"
" exiting.\n");
} else {
if (configure_stream(f, session_key,
NULL, IVEC_NEVER) != 0)
fatal(f,
"Cannot initialize encryption -"
" exiting.\n");
goto startcrypto;
}
&blocksize)) {
"for encryption type %d",
fatal(f, "Cannot determine blocksize "
"for encryption - exiting.\n");
}
fatal(f, "memory error - exiting\n");
/*
* Following MIT convention -
* encrypt IV = 0x01 x blocksize
* decrypt IV = 0x00 x blocksize
* ivec_usage = IVEC_ONETIME
*
* configure_stream separately for encrypt and
* decrypt because there are 2 different IVs.
*
* AES uses 0's for IV.
*/
if (session_key->enctype ==
session_key->enctype ==
else
&ivec, IVEC_ONETIME) != 0)
fatal(f, "Cannot initialize encryption -"
" exiting.\n");
&ivec, IVEC_ONETIME) != 0)
fatal(f, "Cannot initialize encryption -"
" exiting.\n");
}
}
cleanup(0);
/*NOTREACHED*/
fatalperror(f, "address conversion");
/*NOTREACHED*/
}
/*
* rlogin "protocol" machine.
*/
static void
{
struct protocol_arg rloginp;
int ptmfd; /* fd of logindmux coneected to ptmx */
int netfd; /* fd of logindmux connected to netf */
/* indicate new rlogin */
fatalperror(f, "send_oob");
/*
* We cannot send the SECURE_MSG until after the
* client has been signaled with the oobdata (above).
*/
if (encr_flag) {
fatalperror(f, "Error writing SECURE message");
}
/*
* Open logindmux driver and link netf and ptmx
* underneath logindmux.
*/
fatalperror(f, "open /dev/logindmux");
fatalperror(f, "open /dev/logindmux");
fatal(f, "ioctl I_LINK of tcp connection failed\n");
/*
* Figure out the device number of the ptm's mux fd, and pass that
* to the net's mux.
*/
fatalperror(f, "cannot determine device number"
/*
* Figure out the device number of the net's mux fd, and pass that
* to the ptm's mux.
*/
fatalperror(f, "cannot determine device number"
/*
* Send an ioctl type RL_IOC_ENABLE to reenable the
* message queue and reinsert the data read from streamhead
* at the time of pushing rloginmod module.
* We need to send this ioctl even if no data was read earlier
* since we need to reenable the message queue of rloginmod module.
*/
if (nsize) {
} else {
}
/*
* User level daemon now pauses till the shell exits.
*/
(void) pause();
}
/* This is a signal handler, hence the dummy argument */
/*ARGSUSED*/
static void
{
rmut();
/*NOTREACHED*/
}
/*
* TPI style replacement for socket send() primitive, so we don't require
* sockmod to be on the stream.
*/
static int
{
struct T_exdata_req exd_req;
int ret;
if (ret == 0)
return (ret);
}
static void
{
char *bufp;
/* ASCII 001 is the error indicator */
/*NOTREACHED*/
}
/*PRINTFLIKE2*/
static void
{
char *bufp;
const char *errstr;
int save_errno = errno;
} else {
const char fmt[] = "%s: Error %d";
/* -4 for %s & %d. "*8/3" is bytes->decimal, pessimistically */
}
/*NOTREACHED*/
}
static void
rmut(void)
{
/* while cleaning up dont allow disruption */
setutxent();
break; /* Cleaned up elsewhere. */
/*
* call pam_close_session if login changed
* the utmpx user entry from type LOGIN_PROCESS
* to type USER_PROCESS, which happens
* after pam_open_session is called.
*/
sizeof (user));
sizeof (ttyn));
sizeof (rhost));
/*
* Use the same pam_prog_name that
* 'login' used.
*/
&pamh))
== PAM_SUCCESS) {
ttyn);
rhost);
(void) pam_close_session(pamh, 0);
}
}
/*
* Since modutx failed we'll
* write out the new entry
* ourselves.
*/
(void) pututxline(up);
}
break;
}
}
endutxent();
}
static int
{
union T_primitives tpi;
int nbytes = 0;
int ret = 0;
int flags = 0;
int nread;
for (;;) {
return (-1);
}
"buffer allocation failed\n");
return (-1);
}
}
if (ret < 0) {
return (-1);
}
/*
* getmsg() returned no data - this indicates
* that the connection is closing down.
*/
cleanup(0);
}
continue;
}
return (nbytes);
}
cleanup(0);
}
}
/*
* Verify that the named module is at the top of the stream
* and then pop it off.
*/
static int
{
char topmodname[BUFSIZ];
return (-1);
return (-1);
}
return (-1);
return (0);
}