diff --git a/example/port80.go b/example/port80.go index 4b1dd83..3a8f1f3 100644 --- a/example/port80.go +++ b/example/port80.go @@ -11,8 +11,9 @@ import ( func main() { cfg := godrop.Config{ - User: "nobody", - Group: "nobody", + User: "nobody", + Group: "nobody", + Foreground: true, } err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) if err != nil { diff --git a/example/port80and443.go b/example/port80and443.go index 4ffc7af..c24e8ab 100644 --- a/example/port80and443.go +++ b/example/port80and443.go @@ -11,8 +11,9 @@ import ( func main() { cfg := godrop.Config{ - User: "nobody", - Group: "nobody", + User: "nobody", + Group: "nobody", + Foreground: true, } err := godrop.MultiDrop(cfg, func() ([]net.Listener, error) { l1, err := net.Listen("tcp", ":80") diff --git a/godrop.go b/godrop.go index 0b9aa5b..d7d1fdb 100644 --- a/godrop.go +++ b/godrop.go @@ -6,12 +6,10 @@ package godrop import ( "errors" "fmt" - "io/ioutil" "net" "os" "os/exec" - "regexp" - "strconv" + "os/signal" "syscall" ) @@ -19,47 +17,20 @@ func errf(err error) error { return fmt.Errorf("godrop: %s", err) } -func readFile(name, file string) (id int, 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 strconv.Atoi(m[0][1]) - } - } - return -} - -// UID of user -func UID(user string) (int, error) { - return readFile(user, "/etc/passwd") -} - -// GID of group -func GID(group string) (int, error) { - return readFile(group, "/etc/group") -} - // Config represents the drop config type Config struct { - User string - Group string + // User is the user to drop privileges to. + User string + // Group is the group to drop privileges to. + Group string + // Chroot is the directory to chroot into. Leave this emptry for no chroot. + // When running on linux, make sure the chroot directory contains the /etc/passwd and /etc/group files. Chroot string + // Set to true, to run the process in the foreground. + Foreground bool } -// Drop will spawn a new process, hand over the listening socket file descriptor and terminate itself after +// Drop will spawn a new process and hand over the listening socket file descriptor func Drop(c Config, f func() (net.Listener, error)) error { return MultiDrop(c, func() ([]net.Listener, error) { l, err := f() @@ -67,16 +38,16 @@ func Drop(c Config, f func() (net.Listener, error)) error { }) } -// MultiDrop will spawn a new process, hand over the all listening sockets and terminate itself after +// MultiDrop will spawn a new process and hand over the all listening sockets func MultiDrop(c Config, f func() ([]net.Listener, error)) error { - uid, err := UID(c.User) + uid, err := userID(c.User) if err != nil { return errf(err) } if uid == 0 { return errf(errors.New("unable to drop privileges to uid 0 (root)")) } - gid, err := GID(c.Group) + gid, err := groupID(c.Group) if err != nil { return errf(err) } @@ -105,6 +76,12 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { 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{ @@ -118,6 +95,16 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { return errf(err) } + if c.Foreground { + go func() { + sigs := make(chan os.Signal) + signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + sig := <-sigs + cmd.Process.Signal(sig) + }() + _ = cmd.Wait() + os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus))) + } cmd.Process.Release() os.Exit(0) case uid: diff --git a/lookup.go b/lookup.go new file mode 100644 index 0000000..abd19e4 --- /dev/null +++ b/lookup.go @@ -0,0 +1,29 @@ +// Copyright (C) 2017 Marius Schellenberger + +package godrop + +import "strconv" + +func atoi(a string) (int, error) { + i, err := strconv.Atoi(a) + if err != nil { + return -1, err + } + return i, nil +} + +func userID(username string) (uid int, err error) { + id, err := lookupUID(username) + if err != nil { + return -1, err + } + return atoi(id) +} + +func groupID(name string) (int, error) { + id, err := lookupGID(name) + if err != nil { + return -1, err + } + return atoi(id) +} diff --git a/lookup_cgo.go b/lookup_cgo.go new file mode 100644 index 0000000..5b0355c --- /dev/null +++ b/lookup_cgo.go @@ -0,0 +1,23 @@ +// 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 new file mode 100644 index 0000000..0207d23 --- /dev/null +++ b/lookup_nocgo.go @@ -0,0 +1,43 @@ +// 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/godrop_test.go b/lookup_test.go similarity index 68% rename from godrop_test.go rename to lookup_test.go index 65ce0dd..6f69cb0 100644 --- a/godrop_test.go +++ b/lookup_test.go @@ -4,8 +4,8 @@ package godrop import "testing" -func TestUID(t *testing.T) { - uid, err := UID("root") +func TestUserID(t *testing.T) { + uid, err := userID("root") if err != nil { t.Error(err) } @@ -14,8 +14,8 @@ func TestUID(t *testing.T) { } } -func TestGID(t *testing.T) { - gid, err := GID("root") +func TestGroupID(t *testing.T) { + gid, err := groupID("root") if err != nil { t.Error(err) }