8003db49b3
Samba shares now support an 'invalid users' list (deny list), written as 'invalid users = u1,u2' in smb.conf. The UI shows a ChipPicker for valid_users and invalid_users, mutually exclusive, sourced from the system user list. feat: add ImportSystemUsers for fresh installations When NASCTL_IMPORT_ON_BOOT=true, nasctl now imports existing system users from /etc/passwd (UID 1000-60000) and /etc/group (supplemental groups), and detects which have Samba accounts via 'pdbedit -L'. Imported users are marked dirty so the admin can review before applying. New POST /api/import/users endpoint for manual re-import. This mirrors the existing import-on-boot flow for smb.conf and /etc/exports.
156 lines
2.9 KiB
Go
156 lines
2.9 KiB
Go
package importer
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/darroyo/nasctl/internal/db"
|
|
"github.com/darroyo/nasctl/internal/system"
|
|
)
|
|
|
|
const (
|
|
UserMinUID = 1000
|
|
UserMaxUID = 60000
|
|
)
|
|
|
|
var excludedUsernames = map[string]bool{
|
|
"nobody": true,
|
|
"nogroup": true,
|
|
"sshd": true,
|
|
"systemd": true,
|
|
"messagebus": true,
|
|
"polkitd": true,
|
|
}
|
|
|
|
func ImportSystemUsers(ctx context.Context, adminUsername string) ([]db.User, error) {
|
|
passwdMap, err := readPasswd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
groupMap, err := readGroups()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
smbUsers, err := listSMBUsers(ctx)
|
|
if err != nil {
|
|
smbUsers = map[string]bool{}
|
|
}
|
|
|
|
var users []db.User
|
|
for username, uid := range passwdMap {
|
|
if uid < UserMinUID || uid > UserMaxUID {
|
|
continue
|
|
}
|
|
if excludedUsernames[username] {
|
|
continue
|
|
}
|
|
if username == adminUsername {
|
|
continue
|
|
}
|
|
users = append(users, db.User{
|
|
Username: username,
|
|
Groups: groupMap[username],
|
|
SMBEnabled: smbUsers[username],
|
|
Disabled: false,
|
|
})
|
|
}
|
|
|
|
sort.Slice(users, func(i, j int) bool {
|
|
return users[i].Username < users[j].Username
|
|
})
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func readPasswd() (map[string]int, error) {
|
|
f, err := os.Open("/etc/passwd")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
result := make(map[string]int)
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
username := parts[0]
|
|
uid, err := strconv.Atoi(parts[2])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
result[username] = uid
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func readGroups() (map[string][]string, error) {
|
|
f, err := os.Open("/etc/group")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
result := make(map[string][]string)
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) < 4 {
|
|
continue
|
|
}
|
|
membersStr := strings.TrimSpace(parts[3])
|
|
if membersStr == "" {
|
|
continue
|
|
}
|
|
for _, member := range strings.Split(membersStr, ",") {
|
|
member = strings.TrimSpace(member)
|
|
if member != "" {
|
|
result[member] = append(result[member], parts[0])
|
|
}
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func listSMBUsers(ctx context.Context) (map[string]bool, error) {
|
|
stdout, _, err := system.Run(ctx, "pdbedit", "-L")
|
|
if err != nil {
|
|
return map[string]bool{}, nil
|
|
}
|
|
result := make(map[string]bool)
|
|
for _, line := range strings.Split(stdout, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) < 1 {
|
|
continue
|
|
}
|
|
result[parts[0]] = true
|
|
}
|
|
return result, nil
|
|
}
|