mirror of https://github.com/hashicorp/packer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
975 lines
27 KiB
975 lines
27 KiB
package common
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"io"
|
|
"strings"
|
|
"strconv"
|
|
"net"
|
|
"sort"
|
|
)
|
|
|
|
type sentinelSignaller chan struct{}
|
|
|
|
/** low-level parsing */
|
|
// strip the comments and extraneous newlines from a byte channel
|
|
func uncomment(eof sentinelSignaller, in <-chan byte) chan byte {
|
|
out := make(chan byte)
|
|
|
|
go func(in <-chan byte, out chan byte) {
|
|
var endofline bool
|
|
for stillReading := true; stillReading; {
|
|
select {
|
|
case <-eof:
|
|
stillReading = false
|
|
case ch := <-in:
|
|
switch ch {
|
|
case '#':
|
|
endofline = true
|
|
case '\n':
|
|
if endofline { endofline = false }
|
|
}
|
|
if !endofline { out <- ch }
|
|
}
|
|
}
|
|
}(in, out)
|
|
return out
|
|
}
|
|
|
|
// convert a byte channel into a channel of pseudo-tokens
|
|
func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) chan string {
|
|
var ch byte
|
|
var state string
|
|
var quote bool
|
|
|
|
out := make(chan string)
|
|
go func(out chan string) {
|
|
for stillReading := true; stillReading; {
|
|
select {
|
|
case <-eof:
|
|
stillReading = false
|
|
|
|
case ch = <-in:
|
|
if quote {
|
|
if ch == '"' {
|
|
out <- state + string(ch)
|
|
state,quote = "",false
|
|
continue
|
|
}
|
|
state += string(ch)
|
|
continue
|
|
}
|
|
|
|
switch ch {
|
|
case '"':
|
|
quote = true
|
|
state += string(ch)
|
|
continue
|
|
|
|
case '\r':
|
|
fallthrough
|
|
case '\n':
|
|
fallthrough
|
|
case '\t':
|
|
fallthrough
|
|
case ' ':
|
|
if len(state) == 0 { continue }
|
|
out <- state
|
|
state = ""
|
|
|
|
case '{': fallthrough
|
|
case '}': fallthrough
|
|
case ';':
|
|
if len(state) > 0 { out <- state }
|
|
out <- string(ch)
|
|
state = ""
|
|
|
|
default:
|
|
state += string(ch)
|
|
}
|
|
}
|
|
}
|
|
if len(state) > 0 { out <- state }
|
|
}(out)
|
|
return out
|
|
}
|
|
|
|
/** mid-level parsing */
|
|
type tkParameter struct {
|
|
name string
|
|
operand []string
|
|
}
|
|
func (e *tkParameter) String() string {
|
|
var values []string
|
|
for _,val := range e.operand {
|
|
values = append(values, val)
|
|
}
|
|
return fmt.Sprintf("%s [%s]", e.name, strings.Join(values, ","))
|
|
}
|
|
|
|
type tkGroup struct {
|
|
parent *tkGroup
|
|
id tkParameter
|
|
|
|
groups []*tkGroup
|
|
params []tkParameter
|
|
}
|
|
func (e *tkGroup) String() string {
|
|
var id []string
|
|
|
|
id = append(id, e.id.name)
|
|
for _,val := range e.id.operand {
|
|
id = append(id, val)
|
|
}
|
|
|
|
var config []string
|
|
for _,val := range e.params {
|
|
config = append(config, val.String())
|
|
}
|
|
return fmt.Sprintf("%s {\n%s\n}", strings.Join(id, " "), strings.Join(config, "\n"))
|
|
}
|
|
|
|
// convert a channel of pseudo-tokens into an tkParameter struct
|
|
func parseTokenParameter(in chan string) tkParameter {
|
|
var result tkParameter
|
|
for {
|
|
token := <-in
|
|
if result.name == "" {
|
|
result.name = token
|
|
continue
|
|
}
|
|
switch token {
|
|
case "{": fallthrough
|
|
case "}": fallthrough
|
|
case ";": goto leave
|
|
default:
|
|
result.operand = append(result.operand, token)
|
|
}
|
|
}
|
|
leave:
|
|
return result
|
|
}
|
|
|
|
// convert a channel of pseudo-tokens into an tkGroup tree */
|
|
func parseDhcpConfig(eof sentinelSignaller, in chan string) (tkGroup,error) {
|
|
var tokens []string
|
|
var result tkGroup
|
|
|
|
toParameter := func(tokens []string) tkParameter {
|
|
out := make(chan string)
|
|
go func(out chan string){
|
|
for _,v := range tokens { out <- v }
|
|
out <- ";"
|
|
}(out)
|
|
return parseTokenParameter(out)
|
|
}
|
|
|
|
for stillReading,currentgroup := true,&result; stillReading; {
|
|
select {
|
|
case <-eof:
|
|
stillReading = false
|
|
|
|
case tk := <-in:
|
|
switch tk {
|
|
case "{":
|
|
grp := &tkGroup{parent:currentgroup}
|
|
grp.id = toParameter(tokens)
|
|
currentgroup.groups = append(currentgroup.groups, grp)
|
|
currentgroup = grp
|
|
case "}":
|
|
if currentgroup.parent == nil {
|
|
return tkGroup{}, fmt.Errorf("Unable to close the global declaration")
|
|
}
|
|
if len(tokens) > 0 {
|
|
return tkGroup{}, fmt.Errorf("List of tokens was left unterminated : %v", tokens)
|
|
}
|
|
currentgroup = currentgroup.parent
|
|
case ";":
|
|
arg := toParameter(tokens)
|
|
currentgroup.params = append(currentgroup.params, arg)
|
|
default:
|
|
tokens = append(tokens, tk)
|
|
continue
|
|
}
|
|
tokens = []string{}
|
|
}
|
|
}
|
|
return result,nil
|
|
}
|
|
|
|
func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) chan string {
|
|
var ch byte
|
|
var state string
|
|
var quote bool
|
|
var lastnewline bool
|
|
|
|
out := make(chan string)
|
|
go func(out chan string) {
|
|
for stillReading := true; stillReading; {
|
|
select {
|
|
case <-eof:
|
|
stillReading = false
|
|
|
|
case ch = <-in:
|
|
if quote {
|
|
if ch == '"' {
|
|
out <- state + string(ch)
|
|
state,quote = "",false
|
|
continue
|
|
}
|
|
state += string(ch)
|
|
continue
|
|
}
|
|
|
|
switch ch {
|
|
case '"':
|
|
quote = true
|
|
state += string(ch)
|
|
continue
|
|
|
|
case '\r':
|
|
fallthrough
|
|
case '\t':
|
|
fallthrough
|
|
case ' ':
|
|
if len(state) == 0 { continue }
|
|
out <- state
|
|
state = ""
|
|
|
|
case '\n':
|
|
if lastnewline { continue }
|
|
if len(state) > 0 { out <- state }
|
|
out <- string(ch)
|
|
state = ""
|
|
lastnewline = true
|
|
continue
|
|
|
|
case '.': fallthrough
|
|
case '=':
|
|
if len(state) > 0 { out <- state }
|
|
out <- string(ch)
|
|
state = ""
|
|
|
|
default:
|
|
state += string(ch)
|
|
}
|
|
lastnewline = false
|
|
}
|
|
}
|
|
if len(state) > 0 { out <- state }
|
|
}(out)
|
|
return out
|
|
}
|
|
|
|
func parseNetworkMapConfig(eof sentinelSignaller, in chan string) (NetworkMap,error) {
|
|
var unsorted map[string]map[string]string
|
|
var state []string
|
|
|
|
addResult := func(network string, attribute string, value string) error {
|
|
_,ok := unsorted[network]
|
|
if !ok { unsorted[network] = make(map[string]string) }
|
|
|
|
val,err := strconv.Unquote(value)
|
|
if err != nil { return err }
|
|
|
|
current := unsorted[network]
|
|
current[attribute] = val
|
|
return nil
|
|
}
|
|
|
|
stillReading := true
|
|
for unsorted = make(map[string]map[string]string); stillReading; {
|
|
select {
|
|
case <-eof:
|
|
if len(state) == 3 {
|
|
err := addResult(state[0], state[1], state[2])
|
|
if err != nil { return nil,err }
|
|
}
|
|
stillReading = false
|
|
case tk := <-in:
|
|
switch tk {
|
|
case ".":
|
|
if len(state) != 1 { return nil,fmt.Errorf("Missing network index") }
|
|
case "=":
|
|
if len(state) != 2 { return nil,fmt.Errorf("Assignment to empty attribute") }
|
|
case "\n":
|
|
if len(state) == 0 { continue }
|
|
if len(state) != 3 { return nil,fmt.Errorf("Invalid attribute assignment : %v", state) }
|
|
err := addResult(state[0], state[1], state[2])
|
|
if err != nil { return nil,err }
|
|
state = make([]string, 0)
|
|
default:
|
|
state = append(state, tk)
|
|
}
|
|
}
|
|
}
|
|
result := make([]map[string]string, 0)
|
|
var keys []string
|
|
for k := range unsorted { keys = append(keys, k) }
|
|
sort.Strings(keys)
|
|
for _,k := range keys {
|
|
result = append(result, unsorted[k])
|
|
}
|
|
return result,nil
|
|
}
|
|
|
|
/** higher-level parsing */
|
|
/// parameters
|
|
type pParameter interface { repr() string }
|
|
|
|
type pParameterInclude struct {
|
|
filename string
|
|
}
|
|
func (e pParameterInclude) repr() string { return fmt.Sprintf("include-file:filename=%s",e.filename) }
|
|
|
|
type pParameterOption struct {
|
|
name string
|
|
value string
|
|
}
|
|
func (e pParameterOption) repr() string { return fmt.Sprintf("option:%s=%s",e.name,e.value) }
|
|
|
|
// allow some-kind-of-something
|
|
type pParameterGrant struct {
|
|
verb string // allow,deny,ignore
|
|
attribute string
|
|
}
|
|
func (e pParameterGrant) repr() string { return fmt.Sprintf("grant:%s,%s",e.verb,e.attribute) }
|
|
|
|
type pParameterAddress4 []string
|
|
func (e pParameterAddress4) repr() string {
|
|
return fmt.Sprintf("fixed-address4:%s",strings.Join(e,","))
|
|
}
|
|
|
|
type pParameterAddress6 []string
|
|
func (e pParameterAddress6) repr() string {
|
|
return fmt.Sprintf("fixed-address6:%s",strings.Join(e,","))
|
|
}
|
|
|
|
// hardware address 00:00:00:00:00:00
|
|
type pParameterHardware struct {
|
|
class string
|
|
address []byte
|
|
}
|
|
func (e pParameterHardware) repr() string {
|
|
res := make([]string, 0)
|
|
for _,v := range e.address {
|
|
res = append(res, fmt.Sprintf("%02x",v))
|
|
}
|
|
return fmt.Sprintf("hardware-address:%s[%s]",e.class,strings.Join(res,":"))
|
|
}
|
|
|
|
type pParameterBoolean struct {
|
|
parameter string
|
|
truancy bool
|
|
}
|
|
func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%v",e.parameter,e.truancy) }
|
|
|
|
type pParameterClientMatch struct {
|
|
name string
|
|
data string
|
|
}
|
|
func (e pParameterClientMatch) repr() string { return fmt.Sprintf("match-client:%s=%s",e.name,e.data) }
|
|
|
|
// range 127.0.0.1 127.0.0.255
|
|
type pParameterRange4 struct {
|
|
min net.IP
|
|
max net.IP
|
|
}
|
|
func (e pParameterRange4) repr() string { return fmt.Sprintf("range4:%s-%s",e.min.String(),e.max.String()) }
|
|
|
|
type pParameterRange6 struct {
|
|
min net.IP
|
|
max net.IP
|
|
}
|
|
func (e pParameterRange6) repr() string { return fmt.Sprintf("range6:%s-%s",e.min.String(),e.max.String()) }
|
|
|
|
type pParameterPrefix6 struct {
|
|
min net.IP
|
|
max net.IP
|
|
bits int
|
|
}
|
|
func (e pParameterPrefix6) repr() string { return fmt.Sprintf("prefix6:/%d:%s-%s",e.bits,e.min.String(),e.max.String()) }
|
|
|
|
// some-kind-of-parameter 1024
|
|
type pParameterOther struct {
|
|
parameter string
|
|
value string
|
|
}
|
|
func (e pParameterOther) repr() string { return fmt.Sprintf("parameter:%s=%s",e.parameter,e.value) }
|
|
|
|
type pParameterExpression struct {
|
|
parameter string
|
|
expression string
|
|
}
|
|
func (e pParameterExpression) repr() string { return fmt.Sprintf("parameter-expression:%s=\"%s\"",e.parameter,e.expression) }
|
|
|
|
type pDeclarationIdentifier interface { repr() string }
|
|
|
|
type pDeclaration struct {
|
|
id pDeclarationIdentifier
|
|
parent *pDeclaration
|
|
parameters []pParameter
|
|
declarations []pDeclaration
|
|
}
|
|
|
|
func (e *pDeclaration) short() string {
|
|
return e.id.repr()
|
|
}
|
|
|
|
func (e *pDeclaration) repr() string {
|
|
res := e.short()
|
|
|
|
var parameters []string
|
|
for _,v := range e.parameters {
|
|
parameters = append(parameters, v.repr())
|
|
}
|
|
|
|
var groups []string
|
|
for _,v := range e.declarations {
|
|
groups = append(groups, fmt.Sprintf("-> %s",v.short()))
|
|
}
|
|
|
|
if e.parent != nil {
|
|
res = fmt.Sprintf("%s parent:%s",res,e.parent.short())
|
|
}
|
|
return fmt.Sprintf("%s\n%s\n%s\n", res, strings.Join(parameters,"\n"), strings.Join(groups,"\n"))
|
|
}
|
|
|
|
type pDeclarationGlobal struct {}
|
|
func (e pDeclarationGlobal) repr() string { return fmt.Sprintf("{global}") }
|
|
|
|
type pDeclarationShared struct { name string }
|
|
func (e pDeclarationShared) repr() string { return fmt.Sprintf("{shared-network %s}", e.name) }
|
|
|
|
type pDeclarationSubnet4 struct { net.IPNet }
|
|
func (e pDeclarationSubnet4) repr() string { return fmt.Sprintf("{subnet4 %s}", e.String()) }
|
|
|
|
type pDeclarationSubnet6 struct { net.IPNet }
|
|
func (e pDeclarationSubnet6) repr() string { return fmt.Sprintf("{subnet6 %s}", e.String()) }
|
|
|
|
type pDeclarationHost struct { name string }
|
|
func (e pDeclarationHost) repr() string { return fmt.Sprintf("{host name:%s}", e.name) }
|
|
|
|
type pDeclarationPool struct {}
|
|
func (e pDeclarationPool) repr() string { return fmt.Sprintf("{pool}") }
|
|
|
|
type pDeclarationGroup struct {}
|
|
func (e pDeclarationGroup) repr() string { return fmt.Sprintf("{group}") }
|
|
|
|
type pDeclarationClass struct { name string }
|
|
func (e pDeclarationClass) repr() string { return fmt.Sprintf("{class}") }
|
|
|
|
/** parsers */
|
|
func parseParameter(val tkParameter) (pParameter,error) {
|
|
switch val.name {
|
|
case "include":
|
|
if len(val.operand) != 2 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterInclude : %v",val.operand)
|
|
}
|
|
name := val.operand[0]
|
|
return pParameterInclude{filename: name},nil
|
|
|
|
case "option":
|
|
if len(val.operand) != 2 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterOption : %v",val.operand)
|
|
}
|
|
name, value := val.operand[0], val.operand[1]
|
|
return pParameterOption{name: name, value: value},nil
|
|
|
|
case "allow": fallthrough
|
|
case "deny": fallthrough
|
|
case "ignore":
|
|
if len(val.operand) < 1 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterGrant : %v",val.operand)
|
|
}
|
|
attribute := strings.Join(val.operand," ")
|
|
return pParameterGrant{verb: strings.ToLower(val.name), attribute: attribute},nil
|
|
|
|
case "range":
|
|
if len(val.operand) < 1 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange4 : %v",val.operand)
|
|
}
|
|
idxAddress := map[bool]int{true:1,false:0}[strings.ToLower(val.operand[0]) == "bootp"]
|
|
if len(val.operand) > 2 + idxAddress {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange : %v",val.operand)
|
|
}
|
|
if idxAddress + 1 > len(val.operand) {
|
|
res := net.ParseIP(val.operand[idxAddress])
|
|
return pParameterRange4{min: res, max: res},nil
|
|
}
|
|
addr1 := net.ParseIP(val.operand[idxAddress])
|
|
addr2 := net.ParseIP(val.operand[idxAddress+1])
|
|
return pParameterRange4{min: addr1, max: addr2},nil
|
|
|
|
case "range6":
|
|
if len(val.operand) == 1 {
|
|
address := val.operand[0]
|
|
if (strings.Contains(address, "/")) {
|
|
cidr := strings.SplitN(address, "/", 2)
|
|
if len(cidr) != 2 { return nil,fmt.Errorf("Unknown ipv6 format : %v", address) }
|
|
address := net.ParseIP(cidr[0])
|
|
bits,err := strconv.Atoi(cidr[1])
|
|
if err != nil { return nil,err }
|
|
mask := net.CIDRMask(bits, net.IPv6len*8)
|
|
|
|
// figure out the network address
|
|
network := address.Mask(mask)
|
|
|
|
// make a broadcast address
|
|
broadcast := network
|
|
networkSize,totalSize := mask.Size()
|
|
hostSize := totalSize-networkSize
|
|
for i := networkSize / 8; i < totalSize / 8; i++ {
|
|
broadcast[i] = byte(0xff)
|
|
}
|
|
octetIndex := network[networkSize / 8]
|
|
bitsLeft := (uint32)(hostSize%8)
|
|
broadcast[octetIndex] = network[octetIndex] | ((1<<bitsLeft)-1)
|
|
|
|
// FIXME: check that the broadcast address was made correctly
|
|
return pParameterRange6{min: network, max: broadcast},nil
|
|
}
|
|
res := net.ParseIP(address)
|
|
return pParameterRange6{min: res, max:res},nil
|
|
}
|
|
if len(val.operand) == 2 {
|
|
addr := net.ParseIP(val.operand[0])
|
|
if strings.ToLower(val.operand[1]) == "temporary" {
|
|
return pParameterRange6{min: addr, max: addr},nil
|
|
}
|
|
other := net.ParseIP(val.operand[1])
|
|
return pParameterRange6{min: addr, max: other},nil
|
|
}
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v",val.operand)
|
|
|
|
case "prefix6":
|
|
if len(val.operand) != 3 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v",val.operand)
|
|
}
|
|
bits,err := strconv.Atoi(val.operand[2])
|
|
if err != nil {
|
|
return nil,fmt.Errorf("Invalid bits for pParameterPrefix6 : %v",val.operand[2])
|
|
}
|
|
minaddr := net.ParseIP(val.operand[0])
|
|
maxaddr := net.ParseIP(val.operand[1])
|
|
return pParameterPrefix6{min: minaddr, max: maxaddr, bits:bits},nil
|
|
|
|
case "hardware":
|
|
if len(val.operand) != 2 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterHardware : %v",val.operand)
|
|
}
|
|
class := val.operand[0]
|
|
octets := strings.Split(val.operand[1], ":")
|
|
address := make([]byte, 0)
|
|
for _,v := range octets {
|
|
b,err := strconv.ParseInt(v, 16, 0)
|
|
if err != nil { return nil,err }
|
|
address = append(address, byte(b))
|
|
}
|
|
return pParameterHardware{class: class, address: address},nil
|
|
|
|
case "fixed-address":
|
|
ip4addrs := make(pParameterAddress4,len(val.operand))
|
|
copy(ip4addrs, val.operand)
|
|
return ip4addrs,nil
|
|
|
|
case "fixed-address6":
|
|
ip6addrs := make(pParameterAddress6,len(val.operand))
|
|
copy(ip6addrs, val.operand)
|
|
return ip6addrs,nil
|
|
|
|
case "host-identifier":
|
|
if len(val.operand) != 3 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterClientMatch : %v",val.operand)
|
|
}
|
|
if val.operand[0] != "option" {
|
|
return nil,fmt.Errorf("Invalid match parameter : %v",val.operand[0])
|
|
}
|
|
optionName := val.operand[1]
|
|
optionData := val.operand[2]
|
|
return pParameterClientMatch{name: optionName, data: optionData},nil
|
|
|
|
default:
|
|
length := len(val.operand)
|
|
if length < 1 {
|
|
return pParameterBoolean{parameter: val.name, truancy: true},nil
|
|
} else if length > 1 {
|
|
if val.operand[0] == "=" {
|
|
return pParameterExpression{parameter: val.name, expression: strings.Join(val.operand[1:],"")},nil
|
|
}
|
|
}
|
|
if length != 1 {
|
|
return nil,fmt.Errorf("Invalid number of parameters for pParameterOther : %v",val.operand)
|
|
}
|
|
if strings.ToLower(val.name) == "not" {
|
|
return pParameterBoolean{parameter: val.operand[0], truancy: false},nil
|
|
}
|
|
return pParameterOther{parameter: val.name, value: val.operand[0]}, nil
|
|
}
|
|
}
|
|
|
|
func parseTokenGroup(val tkGroup) (*pDeclaration,error) {
|
|
params := val.id.operand
|
|
switch val.id.name {
|
|
case "group":
|
|
return &pDeclaration{id:pDeclarationGroup{}},nil
|
|
|
|
case "pool":
|
|
return &pDeclaration{id:pDeclarationPool{}},nil
|
|
|
|
case "host":
|
|
if len(params) == 1 {
|
|
return &pDeclaration{id:pDeclarationHost{name: params[0]}},nil
|
|
}
|
|
|
|
case "subnet":
|
|
if len(params) == 3 && strings.ToLower(params[1]) == "netmask" {
|
|
addr := make([]byte, 4)
|
|
for i,v := range strings.SplitN(params[2], ".", 4) {
|
|
res,err := strconv.ParseInt(v, 10, 0)
|
|
if err != nil { return nil,err }
|
|
addr[i] = byte(res)
|
|
}
|
|
oc1,oc2,oc3,oc4 := addr[0],addr[1],addr[2],addr[3]
|
|
if subnet,mask := net.ParseIP(params[0]),net.IPv4Mask(oc1,oc2,oc3,oc4); subnet != nil && mask != nil {
|
|
return &pDeclaration{id:pDeclarationSubnet4{net.IPNet{IP:subnet,Mask:mask}}},nil
|
|
}
|
|
}
|
|
case "subnet6":
|
|
if len(params) == 1 {
|
|
ip6 := strings.SplitN(params[0], "/", 2)
|
|
if len(ip6) == 2 && strings.Contains(ip6[0], ":") {
|
|
address := net.ParseIP(ip6[0])
|
|
prefix,err := strconv.Atoi(ip6[1])
|
|
if err != nil { return nil, err }
|
|
return &pDeclaration{id:pDeclarationSubnet6{net.IPNet{IP:address,Mask:net.CIDRMask(prefix, net.IPv6len*8)}}},nil
|
|
}
|
|
}
|
|
case "shared-network":
|
|
if len(params) == 1 {
|
|
return &pDeclaration{id:pDeclarationShared{name: params[0]}},nil
|
|
}
|
|
case "":
|
|
return &pDeclaration{id:pDeclarationGlobal{}},nil
|
|
}
|
|
return nil,fmt.Errorf("Invalid pDeclaration : %v : %v", val.id.name, params)
|
|
}
|
|
|
|
func flattenDhcpConfig(root tkGroup) (*pDeclaration,error) {
|
|
var result *pDeclaration
|
|
result,err := parseTokenGroup(root)
|
|
if err != nil { return nil,err }
|
|
|
|
for _,p := range root.params {
|
|
param,err := parseParameter(p)
|
|
if err != nil { return nil,err }
|
|
result.parameters = append(result.parameters, param)
|
|
}
|
|
for _,p := range root.groups {
|
|
group,err := flattenDhcpConfig(*p)
|
|
if err != nil { return nil,err }
|
|
group.parent = result
|
|
result.declarations = append(result.declarations, *group)
|
|
}
|
|
return result,nil
|
|
}
|
|
|
|
/** reduce the tree into the things that we care about */
|
|
type grant uint
|
|
const (
|
|
ALLOW grant = iota
|
|
IGNORE grant = iota
|
|
DENY grant = iota
|
|
)
|
|
type configDeclaration struct {
|
|
id []pDeclarationIdentifier
|
|
composites []pDeclaration
|
|
|
|
address []pParameter
|
|
|
|
options map[string]string
|
|
grants map[string]grant
|
|
attributes map[string]bool
|
|
parameters map[string]string
|
|
expressions map[string]string
|
|
|
|
hostid []pParameterClientMatch
|
|
}
|
|
|
|
func createDeclaration(node pDeclaration) configDeclaration {
|
|
var hierarchy []pDeclaration
|
|
|
|
for n := &node; n != nil; n = n.parent {
|
|
hierarchy = append(hierarchy, *n)
|
|
}
|
|
|
|
var result configDeclaration
|
|
result.address = make([]pParameter, 0)
|
|
|
|
result.options = make(map[string]string)
|
|
result.grants = make(map[string]grant)
|
|
result.attributes = make(map[string]bool)
|
|
result.parameters = make(map[string]string)
|
|
result.expressions = make(map[string]string)
|
|
|
|
result.hostid = make([]pParameterClientMatch, 0)
|
|
|
|
// walk from globals to pDeclaration collecting all parameters
|
|
for i := len(hierarchy)-1; i >= 0; i-- {
|
|
result.composites = append(result.composites, hierarchy[(len(hierarchy)-1) - i])
|
|
result.id = append(result.id, hierarchy[(len(hierarchy)-1) - i].id)
|
|
|
|
// update configDeclaration parameters
|
|
for _,p := range hierarchy[i].parameters {
|
|
switch p.(type) {
|
|
case pParameterOption:
|
|
result.options[p.(pParameterOption).name] = p.(pParameterOption).value
|
|
case pParameterGrant:
|
|
Grant := map[string]grant{"ignore":IGNORE, "allow":ALLOW, "deny":DENY}
|
|
result.grants[p.(pParameterGrant).attribute] = Grant[p.(pParameterGrant).verb]
|
|
case pParameterBoolean:
|
|
result.attributes[p.(pParameterBoolean).parameter] = p.(pParameterBoolean).truancy
|
|
case pParameterClientMatch:
|
|
result.hostid = append(result.hostid, p.(pParameterClientMatch))
|
|
case pParameterExpression:
|
|
result.expressions[p.(pParameterExpression).parameter] = p.(pParameterExpression).expression
|
|
case pParameterOther:
|
|
result.parameters[p.(pParameterOther).parameter] = p.(pParameterOther).value
|
|
default:
|
|
result.address = append(result.address, p)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (e *configDeclaration) repr() string {
|
|
var result []string
|
|
|
|
var res []string
|
|
|
|
res = make([]string, 0)
|
|
for _,v := range e.id { res = append(res, v.repr()) }
|
|
result = append(result, strings.Join(res, ","))
|
|
|
|
if len(e.address) > 0 {
|
|
res = make([]string, 0)
|
|
for _,v := range e.address { res = append(res, v.repr()) }
|
|
result = append(result, fmt.Sprintf("address : %v", strings.Join(res, ",")))
|
|
}
|
|
|
|
if len(e.options) > 0 { result = append(result, fmt.Sprintf("options : %v", e.options)) }
|
|
if len(e.grants) > 0 { result = append(result, fmt.Sprintf("grants : %v", e.grants)) }
|
|
if len(e.attributes) > 0 { result = append(result, fmt.Sprintf("attributes : %v", e.attributes)) }
|
|
if len(e.parameters) > 0 { result = append(result, fmt.Sprintf("parameters : %v", e.parameters)) }
|
|
if len(e.expressions) > 0 { result = append(result, fmt.Sprintf("parameter-expressions : %v", e.expressions)) }
|
|
|
|
if len(e.hostid) > 0 {
|
|
res = make([]string, 0)
|
|
for _,v := range e.hostid { res = append(res, v.repr()) }
|
|
result = append(result, fmt.Sprintf("hostid : %v", strings.Join(res, " ")))
|
|
}
|
|
return strings.Join(result, "\n") + "\n"
|
|
}
|
|
|
|
func (e *configDeclaration) IP4() (net.IP,error) {
|
|
var result []string
|
|
for _,entry := range e.address {
|
|
switch entry.(type) {
|
|
case pParameterAddress4:
|
|
for _,s := range entry.(pParameterAddress4) {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
}
|
|
if len(result) > 1 {
|
|
return nil,fmt.Errorf("More than one address4 returned : %v", result)
|
|
} else if len(result) == 0 {
|
|
return nil,fmt.Errorf("No IP4 addresses found")
|
|
}
|
|
|
|
if res := net.ParseIP(result[0]); res != nil { return res,nil }
|
|
res,err := net.ResolveIPAddr("ip4", result[0])
|
|
if err != nil { return nil,err }
|
|
return res.IP,nil
|
|
}
|
|
func (e *configDeclaration) IP6() (net.IP,error) {
|
|
var result []string
|
|
for _,entry := range e.address {
|
|
switch entry.(type) {
|
|
case pParameterAddress6:
|
|
for _,s := range entry.(pParameterAddress6) {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
}
|
|
if len(result) > 1 {
|
|
return nil,fmt.Errorf("More than one address6 returned : %v", result)
|
|
} else if len(result) == 0 {
|
|
return nil,fmt.Errorf("No IP6 addresses found")
|
|
}
|
|
|
|
if res := net.ParseIP(result[0]); res != nil { return res,nil }
|
|
res,err := net.ResolveIPAddr("ip6", result[0])
|
|
if err != nil { return nil,err }
|
|
return res.IP,nil
|
|
}
|
|
func (e *configDeclaration) Hardware() (net.HardwareAddr,error) {
|
|
var result []pParameterHardware
|
|
for _,addr := range e.address {
|
|
switch addr.(type) {
|
|
case pParameterHardware:
|
|
result = append(result, addr.(pParameterHardware))
|
|
}
|
|
}
|
|
if len(result) > 0 {
|
|
return nil,fmt.Errorf("More than one hardware address returned : %v", result)
|
|
}
|
|
res := make(net.HardwareAddr, 0)
|
|
for _,by := range result[0].address {
|
|
res = append(res, by)
|
|
}
|
|
return res,nil
|
|
}
|
|
|
|
/*** Dhcp Configuration */
|
|
type DhcpConfiguration []configDeclaration
|
|
func ReadDhcpConfiguration(fd *os.File) (DhcpConfiguration,error) {
|
|
fromfile,eof := consumeFile(fd)
|
|
uncommented := uncomment(eof, fromfile)
|
|
tokenized := tokenizeDhcpConfig(eof, uncommented)
|
|
parsetree,err := parseDhcpConfig(eof, tokenized)
|
|
if err != nil { return nil,err }
|
|
|
|
global,err := flattenDhcpConfig(parsetree)
|
|
if err != nil { return nil,err }
|
|
|
|
var walkDeclarations func(root pDeclaration, out chan*configDeclaration);
|
|
walkDeclarations = func(root pDeclaration, out chan*configDeclaration) {
|
|
res := createDeclaration(root)
|
|
out <- &res
|
|
for _,p := range root.declarations {
|
|
walkDeclarations(p, out)
|
|
}
|
|
}
|
|
|
|
each := make(chan*configDeclaration)
|
|
go func(out chan*configDeclaration) {
|
|
walkDeclarations(*global, out)
|
|
out <- nil
|
|
}(each)
|
|
|
|
var result DhcpConfiguration
|
|
for decl := <-each; decl != nil; decl = <-each {
|
|
result = append(result, *decl)
|
|
}
|
|
return result,nil
|
|
}
|
|
|
|
func (e *DhcpConfiguration) Global() configDeclaration {
|
|
result := (*e)[0]
|
|
if len(result.id) != 1 {
|
|
panic(fmt.Errorf("Something that can't happen happened"))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (e *DhcpConfiguration) SubnetByAddress(address net.IP) (configDeclaration,error) {
|
|
var result []configDeclaration
|
|
for _,entry := range *e {
|
|
switch entry.id[0].(type) {
|
|
case pDeclarationSubnet4:
|
|
id := entry.id[0].(pDeclarationSubnet4)
|
|
if id.Contains(address) {
|
|
result = append(result, entry)
|
|
}
|
|
case pDeclarationSubnet6:
|
|
id := entry.id[0].(pDeclarationSubnet6)
|
|
if id.Contains(address) {
|
|
result = append(result, entry)
|
|
}
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return configDeclaration{},fmt.Errorf("No network declarations containing %s found", address.String())
|
|
}
|
|
if len(result) > 1 {
|
|
return configDeclaration{},fmt.Errorf("More than 1 network declaration found : %v", result)
|
|
}
|
|
return result[0],nil
|
|
}
|
|
|
|
func (e *DhcpConfiguration) HostByName(host string) (configDeclaration,error) {
|
|
var result []configDeclaration
|
|
for _,entry := range *e {
|
|
switch entry.id[0].(type) {
|
|
case pDeclarationHost:
|
|
id := entry.id[0].(pDeclarationHost)
|
|
if strings.ToLower(id.name) == strings.ToLower(host) {
|
|
result = append(result, entry)
|
|
}
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return configDeclaration{},fmt.Errorf("No host declarations containing %s found", host)
|
|
}
|
|
if len(result) > 1 {
|
|
return configDeclaration{},fmt.Errorf("More than 1 host declaration found : %v", result)
|
|
}
|
|
return result[0],nil
|
|
}
|
|
|
|
/*** Network Map */
|
|
type NetworkMap []map[string]string
|
|
func ReadNetworkMap(fd *os.File) (NetworkMap,error) {
|
|
|
|
fromfile,eof := consumeFile(fd)
|
|
uncommented := uncomment(eof,fromfile)
|
|
tokenized := tokenizeNetworkMapConfig(eof, uncommented)
|
|
|
|
result,err := parseNetworkMapConfig(eof, tokenized)
|
|
if err != nil { return nil,err }
|
|
return result,nil
|
|
}
|
|
|
|
func (e *NetworkMap) NameIntoDevice(name string) (string,error) {
|
|
for _,val := range *e {
|
|
if strings.ToLower(val["name"]) == strings.ToLower(name) {
|
|
return val["device"],nil
|
|
}
|
|
}
|
|
return "",fmt.Errorf("Network name not found : %v", name)
|
|
}
|
|
func (e *NetworkMap) DeviceIntoName(device string) (string,error) {
|
|
for _,val := range *e {
|
|
if strings.ToLower(val["device"]) == strings.ToLower(device) {
|
|
return val["name"],nil
|
|
}
|
|
}
|
|
return "",fmt.Errorf("Device name not found : %v", device)
|
|
}
|
|
func (e *NetworkMap) repr() string {
|
|
var result []string
|
|
for idx,val := range *e {
|
|
result = append(result, fmt.Sprintf("network%d.name = \"%s\"", idx, val["name"]))
|
|
result = append(result, fmt.Sprintf("network%d.device = \"%s\"", idx, val["device"]))
|
|
}
|
|
return strings.Join(result, "\n")
|
|
}
|
|
|
|
/** main */
|
|
func consumeFile(fd *os.File) (chan byte,sentinelSignaller) {
|
|
fromfile := make(chan byte)
|
|
eof := make(sentinelSignaller)
|
|
go func() {
|
|
b := make([]byte, 1)
|
|
for {
|
|
_,err := fd.Read(b)
|
|
if err == io.EOF { break }
|
|
fromfile <- b[0]
|
|
}
|
|
close(eof)
|
|
}()
|
|
return fromfile,eof
|
|
}
|