summaryrefslogtreecommitdiff
path: root/source4/kdc
diff options
context:
space:
mode:
Diffstat (limited to 'source4/kdc')
-rw-r--r--source4/kdc/config.mk3
-rw-r--r--source4/kdc/kdc.c262
-rw-r--r--source4/kdc/kdc.h11
-rw-r--r--source4/kdc/kpasswdd.c495
4 files changed, 674 insertions, 97 deletions
diff --git a/source4/kdc/config.mk b/source4/kdc/config.mk
index ce655dea82..5939d0eacb 100644
--- a/source4/kdc/config.mk
+++ b/source4/kdc/config.mk
@@ -6,7 +6,8 @@
INIT_OBJ_FILES = \
kdc/kdc.o \
kdc/pac-glue.o \
- kdc/hdb-ldb.o
+ kdc/hdb-ldb.o \
+ kdc/kpasswdd.o
REQUIRED_SUBSYSTEMS = \
LIBLDB KERBEROS_LIB HEIMDAL_KDC HEIMDAL_HDB
# End SUBSYSTEM KDC
diff --git a/source4/kdc/kdc.c b/source4/kdc/kdc.c
index 158aa85f49..4a1bb0ad05 100644
--- a/source4/kdc/kdc.c
+++ b/source4/kdc/kdc.c
@@ -40,15 +40,6 @@ struct kdc_reply {
DATA_BLOB packet;
};
-/*
- top level context structure for the kdc server
-*/
-struct kdc_server {
- struct task_server *task;
- krb5_kdc_configuration *config;
- struct smb_krb5_context *smb_krb5_context;
-};
-
/* hold information about one kdc socket */
struct kdc_socket {
struct socket_context *sock;
@@ -58,13 +49,12 @@ struct kdc_socket {
/* a queue of outgoing replies that have been deferred */
struct kdc_reply *send_queue;
- int (*process)(krb5_context context,
- krb5_kdc_configuration *config,
- unsigned char *buf,
- size_t len,
- krb5_data *reply,
- const char *from,
- struct sockaddr *addr);
+ BOOL (*process)(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ const char *from,
+ int src_port);
};
/*
state of an open tcp connection
@@ -88,13 +78,12 @@ struct kdc_tcp_connection {
/* a queue of outgoing replies that have been deferred */
struct data_blob_list_item *send_queue;
- int (*process)(krb5_context context,
- krb5_kdc_configuration *config,
- unsigned char *buf,
- size_t len,
- krb5_data *reply,
- const char *from,
- struct sockaddr *addr);
+ BOOL (*process)(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ const char *from,
+ int src_port);
};
/*
@@ -132,12 +121,10 @@ static void kdc_recv_handler(struct kdc_socket *kdc_socket)
TALLOC_CTX *tmp_ctx = talloc_new(kdc_socket);
DATA_BLOB blob;
struct kdc_reply *rep;
- krb5_data reply;
+ DATA_BLOB reply;
size_t nread, dsize;
const char *src_addr;
int src_port;
- struct sockaddr_in src_sock_addr;
- struct ipv4_addr addr;
int ret;
status = socket_pending(kdc_socket->sock, &dsize);
@@ -165,24 +152,13 @@ static void kdc_recv_handler(struct kdc_socket *kdc_socket)
DEBUG(2,("Received krb5 UDP packet of length %u from %s:%u\n",
blob.length, src_addr, (uint16_t)src_port));
- /* TODO: This really should be in a utility function somewhere */
- ZERO_STRUCT(src_sock_addr);
-#ifdef HAVE_SOCK_SIN_LEN
- src_sock_addr.sin_len = sizeof(src_sock_addr);
-#endif
- addr = interpret_addr2(src_addr);
- src_sock_addr.sin_addr.s_addr = addr.addr;
- src_sock_addr.sin_port = htons(src_port);
- src_sock_addr.sin_family = PF_INET;
-
/* Call krb5 */
- ret = kdc_socket->process(kdc_socket->kdc->smb_krb5_context->krb5_context,
- kdc_socket->kdc->config,
- blob.data, blob.length,
+ ret = kdc_socket->process(kdc_socket->kdc,
+ tmp_ctx,
+ &blob,
&reply,
- src_addr,
- (struct sockaddr *)&src_sock_addr);
- if (ret == -1) {
+ src_addr, src_port);
+ if (!ret) {
talloc_free(tmp_ctx);
return;
}
@@ -190,14 +166,13 @@ static void kdc_recv_handler(struct kdc_socket *kdc_socket)
/* queue a pending reply */
rep = talloc(kdc_socket, struct kdc_reply);
if (rep == NULL) {
- krb5_data_free(&reply);
talloc_free(tmp_ctx);
return;
}
rep->dest_address = talloc_steal(rep, src_addr);
rep->dest_port = src_port;
- rep->packet = data_blob_talloc(rep, reply.data, reply.length);
- krb5_data_free(&reply);
+ rep->packet = reply;
+ talloc_steal(rep, reply.data);
if (rep->packet.data == NULL) {
talloc_free(rep);
@@ -231,25 +206,6 @@ static void kdc_tcp_terminate_connection(struct kdc_tcp_connection *kdcconn, con
}
/*
- called when we get a new connection
-*/
-static void kdc_tcp_accept(struct stream_connection *conn)
-{
- struct kdc_server *kdc = talloc_get_type(conn->private, struct kdc_server);
- struct kdc_tcp_connection *kdcconn;
-
- kdcconn = talloc_zero(conn, struct kdc_tcp_connection);
- if (!kdcconn) {
- stream_terminate_connection(conn, "kdc_tcp_accept: out of memory");
- return;
- }
- kdcconn->conn = conn;
- kdcconn->kdc = kdc;
- kdcconn->process = krb5_kdc_process_krb5_request;
- conn->private = kdcconn;
-}
-
-/*
receive some data on a KDC connection
*/
static void kdc_tcp_recv(struct stream_connection *conn, uint16_t flags)
@@ -258,13 +214,11 @@ static void kdc_tcp_recv(struct stream_connection *conn, uint16_t flags)
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
TALLOC_CTX *tmp_ctx = talloc_new(kdcconn);
struct data_blob_list_item *rep;
- krb5_data reply;
size_t nread;
const char *src_addr;
int src_port;
- struct sockaddr_in src_sock_addr;
- struct ipv4_addr addr;
int ret;
+ DATA_BLOB input, reply;
/* avoid recursion, because of half async code */
if (kdcconn->processing) {
@@ -327,26 +281,17 @@ static void kdc_tcp_recv(struct stream_connection *conn, uint16_t flags)
DEBUG(2,("Received krb5 TCP packet of length %u from %s:%u\n",
kdcconn->partial.length - 4, src_addr, src_port));
- /* TODO: This really should be in a utility function somewhere */
- ZERO_STRUCT(src_sock_addr);
-#ifdef HAVE_SOCK_SIN_LEN
- src_sock_addr.sin_len = sizeof(src_sock_addr);
-#endif
- addr = interpret_addr2(src_addr);
- src_sock_addr.sin_addr.s_addr = addr.addr;
- src_sock_addr.sin_port = htons(src_port);
- src_sock_addr.sin_family = PF_INET;
-
/* Call krb5 */
kdcconn->processing = True;
- ret = kdcconn->process(kdcconn->kdc->smb_krb5_context->krb5_context,
- kdcconn->kdc->config,
- kdcconn->partial.data + 4, kdcconn->partial.length - 4,
+ input = data_blob_const(kdcconn->partial.data + 4, kdcconn->partial.length - 4);
+
+ ret = kdcconn->process(kdcconn->kdc,
+ tmp_ctx,
+ &input,
&reply,
- src_addr,
- (struct sockaddr *)&src_sock_addr);
+ src_addr, src_port);
kdcconn->processing = False;
- if (ret == -1) {
+ if (!ret) {
status = NT_STATUS_INTERNAL_ERROR;
goto failed;
}
@@ -354,19 +299,16 @@ static void kdc_tcp_recv(struct stream_connection *conn, uint16_t flags)
/* and now encode the reply */
rep = talloc(kdcconn, struct data_blob_list_item);
if (!rep) {
- krb5_data_free(&reply);
goto nomem;
}
rep->blob = data_blob_talloc(rep, NULL, reply.length + 4);
- if (!rep->blob.data) {
- krb5_data_free(&reply);
+ if (!rep->blob.data) {
goto nomem;
}
RSIVAL(rep->blob.data, 0, reply.length);
memcpy(rep->blob.data + 4, reply.data, reply.length);
- krb5_data_free(&reply);
if (!kdcconn->send_queue) {
EVENT_FD_WRITEABLE(kdcconn->conn->event.fde);
@@ -415,6 +357,68 @@ failed:
kdc_tcp_terminate_connection(kdcconn, nt_errstr(status));
}
+/**
+ Wrapper for krb5_kdc_process_krb5_request, converting to/from Samba
+ calling conventions
+*/
+
+static BOOL kdc_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ const char *src_addr,
+ int src_port)
+{
+ int ret;
+ krb5_data k5_reply;
+ struct ipv4_addr addr;
+ struct sockaddr_in src_sock_addr;
+
+ /* TODO: This really should be in a utility function somewhere */
+ ZERO_STRUCT(src_sock_addr);
+#ifdef HAVE_SOCK_SIN_LEN
+ src_sock_addr.sin_len = sizeof(src_sock_addr);
+#endif
+ addr = interpret_addr2(src_addr);
+ src_sock_addr.sin_addr.s_addr = addr.addr;
+ src_sock_addr.sin_port = htons(src_port);
+ src_sock_addr.sin_family = PF_INET;
+
+
+ ret = krb5_kdc_process_krb5_request(kdc->smb_krb5_context->krb5_context,
+ kdc->config,
+ input->data, input->length,
+ &k5_reply,
+ src_addr,
+ (struct sockaddr *)&src_sock_addr);
+ if (ret == -1) {
+ *reply = data_blob(NULL, 0);
+ return False;
+ }
+ *reply = data_blob_talloc(mem_ctx, k5_reply.data, k5_reply.length);
+ krb5_data_free(&k5_reply);
+ return True;
+}
+
+/*
+ called when we get a new connection
+*/
+static void kdc_tcp_accept(struct stream_connection *conn)
+{
+ struct kdc_server *kdc = talloc_get_type(conn->private, struct kdc_server);
+ struct kdc_tcp_connection *kdcconn;
+
+ kdcconn = talloc_zero(conn, struct kdc_tcp_connection);
+ if (!kdcconn) {
+ stream_terminate_connection(conn, "kdc_tcp_accept: out of memory");
+ return;
+ }
+ kdcconn->conn = conn;
+ kdcconn->kdc = kdc;
+ kdcconn->process = kdc_process;
+ conn->private = kdcconn;
+}
+
static const struct stream_server_ops kdc_tcp_stream_ops = {
.name = "kdc_tcp",
.accept_connection = kdc_tcp_accept,
@@ -423,27 +427,64 @@ static const struct stream_server_ops kdc_tcp_stream_ops = {
};
/*
+ called when we get a new connection
+*/
+void kpasswdd_tcp_accept(struct stream_connection *conn)
+{
+ struct kdc_server *kdc = talloc_get_type(conn->private, struct kdc_server);
+ struct kdc_tcp_connection *kdcconn;
+
+ kdcconn = talloc_zero(conn, struct kdc_tcp_connection);
+ if (!kdcconn) {
+ stream_terminate_connection(conn, "kdc_tcp_accept: out of memory");
+ return;
+ }
+ kdcconn->conn = conn;
+ kdcconn->kdc = kdc;
+ kdcconn->process = kpasswdd_process;
+ conn->private = kdcconn;
+}
+
+static const struct stream_server_ops kpasswdd_tcp_stream_ops = {
+ .name = "kpasswdd_tcp",
+ .accept_connection = kpasswdd_tcp_accept,
+ .recv_handler = kdc_tcp_recv,
+ .send_handler = kdc_tcp_send
+};
+
+/*
start listening on the given address
*/
static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
{
const struct model_ops *model_ops;
struct kdc_socket *kdc_socket;
+ struct kdc_socket *kpasswd_socket;
NTSTATUS status;
- uint16_t port = lp_krb5_port();
+ uint16_t kdc_port = lp_krb5_port();
+ uint16_t kpasswd_port = lp_kpasswd_port();
kdc_socket = talloc(kdc, struct kdc_socket);
NT_STATUS_HAVE_NO_MEMORY(kdc_socket);
+ kpasswd_socket = talloc(kdc, struct kdc_socket);
+ NT_STATUS_HAVE_NO_MEMORY(kpasswd_socket);
+
status = socket_create("ip", SOCKET_TYPE_DGRAM, &kdc_socket->sock, 0);
if (!NT_STATUS_IS_OK(status)) {
talloc_free(kdc_socket);
return status;
}
+ status = socket_create("ip", SOCKET_TYPE_DGRAM, &kpasswd_socket->sock, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(kpasswd_socket);
+ return status;
+ }
+
kdc_socket->kdc = kdc;
kdc_socket->send_queue = NULL;
- kdc_socket->process = krb5_kdc_process_krb5_request;
+ kdc_socket->process = kdc_process;
talloc_steal(kdc_socket, kdc_socket->sock);
@@ -451,14 +492,32 @@ static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
socket_get_fd(kdc_socket->sock), EVENT_FD_READ,
kdc_socket_handler, kdc_socket);
- status = socket_listen(kdc_socket->sock, address, port, 0, 0);
+ status = socket_listen(kdc_socket->sock, address, kdc_port, 0, 0);
if (!NT_STATUS_IS_OK(status)) {
- DEBUG(0,("Failed to bind to %s:%d UDP - %s\n",
- address, port, nt_errstr(status)));
+ DEBUG(0,("Failed to bind to %s:%d UDP for kdc - %s\n",
+ address, kdc_port, nt_errstr(status)));
talloc_free(kdc_socket);
return status;
}
+ kpasswd_socket->kdc = kdc;
+ kpasswd_socket->send_queue = NULL;
+ kpasswd_socket->process = kpasswdd_process;
+
+ talloc_steal(kpasswd_socket, kpasswd_socket->sock);
+
+ kpasswd_socket->fde = event_add_fd(kdc->task->event_ctx, kdc,
+ socket_get_fd(kpasswd_socket->sock), EVENT_FD_READ,
+ kdc_socket_handler, kpasswd_socket);
+
+ status = socket_listen(kpasswd_socket->sock, address, kpasswd_port, 0, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%d UDP for kpasswd - %s\n",
+ address, kpasswd_port, nt_errstr(status)));
+ talloc_free(kpasswd_socket);
+ return status;
+ }
+
/* within the kdc task we want to be a single process, so
ask for the single process model ops and pass these to the
stream_setup_socket() call. */
@@ -469,11 +528,22 @@ static NTSTATUS kdc_add_socket(struct kdc_server *kdc, const char *address)
return NT_STATUS_INTERNAL_ERROR;
}
- status = stream_setup_socket(kdc->task->event_ctx, model_ops, &kdc_tcp_stream_ops,
- "ip", address, &port, kdc);
+ status = stream_setup_socket(kdc->task->event_ctx, model_ops,
+ &kdc_tcp_stream_ops,
+ "ip", address, &kdc_port, kdc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
+ address, kdc_port, nt_errstr(status)));
+ talloc_free(kdc_socket);
+ return status;
+ }
+
+ status = stream_setup_socket(kdc->task->event_ctx, model_ops,
+ &kpasswdd_tcp_stream_ops,
+ "ip", address, &kpasswd_port, kdc);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
- address, port, nt_errstr(status)));
+ address, kpasswd_port, nt_errstr(status)));
talloc_free(kdc_socket);
return status;
}
@@ -565,7 +635,7 @@ static void kdc_task_init(struct task_server *task)
kdc->config->num_db = 1;
ret = hdb_ldb_create(kdc, kdc->smb_krb5_context->krb5_context,
- &kdc->config->db[0], lp_sam_url());
+ &kdc->config->db[0], NULL);
if (ret != 0) {
DEBUG(1, ("kdc_task_init: hdb_ldb_create fails: %s\n",
smb_get_krb5_error_message(kdc->smb_krb5_context->krb5_context, ret, kdc)));
diff --git a/source4/kdc/kdc.h b/source4/kdc/kdc.h
index 2b08d648a9..99c419d4d9 100644
--- a/source4/kdc/kdc.h
+++ b/source4/kdc/kdc.h
@@ -29,3 +29,14 @@
krb5_error_code hdb_ldb_create(TALLOC_CTX *mem_ctx,
krb5_context context, struct HDB **db, const char *arg);
+
+/*
+ top level context structure for the kdc server
+*/
+struct kdc_server {
+ struct task_server *task;
+ krb5_kdc_configuration *config;
+ struct smb_krb5_context *smb_krb5_context;
+};
+
+
diff --git a/source4/kdc/kpasswdd.c b/source4/kdc/kpasswdd.c
new file mode 100644
index 0000000000..4f15cccd34
--- /dev/null
+++ b/source4/kdc/kpasswdd.c
@@ -0,0 +1,495 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ kpasswd Server implementation
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 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 "smbd/service_task.h"
+#include "lib/events/events.h"
+#include "lib/socket/socket.h"
+#include "kdc/kdc.h"
+#include "system/network.h"
+#include "dlinklist.h"
+#include "lib/ldb/include/ldb.h"
+#include "heimdal/lib/krb5/krb5-private.h"
+#include "auth/auth.h"
+
+/* hold information about one kdc socket */
+struct kpasswd_socket {
+ struct socket_context *sock;
+ struct kdc_server *kdc;
+ struct fd_event *fde;
+
+ /* a queue of outgoing replies that have been deferred */
+ struct kdc_reply *send_queue;
+};
+
+/* Return true if there is a valid error packet formed in the error_blob */
+static BOOL kpasswdd_make_error_reply(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ uint16_t result_code,
+ const char *error_string,
+ DATA_BLOB *error_blob)
+{
+ char *error_string_utf8;
+ ssize_t len;
+
+ DEBUG(result_code ? 3 : 10, ("kpasswdd: %s\n", error_string));
+
+ len = push_utf8_talloc(mem_ctx, &error_string_utf8, error_string);
+ if (len == -1) {
+ return False;
+ }
+
+ *error_blob = data_blob_talloc(mem_ctx, NULL, 2 + len + 1);
+ if (!error_blob->data) {
+ return False;
+ }
+ RSSVAL(error_blob->data, 0, result_code);
+ memcpy(error_blob->data + 2, error_string_utf8, len + 1);
+ return True;
+}
+
+/* Return true if there is a valid error packet formed in the error_blob */
+static BOOL kpasswdd_make_unauth_error_reply(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ uint16_t result_code,
+ const char *error_string,
+ DATA_BLOB *error_blob)
+{
+ BOOL ret;
+ int kret;
+ DATA_BLOB error_bytes;
+ krb5_data k5_error_bytes, k5_error_blob;
+ ret = kpasswdd_make_error_reply(kdc, mem_ctx, result_code, error_string,
+ &error_bytes);
+ if (!ret) {
+ return False;
+ }
+ k5_error_bytes.data = error_bytes.data;
+ k5_error_bytes.length = error_bytes.length;
+ kret = krb5_mk_error(kdc->smb_krb5_context->krb5_context,
+ result_code, NULL, &k5_error_bytes,
+ NULL, NULL, NULL, NULL, &k5_error_blob);
+ if (kret) {
+ return False;
+ }
+ *error_blob = data_blob_talloc(mem_ctx, k5_error_blob.data, k5_error_blob.length);
+ krb5_data_free(&k5_error_blob);
+ if (!error_blob->data) {
+ return False;
+ }
+ return True;
+}
+
+static BOOL kpasswd_make_pwchange_reply(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ NTSTATUS status,
+ enum samr_RejectReason reject_reason,
+ struct samr_DomInfo1 *dominfo,
+ DATA_BLOB *error_blob)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ "No such user when changing password",
+ error_blob);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_ACCESSDENIED,
+ "Not permitted to change password",
+ error_blob);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) {
+ const char *reject_string;
+ switch (reject_reason) {
+ case SAMR_REJECT_TOO_SHORT:
+ reject_string = talloc_asprintf(mem_ctx, "Password too short, password must be at least %d characters long",
+ dominfo->min_password_length);
+ break;
+ case SAMR_REJECT_COMPLEXITY:
+ reject_string = "Password does not meet complexity requirements";
+ break;
+ case SAMR_REJECT_OTHER:
+ reject_string = talloc_asprintf(mem_ctx, "Password must be at least %d characters long, and cannot match any of your %d previous passwords",
+ dominfo->min_password_length, dominfo->password_history_length);
+ break;
+ }
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_SOFTERROR,
+ reject_string,
+ error_blob);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx, "failed to set password: %s", nt_errstr(status)),
+ error_blob);
+
+ }
+ return kpasswdd_make_error_reply(kdc, mem_ctx, KRB5_KPASSWD_SUCCESS,
+ "Password changed",
+ error_blob);
+}
+
+/*
+ A user password change
+
+ Return true if there is a valid error packet (or sucess) formed in
+ the error_blob
+*/
+static BOOL kpasswdd_change_password(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info *session_info,
+ const char *password,
+ DATA_BLOB *reply)
+{
+ NTSTATUS status;
+ enum samr_RejectReason reject_reason;
+ struct samr_DomInfo1 *dominfo;
+ struct ldb_context *samdb;
+
+ samdb = samdb_connect(mem_ctx, system_session(mem_ctx));
+ if (!samdb) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ "Failed to open samdb",
+ reply);
+ }
+
+ DEBUG(3, ("Changing password of %s\n", dom_sid_string(mem_ctx, session_info->security_token->user_sid)));
+
+ /* User password change */
+ status = samdb_set_password_sid(samdb, mem_ctx,
+ session_info->security_token->user_sid,
+ password, NULL, NULL,
+ True, /* this is a user password change */
+ True, /* run restriction tests */
+ &reject_reason,
+ &dominfo);
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ reply);
+
+}
+
+static BOOL kpasswd_process_request(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ uint16_t version,
+ DATA_BLOB *input,
+ DATA_BLOB *reply)
+{
+ NTSTATUS status;
+ enum samr_RejectReason reject_reason;
+ struct samr_DomInfo1 *dominfo;
+ struct ldb_context *samdb;
+ struct auth_session_info *session_info;
+ struct ldb_message *msg = ldb_msg_new(gensec_security);
+ krb5_context context = kdc->smb_krb5_context->krb5_context;
+ int ret;
+ if (!samdb || !msg) {
+ return False;
+ }
+
+ if (!NT_STATUS_IS_OK(gensec_session_info(gensec_security,
+ &session_info))) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ "gensec_session_info failed!",
+ reply);
+ }
+
+ switch (version) {
+ case KRB5_KPASSWD_VERS_CHANGEPW:
+ {
+ char *password = talloc_strndup(mem_ctx, input->data, input->length);
+ if (!password) {
+ return False;
+ }
+ return kpasswdd_change_password(kdc, mem_ctx, session_info,
+ password, reply);
+ break;
+ }
+ case KRB5_KPASSWD_VERS_SETPW:
+ {
+ size_t len;
+ ChangePasswdDataMS chpw;
+ char *password;
+ krb5_principal principal;
+ char *set_password_on_princ;
+ struct ldb_dn *set_password_on_dn;
+
+ samdb = samdb_connect(gensec_security, session_info);
+
+ ret = decode_ChangePasswdDataMS(input->data, input->length,
+ &chpw, &len);
+ if (ret) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "failed to decode password change structure",
+ reply);
+ }
+
+ password = talloc_strndup(mem_ctx, chpw.newpasswd.data,
+ chpw.newpasswd.length);
+ if (!password) {
+ free_ChangePasswdDataMS(&chpw);
+ return False;
+ }
+ if ((chpw.targname && !chpw.targrealm)
+ || (!chpw.targname && chpw.targrealm)) {
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "Realm and principal must be both present, or neither present",
+ reply);
+ }
+ if (chpw.targname && chpw.targrealm) {
+ if (_krb5_principalname2krb5_principal(&principal, *chpw.targname,
+ *chpw.targrealm) != 0) {
+ free_ChangePasswdDataMS(&chpw);
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "failed to extract principal to set",
+ reply);
+
+ }
+ } else {
+ free_ChangePasswdDataMS(&chpw);
+ return kpasswdd_change_password(kdc, mem_ctx, session_info,
+ password, reply);
+ }
+ free_ChangePasswdDataMS(&chpw);
+
+ if (krb5_unparse_name(context, principal, &set_password_on_princ) != 0) {
+ krb5_free_principal(context, principal);
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_MALFORMED,
+ "krb5_unparse_name failed!",
+ reply);
+ }
+
+ krb5_free_principal(context, principal);
+
+ status = crack_user_principal_name(samdb, mem_ctx,
+ set_password_on_princ,
+ &set_password_on_dn, NULL);
+ free(set_password_on_princ);
+ if (!NT_STATUS_IS_OK(status)) {
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ reply);
+ }
+
+ /* Admin password set */
+ status = samdb_set_password(samdb, mem_ctx,
+ set_password_on_dn, NULL,
+ msg, password, NULL, NULL,
+ False, /* this is a user password change */
+ True, /* run restriction tests */
+ &reject_reason, &dominfo);
+
+ return kpasswd_make_pwchange_reply(kdc, mem_ctx,
+ status,
+ reject_reason,
+ dominfo,
+ reply);
+ }
+ default:
+ return kpasswdd_make_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_BAD_VERSION,
+ talloc_asprintf(mem_ctx,
+ "Protocol version %u not supported",
+ version),
+ reply);
+ }
+ return True;
+}
+
+BOOL kpasswdd_process(struct kdc_server *kdc,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *input,
+ DATA_BLOB *reply,
+ const char *from,
+ int src_port)
+{
+ BOOL ret;
+ const uint16_t header_len = 6;
+ uint16_t len;
+ uint16_t ap_req_len;
+ uint16_t krb_priv_len;
+ uint16_t version;
+ NTSTATUS nt_status;
+ DATA_BLOB ap_req, krb_priv_req, krb_priv_rep, ap_rep;
+ DATA_BLOB kpasswd_req, kpasswd_rep;
+ struct cli_credentials *server_credentials;
+ struct gensec_security *gensec_security;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ if (!tmp_ctx) {
+ return False;
+ }
+
+ if (input->length <= header_len) {
+ talloc_free(tmp_ctx);
+ return False;
+ }
+
+ len = RSVAL(input->data, 0);
+ if (input->length != len) {
+ talloc_free(tmp_ctx);
+ return False;
+ }
+
+ version = RSVAL(input->data, 2);
+ ap_req_len = RSVAL(input->data, 4);
+ if ((ap_req_len >= len) || (ap_req_len + header_len) >= len) {
+ talloc_free(tmp_ctx);
+ return False;
+ }
+
+ krb_priv_len = len - ap_req_len;
+ ap_req = data_blob_const(&input->data[header_len], ap_req_len);
+ krb_priv_req = data_blob_const(&input->data[header_len + ap_req_len], krb_priv_len);
+
+ nt_status = gensec_server_start(tmp_ctx, &gensec_security, kdc->task->event_ctx);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return False;
+ }
+
+ server_credentials
+ = cli_credentials_init(tmp_ctx);
+ if (!server_credentials) {
+ DEBUG(1, ("Failed to init server credentials\n"));
+ return False;
+ }
+
+ cli_credentials_set_conf(server_credentials);
+ nt_status = cli_credentials_set_stored_principal(server_credentials, "kadmin/changepw");
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "Failed to obtain server credentials for kadmin/changepw: %s\n",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ gensec_set_credentials(gensec_security, server_credentials);
+ gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
+
+ nt_status = gensec_start_mech_by_name(gensec_security, "krb5");
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return False;
+ }
+
+ nt_status = gensec_update(gensec_security, tmp_ctx, ap_req, &ap_rep);
+ if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "gensec_update failed: %s",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ nt_status = gensec_unwrap(gensec_security, tmp_ctx, &krb_priv_req, &kpasswd_req);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "gensec_unwrap failed: %s",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = kpasswd_process_request(kdc, tmp_ctx,
+ gensec_security,
+ version,
+ &kpasswd_req, &kpasswd_rep);
+ if (!ret) {
+ /* Argh! */
+ return False;
+ }
+
+ nt_status = gensec_wrap(gensec_security, tmp_ctx,
+ &kpasswd_rep, &krb_priv_rep);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
+ KRB5_KPASSWD_HARDERROR,
+ talloc_asprintf(mem_ctx,
+ "gensec_wrap failed: %s",
+ nt_errstr(nt_status)),
+ &krb_priv_rep);
+ ap_rep.length = 0;
+ if (ret) {
+ goto reply;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+reply:
+ *reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
+ if (!reply->data) {
+ return False;
+ }
+
+ RSSVAL(reply->data, 0, reply->length);
+ RSSVAL(reply->data, 2, 1); /* This is a version 1 reply, MS change/set or otherwise */
+ RSSVAL(reply->data, 4, ap_rep.length);
+ memcpy(reply->data + header_len,
+ ap_rep.data,
+ ap_rep.length);
+ memcpy(reply->data + header_len + ap_rep.length,
+ krb_priv_rep.data,
+ krb_priv_rep.length);
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+