We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cobbler rejects InfiniBand MAC addresses
### Describe the bug

InfiniBand uses 128-bit MAC addresses like 80:00:02:08:fe:80:00:00:00:00:00:00:f4:52:14:03:00:96:79:31

### Steps to reproduce

1. Try to configure an infiniband network interface with MAC address

### Expected behavior

No error.

### Cobbler version

<!--- Paste output from `cobbler version` -->
````paste below
3.2.2
````

### Operating system

EL9

### Cobbler log

<!--- Paste (partial) output from `/var/log/cobbler/cobbler.log` -->
````paste below
2024-03-12T14:12:29 - INFO | Exception occurred: <class 'cobbler.cexceptions.CX'>
2024-03-12T14:12:29 - INFO | Exception value: 'Invalid mac address format (80:00:02:08:fe:80:00:00:00:00:00:00:f4:52:14:03:00:96:79:31)'
2024-03-12T14:12:29 - INFO | Exception Info:
  File "/usr/lib/python3.9/site-packages/cobbler/remote.py", line 3453, in _dispatch
    return method_handle(*params)

  File "/usr/lib/python3.9/site-packages/cobbler/remote.py", line 1643, in modify_system
    return self.modify_item("system", object_id, attribute, arg, token)

  File "/usr/lib/python3.9/site-packages/cobbler/remote.py", line 1606, in modify_item
    method(arg)

  File "/usr/lib/python3.9/site-packages/cobbler/items/system.py", line 753, in modify_interface
    self.set_mac_address(value, interface)

  File "/usr/lib/python3.9/site-packages/cobbler/items/system.py", line 384, in set_mac_address
    address = validate.mac_address(address)

  File "/usr/lib/python3.9/site-packages/cobbler/validate.py", line 111, in mac_address
    raise CX("Invalid mac address format (%s)" % mac)
````


### Additional information
There is a request for support in netaddr, but it hasn't gone anywhere: https://github.com/netaddr/netaddr/issues/88

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cobbler/validate.py`
Content:
```
1 """
2 Copyright 2014-2015. Jorgen Maas <jorgen.maas@gmail.com>
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301  USA
18 """
19 from typing import Union
20 from uuid import UUID
21 
22 import netaddr
23 import re
24 import shlex
25 
26 from cobbler.cexceptions import CX
27 
28 RE_OBJECT_NAME = re.compile(r'[a-zA-Z0-9_\-.:]*$')
29 RE_HOSTNAME = re.compile(r'^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$')
30 RE_SCRIPT_NAME = re.compile(r"[a-zA-Z0-9_\-.]+")
31 
32 REPO_BREEDS = ["rsync", "rhn", "yum", "apt", "wget"]
33 
34 VIRT_TYPES = ["<<inherit>>", "xenpv", "xenfv", "qemu", "kvm", "vmware", "openvz"]
35 VIRT_DISK_DRIVERS = ["<<inherit>>", "raw", "qcow2", "qed", "vdi", "vmdk"]
36 
37 # blacklist invalid values to the repo statement in autoinsts
38 AUTOINSTALL_REPO_BLACKLIST = ['enabled', 'gpgcheck', 'gpgkey']
39 
40 
41 def object_name(name: str, parent: str) -> str:
42     """
43     Validate the object name.
44 
45     :param name: object name
46     :param parent: Parent object name
47     :returns: name or CX
48     """
49     if not isinstance(name, str) or not isinstance(parent, str):
50         raise CX("Invalid input, name and parent must be strings")
51     else:
52         name = name.strip()
53         parent = parent.strip()
54 
55     if name != "" and parent != "" and name == parent:
56         raise CX("Self parentage is not allowed")
57 
58     if not RE_OBJECT_NAME.match(name):
59         raise CX("Invalid characters in name: '%s'" % name)
60 
61     return name
62 
63 
64 def hostname(dnsname: str) -> str:
65     """
66     Validate the DNS name.
67 
68     :param dnsname: Hostname or FQDN
69     :returns: dnsname
70     :raises CX: If the Hostname/FQDN is not a string or in an invalid format.
71     """
72     if not isinstance(dnsname, str):
73         raise CX("Invalid input, dnsname must be a string")
74     else:
75         dnsname = dnsname.strip()
76 
77     if dnsname == "":
78         # hostname is not required
79         return dnsname
80 
81     if not RE_HOSTNAME.match(dnsname):
82         raise CX("Invalid hostname format (%s)" % dnsname)
83 
84     return dnsname
85 
86 
87 def mac_address(mac: str, for_item=True) -> str:
88     """
89     Validate as an Eternet MAC address.
90 
91     :param mac: MAC address
92     :param for_item: If the check should be performed for an item or not.
93     :returns: str mac or CX
94     """
95     if not isinstance(mac, str):
96         raise CX("Invalid input, mac must be a string")
97     else:
98         mac = mac.lower().strip()
99 
100     if for_item is True:
101         # this value has special meaning for items
102         if mac == "random":
103             return mac
104 
105         # copying system collection will set mac to ""
106         # netaddr will fail to validate this mac and throw an exception
107         if mac == "":
108             return mac
109 
110     if not netaddr.valid_mac(mac):
111         raise CX("Invalid mac address format (%s)" % mac)
112 
113     return mac
114 
115 
116 def ipv4_address(addr: str) -> str:
117     """
118     Validate an IPv4 address.
119 
120     :param addr: IPv4 address
121     :returns: str addr or CX
122     """
123     if not isinstance(addr, str):
124         raise CX("Invalid input, addr must be a string")
125     else:
126         addr = addr.strip()
127 
128     if addr == "":
129         return addr
130 
131     if not netaddr.valid_ipv4(addr):
132         raise CX("Invalid IPv4 address format (%s)" % addr)
133 
134     if netaddr.IPAddress(addr).is_netmask():
135         raise CX("Invalid IPv4 host address (%s)" % addr)
136 
137     return addr
138 
139 
140 def ipv4_netmask(addr: str) -> str:
141     """
142     Validate an IPv4 netmask.
143 
144     :param addr: IPv4 netmask
145     :returns: str addr or CX
146     """
147     if not isinstance(addr, str):
148         raise CX("Invalid input, addr must be a string")
149     else:
150         addr = addr.strip()
151 
152     if addr == "":
153         return addr
154 
155     if not netaddr.valid_ipv4(addr):
156         raise CX("Invalid IPv4 address format (%s)" % addr)
157 
158     if not netaddr.IPAddress(addr).is_netmask():
159         raise CX("Invalid IPv4 netmask (%s)" % addr)
160 
161     return addr
162 
163 
164 def ipv6_address(addr: str) -> str:
165     """
166     Validate an IPv6 address.
167 
168     :param addr: IPv6 address
169     :returns: The IPv6 address.
170     """
171     if not isinstance(addr, str):
172         raise CX("Invalid input, addr must be a string")
173     else:
174         addr = addr.strip()
175 
176     if addr == "":
177         return addr
178 
179     if not netaddr.valid_ipv6(addr):
180         raise CX("Invalid IPv6 address format (%s)" % addr)
181 
182     return addr
183 
184 
185 def name_servers(nameservers: Union[str, list], for_item: bool = True) -> Union[str, list]:
186     """
187     Validate nameservers IP addresses, works for IPv4 and IPv6
188 
189     :param nameservers: string or list of nameserver addresses
190     :param for_item: enable/disable special handling for Item objects
191     :return: The list of valid nameservers.
192     """
193     if isinstance(nameservers, str):
194         nameservers = nameservers.strip()
195         if for_item is True:
196             # special handling for Items
197             if nameservers in ["<<inherit>>", ""]:
198                 return nameservers
199 
200         # convert string to a list; do the real validation
201         # in the isinstance(list) code block below
202         nameservers = shlex.split(nameservers)
203 
204     if isinstance(nameservers, list):
205         for ns in nameservers:
206             ip_version = netaddr.IPAddress(ns).version
207             if ip_version == 4:
208                 ipv4_address(ns)
209             elif ip_version == 6:
210                 ipv6_address(ns)
211             else:
212                 raise CX("Invalid IP address format")
213     else:
214         raise CX("Invalid input type %s, expected str or list" % type(nameservers))
215 
216     return nameservers
217 
218 
219 def name_servers_search(search: Union[str, list], for_item: bool = True) -> Union[str, list]:
220     """
221     Validate nameservers search domains.
222 
223     :param search: One or more search domains to validate.
224     :param for_item: (enable/disable special handling for Item objects)
225     :return: The list of valid nameservers.
226     """
227     if isinstance(search, str):
228         search = search.strip()
229         if for_item is True:
230             # special handling for Items
231             if search in ["<<inherit>>", ""]:
232                 return search
233 
234         # convert string to a list; do the real validation
235         # in the isinstance(list) code block below
236         search = shlex.split(search)
237 
238     if isinstance(search, list):
239         for sl in search:
240             hostname(sl)
241     else:
242         raise CX("Invalid input type %s, expected str or list" % type(search))
243 
244     return search
245 
246 
247 def validate_autoinstall_script_name(name: str) -> bool:
248     """
249     This validates if the name given for the script is valid in the context of the API call made. It will be handed to
250     tftpgen.py#generate_script in the end.
251 
252     :param name: The name of the script. Will end up being a filename. May have an extension but should never be a path.
253     :return: If this is a valid script name or not.
254     """
255     if not isinstance(name, str):
256         return False
257     if re.fullmatch(RE_SCRIPT_NAME, name):
258         return True
259     return False
260 
261 
262 def validate_uuid(possible_uuid: str) -> bool:
263     """
264     Validate if the handed string is a valid UUIDv4.
265 
266     :param possible_uuid: The str with the UUID.
267     :return: True in case it is one, False otherwise.
268     """
269     if not isinstance(possible_uuid, str):
270         return False
271     # Taken from: https://stackoverflow.com/a/33245493/4730773
272     try:
273         uuid_obj = UUID(possible_uuid, version=4)
274     except ValueError:
275         return False
276     return str(uuid_obj) == possible_uuid
277 
278 
279 def validate_obj_type(object_type: str) -> bool:
280     """
281 
282     :param object_type:
283     :return:
284     """
285     if not isinstance(object_type, str):
286         return False
287     return object_type in ["distro", "profile", "system", "repo", "image", "mgmtclass", "package", "file", "menu"]
288 
289 
290 def validate_obj_name(object_name: str) -> bool:
291     """
292 
293     :param object_name:
294     :return:
295     """
296     if not isinstance(object_name, str):
297         return False
298     return bool(re.fullmatch(RE_OBJECT_NAME, object_name))
299 
300 
301 def validate_obj_id(object_id: str) -> bool:
302     """
303 
304     :param object_id:
305     :return: True in case it is one, False otherwise.
306     """
307     if not isinstance(object_id, str):
308         return False
309     if object_id.startswith("___NEW___"):
310         object_id = object_id[9:]
311     (otype, oname) = object_id.split("::", 1)
312     return validate_obj_type(otype) and validate_obj_name(oname)
313
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cobbler/validate.py b/cobbler/validate.py
--- a/cobbler/validate.py
+++ b/cobbler/validate.py
@@ -28,6 +28,9 @@
 RE_OBJECT_NAME = re.compile(r'[a-zA-Z0-9_\-.:]*$')
 RE_HOSTNAME = re.compile(r'^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$')
 RE_SCRIPT_NAME = re.compile(r"[a-zA-Z0-9_\-.]+")
+RE_INFINIBAND_MAC = re.compile(
+    "^" + ":".join(["([0-9A-F]{1,2})"] * 20) + "$", re.IGNORECASE
+)
 
 REPO_BREEDS = ["rsync", "rhn", "yum", "apt", "wget"]
 
@@ -107,7 +110,7 @@
         if mac == "":
             return mac
 
-    if not netaddr.valid_mac(mac):
+    if not netaddr.valid_mac(mac) and RE_INFINIBAND_MAC.match(mac) is None:
         raise CX("Invalid mac address format (%s)" % mac)
 
     return mac

{"golden_diff": "diff --git a/cobbler/validate.py b/cobbler/validate.py\n--- a/cobbler/validate.py\n+++ b/cobbler/validate.py\n@@ -28,6 +28,9 @@\n RE_OBJECT_NAME = re.compile(r'[a-zA-Z0-9_\\-.:]*$')\n RE_HOSTNAME = re.compile(r'^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$')\n RE_SCRIPT_NAME = re.compile(r\"[a-zA-Z0-9_\\-.]+\")\n+RE_INFINIBAND_MAC = re.compile(\n+    \"^\" + \":\".join([\"([0-9A-F]{1,2})\"] * 20) + \"$\", re.IGNORECASE\n+)\n \n REPO_BREEDS = [\"rsync\", \"rhn\", \"yum\", \"apt\", \"wget\"]\n \n@@ -107,7 +110,7 @@\n         if mac == \"\":\n             return mac\n \n-    if not netaddr.valid_mac(mac):\n+    if not netaddr.valid_mac(mac) and RE_INFINIBAND_MAC.match(mac) is None:\n         raise CX(\"Invalid mac address format (%s)\" % mac)\n \n     return mac\n", "issue": "Cobbler rejects InfiniBand MAC addresses\n### Describe the bug\r\n\r\nInfiniBand uses 128-bit MAC addresses like 80:00:02:08:fe:80:00:00:00:00:00:00:f4:52:14:03:00:96:79:31\r\n\r\n### Steps to reproduce\r\n\r\n1. Try to configure an infiniband network interface with MAC address\r\n\r\n### Expected behavior\r\n\r\nNo error.\r\n\r\n### Cobbler version\r\n\r\n<!--- Paste output from `cobbler version` -->\r\n````paste below\r\n3.2.2\r\n````\r\n\r\n### Operating system\r\n\r\nEL9\r\n\r\n### Cobbler log\r\n\r\n<!--- Paste (partial) output from `/var/log/cobbler/cobbler.log` -->\r\n````paste below\r\n2024-03-12T14:12:29 - INFO | Exception occurred: <class 'cobbler.cexceptions.CX'>\r\n2024-03-12T14:12:29 - INFO | Exception value: 'Invalid mac address format (80:00:02:08:fe:80:00:00:00:00:00:00:f4:52:14:03:00:96:79:31)'\r\n2024-03-12T14:12:29 - INFO | Exception Info:\r\n  File \"/usr/lib/python3.9/site-packages/cobbler/remote.py\", line 3453, in _dispatch\r\n    return method_handle(*params)\r\n\r\n  File \"/usr/lib/python3.9/site-packages/cobbler/remote.py\", line 1643, in modify_system\r\n    return self.modify_item(\"system\", object_id, attribute, arg, token)\r\n\r\n  File \"/usr/lib/python3.9/site-packages/cobbler/remote.py\", line 1606, in modify_item\r\n    method(arg)\r\n\r\n  File \"/usr/lib/python3.9/site-packages/cobbler/items/system.py\", line 753, in modify_interface\r\n    self.set_mac_address(value, interface)\r\n\r\n  File \"/usr/lib/python3.9/site-packages/cobbler/items/system.py\", line 384, in set_mac_address\r\n    address = validate.mac_address(address)\r\n\r\n  File \"/usr/lib/python3.9/site-packages/cobbler/validate.py\", line 111, in mac_address\r\n    raise CX(\"Invalid mac address format (%s)\" % mac)\r\n````\r\n\r\n\r\n### Additional information\r\nThere is a request for support in netaddr, but it hasn't gone anywhere: https://github.com/netaddr/netaddr/issues/88\n", "before_files": [{"content": "\"\"\"\nCopyright 2014-2015. Jorgen Maas <jorgen.maas@gmail.com>\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n02110-1301  USA\n\"\"\"\nfrom typing import Union\nfrom uuid import UUID\n\nimport netaddr\nimport re\nimport shlex\n\nfrom cobbler.cexceptions import CX\n\nRE_OBJECT_NAME = re.compile(r'[a-zA-Z0-9_\\-.:]*$')\nRE_HOSTNAME = re.compile(r'^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$')\nRE_SCRIPT_NAME = re.compile(r\"[a-zA-Z0-9_\\-.]+\")\n\nREPO_BREEDS = [\"rsync\", \"rhn\", \"yum\", \"apt\", \"wget\"]\n\nVIRT_TYPES = [\"<<inherit>>\", \"xenpv\", \"xenfv\", \"qemu\", \"kvm\", \"vmware\", \"openvz\"]\nVIRT_DISK_DRIVERS = [\"<<inherit>>\", \"raw\", \"qcow2\", \"qed\", \"vdi\", \"vmdk\"]\n\n# blacklist invalid values to the repo statement in autoinsts\nAUTOINSTALL_REPO_BLACKLIST = ['enabled', 'gpgcheck', 'gpgkey']\n\n\ndef object_name(name: str, parent: str) -> str:\n    \"\"\"\n    Validate the object name.\n\n    :param name: object name\n    :param parent: Parent object name\n    :returns: name or CX\n    \"\"\"\n    if not isinstance(name, str) or not isinstance(parent, str):\n        raise CX(\"Invalid input, name and parent must be strings\")\n    else:\n        name = name.strip()\n        parent = parent.strip()\n\n    if name != \"\" and parent != \"\" and name == parent:\n        raise CX(\"Self parentage is not allowed\")\n\n    if not RE_OBJECT_NAME.match(name):\n        raise CX(\"Invalid characters in name: '%s'\" % name)\n\n    return name\n\n\ndef hostname(dnsname: str) -> str:\n    \"\"\"\n    Validate the DNS name.\n\n    :param dnsname: Hostname or FQDN\n    :returns: dnsname\n    :raises CX: If the Hostname/FQDN is not a string or in an invalid format.\n    \"\"\"\n    if not isinstance(dnsname, str):\n        raise CX(\"Invalid input, dnsname must be a string\")\n    else:\n        dnsname = dnsname.strip()\n\n    if dnsname == \"\":\n        # hostname is not required\n        return dnsname\n\n    if not RE_HOSTNAME.match(dnsname):\n        raise CX(\"Invalid hostname format (%s)\" % dnsname)\n\n    return dnsname\n\n\ndef mac_address(mac: str, for_item=True) -> str:\n    \"\"\"\n    Validate as an Eternet MAC address.\n\n    :param mac: MAC address\n    :param for_item: If the check should be performed for an item or not.\n    :returns: str mac or CX\n    \"\"\"\n    if not isinstance(mac, str):\n        raise CX(\"Invalid input, mac must be a string\")\n    else:\n        mac = mac.lower().strip()\n\n    if for_item is True:\n        # this value has special meaning for items\n        if mac == \"random\":\n            return mac\n\n        # copying system collection will set mac to \"\"\n        # netaddr will fail to validate this mac and throw an exception\n        if mac == \"\":\n            return mac\n\n    if not netaddr.valid_mac(mac):\n        raise CX(\"Invalid mac address format (%s)\" % mac)\n\n    return mac\n\n\ndef ipv4_address(addr: str) -> str:\n    \"\"\"\n    Validate an IPv4 address.\n\n    :param addr: IPv4 address\n    :returns: str addr or CX\n    \"\"\"\n    if not isinstance(addr, str):\n        raise CX(\"Invalid input, addr must be a string\")\n    else:\n        addr = addr.strip()\n\n    if addr == \"\":\n        return addr\n\n    if not netaddr.valid_ipv4(addr):\n        raise CX(\"Invalid IPv4 address format (%s)\" % addr)\n\n    if netaddr.IPAddress(addr).is_netmask():\n        raise CX(\"Invalid IPv4 host address (%s)\" % addr)\n\n    return addr\n\n\ndef ipv4_netmask(addr: str) -> str:\n    \"\"\"\n    Validate an IPv4 netmask.\n\n    :param addr: IPv4 netmask\n    :returns: str addr or CX\n    \"\"\"\n    if not isinstance(addr, str):\n        raise CX(\"Invalid input, addr must be a string\")\n    else:\n        addr = addr.strip()\n\n    if addr == \"\":\n        return addr\n\n    if not netaddr.valid_ipv4(addr):\n        raise CX(\"Invalid IPv4 address format (%s)\" % addr)\n\n    if not netaddr.IPAddress(addr).is_netmask():\n        raise CX(\"Invalid IPv4 netmask (%s)\" % addr)\n\n    return addr\n\n\ndef ipv6_address(addr: str) -> str:\n    \"\"\"\n    Validate an IPv6 address.\n\n    :param addr: IPv6 address\n    :returns: The IPv6 address.\n    \"\"\"\n    if not isinstance(addr, str):\n        raise CX(\"Invalid input, addr must be a string\")\n    else:\n        addr = addr.strip()\n\n    if addr == \"\":\n        return addr\n\n    if not netaddr.valid_ipv6(addr):\n        raise CX(\"Invalid IPv6 address format (%s)\" % addr)\n\n    return addr\n\n\ndef name_servers(nameservers: Union[str, list], for_item: bool = True) -> Union[str, list]:\n    \"\"\"\n    Validate nameservers IP addresses, works for IPv4 and IPv6\n\n    :param nameservers: string or list of nameserver addresses\n    :param for_item: enable/disable special handling for Item objects\n    :return: The list of valid nameservers.\n    \"\"\"\n    if isinstance(nameservers, str):\n        nameservers = nameservers.strip()\n        if for_item is True:\n            # special handling for Items\n            if nameservers in [\"<<inherit>>\", \"\"]:\n                return nameservers\n\n        # convert string to a list; do the real validation\n        # in the isinstance(list) code block below\n        nameservers = shlex.split(nameservers)\n\n    if isinstance(nameservers, list):\n        for ns in nameservers:\n            ip_version = netaddr.IPAddress(ns).version\n            if ip_version == 4:\n                ipv4_address(ns)\n            elif ip_version == 6:\n                ipv6_address(ns)\n            else:\n                raise CX(\"Invalid IP address format\")\n    else:\n        raise CX(\"Invalid input type %s, expected str or list\" % type(nameservers))\n\n    return nameservers\n\n\ndef name_servers_search(search: Union[str, list], for_item: bool = True) -> Union[str, list]:\n    \"\"\"\n    Validate nameservers search domains.\n\n    :param search: One or more search domains to validate.\n    :param for_item: (enable/disable special handling for Item objects)\n    :return: The list of valid nameservers.\n    \"\"\"\n    if isinstance(search, str):\n        search = search.strip()\n        if for_item is True:\n            # special handling for Items\n            if search in [\"<<inherit>>\", \"\"]:\n                return search\n\n        # convert string to a list; do the real validation\n        # in the isinstance(list) code block below\n        search = shlex.split(search)\n\n    if isinstance(search, list):\n        for sl in search:\n            hostname(sl)\n    else:\n        raise CX(\"Invalid input type %s, expected str or list\" % type(search))\n\n    return search\n\n\ndef validate_autoinstall_script_name(name: str) -> bool:\n    \"\"\"\n    This validates if the name given for the script is valid in the context of the API call made. It will be handed to\n    tftpgen.py#generate_script in the end.\n\n    :param name: The name of the script. Will end up being a filename. May have an extension but should never be a path.\n    :return: If this is a valid script name or not.\n    \"\"\"\n    if not isinstance(name, str):\n        return False\n    if re.fullmatch(RE_SCRIPT_NAME, name):\n        return True\n    return False\n\n\ndef validate_uuid(possible_uuid: str) -> bool:\n    \"\"\"\n    Validate if the handed string is a valid UUIDv4.\n\n    :param possible_uuid: The str with the UUID.\n    :return: True in case it is one, False otherwise.\n    \"\"\"\n    if not isinstance(possible_uuid, str):\n        return False\n    # Taken from: https://stackoverflow.com/a/33245493/4730773\n    try:\n        uuid_obj = UUID(possible_uuid, version=4)\n    except ValueError:\n        return False\n    return str(uuid_obj) == possible_uuid\n\n\ndef validate_obj_type(object_type: str) -> bool:\n    \"\"\"\n\n    :param object_type:\n    :return:\n    \"\"\"\n    if not isinstance(object_type, str):\n        return False\n    return object_type in [\"distro\", \"profile\", \"system\", \"repo\", \"image\", \"mgmtclass\", \"package\", \"file\", \"menu\"]\n\n\ndef validate_obj_name(object_name: str) -> bool:\n    \"\"\"\n\n    :param object_name:\n    :return:\n    \"\"\"\n    if not isinstance(object_name, str):\n        return False\n    return bool(re.fullmatch(RE_OBJECT_NAME, object_name))\n\n\ndef validate_obj_id(object_id: str) -> bool:\n    \"\"\"\n\n    :param object_id:\n    :return: True in case it is one, False otherwise.\n    \"\"\"\n    if not isinstance(object_id, str):\n        return False\n    if object_id.startswith(\"___NEW___\"):\n        object_id = object_id[9:]\n    (otype, oname) = object_id.split(\"::\", 1)\n    return validate_obj_type(otype) and validate_obj_name(oname)\n", "path": "cobbler/validate.py"}], "after_files": [{"content": "\"\"\"\nCopyright 2014-2015. Jorgen Maas <jorgen.maas@gmail.com>\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n02110-1301  USA\n\"\"\"\nfrom typing import Union\nfrom uuid import UUID\n\nimport netaddr\nimport re\nimport shlex\n\nfrom cobbler.cexceptions import CX\n\nRE_OBJECT_NAME = re.compile(r'[a-zA-Z0-9_\\-.:]*$')\nRE_HOSTNAME = re.compile(r'^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$')\nRE_SCRIPT_NAME = re.compile(r\"[a-zA-Z0-9_\\-.]+\")\nRE_INFINIBAND_MAC = re.compile(\n    \"^\" + \":\".join([\"([0-9A-F]{1,2})\"] * 20) + \"$\", re.IGNORECASE\n)\n\nREPO_BREEDS = [\"rsync\", \"rhn\", \"yum\", \"apt\", \"wget\"]\n\nVIRT_TYPES = [\"<<inherit>>\", \"xenpv\", \"xenfv\", \"qemu\", \"kvm\", \"vmware\", \"openvz\"]\nVIRT_DISK_DRIVERS = [\"<<inherit>>\", \"raw\", \"qcow2\", \"qed\", \"vdi\", \"vmdk\"]\n\n# blacklist invalid values to the repo statement in autoinsts\nAUTOINSTALL_REPO_BLACKLIST = ['enabled', 'gpgcheck', 'gpgkey']\n\n\ndef object_name(name: str, parent: str) -> str:\n    \"\"\"\n    Validate the object name.\n\n    :param name: object name\n    :param parent: Parent object name\n    :returns: name or CX\n    \"\"\"\n    if not isinstance(name, str) or not isinstance(parent, str):\n        raise CX(\"Invalid input, name and parent must be strings\")\n    else:\n        name = name.strip()\n        parent = parent.strip()\n\n    if name != \"\" and parent != \"\" and name == parent:\n        raise CX(\"Self parentage is not allowed\")\n\n    if not RE_OBJECT_NAME.match(name):\n        raise CX(\"Invalid characters in name: '%s'\" % name)\n\n    return name\n\n\ndef hostname(dnsname: str) -> str:\n    \"\"\"\n    Validate the DNS name.\n\n    :param dnsname: Hostname or FQDN\n    :returns: dnsname\n    :raises CX: If the Hostname/FQDN is not a string or in an invalid format.\n    \"\"\"\n    if not isinstance(dnsname, str):\n        raise CX(\"Invalid input, dnsname must be a string\")\n    else:\n        dnsname = dnsname.strip()\n\n    if dnsname == \"\":\n        # hostname is not required\n        return dnsname\n\n    if not RE_HOSTNAME.match(dnsname):\n        raise CX(\"Invalid hostname format (%s)\" % dnsname)\n\n    return dnsname\n\n\ndef mac_address(mac: str, for_item=True) -> str:\n    \"\"\"\n    Validate as an Eternet MAC address.\n\n    :param mac: MAC address\n    :param for_item: If the check should be performed for an item or not.\n    :returns: str mac or CX\n    \"\"\"\n    if not isinstance(mac, str):\n        raise CX(\"Invalid input, mac must be a string\")\n    else:\n        mac = mac.lower().strip()\n\n    if for_item is True:\n        # this value has special meaning for items\n        if mac == \"random\":\n            return mac\n\n        # copying system collection will set mac to \"\"\n        # netaddr will fail to validate this mac and throw an exception\n        if mac == \"\":\n            return mac\n\n    if not netaddr.valid_mac(mac) and RE_INFINIBAND_MAC.match(mac) is None:\n        raise CX(\"Invalid mac address format (%s)\" % mac)\n\n    return mac\n\n\ndef ipv4_address(addr: str) -> str:\n    \"\"\"\n    Validate an IPv4 address.\n\n    :param addr: IPv4 address\n    :returns: str addr or CX\n    \"\"\"\n    if not isinstance(addr, str):\n        raise CX(\"Invalid input, addr must be a string\")\n    else:\n        addr = addr.strip()\n\n    if addr == \"\":\n        return addr\n\n    if not netaddr.valid_ipv4(addr):\n        raise CX(\"Invalid IPv4 address format (%s)\" % addr)\n\n    if netaddr.IPAddress(addr).is_netmask():\n        raise CX(\"Invalid IPv4 host address (%s)\" % addr)\n\n    return addr\n\n\ndef ipv4_netmask(addr: str) -> str:\n    \"\"\"\n    Validate an IPv4 netmask.\n\n    :param addr: IPv4 netmask\n    :returns: str addr or CX\n    \"\"\"\n    if not isinstance(addr, str):\n        raise CX(\"Invalid input, addr must be a string\")\n    else:\n        addr = addr.strip()\n\n    if addr == \"\":\n        return addr\n\n    if not netaddr.valid_ipv4(addr):\n        raise CX(\"Invalid IPv4 address format (%s)\" % addr)\n\n    if not netaddr.IPAddress(addr).is_netmask():\n        raise CX(\"Invalid IPv4 netmask (%s)\" % addr)\n\n    return addr\n\n\ndef ipv6_address(addr: str) -> str:\n    \"\"\"\n    Validate an IPv6 address.\n\n    :param addr: IPv6 address\n    :returns: The IPv6 address.\n    \"\"\"\n    if not isinstance(addr, str):\n        raise CX(\"Invalid input, addr must be a string\")\n    else:\n        addr = addr.strip()\n\n    if addr == \"\":\n        return addr\n\n    if not netaddr.valid_ipv6(addr):\n        raise CX(\"Invalid IPv6 address format (%s)\" % addr)\n\n    return addr\n\n\ndef name_servers(nameservers: Union[str, list], for_item: bool = True) -> Union[str, list]:\n    \"\"\"\n    Validate nameservers IP addresses, works for IPv4 and IPv6\n\n    :param nameservers: string or list of nameserver addresses\n    :param for_item: enable/disable special handling for Item objects\n    :return: The list of valid nameservers.\n    \"\"\"\n    if isinstance(nameservers, str):\n        nameservers = nameservers.strip()\n        if for_item is True:\n            # special handling for Items\n            if nameservers in [\"<<inherit>>\", \"\"]:\n                return nameservers\n\n        # convert string to a list; do the real validation\n        # in the isinstance(list) code block below\n        nameservers = shlex.split(nameservers)\n\n    if isinstance(nameservers, list):\n        for ns in nameservers:\n            ip_version = netaddr.IPAddress(ns).version\n            if ip_version == 4:\n                ipv4_address(ns)\n            elif ip_version == 6:\n                ipv6_address(ns)\n            else:\n                raise CX(\"Invalid IP address format\")\n    else:\n        raise CX(\"Invalid input type %s, expected str or list\" % type(nameservers))\n\n    return nameservers\n\n\ndef name_servers_search(search: Union[str, list], for_item: bool = True) -> Union[str, list]:\n    \"\"\"\n    Validate nameservers search domains.\n\n    :param search: One or more search domains to validate.\n    :param for_item: (enable/disable special handling for Item objects)\n    :return: The list of valid nameservers.\n    \"\"\"\n    if isinstance(search, str):\n        search = search.strip()\n        if for_item is True:\n            # special handling for Items\n            if search in [\"<<inherit>>\", \"\"]:\n                return search\n\n        # convert string to a list; do the real validation\n        # in the isinstance(list) code block below\n        search = shlex.split(search)\n\n    if isinstance(search, list):\n        for sl in search:\n            hostname(sl)\n    else:\n        raise CX(\"Invalid input type %s, expected str or list\" % type(search))\n\n    return search\n\n\ndef validate_autoinstall_script_name(name: str) -> bool:\n    \"\"\"\n    This validates if the name given for the script is valid in the context of the API call made. It will be handed to\n    tftpgen.py#generate_script in the end.\n\n    :param name: The name of the script. Will end up being a filename. May have an extension but should never be a path.\n    :return: If this is a valid script name or not.\n    \"\"\"\n    if not isinstance(name, str):\n        return False\n    if re.fullmatch(RE_SCRIPT_NAME, name):\n        return True\n    return False\n\n\ndef validate_uuid(possible_uuid: str) -> bool:\n    \"\"\"\n    Validate if the handed string is a valid UUIDv4.\n\n    :param possible_uuid: The str with the UUID.\n    :return: True in case it is one, False otherwise.\n    \"\"\"\n    if not isinstance(possible_uuid, str):\n        return False\n    # Taken from: https://stackoverflow.com/a/33245493/4730773\n    try:\n        uuid_obj = UUID(possible_uuid, version=4)\n    except ValueError:\n        return False\n    return str(uuid_obj) == possible_uuid\n\n\ndef validate_obj_type(object_type: str) -> bool:\n    \"\"\"\n\n    :param object_type:\n    :return:\n    \"\"\"\n    if not isinstance(object_type, str):\n        return False\n    return object_type in [\"distro\", \"profile\", \"system\", \"repo\", \"image\", \"mgmtclass\", \"package\", \"file\", \"menu\"]\n\n\ndef validate_obj_name(object_name: str) -> bool:\n    \"\"\"\n\n    :param object_name:\n    :return:\n    \"\"\"\n    if not isinstance(object_name, str):\n        return False\n    return bool(re.fullmatch(RE_OBJECT_NAME, object_name))\n\n\ndef validate_obj_id(object_id: str) -> bool:\n    \"\"\"\n\n    :param object_id:\n    :return: True in case it is one, False otherwise.\n    \"\"\"\n    if not isinstance(object_id, str):\n        return False\n    if object_id.startswith(\"___NEW___\"):\n        object_id = object_id[9:]\n    (otype, oname) = object_id.split(\"::\", 1)\n    return validate_obj_type(otype) and validate_obj_name(oname)\n", "path": "cobbler/validate.py"}]}