package home import ( "encoding/json" "net/http" "time" "github.com/AdguardTeam/AdGuardHome/querylog" "github.com/miekg/dns" ) type qlogFilterJSON struct { Domain string `json:"domain"` Client string `json:"client"` QuestionType string `json:"question_type"` ResponseStatus string `json:"response_status"` } type queryLogRequest struct { OlderThan string `json:"older_than"` Filter qlogFilterJSON `json:"filter"` } // "value" -> value, return TRUE func getDoubleQuotesEnclosedValue(s *string) bool { t := *s if len(t) >= 2 && t[0] == '"' && t[len(t)-1] == '"' { *s = t[1 : len(t)-1] return true } return false } func handleQueryLog(w http.ResponseWriter, r *http.Request) { req := queryLogRequest{} err := json.NewDecoder(r.Body).Decode(&req) if err != nil { httpError(w, http.StatusBadRequest, "json decode: %s", err) return } params := querylog.GetDataParams{ Domain: req.Filter.Domain, Client: req.Filter.Client, } if len(req.OlderThan) != 0 { params.OlderThan, err = time.Parse(time.RFC3339Nano, req.OlderThan) if err != nil { httpError(w, http.StatusBadRequest, "invalid time stamp: %s", err) return } } if getDoubleQuotesEnclosedValue(¶ms.Domain) { params.StrictMatchDomain = true } if getDoubleQuotesEnclosedValue(¶ms.Client) { params.StrictMatchClient = true } if len(req.Filter.QuestionType) != 0 { qtype, ok := dns.StringToType[req.Filter.QuestionType] if !ok { httpError(w, http.StatusBadRequest, "invalid question_type") return } params.QuestionType = qtype } if len(req.Filter.ResponseStatus) != 0 { switch req.Filter.ResponseStatus { case "filtered": params.ResponseStatus = querylog.ResponseStatusFiltered default: httpError(w, http.StatusBadRequest, "invalid response_status") return } } data := config.queryLog.GetData(params) jsonVal, err := json.Marshal(data) if err != nil { httpError(w, http.StatusInternalServerError, "Couldn't marshal data into json: %s", err) return } w.Header().Set("Content-Type", "application/json") _, err = w.Write(jsonVal) if err != nil { httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err) } } func handleQueryLogClear(w http.ResponseWriter, r *http.Request) { config.queryLog.Clear() returnOK(w) } type qlogConfig struct { Enabled bool `json:"enabled"` Interval uint32 `json:"interval"` } // Get configuration func handleQueryLogInfo(w http.ResponseWriter, r *http.Request) { resp := qlogConfig{} resp.Enabled = config.DNS.QueryLogEnabled resp.Interval = config.DNS.QueryLogInterval jsonVal, err := json.Marshal(resp) if err != nil { httpError(w, http.StatusInternalServerError, "json encode: %s", err) return } w.Header().Set("Content-Type", "application/json") _, err = w.Write(jsonVal) if err != nil { httpError(w, http.StatusInternalServerError, "http write: %s", err) } } // Set configuration func handleQueryLogConfig(w http.ResponseWriter, r *http.Request) { reqData := qlogConfig{} err := json.NewDecoder(r.Body).Decode(&reqData) if err != nil { httpError(w, http.StatusBadRequest, "json decode: %s", err) return } if !checkQueryLogInterval(reqData.Interval) { httpError(w, http.StatusBadRequest, "Unsupported interval") return } config.DNS.QueryLogEnabled = reqData.Enabled config.DNS.QueryLogInterval = reqData.Interval _ = config.write() conf := querylog.Config{ Interval: config.DNS.QueryLogInterval * 24, } config.queryLog.Configure(conf) returnOK(w) } func checkQueryLogInterval(i uint32) bool { return i == 1 || i == 7 || i == 30 || i == 90 } // RegisterQueryLogHandlers - register handlers func RegisterQueryLogHandlers() { httpRegister("POST", "/control/querylog", handleQueryLog) httpRegister(http.MethodGet, "/control/querylog_info", handleQueryLogInfo) httpRegister(http.MethodPost, "/control/querylog_clear", handleQueryLogClear) httpRegister(http.MethodPost, "/control/querylog_config", handleQueryLogConfig) }