2014-07-30 10:01:11 +04:00
/*
Unix SMB / CIFS implementation .
DNS server utils
Copyright ( C ) 2010 Kai Blin
Copyright ( C ) 2014 Stefan Metzmacher
2015-09-22 03:10:00 +03:00
Copyright ( C ) 2015 Andrew Bartlett
2014-07-30 10:01:11 +04:00
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 "libcli/util/ntstatus.h"
# include "libcli/util/werror.h"
# include "librpc/ndr/libndr.h"
# include "librpc/gen_ndr/ndr_dns.h"
# include "librpc/gen_ndr/ndr_dnsp.h"
# include <ldb.h>
# include "dsdb/samdb/samdb.h"
# include "dsdb/common/util.h"
# include "dns_server/dnsserver_common.h"
2015-09-22 03:10:00 +03:00
# include "lib/util/dlinklist.h"
2014-07-30 10:01:11 +04:00
# undef DBGC_CLASS
# define DBGC_CLASS DBGC_DNS
uint8_t werr_to_dns_err ( WERROR werr )
{
if ( W_ERROR_EQUAL ( WERR_OK , werr ) ) {
return DNS_RCODE_OK ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( FORMAT_ERROR ) , werr ) ) {
return DNS_RCODE_FORMERR ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( SERVER_FAILURE ) , werr ) ) {
return DNS_RCODE_SERVFAIL ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( NAME_ERROR ) , werr ) ) {
return DNS_RCODE_NXDOMAIN ;
} else if ( W_ERROR_EQUAL ( WERR_DNS_ERROR_NAME_DOES_NOT_EXIST , werr ) ) {
return DNS_RCODE_NXDOMAIN ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( NOT_IMPLEMENTED ) , werr ) ) {
return DNS_RCODE_NOTIMP ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( REFUSED ) , werr ) ) {
return DNS_RCODE_REFUSED ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( YXDOMAIN ) , werr ) ) {
return DNS_RCODE_YXDOMAIN ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( YXRRSET ) , werr ) ) {
return DNS_RCODE_YXRRSET ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( NXRRSET ) , werr ) ) {
return DNS_RCODE_NXRRSET ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( NOTAUTH ) , werr ) ) {
return DNS_RCODE_NOTAUTH ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( NOTZONE ) , werr ) ) {
return DNS_RCODE_NOTZONE ;
} else if ( W_ERROR_EQUAL ( DNS_ERR ( BADKEY ) , werr ) ) {
return DNS_RCODE_BADKEY ;
}
DEBUG ( 5 , ( " No mapping exists for %s \n " , win_errstr ( werr ) ) ) ;
return DNS_RCODE_SERVFAIL ;
}
2014-07-30 10:24:10 +04:00
WERROR dns_common_extract ( const struct ldb_message_element * el ,
TALLOC_CTX * mem_ctx ,
struct dnsp_DnssrvRpcRecord * * records ,
uint16_t * num_records )
{
uint16_t ri ;
struct dnsp_DnssrvRpcRecord * recs ;
* records = NULL ;
* num_records = 0 ;
recs = talloc_zero_array ( mem_ctx , struct dnsp_DnssrvRpcRecord ,
el - > num_values ) ;
if ( recs = = NULL ) {
2015-12-03 17:24:18 +03:00
return WERR_NOT_ENOUGH_MEMORY ;
2014-07-30 10:24:10 +04:00
}
for ( ri = 0 ; ri < el - > num_values ; ri + + ) {
struct ldb_val * v = & el - > values [ ri ] ;
enum ndr_err_code ndr_err ;
ndr_err = ndr_pull_struct_blob ( v , recs , & recs [ ri ] ,
( ndr_pull_flags_fn_t ) ndr_pull_dnsp_DnssrvRpcRecord ) ;
if ( ! NDR_ERR_CODE_IS_SUCCESS ( ndr_err ) ) {
TALLOC_FREE ( recs ) ;
DEBUG ( 0 , ( " Failed to grab dnsp_DnssrvRpcRecord \n " ) ) ;
return DNS_ERR ( SERVER_FAILURE ) ;
}
}
* records = recs ;
* num_records = el - > num_values ;
return WERR_OK ;
}
WERROR dns_common_lookup ( struct ldb_context * samdb ,
TALLOC_CTX * mem_ctx ,
struct ldb_dn * dn ,
struct dnsp_DnssrvRpcRecord * * records ,
2014-07-31 10:54:17 +04:00
uint16_t * num_records ,
bool * tombstoned )
2014-07-30 10:24:10 +04:00
{
static const char * const attrs [ ] = {
" dnsRecord " ,
2014-07-31 10:54:17 +04:00
" dNSTombstoned " ,
2014-07-30 10:24:10 +04:00
NULL
} ;
int ret ;
WERROR werr ;
struct ldb_message * msg = NULL ;
struct ldb_message_element * el ;
* records = NULL ;
* num_records = 0 ;
2014-07-31 10:54:17 +04:00
if ( tombstoned ! = NULL ) {
* tombstoned = false ;
ret = dsdb_search_one ( samdb , mem_ctx , & msg , dn ,
LDB_SCOPE_BASE , attrs , 0 ,
" (objectClass=dnsNode) " ) ;
} else {
ret = dsdb_search_one ( samdb , mem_ctx , & msg , dn ,
LDB_SCOPE_BASE , attrs , 0 ,
" (&(objectClass=dnsNode)(!(dNSTombstoned=TRUE))) " ) ;
}
2014-07-30 10:24:10 +04:00
if ( ret = = LDB_ERR_NO_SUCH_OBJECT ) {
return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST ;
}
if ( ret ! = LDB_SUCCESS ) {
/* TODO: we need to check if there's a glue record we need to
* create a referral to */
return DNS_ERR ( NAME_ERROR ) ;
}
2014-07-31 10:54:17 +04:00
if ( tombstoned ! = NULL ) {
* tombstoned = ldb_msg_find_attr_as_bool ( msg ,
" dNSTombstoned " , false ) ;
}
2014-07-30 10:24:10 +04:00
el = ldb_msg_find_element ( msg , " dnsRecord " ) ;
if ( el = = NULL ) {
TALLOC_FREE ( msg ) ;
2015-10-14 01:59:26 +03:00
/*
* records produced by older Samba releases
* keep dnsNode objects without dnsRecord and
* without setting dNSTombstoned = TRUE .
*
* We just pretend they ' re tombstones .
*/
2014-07-31 10:54:17 +04:00
if ( tombstoned ! = NULL ) {
struct dnsp_DnssrvRpcRecord * recs ;
recs = talloc_array ( mem_ctx ,
struct dnsp_DnssrvRpcRecord ,
1 ) ;
if ( recs = = NULL ) {
2015-12-03 17:24:18 +03:00
return WERR_NOT_ENOUGH_MEMORY ;
2014-07-31 10:54:17 +04:00
}
recs [ 0 ] = ( struct dnsp_DnssrvRpcRecord ) {
. wType = DNS_TYPE_TOMBSTONE ,
/*
* A value of timestamp ! = 0
* indicated that the object was already
* a tombstone , this will be used
* in dns_common_replace ( )
*/
. data . timestamp = 1 ,
} ;
* tombstoned = true ;
* records = recs ;
* num_records = 1 ;
return WERR_OK ;
2015-10-14 01:59:26 +03:00
} else {
/*
* Because we are not looking for a tombstone
* in this codepath , we just pretend it does
* not exist at all .
*/
return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST ;
2014-07-31 10:54:17 +04:00
}
2014-07-30 10:24:10 +04:00
}
werr = dns_common_extract ( el , mem_ctx , records , num_records ) ;
TALLOC_FREE ( msg ) ;
if ( ! W_ERROR_IS_OK ( werr ) ) {
return werr ;
}
return WERR_OK ;
}
2014-07-30 20:27:56 +04:00
2014-07-31 12:44:41 +04:00
static int rec_cmp ( const struct dnsp_DnssrvRpcRecord * r1 ,
const struct dnsp_DnssrvRpcRecord * r2 )
{
if ( r1 - > wType ! = r2 - > wType ) {
/*
* The records are sorted with higher types first
*/
return r2 - > wType - r1 - > wType ;
}
/*
* Then we need to sort from the oldest to newest timestamp
*/
return r1 - > dwTimeStamp - r2 - > dwTimeStamp ;
}
2016-12-06 05:34:23 +03:00
/*
* Check for valid DNS names . These are names which are non - empty , do not
* start with a dot and do not have any empty segments .
*/
WERROR dns_name_check ( TALLOC_CTX * mem_ctx , size_t len , const char * name )
{
size_t i ;
if ( len = = 0 ) {
return WERR_DS_INVALID_DN_SYNTAX ;
}
for ( i = 0 ; i < len - 1 ; i + + ) {
if ( name [ i ] = = ' . ' & & name [ i + 1 ] = = ' . ' ) {
return WERR_DS_INVALID_DN_SYNTAX ;
}
}
if ( len > 1 & & name [ 0 ] = = ' . ' ) {
return WERR_DS_INVALID_DN_SYNTAX ;
}
return WERR_OK ;
}
static WERROR check_name_list ( TALLOC_CTX * mem_ctx , uint16_t rec_count ,
struct dnsp_DnssrvRpcRecord * records )
{
WERROR werr ;
uint16_t i ;
size_t len ;
struct dnsp_DnssrvRpcRecord record ;
werr = WERR_OK ;
for ( i = 0 ; i < rec_count ; i + + ) {
record = records [ i ] ;
switch ( record . wType ) {
case DNS_TYPE_NS :
len = strlen ( record . data . ns ) ;
werr = dns_name_check ( mem_ctx , len , record . data . ns ) ;
break ;
case DNS_TYPE_CNAME :
len = strlen ( record . data . cname ) ;
werr = dns_name_check ( mem_ctx , len , record . data . cname ) ;
break ;
case DNS_TYPE_SOA :
len = strlen ( record . data . soa . mname ) ;
werr = dns_name_check ( mem_ctx , len , record . data . soa . mname ) ;
if ( ! W_ERROR_IS_OK ( werr ) ) {
break ;
}
len = strlen ( record . data . soa . rname ) ;
werr = dns_name_check ( mem_ctx , len , record . data . soa . rname ) ;
break ;
case DNS_TYPE_PTR :
len = strlen ( record . data . ptr ) ;
werr = dns_name_check ( mem_ctx , len , record . data . ptr ) ;
break ;
case DNS_TYPE_MX :
len = strlen ( record . data . mx . nameTarget ) ;
werr = dns_name_check ( mem_ctx , len , record . data . mx . nameTarget ) ;
break ;
case DNS_TYPE_SRV :
len = strlen ( record . data . srv . nameTarget ) ;
werr = dns_name_check ( mem_ctx , len ,
record . data . srv . nameTarget ) ;
break ;
/*
* In the default case , the record doesn ' t have a DN , so it
* must be ok .
*/
default :
break ;
}
if ( ! W_ERROR_IS_OK ( werr ) ) {
return werr ;
}
}
return WERR_OK ;
}
2014-07-30 20:27:56 +04:00
WERROR dns_common_replace ( struct ldb_context * samdb ,
TALLOC_CTX * mem_ctx ,
struct ldb_dn * dn ,
bool needs_add ,
uint32_t serial ,
struct dnsp_DnssrvRpcRecord * records ,
uint16_t rec_count )
{
struct ldb_message_element * el ;
uint16_t i ;
int ret ;
2016-12-06 05:34:23 +03:00
WERROR werr ;
2014-07-30 20:27:56 +04:00
struct ldb_message * msg = NULL ;
2014-07-31 12:44:41 +04:00
bool was_tombstoned = false ;
bool become_tombstoned = false ;
2014-07-30 20:27:56 +04:00
msg = ldb_msg_new ( mem_ctx ) ;
W_ERROR_HAVE_NO_MEMORY ( msg ) ;
msg - > dn = dn ;
2016-12-06 05:34:23 +03:00
werr = check_name_list ( mem_ctx , rec_count , records ) ;
if ( ! W_ERROR_IS_OK ( werr ) ) {
return werr ;
}
2014-07-30 20:27:56 +04:00
ret = ldb_msg_add_empty ( msg , " dnsRecord " , LDB_FLAG_MOD_REPLACE , & el ) ;
if ( ret ! = LDB_SUCCESS ) {
return DNS_ERR ( SERVER_FAILURE ) ;
}
2014-07-31 12:44:41 +04:00
/*
* we have at least one value ,
* which might be used for the tombstone marker
*/
el - > values = talloc_zero_array ( el , struct ldb_val , MAX ( 1 , rec_count ) ) ;
2014-07-30 20:27:56 +04:00
if ( rec_count > 0 ) {
W_ERROR_HAVE_NO_MEMORY ( el - > values ) ;
2014-07-31 12:44:41 +04:00
/*
* We store a sorted list with the high wType values first
* that ' s what windows does . It also simplifies the
* filtering of DNS_TYPE_TOMBSTONE records
*/
TYPESAFE_QSORT ( records , rec_count , rec_cmp ) ;
2014-07-30 20:27:56 +04:00
}
for ( i = 0 ; i < rec_count ; i + + ) {
struct ldb_val * v = & el - > values [ el - > num_values ] ;
enum ndr_err_code ndr_err ;
2014-07-31 12:44:41 +04:00
if ( records [ i ] . wType = = DNS_TYPE_TOMBSTONE ) {
if ( records [ i ] . data . timestamp ! = 0 ) {
was_tombstoned = true ;
}
2014-07-30 20:27:56 +04:00
continue ;
}
records [ i ] . dwSerial = serial ;
ndr_err = ndr_push_struct_blob ( v , el - > values , & records [ i ] ,
( ndr_push_flags_fn_t ) ndr_push_dnsp_DnssrvRpcRecord ) ;
if ( ! NDR_ERR_CODE_IS_SUCCESS ( ndr_err ) ) {
DEBUG ( 0 , ( " Failed to push dnsp_DnssrvRpcRecord \n " ) ) ;
return DNS_ERR ( SERVER_FAILURE ) ;
}
el - > num_values + + ;
}
if ( needs_add ) {
if ( el - > num_values = = 0 ) {
return WERR_OK ;
}
ret = ldb_msg_add_string ( msg , " objectClass " , " dnsNode " ) ;
if ( ret ! = LDB_SUCCESS ) {
return DNS_ERR ( SERVER_FAILURE ) ;
}
ret = ldb_add ( samdb , msg ) ;
if ( ret ! = LDB_SUCCESS ) {
return DNS_ERR ( SERVER_FAILURE ) ;
}
return WERR_OK ;
}
if ( el - > num_values = = 0 ) {
2014-07-31 12:44:41 +04:00
struct dnsp_DnssrvRpcRecord tbs ;
struct ldb_val * v = & el - > values [ el - > num_values ] ;
enum ndr_err_code ndr_err ;
struct timeval tv ;
if ( was_tombstoned ) {
/*
* This is already a tombstoned object .
* Just leave it instead of updating the time stamp .
*/
return WERR_OK ;
}
tv = timeval_current ( ) ;
tbs = ( struct dnsp_DnssrvRpcRecord ) {
. wType = DNS_TYPE_TOMBSTONE ,
. dwSerial = serial ,
. data . timestamp = timeval_to_nttime ( & tv ) ,
} ;
ndr_err = ndr_push_struct_blob ( v , el - > values , & tbs ,
( ndr_push_flags_fn_t ) ndr_push_dnsp_DnssrvRpcRecord ) ;
if ( ! NDR_ERR_CODE_IS_SUCCESS ( ndr_err ) ) {
DEBUG ( 0 , ( " Failed to push dnsp_DnssrvRpcRecord \n " ) ) ;
return DNS_ERR ( SERVER_FAILURE ) ;
}
el - > num_values + + ;
become_tombstoned = true ;
}
if ( was_tombstoned | | become_tombstoned ) {
ret = ldb_msg_add_empty ( msg , " dNSTombstoned " ,
LDB_FLAG_MOD_REPLACE , NULL ) ;
if ( ret ! = LDB_SUCCESS ) {
return DNS_ERR ( SERVER_FAILURE ) ;
}
ret = ldb_msg_add_fmt ( msg , " dNSTombstoned " , " %s " ,
become_tombstoned ? " TRUE " : " FALSE " ) ;
if ( ret ! = LDB_SUCCESS ) {
return DNS_ERR ( SERVER_FAILURE ) ;
}
2014-07-30 20:27:56 +04:00
}
ret = ldb_modify ( samdb , msg ) ;
if ( ret ! = LDB_SUCCESS ) {
NTSTATUS nt = dsdb_ldb_err_to_ntstatus ( ret ) ;
return ntstatus_to_werror ( nt ) ;
}
return WERR_OK ;
}
2015-09-22 03:10:00 +03:00
bool dns_name_match ( const char * zone , const char * name , size_t * host_part_len )
{
size_t zl = strlen ( zone ) ;
size_t nl = strlen ( name ) ;
ssize_t zi , ni ;
static const size_t fixup = ' a ' - ' A ' ;
if ( zl > nl ) {
return false ;
}
for ( zi = zl , ni = nl ; zi > = 0 ; zi - - , ni - - ) {
char zc = zone [ zi ] ;
char nc = name [ ni ] ;
/* convert to lower case */
if ( zc > = ' A ' & & zc < = ' Z ' ) {
zc + = fixup ;
}
if ( nc > = ' A ' & & nc < = ' Z ' ) {
nc + = fixup ;
}
if ( zc ! = nc ) {
return false ;
}
}
if ( ni > = 0 ) {
if ( name [ ni ] ! = ' . ' ) {
return false ;
}
ni - - ;
}
* host_part_len = ni + 1 ;
return true ;
}
WERROR dns_common_name2dn ( struct ldb_context * samdb ,
struct dns_server_zone * zones ,
TALLOC_CTX * mem_ctx ,
const char * name ,
struct ldb_dn * * _dn )
{
struct ldb_dn * base ;
struct ldb_dn * dn ;
const struct dns_server_zone * z ;
size_t host_part_len = 0 ;
2016-12-07 05:33:06 +03:00
WERROR werr ;
2015-09-22 03:10:00 +03:00
if ( name = = NULL ) {
return DNS_ERR ( FORMAT_ERROR ) ;
}
if ( strcmp ( name , " " ) = = 0 ) {
base = ldb_get_default_basedn ( samdb ) ;
dn = ldb_dn_copy ( mem_ctx , base ) ;
ldb_dn_add_child_fmt ( dn , " DC=@,DC=RootDNSServers,CN=MicrosoftDNS,CN=System " ) ;
* _dn = dn ;
return WERR_OK ;
}
2016-12-07 05:33:06 +03:00
/* Check non-empty names */
werr = dns_name_check ( mem_ctx , strlen ( name ) , name ) ;
if ( ! W_ERROR_IS_OK ( werr ) ) {
return werr ;
}
2015-09-22 03:10:00 +03:00
for ( z = zones ; z ! = NULL ; z = z - > next ) {
bool match ;
match = dns_name_match ( z - > name , name , & host_part_len ) ;
if ( match ) {
break ;
}
}
if ( z = = NULL ) {
return DNS_ERR ( NAME_ERROR ) ;
}
if ( host_part_len = = 0 ) {
dn = ldb_dn_copy ( mem_ctx , z - > dn ) ;
ldb_dn_add_child_fmt ( dn , " DC=@ " ) ;
* _dn = dn ;
return WERR_OK ;
}
dn = ldb_dn_copy ( mem_ctx , z - > dn ) ;
ldb_dn_add_child_fmt ( dn , " DC=%*.*s " , ( int ) host_part_len , ( int ) host_part_len , name ) ;
* _dn = dn ;
return WERR_OK ;
}
static int dns_common_sort_zones ( struct ldb_message * * m1 , struct ldb_message * * m2 )
{
const char * n1 , * n2 ;
size_t l1 , l2 ;
n1 = ldb_msg_find_attr_as_string ( * m1 , " name " , NULL ) ;
n2 = ldb_msg_find_attr_as_string ( * m2 , " name " , NULL ) ;
l1 = strlen ( n1 ) ;
l2 = strlen ( n2 ) ;
/* If the string lengths are not equal just sort by length */
if ( l1 ! = l2 ) {
/* If m1 is the larger zone name, return it first */
return l2 - l1 ;
}
/*TODO: We need to compare DNs here, we want the DomainDNSZones first */
return 0 ;
}
NTSTATUS dns_common_zones ( struct ldb_context * samdb ,
TALLOC_CTX * mem_ctx ,
struct dns_server_zone * * zones_ret )
{
int ret ;
static const char * const attrs [ ] = { " name " , NULL } ;
struct ldb_result * res ;
int i ;
struct dns_server_zone * new_list = NULL ;
TALLOC_CTX * frame = talloc_stackframe ( ) ;
2016-04-11 03:05:20 +03:00
/* TODO: this search does not work against windows */
2015-09-22 03:10:00 +03:00
ret = dsdb_search ( samdb , frame , & res , NULL , LDB_SCOPE_SUBTREE ,
attrs , DSDB_SEARCH_SEARCH_ALL_PARTITIONS , " (objectClass=dnsZone) " ) ;
if ( ret ! = LDB_SUCCESS ) {
TALLOC_FREE ( frame ) ;
return NT_STATUS_INTERNAL_DB_CORRUPTION ;
}
TYPESAFE_QSORT ( res - > msgs , res - > count , dns_common_sort_zones ) ;
for ( i = 0 ; i < res - > count ; i + + ) {
struct dns_server_zone * z ;
z = talloc_zero ( mem_ctx , struct dns_server_zone ) ;
if ( z = = NULL ) {
TALLOC_FREE ( frame ) ;
return NT_STATUS_NO_MEMORY ;
}
z - > name = ldb_msg_find_attr_as_string ( res - > msgs [ i ] , " name " , NULL ) ;
talloc_steal ( z , z - > name ) ;
z - > dn = talloc_move ( z , & res - > msgs [ i ] - > dn ) ;
/*
* Ignore the RootDNSServers zone and zones that we don ' t support yet
* RootDNSServers should never be returned ( Windows DNS server don ' t )
* . . TrustAnchors should never be returned as is , ( Windows returns
* TrustAnchors ) and for the moment we don ' t support DNSSEC so we ' d better
* not return this zone .
*/
if ( ( strcmp ( z - > name , " RootDNSServers " ) = = 0 ) | |
( strcmp ( z - > name , " ..TrustAnchors " ) = = 0 ) )
{
DEBUG ( 10 , ( " Ignoring zone %s \n " , z - > name ) ) ;
talloc_free ( z ) ;
continue ;
}
2016-02-05 13:32:18 +03:00
DLIST_ADD_END ( new_list , z ) ;
2015-09-22 03:10:00 +03:00
}
* zones_ret = new_list ;
TALLOC_FREE ( frame ) ;
return NT_STATUS_OK ;
}