"Fossies" - the Fresh Open Source Software Archive

Member "AdGuardHome-0.104.3/internal/home/home.go" (19 Nov 2020, 17185 Bytes) of package /linux/misc/dns/AdGuardHome-0.104.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Go source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "home.go": 0.104.1_vs_0.104.3.

    1 // Package home contains AdGuard Home's HTTP API methods.
    2 package home
    3 
    4 import (
    5     "context"
    6     "crypto/tls"
    7     "crypto/x509"
    8     "fmt"
    9     "io/ioutil"
   10     "net"
   11     "net/http"
   12     "net/http/pprof"
   13     "net/url"
   14     "os"
   15     "os/signal"
   16     "path/filepath"
   17     "runtime"
   18     "strconv"
   19     "sync"
   20     "syscall"
   21     "time"
   22 
   23     "github.com/AdguardTeam/AdGuardHome/internal/agherr"
   24     "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
   25     "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
   26     "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
   27     "github.com/AdguardTeam/AdGuardHome/internal/querylog"
   28     "github.com/AdguardTeam/AdGuardHome/internal/stats"
   29     "github.com/AdguardTeam/AdGuardHome/internal/update"
   30     "github.com/AdguardTeam/AdGuardHome/internal/util"
   31     "github.com/AdguardTeam/golibs/log"
   32     "gopkg.in/natefinch/lumberjack.v2"
   33 )
   34 
   35 const (
   36     // Used in config to indicate that syslog or eventlog (win) should be used for logger output
   37     configSyslog = "syslog"
   38 )
   39 
   40 // Update-related variables
   41 var (
   42     versionString   = "dev"
   43     updateChannel   = "none"
   44     versionCheckURL = ""
   45     ARMVersion      = ""
   46 )
   47 
   48 // Global context
   49 type homeContext struct {
   50     // Modules
   51     // --
   52 
   53     clients    clientsContainer     // per-client-settings module
   54     stats      stats.Stats          // statistics module
   55     queryLog   querylog.QueryLog    // query log module
   56     dnsServer  *dnsforward.Server   // DNS module
   57     rdns       *RDNS                // rDNS module
   58     whois      *Whois               // WHOIS module
   59     dnsFilter  *dnsfilter.Dnsfilter // DNS filtering module
   60     dhcpServer *dhcpd.Server        // DHCP module
   61     auth       *Auth                // HTTP authentication module
   62     filters    Filtering            // DNS filtering module
   63     web        *Web                 // Web (HTTP, HTTPS) module
   64     tls        *TLSMod              // TLS module
   65     autoHosts  util.AutoHosts       // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
   66     updater    *update.Updater
   67 
   68     // Runtime properties
   69     // --
   70 
   71     configFilename   string // Config filename (can be overridden via the command line arguments)
   72     workDir          string // Location of our directory, used to protect against CWD being somewhere else
   73     firstRun         bool   // if set to true, don't run any services except HTTP web inteface, and serve only first-run html
   74     pidFileName      string // PID file name.  Empty if no PID file was created.
   75     disableUpdate    bool   // If set, don't check for updates
   76     controlLock      sync.Mutex
   77     tlsRoots         *x509.CertPool // list of root CAs for TLSv1.2
   78     tlsCiphers       []uint16       // list of TLS ciphers to use
   79     transport        *http.Transport
   80     client           *http.Client
   81     appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
   82     // runningAsService flag is set to true when options are passed from the service runner
   83     runningAsService bool
   84 }
   85 
   86 // getDataDir returns path to the directory where we store databases and filters
   87 func (c *homeContext) getDataDir() string {
   88     return filepath.Join(c.workDir, dataDir)
   89 }
   90 
   91 // Context - a global context object
   92 var Context homeContext
   93 
   94 // Main is the entry point
   95 func Main(version, channel, armVer string) {
   96     // Init update-related global variables
   97     versionString = version
   98     updateChannel = channel
   99     ARMVersion = armVer
  100     versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json"
  101 
  102     // config can be specified, which reads options from there, but other command line flags have to override config values
  103     // therefore, we must do it manually instead of using a lib
  104     args := loadOptions()
  105 
  106     Context.appSignalChannel = make(chan os.Signal)
  107     signal.Notify(Context.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
  108     go func() {
  109         for {
  110             sig := <-Context.appSignalChannel
  111             log.Info("Received signal %q", sig)
  112             switch sig {
  113             case syscall.SIGHUP:
  114                 Context.clients.Reload()
  115                 Context.tls.Reload()
  116 
  117             default:
  118                 cleanup()
  119                 cleanupAlways()
  120                 os.Exit(0)
  121             }
  122         }
  123     }()
  124 
  125     if args.serviceControlAction != "" {
  126         handleServiceControlAction(args)
  127         return
  128     }
  129 
  130     // run the protection
  131     run(args)
  132 }
  133 
  134 // version - returns the current version string
  135 func version() string {
  136     msg := "AdGuard Home, version %s, channel %s, arch %s %s"
  137     if ARMVersion != "" {
  138         msg = msg + " v" + ARMVersion
  139     }
  140     return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH)
  141 }
  142 
  143 // run initializes configuration and runs the AdGuard Home
  144 // run is a blocking method!
  145 // nolint
  146 func run(args options) {
  147     // configure config filename
  148     initConfigFilename(args)
  149 
  150     // configure working dir and config path
  151     initWorkingDir(args)
  152 
  153     // configure log level and output
  154     configureLogger(args)
  155 
  156     // Go memory hacks
  157     memoryUsage(args)
  158 
  159     // print the first message after logger is configured
  160     log.Println(version())
  161     log.Debug("Current working directory is %s", Context.workDir)
  162     if args.runningAsService {
  163         log.Info("AdGuard Home is running as a service")
  164     }
  165     Context.runningAsService = args.runningAsService
  166     Context.disableUpdate = args.disableUpdate
  167 
  168     Context.firstRun = detectFirstRun()
  169     if Context.firstRun {
  170         log.Info("This is the first time AdGuard Home is launched")
  171         checkPermissions()
  172     }
  173 
  174     initConfig()
  175 
  176     Context.tlsRoots = util.LoadSystemRootCAs()
  177     Context.tlsCiphers = util.InitTLSCiphers()
  178     Context.transport = &http.Transport{
  179         DialContext: customDialContext,
  180         Proxy:       getHTTPProxy,
  181         TLSClientConfig: &tls.Config{
  182             RootCAs: Context.tlsRoots,
  183         },
  184     }
  185     Context.client = &http.Client{
  186         Timeout:   time.Minute * 5,
  187         Transport: Context.transport,
  188     }
  189 
  190     if !Context.firstRun {
  191         // Do the upgrade if necessary
  192         err := upgradeConfig()
  193         if err != nil {
  194             log.Fatal(err)
  195         }
  196 
  197         err = parseConfig()
  198         if err != nil {
  199             log.Error("Failed to parse configuration, exiting")
  200             os.Exit(1)
  201         }
  202 
  203         if args.checkConfig {
  204             log.Info("Configuration file is OK")
  205             os.Exit(0)
  206         }
  207     }
  208 
  209     // 'clients' module uses 'dnsfilter' module's static data (dnsfilter.BlockedSvcKnown()),
  210     //  so we have to initialize dnsfilter's static data first,
  211     //  but also avoid relying on automatic Go init() function
  212     dnsfilter.InitModule()
  213 
  214     config.DHCP.WorkDir = Context.workDir
  215     config.DHCP.HTTPRegister = httpRegister
  216     config.DHCP.ConfigModified = onConfigModified
  217 
  218     Context.dhcpServer = dhcpd.Create(config.DHCP)
  219     if Context.dhcpServer == nil {
  220         log.Fatalf("can't initialize dhcp module")
  221     }
  222 
  223     Context.autoHosts.Init("")
  224 
  225     Context.updater = update.NewUpdater(update.Config{
  226         Client:        Context.client,
  227         WorkDir:       Context.workDir,
  228         VersionURL:    versionCheckURL,
  229         VersionString: versionString,
  230         OS:            runtime.GOOS,
  231         Arch:          runtime.GOARCH,
  232         ARMVersion:    ARMVersion,
  233         ConfigName:    config.getConfigFilename(),
  234     })
  235 
  236     Context.clients.Init(config.Clients, Context.dhcpServer, &Context.autoHosts)
  237     config.Clients = nil
  238 
  239     if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
  240         config.RlimitNoFile != 0 {
  241         util.SetRlimit(config.RlimitNoFile)
  242     }
  243 
  244     // override bind host/port from the console
  245     if args.bindHost != "" {
  246         config.BindHost = args.bindHost
  247     }
  248     if args.bindPort != 0 {
  249         config.BindPort = args.bindPort
  250     }
  251     if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
  252         Context.pidFileName = args.pidFile
  253     }
  254 
  255     if !Context.firstRun {
  256         // Save the updated config
  257         err := config.write()
  258         if err != nil {
  259             log.Fatal(err)
  260         }
  261 
  262         if config.DebugPProf {
  263             mux := http.NewServeMux()
  264             mux.HandleFunc("/debug/pprof/", pprof.Index)
  265             mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
  266             mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
  267             mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
  268             mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
  269             go func() {
  270                 log.Info("pprof: listening on localhost:6060")
  271                 err := http.ListenAndServe("localhost:6060", mux)
  272                 log.Error("Error while running the pprof server: %s", err)
  273             }()
  274         }
  275     }
  276 
  277     err := os.MkdirAll(Context.getDataDir(), 0o755)
  278     if err != nil {
  279         log.Fatalf("Cannot create DNS data dir at %s: %s", Context.getDataDir(), err)
  280     }
  281 
  282     sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
  283     GLMode = args.glinetMode
  284     Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60)
  285     if Context.auth == nil {
  286         log.Fatalf("Couldn't initialize Auth module")
  287     }
  288     config.Users = nil
  289 
  290     Context.tls = tlsCreate(config.TLS)
  291     if Context.tls == nil {
  292         log.Fatalf("Can't initialize TLS module")
  293     }
  294 
  295     webConf := WebConfig{
  296         firstRun: Context.firstRun,
  297         BindHost: config.BindHost,
  298         BindPort: config.BindPort,
  299     }
  300     Context.web = CreateWeb(&webConf)
  301     if Context.web == nil {
  302         log.Fatalf("Can't initialize Web module")
  303     }
  304 
  305     if !Context.firstRun {
  306         err := initDNSServer()
  307         if err != nil {
  308             log.Fatalf("%s", err)
  309         }
  310         Context.tls.Start()
  311         Context.autoHosts.Start()
  312 
  313         go func() {
  314             err := startDNSServer()
  315             if err != nil {
  316                 log.Fatal(err)
  317             }
  318         }()
  319 
  320         if Context.dhcpServer != nil {
  321             _ = Context.dhcpServer.Start()
  322         }
  323     }
  324 
  325     Context.web.Start()
  326 
  327     // wait indefinitely for other go-routines to complete their job
  328     select {}
  329 }
  330 
  331 // StartMods - initialize and start DNS after installation
  332 func StartMods() error {
  333     err := initDNSServer()
  334     if err != nil {
  335         return err
  336     }
  337 
  338     Context.tls.Start()
  339 
  340     err = startDNSServer()
  341     if err != nil {
  342         closeDNSServer()
  343         return err
  344     }
  345     return nil
  346 }
  347 
  348 // Check if the current user permissions are enough to run AdGuard Home
  349 func checkPermissions() {
  350     log.Info("Checking if AdGuard Home has necessary permissions")
  351 
  352     if runtime.GOOS == "windows" {
  353         // On Windows we need to have admin rights to run properly
  354 
  355         admin, _ := util.HaveAdminRights()
  356         if admin {
  357             return
  358         }
  359 
  360         log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.")
  361     }
  362 
  363     // We should check if AdGuard Home is able to bind to port 53
  364     ok, err := util.CanBindPort(53)
  365 
  366     if ok {
  367         log.Info("AdGuard Home can bind to port 53")
  368         return
  369     }
  370 
  371     if opErr, ok := err.(*net.OpError); ok {
  372         if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
  373             if errno, ok := sysErr.Err.(syscall.Errno); ok && errno == syscall.EACCES {
  374                 msg := `Permission check failed.
  375 
  376 AdGuard Home is not allowed to bind to privileged ports (for instance, port 53).
  377 Please note, that this is crucial for a server to be able to use privileged ports.
  378 
  379 You have two options:
  380 1. Run AdGuard Home with root privileges
  381 2. On Linux you can grant the CAP_NET_BIND_SERVICE capability:
  382 https://github.com/AdguardTeam/AdGuardHome/internal/wiki/Getting-Started#running-without-superuser`
  383 
  384                 log.Fatal(msg)
  385             }
  386         }
  387     }
  388 
  389     msg := fmt.Sprintf(`AdGuard failed to bind to port 53 due to %v
  390 
  391 Please note, that this is crucial for a DNS server to be able to use that port.`, err)
  392 
  393     log.Info(msg)
  394 }
  395 
  396 // Write PID to a file
  397 func writePIDFile(fn string) bool {
  398     data := fmt.Sprintf("%d", os.Getpid())
  399     err := ioutil.WriteFile(fn, []byte(data), 0o644)
  400     if err != nil {
  401         log.Error("Couldn't write PID to file %s: %v", fn, err)
  402         return false
  403     }
  404     return true
  405 }
  406 
  407 func initConfigFilename(args options) {
  408     // config file path can be overridden by command-line arguments:
  409     if args.configFilename != "" {
  410         Context.configFilename = args.configFilename
  411     } else {
  412         // Default config file name
  413         Context.configFilename = "AdGuardHome.yaml"
  414     }
  415 }
  416 
  417 // initWorkingDir initializes the workDir
  418 // if no command-line arguments specified, we use the directory where our binary file is located
  419 func initWorkingDir(args options) {
  420     execPath, err := os.Executable()
  421     if err != nil {
  422         panic(err)
  423     }
  424 
  425     if args.workDir != "" {
  426         // If there is a custom config file, use it's directory as our working dir
  427         Context.workDir = args.workDir
  428     } else {
  429         Context.workDir = filepath.Dir(execPath)
  430     }
  431 }
  432 
  433 // configureLogger configures logger level and output
  434 func configureLogger(args options) {
  435     ls := getLogSettings()
  436 
  437     // command-line arguments can override config settings
  438     if args.verbose || config.Verbose {
  439         ls.Verbose = true
  440     }
  441     if args.logFile != "" {
  442         ls.LogFile = args.logFile
  443     } else if config.LogFile != "" {
  444         ls.LogFile = config.LogFile
  445     }
  446 
  447     // Handle default log settings overrides
  448     ls.LogCompress = config.LogCompress
  449     ls.LogLocalTime = config.LogLocalTime
  450     ls.LogMaxBackups = config.LogMaxBackups
  451     ls.LogMaxSize = config.LogMaxSize
  452     ls.LogMaxAge = config.LogMaxAge
  453 
  454     // log.SetLevel(log.INFO) - default
  455     if ls.Verbose {
  456         log.SetLevel(log.DEBUG)
  457     }
  458 
  459     if args.runningAsService && ls.LogFile == "" && runtime.GOOS == "windows" {
  460         // When running as a Windows service, use eventlog by default if nothing else is configured
  461         // Otherwise, we'll simply loose the log output
  462         ls.LogFile = configSyslog
  463     }
  464 
  465     // logs are written to stdout (default)
  466     if ls.LogFile == "" {
  467         return
  468     }
  469 
  470     if ls.LogFile == configSyslog {
  471         // Use syslog where it is possible and eventlog on Windows
  472         err := util.ConfigureSyslog(serviceName)
  473         if err != nil {
  474             log.Fatalf("cannot initialize syslog: %s", err)
  475         }
  476     } else {
  477         logFilePath := filepath.Join(Context.workDir, ls.LogFile)
  478         if filepath.IsAbs(ls.LogFile) {
  479             logFilePath = ls.LogFile
  480         }
  481 
  482         _, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
  483         if err != nil {
  484             log.Fatalf("cannot create a log file: %s", err)
  485         }
  486 
  487         log.SetOutput(&lumberjack.Logger{
  488             Filename:   logFilePath,
  489             Compress:   ls.LogCompress, // disabled by default
  490             LocalTime:  ls.LogLocalTime,
  491             MaxBackups: ls.LogMaxBackups,
  492             MaxSize:    ls.LogMaxSize, // megabytes
  493             MaxAge:     ls.LogMaxAge,  // days
  494         })
  495     }
  496 }
  497 
  498 func cleanup() {
  499     log.Info("Stopping AdGuard Home")
  500 
  501     if Context.web != nil {
  502         Context.web.Close()
  503         Context.web = nil
  504     }
  505     if Context.auth != nil {
  506         Context.auth.Close()
  507         Context.auth = nil
  508     }
  509 
  510     err := stopDNSServer()
  511     if err != nil {
  512         log.Error("Couldn't stop DNS server: %s", err)
  513     }
  514 
  515     if Context.dhcpServer != nil {
  516         Context.dhcpServer.Stop()
  517     }
  518 
  519     Context.autoHosts.Close()
  520 
  521     if Context.tls != nil {
  522         Context.tls.Close()
  523         Context.tls = nil
  524     }
  525 }
  526 
  527 // This function is called before application exits
  528 func cleanupAlways() {
  529     if len(Context.pidFileName) != 0 {
  530         _ = os.Remove(Context.pidFileName)
  531     }
  532     log.Info("Stopped")
  533 }
  534 
  535 func exitWithError() {
  536     os.Exit(64)
  537 }
  538 
  539 // loadOptions reads command line arguments and initializes configuration
  540 func loadOptions() options {
  541     o, f, err := parse(os.Args[0], os.Args[1:])
  542 
  543     if err != nil {
  544         log.Error(err.Error())
  545         _ = printHelp(os.Args[0])
  546         exitWithError()
  547     } else if f != nil {
  548         err = f()
  549         if err != nil {
  550             log.Error(err.Error())
  551             exitWithError()
  552         } else {
  553             os.Exit(0)
  554         }
  555     }
  556 
  557     return o
  558 }
  559 
  560 // prints IP addresses which user can use to open the admin interface
  561 // proto is either "http" or "https"
  562 func printHTTPAddresses(proto string) {
  563     var address string
  564 
  565     tlsConf := tlsConfigSettings{}
  566     if Context.tls != nil {
  567         Context.tls.WriteDiskConfig(&tlsConf)
  568     }
  569 
  570     port := strconv.Itoa(config.BindPort)
  571     if proto == "https" {
  572         port = strconv.Itoa(tlsConf.PortHTTPS)
  573     }
  574 
  575     if proto == "https" && tlsConf.ServerName != "" {
  576         if tlsConf.PortHTTPS == 443 {
  577             log.Printf("Go to https://%s", tlsConf.ServerName)
  578         } else {
  579             log.Printf("Go to https://%s:%s", tlsConf.ServerName, port)
  580         }
  581     } else if config.BindHost == "0.0.0.0" {
  582         log.Println("AdGuard Home is available on the following addresses:")
  583         ifaces, err := util.GetValidNetInterfacesForWeb()
  584         if err != nil {
  585             // That's weird, but we'll ignore it
  586             address = net.JoinHostPort(config.BindHost, port)
  587             log.Printf("Go to %s://%s", proto, address)
  588             return
  589         }
  590 
  591         for _, iface := range ifaces {
  592             for _, addr := range iface.Addresses {
  593                 address = net.JoinHostPort(addr, strconv.Itoa(config.BindPort))
  594                 log.Printf("Go to %s://%s", proto, address)
  595             }
  596         }
  597     } else {
  598         address = net.JoinHostPort(config.BindHost, port)
  599         log.Printf("Go to %s://%s", proto, address)
  600     }
  601 }
  602 
  603 // -------------------
  604 // first run / install
  605 // -------------------
  606 func detectFirstRun() bool {
  607     configfile := Context.configFilename
  608     if !filepath.IsAbs(configfile) {
  609         configfile = filepath.Join(Context.workDir, Context.configFilename)
  610     }
  611     _, err := os.Stat(configfile)
  612     return os.IsNotExist(err)
  613 }
  614 
  615 // Connect to a remote server resolving hostname using our own DNS server
  616 func customDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
  617     log.Tracef("network:%v  addr:%v", network, addr)
  618 
  619     host, port, err := net.SplitHostPort(addr)
  620     if err != nil {
  621         return nil, err
  622     }
  623 
  624     dialer := &net.Dialer{
  625         Timeout: time.Minute * 5,
  626     }
  627 
  628     if net.ParseIP(host) != nil || config.DNS.Port == 0 {
  629         con, err := dialer.DialContext(ctx, network, addr)
  630         return con, err
  631     }
  632 
  633     addrs, e := Context.dnsServer.Resolve(host)
  634     log.Debug("dnsServer.Resolve: %s: %v", host, addrs)
  635     if e != nil {
  636         return nil, e
  637     }
  638 
  639     if len(addrs) == 0 {
  640         return nil, fmt.Errorf("couldn't lookup host: %s", host)
  641     }
  642 
  643     var dialErrs []error
  644     for _, a := range addrs {
  645         addr = net.JoinHostPort(a.String(), port)
  646         con, err := dialer.DialContext(ctx, network, addr)
  647         if err != nil {
  648             dialErrs = append(dialErrs, err)
  649             continue
  650         }
  651         return con, err
  652     }
  653     return nil, agherr.Many(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
  654 }
  655 
  656 func getHTTPProxy(req *http.Request) (*url.URL, error) {
  657     if len(config.ProxyURL) == 0 {
  658         return nil, nil
  659     }
  660     return url.Parse(config.ProxyURL)
  661 }