2018-08-30 07:25:33 -07:00
package main
import (
2019-02-10 10:47:43 -07:00
"bytes"
2019-02-06 06:47:17 -07:00
"context"
2019-02-15 05:16:25 -07:00
"crypto"
"crypto/ecdsa"
"crypto/rsa"
2019-01-30 11:09:29 -07:00
"crypto/tls"
"crypto/x509"
2019-02-01 04:10:39 -07:00
"encoding/base64"
2018-08-30 07:25:33 -07:00
"encoding/json"
2019-01-30 11:09:29 -07:00
"encoding/pem"
2019-02-15 05:16:25 -07:00
"errors"
2018-08-30 07:25:33 -07:00
"fmt"
"io/ioutil"
2018-12-05 09:17:17 -07:00
"net"
2018-08-30 07:25:33 -07:00
"net/http"
"os"
2019-02-13 01:08:07 -07:00
"reflect"
2019-02-10 10:47:43 -07:00
"sort"
2018-08-30 07:25:33 -07:00
"strconv"
"strings"
"time"
2018-12-05 04:21:25 -07:00
"github.com/AdguardTeam/AdGuardHome/dnsforward"
2018-12-29 07:23:42 -07:00
"github.com/AdguardTeam/dnsproxy/upstream"
2018-12-29 09:12:22 -07:00
"github.com/hmage/golibs/log"
2019-02-12 10:02:52 -07:00
"github.com/joomcode/errorx"
2018-12-05 09:17:17 -07:00
"github.com/miekg/dns"
2019-01-25 06:01:27 -07:00
govalidator "gopkg.in/asaskevich/govalidator.v4"
2018-08-30 07:25:33 -07:00
)
const updatePeriod = time . Minute * 30
2018-09-20 10:02:25 -07:00
// cached version.json to avoid hammering github.io for each page reload
var versionCheckJSON [ ] byte
var versionCheckLastTime time . Time
2018-10-15 06:02:19 -07:00
const versionCheckURL = "https://adguardteam.github.io/AdGuardHome/version.json"
2018-09-20 10:02:25 -07:00
const versionCheckPeriod = time . Hour * 8
2018-10-10 05:47:08 -07:00
var client = & http . Client {
Timeout : time . Second * 30 ,
}
2019-02-10 10:47:43 -07:00
// ----------------
// helper functions
// ----------------
func returnOK ( w http . ResponseWriter ) {
_ , err := fmt . Fprintf ( w , "OK\n" )
if err != nil {
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
}
}
func httpError ( w http . ResponseWriter , code int , format string , args ... interface { } ) {
text := fmt . Sprintf ( format , args ... )
log . Println ( text )
http . Error ( w , text , code )
}
// ---------------
2018-12-05 10:29:00 -07:00
// dns run control
2019-02-10 10:47:43 -07:00
// ---------------
2018-12-05 10:29:00 -07:00
func writeAllConfigsAndReloadDNS ( ) error {
2018-08-30 07:25:33 -07:00
err := writeAllConfigs ( )
if err != nil {
log . Printf ( "Couldn't write all configs: %s" , err )
return err
}
2019-01-24 10:11:01 -07:00
return reconfigureDNSServer ( )
2018-08-30 07:25:33 -07:00
}
2018-10-10 10:13:03 -07:00
func httpUpdateConfigReloadDNSReturnOK ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
err := writeAllConfigsAndReloadDNS ( )
2018-10-10 10:13:03 -07:00
if err != nil {
2019-02-15 07:06:55 -07:00
httpError ( w , http . StatusInternalServerError , "Couldn't write config file: %s" , err )
2018-10-10 10:13:03 -07:00
return
}
2019-01-24 10:11:01 -07:00
returnOK ( w )
2018-10-10 10:13:03 -07:00
}
2018-08-30 07:25:33 -07:00
func handleStatus ( w http . ResponseWriter , r * http . Request ) {
data := map [ string ] interface { } {
2018-10-10 10:13:03 -07:00
"dns_address" : config . BindHost ,
2018-12-05 10:29:00 -07:00
"dns_port" : config . DNS . Port ,
"protection_enabled" : config . DNS . ProtectionEnabled ,
"querylog_enabled" : config . DNS . QueryLogEnabled ,
2018-10-10 10:13:03 -07:00
"running" : isRunning ( ) ,
2018-12-05 10:29:00 -07:00
"bootstrap_dns" : config . DNS . BootstrapDNS ,
"upstream_dns" : config . DNS . UpstreamDNS ,
2018-10-10 10:13:03 -07:00
"version" : VersionString ,
2018-11-21 10:42:55 -07:00
"language" : config . Language ,
2018-08-30 07:25:33 -07:00
}
2018-10-20 09:58:39 -07:00
jsonVal , err := json . Marshal ( data )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 09:58:39 -07:00
_ , err = w . Write ( jsonVal )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
}
2018-10-10 10:13:03 -07:00
func handleProtectionEnable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . ProtectionEnabled = true
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
}
func handleProtectionDisable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . ProtectionEnabled = false
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
}
2018-08-30 07:25:33 -07:00
// -----
// stats
// -----
func handleQueryLogEnable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . QueryLogEnabled = true
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleQueryLogDisable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . QueryLogEnabled = false
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
2019-02-10 10:47:43 -07:00
func handleQueryLog ( w http . ResponseWriter , r * http . Request ) {
data := dnsServer . GetQueryLog ( )
jsonVal , err := json . Marshal ( data )
if err != nil {
errorText := fmt . Sprintf ( "Couldn't marshal data into json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( jsonVal )
if err != nil {
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
}
}
func handleStatsTop ( w http . ResponseWriter , r * http . Request ) {
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 {
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
}
}
// handleStatsReset resets the stats caches
func handleStatsReset ( w http . ResponseWriter , r * http . Request ) {
2019-02-11 04:22:36 -07:00
dnsServer . PurgeStats ( )
2019-02-10 10:47:43 -07:00
_ , err := fmt . Fprintf ( w , "OK\n" )
if err != nil {
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
}
}
// handleStats returns aggregated stats data for the 24 hours
func handleStats ( w http . ResponseWriter , r * http . Request ) {
summed := dnsServer . GetAggregatedStats ( )
statsJSON , err := json . Marshal ( summed )
if err != nil {
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( statsJSON )
if err != nil {
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
return
}
2018-09-26 07:47:23 -07:00
}
2019-02-10 10:47:43 -07:00
// HandleStatsHistory returns historical stats data for the 24 hours
func handleStatsHistory ( w http . ResponseWriter , r * http . Request ) {
// 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 {
errorText := fmt . Sprintf ( "Must specify valid start_time parameter: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
return
}
endTime , err := time . Parse ( time . RFC3339 , r . URL . Query ( ) . Get ( "end_time" ) )
if err != nil {
errorText := fmt . Sprintf ( "Must specify valid end_time parameter: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
return
}
data , err := dnsServer . GetStatsHistory ( timeUnit , startTime , endTime )
if err != nil {
errorText := fmt . Sprintf ( "Cannot get stats history: %s" , err )
http . Error ( w , errorText , http . StatusBadRequest )
return
}
statsJSON , err := json . Marshal ( data )
if err != nil {
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( statsJSON )
if err != nil {
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
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
// -----------------------
2018-08-30 07:25:33 -07:00
func handleSetUpstreamDNS ( w http . ResponseWriter , r * http . Request ) {
body , err := ioutil . ReadAll ( r . Body )
if err != nil {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "Failed to read request body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
2018-08-30 07:25:33 -07:00
return
}
// if empty body -- user is asking for default servers
2018-11-05 14:47:59 -07:00
hosts := strings . Fields ( string ( body ) )
2018-08-30 07:25:33 -07:00
if len ( hosts ) == 0 {
2018-12-05 10:29:00 -07:00
config . DNS . UpstreamDNS = defaultDNS
2018-08-30 07:25:33 -07:00
} else {
2018-12-05 10:29:00 -07:00
config . DNS . UpstreamDNS = hosts
2018-08-30 07:25:33 -07:00
}
err = writeAllConfigs ( )
if err != nil {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "Couldn't write config file: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-08-30 07:25:33 -07:00
return
}
2019-01-24 10:11:01 -07:00
err = reconfigureDNSServer ( )
if err != nil {
errorText := fmt . Sprintf ( "Couldn't reconfigure the DNS server: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
return
}
2018-09-14 06:50:56 -07:00
_ , err = fmt . Fprintf ( w , "OK %d servers\n" , len ( hosts ) )
if err != nil {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-09-14 06:50:56 -07:00
}
2018-08-30 07:25:33 -07:00
}
2018-09-19 09:12:09 -07:00
func handleTestUpstreamDNS ( w http . ResponseWriter , r * http . Request ) {
body , err := ioutil . ReadAll ( r . Body )
if err != nil {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "Failed to read request body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 400 )
2018-09-19 09:12:09 -07:00
return
}
hosts := strings . Fields ( string ( body ) )
if len ( hosts ) == 0 {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "No servers specified" )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
2018-09-19 09:12:09 -07:00
return
}
result := map [ string ] string { }
for _ , host := range hosts {
2018-10-07 13:43:24 -07:00
err = checkDNS ( host )
2018-09-19 09:12:09 -07:00
if err != nil {
log . Println ( err )
result [ host ] = err . Error ( )
} else {
result [ host ] = "OK"
}
}
2018-10-20 09:58:39 -07:00
jsonVal , err := json . Marshal ( result )
2018-09-19 09:12:09 -07:00
if err != nil {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-09-19 09:12:09 -07:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 09:58:39 -07:00
_ , err = w . Write ( jsonVal )
2018-09-19 09:12:09 -07:00
if err != nil {
2018-11-01 04:45:32 -07:00
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-09-19 09:12:09 -07:00
}
}
2018-09-26 07:47:23 -07:00
func checkDNS ( input string ) error {
2018-12-05 09:17:17 -07:00
log . Printf ( "Checking if DNS %s works..." , input )
2018-12-24 15:59:38 -07:00
u , err := upstream . AddressToUpstream ( input , "" , dnsforward . DefaultTimeout )
2018-09-26 07:47:23 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
return fmt . Errorf ( "failed to choose upstream for %s: %s" , input , err )
2018-09-19 09:12:09 -07:00
}
2018-09-26 07:47:23 -07:00
2018-12-05 09:17:17 -07: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 09:12:09 -07:00
if err != nil {
2018-10-30 02:24:59 -07:00
return fmt . Errorf ( "couldn't communicate with DNS server %s: %s" , input , err )
2018-09-19 09:12:09 -07:00
}
2018-12-05 09:17:17 -07: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 07:47:23 -07:00
}
2018-12-05 09:17:17 -07:00
log . Printf ( "DNS %s works OK" , input )
2018-11-05 14:47:59 -07:00
return nil
2018-08-30 07:25:33 -07:00
}
2018-09-20 10:02:25 -07:00
func handleGetVersionJSON ( w http . ResponseWriter , r * http . Request ) {
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-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Couldn't get version check json from %s: %T %s\n" , versionCheckURL , err , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadGateway )
2018-09-20 10:02:25 -07: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-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Couldn't read response body from %s: %s" , versionCheckURL , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadGateway )
2018-09-20 10:02:25 -07:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( body )
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-09-20 10:02:25 -07:00
}
versionCheckLastTime = now
versionCheckJSON = body
}
2018-08-30 07:25:33 -07:00
// ---------
// filtering
// ---------
func handleFilteringEnable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . FilteringEnabled = true
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleFilteringDisable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . FilteringEnabled = false
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleFilteringStatus ( w http . ResponseWriter , r * http . Request ) {
data := map [ string ] interface { } {
2018-12-05 10:29:00 -07:00
"enabled" : config . DNS . FilteringEnabled ,
2018-08-30 07:25:33 -07:00
}
2018-10-06 14:58:59 -07:00
config . RLock ( )
2018-08-30 07:25:33 -07:00
data [ "filters" ] = config . Filters
data [ "user_rules" ] = config . UserRules
2018-10-20 09:58:39 -07:00
jsonVal , err := json . Marshal ( data )
2018-10-06 14:58:59 -07:00
config . RUnlock ( )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 09:58:39 -07:00
_ , err = w . Write ( jsonVal )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
}
func handleFilteringAddURL ( w http . ResponseWriter , r * http . Request ) {
2019-01-25 06:01:27 -07:00
f := filter { }
err := json . NewDecoder ( r . Body ) . Decode ( & f )
2018-08-30 07:25:33 -07:00
if err != nil {
2018-10-11 08:32:23 -07:00
httpError ( w , http . StatusBadRequest , "Failed to parse request body json: %s" , err )
2018-08-30 07:25:33 -07:00
return
}
2019-01-25 06:01:27 -07:00
if len ( f . URL ) == 0 {
2018-08-30 07:25:33 -07:00
http . Error ( w , "URL parameter was not specified" , 400 )
return
}
2019-01-25 06:01:27 -07:00
if valid := govalidator . IsRequestURL ( f . URL ) ; ! valid {
2018-08-30 07:25:33 -07:00
http . Error ( w , "URL parameter is not valid request URL" , 400 )
return
}
2018-09-13 18:33:54 -07:00
2018-10-30 02:24:59 -07:00
// Check for duplicates
2018-09-13 18:33:54 -07:00
for i := range config . Filters {
2019-01-25 06:01:27 -07:00
if config . Filters [ i ] . URL == f . URL {
errorText := fmt . Sprintf ( "Filter URL already added -- %s" , f . URL )
2018-10-30 02:24:59 -07:00
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
2018-09-13 18:33:54 -07:00
return
}
}
2018-10-30 02:24:59 -07:00
// Set necessary properties
2019-01-25 06:01:27 -07:00
f . ID = assignUniqueFilterID ( )
f . Enabled = true
2018-10-30 02:24:59 -07:00
// Download the filter contents
2019-01-25 06:01:27 -07:00
ok , err := f . update ( true )
2018-09-13 18:33:54 -07:00
if err != nil {
2019-01-25 06:01:27 -07:00
errorText := fmt . Sprintf ( "Couldn't fetch filter from url %s: %s" , f . URL , err )
2018-10-30 02:24:59 -07:00
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
2018-09-13 18:33:54 -07:00
return
}
2019-01-25 06:01:27 -07:00
if f . RulesCount == 0 {
errorText := fmt . Sprintf ( "Filter at the url %s has no rules (maybe it points to blank page?)" , f . URL )
2018-10-30 02:24:59 -07:00
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
2018-09-13 18:33:54 -07:00
return
}
if ! ok {
2019-01-25 06:01:27 -07:00
errorText := fmt . Sprintf ( "Filter at the url %s is invalid (maybe it points to blank page?)" , f . URL )
2018-10-30 02:24:59 -07:00
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
return
}
// Save the filter contents
2019-01-25 06:01:27 -07:00
err = f . save ( )
2018-10-30 02:24:59 -07:00
if err != nil {
2019-01-25 06:01:27 -07:00
errorText := fmt . Sprintf ( "Failed to save filter %d due to %s" , f . ID , err )
2018-10-30 02:24:59 -07:00
log . Println ( errorText )
http . Error ( w , errorText , http . StatusBadRequest )
2018-09-13 18:33:54 -07:00
return
}
2018-12-05 10:29:00 -07:00
// URL is deemed valid, append it to filters, update config, write new filter file and tell dns to reload it
2019-01-24 10:11:01 -07:00
// TODO: since we directly feed filters in-memory, revisit if writing configs is always necessary
2019-01-25 06:01:27 -07:00
config . Filters = append ( config . Filters , f )
2018-08-30 07:25:33 -07:00
err = writeAllConfigs ( )
if err != nil {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "Couldn't write config file: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-08-30 07:25:33 -07:00
return
}
2018-10-29 16:17:24 -07:00
2019-01-25 06:01:27 -07:00
err = reconfigureDNSServer ( )
if err != nil {
errorText := fmt . Sprintf ( "Couldn't reconfigure the DNS server: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
}
2018-10-29 16:17:24 -07:00
2019-01-25 06:01:27 -07:00
_ , err = fmt . Fprintf ( w , "OK %d rules\n" , f . RulesCount )
2018-09-14 06:50:56 -07:00
if err != nil {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "Couldn't write body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-09-14 06:50:56 -07:00
}
2018-08-30 07:25:33 -07:00
}
func handleFilteringRemoveURL ( w http . ResponseWriter , r * http . Request ) {
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "failed to parse parameters from body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 400 )
2018-08-30 07:25:33 -07:00
return
}
url , ok := parameters [ "url" ]
if ! ok {
http . Error ( w , "URL parameter was not specified" , 400 )
return
}
if valid := govalidator . IsRequestURL ( url ) ; ! valid {
http . Error ( w , "URL parameter is not valid request URL" , 400 )
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-29 16:17:24 -07:00
} else {
// Remove the filter file
2018-11-27 06:48:57 -07:00
err := os . Remove ( filter . Path ( ) )
2019-01-04 11:07:21 -07:00
if err != nil && ! os . IsNotExist ( err ) {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "Couldn't remove the filter file: %s" , err )
http . Error ( w , errorText , http . StatusInternalServerError )
2018-10-29 16:17:24 -07:00
return
}
2018-08-30 07:25:33 -07:00
}
}
2018-10-29 16:17:24 -07:00
// Update the configuration after removing filter files
2018-08-30 07:25:33 -07:00
config . Filters = newFilters
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleFilteringEnableURL ( w http . ResponseWriter , r * http . Request ) {
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "failed to parse parameters from body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 400 )
2018-08-30 07:25:33 -07:00
return
}
url , ok := parameters [ "url" ]
if ! ok {
http . Error ( w , "URL parameter was not specified" , 400 )
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 10:11:01 -07:00
refreshFiltersIfNecessary ( false )
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleFilteringDisableURL ( w http . ResponseWriter , r * http . Request ) {
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "failed to parse parameters from body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 400 )
2018-08-30 07:25:33 -07:00
return
}
url , ok := parameters [ "url" ]
if ! ok {
http . Error ( w , "URL parameter was not specified" , 400 )
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 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleFilteringSetRules ( w http . ResponseWriter , r * http . Request ) {
body , err := ioutil . ReadAll ( r . Body )
if err != nil {
2018-10-30 02:24:59 -07:00
errorText := fmt . Sprintf ( "Failed to read request body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 400 )
2018-08-30 07:25:33 -07:00
return
}
config . UserRules = strings . Split ( string ( body ) , "\n" )
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleFilteringRefresh ( w http . ResponseWriter , r * http . Request ) {
force := r . URL . Query ( ) . Get ( "force" )
2019-01-24 10:11:01 -07:00
updated := refreshFiltersIfNecessary ( force != "" )
2018-08-30 07:25:33 -07:00
fmt . Fprintf ( w , "OK %d filters updated\n" , updated )
}
// ------------
// safebrowsing
// ------------
func handleSafeBrowsingEnable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . SafeBrowsingEnabled = true
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleSafeBrowsingDisable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . SafeBrowsingEnabled = false
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleSafeBrowsingStatus ( w http . ResponseWriter , r * http . Request ) {
data := map [ string ] interface { } {
2018-12-05 10:29:00 -07:00
"enabled" : config . DNS . SafeBrowsingEnabled ,
2018-08-30 07:25:33 -07:00
}
2018-10-20 09:58:39 -07:00
jsonVal , err := json . Marshal ( data )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 09:58:39 -07:00
_ , err = w . Write ( jsonVal )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
}
// --------
// parental
// --------
func handleParentalEnable ( w http . ResponseWriter , r * http . Request ) {
parameters , err := parseParametersFromBody ( r . Body )
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "failed to parse parameters from body: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 400 )
2018-08-30 07:25:33 -07: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 10:29:00 -07:00
config . DNS . ParentalSensitivity = i
config . DNS . ParentalEnabled = true
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleParentalDisable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . ParentalEnabled = false
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleParentalStatus ( w http . ResponseWriter , r * http . Request ) {
data := map [ string ] interface { } {
2018-12-05 10:29:00 -07:00
"enabled" : config . DNS . ParentalEnabled ,
2018-08-30 07:25:33 -07:00
}
2018-12-05 10:29:00 -07:00
if config . DNS . ParentalEnabled {
data [ "sensitivity" ] = config . DNS . ParentalSensitivity
2018-08-30 07:25:33 -07:00
}
2018-10-20 09:58:39 -07:00
jsonVal , err := json . Marshal ( data )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 09:58:39 -07:00
_ , err = w . Write ( jsonVal )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
}
// ------------
// safebrowsing
// ------------
func handleSafeSearchEnable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . SafeSearchEnabled = true
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleSafeSearchDisable ( w http . ResponseWriter , r * http . Request ) {
2018-12-05 10:29:00 -07:00
config . DNS . SafeSearchEnabled = false
2018-10-10 10:13:03 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2018-08-30 07:25:33 -07:00
}
func handleSafeSearchStatus ( w http . ResponseWriter , r * http . Request ) {
data := map [ string ] interface { } {
2018-12-05 10:29:00 -07:00
"enabled" : config . DNS . SafeSearchEnabled ,
2018-08-30 07:25:33 -07:00
}
2018-10-20 09:58:39 -07:00
jsonVal , err := json . Marshal ( data )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to marshal status json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2018-10-20 09:58:39 -07:00
_ , err = w . Write ( jsonVal )
2018-08-30 07:25:33 -07:00
if err != nil {
2019-01-24 10:11:01 -07:00
errorText := fmt . Sprintf ( "Unable to write response json: %s" , err )
log . Println ( errorText )
http . Error ( w , errorText , 500 )
2018-08-30 07:25:33 -07:00
return
}
}
2019-01-29 10:41:57 -07:00
type ipport struct {
2019-02-01 08:59:42 -07:00
IP string ` json:"ip,omitempty" `
2019-02-01 08:18:08 -07:00
Port int ` json:"port" `
Warning string ` json:"warning" `
2019-01-29 10:41:57 -07:00
}
type firstRunData struct {
2019-01-31 04:56:34 -07: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 10:41:57 -07:00
}
2019-02-01 08:52:56 -07:00
func handleInstallGetAddresses ( w http . ResponseWriter , r * http . Request ) {
2019-01-29 10:41:57 -07:00
data := firstRunData { }
ifaces , err := getValidNetInterfaces ( )
if err != nil {
httpError ( w , http . StatusInternalServerError , "Couldn't get interfaces: %s" , err )
return
}
if len ( ifaces ) == 0 {
httpError ( w , http . StatusServiceUnavailable , "Couldn't find any legible interface, plase try again later" )
return
2019-01-17 09:29:54 -07:00
}
2019-01-17 07:16:49 -07:00
2019-01-31 04:56:34 -07:00
// fill out the fields
2019-02-01 08:18:08 -07:00
// find out if port 80 is available -- if not, fall back to 3000
2019-02-07 04:22:08 -07:00
if checkPortAvailable ( "" , 80 ) == nil {
2019-02-01 08:18:08 -07: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 04:22:08 -07:00
if checkPacketPortAvailable ( "" , 53 ) != nil {
2019-02-01 08:18:08 -07:00
data . DNS . Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
}
2019-01-31 04:56:34 -07:00
data . Interfaces = make ( map [ string ] interface { } )
for _ , iface := range ifaces {
2019-02-10 10:47:43 -07:00
addrs , e := iface . Addrs ( )
if e != nil {
2019-02-07 08:07:38 -07:00
httpError ( w , http . StatusInternalServerError , "Failed to get addresses for interface %s: %s" , iface . Name , err )
return
}
jsonIface := netInterface {
Name : iface . Name ,
MTU : iface . MTU ,
HardwareAddr : iface . HardwareAddr . String ( ) ,
}
if iface . Flags != 0 {
jsonIface . Flags = iface . Flags . String ( )
}
// we don't want link-local addresses in json, so skip them
for _ , addr := range addrs {
ipnet , ok := addr . ( * net . IPNet )
if ! ok {
// not an IPNet, should not happen
httpError ( w , http . StatusInternalServerError , "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T" , addr , addr )
return
}
// ignore link-local
if ipnet . IP . IsLinkLocalUnicast ( ) {
2019-02-01 08:38:52 -07:00
continue
}
2019-02-07 08:07:38 -07:00
jsonIface . Addresses = append ( jsonIface . Addresses , ipnet . IP . String ( ) )
}
if len ( jsonIface . Addresses ) != 0 {
data . Interfaces [ iface . Name ] = jsonIface
2019-02-01 08:38:52 -07:00
}
2019-01-31 04:56:34 -07:00
}
2019-01-17 07:16:49 -07:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2019-01-29 10:41:57 -07:00
err = json . NewEncoder ( w ) . Encode ( data )
2019-01-17 07:16:49 -07:00
if err != nil {
httpError ( w , http . StatusInternalServerError , "Unable to marshal default addresses to json: %s" , err )
return
}
}
2019-02-01 08:52:56 -07:00
func handleInstallConfigure ( w http . ResponseWriter , r * http . Request ) {
2019-01-29 10:41:57 -07:00
newSettings := firstRunData { }
2019-01-17 07:16:49 -07:00
err := json . NewDecoder ( r . Body ) . Decode ( & newSettings )
if err != nil {
httpError ( w , http . StatusBadRequest , "Failed to parse new DHCP config json: %s" , err )
return
}
2019-02-07 08:08:25 -07:00
restartHTTP := true
if config . BindHost == newSettings . Web . IP && config . BindPort == newSettings . Web . Port {
// no need to rebind
restartHTTP = false
}
2019-02-01 09:25:04 -07:00
// validate that hosts and ports are bindable
2019-02-07 08:08:25 -07: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 09:25:04 -07:00
}
2019-02-07 04:22:08 -07: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 09:25:04 -07:00
return
}
2019-01-29 10:41:57 -07: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 09:42:33 -07: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 10:41:57 -07:00
httpUpdateConfigReloadDNSReturnOK ( w , r )
2019-02-06 06:47:17 -07: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 08:08:25 -07:00
if restartHTTP {
go func ( ) {
httpServer . Shutdown ( context . TODO ( ) )
} ( )
}
2019-01-17 07:16:49 -07:00
}
2019-01-23 07:26:15 -07:00
// ---
// TLS
// ---
func handleTLSStatus ( w http . ResponseWriter , r * http . Request ) {
2019-02-15 07:06:55 -07:00
marshalTLS ( w , config . TLS )
2019-01-23 07:26:15 -07:00
}
2019-02-12 10:08:11 -07:00
func handleTLSValidate ( w http . ResponseWriter , r * http . Request ) {
data , err := unmarshalTLS ( r )
if err != nil {
httpError ( w , http . StatusBadRequest , "Failed to unmarshal TLS config: %s" , err )
return
}
2019-02-19 05:19:11 -07: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 07:06:55 -07:00
data = validateCertificates ( data )
marshalTLS ( w , data )
2019-02-12 10:08:11 -07:00
}
2019-01-23 07:26:15 -07:00
func handleTLSConfigure ( w http . ResponseWriter , r * http . Request ) {
2019-02-12 10:02:52 -07:00
data , err := unmarshalTLS ( r )
2019-01-23 07:26:15 -07:00
if err != nil {
2019-02-12 10:02:52 -07:00
httpError ( w , http . StatusBadRequest , "Failed to unmarshal TLS config: %s" , err )
2019-01-23 07:26:15 -07:00
return
}
2019-02-19 05:19:11 -07: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 07:06:55 -07:00
restartHTTPS := false
data = validateCertificates ( data )
2019-02-19 05:21:38 -07:00
if data . usable {
2019-02-15 07:06:55 -07:00
if ! reflect . DeepEqual ( config . TLS . tlsConfigSettings , data . tlsConfigSettings ) {
log . Printf ( "tls config settings have changed, will restart HTTPS server" )
restartHTTPS = true
}
config . TLS = data
}
err = writeAllConfigsAndReloadDNS ( )
2019-02-12 10:08:11 -07:00
if err != nil {
2019-02-15 07:06:55 -07:00
httpError ( w , http . StatusInternalServerError , "Couldn't write config file: %s" , err )
2019-02-12 10:08:11 -07:00
return
}
2019-02-15 07:06:55 -07:00
marshalTLS ( w , data )
2019-02-13 01:08:07 -07: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-14 07:33:50 -07:00
if restartHTTPS && httpsServer . server != nil {
2019-02-13 01:08:07 -07:00
go func ( ) {
httpsServer . cond . Broadcast ( )
httpsServer . server . Shutdown ( context . TODO ( ) )
} ( )
}
2019-02-12 10:08:11 -07:00
}
2019-02-15 07:06:55 -07:00
func validateCertificates ( data tlsConfig ) tlsConfig {
2019-02-12 10:08:11 -07:00
var err error
2019-02-15 05:16:25 -07:00
// clear out status for certificates
data . tlsConfigStatus = tlsConfigStatus { }
// check only public certificate separetely from the key
2019-02-01 06:53:10 -07:00
if data . CertificateChain != "" {
2019-02-12 10:02:52 -07:00
log . Printf ( "got certificate: %s" , data . CertificateChain )
2019-02-01 04:10:39 -07:00
2019-02-01 06:53:10 -07:00
// now do a more extended validation
var certs [ ] * pem . Block // PEM-encoded certificates
var skippedBytes [ ] string // skipped bytes
2019-02-12 10:02:52 -07:00
pemblock := [ ] byte ( data . CertificateChain )
2019-02-01 06:53:10 -07: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 11:09:29 -07:00
}
2019-02-01 06:53:10 -07:00
var parsedCerts [ ] * x509 . Certificate
2019-01-30 11:09:29 -07:00
2019-02-01 06:53:10 -07:00
for _ , cert := range certs {
parsed , err := x509 . ParseCertificate ( cert . Bytes )
if err != nil {
2019-02-15 07:06:55 -07:00
data . WarningValidation = fmt . Sprintf ( "Failed to parse certificate: %s" , err )
return data
2019-02-01 06:53:10 -07:00
}
parsedCerts = append ( parsedCerts , parsed )
2019-01-30 11:09:29 -07:00
}
2019-02-01 06:53:10 -07:00
if len ( parsedCerts ) == 0 {
2019-02-15 07:06:55 -07:00
data . WarningValidation = fmt . Sprintf ( "You have specified an empty certificate" )
return data
2019-02-01 06:53:10 -07:00
}
2019-01-30 11:09:29 -07:00
2019-02-19 05:21:19 -07:00
data . ValidCert = true
2019-02-01 06:53:10 -07:00
// spew.Dump(parsedCerts)
2019-01-30 11:09:29 -07:00
2019-02-01 06:53:10 -07:00
opts := x509 . VerifyOptions {
DNSName : data . ServerName ,
}
2019-01-30 11:09:29 -07:00
2019-02-01 06:53:10 -07: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 11:09:29 -07:00
}
2019-02-01 06:53:10 -07:00
// TODO: save it as a warning rather than error it out -- shouldn't be a big problem
mainCert := parsedCerts [ 0 ]
2019-02-12 10:08:11 -07:00
_ , err := mainCert . Verify ( opts )
2019-02-01 06:53:10 -07:00
if err != nil {
2019-02-12 11:14:23 -07:00
// let self-signed certs through
data . WarningValidation = fmt . Sprintf ( "Your certificate does not verify: %s" , err )
2019-02-15 05:16:25 -07:00
} else {
data . ValidChain = true
2019-02-01 06:53:10 -07:00
}
// spew.Dump(chains)
2019-01-30 11:09:29 -07:00
2019-02-12 09:53:53 -07:00
// update status
if mainCert != nil {
2019-02-13 01:08:44 -07:00
notAfter := mainCert . NotAfter
2019-02-15 05:16:25 -07:00
data . Subject = mainCert . Subject . String ( )
data . Issuer = mainCert . Issuer . String ( )
data . NotAfter = notAfter
data . NotBefore = mainCert . NotBefore
data . DNSNames = mainCert . DNSNames
2019-02-13 01:08:44 -07:00
data . StatusCertificate = fmt . Sprintf ( "Certificate expires on %s" , notAfter ) //, valid for hostname %s", mainCert.NotAfter, mainCert.Subject.CommonName)
2019-02-12 09:53:53 -07:00
if len ( mainCert . DNSNames ) == 1 {
data . StatusCertificate += fmt . Sprintf ( ", valid for hostname %s" , mainCert . DNSNames [ 0 ] )
} else if len ( mainCert . DNSNames ) > 1 {
data . StatusCertificate += ", valid for hostnames " + strings . Join ( mainCert . DNSNames , ", " )
}
2019-01-30 11:09:29 -07:00
2019-02-12 09:53:53 -07:00
// issue a warning if certificate is about to expire
now := time . Now ( )
2019-02-13 01:08:44 -07:00
if now . AddDate ( 0 , 0 , 30 ) . After ( notAfter ) {
timeLeft := notAfter . Sub ( now )
2019-02-12 09:53:53 -07:00
if timeLeft > 0 {
data . Warning = fmt . Sprintf ( "Your certificate expires in %.0f days, we recommend you update it soon" , timeLeft . Hours ( ) / 24 )
} else {
data . Warning = fmt . Sprintf ( "Your certificate has expired on %s, we recommend you update it immediatedly" , mainCert . NotAfter )
}
2019-02-01 06:53:10 -07:00
}
2019-01-30 11:09:29 -07:00
}
}
2019-02-12 09:53:53 -07:00
2019-02-15 05:16:25 -07: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 07:06:55 -07:00
data . WarningValidation = "No valid keys were found"
return data
2019-02-15 05:16:25 -07:00
}
// parse the decoded key
_ , keytype , err := parsePrivateKey ( key . Bytes )
if err != nil {
2019-02-15 07:06:55 -07:00
data . WarningValidation = fmt . Sprintf ( "Failed to parse private key: %s" , err )
return data
2019-02-15 05:16:25 -07: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 07:06:55 -07:00
data . WarningValidation = fmt . Sprintf ( "Invalid certificate or key: %s" , err )
return data
2019-02-15 05:16:25 -07:00
}
2019-02-19 05:21:38 -07:00
data . usable = true
2019-02-15 05:16:25 -07:00
}
2019-02-15 07:06:55 -07:00
return data
2019-01-23 07:26:15 -07:00
}
2019-02-15 05:16:25 -07: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 10:02:52 -07: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 07:06:55 -07: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 ) )
data . CertificateChain = string ( encoded )
}
if data . PrivateKey != "" {
encoded := base64 . StdEncoding . EncodeToString ( [ ] byte ( data . PrivateKey ) )
data . PrivateKey = string ( encoded )
}
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-12 10:02:52 -07:00
// ------------------------
// registration of handlers
// ------------------------
2019-02-06 06:48:04 -07:00
func registerInstallHandlers ( ) {
http . HandleFunc ( "/control/install/get_addresses" , preInstall ( ensureGET ( handleInstallGetAddresses ) ) )
http . HandleFunc ( "/control/install/configure" , preInstall ( ensurePOST ( handleInstallConfigure ) ) )
}
2018-08-30 07:25:33 -07:00
func registerControlHandlers ( ) {
2019-01-29 10:41:57 -07: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 10:47:43 -07:00
http . HandleFunc ( "/control/querylog" , postInstall ( optionalAuth ( ensureGET ( handleQueryLog ) ) ) )
2019-01-29 10:41:57 -07:00
http . HandleFunc ( "/control/querylog_enable" , postInstall ( optionalAuth ( ensurePOST ( handleQueryLogEnable ) ) ) )
http . HandleFunc ( "/control/querylog_disable" , postInstall ( optionalAuth ( ensurePOST ( handleQueryLogDisable ) ) ) )
http . HandleFunc ( "/control/set_upstream_dns" , postInstall ( optionalAuth ( ensurePOST ( handleSetUpstreamDNS ) ) ) )
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 10:47:43 -07: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 10:41:57 -07: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 07:26:15 -07:00
http . HandleFunc ( "/control/tls/status" , postInstall ( optionalAuth ( ensureGET ( handleTLSStatus ) ) ) )
http . HandleFunc ( "/control/tls/configure" , postInstall ( optionalAuth ( ensurePOST ( handleTLSConfigure ) ) ) )
2019-02-12 10:08:11 -07:00
http . HandleFunc ( "/control/tls/validate" , postInstall ( optionalAuth ( ensurePOST ( handleTLSValidate ) ) ) )
2018-08-30 07:25:33 -07:00
}