package shorewall import ( "bufio" "fmt" "os" "path/filepath" "strings" ) // ParseFile reads a shorewall columnar config file and returns rows of fields. // Handles comments (#), blank lines, line continuation (\), and ?COMMENT directives. func ParseFile(path string) ([][]string, error) { f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("opening %s: %w", path, err) } defer f.Close() var rows [][]string var continuation string scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasSuffix(line, "\\") { continuation += strings.TrimSuffix(line, "\\") + " " continue } if continuation != "" { line = continuation + line continuation = "" } line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } if strings.HasPrefix(line, "SECTION") || strings.HasPrefix(line, "?SECTION") { raw := strings.TrimPrefix(line, "?") rows = append(rows, []string{"?SECTION", strings.TrimSpace(strings.TrimPrefix(raw, "SECTION"))}) continue } if strings.HasPrefix(line, "?") { continue } fields := splitFields(line) if len(fields) > 0 { rows = append(rows, fields) } } return rows, scanner.Err() } // splitFields splits a shorewall config line into fields. // Fields are whitespace-separated, but supports the { key=value ... } alternate syntax. func splitFields(line string) []string { var fields []string for _, f := range strings.Fields(line) { if f == "#" { break } if strings.HasPrefix(f, "#") { break } fields = append(fields, f) } return fields } // ParseConf reads a shorewall.conf (key=value) file into a map. func ParseConf(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() conf := make(map[string]string) scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } idx := strings.IndexByte(line, '=') if idx < 0 { continue } key := strings.TrimSpace(line[:idx]) val := strings.TrimSpace(line[idx+1:]) val = strings.Trim(val, "\"'") if key != "" { conf[key] = val } } return conf, scanner.Err() } // ParseParams reads a shorewall params file and extracts variable assignments. // This is simplified — it handles VAR=value lines but not full shell evaluation. func ParseParams(path string) (map[string]string, error) { f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } defer f.Close() params := make(map[string]string) scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } idx := strings.IndexByte(line, '=') if idx < 0 { continue } key := strings.TrimSpace(line[:idx]) val := strings.TrimSpace(line[idx+1:]) val = strings.Trim(val, "\"'") if key != "" && !strings.ContainsAny(key, " \t$(){}") { params[key] = val } } return params, scanner.Err() } // field returns the nth field (0-based) from a row, or "-" if missing. func field(row []string, n int) string { if n >= len(row) { return "-" } return row[n] } // isDash returns true if the field is empty or a dash. func isDash(s string) bool { return s == "" || s == "-" } // DirExists checks if a shorewall or shorewall6 config directory looks valid. func DirExists(dir string) bool { for _, name := range []string{"zones", "shorewall.conf", "shorewall6.conf"} { info, err := os.Stat(filepath.Join(dir, name)) if err == nil && !info.IsDir() { return true } } return false } // IsIPv6Dir returns true if the directory contains a shorewall6 config. func IsIPv6Dir(dir string) bool { info, err := os.Stat(filepath.Join(dir, "shorewall6.conf")) return err == nil && !info.IsDir() }