diff --git a/.gitignore b/.gitignore index af033d5..528cd5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -port80 -port80fg -port80and443 +example/example diff --git a/LICENSE b/LICENSE index aa39852..9e52d9b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2022 Marius Schellenberger +Copyright (C) 2016 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 bcd5479..6410577 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ # godrop - drop privileges -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. +Godrop is a simple library to drop privileges on linux maschines. diff --git a/example/port80.go b/example/main.go similarity index 60% rename from example/port80.go rename to example/main.go index 9a67d27..2e4f848 100644 --- a/example/port80.go +++ b/example/main.go @@ -2,12 +2,11 @@ package main import ( "fmt" - "log" "net" "net/http" "os" - "git.giftfish.de/ston1th/godrop/v2" + "git.giftfish.de/ston1th/godrop" ) func main() { @@ -17,16 +16,16 @@ func main() { } err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) if err != nil { - log.Fatal(err) + fmt.Println(err) } l, err := godrop.GetListener() if err != nil { - log.Fatal("Failed to listen on FD 3:", err) + 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, "port %s\npid %d\nuid %d\ngid %d", port, os.Getpid(), os.Getuid(), os.Getgid()) + fmt.Fprintf(w, "pid %d\nuid %d\ngid %d", os.Getpid(), os.Getuid(), os.Getgid()) })) } diff --git a/example/port80and443.go b/example/port80and443.go deleted file mode 100644 index f1ba7ce..0000000 --- a/example/port80and443.go +++ /dev/null @@ -1,54 +0,0 @@ -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.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 { - log.Fatal(err) - } - - l1, err := godrop.GetListener() - if err != nil { - log.Fatal("Failed to listen on FD 3:", err) - } - _, port1, _ := net.SplitHostPort(l1.Addr().String()) - - l2, err := godrop.GetListenerFd(4) - if err != nil { - 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()) - })) - }() - - 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/example/port80fg.go b/example/port80fg.go deleted file mode 100644 index c924719..0000000 --- a/example/port80fg.go +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index c939651..0000000 --- a/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index d88e7bd..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -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 37c7e75..d9a4531 100644 --- a/godrop.go +++ b/godrop.go @@ -1,76 +1,129 @@ -// 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. package godrop import ( "errors" - "fmt" + "io/ioutil" "net" "os" "os/exec" - "os/signal" + "regexp" + "strconv" "syscall" ) -// Config represents the drop config +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") +} + type Config struct { - // 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 compiling without cgo, make sure the chroot directory contains the /etc/passwd and /etc/group files. + User string + Group string Chroot string - // Set to true, to run the process in the foreground. - Foreground bool } -// Drop will spawn a new process and hand over the listening socket file descriptor +// 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 { - return MultiDrop(c, func() ([]net.Listener, error) { - l, err := f() - return []net.Listener{l}, err - }) + 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") } -// MultiDrop will spawn a new process and hand over the all listening sockets +// 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 := userID(c.User) + uid, err := UID(c.User) if err != nil { - return errors.New("godrop: " + err.Error()) + return err } - if uid == 0 { - return fmt.Errorf("godrop: you can't drop privileges to uid 0 (%s)", c.User) - } - gid, err := groupID(c.Group) + gid, err := GID(c.Group) if err != nil { - return errors.New("godrop: " + err.Error()) + return err } switch os.Getuid() { case 0: cmd := exec.Command(os.Args[0], os.Args[1:]...) ln, err := f() if err != nil { - return errors.New("godrop: " + err.Error()) + return err } - 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 fmt.Errorf("godrop: index %d listener is not type of either *net.TCPListener or *net.UnixListener", i) + for _, v := range ln { + l, ok := v.(*net.TCPListener) + if !ok { + return errors.New("godrop: interface conversion failed") } + f, err := l.File() if err != nil { - return fmt.Errorf("godrop: index %d %s", i, err) + return err } cmd.ExtraFiles = append(cmd.ExtraFiles, f) } @@ -84,54 +137,24 @@ 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 errors.New("godrop: " + err.Error()) + return err } - 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))) - } cmd.Process.Release() os.Exit(0) case uid: return nil } - return errors.New("godrop: dropping priviledges failed") + return errors.New("godrop: droping priviledges failed") } // GetListener returns the listener socket of file descriptor 3 func GetListener() (net.Listener, error) { - return GetListenerFd(3) + return net.FileListener(os.NewFile(3, "[socket]")) } // 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) + return net.FileListener(os.NewFile(uintptr(fd), "[socket]")) } diff --git a/lookup_test.go b/godrop_test.go similarity index 57% rename from lookup_test.go rename to godrop_test.go index 49d2a78..38b629d 100644 --- a/lookup_test.go +++ b/godrop_test.go @@ -1,11 +1,9 @@ -// Copyright (C) 2022 Marius Schellenberger - package godrop import "testing" -func TestUserID(t *testing.T) { - uid, err := userID("root") +func TestUID(t *testing.T) { + uid, err := UID("root") if err != nil { t.Error(err) } @@ -14,8 +12,8 @@ func TestUserID(t *testing.T) { } } -func TestGroupID(t *testing.T) { - gid, err := groupID("root") +func TestGID(t *testing.T) { + gid, err := GID("root") if err != nil { t.Error(err) } diff --git a/lookup.go b/lookup.go deleted file mode 100644 index 725dc82..0000000 --- a/lookup.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2022 Marius Schellenberger - -//go:build go1.11 -// +build go1.11 - -package godrop - -import ( - "os/user" - "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) (int, error) { - u, err := user.Lookup(username) - if err != nil { - return -1, err - } - return atoi(u.Uid) -} - -func groupID(name string) (int, error) { - g, err := user.LookupGroup(name) - if err != nil { - return -1, err - } - return atoi(g.Gid) -} diff --git a/pledge.go b/pledge.go deleted file mode 100644 index 8c97aba..0000000 --- a/pledge.go +++ /dev/null @@ -1,15 +0,0 @@ -// 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 deleted file mode 100644 index 0af656a..0000000 --- a/pledge_openbsd.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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 deleted file mode 100644 index 412851b..0000000 --- a/unveil.go +++ /dev/null @@ -1,12 +0,0 @@ -// 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 deleted file mode 100644 index f1e4bbc..0000000 --- a/unveil_openbsd.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 -}