/* Unix SMB/CIFS implementation. DNS Server Copyright (C) Amitay Isaacs 2011 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 . */ #include "includes.h" #include "dnsserver.h" #include "lib/util/dlinklist.h" #include "librpc/gen_ndr/ndr_dnsp.h" #include "librpc/gen_ndr/ndr_security.h" #include "librpc/gen_ndr/ndr_misc.h" #include "dsdb/samdb/samdb.h" #include "libcli/security/security.h" #include "dsdb/common/util.h" #undef strcasecmp /* There are only 2 fixed partitions for DNS */ struct dnsserver_partition *dnsserver_db_enumerate_partitions(TALLOC_CTX *mem_ctx, struct dnsserver_serverinfo *serverinfo, struct ldb_context *samdb) { struct dnsserver_partition *partitions, *p; partitions = NULL; /* Domain partition */ p = talloc_zero(mem_ctx, struct dnsserver_partition); if (p == NULL) { goto failed; } p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszDomainDirectoryPartition); if (p->partition_dn == NULL) { goto failed; } p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn); p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_DOMAIN_DEFAULT | DNS_DP_ENLISTED; p->is_forest = false; DLIST_ADD_END(partitions, p); /* Forest Partition */ p = talloc_zero(mem_ctx, struct dnsserver_partition); if (p == NULL) { goto failed; } p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszForestDirectoryPartition); if (p->partition_dn == NULL) { goto failed; } p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn); p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_FOREST_DEFAULT | DNS_DP_ENLISTED; p->is_forest = true; DLIST_ADD_END(partitions, p); return partitions; failed: return NULL; } /* Search for all dnsZone records */ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct dnsserver_partition *p) { TALLOC_CTX *tmp_ctx; const char * const attrs[] = {"name", "dNSProperty", NULL}; struct ldb_dn *dn; struct ldb_result *res; struct dnsserver_zone *zones, *z; int i, j, ret; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return NULL; } dn = ldb_dn_copy(tmp_ctx, p->partition_dn); if (dn == NULL) { goto failed; } if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) { goto failed; } ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, attrs, "(objectClass=dnsZone)"); if (ret != LDB_SUCCESS) { DEBUG(0, ("dnsserver: Failed to find DNS Zones in %s\n", ldb_dn_get_linearized(dn))); goto failed; } zones = NULL; for(i=0; icount; i++) { char *name; struct ldb_message_element *element = NULL; struct dnsp_DnsProperty *props = NULL; enum ndr_err_code err; z = talloc_zero(mem_ctx, struct dnsserver_zone); if (z == NULL) { goto failed; } z->partition = p; name = talloc_strdup(z, ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL)); if (strcmp(name, "..TrustAnchors") == 0) { talloc_free(z); continue; } if (strcmp(name, "RootDNSServers") == 0) { talloc_free(name); z->name = talloc_strdup(z, "."); } else { z->name = name; } z->zone_dn = talloc_steal(z, res->msgs[i]->dn); DLIST_ADD_END(zones, z); DEBUG(2, ("dnsserver: Found DNS zone %s\n", z->name)); element = ldb_msg_find_element(res->msgs[i], "dNSProperty"); if(element != NULL){ props = talloc_zero_array(tmp_ctx, struct dnsp_DnsProperty, element->num_values); for (j = 0; j < element->num_values; j++ ) { err = ndr_pull_struct_blob( &(element->values[j]), mem_ctx, &props[j], (ndr_pull_flags_fn_t) ndr_pull_dnsp_DnsProperty); if (!NDR_ERR_CODE_IS_SUCCESS(err)){ /* * Per Microsoft we must * ignore invalid data here * and continue as a Windows * server can put in a * structure with an invalid * length. * * We can safely fill in an * extra empty property here * because * dns_zoneinfo_load_zone_property() * just ignores * DSPROPERTY_ZONE_EMPTY */ ZERO_STRUCT(props[j]); props[j].id = DSPROPERTY_ZONE_EMPTY; continue; } } z->tmp_props = props; z->num_props = element->num_values; } } return zones; failed: talloc_free(tmp_ctx); return NULL; } /* Find DNS partition information */ struct dnsserver_partition_info *dnsserver_db_partition_info(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct dnsserver_partition *p) { const char * const attrs[] = { "instanceType", "msDs-masteredBy", NULL }; const char * const attrs_none[] = { NULL }; struct ldb_result *res; struct ldb_message_element *el; struct ldb_dn *dn; struct dnsserver_partition_info *partinfo; int i, ret, instance_type; TALLOC_CTX *tmp_ctx; tmp_ctx = talloc_new(mem_ctx); if (tmp_ctx == NULL) { return NULL; } partinfo = talloc_zero(mem_ctx, struct dnsserver_partition_info); if (partinfo == NULL) { talloc_free(tmp_ctx); return NULL; } /* Search for the active replica and state */ ret = ldb_search(samdb, tmp_ctx, &res, p->partition_dn, LDB_SCOPE_BASE, attrs, NULL); if (ret != LDB_SUCCESS || res->count != 1) { goto failed; } /* Set the state of the partition */ instance_type = ldb_msg_find_attr_as_int(res->msgs[0], "instanceType", -1); if (instance_type == -1) { partinfo->dwState = DNS_DP_STATE_UNKNOWN; } else if (instance_type & INSTANCE_TYPE_NC_COMING) { partinfo->dwState = DNS_DP_STATE_REPL_INCOMING; } else if (instance_type & INSTANCE_TYPE_NC_GOING) { partinfo->dwState = DNS_DP_STATE_REPL_OUTGOING; } else { partinfo->dwState = DNS_DP_OKAY; } el = ldb_msg_find_element(res->msgs[0], "msDs-masteredBy"); if (el == NULL) { partinfo->dwReplicaCount = 0; partinfo->ReplicaArray = NULL; } else { partinfo->dwReplicaCount = el->num_values; partinfo->ReplicaArray = talloc_zero_array(partinfo, struct DNS_RPC_DP_REPLICA *, el->num_values); if (partinfo->ReplicaArray == NULL) { goto failed; } for (i=0; inum_values; i++) { partinfo->ReplicaArray[i] = talloc_zero(partinfo, struct DNS_RPC_DP_REPLICA); if (partinfo->ReplicaArray[i] == NULL) { goto failed; } partinfo->ReplicaArray[i]->pszReplicaDn = talloc_strdup( partinfo, (const char *)el->values[i].data); if (partinfo->ReplicaArray[i]->pszReplicaDn == NULL) { goto failed; } } } talloc_free(res); /* Search for cross-reference object */ dn = ldb_dn_copy(tmp_ctx, ldb_get_config_basedn(samdb)); if (dn == NULL) { goto failed; } ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_DEFAULT, attrs_none, "(nCName=%s)", ldb_dn_get_linearized(p->partition_dn)); if (ret != LDB_SUCCESS || res->count != 1) { goto failed; } partinfo->pszCrDn = talloc_strdup(partinfo, ldb_dn_get_linearized(res->msgs[0]->dn)); if (partinfo->pszCrDn == NULL) { goto failed; } talloc_free(res); talloc_free(tmp_ctx); return partinfo; failed: talloc_free(tmp_ctx); talloc_free(partinfo); return NULL; } /* Increment serial number and update timestamp */ static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct dnsserver_zone *z, WERROR *werr) { const char * const attrs[] = { "dnsRecord", NULL }; struct ldb_result *res; struct dnsp_DnssrvRpcRecord rec; struct ldb_message_element *el; enum ndr_err_code ndr_err; int ret, i, serial = -1; *werr = WERR_INTERNAL_DB_ERROR; ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, "(&(objectClass=dnsNode)(name=@))"); if (ret != LDB_SUCCESS || res->count == 0) { return -1; } el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); if (el == NULL) { return -1; } for (i=0; inum_values; i++) { ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { continue; } if (rec.wType == DNS_TYPE_SOA) { serial = rec.data.soa.serial + 1; rec.dwSerial = serial; rec.dwTimeStamp = 0; rec.data.soa.serial = serial; ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, &rec, (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { *werr = WERR_NOT_ENOUGH_MEMORY; return -1; } break; } } if (serial != -1) { el->flags = LDB_FLAG_MOD_REPLACE; ret = ldb_modify(samdb, res->msgs[0]); if (ret != LDB_SUCCESS) { if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { *werr = WERR_ACCESS_DENIED; } return -1; } } *werr = WERR_OK; return serial; } /* Add DNS record to the database */ static WERROR dnsserver_db_do_add_rec(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct ldb_dn *dn, int num_rec, struct dnsp_DnssrvRpcRecord *rec) { struct ldb_message *msg; struct ldb_val v; int ret; enum ndr_err_code ndr_err; int i; msg = ldb_msg_new(mem_ctx); W_ERROR_HAVE_NO_MEMORY(msg); msg->dn = dn; ret = ldb_msg_add_string(msg, "objectClass", "dnsNode"); if (ret != LDB_SUCCESS) { return WERR_NOT_ENOUGH_MEMORY; } if (num_rec > 0 && rec) { for (i=0; izone_dn, LDB_SCOPE_BASE, attrs, "(&(objectClass=dnsNode)(name=%s))", encoded_name); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } if (res->count > 0) { talloc_free(res); return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS; } dn = ldb_dn_copy(mem_ctx, z->zone_dn); W_ERROR_HAVE_NO_MEMORY(dn); if (!ldb_dn_add_child_val(dn, "DC", name_val)) { return WERR_NOT_ENOUGH_MEMORY; } return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, 0, NULL); } static void set_record_rank(struct dnsserver_zone *z, const char *name, struct dnsp_DnssrvRpcRecord *rec) { if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_PRIMARY) { if (strcmp(name, "@") != 0 && rec->wType == DNS_TYPE_NS) { rec->rank = DNS_RANK_NS_GLUE; } else { rec->rank = DNS_RANK_ZONE; } } else if (strcmp(z->name, ".") == 0) { rec->rank = DNS_RANK_ROOT_HINT; } } /* Add a DNS record */ WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct dnsserver_zone *z, const char *name, struct DNS_RPC_RECORD *add_record) { const char * const attrs[] = { "dnsRecord", "dNSTombstoned", NULL }; struct ldb_result *res; struct dnsp_DnssrvRpcRecord *rec = NULL; struct ldb_message_element *el; struct ldb_dn *dn; enum ndr_err_code ndr_err; int ret, i; int serial; WERROR werr; bool was_tombstoned = false; char *encoded_name = ldb_binary_encode_string(mem_ctx, name); werr = dns_to_dnsp_convert(mem_ctx, add_record, &rec, true); if (!W_ERROR_IS_OK(werr)) { return werr; } /* Set the correct rank for the record. */ set_record_rank(z, name, rec); serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr); if (serial < 0) { return werr; } rec->dwSerial = serial; rec->dwTimeStamp = 0; ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, "(&(objectClass=dnsNode)(name=%s))", encoded_name); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } if (res->count == 0) { dn = dnsserver_name_to_dn(mem_ctx, z, name); W_ERROR_HAVE_NO_MEMORY(dn); return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, 1, rec); } el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); if (el == NULL) { ret = ldb_msg_add_empty(res->msgs[0], "dnsRecord", 0, &el); if (ret != LDB_SUCCESS) { return WERR_NOT_ENOUGH_MEMORY; } } was_tombstoned = ldb_msg_find_attr_as_bool(res->msgs[0], "dNSTombstoned", false); if (was_tombstoned) { el->num_values = 0; } for (i=0; inum_values; i++) { struct dnsp_DnssrvRpcRecord rec2; ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } if (dns_record_match(rec, &rec2)) { break; } } if (i < el->num_values) { return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS; } if (i == el->num_values) { /* adding a new value */ el->values = talloc_realloc(el, el->values, struct ldb_val, el->num_values+1); W_ERROR_HAVE_NO_MEMORY(el->values); el->num_values++; } ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, rec, (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } el->flags = LDB_FLAG_MOD_REPLACE; el = ldb_msg_find_element(res->msgs[0], "dNSTombstoned"); if (el != NULL) { el->flags = LDB_FLAG_MOD_DELETE; } ret = ldb_modify(samdb, res->msgs[0]); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } return WERR_OK; } /* Update a DNS record */ WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct dnsserver_zone *z, const char *name, struct DNS_RPC_RECORD *add_record, struct DNS_RPC_RECORD *del_record) { const char * const attrs[] = { "dnsRecord", NULL }; struct ldb_result *res; struct dnsp_DnssrvRpcRecord rec2; struct dnsp_DnssrvRpcRecord *arec = NULL, *drec = NULL; struct ldb_message_element *el; enum ndr_err_code ndr_err; int ret, i; int serial; WERROR werr; bool updating_ttl = false; char *encoded_name = ldb_binary_encode_string(mem_ctx, name); werr = dns_to_dnsp_convert(mem_ctx, add_record, &arec, true); if (!W_ERROR_IS_OK(werr)) { return werr; } werr = dns_to_dnsp_convert(mem_ctx, del_record, &drec, true); if (!W_ERROR_IS_OK(werr)) { return werr; } ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, "(&(objectClass=dnsNode)(name=%s)(!(dNSTombstoned=TRUE)))", encoded_name); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } if (res->count == 0) { return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; } el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); if (el == NULL || el->num_values == 0) { return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; } for (i=0; inum_values; i++) { ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } if (dns_record_match(arec, &rec2)) { break; } } if (i < el->num_values) { /* * The record already exists, which is an error UNLESS we are * doing an in-place update. * * Therefore we need to see if drec also matches, in which * case it's OK, though we can only update dwTtlSeconds and * reset the timestamp to zero. */ updating_ttl = dns_record_match(drec, &rec2); if (! updating_ttl) { return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS; } /* In this case the next loop is redundant */ } for (i=0; inum_values; i++) { ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } if (dns_record_match(drec, &rec2)) { /* * we are replacing this one with arec, which is done * by pushing arec into el->values[i] below, after the * various manipulations. */ break; } } if (i == el->num_values) { return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; } /* * If we're updating a SOA record, use the specified serial. * * Otherwise, if we are updating ttl in place (i.e., not changing * .wType and .data on a record), we should increment the existing * serial, and save to the SOA. * * Outside of those two cases, we look for the zone's SOA record and * use its serial. */ if (arec->wType != DNS_TYPE_SOA) { if (updating_ttl) { /* * In this case, we keep some of the old values. */ arec->dwSerial = rec2.dwSerial; arec->dwReserved = rec2.dwReserved; /* * TODO: if the old TTL and the new TTL are * different, the serial number is incremented. */ } else { arec->dwReserved = 0; serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr); if (serial < 0) { return werr; } arec->dwSerial = serial; } } /* Set the correct rank for the record. */ set_record_rank(z, name, arec); /* * Successful RPC updates *always* zero timestamp and flags and set * version. */ arec->dwTimeStamp = 0; arec->version = 5; arec->flags = 0; ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, arec, (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } el->flags = LDB_FLAG_MOD_REPLACE; ret = ldb_modify(samdb, res->msgs[0]); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } return WERR_OK; } /* Delete a DNS record */ WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx, struct ldb_context *samdb, struct dnsserver_zone *z, const char *name, struct DNS_RPC_RECORD *del_record) { const char * const attrs[] = { "dnsRecord", NULL }; struct ldb_result *res; struct dnsp_DnssrvRpcRecord *rec = NULL; struct ldb_message_element *el; enum ndr_err_code ndr_err; int ret, i; int serial; WERROR werr; serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr); if (serial < 0) { return werr; } werr = dns_to_dnsp_convert(mem_ctx, del_record, &rec, false); if (!W_ERROR_IS_OK(werr)) { return werr; } ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, "(&(objectClass=dnsNode)(name=%s))", ldb_binary_encode_string(mem_ctx, name)); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } if (res->count == 0) { return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; } if (res->count > 1) { return WERR_DNS_ERROR_RCODE_SERVER_FAILURE; } el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); if (el == NULL || el->num_values == 0) { return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; } for (i=0; inum_values; i++) { struct dnsp_DnssrvRpcRecord rec2; ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } if (dns_record_match(rec, &rec2)) { break; } } if (i == el->num_values) { return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; } if (i < el->num_values-1) { memmove(&el->values[i], &el->values[i+1], sizeof(el->values[0])*((el->num_values-1)-i)); } el->num_values--; if (el->num_values == 0) { ret = ldb_delete(samdb, res->msgs[0]->dn); } else { el->flags = LDB_FLAG_MOD_REPLACE; ret = ldb_modify(samdb, res->msgs[0]); } if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } return WERR_OK; } static bool dnsserver_db_msg_add_dnsproperty(TALLOC_CTX *mem_ctx, struct ldb_message *msg, struct dnsp_DnsProperty *prop) { DATA_BLOB *prop_blob; enum ndr_err_code ndr_err; int ret; prop_blob = talloc_zero(mem_ctx, DATA_BLOB); if (prop_blob == NULL) return false; ndr_err = ndr_push_struct_blob(prop_blob, mem_ctx, prop, (ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return false; } ret = ldb_msg_add_steal_value(msg, "dNSProperty", prop_blob); if (ret != LDB_SUCCESS) { return false; } return true; } WERROR dnsserver_db_do_reset_dword(struct ldb_context *samdb, struct dnsserver_zone *z, struct DNS_RPC_NAME_AND_PARAM *n_p) { struct ldb_message_element *element = NULL; struct dnsp_DnsProperty *prop = NULL; enum ndr_err_code err; TALLOC_CTX *tmp_ctx = NULL; const char * const attrs[] = {"dNSProperty", NULL}; struct ldb_result *res = NULL; int i, ret, prop_id; if (strcasecmp(n_p->pszNodeName, "Aging") == 0) { z->zoneinfo->fAging = n_p->dwParam; prop_id = DSPROPERTY_ZONE_AGING_STATE; } else if (strcasecmp(n_p->pszNodeName, "RefreshInterval") == 0) { z->zoneinfo->dwRefreshInterval = n_p->dwParam; prop_id = DSPROPERTY_ZONE_REFRESH_INTERVAL; } else if (strcasecmp(n_p->pszNodeName, "NoRefreshInterval") == 0) { z->zoneinfo->dwNoRefreshInterval = n_p->dwParam; prop_id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL; } else if (strcasecmp(n_p->pszNodeName, "AllowUpdate") == 0) { z->zoneinfo->fAllowUpdate = n_p->dwParam; prop_id = DSPROPERTY_ZONE_ALLOW_UPDATE; } else { return WERR_UNKNOWN_PROPERTY; } tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { return WERR_NOT_ENOUGH_MEMORY; } ret = ldb_search(samdb, tmp_ctx, &res, z->zone_dn, LDB_SCOPE_BASE, attrs, "(objectClass=dnsZone)"); if (ret != LDB_SUCCESS) { DBG_ERR("dnsserver: no zone: %s\n", ldb_dn_get_linearized(z->zone_dn)); TALLOC_FREE(tmp_ctx); return WERR_INTERNAL_DB_ERROR; } if (res->count != 1) { DBG_ERR("dnsserver: duplicate zone: %s\n", ldb_dn_get_linearized(z->zone_dn)); TALLOC_FREE(tmp_ctx); return WERR_GEN_FAILURE; } element = ldb_msg_find_element(res->msgs[0], "dNSProperty"); if (element == NULL) { DBG_ERR("dnsserver: zone %s has no properties.\n", ldb_dn_get_linearized(z->zone_dn)); TALLOC_FREE(tmp_ctx); return WERR_INTERNAL_DB_ERROR; } for (i = 0; i < element->num_values; i++) { prop = talloc_zero(element, struct dnsp_DnsProperty); if (prop == NULL) { TALLOC_FREE(tmp_ctx); return WERR_NOT_ENOUGH_MEMORY; } err = ndr_pull_struct_blob( &(element->values[i]), tmp_ctx, prop, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty); if (!NDR_ERR_CODE_IS_SUCCESS(err)){ /* * If we can't pull it then try again parsing * it again. A Windows server in the domain * will permit the addition of an invalidly * formed property with a 0 length and cause a * failure here */ struct dnsp_DnsProperty_short *short_property = talloc_zero(element, struct dnsp_DnsProperty_short); if (short_property == NULL) { TALLOC_FREE(tmp_ctx); return WERR_NOT_ENOUGH_MEMORY; } err = ndr_pull_struct_blob_all( &(element->values[i]), tmp_ctx, short_property, (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty_short); if (!NDR_ERR_CODE_IS_SUCCESS(err)) { /* * Unknown invalid data should be * ignored and logged to match Windows * behaviour */ DBG_NOTICE("dnsserver: couldn't PULL " "dnsProperty value#%d in " "zone %s while trying to " "reset id %d\n", i, ldb_dn_get_linearized(z->zone_dn), prop_id); continue; } /* * Initialise the parts of the property not * overwritten by value() in the IDL for * re-push */ *prop = (struct dnsp_DnsProperty){ .namelength = short_property->namelength, .id = short_property->id, .name = short_property->name /* .data will be filled in below */ }; } if (prop->id == prop_id) { switch (prop_id) { case DSPROPERTY_ZONE_AGING_STATE: prop->data.aging_enabled = n_p->dwParam; break; case DSPROPERTY_ZONE_NOREFRESH_INTERVAL: prop->data.norefresh_hours = n_p->dwParam; break; case DSPROPERTY_ZONE_REFRESH_INTERVAL: prop->data.refresh_hours = n_p->dwParam; break; case DSPROPERTY_ZONE_ALLOW_UPDATE: prop->data.allow_update_flag = n_p->dwParam; break; } err = ndr_push_struct_blob( &(element->values[i]), tmp_ctx, prop, (ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty); if (!NDR_ERR_CODE_IS_SUCCESS(err)){ DBG_ERR("dnsserver: couldn't PUSH dns prop id " "%d in zone %s\n", prop->id, ldb_dn_get_linearized(z->zone_dn)); TALLOC_FREE(tmp_ctx); return WERR_INTERNAL_DB_ERROR; } } } element->flags = LDB_FLAG_MOD_REPLACE; ret = ldb_modify(samdb, res->msgs[0]); if (ret != LDB_SUCCESS) { TALLOC_FREE(tmp_ctx); DBG_ERR("dnsserver: Failed to modify zone %s prop %s: %s\n", z->name, n_p->pszNodeName, ldb_errstring(samdb)); return WERR_INTERNAL_DB_ERROR; } TALLOC_FREE(tmp_ctx); return WERR_OK; } /* Create dnsZone record to database and set security descriptor */ static WERROR dnsserver_db_do_create_zone(TALLOC_CTX *tmp_ctx, struct ldb_context *samdb, struct ldb_dn *zone_dn, struct dnsserver_zone *z) { const char * const attrs[] = { "objectSID", NULL }; struct ldb_message *msg; struct ldb_result *res; struct ldb_message_element *el; const char sddl_template[] = "D:AI(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)(A;;CC;;;AU)(A;;RPLCLORC;;;WD)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)(A;CIID;LC;;;RU)(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)S:AI"; char *sddl; struct dom_sid dnsadmins_sid; const struct dom_sid *domain_sid; struct security_descriptor *secdesc; struct dnsp_DnsProperty *prop; DATA_BLOB *sd_encoded; enum ndr_err_code ndr_err; int ret; /* Get DnsAdmins SID */ ret = ldb_search(samdb, tmp_ctx, &res, ldb_get_default_basedn(samdb), LDB_SCOPE_DEFAULT, attrs, "(sAMAccountName=DnsAdmins)"); if (ret != LDB_SUCCESS || res->count != 1) { return WERR_INTERNAL_DB_ERROR; } el = ldb_msg_find_element(res->msgs[0], "objectSID"); if (el == NULL || el->num_values != 1) { return WERR_INTERNAL_DB_ERROR; } ndr_err = ndr_pull_struct_blob(&el->values[0], tmp_ctx, &dnsadmins_sid, (ndr_pull_flags_fn_t)ndr_pull_dom_sid); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_INTERNAL_DB_ERROR; } /* create security descriptor with DnsAdmins GUID in sddl template */ sddl = talloc_asprintf(tmp_ctx, sddl_template, dom_sid_string(tmp_ctx, &dnsadmins_sid)); if (sddl == NULL) { return WERR_NOT_ENOUGH_MEMORY; } talloc_free(res); domain_sid = samdb_domain_sid(samdb); if (domain_sid == NULL) { return WERR_INTERNAL_DB_ERROR; } secdesc = sddl_decode(tmp_ctx, sddl, domain_sid); if (secdesc == NULL) { return WERR_GEN_FAILURE; } msg = ldb_msg_new(tmp_ctx); W_ERROR_HAVE_NO_MEMORY(msg); msg->dn = zone_dn; ret = ldb_msg_add_string(msg, "objectClass", "dnsZone"); if (ret != LDB_SUCCESS) { return WERR_NOT_ENOUGH_MEMORY; } sd_encoded = talloc_zero(tmp_ctx, DATA_BLOB); W_ERROR_HAVE_NO_MEMORY(sd_encoded); ndr_err = ndr_push_struct_blob(sd_encoded, tmp_ctx, secdesc, (ndr_push_flags_fn_t)ndr_push_security_descriptor); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { return WERR_GEN_FAILURE; } ret = ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd_encoded); if (ret != LDB_SUCCESS) { return WERR_NOT_ENOUGH_MEMORY; } /* dns zone Properties */ prop = talloc_zero(tmp_ctx, struct dnsp_DnsProperty); W_ERROR_HAVE_NO_MEMORY(prop); prop->version = 1; /* zone type */ prop->id = DSPROPERTY_ZONE_TYPE; prop->data.zone_type = z->zoneinfo->dwZoneType; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } /* allow update */ prop->id = DSPROPERTY_ZONE_ALLOW_UPDATE; prop->data.allow_update_flag = z->zoneinfo->fAllowUpdate; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } /* secure time */ prop->id = DSPROPERTY_ZONE_SECURE_TIME; prop->data.zone_secure_time = 0; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } /* norefresh interval */ prop->id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL; prop->data.norefresh_hours = 168; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } /* refresh interval */ prop->id = DSPROPERTY_ZONE_REFRESH_INTERVAL; prop->data.refresh_hours = 168; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } /* aging state */ prop->id = DSPROPERTY_ZONE_AGING_STATE; prop->data.aging_enabled = z->zoneinfo->fAging; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } /* aging enabled time */ prop->id = DSPROPERTY_ZONE_AGING_ENABLED_TIME; prop->data.next_scavenging_cycle_hours = 0; if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { return WERR_NOT_ENOUGH_MEMORY; } talloc_free(prop); ret = ldb_add(samdb, msg); if (ret != LDB_SUCCESS) { DEBUG(0, ("dnsserver: Failed to create zone (%s): %s\n", z->name, ldb_errstring(samdb))); if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { return WERR_ACCESS_DENIED; } return WERR_INTERNAL_DB_ERROR; } return WERR_OK; } /* Create new dnsZone record and @ record (SOA + NS) */ WERROR dnsserver_db_create_zone(struct ldb_context *samdb, struct dnsserver_partition *partitions, struct dnsserver_zone *zone, struct loadparm_context *lp_ctx) { struct dnsserver_partition *p; bool in_forest = false; WERROR status; struct ldb_dn *dn; TALLOC_CTX *tmp_ctx; struct dnsp_DnssrvRpcRecord *dns_rec; struct dnsp_soa soa; char *tmpstr, *soa_email; struct ldb_val name_val = data_blob_string_const(zone->name); /* We only support primary zones for now */ if (zone->zoneinfo->dwZoneType != DNS_ZONE_TYPE_PRIMARY) { return WERR_CALL_NOT_IMPLEMENTED; } /* Get the correct partition */ if (zone->partition->dwDpFlags & DNS_DP_FOREST_DEFAULT) { in_forest = true; } for (p = partitions; p; p = p->next) { if (in_forest == p->is_forest) { break; } } if (p == NULL) { return WERR_DNS_ERROR_DP_DOES_NOT_EXIST; } tmp_ctx = talloc_new(NULL); W_ERROR_HAVE_NO_MEMORY(tmp_ctx); dn = ldb_dn_copy(tmp_ctx, p->partition_dn); W_ERROR_HAVE_NO_MEMORY_AND_FREE(dn, tmp_ctx); if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) { talloc_free(tmp_ctx); return WERR_NOT_ENOUGH_MEMORY; } if (!ldb_dn_add_child_val(dn, "DC", name_val)) { talloc_free(tmp_ctx); return WERR_NOT_ENOUGH_MEMORY; } /* Add dnsZone record */ status = dnsserver_db_do_create_zone(tmp_ctx, samdb, dn, zone); if (!W_ERROR_IS_OK(status)) { talloc_free(tmp_ctx); return status; } if (!ldb_dn_add_child_fmt(dn, "DC=@")) { talloc_free(tmp_ctx); return WERR_NOT_ENOUGH_MEMORY; } dns_rec = talloc_zero_array(tmp_ctx, struct dnsp_DnssrvRpcRecord, 2); W_ERROR_HAVE_NO_MEMORY_AND_FREE(dns_rec, tmp_ctx); tmpstr = talloc_asprintf(tmp_ctx, "hostmaster.%s", lpcfg_realm(lp_ctx)); W_ERROR_HAVE_NO_MEMORY_AND_FREE(tmpstr, tmp_ctx); soa_email = strlower_talloc(tmp_ctx, tmpstr); W_ERROR_HAVE_NO_MEMORY_AND_FREE(soa_email, tmp_ctx); talloc_free(tmpstr); /* SOA Record - values same as defined in provision/sambadns.py */ soa.serial = 1; soa.refresh = 900; soa.retry = 600; soa.expire = 86400; soa.minimum = 3600; soa.mname = lpcfg_dns_hostname(lp_ctx); soa.rname = soa_email; dns_rec[0].wType = DNS_TYPE_SOA; dns_rec[0].rank = DNS_RANK_ZONE; dns_rec[0].dwSerial = soa.serial; dns_rec[0].dwTtlSeconds = 3600; dns_rec[0].dwTimeStamp = 0; dns_rec[0].data.soa = soa; /* NS Record */ dns_rec[1].wType = DNS_TYPE_NS; dns_rec[1].rank = DNS_RANK_ZONE; dns_rec[1].dwSerial = soa.serial; dns_rec[1].dwTtlSeconds = 3600; dns_rec[1].dwTimeStamp = 0; dns_rec[1].data.ns = lpcfg_dns_hostname(lp_ctx); /* Add @ Record */ status = dnsserver_db_do_add_rec(tmp_ctx, samdb, dn, 2, dns_rec); talloc_free(tmp_ctx); return status; } /* Delete dnsZone record and all DNS records in the zone */ WERROR dnsserver_db_delete_zone(struct ldb_context *samdb, struct dnsserver_zone *zone) { int ret; ret = ldb_transaction_start(samdb); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } ret = dsdb_delete(samdb, zone->zone_dn, DSDB_TREE_DELETE); if (ret != LDB_SUCCESS) { ldb_transaction_cancel(samdb); if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { return WERR_ACCESS_DENIED; } return WERR_INTERNAL_DB_ERROR; } ret = ldb_transaction_commit(samdb); if (ret != LDB_SUCCESS) { return WERR_INTERNAL_DB_ERROR; } return WERR_OK; }