wmi-1.3.16 from opsview.com
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Password and authentication handling
|
||||
Copyright (C) Andrew Bartlett 2001-2002
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/util/dlinklist.h"
|
||||
#include "auth/auth.h"
|
||||
#include "lib/events/events.h"
|
||||
#include "build.h"
|
||||
|
||||
/***************************************************************************
|
||||
Set a fixed challenge
|
||||
***************************************************************************/
|
||||
NTSTATUS auth_context_set_challenge(struct auth_context *auth_ctx, const uint8_t chal[8], const char *set_by)
|
||||
{
|
||||
auth_ctx->challenge.set_by = talloc_strdup(auth_ctx, set_by);
|
||||
NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.set_by);
|
||||
|
||||
auth_ctx->challenge.data = data_blob_talloc(auth_ctx, chal, 8);
|
||||
NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.data.data);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
Set a fixed challenge
|
||||
***************************************************************************/
|
||||
BOOL auth_challenge_may_be_modified(struct auth_context *auth_ctx)
|
||||
{
|
||||
return auth_ctx->challenge.may_be_modified;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Try to get a challenge out of the various authentication modules.
|
||||
Returns a const char of length 8 bytes.
|
||||
****************************************************************************/
|
||||
_PUBLIC_ NTSTATUS auth_get_challenge(struct auth_context *auth_ctx, const uint8_t **_chal)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_method_context *method;
|
||||
|
||||
if (auth_ctx->challenge.data.length) {
|
||||
DEBUG(5, ("auth_get_challenge: returning previous challenge by module %s (normal)\n",
|
||||
auth_ctx->challenge.set_by));
|
||||
*_chal = auth_ctx->challenge.data.data;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
for (method = auth_ctx->methods; method; method = method->next) {
|
||||
DATA_BLOB challenge = data_blob(NULL,0);
|
||||
|
||||
nt_status = method->ops->get_challenge(method, auth_ctx, &challenge);
|
||||
if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOT_IMPLEMENTED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
if (challenge.length != 8) {
|
||||
DEBUG(0, ("auth_get_challenge: invalid challenge (length %u) by mothod [%s]\n",
|
||||
(unsigned)challenge.length, method->ops->name));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
auth_ctx->challenge.data = challenge;
|
||||
auth_ctx->challenge.set_by = method->ops->name;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!auth_ctx->challenge.set_by) {
|
||||
uint8_t chal[8];
|
||||
generate_random_buffer(chal, 8);
|
||||
|
||||
auth_ctx->challenge.data = data_blob_talloc(auth_ctx, chal, 8);
|
||||
NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.data.data);
|
||||
auth_ctx->challenge.set_by = "random";
|
||||
|
||||
auth_ctx->challenge.may_be_modified = True;
|
||||
}
|
||||
|
||||
DEBUG(10,("auth_get_challenge: challenge set by %s\n",
|
||||
auth_ctx->challenge.set_by));
|
||||
|
||||
*_chal = auth_ctx->challenge.data.data;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
struct auth_check_password_sync_state {
|
||||
BOOL finished;
|
||||
NTSTATUS status;
|
||||
struct auth_serversupplied_info *server_info;
|
||||
};
|
||||
|
||||
static void auth_check_password_sync_callback(struct auth_check_password_request *req,
|
||||
void *private_data)
|
||||
{
|
||||
struct auth_check_password_sync_state *s = talloc_get_type(private_data,
|
||||
struct auth_check_password_sync_state);
|
||||
|
||||
s->finished = True;
|
||||
s->status = auth_check_password_recv(req, s, &s->server_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a user's Plaintext, LM or NTLM password.
|
||||
* (sync version)
|
||||
*
|
||||
* Check a user's password, as given in the user_info struct and return various
|
||||
* interesting details in the server_info struct.
|
||||
*
|
||||
* The return value takes precedence over the contents of the server_info
|
||||
* struct. When the return is other than NT_STATUS_OK the contents
|
||||
* of that structure is undefined.
|
||||
*
|
||||
* @param auth_ctx Supplies the challenges and some other data.
|
||||
* Must be created with auth_context_create(), and the challenges should be
|
||||
* filled in, either at creation or by calling the challenge geneation
|
||||
* function auth_get_challenge().
|
||||
*
|
||||
* @param user_info Contains the user supplied components, including the passwords.
|
||||
*
|
||||
* @param mem_ctx The parent memory context for the server_info structure
|
||||
*
|
||||
* @param server_info If successful, contains information about the authentication,
|
||||
* including a SAM_ACCOUNT struct describing the user.
|
||||
*
|
||||
* @return An NTSTATUS with NT_STATUS_OK or an appropriate error.
|
||||
*
|
||||
**/
|
||||
|
||||
NTSTATUS auth_check_password(struct auth_context *auth_ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
struct auth_check_password_sync_state *sync_state;
|
||||
NTSTATUS status;
|
||||
|
||||
sync_state = talloc_zero(auth_ctx, struct auth_check_password_sync_state);
|
||||
NT_STATUS_HAVE_NO_MEMORY(sync_state);
|
||||
|
||||
auth_check_password_send(auth_ctx, user_info, auth_check_password_sync_callback, sync_state);
|
||||
|
||||
while (!sync_state->finished) {
|
||||
event_loop_once(auth_ctx->event_ctx);
|
||||
}
|
||||
|
||||
status = sync_state->status;
|
||||
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
*server_info = talloc_steal(mem_ctx, sync_state->server_info);
|
||||
}
|
||||
|
||||
talloc_free(sync_state);
|
||||
return status;
|
||||
}
|
||||
|
||||
struct auth_check_password_request {
|
||||
struct auth_context *auth_ctx;
|
||||
const struct auth_usersupplied_info *user_info;
|
||||
struct auth_serversupplied_info *server_info;
|
||||
struct auth_method_context *method;
|
||||
NTSTATUS status;
|
||||
struct {
|
||||
void (*fn)(struct auth_check_password_request *req, void *private_data);
|
||||
void *private_data;
|
||||
} callback;
|
||||
};
|
||||
|
||||
static void auth_check_password_async_timed_handler(struct event_context *ev, struct timed_event *te,
|
||||
struct timeval t, void *ptr)
|
||||
{
|
||||
struct auth_check_password_request *req = talloc_get_type(ptr, struct auth_check_password_request);
|
||||
req->status = req->method->ops->check_password(req->method, req, req->user_info, &req->server_info);
|
||||
req->callback.fn(req, req->callback.private_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a user's Plaintext, LM or NTLM password.
|
||||
* async send hook
|
||||
*
|
||||
* Check a user's password, as given in the user_info struct and return various
|
||||
* interesting details in the server_info struct.
|
||||
*
|
||||
* The return value takes precedence over the contents of the server_info
|
||||
* struct. When the return is other than NT_STATUS_OK the contents
|
||||
* of that structure is undefined.
|
||||
*
|
||||
* @param auth_ctx Supplies the challenges and some other data.
|
||||
* Must be created with make_auth_context(), and the challenges should be
|
||||
* filled in, either at creation or by calling the challenge geneation
|
||||
* function auth_get_challenge().
|
||||
*
|
||||
* @param user_info Contains the user supplied components, including the passwords.
|
||||
*
|
||||
* @param callback A callback function which will be called when the operation is finished.
|
||||
* The callback function needs to call auth_check_password_recv() to get the return values
|
||||
*
|
||||
* @param private_data A private pointer which will ba passed to the callback function
|
||||
*
|
||||
**/
|
||||
|
||||
void auth_check_password_send(struct auth_context *auth_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
void (*callback)(struct auth_check_password_request *req, void *private_data),
|
||||
void *private_data)
|
||||
{
|
||||
/* if all the modules say 'not for me' this is reasonable */
|
||||
NTSTATUS nt_status;
|
||||
struct auth_method_context *method;
|
||||
const uint8_t *challenge;
|
||||
struct auth_usersupplied_info *user_info_tmp;
|
||||
struct auth_check_password_request *req = NULL;
|
||||
|
||||
DEBUG(3, ("auth_check_password_send: Checking password for unmapped user [%s]\\[%s]@[%s]\n",
|
||||
user_info->client.domain_name, user_info->client.account_name, user_info->workstation_name));
|
||||
|
||||
req = talloc_zero(auth_ctx, struct auth_check_password_request);
|
||||
if (!req) {
|
||||
callback(NULL, private_data);
|
||||
return;
|
||||
}
|
||||
req->auth_ctx = auth_ctx;
|
||||
req->user_info = user_info;
|
||||
req->callback.fn = callback;
|
||||
req->callback.private_data = private_data;
|
||||
|
||||
if (!user_info->mapped_state) {
|
||||
nt_status = map_user_info(req, user_info, &user_info_tmp);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) goto failed;
|
||||
user_info = user_info_tmp;
|
||||
req->user_info = user_info_tmp;
|
||||
}
|
||||
|
||||
DEBUGADD(3,("auth_check_password_send: mapped user is: [%s]\\[%s]@[%s]\n",
|
||||
user_info->mapped.domain_name, user_info->mapped.account_name, user_info->workstation_name));
|
||||
|
||||
nt_status = auth_get_challenge(auth_ctx, &challenge);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(0, ("auth_check_password_send: Invalid challenge (length %u) stored for this auth context set_by %s - cannot continue: %s\n",
|
||||
(unsigned)auth_ctx->challenge.data.length, auth_ctx->challenge.set_by, nt_errstr(nt_status)));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (auth_ctx->challenge.set_by) {
|
||||
DEBUG(10, ("auth_check_password_send: auth_context challenge created by %s\n",
|
||||
auth_ctx->challenge.set_by));
|
||||
}
|
||||
|
||||
DEBUG(10, ("auth_check_password_send: challenge is: \n"));
|
||||
dump_data(5, auth_ctx->challenge.data.data, auth_ctx->challenge.data.length);
|
||||
|
||||
nt_status = NT_STATUS_NO_SUCH_USER; /* If all the modules say 'not for me', then this is reasonable */
|
||||
for (method = auth_ctx->methods; method; method = method->next) {
|
||||
NTSTATUS result;
|
||||
struct timed_event *te = NULL;
|
||||
|
||||
/* check if the module wants to chek the password */
|
||||
result = method->ops->want_check(method, req, user_info);
|
||||
if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_IMPLEMENTED)) {
|
||||
DEBUG(11,("auth_check_password_send: %s had nothing to say\n", method->ops->name));
|
||||
continue;
|
||||
}
|
||||
|
||||
nt_status = result;
|
||||
req->method = method;
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) break;
|
||||
|
||||
te = event_add_timed(auth_ctx->event_ctx, req,
|
||||
timeval_zero(),
|
||||
auth_check_password_async_timed_handler, req);
|
||||
if (!te) {
|
||||
nt_status = NT_STATUS_NO_MEMORY;
|
||||
goto failed;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
failed:
|
||||
req->status = nt_status;
|
||||
req->callback.fn(req, req->callback.private_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a user's Plaintext, LM or NTLM password.
|
||||
* async receive function
|
||||
*
|
||||
* The return value takes precedence over the contents of the server_info
|
||||
* struct. When the return is other than NT_STATUS_OK the contents
|
||||
* of that structure is undefined.
|
||||
*
|
||||
*
|
||||
* @param req The async auth_check_password state, passes to the callers callback function
|
||||
*
|
||||
* @param mem_ctx The parent memory context for the server_info structure
|
||||
*
|
||||
* @param server_info If successful, contains information about the authentication,
|
||||
* including a SAM_ACCOUNT struct describing the user.
|
||||
*
|
||||
* @return An NTSTATUS with NT_STATUS_OK or an appropriate error.
|
||||
*
|
||||
**/
|
||||
|
||||
NTSTATUS auth_check_password_recv(struct auth_check_password_request *req,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
NT_STATUS_HAVE_NO_MEMORY(req);
|
||||
|
||||
if (NT_STATUS_IS_OK(req->status)) {
|
||||
DEBUG(5,("auth_check_password_recv: %s authentication for user [%s\\%s] succeeded\n",
|
||||
req->method->ops->name, req->server_info->domain_name, req->server_info->account_name));
|
||||
|
||||
*server_info = talloc_steal(mem_ctx, req->server_info);
|
||||
} else {
|
||||
DEBUG(2,("auth_check_password_recv: %s authentication for user [%s\\%s] FAILED with error %s\n",
|
||||
(req->method ? req->method->ops->name : "NO_METHOD"),
|
||||
req->user_info->mapped.domain_name,
|
||||
req->user_info->mapped.account_name,
|
||||
nt_errstr(req->status)));
|
||||
}
|
||||
|
||||
status = req->status;
|
||||
talloc_free(req);
|
||||
return status;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
Make a auth_info struct for the auth subsystem
|
||||
***************************************************************************/
|
||||
NTSTATUS auth_context_create(TALLOC_CTX *mem_ctx, const char **methods,
|
||||
struct event_context *ev,
|
||||
struct messaging_context *msg,
|
||||
struct auth_context **auth_ctx)
|
||||
{
|
||||
int i;
|
||||
struct auth_context *ctx;
|
||||
|
||||
if (!methods) {
|
||||
DEBUG(0,("auth_context_create: No auth method list!?\n"));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
if (!ev) {
|
||||
DEBUG(0,("auth_context_create: called with out event context\n"));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
if (!msg) {
|
||||
DEBUG(0,("auth_context_create: called with out messaging context\n"));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
ctx = talloc(mem_ctx, struct auth_context);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ctx);
|
||||
ctx->challenge.set_by = NULL;
|
||||
ctx->challenge.may_be_modified = False;
|
||||
ctx->challenge.data = data_blob(NULL, 0);
|
||||
ctx->methods = NULL;
|
||||
ctx->event_ctx = ev;
|
||||
ctx->msg_ctx = msg;
|
||||
|
||||
for (i=0; methods[i] ; i++) {
|
||||
struct auth_method_context *method;
|
||||
|
||||
method = talloc(ctx, struct auth_method_context);
|
||||
NT_STATUS_HAVE_NO_MEMORY(method);
|
||||
|
||||
method->ops = auth_backend_byname(methods[i]);
|
||||
if (!method->ops) {
|
||||
DEBUG(1,("auth_context_create: failed to find method=%s\n",
|
||||
methods[i]));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
method->auth_ctx = ctx;
|
||||
method->depth = i;
|
||||
DLIST_ADD_END(ctx->methods, method, struct auth_method_context *);
|
||||
}
|
||||
|
||||
if (!ctx->methods) {
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
*auth_ctx = ctx;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* the list of currently registered AUTH backends */
|
||||
static struct auth_backend {
|
||||
const struct auth_operations *ops;
|
||||
} *backends = NULL;
|
||||
static int num_backends;
|
||||
|
||||
/*
|
||||
register a AUTH backend.
|
||||
|
||||
The 'name' can be later used by other backends to find the operations
|
||||
structure for this backend.
|
||||
*/
|
||||
NTSTATUS auth_register(const void *_ops)
|
||||
{
|
||||
const struct auth_operations *ops = _ops;
|
||||
struct auth_operations *new_ops;
|
||||
|
||||
if (auth_backend_byname(ops->name) != NULL) {
|
||||
/* its already registered! */
|
||||
DEBUG(0,("AUTH backend '%s' already registered\n",
|
||||
ops->name));
|
||||
return NT_STATUS_OBJECT_NAME_COLLISION;
|
||||
}
|
||||
|
||||
backends = realloc_p(backends, struct auth_backend, num_backends+1);
|
||||
if (!backends) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
new_ops = smb_xmemdup(ops, sizeof(*ops));
|
||||
new_ops->name = smb_xstrdup(ops->name);
|
||||
|
||||
backends[num_backends].ops = new_ops;
|
||||
|
||||
num_backends++;
|
||||
|
||||
DEBUG(3,("AUTH backend '%s' registered\n",
|
||||
ops->name));
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
return the operations structure for a named backend of the specified type
|
||||
*/
|
||||
const struct auth_operations *auth_backend_byname(const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0;i<num_backends;i++) {
|
||||
if (strcmp(backends[i].ops->name, name) == 0) {
|
||||
return backends[i].ops;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
return the AUTH interface version, and the size of some critical types
|
||||
This can be used by backends to either detect compilation errors, or provide
|
||||
multiple implementations for different smbd compilation options in one module
|
||||
*/
|
||||
const struct auth_critical_sizes *auth_interface_version(void)
|
||||
{
|
||||
static const struct auth_critical_sizes critical_sizes = {
|
||||
AUTH_INTERFACE_VERSION,
|
||||
sizeof(struct auth_operations),
|
||||
sizeof(struct auth_method_context),
|
||||
sizeof(struct auth_context),
|
||||
sizeof(struct auth_usersupplied_info),
|
||||
sizeof(struct auth_serversupplied_info)
|
||||
};
|
||||
|
||||
return &critical_sizes;
|
||||
}
|
||||
|
||||
NTSTATUS auth_init(void)
|
||||
{
|
||||
static BOOL initialized = False;
|
||||
|
||||
init_module_fn static_init[] = STATIC_auth_MODULES;
|
||||
init_module_fn *shared_init;
|
||||
|
||||
if (initialized) return NT_STATUS_OK;
|
||||
initialized = True;
|
||||
|
||||
shared_init = load_samba_modules(NULL, "auth");
|
||||
|
||||
run_init_functions(static_init);
|
||||
run_init_functions(shared_init);
|
||||
|
||||
talloc_free(shared_init);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS server_service_auth_init(void)
|
||||
{
|
||||
return auth_init();
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Standardised Authentication types
|
||||
Copyright (C) Andrew Bartlett 2001
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef _SAMBA_AUTH_H
|
||||
#define _SAMBA_AUTH_H
|
||||
|
||||
union netr_Validation;
|
||||
struct netr_SamBaseInfo;
|
||||
struct netr_SamInfo3;
|
||||
|
||||
/* modules can use the following to determine if the interface has changed
|
||||
* please increment the version number after each interface change
|
||||
* with a comment and maybe update struct auth_critical_sizes.
|
||||
*/
|
||||
/* version 1 - version from samba 3.0 - metze */
|
||||
/* version 2 - initial samba4 version - metze */
|
||||
/* version 3 - subsequent samba4 version - abartlet */
|
||||
/* version 4 - subsequent samba4 version - metze */
|
||||
/* version 0 - till samba4 is stable - metze */
|
||||
#define AUTH_INTERFACE_VERSION 0
|
||||
|
||||
#define USER_INFO_CASE_INSENSITIVE_USERNAME 0x01 /* username may be in any case */
|
||||
#define USER_INFO_CASE_INSENSITIVE_PASSWORD 0x02 /* password may be in any case */
|
||||
#define USER_INFO_DONT_CHECK_UNIX_ACCOUNT 0x04 /* dont check unix account status */
|
||||
#define USER_INFO_INTERACTIVE_LOGON 0x08 /* dont check unix account status */
|
||||
|
||||
enum auth_password_state {
|
||||
AUTH_PASSWORD_RESPONSE,
|
||||
AUTH_PASSWORD_HASH,
|
||||
AUTH_PASSWORD_PLAIN
|
||||
};
|
||||
|
||||
struct auth_usersupplied_info
|
||||
{
|
||||
const char *workstation_name;
|
||||
struct socket_address *remote_host;
|
||||
|
||||
uint32_t logon_parameters;
|
||||
|
||||
BOOL mapped_state;
|
||||
/* the values the client gives us */
|
||||
struct {
|
||||
const char *account_name;
|
||||
const char *domain_name;
|
||||
} client, mapped;
|
||||
|
||||
enum auth_password_state password_state;
|
||||
|
||||
union {
|
||||
struct {
|
||||
DATA_BLOB lanman;
|
||||
DATA_BLOB nt;
|
||||
} response;
|
||||
struct {
|
||||
struct samr_Password *lanman;
|
||||
struct samr_Password *nt;
|
||||
} hash;
|
||||
|
||||
char *plaintext;
|
||||
} password;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct auth_serversupplied_info
|
||||
{
|
||||
struct dom_sid *account_sid;
|
||||
struct dom_sid *primary_group_sid;
|
||||
|
||||
size_t n_domain_groups;
|
||||
struct dom_sid **domain_groups;
|
||||
|
||||
DATA_BLOB user_session_key;
|
||||
DATA_BLOB lm_session_key;
|
||||
|
||||
const char *account_name;
|
||||
const char *domain_name;
|
||||
|
||||
const char *full_name;
|
||||
const char *logon_script;
|
||||
const char *profile_path;
|
||||
const char *home_directory;
|
||||
const char *home_drive;
|
||||
const char *logon_server;
|
||||
|
||||
NTTIME last_logon;
|
||||
NTTIME last_logoff;
|
||||
NTTIME acct_expiry;
|
||||
NTTIME last_password_change;
|
||||
NTTIME allow_password_change;
|
||||
NTTIME force_password_change;
|
||||
|
||||
uint16_t logon_count;
|
||||
uint16_t bad_password_count;
|
||||
|
||||
uint32_t acct_flags;
|
||||
|
||||
BOOL authenticated;
|
||||
};
|
||||
|
||||
struct auth_session_info {
|
||||
struct security_token *security_token;
|
||||
struct auth_serversupplied_info *server_info;
|
||||
DATA_BLOB session_key;
|
||||
struct cli_credentials *credentials;
|
||||
};
|
||||
|
||||
struct auth_method_context;
|
||||
struct auth_check_password_request;
|
||||
|
||||
struct auth_operations {
|
||||
const char *name;
|
||||
|
||||
/* If you are using this interface, then you are probably
|
||||
* getting something wrong. This interface is only for
|
||||
* security=server, and makes a number of compromises to allow
|
||||
* that. It is not compatible with being a PDC. */
|
||||
|
||||
NTSTATUS (*get_challenge)(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx, DATA_BLOB *challenge);
|
||||
|
||||
/* Given the user supplied info, check if this backend want to handle the password checking */
|
||||
|
||||
NTSTATUS (*want_check)(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info);
|
||||
|
||||
/* Given the user supplied info, check a password */
|
||||
|
||||
NTSTATUS (*check_password)(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info);
|
||||
};
|
||||
|
||||
struct auth_method_context {
|
||||
struct auth_method_context *prev, *next;
|
||||
struct auth_context *auth_ctx;
|
||||
const struct auth_operations *ops;
|
||||
int depth;
|
||||
void *private_data;
|
||||
};
|
||||
|
||||
struct auth_context {
|
||||
struct {
|
||||
/* Who set this up in the first place? */
|
||||
const char *set_by;
|
||||
|
||||
BOOL may_be_modified;
|
||||
|
||||
DATA_BLOB data;
|
||||
} challenge;
|
||||
|
||||
/* methods, in the order they should be called */
|
||||
struct auth_method_context *methods;
|
||||
|
||||
/* the event context to use for calls that can block */
|
||||
struct event_context *event_ctx;
|
||||
|
||||
/* the messaging context which can be used by backends */
|
||||
struct messaging_context *msg_ctx;
|
||||
};
|
||||
|
||||
/* this structure is used by backends to determine the size of some critical types */
|
||||
struct auth_critical_sizes {
|
||||
int interface_version;
|
||||
int sizeof_auth_operations;
|
||||
int sizeof_auth_methods;
|
||||
int sizeof_auth_context;
|
||||
int sizeof_auth_usersupplied_info;
|
||||
int sizeof_auth_serversupplied_info;
|
||||
};
|
||||
|
||||
NTSTATUS encrypt_user_info(TALLOC_CTX *mem_ctx, struct auth_context *auth_context,
|
||||
enum auth_password_state to_state,
|
||||
const struct auth_usersupplied_info *user_info_in,
|
||||
const struct auth_usersupplied_info **user_info_encrypted);
|
||||
|
||||
#include "auth/auth_proto.h"
|
||||
|
||||
#endif /* _SMBAUTH_H_ */
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Anonymous Authentification
|
||||
|
||||
Copyright (C) Stefan Metzmacher 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
|
||||
/**
|
||||
* Return a anonymous logon for anonymous users (username = "")
|
||||
*
|
||||
* Typically used as the first module in the auth chain, this allows
|
||||
* anonymou logons to be dealt with in one place. Non-anonymou logons 'fail'
|
||||
* and pass onto the next module.
|
||||
**/
|
||||
static NTSTATUS anonymous_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
if (user_info->client.account_name && *user_info->client.account_name) {
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a anonymous logon for anonymous users (username = "")
|
||||
*
|
||||
* Typically used as the first module in the auth chain, this allows
|
||||
* anonymou logons to be dealt with in one place. Non-anonymou logons 'fail'
|
||||
* and pass onto the next module.
|
||||
**/
|
||||
static NTSTATUS anonymous_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
return auth_anonymous_server_info(mem_ctx, _server_info);
|
||||
}
|
||||
|
||||
static struct auth_operations anonymous_auth_ops = {
|
||||
.name = "anonymous",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = anonymous_want_check,
|
||||
.check_password = anonymous_check_password
|
||||
};
|
||||
|
||||
NTSTATUS auth_anonymous_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
ret = auth_register(&anonymous_auth_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'anonymous' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Generic authentication types
|
||||
Copyright (C) Andrew Bartlett 2001-2002
|
||||
Copyright (C) Jelmer Vernooij 2002
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "librpc/gen_ndr/ndr_samr.h"
|
||||
|
||||
static NTSTATUS name_to_ntstatus_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error based on username
|
||||
*
|
||||
* This function allows the testing of obsure errors, as well as the generation
|
||||
* of NT_STATUS -> DOS error mapping tables.
|
||||
*
|
||||
* This module is of no value to end-users.
|
||||
*
|
||||
* The password is ignored.
|
||||
*
|
||||
* @return An NTSTATUS value based on the username
|
||||
**/
|
||||
|
||||
static NTSTATUS name_to_ntstatus_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_serversupplied_info *server_info;
|
||||
uint32_t error_num;
|
||||
const char *user;
|
||||
|
||||
user = user_info->client.account_name;
|
||||
|
||||
if (strncasecmp("NT_STATUS", user, strlen("NT_STATUS")) == 0) {
|
||||
nt_status = nt_status_string_to_code(user);
|
||||
} else {
|
||||
error_num = strtoul(user, NULL, 16);
|
||||
DEBUG(5,("name_to_ntstatus_check_password: Error for user %s was 0x%08X\n", user, error_num));
|
||||
nt_status = NT_STATUS(error_num);
|
||||
}
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
server_info = talloc(mem_ctx, struct auth_serversupplied_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info);
|
||||
|
||||
server_info->account_sid = dom_sid_parse_talloc(server_info, SID_NT_ANONYMOUS);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_sid);
|
||||
|
||||
/* is this correct? */
|
||||
server_info->primary_group_sid = dom_sid_parse_talloc(server_info, SID_BUILTIN_GUESTS);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->primary_group_sid);
|
||||
|
||||
server_info->n_domain_groups = 0;
|
||||
server_info->domain_groups = NULL;
|
||||
|
||||
/* annoying, but the Anonymous really does have a session key,
|
||||
and it is all zeros! */
|
||||
server_info->user_session_key = data_blob_talloc(server_info, NULL, 16);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->user_session_key.data);
|
||||
|
||||
server_info->lm_session_key = data_blob_talloc(server_info, NULL, 16);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->lm_session_key.data);
|
||||
|
||||
data_blob_clear(&server_info->user_session_key);
|
||||
data_blob_clear(&server_info->lm_session_key);
|
||||
|
||||
server_info->account_name = talloc_asprintf(server_info, "NAME TO NTSTATUS %s ANONYMOUS LOGON", user);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_name);
|
||||
|
||||
server_info->domain_name = talloc_strdup(server_info, "NT AUTHORITY");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_name);
|
||||
|
||||
server_info->full_name = talloc_asprintf(server_info, "NAME TO NTSTATUS %s Anonymous Logon", user);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->full_name);
|
||||
|
||||
server_info->logon_script = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_script);
|
||||
|
||||
server_info->profile_path = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->profile_path);
|
||||
|
||||
server_info->home_directory = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_directory);
|
||||
|
||||
server_info->home_drive = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_drive);
|
||||
|
||||
server_info->last_logon = 0;
|
||||
server_info->last_logoff = 0;
|
||||
server_info->acct_expiry = 0;
|
||||
server_info->last_password_change = 0;
|
||||
server_info->allow_password_change = 0;
|
||||
server_info->force_password_change = 0;
|
||||
|
||||
server_info->logon_count = 0;
|
||||
server_info->bad_password_count = 0;
|
||||
|
||||
server_info->acct_flags = ACB_NORMAL;
|
||||
|
||||
server_info->authenticated = False;
|
||||
|
||||
*_server_info = server_info;
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
static struct auth_operations name_to_ntstatus_auth_ops = {
|
||||
.name = "name_to_ntstatus",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = name_to_ntstatus_want_check,
|
||||
.check_password = name_to_ntstatus_check_password
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a 'fixed' challenge instead of a variable one.
|
||||
*
|
||||
* The idea of this function is to make packet snifs consistant
|
||||
* with a fixed challenge, so as to aid debugging.
|
||||
*
|
||||
* This module is of no value to end-users.
|
||||
*
|
||||
* This module does not actually authenticate the user, but
|
||||
* just pretenteds to need a specified challenge.
|
||||
* This module removes *all* security from the challenge-response system
|
||||
*
|
||||
* @return NT_STATUS_UNSUCCESSFUL
|
||||
**/
|
||||
static NTSTATUS fixed_challenge_get_challenge(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx, DATA_BLOB *_blob)
|
||||
{
|
||||
DATA_BLOB blob;
|
||||
const char *challenge = "I am a teapot";
|
||||
|
||||
blob = data_blob_talloc(mem_ctx, challenge, 8);
|
||||
NT_STATUS_HAVE_NO_MEMORY(blob.data);
|
||||
|
||||
*_blob = blob;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS fixed_challenge_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
/* don't handle any users */
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static NTSTATUS fixed_challenge_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
/* don't handle any users */
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
static struct auth_operations fixed_challenge_auth_ops = {
|
||||
.name = "fixed_challenge",
|
||||
.get_challenge = fixed_challenge_get_challenge,
|
||||
.want_check = fixed_challenge_want_check,
|
||||
.check_password = fixed_challenge_check_password
|
||||
};
|
||||
|
||||
NTSTATUS auth_developer_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
ret = auth_register(&name_to_ntstatus_auth_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'name_to_ntstatus' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = auth_register(&fixed_challenge_auth_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'fixed_challenge' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Password and authentication handling
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004
|
||||
Copyright (C) Gerald Carter 2003
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "librpc/gen_ndr/ndr_netlogon.h"
|
||||
#include "system/time.h"
|
||||
#include "db_wrap.h"
|
||||
#include "lib/ldb/include/ldb.h"
|
||||
#include "auth/auth.h"
|
||||
#include "auth/auth_sam.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "libcli/ldap/ldap.h"
|
||||
|
||||
extern const char *user_attrs[];
|
||||
extern const char *domain_ref_attrs[];
|
||||
|
||||
/****************************************************************************
|
||||
Look for the specified user in the sam, return ldb result structures
|
||||
****************************************************************************/
|
||||
|
||||
static NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
|
||||
const char *account_name,
|
||||
const char *domain_name,
|
||||
struct ldb_message ***ret_msgs,
|
||||
struct ldb_message ***ret_msgs_domain_ref)
|
||||
{
|
||||
struct ldb_message **msgs_tmp;
|
||||
struct ldb_message **msgs;
|
||||
struct ldb_message **msgs_domain_ref;
|
||||
struct ldb_dn *partitions_basedn = samdb_partitions_dn(sam_ctx, mem_ctx);
|
||||
|
||||
int ret;
|
||||
int ret_domain;
|
||||
|
||||
struct ldb_dn *domain_dn = NULL;
|
||||
|
||||
if (domain_name) {
|
||||
char *escaped_domain = ldb_binary_encode_string(mem_ctx, domain_name);
|
||||
/* find the domain's DN */
|
||||
ret_domain = gendb_search(sam_ctx, mem_ctx, partitions_basedn, &msgs_domain_ref, domain_ref_attrs,
|
||||
"(&(&(|(&(dnsRoot=%s)(nETBIOSName=*))(nETBIOSName=%s))(objectclass=crossRef))(ncName=*))",
|
||||
escaped_domain, escaped_domain);
|
||||
if (ret_domain == -1) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
if (ret_domain == 0) {
|
||||
DEBUG(3,("sam_search_user: Couldn't find domain [%s] in samdb.\n",
|
||||
domain_name));
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
if (ret_domain > 1) {
|
||||
DEBUG(0,("Found %d records matching domain [%s]\n",
|
||||
ret_domain, domain_name));
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
domain_dn = samdb_result_dn(sam_ctx, mem_ctx, msgs_domain_ref[0], "nCName", NULL);
|
||||
}
|
||||
|
||||
/* pull the user attributes */
|
||||
ret = gendb_search(sam_ctx, mem_ctx, domain_dn, &msgs, user_attrs,
|
||||
"(&(sAMAccountName=%s)(objectclass=user))",
|
||||
ldb_binary_encode_string(mem_ctx, account_name));
|
||||
if (ret == -1) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
DEBUG(3,("sam_search_user: Couldn't find user [%s\\%s] in samdb, under %s\n",
|
||||
domain_name, account_name, ldb_dn_get_linearized(domain_dn)));
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
if (ret > 1) {
|
||||
DEBUG(0,("Found %d records matching user [%s]\n", ret, account_name));
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
if (!domain_name) {
|
||||
struct dom_sid *domain_sid;
|
||||
|
||||
domain_sid = samdb_result_sid_prefix(mem_ctx, msgs[0], "objectSid");
|
||||
if (!domain_sid) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
/* find the domain's DN */
|
||||
ret = gendb_search(sam_ctx, mem_ctx, NULL, &msgs_tmp, NULL,
|
||||
"(&(objectSid=%s)(objectClass=domain))",
|
||||
ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
|
||||
if (ret == -1) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
DEBUG(3,("check_sam_security: Couldn't find domain_sid [%s] in passdb file.\n",
|
||||
dom_sid_string(mem_ctx, domain_sid)));
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
if (ret > 1) {
|
||||
DEBUG(0,("Found %d records matching domain_sid [%s]\n",
|
||||
ret, dom_sid_string(mem_ctx, domain_sid)));
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
ret_domain = gendb_search(sam_ctx, mem_ctx, partitions_basedn, &msgs_domain_ref, domain_ref_attrs,
|
||||
"(nCName=%s)", ldb_dn_alloc_linearized(msgs_tmp, msgs_tmp[0]->dn));
|
||||
|
||||
if (ret_domain == -1) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
if (ret_domain == 0) {
|
||||
DEBUG(3,("check_sam_security: Couldn't find domain [%s] in passdb file.\n",
|
||||
ldb_dn_get_linearized(msgs_tmp[0]->dn)));
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
if (ret_domain > 1) {
|
||||
DEBUG(0,("Found %d records matching domain [%s]\n",
|
||||
ret_domain, ldb_dn_get_linearized(msgs_tmp[0]->dn)));
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*ret_msgs = msgs;
|
||||
*ret_msgs_domain_ref = msgs_domain_ref;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Do a specific test for an smb password being correct, given a smb_password and
|
||||
the lanman and NT responses.
|
||||
****************************************************************************/
|
||||
static NTSTATUS authsam_password_ok(struct auth_context *auth_context,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
uint16_t acct_flags,
|
||||
const struct samr_Password *lm_pwd,
|
||||
const struct samr_Password *nt_pwd,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
DATA_BLOB *user_sess_key,
|
||||
DATA_BLOB *lm_sess_key)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
if (acct_flags & ACB_PWNOTREQ) {
|
||||
if (lp_null_passwords()) {
|
||||
DEBUG(3,("Account for user '%s' has no password and null passwords are allowed.\n",
|
||||
user_info->mapped.account_name));
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
DEBUG(3,("Account for user '%s' has no password and null passwords are NOT allowed.\n",
|
||||
user_info->mapped.account_name));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
switch (user_info->password_state) {
|
||||
case AUTH_PASSWORD_PLAIN:
|
||||
{
|
||||
const struct auth_usersupplied_info *user_info_temp;
|
||||
status = encrypt_user_info(mem_ctx, auth_context,
|
||||
AUTH_PASSWORD_HASH,
|
||||
user_info, &user_info_temp);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(1, ("Failed to convert plaintext password to password HASH: %s\n", nt_errstr(status)));
|
||||
return status;
|
||||
}
|
||||
user_info = user_info_temp;
|
||||
|
||||
/*fall through*/
|
||||
}
|
||||
case AUTH_PASSWORD_HASH:
|
||||
*lm_sess_key = data_blob(NULL, 0);
|
||||
*user_sess_key = data_blob(NULL, 0);
|
||||
status = hash_password_check(mem_ctx,
|
||||
user_info->password.hash.lanman,
|
||||
user_info->password.hash.nt,
|
||||
user_info->mapped.account_name,
|
||||
lm_pwd, nt_pwd);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
break;
|
||||
|
||||
case AUTH_PASSWORD_RESPONSE:
|
||||
status = ntlm_password_check(mem_ctx, user_info->logon_parameters,
|
||||
&auth_context->challenge.data,
|
||||
&user_info->password.response.lanman,
|
||||
&user_info->password.response.nt,
|
||||
user_info->mapped.account_name,
|
||||
user_info->client.account_name,
|
||||
user_info->client.domain_name,
|
||||
lm_pwd, nt_pwd,
|
||||
user_sess_key, lm_sess_key);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (user_sess_key && user_sess_key->data) {
|
||||
talloc_steal(auth_context, user_sess_key->data);
|
||||
}
|
||||
if (lm_sess_key && lm_sess_key->data) {
|
||||
talloc_steal(auth_context, lm_sess_key->data);
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static NTSTATUS authsam_authenticate(struct auth_context *auth_context,
|
||||
TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
|
||||
struct ldb_message **msgs,
|
||||
struct ldb_message **msgs_domain_ref,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
DATA_BLOB *user_sess_key, DATA_BLOB *lm_sess_key)
|
||||
{
|
||||
struct samr_Password *lm_pwd, *nt_pwd;
|
||||
NTSTATUS nt_status;
|
||||
uint16_t acct_flags = samdb_result_acct_flags(msgs[0], "userAccountControl");
|
||||
|
||||
/* Quit if the account was locked out. */
|
||||
if (acct_flags & ACB_AUTOLOCK) {
|
||||
DEBUG(3,("check_sam_security: Account for user %s was locked out.\n",
|
||||
user_info->mapped.account_name));
|
||||
return NT_STATUS_ACCOUNT_LOCKED_OUT;
|
||||
}
|
||||
|
||||
/* You can only do an interactive login to normal accounts */
|
||||
if (user_info->flags & USER_INFO_INTERACTIVE_LOGON) {
|
||||
if (!(acct_flags & ACB_NORMAL)) {
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
}
|
||||
|
||||
nt_status = samdb_result_passwords(mem_ctx, msgs[0], &lm_pwd, &nt_pwd);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
nt_status = authsam_password_ok(auth_context, mem_ctx,
|
||||
acct_flags, lm_pwd, nt_pwd,
|
||||
user_info, user_sess_key, lm_sess_key);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
nt_status = authsam_account_ok(mem_ctx, sam_ctx,
|
||||
user_info->logon_parameters,
|
||||
msgs[0],
|
||||
msgs_domain_ref[0],
|
||||
user_info->workstation_name,
|
||||
user_info->mapped.account_name);
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static NTSTATUS authsam_check_password_internals(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const char *domain,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
const char *account_name = user_info->mapped.account_name;
|
||||
struct ldb_message **msgs;
|
||||
struct ldb_message **domain_ref_msgs;
|
||||
struct ldb_context *sam_ctx;
|
||||
DATA_BLOB user_sess_key, lm_sess_key;
|
||||
TALLOC_CTX *tmp_ctx;
|
||||
|
||||
if (!account_name || !*account_name) {
|
||||
/* 'not for me' */
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
tmp_ctx = talloc_new(mem_ctx);
|
||||
if (!tmp_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
sam_ctx = samdb_connect(tmp_ctx, system_session(mem_ctx));
|
||||
if (sam_ctx == NULL) {
|
||||
talloc_free(tmp_ctx);
|
||||
return NT_STATUS_INVALID_SYSTEM_SERVICE;
|
||||
}
|
||||
|
||||
nt_status = authsam_search_account(tmp_ctx, sam_ctx, account_name, domain, &msgs, &domain_ref_msgs);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = authsam_authenticate(ctx->auth_ctx, tmp_ctx, sam_ctx, msgs, domain_ref_msgs, user_info,
|
||||
&user_sess_key, &lm_sess_key);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = authsam_make_server_info(tmp_ctx, sam_ctx, msgs[0], domain_ref_msgs[0],
|
||||
user_sess_key, lm_sess_key,
|
||||
server_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
talloc_steal(mem_ctx, *server_info);
|
||||
talloc_free(tmp_ctx);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS authsam_ignoredomain_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS authsam_ignoredomain_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
return authsam_check_password_internals(ctx, mem_ctx, NULL, user_info, server_info);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Check SAM security (above) but with a few extra checks.
|
||||
****************************************************************************/
|
||||
static NTSTATUS authsam_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
BOOL is_local_name, is_my_domain;
|
||||
|
||||
if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
is_local_name = is_myname(user_info->mapped.domain_name);
|
||||
is_my_domain = strequal(user_info->mapped.domain_name, lp_workgroup());
|
||||
|
||||
/* check whether or not we service this domain/workgroup name */
|
||||
switch (lp_server_role()) {
|
||||
case ROLE_STANDALONE:
|
||||
return NT_STATUS_OK;
|
||||
|
||||
case ROLE_DOMAIN_MEMBER:
|
||||
if (!is_local_name) {
|
||||
DEBUG(6,("authsam_check_password: %s is not one of my local names (DOMAIN_MEMBER)\n",
|
||||
user_info->mapped.domain_name));
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
|
||||
case ROLE_DOMAIN_PDC:
|
||||
case ROLE_DOMAIN_BDC:
|
||||
if (!is_local_name && !is_my_domain) {
|
||||
DEBUG(6,("authsam_check_password: %s is not one of my local names or domain name (DC)\n",
|
||||
user_info->mapped.domain_name));
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
DEBUG(6,("authsam_check_password: lp_server_role() has an undefined value\n"));
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Check SAM security (above) but with a few extra checks.
|
||||
****************************************************************************/
|
||||
static NTSTATUS authsam_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
const char *domain;
|
||||
|
||||
/* check whether or not we service this domain/workgroup name */
|
||||
switch (lp_server_role()) {
|
||||
case ROLE_STANDALONE:
|
||||
case ROLE_DOMAIN_MEMBER:
|
||||
domain = lp_netbios_name();
|
||||
break;
|
||||
|
||||
case ROLE_DOMAIN_PDC:
|
||||
case ROLE_DOMAIN_BDC:
|
||||
domain = lp_workgroup();
|
||||
break;
|
||||
|
||||
default:
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
return authsam_check_password_internals(ctx, mem_ctx, domain, user_info, server_info);
|
||||
}
|
||||
|
||||
static const struct auth_operations sam_ignoredomain_ops = {
|
||||
.name = "sam_ignoredomain",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = authsam_ignoredomain_want_check,
|
||||
.check_password = authsam_ignoredomain_check_password
|
||||
};
|
||||
|
||||
static const struct auth_operations sam_ops = {
|
||||
.name = "sam",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = authsam_want_check,
|
||||
.check_password = authsam_check_password
|
||||
};
|
||||
|
||||
NTSTATUS auth_sam_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
ret = auth_register(&sam_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'sam' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = auth_register(&sam_ignoredomain_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'sam_ignoredomain' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Convert a server info struct into the form for PAC and NETLOGON replies
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
|
||||
Copyright (C) Stefan Metzmacher <metze@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "librpc/gen_ndr/ndr_netlogon.h"
|
||||
|
||||
NTSTATUS auth_convert_server_info_sambaseinfo(TALLOC_CTX *mem_ctx,
|
||||
struct auth_serversupplied_info *server_info,
|
||||
struct netr_SamBaseInfo **_sam)
|
||||
{
|
||||
struct netr_SamBaseInfo *sam = talloc_zero(mem_ctx, struct netr_SamBaseInfo);
|
||||
NT_STATUS_HAVE_NO_MEMORY(sam);
|
||||
|
||||
sam->domain_sid = dom_sid_dup(mem_ctx, server_info->account_sid);
|
||||
NT_STATUS_HAVE_NO_MEMORY(sam->domain_sid);
|
||||
sam->domain_sid->num_auths--;
|
||||
|
||||
sam->last_logon = server_info->last_logon;
|
||||
sam->last_logoff = server_info->last_logoff;
|
||||
sam->acct_expiry = server_info->acct_expiry;
|
||||
sam->last_password_change = server_info->last_password_change;
|
||||
sam->allow_password_change = server_info->allow_password_change;
|
||||
sam->force_password_change = server_info->force_password_change;
|
||||
|
||||
sam->account_name.string = server_info->account_name;
|
||||
sam->full_name.string = server_info->full_name;
|
||||
sam->logon_script.string = server_info->logon_script;
|
||||
sam->profile_path.string = server_info->profile_path;
|
||||
sam->home_directory.string = server_info->home_directory;
|
||||
sam->home_drive.string = server_info->home_drive;
|
||||
|
||||
sam->logon_count = server_info->logon_count;
|
||||
sam->bad_password_count = sam->bad_password_count;
|
||||
sam->rid = server_info->account_sid->sub_auths[server_info->account_sid->num_auths-1];
|
||||
sam->primary_gid = server_info->primary_group_sid->sub_auths[server_info->primary_group_sid->num_auths-1];
|
||||
|
||||
sam->groups.count = 0;
|
||||
sam->groups.rids = NULL;
|
||||
|
||||
if (server_info->n_domain_groups > 0) {
|
||||
int i;
|
||||
sam->groups.rids = talloc_array(sam, struct samr_RidWithAttribute,
|
||||
server_info->n_domain_groups);
|
||||
|
||||
if (sam->groups.rids == NULL)
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
|
||||
for (i=0; i<server_info->n_domain_groups; i++) {
|
||||
struct dom_sid *group_sid = server_info->domain_groups[i];
|
||||
if (!dom_sid_in_domain(sam->domain_sid, group_sid)) {
|
||||
/* We handle this elsewhere */
|
||||
continue;
|
||||
}
|
||||
sam->groups.rids[sam->groups.count].rid =
|
||||
group_sid->sub_auths[group_sid->num_auths-1];
|
||||
|
||||
sam->groups.rids[sam->groups.count].attributes =
|
||||
SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED;
|
||||
sam->groups.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
sam->user_flags = 0; /* TODO: w2k3 uses 0x120. We know 0x20
|
||||
* as extra sids (PAC doc) but what is
|
||||
* 0x100? */
|
||||
sam->acct_flags = server_info->acct_flags;
|
||||
sam->logon_server.string = server_info->logon_server;
|
||||
sam->domain.string = server_info->domain_name;
|
||||
|
||||
ZERO_STRUCT(sam->unknown);
|
||||
|
||||
ZERO_STRUCT(sam->key);
|
||||
if (server_info->user_session_key.length == sizeof(sam->key.key)) {
|
||||
memcpy(sam->key.key, server_info->user_session_key.data, sizeof(sam->key.key));
|
||||
}
|
||||
|
||||
ZERO_STRUCT(sam->LMSessKey);
|
||||
if (server_info->lm_session_key.length == sizeof(sam->LMSessKey.key)) {
|
||||
memcpy(sam->LMSessKey.key, server_info->lm_session_key.data,
|
||||
sizeof(sam->LMSessKey.key));
|
||||
}
|
||||
|
||||
*_sam = sam;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS auth_convert_server_info_saminfo3(TALLOC_CTX *mem_ctx,
|
||||
struct auth_serversupplied_info *server_info,
|
||||
struct netr_SamInfo3 **_sam3)
|
||||
{
|
||||
struct netr_SamBaseInfo *sam;
|
||||
struct netr_SamInfo3 *sam3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
|
||||
NTSTATUS status;
|
||||
int i;
|
||||
NT_STATUS_HAVE_NO_MEMORY(sam3);
|
||||
|
||||
status = auth_convert_server_info_sambaseinfo(mem_ctx, server_info, &sam);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
sam3->base = *sam;
|
||||
sam3->sidcount = 0;
|
||||
sam3->sids = NULL;
|
||||
|
||||
|
||||
sam3->sids = talloc_array(sam, struct netr_SidAttr,
|
||||
server_info->n_domain_groups);
|
||||
NT_STATUS_HAVE_NO_MEMORY(sam3->sids);
|
||||
|
||||
for (i=0; i<server_info->n_domain_groups; i++) {
|
||||
if (dom_sid_in_domain(sam->domain_sid, server_info->domain_groups[i])) {
|
||||
continue;
|
||||
}
|
||||
sam3->sids[sam3->sidcount].sid = talloc_reference(sam3->sids,server_info->domain_groups[i]);
|
||||
sam3->sids[sam3->sidcount].attribute =
|
||||
SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED;
|
||||
sam3->sidcount += 1;
|
||||
}
|
||||
if (sam3->sidcount) {
|
||||
sam3->base.user_flags |= NETLOGON_EXTRA_SIDS;
|
||||
} else {
|
||||
sam3->sids = NULL;
|
||||
}
|
||||
*_sam3 = sam3;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Authenticate to a remote server
|
||||
Copyright (C) Andrew Tridgell 1992-1998
|
||||
Copyright (C) Andrew Bartlett 2001
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
|
||||
/****************************************************************************
|
||||
Support for server level security.
|
||||
****************************************************************************/
|
||||
|
||||
static struct smbcli_state *server_cryptkey(TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct smbcli_state *cli = NULL;
|
||||
fstring desthost;
|
||||
struct ipv4_addr dest_ip;
|
||||
const char *p;
|
||||
char *pserver;
|
||||
BOOL connected_ok = False;
|
||||
|
||||
if (!(cli = smbcli_initialise(cli)))
|
||||
return NULL;
|
||||
|
||||
/* security = server just can't function with spnego */
|
||||
cli->use_spnego = False;
|
||||
|
||||
pserver = talloc_strdup(mem_ctx, lp_passwordserver());
|
||||
p = pserver;
|
||||
|
||||
while(next_token( &p, desthost, LIST_SEP, sizeof(desthost))) {
|
||||
strupper(desthost);
|
||||
|
||||
if(!resolve_name( desthost, &dest_ip, 0x20)) {
|
||||
DEBUG(1,("server_cryptkey: Can't resolve address for %s\n",desthost));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ismyip(dest_ip)) {
|
||||
DEBUG(1,("Password server loop - disabling password server %s\n",desthost));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* we use a mutex to prevent two connections at once - when a
|
||||
Win2k PDC get two connections where one hasn't completed a
|
||||
session setup yet it will send a TCP reset to the first
|
||||
connection (tridge) */
|
||||
|
||||
if (!grab_server_mutex(desthost)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (smbcli_connect(cli, desthost, &dest_ip)) {
|
||||
DEBUG(3,("connected to password server %s\n",desthost));
|
||||
connected_ok = True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected_ok) {
|
||||
release_server_mutex();
|
||||
DEBUG(0,("password server not available\n"));
|
||||
talloc_free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!attempt_netbios_session_request(cli, lp_netbios_name(),
|
||||
desthost, &dest_ip)) {
|
||||
release_server_mutex();
|
||||
DEBUG(1,("password server fails session request\n"));
|
||||
talloc_free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strequal(desthost,myhostname(mem_ctx))) {
|
||||
exit_server("Password server loop!");
|
||||
}
|
||||
|
||||
DEBUG(3,("got session\n"));
|
||||
|
||||
if (!smbcli_negprot(cli)) {
|
||||
DEBUG(1,("%s rejected the negprot\n",desthost));
|
||||
release_server_mutex();
|
||||
talloc_free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (cli->protocol < PROTOCOL_LANMAN2 ||
|
||||
!(cli->sec_mode & NEGOTIATE_SECURITY_USER_LEVEL)) {
|
||||
DEBUG(1,("%s isn't in user level security mode\n",desthost));
|
||||
release_server_mutex();
|
||||
talloc_free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the first session setup done quickly, to avoid silly
|
||||
Win2k bugs. (The next connection to the server will kill
|
||||
this one...
|
||||
*/
|
||||
|
||||
if (!smbcli_session_setup(cli, "", "", 0, "", 0,
|
||||
"")) {
|
||||
DEBUG(0,("%s rejected the initial session setup (%s)\n",
|
||||
desthost, smbcli_errstr(cli)));
|
||||
release_server_mutex();
|
||||
talloc_free(cli);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
release_server_mutex();
|
||||
|
||||
DEBUG(3,("password server OK\n"));
|
||||
|
||||
return cli;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Clean up our allocated cli.
|
||||
****************************************************************************/
|
||||
|
||||
static void free_server_private_data(void **private_data_pointer)
|
||||
{
|
||||
struct smbcli_state **cli = (struct smbcli_state **)private_data_pointer;
|
||||
if (*cli && (*cli)->initialised) {
|
||||
talloc_free(*cli);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Get the challenge out of a password server.
|
||||
****************************************************************************/
|
||||
|
||||
static DATA_BLOB auth_get_challenge_server(const struct auth_context *auth_context,
|
||||
void **my_private_data,
|
||||
TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct smbcli_state *cli = server_cryptkey(mem_ctx);
|
||||
|
||||
if (cli) {
|
||||
DEBUG(3,("using password server validation\n"));
|
||||
|
||||
if ((cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) == 0) {
|
||||
/* We can't work with unencrypted password servers
|
||||
unless 'encrypt passwords = no' */
|
||||
DEBUG(5,("make_auth_info_server: Server is unencrypted, no challenge available..\n"));
|
||||
|
||||
/* However, it is still a perfectly fine connection
|
||||
to pass that unencrypted password over */
|
||||
*my_private_data = (void *)cli;
|
||||
return data_blob(NULL, 0);
|
||||
|
||||
} else if (cli->secblob.length < 8) {
|
||||
/* We can't do much if we don't get a full challenge */
|
||||
DEBUG(2,("make_auth_info_server: Didn't receive a full challenge from server\n"));
|
||||
talloc_free(cli);
|
||||
return data_blob(NULL, 0);
|
||||
}
|
||||
|
||||
*my_private_data = (void *)cli;
|
||||
|
||||
/* The return must be allocated on the caller's mem_ctx, as our own will be
|
||||
destoyed just after the call. */
|
||||
return data_blob_talloc(auth_context->mem_ctx, cli->secblob.data,8);
|
||||
} else {
|
||||
return data_blob(NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
Check for a valid username and password in security=server mode.
|
||||
- Validate a password with the password server.
|
||||
****************************************************************************/
|
||||
|
||||
static NTSTATUS check_smbserver_security(const struct auth_context *auth_context,
|
||||
void *my_private_data,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const auth_usersupplied_info *user_info,
|
||||
auth_serversupplied_info **server_info)
|
||||
{
|
||||
struct smbcli_state *cli;
|
||||
static uint8_t badpass[24];
|
||||
static fstring baduser;
|
||||
static BOOL tested_password_server = False;
|
||||
static BOOL bad_password_server = False;
|
||||
NTSTATUS nt_status = NT_STATUS_LOGON_FAILURE;
|
||||
BOOL locally_made_cli = False;
|
||||
|
||||
/*
|
||||
* Check that the requested domain is not our own machine name.
|
||||
* If it is, we should never check the PDC here, we use our own local
|
||||
* password file.
|
||||
*/
|
||||
|
||||
if(is_myname(user_info->domain.str)) {
|
||||
DEBUG(3,("check_smbserver_security: Requested domain was for this machine.\n"));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
|
||||
cli = my_private_data;
|
||||
|
||||
if (cli) {
|
||||
} else {
|
||||
cli = server_cryptkey(mem_ctx);
|
||||
locally_made_cli = True;
|
||||
}
|
||||
|
||||
if (!cli || !cli->initialised) {
|
||||
DEBUG(1,("password server is not connected (cli not initilised)\n"));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
|
||||
if ((cli->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) == 0) {
|
||||
if (user_info->encrypted) {
|
||||
DEBUG(1,("password server %s is plaintext, but we are encrypted. This just can't work :-(\n", cli->desthost));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
} else {
|
||||
if (memcmp(cli->secblob.data, auth_context->challenge.data, 8) != 0) {
|
||||
DEBUG(1,("the challenge that the password server (%s) supplied us is not the one we gave our client. This just can't work :-(\n", cli->desthost));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if(badpass[0] == 0)
|
||||
memset(badpass, 0x1f, sizeof(badpass));
|
||||
|
||||
if((user_info->nt_resp.length == sizeof(badpass)) &&
|
||||
!memcmp(badpass, user_info->nt_resp.data, sizeof(badpass))) {
|
||||
/*
|
||||
* Very unlikely, our random bad password is the same as the users
|
||||
* password.
|
||||
*/
|
||||
memset(badpass, badpass[0]+1, sizeof(badpass));
|
||||
}
|
||||
|
||||
if(baduser[0] == 0) {
|
||||
fstrcpy(baduser, INVALID_USER_PREFIX);
|
||||
fstrcat(baduser, lp_netbios_name());
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt a session setup with a totally incorrect password.
|
||||
* If this succeeds with the guest bit *NOT* set then the password
|
||||
* server is broken and is not correctly setting the guest bit. We
|
||||
* need to detect this as some versions of NT4.x are broken. JRA.
|
||||
*/
|
||||
|
||||
/* I sure as hell hope that there aren't servers out there that take
|
||||
* NTLMv2 and have this bug, as we don't test for that...
|
||||
* - abartlet@samba.org
|
||||
*/
|
||||
|
||||
if ((!tested_password_server) && (lp_paranoid_server_security())) {
|
||||
if (smbcli_session_setup(cli, baduser, (char *)badpass, sizeof(badpass),
|
||||
(char *)badpass, sizeof(badpass), user_info->domain.str)) {
|
||||
|
||||
/*
|
||||
* We connected to the password server so we
|
||||
* can say we've tested it.
|
||||
*/
|
||||
tested_password_server = True;
|
||||
|
||||
if ((SVAL(cli->inbuf,smb_vwv2) & 1) == 0) {
|
||||
DEBUG(0,("server_validate: password server %s allows users as non-guest \
|
||||
with a bad password.\n", cli->desthost));
|
||||
DEBUG(0,("server_validate: This is broken (and insecure) behaviour. Please do not \
|
||||
use this machine as the password server.\n"));
|
||||
smbcli_ulogoff(cli);
|
||||
|
||||
/*
|
||||
* Password server has the bug.
|
||||
*/
|
||||
bad_password_server = True;
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
smbcli_ulogoff(cli);
|
||||
}
|
||||
} else {
|
||||
|
||||
/*
|
||||
* We have already tested the password server.
|
||||
* Fail immediately if it has the bug.
|
||||
*/
|
||||
|
||||
if(bad_password_server) {
|
||||
DEBUG(0,("server_validate: [1] password server %s allows users as non-guest \
|
||||
with a bad password.\n", cli->desthost));
|
||||
DEBUG(0,("server_validate: [1] This is broken (and insecure) behaviour. Please do not \
|
||||
use this machine as the password server.\n"));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we know the password server will correctly set the guest bit, or is
|
||||
* not guest enabled, we can try with the real password.
|
||||
*/
|
||||
|
||||
if (!user_info->encrypted) {
|
||||
/* Plaintext available */
|
||||
if (!smbcli_session_setup(cli, user_info->smb_name.str,
|
||||
(char *)user_info->plaintext_password.data,
|
||||
user_info->plaintext_password.length,
|
||||
NULL, 0,
|
||||
user_info->domain.str)) {
|
||||
DEBUG(1,("password server %s rejected the password\n", cli->desthost));
|
||||
/* Make this smbcli_nt_error() when the conversion is in */
|
||||
nt_status = smbcli_nt_error(cli);
|
||||
} else {
|
||||
nt_status = NT_STATUS_OK;
|
||||
}
|
||||
} else {
|
||||
if (!smbcli_session_setup(cli, user_info->smb_name.str,
|
||||
(char *)user_info->lm_resp.data,
|
||||
user_info->lm_resp.length,
|
||||
(char *)user_info->nt_resp.data,
|
||||
user_info->nt_resp.length,
|
||||
user_info->domain.str)) {
|
||||
DEBUG(1,("password server %s rejected the password\n", cli->desthost));
|
||||
/* Make this smbcli_nt_error() when the conversion is in */
|
||||
nt_status = smbcli_nt_error(cli);
|
||||
} else {
|
||||
nt_status = NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* if logged in as guest then reject */
|
||||
if ((SVAL(cli->inbuf,smb_vwv2) & 1) != 0) {
|
||||
DEBUG(1,("password server %s gave us guest only\n", cli->desthost));
|
||||
nt_status = NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
|
||||
smbcli_ulogoff(cli);
|
||||
|
||||
if NT_STATUS_IS_OK(nt_status) {
|
||||
struct passwd *pass = Get_Pwnam(user_info->internal_username.str);
|
||||
if (pass) {
|
||||
nt_status = make_server_info_pw(auth_context, server_info, pass);
|
||||
} else {
|
||||
nt_status = NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
}
|
||||
|
||||
if (locally_made_cli) {
|
||||
talloc_free(cli);
|
||||
}
|
||||
|
||||
return(nt_status);
|
||||
}
|
||||
|
||||
NTSTATUS auth_init_smbserver(struct auth_context *auth_context, const char* param, auth_methods **auth_method)
|
||||
{
|
||||
if (!make_auth_methods(auth_context, auth_method)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
(*auth_method)->name = "smbserver";
|
||||
(*auth_method)->auth = check_smbserver_security;
|
||||
(*auth_method)->get_chal = auth_get_challenge_server;
|
||||
(*auth_method)->send_keepalive = send_server_keepalive;
|
||||
(*auth_method)->free_private_data = free_server_private_data;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
auth functions
|
||||
|
||||
Copyright (C) Simo Sorce 2005
|
||||
Copyright (C) Andrew Tridgell 2005
|
||||
Copyright (C) Andrew Bartlett 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "lib/events/events.h"
|
||||
|
||||
_PUBLIC_ NTSTATUS authenticate_username_pw(TALLOC_CTX *mem_ctx,
|
||||
struct event_context *ev,
|
||||
struct messaging_context *msg,
|
||||
const char *nt4_domain,
|
||||
const char *nt4_username,
|
||||
const char *password,
|
||||
struct auth_session_info **session_info)
|
||||
{
|
||||
struct auth_context *auth_context;
|
||||
struct auth_usersupplied_info *user_info;
|
||||
struct auth_serversupplied_info *server_info;
|
||||
NTSTATUS nt_status;
|
||||
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
|
||||
|
||||
if (!tmp_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
nt_status = auth_context_create(tmp_ctx, lp_auth_methods(),
|
||||
ev, msg,
|
||||
&auth_context);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
user_info = talloc(tmp_ctx, struct auth_usersupplied_info);
|
||||
if (!user_info) {
|
||||
talloc_free(tmp_ctx);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
user_info->mapped_state = True;
|
||||
user_info->client.account_name = nt4_username;
|
||||
user_info->mapped.account_name = nt4_username;
|
||||
user_info->client.domain_name = nt4_domain;
|
||||
user_info->mapped.domain_name = nt4_domain;
|
||||
|
||||
user_info->workstation_name = NULL;
|
||||
|
||||
user_info->remote_host = NULL;
|
||||
|
||||
user_info->password_state = AUTH_PASSWORD_PLAIN;
|
||||
user_info->password.plaintext = talloc_strdup(user_info, password);
|
||||
|
||||
user_info->flags = USER_INFO_CASE_INSENSITIVE_USERNAME |
|
||||
USER_INFO_DONT_CHECK_UNIX_ACCOUNT;
|
||||
|
||||
user_info->logon_parameters = 0;
|
||||
|
||||
nt_status = auth_check_password(auth_context, tmp_ctx, user_info, &server_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = auth_generate_session_info(tmp_ctx, server_info, session_info);
|
||||
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_steal(mem_ctx, *session_info);
|
||||
}
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,839 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Password and authentication handling
|
||||
Copyright (C) Andrew Bartlett 2001
|
||||
Copyright (C) Jeremy Allison 2001
|
||||
Copyright (C) Simo Sorce 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "system/passwd.h" /* needed by some systems for struct passwd */
|
||||
#include "lib/socket/socket.h"
|
||||
#include "auth/pam_errors.h"
|
||||
|
||||
/* TODO: look at how to best fill in parms retrieveing a struct passwd info
|
||||
* except in case USER_INFO_DONT_CHECK_UNIX_ACCOUNT is set
|
||||
*/
|
||||
static NTSTATUS authunix_make_server_info(TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct passwd *pwd,
|
||||
struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
struct auth_serversupplied_info *server_info;
|
||||
NTSTATUS status;
|
||||
|
||||
/* This is a real, real hack */
|
||||
if (pwd->pw_uid == 0) {
|
||||
status = auth_system_server_info(mem_ctx, &server_info);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
server_info->account_name = talloc_steal(server_info, pwd->pw_name);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_name);
|
||||
|
||||
server_info->domain_name = talloc_strdup(server_info, "unix");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_name);
|
||||
} else {
|
||||
server_info = talloc(mem_ctx, struct auth_serversupplied_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info);
|
||||
|
||||
server_info->authenticated = True;
|
||||
|
||||
server_info->account_name = talloc_steal(server_info, pwd->pw_name);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_name);
|
||||
|
||||
server_info->domain_name = talloc_strdup(server_info, "unix");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_name);
|
||||
|
||||
/* This isn't in any way correct.. */
|
||||
server_info->account_sid = NULL;
|
||||
server_info->primary_group_sid = NULL;
|
||||
server_info->n_domain_groups = 0;
|
||||
server_info->domain_groups = NULL;
|
||||
}
|
||||
server_info->user_session_key = data_blob(NULL,0);
|
||||
server_info->lm_session_key = data_blob(NULL,0);
|
||||
|
||||
server_info->full_name = talloc_steal(server_info, pwd->pw_gecos);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->full_name);
|
||||
server_info->logon_script = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_script);
|
||||
server_info->profile_path = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->profile_path);
|
||||
server_info->home_directory = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_directory);
|
||||
server_info->home_drive = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_drive);
|
||||
|
||||
server_info->last_logon = 0;
|
||||
server_info->last_logoff = 0;
|
||||
server_info->acct_expiry = 0;
|
||||
server_info->last_password_change = 0;
|
||||
server_info->allow_password_change = 0;
|
||||
server_info->force_password_change = 0;
|
||||
server_info->logon_count = 0;
|
||||
server_info->bad_password_count = 0;
|
||||
server_info->acct_flags = 0;
|
||||
|
||||
*_server_info = server_info;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS talloc_getpwnam(TALLOC_CTX *ctx, const char *username, struct passwd **pws)
|
||||
{
|
||||
struct passwd *ret;
|
||||
struct passwd *from;
|
||||
|
||||
*pws = NULL;
|
||||
|
||||
ret = talloc(ctx, struct passwd);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ret);
|
||||
|
||||
from = getpwnam(username);
|
||||
if (!from) {
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
ret->pw_name = talloc_strdup(ctx, from->pw_name);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ret->pw_name);
|
||||
|
||||
ret->pw_passwd = talloc_strdup(ctx, from->pw_passwd);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ret->pw_passwd);
|
||||
|
||||
ret->pw_uid = from->pw_uid;
|
||||
ret->pw_gid = from->pw_gid;
|
||||
ret->pw_gecos = talloc_strdup(ctx, from->pw_gecos);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ret->pw_gecos);
|
||||
|
||||
ret->pw_dir = talloc_strdup(ctx, from->pw_dir);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ret->pw_dir);
|
||||
|
||||
ret->pw_shell = talloc_strdup(ctx, from->pw_shell);
|
||||
NT_STATUS_HAVE_NO_MEMORY(ret->pw_shell);
|
||||
|
||||
*pws = ret;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_SECURITY_PAM_APPL_H
|
||||
#include <security/pam_appl.h>
|
||||
|
||||
struct smb_pam_user_info {
|
||||
const char *account_name;
|
||||
const char *plaintext_password;
|
||||
};
|
||||
|
||||
#define COPY_STRING(s) (s) ? strdup(s) : NULL
|
||||
|
||||
/*
|
||||
* Check user password
|
||||
* Currently it uses PAM only and fails on systems without PAM
|
||||
* Samba3 code located in pass_check.c is to ugly to be used directly it will
|
||||
* need major rework that's why pass_check.c is still there.
|
||||
*/
|
||||
|
||||
static int smb_pam_conv(int num_msg, const struct pam_message **msg,
|
||||
struct pam_response **reply, void *appdata_ptr)
|
||||
{
|
||||
struct smb_pam_user_info *info = (struct smb_pam_user_info *)appdata_ptr;
|
||||
int num;
|
||||
|
||||
if (num_msg <= 0) {
|
||||
*reply = NULL;
|
||||
return PAM_CONV_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apparantly HPUX has a buggy PAM that doesn't support the
|
||||
* data pointer. Fail if this is the case. JRA.
|
||||
*/
|
||||
|
||||
if (info == NULL) {
|
||||
*reply = NULL;
|
||||
return PAM_CONV_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
* PAM frees memory in reply messages by itself
|
||||
* so use malloc instead of talloc here.
|
||||
*/
|
||||
*reply = malloc_array_p(struct pam_response, num_msg);
|
||||
if (*reply == NULL) {
|
||||
return PAM_CONV_ERR;
|
||||
}
|
||||
|
||||
for (num = 0; num < num_msg; num++) {
|
||||
switch (msg[num]->msg_style) {
|
||||
case PAM_PROMPT_ECHO_ON:
|
||||
(*reply)[num].resp_retcode = PAM_SUCCESS;
|
||||
(*reply)[num].resp = COPY_STRING(info->account_name);
|
||||
break;
|
||||
|
||||
case PAM_PROMPT_ECHO_OFF:
|
||||
(*reply)[num].resp_retcode = PAM_SUCCESS;
|
||||
(*reply)[num].resp = COPY_STRING(info->plaintext_password);
|
||||
break;
|
||||
|
||||
case PAM_TEXT_INFO:
|
||||
(*reply)[num].resp_retcode = PAM_SUCCESS;
|
||||
(*reply)[num].resp = NULL;
|
||||
DEBUG(4,("PAM Info message in conversation function: %s\n", (msg[num]->msg)));
|
||||
break;
|
||||
|
||||
case PAM_ERROR_MSG:
|
||||
(*reply)[num].resp_retcode = PAM_SUCCESS;
|
||||
(*reply)[num].resp = NULL;
|
||||
DEBUG(4,("PAM Error message in conversation function: %s\n", (msg[num]->msg)));
|
||||
break;
|
||||
|
||||
default:
|
||||
while (num > 0) {
|
||||
SAFE_FREE((*reply)[num-1].resp);
|
||||
num--;
|
||||
}
|
||||
SAFE_FREE(*reply);
|
||||
*reply = NULL;
|
||||
DEBUG(1,("Error: PAM subsystme sent an UNKNOWN message type to the conversation function!\n"));
|
||||
return PAM_CONV_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start PAM authentication for specified account
|
||||
*/
|
||||
|
||||
static NTSTATUS smb_pam_start(pam_handle_t **pamh, const char *account_name, const char *remote_host, struct pam_conv *pconv)
|
||||
{
|
||||
int pam_error;
|
||||
|
||||
if (account_name == NULL || remote_host == NULL) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
DEBUG(4,("smb_pam_start: PAM: Init user: %s\n", account_name));
|
||||
|
||||
pam_error = pam_start("samba", account_name, pconv, pamh);
|
||||
if (pam_error != PAM_SUCCESS) {
|
||||
/* no vaild pamh here, can we reliably call pam_strerror ? */
|
||||
DEBUG(4,("smb_pam_start: pam_start failed!\n"));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
#ifdef PAM_RHOST
|
||||
DEBUG(4,("smb_pam_start: PAM: setting rhost to: %s\n", remote_host));
|
||||
pam_error = pam_set_item(*pamh, PAM_RHOST, remote_host);
|
||||
if (pam_error != PAM_SUCCESS) {
|
||||
NTSTATUS nt_status;
|
||||
|
||||
DEBUG(4,("smb_pam_start: setting rhost failed with error: %s\n",
|
||||
pam_strerror(*pamh, pam_error)));
|
||||
nt_status = pam_to_nt_status(pam_error);
|
||||
|
||||
pam_error = pam_end(*pamh, 0);
|
||||
if (pam_error != PAM_SUCCESS) {
|
||||
/* no vaild pamh here, can we reliably call pam_strerror ? */
|
||||
DEBUG(4,("smb_pam_start: clean up failed, pam_end gave error %d.\n",
|
||||
pam_error));
|
||||
return pam_to_nt_status(pam_error);
|
||||
}
|
||||
return nt_status;
|
||||
}
|
||||
#endif
|
||||
#ifdef PAM_TTY
|
||||
DEBUG(4,("smb_pam_start: PAM: setting tty\n"));
|
||||
pam_error = pam_set_item(*pamh, PAM_TTY, "samba");
|
||||
if (pam_error != PAM_SUCCESS) {
|
||||
NTSTATUS nt_status;
|
||||
|
||||
DEBUG(4,("smb_pam_start: setting tty failed with error: %s\n",
|
||||
pam_strerror(*pamh, pam_error)));
|
||||
nt_status = pam_to_nt_status(pam_error);
|
||||
|
||||
pam_error = pam_end(*pamh, 0);
|
||||
if (pam_error != PAM_SUCCESS) {
|
||||
/* no vaild pamh here, can we reliably call pam_strerror ? */
|
||||
DEBUG(4,("smb_pam_start: clean up failed, pam_end gave error %d.\n",
|
||||
pam_error));
|
||||
return pam_to_nt_status(pam_error);
|
||||
}
|
||||
return nt_status;
|
||||
}
|
||||
#endif
|
||||
DEBUG(4,("smb_pam_start: PAM: Init passed for user: %s\n", account_name));
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS smb_pam_end(pam_handle_t *pamh)
|
||||
{
|
||||
int pam_error;
|
||||
|
||||
if (pamh != NULL) {
|
||||
pam_error = pam_end(pamh, 0);
|
||||
if (pam_error != PAM_SUCCESS) {
|
||||
/* no vaild pamh here, can we reliably call pam_strerror ? */
|
||||
DEBUG(4,("smb_pam_end: clean up failed, pam_end gave error %d.\n",
|
||||
pam_error));
|
||||
return pam_to_nt_status(pam_error);
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
DEBUG(2,("smb_pam_end: pamh is NULL, PAM not initialized ?\n"));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
/*
|
||||
* PAM Authentication Handler
|
||||
*/
|
||||
static NTSTATUS smb_pam_auth(pam_handle_t *pamh, const char *user)
|
||||
{
|
||||
int pam_error;
|
||||
|
||||
/*
|
||||
* To enable debugging set in /etc/pam.d/samba:
|
||||
* auth required /lib/security/pam_pwdb.so nullok shadow audit
|
||||
*/
|
||||
|
||||
DEBUG(4,("smb_pam_auth: PAM: Authenticate User: %s\n", user));
|
||||
|
||||
pam_error = pam_authenticate(pamh, PAM_SILENT | lp_null_passwords() ? 0 : PAM_DISALLOW_NULL_AUTHTOK);
|
||||
switch( pam_error ){
|
||||
case PAM_AUTH_ERR:
|
||||
DEBUG(2, ("smb_pam_auth: PAM: Authentication Error for user %s\n", user));
|
||||
break;
|
||||
case PAM_CRED_INSUFFICIENT:
|
||||
DEBUG(2, ("smb_pam_auth: PAM: Insufficient Credentials for user %s\n", user));
|
||||
break;
|
||||
case PAM_AUTHINFO_UNAVAIL:
|
||||
DEBUG(2, ("smb_pam_auth: PAM: Authentication Information Unavailable for user %s\n", user));
|
||||
break;
|
||||
case PAM_USER_UNKNOWN:
|
||||
DEBUG(2, ("smb_pam_auth: PAM: Username %s NOT known to Authentication system\n", user));
|
||||
break;
|
||||
case PAM_MAXTRIES:
|
||||
DEBUG(2, ("smb_pam_auth: PAM: One or more authentication modules reports user limit for user %s exceeeded\n", user));
|
||||
break;
|
||||
case PAM_ABORT:
|
||||
DEBUG(0, ("smb_pam_auth: PAM: One or more PAM modules failed to load for user %s\n", user));
|
||||
break;
|
||||
case PAM_SUCCESS:
|
||||
DEBUG(4, ("smb_pam_auth: PAM: User %s Authenticated OK\n", user));
|
||||
break;
|
||||
default:
|
||||
DEBUG(0, ("smb_pam_auth: PAM: UNKNOWN ERROR while authenticating user %s\n", user));
|
||||
break;
|
||||
}
|
||||
|
||||
return pam_to_nt_status(pam_error);
|
||||
}
|
||||
|
||||
/*
|
||||
* PAM Account Handler
|
||||
*/
|
||||
static NTSTATUS smb_pam_account(pam_handle_t *pamh, const char * user)
|
||||
{
|
||||
int pam_error;
|
||||
|
||||
DEBUG(4,("smb_pam_account: PAM: Account Management for User: %s\n", user));
|
||||
|
||||
pam_error = pam_acct_mgmt(pamh, PAM_SILENT); /* Is user account enabled? */
|
||||
switch( pam_error ) {
|
||||
case PAM_AUTHTOK_EXPIRED:
|
||||
DEBUG(2, ("smb_pam_account: PAM: User %s is valid but password is expired\n", user));
|
||||
break;
|
||||
case PAM_ACCT_EXPIRED:
|
||||
DEBUG(2, ("smb_pam_account: PAM: User %s no longer permitted to access system\n", user));
|
||||
break;
|
||||
case PAM_AUTH_ERR:
|
||||
DEBUG(2, ("smb_pam_account: PAM: There was an authentication error for user %s\n", user));
|
||||
break;
|
||||
case PAM_PERM_DENIED:
|
||||
DEBUG(0, ("smb_pam_account: PAM: User %s is NOT permitted to access system at this time\n", user));
|
||||
break;
|
||||
case PAM_USER_UNKNOWN:
|
||||
DEBUG(0, ("smb_pam_account: PAM: User \"%s\" is NOT known to account management\n", user));
|
||||
break;
|
||||
case PAM_SUCCESS:
|
||||
DEBUG(4, ("smb_pam_account: PAM: Account OK for User: %s\n", user));
|
||||
break;
|
||||
default:
|
||||
DEBUG(0, ("smb_pam_account: PAM: UNKNOWN PAM ERROR (%d) during Account Management for User: %s\n", pam_error, user));
|
||||
break;
|
||||
}
|
||||
|
||||
return pam_to_nt_status(pam_error);
|
||||
}
|
||||
|
||||
/*
|
||||
* PAM Credential Setting
|
||||
*/
|
||||
|
||||
static NTSTATUS smb_pam_setcred(pam_handle_t *pamh, const char * user)
|
||||
{
|
||||
int pam_error;
|
||||
|
||||
/*
|
||||
* This will allow samba to aquire a kerberos token. And, when
|
||||
* exporting an AFS cell, be able to /write/ to this cell.
|
||||
*/
|
||||
|
||||
DEBUG(4,("PAM: Account Management SetCredentials for User: %s\n", user));
|
||||
|
||||
pam_error = pam_setcred(pamh, (PAM_ESTABLISH_CRED|PAM_SILENT));
|
||||
switch( pam_error ) {
|
||||
case PAM_CRED_UNAVAIL:
|
||||
DEBUG(0, ("smb_pam_setcred: PAM: Credentials not found for user:%s\n", user ));
|
||||
break;
|
||||
case PAM_CRED_EXPIRED:
|
||||
DEBUG(0, ("smb_pam_setcred: PAM: Credentials for user: \"%s\" EXPIRED!\n", user ));
|
||||
break;
|
||||
case PAM_USER_UNKNOWN:
|
||||
DEBUG(0, ("smb_pam_setcred: PAM: User: \"%s\" is NOT known so can not set credentials!\n", user ));
|
||||
break;
|
||||
case PAM_CRED_ERR:
|
||||
DEBUG(0, ("smb_pam_setcred: PAM: Unknown setcredentials error - unable to set credentials for %s\n", user ));
|
||||
break;
|
||||
case PAM_SUCCESS:
|
||||
DEBUG(4, ("smb_pam_setcred: PAM: SetCredentials OK for User: %s\n", user));
|
||||
break;
|
||||
default:
|
||||
DEBUG(0, ("smb_pam_setcred: PAM: UNKNOWN PAM ERROR (%d) during SetCredentials for User: %s\n", pam_error, user));
|
||||
break;
|
||||
}
|
||||
|
||||
return pam_to_nt_status(pam_error);
|
||||
}
|
||||
|
||||
static NTSTATUS check_unix_password(TALLOC_CTX *ctx, const struct auth_usersupplied_info *user_info, struct passwd **pws)
|
||||
{
|
||||
struct smb_pam_user_info *info;
|
||||
struct pam_conv *pamconv;
|
||||
pam_handle_t *pamh;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
info = talloc(ctx, struct smb_pam_user_info);
|
||||
if (info == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
info->account_name = user_info->mapped.account_name;
|
||||
info->plaintext_password = user_info->password.plaintext;
|
||||
|
||||
pamconv = talloc(ctx, struct pam_conv);
|
||||
if (pamconv == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
pamconv->conv = smb_pam_conv;
|
||||
pamconv->appdata_ptr = (void *)info;
|
||||
|
||||
/* TODO:
|
||||
* check for user_info->flags & USER_INFO_CASE_INSENSITIVE_USERNAME
|
||||
* if true set up a crack name routine.
|
||||
*/
|
||||
|
||||
nt_status = smb_pam_start(&pamh, user_info->mapped.account_name, user_info->remote_host ? user_info->remote_host->addr : NULL, pamconv);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = smb_pam_auth(pamh, user_info->mapped.account_name);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
smb_pam_end(pamh);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if ( ! (user_info->flags & USER_INFO_DONT_CHECK_UNIX_ACCOUNT)) {
|
||||
|
||||
nt_status = smb_pam_account(pamh, user_info->mapped.account_name);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
smb_pam_end(pamh);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = smb_pam_setcred(pamh, user_info->mapped.account_name);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
smb_pam_end(pamh);
|
||||
return nt_status;
|
||||
}
|
||||
}
|
||||
|
||||
smb_pam_end(pamh);
|
||||
|
||||
nt_status = talloc_getpwnam(ctx, user_info->mapped.account_name, pws);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/****************************************************************************
|
||||
core of password checking routine
|
||||
****************************************************************************/
|
||||
static NTSTATUS password_check(const char *username, const char *password,
|
||||
const char *crypted, const char *salt)
|
||||
{
|
||||
BOOL ret;
|
||||
|
||||
#ifdef WITH_AFS
|
||||
if (afs_auth(username, password))
|
||||
return NT_STATUS_OK;
|
||||
#endif /* WITH_AFS */
|
||||
|
||||
#ifdef WITH_DFS
|
||||
if (dfs_auth(username, password))
|
||||
return NT_STATUS_OK;
|
||||
#endif /* WITH_DFS */
|
||||
|
||||
#ifdef OSF1_ENH_SEC
|
||||
|
||||
ret = (strcmp(osf1_bigcrypt(password, salt), crypted) == 0);
|
||||
|
||||
if (!ret) {
|
||||
DEBUG(2,
|
||||
("OSF1_ENH_SEC failed. Trying normal crypt.\n"));
|
||||
ret = (strcmp((char *)crypt(password, salt), crypted) == 0);
|
||||
}
|
||||
if (ret) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
#endif /* OSF1_ENH_SEC */
|
||||
|
||||
#ifdef ULTRIX_AUTH
|
||||
ret = (strcmp((char *)crypt16(password, salt), crypted) == 0);
|
||||
if (ret) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
#endif /* ULTRIX_AUTH */
|
||||
|
||||
#ifdef LINUX_BIGCRYPT
|
||||
ret = (linux_bigcrypt(password, salt, crypted));
|
||||
if (ret) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
#endif /* LINUX_BIGCRYPT */
|
||||
|
||||
#if defined(HAVE_BIGCRYPT) && defined(HAVE_CRYPT) && defined(USE_BOTH_CRYPT_CALLS)
|
||||
|
||||
/*
|
||||
* Some systems have bigcrypt in the C library but might not
|
||||
* actually use it for the password hashes (HPUX 10.20) is
|
||||
* a noteable example. So we try bigcrypt first, followed
|
||||
* by crypt.
|
||||
*/
|
||||
|
||||
if (strcmp(bigcrypt(password, salt), crypted) == 0)
|
||||
return NT_STATUS_OK;
|
||||
else
|
||||
ret = (strcmp((char *)crypt(password, salt), crypted) == 0);
|
||||
if (ret) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
#else /* HAVE_BIGCRYPT && HAVE_CRYPT && USE_BOTH_CRYPT_CALLS */
|
||||
|
||||
#ifdef HAVE_BIGCRYPT
|
||||
ret = (strcmp(bigcrypt(password, salt), crypted) == 0);
|
||||
if (ret) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
#endif /* HAVE_BIGCRYPT */
|
||||
|
||||
#ifndef HAVE_CRYPT
|
||||
DEBUG(1, ("Warning - no crypt available\n"));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
#else /* HAVE_CRYPT */
|
||||
ret = (strcmp((char *)crypt(password, salt), crypted) == 0);
|
||||
if (ret) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
#endif /* HAVE_CRYPT */
|
||||
#endif /* HAVE_BIGCRYPT && HAVE_CRYPT && USE_BOTH_CRYPT_CALLS */
|
||||
}
|
||||
|
||||
static NTSTATUS check_unix_password(TALLOC_CTX *ctx, const struct auth_usersupplied_info *user_info, struct passwd **ret_passwd)
|
||||
{
|
||||
char *username;
|
||||
char *password;
|
||||
char *pwcopy;
|
||||
char *salt;
|
||||
char *crypted;
|
||||
struct passwd *pws;
|
||||
NTSTATUS nt_status;
|
||||
int level = lp_passwordlevel();
|
||||
|
||||
*ret_passwd = NULL;
|
||||
|
||||
username = talloc_strdup(ctx, user_info->mapped.account_name);
|
||||
password = talloc_strdup(ctx, user_info->password.plaintext);
|
||||
|
||||
nt_status = talloc_getpwnam(ctx, username, &pws);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
crypted = pws->pw_passwd;
|
||||
salt = pws->pw_passwd;
|
||||
|
||||
#ifdef HAVE_GETSPNAM
|
||||
{
|
||||
struct spwd *spass;
|
||||
|
||||
/* many shadow systems require you to be root to get
|
||||
the password, in most cases this should already be
|
||||
the case when this function is called, except
|
||||
perhaps for IPC password changing requests */
|
||||
|
||||
spass = getspnam(pws->pw_name);
|
||||
if (spass && spass->sp_pwdp) {
|
||||
crypted = talloc_strdup(ctx, spass->sp_pwdp);
|
||||
NT_STATUS_HAVE_NO_MEMORY(crypted);
|
||||
salt = talloc_strdup(ctx, spass->sp_pwdp);
|
||||
NT_STATUS_HAVE_NO_MEMORY(salt);
|
||||
}
|
||||
}
|
||||
#elif defined(IA_UINFO)
|
||||
{
|
||||
char *ia_password;
|
||||
/* Need to get password with SVR4.2's ia_ functions
|
||||
instead of get{sp,pw}ent functions. Required by
|
||||
UnixWare 2.x, tested on version
|
||||
2.1. (tangent@cyberport.com) */
|
||||
uinfo_t uinfo;
|
||||
if (ia_openinfo(pws->pw_name, &uinfo) != -1) {
|
||||
ia_get_logpwd(uinfo, &ia_password);
|
||||
crypted = talloc_strdup(ctx, ia_password);
|
||||
NT_STATUS_HAVE_NO_MEMORY(crypted);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GETPRPWNAM
|
||||
{
|
||||
struct pr_passwd *pr_pw = getprpwnam(pws->pw_name);
|
||||
if (pr_pw && pr_pw->ufld.fd_encrypt) {
|
||||
crypted = talloc_strdup(ctx, pr_pw->ufld.fd_encrypt);
|
||||
NT_STATUS_HAVE_NO_MEMORY(crypted);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GETPWANAM
|
||||
{
|
||||
struct passwd_adjunct *pwret;
|
||||
pwret = getpwanam(s);
|
||||
if (pwret && pwret->pwa_passwd) {
|
||||
crypted = talloc_strdup(ctx, pwret->pwa_passwd);
|
||||
NT_STATUS_HAVE_NO_MEMORY(crypted);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef OSF1_ENH_SEC
|
||||
{
|
||||
struct pr_passwd *mypasswd;
|
||||
DEBUG(5,("Checking password for user %s in OSF1_ENH_SEC\n", username));
|
||||
mypasswd = getprpwnam(username);
|
||||
if (mypasswd) {
|
||||
username = talloc_strdup(ctx, mypasswd->ufld.fd_name);
|
||||
NT_STATUS_HAVE_NO_MEMORY(username);
|
||||
crypted = talloc_strdup(ctx, mypasswd->ufld.fd_encrypt);
|
||||
NT_STATUS_HAVE_NO_MEMORY(crypted);
|
||||
} else {
|
||||
DEBUG(5,("OSF1_ENH_SEC: No entry for user %s in protected database !\n", username));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ULTRIX_AUTH
|
||||
{
|
||||
AUTHORIZATION *ap = getauthuid(pws->pw_uid);
|
||||
if (ap) {
|
||||
crypted = talloc_strdup(ctx, ap->a_password);
|
||||
endauthent();
|
||||
NT_STATUS_HAVE_NO_MEMORY(crypted);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_TRUNCATED_SALT)
|
||||
/* crypt on some platforms (HPUX in particular)
|
||||
won't work with more than 2 salt characters. */
|
||||
salt[2] = 0;
|
||||
#endif
|
||||
|
||||
if (crypted[0] == '\0') {
|
||||
if (!lp_null_passwords()) {
|
||||
DEBUG(2, ("Disallowing %s with null password\n", username));
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
if (password == NULL) {
|
||||
DEBUG(3, ("Allowing access to %s with null password\n", username));
|
||||
*ret_passwd = pws;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* try it as it came to us */
|
||||
nt_status = password_check(username, password, crypted, salt);
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
*ret_passwd = pws;
|
||||
return nt_status;
|
||||
}
|
||||
else if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD)) {
|
||||
/* No point continuing if its not the password thats to blame (ie PAM disabled). */
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if ( user_info->flags | USER_INFO_CASE_INSENSITIVE_PASSWORD) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* if the password was given to us with mixed case then we don't
|
||||
* need to proceed as we know it hasn't been case modified by the
|
||||
* client */
|
||||
if (strhasupper(password) && strhaslower(password)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* make a copy of it */
|
||||
pwcopy = talloc_strdup(ctx, password);
|
||||
if (!pwcopy)
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
|
||||
/* try all lowercase if it's currently all uppercase */
|
||||
if (strhasupper(pwcopy)) {
|
||||
strlower(pwcopy);
|
||||
nt_status = password_check(username, pwcopy, crypted, salt);
|
||||
if NT_STATUS_IS_OK(nt_status) {
|
||||
*ret_passwd = pws;
|
||||
return nt_status;
|
||||
}
|
||||
}
|
||||
|
||||
/* give up? */
|
||||
if (level < 1) {
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
/* last chance - all combinations of up to level chars upper! */
|
||||
strlower(pwcopy);
|
||||
|
||||
#if 0
|
||||
if (NT_STATUS_IS_OK(nt_status = string_combinations(pwcopy, password_check, level))) {
|
||||
*ret_passwd = pws;
|
||||
return nt_status;
|
||||
}
|
||||
#endif
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/** Check a plaintext username/password
|
||||
*
|
||||
**/
|
||||
|
||||
static NTSTATUS authunix_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS authunix_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
TALLOC_CTX *check_ctx;
|
||||
NTSTATUS nt_status;
|
||||
struct passwd *pwd;
|
||||
|
||||
if (user_info->password_state != AUTH_PASSWORD_PLAIN) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
check_ctx = talloc_named_const(mem_ctx, 0, "check_unix_password");
|
||||
if (check_ctx == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
nt_status = check_unix_password(check_ctx, user_info, &pwd);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(check_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = authunix_make_server_info(mem_ctx, user_info, pwd, server_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(check_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
talloc_free(check_ctx);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static const struct auth_operations unix_ops = {
|
||||
.name = "unix",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = authunix_want_check,
|
||||
.check_password = authunix_check_password
|
||||
};
|
||||
|
||||
NTSTATUS auth_unix_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
ret = auth_register(&unix_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register unix auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Authentication utility functions
|
||||
Copyright (C) Andrew Tridgell 1992-1998
|
||||
Copyright (C) Andrew Bartlett 2001
|
||||
Copyright (C) Jeremy Allison 2000-2001
|
||||
Copyright (C) Rafal Szczesniak 2002
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/credentials/credentials_krb5.h"
|
||||
|
||||
/* this default function can be used by mostly all backends
|
||||
* which don't want to set a challenge
|
||||
*/
|
||||
NTSTATUS auth_get_challenge_not_implemented(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx, DATA_BLOB *challenge)
|
||||
{
|
||||
/* we don't want to set a challenge */
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Create an auth_usersupplied_data structure after appropriate mapping.
|
||||
****************************************************************************/
|
||||
|
||||
NTSTATUS map_user_info(TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_usersupplied_info **user_info_mapped)
|
||||
{
|
||||
const char *domain;
|
||||
char *account_name;
|
||||
char *d;
|
||||
DEBUG(5,("map_user_info: Mapping user [%s]\\[%s] from workstation [%s]\n",
|
||||
user_info->client.domain_name, user_info->client.account_name, user_info->workstation_name));
|
||||
|
||||
account_name = talloc_strdup(mem_ctx, user_info->client.account_name);
|
||||
if (!account_name) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* don't allow "" as a domain, fixes a Win9X bug
|
||||
where it doens't supply a domain for logon script
|
||||
'net use' commands. */
|
||||
|
||||
/* Split user@realm names into user and realm components. This is TODO to fix with proper userprincipalname support */
|
||||
if (user_info->client.domain_name && *user_info->client.domain_name) {
|
||||
domain = user_info->client.domain_name;
|
||||
} else if (strchr_m(user_info->client.account_name, '@')) {
|
||||
d = strchr_m(account_name, '@');
|
||||
if (!d) {
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
d[0] = '\0';
|
||||
d++;
|
||||
domain = d;
|
||||
} else {
|
||||
domain = lp_workgroup();
|
||||
}
|
||||
|
||||
*user_info_mapped = talloc(mem_ctx, struct auth_usersupplied_info);
|
||||
if (!*user_info_mapped) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
talloc_reference(*user_info_mapped, user_info);
|
||||
**user_info_mapped = *user_info;
|
||||
(*user_info_mapped)->mapped_state = True;
|
||||
(*user_info_mapped)->mapped.domain_name = talloc_strdup(*user_info_mapped, domain);
|
||||
(*user_info_mapped)->mapped.account_name = talloc_strdup(*user_info_mapped, account_name);
|
||||
talloc_free(account_name);
|
||||
if (!(*user_info_mapped)->mapped.domain_name
|
||||
|| !(*user_info_mapped)->mapped.account_name) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Create an auth_usersupplied_data structure after appropriate mapping.
|
||||
****************************************************************************/
|
||||
|
||||
NTSTATUS encrypt_user_info(TALLOC_CTX *mem_ctx, struct auth_context *auth_context,
|
||||
enum auth_password_state to_state,
|
||||
const struct auth_usersupplied_info *user_info_in,
|
||||
const struct auth_usersupplied_info **user_info_encrypted)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_usersupplied_info *user_info_temp;
|
||||
switch (to_state) {
|
||||
case AUTH_PASSWORD_RESPONSE:
|
||||
switch (user_info_in->password_state) {
|
||||
case AUTH_PASSWORD_PLAIN:
|
||||
{
|
||||
const struct auth_usersupplied_info *user_info_temp2;
|
||||
nt_status = encrypt_user_info(mem_ctx, auth_context,
|
||||
AUTH_PASSWORD_HASH,
|
||||
user_info_in, &user_info_temp2);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
user_info_in = user_info_temp2;
|
||||
/* fall through */
|
||||
}
|
||||
case AUTH_PASSWORD_HASH:
|
||||
{
|
||||
const uint8_t *challenge;
|
||||
DATA_BLOB chall_blob;
|
||||
user_info_temp = talloc(mem_ctx, struct auth_usersupplied_info);
|
||||
if (!user_info_temp) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
talloc_reference(user_info_temp, user_info_in);
|
||||
*user_info_temp = *user_info_in;
|
||||
user_info_temp->mapped_state = to_state;
|
||||
|
||||
nt_status = auth_get_challenge(auth_context, &challenge);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
chall_blob = data_blob_talloc(mem_ctx, challenge, 8);
|
||||
if (lp_client_ntlmv2_auth()) {
|
||||
DATA_BLOB names_blob = NTLMv2_generate_names_blob(mem_ctx, lp_netbios_name(), lp_workgroup());
|
||||
DATA_BLOB lmv2_response, ntlmv2_response, lmv2_session_key, ntlmv2_session_key;
|
||||
|
||||
if (!SMBNTLMv2encrypt_hash(user_info_temp,
|
||||
user_info_in->client.account_name,
|
||||
user_info_in->client.domain_name,
|
||||
user_info_in->password.hash.nt->hash, &chall_blob,
|
||||
&names_blob,
|
||||
&lmv2_response, &ntlmv2_response,
|
||||
&lmv2_session_key, &ntlmv2_session_key)) {
|
||||
data_blob_free(&names_blob);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
data_blob_free(&names_blob);
|
||||
user_info_temp->password.response.lanman = lmv2_response;
|
||||
user_info_temp->password.response.nt = ntlmv2_response;
|
||||
|
||||
data_blob_free(&lmv2_session_key);
|
||||
data_blob_free(&ntlmv2_session_key);
|
||||
} else {
|
||||
DATA_BLOB blob = data_blob_talloc(mem_ctx, NULL, 24);
|
||||
SMBOWFencrypt(user_info_in->password.hash.nt->hash, challenge, blob.data);
|
||||
|
||||
user_info_temp->password.response.nt = blob;
|
||||
if (lp_client_lanman_auth() && user_info_in->password.hash.lanman) {
|
||||
DATA_BLOB lm_blob = data_blob_talloc(mem_ctx, NULL, 24);
|
||||
SMBOWFencrypt(user_info_in->password.hash.lanman->hash, challenge, blob.data);
|
||||
user_info_temp->password.response.lanman = lm_blob;
|
||||
} else {
|
||||
/* if not sending the LM password, send the NT password twice */
|
||||
user_info_temp->password.response.lanman = user_info_temp->password.response.nt;
|
||||
}
|
||||
}
|
||||
|
||||
user_info_in = user_info_temp;
|
||||
/* fall through */
|
||||
}
|
||||
case AUTH_PASSWORD_RESPONSE:
|
||||
*user_info_encrypted = user_info_in;
|
||||
}
|
||||
break;
|
||||
case AUTH_PASSWORD_HASH:
|
||||
{
|
||||
switch (user_info_in->password_state) {
|
||||
case AUTH_PASSWORD_PLAIN:
|
||||
{
|
||||
struct samr_Password lanman;
|
||||
struct samr_Password nt;
|
||||
|
||||
user_info_temp = talloc(mem_ctx, struct auth_usersupplied_info);
|
||||
if (!user_info_temp) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
talloc_reference(user_info_temp, user_info_in);
|
||||
*user_info_temp = *user_info_in;
|
||||
user_info_temp->mapped_state = to_state;
|
||||
|
||||
if (E_deshash(user_info_in->password.plaintext, lanman.hash)) {
|
||||
user_info_temp->password.hash.lanman = talloc(user_info_temp,
|
||||
struct samr_Password);
|
||||
*user_info_temp->password.hash.lanman = lanman;
|
||||
} else {
|
||||
user_info_temp->password.hash.lanman = NULL;
|
||||
}
|
||||
|
||||
E_md4hash(user_info_in->password.plaintext, nt.hash);
|
||||
user_info_temp->password.hash.nt = talloc(user_info_temp,
|
||||
struct samr_Password);
|
||||
*user_info_temp->password.hash.nt = nt;
|
||||
|
||||
user_info_in = user_info_temp;
|
||||
/* fall through */
|
||||
}
|
||||
case AUTH_PASSWORD_HASH:
|
||||
*user_info_encrypted = user_info_in;
|
||||
break;
|
||||
default:
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
Make a server_info struct from the info3 returned by a domain logon
|
||||
***************************************************************************/
|
||||
NTSTATUS make_server_info_netlogon_validation(TALLOC_CTX *mem_ctx,
|
||||
const char *account_name,
|
||||
uint16_t validation_level,
|
||||
union netr_Validation *validation,
|
||||
struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
struct auth_serversupplied_info *server_info;
|
||||
struct netr_SamBaseInfo *base = NULL;
|
||||
int i;
|
||||
|
||||
switch (validation_level) {
|
||||
case 2:
|
||||
if (!validation || !validation->sam2) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
base = &validation->sam2->base;
|
||||
break;
|
||||
case 3:
|
||||
if (!validation || !validation->sam3) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
base = &validation->sam3->base;
|
||||
break;
|
||||
case 6:
|
||||
if (!validation || !validation->sam6) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
base = &validation->sam6->base;
|
||||
break;
|
||||
default:
|
||||
return NT_STATUS_INVALID_LEVEL;
|
||||
}
|
||||
|
||||
server_info = talloc(mem_ctx, struct auth_serversupplied_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info);
|
||||
|
||||
/*
|
||||
Here is where we should check the list of
|
||||
trusted domains, and verify that the SID
|
||||
matches.
|
||||
*/
|
||||
server_info->account_sid = dom_sid_add_rid(server_info, base->domain_sid, base->rid);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_sid);
|
||||
|
||||
|
||||
server_info->primary_group_sid = dom_sid_add_rid(server_info, base->domain_sid, base->primary_gid);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->primary_group_sid);
|
||||
|
||||
server_info->n_domain_groups = base->groups.count;
|
||||
if (base->groups.count) {
|
||||
server_info->domain_groups = talloc_array(server_info, struct dom_sid*, base->groups.count);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_groups);
|
||||
} else {
|
||||
server_info->domain_groups = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < base->groups.count; i++) {
|
||||
server_info->domain_groups[i] = dom_sid_add_rid(server_info, base->domain_sid, base->groups.rids[i].rid);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_groups[i]);
|
||||
}
|
||||
|
||||
/* Copy 'other' sids. We need to do sid filtering here to
|
||||
prevent possible elevation of privileges. See:
|
||||
|
||||
http://www.microsoft.com/windows2000/techinfo/administration/security/sidfilter.asp
|
||||
*/
|
||||
|
||||
if (validation_level == 3) {
|
||||
struct dom_sid **dgrps = server_info->domain_groups;
|
||||
size_t sidcount = server_info->n_domain_groups + validation->sam3->sidcount;
|
||||
size_t n_dgrps = server_info->n_domain_groups;
|
||||
|
||||
if (validation->sam3->sidcount > 0) {
|
||||
dgrps = talloc_realloc(server_info, dgrps, struct dom_sid*, sidcount);
|
||||
NT_STATUS_HAVE_NO_MEMORY(dgrps);
|
||||
|
||||
for (i = 0; i < validation->sam3->sidcount; i++) {
|
||||
dgrps[n_dgrps + i] = talloc_reference(dgrps, validation->sam3->sids[i].sid);
|
||||
}
|
||||
}
|
||||
|
||||
server_info->n_domain_groups = sidcount;
|
||||
server_info->domain_groups = dgrps;
|
||||
|
||||
/* Where are the 'global' sids?... */
|
||||
}
|
||||
|
||||
if (base->account_name.string) {
|
||||
server_info->account_name = talloc_reference(server_info, base->account_name.string);
|
||||
} else {
|
||||
server_info->account_name = talloc_strdup(server_info, account_name);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_name);
|
||||
}
|
||||
|
||||
server_info->domain_name = talloc_reference(server_info, base->domain.string);
|
||||
server_info->full_name = talloc_reference(server_info, base->full_name.string);
|
||||
server_info->logon_script = talloc_reference(server_info, base->logon_script.string);
|
||||
server_info->profile_path = talloc_reference(server_info, base->profile_path.string);
|
||||
server_info->home_directory = talloc_reference(server_info, base->home_directory.string);
|
||||
server_info->home_drive = talloc_reference(server_info, base->home_drive.string);
|
||||
server_info->logon_server = talloc_reference(server_info, base->logon_server.string);
|
||||
server_info->last_logon = base->last_logon;
|
||||
server_info->last_logoff = base->last_logoff;
|
||||
server_info->acct_expiry = base->acct_expiry;
|
||||
server_info->last_password_change = base->last_password_change;
|
||||
server_info->allow_password_change = base->allow_password_change;
|
||||
server_info->force_password_change = base->force_password_change;
|
||||
server_info->logon_count = base->logon_count;
|
||||
server_info->bad_password_count = base->bad_password_count;
|
||||
server_info->acct_flags = base->acct_flags;
|
||||
|
||||
server_info->authenticated = True;
|
||||
|
||||
/* ensure we are never given NULL session keys */
|
||||
|
||||
if (all_zero(base->key.key, sizeof(base->key.key))) {
|
||||
server_info->user_session_key = data_blob(NULL, 0);
|
||||
} else {
|
||||
server_info->user_session_key = data_blob_talloc(server_info, base->key.key, sizeof(base->key.key));
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->user_session_key.data);
|
||||
}
|
||||
|
||||
if (all_zero(base->LMSessKey.key, sizeof(base->LMSessKey.key))) {
|
||||
server_info->lm_session_key = data_blob(NULL, 0);
|
||||
} else {
|
||||
server_info->lm_session_key = data_blob_talloc(server_info, base->LMSessKey.key, sizeof(base->LMSessKey.key));
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->lm_session_key.data);
|
||||
}
|
||||
|
||||
*_server_info = server_info;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
NTSTATUS auth_anonymous_server_info(TALLOC_CTX *mem_ctx, struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
struct auth_serversupplied_info *server_info;
|
||||
server_info = talloc(mem_ctx, struct auth_serversupplied_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info);
|
||||
|
||||
server_info->account_sid = dom_sid_parse_talloc(server_info, SID_NT_ANONYMOUS);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_sid);
|
||||
|
||||
/* is this correct? */
|
||||
server_info->primary_group_sid = dom_sid_parse_talloc(server_info, SID_BUILTIN_GUESTS);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->primary_group_sid);
|
||||
|
||||
server_info->n_domain_groups = 0;
|
||||
server_info->domain_groups = NULL;
|
||||
|
||||
/* annoying, but the Anonymous really does have a session key,
|
||||
and it is all zeros! */
|
||||
server_info->user_session_key = data_blob_talloc(server_info, NULL, 16);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->user_session_key.data);
|
||||
|
||||
server_info->lm_session_key = data_blob_talloc(server_info, NULL, 16);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->lm_session_key.data);
|
||||
|
||||
data_blob_clear(&server_info->user_session_key);
|
||||
data_blob_clear(&server_info->lm_session_key);
|
||||
|
||||
server_info->account_name = talloc_strdup(server_info, "ANONYMOUS LOGON");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_name);
|
||||
|
||||
server_info->domain_name = talloc_strdup(server_info, "NT AUTHORITY");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_name);
|
||||
|
||||
server_info->full_name = talloc_strdup(server_info, "Anonymous Logon");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->full_name);
|
||||
|
||||
server_info->logon_script = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_script);
|
||||
|
||||
server_info->profile_path = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->profile_path);
|
||||
|
||||
server_info->home_directory = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_directory);
|
||||
|
||||
server_info->home_drive = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_drive);
|
||||
|
||||
server_info->logon_server = talloc_strdup(server_info, lp_netbios_name());
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_server);
|
||||
|
||||
server_info->last_logon = 0;
|
||||
server_info->last_logoff = 0;
|
||||
server_info->acct_expiry = 0;
|
||||
server_info->last_password_change = 0;
|
||||
server_info->allow_password_change = 0;
|
||||
server_info->force_password_change = 0;
|
||||
|
||||
server_info->logon_count = 0;
|
||||
server_info->bad_password_count = 0;
|
||||
|
||||
server_info->acct_flags = ACB_NORMAL;
|
||||
|
||||
server_info->authenticated = False;
|
||||
|
||||
*_server_info = server_info;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS auth_system_server_info(TALLOC_CTX *mem_ctx, struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
struct auth_serversupplied_info *server_info;
|
||||
server_info = talloc(mem_ctx, struct auth_serversupplied_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info);
|
||||
|
||||
server_info->account_sid = dom_sid_parse_talloc(server_info, SID_NT_SYSTEM);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_sid);
|
||||
|
||||
/* is this correct? */
|
||||
server_info->primary_group_sid = dom_sid_parse_talloc(server_info, SID_BUILTIN_ADMINISTRATORS);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->primary_group_sid);
|
||||
|
||||
server_info->n_domain_groups = 0;
|
||||
server_info->domain_groups = NULL;
|
||||
|
||||
/* annoying, but the Anonymous really does have a session key,
|
||||
and it is all zeros! */
|
||||
server_info->user_session_key = data_blob_talloc(server_info, NULL, 16);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->user_session_key.data);
|
||||
|
||||
server_info->lm_session_key = data_blob_talloc(server_info, NULL, 16);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->lm_session_key.data);
|
||||
|
||||
data_blob_clear(&server_info->user_session_key);
|
||||
data_blob_clear(&server_info->lm_session_key);
|
||||
|
||||
server_info->account_name = talloc_strdup(server_info, "SYSTEM");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->account_name);
|
||||
|
||||
server_info->domain_name = talloc_strdup(server_info, "NT AUTHORITY");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->domain_name);
|
||||
|
||||
server_info->full_name = talloc_strdup(server_info, "System");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->full_name);
|
||||
|
||||
server_info->logon_script = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_script);
|
||||
|
||||
server_info->profile_path = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->profile_path);
|
||||
|
||||
server_info->home_directory = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_directory);
|
||||
|
||||
server_info->home_drive = talloc_strdup(server_info, "");
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_drive);
|
||||
|
||||
server_info->logon_server = talloc_strdup(server_info, lp_netbios_name());
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_server);
|
||||
|
||||
server_info->last_logon = 0;
|
||||
server_info->last_logoff = 0;
|
||||
server_info->acct_expiry = 0;
|
||||
server_info->last_password_change = 0;
|
||||
server_info->allow_password_change = 0;
|
||||
server_info->force_password_change = 0;
|
||||
|
||||
server_info->logon_count = 0;
|
||||
server_info->bad_password_count = 0;
|
||||
|
||||
server_info->acct_flags = ACB_NORMAL;
|
||||
|
||||
server_info->authenticated = True;
|
||||
|
||||
*_server_info = server_info;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS auth_generate_session_info(TALLOC_CTX *mem_ctx,
|
||||
struct auth_serversupplied_info *server_info,
|
||||
struct auth_session_info **_session_info)
|
||||
{
|
||||
struct auth_session_info *session_info;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
session_info = talloc(mem_ctx, struct auth_session_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(session_info);
|
||||
|
||||
session_info->server_info = talloc_reference(session_info, server_info);
|
||||
|
||||
/* unless set otherwise, the session key is the user session
|
||||
* key from the auth subsystem */
|
||||
session_info->session_key = server_info->user_session_key;
|
||||
|
||||
nt_status = security_token_create(session_info,
|
||||
server_info->account_sid,
|
||||
server_info->primary_group_sid,
|
||||
server_info->n_domain_groups,
|
||||
server_info->domain_groups,
|
||||
server_info->authenticated,
|
||||
&session_info->security_token);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
session_info->credentials = NULL;
|
||||
|
||||
*_session_info = session_info;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS auth_anonymous_session_info(TALLOC_CTX *parent_ctx,
|
||||
struct auth_session_info **_session_info)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_serversupplied_info *server_info = NULL;
|
||||
struct auth_session_info *session_info = NULL;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
|
||||
nt_status = auth_anonymous_server_info(mem_ctx,
|
||||
&server_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* references the server_info into the session_info */
|
||||
nt_status = auth_generate_session_info(parent_ctx, server_info, &session_info);
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
session_info->credentials = cli_credentials_init(session_info);
|
||||
if (!session_info->credentials) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
cli_credentials_set_conf(session_info->credentials);
|
||||
cli_credentials_set_anonymous(session_info->credentials);
|
||||
|
||||
*_session_info = session_info;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
struct auth_session_info *anonymous_session(TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_session_info *session_info = NULL;
|
||||
nt_status = auth_anonymous_session_info(mem_ctx, &session_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return NULL;
|
||||
}
|
||||
return session_info;
|
||||
}
|
||||
|
||||
NTSTATUS auth_system_session_info(TALLOC_CTX *parent_ctx,
|
||||
struct auth_session_info **_session_info)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_serversupplied_info *server_info = NULL;
|
||||
struct auth_session_info *session_info = NULL;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
|
||||
nt_status = auth_system_server_info(mem_ctx,
|
||||
&server_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* references the server_info into the session_info */
|
||||
nt_status = auth_generate_session_info(parent_ctx, server_info, &session_info);
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
session_info->credentials = cli_credentials_init(session_info);
|
||||
if (!session_info->credentials) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
cli_credentials_set_conf(session_info->credentials);
|
||||
|
||||
if (lp_parm_bool(-1,"system","anonymous", False)) {
|
||||
cli_credentials_set_anonymous(session_info->credentials);
|
||||
} else {
|
||||
cli_credentials_set_machine_account_pending(session_info->credentials);
|
||||
}
|
||||
*_session_info = session_info;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
_PUBLIC_ struct auth_session_info *system_session(TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_session_info *session_info = NULL;
|
||||
nt_status = auth_system_session_info(mem_ctx, &session_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return NULL;
|
||||
}
|
||||
return session_info;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
prints a struct auth_session_info security token to debug output.
|
||||
****************************************************************************/
|
||||
void auth_session_info_debug(int dbg_lev,
|
||||
const struct auth_session_info *session_info)
|
||||
{
|
||||
if (!session_info) {
|
||||
DEBUG(dbg_lev, ("Session Info: (NULL)\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
security_token_debug(dbg_lev, session_info->security_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Squash an NT_STATUS in line with security requirements.
|
||||
* In an attempt to avoid giving the whole game away when users
|
||||
* are authenticating, NT replaces both NT_STATUS_NO_SUCH_USER and
|
||||
* NT_STATUS_WRONG_PASSWORD with NT_STATUS_LOGON_FAILURE in certain situations
|
||||
* (session setups in particular).
|
||||
*
|
||||
* @param nt_status NTSTATUS input for squashing.
|
||||
* @return the 'squashed' nt_status
|
||||
**/
|
||||
NTSTATUS auth_nt_status_squash(NTSTATUS nt_status)
|
||||
{
|
||||
if NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER) {
|
||||
/* Match WinXP and don't give the game away */
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
} else if NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD) {
|
||||
/* Match WinXP and don't give the game away */
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Winbind authentication mechnism
|
||||
|
||||
Copyright (C) Tim Potter 2000
|
||||
Copyright (C) Andrew Bartlett 2001 - 2002
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "nsswitch/winbind_client.h"
|
||||
#include "librpc/gen_ndr/ndr_netlogon.h"
|
||||
#include "librpc/gen_ndr/ndr_winbind.h"
|
||||
#include "lib/messaging/irpc.h"
|
||||
|
||||
static NTSTATUS get_info3_from_ndr(TALLOC_CTX *mem_ctx, struct winbindd_response *response, struct netr_SamInfo3 *info3)
|
||||
{
|
||||
size_t len = response->length - sizeof(struct winbindd_response);
|
||||
if (len > 4) {
|
||||
NTSTATUS status;
|
||||
DATA_BLOB blob;
|
||||
blob.length = len - 4;
|
||||
blob.data = (uint8_t *)(((char *)response->extra_data) + 4);
|
||||
|
||||
status = ndr_pull_struct_blob(&blob, mem_ctx, info3,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_netr_SamInfo3);
|
||||
|
||||
return status;
|
||||
} else {
|
||||
DEBUG(2, ("get_info3_from_ndr: No info3 struct found!\n"));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS winbind_want_check(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info)
|
||||
{
|
||||
if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
/* TODO: maybe limit the user scope to remote users only */
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
Authenticate a user with a challenge/response
|
||||
using the samba3 winbind protocol
|
||||
*/
|
||||
static NTSTATUS winbind_check_password_samba3(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
struct winbindd_request request;
|
||||
struct winbindd_response response;
|
||||
NSS_STATUS result;
|
||||
NTSTATUS nt_status;
|
||||
struct netr_SamInfo3 info3;
|
||||
|
||||
/* Send off request */
|
||||
const struct auth_usersupplied_info *user_info_temp;
|
||||
nt_status = encrypt_user_info(mem_ctx, ctx->auth_ctx,
|
||||
AUTH_PASSWORD_RESPONSE,
|
||||
user_info, &user_info_temp);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
user_info = user_info_temp;
|
||||
|
||||
ZERO_STRUCT(request);
|
||||
ZERO_STRUCT(response);
|
||||
request.flags = WBFLAG_PAM_INFO3_NDR;
|
||||
|
||||
request.data.auth_crap.logon_parameters = user_info->logon_parameters;
|
||||
|
||||
winbind_strcpy(request.data.auth_crap.user,
|
||||
user_info->client.account_name);
|
||||
winbind_strcpy(request.data.auth_crap.domain,
|
||||
user_info->client.domain_name);
|
||||
winbind_strcpy(request.data.auth_crap.workstation,
|
||||
user_info->workstation_name);
|
||||
|
||||
memcpy(request.data.auth_crap.chal, ctx->auth_ctx->challenge.data.data, sizeof(request.data.auth_crap.chal));
|
||||
|
||||
request.data.auth_crap.lm_resp_len = MIN(user_info->password.response.lanman.length,
|
||||
sizeof(request.data.auth_crap.lm_resp));
|
||||
request.data.auth_crap.nt_resp_len = MIN(user_info->password.response.nt.length,
|
||||
sizeof(request.data.auth_crap.nt_resp));
|
||||
|
||||
memcpy(request.data.auth_crap.lm_resp, user_info->password.response.lanman.data,
|
||||
request.data.auth_crap.lm_resp_len);
|
||||
memcpy(request.data.auth_crap.nt_resp, user_info->password.response.nt.data,
|
||||
request.data.auth_crap.nt_resp_len);
|
||||
|
||||
result = winbindd_request(WINBINDD_PAM_AUTH_CRAP, &request, &response);
|
||||
|
||||
nt_status = NT_STATUS(response.data.auth.nt_status);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
if (result == NSS_STATUS_SUCCESS && response.extra_data) {
|
||||
union netr_Validation validation;
|
||||
|
||||
nt_status = get_info3_from_ndr(mem_ctx, &response, &info3);
|
||||
SAFE_FREE(response.extra_data);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
validation.sam3 = &info3;
|
||||
nt_status = make_server_info_netlogon_validation(mem_ctx,
|
||||
user_info->client.account_name,
|
||||
3, &validation,
|
||||
server_info);
|
||||
return nt_status;
|
||||
} else if (result == NSS_STATUS_SUCCESS && !response.extra_data) {
|
||||
DEBUG(0, ("Winbindd authenticated the user [%s]\\[%s], "
|
||||
"but did not include the required info3 reply!\n",
|
||||
user_info->client.domain_name, user_info->client.account_name));
|
||||
return NT_STATUS_INSUFFICIENT_LOGON_INFO;
|
||||
} else if (NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(1, ("Winbindd authentication for [%s]\\[%s] failed, "
|
||||
"but no error code is available!\n",
|
||||
user_info->client.domain_name, user_info->client.account_name));
|
||||
return NT_STATUS_NO_LOGON_SERVERS;
|
||||
}
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
struct winbind_check_password_state {
|
||||
struct winbind_SamLogon req;
|
||||
};
|
||||
|
||||
/*
|
||||
Authenticate a user with a challenge/response
|
||||
using IRPC to the winbind task
|
||||
*/
|
||||
static NTSTATUS winbind_check_password(struct auth_method_context *ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const struct auth_usersupplied_info *user_info,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
NTSTATUS status;
|
||||
uint32_t *winbind_servers;
|
||||
struct winbind_check_password_state *s;
|
||||
const struct auth_usersupplied_info *user_info_new;
|
||||
struct netr_IdentityInfo *identity_info;
|
||||
|
||||
winbind_servers = irpc_servers_byname(ctx->auth_ctx->msg_ctx, "winbind_server");
|
||||
if ((winbind_servers == NULL) || (winbind_servers[0] == 0)) {
|
||||
DEBUG(0, ("Winbind authentication for [%s]\\[%s] failed, "
|
||||
"no winbind_server running!\n",
|
||||
user_info->client.domain_name, user_info->client.account_name));
|
||||
return NT_STATUS_NO_LOGON_SERVERS;
|
||||
}
|
||||
|
||||
s = talloc(mem_ctx, struct winbind_check_password_state);
|
||||
NT_STATUS_HAVE_NO_MEMORY(s);
|
||||
|
||||
if (user_info->flags & USER_INFO_INTERACTIVE_LOGON) {
|
||||
struct netr_PasswordInfo *password_info;
|
||||
|
||||
status = encrypt_user_info(s, ctx->auth_ctx, AUTH_PASSWORD_HASH,
|
||||
user_info, &user_info_new);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
user_info = user_info_new;
|
||||
|
||||
password_info = talloc(s, struct netr_PasswordInfo);
|
||||
NT_STATUS_HAVE_NO_MEMORY(password_info);
|
||||
|
||||
password_info->lmpassword = *user_info->password.hash.lanman;
|
||||
password_info->ntpassword = *user_info->password.hash.nt;
|
||||
|
||||
identity_info = &password_info->identity_info;
|
||||
s->req.in.logon_level = 1;
|
||||
s->req.in.logon.password= password_info;
|
||||
} else {
|
||||
struct netr_NetworkInfo *network_info;
|
||||
const uint8_t *challenge;
|
||||
|
||||
status = encrypt_user_info(s, ctx->auth_ctx, AUTH_PASSWORD_RESPONSE,
|
||||
user_info, &user_info_new);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
user_info = user_info_new;
|
||||
|
||||
network_info = talloc(s, struct netr_NetworkInfo);
|
||||
NT_STATUS_HAVE_NO_MEMORY(network_info);
|
||||
|
||||
status = auth_get_challenge(ctx->auth_ctx, &challenge);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
|
||||
memcpy(network_info->challenge, challenge, sizeof(network_info->challenge));
|
||||
|
||||
network_info->nt.length = user_info->password.response.nt.length;
|
||||
network_info->nt.data = user_info->password.response.nt.data;
|
||||
|
||||
network_info->nt.length = user_info->password.response.lanman.length;
|
||||
network_info->nt.data = user_info->password.response.lanman.data;
|
||||
|
||||
identity_info = &network_info->identity_info;
|
||||
s->req.in.logon_level = 2;
|
||||
s->req.in.logon.network = network_info;
|
||||
}
|
||||
|
||||
identity_info->domain_name.string = user_info->client.domain_name;
|
||||
identity_info->parameter_control = user_info->logon_parameters; /* see MSV1_0_* */
|
||||
identity_info->logon_id_low = 0;
|
||||
identity_info->logon_id_high = 0;
|
||||
identity_info->account_name.string = user_info->client.account_name;
|
||||
identity_info->workstation.string = user_info->workstation_name;
|
||||
|
||||
s->req.in.validation_level = 3;
|
||||
status = IRPC_CALL(ctx->auth_ctx->msg_ctx, winbind_servers[0],
|
||||
winbind, WINBIND_SAMLOGON,
|
||||
&s->req, s);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
|
||||
status = make_server_info_netlogon_validation(mem_ctx,
|
||||
user_info->client.account_name,
|
||||
s->req.in.validation_level,
|
||||
&s->req.out.validation,
|
||||
server_info);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static const struct auth_operations winbind_samba3_ops = {
|
||||
.name = "winbind_samba3",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = winbind_want_check,
|
||||
.check_password = winbind_check_password_samba3
|
||||
};
|
||||
|
||||
static const struct auth_operations winbind_ops = {
|
||||
.name = "winbind",
|
||||
.get_challenge = auth_get_challenge_not_implemented,
|
||||
.want_check = winbind_want_check,
|
||||
.check_password = winbind_check_password
|
||||
};
|
||||
|
||||
NTSTATUS auth_winbind_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
ret = auth_register(&winbind_samba3_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'winbind_samba3' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = auth_register(&winbind_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register 'winbind' auth backend!\n"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
###############################
|
||||
# start SMB_EXT_LIB_PAM
|
||||
# check for security/pam_appl.h and -lpam
|
||||
AC_CHECK_HEADERS(security/pam_appl.h)
|
||||
AC_CHECK_LIB_EXT(pam, PAM_LIBS, pam_start)
|
||||
if test x"$ac_cv_header_security_pam_appl_h" = x"yes" -a x"$ac_cv_lib_ext_pam_pam_start" = x"yes";then
|
||||
SMB_ENABLE(PAM,YES)
|
||||
fi
|
||||
SMB_EXT_LIB(PAM, $PAM_LIBS)
|
||||
# end SMB_EXT_LIB_PAM
|
||||
###############################
|
||||
|
||||
################################################
|
||||
# test for where we get crypt() from
|
||||
AC_CHECK_LIB_EXT(crypt, CRYPT_LIBS, crypt)
|
||||
SMB_ENABLE(CRYPT,YES)
|
||||
SMB_EXT_LIB(CRYPT, $CRYPT_LIBS)
|
||||
|
||||
AC_CHECK_FUNCS(crypt16 getauthuid getpwanam)
|
||||
|
||||
AC_CHECK_HEADERS(sasl/sasl.h)
|
||||
AC_CHECK_LIB_EXT(sasl2, SASL_LIBS, sasl_client_init)
|
||||
SMB_EXT_LIB(SASL, $SASL_LIBS)
|
||||
|
||||
if test x"$ac_cv_header_sasl_sasl_h" = x"yes" -a x"$ac_cv_lib_ext_sasl2_sasl_client_init" = x"yes";then
|
||||
SMB_ENABLE(SASL,YES)
|
||||
SMB_ENABLE(cyrus_sasl,YES)
|
||||
else
|
||||
SMB_ENABLE(cyrus_sasl,NO)
|
||||
fi
|
||||
@@ -0,0 +1,79 @@
|
||||
# auth server subsystem
|
||||
include gensec/config.mk
|
||||
include kerberos/config.mk
|
||||
include ntlmssp/config.mk
|
||||
include credentials/config.mk
|
||||
|
||||
[SUBSYSTEM::auth_sam]
|
||||
PRIVATE_PROTO_HEADER = auth_sam.h
|
||||
OBJ_FILES = sam.o auth_sam_reply.o ntlm_check.o
|
||||
PUBLIC_DEPENDENCIES = SAMDB
|
||||
|
||||
#######################
|
||||
# Start MODULE auth_sam
|
||||
[MODULE::auth_sam_module]
|
||||
# gensec_krb5 and gensec_gssapi depend on it
|
||||
INIT_FUNCTION = auth_sam_init
|
||||
SUBSYSTEM = auth
|
||||
OBJ_FILES = auth_sam.o
|
||||
PUBLIC_DEPENDENCIES = \
|
||||
SAMDB auth_sam
|
||||
# End MODULE auth_sam
|
||||
#######################
|
||||
|
||||
#######################
|
||||
# Start MODULE auth_anonymous
|
||||
[MODULE::auth_anonymous]
|
||||
INIT_FUNCTION = auth_anonymous_init
|
||||
SUBSYSTEM = auth
|
||||
OBJ_FILES = auth_anonymous.o
|
||||
# End MODULE auth_anonymous
|
||||
#######################
|
||||
|
||||
#######################
|
||||
# Start MODULE auth_winbind
|
||||
[MODULE::auth_winbind]
|
||||
INIT_FUNCTION = auth_winbind_init
|
||||
SUBSYSTEM = auth
|
||||
OBJ_FILES = auth_winbind.o
|
||||
PUBLIC_DEPENDENCIES = NDR_WINBIND MESSAGING LIBWINBIND-CLIENT
|
||||
# End MODULE auth_winbind
|
||||
#######################
|
||||
|
||||
#######################
|
||||
# Start MODULE auth_developer
|
||||
[MODULE::auth_developer]
|
||||
INIT_FUNCTION = auth_developer_init
|
||||
SUBSYSTEM = auth
|
||||
OBJ_FILES = auth_developer.o
|
||||
# End MODULE auth_developer
|
||||
#######################
|
||||
|
||||
#######################
|
||||
# Start MODULE auth_unix
|
||||
[MODULE::auth_unix]
|
||||
INIT_FUNCTION = auth_unix_init
|
||||
SUBSYSTEM = auth
|
||||
OBJ_FILES = auth_unix.o
|
||||
PUBLIC_DEPENDENCIES = CRYPT PAM PAM_ERRORS
|
||||
# End MODULE auth_unix
|
||||
#######################
|
||||
|
||||
[SUBSYSTEM::PAM_ERRORS]
|
||||
PRIVATE_PROTO_HEADER = pam_errors.h
|
||||
OBJ_FILES = pam_errors.o
|
||||
|
||||
#######################
|
||||
# Start SUBSYSTEM auth
|
||||
[SUBSYSTEM::auth]
|
||||
#VERSION = 0.0.1
|
||||
#SO_VERSION = 0
|
||||
PUBLIC_HEADERS = auth.h
|
||||
PUBLIC_PROTO_HEADER = auth_proto.h
|
||||
OBJ_FILES = \
|
||||
auth.o \
|
||||
auth_util.o \
|
||||
auth_simple.o
|
||||
PUBLIC_DEPENDENCIES = LIBSECURITY SAMDB CREDENTIALS
|
||||
# End SUBSYSTEM auth
|
||||
#######################
|
||||
@@ -0,0 +1,24 @@
|
||||
#################################
|
||||
# Start SUBSYSTEM CREDENTIALS
|
||||
[SUBSYSTEM::CREDENTIALS]
|
||||
PUBLIC_PROTO_HEADER = credentials_proto.h
|
||||
PUBLIC_HEADERS = credentials.h
|
||||
OBJ_FILES = credentials.o \
|
||||
credentials_files.o \
|
||||
credentials_ntlm.o
|
||||
PUBLIC_DEPENDENCIES = \
|
||||
LIBCLI_AUTH SECRETS LIBCRYPTO KERBEROS
|
||||
PRIVATE_DEPENDENCIES = CREDENTIALS_KRB5
|
||||
# End SUBSYSTEM CREDENTIALS
|
||||
#################################
|
||||
|
||||
#################################
|
||||
# Start SUBSYSTEM CREDENTIALS
|
||||
[SUBSYSTEM::CREDENTIALS_KRB5]
|
||||
PUBLIC_PROTO_HEADER = credentials_krb5_proto.h
|
||||
PUBLIC_HEADERS = credentials_krb5.h
|
||||
OBJ_FILES = credentials_krb5.o
|
||||
PUBLIC_DEPENDENCIES = \
|
||||
HEIMDAL_GSSAPI
|
||||
# End SUBSYSTEM CREDENTIALS
|
||||
#################################
|
||||
@@ -0,0 +1,717 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
User credentials handling
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2005
|
||||
Copyright (C) Tim Potter 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/credentials/credentials_krb5.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
|
||||
/**
|
||||
* Create a new credentials structure
|
||||
* @param mem_ctx TALLOC_CTX parent for credentials structure
|
||||
*/
|
||||
struct cli_credentials *cli_credentials_init(TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct cli_credentials *cred = talloc(mem_ctx, struct cli_credentials);
|
||||
if (!cred) {
|
||||
return cred;
|
||||
}
|
||||
|
||||
cred->netlogon_creds = NULL;
|
||||
cred->machine_account_pending = False;
|
||||
cred->workstation_obtained = CRED_UNINITIALISED;
|
||||
cred->username_obtained = CRED_UNINITIALISED;
|
||||
cred->password_obtained = CRED_UNINITIALISED;
|
||||
cred->domain_obtained = CRED_UNINITIALISED;
|
||||
cred->realm_obtained = CRED_UNINITIALISED;
|
||||
cred->ccache_obtained = CRED_UNINITIALISED;
|
||||
cred->client_gss_creds_obtained = CRED_UNINITIALISED;
|
||||
cred->server_gss_creds_obtained = CRED_UNINITIALISED;
|
||||
cred->keytab_obtained = CRED_UNINITIALISED;
|
||||
cred->principal_obtained = CRED_UNINITIALISED;
|
||||
|
||||
cred->old_password = NULL;
|
||||
cred->smb_krb5_context = NULL;
|
||||
cred->salt_principal = NULL;
|
||||
cred->machine_account = False;
|
||||
|
||||
cred->bind_dn = NULL;
|
||||
|
||||
cred->tries = 3;
|
||||
cred->callback_running = False;
|
||||
|
||||
cli_credentials_set_kerberos_state(cred, CRED_AUTO_USE_KERBEROS);
|
||||
|
||||
return cred;
|
||||
}
|
||||
|
||||
void cli_credentials_set_kerberos_state(struct cli_credentials *creds,
|
||||
enum credentials_use_kerberos use_kerberos)
|
||||
{
|
||||
creds->use_kerberos = use_kerberos;
|
||||
}
|
||||
|
||||
enum credentials_use_kerberos cli_credentials_get_kerberos_state(struct cli_credentials *creds)
|
||||
{
|
||||
return creds->use_kerberos;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the username for this credentials context.
|
||||
* @param cred credentials context
|
||||
* @retval The username set on this context.
|
||||
* @note Return value will never be NULL except by programmer error.
|
||||
*/
|
||||
const char *cli_credentials_get_username(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
if (cred->username_obtained == CRED_CALLBACK &&
|
||||
!cred->callback_running) {
|
||||
cred->callback_running = True;
|
||||
cred->username = cred->username_cb(cred);
|
||||
cred->callback_running = False;
|
||||
cred->username_obtained = CRED_SPECIFIED;
|
||||
}
|
||||
|
||||
return cred->username;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_username(struct cli_credentials *cred,
|
||||
const char *val, enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->username_obtained) {
|
||||
cred->username = talloc_strdup(cred, val);
|
||||
cred->username_obtained = obtained;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_username_callback(struct cli_credentials *cred,
|
||||
const char *(*username_cb) (struct cli_credentials *))
|
||||
{
|
||||
if (cred->username_obtained < CRED_CALLBACK) {
|
||||
cred->username_cb = username_cb;
|
||||
cred->username_obtained = CRED_CALLBACK;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_bind_dn(struct cli_credentials *cred,
|
||||
const char *bind_dn)
|
||||
{
|
||||
cred->bind_dn = talloc_strdup(cred, bind_dn);
|
||||
return True;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the BIND DN for this credentials context.
|
||||
* @param cred credentials context
|
||||
* @retval The username set on this context.
|
||||
* @note Return value will be NULL if not specified explictly
|
||||
*/
|
||||
const char *cli_credentials_get_bind_dn(struct cli_credentials *cred)
|
||||
{
|
||||
return cred->bind_dn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the client principal for this credentials context.
|
||||
* @param cred credentials context
|
||||
* @retval The username set on this context.
|
||||
* @note Return value will never be NULL except by programmer error.
|
||||
*/
|
||||
const char *cli_credentials_get_principal(struct cli_credentials *cred, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
if (cred->principal_obtained == CRED_CALLBACK &&
|
||||
!cred->callback_running) {
|
||||
cred->callback_running = True;
|
||||
cred->principal = cred->principal_cb(cred);
|
||||
cred->callback_running = False;
|
||||
cred->principal_obtained = CRED_SPECIFIED;
|
||||
}
|
||||
|
||||
if (cred->principal_obtained < cred->username_obtained) {
|
||||
if (cred->domain_obtained > cred->realm_obtained) {
|
||||
return talloc_asprintf(mem_ctx, "%s@%s",
|
||||
cli_credentials_get_username(cred),
|
||||
cli_credentials_get_domain(cred));
|
||||
} else {
|
||||
return talloc_asprintf(mem_ctx, "%s@%s",
|
||||
cli_credentials_get_username(cred),
|
||||
cli_credentials_get_realm(cred));
|
||||
}
|
||||
}
|
||||
return talloc_reference(mem_ctx, cred->principal);
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_principal(struct cli_credentials *cred,
|
||||
const char *val,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->principal_obtained) {
|
||||
cred->principal = talloc_strdup(cred, val);
|
||||
cred->principal_obtained = obtained;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/* Set a callback to get the principal. This could be a popup dialog,
|
||||
* a terminal prompt or similar. */
|
||||
|
||||
BOOL cli_credentials_set_principal_callback(struct cli_credentials *cred,
|
||||
const char *(*principal_cb) (struct cli_credentials *))
|
||||
{
|
||||
if (cred->principal_obtained < CRED_CALLBACK) {
|
||||
cred->principal_cb = principal_cb;
|
||||
cred->principal_obtained = CRED_CALLBACK;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/* Some of our tools are 'anonymous by default'. This is a single
|
||||
* function to determine if authentication has been explicitly
|
||||
* requested */
|
||||
|
||||
BOOL cli_credentials_authentication_requested(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->bind_dn) {
|
||||
return True;
|
||||
}
|
||||
|
||||
if (cli_credentials_is_anonymous(cred)){
|
||||
return False;
|
||||
}
|
||||
|
||||
if (cred->principal_obtained >= CRED_SPECIFIED) {
|
||||
return True;
|
||||
}
|
||||
if (cred->username_obtained >= CRED_SPECIFIED) {
|
||||
return True;
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the password for this credentials context.
|
||||
* @param cred credentials context
|
||||
* @retval If set, the cleartext password, otherwise NULL
|
||||
*/
|
||||
const char *cli_credentials_get_password(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
if (cred->password_obtained == CRED_CALLBACK &&
|
||||
!cred->callback_running) {
|
||||
cred->callback_running = True;
|
||||
cred->password = cred->password_cb(cred);
|
||||
cred->callback_running = False;
|
||||
cred->password_obtained = CRED_CALLBACK_RESULT;
|
||||
}
|
||||
|
||||
return cred->password;
|
||||
}
|
||||
|
||||
/* Set a password on the credentials context, including an indication
|
||||
* of 'how' the password was obtained */
|
||||
|
||||
BOOL cli_credentials_set_password(struct cli_credentials *cred,
|
||||
const char *val,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->password_obtained) {
|
||||
cred->password = talloc_strdup(cred, val);
|
||||
cred->password_obtained = obtained;
|
||||
|
||||
cred->nt_hash = NULL;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_password_callback(struct cli_credentials *cred,
|
||||
const char *(*password_cb) (struct cli_credentials *))
|
||||
{
|
||||
if (cred->password_obtained < CRED_CALLBACK) {
|
||||
cred->password_cb = password_cb;
|
||||
cred->password_obtained = CRED_CALLBACK;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the 'old' password for this credentials context (used for join accounts).
|
||||
* @param cred credentials context
|
||||
* @retval If set, the cleartext password, otherwise NULL
|
||||
*/
|
||||
const char *cli_credentials_get_old_password(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
return cred->old_password;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_old_password(struct cli_credentials *cred,
|
||||
const char *val,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
cred->old_password = talloc_strdup(cred, val);
|
||||
return True;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the password, in the form MD4(unicode(password)) for this credentials context.
|
||||
*
|
||||
* Sometimes we only have this much of the password, while the rest of
|
||||
* the time this call avoids calling E_md4hash themselves.
|
||||
*
|
||||
* @param cred credentials context
|
||||
* @retval If set, the cleartext password, otherwise NULL
|
||||
*/
|
||||
const struct samr_Password *cli_credentials_get_nt_hash(struct cli_credentials *cred,
|
||||
TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
const char *password = cli_credentials_get_password(cred);
|
||||
|
||||
if (password) {
|
||||
struct samr_Password *nt_hash = talloc(mem_ctx, struct samr_Password);
|
||||
if (!nt_hash) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
E_md4hash(password, nt_hash->hash);
|
||||
|
||||
return nt_hash;
|
||||
} else {
|
||||
return cred->nt_hash;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_nt_hash(struct cli_credentials *cred,
|
||||
const struct samr_Password *nt_hash,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->password_obtained) {
|
||||
cli_credentials_set_password(cred, NULL, obtained);
|
||||
cred->nt_hash = talloc(cred, struct samr_Password);
|
||||
*cred->nt_hash = *nt_hash;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the 'short' or 'NetBIOS' domain for this credentials context.
|
||||
* @param cred credentials context
|
||||
* @retval The domain set on this context.
|
||||
* @note Return value will never be NULL except by programmer error.
|
||||
*/
|
||||
const char *cli_credentials_get_domain(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
if (cred->domain_obtained == CRED_CALLBACK &&
|
||||
!cred->callback_running) {
|
||||
cred->callback_running = True;
|
||||
cred->domain = cred->domain_cb(cred);
|
||||
cred->callback_running = False;
|
||||
cred->domain_obtained = CRED_SPECIFIED;
|
||||
}
|
||||
|
||||
return cred->domain;
|
||||
}
|
||||
|
||||
|
||||
BOOL cli_credentials_set_domain(struct cli_credentials *cred,
|
||||
const char *val,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->domain_obtained) {
|
||||
/* it is important that the domain be in upper case,
|
||||
* particularly for the sensitive NTLMv2
|
||||
* calculations */
|
||||
cred->domain = strupper_talloc(cred, val);
|
||||
cred->domain_obtained = obtained;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_domain_callback(struct cli_credentials *cred,
|
||||
const char *(*domain_cb) (struct cli_credentials *))
|
||||
{
|
||||
if (cred->domain_obtained < CRED_CALLBACK) {
|
||||
cred->domain_cb = domain_cb;
|
||||
cred->domain_obtained = CRED_CALLBACK;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the Kerberos realm for this credentials context.
|
||||
* @param cred credentials context
|
||||
* @retval The realm set on this context.
|
||||
* @note Return value will never be NULL except by programmer error.
|
||||
*/
|
||||
const char *cli_credentials_get_realm(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
if (cred->realm_obtained == CRED_CALLBACK &&
|
||||
!cred->callback_running) {
|
||||
cred->callback_running = True;
|
||||
cred->realm = cred->realm_cb(cred);
|
||||
cred->callback_running = False;
|
||||
cred->realm_obtained = CRED_SPECIFIED;
|
||||
}
|
||||
|
||||
return cred->realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the realm for this credentials context, and force it to
|
||||
* uppercase for the sainity of our local kerberos libraries
|
||||
*/
|
||||
BOOL cli_credentials_set_realm(struct cli_credentials *cred,
|
||||
const char *val,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->realm_obtained) {
|
||||
cred->realm = strupper_talloc(cred, val);
|
||||
cred->realm_obtained = obtained;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_realm_callback(struct cli_credentials *cred,
|
||||
const char *(*realm_cb) (struct cli_credentials *))
|
||||
{
|
||||
if (cred->realm_obtained < CRED_CALLBACK) {
|
||||
cred->realm_cb = realm_cb;
|
||||
cred->realm_obtained = CRED_CALLBACK;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the 'short' or 'NetBIOS' workstation name for this credentials context.
|
||||
*
|
||||
* @param cred credentials context
|
||||
* @retval The workstation name set on this context.
|
||||
* @note Return value will never be NULL except by programmer error.
|
||||
*/
|
||||
const char *cli_credentials_get_workstation(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->workstation_obtained == CRED_CALLBACK &&
|
||||
!cred->callback_running) {
|
||||
cred->callback_running = True;
|
||||
cred->workstation = cred->workstation_cb(cred);
|
||||
cred->callback_running = False;
|
||||
cred->workstation_obtained = CRED_SPECIFIED;
|
||||
}
|
||||
|
||||
return cred->workstation;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_workstation(struct cli_credentials *cred,
|
||||
const char *val,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
if (obtained >= cred->workstation_obtained) {
|
||||
cred->workstation = talloc_strdup(cred, val);
|
||||
cred->workstation_obtained = obtained;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
BOOL cli_credentials_set_workstation_callback(struct cli_credentials *cred,
|
||||
const char *(*workstation_cb) (struct cli_credentials *))
|
||||
{
|
||||
if (cred->workstation_obtained < CRED_CALLBACK) {
|
||||
cred->workstation_cb = workstation_cb;
|
||||
cred->workstation_obtained = CRED_CALLBACK;
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string, typically obtained from a -U argument, parse it into domain, username, realm and password fields
|
||||
*
|
||||
* The format accepted is [domain\\]user[%password] or user[@realm][%password]
|
||||
*
|
||||
* @param credentials Credentials structure on which to set the password
|
||||
* @param data the string containing the username, password etc
|
||||
* @param obtained This enum describes how 'specified' this password is
|
||||
*/
|
||||
|
||||
void cli_credentials_parse_string(struct cli_credentials *credentials, const char *data, enum credentials_obtained obtained)
|
||||
{
|
||||
char *uname, *p;
|
||||
|
||||
if (strcmp("%",data) == 0) {
|
||||
cli_credentials_set_anonymous(credentials);
|
||||
return;
|
||||
}
|
||||
|
||||
uname = talloc_strdup(credentials, data);
|
||||
if ((p = strchr_m(uname,'%'))) {
|
||||
*p = 0;
|
||||
cli_credentials_set_password(credentials, p+1, obtained);
|
||||
}
|
||||
|
||||
if ((p = strchr_m(uname,'@'))) {
|
||||
cli_credentials_set_principal(credentials, uname, obtained);
|
||||
*p = 0;
|
||||
cli_credentials_set_realm(credentials, p+1, obtained);
|
||||
return;
|
||||
} else if ((p = strchr_m(uname,'\\')) || (p = strchr_m(uname, '/'))) {
|
||||
*p = 0;
|
||||
cli_credentials_set_domain(credentials, uname, obtained);
|
||||
uname = p+1;
|
||||
}
|
||||
cli_credentials_set_username(credentials, uname, obtained);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a a credentials structure, print it as a string
|
||||
*
|
||||
* The format output is [domain\\]user[%password] or user[@realm][%password]
|
||||
*
|
||||
* @param credentials Credentials structure on which to set the password
|
||||
* @param mem_ctx The memory context to place the result on
|
||||
*/
|
||||
|
||||
const char *cli_credentials_get_unparsed_name(struct cli_credentials *credentials, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
const char *bind_dn = cli_credentials_get_bind_dn(credentials);
|
||||
const char *domain;
|
||||
const char *username;
|
||||
const char *name;
|
||||
|
||||
if (bind_dn) {
|
||||
name = talloc_reference(mem_ctx, bind_dn);
|
||||
} else {
|
||||
cli_credentials_get_ntlm_username_domain(credentials, mem_ctx, &username, &domain);
|
||||
if (domain && domain[0]) {
|
||||
name = talloc_asprintf(mem_ctx, "%s\\%s",
|
||||
domain, username);
|
||||
} else {
|
||||
name = talloc_asprintf(mem_ctx, "%s",
|
||||
username);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies default values for domain, workstation and realm
|
||||
* from the smb.conf configuration file
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
*/
|
||||
void cli_credentials_set_conf(struct cli_credentials *cred)
|
||||
{
|
||||
cli_credentials_set_username(cred, "", CRED_UNINITIALISED);
|
||||
cli_credentials_set_domain(cred, lp_workgroup(), CRED_UNINITIALISED);
|
||||
cli_credentials_set_workstation(cred, lp_netbios_name(), CRED_UNINITIALISED);
|
||||
cli_credentials_set_realm(cred, lp_realm(), CRED_UNINITIALISED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess defaults for credentials from environment variables,
|
||||
* and from the configuration file
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
*/
|
||||
void cli_credentials_guess(struct cli_credentials *cred)
|
||||
{
|
||||
char *p;
|
||||
|
||||
cli_credentials_set_conf(cred);
|
||||
|
||||
if (getenv("LOGNAME")) {
|
||||
cli_credentials_set_username(cred, getenv("LOGNAME"), CRED_GUESS_ENV);
|
||||
}
|
||||
|
||||
if (getenv("USER")) {
|
||||
cli_credentials_parse_string(cred, getenv("USER"), CRED_GUESS_ENV);
|
||||
if ((p = strchr_m(getenv("USER"),'%'))) {
|
||||
memset(p,0,strlen(cred->password));
|
||||
}
|
||||
}
|
||||
|
||||
if (getenv("DOMAIN")) {
|
||||
cli_credentials_set_domain(cred, getenv("DOMAIN"), CRED_GUESS_ENV);
|
||||
}
|
||||
|
||||
if (getenv("PASSWD")) {
|
||||
cli_credentials_set_password(cred, getenv("PASSWD"), CRED_GUESS_ENV);
|
||||
}
|
||||
|
||||
if (getenv("PASSWD_FD")) {
|
||||
cli_credentials_parse_password_fd(cred, atoi(getenv("PASSWD_FD")), CRED_GUESS_FILE);
|
||||
}
|
||||
|
||||
if (getenv("PASSWD_FILE")) {
|
||||
cli_credentials_parse_password_file(cred, getenv("PASSWD_FILE"), CRED_GUESS_FILE);
|
||||
}
|
||||
|
||||
if (cli_credentials_get_kerberos_state(cred) != CRED_DONT_USE_KERBEROS) {
|
||||
cli_credentials_set_ccache(cred, NULL, CRED_GUESS_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach NETLOGON credentials for use with SCHANNEL
|
||||
*/
|
||||
|
||||
void cli_credentials_set_netlogon_creds(struct cli_credentials *cred,
|
||||
struct creds_CredentialState *netlogon_creds)
|
||||
{
|
||||
cred->netlogon_creds = talloc_reference(cred, netlogon_creds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return attached NETLOGON credentials
|
||||
*/
|
||||
|
||||
struct creds_CredentialState *cli_credentials_get_netlogon_creds(struct cli_credentials *cred)
|
||||
{
|
||||
return cred->netlogon_creds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set NETLOGON secure channel type
|
||||
*/
|
||||
|
||||
void cli_credentials_set_secure_channel_type(struct cli_credentials *cred,
|
||||
enum netr_SchannelType secure_channel_type)
|
||||
{
|
||||
cred->secure_channel_type = secure_channel_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return NETLOGON secure chanel type
|
||||
*/
|
||||
|
||||
enum netr_SchannelType cli_credentials_get_secure_channel_type(struct cli_credentials *cred)
|
||||
{
|
||||
return cred->secure_channel_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in a credentials structure as the anonymous user
|
||||
*/
|
||||
void cli_credentials_set_anonymous(struct cli_credentials *cred)
|
||||
{
|
||||
cli_credentials_set_username(cred, "", CRED_SPECIFIED);
|
||||
cli_credentials_set_domain(cred, "", CRED_SPECIFIED);
|
||||
cli_credentials_set_password(cred, NULL, CRED_SPECIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a credentials context as anonymous or authenticated
|
||||
* @retval True if anonymous, False if a username is specified
|
||||
*/
|
||||
|
||||
BOOL cli_credentials_is_anonymous(struct cli_credentials *cred)
|
||||
{
|
||||
const char *username;
|
||||
|
||||
if (cred->machine_account_pending) {
|
||||
cli_credentials_set_machine_account(cred);
|
||||
}
|
||||
|
||||
username = cli_credentials_get_username(cred);
|
||||
|
||||
/* Yes, it is deliberate that we die if we have a NULL pointer
|
||||
* here - anonymous is "", not NULL, which is 'never specified,
|
||||
* never guessed', ie programmer bug */
|
||||
if (!username[0]) {
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the current password for a credentials struct as wrong. This will
|
||||
* cause the password to be prompted again (if a callback is set).
|
||||
*
|
||||
* This will decrement the number of times the password can be tried.
|
||||
*
|
||||
* @retval whether the credentials struct is finished
|
||||
*/
|
||||
BOOL cli_credentials_wrong_password(struct cli_credentials *cred)
|
||||
{
|
||||
if (cred->password_obtained != CRED_CALLBACK_RESULT) {
|
||||
return False;
|
||||
}
|
||||
|
||||
cred->password_obtained = CRED_CALLBACK;
|
||||
|
||||
cred->tries--;
|
||||
|
||||
return (cred->tries > 0);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
samba -- Unix SMB/CIFS implementation.
|
||||
|
||||
Client credentials structure
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2004-2006
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
#ifndef __CREDENTIALS_H__
|
||||
#define __CREDENTIALS_H__
|
||||
|
||||
#include "librpc/gen_ndr/misc.h"
|
||||
|
||||
struct ccache_container;
|
||||
|
||||
/* In order of priority */
|
||||
enum credentials_obtained {
|
||||
CRED_UNINITIALISED = 0, /* We don't even have a guess yet */
|
||||
CRED_GUESS_ENV, /* Current value should be used, which was guessed */
|
||||
CRED_CALLBACK, /* Callback should be used to obtain value */
|
||||
CRED_GUESS_FILE, /* A guess from a file (or file pointed at in env variable) */
|
||||
CRED_CALLBACK_RESULT, /* Value was obtained from a callback */
|
||||
CRED_SPECIFIED /* Was explicitly specified on the command-line */
|
||||
};
|
||||
|
||||
enum credentials_use_kerberos {
|
||||
CRED_AUTO_USE_KERBEROS = 0, /* Default, we try kerberos if available */
|
||||
CRED_DONT_USE_KERBEROS, /* Sometimes trying kerberos just does 'bad things', so don't */
|
||||
CRED_MUST_USE_KERBEROS /* Sometimes administrators are parinoid, so always do kerberos */
|
||||
};
|
||||
|
||||
#define CLI_CRED_NTLM2 0x01
|
||||
#define CLI_CRED_NTLMv2_AUTH 0x02
|
||||
#define CLI_CRED_LANMAN_AUTH 0x04
|
||||
#define CLI_CRED_NTLM_AUTH 0x08
|
||||
#define CLI_CRED_CLEAR_AUTH 0x10 /* TODO: Push cleartext auth with this flag */
|
||||
|
||||
struct cli_credentials {
|
||||
enum credentials_obtained workstation_obtained;
|
||||
enum credentials_obtained username_obtained;
|
||||
enum credentials_obtained password_obtained;
|
||||
enum credentials_obtained domain_obtained;
|
||||
enum credentials_obtained realm_obtained;
|
||||
enum credentials_obtained ccache_obtained;
|
||||
enum credentials_obtained client_gss_creds_obtained;
|
||||
enum credentials_obtained principal_obtained;
|
||||
enum credentials_obtained keytab_obtained;
|
||||
enum credentials_obtained server_gss_creds_obtained;
|
||||
|
||||
const char *workstation;
|
||||
const char *username;
|
||||
const char *password;
|
||||
const char *old_password;
|
||||
const char *domain;
|
||||
const char *realm;
|
||||
const char *principal;
|
||||
const char *salt_principal;
|
||||
|
||||
const char *bind_dn;
|
||||
|
||||
struct samr_Password *nt_hash;
|
||||
|
||||
struct ccache_container *ccache;
|
||||
struct gssapi_creds_container *client_gss_creds;
|
||||
struct keytab_container *keytab;
|
||||
struct gssapi_creds_container *server_gss_creds;
|
||||
|
||||
const char *(*workstation_cb) (struct cli_credentials *);
|
||||
const char *(*password_cb) (struct cli_credentials *);
|
||||
const char *(*username_cb) (struct cli_credentials *);
|
||||
const char *(*domain_cb) (struct cli_credentials *);
|
||||
const char *(*realm_cb) (struct cli_credentials *);
|
||||
const char *(*principal_cb) (struct cli_credentials *);
|
||||
|
||||
/* Private handle for the callback routines to use */
|
||||
void *priv_data;
|
||||
|
||||
struct creds_CredentialState *netlogon_creds;
|
||||
enum netr_SchannelType secure_channel_type;
|
||||
int kvno;
|
||||
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
|
||||
/* We are flagged to get machine account details from the
|
||||
* secrets.ldb when we are asked for a username or password */
|
||||
|
||||
BOOL machine_account_pending;
|
||||
|
||||
/* Is this a machine account? */
|
||||
BOOL machine_account;
|
||||
|
||||
/* Should we be trying to use kerberos? */
|
||||
enum credentials_use_kerberos use_kerberos;
|
||||
|
||||
/* Number of retries left before bailing out */
|
||||
int tries;
|
||||
|
||||
/* Whether any callback is currently running */
|
||||
BOOL callback_running;
|
||||
};
|
||||
|
||||
#include "auth/credentials/credentials_proto.h"
|
||||
|
||||
#endif /* __CREDENTIALS_H__ */
|
||||
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
User credentials handling (as regards on-disk files)
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2005
|
||||
Copyright (C) Tim Potter 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/ldb/include/ldb.h"
|
||||
#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */
|
||||
#include "param/secrets.h"
|
||||
#include "system/filesys.h"
|
||||
#include "db_wrap.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/credentials/credentials_krb5.h"
|
||||
|
||||
/**
|
||||
* Read a file descriptor, and parse it for a password (eg from a file or stdin)
|
||||
*
|
||||
* @param credentials Credentials structure on which to set the password
|
||||
* @param fd open file descriptor to read the password from
|
||||
* @param obtained This enum describes how 'specified' this password is
|
||||
*/
|
||||
|
||||
BOOL cli_credentials_parse_password_fd(struct cli_credentials *credentials,
|
||||
int fd, enum credentials_obtained obtained)
|
||||
{
|
||||
char *p;
|
||||
char pass[128];
|
||||
|
||||
for(p = pass, *p = '\0'; /* ensure that pass is null-terminated */
|
||||
p && p - pass < sizeof(pass);) {
|
||||
switch (read(fd, p, 1)) {
|
||||
case 1:
|
||||
if (*p != '\n' && *p != '\0') {
|
||||
*++p = '\0'; /* advance p, and null-terminate pass */
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
case 0:
|
||||
if (p - pass) {
|
||||
*p = '\0'; /* null-terminate it, just in case... */
|
||||
p = NULL; /* then force the loop condition to become false */
|
||||
break;
|
||||
} else {
|
||||
fprintf(stderr, "Error reading password from file descriptor %d: %s\n", fd, "empty password\n");
|
||||
return False;
|
||||
}
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Error reading password from file descriptor %d: %s\n",
|
||||
fd, strerror(errno));
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
cli_credentials_set_password(credentials, pass, obtained);
|
||||
return True;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a named file, and parse it for a password
|
||||
*
|
||||
* @param credentials Credentials structure on which to set the password
|
||||
* @param file a named file to read the password from
|
||||
* @param obtained This enum describes how 'specified' this password is
|
||||
*/
|
||||
|
||||
BOOL cli_credentials_parse_password_file(struct cli_credentials *credentials, const char *file, enum credentials_obtained obtained)
|
||||
{
|
||||
int fd = open(file, O_RDONLY, 0);
|
||||
BOOL ret;
|
||||
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Error opening PASSWD_FILE %s: %s\n",
|
||||
file, strerror(errno));
|
||||
return False;
|
||||
}
|
||||
|
||||
ret = cli_credentials_parse_password_fd(credentials, fd, obtained);
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a named file, and parse it for username, domain, realm and password
|
||||
*
|
||||
* @param credentials Credentials structure on which to set the password
|
||||
* @param file a named file to read the details from
|
||||
* @param obtained This enum describes how 'specified' this password is
|
||||
*/
|
||||
|
||||
BOOL cli_credentials_parse_file(struct cli_credentials *cred, const char *file, enum credentials_obtained obtained)
|
||||
{
|
||||
uint16_t len = 0;
|
||||
char *ptr, *val, *param;
|
||||
char **lines;
|
||||
int i, numlines;
|
||||
|
||||
lines = file_lines_load(file, &numlines, NULL);
|
||||
|
||||
if (lines == NULL)
|
||||
{
|
||||
/* fail if we can't open the credentials file */
|
||||
d_printf("ERROR: Unable to open credentials file!\n");
|
||||
return False;
|
||||
}
|
||||
|
||||
for (i = 0; i < numlines; i++) {
|
||||
len = strlen(lines[i]);
|
||||
|
||||
if (len == 0)
|
||||
continue;
|
||||
|
||||
/* break up the line into parameter & value.
|
||||
* will need to eat a little whitespace possibly */
|
||||
param = lines[i];
|
||||
if (!(ptr = strchr_m (lines[i], '=')))
|
||||
continue;
|
||||
|
||||
val = ptr+1;
|
||||
*ptr = '\0';
|
||||
|
||||
/* eat leading white space */
|
||||
while ((*val!='\0') && ((*val==' ') || (*val=='\t')))
|
||||
val++;
|
||||
|
||||
if (strwicmp("password", param) == 0) {
|
||||
cli_credentials_set_password(cred, val, obtained);
|
||||
} else if (strwicmp("username", param) == 0) {
|
||||
cli_credentials_set_username(cred, val, obtained);
|
||||
} else if (strwicmp("domain", param) == 0) {
|
||||
cli_credentials_set_domain(cred, val, obtained);
|
||||
} else if (strwicmp("realm", param) == 0) {
|
||||
cli_credentials_set_realm(cred, val, obtained);
|
||||
}
|
||||
memset(lines[i], 0, len);
|
||||
}
|
||||
|
||||
talloc_free(lines);
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fill in credentials for the machine trust account, from the secrets database.
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
* @retval NTSTATUS error detailing any failure
|
||||
*/
|
||||
NTSTATUS cli_credentials_set_secrets(struct cli_credentials *cred,
|
||||
const char *base,
|
||||
const char *filter)
|
||||
{
|
||||
TALLOC_CTX *mem_ctx;
|
||||
|
||||
struct ldb_context *ldb;
|
||||
int ldb_ret;
|
||||
struct ldb_message **msgs;
|
||||
const char *attrs[] = {
|
||||
"secret",
|
||||
"priorSecret",
|
||||
"samAccountName",
|
||||
"flatname",
|
||||
"realm",
|
||||
"secureChannelType",
|
||||
"ntPwdHash",
|
||||
"msDS-KeyVersionNumber",
|
||||
"saltPrincipal",
|
||||
"privateKeytab",
|
||||
"krb5Keytab",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char *machine_account;
|
||||
const char *password;
|
||||
const char *old_password;
|
||||
const char *domain;
|
||||
const char *realm;
|
||||
enum netr_SchannelType sct;
|
||||
const char *salt_principal;
|
||||
const char *keytab;
|
||||
|
||||
/* ok, we are going to get it now, don't recurse back here */
|
||||
cred->machine_account_pending = False;
|
||||
|
||||
/* some other parts of the system will key off this */
|
||||
cred->machine_account = True;
|
||||
|
||||
mem_ctx = talloc_named(cred, 0, "cli_credentials fetch machine password");
|
||||
|
||||
/* Local secrets are stored in secrets.ldb */
|
||||
ldb = secrets_db_connect(mem_ctx);
|
||||
if (!ldb) {
|
||||
/* set anonymous as the fallback, if the machine account won't work */
|
||||
cli_credentials_set_anonymous(cred);
|
||||
DEBUG(1, ("Could not open secrets.ldb\n"));
|
||||
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
||||
}
|
||||
|
||||
/* search for the secret record */
|
||||
ldb_ret = gendb_search(ldb,
|
||||
mem_ctx, ldb_dn_new(mem_ctx, ldb, base),
|
||||
&msgs, attrs,
|
||||
"%s", filter);
|
||||
if (ldb_ret == 0) {
|
||||
DEBUG(1, ("Could not find entry to match filter: %s\n",
|
||||
filter));
|
||||
/* set anonymous as the fallback, if the machine account won't work */
|
||||
cli_credentials_set_anonymous(cred);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
||||
} else if (ldb_ret != 1) {
|
||||
DEBUG(1, ("Found more than one (%d) entry to match filter: %s\n",
|
||||
ldb_ret, filter));
|
||||
/* set anonymous as the fallback, if the machine account won't work */
|
||||
cli_credentials_set_anonymous(cred);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
||||
}
|
||||
|
||||
password = ldb_msg_find_attr_as_string(msgs[0], "secret", NULL);
|
||||
old_password = ldb_msg_find_attr_as_string(msgs[0], "priorSecret", NULL);
|
||||
|
||||
machine_account = ldb_msg_find_attr_as_string(msgs[0], "samAccountName", NULL);
|
||||
|
||||
if (!machine_account) {
|
||||
DEBUG(1, ("Could not find 'samAccountName' in join record to domain: %s\n",
|
||||
cli_credentials_get_domain(cred)));
|
||||
/* set anonymous as the fallback, if the machine account won't work */
|
||||
cli_credentials_set_anonymous(cred);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
||||
}
|
||||
|
||||
salt_principal = ldb_msg_find_attr_as_string(msgs[0], "saltPrincipal", NULL);
|
||||
cli_credentials_set_salt_principal(cred, salt_principal);
|
||||
|
||||
sct = ldb_msg_find_attr_as_int(msgs[0], "secureChannelType", 0);
|
||||
if (sct) {
|
||||
cli_credentials_set_secure_channel_type(cred, sct);
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
const struct ldb_val *nt_password_hash = ldb_msg_find_ldb_val(msgs[0], "ntPwdHash");
|
||||
struct samr_Password hash;
|
||||
ZERO_STRUCT(hash);
|
||||
if (nt_password_hash) {
|
||||
memcpy(hash.hash, nt_password_hash->data,
|
||||
MIN(nt_password_hash->length, sizeof(hash.hash)));
|
||||
|
||||
cli_credentials_set_nt_hash(cred, &hash, CRED_SPECIFIED);
|
||||
} else {
|
||||
cli_credentials_set_password(cred, NULL, CRED_SPECIFIED);
|
||||
}
|
||||
} else {
|
||||
cli_credentials_set_password(cred, password, CRED_SPECIFIED);
|
||||
}
|
||||
|
||||
|
||||
domain = ldb_msg_find_attr_as_string(msgs[0], "flatname", NULL);
|
||||
if (domain) {
|
||||
cli_credentials_set_domain(cred, domain, CRED_SPECIFIED);
|
||||
}
|
||||
|
||||
realm = ldb_msg_find_attr_as_string(msgs[0], "realm", NULL);
|
||||
if (realm) {
|
||||
cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
|
||||
}
|
||||
|
||||
cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED);
|
||||
|
||||
cli_credentials_set_kvno(cred, ldb_msg_find_attr_as_int(msgs[0], "msDS-KeyVersionNumber", 0));
|
||||
|
||||
/* If there was an external keytab specified by reference in
|
||||
* the LDB, then use this. Otherwise we will make one up
|
||||
* (chewing CPU time) from the password */
|
||||
keytab = ldb_msg_find_attr_as_string(msgs[0], "krb5Keytab", NULL);
|
||||
if (keytab) {
|
||||
cli_credentials_set_keytab_name(cred, keytab, CRED_SPECIFIED);
|
||||
} else {
|
||||
keytab = ldb_msg_find_attr_as_string(msgs[0], "privateKeytab", NULL);
|
||||
if (keytab) {
|
||||
keytab = talloc_asprintf(mem_ctx, "FILE:%s", private_path(mem_ctx, keytab));
|
||||
if (keytab) {
|
||||
cli_credentials_set_keytab_name(cred, keytab, CRED_SPECIFIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in credentials for the machine trust account, from the secrets database.
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
* @retval NTSTATUS error detailing any failure
|
||||
*/
|
||||
NTSTATUS cli_credentials_set_machine_account(struct cli_credentials *cred)
|
||||
{
|
||||
char *filter;
|
||||
/* Bleh, nasty recursion issues: We are setting a machine
|
||||
* account here, so we don't want the 'pending' flag around
|
||||
* any more */
|
||||
cred->machine_account_pending = False;
|
||||
filter = talloc_asprintf(cred, SECRETS_PRIMARY_DOMAIN_FILTER,
|
||||
cli_credentials_get_domain(cred));
|
||||
return cli_credentials_set_secrets(cred, SECRETS_PRIMARY_DOMAIN_DN,
|
||||
filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in credentials for the machine trust account, from the secrets database.
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
* @retval NTSTATUS error detailing any failure
|
||||
*/
|
||||
NTSTATUS cli_credentials_set_krbtgt(struct cli_credentials *cred)
|
||||
{
|
||||
char *filter;
|
||||
/* Bleh, nasty recursion issues: We are setting a machine
|
||||
* account here, so we don't want the 'pending' flag around
|
||||
* any more */
|
||||
cred->machine_account_pending = False;
|
||||
filter = talloc_asprintf(cred, SECRETS_KRBTGT_SEARCH,
|
||||
cli_credentials_get_realm(cred),
|
||||
cli_credentials_get_domain(cred));
|
||||
return cli_credentials_set_secrets(cred, SECRETS_PRINCIPALS_DN,
|
||||
filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in credentials for the machine trust account, from the secrets database.
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
* @retval NTSTATUS error detailing any failure
|
||||
*/
|
||||
NTSTATUS cli_credentials_set_stored_principal(struct cli_credentials *cred,
|
||||
const char *serviceprincipal)
|
||||
{
|
||||
char *filter;
|
||||
/* Bleh, nasty recursion issues: We are setting a machine
|
||||
* account here, so we don't want the 'pending' flag around
|
||||
* any more */
|
||||
cred->machine_account_pending = False;
|
||||
filter = talloc_asprintf(cred, SECRETS_PRINCIPAL_SEARCH,
|
||||
cli_credentials_get_realm(cred),
|
||||
cli_credentials_get_domain(cred),
|
||||
serviceprincipal);
|
||||
return cli_credentials_set_secrets(cred, SECRETS_PRINCIPALS_DN,
|
||||
filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask that when required, the credentials system will be filled with
|
||||
* machine trust account, from the secrets database.
|
||||
*
|
||||
* @param cred Credentials structure to fill in
|
||||
* @note This function is used to call the above function after, rather
|
||||
* than during, popt processing.
|
||||
*
|
||||
*/
|
||||
void cli_credentials_set_machine_account_pending(struct cli_credentials *cred)
|
||||
{
|
||||
cred->machine_account_pending = True;
|
||||
}
|
||||
|
||||
|
||||
NTSTATUS cli_credentials_update_all_keytabs(TALLOC_CTX *parent_ctx)
|
||||
{
|
||||
TALLOC_CTX *mem_ctx;
|
||||
int ldb_ret;
|
||||
struct ldb_context *ldb;
|
||||
struct ldb_message **msgs;
|
||||
const char *attrs[] = { NULL };
|
||||
struct cli_credentials *creds;
|
||||
const char *filter;
|
||||
NTSTATUS status;
|
||||
int i, ret;
|
||||
|
||||
mem_ctx = talloc_new(parent_ctx);
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* Local secrets are stored in secrets.ldb */
|
||||
ldb = secrets_db_connect(mem_ctx);
|
||||
if (!ldb) {
|
||||
DEBUG(1, ("Could not open secrets.ldb\n"));
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
/* search for the secret record, but only of things we can
|
||||
* actually update */
|
||||
ldb_ret = gendb_search(ldb,
|
||||
mem_ctx, NULL,
|
||||
&msgs, attrs,
|
||||
"(&(objectClass=kerberosSecret)(|(secret=*)(ntPwdHash=*)))");
|
||||
if (ldb_ret == -1) {
|
||||
DEBUG(1, ("Error looking for kerberos type secrets to push into a keytab:: %s", ldb_errstring(ldb)));
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
for (i=0; i < ldb_ret; i++) {
|
||||
/* Make a credentials structure from it */
|
||||
creds = cli_credentials_init(mem_ctx);
|
||||
if (!creds) {
|
||||
DEBUG(1, ("cli_credentials_init failed!"));
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
cli_credentials_set_conf(creds);
|
||||
filter = talloc_asprintf(mem_ctx, "dn=%s", ldb_dn_get_linearized(msgs[i]->dn));
|
||||
status = cli_credentials_set_secrets(creds, NULL, filter);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(1, ("Failed to read secrets for keytab update for %s\n",
|
||||
filter));
|
||||
continue;
|
||||
}
|
||||
ret = cli_credentials_update_keytab(creds);
|
||||
if (ret != 0) {
|
||||
DEBUG(1, ("Failed to update keytab for %s\n",
|
||||
filter));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Handle user credentials (as regards krb5)
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2005
|
||||
Copyright (C) Tim Potter 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/credentials/credentials_krb5.h"
|
||||
|
||||
int cli_credentials_get_krb5_context(struct cli_credentials *cred,
|
||||
struct smb_krb5_context **smb_krb5_context)
|
||||
{
|
||||
int ret;
|
||||
if (cred->smb_krb5_context) {
|
||||
*smb_krb5_context = cred->smb_krb5_context;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = smb_krb5_init_context(cred, &cred->smb_krb5_context);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
*smb_krb5_context = cred->smb_krb5_context;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This needs to be called directly after the cli_credentials_init(),
|
||||
* otherwise we might have problems with the krb5 context already
|
||||
* being here.
|
||||
*/
|
||||
NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred,
|
||||
struct smb_krb5_context *smb_krb5_context)
|
||||
{
|
||||
if (!talloc_reference(cred, smb_krb5_context)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
cred->smb_krb5_context = smb_krb5_context;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
int cli_credentials_set_from_ccache(struct cli_credentials *cred,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
|
||||
krb5_principal princ;
|
||||
krb5_error_code ret;
|
||||
char *name;
|
||||
char **realm;
|
||||
|
||||
if (cred->ccache_obtained > obtained) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = krb5_cc_get_principal(cred->ccache->smb_krb5_context->krb5_context,
|
||||
cred->ccache->ccache, &princ);
|
||||
|
||||
if (ret) {
|
||||
char *err_mess = smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context, ret, cred);
|
||||
DEBUG(1,("failed to get principal from ccache: %s\n",
|
||||
err_mess));
|
||||
talloc_free(err_mess);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = krb5_unparse_name(cred->ccache->smb_krb5_context->krb5_context, princ, &name);
|
||||
if (ret) {
|
||||
char *err_mess = smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context, ret, cred);
|
||||
DEBUG(1,("failed to unparse principal from ccache: %s\n",
|
||||
err_mess));
|
||||
talloc_free(err_mess);
|
||||
return ret;
|
||||
}
|
||||
|
||||
realm = krb5_princ_realm(cred->ccache->smb_krb5_context->krb5_context, princ);
|
||||
|
||||
cli_credentials_set_principal(cred, name, obtained);
|
||||
|
||||
free(name);
|
||||
|
||||
krb5_free_principal(cred->ccache->smb_krb5_context->krb5_context, princ);
|
||||
|
||||
cred->ccache_obtained = obtained;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Free a memory ccache */
|
||||
static int free_mccache(struct ccache_container *ccc)
|
||||
{
|
||||
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, ccc->ccache);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Free a disk-based ccache */
|
||||
static int free_dccache(struct ccache_container *ccc) {
|
||||
krb5_cc_close(ccc->smb_krb5_context->krb5_context, ccc->ccache);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cli_credentials_set_ccache(struct cli_credentials *cred,
|
||||
const char *name,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_principal princ;
|
||||
struct ccache_container *ccc;
|
||||
if (cred->ccache_obtained > obtained) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ccc = talloc(cred, struct ccache_container);
|
||||
if (!ccc) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_krb5_context(cred, &ccc->smb_krb5_context);
|
||||
if (ret) {
|
||||
talloc_free(ccc);
|
||||
return ret;
|
||||
}
|
||||
talloc_reference(ccc, ccc->smb_krb5_context);
|
||||
|
||||
if (name) {
|
||||
ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
|
||||
if (ret) {
|
||||
DEBUG(1,("failed to read krb5 ccache: %s: %s\n",
|
||||
name,
|
||||
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
|
||||
talloc_free(ccc);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
|
||||
if (ret) {
|
||||
DEBUG(3,("failed to read default krb5 ccache: %s\n",
|
||||
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
|
||||
talloc_free(ccc);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_set_destructor(ccc, free_dccache);
|
||||
|
||||
ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
|
||||
|
||||
if (ret) {
|
||||
DEBUG(3,("failed to get principal from default ccache: %s\n",
|
||||
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
|
||||
talloc_free(ccc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
|
||||
|
||||
cred->ccache = ccc;
|
||||
talloc_steal(cred, ccc);
|
||||
|
||||
ret = cli_credentials_set_from_ccache(cred, obtained);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int cli_credentials_new_ccache(struct cli_credentials *cred, struct ccache_container **_ccc)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
char *rand_string;
|
||||
struct ccache_container *ccc = talloc(cred, struct ccache_container);
|
||||
char *ccache_name;
|
||||
if (!ccc) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
rand_string = generate_random_str(NULL, 16);
|
||||
if (!rand_string) {
|
||||
talloc_free(ccc);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ccache_name = talloc_asprintf(ccc, "MEMORY:%s",
|
||||
rand_string);
|
||||
talloc_free(rand_string);
|
||||
|
||||
if (!ccache_name) {
|
||||
talloc_free(ccc);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_krb5_context(cred, &ccc->smb_krb5_context);
|
||||
if (ret) {
|
||||
talloc_free(ccc);
|
||||
return ret;
|
||||
}
|
||||
talloc_reference(ccc, ccc->smb_krb5_context);
|
||||
|
||||
ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, &ccc->ccache);
|
||||
if (ret) {
|
||||
DEBUG(1,("failed to generate a new krb5 ccache (%s): %s\n",
|
||||
ccache_name,
|
||||
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, ret, ccc)));
|
||||
talloc_free(ccache_name);
|
||||
talloc_free(ccc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
talloc_set_destructor(ccc, free_mccache);
|
||||
|
||||
cred->ccache = ccc;
|
||||
talloc_steal(cred, ccc);
|
||||
talloc_free(ccache_name);
|
||||
|
||||
if (_ccc) {
|
||||
*_ccc = ccc;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cli_credentials_get_ccache(struct cli_credentials *cred,
|
||||
struct ccache_container **ccc)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
if (cred->ccache_obtained >= (MAX(cred->principal_obtained,
|
||||
cred->username_obtained))) {
|
||||
*ccc = cred->ccache;
|
||||
return 0;
|
||||
}
|
||||
if (cli_credentials_is_anonymous(cred)) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
ret = cli_credentials_new_ccache(cred, NULL);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
ret = kinit_to_ccache(cred, cred, cred->ccache->smb_krb5_context, cred->ccache->ccache);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
ret = cli_credentials_set_from_ccache(cred, cred->principal_obtained);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
*ccc = cred->ccache;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int free_gssapi_creds(struct gssapi_creds_container *gcc)
|
||||
{
|
||||
OM_uint32 min_stat, maj_stat;
|
||||
maj_stat = gss_release_cred(&min_stat, &gcc->creds);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
|
||||
struct gssapi_creds_container **_gcc)
|
||||
{
|
||||
int ret = 0;
|
||||
OM_uint32 maj_stat, min_stat;
|
||||
struct gssapi_creds_container *gcc;
|
||||
struct ccache_container *ccache;
|
||||
if (cred->client_gss_creds_obtained >= (MAX(cred->ccache_obtained,
|
||||
MAX(cred->principal_obtained,
|
||||
cred->username_obtained)))) {
|
||||
*_gcc = cred->client_gss_creds;
|
||||
return 0;
|
||||
}
|
||||
ret = cli_credentials_get_ccache(cred,
|
||||
&ccache);
|
||||
if (ret) {
|
||||
DEBUG(1, ("Failed to get CCACHE for GSSAPI client: %s\n", error_message(ret)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
gcc = talloc(cred, struct gssapi_creds_container);
|
||||
if (!gcc) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
|
||||
&gcc->creds);
|
||||
if (maj_stat) {
|
||||
if (min_stat) {
|
||||
ret = min_stat;
|
||||
} else {
|
||||
ret = EINVAL;
|
||||
}
|
||||
}
|
||||
if (ret == 0) {
|
||||
cred->client_gss_creds_obtained = cred->ccache_obtained;
|
||||
talloc_set_destructor(gcc, free_gssapi_creds);
|
||||
cred->client_gss_creds = gcc;
|
||||
*_gcc = gcc;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
Set a gssapi cred_id_t into the credentails system. (Client case)
|
||||
|
||||
This grabs the credentials both 'intact' and getting the krb5
|
||||
ccache out of it. This routine can be generalised in future for
|
||||
the case where we deal with GSSAPI mechs other than krb5.
|
||||
|
||||
On sucess, the caller must not free gssapi_cred, as it now belongs
|
||||
to the credentials system.
|
||||
*/
|
||||
|
||||
int cli_credentials_set_client_gss_creds(struct cli_credentials *cred,
|
||||
gss_cred_id_t gssapi_cred,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
int ret;
|
||||
OM_uint32 maj_stat, min_stat;
|
||||
struct ccache_container *ccc;
|
||||
struct gssapi_creds_container *gcc;
|
||||
if (cred->client_gss_creds_obtained > obtained) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
gcc = talloc(cred, struct gssapi_creds_container);
|
||||
if (!gcc) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = cli_credentials_new_ccache(cred, &ccc);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
maj_stat = gss_krb5_copy_ccache(&min_stat,
|
||||
gssapi_cred, ccc->ccache);
|
||||
if (maj_stat) {
|
||||
if (min_stat) {
|
||||
ret = min_stat;
|
||||
} else {
|
||||
ret = EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
ret = cli_credentials_set_from_ccache(cred, obtained);
|
||||
}
|
||||
if (ret == 0) {
|
||||
gcc->creds = gssapi_cred;
|
||||
talloc_set_destructor(gcc, free_gssapi_creds);
|
||||
|
||||
cred->client_gss_creds_obtained = obtained;
|
||||
cred->client_gss_creds = gcc;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the keytab (actually, a container containing the krb5_keytab)
|
||||
* attached to this context. If this hasn't been done or set before,
|
||||
* it will be generated from the password.
|
||||
*/
|
||||
int cli_credentials_get_keytab(struct cli_credentials *cred,
|
||||
struct keytab_container **_ktc)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
struct keytab_container *ktc;
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
|
||||
if (cred->keytab_obtained >= (MAX(cred->principal_obtained,
|
||||
cred->username_obtained))) {
|
||||
*_ktc = cred->keytab;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cli_credentials_is_anonymous(cred)) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
mem_ctx = talloc_new(cred);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = smb_krb5_create_memory_keytab(mem_ctx, cred, smb_krb5_context, &ktc);
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cred->keytab_obtained = (MAX(cred->principal_obtained,
|
||||
cred->username_obtained));
|
||||
|
||||
talloc_steal(cred, ktc);
|
||||
cred->keytab = ktc;
|
||||
*_ktc = cred->keytab;
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Given the name of a keytab (presumably in the format
|
||||
* FILE:/etc/krb5.keytab), open it and attach it */
|
||||
|
||||
int cli_credentials_set_keytab_name(struct cli_credentials *cred,
|
||||
const char *keytab_name,
|
||||
enum credentials_obtained obtained)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
struct keytab_container *ktc;
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
|
||||
if (cred->keytab_obtained >= obtained) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
mem_ctx = talloc_new(cred);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = smb_krb5_open_keytab(mem_ctx, smb_krb5_context,
|
||||
keytab_name, &ktc);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
cred->keytab_obtained = obtained;
|
||||
|
||||
talloc_steal(cred, ktc);
|
||||
cred->keytab = ktc;
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cli_credentials_update_keytab(struct cli_credentials *cred)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
struct keytab_container *ktc;
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
|
||||
mem_ctx = talloc_new(cred);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_keytab(cred, &ktc);
|
||||
if (ret != 0) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = smb_krb5_update_keytab(mem_ctx, cred, smb_krb5_context, ktc);
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get server gss credentials (in gsskrb5, this means the keytab) */
|
||||
|
||||
int cli_credentials_get_server_gss_creds(struct cli_credentials *cred,
|
||||
struct gssapi_creds_container **_gcc)
|
||||
{
|
||||
int ret = 0;
|
||||
OM_uint32 maj_stat, min_stat;
|
||||
struct gssapi_creds_container *gcc;
|
||||
struct keytab_container *ktc;
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
krb5_principal princ;
|
||||
|
||||
if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained,
|
||||
MAX(cred->principal_obtained,
|
||||
cred->username_obtained)))) {
|
||||
*_gcc = cred->server_gss_creds;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_krb5_context(cred, &smb_krb5_context);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = cli_credentials_get_keytab(cred,
|
||||
&ktc);
|
||||
if (ret) {
|
||||
DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
mem_ctx = talloc_new(cred);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ);
|
||||
if (ret) {
|
||||
DEBUG(1,("cli_credentials_get_server_gss_creds: makeing krb5 principal failed (%s)\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
gcc = talloc(cred, struct gssapi_creds_container);
|
||||
if (!gcc) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
/* This creates a GSSAPI cred_id_t with the principal and keytab set */
|
||||
maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
|
||||
&gcc->creds);
|
||||
if (maj_stat) {
|
||||
if (min_stat) {
|
||||
ret = min_stat;
|
||||
} else {
|
||||
ret = EINVAL;
|
||||
}
|
||||
}
|
||||
if (ret == 0) {
|
||||
cred->server_gss_creds_obtained = cred->keytab_obtained;
|
||||
talloc_set_destructor(gcc, free_gssapi_creds);
|
||||
cred->server_gss_creds = gcc;
|
||||
*_gcc = gcc;
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Kerberos KVNO
|
||||
*/
|
||||
|
||||
void cli_credentials_set_kvno(struct cli_credentials *cred,
|
||||
int kvno)
|
||||
{
|
||||
cred->kvno = kvno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Kerberos KVNO
|
||||
*/
|
||||
|
||||
int cli_credentials_get_kvno(struct cli_credentials *cred)
|
||||
{
|
||||
return cred->kvno;
|
||||
}
|
||||
|
||||
const char *cli_credentials_get_salt_principal(struct cli_credentials *cred)
|
||||
{
|
||||
return cred->salt_principal;
|
||||
}
|
||||
|
||||
void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal)
|
||||
{
|
||||
cred->salt_principal = talloc_strdup(cred, principal);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
samba -- Unix SMB/CIFS implementation.
|
||||
|
||||
Client credentials structure
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2004-2006
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "heimdal/lib/gssapi/gssapi/gssapi.h"
|
||||
|
||||
struct ccache_container;
|
||||
|
||||
struct gssapi_creds_container {
|
||||
gss_cred_id_t creds;
|
||||
};
|
||||
|
||||
#include "auth/credentials/credentials_krb5_proto.h"
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
User credentials handling
|
||||
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */
|
||||
#include "lib/crypto/crypto.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
|
||||
void cli_credentials_get_ntlm_username_domain(struct cli_credentials *cred, TALLOC_CTX *mem_ctx,
|
||||
const char **username,
|
||||
const char **domain)
|
||||
{
|
||||
if (cred->principal_obtained > cred->username_obtained) {
|
||||
*domain = talloc_strdup(mem_ctx, "");
|
||||
*username = cli_credentials_get_principal(cred, mem_ctx);
|
||||
} else {
|
||||
*domain = cli_credentials_get_domain(cred);
|
||||
*username = cli_credentials_get_username(cred);
|
||||
}
|
||||
}
|
||||
|
||||
NTSTATUS cli_credentials_get_ntlm_response(struct cli_credentials *cred, TALLOC_CTX *mem_ctx,
|
||||
int *flags,
|
||||
DATA_BLOB challenge, DATA_BLOB target_info,
|
||||
DATA_BLOB *_lm_response, DATA_BLOB *_nt_response,
|
||||
DATA_BLOB *_lm_session_key, DATA_BLOB *_session_key)
|
||||
{
|
||||
const char *user, *domain;
|
||||
DATA_BLOB lm_response, nt_response;
|
||||
DATA_BLOB lm_session_key, session_key;
|
||||
const struct samr_Password *nt_hash;
|
||||
lm_session_key = data_blob(NULL, 0);
|
||||
|
||||
nt_hash = cli_credentials_get_nt_hash(cred, mem_ctx);
|
||||
|
||||
cli_credentials_get_ntlm_username_domain(cred, mem_ctx, &user, &domain);
|
||||
|
||||
/* If we are sending a username@realm login (see function
|
||||
* above), then we will not send LM, it will not be
|
||||
* accepted */
|
||||
if (cred->principal_obtained > cred->username_obtained) {
|
||||
*flags = *flags & ~CLI_CRED_LANMAN_AUTH;
|
||||
}
|
||||
|
||||
/* Likewise if we are a machine account (avoid protocol downgrade attacks) */
|
||||
if (cred->machine_account) {
|
||||
*flags = *flags & ~CLI_CRED_LANMAN_AUTH;
|
||||
}
|
||||
|
||||
if (cred->use_kerberos == CRED_MUST_USE_KERBEROS) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (!nt_hash) {
|
||||
static const uint8_t zeros[16];
|
||||
/* do nothing - blobs are zero length */
|
||||
|
||||
/* session key is all zeros */
|
||||
session_key = data_blob_talloc(mem_ctx, zeros, 16);
|
||||
lm_session_key = data_blob_talloc(mem_ctx, zeros, 16);
|
||||
|
||||
lm_response = data_blob(NULL, 0);
|
||||
nt_response = data_blob(NULL, 0);
|
||||
|
||||
/* not doing NTLM2 without a password */
|
||||
*flags &= ~CLI_CRED_NTLM2;
|
||||
} else if (*flags & CLI_CRED_NTLMv2_AUTH) {
|
||||
|
||||
if (!target_info.length) {
|
||||
/* be lazy, match win2k - we can't do NTLMv2 without it */
|
||||
DEBUG(1, ("Server did not provide 'target information', required for NTLMv2\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* TODO: if the remote server is standalone, then we should replace 'domain'
|
||||
with the server name as supplied above */
|
||||
|
||||
if (!SMBNTLMv2encrypt_hash(mem_ctx,
|
||||
user,
|
||||
domain,
|
||||
nt_hash->hash, &challenge,
|
||||
&target_info,
|
||||
&lm_response, &nt_response,
|
||||
NULL, &session_key)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* LM Key is incompatible... */
|
||||
*flags &= ~CLI_CRED_LANMAN_AUTH;
|
||||
} else if (*flags & CLI_CRED_NTLM2) {
|
||||
struct MD5Context md5_session_nonce_ctx;
|
||||
uint8_t session_nonce[16];
|
||||
uint8_t session_nonce_hash[16];
|
||||
uint8_t user_session_key[16];
|
||||
|
||||
lm_response = data_blob_talloc(mem_ctx, NULL, 24);
|
||||
generate_random_buffer(lm_response.data, 8);
|
||||
memset(lm_response.data+8, 0, 16);
|
||||
|
||||
memcpy(session_nonce, challenge.data, 8);
|
||||
memcpy(&session_nonce[8], lm_response.data, 8);
|
||||
|
||||
MD5Init(&md5_session_nonce_ctx);
|
||||
MD5Update(&md5_session_nonce_ctx, challenge.data, 8);
|
||||
MD5Update(&md5_session_nonce_ctx, lm_response.data, 8);
|
||||
MD5Final(session_nonce_hash, &md5_session_nonce_ctx);
|
||||
|
||||
DEBUG(5, ("NTLMSSP challenge set by NTLM2\n"));
|
||||
DEBUG(5, ("challenge is: \n"));
|
||||
dump_data(5, session_nonce_hash, 8);
|
||||
|
||||
nt_response = data_blob_talloc(mem_ctx, NULL, 24);
|
||||
SMBOWFencrypt(nt_hash->hash,
|
||||
session_nonce_hash,
|
||||
nt_response.data);
|
||||
|
||||
session_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
|
||||
SMBsesskeygen_ntv1(nt_hash->hash, user_session_key);
|
||||
hmac_md5(user_session_key, session_nonce, sizeof(session_nonce), session_key.data);
|
||||
dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length);
|
||||
|
||||
/* LM Key is incompatible... */
|
||||
*flags &= ~CLI_CRED_LANMAN_AUTH;
|
||||
} else {
|
||||
uint8_t lm_hash[16];
|
||||
nt_response = data_blob_talloc(mem_ctx, NULL, 24);
|
||||
SMBOWFencrypt(nt_hash->hash, challenge.data,
|
||||
nt_response.data);
|
||||
|
||||
session_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
SMBsesskeygen_ntv1(nt_hash->hash, session_key.data);
|
||||
dump_data_pw("NT session key:\n", session_key.data, session_key.length);
|
||||
|
||||
/* lanman auth is insecure, it may be disabled.
|
||||
We may also not have a password */
|
||||
if (*flags & CLI_CRED_LANMAN_AUTH) {
|
||||
const char *password;
|
||||
password = cli_credentials_get_password(cred);
|
||||
if (!password) {
|
||||
lm_response = nt_response;
|
||||
} else {
|
||||
lm_response = data_blob_talloc(mem_ctx, NULL, 24);
|
||||
if (!SMBencrypt(password,challenge.data,
|
||||
lm_response.data)) {
|
||||
/* If the LM password was too long (and therefore the LM hash being
|
||||
of the first 14 chars only), don't send it.
|
||||
|
||||
We don't have any better options but to send the NT response
|
||||
*/
|
||||
data_blob_free(&lm_response);
|
||||
lm_response = nt_response;
|
||||
/* LM Key is incompatible with 'long' passwords */
|
||||
*flags &= ~CLI_CRED_LANMAN_AUTH;
|
||||
} else {
|
||||
E_deshash(password, lm_hash);
|
||||
lm_session_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
memcpy(lm_session_key.data, lm_hash, 8);
|
||||
memset(&lm_session_key.data[8], '\0', 8);
|
||||
|
||||
if (!(*flags & CLI_CRED_NTLM_AUTH)) {
|
||||
session_key = lm_session_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const char *password;
|
||||
|
||||
/* LM Key is incompatible... */
|
||||
lm_response = nt_response;
|
||||
*flags &= ~CLI_CRED_LANMAN_AUTH;
|
||||
|
||||
password = cli_credentials_get_password(cred);
|
||||
if (password) {
|
||||
E_deshash(password, lm_hash);
|
||||
lm_session_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
memcpy(lm_session_key.data, lm_hash, 8);
|
||||
memset(&lm_session_key.data[8], '\0', 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_lm_response) {
|
||||
*_lm_response = lm_response;
|
||||
}
|
||||
if (_nt_response) {
|
||||
*_nt_response = nt_response;
|
||||
}
|
||||
if (_lm_session_key) {
|
||||
*_lm_session_key = lm_session_key;
|
||||
}
|
||||
if (_session_key) {
|
||||
*_session_key = session_key;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
SMB_ENABLE(gensec_krb5, $HAVE_KRB5)
|
||||
SMB_ENABLE(gensec_gssapi, $HAVE_KRB5)
|
||||
@@ -0,0 +1,91 @@
|
||||
#################################
|
||||
# Start SUBSYSTEM gensec
|
||||
[LIBRARY::gensec]
|
||||
VERSION = 0.0.1
|
||||
SO_VERSION = 0
|
||||
DESCRIPTION = Generic Security Library
|
||||
PUBLIC_HEADERS = gensec.h spnego.h
|
||||
PUBLIC_PROTO_HEADER = gensec_proto.h
|
||||
OBJ_FILES = gensec.o
|
||||
PUBLIC_DEPENDENCIES = \
|
||||
CREDENTIALS LIBSAMBA-UTIL LIBCRYPTO ASN1_UTIL
|
||||
# End SUBSYSTEM gensec
|
||||
#################################
|
||||
|
||||
################################################
|
||||
# Start MODULE gensec_krb5
|
||||
[MODULE::gensec_krb5]
|
||||
SUBSYSTEM = gensec
|
||||
INIT_FUNCTION = gensec_krb5_init
|
||||
OBJ_FILES = gensec_krb5.o
|
||||
PUBLIC_DEPENDENCIES = CREDENTIALS_KRB5 KERBEROS auth auth_sam
|
||||
# End MODULE gensec_krb5
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE gensec_gssapi
|
||||
[MODULE::gensec_gssapi]
|
||||
SUBSYSTEM = gensec
|
||||
INIT_FUNCTION = gensec_gssapi_init
|
||||
OBJ_FILES = gensec_gssapi.o
|
||||
PUBLIC_DEPENDENCIES = CREDENTIALS_KRB5 KERBEROS auth HEIMDAL_GSSAPI
|
||||
# End MODULE gensec_gssapi
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE cyrus_sasl
|
||||
[MODULE::cyrus_sasl]
|
||||
SUBSYSTEM = gensec
|
||||
INIT_FUNCTION = gensec_sasl_init
|
||||
OBJ_FILES = cyrus_sasl.o
|
||||
PUBLIC_DEPENDENCIES = CREDENTIALS SASL auth
|
||||
# End MODULE cyrus_sasl
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE gensec_spnego
|
||||
[MODULE::gensec_spnego]
|
||||
SUBSYSTEM = gensec
|
||||
INIT_FUNCTION = gensec_spnego_init
|
||||
PRIVATE_PROTO_HEADER = spnego_proto.h
|
||||
PRIVATE_DEPENDENCIES = ASN1_UTIL GENSEC_SOCKET
|
||||
PUBLIC_DEPENDENCIES = CREDENTIALS
|
||||
OBJ_FILES = spnego.o \
|
||||
spnego_parse.o
|
||||
# End MODULE gensec_spnego
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE gensec_schannel
|
||||
[MODULE::gensec_schannel]
|
||||
SUBSYSTEM = gensec
|
||||
PRIVATE_PROTO_HEADER = schannel_proto.h
|
||||
INIT_FUNCTION = gensec_schannel_init
|
||||
OBJ_FILES = schannel.o \
|
||||
schannel_sign.o
|
||||
PUBLIC_DEPENDENCIES = auth SCHANNELDB NDR_SCHANNEL CREDENTIALS
|
||||
OUTPUT_TYPE = INTEGRATED
|
||||
# End MODULE gensec_schannel
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start SUBSYSTEM SCHANNELDB
|
||||
[SUBSYSTEM::SCHANNELDB]
|
||||
PRIVATE_PROTO_HEADER = schannel_state.h
|
||||
OBJ_FILES = \
|
||||
schannel_state.o
|
||||
#
|
||||
# End SUBSYSTEM SCHANNELDB
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start SUBSYSTEM GENSEC_SOCKET
|
||||
[SUBSYSTEM::GENSEC_SOCKET]
|
||||
OBJ_FILES = \
|
||||
socket.o
|
||||
PUBLIC_DEPENDENCIES = samba-socket LIBPACKET
|
||||
#PUBLIC_DEPENDENCIES = gensec
|
||||
#
|
||||
# End SUBSYSTEM GENSEC_SOCKET
|
||||
################################################
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Connect GENSEC to an external SASL lib
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
#include "lib/socket/socket.h"
|
||||
#include <sasl/sasl.h>
|
||||
|
||||
struct gensec_sasl_state {
|
||||
sasl_conn_t *conn;
|
||||
int step;
|
||||
};
|
||||
|
||||
static NTSTATUS sasl_nt_status(int sasl_ret)
|
||||
{
|
||||
switch (sasl_ret) {
|
||||
case SASL_CONTINUE:
|
||||
return NT_STATUS_MORE_PROCESSING_REQUIRED;
|
||||
case SASL_NOMEM:
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
case SASL_BADPARAM:
|
||||
case SASL_NOMECH:
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
case SASL_BADMAC:
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
case SASL_OK:
|
||||
return NT_STATUS_OK;
|
||||
default:
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
|
||||
static int gensec_sasl_get_user(void *context, int id,
|
||||
const char **result, unsigned *len)
|
||||
{
|
||||
struct gensec_security *gensec_security = talloc_get_type(context, struct gensec_security);
|
||||
const char *username = cli_credentials_get_username(gensec_get_credentials(gensec_security));
|
||||
if (id != SASL_CB_USER && id != SASL_CB_AUTHNAME) {
|
||||
return SASL_FAIL;
|
||||
}
|
||||
|
||||
*result = username;
|
||||
return SASL_OK;
|
||||
}
|
||||
|
||||
static int gensec_sasl_get_realm(void *context, int id,
|
||||
const char **availrealms,
|
||||
const char **result)
|
||||
{
|
||||
struct gensec_security *gensec_security = talloc_get_type(context, struct gensec_security);
|
||||
const char *realm = cli_credentials_get_realm(gensec_get_credentials(gensec_security));
|
||||
int i;
|
||||
if (id != SASL_CB_GETREALM) {
|
||||
return SASL_FAIL;
|
||||
}
|
||||
|
||||
for (i=0; availrealms && availrealms[i]; i++) {
|
||||
if (strcasecmp_m(realm, availrealms[i]) == 0) {
|
||||
result[i] = availrealms[i];
|
||||
return SASL_OK;
|
||||
}
|
||||
}
|
||||
/* None of the realms match, so lets not specify one */
|
||||
*result = "";
|
||||
return SASL_OK;
|
||||
}
|
||||
|
||||
static int gensec_sasl_get_password(sasl_conn_t *conn, void *context, int id,
|
||||
sasl_secret_t **psecret)
|
||||
{
|
||||
struct gensec_security *gensec_security = talloc_get_type(context, struct gensec_security);
|
||||
const char *password = cli_credentials_get_password(gensec_get_credentials(gensec_security));
|
||||
|
||||
sasl_secret_t *secret;
|
||||
if (!password) {
|
||||
*psecret = NULL;
|
||||
return SASL_OK;
|
||||
}
|
||||
secret = talloc_size(gensec_security, sizeof(sasl_secret_t)+strlen(password));
|
||||
if (!secret) {
|
||||
return SASL_NOMEM;
|
||||
}
|
||||
secret->len = strlen(password);
|
||||
safe_strcpy(secret->data, password, secret->len+1);
|
||||
*psecret = secret;
|
||||
return SASL_OK;
|
||||
}
|
||||
|
||||
static int gensec_sasl_dispose(struct gensec_sasl_state *gensec_sasl_state)
|
||||
{
|
||||
sasl_dispose(&gensec_sasl_state->conn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_sasl_client_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
struct gensec_sasl_state *gensec_sasl_state;
|
||||
const char *service = gensec_get_target_service(gensec_security);
|
||||
const char *target_name = gensec_get_target_hostname(gensec_security);
|
||||
struct socket_address *local_socket_addr = gensec_get_my_addr(gensec_security);
|
||||
struct socket_address *remote_socket_addr = gensec_get_peer_addr(gensec_security);
|
||||
char *local_addr = NULL;
|
||||
char *remote_addr = NULL;
|
||||
int sasl_ret;
|
||||
|
||||
sasl_callback_t *callbacks;
|
||||
|
||||
gensec_sasl_state = talloc(gensec_security, struct gensec_sasl_state);
|
||||
if (!gensec_sasl_state) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
callbacks = talloc_array(gensec_sasl_state, sasl_callback_t, 5);
|
||||
callbacks[0].id = SASL_CB_USER;
|
||||
callbacks[0].proc = gensec_sasl_get_user;
|
||||
callbacks[0].context = gensec_security;
|
||||
|
||||
callbacks[1].id = SASL_CB_AUTHNAME;
|
||||
callbacks[1].proc = gensec_sasl_get_user;
|
||||
callbacks[1].context = gensec_security;
|
||||
|
||||
callbacks[2].id = SASL_CB_GETREALM;
|
||||
callbacks[2].proc = gensec_sasl_get_realm;
|
||||
callbacks[2].context = gensec_security;
|
||||
|
||||
callbacks[3].id = SASL_CB_PASS;
|
||||
callbacks[3].proc = gensec_sasl_get_password;
|
||||
callbacks[3].context = gensec_security;
|
||||
|
||||
callbacks[4].id = SASL_CB_LIST_END;
|
||||
callbacks[4].proc = NULL;
|
||||
callbacks[4].context = NULL;
|
||||
|
||||
gensec_security->private_data = gensec_sasl_state;
|
||||
|
||||
if (local_socket_addr) {
|
||||
local_addr = talloc_asprintf(gensec_sasl_state,
|
||||
"%s;%d",
|
||||
local_socket_addr->addr,
|
||||
local_socket_addr->port);
|
||||
}
|
||||
|
||||
if (remote_socket_addr) {
|
||||
remote_addr = talloc_asprintf(gensec_sasl_state,
|
||||
"%s;%d",
|
||||
remote_socket_addr->addr,
|
||||
remote_socket_addr->port);
|
||||
}
|
||||
gensec_sasl_state->step = 0;
|
||||
|
||||
sasl_ret = sasl_client_new(service,
|
||||
target_name,
|
||||
local_addr, remote_addr, callbacks, 0,
|
||||
&gensec_sasl_state->conn);
|
||||
|
||||
if (sasl_ret == SASL_OK || sasl_ret == SASL_CONTINUE) {
|
||||
sasl_security_properties_t props;
|
||||
talloc_set_destructor(gensec_sasl_state, gensec_sasl_dispose);
|
||||
|
||||
ZERO_STRUCT(props);
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SIGN) {
|
||||
props.min_ssf = 1;
|
||||
}
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SEAL) {
|
||||
props.min_ssf = 40;
|
||||
}
|
||||
|
||||
props.max_ssf = UINT_MAX;
|
||||
props.maxbufsize = 65536;
|
||||
sasl_ret = sasl_setprop(gensec_sasl_state->conn, SASL_SEC_PROPS, &props);
|
||||
if (sasl_ret != SASL_OK) {
|
||||
return sasl_nt_status(sasl_ret);
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG(1, ("GENSEC SASL: client_new failed: %s\n", sasl_errdetail(gensec_sasl_state->conn)));
|
||||
}
|
||||
return sasl_nt_status(sasl_ret);
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_sasl_update(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_sasl_state *gensec_sasl_state = talloc_get_type(gensec_security->private_data,
|
||||
struct gensec_sasl_state);
|
||||
int sasl_ret;
|
||||
const char *out_data;
|
||||
unsigned int out_len;
|
||||
|
||||
if (gensec_sasl_state->step == 0) {
|
||||
const char *mech;
|
||||
sasl_ret = sasl_client_start(gensec_sasl_state->conn, gensec_security->ops->sasl_name,
|
||||
NULL, &out_data, &out_len, &mech);
|
||||
} else {
|
||||
sasl_ret = sasl_client_step(gensec_sasl_state->conn,
|
||||
in.data, in.length, NULL, &out_data, &out_len);
|
||||
}
|
||||
if (sasl_ret == SASL_OK || sasl_ret == SASL_CONTINUE) {
|
||||
*out = data_blob_talloc(out_mem_ctx, out_data, out_len);
|
||||
} else {
|
||||
DEBUG(1, ("GENSEC SASL: step %d update failed: %s\n", gensec_sasl_state->step,
|
||||
sasl_errdetail(gensec_sasl_state->conn)));
|
||||
}
|
||||
gensec_sasl_state->step++;
|
||||
return sasl_nt_status(sasl_ret);
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_sasl_unwrap_packets(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed)
|
||||
{
|
||||
struct gensec_sasl_state *gensec_sasl_state = talloc_get_type(gensec_security->private_data,
|
||||
struct gensec_sasl_state);
|
||||
const char *out_data;
|
||||
unsigned int out_len;
|
||||
|
||||
int sasl_ret = sasl_decode(gensec_sasl_state->conn,
|
||||
in->data, in->length, &out_data, &out_len);
|
||||
if (sasl_ret == SASL_OK) {
|
||||
*out = data_blob_talloc(out_mem_ctx, out_data, out_len);
|
||||
*len_processed = in->length;
|
||||
} else {
|
||||
DEBUG(1, ("GENSEC SASL: unwrap failed: %s\n", sasl_errdetail(gensec_sasl_state->conn)));
|
||||
}
|
||||
return sasl_nt_status(sasl_ret);
|
||||
|
||||
}
|
||||
static NTSTATUS gensec_sasl_wrap_packets(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed)
|
||||
{
|
||||
struct gensec_sasl_state *gensec_sasl_state = talloc_get_type(gensec_security->private_data,
|
||||
struct gensec_sasl_state);
|
||||
const char *out_data;
|
||||
unsigned int out_len;
|
||||
|
||||
int sasl_ret = sasl_encode(gensec_sasl_state->conn,
|
||||
in->data, in->length, &out_data, &out_len);
|
||||
if (sasl_ret == SASL_OK) {
|
||||
*out = data_blob_talloc(out_mem_ctx, out_data, out_len);
|
||||
*len_processed = in->length;
|
||||
} else {
|
||||
DEBUG(1, ("GENSEC SASL: wrap failed: %s\n", sasl_errdetail(gensec_sasl_state->conn)));
|
||||
}
|
||||
return sasl_nt_status(sasl_ret);
|
||||
}
|
||||
|
||||
/* Try to figure out what features we actually got on the connection */
|
||||
static BOOL gensec_sasl_have_feature(struct gensec_security *gensec_security,
|
||||
uint32_t feature)
|
||||
{
|
||||
struct gensec_sasl_state *gensec_sasl_state = talloc_get_type(gensec_security->private_data,
|
||||
struct gensec_sasl_state);
|
||||
sasl_ssf_t ssf;
|
||||
int sasl_ret = sasl_getprop(gensec_sasl_state->conn, SASL_SSF, &ssf);
|
||||
if (sasl_ret != SASL_OK) {
|
||||
return False;
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_SIGN) {
|
||||
if (ssf == 0) {
|
||||
return False;
|
||||
}
|
||||
if (ssf >= 1) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_SEAL) {
|
||||
if (ssf <= 1) {
|
||||
return False;
|
||||
}
|
||||
if (ssf > 1) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
/* This could in theory work with any SASL mech */
|
||||
static const struct gensec_security_ops gensec_sasl_security_ops = {
|
||||
.name = "sasl-DIGEST-MD5",
|
||||
.sasl_name = "DIGEST-MD5",
|
||||
.client_start = gensec_sasl_client_start,
|
||||
.update = gensec_sasl_update,
|
||||
.wrap_packets = gensec_sasl_wrap_packets,
|
||||
.unwrap_packets = gensec_sasl_unwrap_packets,
|
||||
.have_feature = gensec_sasl_have_feature,
|
||||
.enabled = True,
|
||||
.priority = GENSEC_SASL
|
||||
};
|
||||
|
||||
int gensec_sasl_log(void *context,
|
||||
int sasl_log_level,
|
||||
const char *message)
|
||||
{
|
||||
int debug_level;
|
||||
switch (sasl_log_level) {
|
||||
case SASL_LOG_NONE:
|
||||
debug_level = 0;
|
||||
break;
|
||||
case SASL_LOG_ERR:
|
||||
debug_level = 1;
|
||||
break;
|
||||
case SASL_LOG_FAIL:
|
||||
debug_level = 2;
|
||||
break;
|
||||
case SASL_LOG_WARN:
|
||||
debug_level = 3;
|
||||
break;
|
||||
case SASL_LOG_NOTE:
|
||||
debug_level = 5;
|
||||
break;
|
||||
case SASL_LOG_DEBUG:
|
||||
debug_level = 10;
|
||||
break;
|
||||
case SASL_LOG_TRACE:
|
||||
debug_level = 11;
|
||||
break;
|
||||
#if DEBUG_PASSWORD
|
||||
case SASL_LOG_PASS:
|
||||
debug_level = 100;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
debug_level = 0;
|
||||
break;
|
||||
}
|
||||
DEBUG(debug_level, ("gensec_sasl: %s\n", message));
|
||||
|
||||
return SASL_OK;
|
||||
}
|
||||
|
||||
NTSTATUS gensec_sasl_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
int sasl_ret, i;
|
||||
const char **sasl_mechs;
|
||||
|
||||
static const sasl_callback_t callbacks[] = {
|
||||
{
|
||||
.id = SASL_CB_LOG,
|
||||
.proc = gensec_sasl_log,
|
||||
.context = NULL,
|
||||
},
|
||||
{
|
||||
.id = SASL_CB_LIST_END,
|
||||
.proc = gensec_sasl_log,
|
||||
.context = NULL,
|
||||
}
|
||||
};
|
||||
sasl_ret = sasl_client_init(callbacks);
|
||||
|
||||
if (sasl_ret == SASL_NOMECH) {
|
||||
/* Nothing to do here */
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
if (sasl_ret != SASL_OK) {
|
||||
return sasl_nt_status(sasl_ret);
|
||||
}
|
||||
|
||||
/* For now, we just register DIGEST-MD5 */
|
||||
#if 1
|
||||
ret = gensec_register(&gensec_sasl_security_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register '%s' gensec backend!\n",
|
||||
gensec_sasl_security_ops.name));
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
sasl_mechs = sasl_global_listmech();
|
||||
for (i = 0; sasl_mechs && sasl_mechs[i]; i++) {
|
||||
const struct gensec_security_ops *oldmech;
|
||||
struct gensec_security_ops *newmech;
|
||||
oldmech = gensec_security_by_sasl_name(NULL, sasl_mechs[i]);
|
||||
if (oldmech) {
|
||||
continue;
|
||||
}
|
||||
newmech = talloc(talloc_autofree_context(), struct gensec_security_ops);
|
||||
if (!newmech) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
*newmech = gensec_sasl_security_ops;
|
||||
newmech->sasl_name = talloc_strdup(newmech, sasl_mechs[i]);
|
||||
newmech->name = talloc_asprintf(newmech, "sasl-%s", sasl_mechs[i]);
|
||||
if (!newmech->sasl_name || !newmech->name) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
ret = gensec_register(newmech);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register '%s' gensec backend!\n",
|
||||
gensec_sasl_security_ops.name));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Generic Authentication Interface
|
||||
|
||||
Copyright (C) Andrew Tridgell 2003
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GENSEC_H__
|
||||
#define __GENSEC_H__
|
||||
|
||||
#include "core.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
|
||||
#define GENSEC_OID_NTLMSSP "1 3 6 1 4 1 311 2 2 10"
|
||||
#define GENSEC_OID_SPNEGO "1 3 6 1 5 5 2"
|
||||
#define GENSEC_OID_KERBEROS5 "1 2 840 113554 1 2 2"
|
||||
#define GENSEC_OID_KERBEROS5_OLD "1 2 840 48018 1 2 2"
|
||||
#define GENSEC_OID_KERBEROS5_USER2USER "1 2 840 113554 1 2 2 3"
|
||||
|
||||
enum gensec_priority {
|
||||
GENSEC_SPNEGO = 90,
|
||||
GENSEC_GSSAPI = 80,
|
||||
GENSEC_KRB5 = 70,
|
||||
GENSEC_SCHANNEL = 60,
|
||||
GENSEC_NTLMSSP = 50,
|
||||
GENSEC_SASL = 20,
|
||||
GENSEC_OTHER = 0
|
||||
};
|
||||
|
||||
enum credentials_use_kerberos;
|
||||
|
||||
struct gensec_security;
|
||||
struct gensec_target {
|
||||
const char *principal;
|
||||
const char *hostname;
|
||||
const char *service;
|
||||
};
|
||||
|
||||
#define GENSEC_FEATURE_SESSION_KEY 0x00000001
|
||||
#define GENSEC_FEATURE_SIGN 0x00000002
|
||||
#define GENSEC_FEATURE_SEAL 0x00000004
|
||||
#define GENSEC_FEATURE_DCE_STYLE 0x00000008
|
||||
#define GENSEC_FEATURE_ASYNC_REPLIES 0x00000010
|
||||
#define GENSEC_FEATURE_DATAGRAM_MODE 0x00000020
|
||||
|
||||
/* GENSEC mode */
|
||||
enum gensec_role
|
||||
{
|
||||
GENSEC_SERVER,
|
||||
GENSEC_CLIENT
|
||||
};
|
||||
|
||||
struct auth_session_info;
|
||||
|
||||
struct gensec_update_request {
|
||||
struct gensec_security *gensec_security;
|
||||
void *private_data;
|
||||
DATA_BLOB in;
|
||||
DATA_BLOB out;
|
||||
NTSTATUS status;
|
||||
struct {
|
||||
void (*fn)(struct gensec_update_request *req, void *private_data);
|
||||
void *private_data;
|
||||
} callback;
|
||||
};
|
||||
|
||||
struct gensec_security_ops {
|
||||
const char *name;
|
||||
const char *sasl_name;
|
||||
uint8_t auth_type; /* 0 if not offered on DCE-RPC */
|
||||
const char **oid; /* NULL if not offered by SPNEGO */
|
||||
NTSTATUS (*client_start)(struct gensec_security *gensec_security);
|
||||
NTSTATUS (*server_start)(struct gensec_security *gensec_security);
|
||||
/**
|
||||
Determine if a packet has the right 'magic' for this mechanism
|
||||
*/
|
||||
NTSTATUS (*magic)(struct gensec_security *gensec_security,
|
||||
const DATA_BLOB *first_packet);
|
||||
NTSTATUS (*update)(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out);
|
||||
NTSTATUS (*seal_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx,
|
||||
uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
DATA_BLOB *sig);
|
||||
NTSTATUS (*sign_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
DATA_BLOB *sig);
|
||||
size_t (*sig_size)(struct gensec_security *gensec_security, size_t data_size);
|
||||
size_t (*max_input_size)(struct gensec_security *gensec_security);
|
||||
size_t (*max_wrapped_size)(struct gensec_security *gensec_security);
|
||||
NTSTATUS (*check_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
const DATA_BLOB *sig);
|
||||
NTSTATUS (*unseal_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx,
|
||||
uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
const DATA_BLOB *sig);
|
||||
NTSTATUS (*wrap)(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out);
|
||||
NTSTATUS (*unwrap)(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out);
|
||||
NTSTATUS (*wrap_packets)(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed);
|
||||
NTSTATUS (*unwrap_packets)(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed);
|
||||
NTSTATUS (*packet_full_request)(struct gensec_security *gensec_security,
|
||||
DATA_BLOB blob, size_t *size);
|
||||
NTSTATUS (*session_key)(struct gensec_security *gensec_security, DATA_BLOB *session_key);
|
||||
NTSTATUS (*session_info)(struct gensec_security *gensec_security,
|
||||
struct auth_session_info **session_info);
|
||||
BOOL (*have_feature)(struct gensec_security *gensec_security,
|
||||
uint32_t feature);
|
||||
BOOL enabled;
|
||||
BOOL kerberos;
|
||||
enum gensec_priority priority;
|
||||
};
|
||||
|
||||
struct gensec_security_ops_wrapper {
|
||||
const struct gensec_security_ops *op;
|
||||
const char *oid;
|
||||
};
|
||||
|
||||
#define GENSEC_INTERFACE_VERSION 0
|
||||
|
||||
struct gensec_security {
|
||||
const struct gensec_security_ops *ops;
|
||||
void *private_data;
|
||||
struct cli_credentials *credentials;
|
||||
struct gensec_target target;
|
||||
enum gensec_role gensec_role;
|
||||
BOOL subcontext;
|
||||
uint32_t want_features;
|
||||
struct event_context *event_ctx;
|
||||
struct messaging_context *msg_ctx; /* only valid as server */
|
||||
struct socket_address *my_addr, *peer_addr;
|
||||
};
|
||||
|
||||
/* this structure is used by backends to determine the size of some critical types */
|
||||
struct gensec_critical_sizes {
|
||||
int interface_version;
|
||||
int sizeof_gensec_security_ops;
|
||||
int sizeof_gensec_security;
|
||||
};
|
||||
|
||||
#include "auth/gensec/gensec_proto.h"
|
||||
|
||||
#endif /* __GENSEC_H__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,792 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Kerberos backend for GENSEC
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Luke Howard 2002-2003
|
||||
Copyright (C) Stefan Metzmacher 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
#include "librpc/gen_ndr/krb5pac.h"
|
||||
#include "auth/auth.h"
|
||||
#include "lib/ldb/include/ldb.h"
|
||||
#include "auth/auth_sam.h"
|
||||
#include "system/network.h"
|
||||
#include "lib/socket/socket.h"
|
||||
#include "librpc/rpc/dcerpc.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/credentials/credentials_krb5.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
|
||||
enum GENSEC_KRB5_STATE {
|
||||
GENSEC_KRB5_SERVER_START,
|
||||
GENSEC_KRB5_CLIENT_START,
|
||||
GENSEC_KRB5_CLIENT_MUTUAL_AUTH,
|
||||
GENSEC_KRB5_DONE
|
||||
};
|
||||
|
||||
struct gensec_krb5_state {
|
||||
DATA_BLOB session_key;
|
||||
DATA_BLOB pac;
|
||||
enum GENSEC_KRB5_STATE state_position;
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
krb5_auth_context auth_context;
|
||||
krb5_data enc_ticket;
|
||||
krb5_keyblock *keyblock;
|
||||
krb5_ticket *ticket;
|
||||
BOOL gssapi;
|
||||
};
|
||||
|
||||
static int gensec_krb5_destroy(struct gensec_krb5_state *gensec_krb5_state)
|
||||
{
|
||||
if (!gensec_krb5_state->smb_krb5_context) {
|
||||
/* We can't clean anything else up unless we started up this far */
|
||||
return 0;
|
||||
}
|
||||
if (gensec_krb5_state->enc_ticket.length) {
|
||||
kerberos_free_data_contents(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
&gensec_krb5_state->enc_ticket);
|
||||
}
|
||||
|
||||
if (gensec_krb5_state->ticket) {
|
||||
krb5_free_ticket(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
gensec_krb5_state->ticket);
|
||||
}
|
||||
|
||||
/* ccache freed in a child destructor */
|
||||
|
||||
krb5_free_keyblock(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
gensec_krb5_state->keyblock);
|
||||
|
||||
if (gensec_krb5_state->auth_context) {
|
||||
krb5_auth_con_free(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
gensec_krb5_state->auth_context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
struct gensec_krb5_state *gensec_krb5_state;
|
||||
struct cli_credentials *creds;
|
||||
const struct socket_address *my_addr, *peer_addr;
|
||||
krb5_address my_krb5_addr, peer_krb5_addr;
|
||||
|
||||
creds = gensec_get_credentials(gensec_security);
|
||||
if (!creds) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
gensec_krb5_state = talloc(gensec_security, struct gensec_krb5_state);
|
||||
if (!gensec_krb5_state) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
gensec_security->private_data = gensec_krb5_state;
|
||||
gensec_krb5_state->smb_krb5_context = NULL;
|
||||
gensec_krb5_state->auth_context = NULL;
|
||||
gensec_krb5_state->ticket = NULL;
|
||||
ZERO_STRUCT(gensec_krb5_state->enc_ticket);
|
||||
gensec_krb5_state->keyblock = NULL;
|
||||
gensec_krb5_state->session_key = data_blob(NULL, 0);
|
||||
gensec_krb5_state->pac = data_blob(NULL, 0);
|
||||
gensec_krb5_state->gssapi = False;
|
||||
|
||||
talloc_set_destructor(gensec_krb5_state, gensec_krb5_destroy);
|
||||
|
||||
if (cli_credentials_get_krb5_context(creds, &gensec_krb5_state->smb_krb5_context)) {
|
||||
talloc_free(gensec_krb5_state);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
ret = krb5_auth_con_init(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->auth_context);
|
||||
if (ret) {
|
||||
DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, gensec_krb5_state)));
|
||||
talloc_free(gensec_krb5_state);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
ret = krb5_auth_con_setflags(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
gensec_krb5_state->auth_context,
|
||||
KRB5_AUTH_CONTEXT_DO_SEQUENCE);
|
||||
if (ret) {
|
||||
DEBUG(1,("gensec_krb5_start: krb5_auth_con_setflags failed (%s)\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, gensec_krb5_state)));
|
||||
talloc_free(gensec_krb5_state);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
my_addr = gensec_get_my_addr(gensec_security);
|
||||
if (my_addr && my_addr->sockaddr) {
|
||||
ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
my_addr->sockaddr, &my_krb5_addr);
|
||||
if (ret) {
|
||||
DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, gensec_krb5_state)));
|
||||
talloc_free(gensec_krb5_state);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
peer_addr = gensec_get_peer_addr(gensec_security);
|
||||
if (peer_addr && peer_addr->sockaddr) {
|
||||
ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
peer_addr->sockaddr, &peer_krb5_addr);
|
||||
if (ret) {
|
||||
DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, gensec_krb5_state)));
|
||||
talloc_free(gensec_krb5_state);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ret = krb5_auth_con_setaddrs(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
gensec_krb5_state->auth_context,
|
||||
my_addr ? &my_krb5_addr : NULL,
|
||||
peer_addr ? &peer_krb5_addr : NULL);
|
||||
if (ret) {
|
||||
DEBUG(1,("gensec_krb5_start: krb5_auth_con_setaddrs failed (%s)\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, gensec_krb5_state)));
|
||||
talloc_free(gensec_krb5_state);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct gensec_krb5_state *gensec_krb5_state;
|
||||
|
||||
nt_status = gensec_krb5_start(gensec_security);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
gensec_krb5_state = gensec_security->private_data;
|
||||
gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_fake_gssapi_krb5_server_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
NTSTATUS nt_status = gensec_krb5_server_start(gensec_security);
|
||||
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
struct gensec_krb5_state *gensec_krb5_state;
|
||||
gensec_krb5_state = gensec_security->private_data;
|
||||
gensec_krb5_state->gssapi = True;
|
||||
}
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
struct gensec_krb5_state *gensec_krb5_state;
|
||||
krb5_error_code ret;
|
||||
NTSTATUS nt_status;
|
||||
struct ccache_container *ccache_container;
|
||||
const char *hostname;
|
||||
krb5_flags ap_req_options = AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED;
|
||||
|
||||
const char *principal;
|
||||
krb5_data in_data;
|
||||
|
||||
hostname = gensec_get_target_hostname(gensec_security);
|
||||
if (!hostname) {
|
||||
DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
if (is_ipaddress(hostname)) {
|
||||
DEBUG(2, ("Cannot do krb5 to an IP address"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
if (strcmp(hostname, "localhost") == 0) {
|
||||
DEBUG(2, ("krb5 to 'localhost' does not make sense"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
nt_status = gensec_krb5_start(gensec_security);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
gensec_krb5_state = gensec_security->private_data;
|
||||
gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START;
|
||||
|
||||
ret = cli_credentials_get_ccache(gensec_get_credentials(gensec_security), &ccache_container);
|
||||
if (ret) {
|
||||
DEBUG(1,("gensec_krb5_start: cli_credentials_get_ccache failed: %s\n",
|
||||
error_message(ret)));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
in_data.length = 0;
|
||||
|
||||
principal = gensec_get_target_principal(gensec_security);
|
||||
if (principal && lp_client_use_spnego_principal()) {
|
||||
krb5_principal target_principal;
|
||||
ret = krb5_parse_name(gensec_krb5_state->smb_krb5_context->krb5_context, principal,
|
||||
&target_principal);
|
||||
if (ret == 0) {
|
||||
ret = krb5_mk_req_exact(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
&gensec_krb5_state->auth_context,
|
||||
ap_req_options,
|
||||
target_principal,
|
||||
&in_data, ccache_container->ccache,
|
||||
&gensec_krb5_state->enc_ticket);
|
||||
krb5_free_principal(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
target_principal);
|
||||
}
|
||||
} else {
|
||||
ret = krb5_mk_req(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
&gensec_krb5_state->auth_context,
|
||||
ap_req_options,
|
||||
gensec_get_target_service(gensec_security),
|
||||
hostname,
|
||||
&in_data, ccache_container->ccache,
|
||||
&gensec_krb5_state->enc_ticket);
|
||||
}
|
||||
switch (ret) {
|
||||
case 0:
|
||||
return NT_STATUS_OK;
|
||||
case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
|
||||
DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n",
|
||||
hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
|
||||
return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
|
||||
case KRB5_KDC_UNREACH:
|
||||
DEBUG(3, ("Cannot reach a KDC we require to contact host [%s]: %s\n",
|
||||
hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
|
||||
return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
|
||||
case KRB5KDC_ERR_PREAUTH_FAILED:
|
||||
case KRB5KRB_AP_ERR_TKT_EXPIRED:
|
||||
case KRB5_CC_END:
|
||||
/* Too much clock skew - we will need to kinit to re-skew the clock */
|
||||
case KRB5KRB_AP_ERR_SKEW:
|
||||
case KRB5_KDCREP_SKEW:
|
||||
{
|
||||
DEBUG(3, ("kerberos (mk_req) failed: %s\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
|
||||
/*fall through*/
|
||||
}
|
||||
|
||||
/* just don't print a message for these really ordinary messages */
|
||||
case KRB5_FCC_NOFILE:
|
||||
case KRB5_CC_NOTFOUND:
|
||||
case ENOENT:
|
||||
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG(0, ("kerberos: %s\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_fake_gssapi_krb5_client_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
NTSTATUS nt_status = gensec_krb5_client_start(gensec_security);
|
||||
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
struct gensec_krb5_state *gensec_krb5_state;
|
||||
gensec_krb5_state = gensec_security->private_data;
|
||||
gensec_krb5_state->gssapi = True;
|
||||
}
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the packet is one for this mechansim
|
||||
*
|
||||
* @param gensec_security GENSEC state
|
||||
* @param in The request, as a DATA_BLOB
|
||||
* @return Error, INVALID_PARAMETER if it's not a packet for us
|
||||
* or NT_STATUS_OK if the packet is ok.
|
||||
*/
|
||||
|
||||
static NTSTATUS gensec_fake_gssapi_krb5_magic(struct gensec_security *gensec_security,
|
||||
const DATA_BLOB *in)
|
||||
{
|
||||
if (gensec_gssapi_check_oid(in, GENSEC_OID_KERBEROS5)) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Next state function for the Krb5 GENSEC mechanism
|
||||
*
|
||||
* @param gensec_krb5_state KRB5 State
|
||||
* @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
|
||||
* @param in The request, as a DATA_BLOB
|
||||
* @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
|
||||
* @return Error, MORE_PROCESSING_REQUIRED if a reply is sent,
|
||||
* or NT_STATUS_OK if the user is authenticated.
|
||||
*/
|
||||
|
||||
static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
|
||||
krb5_error_code ret = 0;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
switch (gensec_krb5_state->state_position) {
|
||||
case GENSEC_KRB5_CLIENT_START:
|
||||
{
|
||||
DATA_BLOB unwrapped_out;
|
||||
|
||||
if (gensec_krb5_state->gssapi) {
|
||||
unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
|
||||
|
||||
/* wrap that up in a nice GSS-API wrapping */
|
||||
*out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ);
|
||||
} else {
|
||||
*out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
|
||||
}
|
||||
gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH;
|
||||
nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
case GENSEC_KRB5_CLIENT_MUTUAL_AUTH:
|
||||
{
|
||||
DATA_BLOB unwrapped_in;
|
||||
krb5_data inbuf;
|
||||
krb5_ap_rep_enc_part *repl = NULL;
|
||||
uint8_t tok_id[2];
|
||||
|
||||
if (gensec_krb5_state->gssapi) {
|
||||
if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
|
||||
DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n"));
|
||||
dump_data_pw("Mutual authentication message:\n", in.data, in.length);
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
} else {
|
||||
unwrapped_in = in;
|
||||
}
|
||||
/* TODO: check the tok_id */
|
||||
|
||||
inbuf.data = unwrapped_in.data;
|
||||
inbuf.length = unwrapped_in.length;
|
||||
ret = krb5_rd_rep(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
gensec_krb5_state->auth_context,
|
||||
&inbuf, &repl);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
|
||||
dump_data_pw("Mutual authentication message:\n", inbuf.data, inbuf.length);
|
||||
nt_status = NT_STATUS_ACCESS_DENIED;
|
||||
} else {
|
||||
*out = data_blob(NULL, 0);
|
||||
nt_status = NT_STATUS_OK;
|
||||
gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
|
||||
}
|
||||
if (repl) {
|
||||
krb5_free_ap_rep_enc_part(gensec_krb5_state->smb_krb5_context->krb5_context, repl);
|
||||
}
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
case GENSEC_KRB5_SERVER_START:
|
||||
{
|
||||
DATA_BLOB unwrapped_in;
|
||||
DATA_BLOB unwrapped_out = data_blob(NULL, 0);
|
||||
krb5_data inbuf, outbuf;
|
||||
uint8_t tok_id[2];
|
||||
struct keytab_container *keytab;
|
||||
krb5_principal server_in_keytab;
|
||||
|
||||
if (!in.data) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* Grab the keytab, however generated */
|
||||
ret = cli_credentials_get_keytab(gensec_get_credentials(gensec_security), &keytab);
|
||||
if (ret) {
|
||||
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
||||
}
|
||||
|
||||
/* This ensures we lookup the correct entry in that keytab */
|
||||
ret = principal_from_credentials(out_mem_ctx, gensec_get_credentials(gensec_security),
|
||||
gensec_krb5_state->smb_krb5_context,
|
||||
&server_in_keytab);
|
||||
|
||||
if (ret) {
|
||||
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
||||
}
|
||||
|
||||
/* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */
|
||||
if (gensec_krb5_state->gssapi
|
||||
&& gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
|
||||
inbuf.data = unwrapped_in.data;
|
||||
inbuf.length = unwrapped_in.length;
|
||||
} else {
|
||||
inbuf.data = in.data;
|
||||
inbuf.length = in.length;
|
||||
}
|
||||
|
||||
ret = smb_rd_req_return_stuff(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
&gensec_krb5_state->auth_context,
|
||||
&inbuf, keytab->keytab, server_in_keytab,
|
||||
&outbuf,
|
||||
&gensec_krb5_state->ticket,
|
||||
&gensec_krb5_state->keyblock);
|
||||
|
||||
if (ret) {
|
||||
return NT_STATUS_LOGON_FAILURE;
|
||||
}
|
||||
unwrapped_out.data = outbuf.data;
|
||||
unwrapped_out.length = outbuf.length;
|
||||
gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
|
||||
/* wrap that up in a nice GSS-API wrapping */
|
||||
if (gensec_krb5_state->gssapi) {
|
||||
*out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP);
|
||||
} else {
|
||||
*out = data_blob_talloc(out_mem_ctx, outbuf.data, outbuf.length);
|
||||
}
|
||||
krb5_data_free(&outbuf);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
case GENSEC_KRB5_DONE:
|
||||
default:
|
||||
/* Asking too many times... */
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
|
||||
DATA_BLOB *session_key)
|
||||
{
|
||||
struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
|
||||
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
|
||||
krb5_auth_context auth_context = gensec_krb5_state->auth_context;
|
||||
krb5_keyblock *skey;
|
||||
krb5_error_code err = -1;
|
||||
|
||||
if (gensec_krb5_state->session_key.data) {
|
||||
*session_key = gensec_krb5_state->session_key;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
switch (gensec_security->gensec_role) {
|
||||
case GENSEC_CLIENT:
|
||||
err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
|
||||
break;
|
||||
case GENSEC_SERVER:
|
||||
err = krb5_auth_con_getremotesubkey(context, auth_context, &skey);
|
||||
break;
|
||||
}
|
||||
if (err == 0 && skey != NULL) {
|
||||
DEBUG(10, ("Got KRB5 session key of length %d\n",
|
||||
(int)KRB5_KEY_LENGTH(skey)));
|
||||
gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state,
|
||||
KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
|
||||
*session_key = gensec_krb5_state->session_key;
|
||||
dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
|
||||
|
||||
krb5_free_keyblock(context, skey);
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
DEBUG(10, ("KRB5 error getting session key %d\n", err));
|
||||
return NT_STATUS_NO_USER_SESSION_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security,
|
||||
struct auth_session_info **_session_info)
|
||||
{
|
||||
NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
|
||||
struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
|
||||
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
|
||||
struct auth_serversupplied_info *server_info = NULL;
|
||||
struct auth_session_info *session_info = NULL;
|
||||
struct PAC_LOGON_INFO *logon_info;
|
||||
|
||||
krb5_principal client_principal;
|
||||
char *principal_string;
|
||||
|
||||
DATA_BLOB pac;
|
||||
krb5_data pac_data;
|
||||
|
||||
krb5_error_code ret;
|
||||
|
||||
TALLOC_CTX *mem_ctx = talloc_new(gensec_security);
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
ret = krb5_ticket_get_client(context, gensec_krb5_state->ticket, &client_principal);
|
||||
if (ret) {
|
||||
DEBUG(5, ("krb5_ticket_get_client failed to get cleint principal: %s\n",
|
||||
smb_get_krb5_error_message(context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
ret = krb5_unparse_name(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
client_principal, &principal_string);
|
||||
if (ret) {
|
||||
DEBUG(1, ("Unable to parse client principal: %s\n",
|
||||
smb_get_krb5_error_message(context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
ret = krb5_ticket_get_authorization_data_type(context, gensec_krb5_state->ticket,
|
||||
KRB5_AUTHDATA_WIN2K_PAC,
|
||||
&pac_data);
|
||||
|
||||
if (ret && lp_parm_bool(-1, "gensec", "require_pac", False)) {
|
||||
DEBUG(1, ("Unable to find PAC in ticket from %s, failing to allow access: %s \n",
|
||||
principal_string,
|
||||
smb_get_krb5_error_message(context,
|
||||
ret, mem_ctx)));
|
||||
krb5_free_principal(context, client_principal);
|
||||
free(principal_string);
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
} else if (ret) {
|
||||
/* NO pac */
|
||||
DEBUG(5, ("krb5_ticket_get_authorization_data_type failed to find PAC: %s\n",
|
||||
smb_get_krb5_error_message(context,
|
||||
ret, mem_ctx)));
|
||||
nt_status = sam_get_server_info_principal(mem_ctx, principal_string,
|
||||
&server_info);
|
||||
krb5_free_principal(context, client_principal);
|
||||
free(principal_string);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
} else {
|
||||
/* Found pac */
|
||||
union netr_Validation validation;
|
||||
free(principal_string);
|
||||
|
||||
pac = data_blob_talloc(mem_ctx, pac_data.data, pac_data.length);
|
||||
if (!pac.data) {
|
||||
krb5_free_principal(context, client_principal);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* decode and verify the pac */
|
||||
nt_status = kerberos_pac_logon_info(gensec_krb5_state, &logon_info, pac,
|
||||
gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
NULL, gensec_krb5_state->keyblock,
|
||||
client_principal,
|
||||
gensec_krb5_state->ticket->ticket.authtime, NULL);
|
||||
krb5_free_principal(context, client_principal);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
validation.sam3 = &logon_info->info3;
|
||||
nt_status = make_server_info_netlogon_validation(mem_ctx,
|
||||
NULL,
|
||||
3, &validation,
|
||||
&server_info);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
}
|
||||
|
||||
/* references the server_info into the session_info */
|
||||
nt_status = auth_generate_session_info(mem_ctx, server_info, &session_info);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = gensec_krb5_session_key(gensec_security, &session_info->session_key);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
*_session_info = session_info;
|
||||
|
||||
talloc_steal(gensec_krb5_state, session_info);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_wrap(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
|
||||
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
|
||||
krb5_auth_context auth_context = gensec_krb5_state->auth_context;
|
||||
krb5_error_code ret;
|
||||
krb5_data input, output;
|
||||
input.length = in->length;
|
||||
input.data = in->data;
|
||||
|
||||
if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
|
||||
ret = krb5_mk_priv(context, auth_context, &input, &output, NULL);
|
||||
if (ret) {
|
||||
DEBUG(1, ("krb5_mk_priv failed: %s\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
*out = data_blob_talloc(mem_ctx, output.data, output.length);
|
||||
|
||||
krb5_data_free(&output);
|
||||
} else {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_krb5_unwrap(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
|
||||
krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
|
||||
krb5_auth_context auth_context = gensec_krb5_state->auth_context;
|
||||
krb5_error_code ret;
|
||||
krb5_data input, output;
|
||||
krb5_replay_data replay;
|
||||
input.length = in->length;
|
||||
input.data = in->data;
|
||||
|
||||
if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
|
||||
ret = krb5_rd_priv(context, auth_context, &input, &output, &replay);
|
||||
if (ret) {
|
||||
DEBUG(1, ("krb5_rd_priv failed: %s\n",
|
||||
smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
*out = data_blob_talloc(mem_ctx, output.data, output.length);
|
||||
|
||||
krb5_data_free(&output);
|
||||
} else {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static BOOL gensec_krb5_have_feature(struct gensec_security *gensec_security,
|
||||
uint32_t feature)
|
||||
{
|
||||
struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
|
||||
if (feature & GENSEC_FEATURE_SESSION_KEY) {
|
||||
return True;
|
||||
}
|
||||
if (!gensec_krb5_state->gssapi &&
|
||||
(feature & GENSEC_FEATURE_SEAL)) {
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
static const char *gensec_krb5_oids[] = {
|
||||
GENSEC_OID_KERBEROS5,
|
||||
GENSEC_OID_KERBEROS5_OLD,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct gensec_security_ops gensec_fake_gssapi_krb5_security_ops = {
|
||||
.name = "fake_gssapi_krb5",
|
||||
.auth_type = DCERPC_AUTH_TYPE_KRB5,
|
||||
.oid = gensec_krb5_oids,
|
||||
.client_start = gensec_fake_gssapi_krb5_client_start,
|
||||
.server_start = gensec_fake_gssapi_krb5_server_start,
|
||||
.update = gensec_krb5_update,
|
||||
.magic = gensec_fake_gssapi_krb5_magic,
|
||||
.session_key = gensec_krb5_session_key,
|
||||
.session_info = gensec_krb5_session_info,
|
||||
.have_feature = gensec_krb5_have_feature,
|
||||
.enabled = False,
|
||||
.kerberos = True,
|
||||
.priority = GENSEC_KRB5
|
||||
};
|
||||
|
||||
static const struct gensec_security_ops gensec_krb5_security_ops = {
|
||||
.name = "krb5",
|
||||
.client_start = gensec_krb5_client_start,
|
||||
.server_start = gensec_krb5_server_start,
|
||||
.update = gensec_krb5_update,
|
||||
.session_key = gensec_krb5_session_key,
|
||||
.session_info = gensec_krb5_session_info,
|
||||
.have_feature = gensec_krb5_have_feature,
|
||||
.wrap = gensec_krb5_wrap,
|
||||
.unwrap = gensec_krb5_unwrap,
|
||||
.enabled = True,
|
||||
.kerberos = True,
|
||||
.priority = GENSEC_KRB5
|
||||
};
|
||||
|
||||
NTSTATUS gensec_krb5_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
auth_init();
|
||||
|
||||
ret = gensec_register(&gensec_krb5_security_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register '%s' gensec backend!\n",
|
||||
gensec_krb5_security_ops.name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gensec_register(&gensec_fake_gssapi_krb5_security_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register '%s' gensec backend!\n",
|
||||
gensec_krb5_security_ops.name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
dcerpc schannel operations
|
||||
|
||||
Copyright (C) Andrew Tridgell 2004
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "librpc/gen_ndr/ndr_schannel.h"
|
||||
#include "auth/auth.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
#include "auth/gensec/schannel.h"
|
||||
#include "auth/gensec/schannel_state.h"
|
||||
#include "auth/gensec/schannel_proto.h"
|
||||
#include "librpc/rpc/dcerpc.h"
|
||||
|
||||
static size_t schannel_sig_size(struct gensec_security *gensec_security, size_t data_size)
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
static NTSTATUS schannel_session_key(struct gensec_security *gensec_security,
|
||||
DATA_BLOB *session_key)
|
||||
{
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
static NTSTATUS schannel_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct schannel_state *state = gensec_security->private_data;
|
||||
NTSTATUS status;
|
||||
struct schannel_bind bind_schannel;
|
||||
struct schannel_bind_ack bind_schannel_ack;
|
||||
struct creds_CredentialState *creds;
|
||||
|
||||
const char *workstation;
|
||||
const char *domain;
|
||||
*out = data_blob(NULL, 0);
|
||||
|
||||
switch (gensec_security->gensec_role) {
|
||||
case GENSEC_CLIENT:
|
||||
if (state->state != SCHANNEL_STATE_START) {
|
||||
/* we could parse the bind ack, but we don't know what it is yet */
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
state->creds = talloc_reference(state, cli_credentials_get_netlogon_creds(gensec_security->credentials));
|
||||
|
||||
bind_schannel.unknown1 = 0;
|
||||
#if 0
|
||||
/* to support this we'd need to have access to the full domain name */
|
||||
bind_schannel.bind_type = 23;
|
||||
bind_schannel.u.info23.domain = cli_credentials_get_domain(gensec_security->credentials);
|
||||
bind_schannel.u.info23.workstation = cli_credentials_get_workstation(gensec_security->credentials);
|
||||
bind_schannel.u.info23.dnsdomain = cli_credentials_get_realm(gensec_security->credentials);
|
||||
/* w2k3 refuses us if we use the full DNS workstation?
|
||||
why? perhaps because we don't fill in the dNSHostName
|
||||
attribute in the machine account? */
|
||||
bind_schannel.u.info23.dnsworkstation = cli_credentials_get_workstation(gensec_security->credentials);
|
||||
#else
|
||||
bind_schannel.bind_type = 3;
|
||||
bind_schannel.u.info3.domain = cli_credentials_get_domain(gensec_security->credentials);
|
||||
bind_schannel.u.info3.workstation = cli_credentials_get_workstation(gensec_security->credentials);
|
||||
#endif
|
||||
|
||||
status = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel,
|
||||
(ndr_push_flags_fn_t)ndr_push_schannel_bind);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(3, ("Could not create schannel bind: %s\n",
|
||||
nt_errstr(status)));
|
||||
return status;
|
||||
}
|
||||
|
||||
state->state = SCHANNEL_STATE_UPDATE_1;
|
||||
|
||||
return NT_STATUS_MORE_PROCESSING_REQUIRED;
|
||||
case GENSEC_SERVER:
|
||||
|
||||
if (state->state != SCHANNEL_STATE_START) {
|
||||
/* no third leg on this protocol */
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* parse the schannel startup blob */
|
||||
status = ndr_pull_struct_blob(&in, out_mem_ctx, &bind_schannel,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_schannel_bind);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (bind_schannel.bind_type == 23) {
|
||||
workstation = bind_schannel.u.info23.workstation;
|
||||
domain = bind_schannel.u.info23.domain;
|
||||
} else {
|
||||
workstation = bind_schannel.u.info3.workstation;
|
||||
domain = bind_schannel.u.info3.domain;
|
||||
}
|
||||
|
||||
/* pull the session key for this client */
|
||||
status = schannel_fetch_session_key(out_mem_ctx, workstation,
|
||||
domain, &creds);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(3, ("Could not find session key for attempted schannel connection from %s: %s\n",
|
||||
workstation, nt_errstr(status)));
|
||||
return status;
|
||||
}
|
||||
|
||||
state->creds = talloc_reference(state, creds);
|
||||
|
||||
bind_schannel_ack.unknown1 = 1;
|
||||
bind_schannel_ack.unknown2 = 0;
|
||||
bind_schannel_ack.unknown3 = 0x6c0000;
|
||||
|
||||
status = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel_ack,
|
||||
(ndr_push_flags_fn_t)ndr_push_schannel_bind_ack);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(3, ("Could not return schannel bind ack for client %s: %s\n",
|
||||
workstation, nt_errstr(status)));
|
||||
return status;
|
||||
}
|
||||
|
||||
state->state = SCHANNEL_STATE_UPDATE_1;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the struct creds_CredentialState.
|
||||
*
|
||||
* Make sure not to call this unless gensec is using schannel...
|
||||
*/
|
||||
|
||||
/* TODO: make this non-public */
|
||||
_PUBLIC_ NTSTATUS dcerpc_schannel_creds(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
struct creds_CredentialState **creds)
|
||||
{
|
||||
struct schannel_state *state = talloc_get_type(gensec_security->private_data, struct schannel_state);
|
||||
|
||||
*creds = talloc_reference(mem_ctx, state->creds);
|
||||
if (!*creds) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns anonymous credentials for schannel, matching Win2k3.
|
||||
*
|
||||
*/
|
||||
|
||||
static NTSTATUS schannel_session_info(struct gensec_security *gensec_security,
|
||||
struct auth_session_info **_session_info)
|
||||
{
|
||||
struct schannel_state *state = talloc_get_type(gensec_security->private_data, struct schannel_state);
|
||||
return auth_anonymous_session_info(state, _session_info);
|
||||
}
|
||||
|
||||
static NTSTATUS schannel_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
struct schannel_state *state;
|
||||
|
||||
state = talloc(gensec_security, struct schannel_state);
|
||||
if (!state) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
state->state = SCHANNEL_STATE_START;
|
||||
state->seq_num = 0;
|
||||
gensec_security->private_data = state;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS schannel_server_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
NTSTATUS status;
|
||||
struct schannel_state *state;
|
||||
|
||||
status = schannel_start(gensec_security);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
state = gensec_security->private_data;
|
||||
state->initiator = False;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static NTSTATUS schannel_client_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
NTSTATUS status;
|
||||
struct schannel_state *state;
|
||||
|
||||
status = schannel_start(gensec_security);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
state = gensec_security->private_data;
|
||||
state->initiator = True;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
static BOOL schannel_have_feature(struct gensec_security *gensec_security,
|
||||
uint32_t feature)
|
||||
{
|
||||
if (feature & (GENSEC_FEATURE_SIGN |
|
||||
GENSEC_FEATURE_SEAL)) {
|
||||
return True;
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_DCE_STYLE) {
|
||||
return True;
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_ASYNC_REPLIES) {
|
||||
return True;
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
|
||||
static const struct gensec_security_ops gensec_schannel_security_ops = {
|
||||
.name = "schannel",
|
||||
.auth_type = DCERPC_AUTH_TYPE_SCHANNEL,
|
||||
.client_start = schannel_client_start,
|
||||
.server_start = schannel_server_start,
|
||||
.update = schannel_update,
|
||||
.seal_packet = schannel_seal_packet,
|
||||
.sign_packet = schannel_sign_packet,
|
||||
.check_packet = schannel_check_packet,
|
||||
.unseal_packet = schannel_unseal_packet,
|
||||
.session_key = schannel_session_key,
|
||||
.session_info = schannel_session_info,
|
||||
.sig_size = schannel_sig_size,
|
||||
.have_feature = schannel_have_feature,
|
||||
.enabled = True,
|
||||
.priority = GENSEC_SCHANNEL
|
||||
};
|
||||
|
||||
NTSTATUS gensec_schannel_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
ret = gensec_register(&gensec_schannel_security_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register '%s' gensec backend!\n",
|
||||
gensec_schannel_security_ops.name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
dcerpc schannel operations
|
||||
|
||||
Copyright (C) Andrew Tridgell 2004
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "libcli/auth/credentials.h"
|
||||
|
||||
enum schannel_position {
|
||||
SCHANNEL_STATE_START = 0,
|
||||
SCHANNEL_STATE_UPDATE_1
|
||||
};
|
||||
|
||||
struct schannel_state {
|
||||
enum schannel_position state;
|
||||
uint32_t seq_num;
|
||||
BOOL initiator;
|
||||
struct creds_CredentialState *creds;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
schannel library code
|
||||
|
||||
Copyright (C) Andrew Tridgell 2004
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/crypto/crypto.h"
|
||||
#include "auth/auth.h"
|
||||
#include "auth/gensec/schannel.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
|
||||
#define NETSEC_SIGN_SIGNATURE { 0x77, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 }
|
||||
#define NETSEC_SEAL_SIGNATURE { 0x77, 0x00, 0x7a, 0x00, 0xff, 0xff, 0x00, 0x00 }
|
||||
|
||||
/*******************************************************************
|
||||
Encode or Decode the sequence number (which is symmetric)
|
||||
********************************************************************/
|
||||
static void netsec_deal_with_seq_num(struct schannel_state *state,
|
||||
const uint8_t packet_digest[8],
|
||||
uint8_t seq_num[8])
|
||||
{
|
||||
static const uint8_t zeros[4];
|
||||
uint8_t sequence_key[16];
|
||||
uint8_t digest1[16];
|
||||
|
||||
hmac_md5(state->creds->session_key, zeros, sizeof(zeros), digest1);
|
||||
hmac_md5(digest1, packet_digest, 8, sequence_key);
|
||||
arcfour_crypt(seq_num, sequence_key, 8);
|
||||
|
||||
state->seq_num++;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
Calculate the key with which to encode the data payload
|
||||
********************************************************************/
|
||||
static void netsec_get_sealing_key(const uint8_t session_key[16],
|
||||
const uint8_t seq_num[8],
|
||||
uint8_t sealing_key[16])
|
||||
{
|
||||
static const uint8_t zeros[4];
|
||||
uint8_t digest2[16];
|
||||
uint8_t sess_kf0[16];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
sess_kf0[i] = session_key[i] ^ 0xf0;
|
||||
}
|
||||
|
||||
hmac_md5(sess_kf0, zeros, 4, digest2);
|
||||
hmac_md5(digest2, seq_num, 8, sealing_key);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
Create a digest over the entire packet (including the data), and
|
||||
MD5 it with the session key.
|
||||
********************************************************************/
|
||||
static void schannel_digest(const uint8_t sess_key[16],
|
||||
const uint8_t netsec_sig[8],
|
||||
const uint8_t *confounder,
|
||||
const uint8_t *data, size_t data_len,
|
||||
uint8_t digest_final[16])
|
||||
{
|
||||
uint8_t packet_digest[16];
|
||||
static const uint8_t zeros[4];
|
||||
struct MD5Context ctx;
|
||||
|
||||
MD5Init(&ctx);
|
||||
MD5Update(&ctx, zeros, 4);
|
||||
MD5Update(&ctx, netsec_sig, 8);
|
||||
if (confounder) {
|
||||
MD5Update(&ctx, confounder, 8);
|
||||
}
|
||||
MD5Update(&ctx, data, data_len);
|
||||
MD5Final(packet_digest, &ctx);
|
||||
|
||||
hmac_md5(sess_key, packet_digest, sizeof(packet_digest), digest_final);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
unseal a packet
|
||||
*/
|
||||
NTSTATUS schannel_unseal_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
const DATA_BLOB *sig)
|
||||
{
|
||||
struct schannel_state *state = talloc_get_type(gensec_security->private_data, struct schannel_state);
|
||||
|
||||
uint8_t digest_final[16];
|
||||
uint8_t confounder[8];
|
||||
uint8_t seq_num[8];
|
||||
uint8_t sealing_key[16];
|
||||
static const uint8_t netsec_sig[8] = NETSEC_SEAL_SIGNATURE;
|
||||
|
||||
if (sig->length != 32) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
memcpy(confounder, sig->data+24, 8);
|
||||
|
||||
RSIVAL(seq_num, 0, state->seq_num);
|
||||
SIVAL(seq_num, 4, state->initiator?0:0x80);
|
||||
|
||||
netsec_get_sealing_key(state->creds->session_key, seq_num, sealing_key);
|
||||
arcfour_crypt(confounder, sealing_key, 8);
|
||||
arcfour_crypt(data, sealing_key, length);
|
||||
|
||||
schannel_digest(state->creds->session_key,
|
||||
netsec_sig, confounder,
|
||||
data, length, digest_final);
|
||||
|
||||
if (memcmp(digest_final, sig->data+16, 8) != 0) {
|
||||
dump_data_pw("calc digest:", digest_final, 8);
|
||||
dump_data_pw("wire digest:", sig->data+16, 8);
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
netsec_deal_with_seq_num(state, digest_final, seq_num);
|
||||
|
||||
if (memcmp(seq_num, sig->data+8, 8) != 0) {
|
||||
dump_data_pw("calc seq num:", seq_num, 8);
|
||||
dump_data_pw("wire seq num:", sig->data+8, 8);
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
check the signature on a packet
|
||||
*/
|
||||
NTSTATUS schannel_check_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
const DATA_BLOB *sig)
|
||||
{
|
||||
struct schannel_state *state = talloc_get_type(gensec_security->private_data, struct schannel_state);
|
||||
|
||||
uint8_t digest_final[16];
|
||||
uint8_t seq_num[8];
|
||||
static const uint8_t netsec_sig[8] = NETSEC_SIGN_SIGNATURE;
|
||||
|
||||
/* w2k sends just 24 bytes and skip the confounder */
|
||||
if (sig->length != 32 && sig->length != 24) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
RSIVAL(seq_num, 0, state->seq_num);
|
||||
SIVAL(seq_num, 4, state->initiator?0:0x80);
|
||||
|
||||
dump_data_pw("seq_num:\n", seq_num, 8);
|
||||
dump_data_pw("sess_key:\n", state->creds->session_key, 16);
|
||||
|
||||
schannel_digest(state->creds->session_key,
|
||||
netsec_sig, NULL,
|
||||
data, length, digest_final);
|
||||
|
||||
netsec_deal_with_seq_num(state, digest_final, seq_num);
|
||||
|
||||
if (memcmp(seq_num, sig->data+8, 8) != 0) {
|
||||
dump_data_pw("calc seq num:", seq_num, 8);
|
||||
dump_data_pw("wire seq num:", sig->data+8, 8);
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (memcmp(digest_final, sig->data+16, 8) != 0) {
|
||||
dump_data_pw("calc digest:", digest_final, 8);
|
||||
dump_data_pw("wire digest:", sig->data+16, 8);
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
seal a packet
|
||||
*/
|
||||
NTSTATUS schannel_seal_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
DATA_BLOB *sig)
|
||||
{
|
||||
struct schannel_state *state = talloc_get_type(gensec_security->private_data, struct schannel_state);
|
||||
|
||||
uint8_t digest_final[16];
|
||||
uint8_t confounder[8];
|
||||
uint8_t seq_num[8];
|
||||
uint8_t sealing_key[16];
|
||||
static const uint8_t netsec_sig[8] = NETSEC_SEAL_SIGNATURE;
|
||||
|
||||
generate_random_buffer(confounder, 8);
|
||||
|
||||
RSIVAL(seq_num, 0, state->seq_num);
|
||||
SIVAL(seq_num, 4, state->initiator?0x80:0);
|
||||
|
||||
schannel_digest(state->creds->session_key,
|
||||
netsec_sig, confounder,
|
||||
data, length, digest_final);
|
||||
|
||||
netsec_get_sealing_key(state->creds->session_key, seq_num, sealing_key);
|
||||
arcfour_crypt(confounder, sealing_key, 8);
|
||||
arcfour_crypt(data, sealing_key, length);
|
||||
|
||||
netsec_deal_with_seq_num(state, digest_final, seq_num);
|
||||
|
||||
(*sig) = data_blob_talloc(mem_ctx, NULL, 32);
|
||||
|
||||
memcpy(sig->data, netsec_sig, 8);
|
||||
memcpy(sig->data+8, seq_num, 8);
|
||||
memcpy(sig->data+16, digest_final, 8);
|
||||
memcpy(sig->data+24, confounder, 8);
|
||||
|
||||
dump_data_pw("signature:", sig->data+ 0, 8);
|
||||
dump_data_pw("seq_num :", sig->data+ 8, 8);
|
||||
dump_data_pw("digest :", sig->data+16, 8);
|
||||
dump_data_pw("confound :", sig->data+24, 8);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
sign a packet
|
||||
*/
|
||||
NTSTATUS schannel_sign_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
DATA_BLOB *sig)
|
||||
{
|
||||
struct schannel_state *state = talloc_get_type(gensec_security->private_data, struct schannel_state);
|
||||
|
||||
uint8_t digest_final[16];
|
||||
uint8_t seq_num[8];
|
||||
static const uint8_t netsec_sig[8] = NETSEC_SIGN_SIGNATURE;
|
||||
|
||||
RSIVAL(seq_num, 0, state->seq_num);
|
||||
SIVAL(seq_num, 4, state->initiator?0x80:0);
|
||||
|
||||
schannel_digest(state->creds->session_key,
|
||||
netsec_sig, NULL,
|
||||
data, length, digest_final);
|
||||
|
||||
netsec_deal_with_seq_num(state, digest_final, seq_num);
|
||||
|
||||
(*sig) = data_blob_talloc(mem_ctx, NULL, 32);
|
||||
|
||||
memcpy(sig->data, netsec_sig, 8);
|
||||
memcpy(sig->data+8, seq_num, 8);
|
||||
memcpy(sig->data+16, digest_final, 8);
|
||||
memset(sig->data+24, 0, 8);
|
||||
|
||||
dump_data_pw("signature:", sig->data+ 0, 8);
|
||||
dump_data_pw("seq_num :", sig->data+ 8, 8);
|
||||
dump_data_pw("digest :", sig->data+16, 8);
|
||||
dump_data_pw("confound :", sig->data+24, 8);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
module to store/fetch session keys for the schannel server
|
||||
|
||||
Copyright (C) Andrew Tridgell 2004
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/ldb/include/ldb.h"
|
||||
#include "lib/ldb/include/ldb_errors.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "db_wrap.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
#include "auth/auth.h"
|
||||
|
||||
/**
|
||||
connect to the schannel ldb
|
||||
*/
|
||||
struct ldb_context *schannel_db_connect(TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
char *path;
|
||||
struct ldb_context *ldb;
|
||||
BOOL existed;
|
||||
const char *init_ldif =
|
||||
"dn: @ATTRIBUTES\n" \
|
||||
"computerName: CASE_INSENSITIVE\n" \
|
||||
"flatname: CASE_INSENSITIVE\n";
|
||||
|
||||
path = smbd_tmp_path(mem_ctx, "schannel.ldb");
|
||||
if (!path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
existed = file_exist(path);
|
||||
|
||||
ldb = ldb_wrap_connect(mem_ctx, path, system_session(mem_ctx),
|
||||
NULL, LDB_FLG_NOSYNC, NULL);
|
||||
talloc_free(path);
|
||||
if (!ldb) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!existed) {
|
||||
gendb_add_ldif(ldb, init_ldif);
|
||||
}
|
||||
|
||||
return ldb;
|
||||
}
|
||||
|
||||
/*
|
||||
remember an established session key for a netr server authentication
|
||||
use a simple ldb structure
|
||||
*/
|
||||
NTSTATUS schannel_store_session_key_ldb(TALLOC_CTX *mem_ctx,
|
||||
struct ldb_context *ldb,
|
||||
struct creds_CredentialState *creds)
|
||||
{
|
||||
struct ldb_message *msg;
|
||||
struct ldb_val val, seed, client_state, server_state;
|
||||
char *f;
|
||||
char *sct;
|
||||
int ret;
|
||||
|
||||
f = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->negotiate_flags);
|
||||
|
||||
if (f == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
sct = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->secure_channel_type);
|
||||
|
||||
if (sct == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
msg = ldb_msg_new(ldb);
|
||||
if (msg == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
msg->dn = ldb_dn_new_fmt(msg, ldb, "computerName=%s", creds->computer_name);
|
||||
if ( ! msg->dn) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
val.data = creds->session_key;
|
||||
val.length = sizeof(creds->session_key);
|
||||
|
||||
seed.data = creds->seed.data;
|
||||
seed.length = sizeof(creds->seed.data);
|
||||
|
||||
client_state.data = creds->client.data;
|
||||
client_state.length = sizeof(creds->client.data);
|
||||
server_state.data = creds->server.data;
|
||||
server_state.length = sizeof(creds->server.data);
|
||||
|
||||
ldb_msg_add_string(msg, "objectClass", "schannelState");
|
||||
ldb_msg_add_value(msg, "sessionKey", &val, NULL);
|
||||
ldb_msg_add_value(msg, "seed", &seed, NULL);
|
||||
ldb_msg_add_value(msg, "clientState", &client_state, NULL);
|
||||
ldb_msg_add_value(msg, "serverState", &server_state, NULL);
|
||||
ldb_msg_add_string(msg, "negotiateFlags", f);
|
||||
ldb_msg_add_string(msg, "secureChannelType", sct);
|
||||
ldb_msg_add_string(msg, "accountName", creds->account_name);
|
||||
ldb_msg_add_string(msg, "computerName", creds->computer_name);
|
||||
ldb_msg_add_string(msg, "flatname", creds->domain);
|
||||
samdb_msg_add_dom_sid(ldb, mem_ctx, msg, "objectSid", creds->sid);
|
||||
|
||||
ldb_delete(ldb, msg->dn);
|
||||
|
||||
ret = ldb_add(ldb, msg);
|
||||
|
||||
if (ret != 0) {
|
||||
DEBUG(0,("Unable to add %s to session key db - %s\n",
|
||||
ldb_dn_get_linearized(msg->dn), ldb_errstring(ldb)));
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS schannel_store_session_key(TALLOC_CTX *mem_ctx,
|
||||
struct creds_CredentialState *creds)
|
||||
{
|
||||
struct ldb_context *ldb;
|
||||
NTSTATUS nt_status;
|
||||
int ret;
|
||||
|
||||
ldb = schannel_db_connect(mem_ctx);
|
||||
if (!ldb) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
ret = ldb_transaction_start(ldb);
|
||||
if (ret != 0) {
|
||||
talloc_free(ldb);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
nt_status = schannel_store_session_key_ldb(mem_ctx, ldb, creds);
|
||||
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
ret = ldb_transaction_commit(ldb);
|
||||
} else {
|
||||
ret = ldb_transaction_cancel(ldb);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
DEBUG(0,("Unable to commit adding credentials for %s to schannel key db - %s\n",
|
||||
creds->computer_name, ldb_errstring(ldb)));
|
||||
talloc_free(ldb);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
talloc_free(ldb);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/*
|
||||
read back a credentials back for a computer
|
||||
*/
|
||||
NTSTATUS schannel_fetch_session_key_ldb(TALLOC_CTX *mem_ctx,
|
||||
struct ldb_context *ldb,
|
||||
const char *computer_name,
|
||||
const char *domain,
|
||||
struct creds_CredentialState **creds)
|
||||
{
|
||||
struct ldb_result *res;
|
||||
int ret;
|
||||
const struct ldb_val *val;
|
||||
|
||||
*creds = talloc_zero(mem_ctx, struct creds_CredentialState);
|
||||
if (!*creds) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
|
||||
NULL, LDB_SCOPE_SUBTREE, NULL,
|
||||
"(&(computerName=%s)(flatname=%s))", computer_name, domain);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
DEBUG(3,("schannel: Failed to find a record for client %s: %s\n", computer_name, ldb_errstring(ldb)));
|
||||
return NT_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
if (res->count != 1) {
|
||||
DEBUG(3,("schannel: Failed to find a record for client: %s (found %d records)\n", computer_name, res->count));
|
||||
talloc_free(res);
|
||||
return NT_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
val = ldb_msg_find_ldb_val(res->msgs[0], "sessionKey");
|
||||
if (val == NULL || val->length != 16) {
|
||||
DEBUG(1,("schannel: record in schannel DB must contain a sessionKey of length 16, when searching for client: %s\n", computer_name));
|
||||
talloc_free(res);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
memcpy((*creds)->session_key, val->data, 16);
|
||||
|
||||
val = ldb_msg_find_ldb_val(res->msgs[0], "seed");
|
||||
if (val == NULL || val->length != 8) {
|
||||
DEBUG(1,("schannel: record in schannel DB must contain a vaid seed of length 8, when searching for client: %s\n", computer_name));
|
||||
talloc_free(res);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
memcpy((*creds)->seed.data, val->data, 8);
|
||||
|
||||
val = ldb_msg_find_ldb_val(res->msgs[0], "clientState");
|
||||
if (val == NULL || val->length != 8) {
|
||||
DEBUG(1,("schannel: record in schannel DB must contain a vaid clientState of length 8, when searching for client: %s\n", computer_name));
|
||||
talloc_free(res);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
memcpy((*creds)->client.data, val->data, 8);
|
||||
|
||||
val = ldb_msg_find_ldb_val(res->msgs[0], "serverState");
|
||||
if (val == NULL || val->length != 8) {
|
||||
DEBUG(1,("schannel: record in schannel DB must contain a vaid serverState of length 8, when searching for client: %s\n", computer_name));
|
||||
talloc_free(res);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
memcpy((*creds)->server.data, val->data, 8);
|
||||
|
||||
(*creds)->negotiate_flags = ldb_msg_find_attr_as_int(res->msgs[0], "negotiateFlags", 0);
|
||||
|
||||
(*creds)->secure_channel_type = ldb_msg_find_attr_as_int(res->msgs[0], "secureChannelType", 0);
|
||||
|
||||
(*creds)->account_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "accountName", NULL));
|
||||
if ((*creds)->account_name == NULL) {
|
||||
talloc_free(res);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
(*creds)->computer_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "computerName", NULL));
|
||||
if ((*creds)->computer_name == NULL) {
|
||||
talloc_free(res);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
(*creds)->domain = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "flatname", NULL));
|
||||
if ((*creds)->domain == NULL) {
|
||||
talloc_free(res);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
(*creds)->sid = samdb_result_dom_sid(*creds, res->msgs[0], "objectSid");
|
||||
|
||||
talloc_free(res);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS schannel_fetch_session_key(TALLOC_CTX *mem_ctx,
|
||||
const char *computer_name,
|
||||
const char *domain,
|
||||
struct creds_CredentialState **creds)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct ldb_context *ldb;
|
||||
|
||||
ldb = schannel_db_connect(mem_ctx);
|
||||
if (!ldb) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
nt_status = schannel_fetch_session_key_ldb(mem_ctx, ldb,
|
||||
computer_name, domain,
|
||||
creds);
|
||||
talloc_free(ldb);
|
||||
return nt_status;
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
GENSEC socket interface
|
||||
|
||||
Copyright (C) Andrew Bartlett 2006
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/events/events.h"
|
||||
#include "lib/socket/socket.h"
|
||||
#include "lib/stream/packet.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
|
||||
static const struct socket_ops gensec_socket_ops;
|
||||
|
||||
struct gensec_socket {
|
||||
struct gensec_security *gensec_security;
|
||||
struct socket_context *socket;
|
||||
struct event_context *ev;
|
||||
struct packet_context *packet;
|
||||
DATA_BLOB read_buffer; /* SASL packets are turned into liniarlised data here, for reading */
|
||||
size_t orig_send_len;
|
||||
BOOL eof;
|
||||
NTSTATUS error;
|
||||
BOOL interrupted;
|
||||
void (*recv_handler)(void *, uint16_t);
|
||||
void *recv_private;
|
||||
int in_extra_read;
|
||||
BOOL wrap; /* Should we be wrapping on this socket at all? */
|
||||
};
|
||||
|
||||
static NTSTATUS gensec_socket_init_fn(struct socket_context *sock)
|
||||
{
|
||||
switch (sock->type) {
|
||||
case SOCKET_TYPE_STREAM:
|
||||
break;
|
||||
default:
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
sock->backend_name = "gensec";
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* These functions are for use here only (public because SPNEGO must
|
||||
* use them for recursion) */
|
||||
_PUBLIC_ NTSTATUS gensec_wrap_packets(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed)
|
||||
{
|
||||
if (!gensec_security->ops->wrap_packets) {
|
||||
NTSTATUS nt_status;
|
||||
size_t max_input_size;
|
||||
DATA_BLOB unwrapped, wrapped;
|
||||
max_input_size = gensec_max_input_size(gensec_security);
|
||||
unwrapped = data_blob_const(in->data, MIN(max_input_size, (size_t)in->length));
|
||||
|
||||
nt_status = gensec_wrap(gensec_security,
|
||||
mem_ctx,
|
||||
&unwrapped, &wrapped);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
*out = data_blob_talloc(mem_ctx, NULL, 4);
|
||||
if (!out->data) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
RSIVAL(out->data, 0, wrapped.length);
|
||||
|
||||
nt_status = data_blob_append(mem_ctx, out, wrapped.data, wrapped.length);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
*len_processed = unwrapped.length;
|
||||
return nt_status;
|
||||
}
|
||||
return gensec_security->ops->wrap_packets(gensec_security, mem_ctx, in, out,
|
||||
len_processed);
|
||||
}
|
||||
|
||||
/* These functions are for use here only (public because SPNEGO must
|
||||
* use them for recursion) */
|
||||
NTSTATUS gensec_unwrap_packets(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed)
|
||||
{
|
||||
if (!gensec_security->ops->unwrap_packets) {
|
||||
DATA_BLOB wrapped;
|
||||
NTSTATUS nt_status;
|
||||
size_t packet_size;
|
||||
if (in->length < 4) {
|
||||
/* Missing the header we already had! */
|
||||
DEBUG(0, ("Asked to unwrap packet of bogus length! How did we get the short packet?!\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
packet_size = RIVAL(in->data, 0);
|
||||
|
||||
wrapped = data_blob_const(in->data + 4, packet_size);
|
||||
|
||||
if (wrapped.length > (in->length - 4)) {
|
||||
DEBUG(0, ("Asked to unwrap packed of bogus length %d > %d! How did we get this?!\n",
|
||||
(int)wrapped.length, (int)(in->length - 4)));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
nt_status = gensec_unwrap(gensec_security,
|
||||
mem_ctx,
|
||||
&wrapped, out);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
*len_processed = packet_size + 4;
|
||||
return nt_status;
|
||||
}
|
||||
return gensec_security->ops->unwrap_packets(gensec_security, mem_ctx, in, out,
|
||||
len_processed);
|
||||
}
|
||||
|
||||
/* These functions are for use here only (public because SPNEGO must
|
||||
* use them for recursion) */
|
||||
NTSTATUS gensec_packet_full_request(struct gensec_security *gensec_security,
|
||||
DATA_BLOB blob, size_t *size)
|
||||
{
|
||||
if (gensec_security->ops->packet_full_request) {
|
||||
return gensec_security->ops->packet_full_request(gensec_security,
|
||||
blob, size);
|
||||
}
|
||||
if (gensec_security->ops->unwrap_packets) {
|
||||
if (blob.length) {
|
||||
*size = blob.length;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
return STATUS_MORE_ENTRIES;
|
||||
}
|
||||
return packet_full_request_u32(NULL, blob, size);
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_socket_full_request(void *private, DATA_BLOB blob, size_t *size)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket);
|
||||
struct gensec_security *gensec_security = gensec_socket->gensec_security;
|
||||
return gensec_packet_full_request(gensec_security, blob, size);
|
||||
}
|
||||
|
||||
/* Try to figure out how much data is waiting to be read */
|
||||
static NTSTATUS gensec_socket_pending(struct socket_context *sock, size_t *npending)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
if (!gensec_socket->wrap) {
|
||||
return socket_pending(gensec_socket->socket, npending);
|
||||
}
|
||||
|
||||
if (gensec_socket->read_buffer.length > 0) {
|
||||
*npending = gensec_socket->read_buffer.length;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* This is a lie. We hope the decrypted data will always be
|
||||
* less than this value, so the application just gets a short
|
||||
* read. Without reading and decrypting it, we can't tell.
|
||||
* If the SASL mech does compression, then we just need to
|
||||
* manually trigger read events */
|
||||
return socket_pending(gensec_socket->socket, npending);
|
||||
}
|
||||
|
||||
/* Note if an error occours, so we can return it up the stack */
|
||||
static void gensec_socket_error_handler(void *private, NTSTATUS status)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket);
|
||||
if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
|
||||
gensec_socket->eof = True;
|
||||
} else {
|
||||
gensec_socket->error = status;
|
||||
}
|
||||
}
|
||||
|
||||
static void gensec_socket_trigger_read(struct event_context *ev,
|
||||
struct timed_event *te,
|
||||
struct timeval t, void *private)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket);
|
||||
|
||||
gensec_socket->in_extra_read++;
|
||||
gensec_socket->recv_handler(gensec_socket->recv_private, EVENT_FD_READ);
|
||||
gensec_socket->in_extra_read--;
|
||||
|
||||
/* It may well be that, having run the recv handler, we still
|
||||
* have even more data waiting for us!
|
||||
*/
|
||||
if (gensec_socket->read_buffer.length && gensec_socket->recv_handler) {
|
||||
/* Schedule this funcion to run again */
|
||||
event_add_timed(gensec_socket->ev, gensec_socket, timeval_zero(),
|
||||
gensec_socket_trigger_read, gensec_socket);
|
||||
}
|
||||
}
|
||||
|
||||
/* These two routines could be changed to use a circular buffer of
|
||||
* some kind, or linked lists, or ... */
|
||||
static NTSTATUS gensec_socket_recv(struct socket_context *sock, void *buf,
|
||||
size_t wantlen, size_t *nread)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
|
||||
if (!gensec_socket->wrap) {
|
||||
return socket_recv(gensec_socket->socket, buf, wantlen, nread);
|
||||
}
|
||||
|
||||
gensec_socket->error = NT_STATUS_OK;
|
||||
|
||||
if (gensec_socket->read_buffer.length == 0) {
|
||||
/* Process any data on the socket, into the read buffer. At
|
||||
* this point, the socket is not available for read any
|
||||
* longer */
|
||||
packet_recv(gensec_socket->packet);
|
||||
|
||||
if (gensec_socket->eof) {
|
||||
*nread = 0;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
if (!NT_STATUS_IS_OK(gensec_socket->error)) {
|
||||
return gensec_socket->error;
|
||||
}
|
||||
|
||||
if (gensec_socket->read_buffer.length == 0) {
|
||||
/* Clearly we don't have the entire SASL packet yet,
|
||||
* so it has not been written into the buffer */
|
||||
*nread = 0;
|
||||
return STATUS_MORE_ENTRIES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*nread = MIN(wantlen, gensec_socket->read_buffer.length);
|
||||
memcpy(buf, gensec_socket->read_buffer.data, *nread);
|
||||
|
||||
if (gensec_socket->read_buffer.length > *nread) {
|
||||
memmove(gensec_socket->read_buffer.data,
|
||||
gensec_socket->read_buffer.data + *nread,
|
||||
gensec_socket->read_buffer.length - *nread);
|
||||
}
|
||||
|
||||
gensec_socket->read_buffer.length -= *nread;
|
||||
gensec_socket->read_buffer.data = talloc_realloc(gensec_socket,
|
||||
gensec_socket->read_buffer.data,
|
||||
uint8_t,
|
||||
gensec_socket->read_buffer.length);
|
||||
|
||||
if (gensec_socket->read_buffer.length &&
|
||||
gensec_socket->in_extra_read == 0 &&
|
||||
gensec_socket->recv_handler) {
|
||||
/* Manually call a read event, to get this moving
|
||||
* again (as the socket should be dry, so the normal
|
||||
* event handler won't trigger) */
|
||||
event_add_timed(gensec_socket->ev, gensec_socket, timeval_zero(),
|
||||
gensec_socket_trigger_read, gensec_socket);
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* Completed SASL packet callback. When we have a 'whole' SASL
|
||||
* packet, decrypt it, and add it to the read buffer
|
||||
*
|
||||
* This function (and anything under it) MUST NOT call the event system
|
||||
*/
|
||||
static NTSTATUS gensec_socket_unwrap(void *private, DATA_BLOB blob)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket);
|
||||
DATA_BLOB unwrapped;
|
||||
NTSTATUS nt_status;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
size_t packet_size;
|
||||
|
||||
mem_ctx = talloc_new(gensec_socket);
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
nt_status = gensec_unwrap_packets(gensec_socket->gensec_security,
|
||||
mem_ctx,
|
||||
&blob, &unwrapped,
|
||||
&packet_size);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if (packet_size != blob.length) {
|
||||
DEBUG(0, ("gensec_socket_unwrap: Did not consume entire packet!\n"));
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
/* We could change this into a linked list, and have
|
||||
* gensec_socket_recv() and gensec_socket_pending() walk the
|
||||
* linked list */
|
||||
|
||||
nt_status = data_blob_append(gensec_socket, &gensec_socket->read_buffer,
|
||||
unwrapped.data, unwrapped.length);
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* when the data is sent, we know we have not been interrupted */
|
||||
static void send_callback(void *private)
|
||||
{
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket);
|
||||
gensec_socket->interrupted = False;
|
||||
}
|
||||
|
||||
/*
|
||||
send data, but only as much as we allow in one packet.
|
||||
|
||||
If this returns STATUS_MORE_ENTRIES, the caller must retry with
|
||||
exactly the same data, or a NULL blob.
|
||||
*/
|
||||
static NTSTATUS gensec_socket_send(struct socket_context *sock,
|
||||
const DATA_BLOB *blob, size_t *sendlen)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct gensec_socket *gensec_socket = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
DATA_BLOB wrapped;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
|
||||
if (!gensec_socket->wrap) {
|
||||
return socket_send(gensec_socket->socket, blob, sendlen);
|
||||
}
|
||||
|
||||
*sendlen = 0;
|
||||
|
||||
/* We have have been interupted, so the caller should be
|
||||
* giving us the same data again. */
|
||||
if (gensec_socket->interrupted) {
|
||||
packet_queue_run(gensec_socket->packet);
|
||||
|
||||
if (!NT_STATUS_IS_OK(gensec_socket->error)) {
|
||||
return gensec_socket->error;
|
||||
} else if (gensec_socket->interrupted) {
|
||||
return STATUS_MORE_ENTRIES;
|
||||
} else {
|
||||
*sendlen = gensec_socket->orig_send_len;
|
||||
gensec_socket->orig_send_len = 0;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
mem_ctx = talloc_new(gensec_socket);
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
nt_status = gensec_wrap_packets(gensec_socket->gensec_security,
|
||||
mem_ctx,
|
||||
blob, &wrapped,
|
||||
&gensec_socket->orig_send_len);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
gensec_socket->interrupted = True;
|
||||
gensec_socket->error = NT_STATUS_OK;
|
||||
|
||||
nt_status = packet_send_callback(gensec_socket->packet,
|
||||
wrapped,
|
||||
send_callback, gensec_socket);
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
packet_queue_run(gensec_socket->packet);
|
||||
|
||||
if (!NT_STATUS_IS_OK(gensec_socket->error)) {
|
||||
return gensec_socket->error;
|
||||
} else if (gensec_socket->interrupted) {
|
||||
return STATUS_MORE_ENTRIES;
|
||||
} else {
|
||||
*sendlen = gensec_socket->orig_send_len;
|
||||
gensec_socket->orig_send_len = 0;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Turn a normal socket into a potentially GENSEC wrapped socket */
|
||||
|
||||
NTSTATUS gensec_socket_init(struct gensec_security *gensec_security,
|
||||
struct socket_context *current_socket,
|
||||
struct event_context *ev,
|
||||
void (*recv_handler)(void *, uint16_t),
|
||||
void *recv_private,
|
||||
struct socket_context **new_socket)
|
||||
{
|
||||
struct gensec_socket *gensec_socket;
|
||||
struct socket_context *new_sock;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
nt_status = socket_create_with_ops(current_socket, &gensec_socket_ops, &new_sock,
|
||||
SOCKET_TYPE_STREAM, current_socket->flags | SOCKET_FLAG_ENCRYPT);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
*new_socket = NULL;
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
new_sock->state = current_socket->state;
|
||||
|
||||
gensec_socket = talloc(new_sock, struct gensec_socket);
|
||||
if (gensec_socket == NULL) {
|
||||
*new_socket = NULL;
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
new_sock->private_data = gensec_socket;
|
||||
gensec_socket->socket = current_socket;
|
||||
|
||||
if (talloc_reference(gensec_socket, current_socket) == NULL) {
|
||||
*new_socket = NULL;
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* Nothing to do here, if we are not actually wrapping on this socket */
|
||||
if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) &&
|
||||
!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
|
||||
|
||||
gensec_socket->wrap = False;
|
||||
*new_socket = new_sock;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
gensec_socket->gensec_security = gensec_security;
|
||||
|
||||
gensec_socket->wrap = True;
|
||||
gensec_socket->eof = False;
|
||||
gensec_socket->error = NT_STATUS_OK;
|
||||
gensec_socket->interrupted = False;
|
||||
gensec_socket->in_extra_read = 0;
|
||||
|
||||
gensec_socket->read_buffer = data_blob(NULL, 0);
|
||||
|
||||
gensec_socket->recv_handler = recv_handler;
|
||||
gensec_socket->recv_private = recv_private;
|
||||
gensec_socket->ev = ev;
|
||||
|
||||
gensec_socket->packet = packet_init(gensec_socket);
|
||||
if (gensec_socket->packet == NULL) {
|
||||
*new_socket = NULL;
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
packet_set_private(gensec_socket->packet, gensec_socket);
|
||||
packet_set_socket(gensec_socket->packet, gensec_socket->socket);
|
||||
packet_set_callback(gensec_socket->packet, gensec_socket_unwrap);
|
||||
packet_set_full_request(gensec_socket->packet, gensec_socket_full_request);
|
||||
packet_set_error_handler(gensec_socket->packet, gensec_socket_error_handler);
|
||||
packet_set_serialise(gensec_socket->packet);
|
||||
|
||||
/* TODO: full-request that knows about maximum packet size */
|
||||
|
||||
*new_socket = new_sock;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
static NTSTATUS gensec_socket_set_option(struct socket_context *sock, const char *option, const char *val)
|
||||
{
|
||||
set_socket_options(socket_get_fd(sock), option);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static char *gensec_socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
return socket_get_peer_name(gensec->socket, mem_ctx);
|
||||
}
|
||||
|
||||
static struct socket_address *gensec_socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
return socket_get_peer_addr(gensec->socket, mem_ctx);
|
||||
}
|
||||
|
||||
static struct socket_address *gensec_socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
return socket_get_my_addr(gensec->socket, mem_ctx);
|
||||
}
|
||||
|
||||
static int gensec_socket_get_fd(struct socket_context *sock)
|
||||
{
|
||||
struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket);
|
||||
return socket_get_fd(gensec->socket);
|
||||
}
|
||||
|
||||
static const struct socket_ops gensec_socket_ops = {
|
||||
.name = "gensec",
|
||||
.fn_init = gensec_socket_init_fn,
|
||||
.fn_recv = gensec_socket_recv,
|
||||
.fn_send = gensec_socket_send,
|
||||
.fn_pending = gensec_socket_pending,
|
||||
|
||||
.fn_set_option = gensec_socket_set_option,
|
||||
|
||||
.fn_get_peer_name = gensec_socket_get_peer_name,
|
||||
.fn_get_peer_addr = gensec_socket_get_peer_addr,
|
||||
.fn_get_my_addr = gensec_socket_get_my_addr,
|
||||
.fn_get_fd = gensec_socket_get_fd
|
||||
};
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Generic Authentication Interface (socket wrapper)
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
struct gensec_security;
|
||||
struct socket_context;
|
||||
|
||||
NTSTATUS gensec_socket_init(struct gensec_security *gensec_security,
|
||||
struct socket_context *current_socket,
|
||||
struct event_context *ev,
|
||||
void (*recv_handler)(void *, uint16_t),
|
||||
void *recv_private,
|
||||
struct socket_context **new_socket);
|
||||
/* These functions are for use here only (public because SPNEGO must
|
||||
* use them for recursion) */
|
||||
NTSTATUS gensec_wrap_packets(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed);
|
||||
/* These functions are for use here only (public because SPNEGO must
|
||||
* use them for recursion) */
|
||||
NTSTATUS gensec_unwrap_packets(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out,
|
||||
size_t *len_processed);
|
||||
|
||||
/* These functions are for use here only (public because SPNEGO must
|
||||
* use them for recursion) */
|
||||
NTSTATUS gensec_packet_full_request(struct gensec_security *gensec_security,
|
||||
DATA_BLOB blob, size_t *size);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
RFC2478 Compliant SPNEGO implementation
|
||||
|
||||
Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#define SPNEGO_DELEG_FLAG 0x01
|
||||
#define SPNEGO_MUTUAL_FLAG 0x02
|
||||
#define SPNEGO_REPLAY_FLAG 0x04
|
||||
#define SPNEGO_SEQUENCE_FLAG 0x08
|
||||
#define SPNEGO_ANON_FLAG 0x10
|
||||
#define SPNEGO_CONF_FLAG 0x20
|
||||
#define SPNEGO_INTEG_FLAG 0x40
|
||||
#define SPNEGO_REQ_FLAG 0x80
|
||||
|
||||
enum spnego_negResult {
|
||||
SPNEGO_ACCEPT_COMPLETED = 0,
|
||||
SPNEGO_ACCEPT_INCOMPLETE = 1,
|
||||
SPNEGO_REJECT = 2,
|
||||
SPNEGO_NONE_RESULT = 3
|
||||
};
|
||||
|
||||
struct spnego_negTokenInit {
|
||||
const char **mechTypes;
|
||||
int reqFlags;
|
||||
DATA_BLOB mechToken;
|
||||
DATA_BLOB mechListMIC;
|
||||
char *targetPrincipal;
|
||||
};
|
||||
|
||||
struct spnego_negTokenTarg {
|
||||
uint8_t negResult;
|
||||
const char *supportedMech;
|
||||
DATA_BLOB responseToken;
|
||||
DATA_BLOB mechListMIC;
|
||||
};
|
||||
|
||||
struct spnego_data {
|
||||
int type;
|
||||
struct spnego_negTokenInit negTokenInit;
|
||||
struct spnego_negTokenTarg negTokenTarg;
|
||||
};
|
||||
|
||||
enum spnego_message_type {
|
||||
SPNEGO_NEG_TOKEN_INIT = 0,
|
||||
SPNEGO_NEG_TOKEN_TARG = 1,
|
||||
};
|
||||
|
||||
#include "auth/gensec/spnego_proto.h"
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
RFC2478 Compliant SPNEGO implementation
|
||||
|
||||
Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/gensec/spnego.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
#include "libcli/util/asn_1.h"
|
||||
|
||||
static BOOL read_negTokenInit(struct asn1_data *asn1, struct spnego_negTokenInit *token)
|
||||
{
|
||||
ZERO_STRUCTP(token);
|
||||
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_start_tag(asn1, ASN1_SEQUENCE(0));
|
||||
|
||||
while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) {
|
||||
int i;
|
||||
uint8_t context;
|
||||
if (!asn1_peek_uint8(asn1, &context)) {
|
||||
asn1->has_error = True;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (context) {
|
||||
/* Read mechTypes */
|
||||
case ASN1_CONTEXT(0):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_start_tag(asn1, ASN1_SEQUENCE(0));
|
||||
|
||||
token->mechTypes = talloc(NULL, const char *);
|
||||
for (i = 0; !asn1->has_error &&
|
||||
0 < asn1_tag_remaining(asn1); i++) {
|
||||
token->mechTypes = talloc_realloc(NULL,
|
||||
token->mechTypes,
|
||||
const char *, i+2);
|
||||
asn1_read_OID(asn1, token->mechTypes + i);
|
||||
if (token->mechTypes[i]) {
|
||||
talloc_steal(token->mechTypes,
|
||||
token->mechTypes[i]);
|
||||
}
|
||||
}
|
||||
token->mechTypes[i] = NULL;
|
||||
|
||||
asn1_end_tag(asn1);
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
/* Read reqFlags */
|
||||
case ASN1_CONTEXT(1):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(1));
|
||||
asn1_read_Integer(asn1, &token->reqFlags);
|
||||
token->reqFlags |= SPNEGO_REQ_FLAG;
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
/* Read mechToken */
|
||||
case ASN1_CONTEXT(2):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(2));
|
||||
asn1_read_OctetString(asn1, &token->mechToken);
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
/* Read mecListMIC */
|
||||
case ASN1_CONTEXT(3):
|
||||
{
|
||||
uint8_t type_peek;
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(3));
|
||||
if (!asn1_peek_uint8(asn1, &type_peek)) {
|
||||
asn1->has_error = True;
|
||||
break;
|
||||
}
|
||||
if (type_peek == ASN1_OCTET_STRING) {
|
||||
asn1_read_OctetString(asn1,
|
||||
&token->mechListMIC);
|
||||
} else {
|
||||
/* RFC 2478 says we have an Octet String here,
|
||||
but W2k sends something different... */
|
||||
char *mechListMIC;
|
||||
asn1_push_tag(asn1, ASN1_SEQUENCE(0));
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_read_GeneralString(asn1, &mechListMIC);
|
||||
asn1_pop_tag(asn1);
|
||||
asn1_pop_tag(asn1);
|
||||
|
||||
token->targetPrincipal = mechListMIC;
|
||||
}
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
asn1->has_error = True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
asn1_end_tag(asn1);
|
||||
asn1_end_tag(asn1);
|
||||
|
||||
return !asn1->has_error;
|
||||
}
|
||||
|
||||
static BOOL write_negTokenInit(struct asn1_data *asn1, struct spnego_negTokenInit *token)
|
||||
{
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_push_tag(asn1, ASN1_SEQUENCE(0));
|
||||
|
||||
/* Write mechTypes */
|
||||
if (token->mechTypes && *token->mechTypes) {
|
||||
int i;
|
||||
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_push_tag(asn1, ASN1_SEQUENCE(0));
|
||||
for (i = 0; token->mechTypes[i]; i++) {
|
||||
asn1_write_OID(asn1, token->mechTypes[i]);
|
||||
}
|
||||
asn1_pop_tag(asn1);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
/* write reqFlags */
|
||||
if (token->reqFlags & SPNEGO_REQ_FLAG) {
|
||||
int flags = token->reqFlags & ~SPNEGO_REQ_FLAG;
|
||||
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(1));
|
||||
asn1_write_Integer(asn1, flags);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
/* write mechToken */
|
||||
if (token->mechToken.data) {
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(2));
|
||||
asn1_write_OctetString(asn1, token->mechToken.data,
|
||||
token->mechToken.length);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
/* write mechListMIC */
|
||||
if (token->mechListMIC.data) {
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(3));
|
||||
#if 0
|
||||
/* This is what RFC 2478 says ... */
|
||||
asn1_write_OctetString(asn1, token->mechListMIC.data,
|
||||
token->mechListMIC.length);
|
||||
#else
|
||||
/* ... but unfortunately this is what Windows
|
||||
sends/expects */
|
||||
asn1_push_tag(asn1, ASN1_SEQUENCE(0));
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_push_tag(asn1, ASN1_GENERAL_STRING);
|
||||
asn1_write(asn1, token->mechListMIC.data,
|
||||
token->mechListMIC.length);
|
||||
asn1_pop_tag(asn1);
|
||||
asn1_pop_tag(asn1);
|
||||
asn1_pop_tag(asn1);
|
||||
#endif
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
asn1_pop_tag(asn1);
|
||||
asn1_pop_tag(asn1);
|
||||
|
||||
return !asn1->has_error;
|
||||
}
|
||||
|
||||
static BOOL read_negTokenTarg(struct asn1_data *asn1, struct spnego_negTokenTarg *token)
|
||||
{
|
||||
ZERO_STRUCTP(token);
|
||||
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(1));
|
||||
asn1_start_tag(asn1, ASN1_SEQUENCE(0));
|
||||
|
||||
while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) {
|
||||
uint8_t context;
|
||||
if (!asn1_peek_uint8(asn1, &context)) {
|
||||
asn1->has_error = True;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (context) {
|
||||
case ASN1_CONTEXT(0):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_start_tag(asn1, ASN1_ENUMERATED);
|
||||
asn1_read_uint8(asn1, &token->negResult);
|
||||
asn1_end_tag(asn1);
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
case ASN1_CONTEXT(1):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(1));
|
||||
asn1_read_OID(asn1, &token->supportedMech);
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
case ASN1_CONTEXT(2):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(2));
|
||||
asn1_read_OctetString(asn1, &token->responseToken);
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
case ASN1_CONTEXT(3):
|
||||
asn1_start_tag(asn1, ASN1_CONTEXT(3));
|
||||
asn1_read_OctetString(asn1, &token->mechListMIC);
|
||||
asn1_end_tag(asn1);
|
||||
break;
|
||||
default:
|
||||
asn1->has_error = True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
asn1_end_tag(asn1);
|
||||
asn1_end_tag(asn1);
|
||||
|
||||
return !asn1->has_error;
|
||||
}
|
||||
|
||||
static BOOL write_negTokenTarg(struct asn1_data *asn1, struct spnego_negTokenTarg *token)
|
||||
{
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(1));
|
||||
asn1_push_tag(asn1, ASN1_SEQUENCE(0));
|
||||
|
||||
if (token->negResult != SPNEGO_NONE_RESULT) {
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(0));
|
||||
asn1_write_enumerated(asn1, token->negResult);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
if (token->supportedMech) {
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(1));
|
||||
asn1_write_OID(asn1, token->supportedMech);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
if (token->responseToken.data) {
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(2));
|
||||
asn1_write_OctetString(asn1, token->responseToken.data,
|
||||
token->responseToken.length);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
if (token->mechListMIC.data) {
|
||||
asn1_push_tag(asn1, ASN1_CONTEXT(3));
|
||||
asn1_write_OctetString(asn1, token->mechListMIC.data,
|
||||
token->mechListMIC.length);
|
||||
asn1_pop_tag(asn1);
|
||||
}
|
||||
|
||||
asn1_pop_tag(asn1);
|
||||
asn1_pop_tag(asn1);
|
||||
|
||||
return !asn1->has_error;
|
||||
}
|
||||
|
||||
ssize_t spnego_read_data(DATA_BLOB data, struct spnego_data *token)
|
||||
{
|
||||
struct asn1_data asn1;
|
||||
ssize_t ret = -1;
|
||||
uint8_t context;
|
||||
|
||||
ZERO_STRUCTP(token);
|
||||
ZERO_STRUCT(asn1);
|
||||
|
||||
if (data.length == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
asn1_load(&asn1, data);
|
||||
|
||||
if (!asn1_peek_uint8(&asn1, &context)) {
|
||||
asn1.has_error = True;
|
||||
} else {
|
||||
switch (context) {
|
||||
case ASN1_APPLICATION(0):
|
||||
asn1_start_tag(&asn1, ASN1_APPLICATION(0));
|
||||
asn1_check_OID(&asn1, GENSEC_OID_SPNEGO);
|
||||
if (read_negTokenInit(&asn1, &token->negTokenInit)) {
|
||||
token->type = SPNEGO_NEG_TOKEN_INIT;
|
||||
}
|
||||
asn1_end_tag(&asn1);
|
||||
break;
|
||||
case ASN1_CONTEXT(1):
|
||||
if (read_negTokenTarg(&asn1, &token->negTokenTarg)) {
|
||||
token->type = SPNEGO_NEG_TOKEN_TARG;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
asn1.has_error = True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!asn1.has_error) ret = asn1.ofs;
|
||||
asn1_free(&asn1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t spnego_write_data(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct spnego_data *spnego)
|
||||
{
|
||||
struct asn1_data asn1;
|
||||
ssize_t ret = -1;
|
||||
|
||||
ZERO_STRUCT(asn1);
|
||||
|
||||
switch (spnego->type) {
|
||||
case SPNEGO_NEG_TOKEN_INIT:
|
||||
asn1_push_tag(&asn1, ASN1_APPLICATION(0));
|
||||
asn1_write_OID(&asn1, GENSEC_OID_SPNEGO);
|
||||
write_negTokenInit(&asn1, &spnego->negTokenInit);
|
||||
asn1_pop_tag(&asn1);
|
||||
break;
|
||||
case SPNEGO_NEG_TOKEN_TARG:
|
||||
write_negTokenTarg(&asn1, &spnego->negTokenTarg);
|
||||
break;
|
||||
default:
|
||||
asn1.has_error = True;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!asn1.has_error) {
|
||||
*blob = data_blob_talloc(mem_ctx, asn1.data, asn1.length);
|
||||
ret = asn1.ofs;
|
||||
}
|
||||
asn1_free(&asn1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOL spnego_free_data(struct spnego_data *spnego)
|
||||
{
|
||||
BOOL ret = True;
|
||||
|
||||
if (!spnego) goto out;
|
||||
|
||||
switch(spnego->type) {
|
||||
case SPNEGO_NEG_TOKEN_INIT:
|
||||
if (spnego->negTokenInit.mechTypes) {
|
||||
talloc_free(spnego->negTokenInit.mechTypes);
|
||||
}
|
||||
data_blob_free(&spnego->negTokenInit.mechToken);
|
||||
data_blob_free(&spnego->negTokenInit.mechListMIC);
|
||||
talloc_free(spnego->negTokenInit.targetPrincipal);
|
||||
break;
|
||||
case SPNEGO_NEG_TOKEN_TARG:
|
||||
if (spnego->negTokenTarg.supportedMech) {
|
||||
talloc_free(discard_const(spnego->negTokenTarg.supportedMech));
|
||||
}
|
||||
data_blob_free(&spnego->negTokenTarg.responseToken);
|
||||
data_blob_free(&spnego->negTokenTarg.mechListMIC);
|
||||
break;
|
||||
default:
|
||||
ret = False;
|
||||
break;
|
||||
}
|
||||
ZERO_STRUCTP(spnego);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
simple kerberos5 routines for active directory
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Luke Howard 2002-2003
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/network.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "system/time.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
|
||||
#ifdef HAVE_KRB5
|
||||
|
||||
#if defined(HAVE_KRB5_SET_DEFAULT_IN_TKT_ETYPES) && !defined(HAVE_KRB5_SET_DEFAULT_TGS_KTYPES)
|
||||
krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc)
|
||||
{
|
||||
return krb5_set_default_in_tkt_etypes(ctx, enc);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS)
|
||||
/* HEIMDAL */
|
||||
void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr)
|
||||
{
|
||||
pkaddr->addr_type = KRB5_ADDRESS_INET;
|
||||
pkaddr->address.length = sizeof(((struct sockaddr_in *)paddr)->sin_addr);
|
||||
pkaddr->address.data = (char *)&(((struct sockaddr_in *)paddr)->sin_addr);
|
||||
}
|
||||
#elif defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS)
|
||||
/* MIT */
|
||||
void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr)
|
||||
{
|
||||
pkaddr->addrtype = ADDRTYPE_INET;
|
||||
pkaddr->length = sizeof(((struct sockaddr_in *)paddr)->sin_addr);
|
||||
pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in *)paddr)->sin_addr);
|
||||
}
|
||||
#else
|
||||
#error UNKNOWN_ADDRTYPE
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_USE_ENCTYPE) && defined(HAVE_KRB5_STRING_TO_KEY) && defined(HAVE_KRB5_ENCRYPT_BLOCK)
|
||||
int create_kerberos_key_from_string(krb5_context context,
|
||||
krb5_principal host_princ,
|
||||
krb5_data *password,
|
||||
krb5_keyblock *key,
|
||||
krb5_enctype enctype)
|
||||
{
|
||||
int ret;
|
||||
krb5_data salt;
|
||||
krb5_encrypt_block eblock;
|
||||
|
||||
ret = krb5_principal2salt(context, host_princ, &salt);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret)));
|
||||
return ret;
|
||||
}
|
||||
krb5_use_enctype(context, &eblock, enctype);
|
||||
ret = krb5_string_to_key(context, &eblock, key, password, &salt);
|
||||
SAFE_FREE(salt.data);
|
||||
return ret;
|
||||
}
|
||||
#elif defined(HAVE_KRB5_GET_PW_SALT) && defined(HAVE_KRB5_STRING_TO_KEY_SALT)
|
||||
int create_kerberos_key_from_string(krb5_context context,
|
||||
krb5_principal host_princ,
|
||||
krb5_data *password,
|
||||
krb5_keyblock *key,
|
||||
krb5_enctype enctype)
|
||||
{
|
||||
int ret;
|
||||
krb5_salt salt;
|
||||
|
||||
ret = krb5_get_pw_salt(context, host_princ, &salt);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret)));
|
||||
return ret;
|
||||
}
|
||||
ret = krb5_string_to_key_salt(context, enctype, password->data,
|
||||
salt, key);
|
||||
krb5_free_salt(context, salt);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
#error UNKNOWN_CREATE_KEY_FUNCTIONS
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_KRB5_GET_PERMITTED_ENCTYPES)
|
||||
krb5_error_code get_kerberos_allowed_etypes(krb5_context context,
|
||||
krb5_enctype **enctypes)
|
||||
{
|
||||
return krb5_get_permitted_enctypes(context, enctypes);
|
||||
}
|
||||
#elif defined(HAVE_KRB5_GET_DEFAULT_IN_TKT_ETYPES)
|
||||
krb5_error_code get_kerberos_allowed_etypes(krb5_context context,
|
||||
krb5_enctype **enctypes)
|
||||
{
|
||||
return krb5_get_default_in_tkt_etypes(context, enctypes);
|
||||
}
|
||||
#else
|
||||
#error UNKNOWN_GET_ENCTYPES_FUNCTIONS
|
||||
#endif
|
||||
|
||||
void free_kerberos_etypes(krb5_context context,
|
||||
krb5_enctype *enctypes)
|
||||
{
|
||||
#if defined(HAVE_KRB5_FREE_KTYPES)
|
||||
krb5_free_ktypes(context, enctypes);
|
||||
return;
|
||||
#else
|
||||
SAFE_FREE(enctypes);
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY)
|
||||
krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context,
|
||||
krb5_auth_context auth_context,
|
||||
krb5_keyblock *keyblock)
|
||||
{
|
||||
return krb5_auth_con_setkey(context, auth_context, keyblock);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_KRB5_FREE_UNPARSED_NAME)
|
||||
void krb5_free_unparsed_name(krb5_context context, char *val)
|
||||
{
|
||||
SAFE_FREE(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
void kerberos_free_data_contents(krb5_context context, krb5_data *pdata)
|
||||
{
|
||||
#if defined(HAVE_KRB5_FREE_DATA_CONTENTS)
|
||||
if (pdata->data) {
|
||||
krb5_free_data_contents(context, pdata);
|
||||
}
|
||||
#else
|
||||
SAFE_FREE(pdata->data);
|
||||
#endif
|
||||
}
|
||||
|
||||
void kerberos_set_creds_enctype(krb5_creds *pcreds, int enctype)
|
||||
{
|
||||
#if defined(HAVE_KRB5_KEYBLOCK_IN_CREDS)
|
||||
KRB5_KEY_TYPE((&pcreds->keyblock)) = enctype;
|
||||
#elif defined(HAVE_KRB5_SESSION_IN_CREDS)
|
||||
KRB5_KEY_TYPE((&pcreds->session)) = enctype;
|
||||
#else
|
||||
#error UNKNOWN_KEYBLOCK_MEMBER_IN_KRB5_CREDS_STRUCT
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOL kerberos_compatible_enctypes(krb5_context context,
|
||||
krb5_enctype enctype1,
|
||||
krb5_enctype enctype2)
|
||||
{
|
||||
#if defined(HAVE_KRB5_C_ENCTYPE_COMPARE)
|
||||
krb5_boolean similar = 0;
|
||||
|
||||
krb5_c_enctype_compare(context, enctype1, enctype2, &similar);
|
||||
return similar ? True : False;
|
||||
#elif defined(HAVE_KRB5_ENCTYPES_COMPATIBLE_KEYS)
|
||||
return krb5_enctypes_compatible_keys(context, enctype1, enctype2) ? True : False;
|
||||
#endif
|
||||
}
|
||||
|
||||
krb5_error_code smb_krb5_kt_free_entry(krb5_context context, krb5_keytab_entry *kt_entry)
|
||||
{
|
||||
#if defined(HAVE_KRB5_KT_FREE_ENTRY)
|
||||
return krb5_kt_free_entry(context, kt_entry);
|
||||
#elif defined(HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS)
|
||||
return krb5_free_keytab_entry_contents(context, kt_entry);
|
||||
#else
|
||||
#error UNKNOWN_KT_FREE_FUNCTION
|
||||
#endif
|
||||
}
|
||||
|
||||
char *smb_get_krb5_error_message(krb5_context context, krb5_error_code code, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
char *ret;
|
||||
|
||||
#if defined(HAVE_KRB5_GET_ERROR_STRING) && defined(HAVE_KRB5_FREE_ERROR_STRING)
|
||||
char *context_error = krb5_get_error_string(context);
|
||||
if (context_error) {
|
||||
ret = talloc_asprintf(mem_ctx, "%s: %s", error_message(code), context_error);
|
||||
krb5_free_error_string(context, context_error);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
ret = talloc_strdup(mem_ctx, error_message(code));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,540 @@
|
||||
# NOTE! this whole m4 file is disabled in configure.in for now
|
||||
|
||||
#################################################
|
||||
# KRB5 support
|
||||
KRB5_CFLAGS=""
|
||||
KRB5_CPPFLAGS=""
|
||||
KRB5_LDFLAGS=""
|
||||
KRB5_LIBS=""
|
||||
with_krb5_support=auto
|
||||
krb5_withval=auto
|
||||
AC_MSG_CHECKING([for KRB5 support])
|
||||
|
||||
# Do no harm to the values of CFLAGS and LIBS while testing for
|
||||
# Kerberos support.
|
||||
AC_ARG_WITH(krb5,
|
||||
[ --with-krb5=base-dir Locate Kerberos 5 support (default=auto)],
|
||||
[ case "$withval" in
|
||||
no)
|
||||
with_krb5_support=no
|
||||
AC_MSG_RESULT(no)
|
||||
krb5_withval=no
|
||||
;;
|
||||
yes)
|
||||
with_krb5_support=yes
|
||||
AC_MSG_RESULT(yes)
|
||||
krb5_withval=yes
|
||||
;;
|
||||
auto)
|
||||
with_krb5_support=auto
|
||||
AC_MSG_RESULT(auto)
|
||||
krb5_withval=auto
|
||||
;;
|
||||
*)
|
||||
with_krb5_support=yes
|
||||
AC_MSG_RESULT(yes)
|
||||
krb5_withval=$withval
|
||||
KRB5CONFIG="$krb5_withval/bin/krb5-config"
|
||||
;;
|
||||
esac ],
|
||||
AC_MSG_RESULT($with_krb5_support)
|
||||
)
|
||||
|
||||
if test x$with_krb5_support != x"no"; then
|
||||
FOUND_KRB5=no
|
||||
FOUND_KRB5_VIA_CONFIG=no
|
||||
|
||||
#################################################
|
||||
# check for krb5-config from recent MIT and Heimdal kerberos 5
|
||||
AC_MSG_CHECKING(for working specified location for krb5-config)
|
||||
if test x$KRB5CONFIG != "x"; then
|
||||
if test -x "$KRB5CONFIG"; then
|
||||
ac_save_CFLAGS=$CFLAGS
|
||||
CFLAGS="";export CFLAGS
|
||||
ac_save_LDFLAGS=$LDFLAGS
|
||||
LDFLAGS="";export LDFLAGS
|
||||
KRB5_LIBS="`$KRB5CONFIG --libs gssapi`"
|
||||
KRB5_CFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`"
|
||||
KRB5_CPPFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`"
|
||||
CFLAGS=$ac_save_CFLAGS;export CFLAGS
|
||||
LDFLAGS=$ac_save_LDFLAGS;export LDFLAGS
|
||||
FOUND_KRB5=yes
|
||||
FOUND_KRB5_VIA_CONFIG=yes
|
||||
AC_MSG_RESULT(yes. Found $KRB5CONFIG)
|
||||
else
|
||||
AC_MSG_RESULT(no. Fallback to specified directory)
|
||||
fi
|
||||
else
|
||||
AC_MSG_RESULT(no. Fallback to finding krb5-config in path)
|
||||
#################################################
|
||||
# check for krb5-config from recent MIT and Heimdal kerberos 5
|
||||
AC_PATH_PROG(KRB5CONFIG, krb5-config)
|
||||
AC_MSG_CHECKING(for working krb5-config in path)
|
||||
if test -x "$KRB5CONFIG"; then
|
||||
ac_save_CFLAGS=$CFLAGS
|
||||
CFLAGS="";export CFLAGS
|
||||
ac_save_LDFLAGS=$LDFLAGS
|
||||
LDFLAGS="";export LDFLAGS
|
||||
KRB5_LIBS="`$KRB5CONFIG --libs gssapi`"
|
||||
KRB5_CFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`"
|
||||
KRB5_CPPFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`"
|
||||
CFLAGS=$ac_save_CFLAGS;export CFLAGS
|
||||
LDFLAGS=$ac_save_LDFLAGS;export LDFLAGS
|
||||
FOUND_KRB5=yes
|
||||
FOUND_KRB5_VIA_CONFIG=yes
|
||||
AC_MSG_RESULT(yes. Found $KRB5CONFIG)
|
||||
else
|
||||
AC_MSG_RESULT(no. Fallback to previous krb5 detection strategy)
|
||||
fi
|
||||
fi
|
||||
|
||||
if test x$FOUND_KRB5 != x"yes"; then
|
||||
#################################################
|
||||
# check for location of Kerberos 5 install
|
||||
AC_MSG_CHECKING(for kerberos 5 install path)
|
||||
case "$krb5_withval" in
|
||||
no)
|
||||
AC_MSG_RESULT(no krb5-path given)
|
||||
;;
|
||||
yes)
|
||||
AC_MSG_RESULT(/usr)
|
||||
FOUND_KRB5=yes
|
||||
;;
|
||||
*)
|
||||
AC_MSG_RESULT($krb5_withval)
|
||||
KRB5_CFLAGS="-I$krb5_withval/include"
|
||||
KRB5_CPPFLAGS="-I$krb5_withval/include"
|
||||
KRB5_LDFLAGS="-L$krb5_withval/lib"
|
||||
FOUND_KRB5=yes
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if test x$FOUND_KRB5 != x"yes"; then
|
||||
#################################################
|
||||
# see if this box has the SuSE location for the heimdal krb implementation
|
||||
AC_MSG_CHECKING(for /usr/include/heimdal)
|
||||
if test -d /usr/include/heimdal; then
|
||||
if test -f /usr/lib/heimdal/lib/libkrb5.a; then
|
||||
KRB5_CFLAGS="-I/usr/include/heimdal"
|
||||
KRB5_CPPFLAGS="-I/usr/include/heimdal"
|
||||
KRB5_LDFLAGS="-L/usr/lib/heimdal/lib"
|
||||
AC_MSG_RESULT(yes)
|
||||
else
|
||||
KRB5_CFLAGS="-I/usr/include/heimdal"
|
||||
KRB5_CPPFLAGS="-I/usr/include/heimdal"
|
||||
AC_MSG_RESULT(yes)
|
||||
fi
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
fi
|
||||
|
||||
if test x$FOUND_KRB5 != x"yes"; then
|
||||
#################################################
|
||||
# see if this box has the RedHat location for kerberos
|
||||
AC_MSG_CHECKING(for /usr/kerberos)
|
||||
if test -d /usr/kerberos -a -f /usr/kerberos/lib/libkrb5.a; then
|
||||
KRB5_LDFLAGS="-L/usr/kerberos/lib"
|
||||
KRB5_CFLAGS="-I/usr/kerberos/include"
|
||||
KRB5_CPPFLAGS="-I/usr/kerberos/include"
|
||||
AC_MSG_RESULT(yes)
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
fi
|
||||
|
||||
ac_save_CFLAGS=$CFLAGS
|
||||
ac_save_CPPFLAGS=$CPPFLAGS
|
||||
ac_save_LDFLAGS=$LDFLAGS
|
||||
|
||||
#MIT needs this, to let us see 'internal' parts of the headers we use
|
||||
KRB5_CFLAGS="${KRB5_CFLAGS} -DKRB5_PRIVATE -DKRB5_DEPRECATED"
|
||||
|
||||
#Heimdal needs this
|
||||
#TODO: we need to parse KRB5_LIBS for -L path
|
||||
# and set -Wl,-rpath -Wl,path
|
||||
|
||||
CFLAGS="$CFLAGS $KRB5_CFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS"
|
||||
LDFLAGS="$LDFLAGS $KRB5_LDFLAGS"
|
||||
|
||||
KRB5_LIBS="$KRB5_LDFLAGS $KRB5_LIBS"
|
||||
|
||||
# now check for krb5.h. Some systems have the libraries without the headers!
|
||||
# note that this check is done here to allow for different kerberos
|
||||
# include paths
|
||||
AC_CHECK_HEADERS(krb5.h)
|
||||
|
||||
if test x"$ac_cv_header_krb5_h" = x"no"; then
|
||||
# Give a warning if KRB5 support was not explicitly requested,
|
||||
# i.e with_krb5_support = auto, otherwise die with an error.
|
||||
if test x"$with_krb5_support" = x"yes"; then
|
||||
AC_MSG_ERROR([KRB5 cannot be supported without krb5.h])
|
||||
else
|
||||
AC_MSG_WARN([KRB5 cannot be supported without krb5.h])
|
||||
fi
|
||||
# Turn off AD support and restore CFLAGS and LIBS variables
|
||||
with_krb5_support="no"
|
||||
fi
|
||||
|
||||
CFLAGS=$ac_save_CFLAGS
|
||||
CPPFLAGS=$ac_save_CPPFLAGS
|
||||
LDFLAGS=$ac_save_LDFLAGS
|
||||
fi
|
||||
|
||||
# Now we have determined whether we really want KRB5 support
|
||||
|
||||
if test x"$with_krb5_support" != x"no"; then
|
||||
ac_save_CFLAGS=$CFLAGS
|
||||
ac_save_CPPFLAGS=$CPPFLAGS
|
||||
ac_save_LDFLAGS=$LDFLAGS
|
||||
ac_save_LIBS=$LIBS
|
||||
|
||||
CFLAGS="$CFLAGS $KRB5_CFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS"
|
||||
LDFLAGS="$LDFLAGS $KRB5_LDFLAGS"
|
||||
|
||||
# now check for gssapi headers. This is also done here to allow for
|
||||
# different kerberos include paths
|
||||
AC_CHECK_HEADERS(gssapi.h gssapi_krb5.h gssapi/gssapi.h gssapi/gssapi_generic.h gssapi/gssapi_krb5.h com_err.h)
|
||||
|
||||
|
||||
# Heimdal checks.
|
||||
# But only if we didn't have a krb5-config to tell us this already
|
||||
if test x"$FOUND_KRB5_VIA_CONFIG" != x"yes"; then
|
||||
##################################################################
|
||||
# we might need the k5crypto and com_err libraries on some systems
|
||||
AC_CHECK_LIB_EXT(com_err, KRB5_LIBS, _et_list)
|
||||
AC_CHECK_LIB_EXT(k5crypto, KRB5_LIBS, krb5_encrypt_data)
|
||||
|
||||
AC_CHECK_LIB_EXT(crypto, KRB5_LIBS, des_set_key)
|
||||
AC_CHECK_LIB_EXT(asn1, KRB5_LIBS, copy_Authenticator)
|
||||
AC_CHECK_LIB_EXT(roken, KRB5_LIBS, roken_getaddrinfo_hostspec)
|
||||
fi
|
||||
|
||||
# Heimdal checks. On static Heimdal gssapi must be linked before krb5.
|
||||
AC_CHECK_LIB_EXT(gssapi, KRB5_LIBS, gss_display_status,[],[],
|
||||
AC_DEFINE(HAVE_GSSAPI,1,[Whether GSSAPI is available]))
|
||||
|
||||
########################################################
|
||||
# now see if we can find the krb5 libs in standard paths
|
||||
# or as specified above
|
||||
AC_CHECK_LIB_EXT(krb5, KRB5_LIBS, krb5_mk_req_extended)
|
||||
AC_CHECK_LIB_EXT(krb5, KRB5_LIBS, krb5_kt_compare)
|
||||
|
||||
########################################################
|
||||
# now see if we can find the gssapi libs in standard paths
|
||||
if test x"$ac_cv_lib_ext_gssapi_gss_display_status" != x"yes"; then
|
||||
AC_CHECK_LIB_EXT(gssapi_krb5, KRB5_LIBS,gss_display_status,[],[],
|
||||
AC_DEFINE(HAVE_GSSAPI,1,[Whether GSSAPI is available]))
|
||||
fi
|
||||
|
||||
AC_CHECK_FUNC_EXT(krb5_set_real_time, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_set_default_in_tkt_etypes, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_set_default_tgs_ktypes, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_principal2salt, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_use_enctype, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_string_to_key, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_get_pw_salt, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_string_to_key_salt, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_auth_con_setkey, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_auth_con_setuseruserkey, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_locate_kdc, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_get_permitted_enctypes, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_get_default_in_tkt_etypes, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_free_ktypes, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_free_data_contents, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_principal_get_comp_string, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_free_unparsed_name, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_free_keytab_entry_contents, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_kt_free_entry, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_krbhst_get_addrinfo, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_verify_checksum, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_c_verify_checksum, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_ticket_get_authorization_data_type, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_krbhst_get_addrinfo, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_c_enctype_compare, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_enctypes_compatible_keys, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_get_error_string, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_free_error_string, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_initlog, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_addlog_func, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(krb5_set_warn_dest, $KRB5_LIBS)
|
||||
|
||||
LIBS="$LIBS $KRB5_LIBS"
|
||||
|
||||
AC_CACHE_CHECK([for krb5_log_facility type],
|
||||
samba_cv_HAVE_KRB5_LOG_FACILITY,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_log_facility block;],
|
||||
samba_cv_HAVE_KRB5_LOG_FACILITY=yes,
|
||||
samba_cv_HAVE_KRB5_LOG_FACILITY=no)])
|
||||
|
||||
if test x"$samba_cv_HAVE_KRB5_LOG_FACILITY" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_LOG_FACILITY,1,
|
||||
[Whether the type krb5_log_facility exists])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for krb5_encrypt_block type],
|
||||
samba_cv_HAVE_KRB5_ENCRYPT_BLOCK,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_encrypt_block block;],
|
||||
samba_cv_HAVE_KRB5_ENCRYPT_BLOCK=yes,
|
||||
samba_cv_HAVE_KRB5_ENCRYPT_BLOCK=no)])
|
||||
|
||||
if test x"$samba_cv_HAVE_KRB5_ENCRYPT_BLOCK" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_ENCRYPT_BLOCK,1,
|
||||
[Whether the type krb5_encrypt_block exists])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for addrtype in krb5_address],
|
||||
samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_address kaddr; kaddr.addrtype = ADDRTYPE_INET;],
|
||||
samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS=yes,
|
||||
samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS=no)])
|
||||
if test x"$samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS" = x"yes"; then
|
||||
AC_DEFINE(HAVE_ADDRTYPE_IN_KRB5_ADDRESS,1,
|
||||
[Whether the krb5_address struct has a addrtype property])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for addr_type in krb5_address],
|
||||
samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_address kaddr; kaddr.addr_type = KRB5_ADDRESS_INET;],
|
||||
samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS=yes,
|
||||
samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS=no)])
|
||||
if test x"$samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS" = x"yes"; then
|
||||
AC_DEFINE(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS,1,
|
||||
[Whether the krb5_address struct has a addr_type property])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for enc_part2 in krb5_ticket],
|
||||
samba_cv_HAVE_KRB5_TKT_ENC_PART2,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_ticket tkt; tkt.enc_part2->authorization_data[0]->contents = NULL;],
|
||||
samba_cv_HAVE_KRB5_TKT_ENC_PART2=yes,
|
||||
samba_cv_HAVE_KRB5_TKT_ENC_PART2=no)])
|
||||
if test x"$samba_cv_HAVE_KRB5_TKT_ENC_PART2" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_TKT_ENC_PART2,1,
|
||||
[Whether the krb5_ticket struct has a enc_part2 property])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for keyblock in krb5_creds],
|
||||
samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_creds creds; krb5_keyblock kb; creds.keyblock = kb;],
|
||||
samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS=yes,
|
||||
samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS=no)])
|
||||
|
||||
if test x"$samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_KEYBLOCK_IN_CREDS,1,
|
||||
[Whether the krb5_creds struct has a keyblock property])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for session in krb5_creds],
|
||||
samba_cv_HAVE_KRB5_SESSION_IN_CREDS,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_creds creds; krb5_keyblock kb; creds.session = kb;],
|
||||
samba_cv_HAVE_KRB5_SESSION_IN_CREDS=yes,
|
||||
samba_cv_HAVE_KRB5_SESSION_IN_CREDS=no)])
|
||||
|
||||
if test x"$samba_cv_HAVE_KRB5_SESSION_IN_CREDS" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_SESSION_IN_CREDS,1,
|
||||
[Whether the krb5_creds struct has a session property])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for keyvalue in krb5_keyblock],
|
||||
samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_keyblock key; key.keyvalue.data = NULL;],
|
||||
samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE=yes,
|
||||
samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE=no)])
|
||||
if test x"$samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_KEYBLOCK_KEYVALUE,1,
|
||||
[Whether the krb5_keyblock struct has a keyvalue property])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for ENCTYPE_ARCFOUR_HMAC_MD5],
|
||||
samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_enctype enctype; enctype = ENCTYPE_ARCFOUR_HMAC_MD5;],
|
||||
samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5=yes,
|
||||
samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5=no)])
|
||||
AC_CACHE_CHECK([for KEYTYPE_ARCFOUR_56],
|
||||
samba_cv_HAVE_KEYTYPE_ARCFOUR_56,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_keytype keytype; keytype = KEYTYPE_ARCFOUR_56;],
|
||||
samba_cv_HAVE_KEYTYPE_ARCFOUR_56=yes,
|
||||
samba_cv_HAVE_KEYTYPE_ARCFOUR_56=no)])
|
||||
# Heimdals with KEYTYPE_ARCFOUR but not KEYTYPE_ARCFOUR_56 are broken
|
||||
# w.r.t. arcfour and windows, so we must not enable it here
|
||||
if test x"$samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5" = x"yes" -a\
|
||||
x"$samba_cv_HAVE_KEYTYPE_ARCFOUR_56" = x"yes"; then
|
||||
AC_DEFINE(HAVE_ENCTYPE_ARCFOUR_HMAC_MD5,1,
|
||||
[Whether the ENCTYPE_ARCFOUR_HMAC_MD5 key type is available])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for AP_OPTS_USE_SUBKEY],
|
||||
samba_cv_HAVE_AP_OPTS_USE_SUBKEY,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_flags ap_options; ap_options = AP_OPTS_USE_SUBKEY;],
|
||||
samba_cv_HAVE_AP_OPTS_USE_SUBKEY=yes,
|
||||
samba_cv_HAVE_AP_OPTS_USE_SUBKEY=no)])
|
||||
if test x"$samba_cv_HAVE_AP_OPTS_USE_SUBKEY" = x"yes"; then
|
||||
AC_DEFINE(HAVE_AP_OPTS_USE_SUBKEY,1,
|
||||
[Whether the AP_OPTS_USE_SUBKEY ap option is available])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for KV5M_KEYTAB],
|
||||
samba_cv_HAVE_KV5M_KEYTAB,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_keytab_entry entry; entry.magic = KV5M_KEYTAB;],
|
||||
samba_cv_HAVE_KV5M_KEYTAB=yes,
|
||||
samba_cv_HAVE_KV5M_KEYTAB=no)])
|
||||
if test x"$samba_cv_HAVE_KV5M_KEYTAB" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KV5M_KEYTAB,1,
|
||||
[Whether the KV5M_KEYTAB option is available])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for the krb5_princ_component macro],
|
||||
samba_cv_HAVE_KRB5_PRINC_COMPONENT,[
|
||||
AC_TRY_LINK([#include <krb5.h>],
|
||||
[const krb5_data *pkdata; krb5_context context; krb5_principal principal;
|
||||
pkdata = krb5_princ_component(context, principal, 0);],
|
||||
samba_cv_HAVE_KRB5_PRINC_COMPONENT=yes,
|
||||
samba_cv_HAVE_KRB5_PRINC_COMPONENT=no)])
|
||||
if test x"$samba_cv_HAVE_KRB5_PRINC_COMPONENT" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_PRINC_COMPONENT,1,
|
||||
[Whether krb5_princ_component is available])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for key in krb5_keytab_entry],
|
||||
samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_keytab_entry entry; krb5_keyblock e; entry.key = e;],
|
||||
samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY=yes,
|
||||
samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY=no)])
|
||||
if test x"$samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_KEYTAB_ENTRY_KEY,1,
|
||||
[Whether krb5_keytab_entry has key member])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for keyblock in krb5_keytab_entry],
|
||||
samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_keytab_entry entry; entry.keyblock.keytype = 0;],
|
||||
samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK=yes,
|
||||
samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK=no)])
|
||||
if test x"$samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK,1,
|
||||
[Whether krb5_keytab_entry has keyblock member])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for WRFILE: keytab support],
|
||||
samba_cv_HAVE_WRFILE_KEYTAB,[
|
||||
AC_TRY_RUN([
|
||||
#include<krb5.h>
|
||||
main()
|
||||
{
|
||||
krb5_context context;
|
||||
krb5_keytab keytab;
|
||||
krb5_init_context(&context);
|
||||
return krb5_kt_resolve(context, "WRFILE:api", &keytab);
|
||||
}],
|
||||
samba_cv_HAVE_WRFILE_KEYTAB=yes,
|
||||
samba_cv_HAVE_WRFILE_KEYTAB=no)])
|
||||
if test x"$samba_cv_HAVE_WRFILE_KEYTAB" = x"yes"; then
|
||||
AC_DEFINE(HAVE_WRFILE_KEYTAB,1,
|
||||
[Whether the WRFILE:-keytab is supported])
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for krb5_princ_realm returns krb5_realm or krb5_data],
|
||||
samba_cv_KRB5_PRINC_REALM_RETURNS_REALM,[
|
||||
AC_TRY_COMPILE([#include <krb5.h>],
|
||||
[krb5_context context;krb5_principal principal;krb5_realm realm;
|
||||
realm = *krb5_princ_realm(context, principal);],
|
||||
samba_cv_KRB5_PRINC_REALM_RETURNS_REALM=yes,
|
||||
samba_cv_KRB5_PRINC_REALM_RETURNS_REALM=no)])
|
||||
if test x"$samba_cv_KRB5_PRINC_REALM_RETURNS_REALM" = x"yes"; then
|
||||
AC_DEFINE(KRB5_PRINC_REALM_RETURNS_REALM,1,
|
||||
[Whether krb5_princ_realm returns krb5_realm or krb5_data])
|
||||
fi
|
||||
|
||||
# TODO: check all gssapi headers for this
|
||||
AC_CACHE_CHECK([for GSS_C_DCE_STYLE in gssapi.h],
|
||||
samba_cv_GSS_C_DCE_STYLE,[
|
||||
AC_TRY_COMPILE([#include <gssapi.h>],
|
||||
[int flags = GSS_C_DCE_STYLE;],
|
||||
samba_cv_GSS_C_DCE_STYLE=yes,
|
||||
samba_cv_GSS_C_DCE_STYLE=no)])
|
||||
|
||||
AC_CHECK_FUNC_EXT(gsskrb5_get_initiator_subkey, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(gsskrb5_extract_authz_data_from_sec_context, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(gsskrb5_register_acceptor_identity, $KRB5_LIBS)
|
||||
AC_CHECK_FUNC_EXT(gss_krb5_ccache_name, $KRB5_LIBS)
|
||||
if test x"$ac_cv_lib_ext_krb5_krb5_mk_req_extended" = x"yes"; then
|
||||
AC_DEFINE(HAVE_KRB5,1,[Whether to have KRB5 support])
|
||||
AC_MSG_CHECKING(whether KRB5 support is used)
|
||||
SMB_ENABLE(KRB5,YES)
|
||||
AC_MSG_RESULT(yes)
|
||||
echo "KRB5_CFLAGS: ${KRB5_CFLAGS}"
|
||||
echo "KRB5_CPPFLAGS: ${KRB5_CPPFLAGS}"
|
||||
echo "KRB5_LDFLAGS: ${KRB5_LDFLAGS}"
|
||||
echo "KRB5_LIBS: ${KRB5_LIBS}"
|
||||
else
|
||||
if test x"$with_krb5_support" = x"yes"; then
|
||||
AC_MSG_ERROR(a working krb5 library is needed for KRB5 support)
|
||||
else
|
||||
AC_MSG_WARN(a working krb5 library is needed for KRB5 support)
|
||||
fi
|
||||
KRB5_CFLAGS=""
|
||||
KRB5_CPPFLAGS=""
|
||||
KRB5_LDFLAGS=""
|
||||
KRB5_LIBS=""
|
||||
with_krb5_support=no
|
||||
fi
|
||||
|
||||
# checks if we have access to a libkdc
|
||||
# and can use it for our builtin kdc server_service
|
||||
KDC_CFLAGS=""
|
||||
KDC_CPPFLAGS=""
|
||||
KDC_DLFLAGS=""
|
||||
KDC_LIBS=""
|
||||
AC_CHECK_HEADERS(kdc.h)
|
||||
AC_CHECK_LIB_EXT(kdc, KDC_LIBS, krb5_kdc_default_config)
|
||||
AC_CHECK_LIB_EXT(hdb, KDC_LIBS, hdb_generate_key_set_password)
|
||||
|
||||
AC_MSG_CHECKING(whether libkdc is used)
|
||||
if test x"$ac_cv_header_kdc_h" = x"yes"; then
|
||||
if test x"$ac_cv_lib_ext_kdc_krb5_kdc_default_config" = x"yes"; then
|
||||
if test x"$ac_cv_lib_ext_hdb_hdb_generate_key_set_password" = x"yes"; then
|
||||
SMB_ENABLE(KDC,YES)
|
||||
AC_MSG_RESULT(yes)
|
||||
echo "KDC_LIBS: ${KDC_LIBS}"
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
CFLAGS=$ac_save_CFLAGS
|
||||
CPPFLAGS=$ac_save_CPPFLAGS
|
||||
LDFLAGS=$ac_save_LDFLAGS
|
||||
LIBS="$ac_save_LIBS"
|
||||
|
||||
# as a nasty hack add the krb5 stuff to the global vars,
|
||||
# at some point this should not be needed anymore when the build system
|
||||
# can handle that alone
|
||||
CFLAGS="$CFLAGS $KRB5_CFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS"
|
||||
LDFLAGS="$LDFLAGS $KRB5_LDFLAGS"
|
||||
fi
|
||||
|
||||
SMB_EXT_LIB(KRB5,[${KRB5_LIBS}],[${KRB5_CFLAGS}],[${KRB5_CPPFLAGS}],[${KRB5_LDFLAGS}])
|
||||
SMB_EXT_LIB(KDC,[${KDC_LIBS}],[${KDC_CFLAGS}],[${KDC_CPPFLAGS}],[${KDC_LDFLAGS}])
|
||||
@@ -0,0 +1,15 @@
|
||||
#################################
|
||||
# Start SUBSYSTEM KERBEROS
|
||||
[SUBSYSTEM::KERBEROS]
|
||||
PRIVATE_PROTO_HEADER = proto.h
|
||||
OBJ_FILES = kerberos.o \
|
||||
clikrb5.o \
|
||||
kerberos_heimdal.o \
|
||||
kerberos_util.o \
|
||||
kerberos_pac.o \
|
||||
gssapi_parse.o \
|
||||
krb5_init_context.o
|
||||
PUBLIC_DEPENDENCIES = HEIMDAL_KRB5 NDR_KRB5PAC samba-socket LIBCLI_RESOLVE
|
||||
PRIVATE_DEPENDENCIES = ASN1_UTIL HEIMDAL_ROKEN_ADDRINFO auth_sam CREDENTIALS_KRB5
|
||||
# End SUBSYSTEM KERBEROS
|
||||
#################################
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
simple GSSAPI wrappers
|
||||
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
|
||||
Copyright (C) Luke Howard 2003
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "libcli/util/asn_1.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
|
||||
/*
|
||||
generate a krb5 GSS-API wrapper packet given a ticket
|
||||
*/
|
||||
DATA_BLOB gensec_gssapi_gen_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *ticket, const uint8_t tok_id[2])
|
||||
{
|
||||
struct asn1_data data;
|
||||
DATA_BLOB ret = data_blob(NULL,0);
|
||||
|
||||
if (!ticket->data) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ZERO_STRUCT(data);
|
||||
|
||||
asn1_push_tag(&data, ASN1_APPLICATION(0));
|
||||
asn1_write_OID(&data, GENSEC_OID_KERBEROS5);
|
||||
|
||||
asn1_write(&data, tok_id, 2);
|
||||
asn1_write(&data, ticket->data, ticket->length);
|
||||
asn1_pop_tag(&data);
|
||||
|
||||
if (data.has_error) {
|
||||
DEBUG(1,("Failed to build krb5 wrapper at offset %d\n", (int)data.ofs));
|
||||
asn1_free(&data);
|
||||
}
|
||||
|
||||
ret = data_blob_talloc(mem_ctx, data.data, data.length);
|
||||
asn1_free(&data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
parse a krb5 GSS-API wrapper packet giving a ticket
|
||||
*/
|
||||
BOOL gensec_gssapi_parse_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, DATA_BLOB *ticket, uint8_t tok_id[2])
|
||||
{
|
||||
BOOL ret;
|
||||
struct asn1_data data;
|
||||
int data_remaining;
|
||||
|
||||
asn1_load(&data, *blob);
|
||||
asn1_start_tag(&data, ASN1_APPLICATION(0));
|
||||
asn1_check_OID(&data, GENSEC_OID_KERBEROS5);
|
||||
|
||||
data_remaining = asn1_tag_remaining(&data);
|
||||
|
||||
if (data_remaining < 3) {
|
||||
data.has_error = True;
|
||||
} else {
|
||||
asn1_read(&data, tok_id, 2);
|
||||
data_remaining -= 2;
|
||||
*ticket = data_blob_talloc(mem_ctx, NULL, data_remaining);
|
||||
asn1_read(&data, ticket->data, ticket->length);
|
||||
}
|
||||
|
||||
asn1_end_tag(&data);
|
||||
|
||||
ret = !data.has_error;
|
||||
|
||||
asn1_free(&data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
check a GSS-API wrapper packet givin an expected OID
|
||||
*/
|
||||
BOOL gensec_gssapi_check_oid(const DATA_BLOB *blob, const char *oid)
|
||||
{
|
||||
BOOL ret;
|
||||
struct asn1_data data;
|
||||
|
||||
asn1_load(&data, *blob);
|
||||
asn1_start_tag(&data, ASN1_APPLICATION(0));
|
||||
asn1_check_OID(&data, GENSEC_OID_KERBEROS5);
|
||||
|
||||
ret = !data.has_error;
|
||||
|
||||
asn1_free(&data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,466 @@
|
||||
AllowedWorkstationNames and Krb5
|
||||
--------------------------------
|
||||
|
||||
Microsoft uses the clientAddresses *multiple value* field in the krb5
|
||||
protocol (particularly the AS_REQ) to communicate it's netbios name.
|
||||
This is (my guess) to permit the userWorkstations field to work.
|
||||
|
||||
The KDC I imagine checks the netbios address against this value, in
|
||||
the same way that the Samba server does this.
|
||||
|
||||
The checking of this implies a little of the next question:
|
||||
|
||||
Is a DAL the layer we need?
|
||||
---------------------------
|
||||
|
||||
Looking at what we need to pass around, I start to seriously wonder if
|
||||
the DAL is even the right layer - we seem to want to create an account
|
||||
authorization abstraction layer - is this account permitted to login to
|
||||
this computer, at this time?
|
||||
|
||||
This information in AD is much richer than the Heimdal HDB, and it
|
||||
seems to make sense to do AD-specific access control checks in an
|
||||
AD-specific layer, not in the back-end agnostic server.
|
||||
|
||||
Because the DAL only reads in the principalName as the key, it has
|
||||
trouble performing access control decisions on things other than the
|
||||
name.
|
||||
|
||||
I'll be very interested if the DAL really works for eDirectory too.
|
||||
Perhaps all we need to do is add in the same kludges as we have in
|
||||
Samba 3.0 for eDirectory. Hmm...
|
||||
|
||||
That said, the current layer provides us with a very good start, and
|
||||
any redefinition would occour from that basis.
|
||||
|
||||
|
||||
GSSAPI layer requirements
|
||||
-------------------------
|
||||
|
||||
Welcome to the wonderful world of canonicalisation
|
||||
|
||||
The MIT GSSAPI libs do not support kinit returning a different
|
||||
realm to what the client asked for, even just in case differences.
|
||||
|
||||
Heimdal has the same problem, and this applies to the krb5 layer, not
|
||||
just gssapi.
|
||||
|
||||
We need to test if the canonicalisation is controlled by the KDCOption
|
||||
flags, windows always sends the Canonicalize flags
|
||||
|
||||
Old Clients (samba3 and HPUX clients) uses 'selfmade' gssapi/krb5
|
||||
for using it in the CIFS session setup. Because they use krb5_mk_req()
|
||||
they get a chksum field depending on the encryption type, but that's wrong
|
||||
for GSSAPI (see rfc 1964 section 1.1.1). The Cheksum type 8003
|
||||
should be used in the Authenticator of the AP-REQ! That allows the channel bindings,
|
||||
the GCC_C_* req_flags and optional delegation tickets to be passed from the client to the server.
|
||||
Hower windows doesn't seems to care about if the checksum is of the wrong type,
|
||||
for CIFS SessionSetups, it seems that the req_flags are just set to 0.
|
||||
So this can't work for LDAP connections with sign or seal, or for any DCERPC
|
||||
connection.
|
||||
|
||||
So we need to also support old clients!
|
||||
|
||||
Principal Names, long and short names
|
||||
-------------------------------------
|
||||
|
||||
As far as servicePrincipalNames are concerned, these are not
|
||||
canonicalised, except as regards the realm in the reply. That is, the
|
||||
client gets back the principal it asked for, with the realm portion
|
||||
'fixed' to uppercase, long form.
|
||||
|
||||
The short name of the realm seems to be accepted for at least AS_REQ
|
||||
operations, but because the server performs canonicalisation, this
|
||||
causes pain for current client libraries.
|
||||
|
||||
The canonicalisation of names matters not only for the KDC, but also
|
||||
for code that has to deal with keytabs.
|
||||
|
||||
We also need to handle type 10 names (NT-ENTERPRISE), which are a full
|
||||
principal name in the principal field, unrelated to the realm.
|
||||
|
||||
HOST/ Aliases
|
||||
-------------
|
||||
|
||||
There is another post somewhere (ref lost for the moment) that details
|
||||
where in active directory the list of stored aliases for HOST/ is.
|
||||
This should be read, parsed and used to allow any of these requests to
|
||||
use the HOST/ key.
|
||||
|
||||
For example, this is how HTTP/, DNS/ and CIFS/ can use HOST/ without
|
||||
any explicit entry.
|
||||
|
||||
|
||||
Jean-Baptiste.Marchand@hsc.fr reminds me:
|
||||
|
||||
> This is the SPNMappings attribute in Active Directory:
|
||||
|
||||
> http://msdn.microsoft.com/library/en-us/adschema/adschema/a_spnmappings.asp
|
||||
|
||||
We implement this in hdb-ldb.
|
||||
|
||||
Implicit names for Win2000 Accounts
|
||||
-----------------------------------
|
||||
|
||||
Despite not having a DNS name, nor a servicePrincipalName on accounts
|
||||
created by computers running win2000, it appears we are expected to
|
||||
have an implicit mapping from host/computer.full.name and
|
||||
host/computer to it's entry.
|
||||
|
||||
Returned Salt for PreAuthentication
|
||||
-----------------------------------
|
||||
|
||||
When the server replies for pre-authentication, it returns the Salt,
|
||||
which may be in the form of a principalName that is in no way
|
||||
connected with the current names. (ie, even if the userPrincipalName
|
||||
and samAccountName are renamed, the old salt is returned).
|
||||
|
||||
This is probably the kerberos standard salt, kept in the 'Key'. The
|
||||
standard generation rules are found in a Mail from Luke Howard dated
|
||||
10 Nov 2004:
|
||||
|
||||
|
||||
From: Luke Howard <lukeh@padl.com>
|
||||
Message-Id: <200411100231.iAA2VLUW006101@au.padl.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=US-ASCII
|
||||
Organization: PADL Software Pty Ltd
|
||||
To: lukeh@padl.com
|
||||
Date: Wed, 10 Nov 2004 13:31:21 +1100
|
||||
Versions: dmail (bsd44) 2.6d/makemail 2.10
|
||||
Cc: huaraz@moeller.plus.com, samba-technical@lists.samba.org
|
||||
Subject: Re: Samba-3.0.7-1.3E Active Directory Issues
|
||||
X-BeenThere: samba-technical@lists.samba.org
|
||||
X-Mailman-Version: 2.1.4
|
||||
Precedence: list
|
||||
Reply-To: lukeh@padl.com
|
||||
|
||||
Did some more testing, it appears the behaviour has another
|
||||
explanation. It appears that the standard Kerberos password salt
|
||||
algorithm is applied in Windows 2003, just that the source principal
|
||||
name is different.
|
||||
|
||||
Here is what I've been able to deduce from creating a bunch of
|
||||
different accounts:
|
||||
|
||||
Type of account Principal for Salting
|
||||
========================================================================
|
||||
Computer Account host/<SAM-Name-Without-$>.realm@REALM
|
||||
User Account Without UPN <SAM-Name>@REALM
|
||||
User Account With UPN <LHS-Of-UPN>@REALM
|
||||
|
||||
Note that if the computer account's SAM account name does not include
|
||||
the trailing '$', then the entire SAM account name is used as input to
|
||||
the salting principal. Setting a UPN for a computer account has no
|
||||
effect.
|
||||
|
||||
It seems to me odd that the RHS of the UPN is not used in the salting
|
||||
principal. For example, a user with UPN foo@mydomain.com in the realm
|
||||
MYREALM.COM would have a salt of MYREALM.COMfoo. Perhaps this is to
|
||||
allow a user's UPN suffix to be changed without changing the salt. And
|
||||
perhaps using the UPN for salting signifies a move away SAM names and
|
||||
their associated constraints.
|
||||
|
||||
For more information on how UPNs relate to the Kerberos protocol,
|
||||
see:
|
||||
|
||||
http://www.ietf.org/proceedings/01dec/I-D/draft-ietf-krb-wg-kerberos-referrals-02.txt
|
||||
|
||||
-- Luke
|
||||
|
||||
--
|
||||
|
||||
|
||||
|
||||
|
||||
Heimdal oddities
|
||||
----------------
|
||||
|
||||
Heimdal is built such that it should be able to serve multiple realms
|
||||
at the same time. This isn't relevant for Samba's use, but it shows
|
||||
up in a lot of generalisations throughout the code.
|
||||
|
||||
Other odd things:
|
||||
- Support for multiple passwords on a client account: we seem to
|
||||
call hdb_next_enctype2key() in the pre-authentication routines to
|
||||
allow multiple passwords per account in krb5. (I think this was
|
||||
intened to allow multiple salts)
|
||||
|
||||
State Machine safety
|
||||
--------------------
|
||||
|
||||
Samba is a giant state machine, and as such have very different
|
||||
requirements to those traditionally expressed for kerberos and GSSAPI
|
||||
libraries.
|
||||
|
||||
Samba requires all of the libraries it uses to be state machine safe in
|
||||
their use of internal data. This does not mean thread safe, and an
|
||||
application could be thread safe, but not state machine safe (if it
|
||||
instead used thread-local variables).
|
||||
|
||||
So, what does it mean for a library to be state machine safe? This is
|
||||
mostly a question of context, and how the library manages whatever
|
||||
internal state machines it has. If the library uses a context
|
||||
variable, passed in by the caller, which contains all the information
|
||||
about the current state of the library, then it is safe. An example
|
||||
of this state is the sequence number and session keys for an ongoing
|
||||
encrypted session).
|
||||
|
||||
The other issue affecting state machines is 'blocking' (waiting for a
|
||||
read on a network socket).
|
||||
|
||||
Heimdal has this 'state machine safety' in parts, and we have modified
|
||||
the lorikeet branch to improve this behviour, when using a new,
|
||||
non-standard API.
|
||||
|
||||
Heimdal uses a per-context variable for the 'krb5_auth_context', which
|
||||
controls the ongoing encrypted connection, but does use global
|
||||
variables for the ubiquitous krb5_context parameter.
|
||||
|
||||
The modification that has added most to 'state machine safety' of
|
||||
GSSAPI is the addition of the gsskrb5_acquire_creds function. This
|
||||
allows the caller to specify a keytab and ccache, for use by the
|
||||
GSSAPI code. Therefore there is no need to use global variables to
|
||||
communicate this information.
|
||||
|
||||
At a more theoritical level (simply counting static and global
|
||||
variables) Heimdal is not state machine safe for the GSSAPI layer.
|
||||
The Krb5 layer alone is much closer, as far as I can tell, blocking
|
||||
excepted. .
|
||||
|
||||
To deal with blocking, we could have a fork()ed child per context,
|
||||
using the 'GSSAPI export context' function to transfer
|
||||
the GSSAPI state back into the main code for the wrap()/unwrap() part
|
||||
of the operation. This will still hit issues of static storage (one
|
||||
gss_krb5_context per process, and multiple GSSAPI encrypted sessions
|
||||
at a time) but these may not matter in practice.
|
||||
|
||||
In the short-term, we deal with blocking by taking over the network
|
||||
send() and recv() functions, therefore making them 'semi-async'. This
|
||||
doens't apply to DNS yet.
|
||||
|
||||
GSSAPI and Kerberos extensions
|
||||
------------------------------
|
||||
|
||||
This is a general list of the other extensions we have made to / need from
|
||||
the kerberos libraries
|
||||
|
||||
- DCE_STYLE
|
||||
|
||||
- gsskrb5_get_initiator_subkey() (return the exact key that Samba3
|
||||
has always asked for. gsskrb5_get_subkey() might do what we need
|
||||
anyway)
|
||||
|
||||
- gsskrb5_acquire_creds() (takes keytab and/or ccache as input
|
||||
parameters, see keytab and state machine discussion)
|
||||
|
||||
- gss_krb5_copy_service_keyblock() (get the key used to actually
|
||||
encrypt the ticket to the server, because the same key is used for
|
||||
the PAC validation).
|
||||
- gsskrb5_extract_authtime_from_sec_context (get authtime from
|
||||
kerberos ticket)
|
||||
- gsskrb5_extract_authz_data_from_sec_context (get authdata from
|
||||
ticket, ie the PAC. Must unwrap the data if in an AD-IFRELEVENT)
|
||||
- gsskrb5_wrap_size (find out how big the wrapped packet will be,
|
||||
given input length).
|
||||
|
||||
Keytab requirements
|
||||
-------------------
|
||||
|
||||
Because windows machine account handling is very different to the
|
||||
tranditional 'MIT' keytab operation. This starts when we look at the
|
||||
basis of the secrets handling:
|
||||
|
||||
Traditional 'MIT' behaviour is to use a keytab, continaing salted key
|
||||
data, extracted from the KDC. (In this modal, there is no 'service
|
||||
password', instead the keys are often simply application of random
|
||||
bytes). Heimdal also implements this behaviour.
|
||||
|
||||
The windows modal is very different - instead of sharing a keytab with
|
||||
each member server, a password is stored for the whole machine. The
|
||||
password is set with non-kerberos mechanisms (particularly SAMR, a
|
||||
DCE-RPC service) and when interacting on a kerberos basis, the
|
||||
password is salted by the client. (That is, no salt infromation
|
||||
appears to be convayed from the KDC to the member).
|
||||
|
||||
In dealing with this modal, we leverage both the traditional file
|
||||
keytab and in-MEMORY keytabs.
|
||||
|
||||
When dealing with a windows KDC, the behaviour regarding case
|
||||
sensitivity and canonacolisation must be accomidated. This means that
|
||||
an incoming request to a member server may have a wide variety of
|
||||
service principal names. These include:
|
||||
|
||||
machine$@REALM (samba clients)
|
||||
HOST/foo.bar@realm (win2k clients)
|
||||
HOST/foo@realm (win2k clients, using netbios)
|
||||
cifs/foo.bar@realm (winxp clients)
|
||||
cifs/foo@realm (winxp clients, using netbios)
|
||||
|
||||
as well as all case variations on the above.
|
||||
|
||||
Because that all got 'too hard' to put into a keytab in the
|
||||
traditional way (with the client to specify the name), we either
|
||||
pre-compute the keys into a traditional keytab or make an in-MEMORY
|
||||
keytab at run time. In both cases we specifiy the principal name to
|
||||
GSSAPI, which avoids the need to store duplicate principals.
|
||||
|
||||
We use a 'private' keytab in our private dir, referenced from the
|
||||
secrets.ldb by default.
|
||||
|
||||
Extra Heimdal functions used
|
||||
----------------------------
|
||||
(an attempt to list some of the Heimdal-specific functions I know we use)
|
||||
|
||||
krb5_free_keyblock_contents()
|
||||
|
||||
also a raft of prinicpal manipulation functions:
|
||||
|
||||
Prncipal Manipulation
|
||||
---------------------
|
||||
|
||||
Samba makes extensive use of the principal manipulation functions in
|
||||
Heimdal, including the known structure behind krb_principal and
|
||||
krb5_realm (a char *).
|
||||
|
||||
Authz data extraction
|
||||
---------------------
|
||||
|
||||
We use krb5_ticket_get_authorization_data_type(), and expect it to
|
||||
return the correct authz data, even if wrapped in an AD-IFRELEVENT container.
|
||||
|
||||
|
||||
KDC/hdb Extensions
|
||||
--------------
|
||||
|
||||
We have modified Heimdal's 'hdb' interface to specify the 'type' of
|
||||
Principal being requested. This allows us to correctly behave with
|
||||
the different 'classes' of Principal name.
|
||||
|
||||
We currently define 2 classes:
|
||||
- client (kinit)
|
||||
- server (tgt)
|
||||
|
||||
I also now specify the kerberos principal as an explict parameter, not
|
||||
an in/out value on the entry itself.
|
||||
|
||||
Inside hdb-ldb, we add krbtgt as a special class of principal, because
|
||||
of particular special-case backend requirements.
|
||||
|
||||
Callbacks:
|
||||
In addition, I have added a new interface hdb_fetch_ex(), which
|
||||
returns a structure including callbacks, which provide the hook for
|
||||
the PAC, as well as a callback into the main access control routines.
|
||||
|
||||
A new callback should be added to increment the bad password counter
|
||||
on failure.
|
||||
|
||||
Another possability for a callback is to obtain the keys. This would
|
||||
allow the plaintext password to only be hashed into the encryption
|
||||
types we need. This idea from the eDirectory/MIT DAL work.
|
||||
|
||||
This probably should be combined with storing the hashed passwords in
|
||||
the supplementalCredentials attribute. If combined with a kvno
|
||||
parameter, this could also allow changing of the krbtgt password
|
||||
(valuable for security).
|
||||
|
||||
libkdc
|
||||
------
|
||||
|
||||
Samba4 needs to be built as a single binary (design requirement), and
|
||||
this should include the KDC. Samba also (and perhaps more
|
||||
importantly) needs to control the configuration environment of the
|
||||
KDC.
|
||||
|
||||
The interface we have defined for libkdc allow for packet injection
|
||||
into the post-socket layer, with a defined krb5_context and
|
||||
kdb5_kdc_configuration structure. These effectively redirect the
|
||||
kerberos warnings, logging and database calls as we require.
|
||||
|
||||
Using our socket lib
|
||||
--------------------
|
||||
|
||||
An important detail in the use of libkdc is that we use our own socket
|
||||
lib. This allows the KDC code to be as portable as the rest of samba
|
||||
(this cuts both ways), but far more importantly it ensures a
|
||||
consistancy in the handling of requests, binding to sockets etc.
|
||||
|
||||
To handle TCP, we use of our socket layer in much the same way as
|
||||
we deal with TCP for CIFS. Tridge created a generic packet handling
|
||||
layer for this.
|
||||
|
||||
For the client, we likewise must take over the socket functions, so
|
||||
that our single thread smbd will not lock up talking to itself. (We
|
||||
allow processing while waiting for packets in our socket routines).
|
||||
|
||||
Kerberos logging support
|
||||
------------------------
|
||||
|
||||
Samba now (optionally in the main code, required for the KDC) uses the
|
||||
krb5_log_facility from Heimdal. This allows us to redirect the
|
||||
warnings and status from the KDC (and client/server kerberos code) to
|
||||
Samba's DEBUG() system.
|
||||
|
||||
Similarly important is the Heimdal-specific krb5_get_error_string()
|
||||
function, which does a lot to reduce the 'administrator pain' level,
|
||||
by providing specific, english text-string error messages instead of
|
||||
just error code translations.
|
||||
|
||||
|
||||
Short name rules
|
||||
----------------
|
||||
|
||||
Samba is highly likely to be misconfigured, in many weird and
|
||||
interesting ways. As such, we have a patch for Heimdal that avoids
|
||||
DNS lookups on names without a . in them. This should avoid some
|
||||
delay and root server load.
|
||||
|
||||
PAC Correctness
|
||||
---------------
|
||||
|
||||
We now put the PAC into the TGT, not just the service ticket.
|
||||
|
||||
Forwarded tickets
|
||||
-----------------
|
||||
|
||||
We extract forwarded tickets from the GSSAPI layer, and put
|
||||
them into the credentials. We can then use them for proxy work.
|
||||
|
||||
|
||||
Kerberos TODO
|
||||
=============
|
||||
|
||||
(Feel free to contribute to any of these tasks, or ask
|
||||
abartlet@samba.org about them).
|
||||
|
||||
Lockout Control
|
||||
--------------
|
||||
|
||||
We need to get (either if PADL publishes their patch, or write our
|
||||
own) access control hooks in the Heimdal KDC. We need to lockout
|
||||
accounts, and perform other controls.
|
||||
|
||||
Gssmonger
|
||||
---------
|
||||
|
||||
Microsoft has released a testsuite called gssmonger, which tests
|
||||
interop. We should compile it against lorikeet-heimdal, MIT and see
|
||||
if we can build a 'Samba4' server for it.
|
||||
|
||||
Kpasswd server
|
||||
--------------
|
||||
|
||||
I have a partial kpasswd server which needs finishing, and a we need a
|
||||
client testsuite written, either via the krb5 API or directly against
|
||||
GENSEC and the ASN.1 routines.
|
||||
|
||||
Currently it only works for Heimdal, not MIT clients. This may be due
|
||||
to call ordering constraints.
|
||||
|
||||
|
||||
Correct TCP support
|
||||
-------------------
|
||||
|
||||
Our current TCP support does not send back 'too large' error messages
|
||||
if the high bit is set. This is needed for a proposed extension
|
||||
mechanism, but is likewise unsupported in both current Heimdal and MIT.
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
kerberos utility library
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Remus Koos 2001
|
||||
Copyright (C) Nalin Dahyabhai 2004.
|
||||
Copyright (C) Jeremy Allison 2004.
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "roken.h"
|
||||
|
||||
#ifdef HAVE_KRB5
|
||||
|
||||
/*
|
||||
simulate a kinit, putting the tgt in the given credentials cache.
|
||||
Orignally by remus@snapserver.com
|
||||
|
||||
This version is built to use a keyblock, rather than needing the
|
||||
original password.
|
||||
*/
|
||||
int kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc,
|
||||
krb5_principal principal, krb5_keyblock *keyblock,
|
||||
time_t *expire_time, time_t *kdc_time)
|
||||
{
|
||||
krb5_error_code code = 0;
|
||||
krb5_creds my_creds;
|
||||
krb5_get_init_creds_opt options;
|
||||
|
||||
krb5_get_init_creds_opt_init(&options);
|
||||
|
||||
krb5_get_init_creds_opt_set_default_flags(ctx, NULL, NULL, &options);
|
||||
|
||||
if ((code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal, keyblock,
|
||||
0, NULL, &options))) {
|
||||
return code;
|
||||
}
|
||||
|
||||
if ((code = krb5_cc_initialize(ctx, cc, principal))) {
|
||||
krb5_free_cred_contents(ctx, &my_creds);
|
||||
return code;
|
||||
}
|
||||
|
||||
if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
|
||||
krb5_free_cred_contents(ctx, &my_creds);
|
||||
return code;
|
||||
}
|
||||
|
||||
if (expire_time) {
|
||||
*expire_time = (time_t) my_creds.times.endtime;
|
||||
}
|
||||
|
||||
if (kdc_time) {
|
||||
*kdc_time = (time_t) my_creds.times.starttime;
|
||||
}
|
||||
|
||||
krb5_free_cred_contents(ctx, &my_creds);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
simulate a kinit, putting the tgt in the given credentials cache.
|
||||
Orignally by remus@snapserver.com
|
||||
*/
|
||||
int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc,
|
||||
krb5_principal principal, const char *password,
|
||||
time_t *expire_time, time_t *kdc_time)
|
||||
{
|
||||
krb5_error_code code = 0;
|
||||
krb5_creds my_creds;
|
||||
krb5_get_init_creds_opt options;
|
||||
|
||||
krb5_get_init_creds_opt_init(&options);
|
||||
|
||||
krb5_get_init_creds_opt_set_default_flags(ctx, NULL, NULL, &options);
|
||||
|
||||
if ((code = krb5_get_init_creds_password(ctx, &my_creds, principal, password,
|
||||
NULL,
|
||||
NULL, 0, NULL, &options))) {
|
||||
return code;
|
||||
}
|
||||
|
||||
if ((code = krb5_cc_initialize(ctx, cc, principal))) {
|
||||
krb5_free_cred_contents(ctx, &my_creds);
|
||||
return code;
|
||||
}
|
||||
|
||||
if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
|
||||
krb5_free_cred_contents(ctx, &my_creds);
|
||||
return code;
|
||||
}
|
||||
|
||||
if (expire_time) {
|
||||
*expire_time = (time_t) my_creds.times.endtime;
|
||||
}
|
||||
|
||||
if (kdc_time) {
|
||||
*kdc_time = (time_t) my_creds.times.starttime;
|
||||
}
|
||||
|
||||
krb5_free_cred_contents(ctx, &my_creds);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
simple kerberos5 routines for active directory
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Luke Howard 2002-2003
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#if defined(HAVE_KRB5)
|
||||
|
||||
#include "auth/kerberos/krb5_init_context.h"
|
||||
#include "librpc/gen_ndr/krb5pac.h"
|
||||
|
||||
struct auth_serversupplied_info;
|
||||
struct cli_credentials;
|
||||
|
||||
struct ccache_container {
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
krb5_ccache ccache;
|
||||
};
|
||||
|
||||
struct keytab_container {
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
krb5_keytab keytab;
|
||||
};
|
||||
|
||||
/* not really ASN.1, but RFC 1964 */
|
||||
#define TOK_ID_KRB_AP_REQ ((const uint8_t *)"\x01\x00")
|
||||
#define TOK_ID_KRB_AP_REP ((const uint8_t *)"\x02\x00")
|
||||
#define TOK_ID_KRB_ERROR ((const uint8_t *)"\x03\x00")
|
||||
#define TOK_ID_GSS_GETMIC ((const uint8_t *)"\x01\x01")
|
||||
#define TOK_ID_GSS_WRAP ((const uint8_t *)"\x02\x01")
|
||||
|
||||
#ifdef HAVE_KRB5_KEYBLOCK_KEYVALUE
|
||||
#define KRB5_KEY_TYPE(k) ((k)->keytype)
|
||||
#define KRB5_KEY_LENGTH(k) ((k)->keyvalue.length)
|
||||
#define KRB5_KEY_DATA(k) ((k)->keyvalue.data)
|
||||
#else
|
||||
#define KRB5_KEY_TYPE(k) ((k)->enctype)
|
||||
#define KRB5_KEY_LENGTH(k) ((k)->length)
|
||||
#define KRB5_KEY_DATA(k) ((k)->contents)
|
||||
#endif /* HAVE_KRB5_KEYBLOCK_KEYVALUE */
|
||||
|
||||
#ifndef HAVE_KRB5_SET_REAL_TIME
|
||||
krb5_error_code krb5_set_real_time(krb5_context context, int32_t seconds, int32_t microseconds);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_KRB5_SET_DEFAULT_TGS_KTYPES
|
||||
krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY)
|
||||
krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock *keyblock);
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_KRB5_FREE_UNPARSED_NAME
|
||||
void krb5_free_unparsed_name(krb5_context ctx, char *val);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT)
|
||||
const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i );
|
||||
#endif
|
||||
|
||||
/* Samba wrapper function for krb5 functionality. */
|
||||
void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr);
|
||||
int create_kerberos_key_from_string(krb5_context context, krb5_principal host_princ, krb5_data *password, krb5_keyblock *key, krb5_enctype enctype);
|
||||
int create_kerberos_key_from_string_direct(krb5_context context, krb5_principal host_princ, krb5_data *password, krb5_keyblock *key, krb5_enctype enctype);
|
||||
krb5_const_principal get_principal_from_tkt(krb5_ticket *tkt);
|
||||
krb5_error_code get_kerberos_allowed_etypes(krb5_context context, krb5_enctype **enctypes);
|
||||
void free_kerberos_etypes(krb5_context context, krb5_enctype *enctypes);
|
||||
BOOL get_krb5_smb_session_key(krb5_context context, krb5_auth_context auth_context, DATA_BLOB *session_key, BOOL remote);
|
||||
krb5_error_code ads_krb5_mk_req(krb5_context context,
|
||||
krb5_auth_context *auth_context,
|
||||
const krb5_flags ap_req_options,
|
||||
const char *principal,
|
||||
krb5_ccache ccache,
|
||||
krb5_data *outbuf);
|
||||
BOOL get_auth_data_from_tkt(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data, krb5_ticket *tkt);
|
||||
NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_auth_context *auth_context,
|
||||
struct cli_credentials *machine_account,
|
||||
const char *service,
|
||||
const DATA_BLOB *enc_ticket,
|
||||
krb5_ticket **tkt,
|
||||
DATA_BLOB *ap_rep,
|
||||
krb5_keyblock **keyblock);
|
||||
int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc,
|
||||
krb5_principal principal, const char *password,
|
||||
time_t *expire_time, time_t *kdc_time);
|
||||
int kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc,
|
||||
krb5_principal principal, krb5_keyblock *keyblock,
|
||||
time_t *expire_time, time_t *kdc_time);
|
||||
krb5_principal kerberos_fetch_salt_princ_for_host_princ(krb5_context context,
|
||||
krb5_principal host_princ,
|
||||
int enctype);
|
||||
void kerberos_set_creds_enctype(krb5_creds *pcreds, int enctype);
|
||||
BOOL kerberos_compatible_enctypes(krb5_context context, krb5_enctype enctype1, krb5_enctype enctype2);
|
||||
void kerberos_free_data_contents(krb5_context context, krb5_data *pdata);
|
||||
krb5_error_code smb_krb5_kt_free_entry(krb5_context context, krb5_keytab_entry *kt_entry);
|
||||
char *smb_get_krb5_error_message(krb5_context context, krb5_error_code code, TALLOC_CTX *mem_ctx);
|
||||
krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *credentials,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_ccache ccache);
|
||||
krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *credentials,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_principal *princ);
|
||||
NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
|
||||
struct PAC_DATA **pac_data_out,
|
||||
DATA_BLOB blob,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
krb5_const_principal client_principal,
|
||||
time_t tgs_authtime,
|
||||
krb5_error_code *k5ret);
|
||||
NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx,
|
||||
struct PAC_LOGON_INFO **logon_info,
|
||||
DATA_BLOB blob,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
krb5_const_principal client_principal,
|
||||
time_t tgs_authtime,
|
||||
krb5_error_code *k5ret);
|
||||
krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx,
|
||||
struct PAC_DATA *pac_data,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
DATA_BLOB *pac);
|
||||
krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx,
|
||||
struct auth_serversupplied_info *server_info,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
krb5_principal client_principal,
|
||||
time_t tgs_authtime,
|
||||
DATA_BLOB *pac);
|
||||
|
||||
#include "auth/kerberos/proto.h"
|
||||
|
||||
#endif /* HAVE_KRB5 */
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* 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.
|
||||
*
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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.
|
||||
*/
|
||||
|
||||
/* This file for code taken from the Heimdal code, to preserve licence */
|
||||
/* Modified by Andrew Bartlett <abartlet@samba.org> */
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
|
||||
/* Taken from accept_sec_context.c,v 1.65 */
|
||||
krb5_error_code smb_rd_req_return_stuff(krb5_context context,
|
||||
krb5_auth_context *auth_context,
|
||||
const krb5_data *inbuf,
|
||||
krb5_keytab keytab,
|
||||
krb5_principal acceptor_principal,
|
||||
krb5_data *outbuf,
|
||||
krb5_ticket **ticket,
|
||||
krb5_keyblock **keyblock)
|
||||
{
|
||||
krb5_rd_req_in_ctx in = NULL;
|
||||
krb5_rd_req_out_ctx out = NULL;
|
||||
krb5_error_code kret;
|
||||
|
||||
*keyblock = NULL;
|
||||
*ticket = NULL;
|
||||
outbuf->length = 0;
|
||||
outbuf->data = NULL;
|
||||
|
||||
kret = krb5_rd_req_in_ctx_alloc(context, &in);
|
||||
if (kret == 0)
|
||||
kret = krb5_rd_req_in_set_keytab(context, in, keytab);
|
||||
if (kret) {
|
||||
if (in)
|
||||
krb5_rd_req_in_ctx_free(context, in);
|
||||
return kret;
|
||||
}
|
||||
|
||||
kret = krb5_rd_req_ctx(context,
|
||||
auth_context,
|
||||
inbuf,
|
||||
acceptor_principal,
|
||||
in, &out);
|
||||
krb5_rd_req_in_ctx_free(context, in);
|
||||
if (kret) {
|
||||
return kret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to remember some data on the context_handle.
|
||||
*/
|
||||
kret = krb5_rd_req_out_get_ticket(context, out,
|
||||
ticket);
|
||||
if (kret == 0) {
|
||||
kret = krb5_rd_req_out_get_keyblock(context, out,
|
||||
keyblock);
|
||||
}
|
||||
krb5_rd_req_out_ctx_free(context, out);
|
||||
|
||||
if (kret == 0) {
|
||||
kret = krb5_mk_rep(context, *auth_context, outbuf);
|
||||
}
|
||||
|
||||
if (kret) {
|
||||
krb5_free_ticket(context, *ticket);
|
||||
krb5_free_keyblock(context, *keyblock);
|
||||
krb5_data_free(outbuf);
|
||||
}
|
||||
|
||||
return kret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Create and parse the krb5 PAC
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Luke Howard 2002-2003
|
||||
Copyright (C) Stefan Metzmacher 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
#include "librpc/gen_ndr/ndr_krb5pac.h"
|
||||
#include "lib/ldb/include/ldb.h"
|
||||
#include "auth/auth_sam.h"
|
||||
|
||||
static krb5_error_code check_pac_checksum(TALLOC_CTX *mem_ctx,
|
||||
DATA_BLOB pac_data,
|
||||
struct PAC_SIGNATURE_DATA *sig,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *keyblock)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_crypto crypto;
|
||||
Checksum cksum;
|
||||
|
||||
cksum.cksumtype = (CKSUMTYPE)sig->type;
|
||||
cksum.checksum.length = sig->signature.length;
|
||||
cksum.checksum.data = sig->signature.data;
|
||||
|
||||
ret = krb5_crypto_init(context,
|
||||
keyblock,
|
||||
0,
|
||||
&crypto);
|
||||
if (ret) {
|
||||
DEBUG(0,("krb5_crypto_init() failed: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
return ret;
|
||||
}
|
||||
ret = krb5_verify_checksum(context,
|
||||
crypto,
|
||||
KRB5_KU_OTHER_CKSUM,
|
||||
pac_data.data,
|
||||
pac_data.length,
|
||||
&cksum);
|
||||
krb5_crypto_destroy(context, crypto);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx,
|
||||
struct PAC_DATA **pac_data_out,
|
||||
DATA_BLOB blob,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
krb5_const_principal client_principal,
|
||||
time_t tgs_authtime,
|
||||
krb5_error_code *k5ret)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
NTSTATUS status;
|
||||
struct PAC_SIGNATURE_DATA *srv_sig_ptr = NULL;
|
||||
struct PAC_SIGNATURE_DATA *kdc_sig_ptr = NULL;
|
||||
struct PAC_SIGNATURE_DATA *srv_sig_wipe = NULL;
|
||||
struct PAC_SIGNATURE_DATA *kdc_sig_wipe = NULL;
|
||||
struct PAC_LOGON_INFO *logon_info = NULL;
|
||||
struct PAC_LOGON_NAME *logon_name = NULL;
|
||||
struct PAC_DATA *pac_data;
|
||||
struct PAC_DATA_RAW *pac_data_raw;
|
||||
|
||||
DATA_BLOB *srv_sig_blob = NULL;
|
||||
DATA_BLOB *kdc_sig_blob = NULL;
|
||||
|
||||
DATA_BLOB modified_pac_blob;
|
||||
NTTIME tgs_authtime_nttime;
|
||||
krb5_principal client_principal_pac;
|
||||
int i;
|
||||
|
||||
krb5_clear_error_string(context);
|
||||
|
||||
if (k5ret) {
|
||||
*k5ret = KRB5_PARSE_MALFORMED;
|
||||
}
|
||||
|
||||
pac_data = talloc(mem_ctx, struct PAC_DATA);
|
||||
pac_data_raw = talloc(mem_ctx, struct PAC_DATA_RAW);
|
||||
kdc_sig_wipe = talloc(mem_ctx, struct PAC_SIGNATURE_DATA);
|
||||
srv_sig_wipe = talloc(mem_ctx, struct PAC_SIGNATURE_DATA);
|
||||
if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) {
|
||||
if (k5ret) {
|
||||
*k5ret = ENOMEM;
|
||||
}
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
status = ndr_pull_struct_blob(&blob, pac_data, pac_data,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_PAC_DATA);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't parse the PAC\n"));
|
||||
return status;
|
||||
}
|
||||
|
||||
if (pac_data->num_buffers < 4) {
|
||||
/* we need logon_ingo, service_key and kdc_key */
|
||||
DEBUG(0,("less than 4 PAC buffers\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
status = ndr_pull_struct_blob(&blob, pac_data_raw, pac_data_raw,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_PAC_DATA_RAW);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't parse the PAC\n"));
|
||||
return status;
|
||||
}
|
||||
|
||||
if (pac_data_raw->num_buffers < 4) {
|
||||
/* we need logon_ingo, service_key and kdc_key */
|
||||
DEBUG(0,("less than 4 PAC buffers\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (pac_data->num_buffers != pac_data_raw->num_buffers) {
|
||||
/* we need logon_ingo, service_key and kdc_key */
|
||||
DEBUG(0,("misparse! PAC_DATA has %d buffers while PAC_DATA_RAW has %d\n",
|
||||
pac_data->num_buffers, pac_data_raw->num_buffers));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (i=0; i < pac_data->num_buffers; i++) {
|
||||
if (pac_data->buffers[i].type != pac_data_raw->buffers[i].type) {
|
||||
DEBUG(0,("misparse! PAC_DATA buffer %d has type %d while PAC_DATA_RAW has %d\n",
|
||||
i, pac_data->buffers[i].type, pac_data->buffers[i].type));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
switch (pac_data->buffers[i].type) {
|
||||
case PAC_TYPE_LOGON_INFO:
|
||||
if (!pac_data->buffers[i].info) {
|
||||
break;
|
||||
}
|
||||
logon_info = pac_data->buffers[i].info->logon_info.info;
|
||||
break;
|
||||
case PAC_TYPE_SRV_CHECKSUM:
|
||||
if (!pac_data->buffers[i].info) {
|
||||
break;
|
||||
}
|
||||
srv_sig_ptr = &pac_data->buffers[i].info->srv_cksum;
|
||||
srv_sig_blob = &pac_data_raw->buffers[i].info->remaining;
|
||||
break;
|
||||
case PAC_TYPE_KDC_CHECKSUM:
|
||||
if (!pac_data->buffers[i].info) {
|
||||
break;
|
||||
}
|
||||
kdc_sig_ptr = &pac_data->buffers[i].info->kdc_cksum;
|
||||
kdc_sig_blob = &pac_data_raw->buffers[i].info->remaining;
|
||||
break;
|
||||
case PAC_TYPE_LOGON_NAME:
|
||||
logon_name = &pac_data->buffers[i].info->logon_name;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!logon_info) {
|
||||
DEBUG(0,("PAC no logon_info\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!logon_name) {
|
||||
DEBUG(0,("PAC no logon_name\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!srv_sig_ptr || !srv_sig_blob) {
|
||||
DEBUG(0,("PAC no srv_key\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!kdc_sig_ptr || !kdc_sig_blob) {
|
||||
DEBUG(0,("PAC no kdc_key\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* Find and zero out the signatures, as required by the signing algorithm */
|
||||
|
||||
/* We find the data blobs above, now we parse them to get at the exact portion we should zero */
|
||||
status = ndr_pull_struct_blob(kdc_sig_blob, kdc_sig_wipe, kdc_sig_wipe,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't parse the KDC signature\n"));
|
||||
return status;
|
||||
}
|
||||
|
||||
status = ndr_pull_struct_blob(srv_sig_blob, srv_sig_wipe, srv_sig_wipe,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't parse the SRV signature\n"));
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Now zero the decoded structure */
|
||||
memset(kdc_sig_wipe->signature.data, '\0', kdc_sig_wipe->signature.length);
|
||||
memset(srv_sig_wipe->signature.data, '\0', srv_sig_wipe->signature.length);
|
||||
|
||||
/* and reencode, back into the same place it came from */
|
||||
status = ndr_push_struct_blob(kdc_sig_blob, pac_data_raw, kdc_sig_wipe,
|
||||
(ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't repack the KDC signature\n"));
|
||||
return status;
|
||||
}
|
||||
status = ndr_push_struct_blob(srv_sig_blob, pac_data_raw, srv_sig_wipe,
|
||||
(ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't repack the SRV signature\n"));
|
||||
return status;
|
||||
}
|
||||
|
||||
/* push out the whole structure, but now with zero'ed signatures */
|
||||
status = ndr_push_struct_blob(&modified_pac_blob, pac_data_raw, pac_data_raw,
|
||||
(ndr_push_flags_fn_t)ndr_push_PAC_DATA_RAW);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
DEBUG(0,("can't repack the RAW PAC\n"));
|
||||
return status;
|
||||
}
|
||||
|
||||
/* verify by service_key */
|
||||
ret = check_pac_checksum(mem_ctx,
|
||||
modified_pac_blob, srv_sig_ptr,
|
||||
context,
|
||||
service_keyblock);
|
||||
if (ret) {
|
||||
DEBUG(1, ("PAC Decode: Failed to verify the service signature: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
if (k5ret) {
|
||||
*k5ret = ret;
|
||||
}
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
if (krbtgt_keyblock) {
|
||||
ret = check_pac_checksum(mem_ctx,
|
||||
srv_sig_ptr->signature, kdc_sig_ptr,
|
||||
context, krbtgt_keyblock);
|
||||
if (ret) {
|
||||
DEBUG(1, ("PAC Decode: Failed to verify the KDC signature: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
if (k5ret) {
|
||||
*k5ret = ret;
|
||||
}
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert to NT time, so as not to loose accuracy in comparison */
|
||||
unix_to_nt_time(&tgs_authtime_nttime, tgs_authtime);
|
||||
|
||||
if (tgs_authtime_nttime != logon_name->logon_time) {
|
||||
DEBUG(2, ("PAC Decode: Logon time mismatch between ticket and PAC!\n"));
|
||||
DEBUG(2, ("PAC Decode: PAC: %s\n", nt_time_string(mem_ctx, logon_name->logon_time)));
|
||||
DEBUG(2, ("PAC Decode: Ticket: %s\n", nt_time_string(mem_ctx, tgs_authtime_nttime)));
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
ret = krb5_parse_name_flags(context, logon_name->account_name, KRB5_PRINCIPAL_PARSE_NO_REALM,
|
||||
&client_principal_pac);
|
||||
if (ret) {
|
||||
DEBUG(2, ("Could not parse name from incoming PAC: [%s]: %s\n",
|
||||
logon_name->account_name,
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
if (k5ret) {
|
||||
*k5ret = ret;
|
||||
}
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!krb5_principal_compare_any_realm(context, client_principal, client_principal_pac)) {
|
||||
DEBUG(2, ("Name in PAC [%s] does not match principal name in ticket\n",
|
||||
logon_name->account_name));
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (strcasecmp(logon_info->info3.base.account_name.string,
|
||||
"Administrator")== 0) {
|
||||
file_save("tmp_pac_data-admin.dat",blob.data,blob.length);
|
||||
}
|
||||
#endif
|
||||
|
||||
DEBUG(3,("Found account name from PAC: %s [%s]\n",
|
||||
logon_info->info3.base.account_name.string,
|
||||
logon_info->info3.base.full_name.string));
|
||||
*pac_data_out = pac_data;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
_PUBLIC_ NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx,
|
||||
struct PAC_LOGON_INFO **logon_info,
|
||||
DATA_BLOB blob,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
krb5_const_principal client_principal,
|
||||
time_t tgs_authtime,
|
||||
krb5_error_code *k5ret)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct PAC_DATA *pac_data;
|
||||
int i;
|
||||
nt_status = kerberos_decode_pac(mem_ctx, &pac_data,
|
||||
blob,
|
||||
context,
|
||||
krbtgt_keyblock,
|
||||
service_keyblock,
|
||||
client_principal,
|
||||
tgs_authtime,
|
||||
k5ret);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
*logon_info = NULL;
|
||||
for (i=0; i < pac_data->num_buffers; i++) {
|
||||
if (pac_data->buffers[i].type != PAC_TYPE_LOGON_INFO) {
|
||||
continue;
|
||||
}
|
||||
*logon_info = pac_data->buffers[i].info->logon_info.info;
|
||||
}
|
||||
if (!*logon_info) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static krb5_error_code make_pac_checksum(TALLOC_CTX *mem_ctx,
|
||||
DATA_BLOB *pac_data,
|
||||
struct PAC_SIGNATURE_DATA *sig,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *keyblock)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_crypto crypto;
|
||||
Checksum cksum;
|
||||
|
||||
|
||||
ret = krb5_crypto_init(context,
|
||||
keyblock,
|
||||
0,
|
||||
&crypto);
|
||||
if (ret) {
|
||||
DEBUG(0,("krb5_crypto_init() failed: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
return ret;
|
||||
}
|
||||
ret = krb5_create_checksum(context,
|
||||
crypto,
|
||||
KRB5_KU_OTHER_CKSUM,
|
||||
0,
|
||||
pac_data->data,
|
||||
pac_data->length,
|
||||
&cksum);
|
||||
if (ret) {
|
||||
DEBUG(2, ("PAC Verification failed: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
}
|
||||
|
||||
krb5_crypto_destroy(context, crypto);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sig->type = cksum.cksumtype;
|
||||
sig->signature = data_blob_talloc(mem_ctx, cksum.checksum.data, cksum.checksum.length);
|
||||
free_Checksum(&cksum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx,
|
||||
struct PAC_DATA *pac_data,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
DATA_BLOB *pac)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
krb5_error_code ret;
|
||||
DATA_BLOB zero_blob = data_blob(NULL, 0);
|
||||
DATA_BLOB tmp_blob = data_blob(NULL, 0);
|
||||
struct PAC_SIGNATURE_DATA *kdc_checksum = NULL;
|
||||
struct PAC_SIGNATURE_DATA *srv_checksum = NULL;
|
||||
int i;
|
||||
|
||||
/* First, just get the keytypes filled in (and lengths right, eventually) */
|
||||
for (i=0; i < pac_data->num_buffers; i++) {
|
||||
if (pac_data->buffers[i].type != PAC_TYPE_KDC_CHECKSUM) {
|
||||
continue;
|
||||
}
|
||||
kdc_checksum = &pac_data->buffers[i].info->kdc_cksum,
|
||||
ret = make_pac_checksum(mem_ctx, &zero_blob,
|
||||
kdc_checksum,
|
||||
context, krbtgt_keyblock);
|
||||
if (ret) {
|
||||
DEBUG(2, ("making krbtgt PAC checksum failed: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
talloc_free(pac_data);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i < pac_data->num_buffers; i++) {
|
||||
if (pac_data->buffers[i].type != PAC_TYPE_SRV_CHECKSUM) {
|
||||
continue;
|
||||
}
|
||||
srv_checksum = &pac_data->buffers[i].info->srv_cksum;
|
||||
ret = make_pac_checksum(mem_ctx, &zero_blob,
|
||||
srv_checksum,
|
||||
context, service_keyblock);
|
||||
if (ret) {
|
||||
DEBUG(2, ("making service PAC checksum failed: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
talloc_free(pac_data);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!kdc_checksum) {
|
||||
DEBUG(2, ("Invalid PAC constructed for signing, no KDC checksum present!"));
|
||||
return EINVAL;
|
||||
}
|
||||
if (!srv_checksum) {
|
||||
DEBUG(2, ("Invalid PAC constructed for signing, no SRV checksum present!"));
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* But wipe out the actual signatures */
|
||||
memset(kdc_checksum->signature.data, '\0', kdc_checksum->signature.length);
|
||||
memset(srv_checksum->signature.data, '\0', srv_checksum->signature.length);
|
||||
|
||||
nt_status = ndr_push_struct_blob(&tmp_blob, mem_ctx, pac_data,
|
||||
(ndr_push_flags_fn_t)ndr_push_PAC_DATA);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(1, ("PAC (presig) push failed: %s\n", nt_errstr(nt_status)));
|
||||
talloc_free(pac_data);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* Then sign the result of the previous push, where the sig was zero'ed out */
|
||||
ret = make_pac_checksum(mem_ctx, &tmp_blob, srv_checksum,
|
||||
context, service_keyblock);
|
||||
|
||||
/* Then sign Server checksum */
|
||||
ret = make_pac_checksum(mem_ctx, &srv_checksum->signature, kdc_checksum, context, krbtgt_keyblock);
|
||||
if (ret) {
|
||||
DEBUG(2, ("making krbtgt PAC checksum failed: %s\n",
|
||||
smb_get_krb5_error_message(context, ret, mem_ctx)));
|
||||
talloc_free(pac_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* And push it out again, this time to the world. This relies on determanistic pointer values */
|
||||
nt_status = ndr_push_struct_blob(&tmp_blob, mem_ctx, pac_data,
|
||||
(ndr_push_flags_fn_t)ndr_push_PAC_DATA);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(1, ("PAC (final) push failed: %s\n", nt_errstr(nt_status)));
|
||||
talloc_free(pac_data);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
*pac = tmp_blob;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx,
|
||||
struct auth_serversupplied_info *server_info,
|
||||
krb5_context context,
|
||||
const krb5_keyblock *krbtgt_keyblock,
|
||||
const krb5_keyblock *service_keyblock,
|
||||
krb5_principal client_principal,
|
||||
time_t tgs_authtime,
|
||||
DATA_BLOB *pac)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
krb5_error_code ret;
|
||||
struct PAC_DATA *pac_data = talloc(mem_ctx, struct PAC_DATA);
|
||||
struct netr_SamInfo3 *sam3;
|
||||
union PAC_INFO *u_LOGON_INFO;
|
||||
struct PAC_LOGON_INFO *LOGON_INFO;
|
||||
union PAC_INFO *u_LOGON_NAME;
|
||||
struct PAC_LOGON_NAME *LOGON_NAME;
|
||||
union PAC_INFO *u_KDC_CHECKSUM;
|
||||
union PAC_INFO *u_SRV_CHECKSUM;
|
||||
|
||||
char *name;
|
||||
|
||||
enum {
|
||||
PAC_BUF_LOGON_INFO = 0,
|
||||
PAC_BUF_LOGON_NAME = 1,
|
||||
PAC_BUF_SRV_CHECKSUM = 2,
|
||||
PAC_BUF_KDC_CHECKSUM = 3,
|
||||
PAC_BUF_NUM_BUFFERS = 4
|
||||
};
|
||||
|
||||
if (!pac_data) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
pac_data->num_buffers = PAC_BUF_NUM_BUFFERS;
|
||||
pac_data->version = 0;
|
||||
|
||||
pac_data->buffers = talloc_array(pac_data,
|
||||
struct PAC_BUFFER,
|
||||
pac_data->num_buffers);
|
||||
if (!pac_data->buffers) {
|
||||
talloc_free(pac_data);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
/* LOGON_INFO */
|
||||
u_LOGON_INFO = talloc_zero(pac_data->buffers, union PAC_INFO);
|
||||
if (!u_LOGON_INFO) {
|
||||
talloc_free(pac_data);
|
||||
return ENOMEM;
|
||||
}
|
||||
pac_data->buffers[PAC_BUF_LOGON_INFO].type = PAC_TYPE_LOGON_INFO;
|
||||
pac_data->buffers[PAC_BUF_LOGON_INFO].info = u_LOGON_INFO;
|
||||
|
||||
/* LOGON_NAME */
|
||||
u_LOGON_NAME = talloc_zero(pac_data->buffers, union PAC_INFO);
|
||||
if (!u_LOGON_NAME) {
|
||||
talloc_free(pac_data);
|
||||
return ENOMEM;
|
||||
}
|
||||
pac_data->buffers[PAC_BUF_LOGON_NAME].type = PAC_TYPE_LOGON_NAME;
|
||||
pac_data->buffers[PAC_BUF_LOGON_NAME].info = u_LOGON_NAME;
|
||||
LOGON_NAME = &u_LOGON_NAME->logon_name;
|
||||
|
||||
/* SRV_CHECKSUM */
|
||||
u_SRV_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO);
|
||||
if (!u_SRV_CHECKSUM) {
|
||||
talloc_free(pac_data);
|
||||
return ENOMEM;
|
||||
}
|
||||
pac_data->buffers[PAC_BUF_SRV_CHECKSUM].type = PAC_TYPE_SRV_CHECKSUM;
|
||||
pac_data->buffers[PAC_BUF_SRV_CHECKSUM].info = u_SRV_CHECKSUM;
|
||||
|
||||
/* KDC_CHECKSUM */
|
||||
u_KDC_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO);
|
||||
if (!u_KDC_CHECKSUM) {
|
||||
talloc_free(pac_data);
|
||||
return ENOMEM;
|
||||
}
|
||||
pac_data->buffers[PAC_BUF_KDC_CHECKSUM].type = PAC_TYPE_KDC_CHECKSUM;
|
||||
pac_data->buffers[PAC_BUF_KDC_CHECKSUM].info = u_KDC_CHECKSUM;
|
||||
|
||||
/* now the real work begins... */
|
||||
|
||||
LOGON_INFO = talloc_zero(u_LOGON_INFO, struct PAC_LOGON_INFO);
|
||||
if (!LOGON_INFO) {
|
||||
talloc_free(pac_data);
|
||||
return ENOMEM;
|
||||
}
|
||||
nt_status = auth_convert_server_info_saminfo3(LOGON_INFO, server_info, &sam3);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(1, ("Getting Samba info failed: %s\n", nt_errstr(nt_status)));
|
||||
talloc_free(pac_data);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
u_LOGON_INFO->logon_info.info = LOGON_INFO;
|
||||
LOGON_INFO->info3 = *sam3;
|
||||
|
||||
ret = krb5_unparse_name_flags(context, client_principal,
|
||||
KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
LOGON_NAME->account_name = talloc_strdup(LOGON_NAME, name);
|
||||
free(name);
|
||||
/*
|
||||
this logon_time field is absolutely critical. This is what
|
||||
caused all our PAC troubles :-)
|
||||
*/
|
||||
unix_to_nt_time(&LOGON_NAME->logon_time, tgs_authtime);
|
||||
|
||||
ret = kerberos_encode_pac(mem_ctx,
|
||||
pac_data,
|
||||
context,
|
||||
krbtgt_keyblock,
|
||||
service_keyblock,
|
||||
pac);
|
||||
talloc_free(pac_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Kerberos utility functions for GENSEC
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/credentials/credentials_krb5.h"
|
||||
|
||||
struct principal_container {
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
krb5_principal principal;
|
||||
};
|
||||
|
||||
static int free_principal(struct principal_container *pc)
|
||||
{
|
||||
/* current heimdal - 0.6.3, which we need anyway, fixes segfaults here */
|
||||
krb5_free_principal(pc->smb_krb5_context->krb5_context, pc->principal);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code salt_principal_from_credentials(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *machine_account,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_principal *salt_princ)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
char *machine_username;
|
||||
char *salt_body;
|
||||
char *lower_realm;
|
||||
const char *salt_principal;
|
||||
struct principal_container *mem_ctx = talloc(parent_ctx, struct principal_container);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
salt_principal = cli_credentials_get_salt_principal(machine_account);
|
||||
if (salt_principal) {
|
||||
ret = krb5_parse_name(smb_krb5_context->krb5_context, salt_principal, salt_princ);
|
||||
} else {
|
||||
machine_username = talloc_strdup(mem_ctx, cli_credentials_get_username(machine_account));
|
||||
|
||||
if (!machine_username) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
if (machine_username[strlen(machine_username)-1] == '$') {
|
||||
machine_username[strlen(machine_username)-1] = '\0';
|
||||
}
|
||||
lower_realm = strlower_talloc(mem_ctx, cli_credentials_get_realm(machine_account));
|
||||
if (!lower_realm) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
salt_body = talloc_asprintf(mem_ctx, "%s.%s", machine_username,
|
||||
lower_realm);
|
||||
if (!salt_body) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = krb5_make_principal(smb_krb5_context->krb5_context, salt_princ,
|
||||
cli_credentials_get_realm(machine_account),
|
||||
"host", salt_body, NULL);
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
/* This song-and-dance effectivly puts the principal
|
||||
* into talloc, so we can't loose it. */
|
||||
mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, smb_krb5_context);
|
||||
mem_ctx->principal = *salt_princ;
|
||||
talloc_set_destructor(mem_ctx, free_principal);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Obtain the principal set on this context. Requires a
|
||||
* smb_krb5_context because we are doing krb5 principal parsing with
|
||||
* the library routines. The returned princ is placed in the talloc
|
||||
* system by means of a destructor (do *not* free). */
|
||||
|
||||
krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *credentials,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_principal *princ)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
const char *princ_string;
|
||||
struct principal_container *mem_ctx = talloc(parent_ctx, struct principal_container);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
princ_string = cli_credentials_get_principal(credentials, mem_ctx);
|
||||
|
||||
/* A NULL here has meaning, as the gssapi server case will
|
||||
* then use the principal from the client */
|
||||
if (!princ_string) {
|
||||
talloc_free(mem_ctx);
|
||||
princ = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = krb5_parse_name(smb_krb5_context->krb5_context,
|
||||
princ_string, princ);
|
||||
|
||||
if (ret == 0) {
|
||||
/* This song-and-dance effectivly puts the principal
|
||||
* into talloc, so we can't loose it. */
|
||||
mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, smb_krb5_context);
|
||||
mem_ctx->principal = *princ;
|
||||
talloc_set_destructor(mem_ctx, free_principal);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a freshly allocated ccache (destroyed by destructor on child
|
||||
* of parent_ctx), for a given set of client credentials
|
||||
*/
|
||||
|
||||
krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *credentials,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_ccache ccache)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
const char *password;
|
||||
time_t kdc_time = 0;
|
||||
krb5_principal princ;
|
||||
int tries;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ);
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
password = cli_credentials_get_password(credentials);
|
||||
|
||||
tries = 2;
|
||||
while (tries--) {
|
||||
if (password) {
|
||||
ret = kerberos_kinit_password_cc(smb_krb5_context->krb5_context, ccache,
|
||||
princ,
|
||||
password, NULL, &kdc_time);
|
||||
} else {
|
||||
/* No password available, try to use a keyblock instead */
|
||||
|
||||
krb5_keyblock keyblock;
|
||||
const struct samr_Password *mach_pwd;
|
||||
mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx);
|
||||
if (!mach_pwd) {
|
||||
talloc_free(mem_ctx);
|
||||
DEBUG(1, ("kinit_to_ccache: No password available for kinit\n"));
|
||||
return EINVAL;
|
||||
}
|
||||
ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
|
||||
ETYPE_ARCFOUR_HMAC_MD5,
|
||||
mach_pwd->hash, sizeof(mach_pwd->hash),
|
||||
&keyblock);
|
||||
|
||||
if (ret == 0) {
|
||||
ret = kerberos_kinit_keyblock_cc(smb_krb5_context->krb5_context, ccache,
|
||||
princ,
|
||||
&keyblock, NULL, &kdc_time);
|
||||
krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) {
|
||||
/* Perhaps we have been given an invalid skew, so try again without it */
|
||||
time_t t = time(NULL);
|
||||
krb5_set_real_time(smb_krb5_context->krb5_context, t, 0);
|
||||
} else {
|
||||
/* not a skew problem */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) {
|
||||
DEBUG(1,("kinit for %s failed (%s)\n",
|
||||
cli_credentials_get_principal(credentials, mem_ctx),
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* cope with ticket being in the future due to clock skew */
|
||||
if ((unsigned)kdc_time > time(NULL)) {
|
||||
time_t t = time(NULL);
|
||||
int time_offset =(unsigned)kdc_time-t;
|
||||
DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset));
|
||||
krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0);
|
||||
}
|
||||
|
||||
if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) {
|
||||
ret = kinit_to_ccache(parent_ctx,
|
||||
credentials,
|
||||
smb_krb5_context,
|
||||
ccache);
|
||||
}
|
||||
if (ret) {
|
||||
DEBUG(1,("kinit for %s failed (%s)\n",
|
||||
cli_credentials_get_principal(credentials, mem_ctx),
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int free_keytab(struct keytab_container *ktc)
|
||||
{
|
||||
krb5_kt_close(ktc->smb_krb5_context->krb5_context, ktc->keytab);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int smb_krb5_open_keytab(TALLOC_CTX *mem_ctx,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
const char *keytab_name, struct keytab_container **ktc)
|
||||
{
|
||||
krb5_keytab keytab;
|
||||
int ret;
|
||||
ret = krb5_kt_resolve(smb_krb5_context->krb5_context, keytab_name, &keytab);
|
||||
if (ret) {
|
||||
DEBUG(1,("failed to open krb5 keytab: %s\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
*ktc = talloc(mem_ctx, struct keytab_container);
|
||||
if (!*ktc) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
(*ktc)->smb_krb5_context = talloc_reference(*ktc, smb_krb5_context);
|
||||
(*ktc)->keytab = keytab;
|
||||
talloc_set_destructor(*ktc, free_keytab);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct enctypes_container {
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
krb5_enctype *enctypes;
|
||||
};
|
||||
|
||||
static int free_enctypes(struct enctypes_container *etc)
|
||||
{
|
||||
free_kerberos_etypes(etc->smb_krb5_context->krb5_context, etc->enctypes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code keytab_add_keys(TALLOC_CTX *parent_ctx,
|
||||
const char *princ_string,
|
||||
krb5_principal princ,
|
||||
krb5_principal salt_princ,
|
||||
int kvno,
|
||||
const char *password_s,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_keytab keytab)
|
||||
{
|
||||
int i;
|
||||
krb5_error_code ret;
|
||||
krb5_enctype *enctypes;
|
||||
char *enctype_string;
|
||||
struct enctypes_container *etc;
|
||||
krb5_data password;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
etc = talloc(mem_ctx, struct enctypes_container);
|
||||
if (!etc) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
ret = get_kerberos_allowed_etypes(smb_krb5_context->krb5_context,
|
||||
&enctypes);
|
||||
if (ret != 0) {
|
||||
DEBUG(1,("keytab_add_keys: getting encrption types failed (%s)\n",
|
||||
error_message(ret)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
etc->smb_krb5_context = talloc_reference(etc, smb_krb5_context);
|
||||
etc->enctypes = enctypes;
|
||||
|
||||
talloc_set_destructor(etc, free_enctypes);
|
||||
|
||||
password.data = discard_const_p(char *, password_s);
|
||||
password.length = strlen(password_s);
|
||||
|
||||
for (i=0; enctypes[i]; i++) {
|
||||
krb5_keytab_entry entry;
|
||||
ret = create_kerberos_key_from_string(smb_krb5_context->krb5_context,
|
||||
salt_princ, &password, &entry.keyblock, enctypes[i]);
|
||||
if (ret != 0) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
entry.principal = princ;
|
||||
entry.vno = kvno;
|
||||
ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry);
|
||||
enctype_string = NULL;
|
||||
krb5_enctype_to_string(smb_krb5_context->krb5_context, enctypes[i], &enctype_string);
|
||||
if (ret != 0) {
|
||||
DEBUG(1, ("Failed to add %s entry for %s(kvno %d) to keytab: %s\n",
|
||||
enctype_string,
|
||||
princ_string,
|
||||
kvno,
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
free(enctype_string);
|
||||
krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEBUG(5, ("Added %s(kvno %d) to keytab (%s)\n",
|
||||
princ_string, kvno,
|
||||
enctype_string));
|
||||
free(enctype_string);
|
||||
|
||||
krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int create_keytab(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *machine_account,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_keytab keytab,
|
||||
BOOL add_old)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
const char *password_s;
|
||||
char *enctype_string;
|
||||
const char *old_secret;
|
||||
int kvno;
|
||||
krb5_principal salt_princ;
|
||||
krb5_principal princ;
|
||||
const char *princ_string;
|
||||
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
princ_string = cli_credentials_get_principal(machine_account, mem_ctx);
|
||||
/* Get the principal we will store the new keytab entries under */
|
||||
ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ);
|
||||
if (ret) {
|
||||
DEBUG(1,("create_keytab: makeing krb5 principal failed (%s)\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* The salt used to generate these entries may be different however, fetch that */
|
||||
ret = salt_principal_from_credentials(mem_ctx, machine_account,
|
||||
smb_krb5_context,
|
||||
&salt_princ);
|
||||
if (ret) {
|
||||
DEBUG(1,("create_keytab: makeing salt principal failed (%s)\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Finally, do the dance to get the password to put in the entry */
|
||||
password_s = cli_credentials_get_password(machine_account);
|
||||
if (!password_s) {
|
||||
/* If we don't have the plaintext password, try for
|
||||
* the MD4 password hash */
|
||||
|
||||
krb5_keytab_entry entry;
|
||||
const struct samr_Password *mach_pwd;
|
||||
mach_pwd = cli_credentials_get_nt_hash(machine_account, mem_ctx);
|
||||
if (!mach_pwd) {
|
||||
DEBUG(1, ("create_keytab: Domain trust informaton for account %s not available\n",
|
||||
cli_credentials_get_principal(machine_account, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return EINVAL;
|
||||
}
|
||||
ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
|
||||
ETYPE_ARCFOUR_HMAC_MD5,
|
||||
mach_pwd->hash, sizeof(mach_pwd->hash),
|
||||
&entry.keyblock);
|
||||
if (ret) {
|
||||
DEBUG(1, ("create_keytab: krb5_keyblock_init failed: %s\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
entry.principal = princ;
|
||||
entry.vno = cli_credentials_get_kvno(machine_account);
|
||||
ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry);
|
||||
if (ret) {
|
||||
DEBUG(1, ("Failed to add ARCFOUR_HMAC (only) entry for %s to keytab: %s",
|
||||
cli_credentials_get_principal(machine_account, mem_ctx),
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
krb5_enctype_to_string(smb_krb5_context->krb5_context,
|
||||
ETYPE_ARCFOUR_HMAC_MD5,
|
||||
&enctype_string);
|
||||
DEBUG(5, ("Added %s(kvno %d) to keytab (%s)\n",
|
||||
cli_credentials_get_principal(machine_account, mem_ctx),
|
||||
cli_credentials_get_kvno(machine_account),
|
||||
enctype_string));
|
||||
free(enctype_string);
|
||||
|
||||
krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock);
|
||||
|
||||
/* Can't go any further, we only have this one key */
|
||||
talloc_free(mem_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
kvno = cli_credentials_get_kvno(machine_account);
|
||||
/* good, we actually have the real plaintext */
|
||||
ret = keytab_add_keys(mem_ctx, princ_string, princ, salt_princ,
|
||||
kvno, password_s, smb_krb5_context, keytab);
|
||||
if (!ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!add_old || kvno == 0) {
|
||||
talloc_free(mem_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
old_secret = cli_credentials_get_old_password(machine_account);
|
||||
if (!old_secret) {
|
||||
talloc_free(mem_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = keytab_add_keys(mem_ctx, princ_string, princ, salt_princ,
|
||||
kvno - 1, old_secret, smb_krb5_context, keytab);
|
||||
if (!ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Walk the keytab, looking for entries of this principal name, with KVNO other than current kvno -1.
|
||||
*
|
||||
* These entries are now stale, we only keep the current, and previous entries around.
|
||||
*
|
||||
* Inspired by the code in Samba3 for 'use kerberos keytab'.
|
||||
*
|
||||
*/
|
||||
|
||||
static krb5_error_code remove_old_entries(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *machine_account,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
krb5_keytab keytab, BOOL *found_previous)
|
||||
{
|
||||
krb5_error_code ret, ret2;
|
||||
krb5_kt_cursor cursor;
|
||||
krb5_principal princ;
|
||||
int kvno;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
const char *princ_string;
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
*found_previous = False;
|
||||
princ_string = cli_credentials_get_principal(machine_account, mem_ctx);
|
||||
|
||||
/* Get the principal we will store the new keytab entries under */
|
||||
ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ);
|
||||
if (ret) {
|
||||
DEBUG(1,("update_keytab: makeing krb5 principal failed (%s)\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
kvno = cli_credentials_get_kvno(machine_account);
|
||||
|
||||
/* for each entry in the keytab */
|
||||
ret = krb5_kt_start_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
|
||||
switch (ret) {
|
||||
case 0:
|
||||
break;
|
||||
case HEIM_ERR_OPNOTSUPP:
|
||||
case ENOENT:
|
||||
case KRB5_KT_END:
|
||||
/* no point enumerating if there isn't anything here */
|
||||
talloc_free(mem_ctx);
|
||||
return 0;
|
||||
default:
|
||||
DEBUG(1,("failed to open keytab for read of old entries: %s\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
while (!ret) {
|
||||
krb5_keytab_entry entry;
|
||||
ret = krb5_kt_next_entry(smb_krb5_context->krb5_context, keytab, &entry, &cursor);
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
/* if it matches our principal */
|
||||
if (!krb5_kt_compare(smb_krb5_context->krb5_context, &entry, princ, 0, 0)) {
|
||||
/* Free the entry, it wasn't the one we were looking for anyway */
|
||||
krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* delete it, if it is not kvno -1 */
|
||||
if (entry.vno != (kvno - 1 )) {
|
||||
/* Release the enumeration. We are going to
|
||||
* have to start this from the top again,
|
||||
* because deletes during enumeration may not
|
||||
* always be consistant.
|
||||
*
|
||||
* Also, the enumeration locks a FILE: keytab
|
||||
*/
|
||||
|
||||
krb5_kt_end_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
|
||||
|
||||
ret = krb5_kt_remove_entry(smb_krb5_context->krb5_context, keytab, &entry);
|
||||
krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
|
||||
|
||||
/* Deleted: Restart from the top */
|
||||
ret2 = krb5_kt_start_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
|
||||
if (ret2) {
|
||||
krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
|
||||
DEBUG(1,("failed to restart enumeration of keytab: %s\n",
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return ret2;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
*found_previous = True;
|
||||
}
|
||||
|
||||
/* Free the entry, we don't need it any more */
|
||||
krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry);
|
||||
|
||||
|
||||
}
|
||||
krb5_kt_end_seq_get(smb_krb5_context->krb5_context, keytab, &cursor);
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
break;
|
||||
case ENOENT:
|
||||
case KRB5_KT_END:
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
DEBUG(1,("failed in deleting old entries for principal: %s: %s\n",
|
||||
princ_string,
|
||||
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int smb_krb5_update_keytab(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *machine_account,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
struct keytab_container *keytab_container)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
BOOL found_previous;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = remove_old_entries(mem_ctx, machine_account,
|
||||
smb_krb5_context, keytab_container->keytab, &found_previous);
|
||||
if (ret != 0) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Create a new keytab. If during the cleanout we found
|
||||
* entires for kvno -1, then don't try and duplicate them.
|
||||
* Otherwise, add kvno, and kvno -1 */
|
||||
|
||||
ret = create_keytab(mem_ctx, machine_account, smb_krb5_context,
|
||||
keytab_container->keytab,
|
||||
found_previous ? False : True);
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
_PUBLIC_ int smb_krb5_create_memory_keytab(TALLOC_CTX *parent_ctx,
|
||||
struct cli_credentials *machine_account,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
struct keytab_container **keytab_container)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
|
||||
const char *rand_string;
|
||||
const char *keytab_name;
|
||||
if (!mem_ctx) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
*keytab_container = talloc(mem_ctx, struct keytab_container);
|
||||
|
||||
rand_string = generate_random_str(mem_ctx, 16);
|
||||
if (!rand_string) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s",
|
||||
rand_string);
|
||||
if (!keytab_name) {
|
||||
talloc_free(mem_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = smb_krb5_open_keytab(mem_ctx, smb_krb5_context, keytab_name, keytab_container);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = smb_krb5_update_keytab(mem_ctx, machine_account, smb_krb5_context, *keytab_container);
|
||||
if (ret == 0) {
|
||||
talloc_steal(parent_ctx, *keytab_container);
|
||||
} else {
|
||||
*keytab_container = NULL;
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Wrapper for krb5_init_context
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
Copyright (C) Andrew Tridgell 2005
|
||||
Copyright (C) Stefan Metzmacher 2004
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "heimdal/lib/krb5/krb5_locl.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
#include "lib/socket/socket.h"
|
||||
#include "system/network.h"
|
||||
#include "lib/events/events.h"
|
||||
#include "roken.h"
|
||||
|
||||
/*
|
||||
context structure for operations on cldap packets
|
||||
*/
|
||||
struct smb_krb5_socket {
|
||||
struct socket_context *sock;
|
||||
|
||||
/* the fd event */
|
||||
struct fd_event *fde;
|
||||
|
||||
BOOL timeout;
|
||||
NTSTATUS status;
|
||||
DATA_BLOB request, reply, partial;
|
||||
|
||||
size_t partial_read;
|
||||
|
||||
krb5_krbhst_info *hi;
|
||||
};
|
||||
|
||||
static int smb_krb5_context_destroy_1(struct smb_krb5_context *ctx)
|
||||
{
|
||||
krb5_free_context(ctx->krb5_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smb_krb5_context_destroy_2(struct smb_krb5_context *ctx)
|
||||
{
|
||||
/* Otherwise krb5_free_context will try and close what we have already free()ed */
|
||||
krb5_set_warn_dest(ctx->krb5_context, NULL);
|
||||
krb5_closelog(ctx->krb5_context, ctx->logf);
|
||||
smb_krb5_context_destroy_1(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We never close down the DEBUG system, and no need to unreference the use */
|
||||
static void smb_krb5_debug_close(void *private) {
|
||||
return;
|
||||
}
|
||||
|
||||
static void smb_krb5_debug_wrapper(const char *timestr, const char *msg, void *private)
|
||||
{
|
||||
DEBUG(2, ("Kerberos: %s\n", msg));
|
||||
}
|
||||
|
||||
/*
|
||||
handle recv events on a smb_krb5 socket
|
||||
*/
|
||||
static void smb_krb5_socket_recv(struct smb_krb5_socket *smb_krb5)
|
||||
{
|
||||
TALLOC_CTX *tmp_ctx = talloc_new(smb_krb5);
|
||||
DATA_BLOB blob;
|
||||
size_t nread, dsize;
|
||||
|
||||
switch (smb_krb5->hi->proto) {
|
||||
case KRB5_KRBHST_UDP:
|
||||
smb_krb5->status = socket_pending(smb_krb5->sock, &dsize);
|
||||
if (!NT_STATUS_IS_OK(smb_krb5->status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
blob = data_blob_talloc(tmp_ctx, NULL, dsize);
|
||||
if (blob.data == NULL && dsize != 0) {
|
||||
smb_krb5->status = NT_STATUS_NO_MEMORY;
|
||||
talloc_free(tmp_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
smb_krb5->status = socket_recv(smb_krb5->sock, blob.data, blob.length, &nread);
|
||||
if (!NT_STATUS_IS_OK(smb_krb5->status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return;
|
||||
}
|
||||
blob.length = nread;
|
||||
|
||||
if (nread == 0) {
|
||||
smb_krb5->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
|
||||
talloc_free(tmp_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG(2,("Received smb_krb5 packet of length %d\n",
|
||||
(int)blob.length));
|
||||
|
||||
talloc_steal(smb_krb5, blob.data);
|
||||
smb_krb5->reply = blob;
|
||||
talloc_free(tmp_ctx);
|
||||
break;
|
||||
case KRB5_KRBHST_TCP:
|
||||
if (smb_krb5->partial.length == 0) {
|
||||
smb_krb5->partial = data_blob_talloc(smb_krb5, NULL, 4);
|
||||
if (!smb_krb5->partial.data) {
|
||||
smb_krb5->status = NT_STATUS_NO_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
smb_krb5->partial_read = 0;
|
||||
}
|
||||
|
||||
/* read in the packet length */
|
||||
if (smb_krb5->partial_read < 4) {
|
||||
uint32_t packet_length;
|
||||
|
||||
smb_krb5->status = socket_recv(smb_krb5->sock,
|
||||
smb_krb5->partial.data + smb_krb5->partial_read,
|
||||
4 - smb_krb5->partial_read,
|
||||
&nread);
|
||||
/* todo: this should be converted to the packet_*() routines */
|
||||
if (!NT_STATUS_IS_OK(smb_krb5->status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
smb_krb5->partial_read += nread;
|
||||
if (smb_krb5->partial_read != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
packet_length = RIVAL(smb_krb5->partial.data, 0);
|
||||
|
||||
smb_krb5->partial.data = talloc_realloc(smb_krb5, smb_krb5->partial.data,
|
||||
uint8_t, packet_length + 4);
|
||||
if (!smb_krb5->partial.data) {
|
||||
smb_krb5->status = NT_STATUS_NO_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
smb_krb5->partial.length = packet_length + 4;
|
||||
}
|
||||
|
||||
/* read in the body */
|
||||
smb_krb5->status = socket_recv(smb_krb5->sock,
|
||||
smb_krb5->partial.data + smb_krb5->partial_read,
|
||||
smb_krb5->partial.length - smb_krb5->partial_read,
|
||||
&nread);
|
||||
if (!NT_STATUS_IS_OK(smb_krb5->status)) return;
|
||||
|
||||
smb_krb5->partial_read += nread;
|
||||
|
||||
if (smb_krb5->partial_read != smb_krb5->partial.length) return;
|
||||
|
||||
smb_krb5->reply = data_blob_talloc(smb_krb5, smb_krb5->partial.data + 4, smb_krb5->partial.length - 4);
|
||||
break;
|
||||
case KRB5_KRBHST_HTTP:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
handle request timeouts
|
||||
*/
|
||||
static void smb_krb5_request_timeout(struct event_context *event_ctx,
|
||||
struct timed_event *te, struct timeval t,
|
||||
void *private)
|
||||
{
|
||||
struct smb_krb5_socket *smb_krb5 = talloc_get_type(private, struct smb_krb5_socket);
|
||||
DEBUG(5,("Timed out smb_krb5 packet\n"));
|
||||
smb_krb5->timeout = True;
|
||||
}
|
||||
|
||||
/*
|
||||
handle send events on a smb_krb5 socket
|
||||
*/
|
||||
static void smb_krb5_socket_send(struct smb_krb5_socket *smb_krb5)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
size_t len;
|
||||
|
||||
len = smb_krb5->request.length;
|
||||
status = socket_send(smb_krb5->sock, &smb_krb5->request, &len);
|
||||
|
||||
if (!NT_STATUS_IS_OK(status)) return;
|
||||
|
||||
EVENT_FD_READABLE(smb_krb5->fde);
|
||||
|
||||
EVENT_FD_NOT_WRITEABLE(smb_krb5->fde);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
handle fd events on a smb_krb5_socket
|
||||
*/
|
||||
static void smb_krb5_socket_handler(struct event_context *ev, struct fd_event *fde,
|
||||
uint16_t flags, void *private)
|
||||
{
|
||||
struct smb_krb5_socket *smb_krb5 = talloc_get_type(private, struct smb_krb5_socket);
|
||||
if (flags & EVENT_FD_WRITE) {
|
||||
smb_krb5_socket_send(smb_krb5);
|
||||
}
|
||||
if (flags & EVENT_FD_READ) {
|
||||
smb_krb5_socket_recv(smb_krb5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
krb5_error_code smb_krb5_send_and_recv_func(krb5_context context,
|
||||
void *data,
|
||||
krb5_krbhst_info *hi,
|
||||
const krb5_data *send_buf,
|
||||
krb5_data *recv_buf)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
NTSTATUS status;
|
||||
struct socket_address *remote_addr;
|
||||
const char *name;
|
||||
struct addrinfo *ai, *a;
|
||||
struct smb_krb5_socket *smb_krb5;
|
||||
|
||||
struct event_context *ev = talloc_get_type(data, struct event_context);
|
||||
|
||||
DATA_BLOB send_blob = data_blob_const(send_buf->data, send_buf->length);
|
||||
|
||||
ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (a = ai; a; a = ai->ai_next) {
|
||||
smb_krb5 = talloc(NULL, struct smb_krb5_socket);
|
||||
if (!smb_krb5) {
|
||||
return ENOMEM;
|
||||
}
|
||||
smb_krb5->hi = hi;
|
||||
|
||||
switch (a->ai_family) {
|
||||
case PF_INET:
|
||||
name = "ipv4";
|
||||
break;
|
||||
#ifdef HAVE_SOCKET_IPV6
|
||||
case PF_INET6:
|
||||
name = "ipv6";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
talloc_free(smb_krb5);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
status = NT_STATUS_INVALID_PARAMETER;
|
||||
switch (hi->proto) {
|
||||
case KRB5_KRBHST_UDP:
|
||||
if (lp_parm_bool(-1, "krb5", "udp", True)) {
|
||||
status = socket_create(name, SOCKET_TYPE_DGRAM, &smb_krb5->sock, 0);
|
||||
}
|
||||
break;
|
||||
case KRB5_KRBHST_TCP:
|
||||
if (lp_parm_bool(-1, "krb5", "tcp", True)) {
|
||||
status = socket_create(name, SOCKET_TYPE_STREAM, &smb_krb5->sock, 0);
|
||||
}
|
||||
break;
|
||||
case KRB5_KRBHST_HTTP:
|
||||
talloc_free(smb_krb5);
|
||||
return EINVAL;
|
||||
}
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(smb_krb5);
|
||||
continue;
|
||||
}
|
||||
|
||||
talloc_steal(smb_krb5, smb_krb5->sock);
|
||||
|
||||
remote_addr = socket_address_from_sockaddr(smb_krb5, a->ai_addr, a->ai_addrlen);
|
||||
if (!remote_addr) {
|
||||
talloc_free(smb_krb5);
|
||||
continue;
|
||||
}
|
||||
|
||||
status = socket_connect_ev(smb_krb5->sock, NULL, remote_addr, 0, ev);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(smb_krb5);
|
||||
continue;
|
||||
}
|
||||
talloc_free(remote_addr);
|
||||
|
||||
smb_krb5->fde = event_add_fd(ev, smb_krb5,
|
||||
socket_get_fd(smb_krb5->sock), 0,
|
||||
smb_krb5_socket_handler, smb_krb5);
|
||||
|
||||
event_add_timed(ev, smb_krb5,
|
||||
timeval_current_ofs(context->kdc_timeout, 0),
|
||||
smb_krb5_request_timeout, smb_krb5);
|
||||
|
||||
EVENT_FD_WRITEABLE(smb_krb5->fde);
|
||||
|
||||
switch (hi->proto) {
|
||||
case KRB5_KRBHST_UDP:
|
||||
smb_krb5->request = send_blob;
|
||||
break;
|
||||
case KRB5_KRBHST_TCP:
|
||||
smb_krb5->request = data_blob_talloc(smb_krb5, NULL, send_blob.length + 4);
|
||||
RSIVAL(smb_krb5->request.data, 0, send_blob.length);
|
||||
memcpy(smb_krb5->request.data+4, send_blob.data, send_blob.length);
|
||||
break;
|
||||
case KRB5_KRBHST_HTTP:
|
||||
talloc_free(smb_krb5);
|
||||
return EINVAL;
|
||||
}
|
||||
smb_krb5->timeout = False;
|
||||
smb_krb5->status = NT_STATUS_OK;
|
||||
smb_krb5->reply = data_blob(NULL, 0);
|
||||
smb_krb5->partial = data_blob(NULL, 0);
|
||||
|
||||
while (!smb_krb5->timeout && (NT_STATUS_IS_OK(smb_krb5->status)) && !smb_krb5->reply.length) {
|
||||
if (event_loop_once(ev) != 0) {
|
||||
talloc_free(smb_krb5);
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
if (!NT_STATUS_IS_OK(smb_krb5->status)) {
|
||||
DEBUG(2,("Error reading smb_krb5 reply packet: %s\n", nt_errstr(smb_krb5->status)));
|
||||
talloc_free(smb_krb5);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (smb_krb5->timeout) {
|
||||
talloc_free(smb_krb5);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = krb5_data_copy(recv_buf, smb_krb5->reply.data, smb_krb5->reply.length);
|
||||
if (ret) {
|
||||
talloc_free(smb_krb5);
|
||||
return ret;
|
||||
}
|
||||
talloc_free(smb_krb5);
|
||||
|
||||
break;
|
||||
}
|
||||
if (a) {
|
||||
return 0;
|
||||
}
|
||||
return KRB5_KDC_UNREACH;
|
||||
}
|
||||
|
||||
krb5_error_code smb_krb5_init_context(void *parent_ctx,
|
||||
struct smb_krb5_context **smb_krb5_context)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
TALLOC_CTX *tmp_ctx;
|
||||
struct event_context *ev;
|
||||
|
||||
initialize_krb5_error_table();
|
||||
|
||||
tmp_ctx = talloc_new(parent_ctx);
|
||||
*smb_krb5_context = talloc(tmp_ctx, struct smb_krb5_context);
|
||||
|
||||
if (!*smb_krb5_context || !tmp_ctx) {
|
||||
talloc_free(*smb_krb5_context);
|
||||
talloc_free(tmp_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ret = krb5_init_context(&(*smb_krb5_context)->krb5_context);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_init_context failed (%s)\n",
|
||||
error_message(ret)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy_1);
|
||||
|
||||
if (lp_realm() && *lp_realm()) {
|
||||
char *upper_realm = strupper_talloc(tmp_ctx, lp_realm());
|
||||
if (!upper_realm) {
|
||||
DEBUG(1,("gensec_krb5_start: could not uppercase realm: %s\n", lp_realm()));
|
||||
talloc_free(tmp_ctx);
|
||||
return ENOMEM;
|
||||
}
|
||||
ret = krb5_set_default_realm((*smb_krb5_context)->krb5_context, upper_realm);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_set_default_realm failed (%s)\n",
|
||||
smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx)));
|
||||
talloc_free(tmp_ctx);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Should we have a different name here? */
|
||||
ret = krb5_initlog((*smb_krb5_context)->krb5_context, "Samba", &(*smb_krb5_context)->logf);
|
||||
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_initlog failed (%s)\n",
|
||||
smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx)));
|
||||
talloc_free(tmp_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy_2);
|
||||
|
||||
ret = krb5_addlog_func((*smb_krb5_context)->krb5_context, (*smb_krb5_context)->logf, 0 /* min */, -1 /* max */,
|
||||
smb_krb5_debug_wrapper, smb_krb5_debug_close, NULL);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_addlog_func failed (%s)\n",
|
||||
smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx)));
|
||||
talloc_free(tmp_ctx);
|
||||
return ret;
|
||||
}
|
||||
krb5_set_warn_dest((*smb_krb5_context)->krb5_context, (*smb_krb5_context)->logf);
|
||||
|
||||
ev = event_context_find(*smb_krb5_context);
|
||||
/* Set use of our socket lib */
|
||||
ret = krb5_set_send_to_kdc_func((*smb_krb5_context)->krb5_context,
|
||||
smb_krb5_send_and_recv_func,
|
||||
ev);
|
||||
if (ret) {
|
||||
DEBUG(1,("krb5_set_send_recv_func failed (%s)\n",
|
||||
smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx)));
|
||||
talloc_free(tmp_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
(*smb_krb5_context)->krb5_context->mem_ctx = *smb_krb5_context;
|
||||
|
||||
talloc_steal(parent_ctx, *smb_krb5_context);
|
||||
talloc_free(tmp_ctx);
|
||||
|
||||
/* Set options in kerberos */
|
||||
|
||||
krb5_set_dns_canonicalize_hostname((*smb_krb5_context)->krb5_context, FALSE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
simple kerberos5 routines for active directory
|
||||
Copyright (C) Andrew Bartlett 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
struct smb_krb5_context {
|
||||
struct krb5_context_data *krb5_context;
|
||||
krb5_log_facility *logf;
|
||||
};
|
||||
|
||||
krb5_error_code smb_krb5_init_context(void *parent_ctx,
|
||||
struct smb_krb5_context **smb_krb5_context);
|
||||
void smb_krb5_free_context(struct smb_krb5_context *smb_krb5_context);
|
||||
|
||||
krb5_error_code smb_krb5_send_and_recv_func(krb5_context context,
|
||||
void *data,
|
||||
krb5_krbhst_info *hi,
|
||||
const krb5_data *send_buf,
|
||||
krb5_data *recv_buf);
|
||||
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Password and authentication handling
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004
|
||||
Copyright (C) Gerald Carter 2003
|
||||
Copyright (C) Luke Kenneth Casson Leighton 1996-2000
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/crypto/crypto.h"
|
||||
#include "librpc/gen_ndr/netlogon.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
|
||||
/****************************************************************************
|
||||
Core of smb password checking routine.
|
||||
****************************************************************************/
|
||||
|
||||
static BOOL smb_pwd_check_ntlmv1(TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *nt_response,
|
||||
const uint8_t *part_passwd,
|
||||
const DATA_BLOB *sec_blob,
|
||||
DATA_BLOB *user_sess_key)
|
||||
{
|
||||
/* Finish the encryption of part_passwd. */
|
||||
uint8_t p24[24];
|
||||
|
||||
if (part_passwd == NULL) {
|
||||
DEBUG(10,("No password set - DISALLOWING access\n"));
|
||||
/* No password set - always false ! */
|
||||
return False;
|
||||
}
|
||||
|
||||
if (sec_blob->length != 8) {
|
||||
DEBUG(0, ("smb_pwd_check_ntlmv1: incorrect challenge size (%lu)\n",
|
||||
(unsigned long)sec_blob->length));
|
||||
return False;
|
||||
}
|
||||
|
||||
if (nt_response->length != 24) {
|
||||
DEBUG(0, ("smb_pwd_check_ntlmv1: incorrect password length (%lu)\n",
|
||||
(unsigned long)nt_response->length));
|
||||
return False;
|
||||
}
|
||||
|
||||
SMBOWFencrypt(part_passwd, sec_blob->data, p24);
|
||||
|
||||
#if DEBUG_PASSWORD
|
||||
DEBUG(100,("Part password (P16) was |\n"));
|
||||
dump_data(100, part_passwd, 16);
|
||||
DEBUGADD(100,("Password from client was |\n"));
|
||||
dump_data(100, nt_response->data, nt_response->length);
|
||||
DEBUGADD(100,("Given challenge was |\n"));
|
||||
dump_data(100, sec_blob->data, sec_blob->length);
|
||||
DEBUGADD(100,("Value from encryption was |\n"));
|
||||
dump_data(100, p24, 24);
|
||||
#endif
|
||||
if (memcmp(p24, nt_response->data, 24) == 0) {
|
||||
if (user_sess_key != NULL) {
|
||||
*user_sess_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
SMBsesskeygen_ntv1(part_passwd, user_sess_key->data);
|
||||
}
|
||||
return True;
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Core of smb password checking routine. (NTLMv2, LMv2)
|
||||
Note: The same code works with both NTLMv2 and LMv2.
|
||||
****************************************************************************/
|
||||
|
||||
static BOOL smb_pwd_check_ntlmv2(TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *ntv2_response,
|
||||
const uint8_t *part_passwd,
|
||||
const DATA_BLOB *sec_blob,
|
||||
const char *user, const char *domain,
|
||||
BOOL upper_case_domain, /* should the domain be transformed into upper case? */
|
||||
DATA_BLOB *user_sess_key)
|
||||
{
|
||||
/* Finish the encryption of part_passwd. */
|
||||
uint8_t kr[16];
|
||||
uint8_t value_from_encryption[16];
|
||||
DATA_BLOB client_key_data;
|
||||
|
||||
if (part_passwd == NULL) {
|
||||
DEBUG(10,("No password set - DISALLOWING access\n"));
|
||||
/* No password set - always False */
|
||||
return False;
|
||||
}
|
||||
|
||||
if (sec_blob->length != 8) {
|
||||
DEBUG(0, ("smb_pwd_check_ntlmv2: incorrect challenge size (%lu)\n",
|
||||
(unsigned long)sec_blob->length));
|
||||
return False;
|
||||
}
|
||||
|
||||
if (ntv2_response->length < 24) {
|
||||
/* We MUST have more than 16 bytes, or the stuff below will go
|
||||
crazy. No known implementation sends less than the 24 bytes
|
||||
for LMv2, let alone NTLMv2. */
|
||||
DEBUG(0, ("smb_pwd_check_ntlmv2: incorrect password length (%lu)\n",
|
||||
(unsigned long)ntv2_response->length));
|
||||
return False;
|
||||
}
|
||||
|
||||
client_key_data = data_blob_talloc(mem_ctx, ntv2_response->data+16, ntv2_response->length-16);
|
||||
/*
|
||||
todo: should we be checking this for anything? We can't for LMv2,
|
||||
but for NTLMv2 it is meant to contain the current time etc.
|
||||
*/
|
||||
|
||||
if (!ntv2_owf_gen(part_passwd, user, domain, upper_case_domain, kr)) {
|
||||
return False;
|
||||
}
|
||||
|
||||
SMBOWFencrypt_ntv2(kr, sec_blob, &client_key_data, value_from_encryption);
|
||||
|
||||
#if DEBUG_PASSWORD
|
||||
DEBUG(100,("Part password (P16) was |\n"));
|
||||
dump_data(100, part_passwd, 16);
|
||||
DEBUGADD(100,("Password from client was |\n"));
|
||||
dump_data(100, ntv2_response->data, ntv2_response->length);
|
||||
DEBUGADD(100,("Variable data from client was |\n"));
|
||||
dump_data(100, client_key_data.data, client_key_data.length);
|
||||
DEBUGADD(100,("Given challenge was |\n"));
|
||||
dump_data(100, sec_blob->data, sec_blob->length);
|
||||
DEBUGADD(100,("Value from encryption was |\n"));
|
||||
dump_data(100, value_from_encryption, 16);
|
||||
#endif
|
||||
data_blob_clear_free(&client_key_data);
|
||||
if (memcmp(value_from_encryption, ntv2_response->data, 16) == 0) {
|
||||
if (user_sess_key != NULL) {
|
||||
*user_sess_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
SMBsesskeygen_ntv2(kr, value_from_encryption, user_sess_key->data);
|
||||
}
|
||||
return True;
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
Core of smb password checking routine. (NTLMv2, LMv2)
|
||||
Note: The same code works with both NTLMv2 and LMv2.
|
||||
****************************************************************************/
|
||||
|
||||
static BOOL smb_sess_key_ntlmv2(TALLOC_CTX *mem_ctx,
|
||||
const DATA_BLOB *ntv2_response,
|
||||
const uint8_t *part_passwd,
|
||||
const DATA_BLOB *sec_blob,
|
||||
const char *user, const char *domain,
|
||||
BOOL upper_case_domain, /* should the domain be transformed into upper case? */
|
||||
DATA_BLOB *user_sess_key)
|
||||
{
|
||||
/* Finish the encryption of part_passwd. */
|
||||
uint8_t kr[16];
|
||||
uint8_t value_from_encryption[16];
|
||||
DATA_BLOB client_key_data;
|
||||
|
||||
if (part_passwd == NULL) {
|
||||
DEBUG(10,("No password set - DISALLOWING access\n"));
|
||||
/* No password set - always False */
|
||||
return False;
|
||||
}
|
||||
|
||||
if (sec_blob->length != 8) {
|
||||
DEBUG(0, ("smb_sess_key_ntlmv2: incorrect challenge size (%lu)\n",
|
||||
(unsigned long)sec_blob->length));
|
||||
return False;
|
||||
}
|
||||
|
||||
if (ntv2_response->length < 24) {
|
||||
/* We MUST have more than 16 bytes, or the stuff below will go
|
||||
crazy. No known implementation sends less than the 24 bytes
|
||||
for LMv2, let alone NTLMv2. */
|
||||
DEBUG(0, ("smb_sess_key_ntlmv2: incorrect password length (%lu)\n",
|
||||
(unsigned long)ntv2_response->length));
|
||||
return False;
|
||||
}
|
||||
|
||||
client_key_data = data_blob_talloc(mem_ctx, ntv2_response->data+16, ntv2_response->length-16);
|
||||
|
||||
if (!ntv2_owf_gen(part_passwd, user, domain, upper_case_domain, kr)) {
|
||||
return False;
|
||||
}
|
||||
|
||||
SMBOWFencrypt_ntv2(kr, sec_blob, &client_key_data, value_from_encryption);
|
||||
*user_sess_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
SMBsesskeygen_ntv2(kr, value_from_encryption, user_sess_key->data);
|
||||
return True;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare password hashes against those from the SAM
|
||||
*
|
||||
* @param mem_ctx talloc context
|
||||
* @param client_lanman LANMAN password hash, as supplied by the client
|
||||
* @param client_nt NT (MD4) password hash, as supplied by the client
|
||||
* @param username internal Samba username, for log messages
|
||||
* @param client_username username the client used
|
||||
* @param client_domain domain name the client used (may be mapped)
|
||||
* @param stored_lanman LANMAN password hash, as stored on the SAM
|
||||
* @param stored_nt NT (MD4) password hash, as stored on the SAM
|
||||
* @param user_sess_key User session key
|
||||
* @param lm_sess_key LM session key (first 8 bytes of the LM hash)
|
||||
*/
|
||||
|
||||
NTSTATUS hash_password_check(TALLOC_CTX *mem_ctx,
|
||||
const struct samr_Password *client_lanman,
|
||||
const struct samr_Password *client_nt,
|
||||
const char *username,
|
||||
const struct samr_Password *stored_lanman,
|
||||
const struct samr_Password *stored_nt)
|
||||
{
|
||||
if (stored_nt == NULL) {
|
||||
DEBUG(3,("ntlm_password_check: NO NT password stored for user %s.\n",
|
||||
username));
|
||||
}
|
||||
|
||||
if (client_nt && stored_nt) {
|
||||
if (memcmp(client_nt->hash, stored_nt->hash, sizeof(stored_nt->hash)) == 0) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
DEBUG(3,("ntlm_password_check: Interactive logon: NT password check failed for user %s\n",
|
||||
username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
} else if (client_lanman && stored_lanman) {
|
||||
if (!lp_lanman_auth()) {
|
||||
DEBUG(3,("ntlm_password_check: Interactive logon: only LANMAN password supplied for user %s, and LM passwords are disabled!\n",
|
||||
username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
if (strchr_m(username, '@')) {
|
||||
return NT_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (memcmp(client_lanman->hash, stored_lanman->hash, sizeof(stored_lanman->hash)) == 0) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
DEBUG(3,("ntlm_password_check: Interactive logon: LANMAN password check failed for user %s\n",
|
||||
username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
}
|
||||
if (strchr_m(username, '@')) {
|
||||
return NT_STATUS_NOT_FOUND;
|
||||
}
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a challenge-response password against the value of the NT or
|
||||
* LM password hash.
|
||||
*
|
||||
* @param mem_ctx talloc context
|
||||
* @param challenge 8-byte challenge. If all zero, forces plaintext comparison
|
||||
* @param nt_response 'unicode' NT response to the challenge, or unicode password
|
||||
* @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page
|
||||
* @param username internal Samba username, for log messages
|
||||
* @param client_username username the client used
|
||||
* @param client_domain domain name the client used (may be mapped)
|
||||
* @param stored_lanman LANMAN ASCII password from our passdb or similar
|
||||
* @param stored_nt MD4 unicode password from our passdb or similar
|
||||
* @param user_sess_key User session key
|
||||
* @param lm_sess_key LM session key (first 8 bytes of the LM hash)
|
||||
*/
|
||||
|
||||
NTSTATUS ntlm_password_check(TALLOC_CTX *mem_ctx,
|
||||
uint32_t logon_parameters,
|
||||
const DATA_BLOB *challenge,
|
||||
const DATA_BLOB *lm_response,
|
||||
const DATA_BLOB *nt_response,
|
||||
const char *username,
|
||||
const char *client_username,
|
||||
const char *client_domain,
|
||||
const struct samr_Password *stored_lanman,
|
||||
const struct samr_Password *stored_nt,
|
||||
DATA_BLOB *user_sess_key,
|
||||
DATA_BLOB *lm_sess_key)
|
||||
{
|
||||
static const uint8_t zeros[8];
|
||||
DATA_BLOB tmp_sess_key;
|
||||
|
||||
if (stored_nt == NULL) {
|
||||
DEBUG(3,("ntlm_password_check: NO NT password stored for user %s.\n",
|
||||
username));
|
||||
}
|
||||
|
||||
*lm_sess_key = data_blob(NULL, 0);
|
||||
*user_sess_key = data_blob(NULL, 0);
|
||||
|
||||
/* Check for cleartext netlogon. Used by Exchange 5.5. */
|
||||
if ((logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_ALLOWED)
|
||||
&& challenge->length == sizeof(zeros)
|
||||
&& (memcmp(challenge->data, zeros, challenge->length) == 0 )) {
|
||||
struct samr_Password client_nt;
|
||||
struct samr_Password client_lm;
|
||||
char *unix_pw = NULL;
|
||||
BOOL lm_ok;
|
||||
|
||||
DEBUG(4,("ntlm_password_check: checking plaintext passwords for user %s\n",
|
||||
username));
|
||||
mdfour(client_nt.hash, nt_response->data, nt_response->length);
|
||||
|
||||
if (lm_response->length &&
|
||||
(convert_string_talloc(mem_ctx, CH_DOS, CH_UNIX,
|
||||
lm_response->data, lm_response->length,
|
||||
(void **)&unix_pw) != -1)) {
|
||||
if (E_deshash(unix_pw, client_lm.hash)) {
|
||||
lm_ok = True;
|
||||
} else {
|
||||
lm_ok = False;
|
||||
}
|
||||
} else {
|
||||
lm_ok = False;
|
||||
}
|
||||
return hash_password_check(mem_ctx,
|
||||
lm_ok ? &client_lm : NULL,
|
||||
nt_response->length ? &client_nt : NULL,
|
||||
username,
|
||||
stored_lanman, stored_nt);
|
||||
}
|
||||
|
||||
if (nt_response->length != 0 && nt_response->length < 24) {
|
||||
DEBUG(2,("ntlm_password_check: invalid NT password length (%lu) for user %s\n",
|
||||
(unsigned long)nt_response->length, username));
|
||||
}
|
||||
|
||||
if (nt_response->length > 24 && stored_nt) {
|
||||
/* We have the NT MD4 hash challenge available - see if we can
|
||||
use it
|
||||
*/
|
||||
DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with domain [%s]\n", client_domain));
|
||||
if (smb_pwd_check_ntlmv2(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
client_domain,
|
||||
False,
|
||||
user_sess_key)) {
|
||||
*lm_sess_key = *user_sess_key;
|
||||
if (user_sess_key->length) {
|
||||
lm_sess_key->length = 8;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with uppercased version of domain [%s]\n", client_domain));
|
||||
if (smb_pwd_check_ntlmv2(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
client_domain,
|
||||
True,
|
||||
user_sess_key)) {
|
||||
*lm_sess_key = *user_sess_key;
|
||||
if (user_sess_key->length) {
|
||||
lm_sess_key->length = 8;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
DEBUG(4,("ntlm_password_check: Checking NTLMv2 password without a domain\n"));
|
||||
if (smb_pwd_check_ntlmv2(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
"",
|
||||
False,
|
||||
user_sess_key)) {
|
||||
*lm_sess_key = *user_sess_key;
|
||||
if (user_sess_key->length) {
|
||||
lm_sess_key->length = 8;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
DEBUG(3,("ntlm_password_check: NTLMv2 password check failed\n"));
|
||||
}
|
||||
} else if (nt_response->length == 24 && stored_nt) {
|
||||
if (lp_ntlm_auth()) {
|
||||
/* We have the NT MD4 hash challenge available - see if we can
|
||||
use it (ie. does it exist in the smbpasswd file).
|
||||
*/
|
||||
DEBUG(4,("ntlm_password_check: Checking NT MD4 password\n"));
|
||||
if (smb_pwd_check_ntlmv1(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
user_sess_key)) {
|
||||
/* The LM session key for this response is not very secure,
|
||||
so use it only if we otherwise allow LM authentication */
|
||||
|
||||
if (lp_lanman_auth() && stored_lanman) {
|
||||
*lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8);
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
DEBUG(3,("ntlm_password_check: NT MD4 password check failed for user %s\n",
|
||||
username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
} else {
|
||||
DEBUG(2,("ntlm_password_check: NTLMv1 passwords NOT PERMITTED for user %s\n",
|
||||
username));
|
||||
/* no return, becouse we might pick up LMv2 in the LM field */
|
||||
}
|
||||
}
|
||||
|
||||
if (lm_response->length == 0) {
|
||||
DEBUG(3,("ntlm_password_check: NEITHER LanMan nor NT password supplied for user %s\n",
|
||||
username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
if (lm_response->length < 24) {
|
||||
DEBUG(2,("ntlm_password_check: invalid LanMan password length (%lu) for user %s\n",
|
||||
(unsigned long)nt_response->length, username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
if (!lp_lanman_auth()) {
|
||||
DEBUG(3,("ntlm_password_check: Lanman passwords NOT PERMITTED for user %s\n",
|
||||
username));
|
||||
} else if (!stored_lanman) {
|
||||
DEBUG(3,("ntlm_password_check: NO LanMan password set for user %s (and no NT password supplied)\n",
|
||||
username));
|
||||
} else if (strchr_m(username, '@')) {
|
||||
DEBUG(3,("ntlm_password_check: NO LanMan password allowed for username@realm logins (user: %s)\n",
|
||||
username));
|
||||
} else {
|
||||
DEBUG(4,("ntlm_password_check: Checking LM password\n"));
|
||||
if (smb_pwd_check_ntlmv1(mem_ctx,
|
||||
lm_response,
|
||||
stored_lanman->hash, challenge,
|
||||
NULL)) {
|
||||
/* The session key for this response is still very odd.
|
||||
It not very secure, so use it only if we otherwise
|
||||
allow LM authentication */
|
||||
|
||||
if (lp_lanman_auth() && stored_lanman) {
|
||||
uint8_t first_8_lm_hash[16];
|
||||
memcpy(first_8_lm_hash, stored_lanman->hash, 8);
|
||||
memset(first_8_lm_hash + 8, '\0', 8);
|
||||
*user_sess_key = data_blob_talloc(mem_ctx, first_8_lm_hash, 16);
|
||||
*lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8);
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stored_nt) {
|
||||
DEBUG(4,("ntlm_password_check: LM password check failed for user, no NT password %s\n",username));
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
/* This is for 'LMv2' authentication. almost NTLMv2 but limited to 24 bytes.
|
||||
- related to Win9X, legacy NAS pass-though authentication
|
||||
*/
|
||||
DEBUG(4,("ntlm_password_check: Checking LMv2 password with domain %s\n", client_domain));
|
||||
if (smb_pwd_check_ntlmv2(mem_ctx,
|
||||
lm_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
client_domain,
|
||||
False,
|
||||
&tmp_sess_key)) {
|
||||
if (nt_response->length > 24) {
|
||||
/* If NTLMv2 authentication has preceeded us
|
||||
* (even if it failed), then use the session
|
||||
* key from that. See the RPC-SAMLOGON
|
||||
* torture test */
|
||||
smb_sess_key_ntlmv2(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
client_domain,
|
||||
False,
|
||||
user_sess_key);
|
||||
} else {
|
||||
/* Otherwise, use the LMv2 session key */
|
||||
*user_sess_key = tmp_sess_key;
|
||||
}
|
||||
*lm_sess_key = *user_sess_key;
|
||||
if (user_sess_key->length) {
|
||||
lm_sess_key->length = 8;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
DEBUG(4,("ntlm_password_check: Checking LMv2 password with upper-cased version of domain %s\n", client_domain));
|
||||
if (smb_pwd_check_ntlmv2(mem_ctx,
|
||||
lm_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
client_domain,
|
||||
True,
|
||||
&tmp_sess_key)) {
|
||||
if (nt_response->length > 24) {
|
||||
/* If NTLMv2 authentication has preceeded us
|
||||
* (even if it failed), then use the session
|
||||
* key from that. See the RPC-SAMLOGON
|
||||
* torture test */
|
||||
smb_sess_key_ntlmv2(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
client_domain,
|
||||
True,
|
||||
user_sess_key);
|
||||
} else {
|
||||
/* Otherwise, use the LMv2 session key */
|
||||
*user_sess_key = tmp_sess_key;
|
||||
}
|
||||
*lm_sess_key = *user_sess_key;
|
||||
if (user_sess_key->length) {
|
||||
lm_sess_key->length = 8;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
DEBUG(4,("ntlm_password_check: Checking LMv2 password without a domain\n"));
|
||||
if (smb_pwd_check_ntlmv2(mem_ctx,
|
||||
lm_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
"",
|
||||
False,
|
||||
&tmp_sess_key)) {
|
||||
if (nt_response->length > 24) {
|
||||
/* If NTLMv2 authentication has preceeded us
|
||||
* (even if it failed), then use the session
|
||||
* key from that. See the RPC-SAMLOGON
|
||||
* torture test */
|
||||
smb_sess_key_ntlmv2(mem_ctx,
|
||||
nt_response,
|
||||
stored_nt->hash, challenge,
|
||||
client_username,
|
||||
"",
|
||||
False,
|
||||
user_sess_key);
|
||||
} else {
|
||||
/* Otherwise, use the LMv2 session key */
|
||||
*user_sess_key = tmp_sess_key;
|
||||
}
|
||||
*lm_sess_key = *user_sess_key;
|
||||
if (user_sess_key->length) {
|
||||
lm_sess_key->length = 8;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* Apparently NT accepts NT responses in the LM field
|
||||
- I think this is related to Win9X pass-though authentication
|
||||
*/
|
||||
DEBUG(4,("ntlm_password_check: Checking NT MD4 password in LM field\n"));
|
||||
if (lp_ntlm_auth()) {
|
||||
if (smb_pwd_check_ntlmv1(mem_ctx,
|
||||
lm_response,
|
||||
stored_nt->hash, challenge,
|
||||
NULL)) {
|
||||
/* The session key for this response is still very odd.
|
||||
It not very secure, so use it only if we otherwise
|
||||
allow LM authentication */
|
||||
|
||||
if (lp_lanman_auth() && stored_lanman) {
|
||||
uint8_t first_8_lm_hash[16];
|
||||
memcpy(first_8_lm_hash, stored_lanman->hash, 8);
|
||||
memset(first_8_lm_hash + 8, '\0', 8);
|
||||
*user_sess_key = data_blob_talloc(mem_ctx, first_8_lm_hash, 16);
|
||||
*lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8);
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
DEBUG(3,("ntlm_password_check: LM password, NT MD4 password in LM field and LMv2 failed for user %s\n",username));
|
||||
} else {
|
||||
DEBUG(3,("ntlm_password_check: LM password and LMv2 failed for user %s, and NT MD4 password in LM field not permitted\n",username));
|
||||
}
|
||||
|
||||
/* Try and match error codes */
|
||||
if (strchr_m(username, '@')) {
|
||||
return NT_STATUS_NOT_FOUND;
|
||||
}
|
||||
return NT_STATUS_WRONG_PASSWORD;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[SUBSYSTEM::MSRPC_PARSE]
|
||||
PRIVATE_PROTO_HEADER = msrpc_parse.h
|
||||
OBJ_FILES = ntlmssp_parse.o
|
||||
|
||||
################################################
|
||||
# Start MODULE gensec_ntlmssp
|
||||
[MODULE::gensec_ntlmssp]
|
||||
SUBSYSTEM = gensec
|
||||
INIT_FUNCTION = gensec_ntlmssp_init
|
||||
PRIVATE_PROTO_HEADER = proto.h
|
||||
OBJ_FILES = ntlmssp.o \
|
||||
ntlmssp_sign.o \
|
||||
ntlmssp_client.o \
|
||||
ntlmssp_server.o
|
||||
PUBLIC_DEPENDENCIES = auth MSRPC_PARSE
|
||||
OUTPUT_TYPE = INTEGRATED
|
||||
# End MODULE gensec_ntlmssp
|
||||
################################################
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
Unix SMB/Netbios implementation.
|
||||
Version 3.0
|
||||
handle NLTMSSP, client server side parsing
|
||||
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/ntlmssp/ntlmssp.h"
|
||||
#include "auth/ntlmssp/msrpc_parse.h"
|
||||
#include "librpc/gen_ndr/ndr_dcerpc.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
#include "auth/auth.h"
|
||||
|
||||
/**
|
||||
* Callbacks for NTLMSSP - for both client and server operating modes
|
||||
*
|
||||
*/
|
||||
|
||||
static const struct ntlmssp_callbacks {
|
||||
enum ntlmssp_role role;
|
||||
enum ntlmssp_message_type command;
|
||||
NTSTATUS (*sync_fn)(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
DATA_BLOB in, DATA_BLOB *out);
|
||||
} ntlmssp_callbacks[] = {
|
||||
{
|
||||
.role = NTLMSSP_CLIENT,
|
||||
.command = NTLMSSP_INITIAL,
|
||||
.sync_fn = ntlmssp_client_initial,
|
||||
},{
|
||||
.role = NTLMSSP_SERVER,
|
||||
.command = NTLMSSP_NEGOTIATE,
|
||||
.sync_fn = ntlmssp_server_negotiate,
|
||||
},{
|
||||
.role = NTLMSSP_CLIENT,
|
||||
.command = NTLMSSP_CHALLENGE,
|
||||
.sync_fn = ntlmssp_client_challenge,
|
||||
},{
|
||||
.role = NTLMSSP_SERVER,
|
||||
.command = NTLMSSP_AUTH,
|
||||
.sync_fn = ntlmssp_server_auth,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Print out the NTLMSSP flags for debugging
|
||||
* @param neg_flags The flags from the packet
|
||||
*/
|
||||
|
||||
void debug_ntlmssp_flags(uint32_t neg_flags)
|
||||
{
|
||||
DEBUG(3,("Got NTLMSSP neg_flags=0x%08x\n", neg_flags));
|
||||
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_UNICODE)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_UNICODE\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_OEM)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_OEM\n"));
|
||||
if (neg_flags & NTLMSSP_REQUEST_TARGET)
|
||||
DEBUGADD(4, (" NTLMSSP_REQUEST_TARGET\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_SIGN)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_SIGN\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_SEAL)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_SEAL\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_DATAGRAM_STYLE)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_DATAGRAM_STYLE\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_LM_KEY)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_LM_KEY\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_NETWARE)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NETWARE\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_NTLM)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NTLM\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_ALWAYS_SIGN\n"));
|
||||
if (neg_flags & NTLMSSP_CHAL_ACCEPT_RESPONSE)
|
||||
DEBUGADD(4, (" NTLMSSP_CHAL_ACCEPT_RESPONSE\n"));
|
||||
if (neg_flags & NTLMSSP_CHAL_NON_NT_SESSION_KEY)
|
||||
DEBUGADD(4, (" NTLMSSP_CHAL_NON_NT_SESSION_KEY\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_NTLM2)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NTLM2\n"));
|
||||
if (neg_flags & NTLMSSP_CHAL_TARGET_INFO)
|
||||
DEBUGADD(4, (" NTLMSSP_CHAL_TARGET_INFO\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_128)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_128\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_KEY_EXCH\n"));
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_56)
|
||||
DEBUGADD(4, (" NTLMSSP_NEGOTIATE_56\n"));
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_ntlmssp_magic(struct gensec_security *gensec_security,
|
||||
const DATA_BLOB *first_packet)
|
||||
{
|
||||
if (first_packet->length > 8 && memcmp("NTLMSSP\0", first_packet->data, 8) == 0) {
|
||||
return NT_STATUS_OK;
|
||||
} else {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
static NTSTATUS gensec_ntlmssp_update_find(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
const DATA_BLOB input, uint32_t *idx)
|
||||
{
|
||||
struct gensec_security *gensec_security = gensec_ntlmssp_state->gensec_security;
|
||||
uint32_t ntlmssp_command;
|
||||
uint32_t i;
|
||||
|
||||
if (gensec_ntlmssp_state->expected_state == NTLMSSP_DONE) {
|
||||
/* We are strict here because other modules, which we
|
||||
* don't fully control (such as GSSAPI) are also
|
||||
* strict, but are tested less often */
|
||||
|
||||
DEBUG(1, ("Called NTLMSSP after state machine was 'done'\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!input.length) {
|
||||
switch (gensec_ntlmssp_state->role) {
|
||||
case NTLMSSP_CLIENT:
|
||||
ntlmssp_command = NTLMSSP_INITIAL;
|
||||
break;
|
||||
case NTLMSSP_SERVER:
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_DATAGRAM_MODE) {
|
||||
/* 'datagram' mode - no neg packet */
|
||||
ntlmssp_command = NTLMSSP_NEGOTIATE;
|
||||
} else {
|
||||
/* This is normal in SPNEGO mech negotiation fallback */
|
||||
DEBUG(2, ("Failed to parse NTLMSSP packet: zero length\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!msrpc_parse(gensec_ntlmssp_state,
|
||||
&input, "Cd",
|
||||
"NTLMSSP",
|
||||
&ntlmssp_command)) {
|
||||
DEBUG(1, ("Failed to parse NTLMSSP packet, could not extract NTLMSSP command\n"));
|
||||
dump_data(2, input.data, input.length);
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
if (ntlmssp_command != gensec_ntlmssp_state->expected_state) {
|
||||
DEBUG(2, ("got NTLMSSP command %u, expected %u\n", ntlmssp_command, gensec_ntlmssp_state->expected_state));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
for (i=0; i < ARRAY_SIZE(ntlmssp_callbacks); i++) {
|
||||
if (ntlmssp_callbacks[i].role == gensec_ntlmssp_state->role &&
|
||||
ntlmssp_callbacks[i].command == ntlmssp_command) {
|
||||
*idx = i;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(1, ("failed to find NTLMSSP callback for NTLMSSP mode %u, command %u\n",
|
||||
gensec_ntlmssp_state->role, ntlmssp_command));
|
||||
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next state function for the wrapped NTLMSSP state machine
|
||||
*
|
||||
* @param gensec_security GENSEC state, initialised to NTLMSSP
|
||||
* @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
|
||||
* @param in The request, as a DATA_BLOB
|
||||
* @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
|
||||
* @return Error, MORE_PROCESSING_REQUIRED if a reply is sent,
|
||||
* or NT_STATUS_OK if the user is authenticated.
|
||||
*/
|
||||
|
||||
static NTSTATUS gensec_ntlmssp_update(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB input, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
NTSTATUS status;
|
||||
uint32_t i;
|
||||
|
||||
*out = data_blob(NULL, 0);
|
||||
|
||||
if (!out_mem_ctx) {
|
||||
/* if the caller doesn't want to manage/own the memory,
|
||||
we can put it on our context */
|
||||
out_mem_ctx = gensec_ntlmssp_state;
|
||||
}
|
||||
|
||||
status = gensec_ntlmssp_update_find(gensec_ntlmssp_state, input, &i);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
|
||||
status = ntlmssp_callbacks[i].sync_fn(gensec_security, out_mem_ctx, input, out);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the NTLMSSP master session key
|
||||
*
|
||||
* @param gensec_ntlmssp_state NTLMSSP State
|
||||
*/
|
||||
|
||||
NTSTATUS gensec_ntlmssp_session_key(struct gensec_security *gensec_security,
|
||||
DATA_BLOB *session_key)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
if (!gensec_ntlmssp_state->session_key.data) {
|
||||
return NT_STATUS_NO_USER_SESSION_KEY;
|
||||
}
|
||||
*session_key = gensec_ntlmssp_state->session_key;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
void ntlmssp_handle_neg_flags(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
uint32_t neg_flags, BOOL allow_lm)
|
||||
{
|
||||
if (neg_flags & NTLMSSP_NEGOTIATE_UNICODE) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE;
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM;
|
||||
gensec_ntlmssp_state->unicode = True;
|
||||
} else {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_UNICODE;
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM;
|
||||
gensec_ntlmssp_state->unicode = False;
|
||||
}
|
||||
|
||||
if ((neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) && allow_lm && !gensec_ntlmssp_state->use_ntlmv2) {
|
||||
/* other end forcing us to use LM */
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2;
|
||||
} else {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_SIGN)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_SEAL)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_NTLM2)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_128)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_128;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_56)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_56;
|
||||
}
|
||||
|
||||
if (!(neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH;
|
||||
}
|
||||
|
||||
/* Woop Woop - unknown flag for Windows compatibility...
|
||||
What does this really do ? JRA. */
|
||||
if (!(neg_flags & NTLMSSP_UNKNOWN_02000000)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_UNKNOWN_02000000;
|
||||
}
|
||||
|
||||
if ((neg_flags & NTLMSSP_REQUEST_TARGET)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_REQUEST_TARGET;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Weaken NTLMSSP keys to cope with down-level clients and servers.
|
||||
|
||||
We probably should have some parameters to control this, but as
|
||||
it only occours for LM_KEY connections, and this is controlled
|
||||
by the client lanman auth/lanman auth parameters, it isn't too bad.
|
||||
*/
|
||||
|
||||
DATA_BLOB ntlmssp_weakend_key(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
DATA_BLOB weakened_key = data_blob_talloc(mem_ctx,
|
||||
gensec_ntlmssp_state->session_key.data,
|
||||
gensec_ntlmssp_state->session_key.length);
|
||||
/* Nothing to weaken. We certainly don't want to 'extend' the length... */
|
||||
if (weakened_key.length < 16) {
|
||||
/* perhaps there was no key? */
|
||||
return weakened_key;
|
||||
}
|
||||
|
||||
/* Key weakening not performed on the master key for NTLM2
|
||||
and does not occour for NTLM1. Therefore we only need
|
||||
to do this for the LM_KEY.
|
||||
*/
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) {
|
||||
/* LM key doesn't support 128 bit crypto, so this is
|
||||
* the best we can do. If you negotiate 128 bit, but
|
||||
* not 56, you end up with 40 bit... */
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) {
|
||||
weakened_key.data[7] = 0xa0;
|
||||
weakened_key.length = 8;
|
||||
} else { /* forty bits */
|
||||
weakened_key.data[5] = 0xe5;
|
||||
weakened_key.data[6] = 0x38;
|
||||
weakened_key.data[7] = 0xb0;
|
||||
weakened_key.length = 8;
|
||||
}
|
||||
}
|
||||
return weakened_key;
|
||||
}
|
||||
|
||||
static BOOL gensec_ntlmssp_have_feature(struct gensec_security *gensec_security,
|
||||
uint32_t feature)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
if (feature & GENSEC_FEATURE_SIGN) {
|
||||
if (!gensec_ntlmssp_state->session_key.length) {
|
||||
return False;
|
||||
}
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_SEAL) {
|
||||
if (!gensec_ntlmssp_state->session_key.length) {
|
||||
return False;
|
||||
}
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_SESSION_KEY) {
|
||||
if (gensec_ntlmssp_state->session_key.length) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_DCE_STYLE) {
|
||||
return True;
|
||||
}
|
||||
if (feature & GENSEC_FEATURE_ASYNC_REPLIES) {
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
return True;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
NTSTATUS gensec_ntlmssp_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state;
|
||||
|
||||
gensec_ntlmssp_state = talloc_zero(gensec_security, struct gensec_ntlmssp_state);
|
||||
if (!gensec_ntlmssp_state) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
gensec_ntlmssp_state->gensec_security = gensec_security;
|
||||
gensec_ntlmssp_state->auth_context = NULL;
|
||||
gensec_ntlmssp_state->server_info = NULL;
|
||||
|
||||
gensec_security->private_data = gensec_ntlmssp_state;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
static const char *gensec_ntlmssp_oids[] = {
|
||||
GENSEC_OID_NTLMSSP,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct gensec_security_ops gensec_ntlmssp_security_ops = {
|
||||
.name = "ntlmssp",
|
||||
.sasl_name = "NTLM",
|
||||
.auth_type = DCERPC_AUTH_TYPE_NTLMSSP,
|
||||
.oid = gensec_ntlmssp_oids,
|
||||
.client_start = gensec_ntlmssp_client_start,
|
||||
.server_start = gensec_ntlmssp_server_start,
|
||||
.magic = gensec_ntlmssp_magic,
|
||||
.update = gensec_ntlmssp_update,
|
||||
.sig_size = gensec_ntlmssp_sig_size,
|
||||
.sign_packet = gensec_ntlmssp_sign_packet,
|
||||
.check_packet = gensec_ntlmssp_check_packet,
|
||||
.seal_packet = gensec_ntlmssp_seal_packet,
|
||||
.unseal_packet = gensec_ntlmssp_unseal_packet,
|
||||
.wrap = gensec_ntlmssp_wrap,
|
||||
.unwrap = gensec_ntlmssp_unwrap,
|
||||
.session_key = gensec_ntlmssp_session_key,
|
||||
.session_info = gensec_ntlmssp_session_info,
|
||||
.have_feature = gensec_ntlmssp_have_feature,
|
||||
.enabled = True,
|
||||
.priority = GENSEC_NTLMSSP
|
||||
};
|
||||
|
||||
|
||||
NTSTATUS gensec_ntlmssp_init(void)
|
||||
{
|
||||
NTSTATUS ret;
|
||||
|
||||
auth_init();
|
||||
|
||||
ret = gensec_register(&gensec_ntlmssp_security_ops);
|
||||
if (!NT_STATUS_IS_OK(ret)) {
|
||||
DEBUG(0,("Failed to register '%s' gensec backend!\n",
|
||||
gensec_ntlmssp_security_ops.name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
SMB parameters and setup
|
||||
Copyright (C) Andrew Tridgell 1992-1997
|
||||
Copyright (C) Luke Kenneth Casson Leighton 1996-1997
|
||||
Copyright (C) Paul Ashton 1997
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "librpc/gen_ndr/samr.h"
|
||||
|
||||
/* NTLMSSP mode */
|
||||
enum ntlmssp_role
|
||||
{
|
||||
NTLMSSP_SERVER,
|
||||
NTLMSSP_CLIENT
|
||||
};
|
||||
|
||||
/* NTLMSSP message types */
|
||||
enum ntlmssp_message_type
|
||||
{
|
||||
NTLMSSP_INITIAL = 0 /* samba internal state */,
|
||||
NTLMSSP_NEGOTIATE = 1,
|
||||
NTLMSSP_CHALLENGE = 2,
|
||||
NTLMSSP_AUTH = 3,
|
||||
NTLMSSP_UNKNOWN = 4,
|
||||
NTLMSSP_DONE = 5 /* samba final state */
|
||||
};
|
||||
|
||||
/* NTLMSSP negotiation flags */
|
||||
#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001
|
||||
#define NTLMSSP_NEGOTIATE_OEM 0x00000002
|
||||
#define NTLMSSP_REQUEST_TARGET 0x00000004
|
||||
#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 /* Message integrity */
|
||||
#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 /* Message confidentiality */
|
||||
#define NTLMSSP_NEGOTIATE_DATAGRAM_STYLE 0x00000040
|
||||
#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080
|
||||
#define NTLMSSP_NEGOTIATE_NETWARE 0x00000100
|
||||
#define NTLMSSP_NEGOTIATE_NTLM 0x00000200
|
||||
#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000
|
||||
#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000
|
||||
#define NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL 0x00004000
|
||||
#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000
|
||||
#define NTLMSSP_TARGET_TYPE_DOMAIN 0x10000
|
||||
#define NTLMSSP_TARGET_TYPE_SERVER 0x20000
|
||||
#define NTLMSSP_CHAL_INIT_RESPONSE 0x00010000
|
||||
|
||||
#define NTLMSSP_CHAL_ACCEPT_RESPONSE 0x00020000
|
||||
#define NTLMSSP_CHAL_NON_NT_SESSION_KEY 0x00040000
|
||||
#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000
|
||||
#define NTLMSSP_CHAL_TARGET_INFO 0x00800000
|
||||
#define NTLMSSP_UNKNOWN_02000000 0x02000000
|
||||
#define NTLMSSP_NEGOTIATE_128 0x20000000 /* 128-bit encryption */
|
||||
#define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000
|
||||
#define NTLMSSP_NEGOTIATE_56 0x80000000
|
||||
|
||||
#define NTLMSSP_NAME_TYPE_SERVER 0x01
|
||||
#define NTLMSSP_NAME_TYPE_DOMAIN 0x02
|
||||
#define NTLMSSP_NAME_TYPE_SERVER_DNS 0x03
|
||||
#define NTLMSSP_NAME_TYPE_DOMAIN_DNS 0x04
|
||||
|
||||
#define NTLMSSP_SIGN_VERSION 1
|
||||
|
||||
#define NTLMSSP_SIG_SIZE 16
|
||||
|
||||
struct gensec_ntlmssp_state
|
||||
{
|
||||
struct gensec_security *gensec_security;
|
||||
|
||||
enum ntlmssp_role role;
|
||||
enum samr_Role server_role;
|
||||
uint32_t expected_state;
|
||||
|
||||
BOOL unicode;
|
||||
BOOL use_ntlmv2;
|
||||
BOOL use_nt_response; /* Set to 'False' to debug what happens when the NT response is omited */
|
||||
BOOL allow_lm_key; /* The LM_KEY code is not functional at this point, and it's not
|
||||
very secure anyway */
|
||||
|
||||
BOOL server_multiple_authentications; /* Set to 'True' to allow squid 2.5
|
||||
style 'challenge caching' */
|
||||
|
||||
char *user;
|
||||
char *domain;
|
||||
const char *workstation;
|
||||
char *server_domain;
|
||||
|
||||
DATA_BLOB internal_chal; /* Random challenge as supplied to the client for NTLM authentication */
|
||||
|
||||
DATA_BLOB chal; /* Random challenge as input into the actual NTLM (or NTLM2) authentication */
|
||||
DATA_BLOB lm_resp;
|
||||
DATA_BLOB nt_resp;
|
||||
DATA_BLOB session_key;
|
||||
|
||||
uint32_t neg_flags; /* the current state of negotiation with the NTLMSSP partner */
|
||||
|
||||
/* internal variables used by KEY_EXCH (client-supplied user session key */
|
||||
DATA_BLOB encrypted_session_key;
|
||||
|
||||
/**
|
||||
* Callback to get the 'challenge' used for NTLM authentication.
|
||||
*
|
||||
* @param ntlmssp_state This structure
|
||||
* @return 8 bytes of challenge data, determined by the server to be the challenge for NTLM authentication
|
||||
*
|
||||
*/
|
||||
const uint8_t *(*get_challenge)(const struct gensec_ntlmssp_state *);
|
||||
|
||||
/**
|
||||
* Callback to find if the challenge used by NTLM authentication may be modified
|
||||
*
|
||||
* The NTLM2 authentication scheme modifies the effective challenge, but this is not compatiable with the
|
||||
* current 'security=server' implementation..
|
||||
*
|
||||
* @param ntlmssp_state This structure
|
||||
* @return Can the challenge be set to arbitary values?
|
||||
*
|
||||
*/
|
||||
BOOL (*may_set_challenge)(const struct gensec_ntlmssp_state *);
|
||||
|
||||
/**
|
||||
* Callback to set the 'challenge' used for NTLM authentication.
|
||||
*
|
||||
* The callback may use the void *auth_context to store state information, but the same value is always available
|
||||
* from the DATA_BLOB chal on this structure.
|
||||
*
|
||||
* @param ntlmssp_state This structure
|
||||
* @param challenge 8 bytes of data, agreed by the client and server to be the effective challenge for NTLM2 authentication
|
||||
*
|
||||
*/
|
||||
NTSTATUS (*set_challenge)(struct gensec_ntlmssp_state *, DATA_BLOB *challenge);
|
||||
|
||||
/**
|
||||
* Callback to check the user's password.
|
||||
*
|
||||
* The callback must reads the feilds of this structure for the information it needs on the user
|
||||
* @param ntlmssp_state This structure
|
||||
* @param nt_session_key If an NT session key is returned by the authentication process, return it here
|
||||
* @param lm_session_key If an LM session key is returned by the authentication process, return it here
|
||||
*
|
||||
*/
|
||||
NTSTATUS (*check_password)(struct gensec_ntlmssp_state *,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
DATA_BLOB *nt_session_key, DATA_BLOB *lm_session_key);
|
||||
|
||||
const char *server_name;
|
||||
const char *(*get_domain)(void);
|
||||
|
||||
BOOL doing_ntlm2;
|
||||
|
||||
union {
|
||||
/* NTLM */
|
||||
struct {
|
||||
uint32_t seq_num;
|
||||
struct arcfour_state *arcfour_state;
|
||||
} ntlm;
|
||||
|
||||
/* NTLM2 */
|
||||
struct {
|
||||
uint32_t send_seq_num;
|
||||
uint32_t recv_seq_num;
|
||||
DATA_BLOB send_sign_key;
|
||||
DATA_BLOB recv_sign_key;
|
||||
struct arcfour_state *send_seal_arcfour_state;
|
||||
struct arcfour_state *recv_seal_arcfour_state;
|
||||
|
||||
/* internal variables used by NTLM2 */
|
||||
uint8_t session_nonce[16];
|
||||
} ntlm2;
|
||||
} crypt;
|
||||
|
||||
struct auth_context *auth_context;
|
||||
struct auth_serversupplied_info *server_info;
|
||||
};
|
||||
|
||||
struct auth_session_info;
|
||||
#include "auth/ntlmssp/proto.h"
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
Unix SMB/Netbios implementation.
|
||||
Version 3.0
|
||||
handle NLTMSSP, client server side parsing
|
||||
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/ntlmssp/ntlmssp.h"
|
||||
#include "auth/ntlmssp/msrpc_parse.h"
|
||||
#include "lib/crypto/crypto.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
|
||||
/*********************************************************************
|
||||
Client side NTLMSSP
|
||||
*********************************************************************/
|
||||
|
||||
/**
|
||||
* Next state function for the Initial packet
|
||||
*
|
||||
* @param ntlmssp_state NTLMSSP State
|
||||
* @param out_mem_ctx The DATA_BLOB *out will be allocated on this context
|
||||
* @param in A NULL data blob (input ignored)
|
||||
* @param out The initial negotiate request to the server, as an talloc()ed DATA_BLOB, on out_mem_ctx
|
||||
* @return Errors or NT_STATUS_OK.
|
||||
*/
|
||||
|
||||
NTSTATUS ntlmssp_client_initial(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
if (gensec_ntlmssp_state->unicode) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE;
|
||||
} else {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM;
|
||||
}
|
||||
|
||||
if (gensec_ntlmssp_state->use_ntlmv2) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2;
|
||||
}
|
||||
|
||||
/* generate the ntlmssp negotiate packet */
|
||||
msrpc_gen(out_mem_ctx,
|
||||
out, "CddAA",
|
||||
"NTLMSSP",
|
||||
NTLMSSP_NEGOTIATE,
|
||||
gensec_ntlmssp_state->neg_flags,
|
||||
gensec_ntlmssp_state->get_domain(),
|
||||
cli_credentials_get_workstation(gensec_security->credentials));
|
||||
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_CHALLENGE;
|
||||
|
||||
return NT_STATUS_MORE_PROCESSING_REQUIRED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next state function for the Challenge Packet. Generate an auth packet.
|
||||
*
|
||||
* @param gensec_security GENSEC state
|
||||
* @param out_mem_ctx Memory context for *out
|
||||
* @param in The server challnege, as a DATA_BLOB. reply.data must be NULL
|
||||
* @param out The next request (auth packet) to the server, as an allocated DATA_BLOB, on the out_mem_ctx context
|
||||
* @return Errors or NT_STATUS_OK.
|
||||
*/
|
||||
|
||||
NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
uint32_t chal_flags, ntlmssp_command, unkn1, unkn2;
|
||||
DATA_BLOB server_domain_blob;
|
||||
DATA_BLOB challenge_blob;
|
||||
DATA_BLOB target_info = data_blob(NULL, 0);
|
||||
char *server_domain;
|
||||
const char *chal_parse_string;
|
||||
const char *auth_gen_string;
|
||||
DATA_BLOB lm_response = data_blob(NULL, 0);
|
||||
DATA_BLOB nt_response = data_blob(NULL, 0);
|
||||
DATA_BLOB session_key = data_blob(NULL, 0);
|
||||
DATA_BLOB lm_session_key = data_blob(NULL, 0);
|
||||
DATA_BLOB encrypted_session_key = data_blob(NULL, 0);
|
||||
NTSTATUS nt_status;
|
||||
int flags = 0;
|
||||
const char *user, *domain;
|
||||
|
||||
TALLOC_CTX *mem_ctx = talloc_new(out_mem_ctx);
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
if (!msrpc_parse(mem_ctx,
|
||||
&in, "CdBd",
|
||||
"NTLMSSP",
|
||||
&ntlmssp_command,
|
||||
&server_domain_blob,
|
||||
&chal_flags)) {
|
||||
DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#1)\n"));
|
||||
dump_data(2, in.data, in.length);
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
data_blob_free(&server_domain_blob);
|
||||
|
||||
DEBUG(3, ("Got challenge flags:\n"));
|
||||
debug_ntlmssp_flags(chal_flags);
|
||||
|
||||
ntlmssp_handle_neg_flags(gensec_ntlmssp_state, chal_flags, gensec_ntlmssp_state->allow_lm_key);
|
||||
|
||||
if (gensec_ntlmssp_state->unicode) {
|
||||
if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) {
|
||||
chal_parse_string = "CdUdbddB";
|
||||
} else {
|
||||
chal_parse_string = "CdUdbdd";
|
||||
}
|
||||
auth_gen_string = "CdBBUUUBd";
|
||||
} else {
|
||||
if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) {
|
||||
chal_parse_string = "CdAdbddB";
|
||||
} else {
|
||||
chal_parse_string = "CdAdbdd";
|
||||
}
|
||||
|
||||
auth_gen_string = "CdBBAAABd";
|
||||
}
|
||||
|
||||
if (!msrpc_parse(mem_ctx,
|
||||
&in, chal_parse_string,
|
||||
"NTLMSSP",
|
||||
&ntlmssp_command,
|
||||
&server_domain,
|
||||
&chal_flags,
|
||||
&challenge_blob, 8,
|
||||
&unkn1, &unkn2,
|
||||
&target_info)) {
|
||||
DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#2)\n"));
|
||||
dump_data(2, in.data, in.length);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
gensec_ntlmssp_state->server_domain = server_domain;
|
||||
|
||||
if (challenge_blob.length != 8) {
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
cli_credentials_get_ntlm_username_domain(gensec_security->credentials, mem_ctx,
|
||||
&user, &domain);
|
||||
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
flags |= CLI_CRED_NTLM2;
|
||||
}
|
||||
if (gensec_ntlmssp_state->use_ntlmv2) {
|
||||
flags |= CLI_CRED_NTLMv2_AUTH;
|
||||
}
|
||||
if (gensec_ntlmssp_state->use_nt_response) {
|
||||
flags |= CLI_CRED_NTLM_AUTH;
|
||||
}
|
||||
if (lp_client_lanman_auth()) {
|
||||
flags |= CLI_CRED_LANMAN_AUTH;
|
||||
}
|
||||
|
||||
nt_status = cli_credentials_get_ntlm_response(gensec_security->credentials, mem_ctx,
|
||||
&flags, challenge_blob, target_info,
|
||||
&lm_response, &nt_response,
|
||||
&lm_session_key, &session_key);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if (!(flags & CLI_CRED_LANMAN_AUTH)) {
|
||||
/* LM Key is still possible, just silly. Fortunetly
|
||||
* we require command line options to end up here */
|
||||
/* gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; */
|
||||
}
|
||||
|
||||
if (!(flags & CLI_CRED_NTLM2)) {
|
||||
/* NTLM2 is incompatible... */
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2;
|
||||
}
|
||||
|
||||
if ((gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY)
|
||||
&& lp_client_lanman_auth() && lm_session_key.length == 16) {
|
||||
DATA_BLOB new_session_key = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
if (lm_response.length == 24) {
|
||||
SMBsesskeygen_lm_sess_key(lm_session_key.data, lm_response.data,
|
||||
new_session_key.data);
|
||||
} else {
|
||||
static const uint8_t zeros[24];
|
||||
SMBsesskeygen_lm_sess_key(lm_session_key.data, zeros,
|
||||
new_session_key.data);
|
||||
}
|
||||
session_key = new_session_key;
|
||||
dump_data_pw("LM session key\n", session_key.data, session_key.length);
|
||||
}
|
||||
|
||||
|
||||
/* Key exchange encryptes a new client-generated session key with
|
||||
the password-derived key */
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
|
||||
/* Make up a new session key */
|
||||
uint8_t client_session_key[16];
|
||||
generate_random_buffer(client_session_key, sizeof(client_session_key));
|
||||
|
||||
/* Encrypt the new session key with the old one */
|
||||
encrypted_session_key = data_blob_talloc(gensec_ntlmssp_state,
|
||||
client_session_key, sizeof(client_session_key));
|
||||
dump_data_pw("KEY_EXCH session key:\n", encrypted_session_key.data, encrypted_session_key.length);
|
||||
arcfour_crypt(encrypted_session_key.data, session_key.data, encrypted_session_key.length);
|
||||
dump_data_pw("KEY_EXCH session key (enc):\n", encrypted_session_key.data, encrypted_session_key.length);
|
||||
|
||||
/* Mark the new session key as the 'real' session key */
|
||||
session_key = data_blob_talloc(mem_ctx, client_session_key, sizeof(client_session_key));
|
||||
}
|
||||
|
||||
DEBUG(3, ("NTLMSSP: Set final flags:\n"));
|
||||
debug_ntlmssp_flags(gensec_ntlmssp_state->neg_flags);
|
||||
|
||||
/* this generates the actual auth packet */
|
||||
if (!msrpc_gen(mem_ctx,
|
||||
out, auth_gen_string,
|
||||
"NTLMSSP",
|
||||
NTLMSSP_AUTH,
|
||||
lm_response.data, lm_response.length,
|
||||
nt_response.data, nt_response.length,
|
||||
domain,
|
||||
user,
|
||||
cli_credentials_get_workstation(gensec_security->credentials),
|
||||
encrypted_session_key.data, encrypted_session_key.length,
|
||||
gensec_ntlmssp_state->neg_flags)) {
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
gensec_ntlmssp_state->session_key = session_key;
|
||||
talloc_steal(gensec_ntlmssp_state, session_key.data);
|
||||
|
||||
talloc_steal(out_mem_ctx, out->data);
|
||||
|
||||
gensec_ntlmssp_state->chal = challenge_blob;
|
||||
gensec_ntlmssp_state->lm_resp = lm_response;
|
||||
talloc_steal(gensec_ntlmssp_state->lm_resp.data, lm_response.data);
|
||||
gensec_ntlmssp_state->nt_resp = nt_response;
|
||||
talloc_steal(gensec_ntlmssp_state->nt_resp.data, nt_response.data);
|
||||
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_DONE;
|
||||
|
||||
if (gensec_security->want_features & (GENSEC_FEATURE_SIGN|GENSEC_FEATURE_SEAL)) {
|
||||
nt_status = ntlmssp_sign_init(gensec_ntlmssp_state);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(1, ("Could not setup NTLMSSP signing/sealing system (error was: %s)\n",
|
||||
nt_errstr(nt_status)));
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
NTSTATUS gensec_ntlmssp_client_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
nt_status = gensec_ntlmssp_start(gensec_security);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
gensec_ntlmssp_state->role = NTLMSSP_CLIENT;
|
||||
|
||||
gensec_ntlmssp_state->get_domain = lp_workgroup;
|
||||
|
||||
gensec_ntlmssp_state->unicode = lp_parm_bool(-1, "ntlmssp_client", "unicode", True);
|
||||
|
||||
gensec_ntlmssp_state->use_nt_response = lp_parm_bool(-1, "ntlmssp_client", "send_nt_reponse", True);
|
||||
|
||||
gensec_ntlmssp_state->allow_lm_key = (lp_client_lanman_auth()
|
||||
&& (lp_parm_bool(-1, "ntlmssp_client", "allow_lm_key", False)
|
||||
|| lp_parm_bool(-1, "ntlmssp_client", "lm_key", False)));
|
||||
|
||||
gensec_ntlmssp_state->use_ntlmv2 = lp_client_ntlmv2_auth();
|
||||
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_INITIAL;
|
||||
|
||||
gensec_ntlmssp_state->neg_flags =
|
||||
NTLMSSP_NEGOTIATE_NTLM |
|
||||
NTLMSSP_REQUEST_TARGET;
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_client", "128bit", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_128;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_client", "56bit", False)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_client", "lm_key", False)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_client", "keyexchange", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_client", "alwayssign", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_client", "ntlm2", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2;
|
||||
} else {
|
||||
/* apparently we can't do ntlmv2 if we don't do ntlm2 */
|
||||
gensec_ntlmssp_state->use_ntlmv2 = False;
|
||||
}
|
||||
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) {
|
||||
/*
|
||||
* We need to set this to allow a later SetPassword
|
||||
* via the SAMR pipe to succeed. Strange.... We could
|
||||
* also add NTLMSSP_NEGOTIATE_SEAL here. JRA.
|
||||
*
|
||||
* Without this, Windows will not create the master key
|
||||
* that it thinks is only used for NTLMSSP signing and
|
||||
* sealing. (It is actually pulled out and used directly)
|
||||
*/
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN;
|
||||
}
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SIGN) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN;
|
||||
}
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SEAL) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL;
|
||||
}
|
||||
|
||||
gensec_security->private_data = gensec_ntlmssp_state;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
simple kerberos5/SPNEGO routines
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
|
||||
Copyright (C) Andrew Bartlett 2002-2003
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "pstring.h"
|
||||
|
||||
/*
|
||||
this is a tiny msrpc packet generator. I am only using this to
|
||||
avoid tying this code to a particular varient of our rpc code. This
|
||||
generator is not general enough for all our rpc needs, its just
|
||||
enough for the spnego/ntlmssp code
|
||||
|
||||
format specifiers are:
|
||||
|
||||
U = unicode string (input is unix string)
|
||||
a = address (input is char *unix_string)
|
||||
(1 byte type, 1 byte length, unicode/ASCII string, all inline)
|
||||
A = ASCII string (input is unix string)
|
||||
B = data blob (pointer + length)
|
||||
b = data blob in header (pointer + length)
|
||||
D
|
||||
d = word (4 bytes)
|
||||
C = constant ascii string
|
||||
*/
|
||||
BOOL msrpc_gen(TALLOC_CTX *mem_ctx, DATA_BLOB *blob,
|
||||
const char *format, ...)
|
||||
{
|
||||
int i;
|
||||
ssize_t n;
|
||||
va_list ap;
|
||||
char *s;
|
||||
uint8_t *b;
|
||||
int head_size=0, data_size=0;
|
||||
int head_ofs, data_ofs;
|
||||
int *intargs;
|
||||
|
||||
DATA_BLOB *pointers;
|
||||
|
||||
pointers = talloc_array(mem_ctx, DATA_BLOB, strlen(format));
|
||||
intargs = talloc_array(pointers, int, strlen(format));
|
||||
|
||||
/* first scan the format to work out the header and body size */
|
||||
va_start(ap, format);
|
||||
for (i=0; format[i]; i++) {
|
||||
switch (format[i]) {
|
||||
case 'U':
|
||||
s = va_arg(ap, char *);
|
||||
head_size += 8;
|
||||
n = push_ucs2_talloc(pointers, (void **)&pointers[i].data, s);
|
||||
if (n == -1) {
|
||||
return False;
|
||||
}
|
||||
pointers[i].length = n;
|
||||
pointers[i].length -= 2;
|
||||
data_size += pointers[i].length;
|
||||
break;
|
||||
case 'A':
|
||||
s = va_arg(ap, char *);
|
||||
head_size += 8;
|
||||
n = push_ascii_talloc(pointers, (char **)&pointers[i].data, s);
|
||||
if (n == -1) {
|
||||
return False;
|
||||
}
|
||||
pointers[i].length = n;
|
||||
pointers[i].length -= 1;
|
||||
data_size += pointers[i].length;
|
||||
break;
|
||||
case 'a':
|
||||
n = va_arg(ap, int);
|
||||
intargs[i] = n;
|
||||
s = va_arg(ap, char *);
|
||||
n = push_ucs2_talloc(pointers, (void **)&pointers[i].data, s);
|
||||
if (n == -1) {
|
||||
return False;
|
||||
}
|
||||
pointers[i].length = n;
|
||||
pointers[i].length -= 2;
|
||||
data_size += pointers[i].length + 4;
|
||||
break;
|
||||
case 'B':
|
||||
b = va_arg(ap, uint8_t *);
|
||||
head_size += 8;
|
||||
pointers[i].data = b;
|
||||
pointers[i].length = va_arg(ap, int);
|
||||
data_size += pointers[i].length;
|
||||
break;
|
||||
case 'b':
|
||||
b = va_arg(ap, uint8_t *);
|
||||
pointers[i].data = b;
|
||||
pointers[i].length = va_arg(ap, int);
|
||||
head_size += pointers[i].length;
|
||||
break;
|
||||
case 'd':
|
||||
n = va_arg(ap, int);
|
||||
intargs[i] = n;
|
||||
head_size += 4;
|
||||
break;
|
||||
case 'C':
|
||||
s = va_arg(ap, char *);
|
||||
pointers[i].data = (uint8_t *)s;
|
||||
pointers[i].length = strlen(s)+1;
|
||||
head_size += pointers[i].length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* allocate the space, then scan the format again to fill in the values */
|
||||
*blob = data_blob_talloc(mem_ctx, NULL, head_size + data_size);
|
||||
|
||||
head_ofs = 0;
|
||||
data_ofs = head_size;
|
||||
|
||||
va_start(ap, format);
|
||||
for (i=0; format[i]; i++) {
|
||||
switch (format[i]) {
|
||||
case 'U':
|
||||
case 'A':
|
||||
case 'B':
|
||||
n = pointers[i].length;
|
||||
SSVAL(blob->data, head_ofs, n); head_ofs += 2;
|
||||
SSVAL(blob->data, head_ofs, n); head_ofs += 2;
|
||||
SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4;
|
||||
if (pointers[i].data && n) /* don't follow null pointers... */
|
||||
memcpy(blob->data+data_ofs, pointers[i].data, n);
|
||||
data_ofs += n;
|
||||
break;
|
||||
case 'a':
|
||||
n = intargs[i];
|
||||
SSVAL(blob->data, data_ofs, n); data_ofs += 2;
|
||||
|
||||
n = pointers[i].length;
|
||||
SSVAL(blob->data, data_ofs, n); data_ofs += 2;
|
||||
if (n >= 0) {
|
||||
memcpy(blob->data+data_ofs, pointers[i].data, n);
|
||||
}
|
||||
data_ofs += n;
|
||||
break;
|
||||
case 'd':
|
||||
n = intargs[i];
|
||||
SIVAL(blob->data, head_ofs, n);
|
||||
head_ofs += 4;
|
||||
break;
|
||||
case 'b':
|
||||
n = pointers[i].length;
|
||||
memcpy(blob->data + head_ofs, pointers[i].data, n);
|
||||
head_ofs += n;
|
||||
break;
|
||||
case 'C':
|
||||
n = pointers[i].length;
|
||||
memcpy(blob->data + head_ofs, pointers[i].data, n);
|
||||
head_ofs += n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
talloc_free(pointers);
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
|
||||
/* a helpful macro to avoid running over the end of our blob */
|
||||
#define NEED_DATA(amount) \
|
||||
if ((head_ofs + amount) > blob->length) { \
|
||||
return False; \
|
||||
}
|
||||
|
||||
/*
|
||||
this is a tiny msrpc packet parser. This the the partner of msrpc_gen
|
||||
|
||||
format specifiers are:
|
||||
|
||||
U = unicode string (output is unix string)
|
||||
A = ascii string
|
||||
B = data blob
|
||||
b = data blob in header
|
||||
d = word (4 bytes)
|
||||
C = constant ascii string
|
||||
*/
|
||||
|
||||
BOOL msrpc_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob,
|
||||
const char *format, ...)
|
||||
{
|
||||
int i;
|
||||
va_list ap;
|
||||
const char **ps, *s;
|
||||
DATA_BLOB *b;
|
||||
size_t head_ofs = 0;
|
||||
uint16_t len1, len2;
|
||||
uint32_t ptr;
|
||||
uint32_t *v;
|
||||
pstring p;
|
||||
|
||||
va_start(ap, format);
|
||||
for (i=0; format[i]; i++) {
|
||||
switch (format[i]) {
|
||||
case 'U':
|
||||
NEED_DATA(8);
|
||||
len1 = SVAL(blob->data, head_ofs); head_ofs += 2;
|
||||
len2 = SVAL(blob->data, head_ofs); head_ofs += 2;
|
||||
ptr = IVAL(blob->data, head_ofs); head_ofs += 4;
|
||||
|
||||
ps = (const char **)va_arg(ap, char **);
|
||||
if (len1 == 0 && len2 == 0) {
|
||||
*ps = "";
|
||||
} else {
|
||||
/* make sure its in the right format - be strict */
|
||||
if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) {
|
||||
return False;
|
||||
}
|
||||
if (len1 & 1) {
|
||||
/* if odd length and unicode */
|
||||
return False;
|
||||
}
|
||||
if (blob->data + ptr < (uint8_t *)ptr || blob->data + ptr < blob->data)
|
||||
return False;
|
||||
|
||||
if (0 < len1) {
|
||||
pull_string(p, blob->data + ptr, sizeof(p),
|
||||
len1, STR_UNICODE|STR_NOALIGN);
|
||||
(*ps) = talloc_strdup(mem_ctx, p);
|
||||
if (!(*ps)) {
|
||||
return False;
|
||||
}
|
||||
} else {
|
||||
(*ps) = "";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'A':
|
||||
NEED_DATA(8);
|
||||
len1 = SVAL(blob->data, head_ofs); head_ofs += 2;
|
||||
len2 = SVAL(blob->data, head_ofs); head_ofs += 2;
|
||||
ptr = IVAL(blob->data, head_ofs); head_ofs += 4;
|
||||
|
||||
ps = (const char **)va_arg(ap, char **);
|
||||
/* make sure its in the right format - be strict */
|
||||
if (len1 == 0 && len2 == 0) {
|
||||
*ps = "";
|
||||
} else {
|
||||
if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) {
|
||||
return False;
|
||||
}
|
||||
|
||||
if (blob->data + ptr < (uint8_t *)ptr || blob->data + ptr < blob->data)
|
||||
return False;
|
||||
|
||||
if (0 < len1) {
|
||||
pull_string(p, blob->data + ptr, sizeof(p),
|
||||
len1, STR_ASCII|STR_NOALIGN);
|
||||
(*ps) = talloc_strdup(mem_ctx, p);
|
||||
if (!(*ps)) {
|
||||
return False;
|
||||
}
|
||||
} else {
|
||||
(*ps) = "";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
NEED_DATA(8);
|
||||
len1 = SVAL(blob->data, head_ofs); head_ofs += 2;
|
||||
len2 = SVAL(blob->data, head_ofs); head_ofs += 2;
|
||||
ptr = IVAL(blob->data, head_ofs); head_ofs += 4;
|
||||
|
||||
b = (DATA_BLOB *)va_arg(ap, void *);
|
||||
if (len1 == 0 && len2 == 0) {
|
||||
*b = data_blob_talloc(mem_ctx, NULL, 0);
|
||||
} else {
|
||||
/* make sure its in the right format - be strict */
|
||||
if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) {
|
||||
return False;
|
||||
}
|
||||
|
||||
if (blob->data + ptr < (uint8_t *)ptr || blob->data + ptr < blob->data)
|
||||
return False;
|
||||
|
||||
*b = data_blob_talloc(mem_ctx, blob->data + ptr, len1);
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
b = (DATA_BLOB *)va_arg(ap, void *);
|
||||
len1 = va_arg(ap, uint_t);
|
||||
/* make sure its in the right format - be strict */
|
||||
NEED_DATA(len1);
|
||||
if (blob->data + head_ofs < (uint8_t *)head_ofs || blob->data + head_ofs < blob->data)
|
||||
return False;
|
||||
|
||||
*b = data_blob_talloc(mem_ctx, blob->data + head_ofs, len1);
|
||||
head_ofs += len1;
|
||||
break;
|
||||
case 'd':
|
||||
v = va_arg(ap, uint32_t *);
|
||||
NEED_DATA(4);
|
||||
*v = IVAL(blob->data, head_ofs); head_ofs += 4;
|
||||
break;
|
||||
case 'C':
|
||||
s = va_arg(ap, char *);
|
||||
|
||||
if (blob->data + head_ofs < (uint8_t *)head_ofs || blob->data + head_ofs < blob->data)
|
||||
return False;
|
||||
|
||||
head_ofs += pull_string(p, blob->data+head_ofs, sizeof(p),
|
||||
blob->length - head_ofs,
|
||||
STR_ASCII|STR_TERMINATE);
|
||||
if (strcmp(s, p) != 0) {
|
||||
return False;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
return True;
|
||||
}
|
||||
@@ -0,0 +1,852 @@
|
||||
/*
|
||||
Unix SMB/Netbios implementation.
|
||||
Version 3.0
|
||||
handle NLTMSSP, client server side parsing
|
||||
|
||||
Copyright (C) Andrew Tridgell 2001
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/ntlmssp/ntlmssp.h"
|
||||
#include "auth/ntlmssp/msrpc_parse.h"
|
||||
#include "lib/crypto/crypto.h"
|
||||
#include "pstring.h"
|
||||
#include "system/filesys.h"
|
||||
#include "libcli/auth/libcli_auth.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
#include "auth/auth.h"
|
||||
|
||||
/**
|
||||
* Set a username on an NTLMSSP context - ensures it is talloc()ed
|
||||
*
|
||||
*/
|
||||
|
||||
static NTSTATUS ntlmssp_set_username(struct gensec_ntlmssp_state *gensec_ntlmssp_state, const char *user)
|
||||
{
|
||||
if (!user) {
|
||||
/* it should be at least "" */
|
||||
DEBUG(1, ("NTLMSSP failed to set username - cannot accept NULL username\n"));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
gensec_ntlmssp_state->user = talloc_strdup(gensec_ntlmssp_state, user);
|
||||
if (!gensec_ntlmssp_state->user) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a domain on an NTLMSSP context - ensures it is talloc()ed
|
||||
*
|
||||
*/
|
||||
static NTSTATUS ntlmssp_set_domain(struct gensec_ntlmssp_state *gensec_ntlmssp_state, const char *domain)
|
||||
{
|
||||
gensec_ntlmssp_state->domain = talloc_strdup(gensec_ntlmssp_state, domain);
|
||||
if (!gensec_ntlmssp_state->domain) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a workstation on an NTLMSSP context - ensures it is talloc()ed
|
||||
*
|
||||
*/
|
||||
static NTSTATUS ntlmssp_set_workstation(struct gensec_ntlmssp_state *gensec_ntlmssp_state, const char *workstation)
|
||||
{
|
||||
gensec_ntlmssp_state->workstation = talloc_strdup(gensec_ntlmssp_state, workstation);
|
||||
if (!gensec_ntlmssp_state->workstation) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine correct target name flags for reply, given server role
|
||||
* and negotiated flags
|
||||
*
|
||||
* @param gensec_ntlmssp_state NTLMSSP State
|
||||
* @param neg_flags The flags from the packet
|
||||
* @param chal_flags The flags to be set in the reply packet
|
||||
* @return The 'target name' string.
|
||||
*/
|
||||
|
||||
static const char *ntlmssp_target_name(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
uint32_t neg_flags, uint32_t *chal_flags)
|
||||
{
|
||||
if (neg_flags & NTLMSSP_REQUEST_TARGET) {
|
||||
*chal_flags |= NTLMSSP_CHAL_TARGET_INFO;
|
||||
*chal_flags |= NTLMSSP_REQUEST_TARGET;
|
||||
if (gensec_ntlmssp_state->server_role == ROLE_STANDALONE) {
|
||||
*chal_flags |= NTLMSSP_TARGET_TYPE_SERVER;
|
||||
return gensec_ntlmssp_state->server_name;
|
||||
} else {
|
||||
*chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN;
|
||||
return gensec_ntlmssp_state->get_domain();
|
||||
};
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Andrew, please remove these totally bogus calls when you get time
|
||||
*/
|
||||
static BOOL get_myfullname(char *my_name)
|
||||
{
|
||||
pstring hostname;
|
||||
|
||||
*hostname = 0;
|
||||
|
||||
/* get my host name */
|
||||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||||
DEBUG(0,("gethostname failed\n"));
|
||||
return False;
|
||||
}
|
||||
|
||||
/* Ensure null termination. */
|
||||
hostname[sizeof(hostname)-1] = '\0';
|
||||
|
||||
if (my_name)
|
||||
fstrcpy(my_name, hostname);
|
||||
return True;
|
||||
}
|
||||
|
||||
static BOOL get_mydomname(char *my_domname)
|
||||
{
|
||||
pstring hostname;
|
||||
char *p;
|
||||
|
||||
/* arrgh! relies on full name in system */
|
||||
|
||||
*hostname = 0;
|
||||
/* get my host name */
|
||||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||||
DEBUG(0,("gethostname failed\n"));
|
||||
return False;
|
||||
}
|
||||
|
||||
/* Ensure null termination. */
|
||||
hostname[sizeof(hostname)-1] = '\0';
|
||||
|
||||
p = strchr_m(hostname, '.');
|
||||
|
||||
if (!p)
|
||||
return False;
|
||||
|
||||
p++;
|
||||
|
||||
if (my_domname)
|
||||
fstrcpy(my_domname, p);
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Next state function for the Negotiate packet
|
||||
*
|
||||
* @param gensec_security GENSEC state
|
||||
* @param out_mem_ctx Memory context for *out
|
||||
* @param in The request, as a DATA_BLOB. reply.data must be NULL
|
||||
* @param out The reply, as an allocated DATA_BLOB, caller to free.
|
||||
* @return Errors or MORE_PROCESSING_REQUIRED if (normal) a reply is required.
|
||||
*/
|
||||
|
||||
NTSTATUS ntlmssp_server_negotiate(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
DATA_BLOB struct_blob;
|
||||
fstring dnsname, dnsdomname;
|
||||
uint32_t neg_flags = 0;
|
||||
uint32_t ntlmssp_command, chal_flags;
|
||||
const uint8_t *cryptkey;
|
||||
const char *target_name;
|
||||
|
||||
/* parse the NTLMSSP packet */
|
||||
#if 0
|
||||
file_save("ntlmssp_negotiate.dat", request.data, request.length);
|
||||
#endif
|
||||
|
||||
if (in.length) {
|
||||
if ((in.length < 16) || !msrpc_parse(out_mem_ctx, &in, "Cdd",
|
||||
"NTLMSSP",
|
||||
&ntlmssp_command,
|
||||
&neg_flags)) {
|
||||
DEBUG(1, ("ntlmssp_server_negotiate: failed to parse "
|
||||
"NTLMSSP Negotiate of length %u:\n",
|
||||
(unsigned int)in.length ));
|
||||
dump_data(2, in.data, in.length);
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
debug_ntlmssp_flags(neg_flags);
|
||||
}
|
||||
|
||||
ntlmssp_handle_neg_flags(gensec_ntlmssp_state, neg_flags, gensec_ntlmssp_state->allow_lm_key);
|
||||
|
||||
/* Ask our caller what challenge they would like in the packet */
|
||||
cryptkey = gensec_ntlmssp_state->get_challenge(gensec_ntlmssp_state);
|
||||
|
||||
/* Check if we may set the challenge */
|
||||
if (!gensec_ntlmssp_state->may_set_challenge(gensec_ntlmssp_state)) {
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2;
|
||||
}
|
||||
|
||||
/* The flags we send back are not just the negotiated flags,
|
||||
* they are also 'what is in this packet'. Therfore, we
|
||||
* operate on 'chal_flags' from here on
|
||||
*/
|
||||
|
||||
chal_flags = gensec_ntlmssp_state->neg_flags;
|
||||
|
||||
/* get the right name to fill in as 'target' */
|
||||
target_name = ntlmssp_target_name(gensec_ntlmssp_state,
|
||||
neg_flags, &chal_flags);
|
||||
if (target_name == NULL)
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
|
||||
gensec_ntlmssp_state->chal = data_blob_talloc(gensec_ntlmssp_state, cryptkey, 8);
|
||||
gensec_ntlmssp_state->internal_chal = data_blob_talloc(gensec_ntlmssp_state, cryptkey, 8);
|
||||
|
||||
/* This should be a 'netbios domain -> DNS domain' mapping */
|
||||
dnsdomname[0] = '\0';
|
||||
get_mydomname(dnsdomname);
|
||||
strlower_m(dnsdomname);
|
||||
|
||||
dnsname[0] = '\0';
|
||||
get_myfullname(dnsname);
|
||||
|
||||
/* This creates the 'blob' of names that appears at the end of the packet */
|
||||
if (chal_flags & NTLMSSP_CHAL_TARGET_INFO)
|
||||
{
|
||||
const char *target_name_dns = "";
|
||||
if (chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN) {
|
||||
target_name_dns = dnsdomname;
|
||||
} else if (chal_flags |= NTLMSSP_TARGET_TYPE_SERVER) {
|
||||
target_name_dns = dnsname;
|
||||
}
|
||||
|
||||
msrpc_gen(out_mem_ctx,
|
||||
&struct_blob, "aaaaa",
|
||||
NTLMSSP_NAME_TYPE_DOMAIN, target_name,
|
||||
NTLMSSP_NAME_TYPE_SERVER, gensec_ntlmssp_state->server_name,
|
||||
NTLMSSP_NAME_TYPE_DOMAIN_DNS, dnsdomname,
|
||||
NTLMSSP_NAME_TYPE_SERVER_DNS, dnsname,
|
||||
0, "");
|
||||
} else {
|
||||
struct_blob = data_blob(NULL, 0);
|
||||
}
|
||||
|
||||
{
|
||||
/* Marshel the packet in the right format, be it unicode or ASCII */
|
||||
const char *gen_string;
|
||||
if (gensec_ntlmssp_state->unicode) {
|
||||
gen_string = "CdUdbddB";
|
||||
} else {
|
||||
gen_string = "CdAdbddB";
|
||||
}
|
||||
|
||||
msrpc_gen(out_mem_ctx,
|
||||
out, gen_string,
|
||||
"NTLMSSP",
|
||||
NTLMSSP_CHALLENGE,
|
||||
target_name,
|
||||
chal_flags,
|
||||
cryptkey, 8,
|
||||
0, 0,
|
||||
struct_blob.data, struct_blob.length);
|
||||
}
|
||||
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_AUTH;
|
||||
|
||||
return NT_STATUS_MORE_PROCESSING_REQUIRED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next state function for the Authenticate packet
|
||||
*
|
||||
* @param gensec_ntlmssp_state NTLMSSP State
|
||||
* @param request The request, as a DATA_BLOB
|
||||
* @return Errors or NT_STATUS_OK.
|
||||
*/
|
||||
|
||||
static NTSTATUS ntlmssp_server_preauth(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
const DATA_BLOB request)
|
||||
{
|
||||
uint32_t ntlmssp_command, auth_flags;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
uint8_t session_nonce_hash[16];
|
||||
|
||||
const char *parse_string;
|
||||
char *domain = NULL;
|
||||
char *user = NULL;
|
||||
char *workstation = NULL;
|
||||
|
||||
#if 0
|
||||
file_save("ntlmssp_auth.dat", request.data, request.length);
|
||||
#endif
|
||||
|
||||
if (gensec_ntlmssp_state->unicode) {
|
||||
parse_string = "CdBBUUUBd";
|
||||
} else {
|
||||
parse_string = "CdBBAAABd";
|
||||
}
|
||||
|
||||
/* zero these out */
|
||||
data_blob_free(&gensec_ntlmssp_state->lm_resp);
|
||||
data_blob_free(&gensec_ntlmssp_state->nt_resp);
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
|
||||
gensec_ntlmssp_state->user = NULL;
|
||||
gensec_ntlmssp_state->domain = NULL;
|
||||
gensec_ntlmssp_state->workstation = NULL;
|
||||
|
||||
/* now the NTLMSSP encoded auth hashes */
|
||||
if (!msrpc_parse(gensec_ntlmssp_state,
|
||||
&request, parse_string,
|
||||
"NTLMSSP",
|
||||
&ntlmssp_command,
|
||||
&gensec_ntlmssp_state->lm_resp,
|
||||
&gensec_ntlmssp_state->nt_resp,
|
||||
&domain,
|
||||
&user,
|
||||
&workstation,
|
||||
&gensec_ntlmssp_state->encrypted_session_key,
|
||||
&auth_flags)) {
|
||||
DEBUG(10, ("ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal):\n"));
|
||||
dump_data(10, request.data, request.length);
|
||||
|
||||
/* zero this out */
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
auth_flags = 0;
|
||||
|
||||
/* Try again with a shorter string (Win9X truncates this packet) */
|
||||
if (gensec_ntlmssp_state->unicode) {
|
||||
parse_string = "CdBBUUU";
|
||||
} else {
|
||||
parse_string = "CdBBAAA";
|
||||
}
|
||||
|
||||
/* now the NTLMSSP encoded auth hashes */
|
||||
if (!msrpc_parse(gensec_ntlmssp_state,
|
||||
&request, parse_string,
|
||||
"NTLMSSP",
|
||||
&ntlmssp_command,
|
||||
&gensec_ntlmssp_state->lm_resp,
|
||||
&gensec_ntlmssp_state->nt_resp,
|
||||
&domain,
|
||||
&user,
|
||||
&workstation)) {
|
||||
DEBUG(1, ("ntlmssp_server_auth: failed to parse NTLMSSP:\n"));
|
||||
dump_data(2, request.data, request.length);
|
||||
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
if (auth_flags)
|
||||
ntlmssp_handle_neg_flags(gensec_ntlmssp_state, auth_flags, gensec_ntlmssp_state->allow_lm_key);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(gensec_ntlmssp_state, domain))) {
|
||||
/* zero this out */
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(gensec_ntlmssp_state, user))) {
|
||||
/* zero this out */
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_workstation(gensec_ntlmssp_state, workstation))) {
|
||||
/* zero this out */
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu\n",
|
||||
gensec_ntlmssp_state->user, gensec_ntlmssp_state->domain, gensec_ntlmssp_state->workstation, (unsigned long)gensec_ntlmssp_state->lm_resp.length, (unsigned long)gensec_ntlmssp_state->nt_resp.length));
|
||||
|
||||
#if 0
|
||||
file_save("nthash1.dat", &gensec_ntlmssp_state->nt_resp.data, &gensec_ntlmssp_state->nt_resp.length);
|
||||
file_save("lmhash1.dat", &gensec_ntlmssp_state->lm_resp.data, &gensec_ntlmssp_state->lm_resp.length);
|
||||
#endif
|
||||
|
||||
/* NTLM2 uses a 'challenge' that is made of up both the server challenge, and a
|
||||
client challenge
|
||||
|
||||
However, the NTLM2 flag may still be set for the real NTLMv2 logins, be careful.
|
||||
*/
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
if (gensec_ntlmssp_state->nt_resp.length == 24 && gensec_ntlmssp_state->lm_resp.length == 24) {
|
||||
struct MD5Context md5_session_nonce_ctx;
|
||||
SMB_ASSERT(gensec_ntlmssp_state->internal_chal.data
|
||||
&& gensec_ntlmssp_state->internal_chal.length == 8);
|
||||
|
||||
gensec_ntlmssp_state->doing_ntlm2 = True;
|
||||
|
||||
memcpy(gensec_ntlmssp_state->crypt.ntlm2.session_nonce, gensec_ntlmssp_state->internal_chal.data, 8);
|
||||
memcpy(&gensec_ntlmssp_state->crypt.ntlm2.session_nonce[8], gensec_ntlmssp_state->lm_resp.data, 8);
|
||||
|
||||
MD5Init(&md5_session_nonce_ctx);
|
||||
MD5Update(&md5_session_nonce_ctx, gensec_ntlmssp_state->crypt.ntlm2.session_nonce, 16);
|
||||
MD5Final(session_nonce_hash, &md5_session_nonce_ctx);
|
||||
|
||||
gensec_ntlmssp_state->chal = data_blob_talloc(gensec_ntlmssp_state,
|
||||
session_nonce_hash, 8);
|
||||
|
||||
/* LM response is no longer useful, zero it out */
|
||||
data_blob_free(&gensec_ntlmssp_state->lm_resp);
|
||||
|
||||
/* We changed the effective challenge - set it */
|
||||
if (!NT_STATUS_IS_OK(nt_status =
|
||||
gensec_ntlmssp_state->set_challenge(gensec_ntlmssp_state,
|
||||
&gensec_ntlmssp_state->chal))) {
|
||||
/* zero this out */
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* LM Key is incompatible... */
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
}
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next state function for the Authenticate packet
|
||||
* (after authentication - figures out the session keys etc)
|
||||
*
|
||||
* @param gensec_ntlmssp_state NTLMSSP State
|
||||
* @return Errors or NT_STATUS_OK.
|
||||
*/
|
||||
|
||||
static NTSTATUS ntlmssp_server_postauth(struct gensec_security *gensec_security,
|
||||
DATA_BLOB *user_session_key,
|
||||
DATA_BLOB *lm_session_key)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
NTSTATUS nt_status;
|
||||
DATA_BLOB session_key = data_blob(NULL, 0);
|
||||
|
||||
if (user_session_key)
|
||||
dump_data_pw("USER session key:\n", user_session_key->data, user_session_key->length);
|
||||
|
||||
if (lm_session_key)
|
||||
dump_data_pw("LM first-8:\n", lm_session_key->data, lm_session_key->length);
|
||||
|
||||
/* Handle the different session key derivation for NTLM2 */
|
||||
if (gensec_ntlmssp_state->doing_ntlm2) {
|
||||
if (user_session_key && user_session_key->data && user_session_key->length == 16) {
|
||||
session_key = data_blob_talloc(gensec_ntlmssp_state, NULL, 16);
|
||||
hmac_md5(user_session_key->data, gensec_ntlmssp_state->crypt.ntlm2.session_nonce,
|
||||
sizeof(gensec_ntlmssp_state->crypt.ntlm2.session_nonce), session_key.data);
|
||||
DEBUG(10,("ntlmssp_server_auth: Created NTLM2 session key.\n"));
|
||||
dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length);
|
||||
|
||||
} else {
|
||||
DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM2 session key.\n"));
|
||||
session_key = data_blob(NULL, 0);
|
||||
}
|
||||
} else if ((gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY)
|
||||
/* Ensure we can never get here on NTLMv2 */
|
||||
&& (gensec_ntlmssp_state->nt_resp.length == 0 || gensec_ntlmssp_state->nt_resp.length == 24)) {
|
||||
|
||||
if (lm_session_key && lm_session_key->data && lm_session_key->length >= 8) {
|
||||
if (gensec_ntlmssp_state->lm_resp.data && gensec_ntlmssp_state->lm_resp.length == 24) {
|
||||
session_key = data_blob_talloc(gensec_ntlmssp_state, NULL, 16);
|
||||
SMBsesskeygen_lm_sess_key(lm_session_key->data, gensec_ntlmssp_state->lm_resp.data,
|
||||
session_key.data);
|
||||
DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n"));
|
||||
dump_data_pw("LM session key:\n", session_key.data, session_key.length);
|
||||
} else {
|
||||
|
||||
/* When there is no LM response, just use zeros */
|
||||
static const uint8_t zeros[24];
|
||||
session_key = data_blob_talloc(gensec_ntlmssp_state, NULL, 16);
|
||||
SMBsesskeygen_lm_sess_key(zeros, zeros,
|
||||
session_key.data);
|
||||
DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n"));
|
||||
dump_data_pw("LM session key:\n", session_key.data, session_key.length);
|
||||
}
|
||||
} else {
|
||||
/* LM Key not selected */
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
|
||||
DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM session key.\n"));
|
||||
session_key = data_blob(NULL, 0);
|
||||
}
|
||||
|
||||
} else if (user_session_key && user_session_key->data) {
|
||||
session_key = *user_session_key;
|
||||
DEBUG(10,("ntlmssp_server_auth: Using unmodified nt session key.\n"));
|
||||
dump_data_pw("unmodified session key:\n", session_key.data, session_key.length);
|
||||
|
||||
/* LM Key not selected */
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
|
||||
} else if (lm_session_key && lm_session_key->data) {
|
||||
/* Very weird to have LM key, but no user session key, but anyway.. */
|
||||
session_key = *lm_session_key;
|
||||
DEBUG(10,("ntlmssp_server_auth: Using unmodified lm session key.\n"));
|
||||
dump_data_pw("unmodified session key:\n", session_key.data, session_key.length);
|
||||
|
||||
/* LM Key not selected */
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
|
||||
} else {
|
||||
DEBUG(10,("ntlmssp_server_auth: Failed to create unmodified session key.\n"));
|
||||
session_key = data_blob(NULL, 0);
|
||||
|
||||
/* LM Key not selected */
|
||||
gensec_ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
|
||||
}
|
||||
|
||||
/* With KEY_EXCH, the client supplies the proposed session key,
|
||||
but encrypts it with the long-term key */
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
|
||||
if (!gensec_ntlmssp_state->encrypted_session_key.data
|
||||
|| gensec_ntlmssp_state->encrypted_session_key.length != 16) {
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
DEBUG(1, ("Client-supplied KEY_EXCH session key was of invalid length (%u)!\n",
|
||||
(unsigned)gensec_ntlmssp_state->encrypted_session_key.length));
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
} else if (!session_key.data || session_key.length != 16) {
|
||||
DEBUG(5, ("server session key is invalid (len == %u), cannot do KEY_EXCH!\n",
|
||||
(unsigned)session_key.length));
|
||||
gensec_ntlmssp_state->session_key = session_key;
|
||||
} else {
|
||||
dump_data_pw("KEY_EXCH session key (enc):\n",
|
||||
gensec_ntlmssp_state->encrypted_session_key.data,
|
||||
gensec_ntlmssp_state->encrypted_session_key.length);
|
||||
arcfour_crypt(gensec_ntlmssp_state->encrypted_session_key.data,
|
||||
session_key.data,
|
||||
gensec_ntlmssp_state->encrypted_session_key.length);
|
||||
gensec_ntlmssp_state->session_key = data_blob_talloc(gensec_ntlmssp_state,
|
||||
gensec_ntlmssp_state->encrypted_session_key.data,
|
||||
gensec_ntlmssp_state->encrypted_session_key.length);
|
||||
dump_data_pw("KEY_EXCH session key:\n", gensec_ntlmssp_state->encrypted_session_key.data,
|
||||
gensec_ntlmssp_state->encrypted_session_key.length);
|
||||
}
|
||||
} else {
|
||||
gensec_ntlmssp_state->session_key = session_key;
|
||||
}
|
||||
|
||||
/* keep the session key around on the new context */
|
||||
talloc_steal(gensec_ntlmssp_state, session_key.data);
|
||||
|
||||
if ((gensec_security->want_features & GENSEC_FEATURE_SIGN)
|
||||
|| (gensec_security->want_features & GENSEC_FEATURE_SEAL)) {
|
||||
nt_status = ntlmssp_sign_init(gensec_ntlmssp_state);
|
||||
} else {
|
||||
nt_status = NT_STATUS_OK;
|
||||
}
|
||||
|
||||
data_blob_free(&gensec_ntlmssp_state->encrypted_session_key);
|
||||
|
||||
/* allow arbitarily many authentications, but watch that this will cause a
|
||||
memory leak, until the gensec_ntlmssp_state is shutdown
|
||||
*/
|
||||
|
||||
if (gensec_ntlmssp_state->server_multiple_authentications) {
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_AUTH;
|
||||
} else {
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_DONE;
|
||||
}
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Next state function for the Authenticate packet
|
||||
*
|
||||
* @param gensec_security GENSEC state
|
||||
* @param out_mem_ctx Memory context for *out
|
||||
* @param in The request, as a DATA_BLOB. reply.data must be NULL
|
||||
* @param out The reply, as an allocated DATA_BLOB, caller to free.
|
||||
* @return Errors or NT_STATUS_OK if authentication sucessful
|
||||
*/
|
||||
|
||||
NTSTATUS ntlmssp_server_auth(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *out_mem_ctx,
|
||||
const DATA_BLOB in, DATA_BLOB *out)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
DATA_BLOB user_session_key = data_blob(NULL, 0);
|
||||
DATA_BLOB lm_session_key = data_blob(NULL, 0);
|
||||
NTSTATUS nt_status;
|
||||
|
||||
TALLOC_CTX *mem_ctx = talloc_new(out_mem_ctx);
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* zero the outbound NTLMSSP packet */
|
||||
*out = data_blob_talloc(out_mem_ctx, NULL, 0);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status = ntlmssp_server_preauth(gensec_ntlmssp_state, in))) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note we don't check here for NTLMv2 auth settings. If NTLMv2 auth
|
||||
* is required (by "ntlm auth = no" and "lm auth = no" being set in the
|
||||
* smb.conf file) and no NTLMv2 response was sent then the password check
|
||||
* will fail here. JRA.
|
||||
*/
|
||||
|
||||
/* Finally, actually ask if the password is OK */
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status = gensec_ntlmssp_state->check_password(gensec_ntlmssp_state, mem_ctx,
|
||||
&user_session_key, &lm_session_key))) {
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if (gensec_security->want_features
|
||||
& (GENSEC_FEATURE_SIGN|GENSEC_FEATURE_SEAL|GENSEC_FEATURE_SESSION_KEY)) {
|
||||
nt_status = ntlmssp_server_postauth(gensec_security, &user_session_key, &lm_session_key);
|
||||
talloc_free(mem_ctx);
|
||||
return nt_status;
|
||||
} else {
|
||||
gensec_ntlmssp_state->session_key = data_blob(NULL, 0);
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the challenge as determined by the authentication subsystem
|
||||
* @return an 8 byte random challenge
|
||||
*/
|
||||
|
||||
static const uint8_t *auth_ntlmssp_get_challenge(const struct gensec_ntlmssp_state *gensec_ntlmssp_state)
|
||||
{
|
||||
NTSTATUS status;
|
||||
const uint8_t *chal;
|
||||
|
||||
status = auth_get_challenge(gensec_ntlmssp_state->auth_context, &chal);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return chal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some authentication methods 'fix' the challenge, so we may not be able to set it
|
||||
*
|
||||
* @return If the effective challenge used by the auth subsystem may be modified
|
||||
*/
|
||||
static BOOL auth_ntlmssp_may_set_challenge(const struct gensec_ntlmssp_state *gensec_ntlmssp_state)
|
||||
{
|
||||
return auth_challenge_may_be_modified(gensec_ntlmssp_state->auth_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* NTLM2 authentication modifies the effective challenge,
|
||||
* @param challenge The new challenge value
|
||||
*/
|
||||
static NTSTATUS auth_ntlmssp_set_challenge(struct gensec_ntlmssp_state *gensec_ntlmssp_state, DATA_BLOB *challenge)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_context *auth_context = gensec_ntlmssp_state->auth_context;
|
||||
const uint8_t *chal;
|
||||
|
||||
if (challenge->length != 8) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
chal = challenge->data;
|
||||
|
||||
nt_status = auth_context_set_challenge(auth_context, chal, "NTLMSSP callback (NTLM2)");
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the password on an NTLMSSP login.
|
||||
*
|
||||
* Return the session keys used on the connection.
|
||||
*/
|
||||
|
||||
static NTSTATUS auth_ntlmssp_check_password(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
DATA_BLOB *user_session_key, DATA_BLOB *lm_session_key)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct auth_usersupplied_info *user_info = talloc(mem_ctx, struct auth_usersupplied_info);
|
||||
if (!user_info) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
user_info->logon_parameters = MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT;
|
||||
user_info->flags = 0;
|
||||
user_info->mapped_state = False;
|
||||
user_info->client.account_name = gensec_ntlmssp_state->user;
|
||||
user_info->client.domain_name = gensec_ntlmssp_state->domain;
|
||||
user_info->workstation_name = gensec_ntlmssp_state->workstation;
|
||||
user_info->remote_host = gensec_get_peer_addr(gensec_ntlmssp_state->gensec_security);
|
||||
|
||||
user_info->password_state = AUTH_PASSWORD_RESPONSE;
|
||||
user_info->password.response.lanman = gensec_ntlmssp_state->lm_resp;
|
||||
user_info->password.response.lanman.data = talloc_steal(user_info, gensec_ntlmssp_state->lm_resp.data);
|
||||
user_info->password.response.nt = gensec_ntlmssp_state->nt_resp;
|
||||
user_info->password.response.nt.data = talloc_steal(user_info, gensec_ntlmssp_state->nt_resp.data);
|
||||
|
||||
nt_status = auth_check_password(gensec_ntlmssp_state->auth_context, mem_ctx,
|
||||
user_info, &gensec_ntlmssp_state->server_info);
|
||||
talloc_free(user_info);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
talloc_steal(gensec_ntlmssp_state, gensec_ntlmssp_state->server_info);
|
||||
|
||||
if (gensec_ntlmssp_state->server_info->user_session_key.length) {
|
||||
DEBUG(10, ("Got NT session key of length %u\n",
|
||||
(unsigned)gensec_ntlmssp_state->server_info->user_session_key.length));
|
||||
if (!talloc_reference(mem_ctx, gensec_ntlmssp_state->server_info->user_session_key.data)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
*user_session_key = gensec_ntlmssp_state->server_info->user_session_key;
|
||||
}
|
||||
if (gensec_ntlmssp_state->server_info->lm_session_key.length) {
|
||||
DEBUG(10, ("Got LM session key of length %u\n",
|
||||
(unsigned)gensec_ntlmssp_state->server_info->lm_session_key.length));
|
||||
if (!talloc_reference(mem_ctx, gensec_ntlmssp_state->server_info->lm_session_key.data)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
*lm_session_key = gensec_ntlmssp_state->server_info->lm_session_key;
|
||||
}
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the credentials of a logged on user, including session keys
|
||||
* etc.
|
||||
*
|
||||
* Only valid after a successful authentication
|
||||
*
|
||||
* May only be called once per authentication.
|
||||
*
|
||||
*/
|
||||
|
||||
NTSTATUS gensec_ntlmssp_session_info(struct gensec_security *gensec_security,
|
||||
struct auth_session_info **session_info)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
nt_status = auth_generate_session_info(gensec_ntlmssp_state, gensec_ntlmssp_state->server_info, session_info);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
(*session_info)->session_key = data_blob_talloc(*session_info,
|
||||
gensec_ntlmssp_state->session_key.data,
|
||||
gensec_ntlmssp_state->session_key.length);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start NTLMSSP on the server side
|
||||
*
|
||||
*/
|
||||
NTSTATUS gensec_ntlmssp_server_start(struct gensec_security *gensec_security)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state;
|
||||
|
||||
nt_status = gensec_ntlmssp_start(gensec_security);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
gensec_ntlmssp_state->role = NTLMSSP_SERVER;
|
||||
|
||||
gensec_ntlmssp_state->workstation = NULL;
|
||||
gensec_ntlmssp_state->server_name = lp_netbios_name();
|
||||
|
||||
gensec_ntlmssp_state->get_domain = lp_workgroup;
|
||||
|
||||
gensec_ntlmssp_state->expected_state = NTLMSSP_NEGOTIATE;
|
||||
|
||||
gensec_ntlmssp_state->allow_lm_key = (lp_lanman_auth()
|
||||
&& lp_parm_bool(-1, "ntlmssp_server", "allow_lm_key", False));
|
||||
|
||||
gensec_ntlmssp_state->server_multiple_authentications = False;
|
||||
|
||||
gensec_ntlmssp_state->neg_flags =
|
||||
NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_UNKNOWN_02000000;
|
||||
|
||||
gensec_ntlmssp_state->lm_resp = data_blob(NULL, 0);
|
||||
gensec_ntlmssp_state->nt_resp = data_blob(NULL, 0);
|
||||
gensec_ntlmssp_state->encrypted_session_key = data_blob(NULL, 0);
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_server", "128bit", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_128;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_server", "56bit", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_server", "keyexchange", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_server", "alwayssign", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
||||
}
|
||||
|
||||
if (lp_parm_bool(-1, "ntlmssp_server", "ntlm2", True)) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2;
|
||||
}
|
||||
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SIGN) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN;
|
||||
}
|
||||
if (gensec_security->want_features & GENSEC_FEATURE_SEAL) {
|
||||
gensec_ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL;
|
||||
}
|
||||
|
||||
nt_status = auth_context_create(gensec_ntlmssp_state, lp_auth_methods(),
|
||||
gensec_security->event_ctx,
|
||||
gensec_security->msg_ctx,
|
||||
&gensec_ntlmssp_state->auth_context);
|
||||
NT_STATUS_NOT_OK_RETURN(nt_status);
|
||||
|
||||
gensec_ntlmssp_state->get_challenge = auth_ntlmssp_get_challenge;
|
||||
gensec_ntlmssp_state->may_set_challenge = auth_ntlmssp_may_set_challenge;
|
||||
gensec_ntlmssp_state->set_challenge = auth_ntlmssp_set_challenge;
|
||||
gensec_ntlmssp_state->check_password = auth_ntlmssp_check_password;
|
||||
gensec_ntlmssp_state->server_role = lp_server_role();
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
* Unix SMB/CIFS implementation.
|
||||
* Version 3.0
|
||||
* NTLMSSP Signing routines
|
||||
* Copyright (C) Luke Kenneth Casson Leighton 1996-2001
|
||||
* Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2005
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "auth/ntlmssp/ntlmssp.h"
|
||||
#include "auth/ntlmssp/msrpc_parse.h"
|
||||
#include "lib/crypto/crypto.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
#include "auth/gensec/gensec.h"
|
||||
|
||||
#define CLI_SIGN "session key to client-to-server signing key magic constant"
|
||||
#define CLI_SEAL "session key to client-to-server sealing key magic constant"
|
||||
#define SRV_SIGN "session key to server-to-client signing key magic constant"
|
||||
#define SRV_SEAL "session key to server-to-client sealing key magic constant"
|
||||
|
||||
/**
|
||||
* Some notes on the NTLM2 code:
|
||||
*
|
||||
* NTLM2 is a AEAD system. This means that the data encrypted is not
|
||||
* all the data that is signed. In DCE-RPC case, the headers of the
|
||||
* DCE-RPC packets are also signed. This prevents some of the
|
||||
* fun-and-games one might have by changing them.
|
||||
*
|
||||
*/
|
||||
|
||||
static void calc_ntlmv2_key(TALLOC_CTX *mem_ctx,
|
||||
DATA_BLOB *subkey,
|
||||
DATA_BLOB session_key,
|
||||
const char *constant)
|
||||
{
|
||||
struct MD5Context ctx3;
|
||||
*subkey = data_blob_talloc(mem_ctx, NULL, 16);
|
||||
MD5Init(&ctx3);
|
||||
MD5Update(&ctx3, session_key.data, session_key.length);
|
||||
MD5Update(&ctx3, (const uint8_t *)constant, strlen(constant)+1);
|
||||
MD5Final(subkey->data, &ctx3);
|
||||
}
|
||||
|
||||
enum ntlmssp_direction {
|
||||
NTLMSSP_SEND,
|
||||
NTLMSSP_RECEIVE
|
||||
};
|
||||
|
||||
static NTSTATUS ntlmssp_make_packet_signature(struct gensec_ntlmssp_state *gensec_ntlmssp_state,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
enum ntlmssp_direction direction,
|
||||
DATA_BLOB *sig, BOOL encrypt_sig)
|
||||
{
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
|
||||
HMACMD5Context ctx;
|
||||
uint8_t digest[16];
|
||||
uint8_t seq_num[4];
|
||||
|
||||
*sig = data_blob_talloc(sig_mem_ctx, NULL, NTLMSSP_SIG_SIZE);
|
||||
if (!sig->data) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case NTLMSSP_SEND:
|
||||
SIVAL(seq_num, 0, gensec_ntlmssp_state->crypt.ntlm2.send_seq_num);
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_seq_num++;
|
||||
hmac_md5_init_limK_to_64(gensec_ntlmssp_state->crypt.ntlm2.send_sign_key.data,
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_sign_key.length, &ctx);
|
||||
break;
|
||||
case NTLMSSP_RECEIVE:
|
||||
SIVAL(seq_num, 0, gensec_ntlmssp_state->crypt.ntlm2.recv_seq_num);
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_seq_num++;
|
||||
hmac_md5_init_limK_to_64(gensec_ntlmssp_state->crypt.ntlm2.recv_sign_key.data,
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_sign_key.length, &ctx);
|
||||
break;
|
||||
}
|
||||
hmac_md5_update(seq_num, sizeof(seq_num), &ctx);
|
||||
hmac_md5_update(whole_pdu, pdu_length, &ctx);
|
||||
hmac_md5_final(digest, &ctx);
|
||||
|
||||
if (encrypt_sig && gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
|
||||
switch (direction) {
|
||||
case NTLMSSP_SEND:
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state, digest, 8);
|
||||
break;
|
||||
case NTLMSSP_RECEIVE:
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm2.recv_seal_arcfour_state, digest, 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SIVAL(sig->data, 0, NTLMSSP_SIGN_VERSION);
|
||||
memcpy(sig->data + 4, digest, 8);
|
||||
memcpy(sig->data + 12, seq_num, 4);
|
||||
|
||||
DEBUG(10, ("NTLM2: created signature over %llu bytes of input:\n", (unsigned long long)pdu_length));
|
||||
dump_data(11, sig->data, sig->length);
|
||||
|
||||
} else {
|
||||
uint32_t crc;
|
||||
crc = crc32_calc_buffer(data, length);
|
||||
if (!msrpc_gen(sig_mem_ctx, sig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, gensec_ntlmssp_state->crypt.ntlm.seq_num)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
gensec_ntlmssp_state->crypt.ntlm.seq_num++;
|
||||
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm.arcfour_state, sig->data+4, sig->length-4);
|
||||
|
||||
DEBUG(10, ("NTLM1: created signature over %llu bytes of input:\n", (unsigned long long)length));
|
||||
dump_data(11, sig->data, sig->length);
|
||||
}
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* TODO: make this non-public */
|
||||
_PUBLIC_ NTSTATUS gensec_ntlmssp_sign_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
DATA_BLOB *sig)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
return ntlmssp_make_packet_signature(gensec_ntlmssp_state, sig_mem_ctx,
|
||||
data, length,
|
||||
whole_pdu, pdu_length,
|
||||
NTLMSSP_SEND, sig, True);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the signature of an incoming packet
|
||||
*
|
||||
*/
|
||||
|
||||
NTSTATUS gensec_ntlmssp_check_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
const uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
const DATA_BLOB *sig)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
|
||||
DATA_BLOB local_sig;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
if (!gensec_ntlmssp_state->session_key.length) {
|
||||
DEBUG(3, ("NO session key, cannot check packet signature\n"));
|
||||
return NT_STATUS_NO_USER_SESSION_KEY;
|
||||
}
|
||||
|
||||
if (sig->length < 8) {
|
||||
DEBUG(0, ("NTLMSSP packet check failed due to short signature (%lu bytes)!\n",
|
||||
(unsigned long)sig->length));
|
||||
}
|
||||
|
||||
nt_status = ntlmssp_make_packet_signature(gensec_ntlmssp_state, sig_mem_ctx,
|
||||
data, length,
|
||||
whole_pdu, pdu_length,
|
||||
NTLMSSP_RECEIVE, &local_sig, True);
|
||||
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
DEBUG(0, ("NTLMSSP packet check failed with %s\n", nt_errstr(nt_status)));
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
if (local_sig.length != sig->length ||
|
||||
memcmp(local_sig.data,
|
||||
sig->data, sig->length) != 0) {
|
||||
DEBUG(5, ("BAD SIG NTLM2: wanted signature over %llu bytes of input:\n", (unsigned long long)pdu_length));
|
||||
dump_data(5, local_sig.data, local_sig.length);
|
||||
|
||||
DEBUG(5, ("BAD SIG: got signature over %llu bytes of input:\n", (unsigned long long)pdu_length));
|
||||
dump_data(5, sig->data, sig->length);
|
||||
|
||||
DEBUG(0, ("NTLMSSP NTLM2 packet check failed due to invalid signature on %llu bytes of input!\n", (unsigned long long)pdu_length));
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
} else {
|
||||
if (local_sig.length != sig->length ||
|
||||
memcmp(local_sig.data + 8,
|
||||
sig->data + 8, sig->length - 8) != 0) {
|
||||
DEBUG(5, ("BAD SIG NTLM1: wanted signature of %llu bytes of input:\n", (unsigned long long)length));
|
||||
dump_data(5, local_sig.data, local_sig.length);
|
||||
|
||||
DEBUG(5, ("BAD SIG: got signature of %llu bytes of input:\n", (unsigned long long)length));
|
||||
dump_data(5, sig->data, sig->length);
|
||||
|
||||
DEBUG(0, ("NTLMSSP NTLM1 packet check failed due to invalid signature on %llu bytes of input:\n", (unsigned long long)length));
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
dump_data_pw("checked ntlmssp signature\n", sig->data, sig->length);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Seal data with the NTLMSSP algorithm
|
||||
*
|
||||
*/
|
||||
|
||||
NTSTATUS gensec_ntlmssp_seal_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
DATA_BLOB *sig)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
NTSTATUS nt_status;
|
||||
if (!gensec_ntlmssp_state->session_key.length) {
|
||||
DEBUG(3, ("NO session key, cannot seal packet\n"));
|
||||
return NT_STATUS_NO_USER_SESSION_KEY;
|
||||
}
|
||||
|
||||
DEBUG(10,("ntlmssp_seal_data: seal\n"));
|
||||
dump_data_pw("ntlmssp clear data\n", data, length);
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
/* The order of these two operations matters - we must first seal the packet,
|
||||
then seal the sequence number - this is becouse the send_seal_hash is not
|
||||
constant, but is is rather updated with each iteration */
|
||||
nt_status = ntlmssp_make_packet_signature(gensec_ntlmssp_state, sig_mem_ctx,
|
||||
data, length,
|
||||
whole_pdu, pdu_length,
|
||||
NTLMSSP_SEND, sig, False);
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state, data, length);
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state, sig->data+4, 8);
|
||||
}
|
||||
} else {
|
||||
uint32_t crc;
|
||||
crc = crc32_calc_buffer(data, length);
|
||||
if (!msrpc_gen(sig_mem_ctx, sig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, gensec_ntlmssp_state->crypt.ntlm.seq_num)) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
/* The order of these two operations matters - we must
|
||||
first seal the packet, then seal the sequence
|
||||
number - this is becouse the ntlmssp_hash is not
|
||||
constant, but is is rather updated with each
|
||||
iteration */
|
||||
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm.arcfour_state, data, length);
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm.arcfour_state, sig->data+4, sig->length-4);
|
||||
/* increment counter on send */
|
||||
gensec_ntlmssp_state->crypt.ntlm.seq_num++;
|
||||
nt_status = NT_STATUS_OK;
|
||||
}
|
||||
dump_data_pw("ntlmssp signature\n", sig->data, sig->length);
|
||||
dump_data_pw("ntlmssp sealed data\n", data, length);
|
||||
|
||||
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unseal data with the NTLMSSP algorithm
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
wrappers for the ntlmssp_*() functions
|
||||
*/
|
||||
NTSTATUS gensec_ntlmssp_unseal_packet(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
uint8_t *data, size_t length,
|
||||
const uint8_t *whole_pdu, size_t pdu_length,
|
||||
const DATA_BLOB *sig)
|
||||
{
|
||||
struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data;
|
||||
if (!gensec_ntlmssp_state->session_key.length) {
|
||||
DEBUG(3, ("NO session key, cannot unseal packet\n"));
|
||||
return NT_STATUS_NO_USER_SESSION_KEY;
|
||||
}
|
||||
|
||||
dump_data_pw("ntlmssp sealed data\n", data, length);
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm2.recv_seal_arcfour_state, data, length);
|
||||
} else {
|
||||
arcfour_crypt_sbox(gensec_ntlmssp_state->crypt.ntlm.arcfour_state, data, length);
|
||||
}
|
||||
dump_data_pw("ntlmssp clear data\n", data, length);
|
||||
return gensec_ntlmssp_check_packet(gensec_security, sig_mem_ctx, data, length, whole_pdu, pdu_length, sig);
|
||||
}
|
||||
|
||||
/**
|
||||
Initialise the state for NTLMSSP signing.
|
||||
*/
|
||||
/* TODO: make this non-public */
|
||||
_PUBLIC_ NTSTATUS ntlmssp_sign_init(struct gensec_ntlmssp_state *gensec_ntlmssp_state)
|
||||
{
|
||||
TALLOC_CTX *mem_ctx = talloc_new(gensec_ntlmssp_state);
|
||||
|
||||
if (!mem_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
DEBUG(3, ("NTLMSSP Sign/Seal - Initialising with flags:\n"));
|
||||
debug_ntlmssp_flags(gensec_ntlmssp_state->neg_flags);
|
||||
|
||||
if (gensec_ntlmssp_state->session_key.length < 8) {
|
||||
talloc_free(mem_ctx);
|
||||
DEBUG(3, ("NO session key, cannot intialise signing\n"));
|
||||
return NT_STATUS_NO_USER_SESSION_KEY;
|
||||
}
|
||||
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2)
|
||||
{
|
||||
DATA_BLOB weak_session_key = gensec_ntlmssp_state->session_key;
|
||||
const char *send_sign_const;
|
||||
const char *send_seal_const;
|
||||
const char *recv_sign_const;
|
||||
const char *recv_seal_const;
|
||||
|
||||
DATA_BLOB send_seal_key;
|
||||
DATA_BLOB recv_seal_key;
|
||||
|
||||
switch (gensec_ntlmssp_state->role) {
|
||||
case NTLMSSP_CLIENT:
|
||||
send_sign_const = CLI_SIGN;
|
||||
send_seal_const = CLI_SEAL;
|
||||
recv_sign_const = SRV_SIGN;
|
||||
recv_seal_const = SRV_SEAL;
|
||||
break;
|
||||
case NTLMSSP_SERVER:
|
||||
send_sign_const = SRV_SIGN;
|
||||
send_seal_const = SRV_SEAL;
|
||||
recv_sign_const = CLI_SIGN;
|
||||
recv_seal_const = CLI_SEAL;
|
||||
break;
|
||||
default:
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state = talloc(gensec_ntlmssp_state, struct arcfour_state);
|
||||
NT_STATUS_HAVE_NO_MEMORY(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state);
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_seal_arcfour_state = talloc(gensec_ntlmssp_state, struct arcfour_state);
|
||||
NT_STATUS_HAVE_NO_MEMORY(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state);
|
||||
|
||||
/**
|
||||
Weaken NTLMSSP keys to cope with down-level
|
||||
clients, servers and export restrictions.
|
||||
|
||||
We probably should have some parameters to control
|
||||
this, once we get NTLM2 working.
|
||||
*/
|
||||
|
||||
/* Key weakening was not performed on the master key
|
||||
* for NTLM2 (in ntlmssp_weaken_keys()), but must be
|
||||
* done on the encryption subkeys only. That is why
|
||||
* we don't have this code for the ntlmv1 case.
|
||||
*/
|
||||
|
||||
if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) {
|
||||
|
||||
} else if (gensec_ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) {
|
||||
weak_session_key.length = 7;
|
||||
} else { /* forty bits */
|
||||
weak_session_key.length = 5;
|
||||
}
|
||||
dump_data_pw("NTLMSSP weakend master key:\n",
|
||||
weak_session_key.data,
|
||||
weak_session_key.length);
|
||||
|
||||
/* SEND: sign key */
|
||||
calc_ntlmv2_key(gensec_ntlmssp_state,
|
||||
&gensec_ntlmssp_state->crypt.ntlm2.send_sign_key,
|
||||
gensec_ntlmssp_state->session_key, send_sign_const);
|
||||
dump_data_pw("NTLMSSP send sign key:\n",
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_sign_key.data,
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_sign_key.length);
|
||||
|
||||
/* SEND: seal ARCFOUR pad */
|
||||
calc_ntlmv2_key(mem_ctx,
|
||||
&send_seal_key,
|
||||
weak_session_key, send_seal_const);
|
||||
dump_data_pw("NTLMSSP send seal key:\n",
|
||||
send_seal_key.data,
|
||||
send_seal_key.length);
|
||||
arcfour_init(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state,
|
||||
&send_seal_key);
|
||||
dump_data_pw("NTLMSSP send sesl hash:\n",
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state->sbox,
|
||||
sizeof(gensec_ntlmssp_state->crypt.ntlm2.send_seal_arcfour_state->sbox));
|
||||
|
||||
/* RECV: sign key */
|
||||
calc_ntlmv2_key(gensec_ntlmssp_state,
|
||||
&gensec_ntlmssp_state->crypt.ntlm2.recv_sign_key,
|
||||
gensec_ntlmssp_state->session_key, recv_sign_const);
|
||||
dump_data_pw("NTLMSSP recv sign key:\n",
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_sign_key.data,
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_sign_key.length);
|
||||
|
||||
/* RECV: seal ARCFOUR pad */
|
||||
calc_ntlmv2_key(mem_ctx,
|
||||
&recv_seal_key,
|
||||
weak_session_key, recv_seal_const);
|
||||
dump_data_pw("NTLMSSP recv seal key:\n",
|
||||
recv_seal_key.data,
|
||||
recv_seal_key.length);
|
||||
arcfour_init(gensec_ntlmssp_state->crypt.ntlm2.recv_seal_arcfour_state,
|
||||
&recv_seal_key);
|
||||
dump_data_pw("NTLMSSP receive seal hash:\n",
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_seal_arcfour_state->sbox,
|
||||
sizeof(gensec_ntlmssp_state->crypt.ntlm2.recv_seal_arcfour_state->sbox));
|
||||
|
||||
gensec_ntlmssp_state->crypt.ntlm2.send_seq_num = 0;
|
||||
gensec_ntlmssp_state->crypt.ntlm2.recv_seq_num = 0;
|
||||
|
||||
} else {
|
||||
DATA_BLOB weak_session_key = ntlmssp_weakend_key(gensec_ntlmssp_state, mem_ctx);
|
||||
DEBUG(5, ("NTLMSSP Sign/Seal - using NTLM1\n"));
|
||||
|
||||
gensec_ntlmssp_state->crypt.ntlm.arcfour_state = talloc(gensec_ntlmssp_state, struct arcfour_state);
|
||||
NT_STATUS_HAVE_NO_MEMORY(gensec_ntlmssp_state->crypt.ntlm.arcfour_state);
|
||||
|
||||
arcfour_init(gensec_ntlmssp_state->crypt.ntlm.arcfour_state,
|
||||
&weak_session_key);
|
||||
dump_data_pw("NTLMSSP hash:\n", gensec_ntlmssp_state->crypt.ntlm.arcfour_state->sbox,
|
||||
sizeof(gensec_ntlmssp_state->crypt.ntlm.arcfour_state->sbox));
|
||||
|
||||
gensec_ntlmssp_state->crypt.ntlm.seq_num = 0;
|
||||
}
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
size_t gensec_ntlmssp_sig_size(struct gensec_security *gensec_security, size_t data_size)
|
||||
{
|
||||
return NTLMSSP_SIG_SIZE;
|
||||
}
|
||||
|
||||
NTSTATUS gensec_ntlmssp_wrap(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out)
|
||||
{
|
||||
DATA_BLOB sig;
|
||||
NTSTATUS nt_status;
|
||||
|
||||
if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
|
||||
|
||||
*out = data_blob_talloc(sig_mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE);
|
||||
if (!out->data) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length);
|
||||
|
||||
nt_status = gensec_ntlmssp_seal_packet(gensec_security, sig_mem_ctx,
|
||||
out->data + NTLMSSP_SIG_SIZE,
|
||||
out->length - NTLMSSP_SIG_SIZE,
|
||||
out->data + NTLMSSP_SIG_SIZE,
|
||||
out->length - NTLMSSP_SIG_SIZE,
|
||||
&sig);
|
||||
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE);
|
||||
}
|
||||
return nt_status;
|
||||
|
||||
} else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
|
||||
|
||||
*out = data_blob_talloc(sig_mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE);
|
||||
if (!out->data) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length);
|
||||
|
||||
nt_status = gensec_ntlmssp_sign_packet(gensec_security, sig_mem_ctx,
|
||||
out->data + NTLMSSP_SIG_SIZE,
|
||||
out->length - NTLMSSP_SIG_SIZE,
|
||||
out->data + NTLMSSP_SIG_SIZE,
|
||||
out->length - NTLMSSP_SIG_SIZE,
|
||||
&sig);
|
||||
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE);
|
||||
}
|
||||
return nt_status;
|
||||
|
||||
} else {
|
||||
*out = *in;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NTSTATUS gensec_ntlmssp_unwrap(struct gensec_security *gensec_security,
|
||||
TALLOC_CTX *sig_mem_ctx,
|
||||
const DATA_BLOB *in,
|
||||
DATA_BLOB *out)
|
||||
{
|
||||
DATA_BLOB sig;
|
||||
|
||||
if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
|
||||
if (in->length < NTLMSSP_SIG_SIZE) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
sig.data = in->data;
|
||||
sig.length = NTLMSSP_SIG_SIZE;
|
||||
|
||||
*out = data_blob_talloc(sig_mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE);
|
||||
|
||||
return gensec_ntlmssp_unseal_packet(gensec_security, sig_mem_ctx,
|
||||
out->data, out->length,
|
||||
out->data, out->length,
|
||||
&sig);
|
||||
|
||||
} else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) {
|
||||
if (in->length < NTLMSSP_SIG_SIZE) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
sig.data = in->data;
|
||||
sig.length = NTLMSSP_SIG_SIZE;
|
||||
|
||||
*out = data_blob_talloc(sig_mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE);
|
||||
|
||||
return gensec_ntlmssp_check_packet(gensec_security, sig_mem_ctx,
|
||||
out->data, out->length,
|
||||
out->data, out->length,
|
||||
&sig);
|
||||
} else {
|
||||
*out = *in;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Unix SMB/CIFS implementation.
|
||||
* PAM error mapping functions
|
||||
* Copyright (C) Andrew Bartlett 2002
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
|
||||
#ifdef WITH_HAVE_SECURITY_PAM_APPL_H
|
||||
#include <security/pam_appl.h>
|
||||
|
||||
#if defined(PAM_AUTHTOK_RECOVERY_ERR) && !defined(PAM_AUTHTOK_RECOVER_ERR)
|
||||
#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR
|
||||
#endif
|
||||
|
||||
/* PAM -> NT_STATUS map */
|
||||
static const struct {
|
||||
int pam_code;
|
||||
NTSTATUS ntstatus;
|
||||
} pam_to_nt_status_map[] = {
|
||||
{PAM_OPEN_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
{PAM_SYMBOL_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
{PAM_SERVICE_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
{PAM_SYSTEM_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
{PAM_BUF_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
{PAM_PERM_DENIED, NT_STATUS_ACCESS_DENIED},
|
||||
{PAM_AUTH_ERR, NT_STATUS_WRONG_PASSWORD},
|
||||
{PAM_CRED_INSUFFICIENT, NT_STATUS_INSUFFICIENT_LOGON_INFO}, /* FIXME: Is this correct? */
|
||||
{PAM_AUTHINFO_UNAVAIL, NT_STATUS_LOGON_FAILURE},
|
||||
{PAM_USER_UNKNOWN, NT_STATUS_NO_SUCH_USER},
|
||||
{PAM_MAXTRIES, NT_STATUS_REMOTE_SESSION_LIMIT}, /* FIXME: Is this correct? */
|
||||
{PAM_NEW_AUTHTOK_REQD, NT_STATUS_PASSWORD_MUST_CHANGE},
|
||||
{PAM_ACCT_EXPIRED, NT_STATUS_ACCOUNT_EXPIRED},
|
||||
{PAM_SESSION_ERR, NT_STATUS_INSUFFICIENT_RESOURCES},
|
||||
{PAM_CRED_UNAVAIL, NT_STATUS_NO_TOKEN}, /* FIXME: Is this correct? */
|
||||
{PAM_CRED_EXPIRED, NT_STATUS_PASSWORD_EXPIRED}, /* FIXME: Is this correct? */
|
||||
{PAM_CRED_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
{PAM_AUTHTOK_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
#ifdef PAM_AUTHTOK_RECOVER_ERR
|
||||
{PAM_AUTHTOK_RECOVER_ERR, NT_STATUS_UNSUCCESSFUL},
|
||||
#endif
|
||||
{PAM_AUTHTOK_EXPIRED, NT_STATUS_PASSWORD_EXPIRED},
|
||||
{PAM_SUCCESS, NT_STATUS_OK}
|
||||
};
|
||||
|
||||
/* NT_STATUS -> PAM map */
|
||||
static const struct {
|
||||
NTSTATUS ntstatus;
|
||||
int pam_code;
|
||||
} nt_status_to_pam_map[] = {
|
||||
{NT_STATUS_UNSUCCESSFUL, PAM_SYSTEM_ERR},
|
||||
{NT_STATUS_NO_SUCH_USER, PAM_USER_UNKNOWN},
|
||||
{NT_STATUS_WRONG_PASSWORD, PAM_AUTH_ERR},
|
||||
{NT_STATUS_LOGON_FAILURE, PAM_AUTH_ERR},
|
||||
{NT_STATUS_ACCOUNT_EXPIRED, PAM_ACCT_EXPIRED},
|
||||
{NT_STATUS_PASSWORD_EXPIRED, PAM_AUTHTOK_EXPIRED},
|
||||
{NT_STATUS_PASSWORD_MUST_CHANGE, PAM_NEW_AUTHTOK_REQD},
|
||||
{NT_STATUS_OK, PAM_SUCCESS}
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
convert a PAM error to a NT status32 code
|
||||
*****************************************************************************/
|
||||
NTSTATUS pam_to_nt_status(int pam_error)
|
||||
{
|
||||
int i;
|
||||
if (pam_error == 0) return NT_STATUS_OK;
|
||||
|
||||
for (i=0; NT_STATUS_V(pam_to_nt_status_map[i].ntstatus); i++) {
|
||||
if (pam_error == pam_to_nt_status_map[i].pam_code)
|
||||
return pam_to_nt_status_map[i].ntstatus;
|
||||
}
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
convert an NT status32 code to a PAM error
|
||||
*****************************************************************************/
|
||||
int nt_status_to_pam(NTSTATUS nt_status)
|
||||
{
|
||||
int i;
|
||||
if NT_STATUS_IS_OK(nt_status) return PAM_SUCCESS;
|
||||
|
||||
for (i=0; NT_STATUS_V(nt_status_to_pam_map[i].ntstatus); i++) {
|
||||
if (NT_STATUS_EQUAL(nt_status,nt_status_to_pam_map[i].ntstatus))
|
||||
return nt_status_to_pam_map[i].pam_code;
|
||||
}
|
||||
return PAM_SYSTEM_ERR;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/*****************************************************************************
|
||||
convert a PAM error to a NT status32 code
|
||||
*****************************************************************************/
|
||||
NTSTATUS pam_to_nt_status(int pam_error)
|
||||
{
|
||||
if (pam_error == 0) return NT_STATUS_OK;
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
convert an NT status32 code to a PAM error
|
||||
*****************************************************************************/
|
||||
int nt_status_to_pam(NTSTATUS nt_status)
|
||||
{
|
||||
if (NT_STATUS_EQUAL(nt_status, NT_STATUS_OK)) return 0;
|
||||
return 4; /* PAM_SYSTEM_ERR */
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
Password and authentication handling
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004
|
||||
Copyright (C) Gerald Carter 2003
|
||||
Copyright (C) Stefan Metzmacher 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/time.h"
|
||||
#include "auth/auth.h"
|
||||
#include "db_wrap.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "libcli/ldap/ldap.h"
|
||||
#include "librpc/gen_ndr/ndr_netlogon.h"
|
||||
|
||||
const char *user_attrs[] = {
|
||||
/* required for the krb5 kdc */
|
||||
"objectClass",
|
||||
"sAMAccountName",
|
||||
"userPrincipalName",
|
||||
"servicePrincipalName",
|
||||
"msDS-KeyVersionNumber",
|
||||
"krb5Key",
|
||||
|
||||
/* passwords */
|
||||
"lmPwdHash",
|
||||
"ntPwdHash",
|
||||
|
||||
"userAccountControl",
|
||||
|
||||
"pwdLastSet",
|
||||
"accountExpires",
|
||||
|
||||
"objectSid",
|
||||
|
||||
/* check 'allowed workstations' */
|
||||
"userWorkstations",
|
||||
|
||||
/* required for server_info, not access control: */
|
||||
"displayName",
|
||||
"scriptPath",
|
||||
"profilePath",
|
||||
"homeDirectory",
|
||||
"homeDrive",
|
||||
"lastLogon",
|
||||
"lastLogoff",
|
||||
"accountExpires",
|
||||
"badPwdCount",
|
||||
"logonCount",
|
||||
"primaryGroupID",
|
||||
NULL,
|
||||
};
|
||||
|
||||
const char *domain_ref_attrs[] = {"nETBIOSName", "nCName",
|
||||
"dnsRoot", "objectClass", NULL};
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
Do a specific test for a SAM_ACCOUNT being vaild for this connection
|
||||
(ie not disabled, expired and the like).
|
||||
****************************************************************************/
|
||||
_PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
|
||||
struct ldb_context *sam_ctx,
|
||||
uint32_t logon_parameters,
|
||||
struct ldb_message *msg,
|
||||
struct ldb_message *msg_domain_ref,
|
||||
const char *logon_workstation,
|
||||
const char *name_for_logs)
|
||||
{
|
||||
uint16_t acct_flags;
|
||||
const char *workstation_list;
|
||||
NTTIME acct_expiry;
|
||||
NTTIME must_change_time;
|
||||
NTTIME last_set_time;
|
||||
|
||||
struct ldb_dn *domain_dn = samdb_result_dn(sam_ctx, mem_ctx, msg_domain_ref, "nCName", ldb_dn_new(mem_ctx, sam_ctx, NULL));
|
||||
|
||||
NTTIME now;
|
||||
DEBUG(4,("authsam_account_ok: Checking SMB password for user %s\n", name_for_logs));
|
||||
|
||||
acct_flags = samdb_result_acct_flags(msg, "userAccountControl");
|
||||
|
||||
acct_expiry = samdb_result_nttime(msg, "accountExpires", 0);
|
||||
must_change_time = samdb_result_force_password_change(sam_ctx, mem_ctx,
|
||||
domain_dn, msg);
|
||||
last_set_time = samdb_result_nttime(msg, "pwdLastSet", 0);
|
||||
|
||||
workstation_list = samdb_result_string(msg, "userWorkstations", NULL);
|
||||
|
||||
/* Quit if the account was disabled. */
|
||||
if (acct_flags & ACB_DISABLED) {
|
||||
DEBUG(1,("authsam_account_ok: Account for user '%s' was disabled.\n", name_for_logs));
|
||||
return NT_STATUS_ACCOUNT_DISABLED;
|
||||
}
|
||||
|
||||
/* Quit if the account was locked out. */
|
||||
if (acct_flags & ACB_AUTOLOCK) {
|
||||
DEBUG(1,("authsam_account_ok: Account for user %s was locked out.\n", name_for_logs));
|
||||
return NT_STATUS_ACCOUNT_LOCKED_OUT;
|
||||
}
|
||||
|
||||
/* Test account expire time */
|
||||
unix_to_nt_time(&now, time(NULL));
|
||||
if (now > acct_expiry) {
|
||||
DEBUG(1,("authsam_account_ok: Account for user '%s' has expired.\n", name_for_logs));
|
||||
DEBUG(3,("authsam_account_ok: Account expired at '%s'.\n",
|
||||
nt_time_string(mem_ctx, acct_expiry)));
|
||||
return NT_STATUS_ACCOUNT_EXPIRED;
|
||||
}
|
||||
|
||||
if (!(acct_flags & ACB_PWNOEXP)) {
|
||||
/* check for immediate expiry "must change at next logon" */
|
||||
if (must_change_time == 0 && last_set_time != 0) {
|
||||
DEBUG(1,("sam_account_ok: Account for user '%s' password must change!.\n",
|
||||
name_for_logs));
|
||||
return NT_STATUS_PASSWORD_MUST_CHANGE;
|
||||
}
|
||||
|
||||
/* check for expired password */
|
||||
if ((must_change_time != 0) && (must_change_time < now)) {
|
||||
DEBUG(1,("sam_account_ok: Account for user '%s' password expired!.\n",
|
||||
name_for_logs));
|
||||
DEBUG(1,("sam_account_ok: Password expired at '%s' unix time.\n",
|
||||
nt_time_string(mem_ctx, must_change_time)));
|
||||
return NT_STATUS_PASSWORD_EXPIRED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Test workstation. Workstation list is comma separated. */
|
||||
if (logon_workstation && workstation_list && *workstation_list) {
|
||||
BOOL invalid_ws = True;
|
||||
int i;
|
||||
const char **workstations = str_list_make(mem_ctx, workstation_list, ",");
|
||||
|
||||
for (i = 0; workstations && workstations[i]; i++) {
|
||||
DEBUG(10,("sam_account_ok: checking for workstation match '%s' and '%s'\n",
|
||||
workstations[i], logon_workstation));
|
||||
|
||||
if (strequal(workstations[i], logon_workstation) == 0) {
|
||||
invalid_ws = False;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(workstations);
|
||||
|
||||
if (invalid_ws) {
|
||||
return NT_STATUS_INVALID_WORKSTATION;
|
||||
}
|
||||
}
|
||||
|
||||
if (acct_flags & ACB_DOMTRUST) {
|
||||
DEBUG(2,("sam_account_ok: Domain trust account %s denied by server\n", name_for_logs));
|
||||
return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
|
||||
}
|
||||
|
||||
if (!(logon_parameters & MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT)) {
|
||||
if (acct_flags & ACB_SVRTRUST) {
|
||||
DEBUG(2,("sam_account_ok: Server trust account %s denied by server\n", name_for_logs));
|
||||
return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
|
||||
}
|
||||
}
|
||||
if (!(logon_parameters & MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT)) {
|
||||
if (acct_flags & ACB_WSTRUST) {
|
||||
DEBUG(4,("sam_account_ok: Wksta trust account %s denied by server\n", name_for_logs));
|
||||
return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
|
||||
}
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
_PUBLIC_ NTSTATUS authsam_make_server_info(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
|
||||
struct ldb_message *msg,
|
||||
struct ldb_message *msg_domain_ref,
|
||||
DATA_BLOB user_sess_key, DATA_BLOB lm_sess_key,
|
||||
struct auth_serversupplied_info **_server_info)
|
||||
{
|
||||
struct auth_serversupplied_info *server_info;
|
||||
struct ldb_message **group_msgs;
|
||||
int group_ret;
|
||||
const char *group_attrs[3] = { "sAMAccountType", "objectSid", NULL };
|
||||
/* find list of sids */
|
||||
struct dom_sid **groupSIDs = NULL;
|
||||
struct dom_sid *account_sid;
|
||||
struct dom_sid *primary_group_sid;
|
||||
const char *str;
|
||||
struct ldb_dn *ncname;
|
||||
int i;
|
||||
uint_t rid;
|
||||
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
|
||||
|
||||
group_ret = gendb_search(sam_ctx,
|
||||
tmp_ctx, NULL, &group_msgs, group_attrs,
|
||||
"(&(member=%s)(sAMAccountType=*))",
|
||||
ldb_dn_get_linearized(msg->dn));
|
||||
if (group_ret == -1) {
|
||||
talloc_free(tmp_ctx);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
server_info = talloc(mem_ctx, struct auth_serversupplied_info);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info);
|
||||
|
||||
if (group_ret > 0) {
|
||||
groupSIDs = talloc_array(server_info, struct dom_sid *, group_ret);
|
||||
NT_STATUS_HAVE_NO_MEMORY(groupSIDs);
|
||||
}
|
||||
|
||||
/* Need to unroll some nested groups, but not aliases */
|
||||
for (i = 0; i < group_ret; i++) {
|
||||
groupSIDs[i] = samdb_result_dom_sid(groupSIDs,
|
||||
group_msgs[i], "objectSid");
|
||||
NT_STATUS_HAVE_NO_MEMORY(groupSIDs[i]);
|
||||
}
|
||||
|
||||
talloc_free(tmp_ctx);
|
||||
|
||||
account_sid = samdb_result_dom_sid(server_info, msg, "objectSid");
|
||||
NT_STATUS_HAVE_NO_MEMORY(account_sid);
|
||||
|
||||
primary_group_sid = dom_sid_dup(server_info, account_sid);
|
||||
NT_STATUS_HAVE_NO_MEMORY(primary_group_sid);
|
||||
|
||||
rid = samdb_result_uint(msg, "primaryGroupID", ~0);
|
||||
if (rid == ~0) {
|
||||
if (group_ret > 0) {
|
||||
primary_group_sid = groupSIDs[0];
|
||||
} else {
|
||||
primary_group_sid = NULL;
|
||||
}
|
||||
} else {
|
||||
primary_group_sid->sub_auths[primary_group_sid->num_auths-1] = rid;
|
||||
}
|
||||
|
||||
server_info->account_sid = account_sid;
|
||||
server_info->primary_group_sid = primary_group_sid;
|
||||
|
||||
server_info->n_domain_groups = group_ret;
|
||||
server_info->domain_groups = groupSIDs;
|
||||
|
||||
server_info->account_name = talloc_steal(server_info, samdb_result_string(msg, "sAMAccountName", NULL));
|
||||
|
||||
server_info->domain_name = talloc_steal(server_info, samdb_result_string(msg_domain_ref, "nETBIOSName", NULL));
|
||||
|
||||
str = samdb_result_string(msg, "displayName", "");
|
||||
server_info->full_name = talloc_strdup(server_info, str);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->full_name);
|
||||
|
||||
str = samdb_result_string(msg, "scriptPath", "");
|
||||
server_info->logon_script = talloc_strdup(server_info, str);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_script);
|
||||
|
||||
str = samdb_result_string(msg, "profilePath", "");
|
||||
server_info->profile_path = talloc_strdup(server_info, str);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->profile_path);
|
||||
|
||||
str = samdb_result_string(msg, "homeDirectory", "");
|
||||
server_info->home_directory = talloc_strdup(server_info, str);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_directory);
|
||||
|
||||
str = samdb_result_string(msg, "homeDrive", "");
|
||||
server_info->home_drive = talloc_strdup(server_info, str);
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->home_drive);
|
||||
|
||||
server_info->logon_server = talloc_strdup(server_info, lp_netbios_name());
|
||||
NT_STATUS_HAVE_NO_MEMORY(server_info->logon_server);
|
||||
|
||||
server_info->last_logon = samdb_result_nttime(msg, "lastLogon", 0);
|
||||
server_info->last_logoff = samdb_result_nttime(msg, "lastLogoff", 0);
|
||||
server_info->acct_expiry = samdb_result_nttime(msg, "accountExpires", 0);
|
||||
server_info->last_password_change = samdb_result_nttime(msg, "pwdLastSet", 0);
|
||||
|
||||
ncname = samdb_result_dn(sam_ctx, mem_ctx, msg_domain_ref, "nCName", NULL);
|
||||
if (!ncname) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
server_info->allow_password_change
|
||||
= samdb_result_allow_password_change(sam_ctx, mem_ctx,
|
||||
ncname, msg, "pwdLastSet");
|
||||
server_info->force_password_change
|
||||
= samdb_result_force_password_change(sam_ctx, mem_ctx,
|
||||
ncname, msg);
|
||||
|
||||
server_info->logon_count = samdb_result_uint(msg, "logonCount", 0);
|
||||
server_info->bad_password_count = samdb_result_uint(msg, "badPwdCount", 0);
|
||||
|
||||
server_info->acct_flags = samdb_result_acct_flags(msg, "userAccountControl");
|
||||
|
||||
server_info->user_session_key = user_sess_key;
|
||||
server_info->lm_session_key = lm_sess_key;
|
||||
|
||||
server_info->authenticated = True;
|
||||
|
||||
*_server_info = server_info;
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
_PUBLIC_ NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
|
||||
TALLOC_CTX *mem_ctx, const char *principal,
|
||||
struct ldb_message ***msgs,
|
||||
struct ldb_message ***msgs_domain_ref)
|
||||
{
|
||||
struct ldb_dn *user_dn, *domain_dn;
|
||||
NTSTATUS nt_status;
|
||||
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
|
||||
int ret;
|
||||
struct ldb_dn *partitions_basedn = samdb_partitions_dn(sam_ctx, mem_ctx);
|
||||
|
||||
if (!tmp_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
nt_status = crack_user_principal_name(sam_ctx, tmp_ctx, principal, &user_dn, &domain_dn);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
/* grab domain info from the reference */
|
||||
ret = gendb_search(sam_ctx, tmp_ctx, partitions_basedn, msgs_domain_ref, domain_ref_attrs,
|
||||
"(ncName=%s)", ldb_dn_get_linearized(domain_dn));
|
||||
|
||||
if (ret != 1) {
|
||||
talloc_free(tmp_ctx);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
/* pull the user attributes */
|
||||
ret = gendb_search_dn(sam_ctx, tmp_ctx, user_dn, msgs, user_attrs);
|
||||
if (ret != 1) {
|
||||
talloc_free(tmp_ctx);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
talloc_steal(mem_ctx, *msgs);
|
||||
talloc_steal(mem_ctx, *msgs_domain_ref);
|
||||
talloc_free(tmp_ctx);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* Used in the gensec_gssapi and gensec_krb5 server-side code, where the PAC isn't available */
|
||||
NTSTATUS sam_get_server_info_principal(TALLOC_CTX *mem_ctx, const char *principal,
|
||||
struct auth_serversupplied_info **server_info)
|
||||
{
|
||||
NTSTATUS nt_status;
|
||||
DATA_BLOB user_sess_key = data_blob(NULL, 0);
|
||||
DATA_BLOB lm_sess_key = data_blob(NULL, 0);
|
||||
|
||||
struct ldb_message **msgs;
|
||||
struct ldb_message **msgs_domain_ref;
|
||||
struct ldb_context *sam_ctx;
|
||||
|
||||
TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
|
||||
if (!tmp_ctx) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
sam_ctx = samdb_connect(tmp_ctx, system_session(tmp_ctx));
|
||||
if (sam_ctx == NULL) {
|
||||
talloc_free(tmp_ctx);
|
||||
return NT_STATUS_INVALID_SYSTEM_SERVICE;
|
||||
}
|
||||
|
||||
nt_status = sam_get_results_principal(sam_ctx, tmp_ctx, principal,
|
||||
&msgs, &msgs_domain_ref);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
return nt_status;
|
||||
}
|
||||
|
||||
nt_status = authsam_make_server_info(tmp_ctx, sam_ctx, msgs[0], msgs_domain_ref[0],
|
||||
user_sess_key, lm_sess_key,
|
||||
server_info);
|
||||
if (NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_steal(mem_ctx, *server_info);
|
||||
}
|
||||
talloc_free(tmp_ctx);
|
||||
return nt_status;
|
||||
}
|
||||
Reference in New Issue
Block a user