samba.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package samba
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "os"
  8. "path/filepath"
  9. "runtime"
  10. "strings"
  11. "time"
  12. "imuslab.com/arozos/mod/user"
  13. "imuslab.com/arozos/mod/utils"
  14. )
  15. /*
  16. Samba Share Warpper
  17. Note that this module only provide exposing of local disk / storage.
  18. This module do not handle providing the virtualized interface for samba
  19. */
  20. type ShareManager struct {
  21. SambaConfigPath string //Config file for samba, aka smb.conf
  22. BackupDir string //Backup directory for restoring previous config
  23. UserHandler *user.UserHandler
  24. }
  25. type ShareConfig struct {
  26. Name string
  27. Path string
  28. ValidUsers []string
  29. ReadOnly bool
  30. Browseable bool
  31. GuestOk bool
  32. }
  33. func NewSambaShareManager() (*ShareManager, error) {
  34. if runtime.GOOS == "linux" {
  35. //Check if samba installed
  36. if !utils.FileExists("/bin/smbcontrol") {
  37. return nil, errors.New("samba not installed")
  38. }
  39. } else {
  40. return nil, errors.New("platform not supported")
  41. }
  42. return &ShareManager{
  43. SambaConfigPath: "/etc/samba/smb.conf",
  44. BackupDir: "./backup",
  45. }, nil
  46. }
  47. // ReadSambaShares reads the smb.conf file and extracts all existing shares
  48. func (s *ShareManager) ReadSambaShares() ([]ShareConfig, error) {
  49. file, err := os.Open(s.SambaConfigPath)
  50. if err != nil {
  51. return nil, err
  52. }
  53. defer file.Close()
  54. var shares []ShareConfig
  55. var currentShare *ShareConfig
  56. scanner := bufio.NewScanner(file)
  57. for scanner.Scan() {
  58. line := strings.TrimSpace(scanner.Text())
  59. // Check for section headers
  60. if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
  61. if currentShare != nil {
  62. shares = append(shares, *currentShare)
  63. }
  64. currentShare = &ShareConfig{
  65. Name: strings.Trim(line, "[]"),
  66. }
  67. continue
  68. }
  69. // Check if we are currently processing a share section
  70. if currentShare != nil {
  71. tokens := strings.SplitN(line, "=", 2)
  72. if len(tokens) != 2 {
  73. continue
  74. }
  75. key := strings.TrimSpace(tokens[0])
  76. value := strings.TrimSpace(tokens[1])
  77. switch key {
  78. case "path":
  79. currentShare.Path = value
  80. case "valid users":
  81. currentShare.ValidUsers = strings.Fields(value)
  82. case "read only":
  83. currentShare.ReadOnly = (value == "yes")
  84. case "browseable":
  85. currentShare.Browseable = (value == "yes")
  86. case "guest ok":
  87. currentShare.GuestOk = (value == "yes")
  88. }
  89. }
  90. }
  91. // Add the last share if there is one
  92. if currentShare != nil {
  93. shares = append(shares, *currentShare)
  94. }
  95. // Check for scanner errors
  96. if err := scanner.Err(); err != nil {
  97. return nil, err
  98. }
  99. return shares, nil
  100. }
  101. // Check if a share name is already used
  102. func (s *ShareManager) ShareNameExists(sharename string) (bool, error) {
  103. allShares, err := s.ReadSambaShares()
  104. if err != nil {
  105. return false, err
  106. }
  107. for _, share := range allShares {
  108. if strings.EqualFold(share.Name, sharename) {
  109. return true, nil
  110. }
  111. }
  112. return false, nil
  113. }
  114. // A basic filter to remove system created smb shares entry in the list
  115. func (s *ShareManager) FilterSystemCreatedShares(shares []ShareConfig) []ShareConfig {
  116. namesToRemove := []string{"global", "homes", "printers", "print$"}
  117. filteredShares := []ShareConfig{}
  118. for _, share := range shares {
  119. if !utils.StringInArray(namesToRemove, share.Name) {
  120. filteredShares = append(filteredShares, share)
  121. }
  122. }
  123. return filteredShares
  124. }
  125. // CreateNewSambaShare converts the shareConfig to string and appends it to smb.conf if the share name does not already exist
  126. func (s *ShareManager) CreateNewSambaShare(shareToCreate *ShareConfig) error {
  127. // Path to smb.conf
  128. smbConfPath := s.SambaConfigPath
  129. // Open the smb.conf file for reading
  130. file, err := os.Open(smbConfPath)
  131. if err != nil {
  132. return fmt.Errorf("failed to open smb.conf: %v", err)
  133. }
  134. defer file.Close()
  135. // Check if the share already exists
  136. scanner := bufio.NewScanner(file)
  137. shareExists := false
  138. shareNameSection := fmt.Sprintf("[%s]", shareToCreate.Name)
  139. for scanner.Scan() {
  140. if strings.TrimSpace(scanner.Text()) == shareNameSection {
  141. shareExists = true
  142. break
  143. }
  144. }
  145. if err := scanner.Err(); err != nil {
  146. return fmt.Errorf("failed to read smb.conf: %v", err)
  147. }
  148. if shareExists {
  149. return fmt.Errorf("share %s already exists", shareToCreate.Name)
  150. }
  151. // Convert ShareConfig to string
  152. shareConfigString := convertShareConfigToString(shareToCreate)
  153. // Open the smb.conf file for appending
  154. file, err = os.OpenFile(smbConfPath, os.O_APPEND|os.O_WRONLY, 0644)
  155. if err != nil {
  156. return fmt.Errorf("failed to open smb.conf for writing: %v", err)
  157. }
  158. defer file.Close()
  159. // Append the new share configuration
  160. if _, err := file.WriteString(shareConfigString); err != nil {
  161. return fmt.Errorf("failed to write to smb.conf: %v", err)
  162. }
  163. return nil
  164. }
  165. // RemoveSambaShareConfig removes the Samba share configuration from smb.conf
  166. func (s *ShareManager) RemoveSambaShareConfig(shareName string) error {
  167. // Open the smb.conf file for reading
  168. file, err := os.Open(s.SambaConfigPath)
  169. if err != nil {
  170. return err
  171. }
  172. defer file.Close()
  173. // Create a temporary file to store modified smb.conf
  174. tmpFile, err := os.CreateTemp("", "smb.conf.*.tmp")
  175. if err != nil {
  176. return err
  177. }
  178. defer tmpFile.Close()
  179. // Create a scanner to read the smb.conf file line by line
  180. scanner := bufio.NewScanner(file)
  181. for scanner.Scan() {
  182. line := scanner.Text()
  183. // Check if the line contains the share name
  184. if strings.HasPrefix(line, "["+shareName+"]") {
  185. // Skip the lines until the next section
  186. for scanner.Scan() {
  187. if strings.HasPrefix(scanner.Text(), "[") {
  188. break
  189. }
  190. }
  191. continue // Skip writing the share configuration to the temporary file
  192. }
  193. // Write the line to the temporary file
  194. _, err := fmt.Fprintln(tmpFile, line)
  195. if err != nil {
  196. return err
  197. }
  198. }
  199. // Check for scanner errors
  200. if err := scanner.Err(); err != nil {
  201. return err
  202. }
  203. // Close the original smb.conf file
  204. if err := file.Close(); err != nil {
  205. return err
  206. }
  207. // Close the temporary file
  208. if err := tmpFile.Close(); err != nil {
  209. return err
  210. }
  211. // Replace the original smb.conf file with the temporary file
  212. if err := os.Rename(tmpFile.Name(), "/etc/samba/smb.conf"); err != nil {
  213. return err
  214. }
  215. return nil
  216. }
  217. // ShareExists checks if a given share name exists in smb.conf
  218. func (s *ShareManager) ShareExists(shareName string) (bool, error) {
  219. // Path to smb.conf
  220. smbConfPath := s.SambaConfigPath
  221. // Open the smb.conf file for reading
  222. file, err := os.Open(smbConfPath)
  223. if err != nil {
  224. return false, fmt.Errorf("failed to open smb.conf: %v", err)
  225. }
  226. defer file.Close()
  227. // Check if the share already exists
  228. scanner := bufio.NewScanner(file)
  229. shareNameSection := fmt.Sprintf("[%s]", shareName)
  230. for scanner.Scan() {
  231. if strings.TrimSpace(scanner.Text()) == shareNameSection {
  232. return true, nil
  233. }
  234. }
  235. if err := scanner.Err(); err != nil {
  236. return false, fmt.Errorf("failed to read smb.conf: %v", err)
  237. }
  238. return false, nil
  239. }
  240. // Backup the current smb.conf to the backup folder
  241. func (s *ShareManager) BackupSmbConf() error {
  242. // Define source and backup directory
  243. sourceFile := s.SambaConfigPath
  244. backupDir := s.BackupDir
  245. // Ensure the backup directory exists
  246. err := os.MkdirAll(backupDir, 0755)
  247. if err != nil {
  248. return fmt.Errorf("failed to create backup directory: %v", err)
  249. }
  250. // Create a timestamped backup filename
  251. timestamp := time.Now().Format("20060102_150405")
  252. backupFile := filepath.Join(backupDir, fmt.Sprintf("%s.smb.conf", timestamp))
  253. // Open the source file
  254. src, err := os.Open(sourceFile)
  255. if err != nil {
  256. return fmt.Errorf("failed to open source file: %v", err)
  257. }
  258. defer src.Close()
  259. // Create the destination file
  260. dst, err := os.Create(backupFile)
  261. if err != nil {
  262. return fmt.Errorf("failed to create backup file: %v", err)
  263. }
  264. defer dst.Close()
  265. // Copy the contents of the source file to the backup file
  266. _, err = io.Copy(dst, src)
  267. if err != nil {
  268. return fmt.Errorf("failed to copy file contents: %v", err)
  269. }
  270. return nil
  271. }
  272. // Add a new user to smb.conf share by name
  273. func (s *ShareManager) AddUserToSambaShare(shareName, username string) error {
  274. // Open the smb.conf file for reading
  275. file, err := os.Open(s.SambaConfigPath)
  276. if err != nil {
  277. return fmt.Errorf("failed to open smb.conf: %v", err)
  278. }
  279. defer file.Close()
  280. var lines []string
  281. var insideShare bool
  282. var shareExists bool
  283. var userAdded bool
  284. scanner := bufio.NewScanner(file)
  285. for scanner.Scan() {
  286. line := scanner.Text()
  287. lines = append(lines, line)
  288. // Check if we are inside the specified share section
  289. if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
  290. if insideShare && shareExists && !userAdded {
  291. // Add the username to the valid users list
  292. lines = append(lines, " valid users = @"+username)
  293. userAdded = true
  294. }
  295. insideShare = false
  296. }
  297. if strings.TrimSpace(line) == fmt.Sprintf("[%s]", shareName) {
  298. insideShare = true
  299. shareExists = true
  300. }
  301. if insideShare && strings.HasPrefix(strings.TrimSpace(line), "valid users =") {
  302. // Check if the username already exists in the valid users list
  303. validUsersLine := strings.TrimSpace(line)
  304. if !strings.Contains(validUsersLine, username) {
  305. lines[len(lines)-1] = validUsersLine + ", @" + username
  306. userAdded = true
  307. }
  308. }
  309. }
  310. if err := scanner.Err(); err != nil {
  311. return fmt.Errorf("error reading smb.conf: %v", err)
  312. }
  313. if !shareExists {
  314. return fmt.Errorf("share [%s] not found in smb.conf", shareName)
  315. }
  316. if !userAdded {
  317. // If no valid users line was found, add the username to the share
  318. lines = append(lines, fmt.Sprintf("[%s]", shareName))
  319. lines = append(lines, " valid users = @"+username)
  320. }
  321. // Write the updated configuration back to the smb.conf file
  322. outputFile, err := os.Create(s.SambaConfigPath)
  323. if err != nil {
  324. return fmt.Errorf("failed to open smb.conf for writing: %v", err)
  325. }
  326. defer outputFile.Close()
  327. writer := bufio.NewWriter(outputFile)
  328. for _, line := range lines {
  329. _, err := writer.WriteString(line + "\n")
  330. if err != nil {
  331. return fmt.Errorf("error writing to smb.conf: %v", err)
  332. }
  333. }
  334. writer.Flush()
  335. return nil
  336. }