diff --git a/.gitignore b/.gitignore index 46d10b7..af033d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ port80 +port80fg port80and443 diff --git a/LICENSE b/LICENSE index 2bb060c..aa39852 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2017 Marius Schellenberger +Copyright (C) 2022 Marius Schellenberger All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 011c0da..bcd5479 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # godrop - drop privileges -Godrop is a simple library to drop privileges on linux maschines. +Godrop is a simple library to drop privileges on Linux and OpenBSD. See the examples directory on how to use the `Drop` and `MultiDrop` functions. diff --git a/example/port80.go b/example/port80.go index 3a8f1f3..9a67d27 100644 --- a/example/port80.go +++ b/example/port80.go @@ -2,28 +2,27 @@ package main import ( "fmt" + "log" "net" "net/http" "os" - "git.giftfish.de/ston1th/godrop" + "git.giftfish.de/ston1th/godrop/v2" ) func main() { cfg := godrop.Config{ - User: "nobody", - Group: "nobody", - Foreground: true, + User: "nobody", + Group: "nobody", } err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) if err != nil { - fmt.Println(err) + log.Fatal(err) } l, err := godrop.GetListener() if err != nil { - fmt.Println("Failed to listen on FD 3:", err) - os.Exit(1) + log.Fatal("Failed to listen on FD 3:", err) } _, port, _ := net.SplitHostPort(l.Addr().String()) diff --git a/example/port80and443.go b/example/port80and443.go index c24e8ab..f1ba7ce 100644 --- a/example/port80and443.go +++ b/example/port80and443.go @@ -2,11 +2,12 @@ package main import ( "fmt" + "log" "net" "net/http" "os" - "git.giftfish.de/ston1th/godrop" + "git.giftfish.de/ston1th/godrop/v2" ) func main() { @@ -24,23 +25,23 @@ func main() { return []net.Listener{l1, l2}, err }) if err != nil { - fmt.Println(err) + log.Fatal(err) } l1, err := godrop.GetListener() if err != nil { - fmt.Println("Failed to listen on FD 3:", err) - os.Exit(1) + log.Fatal("Failed to listen on FD 3:", err) } _, port1, _ := net.SplitHostPort(l1.Addr().String()) l2, err := godrop.GetListenerFd(4) if err != nil { - fmt.Println("Failed to listen on FD 4:", err) - os.Exit(1) + log.Fatal("Failed to listen on FD 4:", err) } _, port2, _ := net.SplitHostPort(l2.Addr().String()) + fmt.Printf("port1 %s\nport2 %s\npid %d\nuid %d\ngid %d\n", port1, port2, os.Getpid(), os.Getuid(), os.Getgid()) + go func() { http.Serve(l1, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port1, os.Getpid(), os.Getuid(), os.Getgid()) diff --git a/example/port80fg.go b/example/port80fg.go new file mode 100644 index 0000000..c924719 --- /dev/null +++ b/example/port80fg.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "log" + "net" + "net/http" + "os" + + "git.giftfish.de/ston1th/godrop/v2" +) + +func main() { + cfg := godrop.Config{ + User: "nobody", + Group: "nobody", + Foreground: true, + } + err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) + if err != nil { + log.Fatal(err) + } + + l, err := godrop.GetListener() + if err != nil { + log.Fatal("Failed to listen on FD 3:", err) + } + _, port, _ := net.SplitHostPort(l.Addr().String()) + + fmt.Printf("port %s\npid %d\nuid %d\ngid %d\n", port, os.Getpid(), os.Getuid(), os.Getgid()) + + http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port, os.Getpid(), os.Getuid(), os.Getgid()) + })) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c939651 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.giftfish.de/ston1th/godrop/v2 + +go 1.19 + +require golang.org/x/sys v0.24.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d88e7bd --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/godrop.go b/godrop.go index 8df3298..37c7e75 100644 --- a/godrop.go +++ b/godrop.go @@ -1,6 +1,9 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger -// Package godrop provides a simple privileges dropping library +//go:build go1.11 +// +build go1.11 + +// Package godrop provides a simple library to drop privileges on Linux and OpenBSD. package godrop import ( @@ -13,10 +16,6 @@ import ( "syscall" ) -func errf(err error) error { - return fmt.Errorf("godrop: %s", err) -} - // Config represents the drop config type Config struct { // User is the user to drop privileges to. @@ -42,21 +41,21 @@ func Drop(c Config, f func() (net.Listener, error)) error { func MultiDrop(c Config, f func() ([]net.Listener, error)) error { uid, err := userID(c.User) if err != nil { - return errf(err) + return errors.New("godrop: " + err.Error()) } if uid == 0 { - return errf(errors.New("unable to drop privileges to uid 0 (root)")) + return fmt.Errorf("godrop: you can't drop privileges to uid 0 (%s)", c.User) } gid, err := groupID(c.Group) if err != nil { - return errf(err) + return errors.New("godrop: " + err.Error()) } switch os.Getuid() { case 0: cmd := exec.Command(os.Args[0], os.Args[1:]...) ln, err := f() if err != nil { - return errf(err) + return errors.New("godrop: " + err.Error()) } for i, v := range ln { var f *os.File @@ -68,20 +67,14 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { f, err = l.File() l.Close() default: - return errf(fmt.Errorf("index: %d listener is not type of either *net.TCPListener or *net.UnixListener", i)) + return fmt.Errorf("godrop: index %d listener is not type of either *net.TCPListener or *net.UnixListener", i) } if err != nil { - return errf(fmt.Errorf("index: %d %s", i, err)) + return fmt.Errorf("godrop: index %d %s", i, err) } cmd.ExtraFiles = append(cmd.ExtraFiles, f) } - if c.Foreground { - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } - cmd.SysProcAttr = &syscall.SysProcAttr{ Chroot: c.Chroot, Credential: &syscall.Credential{ @@ -91,16 +84,31 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { Setsid: true, } + if c.Foreground { + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + if err := cmd.Start(); err != nil { - return errf(err) + return errors.New("godrop: " + err.Error()) } if c.Foreground { go func() { + term := make(chan os.Signal) + signal.Notify(term, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) sigs := make(chan os.Signal) - signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) - sig := <-sigs - cmd.Process.Signal(sig) + signal.Notify(sigs, syscall.SIGSTOP, syscall.SIGCONT, syscall.SIGUSR1, syscall.SIGUSR2) + for { + select { + case sig := <-term: + cmd.Process.Signal(sig) + return + case sig := <-sigs: + cmd.Process.Signal(sig) + } + } }() _ = cmd.Wait() os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus))) @@ -110,7 +118,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { case uid: return nil } - return errf(errors.New("dropping priviledges failed")) + return errors.New("godrop: dropping priviledges failed") } // GetListener returns the listener socket of file descriptor 3 @@ -120,6 +128,9 @@ func GetListener() (net.Listener, error) { // GetListenerFd returns the listener socket of the given file descriptor func GetListenerFd(fd int) (net.Listener, error) { + if fd < 3 { + return nil, errors.New("godrop: fd is less than 3") + } f := os.NewFile(uintptr(fd), "") defer f.Close() return net.FileListener(f) diff --git a/lookup.go b/lookup.go index abd19e4..725dc82 100644 --- a/lookup.go +++ b/lookup.go @@ -1,8 +1,14 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger + +//go:build go1.11 +// +build go1.11 package godrop -import "strconv" +import ( + "os/user" + "strconv" +) func atoi(a string) (int, error) { i, err := strconv.Atoi(a) @@ -12,18 +18,18 @@ func atoi(a string) (int, error) { return i, nil } -func userID(username string) (uid int, err error) { - id, err := lookupUID(username) +func userID(username string) (int, error) { + u, err := user.Lookup(username) if err != nil { return -1, err } - return atoi(id) + return atoi(u.Uid) } func groupID(name string) (int, error) { - id, err := lookupGID(name) + g, err := user.LookupGroup(name) if err != nil { return -1, err } - return atoi(id) + return atoi(g.Gid) } diff --git a/lookup_cgo.go b/lookup_cgo.go deleted file mode 100644 index 5b0355c..0000000 --- a/lookup_cgo.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2017 Marius Schellenberger - -// +build cgo - -package godrop - -import "os/user" - -func lookupUID(username string) (string, error) { - u, err := user.Lookup(username) - if err != nil { - return "", err - } - return u.Uid, err -} - -func lookupGID(name string) (string, error) { - g, err := user.LookupGroup(name) - if err != nil { - return "", err - } - return g.Gid, err -} diff --git a/lookup_nocgo.go b/lookup_nocgo.go deleted file mode 100644 index 01da896..0000000 --- a/lookup_nocgo.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2017 Marius Schellenberger - -// +build darwin dragonfly freebsd android linux netbsd openbsd solaris -// +build !cgo - -package godrop - -import ( - "io/ioutil" - "os" - "regexp" -) - -func readFile(name, file string) (id string, err error) { - id = "-1" - if name == "" { - return - } - f, err := os.Open(file) - if err != nil { - return - } - b, err := ioutil.ReadAll(f) - if err != nil { - return - } - r := regexp.MustCompile(name + `:.*?:(\d+):`) - m := r.FindAllStringSubmatch(string(b), 1) - if len(m) == 1 { - if len(m[0]) == 2 { - return m[0][1], nil - } - } - return -} - -func lookupUID(username string) (string, error) { - return readFile(username, "/etc/passwd") -} - -func lookupGID(name string) (string, error) { - return readFile(name, "/etc/group") -} diff --git a/lookup_test.go b/lookup_test.go index 6f69cb0..49d2a78 100644 --- a/lookup_test.go +++ b/lookup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger package godrop diff --git a/pledge.go b/pledge.go new file mode 100644 index 0000000..8c97aba --- /dev/null +++ b/pledge.go @@ -0,0 +1,15 @@ +// Copyright (C) 2022 Marius Schellenberger + +//go:build !openbsd +// +build !openbsd + +package godrop + +// Pledge is currently only supported on OpenBSD. +func Pledge(promises, execpromises string) error { return nil } + +// PledgePromises is currently only supported on OpenBSD. +func PledgePromises(promises string) error { return nil } + +// PledgeExecPromises is currently only supported on OpenBSD. +func PledgeExecpromises(execpromises string) error { return nil } diff --git a/pledge_openbsd.go b/pledge_openbsd.go new file mode 100644 index 0000000..0af656a --- /dev/null +++ b/pledge_openbsd.go @@ -0,0 +1,44 @@ +// Copyright (C) 2022 Marius Schellenberger + +//go:build openbsd +// +build openbsd + +package godrop + +import ( + "errors" + "golang.org/x/sys/unix" +) + +// Pledge is a wrapper for x/sys/unix Pledge. +// +// See https://go.googlesource.com/sys/+/master/unix/openbsd_pledge.go for usage. +func Pledge(promises, execpromises string) (err error) { + err = unix.Pledge(promises, execpromises) + if err != nil { + err = errors.New("pledge: " + err.Error()) + } + return +} + +// PledgePromises is a wrapper for x/sys/unix PledgePromises. +// +// See https://go.googlesource.com/sys/+/master/unix/openbsd_pledge.go for usage. +func PledgePromises(promises string) (err error) { + err = unix.PledgePromises(promises) + if err != nil { + err = errors.New("pledge: " + err.Error()) + } + return +} + +// PledgeExecpromises is a wrapper for x/sys/unix PledgeExecpromises. +// +// See https://go.googlesource.com/sys/+/master/unix/openbsd_pledge.go for usage. +func PledgeExecpromises(execpromises string) (err error) { + err = unix.PledgeExecpromises(execpromises) + if err != nil { + err = errors.New("pledge: " + err.Error()) + } + return +} diff --git a/unveil.go b/unveil.go new file mode 100644 index 0000000..412851b --- /dev/null +++ b/unveil.go @@ -0,0 +1,12 @@ +// Copyright (C) 2022 Marius Schellenberger + +//go:build !openbsd +// +build !openbsd + +package godrop + +// Unveil is currently only supported on OpenBSD. +func Unveil(path, flags string) error { return nil } + +// UnveilBlock is currently only supported on OpenBSD. +func UnveilBlock() error { return nil } diff --git a/unveil_openbsd.go b/unveil_openbsd.go new file mode 100644 index 0000000..f1e4bbc --- /dev/null +++ b/unveil_openbsd.go @@ -0,0 +1,33 @@ +// Copyright (C) 2022 Marius Schellenberger + +//go:build openbsd +// +build openbsd + +package godrop + +import ( + "errors" + "golang.org/x/sys/unix" +) + +// Unveil is a wrapper for x/sys/unix Unveil. +// +// See https://go.googlesource.com/sys/+/master/unix/openbsd_unveil.go for usage. +func Unveil(path, flags string) (err error) { + err = unix.Unveil(path, flags) + if err != nil { + err = errors.New("unveil: " + err.Error()) + } + return +} + +// UnveilBlock is a wrapper for x/sys/unix UnveilBlock. +// +// See https://go.googlesource.com/sys/+/master/unix/openbsd_unveil.go for usage. +func UnveilBlock() (err error) { + err = unix.UnveilBlock() + if err != nil { + err = errors.New("unveil: " + err.Error()) + } + return +}