]>
Commit | Line | Data |
---|---|---|
5d4e1ef8 RG |
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 | ||
555970c9 | 38 | #include "dnsdist.hh" |
5d4e1ef8 RG |
39 | #include "dnsdist-secpoll.hh" |
40 | ||
555970c9 RG |
41 | #ifndef PACKAGEVERSION |
42 | #define PACKAGEVERSION PACKAGE_VERSION | |
43 | #endif | |
44 | ||
5d4e1ef8 RG |
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 | { | |
555970c9 | 93 | const DNSName& sentName = DNSName(queriedName); |
5d4e1ef8 | 94 | vector<uint8_t> packet; |
555970c9 RG |
95 | DNSPacketWriter pw(packet, sentName, QType::TXT); |
96 | pw.getHeader()->id = getRandomDNSID(); | |
5d4e1ef8 RG |
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) { | |
aa74ed19 | 162 | warnlog("Invalid answer (qdcount %d / ancount %d) received from the secpoll stub resolver %s", ntohs(d.qdcount), ntohs(d.ancount), dest.toString()); |
5d4e1ef8 RG |
163 | } |
164 | continue; | |
165 | } | |
166 | ||
555970c9 RG |
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 | ||
5d4e1ef8 RG |
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 | ||
555970c9 | 194 | const std::string pkgv(PACKAGEVERSION); |
4f9f2154 | 195 | bool releaseVersion = std::count(pkgv.begin(), pkgv.end(), '.') == 2; |
650754df | 196 | const std::string version = "dnsdist-" + pkgv; |
5d4e1ef8 RG |
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 | ||
f29758cc | 223 | g_stats.securityStatus = securityStatus; |
5d4e1ef8 RG |
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) { | |
0590f3a3 | 237 | warnlog("Failed to retrieve security status update for '%s' on %s", pkgv, queriedName); |
5d4e1ef8 RG |
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 | } |