diff --git a/zscan.go b/zscan.go new file mode 100644 index 00000000..b5e60b58 --- /dev/null +++ b/zscan.go @@ -0,0 +1,356 @@ +package main + +import ( + "dns" + "fmt" + "os" + "strconv" + "strings" + "text/scanner" +) + +// Tokinize a RFC 1035 zone file. The tokenizer will normalize it: +// * Add ownernames; +// * Suppress sequences of spaces; +// * Make each RR fit on one line (NEWLINE is send as last) +// * Handle comments: ; +const ( + _STRING = iota + _BLANK + _NEWLINE + _RRTYPE + _OWNER + _CLASS +) + +const ( + _EXPECT_OWNER = iota // Ownername + _EXPECT_OWNER_BL // Whitespace after the ownername + _EXPECT_ANY // Expect rrtype, ttl or class + _EXPECT_ANY_NO_CLASS // Expect rrtype or ttl + _EXPECT_ANY_NO_CLASS_BL // The Whitespace after _EXPECT_ANY_NO_CLASS + _EXPECT_ANY_NOTTL // Expect rrtype or class + _EXPECT_ANY_NOTTL_BL // Whitespace after _EXPECT_ANY_NOTTL + _EXPECT_RRTYPE // Expect rrtype + _EXPECT_RRTYPE_BL // Whitespace BEFORE rrype + _EXPECT_RDATA // The first element of the rdata + _EXPECT_RDATA_BL // Whitespace BEFORE rdata starts +) + +type Lex struct { + token string + value int + line int + column int +} + +func main() { + //f, e := os.Open("test") + f, e := os.Open("dnssex.nl.signed") + if e != nil { + fmt.Printf("Err: " + e.Error()) + return + } + var s scanner.Scanner + c := make(chan Lex) + s.Init(f) + s.Mode = 0 + s.Whitespace = 0 + go lexer(s, c) + // 5 possible beginnings of a line, _ is a space + // 1. _OWNER _ _RRTYPE -> class/ttl omitted + // 2. _OWNER _ _STRING _ _RRTYPE -> class omitted + // 3. _OWNER _ _STRING _ _CLASS _ _RRTYPE -> ttl/class + // 4. _OWNER _ _CLASS _ _RRTYPE -> ttl omitted + // 5. _OWNER _ _CLASS _ _STRING _ _RRTYPE -> class/ttl (reversed) + // After detecting these, we know the _RRTYPE so we can jump to functions + // handling the rdata for each of these types. + st := _EXPECT_OWNER + var h dns.RR_Header + var ok bool + for l := range c { + switch st { + case _EXPECT_OWNER: // Owername + switch l.value { + case _NEWLINE: + // empty line + st = _EXPECT_OWNER + case _OWNER: + h.Name = l.token + st = _EXPECT_OWNER_BL + default: + println("Error at the start") + // Reset + st = _EXPECT_OWNER + } + case _EXPECT_OWNER_BL: // Expect a blank + if l.value != _BLANK { + println("No blank after owner error") + } + st = _EXPECT_ANY + case _EXPECT_ANY: // Expect _CLASS _STRING (TTL here) or _RRTYPE + switch l.value { + case _RRTYPE: + h.Rrtype, ok = dns.Str_rr[strings.ToUpper(l.token)] + if !ok { + println("Unknown RR type") + } + h.Ttl = dns.DefaultTTL + st = _EXPECT_RDATA_BL + case _CLASS: + h.Class, ok = dns.Str_class[strings.ToUpper(l.token)] + if !ok { + println("Unknown Class") + } + st = _EXPECT_ANY_NO_CLASS_BL + case _STRING: // TTL + ttl, ok := strconv.Atoi(l.token) + if ok != nil { + println("Not a TTL") + } else { + h.Ttl = uint32(ttl) + } + st = _EXPECT_ANY_NOTTL_BL + default: + println("Error not expected") + } + case _EXPECT_ANY_NO_CLASS_BL: + if l.value != _BLANK { + println("No blank before NO_CLASS error") + } + st = _EXPECT_ANY_NO_CLASS + case _EXPECT_ANY_NOTTL_BL: + if l.value != _BLANK { + println("No blank before NOTTL error") + } + st = _EXPECT_ANY_NOTTL + case _EXPECT_ANY_NOTTL: + switch l.value { + case _CLASS: + h.Class, ok = dns.Str_class[strings.ToUpper(l.token)] + if !ok { + println("Unknown Class") + } + st = _EXPECT_RRTYPE_BL + case _RRTYPE: + h.Rrtype, ok = dns.Str_rr[strings.ToUpper(l.token)] + if !ok { + println("Unknown RR type") + } + st = _EXPECT_RDATA_BL + } + case _EXPECT_ANY_NO_CLASS: + switch l.value { + case _STRING: // TTL + ttl, ok := strconv.Atoi(l.token) + if ok != nil { + println("Not a TTL") + } else { + h.Ttl = uint32(ttl) + } + st = _EXPECT_RDATA_BL + case _RRTYPE: + h.Rrtype, ok = dns.Str_rr[strings.ToUpper(l.token)] + if !ok { + println("Unknown RR type") + } + st = _EXPECT_RDATA_BL + default: + println("Error not TTL nor _RRTYPE seen") + } + case _EXPECT_RRTYPE_BL: + if l.value != _BLANK { + println("No blank after error") + } + st = _EXPECT_RRTYPE + case _EXPECT_RRTYPE: + if l.value != _RRTYPE { + println("Error, not an rrtype") + } + h.Rrtype, ok = dns.Str_rr[strings.ToUpper(l.token)] + if !ok { + println("Unknown RR type") + } + st = _EXPECT_RDATA_BL + case _EXPECT_RDATA_BL: + if l.value != _BLANK { + println("No blank after error") + } + st = _EXPECT_RDATA + case _EXPECT_RDATA: + fmt.Printf("%v\n", h) + // Remaining items until newline are rdata + // reset + fmt.Printf("%v", l) + for rdata := range c { + fmt.Printf("%v", rdata) + if rdata.value == _NEWLINE { + break + } + } + st = _EXPECT_OWNER + } + } +} + +func (l Lex) String() string { + switch l.value { + case _STRING: + return l.token + case _BLANK: + return " " //"_" // seems to work, make then invisible for now + case _NEWLINE: + return "|\n" + case _RRTYPE: + return "R:" + l.token + case _OWNER: + return "O:" + l.token + case _CLASS: + return "C:" + l.token + } + return "" +} + +func isrrtype(s string) bool { + switch strings.ToUpper(s) { + case "SOA": + fallthrough + case "TXT": + fallthrough + case "CNAME": + fallthrough + case "NSEC": + fallthrough + case "RRSIG": + return true + } + return false +} + +func isclass(s string) bool { + switch strings.ToUpper(s) { + case "IN": + fallthrough + case "CH": + return true + } + return false +} + +// lexer scans the sourcefile and returns tokens on the channel c. +func lexer(s scanner.Scanner, c chan Lex) { + var l Lex + str := "" // Hold the current read text + quote := false + space := false + commt := false + rrtype := false + owner := true + brace := 0 + tok := s.Scan() + defer close(c) + for tok != scanner.EOF { + switch x := s.TokenText(); x { + case " ", "\t": + if commt { + break + } + if str == "" { + //l.value = _BLANK + //l.token = " " + } else if owner { + // If we have a string, its the first make it an owner + l.value = _OWNER + l.token = str + c <- l + } else { + l.value = _STRING + l.token = str + + if !rrtype && isrrtype(str) { + l.value = _RRTYPE + rrtype = true // We've seen one + } + if !rrtype && isclass(str) { + l.value = _CLASS + } + c <- l + } + str = "" + if !space && !commt { + l.value = _BLANK + l.token = " " + c <- l + } + space = true + owner = false + case ";": + if quote { + // Inside quoted text we allow ; + str += ";" + break + } + commt = true + case "\n": + if commt { + // Reset + commt = false + rrtype = false + str = "" + break + } + if str != "" { + l.value = _STRING + l.token = str + c <- l + } + if brace > 0 { + l.value = _BLANK + l.token = " " + if !space { + c <- l + } + } else { + l.value = _NEWLINE + l.token = "\n" + c <- l + } + if l.value == _BLANK { + space = true + } + + str = "" + commt = false + rrtype = false + owner = true + case "\"": + if commt { + break + } + str += "\"" + quote = !quote + case "(": + if commt { + break + } + brace++ + case ")": + if commt { + break + } + brace-- + if brace < 0 { + fmt.Printf("Error\n") + } + default: + if commt { + break + } + str += x + space = false + } + tok = s.Scan() + } + // Hmm + // fmt.Printf("XX %s XXX", str) +}