• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 12595591960

03 Jan 2025 09:27AM UTC coverage: 62.774% (+2.5%) from 60.245%
12595591960

Pull #15008

github

web-flow
Merge c2a2749d3 into 788f396a7
Pull Request #15008: Do not follow CNAME records for ANY or CNAME queries

30393 of 78644 branches covered (38.65%)

Branch coverage included in aggregate %.

105822 of 138350 relevant lines covered (76.49%)

4613078.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.76
/pdns/ws-api.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#ifdef HAVE_CONFIG_H
23
#include "config.h"
24
#endif
25

26
#include <boost/tokenizer.hpp>
27
#include <boost/format.hpp>
28

29
#include "namespaces.hh"
30
#include "ws-api.hh"
31
#include "json.hh"
32
#include "version.hh"
33
#include "arguments.hh"
34
#include "dnsparser.hh"
35
#ifdef RECURSOR
36
#include "syncres.hh"
37
#else
38
#include "responsestats.hh"
39
#include "statbag.hh"
40
#endif
41
#include <cstdio>
42
#include <cstring>
43
#include <cctype>
44
#include <sys/types.h>
45
#include <iomanip>
46

47
using json11::Json;
48

49
#ifndef RECURSOR
50
extern StatBag S;
51
#endif
52

53
#ifndef HAVE_STRCASESTR
54

55
/*
56
 * strcasestr() locates the first occurrence in the string s1 of the
57
 * sequence of characters (excluding the terminating null character)
58
 * in the string s2, ignoring case.  strcasestr() returns a pointer
59
 * to the located string, or a null pointer if the string is not found.
60
 * If s2 is empty, the function returns s1.
61
 */
62

63
static char*
64
strcasestr(const char* s1, const char* s2)
65
{
66
  int* cm = __trans_lower;
67
  const uchar_t* us1 = (const uchar_t*)s1;
68
  const uchar_t* us2 = (const uchar_t*)s2;
69
  const uchar_t* tptr;
70
  int c;
71

72
  if (us2 == NULL || *us2 == '\0')
73
    return ((char*)us1);
74

75
  c = cm[*us2];
76
  while (*us1 != '\0') {
77
    if (c == cm[*us1++]) {
78
      tptr = us1;
79
      while (cm[c = *++us2] == cm[*us1++] && c != '\0')
80
        continue;
81
      if (c == '\0')
82
        return ((char*)tptr - 1);
83
      us1 = tptr;
84
      us2 = (const uchar_t*)s2;
85
      c = cm[*us2];
86
    }
87
  }
88

89
  return (NULL);
90
}
91

92
#endif // HAVE_STRCASESTR
93

94
static Json getServerDetail()
95
{
2✔
96
  return Json::object{
2✔
97
    {"type", "Server"},
2✔
98
    {"id", "localhost"},
2✔
99
    {"url", "/api/v1/servers/localhost"},
2✔
100
    {"daemon_type", productTypeApiType()},
2✔
101
    {"version", getPDNSVersion()},
2✔
102
    {"config_url", "/api/v1/servers/localhost/config{/config_setting}"},
2✔
103
    {"zones_url", "/api/v1/servers/localhost/zones{/zone}"},
2✔
104
#ifndef RECURSOR
105
    {"autoprimaries_url", "/api/v1/servers/localhost/autoprimaries{/autoprimary}"}
106
#endif
107
  };
2✔
108
}
2✔
109

110
/* Return information about the supported API versions.
111
 * The format of this MUST NEVER CHANGE at it's not versioned.
112
 */
113
void apiDiscovery(HttpRequest* /* req */, HttpResponse* resp)
114
{
1✔
115
  Json version1 = Json::object{
1✔
116
    {"version", 1},
1✔
117
    {"url", "/api/v1"}};
1✔
118
  Json doc = Json::array{std::move(version1)};
1✔
119

120
  resp->setJsonBody(doc);
1✔
121
}
1✔
122

123
void apiDiscoveryV1(HttpRequest* /* req */, HttpResponse* resp)
124
{
1✔
125
  const Json& version1 = Json::object{
1✔
126
    {"server_url", "/api/v1/servers{/server}"},
1✔
127
    {"api_features", Json::array{}}};
1✔
128
  const Json& doc = Json::array{version1};
1✔
129

130
  resp->setJsonBody(doc);
1✔
131
}
1✔
132

133
void apiServer(HttpRequest* /* req */, HttpResponse* resp)
134
{
1✔
135
  const Json& doc = Json::array{getServerDetail()};
1✔
136
  resp->setJsonBody(doc);
1✔
137
}
1✔
138

139
void apiServerDetail(HttpRequest* /* req */, HttpResponse* resp)
140
{
1✔
141
  resp->setJsonBody(getServerDetail());
1✔
142
}
1✔
143

144
void apiServerConfig(HttpRequest* /* req */, HttpResponse* resp)
145
{
1✔
146
  const vector<string>& items = ::arg().list();
1✔
147
  string value;
1✔
148
  Json::array doc;
1✔
149
  for (const string& item : items) {
244✔
150
    if (item.find("password") != string::npos || item.find("api-key") != string::npos) {
244✔
151
      value = "***";
2✔
152
    }
2✔
153
    else {
242✔
154
      value = ::arg()[item];
242✔
155
    }
242✔
156

157
    doc.push_back(Json::object{
244✔
158
      {"type", "ConfigSetting"},
244✔
159
      {"name", item},
244✔
160
      {"value", value},
244✔
161
    });
244✔
162
  }
244✔
163
  resp->setJsonBody(doc);
1✔
164
}
1✔
165

166
void apiServerStatistics(HttpRequest* req, HttpResponse* resp)
167
{
81✔
168
  Json::array doc;
81✔
169
  string name = req->getvars["statistic"];
81✔
170
  if (!name.empty()) {
81✔
171
    const auto& stat = productServerStatisticsFetch(name);
2✔
172
    if (!stat) {
2✔
173
      throw ApiException("Unknown statistic name");
1✔
174
    }
1✔
175

176
    doc.push_back(Json::object{
1✔
177
      {"type", "StatisticItem"},
1✔
178
      {"name", name},
1✔
179
      {"value", std::to_string(*stat)},
1✔
180
    });
1✔
181

182
    resp->setJsonBody(doc);
1✔
183

184
    return;
1✔
185
  }
2✔
186

187
  typedef map<string, string> stat_items_t;
79✔
188
  stat_items_t general_stats;
79✔
189
  productServerStatisticsFetch(general_stats);
79✔
190

191
  for (const auto& item : general_stats) {
18,881✔
192
    doc.push_back(Json::object{
18,881✔
193
      {"type", "StatisticItem"},
18,881✔
194
      {"name", item.first},
18,881✔
195
      {"value", item.second},
18,881✔
196
    });
18,881✔
197
  }
18,881✔
198

199
#ifdef RECURSOR
79✔
200
  auto stats = g_Counters.sum(rec::ResponseStats::responseStats);
79✔
201
  auto resp_qtype_stats = stats.getQTypeResponseCounts();
79✔
202
  auto resp_size_stats = stats.getSizeResponseCounts();
79✔
203
  auto resp_rcode_stats = stats.getRCodeResponseCounts();
79✔
204
#else
205
  auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
206
  auto resp_size_stats = g_rs.getSizeResponseCounts();
207
  auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
208
#endif
209
  {
79✔
210
    Json::array values;
79✔
211
    for (const auto& item : resp_qtype_stats) {
120✔
212
      if (item.second == 0) {
120!
213
        continue;
×
214
      }
×
215
      values.push_back(Json::object{
120✔
216
        {"name", DNSRecordContent::NumberToType(item.first)},
120✔
217
        {"value", std::to_string(item.second)},
120✔
218
      });
120✔
219
    }
120✔
220

221
    doc.push_back(Json::object{
79✔
222
      {"type", "MapStatisticItem"},
79✔
223
      {"name", "response-by-qtype"},
79✔
224
      {"value", values},
79✔
225
    });
79✔
226
  }
79✔
227

228
  {
79✔
229
    Json::array values;
79✔
230
    for (const auto& item : resp_size_stats) {
147✔
231
      if (item.second == 0) {
147!
232
        continue;
×
233
      }
×
234

235
      values.push_back(Json::object{
147✔
236
        {"name", std::to_string(item.first)},
147✔
237
        {"value", std::to_string(item.second)},
147✔
238
      });
147✔
239
    }
147✔
240

241
    doc.push_back(Json::object{
79✔
242
      {"type", "MapStatisticItem"},
79✔
243
      {"name", "response-sizes"},
79✔
244
      {"value", values},
79✔
245
    });
79✔
246
  }
79✔
247

248
  {
79✔
249
    Json::array values;
79✔
250
    for (const auto& item : resp_rcode_stats) {
96✔
251
      if (item.second == 0) {
96!
252
        continue;
×
253
      }
×
254

255
      values.push_back(Json::object{
96✔
256
        {"name", RCode::to_s(item.first)},
96✔
257
        {"value", std::to_string(item.second)},
96✔
258
      });
96✔
259
    }
96✔
260

261
    doc.push_back(Json::object{
79✔
262
      {"type", "MapStatisticItem"},
79✔
263
      {"name", "response-by-rcode"},
79✔
264
      {"value", values},
79✔
265
    });
79✔
266
  }
79✔
267

268
#ifndef RECURSOR
269
  if ((req->getvars.count("includerings") == 0) || req->getvars["includerings"] != "false") {
270
    for (const auto& ringName : S.listRings()) {
271
      Json::array values;
272
      const auto& ring = S.getRing(ringName);
273
      for (const auto& item : ring) {
274
        if (item.second == 0) {
275
          continue;
276
        }
277

278
        values.push_back(Json::object{
279
          {"name", item.first},
280
          {"value", std::to_string(item.second)},
281
        });
282
      }
283

284
      doc.push_back(Json::object{
285
        {"type", "RingStatisticItem"},
286
        {"name", ringName},
287
        {"size", std::to_string(S.getRingSize(ringName))},
288
        {"value", values},
289
      });
290
    }
291
  }
292
#endif
293

294
  resp->setJsonBody(doc);
79✔
295
}
79✔
296

297
DNSName apiNameToDNSName(const string& name)
298
{
27✔
299
  if (!isCanonical(name)) {
27✔
300
    throw ApiException("DNS Name '" + name + "' is not canonical");
3✔
301
  }
3✔
302
  try {
24✔
303
    return DNSName(name);
24✔
304
  }
24✔
305
  catch (...) {
24✔
306
    throw ApiException("Unable to parse DNS Name '" + name + "'");
×
307
  }
×
308
}
24✔
309

310
DNSName apiZoneIdToName(const string& identifier)
311
{
3✔
312
  string zonename;
3✔
313
  ostringstream outputStringStream;
3✔
314

315
  if (identifier.empty()) {
3!
316
    throw HttpBadRequestException();
×
317
  }
×
318

319
  std::size_t lastpos = 0;
3✔
320
  std::size_t pos = 0;
3✔
321
  while ((pos = identifier.find('=', lastpos)) != string::npos) {
3!
322
    outputStringStream << identifier.substr(lastpos, pos - lastpos);
×
323
    char currentChar{};
×
324
    // decode tens
325
    if (identifier[pos + 1] >= '0' && identifier[pos + 1] <= '9') {
×
326
      currentChar = static_cast<char>(identifier[pos + 1] - '0');
×
327
    }
×
328
    else if (identifier[pos + 1] >= 'A' && identifier[pos + 1] <= 'F') {
×
329
      currentChar = static_cast<char>(identifier[pos + 1] - 'A' + 10);
×
330
    }
×
331
    else {
×
332
      throw HttpBadRequestException();
×
333
    }
×
334
    currentChar = static_cast<char>(currentChar * 16);
×
335

336
    // decode unit place
337
    if (identifier[pos + 2] >= '0' && identifier[pos + 2] <= '9') {
×
338
      currentChar = static_cast<char>(currentChar + identifier[pos + 2] - '0');
×
339
    }
×
340
    else if (identifier[pos + 2] >= 'A' && identifier[pos + 2] <= 'F') {
×
341
      currentChar = static_cast<char>(currentChar + identifier[pos + 2] - 'A' + 10);
×
342
    }
×
343
    else {
×
344
      throw HttpBadRequestException();
×
345
    }
×
346

347
    outputStringStream << currentChar;
×
348

349
    lastpos = pos + 3;
×
350
  }
×
351
  if (lastpos < pos) {
3!
352
    outputStringStream << identifier.substr(lastpos, pos - lastpos);
3✔
353
  }
3✔
354

355
  zonename = outputStringStream.str();
3✔
356

357
  try {
3✔
358
    return DNSName(zonename);
3✔
359
  }
3✔
360
  catch (...) {
3✔
361
    throw ApiException("Unable to parse DNS Name '" + zonename + "'");
×
362
  }
×
363
}
3✔
364

365
string apiZoneNameToId(const DNSName& dname)
366
{
136✔
367
  string name = dname.toString();
136✔
368
  ostringstream outputStringStream;
136✔
369

370
  for (char iter : name) {
2,958✔
371
    if ((iter >= 'A' && iter <= 'Z') || (iter >= 'a' && iter <= 'z') || (iter >= '0' && iter <= '9') || (iter == '.') || (iter == '-')) {
2,958!
372
      outputStringStream << iter;
2,954✔
373
    }
2,954✔
374
    else {
4✔
375
      outputStringStream << (boost::format("=%02X") % (int)iter);
4✔
376
    }
4✔
377
  }
2,958✔
378

379
  string identifier = outputStringStream.str();
136✔
380

381
  // add trailing dot
382
  if (identifier.empty() || identifier.substr(identifier.size() - 1) != ".") {
136!
383
    identifier += ".";
×
384
  }
×
385

386
  // special handling for the root zone, as a dot on it's own doesn't work
387
  // everywhere.
388
  if (identifier == ".") {
136!
389
    identifier = (boost::format("=%02X") % (int)('.')).str();
×
390
  }
×
391
  return identifier;
136✔
392
}
136✔
393

394
void apiCheckNameAllowedCharacters(const string& name)
395
{
10✔
396
  if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos) {
10!
397
    throw ApiException("Name '" + name + "' contains unsupported characters");
×
398
  }
×
399
}
10✔
400

401
void apiCheckQNameAllowedCharacters(const string& qname)
402
{
×
403
  if (qname.compare(0, 2, "*.") == 0) {
×
404
    apiCheckNameAllowedCharacters(qname.substr(2));
×
405
  }
×
406
  else {
×
407
    apiCheckNameAllowedCharacters(qname);
×
408
  }
×
409
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc