diff --git a/TODO b/TODO index 1f5f357f..bec6d22d 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,7 @@ Todo: * Cleanup the code * Tsig testing * Private key file parsing, exponent 65536 does not work +* Parsing from /etc/resolv.conf Longer term: * Parsing from strings, going with goyacc and own lexer diff --git a/resolver/Makefile b/resolver/Makefile index 9722e42a..ee16829c 100644 --- a/resolver/Makefile +++ b/resolver/Makefile @@ -7,6 +7,8 @@ include $(GOROOT)/src/Make.inc TARG=dns/resolver GOFILES=\ resolver.go\ + parse.go\ + config.go\ DEPS=.. include $(GOROOT)/src/Make.pkg diff --git a/resolver/config.go b/resolver/config.go new file mode 100644 index 00000000..bdfe2acc --- /dev/null +++ b/resolver/config.go @@ -0,0 +1,96 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Read system DNS config from /etc/resolv.conf + +package resolver + +import ( "os"; "net" ) + +// See resolv.conf(5) on a Linux machine. +// TODO(rsc): Supposed to call uname() and chop the beginning +// of the host name to get the default search domain. +// We assume it's in resolv.conf anyway. +func dnsReadConfig() (*Resolver, os.Error) { + file, err := open("/etc/resolv.conf") + if err != nil { + return nil, err + } + conf := new(Resolver) + conf.Servers = make([]string, 3)[0:0] // small, but the standard limit + conf.Search = make([]string, 0) + conf.Ndots = 1 + conf.Timeout = 5 + conf.Attempts = 2 + conf.Rotate = false + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + f := getFields(line) + if len(f) < 1 { + continue + } + switch f[0] { + case "nameserver": // add one name server + a := conf.Servers + n := len(a) + if len(f) > 1 && n < cap(a) { + // One more check: make sure server name is + // just an IP address. Otherwise we need DNS + // to look it up. + name := f[1] + switch len(net.ParseIP(name)) { + case 16: + name = "[" + name + "]" + fallthrough + case 4: + a = a[0 : n+1] + a[n] = name + conf.Servers = a + } + } + + case "domain": // set search path to just this domain + if len(f) > 1 { + conf.Search = make([]string, 1) + conf.Search[0] = f[1] + } else { + conf.Search = make([]string, 0) + } + + case "search": // set search path to given servers + conf.Search = make([]string, len(f)-1) + for i := 0; i < len(conf.Search); i++ { + conf.Search[i] = f[i+1] + } + + case "options": // magic options + for i := 1; i < len(f); i++ { + s := f[i] + switch { + case len(s) >= 6 && s[0:6] == "ndots:": + n, _, _ := dtoi(s, 6) + if n < 1 { + n = 1 + } + conf.Ndots = n + case len(s) >= 8 && s[0:8] == "timeout:": + n, _, _ := dtoi(s, 8) + if n < 1 { + n = 1 + } + conf.Timeout = n + case len(s) >= 8 && s[0:9] == "attempts:": + n, _, _ := dtoi(s, 9) + if n < 1 { + n = 1 + } + conf.Attempts = n + case s == "rotate": + conf.Rotate = true + } + } + } + } + file.close() + return conf, nil +} diff --git a/resolver/parse.go b/resolver/parse.go new file mode 100644 index 00000000..8821836e --- /dev/null +++ b/resolver/parse.go @@ -0,0 +1,214 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Simple file i/o and string manipulation, to avoid +// depending on strconv and bufio and strings. + +package resolver + +import ( + "io" + "os" +) + +type file struct { + file *os.File + data []byte + atEOF bool +} + +func (f *file) close() { f.file.Close() } + +func (f *file) getLineFromData() (s string, ok bool) { + data := f.data + i := 0 + for i = 0; i < len(data); i++ { + if data[i] == '\n' { + s = string(data[0:i]) + ok = true + // move data + i++ + n := len(data) - i + copy(data[0:], data[i:]) + f.data = data[0:n] + return + } + } + if f.atEOF && len(f.data) > 0 { + // EOF, return all we have + s = string(data) + f.data = f.data[0:0] + ok = true + } + return +} + +func (f *file) readLine() (s string, ok bool) { + if s, ok = f.getLineFromData(); ok { + return + } + if len(f.data) < cap(f.data) { + ln := len(f.data) + n, err := io.ReadFull(f.file, f.data[ln:cap(f.data)]) + if n >= 0 { + f.data = f.data[0 : ln+n] + } + if err == os.EOF { + f.atEOF = true + } + } + s, ok = f.getLineFromData() + return +} + +func open(name string) (*file, os.Error) { + fd, err := os.Open(name, os.O_RDONLY, 0) + if err != nil { + return nil, err + } + return &file{fd, make([]byte, 1024)[0:0], false}, nil +} + +func byteIndex(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} + +// Count occurrences in s of any bytes in t. +func countAnyByte(s string, t string) int { + n := 0 + for i := 0; i < len(s); i++ { + if byteIndex(t, s[i]) >= 0 { + n++ + } + } + return n +} + +// Split s at any bytes in t. +func splitAtBytes(s string, t string) []string { + a := make([]string, 1+countAnyByte(s, t)) + n := 0 + last := 0 + for i := 0; i < len(s); i++ { + if byteIndex(t, s[i]) >= 0 { + if last < i { + a[n] = string(s[last:i]) + n++ + } + last = i + 1 + } + } + if last < len(s) { + a[n] = string(s[last:]) + n++ + } + return a[0:n] +} + +func getFields(s string) []string { return splitAtBytes(s, " \r\t\n") } + +// Bigger than we need, not too big to worry about overflow +const big = 0xFFFFFF + +// Decimal to integer starting at &s[i0]. +// Returns number, new offset, success. +func dtoi(s string, i0 int) (n int, i int, ok bool) { + n = 0 + for i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n >= big { + return 0, i, false + } + } + if i == i0 { + return 0, i, false + } + return n, i, true +} + +// Hexadecimal to integer starting at &s[i0]. +// Returns number, new offset, success. +func xtoi(s string, i0 int) (n int, i int, ok bool) { + n = 0 + for i = i0; i < len(s); i++ { + if '0' <= s[i] && s[i] <= '9' { + n *= 16 + n += int(s[i] - '0') + } else if 'a' <= s[i] && s[i] <= 'f' { + n *= 16 + n += int(s[i]-'a') + 10 + } else if 'A' <= s[i] && s[i] <= 'F' { + n *= 16 + n += int(s[i]-'A') + 10 + } else { + break + } + if n >= big { + return 0, i, false + } + } + if i == i0 { + return 0, i, false + } + return n, i, true +} + +// Integer to decimal. +func itoa(i int) string { + var buf [30]byte + n := len(buf) + neg := false + if i < 0 { + i = -i + neg = true + } + ui := uint(i) + for ui > 0 || n == len(buf) { + n-- + buf[n] = byte('0' + ui%10) + ui /= 10 + } + if neg { + n-- + buf[n] = '-' + } + return string(buf[n:]) +} + +// Number of occurrences of b in s. +func count(s string, b byte) int { + n := 0 + for i := 0; i < len(s); i++ { + if s[i] == b { + n++ + } + } + return n +} + +// Returns the prefix of s up to but not including the character c +func prefixBefore(s string, c byte) string { + for i, v := range s { + if v == int(c) { + return s[0:i] + } + } + return s +} + +// Index of rightmost occurrence of b in s. +func last(s string, b byte) int { + i := len(s) + for i--; i >= 0; i-- { + if s[i] == b { + break + } + } + return i +}