mirror of
git://anongit.mindrot.org/openssh.git
synced 2024-12-15 23:33:56 +08:00
da108ece68
[auth-options.c auth1.c auth2.c bufaux.c buffer.h kex.c key.c packet.c] [packet.h ssh-dss.c ssh-rsa.c] Add buffer_get_cstring() and related functions that verify that the string extracted from the buffer contains no embedded \0 characters* This prevents random (possibly malicious) crap from being appended to strings where it would not be noticed if the string is used with a string(3) function. Use the new API in a few sensitive places. * actually, we allow a single one at the end of the string for now because we don't know how many deployed implementations get this wrong, but don't count on this to remain indefinitely.
438 lines
10 KiB
C
438 lines
10 KiB
C
/* $OpenBSD: auth1.c,v 1.75 2010/08/31 09:58:37 djm Exp $ */
|
|
/*
|
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
|
* All rights reserved
|
|
*
|
|
* As far as I am concerned, the code I have written for this software
|
|
* can be used freely for any purpose. Any derived versions of this
|
|
* software must be clearly marked as such, and if the derived work is
|
|
* incompatible with the protocol description in the RFC file, it must be
|
|
* called by a name other than "ssh" or "Secure Shell".
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <pwd.h>
|
|
|
|
#include "openbsd-compat/sys-queue.h"
|
|
#include "xmalloc.h"
|
|
#include "rsa.h"
|
|
#include "ssh1.h"
|
|
#include "packet.h"
|
|
#include "buffer.h"
|
|
#include "log.h"
|
|
#include "servconf.h"
|
|
#include "compat.h"
|
|
#include "key.h"
|
|
#include "hostfile.h"
|
|
#include "auth.h"
|
|
#include "channels.h"
|
|
#include "session.h"
|
|
#include "uidswap.h"
|
|
#ifdef GSSAPI
|
|
#include "ssh-gss.h"
|
|
#endif
|
|
#include "monitor_wrap.h"
|
|
#include "buffer.h"
|
|
|
|
/* import */
|
|
extern ServerOptions options;
|
|
extern Buffer loginmsg;
|
|
|
|
static int auth1_process_password(Authctxt *, char *, size_t);
|
|
static int auth1_process_rsa(Authctxt *, char *, size_t);
|
|
static int auth1_process_rhosts_rsa(Authctxt *, char *, size_t);
|
|
static int auth1_process_tis_challenge(Authctxt *, char *, size_t);
|
|
static int auth1_process_tis_response(Authctxt *, char *, size_t);
|
|
|
|
static char *client_user = NULL; /* Used to fill in remote user for PAM */
|
|
|
|
struct AuthMethod1 {
|
|
int type;
|
|
char *name;
|
|
int *enabled;
|
|
int (*method)(Authctxt *, char *, size_t);
|
|
};
|
|
|
|
const struct AuthMethod1 auth1_methods[] = {
|
|
{
|
|
SSH_CMSG_AUTH_PASSWORD, "password",
|
|
&options.password_authentication, auth1_process_password
|
|
},
|
|
{
|
|
SSH_CMSG_AUTH_RSA, "rsa",
|
|
&options.rsa_authentication, auth1_process_rsa
|
|
},
|
|
{
|
|
SSH_CMSG_AUTH_RHOSTS_RSA, "rhosts-rsa",
|
|
&options.rhosts_rsa_authentication, auth1_process_rhosts_rsa
|
|
},
|
|
{
|
|
SSH_CMSG_AUTH_TIS, "challenge-response",
|
|
&options.challenge_response_authentication,
|
|
auth1_process_tis_challenge
|
|
},
|
|
{
|
|
SSH_CMSG_AUTH_TIS_RESPONSE, "challenge-response",
|
|
&options.challenge_response_authentication,
|
|
auth1_process_tis_response
|
|
},
|
|
{ -1, NULL, NULL, NULL}
|
|
};
|
|
|
|
static const struct AuthMethod1
|
|
*lookup_authmethod1(int type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; auth1_methods[i].name != NULL; i++)
|
|
if (auth1_methods[i].type == type)
|
|
return (&(auth1_methods[i]));
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static char *
|
|
get_authname(int type)
|
|
{
|
|
const struct AuthMethod1 *a;
|
|
static char buf[64];
|
|
|
|
if ((a = lookup_authmethod1(type)) != NULL)
|
|
return (a->name);
|
|
snprintf(buf, sizeof(buf), "bad-auth-msg-%d", type);
|
|
return (buf);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
auth1_process_password(Authctxt *authctxt, char *info, size_t infolen)
|
|
{
|
|
int authenticated = 0;
|
|
char *password;
|
|
u_int dlen;
|
|
|
|
/*
|
|
* Read user password. It is in plain text, but was
|
|
* transmitted over the encrypted channel so it is
|
|
* not visible to an outside observer.
|
|
*/
|
|
password = packet_get_string(&dlen);
|
|
packet_check_eom();
|
|
|
|
/* Try authentication with the password. */
|
|
authenticated = PRIVSEP(auth_password(authctxt, password));
|
|
|
|
memset(password, 0, dlen);
|
|
xfree(password);
|
|
|
|
return (authenticated);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
auth1_process_rsa(Authctxt *authctxt, char *info, size_t infolen)
|
|
{
|
|
int authenticated = 0;
|
|
BIGNUM *n;
|
|
|
|
/* RSA authentication requested. */
|
|
if ((n = BN_new()) == NULL)
|
|
fatal("do_authloop: BN_new failed");
|
|
packet_get_bignum(n);
|
|
packet_check_eom();
|
|
authenticated = auth_rsa(authctxt, n);
|
|
BN_clear_free(n);
|
|
|
|
return (authenticated);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
auth1_process_rhosts_rsa(Authctxt *authctxt, char *info, size_t infolen)
|
|
{
|
|
int keybits, authenticated = 0;
|
|
u_int bits;
|
|
Key *client_host_key;
|
|
u_int ulen;
|
|
|
|
/*
|
|
* Get client user name. Note that we just have to
|
|
* trust the client; root on the client machine can
|
|
* claim to be any user.
|
|
*/
|
|
client_user = packet_get_cstring(&ulen);
|
|
|
|
/* Get the client host key. */
|
|
client_host_key = key_new(KEY_RSA1);
|
|
bits = packet_get_int();
|
|
packet_get_bignum(client_host_key->rsa->e);
|
|
packet_get_bignum(client_host_key->rsa->n);
|
|
|
|
keybits = BN_num_bits(client_host_key->rsa->n);
|
|
if (keybits < 0 || bits != (u_int)keybits) {
|
|
verbose("Warning: keysize mismatch for client_host_key: "
|
|
"actual %d, announced %d",
|
|
BN_num_bits(client_host_key->rsa->n), bits);
|
|
}
|
|
packet_check_eom();
|
|
|
|
authenticated = auth_rhosts_rsa(authctxt, client_user,
|
|
client_host_key);
|
|
key_free(client_host_key);
|
|
|
|
snprintf(info, infolen, " ruser %.100s", client_user);
|
|
|
|
return (authenticated);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
auth1_process_tis_challenge(Authctxt *authctxt, char *info, size_t infolen)
|
|
{
|
|
char *challenge;
|
|
|
|
if ((challenge = get_challenge(authctxt)) == NULL)
|
|
return (0);
|
|
|
|
debug("sending challenge '%s'", challenge);
|
|
packet_start(SSH_SMSG_AUTH_TIS_CHALLENGE);
|
|
packet_put_cstring(challenge);
|
|
xfree(challenge);
|
|
packet_send();
|
|
packet_write_wait();
|
|
|
|
return (-1);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
auth1_process_tis_response(Authctxt *authctxt, char *info, size_t infolen)
|
|
{
|
|
int authenticated = 0;
|
|
char *response;
|
|
u_int dlen;
|
|
|
|
response = packet_get_string(&dlen);
|
|
packet_check_eom();
|
|
authenticated = verify_response(authctxt, response);
|
|
memset(response, 'r', dlen);
|
|
xfree(response);
|
|
|
|
return (authenticated);
|
|
}
|
|
|
|
/*
|
|
* read packets, try to authenticate the user and
|
|
* return only if authentication is successful
|
|
*/
|
|
static void
|
|
do_authloop(Authctxt *authctxt)
|
|
{
|
|
int authenticated = 0;
|
|
char info[1024];
|
|
int prev = 0, type = 0;
|
|
const struct AuthMethod1 *meth;
|
|
|
|
debug("Attempting authentication for %s%.100s.",
|
|
authctxt->valid ? "" : "invalid user ", authctxt->user);
|
|
|
|
/* If the user has no password, accept authentication immediately. */
|
|
if (options.permit_empty_passwd && options.password_authentication &&
|
|
#ifdef KRB5
|
|
(!options.kerberos_authentication || options.kerberos_or_local_passwd) &&
|
|
#endif
|
|
PRIVSEP(auth_password(authctxt, ""))) {
|
|
#ifdef USE_PAM
|
|
if (options.use_pam && (PRIVSEP(do_pam_account())))
|
|
#endif
|
|
{
|
|
auth_log(authctxt, 1, "without authentication", "");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Indicate that authentication is needed. */
|
|
packet_start(SSH_SMSG_FAILURE);
|
|
packet_send();
|
|
packet_write_wait();
|
|
|
|
for (;;) {
|
|
/* default to fail */
|
|
authenticated = 0;
|
|
|
|
info[0] = '\0';
|
|
|
|
/* Get a packet from the client. */
|
|
prev = type;
|
|
type = packet_read();
|
|
|
|
/*
|
|
* If we started challenge-response authentication but the
|
|
* next packet is not a response to our challenge, release
|
|
* the resources allocated by get_challenge() (which would
|
|
* normally have been released by verify_response() had we
|
|
* received such a response)
|
|
*/
|
|
if (prev == SSH_CMSG_AUTH_TIS &&
|
|
type != SSH_CMSG_AUTH_TIS_RESPONSE)
|
|
abandon_challenge_response(authctxt);
|
|
|
|
if (authctxt->failures >= options.max_authtries)
|
|
goto skip;
|
|
if ((meth = lookup_authmethod1(type)) == NULL) {
|
|
logit("Unknown message during authentication: "
|
|
"type %d", type);
|
|
goto skip;
|
|
}
|
|
|
|
if (!*(meth->enabled)) {
|
|
verbose("%s authentication disabled.", meth->name);
|
|
goto skip;
|
|
}
|
|
|
|
authenticated = meth->method(authctxt, info, sizeof(info));
|
|
if (authenticated == -1)
|
|
continue; /* "postponed" */
|
|
|
|
#ifdef BSD_AUTH
|
|
if (authctxt->as) {
|
|
auth_close(authctxt->as);
|
|
authctxt->as = NULL;
|
|
}
|
|
#endif
|
|
if (!authctxt->valid && authenticated)
|
|
fatal("INTERNAL ERROR: authenticated invalid user %s",
|
|
authctxt->user);
|
|
|
|
#ifdef _UNICOS
|
|
if (authenticated && cray_access_denied(authctxt->user)) {
|
|
authenticated = 0;
|
|
fatal("Access denied for user %s.",authctxt->user);
|
|
}
|
|
#endif /* _UNICOS */
|
|
|
|
#ifndef HAVE_CYGWIN
|
|
/* Special handling for root */
|
|
if (authenticated && authctxt->pw->pw_uid == 0 &&
|
|
!auth_root_allowed(meth->name)) {
|
|
authenticated = 0;
|
|
# ifdef SSH_AUDIT_EVENTS
|
|
PRIVSEP(audit_event(SSH_LOGIN_ROOT_DENIED));
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_PAM
|
|
if (options.use_pam && authenticated &&
|
|
!PRIVSEP(do_pam_account())) {
|
|
char *msg;
|
|
size_t len;
|
|
|
|
error("Access denied for user %s by PAM account "
|
|
"configuration", authctxt->user);
|
|
len = buffer_len(&loginmsg);
|
|
buffer_append(&loginmsg, "\0", 1);
|
|
msg = buffer_ptr(&loginmsg);
|
|
/* strip trailing newlines */
|
|
if (len > 0)
|
|
while (len > 0 && msg[--len] == '\n')
|
|
msg[len] = '\0';
|
|
else
|
|
msg = "Access denied.";
|
|
packet_disconnect("%s", msg);
|
|
}
|
|
#endif
|
|
|
|
skip:
|
|
/* Log before sending the reply */
|
|
auth_log(authctxt, authenticated, get_authname(type), info);
|
|
|
|
if (client_user != NULL) {
|
|
xfree(client_user);
|
|
client_user = NULL;
|
|
}
|
|
|
|
if (authenticated)
|
|
return;
|
|
|
|
if (++authctxt->failures >= options.max_authtries) {
|
|
#ifdef SSH_AUDIT_EVENTS
|
|
PRIVSEP(audit_event(SSH_LOGIN_EXCEED_MAXTRIES));
|
|
#endif
|
|
packet_disconnect(AUTH_FAIL_MSG, authctxt->user);
|
|
}
|
|
|
|
packet_start(SSH_SMSG_FAILURE);
|
|
packet_send();
|
|
packet_write_wait();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Performs authentication of an incoming connection. Session key has already
|
|
* been exchanged and encryption is enabled.
|
|
*/
|
|
void
|
|
do_authentication(Authctxt *authctxt)
|
|
{
|
|
u_int ulen;
|
|
char *user, *style = NULL;
|
|
|
|
/* Get the name of the user that we wish to log in as. */
|
|
packet_read_expect(SSH_CMSG_USER);
|
|
|
|
/* Get the user name. */
|
|
user = packet_get_cstring(&ulen);
|
|
packet_check_eom();
|
|
|
|
if ((style = strchr(user, ':')) != NULL)
|
|
*style++ = '\0';
|
|
|
|
authctxt->user = user;
|
|
authctxt->style = style;
|
|
|
|
/* Verify that the user is a valid user. */
|
|
if ((authctxt->pw = PRIVSEP(getpwnamallow(user))) != NULL)
|
|
authctxt->valid = 1;
|
|
else {
|
|
debug("do_authentication: invalid user %s", user);
|
|
authctxt->pw = fakepw();
|
|
}
|
|
|
|
setproctitle("%s%s", authctxt->valid ? user : "unknown",
|
|
use_privsep ? " [net]" : "");
|
|
|
|
#ifdef USE_PAM
|
|
if (options.use_pam)
|
|
PRIVSEP(start_pam(authctxt));
|
|
#endif
|
|
|
|
/*
|
|
* If we are not running as root, the user must have the same uid as
|
|
* the server.
|
|
*/
|
|
#ifndef HAVE_CYGWIN
|
|
if (!use_privsep && getuid() != 0 && authctxt->pw &&
|
|
authctxt->pw->pw_uid != getuid())
|
|
packet_disconnect("Cannot change user when server not running as root.");
|
|
#endif
|
|
|
|
/*
|
|
* Loop until the user has been authenticated or the connection is
|
|
* closed, do_authloop() returns only if authentication is successful
|
|
*/
|
|
do_authloop(authctxt);
|
|
|
|
/* The user has been authenticated and accepted. */
|
|
packet_start(SSH_SMSG_SUCCESS);
|
|
packet_send();
|
|
packet_write_wait();
|
|
}
|