summaryrefslogtreecommitdiff
path: root/source4/kdc
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2005-10-21 01:25:55 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 13:45:06 -0500
commit16bbafb7e862016e4c6281c32cc5b25adeae3cfc (patch)
tree3cdc7e0ca64b541ba61d7659b5b2867a39384175 /source4/kdc
parentf203903f1cc00ce443632ac5e9f725276b6c22a2 (diff)
downloadsamba-16bbafb7e862016e4c6281c32cc5b25adeae3cfc.tar.gz
samba-16bbafb7e862016e4c6281c32cc5b25adeae3cfc.tar.bz2
samba-16bbafb7e862016e4c6281c32cc5b25adeae3cfc.zip
r11239: Use ${REALM} for the realm in rootdse.ldif
Add the kpasswd server to our KDC, implementing the 'original' and Microsoft versions of the protocol. This works with the Heimdal kpasswd client, but not with MIT, I think due to ordering issues. It may not be worth the pain to have this code go via GENSEC, as it is very, very tied to krb5. This gets us one step closer to joins from Apple, Samba3 and other similar implementations. Andrew Bartlett (This used to be commit ab5dbbe10a162286aa6694c7e08de43b48e34cdb)
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;
+}
+