From 694ac1e8a9505a8914074c28bbabf698dd00b1b2 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 19 Apr 2017 20:42:19 +0200 Subject: [PATCH 01/12] simplified library and added better examples and net.UnixListener support --- .gitignore | 3 +- LICENSE | 2 +- README.md | 2 + example/{main.go => port80.go} | 3 +- example/port80and443.go | 52 +++++++++++++++++++ godrop.go | 92 +++++++++++++--------------------- godrop_test.go | 2 + 7 files changed, 95 insertions(+), 61 deletions(-) rename example/{main.go => port80.go} (77%) create mode 100644 example/port80and443.go diff --git a/.gitignore b/.gitignore index 528cd5b..46d10b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -example/example +port80 +port80and443 diff --git a/LICENSE b/LICENSE index 9e52d9b..2bb060c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2016 Marius Schellenberger +Copyright (C) 2017 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 6410577..011c0da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # godrop - drop privileges Godrop is a simple library to drop privileges on linux maschines. + +See the examples directory on how to use the `Drop` and `MultiDrop` functions. diff --git a/example/main.go b/example/port80.go similarity index 77% rename from example/main.go rename to example/port80.go index 2e4f848..4b1dd83 100644 --- a/example/main.go +++ b/example/port80.go @@ -24,8 +24,9 @@ func main() { fmt.Println("Failed to listen on FD 3:", err) os.Exit(1) } + _, port, _ := net.SplitHostPort(l.Addr().String()) http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "pid %d\nuid %d\ngid %d", os.Getpid(), os.Getuid(), os.Getgid()) + fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port, os.Getpid(), os.Getuid(), os.Getgid()) })) } diff --git a/example/port80and443.go b/example/port80and443.go new file mode 100644 index 0000000..4ffc7af --- /dev/null +++ b/example/port80and443.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "os" + + "git.giftfish.de/ston1th/godrop" +) + +func main() { + cfg := godrop.Config{ + User: "nobody", + Group: "nobody", + } + err := godrop.MultiDrop(cfg, func() ([]net.Listener, error) { + l1, err := net.Listen("tcp", ":80") + if err != nil { + return nil, err + } + l2, err := net.Listen("tcp", ":443") + return []net.Listener{l1, l2}, err + }) + if err != nil { + fmt.Println(err) + } + + l1, err := godrop.GetListener() + if err != nil { + fmt.Println("Failed to listen on FD 3:", err) + os.Exit(1) + } + _, 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) + } + _, port2, _ := net.SplitHostPort(l2.Addr().String()) + + 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()) + })) + }() + + http.Serve(l2, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port2, os.Getpid(), os.Getuid(), os.Getgid()) + })) +} diff --git a/godrop.go b/godrop.go index d9a4531..292c276 100644 --- a/godrop.go +++ b/godrop.go @@ -1,7 +1,11 @@ +// Copyright (C) 2017 Marius Schellenberger + +// Package godrop provides a simple privileges dropping library package godrop import ( "errors" + "fmt" "io/ioutil" "net" "os" @@ -11,6 +15,10 @@ import ( "syscall" ) +func errf(err error) error { + return fmt.Errorf("godrop: %s", err) +} + func readFile(name, file string) (id int, err error) { id = -1 if name == "" { @@ -44,6 +52,7 @@ func GID(group string) (int, error) { return readFile(group, "/etc/group") } +// Config represents the drop config type Config struct { User string Group string @@ -52,78 +61,43 @@ type Config struct { // Drop will spawn a new process, hand over the listening socket file descriptor and terminate itself after func Drop(c Config, f func() (net.Listener, error)) error { - uid, err := UID(c.User) - if err != nil { - return err - } - gid, err := GID(c.Group) - if err != nil { - return err - } - switch os.Getuid() { - case 0: - ln, err := f() - if err != nil { - return err - } - - l, ok := ln.(*net.TCPListener) - if !ok { - return errors.New("godrop: interface conversion failed") - } - f, err := l.File() - if err != nil { - return err - } - - cmd := exec.Command(os.Args[0], os.Args[1:]...) - cmd.ExtraFiles = []*os.File{f} - cmd.SysProcAttr = &syscall.SysProcAttr{ - Chroot: c.Chroot, - Credential: &syscall.Credential{ - Uid: uint32(uid), - Gid: uint32(gid), - }, - Setsid: true, - } - - if err := cmd.Start(); err != nil { - return err - } - - cmd.Process.Release() - os.Exit(0) - case uid: - return nil - } - return errors.New("godrop: droping priviledges failed") + return MultiDrop(c, func() ([]net.Listener, error) { + l, err := f() + return []net.Listener{l}, err + }) } // MultiDrop will spawn a new process, hand over the all listening sockets and terminate itself after func MultiDrop(c Config, f func() ([]net.Listener, error)) error { uid, err := UID(c.User) if err != nil { - return err + return errf(err) } gid, err := GID(c.Group) if err != nil { - return err + return errf(err) } switch os.Getuid() { case 0: cmd := exec.Command(os.Args[0], os.Args[1:]...) ln, err := f() if err != nil { - return err + return errf(err) } - for _, v := range ln { - l, ok := v.(*net.TCPListener) - if !ok { - return errors.New("godrop: interface conversion failed") + for i, v := range ln { + var f *os.File + switch l := v.(type) { + case *net.TCPListener: + f, err = l.File() + l.Close() + case *net.UnixListener: + 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)) } - f, err := l.File() if err != nil { - return err + return errf(fmt.Errorf("index: %d %s", i, err)) } cmd.ExtraFiles = append(cmd.ExtraFiles, f) } @@ -138,7 +112,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { } if err := cmd.Start(); err != nil { - return err + return errf(err) } cmd.Process.Release() @@ -146,15 +120,17 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { case uid: return nil } - return errors.New("godrop: droping priviledges failed") + return errf(errors.New("dropping priviledges failed")) } // GetListener returns the listener socket of file descriptor 3 func GetListener() (net.Listener, error) { - return net.FileListener(os.NewFile(3, "[socket]")) + return GetListenerFd(3) } // GetListenerFd returns the listener socket of the given file descriptor func GetListenerFd(fd int) (net.Listener, error) { - return net.FileListener(os.NewFile(uintptr(fd), "[socket]")) + f := os.NewFile(uintptr(fd), "") + defer f.Close() + return net.FileListener(f) } diff --git a/godrop_test.go b/godrop_test.go index 38b629d..65ce0dd 100644 --- a/godrop_test.go +++ b/godrop_test.go @@ -1,3 +1,5 @@ +// Copyright (C) 2017 Marius Schellenberger + package godrop import "testing" From 4b88a07ef5e93686afdd7db86c86684e99963736 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 19 Apr 2017 21:00:24 +0200 Subject: [PATCH 02/12] infinite loop bugfix --- godrop.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/godrop.go b/godrop.go index 292c276..0b9aa5b 100644 --- a/godrop.go +++ b/godrop.go @@ -73,6 +73,9 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { 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) if err != nil { return errf(err) From 1e230617cec845b25599af3cdd4bc08fd771513a Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 19 Apr 2017 23:20:52 +0200 Subject: [PATCH 03/12] added generic user lookup and foreground processes --- example/port80.go | 5 ++- example/port80and443.go | 5 ++- godrop.go | 71 +++++++++++++------------------- lookup.go | 29 +++++++++++++ lookup_cgo.go | 23 +++++++++++ lookup_nocgo.go | 43 +++++++++++++++++++ godrop_test.go => lookup_test.go | 8 ++-- 7 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 lookup.go create mode 100644 lookup_cgo.go create mode 100644 lookup_nocgo.go rename godrop_test.go => lookup_test.go (68%) 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) } From 09266c9d51cf92b2853387c16d7cd6eaf74ea6f7 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 19 Apr 2017 23:27:45 +0200 Subject: [PATCH 04/12] fixed typo --- godrop.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/godrop.go b/godrop.go index d7d1fdb..8df3298 100644 --- a/godrop.go +++ b/godrop.go @@ -24,7 +24,7 @@ type Config struct { // 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. + // When compiling without cgo, 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 From 108583814ef55f34ac8e939cc47699dc7e9befb8 Mon Sep 17 00:00:00 2001 From: ston1th Date: Thu, 20 Apr 2017 15:17:45 +0200 Subject: [PATCH 05/12] user lookup bugfix --- lookup_nocgo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lookup_nocgo.go b/lookup_nocgo.go index 0207d23..01da896 100644 --- a/lookup_nocgo.go +++ b/lookup_nocgo.go @@ -24,7 +24,7 @@ func readFile(name, file string) (id string, err error) { if err != nil { return } - r := regexp.MustCompile(name + ":.*:(\\d+)") + r := regexp.MustCompile(name + `:.*?:(\d+):`) m := r.FindAllStringSubmatch(string(b), 1) if len(m) == 1 { if len(m[0]) == 2 { From de6a48c088077703691e52c9aad7372f180fd3b4 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 19 Sep 2018 21:12:43 +0200 Subject: [PATCH 06/12] version 2.0.0 --- .gitignore | 1 + example/port80.go | 13 ++++++------- example/port80and443.go | 13 +++++++------ example/port80fg.go | 35 +++++++++++++++++++++++++++++++++ go.mod | 1 + godrop.go | 42 ++++++++++++++++++++-------------------- lookup.go | 19 +++++++++++------- lookup_cgo.go | 23 ---------------------- lookup_nocgo.go | 43 ----------------------------------------- lookup_test.go | 2 +- 10 files changed, 84 insertions(+), 108 deletions(-) create mode 100644 example/port80fg.go create mode 100644 go.mod delete mode 100644 lookup_cgo.go delete mode 100644 lookup_nocgo.go diff --git a/.gitignore b/.gitignore index 46d10b7..af033d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ port80 +port80fg port80and443 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..1d3f890 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module git.giftfish.de/ston1th/godrop/v2 diff --git a/godrop.go b/godrop.go index 8df3298..894cb23 100644 --- a/godrop.go +++ b/godrop.go @@ -1,20 +1,20 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger + +// +build go1.11 // Package godrop provides a simple privileges dropping library package godrop import ( - "errors" "fmt" "net" "os" "os/exec" - "os/signal" "syscall" ) -func errf(err error) error { - return fmt.Errorf("godrop: %s", err) +func errf(i interface{}) error { + return fmt.Errorf("godrop: %s", i) } // Config represents the drop config @@ -45,7 +45,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { return errf(err) } if uid == 0 { - return errf(errors.New("unable to drop privileges to uid 0 (root)")) + return errf("unable to drop privileges to uid 0 (root)") } gid, err := groupID(c.Group) if err != nil { @@ -68,20 +68,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 errf(fmt.Sprintf("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 errf(fmt.Sprintf("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,17 +85,20 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { Setsid: true, } + if c.Foreground { + cmd.SysProcAttr.Setctty = true + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.SysProcAttr.Noctty = true + } + if err := cmd.Start(); err != nil { 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))) } @@ -110,7 +107,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { case uid: return nil } - return errf(errors.New("dropping priviledges failed")) + return errf("dropping priviledges failed") } // GetListener returns the listener socket of file descriptor 3 @@ -120,6 +117,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, errf("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..2d0e1a9 100644 --- a/lookup.go +++ b/lookup.go @@ -1,8 +1,13 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger + +// +build go1.11 package godrop -import "strconv" +import ( + "os/user" + "strconv" +) func atoi(a string) (int, error) { i, err := strconv.Atoi(a) @@ -12,18 +17,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..bdcf7bd 100644 --- a/lookup_test.go +++ b/lookup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger package godrop From 23731a3702508e1184cd9986f21c7523e7cd78f8 Mon Sep 17 00:00:00 2001 From: ston1th Date: Thu, 20 Sep 2018 00:14:13 +0200 Subject: [PATCH 07/12] bugfix --- godrop.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/godrop.go b/godrop.go index 894cb23..f1cf20e 100644 --- a/godrop.go +++ b/godrop.go @@ -10,6 +10,7 @@ import ( "net" "os" "os/exec" + "os/signal" "syscall" ) @@ -86,12 +87,9 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { } if c.Foreground { - cmd.SysProcAttr.Setctty = true cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - } else { - cmd.SysProcAttr.Noctty = true } if err := cmd.Start(); err != nil { @@ -99,6 +97,21 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) 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.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))) } From 089d8d16c8902848caad1b1d45300b0bb289214f Mon Sep 17 00:00:00 2001 From: ston1th Date: Fri, 26 Oct 2018 16:52:08 +0200 Subject: [PATCH 08/12] added pledge and unveil support --- README.md | 2 +- go.mod | 2 ++ go.sum | 2 ++ godrop.go | 25 +++++++++++-------------- pledge.go | 14 ++++++++++++++ pledge_openbsd.go | 43 +++++++++++++++++++++++++++++++++++++++++++ unveil.go | 11 +++++++++++ unveil_openbsd.go | 32 ++++++++++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 go.sum create mode 100644 pledge.go create mode 100644 pledge_openbsd.go create mode 100644 unveil.go create mode 100644 unveil_openbsd.go diff --git a/README.md b/README.md index 011c0da..86550e9 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 maschines. See the examples directory on how to use the `Drop` and `MultiDrop` functions. diff --git a/go.mod b/go.mod index 1d3f890..a41c9af 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module git.giftfish.de/ston1th/godrop/v2 + +require golang.org/x/sys v0.0.0-20181026144532-2772b66316d2 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..36d0abd --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20181026144532-2772b66316d2 h1:W7CqTdBJ1CmxLKe7LptKDnBYV6PHrVLiGnoyBjaG/JQ= +golang.org/x/sys v0.0.0-20181026144532-2772b66316d2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/godrop.go b/godrop.go index f1cf20e..55f39ec 100644 --- a/godrop.go +++ b/godrop.go @@ -2,10 +2,11 @@ // +build go1.11 -// Package godrop provides a simple privileges dropping library +// Package godrop provides a simple library to drop privileges on Linux and OpenBSD. package godrop import ( + "errors" "fmt" "net" "os" @@ -14,10 +15,6 @@ import ( "syscall" ) -func errf(i interface{}) error { - return fmt.Errorf("godrop: %s", i) -} - // Config represents the drop config type Config struct { // User is the user to drop privileges to. @@ -43,21 +40,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("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 @@ -69,10 +66,10 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { f, err = l.File() l.Close() default: - return errf(fmt.Sprintf("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.Sprintf("index %d %s", i, err)) + return fmt.Errorf("godrop: index %d %s", i, err) } cmd.ExtraFiles = append(cmd.ExtraFiles, f) } @@ -93,7 +90,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { } if err := cmd.Start(); err != nil { - return errf(err) + return errors.New("godrop: " + err.Error()) } if c.Foreground { @@ -120,7 +117,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error { case uid: return nil } - return errf("dropping priviledges failed") + return errors.New("godrop: dropping priviledges failed") } // GetListener returns the listener socket of file descriptor 3 @@ -131,7 +128,7 @@ 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, errf("fd is less than 3") + return nil, errors.New("godrop: fd is less than 3") } f := os.NewFile(uintptr(fd), "") defer f.Close() diff --git a/pledge.go b/pledge.go new file mode 100644 index 0000000..d0e361e --- /dev/null +++ b/pledge.go @@ -0,0 +1,14 @@ +// Copyright (C) 2018 Marius Schellenberger + +// +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..7369b03 --- /dev/null +++ b/pledge_openbsd.go @@ -0,0 +1,43 @@ +// Copyright (C) 2018 Marius Schellenberger + +// +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..06c8c90 --- /dev/null +++ b/unveil.go @@ -0,0 +1,11 @@ +// Copyright (C) 2018 Marius Schellenberger + +// +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..c913e79 --- /dev/null +++ b/unveil_openbsd.go @@ -0,0 +1,32 @@ +// Copyright (C) 2018 Marius Schellenberger + +// +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 +} From ec32483e7f69d9c5600a66a99fba4e37ad5560c4 Mon Sep 17 00:00:00 2001 From: ston1th Date: Fri, 26 Oct 2018 16:54:12 +0200 Subject: [PATCH 09/12] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86550e9..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 and OpenBSD 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. From 3e528ceb3f460f3691054e3551272ea7d078cd50 Mon Sep 17 00:00:00 2001 From: ston1th Date: Fri, 26 Oct 2018 17:02:28 +0200 Subject: [PATCH 10/12] unveil bugfix --- unveil_openbsd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unveil_openbsd.go b/unveil_openbsd.go index c913e79..f32d630 100644 --- a/unveil_openbsd.go +++ b/unveil_openbsd.go @@ -15,7 +15,7 @@ import ( func Unveil(path, flags string) (err error) { err = unix.Unveil(path, flags) if err != nil { - err = errors.New("unveil: ", err.Error()) + err = errors.New("unveil: " + err.Error()) } return } @@ -26,7 +26,7 @@ func Unveil(path, flags string) (err error) { func UnveilBlock() (err error) { err = unix.UnveilBlock() if err != nil { - err = errors.New("unveil: ", err.Error()) + err = errors.New("unveil: " + err.Error()) } return } From 77230f5240d853785717947c9f934c0adb78870b Mon Sep 17 00:00:00 2001 From: ston1th Date: Sat, 8 Oct 2022 16:59:34 +0200 Subject: [PATCH 11/12] update dependencies --- LICENSE | 2 +- go.mod | 4 +++- go.sum | 4 ++-- godrop.go | 3 ++- lookup.go | 3 ++- lookup_test.go | 2 +- pledge.go | 3 ++- pledge_openbsd.go | 3 ++- unveil.go | 3 ++- unveil_openbsd.go | 3 ++- 10 files changed, 19 insertions(+), 11 deletions(-) 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/go.mod b/go.mod index a41c9af..54387f6 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.giftfish.de/ston1th/godrop/v2 -require golang.org/x/sys v0.0.0-20181026144532-2772b66316d2 // indirect +go 1.19 + +require golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 diff --git a/go.sum b/go.sum index 36d0abd..e04231f 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.0.0-20181026144532-2772b66316d2 h1:W7CqTdBJ1CmxLKe7LptKDnBYV6PHrVLiGnoyBjaG/JQ= -golang.org/x/sys v0.0.0-20181026144532-2772b66316d2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/godrop.go b/godrop.go index 55f39ec..37c7e75 100644 --- a/godrop.go +++ b/godrop.go @@ -1,5 +1,6 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger +//go:build go1.11 // +build go1.11 // Package godrop provides a simple library to drop privileges on Linux and OpenBSD. diff --git a/lookup.go b/lookup.go index 2d0e1a9..725dc82 100644 --- a/lookup.go +++ b/lookup.go @@ -1,5 +1,6 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger +//go:build go1.11 // +build go1.11 package godrop diff --git a/lookup_test.go b/lookup_test.go index bdcf7bd..49d2a78 100644 --- a/lookup_test.go +++ b/lookup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger package godrop diff --git a/pledge.go b/pledge.go index d0e361e..8c97aba 100644 --- a/pledge.go +++ b/pledge.go @@ -1,5 +1,6 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger +//go:build !openbsd // +build !openbsd package godrop diff --git a/pledge_openbsd.go b/pledge_openbsd.go index 7369b03..0af656a 100644 --- a/pledge_openbsd.go +++ b/pledge_openbsd.go @@ -1,5 +1,6 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger +//go:build openbsd // +build openbsd package godrop diff --git a/unveil.go b/unveil.go index 06c8c90..412851b 100644 --- a/unveil.go +++ b/unveil.go @@ -1,5 +1,6 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger +//go:build !openbsd // +build !openbsd package godrop diff --git a/unveil_openbsd.go b/unveil_openbsd.go index f32d630..f1e4bbc 100644 --- a/unveil_openbsd.go +++ b/unveil_openbsd.go @@ -1,5 +1,6 @@ -// Copyright (C) 2018 Marius Schellenberger +// Copyright (C) 2022 Marius Schellenberger +//go:build openbsd // +build openbsd package godrop From b2f6d0d478f061ffb31effecfa38201a16527f3d Mon Sep 17 00:00:00 2001 From: ston1th Date: Mon, 19 Aug 2024 20:37:21 +0200 Subject: [PATCH 12/12] use latest sys --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 54387f6..c939651 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module git.giftfish.de/ston1th/godrop/v2 go 1.19 -require golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 +require golang.org/x/sys v0.24.0 diff --git a/go.sum b/go.sum index e04231f..d88e7bd 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= -golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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=