]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdistdist/dnsdist-secpoll.cc
Merge pull request #9229 from rgacogne/dnsdist-webserver-allow-from
[thirdparty/pdns.git] / 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 "config.h"
24
25 #include <string>
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-secpoll.hh"
40
41 #ifndef PACKAGEVERSION
42 #define PACKAGEVERSION PACKAGE_VERSION
43 #endif
44
45 static std::string getFirstTXTAnswer(const std::string& answer)
46 {
47 if (answer.size() <= sizeof(struct dnsheader)) {
48 throw std::runtime_error("Looking for a TXT record in an answer smaller than the DNS header");
49 }
50
51 const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
52 PacketReader pr(answer);
53 uint16_t qdcount = ntohs(dh->qdcount);
54 uint16_t ancount = ntohs(dh->ancount);
55
56 DNSName rrname;
57 uint16_t rrtype;
58 uint16_t rrclass;
59
60 size_t idx = 0;
61 /* consume qd */
62 for(; idx < qdcount; idx++) {
63 rrname = pr.getName();
64 rrtype = pr.get16BitInt();
65 rrclass = pr.get16BitInt();
66 (void) rrtype;
67 (void) rrclass;
68 }
69
70 /* parse AN */
71 for (idx = 0; idx < ancount; idx++) {
72 string blob;
73 struct dnsrecordheader ah;
74 rrname = pr.getName();
75 pr.getDnsrecordheader(ah);
76
77 if (ah.d_type == QType::TXT) {
78 string txt;
79 pr.xfrText(txt);
80
81 return txt;
82 }
83 else {
84 pr.xfrBlob(blob);
85 }
86 }
87
88 throw std::runtime_error("No TXT record in answer");
89 }
90
91 static std::string getSecPollStatus(const std::string& queriedName, int timeout=2)
92 {
93 const DNSName& sentName = DNSName(queriedName);
94 vector<uint8_t> packet;
95 DNSPacketWriter pw(packet, sentName, QType::TXT);
96 pw.getHeader()->id = getRandomDNSID();
97 pw.getHeader()->rd = 1;
98
99 const auto& resolversForStub = getResolvers("/etc/resolv.conf");
100
101 for(const auto& dest : resolversForStub) {
102 Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
103 sock.setNonBlocking();
104 sock.connect(dest);
105 sock.send(string(packet.begin(), packet.end()));
106
107 string reply;
108 int ret = waitForData(sock.getHandle(), timeout, 0);
109 if (ret < 0) {
110 if (g_verbose) {
111 warnlog("Error while waiting for the secpoll response from stub resolver %s: %d", dest.toString(), ret);
112 }
113 continue;
114 }
115 else if (ret == 0) {
116 if (g_verbose) {
117 warnlog("Timeout while waiting for the secpoll response from stub resolver %s", dest.toString());
118 }
119 continue;
120 }
121
122 try {
123 sock.read(reply);
124 }
125 catch(const std::exception& e) {
126 if (g_verbose) {
127 warnlog("Error while reading for the secpoll response from stub resolver %s: %s", dest.toString(), e.what());
128 }
129 continue;
130 }
131
132 if (reply.size() <= sizeof(struct dnsheader)) {
133 if (g_verbose) {
134 warnlog("Too short answer of size %d received from the secpoll stub resolver %s", reply.size(), dest.toString());
135 }
136 continue;
137 }
138
139 struct dnsheader d;
140 memcpy(&d, reply.c_str(), sizeof(d));
141 if (d.id != pw.getHeader()->id) {
142 if (g_verbose) {
143 warnlog("Invalid ID (%d / %d) received from the secpoll stub resolver %s", d.id, pw.getHeader()->id, dest.toString());
144 }
145 continue;
146 }
147
148 if (d.rcode != RCode::NoError) {
149 if (g_verbose) {
150 warnlog("Response code '%s' received from the secpoll stub resolver %s for '%s'", RCode::to_s(d.rcode), dest.toString(), queriedName);
151 }
152
153 /* no need to try another resolver if the domain does not exist */
154 if (d.rcode == RCode::NXDomain) {
155 throw std::runtime_error("Unable to get a valid Security Status update");
156 }
157 continue;
158 }
159
160 if (ntohs(d.qdcount) != 1 || ntohs(d.ancount) != 1) {
161 if (g_verbose) {
162 warnlog("Invalid answer (qdcount %d / ancount %d) received from the secpoll stub resolver %s", ntohs(d.qdcount), ntohs(d.ancount), dest.toString());
163 }
164 continue;
165 }
166
167 uint16_t receivedType;
168 uint16_t receivedClass;
169 DNSName receivedName(reply.c_str(), reply.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
170
171 if (receivedName != sentName || receivedType != QType::TXT || receivedClass != QClass::IN) {
172 if (g_verbose) {
173 warnlog("Invalid answer, either the qname (%s / %s), qtype (%s / %s) or qclass (%d / %d) does not match, received from the secpoll stub resolver %s", receivedName, sentName, QType(receivedType).getName(), QType(QType::TXT).getName(), receivedClass, QClass::IN, dest.toString());
174 }
175 continue;
176 }
177
178 return getFirstTXTAnswer(reply);
179 }
180
181 throw std::runtime_error("Unable to get a valid Security Status update");
182 }
183
184 static bool g_secPollDone{false};
185 std::string g_secPollSuffix{"secpoll.powerdns.com."};
186 time_t g_secPollInterval{3600};
187
188 void doSecPoll(const std::string& suffix)
189 {
190 if (suffix.empty()) {
191 return;
192 }
193
194 const std::string pkgv(PACKAGEVERSION);
195 bool releaseVersion = std::count(pkgv.begin(), pkgv.end(), '.') == 2;
196 const std::string version = "dnsdist-" + pkgv;
197 std::string queriedName = version.substr(0, 63) + ".security-status." + suffix;
198
199 if (*queriedName.rbegin() != '.') {
200 queriedName += '.';
201 }
202
203 boost::replace_all(queriedName, "+", "_");
204 boost::replace_all(queriedName, "~", "_");
205
206 try {
207 std::string status = getSecPollStatus(queriedName);
208 pair<string, string> split = splitField(unquotify(status), ' ');
209
210 int securityStatus = std::stoi(split.first);
211 std::string securityMessage = split.second;
212
213 if(securityStatus == 1 && !g_secPollDone) {
214 warnlog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
215 }
216 if(securityStatus == 2) {
217 errlog("PowerDNS DNSDist Security Update Recommended: %s", securityMessage);
218 }
219 else if(securityStatus == 3) {
220 errlog("PowerDNS DNSDist Security Update Mandatory: %s", securityMessage);
221 }
222
223 g_stats.securityStatus = securityStatus;
224 g_secPollDone = true;
225 return;
226 }
227 catch(const std::exception& e) {
228 if (releaseVersion) {
229 warnlog("Error while retrieving the security update for version %s: %s", version, e.what());
230 }
231 else if (!g_secPollDone) {
232 infolog("Error while retrieving the security update for version %s: %s", version, e.what());
233 }
234 }
235
236 if (releaseVersion) {
237 warnlog("Failed to retrieve security status update for '%s' on %s", pkgv, queriedName);
238 }
239 else if (!g_secPollDone) {
240 infolog("Not validating response for security status update, this is a non-release version.");
241
242 /* for non-released versions, there is no use sending the same message several times,
243 let's just accept that there will be no security polling for this exact version */
244 g_secPollDone = true;
245 }
246 }