diff options
Diffstat (limited to 'source4/libnet/libnet_samsync_ldb.c')
-rw-r--r-- | source4/libnet/libnet_samsync_ldb.c | 1247 |
1 files changed, 1247 insertions, 0 deletions
diff --git a/source4/libnet/libnet_samsync_ldb.c b/source4/libnet/libnet_samsync_ldb.c new file mode 100644 index 0000000000..c72aef7d70 --- /dev/null +++ b/source4/libnet/libnet_samsync_ldb.c @@ -0,0 +1,1247 @@ +/* + Unix SMB/CIFS implementation. + + Extract the user/system database from a remote SamSync server + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Volker Lendecke 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" +#include "libnet/libnet.h" +#include "libcli/ldap/ldap_ndr.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "util/util_ldb.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "ldb_wrap.h" +#include "libcli/security/security.h" +#include "librpc/rpc/dcerpc.h" +#include "param/param.h" + +struct samsync_ldb_secret { + struct samsync_ldb_secret *prev, *next; + DATA_BLOB secret; + char *name; + NTTIME mtime; +}; + +struct samsync_ldb_trusted_domain { + struct samsync_ldb_trusted_domain *prev, *next; + struct dom_sid *sid; + char *name; +}; + +struct samsync_ldb_state { + /* Values from the LSA lookup */ + const struct libnet_SamSync_state *samsync_state; + + struct dom_sid *dom_sid[3]; + struct ldb_context *sam_ldb, *remote_ldb; + struct ldb_dn *base_dn[3]; + struct samsync_ldb_secret *secrets; + struct samsync_ldb_trusted_domain *trusted_domains; +}; + +static NTSTATUS samsync_ldb_add_foreignSecurityPrincipal(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + struct dom_sid *sid, + struct ldb_dn **fsp_dn, + char **error_string) +{ + const char *sidstr = dom_sid_string(mem_ctx, sid); + /* We assume that ForeignSecurityPrincipals are under the BASEDN of the main domain */ + struct ldb_dn *basedn = samdb_search_dn(state->sam_ldb, mem_ctx, + state->base_dn[SAM_DATABASE_DOMAIN], + "(&(objectClass=container)(cn=ForeignSecurityPrincipals))"); + struct ldb_message *msg; + int ret; + + if (!sidstr) { + return NT_STATUS_NO_MEMORY; + } + + if (basedn == NULL) { + *error_string = talloc_asprintf(mem_ctx, + "Failed to find DN for " + "ForeignSecurityPrincipal container under %s", + ldb_dn_get_linearized(state->base_dn[SAM_DATABASE_DOMAIN])); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* add core elements to the ldb_message for the alias */ + msg->dn = basedn; + if ( ! ldb_dn_add_child_fmt(msg->dn, "CN=%s", sidstr)) + return NT_STATUS_UNSUCCESSFUL; + + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, + "objectClass", + "foreignSecurityPrincipal"); + + *fsp_dn = msg->dn; + + /* create the alias */ + ret = ldb_add(state->sam_ldb, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to create foreignSecurityPrincipal " + "record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_domain(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + struct netr_DELTA_DOMAIN *domain = delta->delta_union.domain; + const char *domain_name = domain->domain_name.string; + struct ldb_message *msg; + int ret; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (database == SAM_DATABASE_DOMAIN) { + struct ldb_dn *partitions_basedn; + const char *domain_attrs[] = {"nETBIOSName", "nCName", NULL}; + struct ldb_message **msgs_domain; + int ret_domain; + + partitions_basedn = samdb_partitions_dn(state->sam_ldb, mem_ctx); + + ret_domain = gendb_search(state->sam_ldb, mem_ctx, partitions_basedn, &msgs_domain, domain_attrs, + "(&(&(nETBIOSName=%s)(objectclass=crossRef))(ncName=*))", + domain_name); + if (ret_domain == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search for domain failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (ret_domain != 1) { + *error_string = talloc_asprintf(mem_ctx, "Failed to find existing domain record for %s: %d results", domain_name, + ret_domain); + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->base_dn[database] = samdb_result_dn(state->sam_ldb, state, msgs_domain[0], "nCName", NULL); + + if (state->dom_sid[database]) { + /* Update the domain sid with the incoming + * domain (found on LSA pipe, database sid may + * be random) */ + samdb_msg_add_dom_sid(state->sam_ldb, mem_ctx, + msg, "objectSid", state->dom_sid[database]); + } else { + /* Well, we will have to use the one from the database */ + state->dom_sid[database] = samdb_search_dom_sid(state->sam_ldb, state, + state->base_dn[database], + "objectSid", NULL); + } + + if (state->samsync_state->domain_guid) { + enum ndr_err_code ndr_err; + struct ldb_val v; + ndr_err = ndr_push_struct_blob(&v, msg, NULL, + state->samsync_state->domain_guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + *error_string = talloc_asprintf(mem_ctx, "ndr_push of domain GUID failed!"); + return ndr_map_error2ntstatus(ndr_err); + } + + ldb_msg_add_value(msg, "objectGUID", &v, NULL); + } + } else if (database == SAM_DATABASE_BUILTIN) { + /* work out the builtin_dn - useful for so many calls its worth + fetching here */ + const char *dnstring = samdb_search_string(state->sam_ldb, mem_ctx, NULL, + "distinguishedName", "objectClass=builtinDomain"); + state->base_dn[database] = ldb_dn_new(state, state->sam_ldb, dnstring); + if ( ! ldb_dn_validate(state->base_dn[database])) { + return NT_STATUS_INTERNAL_ERROR; + } + } else { + /* PRIVs DB */ + return NT_STATUS_INVALID_PARAMETER; + } + + msg->dn = talloc_reference(mem_ctx, state->base_dn[database]); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + samdb_msg_add_string(state->sam_ldb, mem_ctx, + msg, "oEMInformation", domain->oem_information.string); + + samdb_msg_add_int64(state->sam_ldb, mem_ctx, + msg, "forceLogoff", domain->force_logoff_time); + + samdb_msg_add_uint(state->sam_ldb, mem_ctx, + msg, "minPwdLen", domain->min_password_length); + + samdb_msg_add_int64(state->sam_ldb, mem_ctx, + msg, "maxPwdAge", domain->max_password_age); + + samdb_msg_add_int64(state->sam_ldb, mem_ctx, + msg, "minPwdAge", domain->min_password_age); + + samdb_msg_add_uint(state->sam_ldb, mem_ctx, + msg, "pwdHistoryLength", domain->password_history_length); + + samdb_msg_add_uint64(state->sam_ldb, mem_ctx, + msg, "modifiedCount", + domain->sequence_num); + + samdb_msg_add_uint64(state->sam_ldb, mem_ctx, + msg, "creationTime", domain->domain_create_time); + + /* TODO: Account lockout, password properties */ + + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + + if (ret) { + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_user(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_USER *user = delta->delta_union.user; + const char *container, *obj_class; + char *cn_name; + int cn_name_len; + const struct dom_sid *user_sid; + struct ldb_message *msg; + struct ldb_message **msgs; + struct ldb_message **remote_msgs = NULL; + int ret, i; + uint32_t acb; + bool add = false; + const char *attrs[] = { NULL }; + /* we may change this to a global search, then fill in only the things not in ldap later */ + const char *remote_attrs[] = { "userPrincipalName", "servicePrincipalName", + "msDS-KeyVersionNumber", "objectGUID", NULL}; + + user_sid = dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid); + if (!user_sid) { + return NT_STATUS_NO_MEMORY; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = NULL; + /* search for the user, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], + &msgs, attrs, "(&(objectClass=user)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, user_sid)); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "LDB for user %s failed: %s", + dom_sid_string(mem_ctx, user_sid), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + add = true; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one user with SID: %s in local LDB", + dom_sid_string(mem_ctx, user_sid)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = msgs[0]->dn; + talloc_steal(msg, msgs[0]->dn); + } + + /* and do the same on the remote database */ + if (state->remote_ldb) { + ret = gendb_search(state->remote_ldb, mem_ctx, state->base_dn[database], + &remote_msgs, remote_attrs, "(&(objectClass=user)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, user_sid)); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "remote LDAP for user %s failed: %s", + dom_sid_string(mem_ctx, user_sid), + ldb_errstring(state->remote_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + *error_string = talloc_asprintf(mem_ctx, "User exists in samsync but not in remote LDAP domain! (base: %s, SID: %s)", + ldb_dn_get_linearized(state->base_dn[database]), + dom_sid_string(mem_ctx, user_sid)); + return NT_STATUS_NO_SUCH_USER; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one user in remote LDAP domain with SID: %s", + dom_sid_string(mem_ctx, user_sid)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + + /* Try to put things in the same location as the remote server */ + } else if (add) { + msg->dn = remote_msgs[0]->dn; + talloc_steal(msg, remote_msgs[0]->dn); + } + } + + cn_name = talloc_strdup(mem_ctx, user->account_name.string); + NT_STATUS_HAVE_NO_MEMORY(cn_name); + cn_name_len = strlen(cn_name); + +#define ADD_OR_DEL(type, attrib, field) do { \ + if (user->field) { \ + samdb_msg_add_ ## type(state->sam_ldb, mem_ctx, msg, \ + attrib, user->field); \ + } else if (!add) { \ + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, \ + attrib); \ + } \ + } while (0); + + ADD_OR_DEL(string, "samAccountName", account_name.string); + ADD_OR_DEL(string, "displayName", full_name.string); + + if (samdb_msg_add_dom_sid(state->sam_ldb, mem_ctx, msg, + "objectSid", dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))) { + return NT_STATUS_NO_MEMORY; + } + + ADD_OR_DEL(uint, "primaryGroupID", primary_gid); + ADD_OR_DEL(string, "homeDirectory", home_directory.string); + ADD_OR_DEL(string, "homeDrive", home_drive.string); + ADD_OR_DEL(string, "scriptPath", logon_script.string); + ADD_OR_DEL(string, "description", description.string); + ADD_OR_DEL(string, "userWorkstations", workstations.string); + + ADD_OR_DEL(uint64, "lastLogon", last_logon); + ADD_OR_DEL(uint64, "lastLogoff", last_logoff); + + if (samdb_msg_add_logon_hours(state->sam_ldb, mem_ctx, msg, "logonHours", &user->logon_hours) != 0) { + return NT_STATUS_NO_MEMORY; + } + + ADD_OR_DEL(uint, "badPwdCount", bad_password_count); + ADD_OR_DEL(uint, "logonCount", logon_count); + + ADD_OR_DEL(uint64, "pwdLastSet", last_password_change); + ADD_OR_DEL(uint64, "accountExpires", acct_expiry); + + if (samdb_msg_add_acct_flags(state->sam_ldb, mem_ctx, msg, + "userAccountControl", user->acct_flags) != 0) { + return NT_STATUS_NO_MEMORY; + } + + if (!add) { + /* Passwords. Ensure there is no plaintext stored against + * this entry, as we only have hashes */ + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, + "userPassword"); + } + if (user->lm_password_present) { + samdb_msg_add_hash(state->sam_ldb, mem_ctx, msg, + "dBCSPwd", &user->lmpassword); + } else if (!add) { + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, + "dBCSPwd"); + } + if (user->nt_password_present) { + samdb_msg_add_hash(state->sam_ldb, mem_ctx, msg, + "unicodePwd", &user->ntpassword); + } else if (!add) { + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, + "unicodePwd"); + } + + ADD_OR_DEL(string, "comment", comment.string); + ADD_OR_DEL(string, "userParameters", parameters.string); + ADD_OR_DEL(uint, "countryCode", country_code); + ADD_OR_DEL(uint, "codePage", code_page); + + ADD_OR_DEL(string, "profilePath", profile_path.string); + +#undef ADD_OR_DEL + + for (i=0; remote_attrs[i]; i++) { + struct ldb_message_element *el = ldb_msg_find_element(remote_msgs[0], remote_attrs[i]); + if (!el) { + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, + remote_attrs[i]); + } else { + ldb_msg_add(msg, el, LDB_FLAG_MOD_REPLACE); + } + } + + acb = user->acct_flags; + if (acb & (ACB_WSTRUST)) { + cn_name[cn_name_len - 1] = '\0'; + container = "Computers"; + obj_class = "computer"; + + } else if (acb & ACB_SVRTRUST) { + if (cn_name[cn_name_len - 1] != '$') { + return NT_STATUS_FOOBAR; + } + cn_name[cn_name_len - 1] = '\0'; + container = "Domain Controllers"; + obj_class = "computer"; + } else { + container = "Users"; + obj_class = "user"; + } + if (add) { + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, + "objectClass", obj_class); + if (!msg->dn) { + msg->dn = ldb_dn_copy(mem_ctx, state->base_dn[database]); + ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=%s", cn_name, container); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + } + + ret = ldb_add(state->sam_ldb, msg); + if (ret != 0) { + struct ldb_dn *first_try_dn = msg->dn; + /* Try again with the default DN */ + if (!remote_msgs) { + *error_string = talloc_asprintf(mem_ctx, "Failed to create user record. Tried %s: %s", + ldb_dn_get_linearized(first_try_dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = talloc_steal(msg, remote_msgs[0]->dn); + ret = ldb_add(state->sam_ldb, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to create user record. Tried both %s and %s: %s", + ldb_dn_get_linearized(first_try_dn), + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + } + } else { + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify user record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_delete_user(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct ldb_message **msgs; + int ret; + const char *attrs[] = { NULL }; + + /* search for the user, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], + &msgs, attrs, "(&(objectClass=user)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one user with SID: %s", + dom_sid_string(mem_ctx, + dom_sid_add_rid(mem_ctx, + state->dom_sid[database], + rid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + ret = ldb_delete(state->sam_ldb, msgs[0]->dn); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to delete user record %s: %s", + ldb_dn_get_linearized(msgs[0]->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_group(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_GROUP *group = delta->delta_union.group; + const char *container, *obj_class; + const char *cn_name; + + struct ldb_message *msg; + struct ldb_message **msgs; + int ret; + bool add = false; + const char *attrs[] = { NULL }; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the group, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=group)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + add = true; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one group/alias with SID: %s", + dom_sid_string(mem_ctx, + dom_sid_add_rid(mem_ctx, + state->dom_sid[database], + rid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = talloc_steal(msg, msgs[0]->dn); + } + + cn_name = group->group_name.string; + +#define ADD_OR_DEL(type, attrib, field) do { \ + if (group->field) { \ + samdb_msg_add_ ## type(state->sam_ldb, mem_ctx, msg, \ + attrib, group->field); \ + } else if (!add) { \ + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, \ + attrib); \ + } \ + } while (0); + + ADD_OR_DEL(string, "samAccountName", group_name.string); + + if (samdb_msg_add_dom_sid(state->sam_ldb, mem_ctx, msg, + "objectSid", dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))) { + return NT_STATUS_NO_MEMORY; + } + + ADD_OR_DEL(string, "description", description.string); + +#undef ADD_OR_DEL + + container = "Users"; + obj_class = "group"; + + if (add) { + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, + "objectClass", obj_class); + msg->dn = ldb_dn_copy(mem_ctx, state->base_dn[database]); + ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=%s", cn_name, container); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_add(state->sam_ldb, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to create group record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } else { + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify group record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_delete_group(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct ldb_message **msgs; + int ret; + const char *attrs[] = { NULL }; + + /* search for the group, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=group)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one group/alias with SID: %s", + dom_sid_string(mem_ctx, + dom_sid_add_rid(mem_ctx, + state->dom_sid[database], + rid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + ret = ldb_delete(state->sam_ldb, msgs[0]->dn); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to delete group record %s: %s", + ldb_dn_get_linearized(msgs[0]->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_group_member(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_GROUP_MEMBER *group_member = delta->delta_union.group_member; + struct ldb_message *msg; + struct ldb_message **msgs; + int ret; + const char *attrs[] = { NULL }; + int i; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the group, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=group)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one group/alias with SID: %s", + dom_sid_string(mem_ctx, + dom_sid_add_rid(mem_ctx, + state->dom_sid[database], + rid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = talloc_steal(msg, msgs[0]->dn); + } + + talloc_free(msgs); + + for (i=0; i<group_member->num_rids; i++) { + /* search for the group, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=user)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], group_member->rids[i]))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } else if (ret > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, "member", ldb_dn_alloc_linearized(mem_ctx, msgs[0]->dn)); + } + + talloc_free(msgs); + } + + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify group record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_alias(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_ALIAS *alias = delta->delta_union.alias; + const char *container, *obj_class; + const char *cn_name; + + struct ldb_message *msg; + struct ldb_message **msgs; + int ret; + bool add = false; + const char *attrs[] = { NULL }; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the alias, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=group)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + add = true; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one group/alias with SID: %s", + dom_sid_string(mem_ctx, + dom_sid_add_rid(mem_ctx, + state->dom_sid[database], + rid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = talloc_steal(mem_ctx, msgs[0]->dn); + } + + cn_name = alias->alias_name.string; + +#define ADD_OR_DEL(type, attrib, field) do { \ + if (alias->field) { \ + samdb_msg_add_ ## type(state->sam_ldb, mem_ctx, msg, \ + attrib, alias->field); \ + } else if (!add) { \ + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, \ + attrib); \ + } \ + } while (0); + + ADD_OR_DEL(string, "samAccountName", alias_name.string); + + if (samdb_msg_add_dom_sid(state->sam_ldb, mem_ctx, msg, + "objectSid", dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))) { + return NT_STATUS_NO_MEMORY; + } + + ADD_OR_DEL(string, "description", description.string); + +#undef ADD_OR_DEL + + samdb_msg_add_uint(state->sam_ldb, mem_ctx, msg, "groupType", 0x80000004); + + container = "Users"; + obj_class = "group"; + + if (add) { + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, + "objectClass", obj_class); + msg->dn = ldb_dn_copy(mem_ctx, state->base_dn[database]); + ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=%s", cn_name, container); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_add(state->sam_ldb, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to create alias record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } else { + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify alias record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_delete_alias(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct ldb_message **msgs; + int ret; + const char *attrs[] = { NULL }; + + /* search for the alias, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=group)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_ALIAS; + } else if (ret > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + ret = ldb_delete(state->sam_ldb, msgs[0]->dn); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to delete alias record %s: %s", + ldb_dn_get_linearized(msgs[0]->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_alias_member(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_ALIAS_MEMBER *alias_member = delta->delta_union.alias_member; + struct ldb_message *msg; + struct ldb_message **msgs; + int ret; + const char *attrs[] = { NULL }; + int i; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the alias, by rid */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[database], &msgs, attrs, + "(&(objectClass=group)(objectSid=%s))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid_add_rid(mem_ctx, state->dom_sid[database], rid))); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one group/alias with SID: %s", + dom_sid_string(mem_ctx, + dom_sid_add_rid(mem_ctx, + state->dom_sid[database], + rid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = talloc_steal(msg, msgs[0]->dn); + } + + talloc_free(msgs); + + for (i=0; i<alias_member->sids.num_sids; i++) { + struct ldb_dn *alias_member_dn; + /* search for members, in the top basedn (normal users are builtin aliases) */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[SAM_DATABASE_DOMAIN], &msgs, attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, alias_member->sids.sids[i].sid)); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + NTSTATUS nt_status; + nt_status = samsync_ldb_add_foreignSecurityPrincipal(mem_ctx, state, + alias_member->sids.sids[i].sid, + &alias_member_dn, + error_string); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else if (ret > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + alias_member_dn = msgs[0]->dn; + } + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, "member", ldb_dn_alloc_linearized(mem_ctx, alias_member_dn)); + + talloc_free(msgs); + } + + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify group record %s: %s", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_handle_account(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + struct dom_sid *sid = delta->delta_id_union.sid; + struct netr_DELTA_ACCOUNT *account = delta->delta_union.account; + + struct ldb_message *msg; + struct ldb_message **msgs; + struct ldb_dn *privilege_dn; + int ret; + const char *attrs[] = { NULL }; + int i; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the account, by sid, in the top basedn */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[SAM_DATABASE_DOMAIN], &msgs, attrs, + "(objectSid=%s)", ldap_encode_ndr_dom_sid(mem_ctx, sid)); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + NTSTATUS nt_status; + nt_status = samsync_ldb_add_foreignSecurityPrincipal(mem_ctx, state, + sid, + &privilege_dn, + error_string); + privilege_dn = talloc_steal(msg, privilege_dn); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one account with SID: %s", + dom_sid_string(mem_ctx, sid)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + privilege_dn = talloc_steal(msg, msgs[0]->dn); + } + + msg->dn = privilege_dn; + + for (i=0; i< account->privilege_entries; i++) { + samdb_msg_add_string(state->sam_ldb, mem_ctx, msg, "privilege", + account->privilege_name[i].string); + } + + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify privilege record %s", + ldb_dn_get_linearized(msg->dn)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samsync_ldb_delete_account(TALLOC_CTX *mem_ctx, + struct samsync_ldb_state *state, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + struct dom_sid *sid = delta->delta_id_union.sid; + + struct ldb_message *msg; + struct ldb_message **msgs; + int ret; + const char *attrs[] = { NULL }; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the account, by sid, in the top basedn */ + ret = gendb_search(state->sam_ldb, mem_ctx, state->base_dn[SAM_DATABASE_DOMAIN], &msgs, attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, sid)); + + if (ret == -1) { + *error_string = talloc_asprintf(mem_ctx, "gendb_search failed: %s", ldb_errstring(state->sam_ldb)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } else if (ret > 1) { + *error_string = talloc_asprintf(mem_ctx, "More than one account with SID: %s", + dom_sid_string(mem_ctx, sid)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else { + msg->dn = talloc_steal(msg, msgs[0]->dn); + } + + samdb_msg_add_delete(state->sam_ldb, mem_ctx, msg, + "privilage"); + + ret = samdb_replace(state->sam_ldb, mem_ctx, msg); + if (ret != 0) { + *error_string = talloc_asprintf(mem_ctx, "Failed to modify privilege record %s", + ldb_dn_get_linearized(msg->dn)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS libnet_samsync_ldb_fn(TALLOC_CTX *mem_ctx, + void *private, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta, + char **error_string) +{ + NTSTATUS nt_status = NT_STATUS_OK; + struct samsync_ldb_state *state = talloc_get_type(private, struct samsync_ldb_state); + + *error_string = NULL; + switch (delta->delta_type) { + case NETR_DELTA_DOMAIN: + { + nt_status = samsync_ldb_handle_domain(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_USER: + { + nt_status = samsync_ldb_handle_user(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_DELETE_USER: + { + nt_status = samsync_ldb_delete_user(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_GROUP: + { + nt_status = samsync_ldb_handle_group(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_DELETE_GROUP: + { + nt_status = samsync_ldb_delete_group(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_GROUP_MEMBER: + { + nt_status = samsync_ldb_handle_group_member(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_ALIAS: + { + nt_status = samsync_ldb_handle_alias(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_DELETE_ALIAS: + { + nt_status = samsync_ldb_delete_alias(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_ALIAS_MEMBER: + { + nt_status = samsync_ldb_handle_alias_member(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_ACCOUNT: + { + nt_status = samsync_ldb_handle_account(mem_ctx, + state, + database, + delta, + error_string); + break; + } + case NETR_DELTA_DELETE_ACCOUNT: + { + nt_status = samsync_ldb_delete_account(mem_ctx, + state, + database, + delta, + error_string); + break; + } + default: + /* Can't dump them all right now */ + break; + } + if (!NT_STATUS_IS_OK(nt_status) && !*error_string) { + *error_string = talloc_asprintf(mem_ctx, "Failed to handle samsync delta: %s", nt_errstr(nt_status)); + } + return nt_status; +} + +static NTSTATUS libnet_samsync_ldb_init(TALLOC_CTX *mem_ctx, + void *private, + struct libnet_SamSync_state *samsync_state, + char **error_string) +{ + struct samsync_ldb_state *state = talloc_get_type(private, struct samsync_ldb_state); + const char *server = dcerpc_server_name(samsync_state->netlogon_pipe); + char *ldap_url; + + state->samsync_state = samsync_state; + + ZERO_STRUCT(state->dom_sid); + if (state->samsync_state->domain_sid) { + state->dom_sid[SAM_DATABASE_DOMAIN] = dom_sid_dup(state, state->samsync_state->domain_sid); + } + + state->dom_sid[SAM_DATABASE_BUILTIN] = dom_sid_parse_talloc(state, SID_BUILTIN); + + if (state->samsync_state->realm) { + if (!server || !*server) { + /* huh? how do we not have a server name? */ + *error_string = talloc_strdup(mem_ctx, "No DCE/RPC server name available. How did we connect?"); + return NT_STATUS_INVALID_PARAMETER; + } + ldap_url = talloc_asprintf(state, "ldap://%s", server); + + state->remote_ldb = ldb_wrap_connect(mem_ctx, + state->samsync_state->machine_net_ctx->event_ctx, + state->samsync_state->machine_net_ctx->lp_ctx, + ldap_url, + NULL, state->samsync_state->machine_net_ctx->cred, + 0, NULL); + if (!state->remote_ldb) { + *error_string = talloc_asprintf(mem_ctx, "Failed to connect to remote LDAP server at %s (used to extract additional data in SamSync replication)", ldap_url); + return NT_STATUS_NO_LOGON_SERVERS; + } + } else { + state->remote_ldb = NULL; + } + return NT_STATUS_OK; +} + +NTSTATUS libnet_samsync_ldb(struct libnet_context *ctx, TALLOC_CTX *mem_ctx, struct libnet_samsync_ldb *r) +{ + NTSTATUS nt_status; + struct libnet_SamSync r2; + struct samsync_ldb_state *state = talloc(mem_ctx, struct samsync_ldb_state); + + if (!state) { + return NT_STATUS_NO_MEMORY; + } + + state->secrets = NULL; + state->trusted_domains = NULL; + + state->sam_ldb = samdb_connect(mem_ctx, + ctx->event_ctx, + ctx->lp_ctx, + r->in.session_info); + + r2.out.error_string = NULL; + r2.in.binding_string = r->in.binding_string; + r2.in.rid_crypt = true; + r2.in.init_fn = libnet_samsync_ldb_init; + r2.in.delta_fn = libnet_samsync_ldb_fn; + r2.in.fn_ctx = state; + r2.in.machine_account = NULL; /* TODO: Create a machine account, fill this in, and the delete it */ + nt_status = libnet_SamSync_netlogon(ctx, state, &r2); + r->out.error_string = r2.out.error_string; + talloc_steal(mem_ctx, r->out.error_string); + + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(state); + return nt_status; + } + talloc_free(state); + return nt_status; +} |