Compare commits

..

No commits in common. "master" and "v1.1" have entirely different histories.

17 changed files with 112 additions and 210 deletions

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
port80 port80
port80fg
port80and443 port80and443

View file

@ -1,4 +1,4 @@
Copyright (C) 2022 Marius Schellenberger Copyright (C) 2017 Marius Schellenberger
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -1,5 +1,5 @@
# godrop - drop privileges # godrop - drop privileges
Godrop is a simple library to drop privileges on Linux and OpenBSD. Godrop is a simple library to drop privileges on linux maschines.
See the examples directory on how to use the `Drop` and `MultiDrop` functions. See the examples directory on how to use the `Drop` and `MultiDrop` functions.

View file

@ -2,27 +2,28 @@ package main
import ( import (
"fmt" "fmt"
"log"
"net" "net"
"net/http" "net/http"
"os" "os"
"git.giftfish.de/ston1th/godrop/v2" "git.giftfish.de/ston1th/godrop"
) )
func main() { func main() {
cfg := godrop.Config{ cfg := godrop.Config{
User: "nobody", User: "nobody",
Group: "nobody", Group: "nobody",
Foreground: true,
} }
err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") })
if err != nil { if err != nil {
log.Fatal(err) fmt.Println(err)
} }
l, err := godrop.GetListener() l, err := godrop.GetListener()
if err != nil { 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()) _, port, _ := net.SplitHostPort(l.Addr().String())

View file

@ -2,12 +2,11 @@ package main
import ( import (
"fmt" "fmt"
"log"
"net" "net"
"net/http" "net/http"
"os" "os"
"git.giftfish.de/ston1th/godrop/v2" "git.giftfish.de/ston1th/godrop"
) )
func main() { func main() {
@ -25,23 +24,23 @@ func main() {
return []net.Listener{l1, l2}, err return []net.Listener{l1, l2}, err
}) })
if err != nil { if err != nil {
log.Fatal(err) fmt.Println(err)
} }
l1, err := godrop.GetListener() l1, err := godrop.GetListener()
if err != nil { if err != nil {
log.Fatal("Failed to listen on FD 3:", err) fmt.Println("Failed to listen on FD 3:", err)
os.Exit(1)
} }
_, port1, _ := net.SplitHostPort(l1.Addr().String()) _, port1, _ := net.SplitHostPort(l1.Addr().String())
l2, err := godrop.GetListenerFd(4) l2, err := godrop.GetListenerFd(4)
if err != nil { if err != nil {
log.Fatal("Failed to listen on FD 4:", err) fmt.Println("Failed to listen on FD 4:", err)
os.Exit(1)
} }
_, port2, _ := net.SplitHostPort(l2.Addr().String()) _, 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() { go func() {
http.Serve(l1, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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()) fmt.Fprintf(w, "port %s\npid %d\nuid %d\ngid %d", port1, os.Getpid(), os.Getuid(), os.Getgid())

View file

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

5
go.mod
View file

@ -1,5 +0,0 @@
module git.giftfish.de/ston1th/godrop/v2
go 1.19
require golang.org/x/sys v0.24.0

2
go.sum
View file

@ -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=

View file

@ -1,9 +1,6 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2017 Marius Schellenberger
//go:build go1.11 // Package godrop provides a simple privileges dropping library
// +build go1.11
// Package godrop provides a simple library to drop privileges on Linux and OpenBSD.
package godrop package godrop
import ( import (
@ -16,6 +13,10 @@ import (
"syscall" "syscall"
) )
func errf(err error) error {
return fmt.Errorf("godrop: %s", err)
}
// Config represents the drop config // Config represents the drop config
type Config struct { type Config struct {
// User is the user to drop privileges to. // User is the user to drop privileges to.
@ -41,21 +42,21 @@ func Drop(c Config, f func() (net.Listener, error)) error {
func MultiDrop(c Config, f func() ([]net.Listener, error)) error { func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
uid, err := userID(c.User) uid, err := userID(c.User)
if err != nil { if err != nil {
return errors.New("godrop: " + err.Error()) return errf(err)
} }
if uid == 0 { if uid == 0 {
return fmt.Errorf("godrop: you can't drop privileges to uid 0 (%s)", c.User) return errf(errors.New("unable to drop privileges to uid 0 (root)"))
} }
gid, err := groupID(c.Group) gid, err := groupID(c.Group)
if err != nil { if err != nil {
return errors.New("godrop: " + err.Error()) return errf(err)
} }
switch os.Getuid() { switch os.Getuid() {
case 0: case 0:
cmd := exec.Command(os.Args[0], os.Args[1:]...) cmd := exec.Command(os.Args[0], os.Args[1:]...)
ln, err := f() ln, err := f()
if err != nil { if err != nil {
return errors.New("godrop: " + err.Error()) return errf(err)
} }
for i, v := range ln { for i, v := range ln {
var f *os.File var f *os.File
@ -67,14 +68,20 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
f, err = l.File() f, err = l.File()
l.Close() l.Close()
default: default:
return fmt.Errorf("godrop: index %d listener is not type of either *net.TCPListener or *net.UnixListener", i) return errf(fmt.Errorf("index: %d listener is not type of either *net.TCPListener or *net.UnixListener", i))
} }
if err != nil { if err != nil {
return fmt.Errorf("godrop: index %d %s", i, err) return errf(fmt.Errorf("index: %d %s", i, err))
} }
cmd.ExtraFiles = append(cmd.ExtraFiles, f) 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{ cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: c.Chroot, Chroot: c.Chroot,
Credential: &syscall.Credential{ Credential: &syscall.Credential{
@ -84,31 +91,16 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
Setsid: true, Setsid: true,
} }
if c.Foreground {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return errors.New("godrop: " + err.Error()) return errf(err)
} }
if c.Foreground { if c.Foreground {
go func() { go func() {
term := make(chan os.Signal)
signal.Notify(term, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
sigs := make(chan os.Signal) sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGSTOP, syscall.SIGCONT, syscall.SIGUSR1, syscall.SIGUSR2) signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
for { sig := <-sigs
select {
case sig := <-term:
cmd.Process.Signal(sig) cmd.Process.Signal(sig)
return
case sig := <-sigs:
cmd.Process.Signal(sig)
}
}
}() }()
_ = cmd.Wait() _ = cmd.Wait()
os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus))) os.Exit(int(cmd.ProcessState.Sys().(syscall.WaitStatus)))
@ -118,7 +110,7 @@ func MultiDrop(c Config, f func() ([]net.Listener, error)) error {
case uid: case uid:
return nil return nil
} }
return errors.New("godrop: dropping priviledges failed") return errf(errors.New("dropping priviledges failed"))
} }
// GetListener returns the listener socket of file descriptor 3 // GetListener returns the listener socket of file descriptor 3
@ -128,9 +120,6 @@ func GetListener() (net.Listener, error) {
// GetListenerFd returns the listener socket of the given file descriptor // GetListenerFd returns the listener socket of the given file descriptor
func GetListenerFd(fd int) (net.Listener, error) { 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), "") f := os.NewFile(uintptr(fd), "")
defer f.Close() defer f.Close()
return net.FileListener(f) return net.FileListener(f)

View file

@ -1,14 +1,8 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2017 Marius Schellenberger
//go:build go1.11
// +build go1.11
package godrop package godrop
import ( import "strconv"
"os/user"
"strconv"
)
func atoi(a string) (int, error) { func atoi(a string) (int, error) {
i, err := strconv.Atoi(a) i, err := strconv.Atoi(a)
@ -18,18 +12,18 @@ func atoi(a string) (int, error) {
return i, nil return i, nil
} }
func userID(username string) (int, error) { func userID(username string) (uid int, err error) {
u, err := user.Lookup(username) id, err := lookupUID(username)
if err != nil { if err != nil {
return -1, err return -1, err
} }
return atoi(u.Uid) return atoi(id)
} }
func groupID(name string) (int, error) { func groupID(name string) (int, error) {
g, err := user.LookupGroup(name) id, err := lookupGID(name)
if err != nil { if err != nil {
return -1, err return -1, err
} }
return atoi(g.Gid) return atoi(id)
} }

23
lookup_cgo.go Normal file
View file

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

43
lookup_nocgo.go Normal file
View file

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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2017 Marius Schellenberger
package godrop package godrop

View file

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

View file

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

View file

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

View file

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