main
6d86999 ยท 6 months ago 26 commits
  1package ufw
  2
  3import (
  4	"bufio"
  5	"regexp"
  6	"strconv"
  7	"strings"
  8)
  9
 10func ParseRules(output string) []Rule {
 11	var rules []Rule
 12	reNumbered := regexp.MustCompile(`^\[\s*(\d+)\]\s+(.+)$`)
 13	scanner := bufio.NewScanner(strings.NewReader(output))
 14
 15	for scanner.Scan() {
 16		line := scanner.Text()
 17
 18		if shouldSkipLine(line) {
 19			continue
 20		}
 21
 22		matches := reNumbered.FindStringSubmatch(line)
 23		if len(matches) < 3 {
 24			continue
 25		}
 26
 27		num, _ := strconv.Atoi(matches[1])
 28		ruleLine := strings.TrimSpace(matches[2])
 29
 30		rule := parseRuleLine(num, ruleLine)
 31		rules = append(rules, rule)
 32	}
 33
 34	return rules
 35}
 36
 37func shouldSkipLine(line string) bool {
 38	trimmed := strings.TrimSpace(line)
 39
 40	if trimmed == "" {
 41		return true
 42	}
 43
 44	if strings.Contains(line, "Status:") {
 45		return true
 46	}
 47
 48	if strings.Contains(line, "To") && strings.Contains(line, "Action") && strings.Contains(line, "From") {
 49		return true
 50	}
 51
 52	if strings.HasPrefix(trimmed, "--") || strings.HasPrefix(trimmed, "==") {
 53		return true
 54	}
 55
 56	return false
 57}
 58
 59func parseRuleLine(num int, line string) Rule {
 60	rule := Rule{
 61		Num: num,
 62		Raw: line,
 63	}
 64
 65	rule.IPv6 = strings.Contains(line, "(v6)")
 66
 67	commentIdx := strings.Index(line, "#")
 68	if commentIdx >= 0 {
 69		rule.Comment = strings.TrimSpace(line[commentIdx+1:])
 70		line = strings.TrimSpace(line[:commentIdx])
 71	}
 72
 73	parts := splitByMultipleSpaces(line)
 74
 75	if len(parts) < 3 {
 76		return rule
 77	}
 78
 79	actionIndex := findActionIndex(parts)
 80	if actionIndex == -1 {
 81		return rule
 82	}
 83
 84	actionParts := strings.Fields(parts[actionIndex])
 85	if len(actionParts) > 0 {
 86		rule.Action = actionParts[0]
 87	}
 88
 89	if actionIndex > 0 {
 90		toField := strings.Join(parts[:actionIndex], " ")
 91		rule.ToDest, rule.ToPort, rule.ToProtocol = parseDestination(toField)
 92	}
 93
 94	if actionIndex+1 < len(parts) {
 95		fromField := strings.Join(parts[actionIndex+1:], " ")
 96		rule.FromSource, rule.FromPort = parseSource(fromField)
 97	}
 98
 99	return rule
100}
101
102func findActionIndex(parts []string) int {
103	for i, part := range parts {
104		upper := strings.ToUpper(strings.Fields(part)[0])
105		if upper == ActionAllow || upper == ActionDeny || upper == ActionReject || upper == ActionLimit {
106			return i
107		}
108	}
109	return -1
110}
111
112func splitByMultipleSpaces(s string) []string {
113	re := regexp.MustCompile(`\s{2,}`)
114	parts := re.Split(s, -1)
115
116	cleaned := make([]string, 0, len(parts))
117	for _, part := range parts {
118		trimmed := strings.TrimSpace(part)
119		if trimmed != "" {
120			cleaned = append(cleaned, trimmed)
121		}
122	}
123
124	return cleaned
125}
126
127func parseDestination(s string) (dest, port, protocol string) {
128	s = strings.TrimSpace(s)
129
130	if strings.HasPrefix(s, "Anywhere") {
131		return s, "any", "any"
132	}
133
134	spaceParts := strings.Fields(s)
135	if len(spaceParts) >= 2 && isIPOrCIDR(spaceParts[0]) {
136		dest = spaceParts[0]
137		port, protocol = parsePortSpec(strings.Join(spaceParts[1:], " "))
138		return dest, port, protocol
139	}
140
141	if isIPOrCIDR(s) {
142		return s, "any", "any"
143	}
144
145	port, protocol = parsePortSpec(s)
146	return "Anywhere", port, protocol
147}
148
149func parseSource(s string) (source, port string) {
150	s = strings.TrimSpace(s)
151
152	if strings.HasPrefix(s, "Anywhere") {
153		return s, "any"
154	}
155
156	if strings.Contains(s, " ") {
157		parts := strings.Fields(s)
158		if len(parts) >= 2 {
159			return parts[0], parts[1]
160		}
161	}
162
163	return s, "any"
164}
165
166func isIPOrCIDR(s string) bool {
167	if strings.Contains(s, ".") {
168		parts := strings.Split(s, "/")
169		if strings.Contains(parts[0], ".") {
170			return true
171		}
172	}
173
174	colonCount := strings.Count(s, ":")
175	if colonCount > 1 {
176		return true
177	}
178
179	return false
180}
181
182func parsePortSpec(s string) (port, protocol string) {
183	s = strings.TrimSpace(s)
184
185	if strings.Contains(s, "/") {
186		parts := strings.SplitN(s, "/", 2)
187		if len(parts) == 2 {
188			portNum := parts[0]
189			protoWithExtra := parts[1]
190
191			protoWords := strings.Fields(protoWithExtra)
192			if len(protoWords) > 0 {
193				protocol = strings.ToLower(protoWords[0])
194				return portNum, protocol
195			}
196		}
197	}
198
199	if strings.Contains(s, ":") && !strings.Contains(s, "::") {
200		return s, "any"
201	}
202
203	return s, "any"
204}