diff --git a/dnscrypt-proxy/common.go b/dnscrypt-proxy/common.go index ab9b1111..c3f268d5 100644 --- a/dnscrypt-proxy/common.go +++ b/dnscrypt-proxy/common.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "unicode" + "os" ) type CryptoConstruction uint16 @@ -36,6 +37,11 @@ var ( InitialMinQuestionSize = 256 ) +var ( + FileDescriptors = make([]*os.File, 0) + FileDescriptorNum = 0 +) + func PrefixWithSize(packet []byte) ([]byte, error) { packetLen := len(packet) if packetLen > 0xffff { diff --git a/dnscrypt-proxy/config.go b/dnscrypt-proxy/config.go index 443e49eb..eab2dc68 100644 --- a/dnscrypt-proxy/config.go +++ b/dnscrypt-proxy/config.go @@ -26,6 +26,7 @@ type Config struct { ServerNames []string `toml:"server_names"` ListenAddresses []string `toml:"listen_addresses"` Daemonize bool + Username string `toml:"username"` ForceTCP bool `toml:"force_tcp"` Timeout int `toml:"timeout"` KeepAlive int `toml:"keepalive"` @@ -186,6 +187,8 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error { jsonOutput := flag.Bool("json", false, "output list as JSON") check := flag.Bool("check", false, "check the configuration file and exit") configFile := flag.String("config", DefaultConfigFileName, "Path to the configuration file") + username := flag.String("username", "", "After binding to the port user privileges are dropped") + child := flag.Bool("start-child", false, "Invokes program as a child process") flag.Parse() @@ -225,11 +228,22 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error { dlog.UseSyslog(true) } else if config.LogFile != nil { dlog.UseLogFile(*config.LogFile) + if !*child { + FileDescriptors = append(FileDescriptors, dlog.GetFileDescriptor()) + } else { + FileDescriptorNum++ + dlog.SetFileDescriptor(os.NewFile(uintptr(3), "logFile")) + } } proxy.logMaxSize = config.LogMaxSize proxy.logMaxAge = config.LogMaxAge proxy.logMaxBackups = config.LogMaxBackups + proxy.username = config.Username + if len(*username) > 0 { + proxy.username = *username + } + proxy.child = *child proxy.xTransport = NewXTransport() proxy.xTransport.tlsDisableSessionTickets = config.TLSDisableSessionTickets proxy.xTransport.tlsCipherSuite = config.TLSCipherSuite diff --git a/dnscrypt-proxy/example-dnscrypt-proxy.toml b/dnscrypt-proxy/example-dnscrypt-proxy.toml index 124109ef..21e234ea 100644 --- a/dnscrypt-proxy/example-dnscrypt-proxy.toml +++ b/dnscrypt-proxy/example-dnscrypt-proxy.toml @@ -40,6 +40,11 @@ listen_addresses = ['127.0.0.1:53', '[::1]:53'] max_clients = 250 +## After binding to the port(s) user privileges are dropped by calling itself +## via exec.command() with SysProcAttr.Credential() + +username = 'nobody' + ## Require servers (from static + remote sources) to satisfy specific properties diff --git a/dnscrypt-proxy/privilege_free.go b/dnscrypt-proxy/privilege_free.go new file mode 100644 index 00000000..4f29db1b --- /dev/null +++ b/dnscrypt-proxy/privilege_free.go @@ -0,0 +1,56 @@ +// +build !windows + +package main + +import ( + "os" + "os/exec" + "os/user" + "path/filepath" + "strconv" + "syscall" + "github.com/jedisct1/dlog" +) + +func (proxy *Proxy) dropPrivilege(userStr string, fds []*os.File) { + user, err := user.Lookup(userStr) + args := os.Args + + if err != nil { + dlog.Fatal(err) + } + uid, err := strconv.Atoi(user.Uid) + if err != nil { + dlog.Fatal(err) + } + gid, err := strconv.Atoi(user.Gid) + if err != nil { + dlog.Fatal(err) + } + exec_path, err := exec.LookPath(args[0]) + if err != nil { + dlog.Fatal(err) + } + path, err := filepath.Abs(exec_path) + if err != nil { + dlog.Fatal(err) + } + + // remove arg[0] + copy(args[0:], args[0+1:]) + args[len(args)-1] = "" + args = args[:len(args)-1] + args = append(args, "-start-child") + + cmd := exec.Command(path, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.ExtraFiles = fds + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} + dlog.Notice("Dropping privileges") + if err := cmd.Start(); err != nil { + dlog.Fatal(err) + } + os.Exit(0) +} diff --git a/dnscrypt-proxy/privilege_windows.go b/dnscrypt-proxy/privilege_windows.go new file mode 100644 index 00000000..61c08d1b --- /dev/null +++ b/dnscrypt-proxy/privilege_windows.go @@ -0,0 +1,5 @@ +package main + +import "os" + +func (proxy *Proxy) dropPrivilege(userStr string, fds []*os.File) {} diff --git a/dnscrypt-proxy/proxy.go b/dnscrypt-proxy/proxy.go index f97bc43e..1022b13d 100644 --- a/dnscrypt-proxy/proxy.go +++ b/dnscrypt-proxy/proxy.go @@ -3,6 +3,7 @@ package main import ( "io" "io/ioutil" + "os" "math/rand" "net" "sync/atomic" @@ -15,6 +16,8 @@ import ( ) type Proxy struct { + username string + child bool proxyPublicKey [32]byte proxySecretKey [32]byte ephemeralKeys bool @@ -71,6 +74,7 @@ func (proxy *Proxy) StartProxy() { for _, registeredServer := range proxy.registeredServers { proxy.serversInfo.registerServer(proxy, registeredServer.name, registeredServer.stamp) } + for _, listenAddrStr := range proxy.listenAddresses { listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr) if err != nil { @@ -80,13 +84,69 @@ func (proxy *Proxy) StartProxy() { if err != nil { dlog.Fatal(err) } - if err := proxy.udpListenerFromAddr(listenUDPAddr); err != nil { - dlog.Fatal(err) - } - if err := proxy.tcpListenerFromAddr(listenTCPAddr); err != nil { - dlog.Fatal(err) + + // if 'username' is not set, continue as before (Todo: refactor for DRYniss) + if !(len(proxy.username) > 0) { + if err := proxy.udpListenerFromAddr(listenUDPAddr); err != nil { + dlog.Fatal(err) + } + if err := proxy.tcpListenerFromAddr(listenTCPAddr); err != nil { + dlog.Fatal(err) + } + } else { + // if 'username' is set and we are the parent process + if !proxy.child { + // parent + listenerUDP, err := net.ListenUDP("udp", listenUDPAddr) + if err != nil { + dlog.Fatal(err) + } + listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr) + if err != nil { + dlog.Fatal(err) + } + + fdUDP, err := listenerUDP.File() // On Windows, the File method of UDPConn is not implemented. + if err != nil { + dlog.Fatal(err) + } + fdTCP, err := listenerTCP.File() // On Windows, the File method of TCPListener is not implemented. + if err != nil { + dlog.Fatal(err) + } + defer listenerUDP.Close() + defer listenerTCP.Close() + FileDescriptors = append(FileDescriptors, fdUDP) + FileDescriptors = append(FileDescriptors, fdTCP) + + // if 'username' is set and we are the child process + } else { + // child + listenerUDP, err := net.FilePacketConn(os.NewFile(uintptr(3+FileDescriptorNum), "listenerUDP")) + if err != nil { + dlog.Fatal(err) + } + FileDescriptorNum++ + + listenerTCP, err := net.FileListener(os.NewFile(uintptr(3+FileDescriptorNum), "listenerTCP")) + if err != nil { + dlog.Fatal(err) + } + FileDescriptorNum++ + + dlog.Noticef("Now listening to %v [UDP]", listenUDPAddr) + go proxy.udpListener(listenerUDP.(*net.UDPConn)) + + dlog.Noticef("Now listening to %v [TCP]", listenAddrStr) + go proxy.tcpListener(listenerTCP.(*net.TCPListener)) + } } } + + // if 'username' is set and we are the parent process drop privilege and exit + if len(proxy.username) > 0 && !proxy.child { + proxy.dropPrivilege(proxy.username, FileDescriptors) + } if err := proxy.SystemDListeners(); err != nil { dlog.Fatal(err) }