I'm trying to parse a dhcpd.conf file to python dict using pyparsing. Here is a syntax sample:
ddns-update-style none;
log-facility local7;
deny unknown-clients;
deny booting;
subnet 10.42.200.0 netmask 255.255.255.0 {
max-lease-time 28800;
option routers 10.42.200.1;
option domain-name-servers 8.8.8.8, 8.8.4.4;
range 10.42.200.10 10.42.200.200;
}
shared-network 224-29 {
subnet 10.17.224.0 netmask 255.255.255.0 {
option routers rtr-224.example.org;
}
subnet 10.0.29.0 netmask 255.255.255.0 {
option routers rtr-29.example.org;
}
pool {
allow members of "foo";
range 10.17.224.10 10.17.224.250;
}
pool {
deny members of "foo";
range 10.0.29.10 10.0.29.230;
}
}
As you can see multiple 'pool's can be created on this dhcpd.conf. But if I parse them as a dictionary one will overwrite the other because they would have the same key("pool"). Also multiple "deny" statements can happen and again will be overwriten while parsing resulting on only 1 "deny" statement per nest level.
The ideal parsed dict would be something like this:
{
'ddns-update-style': 'none',
'log-facility': 'local7',
'deny': [
'booting',
'unknown-clients'
],
'shared-networks': {
'224-29': {
'subnet 10.0.29.0 netmask 255.255.255.0': {
'options': {
'routers': 'rtr-29.example.org'
},
},
'subnet 10.17.224.0 netmask 255.255.255.0': {
'options': {
'routers': 'rtr-224.example.org'
}
},
'pools': [
{
'allow': ['members of "foo"'],
'range': '10.17.224.10 10.17.224.250',
},
{
'deny': ['members of "foo"'],
'range': '10.0.29.10 10.0.29.230'
}
]
}
},
'subnet 10.42.200.0 netmask 255.255.255.0': {
'max-lease-time': '28800',
'options': {
'domain-name-servers': ['8.8.8.8', '8.8.4.4'],
'routers': '10.42.200.1',
}
'range': '10.42.200.10 10.42.200.200'
}
}
So far i was able to get around this by Combining named sections with their name. (Like options and shared networks) but this is no possible for pools or allow/deny statements.
This is my current output and code:
{
'ddns-update-style': 'none',
'deny': 'booting',
'log-facility': 'local7',
'shared-network 224-29': {
'pool': {
'deny': 'members of "foo"',
'range': '10.0.29.10 10.0.29.230'
},
'subnet 10.0.29.0 netmask 255.255.255.0': {'option routers': 'rtr-29.example.org'},
'subnet 10.17.224.0 netmask 255.255.255.0': {'option routers': 'rtr-224.example.org'}
},
'subnet 10.42.200.0 netmask 255.255.255.0': {
'max-lease-time': '28800',
'option domain-name-servers': ['8.8.8.8', '8.8.4.4'],
'option routers': '10.42.200.1',
'range': '10.42.200.10 10.42.200.200'
}
}
Code:
from pprint import pprint
from pyparsing import (
CharsNotIn,
Literal,
White,
Word,
Combine,
ZeroOrMore,
OneOrMore,
Forward,
Dict,
ParseException,
Group,
Suppress,
delimitedList,
nums,
restOfLine,
alphanums,
pyparsing_common,
)
prop = delimitedList(Word(alphanums + "-_.!@#$%^&*"))
ipAddress = pyparsing_common.ipv4_address | pyparsing_common.ipv6_address
macAddress = Combine(
Word(alphanums, exact=2)
+ ":"
+ Word(alphanums, exact=2)
+ ":"
+ Word(alphanums, exact=2)
+ ":"
+ Word(alphanums, exact=2)
+ ":"
+ Word(alphanums, exact=2)
+ ":"
+ Word(alphanums, exact=2)
)
list_ipAddress = delimitedList(ipAddress)
name = Word(alphanums + "-_")
value = list_ipAddress | macAddress | prop | CharsNotIn(";")
comment = "#" + restOfLine
LBRACE, RBRACE, WHITE_SPACE = map(Suppress, "{} ")
struct = Forward()
hw_eth = Group(
Combine(Literal("hardware") + White(" ") + name) + macAddress
)
name_value = Group(name + value)
subnet = Group(
Combine(
"subnet"
+ White(" ")
+ ipAddress
+ White(" ")
+ "netmask"
+ White(" ")
+ ipAddress
)
+ struct
)
option = Group(Combine("option" + White(" ") + name) + value)
host = Group(Combine("host" + White(" ") + name) + struct)
ip_range = Group(
Literal("range") + Combine(ipAddress + White(" ") + ipAddress)
)
shared_network = Group(
Combine(Literal("shared-network") + White(" ") + name) + struct
)
allow_deny = Group(
(Literal("allow") | Literal("deny"))
+ ZeroOrMore(WHITE_SPACE)
+ CharsNotIn(";")
)
named_struct = Group(name + struct)
struct << Dict(
LBRACE
+ ZeroOrMore(
subnet
| host
| shared_network
| ip_range
| option
| hw_eth
| named_struct
| allow_deny
| name_value
)
+ RBRACE
)
parser = Dict(
OneOrMore(
subnet
| host
| shared_network
| ip_range
| option
| hw_eth
| named_struct
| allow_deny
| name_value
)
)
parser.ignore(comment)
parser.ignore(";")
try:
result = parser.parseString(data)
except ParseException as pe:
str_pe = str(pe)
if "found end of text" in str_pe:
pass
else:
raise pe
pprint(result.asDict())