hnakra/cmd/wrench/main.go
2023-05-30 18:03:26 -04:00

256 lines
6.0 KiB
Go

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)
}
}