2018-08-30 17:25:33 +03:00
package main
import (
2019-02-10 20:47:43 +03:00
"bytes"
2019-02-06 16:47:17 +03:00
"context"
2019-02-15 15:16:25 +03:00
"crypto"
"crypto/ecdsa"
"crypto/rsa"
2019-01-30 21:09:29 +03:00
"crypto/tls"
"crypto/x509"
2019-02-01 14:10:39 +03:00
"encoding/base64"
2018-08-30 17:25:33 +03:00
"encoding/json"
2019-01-30 21:09:29 +03:00
"encoding/pem"
2019-02-15 15:16:25 +03:00
"errors"
2018-08-30 17:25:33 +03:00
"fmt"
"io/ioutil"
2018-12-05 19:17:17 +03:00
"net"
2018-08-30 17:25:33 +03:00
"net/http"
"os"
2019-02-13 11:08:07 +03:00
"reflect"
2019-02-10 20:47:43 +03:00
"sort"
2018-08-30 17:25:33 +03:00
"strconv"
"strings"
"time"
2018-12-05 14:21:25 +03:00
"github.com/AdguardTeam/AdGuardHome/dnsforward"
2018-12-29 17:23:42 +03:00
"github.com/AdguardTeam/dnsproxy/upstream"
2019-02-27 18:28:09 +03:00
"github.com/AdguardTeam/golibs/log"
2019-02-12 20:02:52 +03:00
"github.com/joomcode/errorx"
2018-12-05 19:17:17 +03:00
"github.com/miekg/dns"
2019-01-25 16:01:27 +03:00
govalidator "gopkg.in/asaskevich/govalidator.v4"
2018-08-30 17:25:33 +03:00
)
const updatePeriod = time . Minute * 30
2018-09-20 20:02:25 +03:00
// cached version.json to avoid hammering github.io for each page reload
var versionCheckJSON [ ] byte
var versionCheckLastTime time . Time
2018-10-15 16:02:19 +03:00
const versionCheckURL = "https://adguardteam.github.io/AdGuardHome/version.json"
2018-09-20 20:02:25 +03:00
const versionCheckPeriod = time . Hour * 8
2018-10-10 15:47:08 +03:00
var client = & http . Client {
Timeout : time . Second * 30 ,
}
2019-02-10 20:47:43 +03:00
// ----------------
// helper functions
// ----------------
func returnOK ( w http . ResponseWriter ) {
_ , err := fmt . Fprintf ( w , "OK\n" )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write body: %s" , err )
2019-02-10 20:47:43 +03:00
}
}
func httpError ( w http . ResponseWriter , code int , format string , args ... interface { } ) {
text := fmt . Sprintf ( format , args ... )
2019-02-25 16:44:22 +03:00
log . Info ( text )
2019-02-10 20:47:43 +03:00
http . Error ( w , text , code )
}
// ---------------
2018-12-05 20:29:00 +03:00
// dns run control
2019-02-10 20:47:43 +03:00
// ---------------
2018-12-05 20:29:00 +03:00
func writeAllConfigsAndReloadDNS ( ) error {
2018-08-30 17:25:33 +03:00
err := writeAllConfigs ( )
if err != nil {
2019-02-25 16:44:22 +03:00
log . Error ( "Couldn't write all configs: %s" , err )
2018-08-30 17:25:33 +03:00
return err
}
2019-01-24 20:11:01 +03:00
return reconfigureDNSServer ( )
2018-08-30 17:25:33 +03:00
}
2018-10-10 20:13:03 +03:00
func httpUpdateConfigReloadDNSReturnOK ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 20:29:00 +03:00
err := writeAllConfigsAndReloadDNS ( )
2018-10-10 20:13:03 +03:00
if err != nil {
2019-02-15 17:06:55 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write config file: %s" , err )
2018-10-10 20:13:03 +03:00
return
}
2019-01-24 20:11:01 +03:00
returnOK ( w )
2018-10-10 20:13:03 +03:00
}
2018-08-30 17:25:33 +03:00
func handleStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
data := map [ string ] interface { } {
2019-02-22 18:16:47 +03:00
"dns_address" : config . DNS . BindHost ,
2019-02-20 12:24:56 +03:00
"http_port" : config . BindPort ,
2018-12-05 20:29:00 +03:00
"dns_port" : config . DNS . Port ,
"protection_enabled" : config . DNS . ProtectionEnabled ,
"querylog_enabled" : config . DNS . QueryLogEnabled ,
2018-10-10 20:13:03 +03:00
"running" : isRunning ( ) ,
2018-12-05 20:29:00 +03:00
"bootstrap_dns" : config . DNS . BootstrapDNS ,
"upstream_dns" : config . DNS . UpstreamDNS ,
2019-02-26 18:19:05 +03:00
"all_servers" : config . DNS . AllServers ,
2018-10-10 20:13:03 +03:00
"version" : VersionString ,
2018-11-21 20:42:55 +03:00
"language" : config . Language ,
2018-08-30 17:25:33 +03:00
}
2018-10-20 19:58:39 +03:00
jsonVal , err := json . Marshal ( data )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 19:58:39 +03:00
_ , err = w . Write ( jsonVal )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
}
2018-10-10 20:13:03 +03:00
func handleProtectionEnable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . ProtectionEnabled = true
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
}
func handleProtectionDisable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . ProtectionEnabled = false
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
}
2018-08-30 17:25:33 +03:00
// -----
// stats
// -----
func handleQueryLogEnable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . QueryLogEnabled = true
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleQueryLogDisable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . QueryLogEnabled = false
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
2019-02-10 20:47:43 +03:00
func handleQueryLog ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-10 20:47:43 +03:00
data := dnsServer . GetQueryLog ( )
jsonVal , err := json . Marshal ( data )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't marshal data into json: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( jsonVal )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2019-02-10 20:47:43 +03:00
}
}
func handleStatsTop ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-10 20:47:43 +03:00
s := dnsServer . GetStatsTop ( )
// use manual json marshalling because we want maps to be sorted by value
statsJSON := bytes . Buffer { }
statsJSON . WriteString ( "{\n" )
gen := func ( json * bytes . Buffer , name string , top map [ string ] int , addComma bool ) {
json . WriteString ( " " )
json . WriteString ( fmt . Sprintf ( "%q" , name ) )
json . WriteString ( ": {\n" )
sorted := sortByValue ( top )
// no more than 50 entries
if len ( sorted ) > 50 {
sorted = sorted [ : 50 ]
}
for i , key := range sorted {
json . WriteString ( " " )
json . WriteString ( fmt . Sprintf ( "%q" , key ) )
json . WriteString ( ": " )
json . WriteString ( strconv . Itoa ( top [ key ] ) )
if i + 1 != len ( sorted ) {
json . WriteByte ( ',' )
}
json . WriteByte ( '\n' )
}
json . WriteString ( " }" )
if addComma {
json . WriteByte ( ',' )
}
json . WriteByte ( '\n' )
}
gen ( & statsJSON , "top_queried_domains" , s . Domains , true )
gen ( & statsJSON , "top_blocked_domains" , s . Blocked , true )
gen ( & statsJSON , "top_clients" , s . Clients , true )
statsJSON . WriteString ( " \"stats_period\": \"24 hours\"\n" )
statsJSON . WriteString ( "}\n" )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err := w . Write ( statsJSON . Bytes ( ) )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write body: %s" , err )
2019-02-10 20:47:43 +03:00
}
}
// handleStatsReset resets the stats caches
func handleStatsReset ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-11 14:22:36 +03:00
dnsServer . PurgeStats ( )
2019-02-10 20:47:43 +03:00
_ , err := fmt . Fprintf ( w , "OK\n" )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write body: %s" , err )
2019-02-10 20:47:43 +03:00
}
}
// handleStats returns aggregated stats data for the 24 hours
func handleStats ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-10 20:47:43 +03:00
summed := dnsServer . GetAggregatedStats ( )
statsJSON , err := json . Marshal ( summed )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( statsJSON )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
2018-09-26 17:47:23 +03:00
}
2019-02-10 20:47:43 +03:00
// HandleStatsHistory returns historical stats data for the 24 hours
func handleStatsHistory ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-10 20:47:43 +03:00
// handle time unit and prepare our time window size
timeUnitString := r . URL . Query ( ) . Get ( "time_unit" )
var timeUnit time . Duration
switch timeUnitString {
case "seconds" :
timeUnit = time . Second
case "minutes" :
timeUnit = time . Minute
case "hours" :
timeUnit = time . Hour
case "days" :
timeUnit = time . Hour * 24
default :
http . Error ( w , "Must specify valid time_unit parameter" , http . StatusBadRequest )
return
}
// parse start and end time
startTime , err := time . Parse ( time . RFC3339 , r . URL . Query ( ) . Get ( "start_time" ) )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Must specify valid start_time parameter: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
endTime , err := time . Parse ( time . RFC3339 , r . URL . Query ( ) . Get ( "end_time" ) )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Must specify valid end_time parameter: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
data , err := dnsServer . GetStatsHistory ( timeUnit , startTime , endTime )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Cannot get stats history: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
statsJSON , err := json . Marshal ( data )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( statsJSON )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2019-02-10 20:47:43 +03:00
return
}
}
// sortByValue is a helper function for querylog API
func sortByValue ( m map [ string ] int ) [ ] string {
type kv struct {
k string
v int
}
var ss [ ] kv
for k , v := range m {
ss = append ( ss , kv { k , v } )
}
sort . Slice ( ss , func ( l , r int ) bool {
return ss [ l ] . v > ss [ r ] . v
} )
sorted := [ ] string { }
for _ , v := range ss {
sorted = append ( sorted , v . k )
}
return sorted
}
// -----------------------
// upstreams configuration
// -----------------------
2019-02-28 13:01:41 +03:00
// TODO this struct will become unnecessary after config file rework
type upstreamConfig struct {
upstreams [ ] string // Upstreams
bootstrapDNS [ ] string // Bootstrap DNS
allServers bool // --all-servers param for dnsproxy
2019-02-28 11:10:43 +03:00
}
2019-02-28 13:01:41 +03:00
func handleSetUpstreamConfig ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:49:53 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-28 13:01:41 +03:00
newconfig := upstreamConfig { }
err := json . NewDecoder ( r . Body ) . Decode ( & newconfig )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-28 13:01:41 +03:00
httpError ( w , http . StatusBadRequest , "Failed to parse new upstreams config json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
2018-11-06 00:47:59 +03:00
2019-02-28 15:06:30 +03:00
config . DNS . UpstreamDNS = defaultDNS
if len ( newconfig . upstreams ) > 0 {
config . DNS . UpstreamDNS = newconfig . upstreams
}
2019-02-28 13:01:41 +03:00
2019-02-27 12:58:42 +03:00
// bootstrap servers are plain DNS only. We should remove tls:// https:// and sdns:// hosts from slice
bootstraps := [ ] string { }
2019-02-28 15:18:51 +03:00
for _ , host := range newconfig . bootstrapDNS {
err := checkBootstrapDNS ( host )
if err != nil {
log . Tracef ( "%s can not be used as bootstrap DNS cause: %s" , host , err )
continue
2019-02-27 12:58:42 +03:00
}
2019-02-28 15:18:51 +03:00
bootstraps = append ( bootstraps , host )
2019-02-27 12:58:42 +03:00
}
2019-02-28 15:06:30 +03:00
config . DNS . BootstrapDNS = defaultBootstrap
if len ( bootstraps ) > 0 {
config . DNS . BootstrapDNS = bootstraps
2019-02-27 12:58:42 +03:00
}
2019-02-28 15:06:30 +03:00
config . DNS . AllServers = newconfig . allServers
httpUpdateConfigReloadDNSReturnOK ( w , r )
2019-02-26 18:19:05 +03:00
}
2019-02-27 13:12:06 +03:00
// checkBootstrapDNS checks if host is plain DNS
2019-02-27 12:58:42 +03:00
func checkBootstrapDNS ( host string ) error {
// Check if host is ip without port
if net . ParseIP ( host ) != nil {
return nil
}
// Check if host is ip with port
_ , _ , err := net . SplitHostPort ( host )
return err
}
2018-09-19 19:12:09 +03:00
func handleTestUpstreamDNS ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-03-05 12:29:52 +03:00
hosts := [ ] string { }
err := json . NewDecoder ( r . Body ) . Decode ( & hosts )
2018-09-19 19:12:09 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Failed to read request body: %s" , err )
2018-09-19 19:12:09 +03:00
return
}
if len ( hosts ) == 0 {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "No servers specified" )
2018-09-19 19:12:09 +03:00
return
}
result := map [ string ] string { }
for _ , host := range hosts {
2018-10-07 23:43:24 +03:00
err = checkDNS ( host )
2018-09-19 19:12:09 +03:00
if err != nil {
2019-02-25 16:44:22 +03:00
log . Info ( "%v" , err )
2018-09-19 19:12:09 +03:00
result [ host ] = err . Error ( )
} else {
result [ host ] = "OK"
}
}
2018-10-20 19:58:39 +03:00
jsonVal , err := json . Marshal ( result )
2018-09-19 19:12:09 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2018-09-19 19:12:09 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 19:58:39 +03:00
_ , err = w . Write ( jsonVal )
2018-09-19 19:12:09 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write body: %s" , err )
2018-09-19 19:12:09 +03:00
}
}
2018-09-26 17:47:23 +03:00
func checkDNS ( input string ) error {
2019-02-25 16:44:22 +03:00
log . Debug ( "Checking if DNS %s works..." , input )
2019-02-22 18:16:47 +03:00
u , err := upstream . AddressToUpstream ( input , upstream . Options { Timeout : dnsforward . DefaultTimeout } )
2018-09-26 17:47:23 +03:00
if err != nil {
2019-01-24 20:11:01 +03:00
return fmt . Errorf ( "failed to choose upstream for %s: %s" , input , err )
2018-09-19 19:12:09 +03:00
}
2018-09-26 17:47:23 +03:00
2018-12-05 19:17:17 +03:00
req := dns . Msg { }
req . Id = dns . Id ( )
req . RecursionDesired = true
req . Question = [ ] dns . Question {
{ Name : "google-public-dns-a.google.com." , Qtype : dns . TypeA , Qclass : dns . ClassINET } ,
}
reply , err := u . Exchange ( & req )
2018-09-19 19:12:09 +03:00
if err != nil {
2018-10-30 12:24:59 +03:00
return fmt . Errorf ( "couldn't communicate with DNS server %s: %s" , input , err )
2018-09-19 19:12:09 +03:00
}
2018-12-05 19:17:17 +03:00
if len ( reply . Answer ) != 1 {
return fmt . Errorf ( "DNS server %s returned wrong answer" , input )
}
if t , ok := reply . Answer [ 0 ] . ( * dns . A ) ; ok {
if ! net . IPv4 ( 8 , 8 , 8 , 8 ) . Equal ( t . A ) {
return fmt . Errorf ( "DNS server %s returned wrong answer: %v" , input , t . A )
}
2018-09-26 17:47:23 +03:00
}
2019-02-25 16:44:22 +03:00
log . Debug ( "DNS %s works OK" , input )
2018-11-06 00:47:59 +03:00
return nil
2018-08-30 17:25:33 +03:00
}
2018-09-20 20:02:25 +03:00
func handleGetVersionJSON ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-09-20 20:02:25 +03:00
now := time . Now ( )
if now . Sub ( versionCheckLastTime ) <= versionCheckPeriod && len ( versionCheckJSON ) != 0 {
// return cached copy
w . Header ( ) . Set ( "Content-Type" , "application/json" )
w . Write ( versionCheckJSON )
return
}
resp , err := client . Get ( versionCheckURL )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadGateway , "Couldn't get version check json from %s: %T %s\n" , versionCheckURL , err , err )
2018-09-20 20:02:25 +03:00
return
}
if resp != nil && resp . Body != nil {
defer resp . Body . Close ( )
}
// read the body entirely
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadGateway , "Couldn't read response body from %s: %s" , versionCheckURL , err )
2018-09-20 20:02:25 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write body: %s" , err )
2018-09-20 20:02:25 +03:00
}
versionCheckLastTime = now
versionCheckJSON = body
}
2018-08-30 17:25:33 +03:00
// ---------
// filtering
// ---------
func handleFilteringEnable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . FilteringEnabled = true
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleFilteringDisable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . FilteringEnabled = false
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleFilteringStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
data := map [ string ] interface { } {
2018-12-05 20:29:00 +03:00
"enabled" : config . DNS . FilteringEnabled ,
2018-08-30 17:25:33 +03:00
}
2018-10-07 00:58:59 +03:00
config . RLock ( )
2018-08-30 17:25:33 +03:00
data [ "filters" ] = config . Filters
data [ "user_rules" ] = config . UserRules
2018-10-20 19:58:39 +03:00
jsonVal , err := json . Marshal ( data )
2018-10-07 00:58:59 +03:00
config . RUnlock ( )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 19:58:39 +03:00
_ , err = w . Write ( jsonVal )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
}
func handleFilteringAddURL ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-01-25 16:01:27 +03:00
f := filter { }
err := json . NewDecoder ( r . Body ) . Decode ( & f )
2018-08-30 17:25:33 +03:00
if err != nil {
2018-10-11 18:32:23 +03:00
httpError ( w , http . StatusBadRequest , "Failed to parse request body json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
2019-01-25 16:01:27 +03:00
if len ( f . URL ) == 0 {
2019-02-27 17:28:10 +03:00
http . Error ( w , "URL parameter was not specified" , http . StatusBadRequest )
2018-08-30 17:25:33 +03:00
return
}
2019-01-25 16:01:27 +03:00
if valid := govalidator . IsRequestURL ( f . URL ) ; ! valid {
2019-02-27 17:28:10 +03:00
http . Error ( w , "URL parameter is not valid request URL" , http . StatusBadRequest )
2018-08-30 17:25:33 +03:00
return
}
2018-09-14 04:33:54 +03:00
2018-10-30 12:24:59 +03:00
// Check for duplicates
2018-09-14 04:33:54 +03:00
for i := range config . Filters {
2019-01-25 16:01:27 +03:00
if config . Filters [ i ] . URL == f . URL {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Filter URL already added -- %s" , f . URL )
2018-09-14 04:33:54 +03:00
return
}
}
2018-10-30 12:24:59 +03:00
// Set necessary properties
2019-01-25 16:01:27 +03:00
f . ID = assignUniqueFilterID ( )
f . Enabled = true
2018-10-30 12:24:59 +03:00
// Download the filter contents
2019-01-25 16:01:27 +03:00
ok , err := f . update ( true )
2018-09-14 04:33:54 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Couldn't fetch filter from url %s: %s" , f . URL , err )
2018-09-14 04:33:54 +03:00
return
}
2019-01-25 16:01:27 +03:00
if f . RulesCount == 0 {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Filter at the url %s has no rules (maybe it points to blank page?)" , f . URL )
2018-09-14 04:33:54 +03:00
return
}
if ! ok {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Filter at the url %s is invalid (maybe it points to blank page?)" , f . URL )
2018-10-30 12:24:59 +03:00
return
}
// Save the filter contents
2019-01-25 16:01:27 +03:00
err = f . save ( )
2018-10-30 12:24:59 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Failed to save filter %d due to %s" , f . ID , err )
2018-09-14 04:33:54 +03:00
return
}
2018-12-05 20:29:00 +03:00
// URL is deemed valid, append it to filters, update config, write new filter file and tell dns to reload it
2019-01-24 20:11:01 +03:00
// TODO: since we directly feed filters in-memory, revisit if writing configs is always necessary
2019-01-25 16:01:27 +03:00
config . Filters = append ( config . Filters , f )
2018-08-30 17:25:33 +03:00
err = writeAllConfigs ( )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write config file: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
2018-10-30 02:17:24 +03:00
2019-01-25 16:01:27 +03:00
err = reconfigureDNSServer ( )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't reconfigure the DNS server: %s" , err )
2019-01-25 16:01:27 +03:00
}
2018-10-30 02:17:24 +03:00
2019-01-25 16:01:27 +03:00
_ , err = fmt . Fprintf ( w , "OK %d rules\n" , f . RulesCount )
2018-09-14 16:50:56 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write body: %s" , err )
2018-09-14 16:50:56 +03:00
}
2018-08-30 17:25:33 +03:00
}
func handleFilteringRemoveURL ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "failed to parse parameters from body: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
url , ok := parameters [ "url" ]
if ! ok {
2019-02-27 17:28:10 +03:00
http . Error ( w , "URL parameter was not specified" , http . StatusBadRequest )
2018-08-30 17:25:33 +03:00
return
}
if valid := govalidator . IsRequestURL ( url ) ; ! valid {
2019-02-27 17:28:10 +03:00
http . Error ( w , "URL parameter is not valid request URL" , http . StatusBadRequest )
2018-08-30 17:25:33 +03:00
return
}
// go through each element and delete if url matches
newFilters := config . Filters [ : 0 ]
for _ , filter := range config . Filters {
if filter . URL != url {
newFilters = append ( newFilters , filter )
2018-10-30 02:17:24 +03:00
} else {
// Remove the filter file
2018-11-27 16:48:57 +03:00
err := os . Remove ( filter . Path ( ) )
2019-01-04 21:07:21 +03:00
if err != nil && ! os . IsNotExist ( err ) {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't remove the filter file: %s" , err )
2018-10-30 02:17:24 +03:00
return
}
2018-08-30 17:25:33 +03:00
}
}
2018-10-30 02:17:24 +03:00
// Update the configuration after removing filter files
2018-08-30 17:25:33 +03:00
config . Filters = newFilters
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleFilteringEnableURL ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "failed to parse parameters from body: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
url , ok := parameters [ "url" ]
if ! ok {
2019-02-27 17:28:10 +03:00
http . Error ( w , "URL parameter was not specified" , http . StatusBadRequest )
2018-08-30 17:25:33 +03:00
return
}
if valid := govalidator . IsRequestURL ( url ) ; ! valid {
http . Error ( w , "URL parameter is not valid request URL" , http . StatusBadRequest )
return
}
found := false
for i := range config . Filters {
filter := & config . Filters [ i ] // otherwise we will be operating on a copy
if filter . URL == url {
filter . Enabled = true
found = true
}
}
if ! found {
http . Error ( w , "URL parameter was not previously added" , http . StatusBadRequest )
return
}
// kick off refresh of rules from new URLs
2019-01-24 20:11:01 +03:00
refreshFiltersIfNecessary ( false )
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleFilteringDisableURL ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "failed to parse parameters from body: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
url , ok := parameters [ "url" ]
if ! ok {
2019-02-27 17:28:10 +03:00
http . Error ( w , "URL parameter was not specified" , http . StatusBadRequest )
2018-08-30 17:25:33 +03:00
return
}
if valid := govalidator . IsRequestURL ( url ) ; ! valid {
http . Error ( w , "URL parameter is not valid request URL" , http . StatusBadRequest )
return
}
found := false
for i := range config . Filters {
filter := & config . Filters [ i ] // otherwise we will be operating on a copy
if filter . URL == url {
filter . Enabled = false
found = true
}
}
if ! found {
http . Error ( w , "URL parameter was not previously added" , http . StatusBadRequest )
return
}
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleFilteringSetRules ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
body , err := ioutil . ReadAll ( r . Body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "Failed to read request body: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
config . UserRules = strings . Split ( string ( body ) , "\n" )
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleFilteringRefresh ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
force := r . URL . Query ( ) . Get ( "force" )
2019-01-24 20:11:01 +03:00
updated := refreshFiltersIfNecessary ( force != "" )
2018-08-30 17:25:33 +03:00
fmt . Fprintf ( w , "OK %d filters updated\n" , updated )
}
// ------------
// safebrowsing
// ------------
func handleSafeBrowsingEnable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . SafeBrowsingEnabled = true
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleSafeBrowsingDisable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . SafeBrowsingEnabled = false
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleSafeBrowsingStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
data := map [ string ] interface { } {
2018-12-05 20:29:00 +03:00
"enabled" : config . DNS . SafeBrowsingEnabled ,
2018-08-30 17:25:33 +03:00
}
2018-10-20 19:58:39 +03:00
jsonVal , err := json . Marshal ( data )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2018-08-30 17:25:33 +03:00
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 19:58:39 +03:00
_ , err = w . Write ( jsonVal )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
}
// --------
// parental
// --------
func handleParentalEnable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusBadRequest , "failed to parse parameters from body: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
sensitivity , ok := parameters [ "sensitivity" ]
if ! ok {
http . Error ( w , "URL parameter was not specified" , 400 )
return
}
switch sensitivity {
case "3" :
break
case "EARLY_CHILDHOOD" :
sensitivity = "3"
case "10" :
break
case "YOUNG" :
sensitivity = "10"
case "13" :
break
case "TEEN" :
sensitivity = "13"
case "17" :
break
case "MATURE" :
sensitivity = "17"
default :
http . Error ( w , "Sensitivity must be set to valid value" , 400 )
return
}
i , err := strconv . Atoi ( sensitivity )
if err != nil {
http . Error ( w , "Sensitivity must be set to valid value" , 400 )
return
}
2018-12-05 20:29:00 +03:00
config . DNS . ParentalSensitivity = i
config . DNS . ParentalEnabled = true
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleParentalDisable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . ParentalEnabled = false
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleParentalStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
data := map [ string ] interface { } {
2018-12-05 20:29:00 +03:00
"enabled" : config . DNS . ParentalEnabled ,
2018-08-30 17:25:33 +03:00
}
2018-12-05 20:29:00 +03:00
if config . DNS . ParentalEnabled {
data [ "sensitivity" ] = config . DNS . ParentalSensitivity
2018-08-30 17:25:33 +03:00
}
2018-10-20 19:58:39 +03:00
jsonVal , err := json . Marshal ( data )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 19:58:39 +03:00
_ , err = w . Write ( jsonVal )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
}
// ------------
// safebrowsing
// ------------
func handleSafeSearchEnable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . SafeSearchEnabled = true
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleSafeSearchDisable ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-12-05 20:29:00 +03:00
config . DNS . SafeSearchEnabled = false
2018-10-10 20:13:03 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 17:25:33 +03:00
}
func handleSafeSearchStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2018-08-30 17:25:33 +03:00
data := map [ string ] interface { } {
2018-12-05 20:29:00 +03:00
"enabled" : config . DNS . SafeSearchEnabled ,
2018-08-30 17:25:33 +03:00
}
2018-10-20 19:58:39 +03:00
jsonVal , err := json . Marshal ( data )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to marshal status json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 19:58:39 +03:00
_ , err = w . Write ( jsonVal )
2018-08-30 17:25:33 +03:00
if err != nil {
2019-02-27 17:28:10 +03:00
httpError ( w , http . StatusInternalServerError , "Unable to write response json: %s" , err )
2018-08-30 17:25:33 +03:00
return
}
}
2019-01-29 20:41:57 +03:00
type ipport struct {
2019-02-01 18:59:42 +03:00
IP string ` json:"ip,omitempty" `
2019-02-01 18:18:08 +03:00
Port int ` json:"port" `
Warning string ` json:"warning" `
2019-01-29 20:41:57 +03:00
}
type firstRunData struct {
2019-01-31 14:56:34 +03:00
Web ipport ` json:"web" `
DNS ipport ` json:"dns" `
Username string ` json:"username,omitempty" `
Password string ` json:"password,omitempty" `
Interfaces map [ string ] interface { } ` json:"interfaces" `
2019-01-29 20:41:57 +03:00
}
2019-02-01 18:52:56 +03:00
func handleInstallGetAddresses ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-01-29 20:41:57 +03:00
data := firstRunData { }
2019-02-01 18:18:08 +03:00
// find out if port 80 is available -- if not, fall back to 3000
2019-02-07 14:22:08 +03:00
if checkPortAvailable ( "" , 80 ) == nil {
2019-02-01 18:18:08 +03:00
data . Web . Port = 80
} else {
data . Web . Port = 3000
}
// find out if port 53 is available -- if not, show a big warning
data . DNS . Port = 53
2019-02-07 14:22:08 +03:00
if checkPacketPortAvailable ( "" , 53 ) != nil {
2019-02-01 18:18:08 +03:00
data . DNS . Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
}
2019-02-22 17:59:42 +03:00
ifaces , err := getValidNetInterfacesForWeb ( )
if err != nil {
httpError ( w , http . StatusInternalServerError , "Couldn't get interfaces: %s" , err )
return
}
2019-01-31 14:56:34 +03:00
data . Interfaces = make ( map [ string ] interface { } )
for _ , iface := range ifaces {
2019-02-22 17:59:42 +03:00
data . Interfaces [ iface . Name ] = iface
2019-01-31 14:56:34 +03:00
}
2019-01-17 17:16:49 +03:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2019-01-29 20:41:57 +03:00
err = json . NewEncoder ( w ) . Encode ( data )
2019-01-17 17:16:49 +03:00
if err != nil {
httpError ( w , http . StatusInternalServerError , "Unable to marshal default addresses to json: %s" , err )
return
}
}
2019-02-01 18:52:56 +03:00
func handleInstallConfigure ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-01-29 20:41:57 +03:00
newSettings := firstRunData { }
2019-01-17 17:16:49 +03:00
err := json . NewDecoder ( r . Body ) . Decode ( & newSettings )
if err != nil {
2019-02-22 17:59:42 +03:00
httpError ( w , http . StatusBadRequest , "Failed to parse new config json: %s" , err )
2019-01-17 17:16:49 +03:00
return
}
2019-02-07 18:08:25 +03:00
restartHTTP := true
if config . BindHost == newSettings . Web . IP && config . BindPort == newSettings . Web . Port {
// no need to rebind
restartHTTP = false
}
2019-02-01 19:25:04 +03:00
// validate that hosts and ports are bindable
2019-02-07 18:08:25 +03:00
if restartHTTP {
err = checkPortAvailable ( newSettings . Web . IP , newSettings . Web . Port )
if err != nil {
httpError ( w , http . StatusBadRequest , "Impossible to listen on IP:port %s due to %s" , net . JoinHostPort ( newSettings . Web . IP , strconv . Itoa ( newSettings . Web . Port ) ) , err )
return
}
2019-02-01 19:25:04 +03:00
}
2019-02-07 14:22:08 +03:00
err = checkPacketPortAvailable ( newSettings . DNS . IP , newSettings . DNS . Port )
if err != nil {
httpError ( w , http . StatusBadRequest , "Impossible to listen on IP:port %s due to %s" , net . JoinHostPort ( newSettings . DNS . IP , strconv . Itoa ( newSettings . DNS . Port ) ) , err )
2019-02-01 19:25:04 +03:00
return
}
2019-01-29 20:41:57 +03:00
config . firstRun = false
config . BindHost = newSettings . Web . IP
config . BindPort = newSettings . Web . Port
config . DNS . BindHost = newSettings . DNS . IP
config . DNS . Port = newSettings . DNS . Port
config . AuthName = newSettings . Username
config . AuthPass = newSettings . Password
2019-02-01 19:42:33 +03:00
if config . DNS . Port != 0 {
err = startDNSServer ( )
if err != nil {
httpError ( w , http . StatusInternalServerError , "Couldn't start DNS server: %s" , err )
return
}
}
2019-01-29 20:41:57 +03:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2019-02-06 16:47:17 +03:00
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
2019-02-07 18:08:25 +03:00
if restartHTTP {
go func ( ) {
httpServer . Shutdown ( context . TODO ( ) )
} ( )
}
2019-01-17 17:16:49 +03:00
}
2019-01-23 17:26:15 +03:00
// ---
// TLS
// ---
func handleTLSStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-15 17:06:55 +03:00
marshalTLS ( w , config . TLS )
2019-01-23 17:26:15 +03:00
}
2019-02-12 20:08:11 +03:00
func handleTLSValidate ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-12 20:08:11 +03:00
data , err := unmarshalTLS ( r )
if err != nil {
httpError ( w , http . StatusBadRequest , "Failed to unmarshal TLS config: %s" , err )
return
}
2019-02-19 15:19:11 +03:00
// check if port is available
// BUT: if we are already using this port, no need
alreadyRunning := false
if httpsServer . server != nil {
alreadyRunning = true
}
if ! alreadyRunning {
err = checkPortAvailable ( config . BindHost , data . PortHTTPS )
if err != nil {
httpError ( w , http . StatusBadRequest , "port %d is not available, cannot enable HTTPS on it" , data . PortHTTPS )
return
}
}
2019-02-15 17:06:55 +03:00
data = validateCertificates ( data )
marshalTLS ( w , data )
2019-02-12 20:08:11 +03:00
}
2019-01-23 17:26:15 +03:00
func handleTLSConfigure ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-12 20:02:52 +03:00
data , err := unmarshalTLS ( r )
2019-01-23 17:26:15 +03:00
if err != nil {
2019-02-12 20:02:52 +03:00
httpError ( w , http . StatusBadRequest , "Failed to unmarshal TLS config: %s" , err )
2019-01-23 17:26:15 +03:00
return
}
2019-02-19 15:19:11 +03:00
// check if port is available
// BUT: if we are already using this port, no need
alreadyRunning := false
if httpsServer . server != nil {
alreadyRunning = true
}
if ! alreadyRunning {
err = checkPortAvailable ( config . BindHost , data . PortHTTPS )
if err != nil {
httpError ( w , http . StatusBadRequest , "port %d is not available, cannot enable HTTPS on it" , data . PortHTTPS )
return
}
}
2019-02-15 17:06:55 +03:00
restartHTTPS := false
data = validateCertificates ( data )
2019-02-19 17:52:27 +03:00
if ! reflect . DeepEqual ( config . TLS . tlsConfigSettings , data . tlsConfigSettings ) {
log . Printf ( "tls config settings have changed, will restart HTTPS server" )
restartHTTPS = true
2019-02-15 17:06:55 +03:00
}
2019-02-19 17:52:27 +03:00
config . TLS = data
2019-02-15 17:06:55 +03:00
err = writeAllConfigsAndReloadDNS ( )
2019-02-12 20:08:11 +03:00
if err != nil {
2019-02-15 17:06:55 +03:00
httpError ( w , http . StatusInternalServerError , "Couldn't write config file: %s" , err )
2019-02-12 20:08:11 +03:00
return
}
2019-02-15 17:06:55 +03:00
marshalTLS ( w , data )
2019-02-13 11:08:07 +03:00
// this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block
// until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely
2019-02-19 19:08:34 +03:00
if restartHTTPS {
2019-02-13 11:08:07 +03:00
go func ( ) {
2019-02-21 19:01:20 +03:00
time . Sleep ( time . Second ) // TODO: could not find a way to reliably know that data was fully sent to client by https server, so we wait a bit to let response through before closing the server
2019-02-21 19:07:12 +03:00
httpsServer . cond . L . Lock ( )
2019-02-13 11:08:07 +03:00
httpsServer . cond . Broadcast ( )
2019-02-19 19:08:34 +03:00
if httpsServer . server != nil {
httpsServer . server . Shutdown ( context . TODO ( ) )
}
2019-02-21 19:07:12 +03:00
httpsServer . cond . L . Unlock ( )
2019-02-13 11:08:07 +03:00
} ( )
}
2019-02-12 20:08:11 +03:00
}
2019-02-15 17:06:55 +03:00
func validateCertificates ( data tlsConfig ) tlsConfig {
2019-02-12 20:08:11 +03:00
var err error
2019-02-15 15:16:25 +03:00
// clear out status for certificates
data . tlsConfigStatus = tlsConfigStatus { }
2019-02-21 17:33:46 +03:00
// check only public certificate separately from the key
2019-02-01 16:53:10 +03:00
if data . CertificateChain != "" {
2019-02-20 12:32:10 +03:00
log . Tracef ( "got certificate: %s" , data . CertificateChain )
2019-02-01 14:10:39 +03:00
2019-02-01 16:53:10 +03:00
// now do a more extended validation
var certs [ ] * pem . Block // PEM-encoded certificates
var skippedBytes [ ] string // skipped bytes
2019-02-12 20:02:52 +03:00
pemblock := [ ] byte ( data . CertificateChain )
2019-02-01 16:53:10 +03:00
for {
var decoded * pem . Block
decoded , pemblock = pem . Decode ( pemblock )
if decoded == nil {
break
}
if decoded . Type == "CERTIFICATE" {
certs = append ( certs , decoded )
} else {
skippedBytes = append ( skippedBytes , decoded . Type )
}
2019-01-30 21:09:29 +03:00
}
2019-02-01 16:53:10 +03:00
var parsedCerts [ ] * x509 . Certificate
2019-01-30 21:09:29 +03:00
2019-02-01 16:53:10 +03:00
for _ , cert := range certs {
parsed , err := x509 . ParseCertificate ( cert . Bytes )
if err != nil {
2019-02-15 17:06:55 +03:00
data . WarningValidation = fmt . Sprintf ( "Failed to parse certificate: %s" , err )
return data
2019-02-01 16:53:10 +03:00
}
parsedCerts = append ( parsedCerts , parsed )
2019-01-30 21:09:29 +03:00
}
2019-02-01 16:53:10 +03:00
if len ( parsedCerts ) == 0 {
2019-02-15 17:06:55 +03:00
data . WarningValidation = fmt . Sprintf ( "You have specified an empty certificate" )
return data
2019-02-01 16:53:10 +03:00
}
2019-01-30 21:09:29 +03:00
2019-02-19 15:21:19 +03:00
data . ValidCert = true
2019-02-01 16:53:10 +03:00
// spew.Dump(parsedCerts)
2019-01-30 21:09:29 +03:00
2019-02-01 16:53:10 +03:00
opts := x509 . VerifyOptions {
DNSName : data . ServerName ,
}
2019-01-30 21:09:29 +03:00
2019-02-01 16:53:10 +03:00
log . Printf ( "number of certs - %d" , len ( parsedCerts ) )
if len ( parsedCerts ) > 1 {
// set up an intermediate
pool := x509 . NewCertPool ( )
for _ , cert := range parsedCerts [ 1 : ] {
log . Printf ( "got an intermediate cert" )
pool . AddCert ( cert )
}
opts . Intermediates = pool
2019-01-30 21:09:29 +03:00
}
2019-02-01 16:53:10 +03:00
// TODO: save it as a warning rather than error it out -- shouldn't be a big problem
mainCert := parsedCerts [ 0 ]
2019-02-12 20:08:11 +03:00
_ , err := mainCert . Verify ( opts )
2019-02-01 16:53:10 +03:00
if err != nil {
2019-02-12 21:14:23 +03:00
// let self-signed certs through
data . WarningValidation = fmt . Sprintf ( "Your certificate does not verify: %s" , err )
2019-02-15 15:16:25 +03:00
} else {
data . ValidChain = true
2019-02-01 16:53:10 +03:00
}
// spew.Dump(chains)
2019-01-30 21:09:29 +03:00
2019-02-12 19:53:53 +03:00
// update status
if mainCert != nil {
2019-02-13 11:08:44 +03:00
notAfter := mainCert . NotAfter
2019-02-15 15:16:25 +03:00
data . Subject = mainCert . Subject . String ( )
data . Issuer = mainCert . Issuer . String ( )
data . NotAfter = notAfter
data . NotBefore = mainCert . NotBefore
data . DNSNames = mainCert . DNSNames
2019-01-30 21:09:29 +03:00
}
}
2019-02-12 19:53:53 +03:00
2019-02-15 15:16:25 +03:00
// validate private key (right now the only validation possible is just parsing it)
if data . PrivateKey != "" {
// now do a more extended validation
var key * pem . Block // PEM-encoded certificates
var skippedBytes [ ] string // skipped bytes
// go through all pem blocks, but take first valid pem block and drop the rest
pemblock := [ ] byte ( data . PrivateKey )
for {
var decoded * pem . Block
decoded , pemblock = pem . Decode ( pemblock )
if decoded == nil {
break
}
if decoded . Type == "PRIVATE KEY" || strings . HasSuffix ( decoded . Type , " PRIVATE KEY" ) {
key = decoded
break
} else {
skippedBytes = append ( skippedBytes , decoded . Type )
}
}
if key == nil {
2019-02-15 17:06:55 +03:00
data . WarningValidation = "No valid keys were found"
return data
2019-02-15 15:16:25 +03:00
}
// parse the decoded key
_ , keytype , err := parsePrivateKey ( key . Bytes )
if err != nil {
2019-02-15 17:06:55 +03:00
data . WarningValidation = fmt . Sprintf ( "Failed to parse private key: %s" , err )
return data
2019-02-15 15:16:25 +03:00
}
data . ValidKey = true
data . KeyType = keytype
}
// if both are set, validate both in unison
if data . PrivateKey != "" && data . CertificateChain != "" {
_ , err = tls . X509KeyPair ( [ ] byte ( data . CertificateChain ) , [ ] byte ( data . PrivateKey ) )
if err != nil {
2019-02-15 17:06:55 +03:00
data . WarningValidation = fmt . Sprintf ( "Invalid certificate or key: %s" , err )
return data
2019-02-15 15:16:25 +03:00
}
2019-02-19 15:21:38 +03:00
data . usable = true
2019-02-15 15:16:25 +03:00
}
2019-02-15 17:06:55 +03:00
return data
2019-01-23 17:26:15 +03:00
}
2019-02-15 15:16:25 +03:00
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
func parsePrivateKey ( der [ ] byte ) ( crypto . PrivateKey , string , error ) {
if key , err := x509 . ParsePKCS1PrivateKey ( der ) ; err == nil {
return key , "RSA" , nil
}
if key , err := x509 . ParsePKCS8PrivateKey ( der ) ; err == nil {
switch key := key . ( type ) {
case * rsa . PrivateKey :
return key , "RSA" , nil
case * ecdsa . PrivateKey :
return key , "ECDSA" , nil
default :
return nil , "" , errors . New ( "tls: found unknown private key type in PKCS#8 wrapping" )
}
}
if key , err := x509 . ParseECPrivateKey ( der ) ; err == nil {
return key , "ECDSA" , nil
}
return nil , "" , errors . New ( "tls: failed to parse private key" )
}
2019-02-12 20:02:52 +03:00
// unmarshalTLS handles base64-encoded certificates transparently
func unmarshalTLS ( r * http . Request ) ( tlsConfig , error ) {
data := tlsConfig { }
err := json . NewDecoder ( r . Body ) . Decode ( & data )
if err != nil {
return data , errorx . Decorate ( err , "Failed to parse new TLS config json" )
}
if data . CertificateChain != "" {
certPEM , err := base64 . StdEncoding . DecodeString ( data . CertificateChain )
if err != nil {
return data , errorx . Decorate ( err , "Failed to base64-decode certificate chain" )
}
data . CertificateChain = string ( certPEM )
}
if data . PrivateKey != "" {
keyPEM , err := base64 . StdEncoding . DecodeString ( data . PrivateKey )
if err != nil {
return data , errorx . Decorate ( err , "Failed to base64-decode private key" )
}
data . PrivateKey = string ( keyPEM )
}
return data , nil
}
2019-02-15 17:06:55 +03:00
func marshalTLS ( w http . ResponseWriter , data tlsConfig ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
if data . CertificateChain != "" {
encoded := base64 . StdEncoding . EncodeToString ( [ ] byte ( data . CertificateChain ) )
2019-02-21 17:33:46 +03:00
data . CertificateChain = encoded
2019-02-15 17:06:55 +03:00
}
if data . PrivateKey != "" {
encoded := base64 . StdEncoding . EncodeToString ( [ ] byte ( data . PrivateKey ) )
2019-02-21 17:33:46 +03:00
data . PrivateKey = encoded
2019-02-15 17:06:55 +03:00
}
err := json . NewEncoder ( w ) . Encode ( data )
if err != nil {
httpError ( w , http . StatusInternalServerError , "Failed to marshal json with TLS status: %s" , err )
return
}
}
2019-02-22 15:52:12 +03:00
// --------------
// DNS-over-HTTPS
// --------------
func handleDOH ( w http . ResponseWriter , r * http . Request ) {
2019-02-27 18:09:57 +03:00
log . Tracef ( "%s %v" , r . Method , r . URL )
2019-02-22 15:52:12 +03:00
if r . TLS == nil {
httpError ( w , http . StatusNotFound , "Not Found" )
return
}
if ! isRunning ( ) {
httpError ( w , http . StatusInternalServerError , "DNS server is not running" )
return
}
dnsServer . ServeHTTP ( w , r )
}
2019-02-12 20:02:52 +03:00
// ------------------------
// registration of handlers
// ------------------------
2019-02-06 16:48:04 +03:00
func registerInstallHandlers ( ) {
http . HandleFunc ( "/control/install/get_addresses" , preInstall ( ensureGET ( handleInstallGetAddresses ) ) )
http . HandleFunc ( "/control/install/configure" , preInstall ( ensurePOST ( handleInstallConfigure ) ) )
}
2018-08-30 17:25:33 +03:00
func registerControlHandlers ( ) {
2019-01-29 20:41:57 +03:00
http . HandleFunc ( "/control/status" , postInstall ( optionalAuth ( ensureGET ( handleStatus ) ) ) )
http . HandleFunc ( "/control/enable_protection" , postInstall ( optionalAuth ( ensurePOST ( handleProtectionEnable ) ) ) )
http . HandleFunc ( "/control/disable_protection" , postInstall ( optionalAuth ( ensurePOST ( handleProtectionDisable ) ) ) )
2019-02-10 20:47:43 +03:00
http . HandleFunc ( "/control/querylog" , postInstall ( optionalAuth ( ensureGET ( handleQueryLog ) ) ) )
2019-01-29 20:41:57 +03:00
http . HandleFunc ( "/control/querylog_enable" , postInstall ( optionalAuth ( ensurePOST ( handleQueryLogEnable ) ) ) )
http . HandleFunc ( "/control/querylog_disable" , postInstall ( optionalAuth ( ensurePOST ( handleQueryLogDisable ) ) ) )
2019-02-28 11:10:43 +03:00
http . HandleFunc ( "/control/set_upstreams_config" , postInstall ( optionalAuth ( ensurePOST ( handleSetUpstreamConfig ) ) ) )
2019-01-29 20:41:57 +03:00
http . HandleFunc ( "/control/test_upstream_dns" , postInstall ( optionalAuth ( ensurePOST ( handleTestUpstreamDNS ) ) ) )
http . HandleFunc ( "/control/i18n/change_language" , postInstall ( optionalAuth ( ensurePOST ( handleI18nChangeLanguage ) ) ) )
http . HandleFunc ( "/control/i18n/current_language" , postInstall ( optionalAuth ( ensureGET ( handleI18nCurrentLanguage ) ) ) )
2019-02-10 20:47:43 +03:00
http . HandleFunc ( "/control/stats_top" , postInstall ( optionalAuth ( ensureGET ( handleStatsTop ) ) ) )
http . HandleFunc ( "/control/stats" , postInstall ( optionalAuth ( ensureGET ( handleStats ) ) ) )
http . HandleFunc ( "/control/stats_history" , postInstall ( optionalAuth ( ensureGET ( handleStatsHistory ) ) ) )
http . HandleFunc ( "/control/stats_reset" , postInstall ( optionalAuth ( ensurePOST ( handleStatsReset ) ) ) )
2019-01-29 20:41:57 +03:00
http . HandleFunc ( "/control/version.json" , postInstall ( optionalAuth ( handleGetVersionJSON ) ) )
http . HandleFunc ( "/control/filtering/enable" , postInstall ( optionalAuth ( ensurePOST ( handleFilteringEnable ) ) ) )
http . HandleFunc ( "/control/filtering/disable" , postInstall ( optionalAuth ( ensurePOST ( handleFilteringDisable ) ) ) )
http . HandleFunc ( "/control/filtering/add_url" , postInstall ( optionalAuth ( ensurePUT ( handleFilteringAddURL ) ) ) )
http . HandleFunc ( "/control/filtering/remove_url" , postInstall ( optionalAuth ( ensureDELETE ( handleFilteringRemoveURL ) ) ) )
http . HandleFunc ( "/control/filtering/enable_url" , postInstall ( optionalAuth ( ensurePOST ( handleFilteringEnableURL ) ) ) )
http . HandleFunc ( "/control/filtering/disable_url" , postInstall ( optionalAuth ( ensurePOST ( handleFilteringDisableURL ) ) ) )
http . HandleFunc ( "/control/filtering/refresh" , postInstall ( optionalAuth ( ensurePOST ( handleFilteringRefresh ) ) ) )
http . HandleFunc ( "/control/filtering/status" , postInstall ( optionalAuth ( ensureGET ( handleFilteringStatus ) ) ) )
http . HandleFunc ( "/control/filtering/set_rules" , postInstall ( optionalAuth ( ensurePUT ( handleFilteringSetRules ) ) ) )
http . HandleFunc ( "/control/safebrowsing/enable" , postInstall ( optionalAuth ( ensurePOST ( handleSafeBrowsingEnable ) ) ) )
http . HandleFunc ( "/control/safebrowsing/disable" , postInstall ( optionalAuth ( ensurePOST ( handleSafeBrowsingDisable ) ) ) )
http . HandleFunc ( "/control/safebrowsing/status" , postInstall ( optionalAuth ( ensureGET ( handleSafeBrowsingStatus ) ) ) )
http . HandleFunc ( "/control/parental/enable" , postInstall ( optionalAuth ( ensurePOST ( handleParentalEnable ) ) ) )
http . HandleFunc ( "/control/parental/disable" , postInstall ( optionalAuth ( ensurePOST ( handleParentalDisable ) ) ) )
http . HandleFunc ( "/control/parental/status" , postInstall ( optionalAuth ( ensureGET ( handleParentalStatus ) ) ) )
http . HandleFunc ( "/control/safesearch/enable" , postInstall ( optionalAuth ( ensurePOST ( handleSafeSearchEnable ) ) ) )
http . HandleFunc ( "/control/safesearch/disable" , postInstall ( optionalAuth ( ensurePOST ( handleSafeSearchDisable ) ) ) )
http . HandleFunc ( "/control/safesearch/status" , postInstall ( optionalAuth ( ensureGET ( handleSafeSearchStatus ) ) ) )
http . HandleFunc ( "/control/dhcp/status" , postInstall ( optionalAuth ( ensureGET ( handleDHCPStatus ) ) ) )
http . HandleFunc ( "/control/dhcp/interfaces" , postInstall ( optionalAuth ( ensureGET ( handleDHCPInterfaces ) ) ) )
http . HandleFunc ( "/control/dhcp/set_config" , postInstall ( optionalAuth ( ensurePOST ( handleDHCPSetConfig ) ) ) )
http . HandleFunc ( "/control/dhcp/find_active_dhcp" , postInstall ( optionalAuth ( ensurePOST ( handleDHCPFindActiveServer ) ) ) )
2019-01-23 17:26:15 +03:00
http . HandleFunc ( "/control/tls/status" , postInstall ( optionalAuth ( ensureGET ( handleTLSStatus ) ) ) )
http . HandleFunc ( "/control/tls/configure" , postInstall ( optionalAuth ( ensurePOST ( handleTLSConfigure ) ) ) )
2019-02-12 20:08:11 +03:00
http . HandleFunc ( "/control/tls/validate" , postInstall ( optionalAuth ( ensurePOST ( handleTLSValidate ) ) ) )
2019-02-22 15:52:12 +03:00
http . HandleFunc ( "/dns-query" , postInstall ( handleDOH ) )
2018-08-30 17:25:33 +03:00
}