diff --git a/cmd/cubby/main.go b/cmd/cubby/main.go index e58acbf..692a61f 100644 --- a/cmd/cubby/main.go +++ b/cmd/cubby/main.go @@ -8,6 +8,7 @@ import ( "net" "os" "os/signal" + "os/user" "strconv" "strings" "sync" @@ -257,20 +258,58 @@ func tokenize(line string) ([]string, error) { return args, nil } +// lookupGID resolves a group name or numeric GID string to a GID. +func lookupGID(s string) (int, error) { + if n, err := strconv.Atoi(s); err == nil { + return n, nil + } + g, err := user.LookupGroup(s) + if err != nil { + return 0, fmt.Errorf("lookup group %q: %w", s, err) + } + n, err := strconv.Atoi(g.Gid) + if err != nil { + return 0, fmt.Errorf("parse gid %q: %w", g.Gid, err) + } + return n, nil +} + func main() { sock := flag.String("socket", "/tmp/cubby.sock", "unix socket path") + group := flag.String("group", "", "group name or GID to share the socket with (mode 0660); default is owner-only (0600)") flag.Parse() + mode := os.FileMode(0600) + gid := -1 + if *group != "" { + g, err := lookupGID(*group) + if err != nil { + log.Fatalf("group: %v", err) + } + gid = g + mode = 0660 + } + // Clean up any stale socket file from a previous run. if _, err := os.Stat(*sock); err == nil { _ = os.Remove(*sock) } + // Set umask before Listen so the socket is created with the desired + // permissions immediately, with no window where it's world-accessible. + oldMask := syscall.Umask(int(^mode & 0777)) ln, err := net.Listen("unix", *sock) + syscall.Umask(oldMask) if err != nil { log.Fatalf("listen: %v", err) } - _ = os.Chmod(*sock, 0600) // current user only + if gid != -1 { + if err := os.Chown(*sock, os.Getuid(), gid); err != nil { + log.Fatalf("chown socket: %v", err) + } + } + // Belt-and-suspenders: ensure the final mode regardless of umask quirks. + _ = os.Chmod(*sock, mode) store := NewStore()