-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
223 lines (186 loc) · 5.31 KB
/
main.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package main
import (
"bufio"
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
// StringSliceFlag is a custom flag type for string slices
type StringSliceFlag []string
// String returns the string representation of the flag value
func (s *StringSliceFlag) String() string {
return fmt.Sprintf("%v", *s)
}
// Set adds a value to the flag
func (s *StringSliceFlag) Set(value string) error {
*s = append(*s, value)
return nil
}
func main() {
// Define command-line flags
var inputFiles StringSliceFlag
var excludeFiles StringSliceFlag
outputFlag := flag.String("o", "", "Output file path")
wildcardFlag := flag.Bool("w", false, "Include wildcard blocking")
flag.Var(&inputFiles, "i", "Input file paths or URLs")
flag.Var(&excludeFiles, "e", "File paths or URLs containing domains to exclude")
flag.Parse()
// Check if input files or URLs are provided
if len(inputFiles) == 0 {
fmt.Println("Input file paths or URLs are required")
return
}
// Check if output file path is provided
if *outputFlag == "" {
fmt.Println("Output file path is required")
return
}
// Create the output file
outputFile, err := os.Create(*outputFlag)
if err != nil {
fmt.Println("Error creating the output file:", err)
return
}
defer outputFile.Close()
// Write the RPZ file header
header := generateRPZHeader()
_, err = fmt.Fprint(outputFile, header)
if err != nil {
fmt.Println("Error writing to the output file:", err)
return
}
// Create a set to store excluded domains
excludedDomains := make(map[string]bool)
// Create a set to store unique hosts
uniqueHosts := make(map[string]bool)
// Process each exclude file or URL
for _, excludeFile := range excludeFiles {
// Trim whitespace from the input
excludeFile = strings.TrimSpace(excludeFile)
// Read the exclude file or URL
var file io.Reader
if strings.HasPrefix(excludeFile, "http://") || strings.HasPrefix(excludeFile, "https://") {
resp, err := http.Get(excludeFile)
if err != nil {
fmt.Println("Error retrieving the exclude file:", err)
continue
}
file = resp.Body
} else {
file, err = os.Open(excludeFile)
if err != nil {
fmt.Println("Error opening the exclude file:", err)
continue
}
}
scanner := bufio.NewScanner(file)
// Process each line in the exclude file
for scanner.Scan() {
domain := strings.TrimSpace(scanner.Text())
// Skip empty lines and comments
if domain == "" || strings.HasPrefix(domain, "#") {
continue
}
// Add domain to the excluded domains set
excludedDomains[domain] = true
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading the exclude file:", err)
continue
}
if closer, ok := file.(io.Closer); ok {
closer.Close()
}
}
// Process each input file or URL
for _, input := range inputFiles {
// Trim whitespace from the input
input = strings.TrimSpace(input)
// Read the input file or URL
var file io.Reader
if strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://") {
resp, err := http.Get(input)
if err != nil {
fmt.Println("Error retrieving the input file:", err)
continue
}
file = resp.Body
} else {
file, err = os.Open(input)
if err != nil {
fmt.Println("Error opening the input file:", err)
continue
}
}
scanner := bufio.NewScanner(file)
// Process each line in the input file
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Skip empty lines and comments
if line == "" || strings.HasPrefix(line, "#") {
continue
}
// Skip comments in the middle of the line
parts := strings.SplitN(line, "#", 2)
line = strings.TrimSpace(parts[0])
parts = strings.Fields(line)
if len(parts) != 2 {
fmt.Println("Invalid input line:", line)
continue
}
ip := parts[0]
host := parts[1]
// Check if the domain should be excluded
if excludedDomains[host] {
continue
}
// Check if the host is already encountered
if uniqueHosts[host] {
continue
}
// Add host to the unique hosts set
uniqueHosts[host] = true
// Write the RPZ record with A record
_, err = fmt.Fprintf(outputFile, "%s IN A %s\n", host, ip)
if err != nil {
fmt.Println("Error writing to the output file:", err)
return
}
// Check if IP is "0.0.0.0" and include wildcard record
if *wildcardFlag && ip == "0.0.0.0" {
wildcardHost := "*." + host
_, err = fmt.Fprintf(outputFile, "%s IN A %s\n", wildcardHost, ip)
if err != nil {
fmt.Println("Error writing to the output file:", err)
return
}
}
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading the input file:", err)
continue
}
if closer, ok := file.(io.Closer); ok {
closer.Close()
}
}
fmt.Println("Conversion completed successfully!")
}
// generateRPZHeader generates the RPZ file header with the current date in the format YYYYMMDD.
func generateRPZHeader() string {
date := time.Now().Format("20060102") // Format: YYYYMMDD
header := fmt.Sprintf("$TTL 60\n"+
"@ IN SOA localhost. filters.puredns.org. (\n"+
" %s ; serial\n"+
" 2w ; refresh\n"+
" 2w ; retry\n"+
" 2w ; expiry\n"+
" 2w ; minimum\n"+
" IN NS localhost.\n"+
")\n\n", date)
return header
}