*(home): do not require root privileges on the first run

Instead of requiring root privileges, we now check if AdGuard Home can
bind to privileged ports. If it cannot, we suggest either running it
with root privileges or grant CAP_NET_BIND_SERVICE capability. Please
note, that on Windows we still require root access.

Closes: #1699
This commit is contained in:
Andrey Meshkov 2020-06-23 18:02:28 +03:00
parent 49a92605b8
commit b43223d302
2 changed files with 63 additions and 28 deletions

View File

@ -1,23 +1,19 @@
package home package home
import ( import (
"bufio"
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@ -172,7 +168,7 @@ func run(args options) {
Context.firstRun = detectFirstRun() Context.firstRun = detectFirstRun()
if Context.firstRun { if Context.firstRun {
log.Info("This is the first time AdGuard Home is launched") log.Info("This is the first time AdGuard Home is launched")
requireAdminRights() checkPermissions()
} }
initConfig() initConfig()
@ -334,36 +330,54 @@ func StartMods() error {
// Check if the current user has root (administrator) rights // Check if the current user has root (administrator) rights
// and if not, ask and try to run as root // and if not, ask and try to run as root
func requireAdminRights() { func checkPermissions() {
admin, _ := util.HaveAdminRights() log.Info("Checking if AdGuard Home has necessary permissions")
if //noinspection ALL
admin || isdelve.Enabled { if runtime.GOOS == "windows" {
// Don't forget that for this to work you need to add "delve" tag explicitly // On Windows we need to have admin rights to run properly
// https://stackoverflow.com/questions/47879070/how-can-i-see-if-the-goland-debugger-is-running-in-the-program
admin, _ := util.HaveAdminRights()
if //noinspection ALL
admin || isdelve.Enabled {
// Don't forget that for this to work you need to add "delve" tag explicitly
// https://stackoverflow.com/questions/47879070/how-can-i-see-if-the-goland-debugger-is-running-in-the-program
return
}
log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.")
}
// We should check if AdGuard Home is able to bind to port 53
ok, err := util.CanBindPort(53)
if ok {
log.Info("AdGuard Home can bind to port 53")
return return
} }
if runtime.GOOS == "windows" { if opErr, ok := err.(*net.OpError); ok {
log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.") if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
if errno, ok := sysErr.Err.(syscall.Errno); ok && errno == syscall.EACCES {
msg := `Permission check failed.
} else { AdGuard Home is not allowed to bind to privileged ports (for instance, port 53).
log.Error("This is the first launch of AdGuard Home. You must run it as root.") Please note, that this is crucial for a server to be able to use privileged ports.
_, _ = io.WriteString(os.Stdout, "Do you want to start AdGuard Home as root user? [y/n] ") You have two options:
stdin := bufio.NewReader(os.Stdin) 1. Run AdGuard Home with root privileges
buf, _ := stdin.ReadString('\n') 2. On Linux you can grant the CAP_NET_BIND_SERVICE capability:
buf = strings.TrimSpace(buf) https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser`
if buf != "y" {
os.Exit(1) log.Fatal(msg)
}
} }
cmd := exec.Command("sudo", os.Args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
_ = cmd.Run()
os.Exit(1)
} }
msg := fmt.Sprintf(`AdGuard failed to bind to port 53 due to %v
Please note, that this is crucial for a DNS server to be able to use that port.`, err)
log.Info(msg)
} }
// Write PID to a file // Write PID to a file

21
util/net.go Normal file
View File

@ -0,0 +1,21 @@
package util
import (
"fmt"
"net"
)
// CanBindPort - checks if we can bind to this port or not
func CanBindPort(port int) (bool, error) {
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return false, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return false, err
}
_ = l.Close()
return true, nil
}