1# Add/remove subnets to sites. 2# 3# Copyright (C) Catalyst.Net Ltd 2015 4# Copyright Matthieu Patou <mat@matws.net> 2011 5# 6# Catalyst.Net's contribution was written by Douglas Bagnall 7# <douglas.bagnall@catalyst.net.nz>. 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 3 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21# 22 23import ldb 24from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, LdbError 25from . sites import SiteNotFoundException 26 27 28class SubnetException(Exception): 29 """Base element for Subnet errors""" 30 pass 31 32 33class SubnetNotFound(SubnetException): 34 """The subnet requested does not exist.""" 35 pass 36 37 38class SubnetAlreadyExists(SubnetException): 39 """The subnet being added already exists.""" 40 pass 41 42 43class SubnetInvalid(SubnetException): 44 """The subnet CIDR is invalid.""" 45 pass 46 47 48class SiteNotFound(SubnetException): 49 """The site to be used for the subnet does not exist.""" 50 pass 51 52 53def create_subnet(samdb, configDn, subnet_name, site_name): 54 """Create a subnet and associate it with a site. 55 56 :param samdb: A samdb connection 57 :param configDn: The DN of the configuration partition 58 :param subnet_name: name of the subnet to create (a CIDR range) 59 :return: None 60 :raise SubnetAlreadyExists: if the subnet to be created already exists. 61 :raise SiteNotFound: if the site does not exist. 62 """ 63 ret = samdb.search(base=configDn, scope=ldb.SCOPE_SUBTREE, 64 expression='(&(objectclass=Site)(cn=%s))' % 65 ldb.binary_encode(site_name)) 66 if len(ret) != 1: 67 raise SiteNotFound('A site with the name %s does not exist' % 68 site_name) 69 dn_site = ret[0].dn 70 71 if not isinstance(subnet_name, str): 72 raise SubnetInvalid("%s is not a valid subnet (not a string)" % subnet_name) 73 74 dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites") 75 if dnsubnet.add_base(configDn) == False: 76 raise SubnetException("dnsubnet.add_base() failed") 77 if dnsubnet.add_child("CN=X") == False: 78 raise SubnetException("dnsubnet.add_child() failed") 79 dnsubnet.set_component(0, "CN", subnet_name) 80 81 try: 82 m = ldb.Message() 83 m.dn = dnsubnet 84 m["objectclass"] = ldb.MessageElement("subnet", FLAG_MOD_ADD, 85 "objectclass") 86 m["siteObject"] = ldb.MessageElement(str(dn_site), FLAG_MOD_ADD, 87 "siteObject") 88 samdb.add(m) 89 except ldb.LdbError as e: 90 (enum, estr) = e.args 91 if enum == ldb.ERR_INVALID_DN_SYNTAX: 92 raise SubnetInvalid("%s is not a valid subnet: %s" % (subnet_name, estr)) 93 elif enum == ldb.ERR_ENTRY_ALREADY_EXISTS: 94 # Subnet collisions are checked by exact match only, not 95 # overlapping range. This won't stop you creating 96 # 10.1.1.0/24 when there is already 10.1.0.0/16, or 97 # prevent you from having numerous IPv6 subnets that refer 98 # to the same range (e.g 5::0/16, 5::/16, 5:0:0::/16). 99 raise SubnetAlreadyExists('A subnet with the CIDR %s already exists' 100 % subnet_name) 101 else: 102 raise 103 104 105def delete_subnet(samdb, configDn, subnet_name): 106 """Delete a subnet. 107 108 :param samdb: A samdb connection 109 :param configDn: The DN of the configuration partition 110 :param subnet_name: Name of the subnet to delete 111 :return: None 112 :raise SubnetNotFound: if the subnet to be deleted does not exist. 113 """ 114 dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites") 115 if dnsubnet.add_base(configDn) == False: 116 raise SubnetException("dnsubnet.add_base() failed") 117 if dnsubnet.add_child("CN=X") == False: 118 raise SubnetException("dnsubnet.add_child() failed") 119 dnsubnet.set_component(0, "CN", subnet_name) 120 121 try: 122 ret = samdb.search(base=dnsubnet, scope=ldb.SCOPE_BASE, 123 expression="objectClass=subnet") 124 if len(ret) != 1: 125 raise SubnetNotFound('Subnet %s does not exist' % subnet_name) 126 except LdbError as e1: 127 (enum, estr) = e1.args 128 if enum == ldb.ERR_NO_SUCH_OBJECT: 129 raise SubnetNotFound('Subnet %s does not exist' % subnet_name) 130 131 samdb.delete(dnsubnet) 132 133 134def rename_subnet(samdb, configDn, subnet_name, new_name): 135 """Rename a subnet. 136 137 :param samdb: A samdb connection 138 :param configDn: The DN of the configuration partition 139 :param subnet_name: Name of the subnet to rename 140 :param new_name: New name for the subnet 141 :return: None 142 :raise SubnetNotFound: if the subnet to be renamed does not exist. 143 :raise SubnetExists: if the subnet to be created already exists. 144 """ 145 dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites") 146 if dnsubnet.add_base(configDn) == False: 147 raise SubnetException("dnsubnet.add_base() failed") 148 if dnsubnet.add_child("CN=X") == False: 149 raise SubnetException("dnsubnet.add_child() failed") 150 dnsubnet.set_component(0, "CN", subnet_name) 151 152 newdnsubnet = ldb.Dn(samdb, str(dnsubnet)) 153 newdnsubnet.set_component(0, "CN", new_name) 154 try: 155 samdb.rename(dnsubnet, newdnsubnet) 156 except LdbError as e2: 157 (enum, estr) = e2.args 158 if enum == ldb.ERR_NO_SUCH_OBJECT: 159 raise SubnetNotFound('Subnet %s does not exist' % dnsubnet) 160 elif enum == ldb.ERR_ENTRY_ALREADY_EXISTS: 161 raise SubnetAlreadyExists('A subnet with the CIDR %s already exists' 162 % new_name) 163 elif enum == ldb.ERR_INVALID_DN_SYNTAX: 164 raise SubnetInvalid("%s is not a valid subnet: %s" % (new_name, 165 estr)) 166 else: 167 raise 168 169 170def set_subnet_site(samdb, configDn, subnet_name, site_name): 171 """Assign a subnet to a site. 172 173 This dissociates the subnet from its previous site. 174 175 :param samdb: A samdb connection 176 :param configDn: The DN of the configuration partition 177 :param subnet_name: Name of the subnet 178 :param site_name: Name of the site 179 :return: None 180 :raise SubnetNotFound: if the subnet does not exist. 181 :raise SiteNotFound: if the site does not exist. 182 """ 183 184 dnsubnet = ldb.Dn(samdb, "CN=Subnets,CN=Sites") 185 if dnsubnet.add_base(configDn) == False: 186 raise SubnetException("dnsubnet.add_base() failed") 187 if dnsubnet.add_child("CN=X") == False: 188 raise SubnetException("dnsubnet.add_child() failed") 189 dnsubnet.set_component(0, "CN", subnet_name) 190 191 try: 192 ret = samdb.search(base=dnsubnet, scope=ldb.SCOPE_BASE, 193 expression="objectClass=subnet") 194 if len(ret) != 1: 195 raise SubnetNotFound('Subnet %s does not exist' % subnet_name) 196 except LdbError as e3: 197 (enum, estr) = e3.args 198 if enum == ldb.ERR_NO_SUCH_OBJECT: 199 raise SubnetNotFound('Subnet %s does not exist' % subnet_name) 200 201 dnsite = ldb.Dn(samdb, "CN=Sites") 202 if dnsite.add_base(configDn) == False: 203 raise SubnetException("dnsites.add_base() failed") 204 if dnsite.add_child("CN=X") == False: 205 raise SubnetException("dnsites.add_child() failed") 206 dnsite.set_component(0, "CN", site_name) 207 208 dnservers = ldb.Dn(samdb, "CN=Servers") 209 dnservers.add_base(dnsite) 210 211 try: 212 ret = samdb.search(base=dnsite, scope=ldb.SCOPE_BASE, 213 expression="objectClass=site") 214 if len(ret) != 1: 215 raise SiteNotFoundException('Site %s does not exist' % site_name) 216 except LdbError as e4: 217 (enum, estr) = e4.args 218 if enum == ldb.ERR_NO_SUCH_OBJECT: 219 raise SiteNotFoundException('Site %s does not exist' % site_name) 220 221 siteDn = str(ret[0].dn) 222 223 m = ldb.Message() 224 m.dn = dnsubnet 225 m["siteObject"] = ldb.MessageElement(siteDn, FLAG_MOD_REPLACE, 226 "siteObject") 227 samdb.modify(m) 228