# -*- encoding: utf-8 -*-
"""
libpydhcpserver.dhcp_types.rfc
==============================
Provides a number of convenience-classes and methods for working with RFC
extensions to the DHCP spec.
Legal
-----
This file is part of libpydhcpserver.
libpydhcpserver is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
(C) Neil Tallim, 2014 <flan@uguu.ca>
"""
try:
from types import StringTypes
except ImportError: #py3k
StringTypes = (str,)
from conversion import (intToList, longToList)
from ipv4 import IPv4
[docs]def rfc3046_decode(s):
"""
Extracts sub-options from an RFC3046 option (82).
:param sequence s: The option's raw data.
:return dict: The sub-options, as byte-lists, keyed by ID.
"""
sub_options = {}
while s:
id = s.pop(0)
length = s.pop(0)
sub_options[id] = s[:length]
s = s[length:]
return sub_options
[docs]def rfc3925_decode(s, identifier_size=4):
"""
Extracts sub-options from an RFC3925 option (124, 125 (with
identifier_size=1)).
You probably want to use :func:`rfc3925_125_decode` when dealing
with option 125 specifically.
:param sequence s: The option's raw data.
:param int identifier_size: The width of the identifier in bytes.
:return dict: A dictionary of data as strings keyed by ID numbers.
"""
data = {}
while s:
enterprise_number = conversion.listToNumber(s[:identifier_size])
payload_size = s[identifier_size]
payload = s[1 + identifier_size:1 + identifier_size + payload_size]
s = s[1 + identifier_size + payload_size:]
data[enterprise_number] = payload
return data
[docs]def rfc3925_125_decode(l):
"""
Extracts sub-options from an RFC3925 option (125).
:param sequence s: The option's raw data.
:return dict: A dictionary of data as dictionaries mapping data as strings
keyed by ID numbers.
"""
value = rfc3925_decode(value, identifier_size=4)
for i in value:
value[i] = rfc3925_decode(value[i], identifier_size=1)
return value
def _rfc1035Parse(domain_name):
"""
Splits an FQDN on dots, outputting data like
['g', 'o', 'o', 'g', 'l', 'e', 2, 'c', 'a', 0], in conformance with
RFC1035.
:param str domain_name: The FQDN to be converted.
:return list: The converted FQDN.
"""
bytes = []
for fragment in domain_name.split('.'):
bytes += [len(fragment)] + [ord(c) for c in fragment]
return bytes + [0]
[docs]class RFC(object):
"""
A generic special RFC object, used to simplify the process of setting
complex options.
"""
_value = None #: The bytes associated with this object.
[docs] def getValue(self):
"""
Provides the type's data as a list of bytes.
Note that this is the actual internal list; modifications to the list
will be persistent.
:return list: A list of bytes.
"""
return self._value
def __repr__(self):
return "<%(name)s : %(value)r>" % {
'name': self.__class__.__name__,
'value': self._value,
}
def __nonzero__(self):
return 1
def __cmp__(self, other):
"""
If ``other`` is an RFC instance, their values are compared. Otherwise,
the internal list's value is compared directly to ``other``.
"""
if isinstance(other, RFC):
return cmp(self._value, other.getValue())
return cmp(self._value, other)
[docs]class rfc1035_plus(RFC):
def __init__(self, data):
"""
Formats FQDNs as an RFC1035-formatted sequence.
:param str data: The comma-delimited FQDNs to process.
"""
self._value = []
for token in (tok for tok in (t.strip() for t in data.split(',')) if tok):
self._value += _rfc1035Parse(token)
[docs]class rfc2610_78(RFC):
def __init__(self, mandatory, data):
"""
Formats native IPv4s as encoded IPv4 addresses.
:param bool mandatory: True if the IPv4 addresses have to be respected.
:param str data: The comma-delimited IPv4s to process.
"""
self._value = [int(mandatory)]
for token in (tok for tok in (t.strip() for t in data.split(',')) if tok):
self._value.extend(IPv4(token))
[docs]class rfc2610_79(RFC):
def __init__(self, mandatory, data):
"""
Formats scope-list data.
:param bool mandatory: True if the scope-list has to be respected.
:param str data: The scope-list to process.
"""
self._value = [int(mandatory)] + [ord(c) for c in data.encode('utf-8')]
[docs]class rfc3361_120(RFC):
def __init__(self, data):
"""
Formats the given data into multiple IPv4 addresses or
RFC1035-formatted strings.
:param str data: The comma-delimited IPv4s or FQDNs to process.
:except ValueError: Both IPv4s and FQDNs were provided.
"""
ip_4_mode = False
dns_mode = False
self._value = []
for token in (tok for tok in (t.strip() for t in data.split(',')) if tok):
try:
self._value.extend(IPv4(token))
ip_4_mode = True
except ValueError:
self._value += _rfc1035Parse(token)
dns_mode = True
if ip_4_mode == dns_mode:
raise ValueError("'%(data)s contains both IPv4 and DNS-based entries" % {
'data': data,
})
self._value.insert(0, int(ip_4_mode))
[docs]class rfc3397_119(rfc1035_plus): pass
[docs]class rfc3442_121(RFC):
def __init__(self, classless_addresses):
"""
Accepts a sequence of CIDR entries and routers to produce classless
static route byte-blocks.
You'll probably want to put (("0.0.0.0", 0), packet.getOption("router"))
at the start to ensure the default gateway is set as expected.
:param classless_addresses: ((network IPv4, CIDR-mask), router IPv4)
elements, like (("169.254.0.0", 16), "0.0.0.0").
:except ValueError: An illegal address or mask was encountered.
"""
self._value = []
for ((ip, mask), router) in classless_addresses:
ip = IPv4(ip)
router = IPv4(router)
if not 0 <= mask <= 32:
raise ValueError("CIDR mask %(mask)i is not between 0 and 32" % {
'mask': mask,
})
width = mask / 8
if mask % 8:
width += 1
self._value.append(mask)
if width:
self._value.extend(tuple(ip)[:width])
self._value.extend(router)
[docs]class rfc3925_124(RFC):
def __init__(self, data):
"""
Sets `vendor_class` data.
:param dict data: A dictionary of data-strings keyed by ID-ints.
"""
self._value = []
for (enterprise_number, payload) in sorted(data.items()):
self._value += longToList(enterprise_number)
self._value.append(chr(len(payload)))
self._value += payload
[docs]class rfc3925_125(RFC):
def __init__(self, data):
"""
Sets `vendor_specific` data.
:param dict data: A dictionary of dictionaries of data-strings, keyed
by ID-ints at both levels.
"""
self._value = []
for (enterprise_number, payload) in sorted(data.items()):
self._value += longToList(enterprise_number)
subdata = []
for (subopt_code, subpayload) in sorted(payload.items()):
subdata.append(chr(subopt_code))
subdata.append(chr(len(subpayload)))
subdata += subpayload
self._value.append(chr(len(subdata)))
self._value += subdata
[docs]class rfc4174_83(RFC):
def __init__(self, isns_functions, dd_access, admin_flags, isns_security, ips):
"""
Sets iSNS configuration parameters.
:param int isns_functions: A sixteen-bit value.
:param int dd_access: A sixteen-bit value.
:param int admin_flags: A sixteen-bit value.
:param int isns_security: A thirty-two-bit value.
:param str ips: Comma-delimited IPv4s to be processed.
"""
isns_functions = intToList(isns_functions)
dd_access = intToList(dd_access)
admin_flags = intToList(admin_flags)
isns_security = longToList(isns_security)
self._value = isns_functions + dd_access + admin_flags + isns_security
for token in [tok for tok in [t.strip() for t in ips.split(',')] if tok]:
self._value.extend(IPv4(token))
[docs]class rfc4280_88(rfc1035_plus): pass
[docs]class rfc5223_137(rfc1035_plus): pass
[docs]class rfc5678_139(RFC):
def __init__(self, values):
"""
Formats the given data into multiple IPv4 addresses associated with
sub-option codes.
:param sequence values: A sequence of (code:int, IPv4s:string) elements.
"""
self._value = []
for (code, addresses) in values:
self._value.append(code)
for token in [tok for tok in [address.strip() for address in addresses.split(',')] if tok]:
self._value.extend(IPv4(token))
[docs]class rfc5678_140(RFC):
def __init__(self, values):
"""
Formats the given data into multiple RFC1035-formatted strings
associated with sub-option codes.
:param sequence values: A sequence of (code:int, FQDNs:string)
elements.
"""
self._value = []
for (code, addresses) in values:
self._value.append(code)
self._value += rfc1035_plus(addresses).getValue()