/*
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"
struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
struct ldb_context *samdb,
bool is_forest)
{
TALLOC_CTX *tmp_ctx;
const char *domain_prefix = "DC=DomainDnsZones";
const char *forest_prefix = "DC=ForestDnsZones";
const char *prefix;
const char * const attrs[] = {"name", NULL};
struct ldb_dn *dn_base, *partition_dn, *dn;
struct ldb_result *res;
struct dnsserver_zone *zones, *z;
int i, ret;
tmp_ctx = talloc_new(mem_ctx);
if (tmp_ctx == NULL) {
return NULL;
}
if (is_forest) {
dn_base = ldb_get_root_basedn(samdb);
} else {
dn_base = ldb_get_default_basedn(samdb);
}
partition_dn = ldb_dn_copy(tmp_ctx, dn_base);
if (partition_dn == NULL) {
goto failed;
}
if (is_forest) {
prefix = forest_prefix;
} else {
prefix = domain_prefix;
}
if (!ldb_dn_add_child_fmt(partition_dn, "%s", prefix)) {
goto failed;
}
dn = ldb_dn_copy(tmp_ctx, 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)(!(name=RootDNSServers)))");
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++) {
z = talloc_zero(mem_ctx, struct dnsserver_zone);
if (z == NULL) {
goto failed;
}
z->name = talloc_strdup(z,
ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL));
DEBUG(2, ("dnsserver: Found DNS zone %s\n", z->name));
z->zone_dn = talloc_steal(z, res->msgs[i]->dn);
z->partition_dn = talloc_steal(z, partition_dn);
DLIST_ADD_END(zones, z, NULL);
}
return zones;
failed:
talloc_free(tmp_ctx);
return NULL;
}
static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx,
struct ldb_context *samdb,
struct dnsserver_zone *z)
{
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;
NTTIME t;
unix_to_nt_time(&t, time(NULL));
t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
t /= 3600; /* convert to hours */
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 = (uint32_t)t;
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)) {
return -1;
}
break;
}
}
if (serial != -1) {
el->flags = LDB_FLAG_MOD_REPLACE;
ret = ldb_modify(samdb, res->msgs[0]);
if (ret != LDB_SUCCESS) {
return -1;
}
}
return serial;
}
static WERROR dnsserver_db_do_add_rec(TALLOC_CTX *mem_ctx,
struct ldb_context *samdb,
struct ldb_dn *dn,
struct dnsp_DnssrvRpcRecord *rec)
{
struct ldb_message *msg;
struct ldb_val v;
int ret;
enum ndr_err_code ndr_err;
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_NOMEM;
}
if (rec) {
ndr_err = ndr_push_struct_blob(&v, mem_ctx, rec,
(ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
return WERR_GENERAL_FAILURE;
}
ret = ldb_msg_add_value(msg, "dnsRecord", &v, NULL);
if (ret != LDB_SUCCESS) {
return WERR_NOMEM;
}
}
ret = ldb_add(samdb, msg);
if (ret != LDB_SUCCESS) {
return WERR_INTERNAL_DB_ERROR;
}
return WERR_OK;
}
WERROR dnsserver_db_add_empty_node(TALLOC_CTX *mem_ctx,
struct ldb_context *samdb,
struct dnsserver_zone *z,
const char *name)
{
const char * const attrs[] = { "name", NULL };
struct ldb_result *res;
struct ldb_dn *dn;
int ret;
ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_BASE, attrs,
"(&(objectClass=dnsNode)(name=%s))", 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_fmt(dn, "DC=%s", name)) {
return WERR_NOMEM;
}
return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, NULL);
}
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", NULL };
struct ldb_result *res;
struct dnsp_DnssrvRpcRecord *rec;
struct ldb_message_element *el;
struct ldb_dn *dn;
enum ndr_err_code ndr_err;
NTTIME t;
int ret, i;
int serial;
rec = dns_to_dnsp_copy(mem_ctx, add_record);
W_ERROR_HAVE_NO_MEMORY(rec);
serial = dnsserver_update_soa(mem_ctx, samdb, z);
if (serial < 0) {
return WERR_INTERNAL_DB_ERROR;
}
unix_to_nt_time(&t, time(NULL));
t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
t /= 3600; /* convert to hours */
rec->dwSerial = serial;
rec->dwTimeStamp = t;
ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
"(&(objectClass=dnsNode)(name=%s))", 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, 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_NOMEM;
}
}
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_GENERAL_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_GENERAL_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;
}
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 *arec, *drec;
struct ldb_message_element *el;
enum ndr_err_code ndr_err;
NTTIME t;
int ret, i;
int serial;
serial = dnsserver_update_soa(mem_ctx, samdb, z);
if (serial < 0) {
return WERR_INTERNAL_DB_ERROR;
}
arec = dns_to_dnsp_copy(mem_ctx, add_record);
W_ERROR_HAVE_NO_MEMORY(arec);
drec = dns_to_dnsp_copy(mem_ctx, del_record);
W_ERROR_HAVE_NO_MEMORY(drec);
unix_to_nt_time(&t, time(NULL));
t /= 10*1000*1000;
arec->dwSerial = serial;
arec->dwTimeStamp = t;
ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
"(&(objectClass=dnsNode)(name=%s))", 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++) {
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_GENERAL_FAILURE;
}
if (dns_record_match(arec, &rec2)) {
break;
}
}
if (i < el->num_values) {
return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
}
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_GENERAL_FAILURE;
}
if (dns_record_match(drec, &rec2)) {
break;
}
}
if (i == el->num_values) {
return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
}
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_GENERAL_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;
}
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;
struct ldb_message_element *el;
enum ndr_err_code ndr_err;
int ret, i;
int serial;
serial = dnsserver_update_soa(mem_ctx, samdb, z);
if (serial < 0) {
return WERR_INTERNAL_DB_ERROR;
}
rec = dns_to_dnsp_copy(mem_ctx, del_record);
W_ERROR_HAVE_NO_MEMORY(rec);
ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
"(&(objectClass=dnsNode)(name=%s))", 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++) {
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_GENERAL_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;
}