package main import "os" import "fmt" import "flag" import "strconv" import "os/exec" import "os/user" import "hnakra/cli" import "path/filepath" import "golang.org/x/crypto/bcrypt" func tryCommand (cmd *exec.Cmd, failReason string) { output, err := cmd.CombinedOutput() if err != nil { cli.Sayf("%s: %s\n", failReason, string(output)) os.Exit(1) } } func ownOne (path string, uid, gid int) { file, err := os.Stat(path) if err != nil { cli.Sayf("could not stat %s: %v\n", path, err) return } err = os.Chown(path, uid, gid) if err != nil { cli.Sayf("could not change ownership of %s: %v\n", path, err) return } if file.IsDir() { err = os.Chmod(path, 0770) } else { err = os.Chmod(path, 0660) } if err != nil { cli.Sayf("could not change mode of %s: %v\n", path, err) return } } func main () { user, err := user.Current() if err != nil { cli.Sayf("could not get username %v\n", err) os.Exit(1) } flag.Usage = func () { cli.Printf("Usage of %s:\n", os.Args[0]) cli.Printf(" hash\n") cli.Printf(" Generate a bcrypt hash of a key\n") cli.Printf(" adduser\n") cli.Printf(" Add a system user to run a service as\n") cli.Printf(" deluser\n") cli.Printf(" Remove a user added with adduser\n") cli.Printf(" auth\n") cli.Printf(" Authorize a system user to access a service's files\n") cli.Printf(" own\n") cli.Printf(" Give ownership of a file to a service\n") os.Exit(1) } // define commands hashCommand := flag.NewFlagSet("hash", flag.ExitOnError) hashCost := hashCommand.Uint("cost", uint(bcrypt.DefaultCost), "Cost of the hash") hashText := hashCommand.String("k", "", "Text content of the key") addUserCommand := flag.NewFlagSet("adduser", flag.ExitOnError) addUserService := addUserCommand.String ("s", "router", "Service to add a user for") delUserCommand := flag.NewFlagSet("deluser", flag.ExitOnError) delUserService := delUserCommand.String ("s", "router", "Service to delete the user for") authCommand := flag.NewFlagSet("auth", flag.ExitOnError) authService := authCommand.String ("s", "router", "Service to authorize the user to access") authUser := authCommand.String ("u", user.Username, "User to be given access") ownCommand := flag.NewFlagSet("own", flag.ExitOnError) ownService := ownCommand.String ("s", "router", "Service to give ownership of the file to") ownFile := ownCommand.String ("f", ".", "File to take ownership of") ownRecursive := ownCommand.Bool ("r", false, "Whether or not to recurse into sub-directories") flag.Parse() // execute correct command if len(os.Args) < 2 { flag.Usage() os.Exit(1) } subCommandArgs := os.Args[2:] switch os.Args[1] { case "hash": hashCommand.Parse(subCommandArgs) execHash(int(*hashCost), *hashText) case "adduser": addUserCommand.Parse(subCommandArgs) execAdduser(*addUserService) case "deluser": delUserCommand.Parse(subCommandArgs) execDeluser(*delUserService) case "auth": authCommand.Parse(subCommandArgs) execAuth(*authService, *authUser) case "own": ownCommand.Parse(subCommandArgs) execOwn(*ownService, *ownFile, *ownRecursive) } } func execHash (cost int, key string) { if key == "" { cli.Sayf("please specify key text content\n") os.Exit(1) } if cost < bcrypt.MinCost { cli.Sayf("cost is too low, must be at least %v\n", bcrypt.MinCost) os.Exit(1) } if cost > bcrypt.MaxCost { cli.Sayf("cost is too hight, can be at most %v\n", bcrypt.MaxCost) os.Exit(1) } hash, err := bcrypt.GenerateFromPassword([]byte(key), cost) if err != nil { cli.Sayf("could not hash key: %v\n", err) os.Exit(1) } fmt.Println(string(hash)) } func execAdduser (service string) { fullName := cli.ServiceUser(service) // BUSYBOX adduser, err := exec.LookPath("adduser") if err == nil { addgroup, _ := exec.LookPath("addgroup") tryCommand (exec.Command(addgroup, fullName, "-S"), "could not add group") tryCommand (exec.Command(adduser, fullName, "-SHDG", fullName), "could not add user") return } // GNU useradd, err := exec.LookPath("useradd") if err == nil { tryCommand (exec.Command ( useradd, fullName, "-rUM", "--shell", "/sbin/nologin"), "could not add user") return } cli.Sayf("could not add user: no command adduser or useradd\n") os.Exit(1) } func execDeluser (service string) { fullName := cli.ServiceUser(service) // BUSYBOX deluser, err := exec.LookPath("deluser") if err == nil { tryCommand (exec.Command(deluser, fullName, "--remove-home"), "could not delete user") return } // GNU userdel, err := exec.LookPath("userdel") if err == nil { tryCommand (exec.Command(userdel, fullName, "-r"), "could not delete user") groupdel, _ := exec.LookPath("groupdel") tryCommand (exec.Command(groupdel, fullName), "could not delete group") return } cli.Sayf("could not delete user: no command deluser or userdel\n") os.Exit(1) } func execAuth (service, user string) { fullName := cli.ServiceUser(service) adduser, err := exec.LookPath("adduser") if err == nil { tryCommand (exec.Command(adduser, user, fullName), "could not add user to group " + fullName) return } // GNU useradd, err := exec.LookPath("usermod") if err == nil { tryCommand (exec.Command(useradd, "-a", "-g", fullName, user), "could not add user to group " + fullName) return } cli.Sayf("could not auth user: no command adduser or usermod\n") os.Exit(1) } func execOwn (service, file string, recurse bool) { fullName := cli.ServiceUser(service) userInfo, err := user.Lookup(fullName) uid, _ := strconv.Atoi(userInfo.Uid) gid, _ := strconv.Atoi(userInfo.Gid) if err != nil { cli.Sayf("could not get user info: %v\n", err) os.Exit(1) } if !recurse { ownOne(file, uid, gid) return } err = filepath.Walk(file, func( filePath string, file os.FileInfo, err error, ) error { if err != nil { cli.Sayf("could not traverse filesystem: %v\n", err) return nil } ownOne(filePath, uid, gid) return nil }) if err != nil { cli.Sayf("could not traverse filesystem: %v\n", err) os.Exit(1) } }