r17610: Added the ability for firefox to drive the winbindd
authorJeremy Allison <jra@samba.org>
Sat, 19 Aug 2006 01:04:54 +0000 (01:04 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 16:38:43 +0000 (11:38 -0500)
ntlm_auth module to allow it to use winbindd cached
credentials.The credentials are currently only stored
in a krb5 MIT environment - we need to add an option to
winbindd to allow passwords to be stored even in an NTLM-only
environment.
Patch from Robert O'Callahan, modified with some fixes
by me.
Jeremy.

source/Makefile.in
source/configure.in
source/lib/system.c
source/nsswitch/winbindd.c
source/nsswitch/winbindd_ccache_access.c [new file with mode: 0644]
source/nsswitch/winbindd_dual.c
source/nsswitch/winbindd_nss.h
source/nsswitch/winbindd_pam.c
source/utils/ntlm_auth.c

index 8f4f50a41acaeeaa79963ac5c911810344fe99d6..c6ea72c92f7c4fc498b917904a8ae3abdc5abc3c 100644 (file)
@@ -729,7 +729,8 @@ WINBINDD_OBJ1 = \
                nsswitch/winbindd_dual.o \
                nsswitch/winbindd_async.o \
                nsswitch/winbindd_creds.o \
-               nsswitch/winbindd_cred_cache.o 
+               nsswitch/winbindd_cred_cache.o \
+               nsswitch/winbindd_ccache_access.o
 
 WINBINDD_OBJ = \
                $(WINBINDD_OBJ1) $(PASSDB_OBJ) $(GROUPDB_OBJ) \
index 850b3cd9d39f58e480cc89a29cee83af3a9632e6..dc2f5161396959ec14f348177faba49d64379c3c 100644 (file)
@@ -5395,6 +5395,18 @@ AC_CHECK_MEMBERS([struct secmethod_table.method_attrlist], , ,
 AC_CHECK_MEMBERS([struct secmethod_table.method_version], , ,
        [#include <usersec.h>])
 
+AC_CACHE_CHECK([for SO_PEERCRED],samba_cv_HAVE_PEERCRED,[
+AC_TRY_COMPILE([#include <sys/types.h>
+#include <sys/socket.h>],
+[struct ucred cred;
+ socklen_t cred_len;
+ int ret = getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len);
+],
+samba_cv_HAVE_PEERCRED=yes,samba_cv_HAVE_PEERCRED=no,samba_cv_HAVE_PEERCRED=cross)])
+if test x"$samba_cv_HAVE_PEERCRED" = x"yes"; then
+    AC_DEFINE(HAVE_PEERCRED,1,[Whether we can use SO_PEERCRED to get socket credentials])
+fi
+
 
 #################################################
 # Check to see if we should use the included popt 
index 24c726b8f75ec6155822c9b430cbd7762437d2d0..bd7e4b8a67f26c811efccdcc8c6c43d02b57f1f1 100644 (file)
@@ -2242,3 +2242,28 @@ int sys_aio_suspend(const SMB_STRUCT_AIOCB * const cblist[], int n, const struct
        return -1;
 }
 #endif /* WITH_AIO */
+
+int getpeereid( int s, uid_t *uid)
+{
+#if defined(HAVE_PEERCRED)
+       struct ucred cred;
+       socklen_t cred_len = sizeof(struct ucred);
+       int ret;
+
+       ret = getsockopt(s, SOL_SOCKET, SO_PEERCRED, (void *)&cred, &cred_len);
+       if (ret != 0) {
+               return -1;
+       }
+
+       if (cred_len != sizeof(struct ucred)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       *uid = cred.uid;
+       return 0;
+#else
+       errno = ENOSYS;
+       return -1;
+#endif
+}
index a4cd724e00639af7a96798b9e66437e420d3860e..f6e742ac20ada6cf3cf93c55e8a82a88d1ad0cea 100644 (file)
@@ -253,6 +253,9 @@ static struct winbindd_dispatch_table {
          "WINBINDD_PRIV_PIPE_DIR" },
        { WINBINDD_GETDCNAME, winbindd_getdcname, "GETDCNAME" },
 
+       /* Credential cache access */
+       { WINBINDD_CCACHE_NTLMAUTH, winbindd_ccache_ntlm_auth, "NTLMAUTH" },
+
        /* WINS functions */
 
        { WINBINDD_WINS_BYNAME, winbindd_wins_byname, "WINS_BYNAME" },
diff --git a/source/nsswitch/winbindd_ccache_access.c b/source/nsswitch/winbindd_ccache_access.c
new file mode 100644 (file)
index 0000000..35d2cb4
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   Winbind daemon - cached credentials funcions
+
+   Copyright (C) Robert O'Callahan 2006
+   Copyright (C) Jeremy Allison 2006 (minor fixes to fit into Samba and
+                                     protect against integer wrap).
+   
+   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 "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static BOOL client_can_access_ccache_entry(uid_t client_uid,
+                                       struct WINBINDD_CCACHE_ENTRY *entry)
+{
+       if (client_uid == entry->uid || client_uid == 0) {
+               DEBUG(10, ("Access granted to uid %d\n", client_uid));
+               return True;
+       }
+
+       DEBUG(1, ("Access denied to uid %d (expected %d)\n", client_uid, entry->uid));
+       return False;
+}
+
+static NTSTATUS do_ntlm_auth_with_password(const char *username,
+                                       const char *domain,
+                                       const char *password,
+                                       const DATA_BLOB initial_msg,
+                                       const DATA_BLOB challenge_msg,
+                                       DATA_BLOB *auth_msg)
+{
+       NTSTATUS status;
+       NTLMSSP_STATE *ntlmssp_state = NULL;
+       DATA_BLOB dummy_msg, reply;
+
+       status = ntlmssp_client_start(&ntlmssp_state);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Could not start NTLMSSP client: %s\n",
+                       nt_errstr(status)));
+               goto done;
+       }
+
+       status = ntlmssp_set_username(ntlmssp_state, username);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Could not set username: %s\n",
+                       nt_errstr(status)));
+               goto done;
+       }
+
+       status = ntlmssp_set_domain(ntlmssp_state, domain);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Could not set domain: %s\n",
+                       nt_errstr(status)));
+               goto done;
+       }
+
+       status = ntlmssp_set_password(ntlmssp_state, password);
+        
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("Could not set password: %s\n",
+                       nt_errstr(status)));
+               goto done;
+       }
+
+       /* We need to get our protocol handler into the right state. So first
+          we ask it to generate the initial message. Actually the client has already
+          sent its own initial message, so we're going to drop this one on the floor.
+          The client might have sent a different message, for example with different
+          negotiation options, but as far as I can tell this won't hurt us. (Unless
+          the client sent a different username or domain, in which case that's their
+          problem for telling us the wrong username or domain.)
+          Since we have a copy of the initial message that the client sent, we could
+          resolve any discrepancies if we had to.
+       */
+       dummy_msg = data_blob(NULL, 0);
+       reply = data_blob(NULL, 0);
+       status = ntlmssp_update(ntlmssp_state, dummy_msg, &reply);
+       data_blob_free(&dummy_msg);
+       data_blob_free(&reply);
+
+       if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+               DEBUG(1, ("Failed to create initial message! [%s]\n",
+                       nt_errstr(status)));
+               goto done;
+       }
+
+       /* Now we are ready to handle the server's actual response. */
+       status = ntlmssp_update(ntlmssp_state, challenge_msg, &reply);
+
+       if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+               DEBUG(1, ("We didn't get a response to the challenge! [%s]\n",
+                       nt_errstr(status)));
+               data_blob_free(&reply);
+               goto done;
+       }
+       *auth_msg = reply;
+       status = NT_STATUS_OK;
+
+done:
+       ntlmssp_end(&ntlmssp_state);
+       return status;
+}
+
+static BOOL check_client_uid(struct winbindd_cli_state *state, uid_t uid)
+{
+       int ret;
+       uid_t ret_uid;
+
+       ret_uid = (uid_t)-1;
+
+       ret = getpeereid(state->sock, &ret_uid);
+       if (ret != 0) {
+               DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
+                       "denying access\n", strerror(errno)));
+               return False;
+       }
+
+       if (uid != ret_uid) {
+               DEBUG(1, ("check_client_uid: Client lied about its uid: said %d, "
+                       "actually was %d; denying access\n",
+                       uid, ret_uid));
+               return False;
+       }
+
+       return True;
+}
+
+void winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state)
+{
+       struct winbindd_domain *domain;
+       fstring name_domain, name_user;
+
+       /* Ensure null termination */
+       state->request.data.ccache_ntlm_auth.user[
+                       sizeof(state->request.data.ccache_ntlm_auth.user)-1]='\0';
+
+       DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid,
+               state->request.data.ccache_ntlm_auth.user));
+
+       /* Parse domain and username */
+
+       if (!parse_domain_user(state->request.data.ccache_ntlm_auth.user,
+                               name_domain, name_user)) {
+               DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
+                       state->request.data.ccache_ntlm_auth.user));
+               request_error(state);
+               return;
+       }
+
+       domain = find_auth_domain(state, name_domain);
+
+       if (domain == NULL) {
+               DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
+                       name_domain));
+               request_error(state);
+               return;
+       }
+
+       if (!check_client_uid(state, state->request.data.ccache_ntlm_auth.uid)) {
+               request_error(state);
+               return;
+       }
+
+       sendto_domain(state, domain);
+}
+
+enum winbindd_result winbindd_dual_ccache_ntlm_auth(struct winbindd_domain *domain,
+                                               struct winbindd_cli_state *state)
+{
+       NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
+       struct WINBINDD_CCACHE_ENTRY *entry;
+       DATA_BLOB initial, challenge, auth;
+       fstring name_domain, name_user;
+       uint32 initial_blob_len, challenge_blob_len, extra_len;
+
+       /* Ensure null termination */
+       state->request.data.ccache_ntlm_auth.user[
+               sizeof(state->request.data.ccache_ntlm_auth.user)-1]='\0';
+
+       DEBUG(3, ("winbindd_dual_ccache_ntlm_auth: [%5lu]: perform NTLM auth on "
+               "behalf of user %s (dual)\n", (unsigned long)state->pid,
+               state->request.data.ccache_ntlm_auth.user));
+
+       /* validate blob lengths */
+       initial_blob_len = state->request.data.ccache_ntlm_auth.initial_blob_len;
+       challenge_blob_len = state->request.data.ccache_ntlm_auth.challenge_blob_len;
+       extra_len = state->request.extra_len;
+
+       if (initial_blob_len > extra_len || challenge_blob_len > extra_len ||
+               initial_blob_len + challenge_blob_len > extra_len ||
+               initial_blob_len + challenge_blob_len < initial_blob_len ||
+               initial_blob_len + challenge_blob_len < challenge_blob_len) {
+
+               DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
+                       "or wrap. Buffer [%d+%d > %d]\n",
+                       initial_blob_len,
+                       challenge_blob_len,
+                       extra_len));
+               goto process_result;
+       }
+
+       /* Parse domain and username */
+       if (!parse_domain_user(state->request.data.ccache_ntlm_auth.user, name_domain, name_user)) {
+               DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse "
+                       "domain and user from name [%s]\n",
+                       state->request.data.ccache_ntlm_auth.user));
+               goto process_result;
+       }
+
+       entry = get_ccache_by_username(state->request.data.ccache_ntlm_auth.user);
+       if (entry == NULL) {
+               DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find "
+                       "credentials for user %s\n", 
+                       state->request.data.ccache_ntlm_auth.user));
+               goto process_result;
+       }
+
+       DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->ccname));
+
+       if (!client_can_access_ccache_entry(state->request.data.ccache_ntlm_auth.uid, entry)) {
+               goto process_result;
+       }
+
+       if (initial_blob_len == 0 && challenge_blob_len == 0) {
+               /* this is just a probe to see if credentials are available. */
+               result = NT_STATUS_OK;
+               state->response.data.ccache_ntlm_auth.auth_blob_len = 0;
+               goto process_result;
+       }
+
+       initial = data_blob(state->request.extra_data.data, initial_blob_len);
+       challenge = data_blob(state->request.extra_data.data + initial_blob_len, 
+                               state->request.data.ccache_ntlm_auth.challenge_blob_len);
+
+       if (!initial.data || !challenge.data) {
+               result = NT_STATUS_NO_MEMORY;
+       } else {
+               result = do_ntlm_auth_with_password(name_user, name_domain, entry->pass,
+                                               initial, challenge, &auth);
+       }
+
+       data_blob_free(&initial);
+       data_blob_free(&challenge);
+
+       if (!NT_STATUS_IS_OK(result)) {
+               goto process_result;
+       }
+
+       state->response.extra_data.data = smb_xmemdup(auth.data, auth.length);
+       if (!state->response.extra_data.data) {
+               result = NT_STATUS_NO_MEMORY;
+               goto process_result;
+       }
+       state->response.length += auth.length;
+       state->response.data.ccache_ntlm_auth.auth_blob_len = auth.length;
+
+       data_blob_free(&auth);
+
+  process_result:
+       return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
+}
index 32d85688b67fe84fc242086ae95127c3b1fe62d4..55f897603c11473f804345556c41a6d57df8f308 100644 (file)
@@ -369,6 +369,7 @@ static struct winbindd_child_dispatch_table child_dispatch_table[] = {
        { WINBINDD_ALLOCATE_GID,         winbindd_dual_allocate_gid,          "ALLOCATE_GID" },
        { WINBINDD_GETUSERDOMGROUPS,     winbindd_dual_getuserdomgroups,      "GETUSERDOMGROUPS" },
        { WINBINDD_DUAL_GETSIDALIASES,   winbindd_dual_getsidaliases,         "GETSIDALIASES" },
+       { WINBINDD_CCACHE_NTLMAUTH,      winbindd_dual_ccache_ntlm_auth,      "CCACHE_NTLM_AUTH" },
        /* End of list */
 
        { WINBINDD_NUM_CMDS, NULL, "NONE" }
index 4a95a3cf42c015cea5c720dfcb541ca0a9999205..b19026b4dbf126813a691c81f48c518c6408f324 100644 (file)
@@ -35,7 +35,7 @@
 
 /* Update this when you change the interface.  */
 
-#define WINBIND_INTERFACE_VERSION 16
+#define WINBIND_INTERFACE_VERSION 17
 
 /* Have to deal with time_t being 4 or 8 bytes due to structure alignment.
    On a 64bit Linux box, we have to support a constant structure size
@@ -153,6 +153,10 @@ enum winbindd_cmd {
        WINBINDD_DUAL_USERINFO,
        WINBINDD_DUAL_GETSIDALIASES,
 
+       /* Complete the challenge phase of the NTLM authentication
+          protocol using cached password. */
+       WINBINDD_CCACHE_NTLMAUTH,
+
        WINBINDD_NUM_CMDS
 };
 
@@ -292,8 +296,21 @@ struct winbindd_request {
                } dual_idmapset;
                BOOL list_all_domains;
 
+               struct {
+                       uid_t uid;
+                       fstring user;
+                       /* the effective uid of the client, must be the uid for 'user'.
+                          This is checked by the main daemon, trusted by children. */
+                       /* if the blobs are length zero, then this doesn't
+                          produce an actual challenge response. It merely
+                          succeeds if there are cached credentials available
+                          that could be used. */
+                       uint32 initial_blob_len; /* blobs in extra_data */
+                       uint32 challenge_blob_len;
+               } ccache_ntlm_auth;
+
                /* padding -- needed to fix alignment between 32bit and 64bit libs.
-                  The size if the sizeof the union without the padding aligned on 
+                  The size is the sizeof the union without the padding aligned on 
                   an 8 byte boundary.   --jerry */
 
                char padding[1560];
@@ -426,6 +443,9 @@ struct winbindd_response {
                        fstring shell;
                        uint32 group_rid;
                } user_info;
+               struct {
+                       uint32 auth_blob_len; /* blob in extra_data */
+               } ccache_ntlm_auth;
        } data;
 
        /* Variable length return data */
index 66d297090bf5d65ec3aab283427e69cbb8b49a78..365b2771606afd355aaa4e65b80c65bf996789f5 100644 (file)
@@ -200,8 +200,8 @@ static NTSTATUS check_info3_in_group(TALLOC_CTX *mem_ctx,
        return NT_STATUS_LOGON_FAILURE;
 }
 
-static struct winbindd_domain *find_auth_domain(struct winbindd_cli_state *state, 
-                                               const char *domain_name)
+struct winbindd_domain *find_auth_domain(struct winbindd_cli_state *state, 
+                                       const char *domain_name)
 {
        struct winbindd_domain *domain;
 
index f53b226fbb2f2f1d6f8befc0820c111b7ad3464d..1bed7794da1f1a9b4a4efdf7ea5952eb75a78f61 100644 (file)
@@ -6,6 +6,7 @@
    Copyright (C) Tim Potter      2000-2003
    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004
    Copyright (C) Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it> 2000 
+   Copyright (C) Robert O'Callahan 2006 (added cached credential code).
 
    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
@@ -92,6 +93,7 @@ static DATA_BLOB opt_lm_response;
 static DATA_BLOB opt_nt_response;
 static int request_lm_key;
 static int request_user_session_key;
+static int use_cached_creds;
 
 static const char *require_membership_of;
 static const char *require_membership_of_sid;
@@ -583,14 +585,17 @@ static NTSTATUS ntlm_auth_start_ntlmssp_client(NTLMSSP_STATE **client_ntlmssp_st
                return status;
        }
 
-       status = ntlmssp_set_password(*client_ntlmssp_state, opt_password);
+       if (opt_password) {
+               status = ntlmssp_set_password(*client_ntlmssp_state, opt_password);
        
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Could not set password: %s\n",
-                         nt_errstr(status)));
-               ntlmssp_end(client_ntlmssp_state);
-               return status;
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(1, ("Could not set password: %s\n",
+                                 nt_errstr(status)));
+                       ntlmssp_end(client_ntlmssp_state);
+                       return status;
+               }
        }
+
        return NT_STATUS_OK;
 }
 
@@ -617,6 +622,63 @@ static NTSTATUS ntlm_auth_start_ntlmssp_server(NTLMSSP_STATE **ntlmssp_state)
        return NT_STATUS_OK;
 }
 
+/*******************************************************************
+ Used by firefox to drive NTLM auth to IIS servers. Currently
+ requires krb5 enabled in winbindd as only then are the credentials
+ cached in memory. This needs fixing in winbindd. JRA.
+*******************************************************************/
+
+static NTSTATUS do_ccache_ntlm_auth(DATA_BLOB initial_msg, DATA_BLOB challenge_msg,
+                               DATA_BLOB *reply)
+{
+       struct winbindd_request wb_request;
+       struct winbindd_response wb_response;
+       NSS_STATUS result;
+
+       /* get winbindd to do the ntlmssp step on our behalf */
+       ZERO_STRUCT(wb_request);
+       ZERO_STRUCT(wb_response);
+
+       fstr_sprintf(wb_request.data.ccache_ntlm_auth.user,
+               "%s%c%s", opt_domain, winbind_separator(), opt_username);
+       wb_request.data.ccache_ntlm_auth.uid = geteuid();
+       wb_request.data.ccache_ntlm_auth.initial_blob_len = initial_msg.length;
+       wb_request.data.ccache_ntlm_auth.challenge_blob_len = challenge_msg.length;
+       wb_request.extra_len = initial_msg.length + challenge_msg.length;
+
+       if (wb_request.extra_len > 0) {
+               wb_request.extra_data.data = SMB_MALLOC_ARRAY(char, wb_request.extra_len);
+               if (wb_request.extra_data.data == NULL) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               memcpy(wb_request.extra_data.data, initial_msg.data, initial_msg.length);
+               memcpy(wb_request.extra_data.data + initial_msg.length,
+                       challenge_msg.data, challenge_msg.length);
+       }
+
+       result = winbindd_request_response(WINBINDD_CCACHE_NTLMAUTH, &wb_request, &wb_response);
+       SAFE_FREE(wb_request.extra_data.data);
+
+       if (result != NSS_STATUS_SUCCESS) {
+               free_response(&wb_response);
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       if (reply) {
+               *reply = data_blob(wb_response.extra_data.data,
+                               wb_response.data.ccache_ntlm_auth.auth_blob_len);
+               if (wb_response.data.ccache_ntlm_auth.auth_blob_len > 0 &&
+                               reply->data == NULL) {
+                       free_response(&wb_response);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+
+       free_response(&wb_response);
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
 static void manage_squid_ntlmssp_request(enum stdio_helper_mode stdio_helper_mode, 
                                         char *buf, int length) 
 {
@@ -737,7 +799,12 @@ static void manage_squid_ntlmssp_request(enum stdio_helper_mode stdio_helper_mod
 static void manage_client_ntlmssp_request(enum stdio_helper_mode stdio_helper_mode, 
                                         char *buf, int length) 
 {
+       /* The statics here are *HORRIBLE* and this entire concept
+          needs to be rewritten. Essentially it's using these statics
+          as the state in a state machine. BLEEEGH ! JRA. */
+
        static NTLMSSP_STATE *ntlmssp_state = NULL;
+       static DATA_BLOB initial_message;
        static char* want_feature_list = NULL;
        static uint32 neg_flags = 0;
        static BOOL have_session_key = False;
@@ -782,7 +849,18 @@ static void manage_client_ntlmssp_request(enum stdio_helper_mode stdio_helper_mo
                return;
        }
 
-       if (opt_password == NULL) {
+       if (!ntlmssp_state && use_cached_creds) {
+               /* check whether credentials are usable. */
+               DATA_BLOB empty_blob = data_blob(NULL, 0);
+
+               nt_status = do_ccache_ntlm_auth(empty_blob, empty_blob, NULL);
+               if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+                       /* failed to use cached creds */
+                       use_cached_creds = False;
+               }
+       }
+
+       if (opt_password == NULL && !use_cached_creds) {
                
                /* Request a password from the calling process.  After
                   sending it, the calling process should retry asking for the negotiate. */
@@ -829,12 +907,17 @@ static void manage_client_ntlmssp_request(enum stdio_helper_mode stdio_helper_mo
                }
                ntlmssp_want_feature_list(ntlmssp_state, want_feature_list);
                first = True;
+               initial_message = data_blob(NULL, 0);
        }
 
        DEBUG(10, ("got NTLMSSP packet:\n"));
        dump_data(10, (const char *)request.data, request.length);
 
-       nt_status = ntlmssp_update(ntlmssp_state, request, &reply);
+       if (use_cached_creds && !opt_password && !first) {
+               nt_status = do_ccache_ntlm_auth(initial_message, request, &reply);
+       } else {
+               nt_status = ntlmssp_update(ntlmssp_state, request, &reply);
+       }
        
        if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
                char *reply_base64 = base64_encode_data_blob(reply);
@@ -844,7 +927,11 @@ static void manage_client_ntlmssp_request(enum stdio_helper_mode stdio_helper_mo
                        x_fprintf(x_stdout, "KK %s\n", reply_base64);
                }
                SAFE_FREE(reply_base64);
-               data_blob_free(&reply);
+               if (first) {
+                       initial_message = reply;
+               } else {
+                       data_blob_free(&reply);
+               }
                DEBUG(10, ("NTLMSSP challenge\n"));
        } else if (NT_STATUS_IS_OK(nt_status)) {
                char *reply_base64 = base64_encode_data_blob(reply);
@@ -2081,7 +2168,8 @@ enum {
        OPT_LM_KEY,
        OPT_USER_SESSION_KEY,
        OPT_DIAGNOSTICS,
-       OPT_REQUIRE_MEMBERSHIP
+       OPT_REQUIRE_MEMBERSHIP,
+       OPT_USE_CACHED_CREDS
 };
 
  int main(int argc, const char **argv)
@@ -2116,6 +2204,7 @@ enum {
                { "password", 0, POPT_ARG_STRING, &opt_password, OPT_PASSWORD, "User's plaintext password"},            
                { "request-lm-key", 0, POPT_ARG_NONE, &request_lm_key, OPT_LM_KEY, "Retrieve LM session key"},
                { "request-nt-key", 0, POPT_ARG_NONE, &request_user_session_key, OPT_USER_SESSION_KEY, "Retrieve User (NT) session key"},
+               { "use-cached-creds", 0, POPT_ARG_NONE, &use_cached_creds, OPT_USE_CACHED_CREDS, "Use cached credentials if no password is given"},
                { "diagnostics", 0, POPT_ARG_NONE, &diagnostics, OPT_DIAGNOSTICS, "Perform diagnostics on the authentictaion chain"},
                { "require-membership-of", 0, POPT_ARG_STRING, &require_membership_of, OPT_REQUIRE_MEMBERSHIP, "Require that a user be a member of this group (either name or SID) for authentication to succeed" },
                POPT_COMMON_SAMBA