mdb_util: Better error message if lmdb-utils not installed
[amitay/samba.git] / python / samba / subnets.py
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
23 import ldb
24 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, LdbError
25 from . sites import SiteNotFoundException
26
27
28 class SubnetException(Exception):
29     """Base element for Subnet errors"""
30     pass
31
32
33 class SubnetNotFound(SubnetException):
34     """The subnet requested does not exist."""
35     pass
36
37
38 class SubnetAlreadyExists(SubnetException):
39     """The subnet being added already exists."""
40     pass
41
42
43 class SubnetInvalid(SubnetException):
44     """The subnet CIDR is invalid."""
45     pass
46
47
48 class SiteNotFound(SubnetException):
49     """The site to be used for the subnet does not exist."""
50     pass
51
52
53 def 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
105 def 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
134 def 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
170 def 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)