s4:schannel more readable check logic
[ira/wip.git] / libcli / auth / schannel_state_ldb.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    module to store/fetch session keys for the schannel server
5
6    Copyright (C) Andrew Tridgell 2004
7    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2009
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "lib/ldb/include/ldb.h"
25 #include "librpc/gen_ndr/ndr_security.h"
26 #include "ldb_wrap.h"
27 #include "../lib/util/util_ldb.h"
28 #include "libcli/auth/libcli_auth.h"
29 #include "auth/auth.h"
30 #include "param/param.h"
31 #include "../libcli/auth/schannel_state.h"
32
33 static struct ldb_val *schannel_dom_sid_ldb_val(TALLOC_CTX *mem_ctx,
34                                                 struct dom_sid *sid)
35 {
36         enum ndr_err_code ndr_err;
37         struct ldb_val *v;
38
39         v = talloc(mem_ctx, struct ldb_val);
40         if (!v) return NULL;
41
42         ndr_err = ndr_push_struct_blob(v, mem_ctx, NULL, sid,
43                                        (ndr_push_flags_fn_t)ndr_push_dom_sid);
44         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
45                 talloc_free(v);
46                 return NULL;
47         }
48
49         return v;
50 }
51
52 static struct dom_sid *schannel_ldb_val_dom_sid(TALLOC_CTX *mem_ctx,
53                                                  const struct ldb_val *v)
54 {
55         enum ndr_err_code ndr_err;
56         struct dom_sid *sid;
57
58         sid = talloc(mem_ctx, struct dom_sid);
59         if (!sid) return NULL;
60
61         ndr_err = ndr_pull_struct_blob(v, sid, NULL, sid,
62                                         (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
63         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
64                 talloc_free(sid);
65                 return NULL;
66         }
67         return sid;
68 }
69
70
71 /*
72   remember an established session key for a netr server authentication
73   use a simple ldb structure
74 */
75 NTSTATUS schannel_store_session_key_ldb(struct ldb_context *ldb,
76                                         TALLOC_CTX *mem_ctx,
77                                         struct netlogon_creds_CredentialState *creds)
78 {
79         struct ldb_message *msg;
80         struct ldb_val val, seed, client_state, server_state;
81         struct ldb_val *sid_val;
82         char *f;
83         char *sct;
84         int ret;
85
86         f = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->negotiate_flags);
87
88         if (f == NULL) {
89                 return NT_STATUS_NO_MEMORY;
90         }
91
92         sct = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->secure_channel_type);
93
94         if (sct == NULL) {
95                 return NT_STATUS_NO_MEMORY;
96         }
97
98         msg = ldb_msg_new(mem_ctx);
99         if (msg == NULL) {
100                 return NT_STATUS_NO_MEMORY;
101         }
102
103         msg->dn = ldb_dn_new_fmt(msg, ldb, "computerName=%s", creds->computer_name);
104         if ( ! msg->dn) {
105                 return NT_STATUS_NO_MEMORY;
106         }
107
108         sid_val = schannel_dom_sid_ldb_val(msg, creds->sid);
109         if (sid_val == NULL) {
110                 return NT_STATUS_NO_MEMORY;
111         }
112
113         val.data = creds->session_key;
114         val.length = sizeof(creds->session_key);
115
116         seed.data = creds->seed.data;
117         seed.length = sizeof(creds->seed.data);
118
119         client_state.data = creds->client.data;
120         client_state.length = sizeof(creds->client.data);
121         server_state.data = creds->server.data;
122         server_state.length = sizeof(creds->server.data);
123
124         ldb_msg_add_string(msg, "objectClass", "schannelState");
125         ldb_msg_add_value(msg, "sessionKey", &val, NULL);
126         ldb_msg_add_value(msg, "seed", &seed, NULL);
127         ldb_msg_add_value(msg, "clientState", &client_state, NULL);
128         ldb_msg_add_value(msg, "serverState", &server_state, NULL);
129         ldb_msg_add_string(msg, "negotiateFlags", f);
130         ldb_msg_add_string(msg, "secureChannelType", sct);
131         ldb_msg_add_string(msg, "accountName", creds->account_name);
132         ldb_msg_add_string(msg, "computerName", creds->computer_name);
133         ldb_msg_add_value(msg, "objectSid", sid_val, NULL);
134
135         ret = ldb_add(ldb, msg);
136         if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
137                 int i;
138                 /* from samdb_replace() */
139                 /* mark all the message elements as LDB_FLAG_MOD_REPLACE */
140                 for (i=0;i<msg->num_elements;i++) {
141                         msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
142                 }
143
144                 ret = ldb_modify(ldb, msg);
145         }
146
147         /* We don't need a transaction here, as we either add or
148          * modify records, never delete them, so it must exist */
149
150         if (ret != LDB_SUCCESS) {
151                 DEBUG(0,("Unable to add %s to session key db - %s\n",
152                          ldb_dn_get_linearized(msg->dn), ldb_errstring(ldb)));
153                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
154         }
155
156         return NT_STATUS_OK;
157 }
158
159 /*
160   read back a credentials back for a computer
161 */
162 NTSTATUS schannel_fetch_session_key_ldb(struct ldb_context *ldb,
163                                         TALLOC_CTX *mem_ctx,
164                                         const char *computer_name,
165                                         struct netlogon_creds_CredentialState **creds)
166 {
167         struct ldb_result *res;
168         int ret;
169         const struct ldb_val *val;
170
171         *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState);
172         if (!*creds) {
173                 return NT_STATUS_NO_MEMORY;
174         }
175
176         ret = ldb_search(ldb, mem_ctx, &res,
177                                  NULL, LDB_SCOPE_SUBTREE, NULL,
178                                 "(computerName=%s)", computer_name);
179         if (ret != LDB_SUCCESS) {
180                 DEBUG(3,("schannel: Failed to find a record for client %s: %s\n", computer_name, ldb_errstring(ldb)));
181                 return NT_STATUS_INVALID_HANDLE;
182         }
183         if (res->count != 1) {
184                 DEBUG(3,("schannel: Failed to find a record for client: %s (found %d records)\n", computer_name, res->count));
185                 talloc_free(res);
186                 return NT_STATUS_INVALID_HANDLE;
187         }
188
189         val = ldb_msg_find_ldb_val(res->msgs[0], "sessionKey");
190         if (val == NULL || val->length != 16) {
191                 DEBUG(1,("schannel: record in schannel DB must contain a sessionKey of length 16, when searching for client: %s\n", computer_name));
192                 talloc_free(res);
193                 return NT_STATUS_INTERNAL_ERROR;
194         }
195
196         memcpy((*creds)->session_key, val->data, 16);
197
198         val = ldb_msg_find_ldb_val(res->msgs[0], "seed");
199         if (val == NULL || val->length != 8) {
200                 DEBUG(1,("schannel: record in schannel DB must contain a vaid seed of length 8, when searching for client: %s\n", computer_name));
201                 talloc_free(res);
202                 return NT_STATUS_INTERNAL_ERROR;
203         }
204
205         memcpy((*creds)->seed.data, val->data, 8);
206
207         val = ldb_msg_find_ldb_val(res->msgs[0], "clientState");
208         if (val == NULL || val->length != 8) {
209                 DEBUG(1,("schannel: record in schannel DB must contain a vaid clientState of length 8, when searching for client: %s\n", computer_name));
210                 talloc_free(res);
211                 return NT_STATUS_INTERNAL_ERROR;
212         }
213         memcpy((*creds)->client.data, val->data, 8);
214
215         val = ldb_msg_find_ldb_val(res->msgs[0], "serverState");
216         if (val == NULL || val->length != 8) {
217                 DEBUG(1,("schannel: record in schannel DB must contain a vaid serverState of length 8, when searching for client: %s\n", computer_name));
218                 talloc_free(res);
219                 return NT_STATUS_INTERNAL_ERROR;
220         }
221         memcpy((*creds)->server.data, val->data, 8);
222
223         (*creds)->negotiate_flags = ldb_msg_find_attr_as_int(res->msgs[0], "negotiateFlags", 0);
224
225         (*creds)->secure_channel_type = ldb_msg_find_attr_as_int(res->msgs[0], "secureChannelType", 0);
226
227         (*creds)->account_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "accountName", NULL));
228         if ((*creds)->account_name == NULL) {
229                 talloc_free(res);
230                 return NT_STATUS_NO_MEMORY;
231         }
232
233         (*creds)->computer_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "computerName", NULL));
234         if ((*creds)->computer_name == NULL) {
235                 talloc_free(res);
236                 return NT_STATUS_NO_MEMORY;
237         }
238
239         val = ldb_msg_find_ldb_val(res->msgs[0], "objectSid");
240         if (val) {
241                 (*creds)->sid = schannel_ldb_val_dom_sid(*creds, val);
242                 if ((*creds)->sid == NULL) {
243                         talloc_free(res);
244                         return NT_STATUS_INTERNAL_ERROR;
245                 }
246         } else {
247                 (*creds)->sid = NULL;
248         }
249
250         talloc_free(res);
251         return NT_STATUS_OK;
252 }
253
254 /*
255   Validate an incoming authenticator against the credentials for the remote machine.
256
257   The credentials are (re)read and from the schannel database, and
258   written back after the caclulations are performed.
259
260   The creds_out parameter (if not NULL) returns the credentials, if
261   the caller needs some of that information.
262
263 */
264 NTSTATUS schannel_creds_server_step_check_ldb(struct ldb_context *ldb,
265                                               TALLOC_CTX *mem_ctx,
266                                               const char *computer_name,
267                                               struct netr_Authenticator *received_authenticator,
268                                               struct netr_Authenticator *return_authenticator,
269                                               struct netlogon_creds_CredentialState **creds_out)
270 {
271         struct netlogon_creds_CredentialState *creds = NULL;
272         NTSTATUS nt_status;
273         int ret;
274
275         /* If we are flaged that schannel is required for a call, and
276          * it is not in use, then make this an error */
277
278         ret = ldb_transaction_start(ldb);
279         if (ret != 0) {
280                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
281         }
282
283         /* Because this is a shared structure (even across
284          * disconnects) we must update the database every time we
285          * update the structure */
286
287         nt_status = schannel_fetch_session_key_ldb(ldb, ldb, computer_name,
288                                                    &creds);
289         if (!NT_STATUS_IS_OK(nt_status)) {
290                 ldb_transaction_cancel(ldb);
291                 return nt_status;
292         }
293
294         nt_status = netlogon_creds_server_step_check(creds,
295                                                      received_authenticator,
296                                                      return_authenticator);
297         if (!NT_STATUS_IS_OK(nt_status)) {
298                 ldb_transaction_cancel(ldb);
299                 talloc_free(creds);
300                 return nt_status;
301         }
302
303         nt_status = schannel_store_session_key_ldb(ldb, mem_ctx, creds);
304         if (!NT_STATUS_IS_OK(nt_status)) {
305                 ldb_transaction_cancel(ldb);
306                 talloc_free(creds);
307                 return nt_status;
308         }
309
310         ldb_transaction_commit(ldb);
311         if (ret != 0) {
312                 talloc_free(creds);
313                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
314         }
315
316         if (creds_out) {
317                 *creds_out = creds;
318                 talloc_steal(mem_ctx, creds);
319         } else {
320                 talloc_free(creds);
321         }
322
323         return NT_STATUS_OK;
324 }