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=%s",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< 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 }