#include "ldb_wrap.h"
#include "param/param.h"
#include "libds/common/flag_mapping.h"
+#include "system/network.h"
struct samldb_ctx;
enum samldb_add_type {
return LDB_SUCCESS;
}
+/*
+ * Return zero if the number of zero bits in the address (looking from low to
+ * high) is equal to or greater than the length minus the mask. Otherwise it
+ * returns -1.
+ */
+static int check_cidr_zero_bits(uint8_t *address, unsigned int len,
+ unsigned int mask)
+{
+ /* <address> is an integer in big-endian form, <len> bits long. All
+ bits between <mask> and <len> must be zero. */
+ int i;
+ unsigned int byte_len;
+ unsigned int byte_mask;
+ unsigned int bit_mask;
+ if (len == 32) {
+ DBG_INFO("Looking at address %02x%02x%02x%02x, mask %u\n",
+ address[0], address[1], address[2], address[3],
+ mask);
+ } else if (len == 128){
+ DBG_INFO("Looking at address "
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x, mask %u\n",
+ address[0], address[1], address[2], address[3],
+ address[4], address[5], address[6], address[7],
+ address[8], address[9], address[10], address[11],
+ address[12], address[13], address[14], address[15],
+ mask);
+ }
+
+ if (mask > len){
+ DBG_INFO("mask %u is too big (> %u)\n", mask, len);
+ return -1;
+ }
+ if (mask == len){
+ /* single address subnet.
+ * In IPv4 all 255s is invalid by the bitmask != address rule
+ * in MS-ADTS. IPv6 does not suffer.
+ */
+ if (len == 32){
+ if (address[0] == 255 &&
+ address[1] == 255 &&
+ address[2] == 255 &&
+ address[3] == 255){
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ byte_len = len / 8;
+ byte_mask = mask / 8;
+
+ for (i = byte_len - 1; i > byte_mask; i--){
+ DBG_DEBUG("checking byte %d %02x\n", i, address[i]);
+ if (address[i] != 0){
+ return -1;
+ }
+ }
+ bit_mask = (1 << (8 - (mask & 7))) - 1;
+ DBG_DEBUG("checking bitmask %02x & %02x overlap %02x\n", bit_mask, address[byte_mask],
+ bit_mask & address[byte_mask]);
+ if (address[byte_mask] & bit_mask){
+ return -1;
+ }
+
+ /* According to MS-ADTS, the mask can't exactly equal the bitmask for
+ * IPv4 (but this is fine for v6). That is 255.255.80.0/17 is bad,
+ * because the bitmask implied by "/17" is 255.255.80.0.
+ *
+ * The bit_mask used in the previous check is the complement of what
+ * we want here.
+ */
+ if (len == 32 && address[byte_mask] == (uint8_t)~bit_mask){
+ bool ok = false;
+ for (i = 0; i < byte_mask; i++){
+ if (address[i] != 255){
+ ok = true;
+ break;
+ }
+ }
+ if (ok == false){
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+
+static int check_address_roundtrip(const char *address, int family,
+ const uint8_t *address_bytes,
+ char *buffer, int buffer_len)
+{
+ /*
+ * Check that the address is in the canonical RFC5952 format for IPv6,
+ * and lacks extra leading zeros for each dotted decimal for IPv4.
+ * Handily this is what inet_ntop() gives you.
+ */
+ const char *address_redux = inet_ntop(family, address_bytes,
+ buffer, buffer_len);
+ if (address_redux == NULL){
+ DBG_INFO("Address round trip %s failed unexpectedly"
+ " with errno %d\n", address, errno);
+ return -1;
+ }
+ if (strcasecmp(address, address_redux) != 0){
+ DBG_INFO("Address %s round trips to %s; fail!\n",
+ address, address_redux);
+ return -1;
+ }
+ return 0;
+}
+
+
+
+/*
+ * MS-ADTS v20150630 6.1.1.2.2.2.1 Subnet Object, refers to RFC1166 and
+ * RFC2373. It specifies something seemingly indistinguishable from an RFC4632
+ * CIDR address range without saying so explicitly. Here we follow the CIDR
+ * spec.
+ *
+ * Return 0 on success, -1 on error.
+ */
+static int verify_cidr(const char *cidr)
+{
+ char *address = NULL, *slash = NULL, *endptr = NULL;
+ bool has_colon, has_dot;
+ int res, ret;
+ unsigned long mask;
+ uint8_t *address_bytes = NULL;
+ char *address_redux = NULL;
+ unsigned int address_len;
+ TALLOC_CTX *frame = NULL;
+
+ DBG_DEBUG("CIDR is %s\n", cidr);
+ frame = talloc_stackframe();
+ address = talloc_strdup(frame, cidr);
+ if (address == NULL){
+ goto error;
+ }
+
+ /* there must be a '/' */
+ slash = strchr(address, '/');
+ if (slash == NULL){
+ goto error;
+ }
+ /* terminate the address for strchr, inet_pton */
+ *slash = '\0';
+
+ mask = strtoul(slash + 1, &endptr, 10);
+ if (mask == 0){
+ DBG_INFO("Windows does not like the zero mask, "
+ "so nor do we: %s\n", cidr);
+ goto error;
+ }
+
+ if (*endptr != '\0' || endptr == slash + 1){
+ DBG_INFO("CIDR mask is not a proper integer: %s\n", cidr);
+ goto error;
+ }
+
+ address_bytes = talloc_size(frame, sizeof(struct in6_addr));
+ if (address_bytes == NULL){
+ goto error;
+ }
+
+ address_redux = talloc_size(frame, INET6_ADDRSTRLEN);
+ if (address_redux == NULL){
+ goto error;
+ }
+
+ DBG_INFO("found address %s, mask %lu\n", address, mask);
+ has_colon = (strchr(address, ':') == NULL) ? false : true;
+ has_dot = (strchr(address, '.') == NULL) ? false : true;
+ if (has_dot && has_colon){
+ /* This seems to be an IPv4 address embedded in IPv6, which is
+ icky. We don't support it. */
+ DBG_INFO("Refusing to consider cidr '%s' with dots and colons\n",
+ cidr);
+ goto error;
+ } else if (has_colon){ /* looks like IPv6 */
+ res = inet_pton(AF_INET6, address, address_bytes);
+ if (res != 1) {
+ DBG_INFO("Address in %s fails to parse as IPv6\n", cidr);
+ goto error;
+ }
+ address_len = 128;
+ if (check_address_roundtrip(address, AF_INET6, address_bytes,
+ address_redux, INET6_ADDRSTRLEN)){
+ goto error;
+ }
+ } else if (has_dot) {
+ /* looks like IPv4 */
+ if (strcmp(address, "0.0.0.0") == 0){
+ DBG_INFO("Windows does not like the zero IPv4 address, "
+ "so nor do we.\n");
+ goto error;
+ }
+ res = inet_pton(AF_INET, address, address_bytes);
+ if (res != 1) {
+ DBG_INFO("Address in %s fails to parse as IPv4\n", cidr);
+ goto error;
+ }
+ address_len = 32;
+
+ if (check_address_roundtrip(address, AF_INET, address_bytes,
+ address_redux, INET_ADDRSTRLEN)){
+ goto error;
+ }
+ } else {
+ /* This doesn't look like an IP address at all. */
+ goto error;
+ }
+
+ ret = check_cidr_zero_bits(address_bytes, address_len, mask);
+ talloc_free(frame);
+ return ret;
+ error:
+ talloc_free(frame);
+ return -1;
+}
+
+
+static int samldb_verify_subnet(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char *cidr = NULL;
+ const struct ldb_val *rdn_value = NULL;
+
+ rdn_value = ldb_dn_get_rdn_val(ac->msg->dn);
+ cidr = ldb_dn_escape_value(ac, *rdn_value);
+ DBG_INFO("looking at cidr '%s'\n", cidr);
+ if (cidr == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: adding an empty subnet cidr seems wrong");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (verify_cidr(cidr)){
+ ldb_set_errstring(ldb,
+ "samldb: subnet value is invalid");
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ return LDB_SUCCESS;
+}
+
/* add */
static int samldb_add(struct ldb_module *module, struct ldb_request *req)
return samldb_fill_object(ac);
}
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass", "subnet") != NULL) {
+ ret = samldb_verify_subnet(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ /* We are just checking the value is valid, and there are no
+ values to fill in. */
+ }
+
talloc_free(ac);
/* nothing matched, go on */
return LDB_ERR_UNWILLING_TO_PERFORM;
}
+ /* subnet objects */
+ if (samdb_find_attribute(ldb, msg, "objectclass", "subnet") != NULL) {
+ ret = samldb_verify_subnet(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
/* systemFlags */
dn1 = ldb_dn_get_parent(ac, olddn);
self.assertRaises(subnets.SubnetNotFound,
subnets.delete_subnet, self.ldb, basedn, cidr)
+ def test_create_bad_ranges(self):
+ """These CIDR ranges all have something wrong with them, and they
+ should all fail."""
+ basedn = self.ldb.get_config_basedn()
+
+ cidrs = [
+ # IPv4
+ # insufficient zeros
+ "10.11.12.0/14",
+ "110.0.0.0/6",
+ "1.0.0.0/0",
+ "10.11.13.1/24",
+ "1.2.3.4/29",
+ "10.11.12.0/21",
+ # out of range mask
+ "110.0.0.0/33",
+ "110.0.0.0/-1",
+ "4.0.0.0/111",
+ # out of range address
+ "310.0.0.0/24",
+ "10.0.0.256/32",
+ "1.1.-20.0/24",
+ # badly formed
+ "1.0.0.0/1e",
+ "1.0.0.0/24.0",
+ "1.0.0.0/1/1",
+ "1.0.0.0",
+ "1.c.0.0/24",
+ "1.2.0.0.0/27",
+ "1.23.0/24",
+ "1.23.0.-7/24",
+ "1.-23.0.7/24",
+ "1.23.-0.7/24",
+ "1.23.0.0/0x10",
+ # IPv6 insufficient zeros -- this could be a subtle one
+ # due to the vagaries of endianness in the 16 bit groups.
+ "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/119",
+ "aaaa:bbbb::/31",
+ "a:b::/31",
+ "c000::/1",
+ "a::b00/119",
+ "1::1/127",
+ "1::2/126",
+ "1::100/119",
+ "1::8000/112",
+ # out of range mask
+ "a:b::/130",
+ "a:b::/-1",
+ "::/129",
+ # An IPv4 address can't be exactly the bitmask (MS ADTS)
+ "128.0.0.0/1",
+ "192.0.0.0/2",
+ "255.192.0.0/10",
+ "255.255.255.0/24",
+ "255.255.255.255/32",
+ "0.0.0.0/0",
+ # The address can't have leading zeros (not RFC 4632, but MS ADTS)
+ "00.1.2.0/24",
+ "003.1.2.0/24",
+ "022.1.0.0/16",
+ "00000000000000000000000003.1.2.0/24",
+ "09876::abfc/126",
+ "0aaaa:bbbb::/32",
+ "009876::abfc/126",
+ "000a:bbbb::/32",
+
+ # How about extraneous zeros later on
+ "3.01.2.0/24",
+ "3.1.2.00/24",
+ "22.001.0.0/16",
+ "3.01.02.0/24",
+ "100a:0bbb:0023::/48",
+ "100a::0023/128",
+
+ # Windows doesn't like the zero IPv4 address
+ "0.0.0.0/8",
+ # or the zero mask on IPv6
+ "::/0",
+
+ # various violations of RFC5952
+ "0:0:0:0:0:0:0:0/8",
+ "0::0/0",
+ "::0:0/48",
+ "::0:4/128",
+ "0::/8",
+ "0::4f/128",
+ "0::42:0:0:0:0/64",
+ "4f::0/48",
+
+ # badly formed -- mostly the wrong arrangement of colons
+ "a::b::0/120",
+ "a::abcdf:0/120",
+ "a::g:0/120",
+ "::0::3/48",
+ "2001:3::110::3/118",
+ "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1111:0000/128",
+ "a:::5:0/120",
+
+ # non-canonical representations (vs RFC 5952)
+ # "2001:0:c633:63::1:0/120" is correct
+ "2001:0:c633:63:0:0:1:0/120",
+ "2001::c633:63:0:0:1:0/120",
+ "2001:0:c633:63:0:0:1::/120",
+
+ # "10:0:0:42::/64" is correct
+ "10::42:0:0:0:0/64",
+ "10:0:0:42:0:0:0:0/64",
+
+ # "1::4:5:0:0:8/127" is correct
+ "1:0:0:4:5:0:0:8/127",
+ "1:0:0:4:5::8/127",
+
+ # "2001:db8:0:1:1:1:1:1/128" is correct
+ "2001:db8::1:1:1:1:1/128",
+
+ # IP4 embedded - rejected
+ "a::10.0.0.0/120",
+ "a::10.9.8.7/128",
+ "::ffff:0:0/96", #this one fails on WIN2012r2
+ # completely wrong
+ None,
+ "bob",
+ 3.1415,
+ False,
+ "10.11.16.0/24\x00hidden bytes past a zero",
+ self,
+ ]
+
+ failures = []
+ for cidr in cidrs:
+ try:
+ subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
+ except subnets.SubnetInvalid:
+ print >> sys.stderr, "%s fails properly" % (cidr,)
+ continue
+
+ # we are here because it succeeded when it shouldn't have.
+ print >> sys.stderr, "CIDR %s fails to fail" % (cidr,)
+ failures.append(cidr)
+ subnets.delete_subnet(self.ldb, basedn, cidr)
+
+ if failures:
+ print "These bad subnet names were accepted:"
+ for cidr in failures:
+ print " %s" % cidr
+ self.fail()
+
+ def test_create_good_ranges(self):
+ """All of these CIDRs are good, and the subnet creation should
+ succeed."""
+ basedn = self.ldb.get_config_basedn()
+
+ cidrs = [
+ # IPv4
+ "10.11.12.0/24",
+ "10.11.12.0/23",
+ "10.11.12.0/25",
+ "110.0.0.0/7",
+ "1.0.0.0/32",
+ "10.11.13.0/32",
+ "10.11.13.1/32",
+ "99.0.97.0/24",
+ "1.2.3.4/30",
+ "10.11.12.0/22",
+ "0.12.13.0/24",
+ # IPv6
+ "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:1100/120",
+ "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:11f0/124",
+ "aaaa:bbbb:cccc:dddd:eeee:ffff:2222:11fc/126",
+ # don't forget upper case
+ "FFFF:FFFF:FFFF:FFFF:ABCD:EfFF:FFFF:FFeF/128",
+ "9876::ab00/120",
+ "9876::abf0/124",
+ "9876::abfc/126",
+ "aaaa:bbbb::/32",
+ "aaaa:bbba::/31",
+ "aaaa:ba00::/23",
+ "aaaa:bb00::/24",
+ "aaaa:bb00::/77",
+ "::/48",
+ "a:b::/32",
+ "c000::/2",
+ "a::b00/120",
+ "1::2/127",
+ # this pattern of address suffix == mask is forbidden with
+ # IPv4 but OK for IPv6.
+ "8000::/1",
+ "c000::/2",
+ "ffff:ffff:ffc0::/42",
+ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF/128",
+ # leading zeros are forbidden, but implicit IPv6 zeros
+ # (via "::") are OK.
+ "::1000/116",
+ "::8000/113",
+ # taken to the logical conclusion, "::/0" should be OK, but no.
+ "::/48",
+
+ # Try some reserved ranges, which it might be reasonable
+ # to exclude, but which are not excluded in practice.
+ "129.0.0.0/16",
+ "129.255.0.0/16",
+ "100.64.0.0/10",
+ "127.0.0.0/8",
+ "127.0.0.0/24",
+ "169.254.0.0/16",
+ "169.254.1.0/24",
+ "192.0.0.0/24",
+ "192.0.2.0/24",
+ "198.18.0.0/15",
+ "198.51.100.0/24",
+ "203.0.113.0/24",
+ "224.0.0.0/4",
+ "130.129.0.0/16",
+ "130.255.0.0/16",
+ "192.12.0.0/24",
+ "223.255.255.0/24",
+ "240.255.255.0/24",
+ "224.0.0.0/8",
+ "::/96",
+ "100::/64",
+ "2001:10::/28",
+ "fec0::/10",
+ "ff00::/8",
+ "::1/128",
+ "2001:db8::/32",
+ "2001:10::/28",
+ "2002::/24",
+ "2002:a00::/24",
+ "2002:7f00::/24",
+ "2002:a9fe::/32",
+ "2002:ac10::/28",
+ "2002:c000::/40",
+ "2002:c000:200::/40",
+ "2002:c0a8::/32",
+ "2002:c612::/31",
+ "2002:c633:6400::/40",
+ "2002:cb00:7100::/40",
+ "2002:e000::/20",
+ "2002:f000::/20",
+ "2002:ffff:ffff::/48",
+ "2001::/40",
+ "2001:0:a00::/40",
+ "2001:0:7f00::/40",
+ "2001:0:a9fe::/48",
+ "2001:0:ac10::/44",
+ "2001:0:c000::/56",
+ "2001:0:c000:200::/56",
+ "2001:0:c0a8::/48",
+ "2001:0:c612::/47",
+ "2001:0:c633:6400::/56",
+ "2001:0:cb00:7100::/56",
+ "2001:0:e000::/36",
+ "2001:0:f000::/36",
+ "2001:0:ffff:ffff::/64",
+
+ # non-RFC-5952 versions of these are tested in create_bad_ranges
+ "2001:0:c633:63::1:0/120",
+ "10:0:0:42::/64",
+ "1::4:5:0:0:8/127",
+ "2001:db8:0:1:1:1:1:1/128",
+ ]
+ failures = []
+
+ for cidr in cidrs:
+ try:
+ subnets.create_subnet(self.ldb, basedn, cidr, self.sitename)
+ except subnets.SubnetInvalid, e:
+ print e
+ failures.append(cidr)
+ continue
+
+ ret = self.ldb.search(base=basedn, scope=SCOPE_SUBTREE,
+ expression=('(&(objectclass=subnet)(cn=%s))' %
+ cidr))
+
+ if len(ret) != 1:
+ print "%s was not created" % cidr
+ failures.append(cidr)
+ continue
+ subnets.delete_subnet(self.ldb, basedn, cidr)
+
+ if failures:
+ print "These good subnet names were not accepted:"
+ for cidr in failures:
+ print " %s" % cidr
+ self.fail()
+
+
TestProgram(module=__name__, opts=subunitopts)