300 lines
7.1 KiB
Go
300 lines
7.1 KiB
Go
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
|
|
}
|
|
}
|