package utils import ( "bufio" "bytes" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "time" ) // Custom errors type ConfigError struct { Op string Err error } func (e *ConfigError) Error() string { return fmt.Sprintf("%s: %v", e.Op, e.Err) } // Section represents a configuration section type Section struct { Name string `json:"name"` Settings map[string]string `json:"settings"` } // SambaConfig represents the main configuration manager class type SambaConfig struct { filepath string Global Section `json:"global"` Sections map[string]Section `json:"sections"` } // validateFilePath checks if the file path is valid and accessible func validateFilePath(path string) error { if path == "" { return fmt.Errorf("file path cannot be empty") } absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("invalid path: %v", err) } dir := filepath.Dir(absPath) if _, err := os.Stat(dir); os.IsNotExist(err) { return fmt.Errorf("directory does not exist: %s", dir) } return nil } // NewSambaConfig creates a new instance of SambaConfig func NewSambaConfig(filepath string) (*SambaConfig, error) { if err := validateFilePath(filepath); err != nil { return nil, &ConfigError{Op: "new_config", Err: err} } return &SambaConfig{ filepath: filepath, Global: Section{ Name: "global", Settings: make(map[string]string), }, Sections: make(map[string]Section), }, nil } // createBackup creates a timestamped backup of the existing config file func (sc *SambaConfig) createBackup() error { // Check if original file exists if _, err := os.Stat(sc.filepath); os.IsNotExist(err) { return nil // No need to backup if file doesn't exist } // Create timestamp for backup file timestamp := time.Now().Format("20060102_150405") backupPath := fmt.Sprintf("%s.%s.bak", sc.filepath, timestamp) source, err := os.Open(sc.filepath) if err != nil { return &ConfigError{Op: "backup_open", Err: err} } defer source.Close() destination, err := os.Create(backupPath) if err != nil { return &ConfigError{Op: "backup_create", Err: err} } defer destination.Close() if _, err := destination.ReadFrom(source); err != nil { return &ConfigError{Op: "backup_copy", Err: err} } return nil } // LoadConfig reads and parses the configuration file func (sc *SambaConfig) LoadConfig() error { if _, err := os.Stat(sc.filepath); os.IsNotExist(err) { return &ConfigError{Op: "load_config", Err: fmt.Errorf("file does not exist: %s", sc.filepath)} } file, err := os.OpenFile(sc.filepath, os.O_RDONLY, 0644) if err != nil { return &ConfigError{Op: "load_config", Err: err} } defer file.Close() scanner := bufio.NewScanner(file) var currentSection Section currentSection = sc.Global const maxCapacity = 512 * 1024 buf := make([]byte, maxCapacity) scanner.Buffer(buf, maxCapacity) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { sectionName := line[1 : len(line)-1] if sectionName == "global" { currentSection = sc.Global } else { currentSection = Section{ Name: sectionName, Settings: make(map[string]string), } } continue } parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) currentSection.Settings[key] = value if currentSection.Name == "global" { sc.Global = currentSection } else { sc.Sections[currentSection.Name] = currentSection } } } if err := scanner.Err(); err != nil { return &ConfigError{Op: "load_config_scan", Err: err} } return nil } // SaveConfig updates the configuration from JSON string and saves to file // SaveConfig saves the Samba configuration after validating it func (sc *SambaConfig) SaveConfig(jsonConfig string) error { // Parse JSON config var newConfig SambaConfig if err := json.Unmarshal([]byte(jsonConfig), &newConfig); err != nil { return &ConfigError{Op: "parse_json", Err: err} } // Create backup with timestamp if err := sc.createBackup(); err != nil { return err } // Update current config with new values sc.Global = newConfig.Global sc.Sections = newConfig.Sections // Create temporary file tmpFile, err := os.CreateTemp(filepath.Dir(sc.filepath), "smb.conf.tmp") if err != nil { return &ConfigError{Op: "save_config_temp", Err: err} } tmpPath := tmpFile.Name() defer os.Remove(tmpPath) // Write to temporary file if err := sc.writeConfig(tmpFile); err != nil { tmpFile.Close() return err } tmpFile.Close() // Validate the configuration file using `testparm` cmd := exec.Command("testparm", "-s", tmpPath) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { return &ConfigError{ Op: "validate_config", Err: fmt.Errorf("validation failed: %s", stderr.String()), } } // Rename temporary file to target file if err := os.Rename(tmpPath, sc.filepath); err != nil { return &ConfigError{Op: "save_config_rename", Err: err} } // Set permissions if err := os.Chmod(sc.filepath, 0644); err != nil { return &ConfigError{Op: "save_config_chmod", Err: err} } return nil } // writeConfig writes the configuration to the provided file func (sc *SambaConfig) writeConfig(file *os.File) error { writer := bufio.NewWriter(file) // Write global section if _, err := writer.WriteString("[global]\n"); err != nil { return &ConfigError{Op: "write_config", Err: err} } for key, value := range sc.Global.Settings { if _, err := writer.WriteString(fmt.Sprintf(" %s = %s\n", key, value)); err != nil { return &ConfigError{Op: "write_config", Err: err} } } if _, err := writer.WriteString("\n"); err != nil { return &ConfigError{Op: "write_config", Err: err} } // Write other sections for _, section := range sc.Sections { if _, err := writer.WriteString(fmt.Sprintf("[%s]\n", section.Name)); err != nil { return &ConfigError{Op: "write_config", Err: err} } for key, value := range section.Settings { if _, err := writer.WriteString(fmt.Sprintf(" %s = %s\n", key, value)); err != nil { return &ConfigError{Op: "write_config", Err: err} } } if _, err := writer.WriteString("\n"); err != nil { return &ConfigError{Op: "write_config", Err: err} } } return writer.Flush() } func main() { // Example usage with JSON config, err := NewSambaConfig("utils/smb.conf") if err != nil { fmt.Printf("Error creating config: %v\n", err) return } // Load existing config if err := config.LoadConfig(); err != nil { fmt.Printf("Error loading config: %v\n", err) return } // Example JSON configuration jsonConfig := `{ "global": { "name": "global", "settings": { "workgroup": "WORKGROUP", "server string": "Samba Server" } }, "sections": { "share1": { "name": "share1", "settings": { "path": "/path/to/share1", "valid users": "@users", "writeable": "yes" } } } }` // Update config with JSON if err := config.SaveConfig(jsonConfig); err != nil { fmt.Printf("Error saving config: %v\n", err) return } }