s3-build: add SCHANNEL_OBJ to Makefile.in.
[ira/wip.git] / libcli / auth / schannel_state.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
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(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(ldb);
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(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(struct ldb_context *ldb,
265                                           TALLOC_CTX *mem_ctx, 
266                                           const char *computer_name,
267                                           bool schannel_required_for_call,
268                                           bool schannel_in_use,
269                                           struct netr_Authenticator *received_authenticator,
270                                           struct netr_Authenticator *return_authenticator,
271                                           struct netlogon_creds_CredentialState **creds_out) 
272 {
273         struct netlogon_creds_CredentialState *creds;
274         NTSTATUS nt_status;
275         int ret;
276
277         ret = ldb_transaction_start(ldb);
278         if (ret != 0) {
279                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
280         }
281
282         /* Because this is a shared structure (even across
283          * disconnects) we must update the database every time we
284          * update the structure */ 
285         
286         nt_status = schannel_fetch_session_key(ldb, ldb, computer_name, 
287                                                &creds);
288
289         /* If we are flaged that schannel is required for a call, and
290          * it is not in use, then make this an error */
291
292         /* It would be good to make this mandetory once schannel is
293          * negoiated, bu this is not what windows does */
294         if (schannel_required_for_call && !schannel_in_use) {
295                 DEBUG(0,("schannel_creds_server_step_check: client %s not using schannel for netlogon, despite negotiating it\n",
296                         creds->computer_name ));
297                 ldb_transaction_cancel(ldb);
298                 return NT_STATUS_ACCESS_DENIED;
299         }
300
301         if (NT_STATUS_IS_OK(nt_status)) {
302                 nt_status = netlogon_creds_server_step_check(creds, 
303                                                              received_authenticator, 
304                                                              return_authenticator);
305         }
306
307         if (NT_STATUS_IS_OK(nt_status)) {
308                 nt_status = schannel_store_session_key(ldb, mem_ctx, creds);
309         }
310
311         if (NT_STATUS_IS_OK(nt_status)) {
312                 ldb_transaction_commit(ldb);
313                 if (creds_out) {
314                         *creds_out = creds;
315                         talloc_steal(mem_ctx, creds);
316                 }
317         } else {
318                 ldb_transaction_cancel(ldb);
319         }
320         return nt_status;
321 }