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
34static 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
53static 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*/
76NTSTATUS 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*/
163NTSTATUS 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*/
265NTSTATUS 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}
323