mirror of
https://github.com/samba-team/samba.git
synced 2025-01-06 13:18:07 +03:00
7ee246ef9c
To ensure that, when decrypting the kpasswd ticket, we look up the correct principal and don't trust the sname from the ticket, we should pass the principal name of the kpasswd service into krb5_rd_req_ctx(). However, gensec_krb5_update_internal() will pass in NULL unless the principal in our credentials is CRED_SPECIFIED. At present, our principal will be considered obtained as CRED_SMB_CONF (from the cli_credentials_set_conf() a few lines up), so we explicitly set the realm again, but this time as CRED_SPECIFIED. Now the value of server_in_keytab that we provide to smb_krb5_rd_req_decoded() will not be NULL. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15074 Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Reviewed-by: Andreas Schneider <asn@samba.org>
392 lines
10 KiB
C
392 lines
10 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
Samba kpasswd implementation
|
|
|
|
Copyright (c) 2005 Andrew Bartlett <abartlet@samba.org>
|
|
Copyright (c) 2016 Andreas Schneider <asn@samba.org>
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "samba/service_task.h"
|
|
#include "tsocket/tsocket.h"
|
|
#include "auth/credentials/credentials.h"
|
|
#include "auth/auth.h"
|
|
#include "auth/gensec/gensec.h"
|
|
#include "kdc/kdc-server.h"
|
|
#include "kdc/kpasswd-service.h"
|
|
#include "kdc/kpasswd-helper.h"
|
|
#include "param/param.h"
|
|
|
|
#define HEADER_LEN 6
|
|
#ifndef RFC3244_VERSION
|
|
#define RFC3244_VERSION 0xff80
|
|
#endif
|
|
|
|
kdc_code kpasswd_process(struct kdc_server *kdc,
|
|
TALLOC_CTX *mem_ctx,
|
|
DATA_BLOB *request,
|
|
DATA_BLOB *reply,
|
|
struct tsocket_address *remote_addr,
|
|
struct tsocket_address *local_addr,
|
|
int datagram)
|
|
{
|
|
uint16_t len;
|
|
uint16_t verno;
|
|
uint16_t ap_req_len;
|
|
uint16_t enc_data_len;
|
|
DATA_BLOB ap_req_blob = data_blob_null;
|
|
DATA_BLOB ap_rep_blob = data_blob_null;
|
|
DATA_BLOB enc_data_blob = data_blob_null;
|
|
DATA_BLOB dec_data_blob = data_blob_null;
|
|
DATA_BLOB kpasswd_dec_reply = data_blob_null;
|
|
const char *error_string = NULL;
|
|
krb5_error_code error_code = 0;
|
|
struct cli_credentials *server_credentials;
|
|
struct gensec_security *gensec_security;
|
|
#ifndef SAMBA4_USES_HEIMDAL
|
|
struct sockaddr_storage remote_ss;
|
|
#endif
|
|
struct sockaddr_storage local_ss;
|
|
ssize_t socklen;
|
|
TALLOC_CTX *tmp_ctx;
|
|
kdc_code rc = KDC_ERROR;
|
|
krb5_error_code code = 0;
|
|
NTSTATUS status;
|
|
int rv;
|
|
bool is_inet;
|
|
bool ok;
|
|
|
|
if (kdc->am_rodc) {
|
|
return KDC_PROXY_REQUEST;
|
|
}
|
|
|
|
tmp_ctx = talloc_new(mem_ctx);
|
|
if (tmp_ctx == NULL) {
|
|
return KDC_ERROR;
|
|
}
|
|
|
|
is_inet = tsocket_address_is_inet(remote_addr, "ip");
|
|
if (!is_inet) {
|
|
DBG_WARNING("Invalid remote IP address");
|
|
goto done;
|
|
}
|
|
|
|
#ifndef SAMBA4_USES_HEIMDAL
|
|
/*
|
|
* FIXME: Heimdal fails to to do a krb5_rd_req() in gensec_krb5 if we
|
|
* set the remote address.
|
|
*/
|
|
|
|
/* remote_addr */
|
|
socklen = tsocket_address_bsd_sockaddr(remote_addr,
|
|
(struct sockaddr *)&remote_ss,
|
|
sizeof(struct sockaddr_storage));
|
|
if (socklen < 0) {
|
|
DBG_WARNING("Invalid remote IP address");
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
/* local_addr */
|
|
socklen = tsocket_address_bsd_sockaddr(local_addr,
|
|
(struct sockaddr *)&local_ss,
|
|
sizeof(struct sockaddr_storage));
|
|
if (socklen < 0) {
|
|
DBG_WARNING("Invalid local IP address");
|
|
goto done;
|
|
}
|
|
|
|
if (request->length <= HEADER_LEN) {
|
|
DBG_WARNING("Request truncated\n");
|
|
goto done;
|
|
}
|
|
|
|
len = RSVAL(request->data, 0);
|
|
if (request->length != len) {
|
|
DBG_WARNING("Request length does not match\n");
|
|
goto done;
|
|
}
|
|
|
|
verno = RSVAL(request->data, 2);
|
|
if (verno != 1 && verno != RFC3244_VERSION) {
|
|
DBG_WARNING("Unsupported version: 0x%04x\n", verno);
|
|
}
|
|
|
|
ap_req_len = RSVAL(request->data, 4);
|
|
if ((ap_req_len >= len) || ((ap_req_len + HEADER_LEN) >= len)) {
|
|
DBG_WARNING("AP_REQ truncated\n");
|
|
goto done;
|
|
}
|
|
|
|
ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len);
|
|
|
|
enc_data_len = len - ap_req_len;
|
|
enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len],
|
|
enc_data_len);
|
|
|
|
server_credentials = cli_credentials_init(tmp_ctx);
|
|
if (server_credentials == NULL) {
|
|
DBG_ERR("Failed to initialize server credentials!\n");
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* We want the credentials subsystem to use the krb5 context we already
|
|
* have, rather than a new context.
|
|
*
|
|
* On this context the KDB plugin has been loaded, so we can access
|
|
* dsdb.
|
|
*/
|
|
status = cli_credentials_set_krb5_context(server_credentials,
|
|
kdc->smb_krb5_context);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto done;
|
|
}
|
|
|
|
ok = cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
|
|
if (!ok) {
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* After calling cli_credentials_set_conf(), explicitly set the realm
|
|
* with CRED_SPECIFIED. We need to do this so the result of
|
|
* principal_from_credentials() called from the gensec layer is
|
|
* CRED_SPECIFIED rather than CRED_SMB_CONF, avoiding a fallback to
|
|
* match-by-key (very undesirable in this case).
|
|
*/
|
|
ok = cli_credentials_set_realm(server_credentials,
|
|
lpcfg_realm(kdc->task->lp_ctx),
|
|
CRED_SPECIFIED);
|
|
if (!ok) {
|
|
goto done;
|
|
}
|
|
|
|
ok = cli_credentials_set_username(server_credentials,
|
|
"kadmin/changepw",
|
|
CRED_SPECIFIED);
|
|
if (!ok) {
|
|
goto done;
|
|
}
|
|
|
|
/* Check that the server principal is indeed CRED_SPECIFIED. */
|
|
{
|
|
char *principal = NULL;
|
|
enum credentials_obtained obtained;
|
|
|
|
principal = cli_credentials_get_principal_and_obtained(server_credentials,
|
|
tmp_ctx,
|
|
&obtained);
|
|
if (obtained < CRED_SPECIFIED) {
|
|
goto done;
|
|
}
|
|
|
|
TALLOC_FREE(principal);
|
|
}
|
|
|
|
rv = cli_credentials_set_keytab_name(server_credentials,
|
|
kdc->task->lp_ctx,
|
|
kdc->kpasswd_keytab_name,
|
|
CRED_SPECIFIED);
|
|
if (rv != 0) {
|
|
DBG_ERR("Failed to set credentials keytab name\n");
|
|
goto done;
|
|
}
|
|
|
|
status = samba_server_gensec_start(tmp_ctx,
|
|
kdc->task->event_ctx,
|
|
kdc->task->msg_ctx,
|
|
kdc->task->lp_ctx,
|
|
server_credentials,
|
|
"kpasswd",
|
|
&gensec_security);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto done;
|
|
}
|
|
|
|
status = gensec_set_local_address(gensec_security, local_addr);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto done;
|
|
}
|
|
|
|
#ifndef SAMBA4_USES_HEIMDAL
|
|
status = gensec_set_remote_address(gensec_security, remote_addr);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
/* We want the GENSEC wrap calls to generate PRIV tokens */
|
|
gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
|
|
|
|
/* Use the krb5 gesec mechanism so we can load DB modules */
|
|
status = gensec_start_mech_by_name(gensec_security, "krb5");
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Accept the AP-REQ and generate the AP-REP we need for the reply
|
|
*
|
|
* We only allow KRB5 and make sure the backend to is RPC/IPC free.
|
|
*
|
|
* See gensec_krb5_update_internal() as GENSEC_SERVER.
|
|
*
|
|
* It allows gensec_update() not to block.
|
|
*
|
|
* If that changes in future we need to use
|
|
* gensec_update_send/recv here!
|
|
*/
|
|
status = gensec_update(gensec_security, tmp_ctx,
|
|
ap_req_blob, &ap_rep_blob);
|
|
if (!NT_STATUS_IS_OK(status) &&
|
|
!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
|
|
ap_rep_blob = data_blob_null;
|
|
error_code = KRB5_KPASSWD_HARDERROR;
|
|
error_string = talloc_asprintf(tmp_ctx,
|
|
"gensec_update failed - %s\n",
|
|
nt_errstr(status));
|
|
DBG_ERR("%s", error_string);
|
|
goto reply;
|
|
}
|
|
|
|
status = gensec_unwrap(gensec_security,
|
|
tmp_ctx,
|
|
&enc_data_blob,
|
|
&dec_data_blob);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
ap_rep_blob = data_blob_null;
|
|
error_code = KRB5_KPASSWD_HARDERROR;
|
|
error_string = talloc_asprintf(tmp_ctx,
|
|
"gensec_unwrap failed - %s\n",
|
|
nt_errstr(status));
|
|
DBG_ERR("%s", error_string);
|
|
goto reply;
|
|
}
|
|
|
|
code = kpasswd_handle_request(kdc,
|
|
tmp_ctx,
|
|
gensec_security,
|
|
verno,
|
|
&dec_data_blob,
|
|
&kpasswd_dec_reply,
|
|
&error_string);
|
|
if (code != 0) {
|
|
ap_rep_blob = data_blob_null;
|
|
error_code = code;
|
|
goto reply;
|
|
}
|
|
|
|
status = gensec_wrap(gensec_security,
|
|
tmp_ctx,
|
|
&kpasswd_dec_reply,
|
|
&enc_data_blob);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
ap_rep_blob = data_blob_null;
|
|
error_code = KRB5_KPASSWD_HARDERROR;
|
|
error_string = talloc_asprintf(tmp_ctx,
|
|
"gensec_wrap failed - %s\n",
|
|
nt_errstr(status));
|
|
DBG_ERR("%s", error_string);
|
|
goto reply;
|
|
}
|
|
|
|
reply:
|
|
if (error_code != 0) {
|
|
krb5_data k_enc_data;
|
|
krb5_data k_dec_data;
|
|
const char *principal_string;
|
|
krb5_principal server_principal;
|
|
|
|
if (error_string == NULL) {
|
|
DBG_ERR("Invalid error string! This should not happen\n");
|
|
goto done;
|
|
}
|
|
|
|
ok = kpasswd_make_error_reply(tmp_ctx,
|
|
error_code,
|
|
error_string,
|
|
&dec_data_blob);
|
|
if (!ok) {
|
|
DBG_ERR("Failed to create error reply\n");
|
|
goto done;
|
|
}
|
|
|
|
k_dec_data.length = dec_data_blob.length;
|
|
k_dec_data.data = (char *)dec_data_blob.data;
|
|
|
|
principal_string = cli_credentials_get_principal(server_credentials,
|
|
tmp_ctx);
|
|
if (principal_string == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
code = smb_krb5_parse_name(kdc->smb_krb5_context->krb5_context,
|
|
principal_string,
|
|
&server_principal);
|
|
if (code != 0) {
|
|
DBG_ERR("Failed to create principal: %s\n",
|
|
error_message(code));
|
|
goto done;
|
|
}
|
|
|
|
code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context,
|
|
KRB5KDC_ERR_NONE + error_code,
|
|
NULL, /* e_text */
|
|
&k_dec_data,
|
|
NULL, /* client */
|
|
server_principal,
|
|
&k_enc_data);
|
|
krb5_free_principal(kdc->smb_krb5_context->krb5_context,
|
|
server_principal);
|
|
if (code != 0) {
|
|
DBG_ERR("Failed to create krb5 error reply: %s\n",
|
|
error_message(code));
|
|
goto done;
|
|
}
|
|
|
|
enc_data_blob = data_blob_talloc(tmp_ctx,
|
|
k_enc_data.data,
|
|
k_enc_data.length);
|
|
if (enc_data_blob.data == NULL) {
|
|
DBG_ERR("Failed to allocate memory for error reply\n");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
*reply = data_blob_talloc(mem_ctx,
|
|
NULL,
|
|
HEADER_LEN + ap_rep_blob.length + enc_data_blob.length);
|
|
if (reply->data == NULL) {
|
|
goto done;
|
|
}
|
|
RSSVAL(reply->data, 0, reply->length);
|
|
RSSVAL(reply->data, 2, 1);
|
|
RSSVAL(reply->data, 4, ap_rep_blob.length);
|
|
memcpy(reply->data + HEADER_LEN,
|
|
ap_rep_blob.data,
|
|
ap_rep_blob.length);
|
|
memcpy(reply->data + HEADER_LEN + ap_rep_blob.length,
|
|
enc_data_blob.data,
|
|
enc_data_blob.length);
|
|
|
|
rc = KDC_OK;
|
|
done:
|
|
talloc_free(tmp_ctx);
|
|
return rc;
|
|
}
|