-
Notifications
You must be signed in to change notification settings - Fork 196
/
Copy pathparser.go
144 lines (124 loc) · 3.31 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package parser
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"github.com/schollz/progressbar/v3"
)
type OnResultFN func(domain string, ip []string) error
func ParseFile(filename string, onResult OnResultFN) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
return ParseReader(file, onResult)
}
// Parse parses the massdns output returning the found
// domain and ip pair to a onResult function.
func ParseReader(reader io.Reader, onResult OnResultFN) error {
var (
// Some boolean various needed for state management
cnameStart bool
nsStart bool
// Result variables to store the results
domain string
ip []string
)
// Parse the input line by line and act on what the line means
scanner := bufio.NewScanner(reader)
// Create a progress bar
var totalLines int
for scanner.Scan() {
totalLines++
}
if err := scanner.Err(); err != nil {
return err
}
// Reset the reader
readSeeker, ok := reader.(io.ReadSeeker)
if ok {
_, err := readSeeker.Seek(0, io.SeekStart)
if err != nil {
return err
}
} else {
return fmt.Errorf("reader seeking error")
}
// Re-initialize the scanner
scanner = bufio.NewScanner(reader)
bar := progressbar.Default(int64(totalLines))
for scanner.Scan() {
text := scanner.Text()
// Update prograss bar
bar.Add(1)
// Empty line represents a seperator between DNS reply
// due to `-o Snl` option set in massdns. Thus it can be
// interpreted as a DNS answer header.
//
// If we have start of a DNS answer header, set the
// bool state to default, and return the results to the
// consumer via the callback.
if text == "" {
if domain != "" {
cnameStart, nsStart = false, false
if err := onResult(domain, ip); err != nil {
return err
}
domain, ip = "", nil
}
} else {
// Non empty line represents DNS answer section, we split on space,
// iterate over all the parts, and write the answer to the struct.
parts := strings.Split(text, " ")
if len(parts) != 3 {
continue
}
// Switch on the record type, deciding what to do with
// a record based on the type of record.
switch parts[1] {
case "NS":
// If we have a NS record, then set nsStart
// which will ignore all the next records
nsStart = true
case "CNAME":
// If we have a CNAME record, then the next record should be
// the values for the CNAME record, so set the cnameStart value.
//
// Use the domain in the first cname field since the next fields for
// A record may contain domain for secondary CNAME which messes
// up recursive CNAME records.
if !cnameStart {
nsStart = false
domain = strings.TrimSuffix(parts[0], ".")
cnameStart = true
}
case "A":
// If we have an A record, check if it's not after
// an NS record. If not, append it to the ips.
//
// Also if we aren't inside a CNAME block, set the domain too.
if !nsStart {
if !cnameStart && domain == "" {
domain = strings.TrimSuffix(parts[0], ".")
}
ip = append(ip, parts[2])
}
}
}
}
// Return error if there was any.
if err := scanner.Err(); err != nil {
return err
}
// Final callback to deliver the last piece of result
// if there's any.
if domain != "" {
if err := onResult(domain, ip); err != nil {
return err
}
}
return nil
}