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.
408 lines
10 KiB
C
408 lines
10 KiB
C
/* $OpenBSD: auth2.c,v 1.122 2010/08/31 09:58:37 djm Exp $ */
|
|
/*
|
|
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <pwd.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "atomicio.h"
|
|
#include "xmalloc.h"
|
|
#include "ssh2.h"
|
|
#include "packet.h"
|
|
#include "log.h"
|
|
#include "buffer.h"
|
|
#include "servconf.h"
|
|
#include "compat.h"
|
|
#include "key.h"
|
|
#include "hostfile.h"
|
|
#include "auth.h"
|
|
#include "dispatch.h"
|
|
#include "pathnames.h"
|
|
#include "buffer.h"
|
|
|
|
#ifdef GSSAPI
|
|
#include "ssh-gss.h"
|
|
#endif
|
|
#include "monitor_wrap.h"
|
|
|
|
/* import */
|
|
extern ServerOptions options;
|
|
extern u_char *session_id2;
|
|
extern u_int session_id2_len;
|
|
extern Buffer loginmsg;
|
|
|
|
/* methods */
|
|
|
|
extern Authmethod method_none;
|
|
extern Authmethod method_pubkey;
|
|
extern Authmethod method_passwd;
|
|
extern Authmethod method_kbdint;
|
|
extern Authmethod method_hostbased;
|
|
#ifdef GSSAPI
|
|
extern Authmethod method_gssapi;
|
|
#endif
|
|
#ifdef JPAKE
|
|
extern Authmethod method_jpake;
|
|
#endif
|
|
|
|
Authmethod *authmethods[] = {
|
|
&method_none,
|
|
&method_pubkey,
|
|
#ifdef GSSAPI
|
|
&method_gssapi,
|
|
#endif
|
|
#ifdef JPAKE
|
|
&method_jpake,
|
|
#endif
|
|
&method_passwd,
|
|
&method_kbdint,
|
|
&method_hostbased,
|
|
NULL
|
|
};
|
|
|
|
/* protocol */
|
|
|
|
static void input_service_request(int, u_int32_t, void *);
|
|
static void input_userauth_request(int, u_int32_t, void *);
|
|
|
|
/* helper */
|
|
static Authmethod *authmethod_lookup(const char *);
|
|
static char *authmethods_get(void);
|
|
|
|
char *
|
|
auth2_read_banner(void)
|
|
{
|
|
struct stat st;
|
|
char *banner = NULL;
|
|
size_t len, n;
|
|
int fd;
|
|
|
|
if ((fd = open(options.banner, O_RDONLY)) == -1)
|
|
return (NULL);
|
|
if (fstat(fd, &st) == -1) {
|
|
close(fd);
|
|
return (NULL);
|
|
}
|
|
if (st.st_size > 1*1024*1024) {
|
|
close(fd);
|
|
return (NULL);
|
|
}
|
|
|
|
len = (size_t)st.st_size; /* truncate */
|
|
banner = xmalloc(len + 1);
|
|
n = atomicio(read, fd, banner, len);
|
|
close(fd);
|
|
|
|
if (n != len) {
|
|
xfree(banner);
|
|
return (NULL);
|
|
}
|
|
banner[n] = '\0';
|
|
|
|
return (banner);
|
|
}
|
|
|
|
void
|
|
userauth_send_banner(const char *msg)
|
|
{
|
|
if (datafellows & SSH_BUG_BANNER)
|
|
return;
|
|
|
|
packet_start(SSH2_MSG_USERAUTH_BANNER);
|
|
packet_put_cstring(msg);
|
|
packet_put_cstring(""); /* language, unused */
|
|
packet_send();
|
|
debug("%s: sent", __func__);
|
|
}
|
|
|
|
static void
|
|
userauth_banner(void)
|
|
{
|
|
char *banner = NULL;
|
|
|
|
if (options.banner == NULL ||
|
|
strcasecmp(options.banner, "none") == 0 ||
|
|
(datafellows & SSH_BUG_BANNER) != 0)
|
|
return;
|
|
|
|
if ((banner = PRIVSEP(auth2_read_banner())) == NULL)
|
|
goto done;
|
|
userauth_send_banner(banner);
|
|
|
|
done:
|
|
if (banner)
|
|
xfree(banner);
|
|
}
|
|
|
|
/*
|
|
* loop until authctxt->success == TRUE
|
|
*/
|
|
void
|
|
do_authentication2(Authctxt *authctxt)
|
|
{
|
|
dispatch_init(&dispatch_protocol_error);
|
|
dispatch_set(SSH2_MSG_SERVICE_REQUEST, &input_service_request);
|
|
dispatch_run(DISPATCH_BLOCK, &authctxt->success, authctxt);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
input_service_request(int type, u_int32_t seq, void *ctxt)
|
|
{
|
|
Authctxt *authctxt = ctxt;
|
|
u_int len;
|
|
int acceptit = 0;
|
|
char *service = packet_get_cstring(&len);
|
|
packet_check_eom();
|
|
|
|
if (authctxt == NULL)
|
|
fatal("input_service_request: no authctxt");
|
|
|
|
if (strcmp(service, "ssh-userauth") == 0) {
|
|
if (!authctxt->success) {
|
|
acceptit = 1;
|
|
/* now we can handle user-auth requests */
|
|
dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &input_userauth_request);
|
|
}
|
|
}
|
|
/* XXX all other service requests are denied */
|
|
|
|
if (acceptit) {
|
|
packet_start(SSH2_MSG_SERVICE_ACCEPT);
|
|
packet_put_cstring(service);
|
|
packet_send();
|
|
packet_write_wait();
|
|
} else {
|
|
debug("bad service request %s", service);
|
|
packet_disconnect("bad service request %s", service);
|
|
}
|
|
xfree(service);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
input_userauth_request(int type, u_int32_t seq, void *ctxt)
|
|
{
|
|
Authctxt *authctxt = ctxt;
|
|
Authmethod *m = NULL;
|
|
char *user, *service, *method, *style = NULL;
|
|
int authenticated = 0;
|
|
|
|
if (authctxt == NULL)
|
|
fatal("input_userauth_request: no authctxt");
|
|
|
|
user = packet_get_cstring(NULL);
|
|
service = packet_get_cstring(NULL);
|
|
method = packet_get_cstring(NULL);
|
|
debug("userauth-request for user %s service %s method %s", user, service, method);
|
|
debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
|
|
|
|
if ((style = strchr(user, ':')) != NULL)
|
|
*style++ = 0;
|
|
|
|
if (authctxt->attempt++ == 0) {
|
|
/* setup auth context */
|
|
authctxt->pw = PRIVSEP(getpwnamallow(user));
|
|
authctxt->user = xstrdup(user);
|
|
if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
|
|
authctxt->valid = 1;
|
|
debug2("input_userauth_request: setting up authctxt for %s", user);
|
|
} else {
|
|
logit("input_userauth_request: invalid user %s", user);
|
|
authctxt->pw = fakepw();
|
|
#ifdef SSH_AUDIT_EVENTS
|
|
PRIVSEP(audit_event(SSH_INVALID_USER));
|
|
#endif
|
|
}
|
|
#ifdef USE_PAM
|
|
if (options.use_pam)
|
|
PRIVSEP(start_pam(authctxt));
|
|
#endif
|
|
setproctitle("%s%s", authctxt->valid ? user : "unknown",
|
|
use_privsep ? " [net]" : "");
|
|
authctxt->service = xstrdup(service);
|
|
authctxt->style = style ? xstrdup(style) : NULL;
|
|
if (use_privsep)
|
|
mm_inform_authserv(service, style);
|
|
userauth_banner();
|
|
} else if (strcmp(user, authctxt->user) != 0 ||
|
|
strcmp(service, authctxt->service) != 0) {
|
|
packet_disconnect("Change of username or service not allowed: "
|
|
"(%s,%s) -> (%s,%s)",
|
|
authctxt->user, authctxt->service, user, service);
|
|
}
|
|
/* reset state */
|
|
auth2_challenge_stop(authctxt);
|
|
#ifdef JPAKE
|
|
auth2_jpake_stop(authctxt);
|
|
#endif
|
|
|
|
#ifdef GSSAPI
|
|
/* XXX move to auth2_gssapi_stop() */
|
|
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
|
|
dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL);
|
|
#endif
|
|
|
|
authctxt->postponed = 0;
|
|
|
|
/* try to authenticate user */
|
|
m = authmethod_lookup(method);
|
|
if (m != NULL && authctxt->failures < options.max_authtries) {
|
|
debug2("input_userauth_request: try method %s", method);
|
|
authenticated = m->userauth(authctxt);
|
|
}
|
|
userauth_finish(authctxt, authenticated, method);
|
|
|
|
xfree(service);
|
|
xfree(user);
|
|
xfree(method);
|
|
}
|
|
|
|
void
|
|
userauth_finish(Authctxt *authctxt, int authenticated, char *method)
|
|
{
|
|
char *methods;
|
|
|
|
if (!authctxt->valid && authenticated)
|
|
fatal("INTERNAL ERROR: authenticated invalid user %s",
|
|
authctxt->user);
|
|
|
|
/* Special handling for root */
|
|
if (authenticated && authctxt->pw->pw_uid == 0 &&
|
|
!auth_root_allowed(method)) {
|
|
authenticated = 0;
|
|
#ifdef SSH_AUDIT_EVENTS
|
|
PRIVSEP(audit_event(SSH_LOGIN_ROOT_DENIED));
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_PAM
|
|
if (options.use_pam && authenticated) {
|
|
if (!PRIVSEP(do_pam_account())) {
|
|
/* if PAM returned a message, send it to the user */
|
|
if (buffer_len(&loginmsg) > 0) {
|
|
buffer_append(&loginmsg, "\0", 1);
|
|
userauth_send_banner(buffer_ptr(&loginmsg));
|
|
packet_write_wait();
|
|
}
|
|
fatal("Access denied for user %s by PAM account "
|
|
"configuration", authctxt->user);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef _UNICOS
|
|
if (authenticated && cray_access_denied(authctxt->user)) {
|
|
authenticated = 0;
|
|
fatal("Access denied for user %s.",authctxt->user);
|
|
}
|
|
#endif /* _UNICOS */
|
|
|
|
/* Log before sending the reply */
|
|
auth_log(authctxt, authenticated, method, " ssh2");
|
|
|
|
if (authctxt->postponed)
|
|
return;
|
|
|
|
/* XXX todo: check if multiple auth methods are needed */
|
|
if (authenticated == 1) {
|
|
/* turn off userauth */
|
|
dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore);
|
|
packet_start(SSH2_MSG_USERAUTH_SUCCESS);
|
|
packet_send();
|
|
packet_write_wait();
|
|
/* now we can break out */
|
|
authctxt->success = 1;
|
|
} else {
|
|
|
|
/* Allow initial try of "none" auth without failure penalty */
|
|
if (authctxt->attempt > 1 || strcmp(method, "none") != 0)
|
|
authctxt->failures++;
|
|
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);
|
|
}
|
|
methods = authmethods_get();
|
|
packet_start(SSH2_MSG_USERAUTH_FAILURE);
|
|
packet_put_cstring(methods);
|
|
packet_put_char(0); /* XXX partial success, unused */
|
|
packet_send();
|
|
packet_write_wait();
|
|
xfree(methods);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
authmethods_get(void)
|
|
{
|
|
Buffer b;
|
|
char *list;
|
|
int i;
|
|
|
|
buffer_init(&b);
|
|
for (i = 0; authmethods[i] != NULL; i++) {
|
|
if (strcmp(authmethods[i]->name, "none") == 0)
|
|
continue;
|
|
if (authmethods[i]->enabled != NULL &&
|
|
*(authmethods[i]->enabled) != 0) {
|
|
if (buffer_len(&b) > 0)
|
|
buffer_append(&b, ",", 1);
|
|
buffer_append(&b, authmethods[i]->name,
|
|
strlen(authmethods[i]->name));
|
|
}
|
|
}
|
|
buffer_append(&b, "\0", 1);
|
|
list = xstrdup(buffer_ptr(&b));
|
|
buffer_free(&b);
|
|
return list;
|
|
}
|
|
|
|
static Authmethod *
|
|
authmethod_lookup(const char *name)
|
|
{
|
|
int i;
|
|
|
|
if (name != NULL)
|
|
for (i = 0; authmethods[i] != NULL; i++)
|
|
if (authmethods[i]->enabled != NULL &&
|
|
*(authmethods[i]->enabled) != 0 &&
|
|
strcmp(name, authmethods[i]->name) == 0)
|
|
return authmethods[i];
|
|
debug2("Unrecognized authentication method name: %s",
|
|
name ? name : "NULL");
|
|
return NULL;
|
|
}
|
|
|