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