package importer import ( "bufio" "bytes" "fmt" "os" "strings" "github.com/darroyo/nasctl/internal/db" "github.com/darroyo/nasctl/internal/system" ) var ( sambaIgnoredSections = map[string]bool{ "global": true, "printers": true, "homes": true, } ) type shareBuilder struct { Name string Path string Comment string ReadOnly bool GuestOK bool ValidUsers []string ValidGroups []string } func ImportSambaShares(path string) ([]db.SambaShare, error) { data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("read smb.conf: %w", err) } return parseSambaConf(data) } func parseSambaConf(data []byte) ([]db.SambaShare, error) { var shares []db.SambaShare var current *shareBuilder scanner := bufio.NewScanner(bytes.NewReader(data)) var lineNum int var continuation string for scanner.Scan() { lineNum++ raw := scanner.Text() if continuation != "" { continuation += "\n" + raw if strings.HasSuffix(raw, "\\") { continuation = strings.TrimSuffix(continuation, "\\") continue } raw = continuation continuation = "" } else if strings.HasSuffix(strings.TrimSpace(raw), "\\") { continuation = strings.TrimSuffix(strings.TrimSpace(raw), "\\") continue } raw = strings.TrimSpace(raw) if raw == "" || startsWith(raw, '#', ';') { continue } if strings.HasPrefix(raw, "[") { if current != nil && current.Name != "" && current.Path != "" { if share := currentToShare(current); share.Name != "" { shares = append(shares, share) } } sectionName := parseSectionName(raw) if sectionName == "" || sambaIgnoredSections[strings.ToLower(sectionName)] { current = nil continue } current = &shareBuilder{Name: sectionName} continue } if current == nil { continue } key, value := parseKeyValue(raw) if key == "" { continue } switch strings.ToLower(key) { case "path": current.Path = value case "comment": current.Comment = value case "read only": current.ReadOnly = parseBool(value) case "writable": current.ReadOnly = !parseBool(value) case "guest ok": current.GuestOK = parseBool(value) case "valid users": current.ValidUsers = parseCommaList(value) case "valid groups": current.ValidGroups = parseCommaList(value) case "include", "copy", "inherit owner", "inherit permissions", "nt acl support", "printable", "print mode", "lppause command", "lpresume command", "queuepause command", "queueresume command": } } if current != nil && current.Name != "" && current.Path != "" { if share := currentToShare(current); share.Name != "" { shares = append(shares, share) } } return shares, nil } func startsWith(s string, chars ...byte) bool { for _, c := range chars { if len(s) > 0 && s[0] == c { return true } } return false } func parseSectionName(raw string) string { raw = strings.TrimSpace(raw) if !strings.HasPrefix(raw, "[") || !strings.HasSuffix(raw, "]") { return "" } return strings.Trim(raw, "[]") } func parseKeyValue(raw string) (key, value string) { eq := strings.IndexByte(raw, '=') if eq < 0 { return "", "" } key = strings.TrimSpace(raw[:eq]) value = strings.TrimSpace(raw[eq+1:]) value = strings.Trim(value, "\"") value = strings.TrimPrefix(value, "\"") value = strings.TrimSuffix(value, "\"") return key, value } func parseBool(s string) bool { s = strings.ToLower(strings.TrimSpace(s)) return s == "yes" || s == "true" || s == "1" || s == "on" } func parseCommaList(s string) []string { s = strings.TrimSpace(s) if s == "" { return nil } parts := strings.Split(s, ",") var out []string for _, p := range parts { p = strings.TrimSpace(p) if p != "" { out = append(out, p) } } return out } func currentToShare(b *shareBuilder) db.SambaShare { if b.Name == "" || b.Path == "" { return db.SambaShare{} } if err := system.ValidateShareName(b.Name); err != nil { return db.SambaShare{} } if err := system.ValidatePath(b.Path); err != nil { return db.SambaShare{} } for _, u := range b.ValidUsers { if err := system.ValidateUsername(u); err != nil { return db.SambaShare{} } } return db.SambaShare{ Name: b.Name, Path: b.Path, Comment: b.Comment, ReadOnly: b.ReadOnly, GuestOK: b.GuestOK, ValidUsers: b.ValidUsers, ValidGroups: b.ValidGroups, } }