• 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

0.0
/pdns/dnsdistdist/dnsdist-secpoll.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

23
#include "dnsdist-secpoll.hh"
24
#ifndef DISABLE_SECPOLL
25

26
#include <vector>
27

28
#ifdef HAVE_LIBSODIUM
29
#include <sodium.h>
30
#endif /* HAVE_LIBSODIUM */
31

32
#include "dnsparser.hh"
33
#include "dolog.hh"
34
#include "iputils.hh"
35
#include "misc.hh"
36
#include "sstuff.hh"
37

38
#include "dnsdist.hh"
39
#include "dnsdist-metrics.hh"
40
#include "dnsdist-random.hh"
41

42
#ifndef PACKAGEVERSION
43
#define PACKAGEVERSION PACKAGE_VERSION
×
44
#endif
45

46
static std::string getFirstTXTAnswer(const std::string& answer)
47
{
×
48
  if (answer.size() <= sizeof(struct dnsheader)) {
×
49
    throw std::runtime_error("Looking for a TXT record in an answer smaller than the DNS header");
×
50
  }
×
51

52
  const dnsheader_aligned dh(answer.data());
×
53
  PacketReader pr(answer);
×
54
  uint16_t qdcount = ntohs(dh->qdcount);
×
55
  uint16_t ancount = ntohs(dh->ancount);
×
56

57
  DNSName rrname;
×
58
  uint16_t rrtype;
×
59
  uint16_t rrclass;
×
60

61
  size_t idx = 0;
×
62
  /* consume qd */
63
  for(; idx < qdcount; idx++) {
×
64
    rrname = pr.getName();
×
65
    rrtype = pr.get16BitInt();
×
66
    rrclass = pr.get16BitInt();
×
67
    (void) rrtype;
×
68
    (void) rrclass;
×
69
  }
×
70

71
  /* parse AN */
72
  for (idx = 0; idx < ancount; idx++) {
×
73
    string blob;
×
74
    struct dnsrecordheader ah;
×
75
    rrname = pr.getName();
×
76
    pr.getDnsrecordheader(ah);
×
77

78
    if (ah.d_type == QType::TXT) {
×
79
      string txt;
×
80
      pr.xfrText(txt);
×
81

82
      return txt;
×
83
    }
×
84
    else {
×
85
      pr.xfrBlob(blob);
×
86
    }
×
87
  }
×
88

89
  throw std::runtime_error("No TXT record in answer");
×
90
}
×
91

92
static std::string getSecPollStatus(const std::string& queriedName, int timeout=2)
93
{
×
94
  const auto verbose = dnsdist::configuration::getCurrentRuntimeConfiguration().d_verbose;
×
95

96
  const DNSName sentName(queriedName);
×
97
  std::vector<uint8_t> packet;
×
98
  DNSPacketWriter pw(packet, sentName, QType::TXT);
×
99
  pw.getHeader()->id = dnsdist::getRandomDNSID();
×
100
  pw.getHeader()->rd = 1;
×
101

102
  const auto& resolversForStub = getResolvers("/etc/resolv.conf");
×
103

104
  for(const auto& dest : resolversForStub) {
×
105
    Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
×
106
    sock.setNonBlocking();
×
107
    sock.connect(dest);
×
108
    sock.send(string(packet.begin(), packet.end()));
×
109

110
    string reply;
×
111
    int ret = waitForData(sock.getHandle(), timeout, 0);
×
112
    if (ret < 0) {
×
113
      if (verbose) {
×
114
        warnlog("Error while waiting for the secpoll response from stub resolver %s: %d", dest.toString(), ret);
×
115
      }
×
116
      continue;
×
117
    }
×
118
    else if (ret == 0) {
×
119
      if (verbose) {
×
120
        warnlog("Timeout while waiting for the secpoll response from stub resolver %s", dest.toString());
×
121
      }
×
122
      continue;
×
123
    }
×
124

125
    try {
×
126
      sock.read(reply);
×
127
    }
×
128
    catch(const std::exception& e) {
×
129
      if (verbose) {
×
130
        warnlog("Error while reading for the secpoll response from stub resolver %s: %s", dest.toString(), e.what());
×
131
      }
×
132
      continue;
×
133
    }
×
134

135
    if (reply.size() <= sizeof(struct dnsheader)) {
×
136
      if (verbose) {
×
137
        warnlog("Too short answer of size %d received from the secpoll stub resolver %s", reply.size(), dest.toString());
×
138
      }
×
139
      continue;
×
140
    }
×
141

142
    struct dnsheader d;
×
143
    memcpy(&d, reply.c_str(), sizeof(d));
×
144
    if (d.id != pw.getHeader()->id) {
×
145
      if (verbose) {
×
146
        warnlog("Invalid ID (%d / %d) received from the secpoll stub resolver %s", d.id, pw.getHeader()->id, dest.toString());
×
147
      }
×
148
      continue;
×
149
    }
×
150

151
    if (d.rcode != RCode::NoError) {
×
152
      if (verbose) {
×
153
        warnlog("Response code '%s' received from the secpoll stub resolver %s for '%s'", RCode::to_s(d.rcode), dest.toString(), queriedName);
×
154
      }
×
155

156
      /* no need to try another resolver if the domain does not exist */
157
      if (d.rcode == RCode::NXDomain) {
×
158
        throw std::runtime_error("Unable to get a valid Security Status update");
×
159
      }
×
160
      continue;
×
161
    }
×
162

163
    if (ntohs(d.qdcount) != 1 || ntohs(d.ancount) != 1) {
×
164
      if (verbose) {
×
165
        warnlog("Invalid answer (qdcount %d / ancount %d) received from the secpoll stub resolver %s", ntohs(d.qdcount), ntohs(d.ancount), dest.toString());
×
166
      }
×
167
      continue;
×
168
    }
×
169

170
    uint16_t receivedType;
×
171
    uint16_t receivedClass;
×
172
    DNSName receivedName(reply.c_str(), reply.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
×
173

174
    if (receivedName != sentName || receivedType != QType::TXT || receivedClass != QClass::IN) {
×
175
      if (verbose) {
×
176
        warnlog("Invalid answer, either the qname (%s / %s), qtype (%s / %s) or qclass (%s / %s) does not match, received from the secpoll stub resolver %s", receivedName, sentName, QType(receivedType).toString(), QType(QType::TXT).toString(), QClass(receivedClass).toString(), QClass::IN.toString(), dest.toString());
×
177
      }
×
178
      continue;
×
179
    }
×
180

181
    return getFirstTXTAnswer(reply);
×
182
  }
×
183

184
  throw std::runtime_error("Unable to get a valid Security Status update");
×
185
}
×
186

187
namespace dnsdist::secpoll
188
{
189
void doSecPoll(const std::string& suffix)
190
{
×
191
  static bool s_secPollDone{false};
×
192

193
  if (suffix.empty()) {
×
194
    return;
×
195
  }
×
196

197
  const std::string pkgv(PACKAGEVERSION);
×
198
  bool releaseVersion = std::count(pkgv.begin(), pkgv.end(), '.') == 2;
×
199
  const std::string version = "dnsdist-" + pkgv;
×
200
  std::string queriedName = version.substr(0, 63) + ".security-status." + suffix;
×
201

202
  if (*queriedName.rbegin() != '.') {
×
203
    queriedName += '.';
×
204
  }
×
205

206
  boost::replace_all(queriedName, "+", "_");
×
207
  boost::replace_all(queriedName, "~", "_");
×
208

209
  try {
×
210
    std::string status = getSecPollStatus(queriedName);
×
211
    pair<string, string> split = splitField(unquotify(status), ' ');
×
212

213
    int securityStatus = std::stoi(split.first);
×
214
    std::string securityMessage = split.second;
×
215

216
    if (securityStatus == 1 && !s_secPollDone) {
×
217
      infolog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
×
218
    }
×
219
    if (securityStatus == 2) {
×
220
      errlog("PowerDNS DNSDist Security Update Recommended: %s", securityMessage);
×
221
    }
×
222
    else if(securityStatus == 3) {
×
223
      errlog("PowerDNS DNSDist Security Update Mandatory: %s", securityMessage);
×
224
    }
×
225

226
    dnsdist::metrics::g_stats.securityStatus = securityStatus;
×
227
    s_secPollDone = true;
×
228
    return;
×
229
  }
×
230
  catch (const std::exception& e) {
×
231
    if (releaseVersion) {
×
232
      warnlog("Error while retrieving the security update for version %s: %s", version, e.what());
×
233
    }
×
234
    else if (!s_secPollDone) {
×
235
      infolog("Error while retrieving the security update for version %s: %s", version, e.what());
×
236
    }
×
237
  }
×
238

239
  if (releaseVersion) {
×
240
    warnlog("Failed to retrieve security status update for '%s' on %s", pkgv, queriedName);
×
241
  }
×
242
  else if (!s_secPollDone) {
×
243
    infolog("Not validating response for security status update, this is a non-release version.");
×
244

245
    /* for non-released versions, there is no use sending the same message several times,
246
       let's just accept that there will be no security polling for this exact version */
247
    s_secPollDone = true;
×
248
  }
×
249
}
×
250
}
251

252
#endif /* DISABLE_SECPOLL */
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