sync 3.0 into HEAD for the last time
[tprouty/samba.git] / source / libsmb / ntlmssp.c
index 92a18d25c03f3f78770ea93704c989d3db2b251b..42bf18d1d26516f85d9eba897ecded27b1224a7a 100644 (file)
@@ -25,6 +25,7 @@
 
 /**
  * Print out the NTLMSSP flags for debugging 
+ * @param neg_flags The flags from the packet
  */
 
 void debug_ntlmssp_flags(uint32 neg_flags)
@@ -78,6 +79,16 @@ static const uint8 *get_challenge(struct ntlmssp_state *ntlmssp_state)
        return chal;
 }
 
+/**
+ * Determine correct target name flags for reply, given server role 
+ * and negotiated flags
+ * 
+ * @param ntlmssp_state NTLMSSP State
+ * @param neg_flags The flags from the packet
+ * @param chal_flags The flags to be set in the reply packet
+ * @return The 'target name' string.
+ */
+
 static const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state,
                                       uint32 neg_flags, uint32 *chal_flags) 
 {
@@ -96,12 +107,22 @@ static const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state,
        }
 }
 
+/**
+ * Next state function for the Negotiate packet
+ * 
+ * @param ntlmssp_state NTLMSSP State
+ * @param request The request, as a DATA_BLOB
+ * @param request The reply, as an allocated DATA_BLOB, caller to free.
+ * @return Errors or MORE_PROCESSING_REQUIRED if a reply is sent. 
+ */
+
 static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state,
-                                        DATA_BLOB request, DATA_BLOB *reply) 
+                                        const DATA_BLOB request, DATA_BLOB *reply) 
 {
        DATA_BLOB struct_blob;
        fstring dnsname, dnsdomname;
-       uint32 ntlmssp_command, neg_flags, chal_flags;
+       uint32 neg_flags = 0;
+       uint32 ntlmssp_command, chal_flags;
        char *cliname=NULL, *domname=NULL;
        const uint8 *cryptkey;
        const char *target_name;
@@ -111,20 +132,24 @@ static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state,
        file_save("ntlmssp_negotiate.dat", request.data, request.length);
 #endif
 
-       if (!msrpc_parse(&request, "CddAA",
-                        "NTLMSSP",
-                        &ntlmssp_command,
-                        &neg_flags,
-                        &cliname,
-                        &domname)) {
-               return NT_STATUS_INVALID_PARAMETER;
+       if (request.length) {
+               if (!msrpc_parse(&request, "CddAA",
+                                "NTLMSSP",
+                                &ntlmssp_command,
+                                &neg_flags,
+                                &cliname,
+                                &domname)) {
+                       DEBUG(1, ("ntlmssp_server_negotiate: failed to parse NTLMSSP:\n"));
+                       dump_data(2, (const char *)request.data, request.length);
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+               
+               SAFE_FREE(cliname);
+               SAFE_FREE(domname);
+               
+               debug_ntlmssp_flags(neg_flags);
        }
-
-       SAFE_FREE(cliname);
-       SAFE_FREE(domname);
-  
-       debug_ntlmssp_flags(neg_flags);
-
+       
        cryptkey = ntlmssp_state->get_challenge(ntlmssp_state);
 
        data_blob_free(&ntlmssp_state->chal);
@@ -147,14 +172,17 @@ static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state,
        target_name = ntlmssp_target_name(ntlmssp_state, 
                                          neg_flags, &chal_flags); 
 
+       if (target_name == NULL)
+               return NT_STATUS_INVALID_PARAMETER;
+
        /* This should be a 'netbios domain -> DNS domain' mapping */
        dnsdomname[0] = '\0';
        get_mydomname(dnsdomname);
-       strlower(dnsdomname);
+       strlower_m(dnsdomname);
        
        dnsname[0] = '\0';
        get_myfullname(dnsname);
-       strlower(dnsname);
+       strlower_m(dnsname);
        
        if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) 
        {
@@ -165,7 +193,6 @@ static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state,
                        target_name_dns = dnsname;
                }
 
-               /* the numbers here are the string type flags */
                msrpc_gen(&struct_blob, "aaaaa",
                          ntlmssp_state->unicode, NTLMSSP_NAME_TYPE_DOMAIN, target_name,
                          ntlmssp_state->unicode, NTLMSSP_NAME_TYPE_SERVER, ntlmssp_state->get_global_myname(),
@@ -201,8 +228,17 @@ static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state,
        return NT_STATUS_MORE_PROCESSING_REQUIRED;
 }
 
+/**
+ * Next state function for the Authenticate packet
+ * 
+ * @param ntlmssp_state NTLMSSP State
+ * @param request The request, as a DATA_BLOB
+ * @param request The reply, as an allocated DATA_BLOB, caller to free.
+ * @return Errors or NT_STATUS_OK. 
+ */
+
 static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state,
-                                   DATA_BLOB request, DATA_BLOB *reply) 
+                                   const DATA_BLOB request, DATA_BLOB *reply) 
 {
        DATA_BLOB sess_key;
        uint32 ntlmssp_command, neg_flags;
@@ -239,13 +275,15 @@ static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state,
                         &ntlmssp_state->workstation,
                         &sess_key,
                         &neg_flags)) {
+               DEBUG(1, ("ntlmssp_server_auth: failed to parse NTLMSSP:\n"));
+               dump_data(2, (const char *)request.data, request.length);
                return NT_STATUS_INVALID_PARAMETER;
        }
 
        data_blob_free(&sess_key);
        
-       DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%d len2=%d\n",
-                ntlmssp_state->user, ntlmssp_state->domain, ntlmssp_state->workstation, ntlmssp_state->lm_resp.length, ntlmssp_state->nt_resp.length));
+       DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu\n",
+                ntlmssp_state->user, ntlmssp_state->domain, ntlmssp_state->workstation, (unsigned long)ntlmssp_state->lm_resp.length, (unsigned long)ntlmssp_state->nt_resp.length));
 
 #if 0
        file_save("nthash1.dat",  &ntlmssp_state->nt_resp.data,  &ntlmssp_state->nt_resp.length);
@@ -259,6 +297,12 @@ static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state,
        return nt_status;
 }
 
+/**
+ * Create an NTLMSSP state machine
+ * 
+ * @param ntlmssp_state NTLMSSP State, allocated by this function
+ */
+
 NTSTATUS ntlmssp_server_start(NTLMSSP_STATE **ntlmssp_state)
 {
        TALLOC_CTX *mem_ctx;
@@ -272,8 +316,6 @@ NTSTATUS ntlmssp_server_start(NTLMSSP_STATE **ntlmssp_state)
                return NT_STATUS_NO_MEMORY;
        }
 
-       ZERO_STRUCTP(*ntlmssp_state);
-
        (*ntlmssp_state)->mem_ctx = mem_ctx;
        (*ntlmssp_state)->get_challenge = get_challenge;
 
@@ -286,6 +328,12 @@ NTSTATUS ntlmssp_server_start(NTLMSSP_STATE **ntlmssp_state)
        return NT_STATUS_OK;
 }
 
+/**
+ * End an NTLMSSP state machine
+ * 
+ * @param ntlmssp_state NTLMSSP State, free()ed by this function
+ */
+
 NTSTATUS ntlmssp_server_end(NTLMSSP_STATE **ntlmssp_state)
 {
        TALLOC_CTX *mem_ctx = (*ntlmssp_state)->mem_ctx;
@@ -303,19 +351,34 @@ NTSTATUS ntlmssp_server_end(NTLMSSP_STATE **ntlmssp_state)
        return NT_STATUS_OK;
 }
 
+/**
+ * Next state function for the NTLMSSP state machine
+ * 
+ * @param ntlmssp_state NTLMSSP State
+ * @param request The request, as a DATA_BLOB
+ * @param request The reply, as an allocated DATA_BLOB, caller to free.
+ * @return Errors, NT_STATUS_MORE_PROCESSING_REQUIRED or NT_STATUS_OK. 
+ */
+
 NTSTATUS ntlmssp_server_update(NTLMSSP_STATE *ntlmssp_state, 
-                              DATA_BLOB request, DATA_BLOB *reply) 
+                              const DATA_BLOB request, DATA_BLOB *reply) 
 {
        uint32 ntlmssp_command;
        *reply = data_blob(NULL, 0);
 
-       if (!msrpc_parse(&request, "Cd",
-                        "NTLMSSP",
-                        &ntlmssp_command)) {
-               return NT_STATUS_INVALID_PARAMETER;
+       if (request.length) {
+               if (!msrpc_parse(&request, "Cd",
+                                "NTLMSSP",
+                                &ntlmssp_command)) {
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+       } else {
+               /* 'datagram' mode - no neg packet */
+               ntlmssp_command = NTLMSSP_NEGOTIATE;
        }
 
        if (ntlmssp_command != ntlmssp_state->expected_state) {
+               DEBUG(1, ("got NTLMSSP command %u, expected %u\n", ntlmssp_command, ntlmssp_state->expected_state));
                return NT_STATUS_INVALID_PARAMETER;
        }
 
@@ -324,7 +387,408 @@ NTSTATUS ntlmssp_server_update(NTLMSSP_STATE *ntlmssp_state,
        } else if (ntlmssp_command == NTLMSSP_AUTH) {
                return ntlmssp_server_auth(ntlmssp_state, request, reply);
        } else {
+               DEBUG(1, ("unknown NTLMSSP command %u, expected %u\n", ntlmssp_command, ntlmssp_state->expected_state));
                return NT_STATUS_INVALID_PARAMETER;
        }
 }
 
+/*********************************************************************
+ Client side NTLMSSP
+*********************************************************************/
+
+/**
+ * Next state function for the Initial packet
+ * 
+ * @param ntlmssp_state NTLMSSP State
+ * @param request The request, as a DATA_BLOB.  reply.data must be NULL
+ * @param request The reply, as an allocated DATA_BLOB, caller to free.
+ * @return Errors or NT_STATUS_OK. 
+ */
+
+static NTSTATUS ntlmssp_client_initial(struct ntlmssp_client_state *ntlmssp_state, 
+                                 DATA_BLOB reply, DATA_BLOB *next_request) 
+{
+       if (ntlmssp_state->unicode) {
+               ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE;
+       }
+       
+       if (ntlmssp_state->use_ntlmv2) {
+               ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2;
+       }
+
+       ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2;
+       
+       /* generate the ntlmssp negotiate packet */
+       msrpc_gen(next_request, "CddAA",
+                 "NTLMSSP",
+                 NTLMSSP_NEGOTIATE,
+                 ntlmssp_state->neg_flags,
+                 ntlmssp_state->get_domain(), 
+                 ntlmssp_state->get_global_myname());
+
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+/**
+ * Next state function for the Challenge Packet.  Generate an auth packet.
+ * 
+ * @param ntlmssp_state NTLMSSP State
+ * @param request The request, as a DATA_BLOB.  reply.data must be NULL
+ * @param request The reply, as an allocated DATA_BLOB, caller to free.
+ * @return Errors or NT_STATUS_OK. 
+ */
+
+static NTSTATUS ntlmssp_client_challenge(struct ntlmssp_client_state *ntlmssp_state, 
+                                        const DATA_BLOB reply, DATA_BLOB *next_request) 
+{
+       uint32 chal_flags, ntlmssp_command, unkn1, unkn2;
+       DATA_BLOB server_domain_blob;
+       DATA_BLOB challenge_blob;
+       DATA_BLOB struct_blob = data_blob(NULL, 0);
+       char *server_domain;
+       const char *chal_parse_string;
+       const char *auth_gen_string;
+       DATA_BLOB lm_response = data_blob(NULL, 0);
+       DATA_BLOB nt_response = data_blob(NULL, 0);
+       DATA_BLOB session_key = data_blob(NULL, 0);
+       DATA_BLOB encrypted_session_key = data_blob(NULL, 0);
+
+       if (!msrpc_parse(&reply, "CdBd",
+                        "NTLMSSP",
+                        &ntlmssp_command, 
+                        &server_domain_blob,
+                        &chal_flags)) {
+               DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#1)\n"));
+               dump_data(2, (const char *)reply.data, reply.length);
+
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+       
+       data_blob_free(&server_domain_blob);
+
+       DEBUG(3, ("Got challenge flags:\n"));
+       debug_ntlmssp_flags(chal_flags);
+
+       if (chal_flags & NTLMSSP_NEGOTIATE_UNICODE) {
+               if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) {
+                       chal_parse_string = "CdUdbddB";
+               } else {
+                       chal_parse_string = "CdUdbdd";
+               }
+               auth_gen_string = "CdBBUUUBd";
+               ntlmssp_state->unicode = True;
+               ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE;
+               ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM;
+       } else if (chal_flags & NTLMSSP_NEGOTIATE_OEM) {
+               if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) {
+                       chal_parse_string = "CdAdbddB";
+               } else {
+                       chal_parse_string = "CdAdbdd";
+               }
+
+               auth_gen_string = "CdBBAAABd";
+
+               ntlmssp_state->unicode = False;
+               ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_UNICODE;
+               ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM;
+       } else {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (chal_flags & NTLMSSP_NEGOTIATE_LM_KEY && lp_client_lanman_auth()) {
+               /* server forcing us to use LM */
+               ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY;
+               ntlmssp_state->use_ntlmv2 = False;
+       } else {
+               ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY;
+       }
+
+       if (!(chal_flags & NTLMSSP_NEGOTIATE_NTLM2)) {
+               ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2;
+       }
+
+       if (!(chal_flags & NTLMSSP_NEGOTIATE_128)) {
+               ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_128;
+       }
+
+       if (!(chal_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) {
+               ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH;
+       }
+
+       DEBUG(3, ("NTLMSSP: Set final flags:\n"));
+       debug_ntlmssp_flags(ntlmssp_state->neg_flags);
+
+       if (!msrpc_parse(&reply, chal_parse_string,
+                        "NTLMSSP",
+                        &ntlmssp_command, 
+                        &server_domain,
+                        &chal_flags,
+                        &challenge_blob, 8,
+                        &unkn1, &unkn2,
+                        &struct_blob)) {
+               DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#2)\n"));
+               dump_data(2, (const char *)reply.data, reply.length);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       ntlmssp_state->server_domain = talloc_strdup(ntlmssp_state->mem_ctx,
+                                                    server_domain);
+
+       SAFE_FREE(server_domain);
+       if (challenge_blob.length != 8) {
+               data_blob_free(&struct_blob);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (!ntlmssp_state->password) {
+               /* do nothing - blobs are zero length */
+       } else if (ntlmssp_state->use_ntlmv2) {
+
+               if (!struct_blob.length) {
+                       /* be lazy, match win2k - we can't do NTLMv2 without it */
+                       return NT_STATUS_INVALID_PARAMETER;
+               }
+
+               /* TODO: if the remote server is standalone, then we should replace 'domain'
+                  with the server name as supplied above */
+               
+               if (!SMBNTLMv2encrypt(ntlmssp_state->user, 
+                                     ntlmssp_state->domain, 
+                                     ntlmssp_state->password, &challenge_blob, 
+                                     &struct_blob, 
+                                     &lm_response, &nt_response, &session_key)) {
+                       data_blob_free(&challenge_blob);
+                       data_blob_free(&struct_blob);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) {
+               struct MD5Context md5_session_nonce_ctx;
+               uchar nt_hash[16];
+               uchar session_nonce[16];
+               uchar session_nonce_hash[16];
+               uchar nt_session_key[16];
+               E_md4hash(ntlmssp_state->password, nt_hash);
+               
+               lm_response = data_blob(NULL, 24);
+               generate_random_buffer(lm_response.data, 8, False);
+               memset(lm_response.data+8, 0, 16);
+
+               memcpy(session_nonce, challenge_blob.data, 8);
+               memcpy(&session_nonce[8], lm_response.data, 8);
+       
+               MD5Init(&md5_session_nonce_ctx);
+               MD5Update(&md5_session_nonce_ctx, challenge_blob.data, 8);
+               MD5Update(&md5_session_nonce_ctx, lm_response.data, 8);
+               MD5Final(session_nonce_hash, &md5_session_nonce_ctx);
+               
+               nt_response = data_blob(NULL, 24);
+               SMBNTencrypt(ntlmssp_state->password,
+                            session_nonce_hash,
+                            nt_response.data);
+
+               session_key = data_blob(NULL, 16);
+
+               SMBsesskeygen_ntv1(nt_hash, NULL, nt_session_key);
+               hmac_md5(nt_session_key, session_nonce, sizeof(session_nonce), session_key.data);
+       } else {
+               
+               
+               uchar lm_hash[16];
+               uchar nt_hash[16];
+               E_deshash(ntlmssp_state->password, lm_hash);
+               E_md4hash(ntlmssp_state->password, nt_hash);
+               
+               /* lanman auth is insecure, it may be disabled */
+               if (lp_client_lanman_auth()) {
+                       lm_response = data_blob(NULL, 24);
+                       SMBencrypt(ntlmssp_state->password,challenge_blob.data,
+                                  lm_response.data);
+                       }
+               
+               nt_response = data_blob(NULL, 24);
+               SMBNTencrypt(ntlmssp_state->password,challenge_blob.data,
+                            nt_response.data);
+               
+               session_key = data_blob(NULL, 16);
+               if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) 
+                   && lp_client_lanman_auth()) {
+                       SMBsesskeygen_lmv1(lm_hash, lm_response.data, 
+                                          session_key.data);
+               } else {
+                       SMBsesskeygen_ntv1(nt_hash, NULL, session_key.data);
+               }
+       }
+       data_blob_free(&struct_blob);
+
+       /* Key exchange encryptes a new client-generated session key with
+          the password-derived key */
+       if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
+               uint8 client_session_key[16];
+               
+               generate_random_buffer(client_session_key, sizeof(client_session_key), False);  
+               encrypted_session_key = data_blob(client_session_key, sizeof(client_session_key));
+               SamOEMhash(encrypted_session_key.data, session_key.data, encrypted_session_key.length);
+               data_blob_free(&session_key);
+               session_key = data_blob(client_session_key, sizeof(client_session_key));
+       }
+
+       /* this generates the actual auth packet */
+       if (!msrpc_gen(next_request, auth_gen_string, 
+                      "NTLMSSP", 
+                      NTLMSSP_AUTH, 
+                      lm_response.data, lm_response.length,
+                      nt_response.data, nt_response.length,
+                      ntlmssp_state->domain, 
+                      ntlmssp_state->user, 
+                      ntlmssp_state->get_global_myname(), 
+                      encrypted_session_key.data, encrypted_session_key.length,
+                      ntlmssp_state->neg_flags)) {
+               
+               data_blob_free(&lm_response);
+               data_blob_free(&nt_response);
+               data_blob_free(&session_key);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       data_blob_free(&encrypted_session_key);
+
+       data_blob_free(&ntlmssp_state->chal);
+       data_blob_free(&ntlmssp_state->lm_resp);
+       data_blob_free(&ntlmssp_state->nt_resp);
+       data_blob_free(&ntlmssp_state->session_key);
+
+       ntlmssp_state->chal = challenge_blob;
+       ntlmssp_state->lm_resp = lm_response;
+       ntlmssp_state->nt_resp = nt_response;
+       ntlmssp_state->session_key = session_key;
+
+       return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+NTSTATUS ntlmssp_client_start(NTLMSSP_CLIENT_STATE **ntlmssp_state)
+{
+       TALLOC_CTX *mem_ctx;
+
+       mem_ctx = talloc_init("NTLMSSP Client context");
+       
+       *ntlmssp_state = talloc_zero(mem_ctx, sizeof(**ntlmssp_state));
+       if (!*ntlmssp_state) {
+               DEBUG(0,("ntlmssp_server_start: talloc failed!\n"));
+               talloc_destroy(mem_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       (*ntlmssp_state)->mem_ctx = mem_ctx;
+
+       (*ntlmssp_state)->get_global_myname = global_myname;
+       (*ntlmssp_state)->get_domain = lp_workgroup;
+
+       (*ntlmssp_state)->unicode = True;
+
+       (*ntlmssp_state)->use_ntlmv2 = lp_client_ntlmv2_auth();
+
+       (*ntlmssp_state)->neg_flags = 
+               NTLMSSP_NEGOTIATE_128 |
+               NTLMSSP_NEGOTIATE_NTLM |
+               NTLMSSP_NEGOTIATE_NTLM2 |
+               NTLMSSP_NEGOTIATE_KEY_EXCH |
+               NTLMSSP_REQUEST_TARGET;
+
+       (*ntlmssp_state)->ref_count = 1;
+
+       return NT_STATUS_OK;
+}
+
+NTSTATUS ntlmssp_client_end(NTLMSSP_CLIENT_STATE **ntlmssp_state)
+{
+       TALLOC_CTX *mem_ctx = (*ntlmssp_state)->mem_ctx;
+
+       (*ntlmssp_state)->ref_count--;
+
+       if ((*ntlmssp_state)->ref_count == 0) {
+               data_blob_free(&(*ntlmssp_state)->chal);
+               data_blob_free(&(*ntlmssp_state)->lm_resp);
+               data_blob_free(&(*ntlmssp_state)->nt_resp);
+               data_blob_free(&(*ntlmssp_state)->session_key);
+               data_blob_free(&(*ntlmssp_state)->stored_response);
+               talloc_destroy(mem_ctx);
+       }
+
+       *ntlmssp_state = NULL;
+       return NT_STATUS_OK;
+}
+
+NTSTATUS ntlmssp_client_update(NTLMSSP_CLIENT_STATE *ntlmssp_state, 
+                              DATA_BLOB reply, DATA_BLOB *next_request) 
+{
+       NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
+       uint32 ntlmssp_command;
+       *next_request = data_blob(NULL, 0);
+
+       if (!reply.length) {
+               /* If there is a cached reply, use it - otherwise this is the first packet */
+               if (!ntlmssp_state->stored_response.length) {
+                       return ntlmssp_client_initial(ntlmssp_state, reply, next_request);
+               }
+               
+               reply = ntlmssp_state->stored_response;
+       }
+
+       if (!msrpc_parse(&reply, "Cd",
+                        "NTLMSSP",
+                        &ntlmssp_command)) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (ntlmssp_command == NTLMSSP_CHALLENGE) {
+               nt_status = ntlmssp_client_challenge(ntlmssp_state, reply, next_request);
+       }
+       if (ntlmssp_state->stored_response.length) {
+               data_blob_free(&ntlmssp_state->stored_response);
+       }
+       return nt_status;
+}
+
+NTSTATUS ntlmssp_set_username(NTLMSSP_CLIENT_STATE *ntlmssp_state, const char *user) 
+{
+       ntlmssp_state->user = talloc_strdup(ntlmssp_state->mem_ctx, user);
+       if (!ntlmssp_state->user) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       return NT_STATUS_OK;
+}
+
+NTSTATUS ntlmssp_set_password(NTLMSSP_CLIENT_STATE *ntlmssp_state, const char *password) 
+{
+       if (!password) {
+               ntlmssp_state->password = NULL;
+       } else {
+               ntlmssp_state->password = talloc_strdup(ntlmssp_state->mem_ctx, password);
+               if (!ntlmssp_state->password) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+       return NT_STATUS_OK;
+}
+
+NTSTATUS ntlmssp_set_domain(NTLMSSP_CLIENT_STATE *ntlmssp_state, const char *domain) 
+{
+       ntlmssp_state->domain = talloc_strdup(ntlmssp_state->mem_ctx, domain);
+       if (!ntlmssp_state->domain) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       return NT_STATUS_OK;
+}
+
+/**
+ *  Store a DATA_BLOB containing an NTLMSSP response, for use later.
+ *  This 'keeps' the data blob - the caller must *not* free it.
+ */
+
+NTSTATUS ntlmssp_client_store_response(NTLMSSP_CLIENT_STATE *ntlmssp_state,
+                                      DATA_BLOB response) 
+{
+       data_blob_free(&ntlmssp_state->stored_response);
+       ntlmssp_state->stored_response = response;
+       return NT_STATUS_OK;
+}