]>
Commit | Line | Data |
---|---|---|
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 | |
17 | bool 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 | ||
38 | static 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 | 234 | void 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 | } |