]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdist-web.cc
dnsdist: Support command= only on /jsonstat URL
[thirdparty/pdns.git] / pdns / dnsdist-web.cc
CommitLineData
50bed881 1#include "dnsdist.hh"
2#include "sstuff.hh"
3#include "ext/json11/json11.hpp"
4#include "ext/incbin/incbin.h"
5#include "dolog.hh"
6#include <thread>
7#include <sstream>
8#include <yahttp/yahttp.hpp>
9#include "namespaces.hh"
10#include <sys/time.h>
11#include <sys/resource.h>
12#include "ext/incbin/incbin.h"
13#include "htmlfiles.h"
14#include "base64.hh"
15
50bed881 16
17bool compareAuthorization(YaHTTP::Request& req, const string &expected_password)
18{
19 // validate password
20 YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
21 bool auth_ok = false;
22 if (header != req.headers.end() && toLower(header->second).find("basic ") == 0) {
23 string cookie = header->second.substr(6);
24
25 string plain;
26 B64Decode(cookie, plain);
27
28 vector<string> cparts;
29 stringtok(cparts, plain, ":");
30
31 // this gets rid of terminating zeros
32 auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str())));
33 }
34 return auth_ok;
35}
36
37
38static void connectionThread(int sock, ComboAddress remote, string password)
39{
40 using namespace json11;
fb7f8ec3 41 vinfolog("Webserver handling connection from %s", remote.toStringWithPort());
438a189b 42 FILE* fp=0;
43 fp=fdopen(sock, "r");
44 try {
45 string line;
46 string request;
47 while(stringfgets(fp, line)) {
48 request+=line;
49 trim(line);
50
51 if(line.empty())
52 break;
53 }
50bed881 54
438a189b 55 std::istringstream ifs(request);
56 YaHTTP::Request req;
57 ifs >> req;
50bed881 58
438a189b 59 string command=req.getvars["command"];
50bed881 60
438a189b 61 req.getvars.erase("_"); // jQuery cache buster
50bed881 62
438a189b 63 YaHTTP::Response resp(req);
ed5d8c79
RG
64 resp.headers["X-Content-Type-Options"] = "nosniff";
65 resp.headers["X-Frame-Options"] = "deny";
66 resp.headers["X-Permitted-Cross-Domain-Policies"] = "none";
67 resp.headers["X-XSS-Protection"] = "1; mode=block";
68 resp.headers["Content-Security-Policy"] = "default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'";
50bed881 69
438a189b 70 if (!compareAuthorization(req, password)) {
71 errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, remote.toStringWithPort());
72 resp.status=401;
73 resp.body="<h1>Unauthorized</h1>";
74 resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
50bed881 75
50bed881 76 }
1dd4c706 77 else if(req.url.path=="/jsonstat" && command=="stats") {
438a189b 78 resp.status=200;
d745cd6f 79
80 auto obj=Json::object {
438a189b 81 { "packetcache-hits", 0},
82 { "packetcache-misses", 0},
438a189b 83 { "over-capacity-drops", 0 },
b81b8710 84 { "too-old-drops", 0 },
85 { "server-policy", g_policy.getLocal()->name}
438a189b 86 };
87
d745cd6f 88 for(const auto& e : g_stats.entries) {
89 if(const auto& val = boost::get<DNSDistStats::stat_t*>(&e.second))
90 obj.insert({e.first, (int)(*val)->load()});
91 else if (const auto& val = boost::get<double*>(&e.second))
92 obj.insert({e.first, (**val)});
93 else
94 obj.insert({e.first, (int)(*boost::get<DNSDistStats::statfunction_t>(&e.second))(e.first)});
95 }
96 Json my_json = obj;
97
438a189b 98 resp.headers["Content-Type"] = "application/json";
99 resp.body=my_json.dump();
fcadd56e 100 }
1dd4c706 101 else if(req.url.path=="/jsonstat" && command=="dynblocklist") {
7be71139 102 resp.status=200;
103
104 Json::object obj;
105 auto slow = g_dynblockNMG.getCopy();
78ffa782 106 struct timespec now;
107 clock_gettime(CLOCK_MONOTONIC, &now);
7be71139 108 for(const auto& e: slow) {
78ffa782 109 if(now < e->second.until ) {
110 Json::object thing{{"reason", e->second.reason}, {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)},
111 {"blocks", (double)e->second.blocks} };
112 obj.insert({e->first.toString(), thing});
113 }
7be71139 114 }
115 Json my_json=obj;
116
117 resp.headers["Content-Type"] = "application/json";
118 resp.body=my_json.dump();
119 }
46d06a12 120 else if(req.url.path=="/api/v1/servers/localhost") {
438a189b 121 resp.status=200;
122
123 Json::array servers;
124 auto localServers = g_dstates.getCopy();
125 int num=0;
126 for(const auto& a : localServers) {
127 string status;
128 if(a->availability == DownstreamState::Availability::Up)
129 status = "UP";
130 else if(a->availability == DownstreamState::Availability::Down)
131 status = "DOWN";
132 else
133 status = (a->upStatus ? "up" : "down");
134 string pools;
135 for(const auto& p: a->pools)
136 pools+=p+" ";
137 Json::object server{
138 {"id", num++},
18eeccc9 139 {"name", a->name},
438a189b 140 {"address", a->remote.toStringWithPort()},
141 {"state", status},
142 {"qps", (int)a->queryLoad},
143 {"qpsLimit", (int)a->qps.getRate()},
144 {"outstanding", (int)a->outstanding},
145 {"reuseds", (int)a->reuseds},
146 {"weight", (int)a->weight},
147 {"order", (int)a->order},
148 {"pools", pools},
f20fbb61 149 {"latency", (int)(a->latencyUsec/1000.0)},
438a189b 150 {"queries", (int)a->queries}};
151
152 servers.push_back(server);
153 }
154
155 Json::array rules;
156 auto localRules = g_rulactions.getCopy();
157 num=0;
158 for(const auto& a : localRules) {
159 Json::object rule{
160 {"id", num++},
161 {"matches", (int)a.first->d_matches},
162 {"rule", a.first->toString()},
163 {"action", a.second->toString()} };
164 rules.push_back(rule);
165 }
da5d4999 166
167
168 string acl;
169
170 vector<string> vec;
fcadd56e 171
da5d4999 172 g_ACL.getCopy().toStringVector(&vec);
173
174 for(const auto& s : vec) {
175 if(!acl.empty()) acl += ", ";
176 acl+=s;
177 }
178 string localaddresses;
179 for(const auto& loc : g_locals) {
180 if(!localaddresses.empty()) localaddresses += ", ";
181 localaddresses += loc.first.toStringWithPort();
182 }
50bed881 183
438a189b 184 Json my_json = Json::object {
185 { "daemon_type", "dnsdist" },
52f40d27 186 { "version", VERSION},
438a189b 187 { "servers", servers},
188 { "rules", rules},
da5d4999 189 { "acl", acl},
190 { "local", localaddresses}
438a189b 191 };
192 resp.headers["Content-Type"] = "application/json";
193 resp.body=my_json.dump();
50bed881 194
438a189b 195 }
196 else if(!resp.url.path.empty() && g_urlmap.count(resp.url.path.c_str()+1)) {
197 resp.body.assign(g_urlmap[resp.url.path.c_str()+1]);
fe20a1c1
PL
198 vector<string> parts;
199 stringtok(parts, resp.url.path, ".");
200 if(parts.back() == "html")
201 resp.headers["Content-Type"] = "text/html";
202 else if(parts.back() == "css")
203 resp.headers["Content-Type"] = "text/css";
204 else if(parts.back() == "js")
205 resp.headers["Content-Type"] = "application/javascript";
438a189b 206 resp.status=200;
207 }
208 else if(resp.url.path=="/") {
209 resp.body.assign(g_urlmap["index.html"]);
fe20a1c1 210 resp.headers["Content-Type"] = "text/html";
438a189b 211 resp.status=200;
212 }
213 else {
214 // cerr<<"404 for: "<<resp.url.path<<endl;
215 resp.status=404;
216 }
50bed881 217
438a189b 218 std::ostringstream ofs;
219 ofs << resp;
220 string done;
221 done=ofs.str();
222 writen2(sock, done.c_str(), done.size());
50bed881 223
438a189b 224 fclose(fp);
225 fp=0;
226 }
227 catch(...)
228 {
229 errlog("Webserver thread died with exception");
230 if(fp)
231 fclose(fp);
232 }
50bed881 233}
50bed881 234void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::string& password)
235{
d745cd6f 236 warnlog("Webserver launched on %s", local.toStringWithPort());
50bed881 237 for(;;) {
238 try {
239 ComboAddress remote(local);
240 int fd = SAccept(sock, remote);
241 vinfolog("Got connection from %s", remote.toStringWithPort());
242 std::thread t(connectionThread, fd, remote, password);
243 t.detach();
244 }
245 catch(std::exception& e) {
246 errlog("Had an error accepting new webserver connection: %s", e.what());
247 }
248 }
249}