wmi-1.3.16 from opsview.com
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user