diff options
author | Stefan Metzmacher <metze@samba.org> | 2016-03-08 14:11:51 +0100 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2016-03-08 14:11:51 +0100 |
commit | e103e6b89168b02a16d2f432d347515887a49aa4 (patch) | |
tree | 5ccf8cb4e3c082d4cf2c7a72070c586520c69ef7 | |
parent | 08cff9ca228a3d7714768eb5727201895cd1dd41 (diff) | |
parent | fd69161868b5aa4d644488cc4e8069ba40266576 (diff) | |
download | samba-e103e6b89168b02a16d2f432d347515887a49aa4.tar.gz |
Merge tag 'samba-4.1.23' into v4-1-test
samba: tag release samba-4.1.23
Signed-off-by: Stefan Metzmacher <metze@samba.org>
-rw-r--r-- | WHATSNEW.txt | 87 | ||||
-rw-r--r-- | librpc/idl/dns.idl | 18 | ||||
-rw-r--r-- | librpc/idl/dnsp.idl | 4 | ||||
-rw-r--r-- | librpc/idl/dnsserver.idl | 2 | ||||
-rw-r--r-- | librpc/ndr/ndr_dns.c | 27 | ||||
-rw-r--r-- | librpc/ndr/ndr_dnsp.c | 24 | ||||
-rw-r--r-- | librpc/ndr/ndr_dnsp.h | 4 | ||||
-rw-r--r-- | librpc/wscript_build | 20 | ||||
-rw-r--r-- | python/samba/tests/dns.py | 577 | ||||
-rw-r--r-- | python/samba/tests/get_opt.py (renamed from python/samba/tests/getopt.py) | 0 | ||||
-rw-r--r-- | selftest/knownfail | 2 | ||||
-rw-r--r-- | selftest/tests.py | 2 | ||||
-rw-r--r-- | source3/client/client.c | 2 | ||||
-rw-r--r-- | source3/libsmb/clifile.c | 130 | ||||
-rw-r--r-- | source3/libsmb/proto.h | 17 | ||||
-rwxr-xr-x | source3/selftest/tests.py | 2 | ||||
-rw-r--r-- | source3/smbd/nttrans.c | 13 | ||||
-rw-r--r-- | source3/smbd/trans2.c | 68 | ||||
-rw-r--r-- | source3/torture/torture.c | 376 | ||||
-rw-r--r-- | source4/dns_server/dns_query.c | 15 | ||||
-rw-r--r-- | source4/dns_server/dns_update.c | 31 | ||||
-rwxr-xr-x | source4/librpc/wscript_build | 4 | ||||
-rwxr-xr-x | source4/selftest/tests.py | 3 |
23 files changed, 1197 insertions, 231 deletions
diff --git a/WHATSNEW.txt b/WHATSNEW.txt index 2cd1a200f9a..dc94dd401e8 100644 --- a/WHATSNEW.txt +++ b/WHATSNEW.txt @@ -1,4 +1,87 @@ ============================== + Release Notes for Samba 4.1.23 + March 8, 2015 + ============================== + + +This is a security release in order to address the following CVEs: + +o CVE-2015-7560 (Incorrect ACL get/set allowed on symlink path) +o CVE-2016-0771 (Out-of-bounds read in internal DNS server) + +======= +Details +======= + +o CVE-2015-7560: + All versions of Samba from 3.2.0 to 4.4.0rc3 inclusive are vulnerable to + a malicious client overwriting the ownership of ACLs using symlinks. + + An authenticated malicious client can use SMB1 UNIX extensions to + create a symlink to a file or directory, and then use non-UNIX SMB1 + calls to overwrite the contents of the ACL on the file or directory + linked to. + +o CVE-2016-0771: + All versions of Samba from 4.0.0 to 4.4.0rc3 inclusive, when deployed as + an AD DC and choose to run the internal DNS server, are vulnerable to an + out-of-bounds read issue during DNS TXT record handling caused by users + with permission to modify DNS records. + + A malicious client can upload a specially constructed DNS TXT record, + resulting in a remote denial-of-service attack. As long as the affected + TXT record remains undisturbed in the Samba database, a targeted DNS + query may continue to trigger this exploit. + + While unlikely, the out-of-bounds read may bypass safety checks and + allow leakage of memory from the server in the form of a DNS TXT reply. + + By default only authenticated accounts can upload DNS records, + as "allow dns updates = secure only" is the default. + Any other value would allow anonymous clients to trigger this + bug, which is a much higher risk. + + +Changes since 4.1.22: +--------------------- + +o Jeremy Allison <jra@samba.org> + * BUG 11648: CVE-2015-7560: Getting and setting Windows ACLs on symlinks can + change permissions on link target. + +o Garming Sam <garming@catalyst.net.nz> + * BUGs 11128, 11686: CVE-2016-0771: Read of uninitialized memory DNS TXT + handling. + +o Stefan Metzmacher <metze@samba.org> + * BUGs 11128, 11686: CVE-2016-0771: Read of uninitialized memory DNS TXT + handling. + + +####################################### +Reporting bugs & Development Discussion +####################################### + +Please discuss this release on the samba-technical mailing list or by +joining the #samba-technical IRC channel on irc.freenode.net. + +If you do report problems then please try to send high quality +feedback. If you don't provide vital information to help us track down +the problem then you will probably be ignored. All bug reports should +be filed under the "Samba 4.1 and newer" product in the project's Bugzilla +database (https://bugzilla.samba.org/). + + +====================================================================== +== Our Code, Our Bugs, Our Responsibility. +== The Samba Team +====================================================================== + + +Release notes for older releases follow: +---------------------------------------- + + ============================== Release Notes for Samba 4.1.22 December 16, 2015 ============================== @@ -153,8 +236,8 @@ database (https://bugzilla.samba.org/). ====================================================================== -Release notes for older releases follow: ----------------------------------------- +---------------------------------------------------------------------- + ============================== Release Notes for Samba 4.1.21 diff --git a/librpc/idl/dns.idl b/librpc/idl/dns.idl index d247e0e67d5..5435fcf38e6 100644 --- a/librpc/idl/dns.idl +++ b/librpc/idl/dns.idl @@ -8,7 +8,7 @@ encoding if it doesn't work out */ -import "misc.idl"; +import "misc.idl", "dnsp.idl"; [ helper("librpc/ndr/ndr_dns.h"), helpstring("DNS records"), @@ -152,20 +152,12 @@ interface dns } dns_soa_record; typedef [public] struct { - [value(strlen(cpu))] uint8 cpu_length; - [charset(DOS)] uint8 cpu[cpu_length]; - [value(strlen(os))] uint8 os_length; - [charset(DOS)] uint8 os[os_length]; - } dns_hinfo_record; - - typedef [public] struct { uint16 preference; dns_string exchange; } dns_mx_record; - typedef [public] struct { - [value(strlen(txt))] uint8 length; - [charset(DOS)] uint8 txt[length]; + typedef [public,nopull] struct { + dnsp_string_list txt; } dns_txt_record; typedef [public] struct { @@ -232,7 +224,7 @@ interface dns [case(DNS_QTYPE_CNAME)] dns_string cname_record; [case(DNS_QTYPE_SOA)] dns_soa_record soa_record; [case(DNS_QTYPE_PTR)] dns_string ptr_record; - [case(DNS_QTYPE_HINFO)] dns_hinfo_record hinfo_record; + [case(DNS_QTYPE_HINFO)] dnsp_hinfo hinfo_record; [case(DNS_QTYPE_MX)] dns_mx_record mx_record; [case(DNS_QTYPE_TXT)] dns_txt_record txt_record; [case(DNS_QTYPE_RP)] dns_rp_record rp_record; @@ -270,7 +262,7 @@ interface dns /* this is a convenience hook for ndrdump */ - void decode_dns_name_packet( + [nopython] void decode_dns_name_packet( [in] dns_name_packet packet ); } diff --git a/librpc/idl/dnsp.idl b/librpc/idl/dnsp.idl index 4c49001c856..d705cfcbfa3 100644 --- a/librpc/idl/dnsp.idl +++ b/librpc/idl/dnsp.idl @@ -263,11 +263,11 @@ interface dnsp /* these are convenience hooks for ndrdump */ - void decode_DnssrvRpcRecord( + [nopython] void decode_DnssrvRpcRecord( [in] dnsp_DnssrvRpcRecord blob ); - void decode_DnsProperty( + [nopython] void decode_DnsProperty( [in] dnsp_DnsProperty blob ); } diff --git a/librpc/idl/dnsserver.idl b/librpc/idl/dnsserver.idl index 506d72ef769..d567ec9e38e 100644 --- a/librpc/idl/dnsserver.idl +++ b/librpc/idl/dnsserver.idl @@ -73,7 +73,7 @@ import "misc.idl", "dnsp.idl"; typedef [public,gensize] struct { [value(strlen(str))] uint8 len; - [charset(UTF8)] uint8 str[len]; + [charset(UNIX)] uint8 str[len]; } DNS_RPC_NAME; diff --git a/librpc/ndr/ndr_dns.c b/librpc/ndr/ndr_dns.c index 0b9e3b05071..065d992684c 100644 --- a/librpc/ndr/ndr_dns.c +++ b/librpc/ndr/ndr_dns.c @@ -30,6 +30,7 @@ #include "includes.h" #include "librpc/gen_ndr/ndr_dns.h" #include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_dnsp.h" #include "system/locale.h" #include "lib/util/util_net.h" @@ -230,6 +231,29 @@ _PUBLIC_ enum ndr_err_code ndr_push_dns_string(struct ndr_push *ndr, return ndr_push_bytes(ndr, (const uint8_t *)"", 1); } +_PUBLIC_ enum ndr_err_code ndr_pull_dns_txt_record(struct ndr_pull *ndr, int ndr_flags, struct dns_txt_record *r) +{ + NDR_PULL_CHECK_FLAGS(ndr, ndr_flags); + if (ndr_flags & NDR_SCALARS) { + enum ndr_err_code ndr_err; + uint32_t data_size = ndr->data_size; + uint32_t record_size = 0; + ndr_err = ndr_token_retrieve(&ndr->array_size_list, r, + &record_size); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NDR_PULL_NEED_BYTES(ndr, record_size); + ndr->data_size = ndr->offset + record_size; + } + NDR_CHECK(ndr_pull_align(ndr, 1)); + NDR_CHECK(ndr_pull_dnsp_string_list(ndr, NDR_SCALARS, &r->txt)); + NDR_CHECK(ndr_pull_trailer_align(ndr, 1)); + ndr->data_size = data_size; + } + if (ndr_flags & NDR_BUFFERS) { + } + return NDR_ERR_SUCCESS; +} + _PUBLIC_ enum ndr_err_code ndr_push_dns_res_rec(struct ndr_push *ndr, int ndr_flags, const struct dns_res_rec *r) @@ -302,6 +326,9 @@ _PUBLIC_ enum ndr_err_code ndr_pull_dns_res_rec(struct ndr_pull *ndr, NDR_CHECK(ndr_pull_uint16(ndr, NDR_SCALARS, &r->length)); _saved_offset1 = ndr->offset; if (r->length > 0) { + NDR_CHECK(ndr_token_store(ndr, &ndr->array_size_list, + &r->rdata, + r->length)); NDR_CHECK(ndr_pull_set_switch_value(ndr, &r->rdata, r->rr_type)); NDR_CHECK(ndr_pull_dns_rdata(ndr, NDR_SCALARS, diff --git a/librpc/ndr/ndr_dnsp.c b/librpc/ndr/ndr_dnsp.c index fcb623ad822..82b5fb5d55e 100644 --- a/librpc/ndr/ndr_dnsp.c +++ b/librpc/ndr/ndr_dnsp.c @@ -225,3 +225,27 @@ enum ndr_err_code ndr_push_dnsp_string_list(struct ndr_push *ndr, int ndr_flags, } return NDR_ERR_SUCCESS; } + +enum ndr_err_code ndr_dnsp_string_list_copy(TALLOC_CTX *mem_ctx, + const struct dnsp_string_list *src, + struct dnsp_string_list *dst) +{ + size_t i; + + dst->count = 0; + dst->str = talloc_zero_array(mem_ctx, const char *, src->count); + if (dst->str == NULL) { + return NDR_ERR_ALLOC; + } + + for (i = 0; i < src->count; i++) { + dst->str[i] = talloc_strdup(dst->str, src->str[i]); + if (dst->str[i] == NULL) { + TALLOC_FREE(dst->str); + return NDR_ERR_ALLOC; + } + } + + dst->count = src->count; + return NDR_ERR_SUCCESS; +} diff --git a/librpc/ndr/ndr_dnsp.h b/librpc/ndr/ndr_dnsp.h index 67f952ccb38..0d566336c35 100644 --- a/librpc/ndr/ndr_dnsp.h +++ b/librpc/ndr/ndr_dnsp.h @@ -27,3 +27,7 @@ void ndr_print_dnsp_string(struct ndr_print *ndr, const char *name, const char *dns_string); enum ndr_err_code ndr_pull_dnsp_string(struct ndr_pull *ndr, int ndr_flags, const char **string); enum ndr_err_code ndr_push_dnsp_string(struct ndr_push *ndr, int ndr_flags, const char *string); + +enum ndr_err_code ndr_dnsp_string_list_copy(TALLOC_CTX *mem_ctx, + const struct dnsp_string_list *src, + struct dnsp_string_list *dst); diff --git a/librpc/wscript_build b/librpc/wscript_build index 2017a291fc3..30820d2c034 100644 --- a/librpc/wscript_build +++ b/librpc/wscript_build @@ -27,12 +27,12 @@ bld.SAMBA_SUBSYSTEM('NDR_NAMED_PIPE_AUTH', bld.SAMBA_SUBSYSTEM('NDR_DNSSERVER', source='gen_ndr/ndr_dnsserver.c ndr/ndr_dnsserver.c', - public_deps='ndr' + public_deps='ndr NDR_DNSP' ) bld.SAMBA_SUBSYSTEM('NDR_DNS', source='gen_ndr/ndr_dns.c ndr/ndr_dns.c', - public_deps='ndr' + public_deps='ndr NDR_DNSP' ) bld.SAMBA_SUBSYSTEM('NDR_DSBACKUP', @@ -336,7 +336,7 @@ bld.SAMBA_LIBRARY('ndr-standard', pc_files='ndr_standard.pc', deps='''NDR_SECURITY NDR_LSA NDR_SAMR NDR_NETLOGON NDR_EVENTLOG NDR_DFS NDR_NTSVCS NDR_SVCCTL NDR_INITSHUTDOWN NDR_WKSSVC NDR_SRVSVC NDR_WINREG - NDR_ECHO security NDR_DNS NDR_ATSVC NDR_SPOOLSS NDR_DSSETUP + NDR_ECHO security NDR_DNS NDR_DNSP NDR_ATSVC NDR_SPOOLSS NDR_DSSETUP NDR_SERVER_ID NDR_NOTIFY''', public_deps='ndr', public_headers='gen_ndr/samr.h gen_ndr/ndr_samr.h gen_ndr/lsa.h gen_ndr/netlogon.h gen_ndr/atsvc.h gen_ndr/ndr_atsvc.h gen_ndr/ndr_svcctl.h gen_ndr/svcctl.h', @@ -407,11 +407,6 @@ bld.SAMBA_SUBSYSTEM('RPC_NDR_AUDIOSRV', public_deps='NDR_AUDIOSRV dcerpc-binding' ) -bld.SAMBA_SUBSYSTEM('RPC_NDR_DNS', - source='gen_ndr/ndr_dns_c.c', - public_deps='dcerpc-binding NDR_DNS' - ) - bld.SAMBA_SUBSYSTEM('RPC_NDR_ECHO', source='gen_ndr/ndr_echo_c.c', public_deps='dcerpc-binding NDR_ECHO' @@ -594,11 +589,6 @@ bld.SAMBA_SUBSYSTEM('RPC_NDR_BACKUPKEY', public_deps='dcerpc-binding NDR_BACKUPKEY' ) -bld.SAMBA_SUBSYSTEM('RPC_NDR_DNSP', - source='gen_ndr/ndr_dnsp_c.c', - public_deps='dcerpc-binding NDR_DNSP' - ) - bld.SAMBA_SUBSYSTEM('RPC_NDR_DNSSERVER', source='gen_ndr/ndr_dnsserver_c.c', public_deps='dcerpc-binding ndr-standard' @@ -618,7 +608,7 @@ bld.SAMBA_SUBSYSTEM('RPC_NDR_FSRVP', bld.SAMBA_LIBRARY('ndr-samba', source=[], deps='''NDR_DRSBLOBS NDR_DRSUAPI NDR_IDMAP NDR_NTLMSSP NDR_SCHANNEL NDR_MGMT - NDR_DNSP NDR_EPMAPPER NDR_XATTR NDR_UNIXINFO NDR_NAMED_PIPE_AUTH NDR_DCOM + NDR_DNSSERVER NDR_EPMAPPER NDR_XATTR NDR_UNIXINFO NDR_NAMED_PIPE_AUTH NDR_DCOM NDR_NTPRINTING NDR_FSRVP NDR_OPEN_FILES NDR_SMBXSRV''', private_library=True, grouping_library=True @@ -630,7 +620,7 @@ bld.SAMBA_LIBRARY('dcerpc-samba', deps='''RPC_NDR_LSA RPC_NDR_SAMR RPC_NDR_NETLOGON RPC_NDR_EVENTLOG RPC_NDR_DFS RPC_NDR_NTSVCS RPC_NDR_SVCCTL RPC_NDR_INITSHUTDOWN RPC_NDR_WKSSVC RPC_NDR_SRVSVC RPC_NDR_WINREG RPC_NDR_ECHO RPC_NDR_EPMAPPER - RPC_NDR_ATSVC RPC_NDR_SPOOLSS RPC_NDR_DNS''', + RPC_NDR_ATSVC RPC_NDR_SPOOLSS RPC_NDR_DNSSERVER''', public_deps='ndr-standard', private_library=True, grouping_library=True diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py index 2983de34ac5..75b5b7f085a 100644 --- a/python/samba/tests/dns.py +++ b/python/samba/tests/dns.py @@ -16,18 +16,67 @@ # import os +import sys import struct import random + +sys.path.insert(0, "bin/python") +import samba +samba.ensure_external_module("testtools", "testtools") +samba.ensure_external_module("subunit", "subunit/python") +from subunit.run import SubunitTestRunner +import unittest + from samba import socket import samba.ndr as ndr -import samba.dcerpc.dns as dns +from samba import credentials, param from samba.tests import TestCase +from samba.dcerpc import dns, dnsp, dnsserver +from samba.netcmd.dns import TXTRecord, dns_record_match, data_to_dns_record +import samba.getopt as options +import optparse + +parser = optparse.OptionParser("dns.py <server name> <server ip> [options]") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) + +opts, args = parser.parse_args() + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) + +if len(args) < 2: + parser.print_usage() + sys.exit(1) + +server_name = args[0] +server_ip = args[1] +creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE) + +def make_txt_record(records): + rdata_txt = dns.txt_record() + s_list = dnsp.string_list() + s_list.count = len(records) + s_list.str = records + rdata_txt.txt = s_list + return rdata_txt class DNSTest(TestCase): + def setUp(self): + global server, server_ip, lp, creds + super(DNSTest, self).setUp() + self.server = server_name + self.server_ip = server_ip + self.lp = lp + self.creds = creds + def errstr(self, errcode): "Return a readable error code" string_codes = [ @@ -83,9 +132,10 @@ class DNSTest(TestCase): def get_dns_domain(self): "Helper to get dns domain" - return os.getenv('REALM', 'example.com').lower() + return self.creds.get_realm().lower() - def dns_transaction_udp(self, packet, host=os.getenv('SERVER_IP'), dump=False): + def dns_transaction_udp(self, packet, host=server_ip, + dump=False): "send a DNS query and read the reply" s = None try: @@ -103,7 +153,8 @@ class DNSTest(TestCase): if s is not None: s.close() - def dns_transaction_tcp(self, packet, host=os.getenv('SERVER_IP'), dump=False): + def dns_transaction_tcp(self, packet, host=server_ip, + dump=False): "send a DNS query and read the reply" s = None try: @@ -133,6 +184,47 @@ class DNSTest(TestCase): N+=length return result + def make_txt_update(self, prefix, txt_array): + p = self.make_name_packet(dns.DNS_OPCODE_UPDATE) + updates = [] + + name = self.get_dns_domain() + u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN) + updates.append(u) + self.finish_name_packet(p, updates) + + updates = [] + r = dns.res_rec() + r.name = "%s.%s" % (prefix, self.get_dns_domain()) + r.rr_type = dns.DNS_QTYPE_TXT + r.rr_class = dns.DNS_QCLASS_IN + r.ttl = 900 + r.length = 0xffff + rdata = make_txt_record(txt_array) + r.rdata = rdata + updates.append(r) + p.nscount = len(updates) + p.nsrecs = updates + + return p + + def check_query_txt(self, prefix, txt_array): + name = "%s.%s" % (prefix, self.get_dns_domain()) + p = self.make_name_packet(dns.DNS_OPCODE_QUERY) + questions = [] + + q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN) + questions.append(q) + + self.finish_name_packet(p, questions) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.assertEquals(response.ancount, 1) + self.assertEquals(response.answers[0].rdata.txt.str, txt_array) + + def assertIsNotNone(self, item): + self.assertTrue(item is not None) + class TestSimpleQueries(DNSTest): def test_one_a_query(self): @@ -140,7 +232,7 @@ class TestSimpleQueries(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN) print "asking for ", q.name questions.append(q) @@ -151,14 +243,14 @@ class TestSimpleQueries(DNSTest): self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) self.assertEquals(response.ancount, 1) self.assertEquals(response.answers[0].rdata, - os.getenv('SERVER_IP')) + self.server_ip) def test_one_a_query_tcp(self): "create a query packet containing one query record via TCP" p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN) print "asking for ", q.name questions.append(q) @@ -169,14 +261,14 @@ class TestSimpleQueries(DNSTest): self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) self.assertEquals(response.ancount, 1) self.assertEquals(response.answers[0].rdata, - os.getenv('SERVER_IP')) + self.server_ip) def test_one_mx_query(self): "create a query packet causing an empty RCODE_OK answer" p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_MX, dns.DNS_QCLASS_IN) print "asking for ", q.name questions.append(q) @@ -190,7 +282,7 @@ class TestSimpleQueries(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "invalid-%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "invalid-%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_MX, dns.DNS_QCLASS_IN) print "asking for ", q.name questions.append(q) @@ -206,7 +298,7 @@ class TestSimpleQueries(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN) questions.append(q) @@ -223,7 +315,7 @@ class TestSimpleQueries(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_ALL, dns.DNS_QCLASS_IN) print "asking for ", q.name questions.append(q) @@ -240,7 +332,7 @@ class TestSimpleQueries(DNSTest): self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) self.assertEquals(response.ancount, num_answers) self.assertEquals(response.answers[0].rdata, - os.getenv('SERVER_IP')) + self.server_ip) if dc_ipv6 is not None: self.assertEquals(response.answers[1].rdata, dc_ipv6) @@ -249,7 +341,7 @@ class TestSimpleQueries(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_ALL, dns.DNS_QCLASS_NONE) questions.append(q) @@ -299,7 +391,7 @@ class TestDNSUpdates(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_UPDATE) updates = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) u = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN) updates.append(u) @@ -337,7 +429,7 @@ class TestDNSUpdates(DNSTest): prereqs = [] r = dns.res_rec() - r.name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + r.name = "%s.%s" % (self.server, self.get_dns_domain()) r.rr_type = dns.DNS_QTYPE_TXT r.rr_class = dns.DNS_QCLASS_NONE r.ttl = 1 @@ -350,35 +442,8 @@ class TestDNSUpdates(DNSTest): response = self.dns_transaction_udp(p) self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR) -# I'd love to test this one, but it segfaults. :) -# def test_update_prereq_with_non_null_length(self): -# "test update with a non-null length" -# p = self.make_name_packet(dns.DNS_OPCODE_UPDATE) -# updates = [] -# -# name = self.get_dns_domain() -# -# u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN) -# updates.append(u) -# self.finish_name_packet(p, updates) -# -# prereqs = [] -# r = dns.res_rec() -# r.name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) -# r.rr_type = dns.DNS_QTYPE_TXT -# r.rr_class = dns.DNS_QCLASS_ANY -# r.ttl = 0 -# r.length = 1 -# prereqs.append(r) -# -# p.ancount = len(prereqs) -# p.answers = prereqs -# -# response = self.dns_transaction_udp(p) -# self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR) - - def test_update_prereq_nonexisting_name(self): - "test update with a nonexisting name" + def test_update_prereq_with_non_null_length(self): + "test update with a non-null length" p = self.make_name_packet(dns.DNS_OPCODE_UPDATE) updates = [] @@ -390,11 +455,11 @@ class TestDNSUpdates(DNSTest): prereqs = [] r = dns.res_rec() - r.name = "idontexist.%s" % self.get_dns_domain() + r.name = "%s.%s" % (self.server, self.get_dns_domain()) r.rr_type = dns.DNS_QTYPE_TXT r.rr_class = dns.DNS_QCLASS_ANY r.ttl = 0 - r.length = 0 + r.length = 1 prereqs.append(r) p.ancount = len(prereqs) @@ -403,8 +468,8 @@ class TestDNSUpdates(DNSTest): response = self.dns_transaction_udp(p) self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXRRSET) - def test_update_add_txt_record(self): - "test adding records works" + def test_update_prereq_nonexisting_name(self): + "test update with a nonexisting name" p = self.make_name_packet(dns.DNS_OPCODE_UPDATE) updates = [] @@ -414,76 +479,28 @@ class TestDNSUpdates(DNSTest): updates.append(u) self.finish_name_packet(p, updates) - updates = [] + prereqs = [] r = dns.res_rec() - r.name = "textrec.%s" % self.get_dns_domain() + r.name = "idontexist.%s" % self.get_dns_domain() r.rr_type = dns.DNS_QTYPE_TXT - r.rr_class = dns.DNS_QCLASS_IN - r.ttl = 900 - r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test"' - r.rdata = rdata - updates.append(r) - p.nscount = len(updates) - p.nsrecs = updates - - response = self.dns_transaction_udp(p) - self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) - - p = self.make_name_packet(dns.DNS_OPCODE_QUERY) - questions = [] - - name = "textrec.%s" % self.get_dns_domain() - q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN) - questions.append(q) - - self.finish_name_packet(p, questions) - response = self.dns_transaction_udp(p) - self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) - self.assertEquals(response.ancount, 1) - self.assertEquals(response.answers[0].rdata.txt, '"This is a test"') - - def test_update_add_two_txt_records(self): - "test adding two txt records works" - p = self.make_name_packet(dns.DNS_OPCODE_UPDATE) - updates = [] - - name = self.get_dns_domain() - - u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN) - updates.append(u) - self.finish_name_packet(p, updates) + r.rr_class = dns.DNS_QCLASS_ANY + r.ttl = 0 + r.length = 0 + prereqs.append(r) - updates = [] - r = dns.res_rec() - r.name = "textrec2.%s" % self.get_dns_domain() - r.rr_type = dns.DNS_QTYPE_TXT - r.rr_class = dns.DNS_QCLASS_IN - r.ttl = 900 - r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test" "and this is a test, too"' - r.rdata = rdata - updates.append(r) - p.nscount = len(updates) - p.nsrecs = updates + p.ancount = len(prereqs) + p.answers = prereqs response = self.dns_transaction_udp(p) - self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) - - p = self.make_name_packet(dns.DNS_OPCODE_QUERY) - questions = [] - - name = "textrec2.%s" % self.get_dns_domain() - q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN) - questions.append(q) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXRRSET) - self.finish_name_packet(p, questions) + def test_update_add_txt_record(self): + "test adding records works" + prefix, txt = 'textrec', ['"This is a test"'] + p = self.make_txt_update(prefix, txt) response = self.dns_transaction_udp(p) self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) - self.assertEquals(response.ancount, 1) - self.assertEquals(response.answers[0].rdata.txt, '"This is a test" "and this is a test, too"') + self.check_query_txt(prefix, txt) def test_delete_record(self): "Test if deleting records works" @@ -507,8 +524,7 @@ class TestDNSUpdates(DNSTest): r.rr_class = dns.DNS_QCLASS_IN r.ttl = 900 r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test"' + rdata = make_txt_record(['"This is a test"']) r.rdata = rdata updates.append(r) p.nscount = len(updates) @@ -544,8 +560,7 @@ class TestDNSUpdates(DNSTest): r.rr_class = dns.DNS_QCLASS_NONE r.ttl = 0 r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test"' + rdata = make_txt_record(['"This is a test"']) r.rdata = rdata updates.append(r) p.nscount = len(updates) @@ -587,8 +602,7 @@ class TestDNSUpdates(DNSTest): r.rr_class = dns.DNS_QCLASS_IN r.ttl = 900 r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test"' + rdata = make_txt_record(['"This is a test"']) r.rdata = rdata updates.append(r) p.nscount = len(updates) @@ -624,8 +638,7 @@ class TestDNSUpdates(DNSTest): r.rr_class = dns.DNS_QCLASS_NONE r.ttl = 0 r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test"' + rdata = make_txt_record(['"This is a test"']) r.rdata = rdata updates.append(r) p.nscount = len(updates) @@ -662,8 +675,7 @@ class TestDNSUpdates(DNSTest): r.rr_class = dns.DNS_QCLASS_IN r.ttl = 900 r.length = 0xffff - rdata = dns.txt_record() - rdata.txt = '"This is a test"' + rdata = make_txt_record(['"This is a test"']) r.rdata = rdata updates.append(r) p.nscount = len(updates) @@ -748,7 +760,7 @@ class TestComplexQueries(DNSTest): r.rr_class = dns.DNS_QCLASS_IN r.ttl = 900 r.length = 0xffff - r.rdata = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + r.rdata = "%s.%s" % (self.server, self.get_dns_domain()) updates.append(r) p.nscount = len(updates) p.nsrecs = updates @@ -774,7 +786,7 @@ class TestComplexQueries(DNSTest): r.rr_class = dns.DNS_QCLASS_NONE r.ttl = 0 r.length = 0xffff - r.rdata = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + r.rdata = "%s.%s" % (self.server, self.get_dns_domain()) updates.append(r) p.nscount = len(updates) p.nsrecs = updates @@ -799,10 +811,10 @@ class TestComplexQueries(DNSTest): self.assertEquals(response.ancount, 2) self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_CNAME) self.assertEquals(response.answers[0].rdata, "%s.%s" % - (os.getenv('SERVER'), self.get_dns_domain())) + (self.server, self.get_dns_domain())) self.assertEquals(response.answers[1].rr_type, dns.DNS_QTYPE_A) self.assertEquals(response.answers[1].rdata, - os.getenv('SERVER_IP')) + self.server_ip) class TestInvalidQueries(DNSTest): @@ -812,7 +824,7 @@ class TestInvalidQueries(DNSTest): s = None try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) - s.connect((os.getenv('SERVER_IP'), 53)) + s.connect((self.server_ip, 53)) s.send("", 0) finally: if s is not None: @@ -821,7 +833,7 @@ class TestInvalidQueries(DNSTest): p = self.make_name_packet(dns.DNS_OPCODE_QUERY) questions = [] - name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain()) + name = "%s.%s" % (self.server, self.get_dns_domain()) q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN) print "asking for ", q.name questions.append(q) @@ -832,7 +844,7 @@ class TestInvalidQueries(DNSTest): self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY) self.assertEquals(response.ancount, 1) self.assertEquals(response.answers[0].rdata, - os.getenv('SERVER_IP')) + self.server_ip) def test_one_a_reply(self): "send a reply instead of a query" @@ -851,7 +863,7 @@ class TestInvalidQueries(DNSTest): try: send_packet = ndr.ndr_pack(p) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - host=os.getenv('SERVER_IP') + host=self.server_ip s.connect((host, 53)) tcp_packet = struct.pack('!H', len(send_packet)) tcp_packet += send_packet @@ -862,7 +874,308 @@ class TestInvalidQueries(DNSTest): if s is not None: s.close() +class TestRPCRoundtrip(DNSTest): + def setUp(self): + super(TestRPCRoundtrip, self).setUp() + self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" % (self.server_ip), + self.lp, self.creds) + + def tearDown(self): + super(TestRPCRoundtrip, self).tearDown() + + def test_update_add_txt_rpc_to_dns(self): + prefix, txt = 'rpctextrec', ['"This is a test"'] + + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"\\"This is a test\\""') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + def test_update_add_null_padded_txt_record(self): + "test adding records works" + prefix, txt = 'pad1textrec', ['"This is a test"', '', ''] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" ""')) + + prefix, txt = 'pad2textrec', ['"This is a test"', '', '', 'more text'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" "" "more text"')) + + prefix, txt = 'pad3textrec', ['', '', '"This is a test"'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"" "" "\\"This is a test\\""')) + + def test_update_add_padding_rpc_to_dns(self): + prefix, txt = 'pad1textrec', ['"This is a test"', '', ''] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" ""') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + prefix, txt = 'pad2textrec', ['"This is a test"', '', '', 'more text'] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" "" "more text"') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) -if __name__ == "__main__": - import unittest - unittest.main() + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + prefix, txt = 'pad3textrec', ['', '', '"This is a test"'] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"" "" "\\"This is a test\\""') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + # Test is incomplete due to strlen against txt records + def test_update_add_null_char_txt_record(self): + "test adding records works" + prefix, txt = 'nulltextrec', ['NULL\x00BYTE'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, ['NULL']) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"NULL"')) + + prefix, txt = 'nulltextrec2', ['NULL\x00BYTE', 'NULL\x00BYTE'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, ['NULL', 'NULL']) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"NULL" "NULL"')) + + def test_update_add_null_char_rpc_to_dns(self): + prefix, txt = 'nulltextrec', ['NULL\x00BYTE'] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"NULL"') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, ['NULL']) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + def test_update_add_hex_char_txt_record(self): + "test adding records works" + prefix, txt = 'hextextrec', ['HIGH\xFFBYTE'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"HIGH\xFFBYTE"')) + + def test_update_add_hex_rpc_to_dns(self): + prefix, txt = 'hextextrec', ['HIGH\xFFBYTE'] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"HIGH\xFFBYTE"') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + def test_update_add_slash_txt_record(self): + "test adding records works" + prefix, txt = 'slashtextrec', ['Th\\=is=is a test'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"Th\\\\=is=is a test"')) + + # This test fails against Windows as it eliminates slashes in RPC + # One typical use for a slash is in records like 'var=value' to + # escape '=' characters. + def test_update_add_slash_rpc_to_dns(self): + prefix, txt = 'slashtextrec', ['Th\\=is=is a test'] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"Th\\\\=is=is a test"') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + def test_update_add_two_txt_records(self): + "test adding two txt records works" + prefix, txt = 'textrec2', ['"This is a test"', + '"and this is a test, too"'] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '"\\"This is a test\\""' + + ' "\\"and this is a test, too\\""')) + + def test_update_add_two_rpc_to_dns(self): + prefix, txt = 'textrec2', ['"This is a test"', + '"and this is a test, too"'] + prefix = 'rpc' + prefix + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, + '"\\"This is a test\\""' + + ' "\\"and this is a test, too\\""') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + + def test_update_add_empty_txt_records(self): + "test adding two txt records works" + prefix, txt = 'emptytextrec', [] + p = self.make_txt_update(prefix, txt) + response = self.dns_transaction_udp(p) + self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK) + self.check_query_txt(prefix, txt) + self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip, + self.get_dns_domain(), + "%s.%s" % (prefix, self.get_dns_domain()), + dnsp.DNS_TYPE_TXT, '')) + + def test_update_add_empty_rpc_to_dns(self): + prefix, txt = 'rpcemptytextrec', [] + + name = "%s.%s" % (prefix, self.get_dns_domain()) + + rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '') + add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF() + add_rec_buf.rec = rec + try: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, add_rec_buf, None) + + self.check_query_txt(prefix, txt) + finally: + self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN, + 0, self.server_ip, self.get_dns_domain(), + name, None, add_rec_buf) + +runner = SubunitTestRunner() +rc = 0 +if not runner.run(unittest.makeSuite(DNSTest)).wasSuccessful(): + rc = 1 +if not runner.run(unittest.makeSuite(TestSimpleQueries)).wasSuccessful(): + rc = 1 +if not runner.run(unittest.makeSuite(TestDNSUpdates)).wasSuccessful(): + rc = 1 +if not runner.run(unittest.makeSuite(TestComplexQueries)).wasSuccessful(): + rc = 1 +if not runner.run(unittest.makeSuite(TestInvalidQueries)).wasSuccessful(): + rc = 1 +if not runner.run(unittest.makeSuite(TestRPCRoundtrip)).wasSuccessful(): + rc = 1 + +sys.exit(rc) diff --git a/python/samba/tests/getopt.py b/python/samba/tests/get_opt.py index 14ee0a7428b..14ee0a7428b 100644 --- a/python/samba/tests/getopt.py +++ b/python/samba/tests/get_opt.py diff --git a/selftest/knownfail b/selftest/knownfail index 104ddbd4029..bf35b75d558 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -16,6 +16,8 @@ ^samba3.smbtorture_s3.plain\(dc\).UID-REGRESSION-TEST # Fails against the s4 ntvfs server ^samba3.smbtorture_s3.plain\(dc\).SHORTNAME-TEST # Fails against the s4 ntvfs server ^samba3.smbtorture_s3.plain\(dc\).POSIX-APPEND # Fails against the s4 ntvfs server +^samba3.smbtorture_s3.plain\(ad_dc_ntvfs\).POSIX-SYMLINK-ACL # Fails against the s4 ntvfs server +^samba3.smbtorture_s3.plain\(ad_dc_ntvfs\).POSIX-SYMLINK-EA # Fails against the s4 ntvfs server ^samba3.smbtorture_s3.plain\(dc\).NTTRANS-FSCTL # Fails against the s4 ntvfs server ^samba3.smbtorture_s3.plain\(dc\).SMB2-NEGPROT # Fails against the s4 ntvfs server ^samba3.smbtorture_s3.plain\(dc\).BAD-NBT-SESSION # Fails against the s4 ntvfs server diff --git a/selftest/tests.py b/selftest/tests.py index 88a08c9724d..a3806ed4069 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -43,7 +43,7 @@ planpythontestsuite("none", "api", name="ldb.python", extra_path=['lib/ldb/tests planpythontestsuite("none", "samba.tests.credentials") planpythontestsuite("none", "samba.tests.registry") planpythontestsuite("none", "samba.tests.auth") -planpythontestsuite("none", "samba.tests.getopt") +planpythontestsuite("none", "samba.tests.get_opt") planpythontestsuite("none", "samba.tests.security") planpythontestsuite("none", "samba.tests.dcerpc.misc") planpythontestsuite("none", "samba.tests.param") diff --git a/source3/client/client.c b/source3/client/client.c index 00f8282153c..aa547362d40 100644 --- a/source3/client/client.c +++ b/source3/client/client.c @@ -3380,7 +3380,7 @@ static int cmd_getfacl(void) return 1; } - status = cli_posix_getfacl(targetcli, targetname, ctx, &rb_size, &retbuf); + status = cli_posix_getacl(targetcli, targetname, ctx, &rb_size, &retbuf); if (!NT_STATUS_IS_OK(status)) { d_printf("%s getfacl file %s\n", nt_errstr(status), src); diff --git a/source3/libsmb/clifile.c b/source3/libsmb/clifile.c index 70b769d1bd8..642f5974115 100644 --- a/source3/libsmb/clifile.c +++ b/source3/libsmb/clifile.c @@ -590,25 +590,25 @@ NTSTATUS cli_posix_hardlink(struct cli_state *cli, } /**************************************************************************** - Do a POSIX getfacl (UNIX extensions). + Do a POSIX getacl - pathname based ACL get (UNIX extensions). ****************************************************************************/ -struct getfacl_state { +struct getacl_state { uint32_t num_data; uint8_t *data; }; -static void cli_posix_getfacl_done(struct tevent_req *subreq); +static void cli_posix_getacl_done(struct tevent_req *subreq); -struct tevent_req *cli_posix_getfacl_send(TALLOC_CTX *mem_ctx, +struct tevent_req *cli_posix_getacl_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *fname) { struct tevent_req *req = NULL, *subreq = NULL; - struct getfacl_state *state = NULL; + struct getacl_state *state = NULL; - req = tevent_req_create(mem_ctx, &state, struct getfacl_state); + req = tevent_req_create(mem_ctx, &state, struct getacl_state); if (req == NULL) { return NULL; } @@ -617,16 +617,16 @@ struct tevent_req *cli_posix_getfacl_send(TALLOC_CTX *mem_ctx, if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } - tevent_req_set_callback(subreq, cli_posix_getfacl_done, req); + tevent_req_set_callback(subreq, cli_posix_getacl_done, req); return req; } -static void cli_posix_getfacl_done(struct tevent_req *subreq) +static void cli_posix_getacl_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); - struct getfacl_state *state = tevent_req_data( - req, struct getfacl_state); + struct getacl_state *state = tevent_req_data( + req, struct getacl_state); NTSTATUS status; status = cli_qpathinfo_recv(subreq, state, &state->data, @@ -638,12 +638,12 @@ static void cli_posix_getfacl_done(struct tevent_req *subreq) tevent_req_done(req); } -NTSTATUS cli_posix_getfacl_recv(struct tevent_req *req, +NTSTATUS cli_posix_getacl_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *prb_size, char **retbuf) { - struct getfacl_state *state = tevent_req_data(req, struct getfacl_state); + struct getacl_state *state = tevent_req_data(req, struct getacl_state); NTSTATUS status; if (tevent_req_is_nterror(req, &status)) { @@ -654,7 +654,7 @@ NTSTATUS cli_posix_getfacl_recv(struct tevent_req *req, return NT_STATUS_OK; } -NTSTATUS cli_posix_getfacl(struct cli_state *cli, +NTSTATUS cli_posix_getacl(struct cli_state *cli, const char *fname, TALLOC_CTX *mem_ctx, size_t *prb_size, @@ -679,7 +679,7 @@ NTSTATUS cli_posix_getfacl(struct cli_state *cli, goto fail; } - req = cli_posix_getfacl_send(frame, + req = cli_posix_getacl_send(frame, ev, cli, fname); @@ -693,7 +693,107 @@ NTSTATUS cli_posix_getfacl(struct cli_state *cli, goto fail; } - status = cli_posix_getfacl_recv(req, mem_ctx, prb_size, retbuf); + status = cli_posix_getacl_recv(req, mem_ctx, prb_size, retbuf); + + fail: + TALLOC_FREE(frame); + return status; +} + +/**************************************************************************** + Do a POSIX setacl - pathname based ACL set (UNIX extensions). +****************************************************************************/ + +struct setacl_state { + uint8_t *data; +}; + +static void cli_posix_setacl_done(struct tevent_req *subreq); + +struct tevent_req *cli_posix_setacl_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_state *cli, + const char *fname, + const void *data, + size_t num_data) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct setacl_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, struct setacl_state); + if (req == NULL) { + return NULL; + } + state->data = talloc_memdup(state, data, num_data); + if (tevent_req_nomem(state->data, req)) { + return tevent_req_post(req, ev); + } + + subreq = cli_setpathinfo_send(state, + ev, + cli, + SMB_SET_POSIX_ACL, + fname, + state->data, + num_data); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cli_posix_setacl_done, req); + return req; +} + +static void cli_posix_setacl_done(struct tevent_req *subreq) +{ + NTSTATUS status = cli_setpathinfo_recv(subreq); + tevent_req_simple_finish_ntstatus(subreq, status); +} + +NTSTATUS cli_posix_setacl_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +NTSTATUS cli_posix_setacl(struct cli_state *cli, + const char *fname, + const void *acl_buf, + size_t acl_buf_size) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev = NULL; + struct tevent_req *req = NULL; + NTSTATUS status = NT_STATUS_OK; + + if (smbXcli_conn_has_async_calls(cli->conn)) { + /* + * Can't use sync call while an async call is in flight + */ + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + req = cli_posix_setacl_send(frame, + ev, + cli, + fname, + acl_buf, + acl_buf_size); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + + status = cli_posix_setacl_recv(req); fail: TALLOC_FREE(frame); diff --git a/source3/libsmb/proto.h b/source3/libsmb/proto.h index 525625cd300..d6706939ab8 100644 --- a/source3/libsmb/proto.h +++ b/source3/libsmb/proto.h @@ -255,19 +255,30 @@ NTSTATUS cli_posix_hardlink(struct cli_state *cli, const char *newname); uint32_t unix_perms_to_wire(mode_t perms); mode_t wire_perms_to_unix(uint32_t perms); -struct tevent_req *cli_posix_getfacl_send(TALLOC_CTX *mem_ctx, +struct tevent_req *cli_posix_getacl_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *fname); -NTSTATUS cli_posix_getfacl_recv(struct tevent_req *req, +NTSTATUS cli_posix_getacl_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *prb_size, char **retbuf); -NTSTATUS cli_posix_getfacl(struct cli_state *cli, +NTSTATUS cli_posix_getacl(struct cli_state *cli, const char *fname, TALLOC_CTX *mem_ctx, size_t *prb_size, char **retbuf); +struct tevent_req *cli_posix_setacl_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_state *cli, + const char *fname, + const void *acl_buf, + size_t acl_buf_size); +NTSTATUS cli_posix_setacl_recv(struct tevent_req *req); +NTSTATUS cli_posix_setacl(struct cli_state *cli, + const char *fname, + const void *acl_buf, + size_t acl_buf_size); struct tevent_req *cli_posix_stat_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py index 786b52ab217..02adeed5c7a 100755 --- a/source3/selftest/tests.py +++ b/source3/selftest/tests.py @@ -79,7 +79,7 @@ tests = ["RW1", "RW2", "RW3"] for t in tests: plantestsuite("samba3.smbtorture_s3.vfs_aio_fork(simpleserver).%s" % t, "simpleserver", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/vfs_aio_fork', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"]) -posix_tests = ["POSIX", "POSIX-APPEND"] +posix_tests = ["POSIX", "POSIX-APPEND", "POSIX-SYMLINK-ACL", "POSIX-SYMLINK-EA"] for t in posix_tests: plantestsuite("samba3.smbtorture_s3.plain(s3dc).%s" % t, "s3dc", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/posix_share', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"]) diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c index 131009b14c3..8d887a30076 100644 --- a/source3/smbd/nttrans.c +++ b/source3/smbd/nttrans.c @@ -882,6 +882,12 @@ NTSTATUS set_sd(files_struct *fsp, struct security_descriptor *psd, return NT_STATUS_OK; } + if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) { + DEBUG(10, ("ACL set on symlink %s denied.\n", + fsp_str_dbg(fsp))); + return NT_STATUS_ACCESS_DENIED; + } + if (psd->owner_sid == NULL) { security_info_sent &= ~SECINFO_OWNER; } @@ -1912,6 +1918,13 @@ NTSTATUS smbd_do_query_security_desc(connection_struct *conn, return NT_STATUS_ACCESS_DENIED; } + if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) { + DEBUG(10, ("ACL get on symlink %s denied.\n", + fsp_str_dbg(fsp))); + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; + } + if (security_info_wanted & (SECINFO_DACL|SECINFO_OWNER| SECINFO_GROUP|SECINFO_SACL)) { /* Don't return SECINFO_LABEL if anything else was diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c index b6109b2d07a..103e601072e 100644 --- a/source3/smbd/trans2.c +++ b/source3/smbd/trans2.c @@ -53,6 +53,34 @@ static char *store_file_unix_basic_info2(connection_struct *conn, files_struct *fsp, const SMB_STRUCT_STAT *psbuf); +/**************************************************************************** + Check if an open file handle or pathname is a symlink. +****************************************************************************/ + +static NTSTATUS refuse_symlink(connection_struct *conn, + const files_struct *fsp, + const char *name) +{ + SMB_STRUCT_STAT sbuf; + const SMB_STRUCT_STAT *pst = NULL; + + if (fsp) { + pst = &fsp->fsp_name->st; + } else { + int ret = vfs_stat_smb_basename(conn, + name, + &sbuf); + if (ret == -1) { + return map_nt_error_from_unix(errno); + } + pst = &sbuf; + } + if (S_ISLNK(pst->st_ex_mode)) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + /******************************************************************** The canonical "check access" based on object handle or path function. ********************************************************************/ @@ -208,12 +236,22 @@ NTSTATUS get_ea_names_from_file(TALLOC_CTX *mem_ctx, connection_struct *conn, char **names, **tmp; size_t num_names; ssize_t sizeret = -1; + NTSTATUS status; + + if (pnames) { + *pnames = NULL; + } + *pnum_names = 0; if (!lp_ea_support(SNUM(conn))) { - if (pnames) { - *pnames = NULL; - } - *pnum_names = 0; + return NT_STATUS_OK; + } + + status = refuse_symlink(conn, fsp, fname); + if (!NT_STATUS_IS_OK(status)) { + /* + * Just return no EA's on a symlink. + */ return NT_STATUS_OK; } @@ -263,10 +301,6 @@ NTSTATUS get_ea_names_from_file(TALLOC_CTX *mem_ctx, connection_struct *conn, if (sizeret == 0) { TALLOC_FREE(names); - if (pnames) { - *pnames = NULL; - } - *pnum_names = 0; return NT_STATUS_OK; } @@ -624,6 +658,11 @@ NTSTATUS set_ea(connection_struct *conn, files_struct *fsp, return NT_STATUS_EAS_NOT_SUPPORTED; } + status = refuse_symlink(conn, fsp, smb_fname->base_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = check_access(conn, fsp, smb_fname, FILE_WRITE_EA); if (!NT_STATUS_IS_OK(status)) { return status; @@ -5030,6 +5069,13 @@ NTSTATUS smbd_do_qfilepathinfo(connection_struct *conn, uint16 num_file_acls = 0; uint16 num_def_acls = 0; + status = refuse_symlink(conn, + fsp, + smb_fname->base_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (fsp && fsp->fh->fd != -1) { file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, talloc_tos()); @@ -6539,6 +6585,7 @@ static NTSTATUS smb_set_posix_acl(connection_struct *conn, uint16 num_def_acls; bool valid_file_acls = True; bool valid_def_acls = True; + NTSTATUS status; if (total_data < SMB_POSIX_ACL_HEADER_SIZE) { return NT_STATUS_INVALID_PARAMETER; @@ -6566,6 +6613,11 @@ static NTSTATUS smb_set_posix_acl(connection_struct *conn, return NT_STATUS_INVALID_PARAMETER; } + status = refuse_symlink(conn, fsp, smb_fname->base_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + DEBUG(10,("smb_set_posix_acl: file %s num_file_acls = %u, num_def_acls = %u\n", smb_fname ? smb_fname_str_dbg(smb_fname) : fsp_str_dbg(fsp), (unsigned int)num_file_acls, diff --git a/source3/torture/torture.c b/source3/torture/torture.c index 2e66912a942..78803cf7b5a 100644 --- a/source3/torture/torture.c +++ b/source3/torture/torture.c @@ -5754,6 +5754,380 @@ static bool run_simple_posix_open_test(int dummy) return correct; } +/* + Test POSIX and Windows ACLs are rejected on symlinks. + */ +static bool run_acl_symlink_test(int dummy) +{ + static struct cli_state *cli; + const char *fname = "posix_file"; + const char *sname = "posix_symlink"; + uint16_t fnum = (uint16_t)-1; + bool correct = false; + NTSTATUS status; + char *posix_acl = NULL; + size_t posix_acl_len = 0; + char *posix_acl_sym = NULL; + size_t posix_acl_len_sym = 0; + struct security_descriptor *sd = NULL; + struct security_descriptor *sd_sym = NULL; + TALLOC_CTX *frame = NULL; + + frame = talloc_stackframe(); + + printf("Starting acl symlink test\n"); + + if (!torture_open_connection(&cli, 0)) { + TALLOC_FREE(frame); + return false; + } + + smbXcli_conn_set_sockopt(cli->conn, sockops); + + status = torture_setup_unix_extensions(cli); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return false; + } + + cli_setatr(cli, fname, 0, 0); + cli_posix_unlink(cli, fname); + cli_setatr(cli, sname, 0, 0); + cli_posix_unlink(cli, sname); + + status = cli_ntcreate(cli, + fname, + 0, + READ_CONTROL_ACCESS, + 0, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + FILE_CREATE, + 0x0, + 0x0, + &fnum, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_ntcreate of %s failed (%s)\n", + fname, + nt_errstr(status)); + goto out; + } + + /* Get the Windows ACL on the file. */ + status = cli_query_secdesc(cli, + fnum, + frame, + &sd); + if (!NT_STATUS_IS_OK(status)) { + printf("cli_query_secdesc failed (%s)\n", + nt_errstr(status)); + goto out; + } + + /* Get the POSIX ACL on the file. */ + status = cli_posix_getacl(cli, + fname, + frame, + &posix_acl_len, + &posix_acl); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_posix_getacl failed (%s)\n", + nt_errstr(status)); + goto out; + } + + status = cli_close(cli, fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("close failed (%s)\n", nt_errstr(status)); + goto out; + } + fnum = (uint16_t)-1; + + /* Now create a symlink. */ + status = cli_posix_symlink(cli, fname, sname); + if (!NT_STATUS_IS_OK(status)) { + printf("cli_posix_symlink of %s -> %s failed (%s)\n", + sname, + fname, + nt_errstr(status)); + goto out; + } + + /* Open a handle on the symlink. */ + status = cli_ntcreate(cli, + sname, + 0, + READ_CONTROL_ACCESS|SEC_STD_WRITE_DAC, + 0, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + FILE_OPEN, + 0x0, + 0x0, + &fnum, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_posix_open of %s failed (%s)\n", + sname, + nt_errstr(status)); + goto out; + } + + /* Get the Windows ACL on the symlink handle. Should fail */ + status = cli_query_secdesc(cli, + fnum, + frame, + &sd_sym); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("cli_query_secdesc on a symlink gave %s. " + "Should be NT_STATUS_ACCESS_DENIED.\n", + nt_errstr(status)); + goto out; + } + + /* Get the POSIX ACL on the symlink pathname. Should fail. */ + status = cli_posix_getacl(cli, + sname, + frame, + &posix_acl_len_sym, + &posix_acl_sym); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("cli_posix_getacl on a symlink gave %s. " + "Should be NT_STATUS_ACCESS_DENIED.\n", + nt_errstr(status)); + goto out; + } + + /* Set the Windows ACL on the symlink handle. Should fail */ + status = cli_set_security_descriptor(cli, + fnum, + SECINFO_DACL, + sd); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("cli_query_secdesc on a symlink gave %s. " + "Should be NT_STATUS_ACCESS_DENIED.\n", + nt_errstr(status)); + goto out; + } + + /* Set the POSIX ACL on the symlink pathname. Should fail. */ + status = cli_posix_setacl(cli, + sname, + posix_acl, + posix_acl_len); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("cli_posix_getacl on a symlink gave %s. " + "Should be NT_STATUS_ACCESS_DENIED.\n", + nt_errstr(status)); + goto out; + } + + printf("ACL symlink test passed\n"); + correct = true; + + out: + + if (fnum != (uint16_t)-1) { + cli_close(cli, fnum); + fnum = (uint16_t)-1; + } + + cli_setatr(cli, sname, 0, 0); + cli_posix_unlink(cli, sname); + cli_setatr(cli, fname, 0, 0); + cli_posix_unlink(cli, fname); + + if (!torture_close_connection(cli)) { + correct = false; + } + + TALLOC_FREE(frame); + return correct; +} + +/* + Test setting EA's are rejected on symlinks. + */ +static bool run_ea_symlink_test(int dummy) +{ + static struct cli_state *cli; + const char *fname = "posix_file_ea"; + const char *sname = "posix_symlink_ea"; + const char *ea_name = "testea_name"; + const char *ea_value = "testea_value"; + uint16_t fnum = (uint16_t)-1; + bool correct = false; + NTSTATUS status; + size_t i, num_eas; + struct ea_struct *eas = NULL; + TALLOC_CTX *frame = NULL; + + frame = talloc_stackframe(); + + printf("Starting EA symlink test\n"); + + if (!torture_open_connection(&cli, 0)) { + TALLOC_FREE(frame); + return false; + } + + smbXcli_conn_set_sockopt(cli->conn, sockops); + + status = torture_setup_unix_extensions(cli); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return false; + } + + cli_setatr(cli, fname, 0, 0); + cli_posix_unlink(cli, fname); + cli_setatr(cli, sname, 0, 0); + cli_posix_unlink(cli, sname); + + status = cli_ntcreate(cli, + fname, + 0, + READ_CONTROL_ACCESS, + 0, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + FILE_CREATE, + 0x0, + 0x0, + &fnum, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_ntcreate of %s failed (%s)\n", + fname, + nt_errstr(status)); + goto out; + } + + status = cli_close(cli, fnum); + if (!NT_STATUS_IS_OK(status)) { + printf("close failed (%s)\n", + nt_errstr(status)); + goto out; + } + fnum = (uint16_t)-1; + + /* Set an EA on the path. */ + status = cli_set_ea_path(cli, + fname, + ea_name, + ea_value, + strlen(ea_value)+1); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_set_ea_path failed (%s)\n", + nt_errstr(status)); + goto out; + } + + /* Now create a symlink. */ + status = cli_posix_symlink(cli, fname, sname); + if (!NT_STATUS_IS_OK(status)) { + printf("cli_posix_symlink of %s -> %s failed (%s)\n", + sname, + fname, + nt_errstr(status)); + goto out; + } + + /* Get the EA list on the path. Should return value set. */ + status = cli_get_ea_list_path(cli, + fname, + frame, + &num_eas, + &eas); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_get_ea_list_path failed (%s)\n", + nt_errstr(status)); + goto out; + } + + /* Ensure the EA we set is there. */ + for (i=0; i<num_eas; i++) { + if (strcmp(eas[i].name, ea_name) == 0 && + eas[i].value.length == strlen(ea_value)+1 && + memcmp(eas[i].value.data, + ea_value, + eas[i].value.length) == 0) { + break; + } + } + + if (i == num_eas) { + printf("Didn't find EA on pathname %s\n", + fname); + goto out; + } + + num_eas = 0; + TALLOC_FREE(eas); + + /* Get the EA list on the symlink. Should return empty list. */ + status = cli_get_ea_list_path(cli, + sname, + frame, + &num_eas, + &eas); + + if (!NT_STATUS_IS_OK(status)) { + printf("cli_get_ea_list_path failed (%s)\n", + nt_errstr(status)); + goto out; + } + + if (num_eas != 0) { + printf("cli_get_ea_list_path failed (%s)\n", + nt_errstr(status)); + goto out; + } + + /* Set an EA on the symlink. Should fail. */ + status = cli_set_ea_path(cli, + sname, + ea_name, + ea_value, + strlen(ea_value)+1); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("cli_set_ea_path on a symlink gave %s. " + "Should be NT_STATUS_ACCESS_DENIED.\n", + nt_errstr(status)); + goto out; + } + + printf("EA symlink test passed\n"); + correct = true; + + out: + + if (fnum != (uint16_t)-1) { + cli_close(cli, fnum); + fnum = (uint16_t)-1; + } + + cli_setatr(cli, sname, 0, 0); + cli_posix_unlink(cli, sname); + cli_setatr(cli, fname, 0, 0); + cli_posix_unlink(cli, fname); + + if (!torture_close_connection(cli)) { + correct = false; + } + + TALLOC_FREE(frame); + return correct; +} static uint32 open_attrs_table[] = { FILE_ATTRIBUTE_NORMAL, @@ -9458,6 +9832,8 @@ static struct { {"OPEN", run_opentest, 0}, {"POSIX", run_simple_posix_open_test, 0}, {"POSIX-APPEND", run_posix_append, 0}, + {"POSIX-SYMLINK-ACL", run_acl_symlink_test, 0}, + {"POSIX-SYMLINK-EA", run_ea_symlink_test, 0}, {"CASE-INSENSITIVE-CREATE", run_case_insensitive_create, 0}, {"ASYNC-ECHO", run_async_echo, 0}, { "UID-REGRESSION-TEST", run_uid_regression_test, 0}, diff --git a/source4/dns_server/dns_query.c b/source4/dns_server/dns_query.c index 77f797bf5b2..f246958a94b 100644 --- a/source4/dns_server/dns_query.c +++ b/source4/dns_server/dns_query.c @@ -46,8 +46,7 @@ static WERROR create_response_rr(const struct dns_name_question *question, { struct dns_res_rec *ans = *answers; uint16_t ai = *ancount; - char *tmp; - uint32_t i; + enum ndr_err_code ndr_err; ZERO_STRUCT(ans[ai]); @@ -101,14 +100,12 @@ static WERROR create_response_rr(const struct dns_name_question *question, } break; case DNS_QTYPE_TXT: - tmp = talloc_asprintf(ans, "\"%s\"", rec->data.txt.str[0]); - W_ERROR_HAVE_NO_MEMORY(tmp); - for (i=1; i<rec->data.txt.count; i++) { - tmp = talloc_asprintf_append_buffer( - tmp, " \"%s\"", rec->data.txt.str[i]); - W_ERROR_HAVE_NO_MEMORY(tmp); + ndr_err = ndr_dnsp_string_list_copy(ans, + &rec->data.txt, + &ans[ai].rdata.txt_record.txt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_NOMEM; } - ans[ai].rdata.txt_record.txt = tmp; break; default: DEBUG(0, ("Got unhandled type %u query.\n", rec->wType)); diff --git a/source4/dns_server/dns_update.c b/source4/dns_server/dns_update.c index 04e7d9adff7..fb02ba074d4 100644 --- a/source4/dns_server/dns_update.c +++ b/source4/dns_server/dns_update.c @@ -299,9 +299,7 @@ static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx, const struct dns_res_rec *rrec, struct dnsp_DnssrvRpcRecord *r) { - char *tmp; - char *txt_record_txt; - char *saveptr = NULL; + enum ndr_err_code ndr_err; if (rrec->rr_type == DNS_QTYPE_ALL) { return DNS_ERR(FORMAT_ERROR); @@ -354,28 +352,11 @@ static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx, W_ERROR_HAVE_NO_MEMORY(r->data.mx.nameTarget); break; case DNS_QTYPE_TXT: - r->data.txt.count = 0; - r->data.txt.str = talloc_array(mem_ctx, const char *, - r->data.txt.count); - W_ERROR_HAVE_NO_MEMORY(r->data.txt.str); - - txt_record_txt = talloc_strdup(r->data.txt.str, - rrec->rdata.txt_record.txt); - W_ERROR_HAVE_NO_MEMORY(txt_record_txt); - - tmp = strtok_r(txt_record_txt, "\"", &saveptr); - while (tmp) { - if (strcmp(tmp, " ") == 0) { - tmp = strtok_r(NULL, "\"", &saveptr); - continue; - } - r->data.txt.str = talloc_realloc(mem_ctx, r->data.txt.str, const char *, - r->data.txt.count+1); - r->data.txt.str[r->data.txt.count] = talloc_strdup(r->data.txt.str, tmp); - W_ERROR_HAVE_NO_MEMORY(r->data.txt.str[r->data.txt.count]); - - r->data.txt.count++; - tmp = strtok_r(NULL, "\"", &saveptr); + ndr_err = ndr_dnsp_string_list_copy(mem_ctx, + &rrec->rdata.txt_record.txt, + &r->data.txt); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_NOMEM; } break; diff --git a/source4/librpc/wscript_build b/source4/librpc/wscript_build index f49c30dbe30..83464ba7c93 100755 --- a/source4/librpc/wscript_build +++ b/source4/librpc/wscript_build @@ -176,7 +176,7 @@ bld.SAMBA_PYTHON('python_echo', bld.SAMBA_PYTHON('python_dns', source='../../librpc/gen_ndr/py_dns.c', - deps='RPC_NDR_DNS pytalloc-util pyrpc_util', + deps='NDR_DNS pytalloc-util pyrpc_util', realname='samba/dcerpc/dns.so' ) @@ -328,7 +328,7 @@ bld.SAMBA_PYTHON('python_dcerpc_drsblobs', bld.SAMBA_PYTHON('python_dcerpc_dnsp', source='../../librpc/gen_ndr/py_dnsp.c', - deps='pytalloc-util pyrpc_util NDR_SECURITY RPC_NDR_DNSP', + deps='pytalloc-util pyrpc_util NDR_SECURITY NDR_DNSP', realname='samba/dcerpc/dnsp.so' ) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 08b59a17de5..a7cf37792a4 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -283,7 +283,8 @@ for f in sorted(os.listdir(os.path.join(samba4srcdir, "../pidl/tests"))): planperltestsuite("pidl.%s" % f[:-3], os.path.normpath(os.path.join(samba4srcdir, "../pidl/tests", f))) # DNS tests -planpythontestsuite("fl2003dc", "samba.tests.dns") +plantestsuite_loadlist("samba.tests.dns", "fl2003dc:local", [python, os.path.join(srcdir(), "python/samba/tests/dns.py"), '$SERVER', '$SERVER_IP', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) + for t in smbtorture4_testsuites("dns_internal."): plansmbtorture4testsuite(t, "dc:local", '//$SERVER/whavever') |