s3:Makefile: another attempt to fix the wbclient dependency
[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 "auth/gensec/schannel_state.h"
32 #include "../libcli/auth/schannel_state_proto.h"
33
34 static struct ldb_val *schannel_dom_sid_ldb_val(TALLOC_CTX *mem_ctx,
35                                                 struct dom_sid *sid)
36 {
37         enum ndr_err_code ndr_err;
38         struct ldb_val *v;
39
40         v = talloc(mem_ctx, struct ldb_val);
41         if (!v) return NULL;
42
43         ndr_err = ndr_push_struct_blob(v, mem_ctx, NULL, sid,
44                                        (ndr_push_flags_fn_t)ndr_push_dom_sid);
45         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
46                 talloc_free(v);
47                 return NULL;
48         }
49
50         return v;
51 }
52
53 static struct dom_sid *schannel_ldb_val_dom_sid(TALLOC_CTX *mem_ctx,
54                                                  const struct ldb_val *v)
55 {
56         enum ndr_err_code ndr_err;
57         struct dom_sid *sid;
58
59         sid = talloc(mem_ctx, struct dom_sid);
60         if (!sid) return NULL;
61
62         ndr_err = ndr_pull_struct_blob(v, sid, NULL, sid,
63                                         (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
64         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
65                 talloc_free(sid);
66                 return NULL;
67         }
68         return sid;
69 }
70
71
72 /*
73   remember an established session key for a netr server authentication
74   use a simple ldb structure
75 */
76 NTSTATUS schannel_store_session_key_ldb(struct ldb_context *ldb,
77                                         TALLOC_CTX *mem_ctx,
78                                         struct netlogon_creds_CredentialState *creds)
79 {
80         struct ldb_message *msg;
81         struct ldb_val val, seed, client_state, server_state;
82         struct ldb_val *sid_val;
83         char *f;
84         char *sct;
85         int ret;
86
87         f = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->negotiate_flags);
88
89         if (f == NULL) {
90                 return NT_STATUS_NO_MEMORY;
91         }
92
93         sct = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->secure_channel_type);
94
95         if (sct == NULL) {
96                 return NT_STATUS_NO_MEMORY;
97         }
98
99         msg = ldb_msg_new(ldb);
100         if (msg == NULL) {
101                 return NT_STATUS_NO_MEMORY;
102         }
103
104         msg->dn = ldb_dn_new_fmt(msg, ldb, "computerName=%s", creds->computer_name);
105         if ( ! msg->dn) {
106                 return NT_STATUS_NO_MEMORY;
107         }
108
109         sid_val = schannel_dom_sid_ldb_val(msg, creds->sid);
110         if (sid_val == NULL) {
111                 return NT_STATUS_NO_MEMORY;
112         }
113
114         val.data = creds->session_key;
115         val.length = sizeof(creds->session_key);
116
117         seed.data = creds->seed.data;
118         seed.length = sizeof(creds->seed.data);
119
120         client_state.data = creds->client.data;
121         client_state.length = sizeof(creds->client.data);
122         server_state.data = creds->server.data;
123         server_state.length = sizeof(creds->server.data);
124
125         ldb_msg_add_string(msg, "objectClass", "schannelState");
126         ldb_msg_add_value(msg, "sessionKey", &val, NULL);
127         ldb_msg_add_value(msg, "seed", &seed, NULL);
128         ldb_msg_add_value(msg, "clientState", &client_state, NULL);
129         ldb_msg_add_value(msg, "serverState", &server_state, NULL);
130         ldb_msg_add_string(msg, "negotiateFlags", f);
131         ldb_msg_add_string(msg, "secureChannelType", sct);
132         ldb_msg_add_string(msg, "accountName", creds->account_name);
133         ldb_msg_add_string(msg, "computerName", creds->computer_name);
134         ldb_msg_add_value(msg, "objectSid", sid_val, NULL);
135
136         ret = ldb_add(ldb, msg);
137         if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
138                 int i;
139                 /* from samdb_replace() */
140                 /* mark all the message elements as LDB_FLAG_MOD_REPLACE */
141                 for (i=0;i<msg->num_elements;i++) {
142                         msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
143                 }
144
145                 ret = ldb_modify(ldb, msg);
146         }
147
148         /* We don't need a transaction here, as we either add or
149          * modify records, never delete them, so it must exist */
150
151         if (ret != LDB_SUCCESS) {
152                 DEBUG(0,("Unable to add %s to session key db - %s\n",
153                          ldb_dn_get_linearized(msg->dn), ldb_errstring(ldb)));
154                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
155         }
156
157         return NT_STATUS_OK;
158 }
159
160 /*
161   read back a credentials back for a computer
162 */
163 NTSTATUS schannel_fetch_session_key_ldb(struct ldb_context *ldb,
164                                         TALLOC_CTX *mem_ctx,
165                                         const char *computer_name,
166                                         struct netlogon_creds_CredentialState **creds)
167 {
168         struct ldb_result *res;
169         int ret;
170         const struct ldb_val *val;
171
172         *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState);
173         if (!*creds) {
174                 return NT_STATUS_NO_MEMORY;
175         }
176
177         ret = ldb_search(ldb, mem_ctx, &res,
178                                  NULL, LDB_SCOPE_SUBTREE, NULL,
179                                 "(computerName=%s)", computer_name);
180         if (ret != LDB_SUCCESS) {
181                 DEBUG(3,("schannel: Failed to find a record for client %s: %s\n", computer_name, ldb_errstring(ldb)));
182                 return NT_STATUS_INVALID_HANDLE;
183         }
184         if (res->count != 1) {
185                 DEBUG(3,("schannel: Failed to find a record for client: %s (found %d records)\n", computer_name, res->count));
186                 talloc_free(res);
187                 return NT_STATUS_INVALID_HANDLE;
188         }
189
190         val = ldb_msg_find_ldb_val(res->msgs[0], "sessionKey");
191         if (val == NULL || val->length != 16) {
192                 DEBUG(1,("schannel: record in schannel DB must contain a sessionKey of length 16, when searching for client: %s\n", computer_name));
193                 talloc_free(res);
194                 return NT_STATUS_INTERNAL_ERROR;
195         }
196
197         memcpy((*creds)->session_key, val->data, 16);
198
199         val = ldb_msg_find_ldb_val(res->msgs[0], "seed");
200         if (val == NULL || val->length != 8) {
201                 DEBUG(1,("schannel: record in schannel DB must contain a vaid seed of length 8, when searching for client: %s\n", computer_name));
202                 talloc_free(res);
203                 return NT_STATUS_INTERNAL_ERROR;
204         }
205
206         memcpy((*creds)->seed.data, val->data, 8);
207
208         val = ldb_msg_find_ldb_val(res->msgs[0], "clientState");
209         if (val == NULL || val->length != 8) {
210                 DEBUG(1,("schannel: record in schannel DB must contain a vaid clientState of length 8, when searching for client: %s\n", computer_name));
211                 talloc_free(res);
212                 return NT_STATUS_INTERNAL_ERROR;
213         }
214         memcpy((*creds)->client.data, val->data, 8);
215
216         val = ldb_msg_find_ldb_val(res->msgs[0], "serverState");
217         if (val == NULL || val->length != 8) {
218                 DEBUG(1,("schannel: record in schannel DB must contain a vaid serverState of length 8, when searching for client: %s\n", computer_name));
219                 talloc_free(res);
220                 return NT_STATUS_INTERNAL_ERROR;
221         }
222         memcpy((*creds)->server.data, val->data, 8);
223
224         (*creds)->negotiate_flags = ldb_msg_find_attr_as_int(res->msgs[0], "negotiateFlags", 0);
225
226         (*creds)->secure_channel_type = ldb_msg_find_attr_as_int(res->msgs[0], "secureChannelType", 0);
227
228         (*creds)->account_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "accountName", NULL));
229         if ((*creds)->account_name == NULL) {
230                 talloc_free(res);
231                 return NT_STATUS_NO_MEMORY;
232         }
233
234         (*creds)->computer_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "computerName", NULL));
235         if ((*creds)->computer_name == NULL) {
236                 talloc_free(res);
237                 return NT_STATUS_NO_MEMORY;
238         }
239
240         val = ldb_msg_find_ldb_val(res->msgs[0], "objectSid");
241         if (val) {
242                 (*creds)->sid = schannel_ldb_val_dom_sid(*creds, val);
243                 if ((*creds)->sid == NULL) {
244                         talloc_free(res);
245                         return NT_STATUS_INTERNAL_ERROR;
246                 }
247         } else {
248                 (*creds)->sid = NULL;
249         }
250
251         talloc_free(res);
252         return NT_STATUS_OK;
253 }
254
255 /*
256   Validate an incoming authenticator against the credentials for the remote machine.
257
258   The credentials are (re)read and from the schannel database, and
259   written back after the caclulations are performed.
260
261   The creds_out parameter (if not NULL) returns the credentials, if
262   the caller needs some of that information.
263
264 */
265 NTSTATUS schannel_creds_server_step_check_ldb(struct ldb_context *ldb,
266                                               TALLOC_CTX *mem_ctx,
267                                               const char *computer_name,
268                                               bool schannel_required_for_call,
269                                               bool schannel_in_use,
270                                               struct netr_Authenticator *received_authenticator,
271                                               struct netr_Authenticator *return_authenticator,
272                                               struct netlogon_creds_CredentialState **creds_out)
273 {
274         struct netlogon_creds_CredentialState *creds;
275         NTSTATUS nt_status;
276         int ret;
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
290         /* If we are flaged that schannel is required for a call, and
291          * it is not in use, then make this an error */
292
293         /* It would be good to make this mandetory once schannel is
294          * negoiated, bu this is not what windows does */
295         if (schannel_required_for_call && !schannel_in_use) {
296                 DEBUG(0,("schannel_creds_server_step_check: client %s not using schannel for netlogon, despite negotiating it\n",
297                         creds->computer_name ));
298                 ldb_transaction_cancel(ldb);
299                 return NT_STATUS_ACCESS_DENIED;
300         }
301
302         if (NT_STATUS_IS_OK(nt_status)) {
303                 nt_status = netlogon_creds_server_step_check(creds,
304                                                              received_authenticator,
305                                                              return_authenticator);
306         }
307
308         if (NT_STATUS_IS_OK(nt_status)) {
309                 nt_status = schannel_store_session_key_ldb(ldb, mem_ctx, creds);
310         }
311
312         if (NT_STATUS_IS_OK(nt_status)) {
313                 ldb_transaction_commit(ldb);
314                 if (creds_out) {
315                         *creds_out = creds;
316                         talloc_steal(mem_ctx, creds);
317                 }
318         } else {
319                 ldb_transaction_cancel(ldb);
320         }
321         return nt_status;
322 }