r11239: Use ${REALM} for the realm in rootdse.ldif
authorAndrew Bartlett <abartlet@samba.org>
Fri, 21 Oct 2005 01:25:55 +0000 (01:25 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 18:45:06 +0000 (13:45 -0500)
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)

source4/auth/gensec/gensec_krb5.c
source4/kdc/config.mk
source4/kdc/kdc.c
source4/kdc/kdc.h
source4/kdc/kpasswdd.c [new file with mode: 0644]
source4/param/loadparm.c
source4/rpc_server/drsuapi/drsuapi_cracknames.c
source4/setup/rootdse.ldif

index 5297a1d9645e7a0f64317162df95803ba7153483..64de8211dd2b60be3fbfeb2018069b38d3369ec8 100644 (file)
@@ -566,12 +566,11 @@ static NTSTATUS gensec_krb5_wrap(struct gensec_security *gensec_security,
        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_mk_priv(context, auth_context, &input, &output, &replay);
+               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, 
index ce655dea82d54fa69f26ef948f7c6b44ccf81c9d..5939d0eacb4b3f30fc0f0fbd66ec61b68de5415a 100644 (file)
@@ -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
index 158aa85f4974f2bf08d5c7e065b37ac85d2c5a8a..4a1bb0ad05cdc42c75341b2ed00430bb5b6a6a6b 100644 (file)
@@ -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);
@@ -230,25 +205,6 @@ static void kdc_tcp_terminate_connection(struct kdc_tcp_connection *kdcconn, con
        stream_terminate_connection(kdcconn->conn, reason);
 }
 
-/*
-  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
 */
@@ -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,
@@ -422,6 +426,32 @@ static const struct stream_server_ops kdc_tcp_stream_ops = {
        .send_handler           = kdc_tcp_send
 };
 
+/*
+  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
 */
@@ -429,21 +459,32 @@ 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))); 
index 2b08d648a95f670fe3a1eff78d0f256287274105..99c419d4d9c655c85a0d91998788d6f435092034 100644 (file)
 
 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 (file)
index 0000000..4f15ccc
--- /dev/null
@@ -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;
+}
+
index 03c05998715b9f1bce7c495c4942cb447153ab42..6395bff1826ddd5be4e7470e69ce0f77b2d03878 100644 (file)
@@ -162,6 +162,7 @@ typedef struct
        int dgram_port;
        int cldap_port;
        int krb5_port;
+       int kpasswd_port;
        int web_port;
        char *socket_options;
        BOOL bWINSsupport;
@@ -445,6 +446,7 @@ static struct parm_struct parm_table[] = {
        {"dgram port", P_INTEGER, P_GLOBAL, &Globals.dgram_port, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
        {"cldap port", P_INTEGER, P_GLOBAL, &Globals.cldap_port, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
        {"krb5 port", P_INTEGER, P_GLOBAL, &Globals.krb5_port, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
+       {"kpasswd port", P_INTEGER, P_GLOBAL, &Globals.kpasswd_port, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
        {"web port", P_INTEGER, P_GLOBAL, &Globals.web_port, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
        {"tls enabled", P_BOOL, P_GLOBAL, &Globals.tls_enabled, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
        {"tls keyfile", P_STRING, P_GLOBAL, &Globals.tls_keyfile, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER},
@@ -685,6 +687,7 @@ static void init_globals(void)
        do_parameter("dgram port", "138", NULL);
        do_parameter("cldap port", "389", NULL);
        do_parameter("krb5 port", "88", NULL);
+       do_parameter("kpasswd port", "464", NULL);
        do_parameter("web port", "901", NULL);
        do_parameter("swat directory", dyn_SWATDIR, NULL);
 
@@ -799,6 +802,7 @@ FN_GLOBAL_INTEGER(lp_nbt_port, &Globals.nbt_port)
 FN_GLOBAL_INTEGER(lp_dgram_port, &Globals.dgram_port)
 FN_GLOBAL_INTEGER(lp_cldap_port, &Globals.cldap_port)
 FN_GLOBAL_INTEGER(lp_krb5_port, &Globals.krb5_port)
+FN_GLOBAL_INTEGER(lp_kpasswd_port, &Globals.kpasswd_port)
 FN_GLOBAL_INTEGER(lp_web_port, &Globals.web_port)
 FN_GLOBAL_STRING(lp_dos_charset, &Globals.dos_charset)
 FN_GLOBAL_STRING(lp_swat_directory, &Globals.swat_directory)
index 8adeb024d8505cfe92e4f63f6e2b3aa7ab7929f8..3a4d33715423d1fa556643d2934345565702dec7 100644 (file)
@@ -768,3 +768,63 @@ WERROR dcesrv_drsuapi_DsCrackNames(struct dcesrv_call_state *dce_call, TALLOC_CT
        
        return WERR_UNKNOWN_LEVEL;
 }
+
+NTSTATUS crack_user_principal_name(struct ldb_context *sam_ctx, 
+                                  TALLOC_CTX *mem_ctx, 
+                                  const char *user_principal_name, 
+                                  struct ldb_dn **user_dn,
+                                  struct ldb_dn **domain_dn) 
+{
+       WERROR werr;
+       NTSTATUS status;
+       struct drsuapi_DsNameInfo1 info1;
+       werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
+                                 DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL,
+                                 DRSUAPI_DS_NAME_FORMAT_FQDN_1779, 
+                                 user_principal_name,
+                                 &info1);
+       if (!W_ERROR_IS_OK(werr)) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       switch (info1.status) {
+       case DRSUAPI_DS_NAME_STATUS_OK:
+               break;
+       case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+       case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+       case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+               return NT_STATUS_NO_SUCH_USER;
+       case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+       default:
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+       
+       *user_dn = ldb_dn_explode(mem_ctx, info1.result_name);
+       
+       if (domain_dn) {
+               werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
+                                         DRSUAPI_DS_NAME_FORMAT_CANONICAL,
+                                         DRSUAPI_DS_NAME_FORMAT_FQDN_1779, 
+                                         talloc_asprintf(mem_ctx, "%s/", 
+                                                         info1.dns_domain_name),
+                                         &info1);
+               if (!W_ERROR_IS_OK(werr)) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+               switch (info1.status) {
+               case DRSUAPI_DS_NAME_STATUS_OK:
+                       break;
+               case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
+               case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
+               case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
+                       return NT_STATUS_NO_SUCH_USER;
+               case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
+               default:
+                       return NT_STATUS_UNSUCCESSFUL;
+               }
+               
+               *domain_dn = ldb_dn_explode(mem_ctx, info1.result_name);
+       }
+
+       return NT_STATUS_OK;
+       
+}
index 6c74b5d63104c48893b777920c0a47848b409ffb..67c49f0f26964f1eef3a2c5f6d9f7495d79908c3 100644 (file)
@@ -24,7 +24,7 @@ supportedLDAPVersion: 3
 highestCommittedUSN: _DYNAMIC_
 supportedSASLMechanisms: GSS-SPNEGO
 dnsHostName: ${DNSNAME}
-ldapServiceName: ${DNSDOMAIN}:${NETBIOSNAME}$@${DNSDOMAIN}
+ldapServiceName: ${DNSDOMAIN}:${NETBIOSNAME}$@${REALM}
 serverName: CN=${NETBIOSNAME},CN=Servers,CN=Default-First-Site,CN=Sites,CN=Configuration,${BASEDN}
 isSynchronized: _DYNAMIC_
 domainFunctionality: 0