]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixplore.cc
8646eaf254d4828088afec565c35353044123065
[thirdparty/pdns.git] / pdns / ixplore.cc
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include "arguments.hh"
5 #include "base64.hh"
6 #include <sys/types.h>
7 #include <dirent.h>
8
9 #include "dnsparser.hh"
10 #include "sstuff.hh"
11 #include "misc.hh"
12 #include "dnswriter.hh"
13 #include "dnsrecords.hh"
14 #include "statbag.hh"
15 #include "base32.hh"
16 #include "dnssecinfra.hh"
17
18 #include "dns_random.hh"
19 #include "gss_context.hh"
20 #include "zoneparser-tng.hh"
21 #include <boost/multi_index_container.hpp>
22 #include "resolver.hh"
23 #include <fstream>
24 #include "ixfr.hh"
25 using namespace boost::multi_index;
26 StatBag S;
27
28 ArgvMap &arg()
29 {
30 static ArgvMap theArg;
31 return theArg;
32 }
33
34
35 struct CIContentCompareStruct
36 {
37 bool operator()(const shared_ptr<DNSRecordContent>&a, const shared_ptr<DNSRecordContent>& b) const
38 {
39 return toLower(a->getZoneRepresentation()) < toLower(b->getZoneRepresentation());
40 }
41 };
42
43
44 typedef multi_index_container<
45 DNSRecord,
46 indexed_by<
47 ordered_non_unique<
48 composite_key<DNSRecord,
49 member<DNSRecord, DNSName, &DNSRecord::d_name>,
50 member<DNSRecord, uint16_t, &DNSRecord::d_type>,
51 member<DNSRecord, uint16_t, &DNSRecord::d_class>,
52 member<DNSRecord, shared_ptr<DNSRecordContent>, &DNSRecord::d_content> >,
53 composite_key_compare<CanonDNSNameCompare, std::less<uint16_t>, std::less<uint16_t>, CIContentCompareStruct >
54
55 >
56 >
57 >records_t;
58
59 uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<SOARecordContent>& sr, const TSIGTriplet& tt = TSIGTriplet())
60 {
61 vector<uint8_t> packet;
62 DNSPacketWriter pw(packet, zone, QType::SOA);
63 if(!tt.algo.empty()) {
64 TSIGRecordContent trc;
65 trc.d_algoName = tt.algo;
66 trc.d_time = time((time_t*)NULL);
67 trc.d_fudge = 300;
68 trc.d_origID=ntohs(pw.getHeader()->id);
69 trc.d_eRcode=0;
70 addTSIG(pw, &trc, tt.name, tt.secret, "", false);
71 }
72
73 Socket s(master.sin4.sin_family, SOCK_DGRAM);
74 s.connect(master);
75 string msg((const char*)&packet[0], packet.size());
76 s.writen(msg);
77
78 string reply;
79 s.read(reply);
80 MOADNSParser mdp(reply);
81 if(mdp.d_header.rcode) {
82 throw std::runtime_error("Unable to retrieve SOA serial from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
83 }
84 for(const auto& r: mdp.d_answers) {
85 if(r.first.d_type == QType::SOA) {
86 sr = std::dynamic_pointer_cast<SOARecordContent>(r.first.d_content);
87 return sr->d_st.serial;
88 }
89 }
90 return 0;
91 }
92
93
94 uint32_t getSerialsFromDir(const std::string& dir)
95 {
96 uint32_t ret=0;
97 DIR* dirhdl=opendir(dir.c_str());
98 if(!dirhdl)
99 throw runtime_error("Could not open IXFR directory");
100 struct dirent *entry;
101
102 while((entry = readdir(dirhdl))) {
103 uint32_t num = atoi(entry->d_name);
104 if(std::to_string(num) == entry->d_name)
105 ret = max(num, ret);
106 }
107 closedir(dirhdl);
108 return ret;
109 }
110
111 uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret)
112 {
113 DNSName root(".");
114 uint16_t t=QType::SOA;
115
116 auto found = records.equal_range(tie(root, t));
117
118 for(auto iter = found.first; iter != found.second; ++iter) {
119 auto soa = std::dynamic_pointer_cast<SOARecordContent>(iter->d_content);
120 soaret = *iter;
121 return soa->d_st.serial;
122 }
123 return 0;
124 }
125
126 void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::string& directory)
127 {
128 DNSRecord soa;
129 int serial = getSerialFromRecords(records, soa);
130 string fname=directory +"/"+std::to_string(serial);
131 FILE* fp=fopen((fname+".partial").c_str(), "w");
132 if(!fp)
133 throw runtime_error("Unable to open file '"+fname+".partial' for writing: "+string(strerror(errno)));
134
135 records_t soarecord;
136 soarecord.insert(soa);
137 fprintf(fp, "$ORIGIN %s\n", zone.toString().c_str());
138 for(const auto& outer : {soarecord, records, soarecord} ) {
139 for(const auto& r: outer) {
140 fprintf(fp, "%s\tIN\t%s\t%s\n",
141 r.d_name.isRoot() ? "@" : r.d_name.toStringNoDot().c_str(),
142 DNSRecordContent::NumberToType(r.d_type).c_str(),
143 r.d_content->getZoneRepresentation().c_str());
144 }
145 }
146 fclose(fp);
147 rename( (fname+".partial").c_str(), fname.c_str());
148 }
149
150 void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zone)
151 {
152 ZoneParserTNG zpt(fname, zone);
153
154 DNSResourceRecord rr;
155 bool seenSOA=false;
156 unsigned int nrecords=0;
157 while(zpt.get(rr)) {
158 ++nrecords;
159 if(rr.qtype.getCode() == QType::CNAME && rr.content.empty())
160 rr.content=".";
161 rr.qname = rr.qname.makeRelative(zone);
162
163 if(rr.qtype.getCode() != QType::SOA || seenSOA==false)
164 records.insert(DNSRecord(rr));
165 if(rr.qtype.getCode() == QType::SOA) {
166 seenSOA=true;
167 }
168 }
169 cout<<"Parsed "<<nrecords<<" records"<<endl;
170 if(rr.qtype.getCode() == QType::SOA && seenSOA) {
171 cout<<"Zone was complete (SOA at end)"<<endl;
172 }
173 else {
174 records.clear();
175 throw runtime_error("Zone not complete!");
176 }
177 }
178
179 void usage() {
180 cerr<<"Syntax: ixplore diff ZONE BEFORE_FILE AFTER_FILE"<<endl;
181 cerr<<"Syntax: ixplore track IP-ADDRESS PORT ZONE DIRECTORY [TSIGKEY TSIGALGO TSIGSECRET]"<<endl;
182 }
183
184 int main(int argc, char** argv)
185 try
186 {
187 for(int n=1 ; n < argc; ++n) {
188 if ((string) argv[n] == "--help") {
189 usage();
190 return EXIT_SUCCESS;
191 }
192
193 if ((string) argv[n] == "--version") {
194 cerr<<"ixplore "<<VERSION<<endl;
195 return EXIT_SUCCESS;
196 }
197 }
198
199 reportAllTypes();
200 string command;
201 if(argc < 5 || (command=argv[1], (command!="diff" && command !="track"))) {
202 usage();
203 exit(EXIT_FAILURE);
204 }
205 if(command=="diff") {
206 records_t before, after;
207 DNSName zone(argv[2]);
208 cout<<"Loading before from "<<argv[3]<<endl;
209 loadZoneFromDisk(before, argv[3], zone);
210 cout<<"Loading after from "<<argv[4]<<endl;
211 loadZoneFromDisk(after, argv[4], zone);
212
213 vector<DNSRecord> diff;
214
215 set_difference(before.cbegin(), before.cend(), after.cbegin(), after.cend(), back_inserter(diff), before.value_comp());
216 for(const auto& d : diff) {
217 cout<<'-'<< (d.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.d_content->getZoneRepresentation()<<endl;
218 }
219 diff.clear();
220 set_difference(after.cbegin(), after.cend(), before.cbegin(), before.cend(), back_inserter(diff), before.value_comp());
221 for(const auto& d : diff) {
222 cout<<'+'<< (d.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(d.d_type)<<" "<<d.d_content->getZoneRepresentation()<<endl;
223 }
224 exit(1);
225 }
226
227 // must be "track" then
228
229 /* goal in life:
230 in directory/zone-name we leave files with their name the serial number
231 at startup, retrieve current SOA SERIAL for domain from master server
232
233 compare with what the best is we have in our directory, IXFR from that.
234 Store result in memory, read that best zone in memory, apply deltas, write it out.
235
236 Next up, loop this every REFRESH seconds */
237 dns_random_init("0123456789abcdef");
238
239 DNSName zone(argv[4]);
240 ComboAddress master(argv[2], atoi(argv[3]));
241 string directory(argv[5]);
242 records_t records;
243
244 uint32_t ourSerial = getSerialsFromDir(directory);
245
246 cout<<"Loading zone, our highest available serial is "<< ourSerial<<endl;
247
248 TSIGTriplet tt;
249 if(argc > 6)
250 tt.name=DNSName(toLower(argv[6]));
251 if(argc > 7)
252 tt.algo=DNSName(toLower(argv[7]));
253
254 if(argc > 8) {
255 if(B64Decode(argv[8], tt.secret) < 0) {
256 cerr<<"Could not decode tsig secret!"<<endl;
257 exit(EXIT_FAILURE);
258 }
259 }
260
261 try {
262 if(!ourSerial)
263 throw std::runtime_error("There is no local zone available");
264 string fname=directory+"/"+std::to_string(ourSerial);
265 cout<<"Loading serial number "<<ourSerial<<" from file "<<fname<<endl;
266 loadZoneFromDisk(records, fname, zone);
267 }
268 catch(std::exception& e) {
269 cout<<"Could not load zone from disk: "<<e.what()<<endl;
270 cout<<"Retrieving latest from master "<<master.toStringWithPort()<<endl;
271 ComboAddress local = master.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::");
272 AXFRRetriever axfr(master, zone, tt, &local);
273 unsigned int nrecords=0;
274 Resolver::res_t nop;
275 vector<DNSRecord> chunk;
276 char wheel[]="|/-\\";
277 int count=0;
278 time_t last=0;
279 while(axfr.getChunk(nop, &chunk)) {
280 for(auto& dr : chunk) {
281 if(dr.d_type == QType::TSIG)
282 continue;
283 dr.d_name.makeUsRelative(zone);
284 records.insert(dr);
285 nrecords++;
286 }
287
288 if(last != time(0)) {
289 cout << '\r' << wheel[count % (sizeof(wheel)-1)] << ' ' <<nrecords;
290 count++;
291 cout.flush();
292 last=time(0);
293 }
294 }
295 cout <<"\rDone, got "<<nrecords<<" "<<endl;
296 cout<<"Writing to disk.."<<endl;
297 writeZoneToDisk(records, zone, directory);
298 }
299
300 for(;;) {
301 DNSRecord ourSoa;
302 ourSerial = getSerialFromRecords(records, ourSoa);
303
304 cout<<"Checking for update, our serial number is "<<ourSerial<<".. ";
305 cout.flush();
306 shared_ptr<SOARecordContent> sr;
307 uint32_t serial = getSerialFromMaster(master, zone, sr, tt);
308 if(ourSerial == serial) {
309 cout<<"still up to date, their serial is "<<serial<<", sleeping "<<sr->d_st.refresh<<" seconds"<<endl;
310 sleep(sr->d_st.refresh);
311 continue;
312 }
313
314 cout<<"got new serial: "<<serial<<", initiating IXFR!"<<endl;
315 auto deltas = getIXFRDeltas(master, zone, ourSoa, tt);
316 cout<<"Got "<<deltas.size()<<" deltas, applying.."<<endl;
317
318 for(const auto& delta : deltas) {
319
320 const auto& remove = delta.first;
321 const auto& add = delta.second;
322
323 ourSerial=getSerialFromRecords(records, ourSoa);
324 uint32_t newserial=0;
325 for(const auto& rr : add) {
326 if(rr.d_type == QType::SOA) {
327 newserial=std::dynamic_pointer_cast<SOARecordContent>(rr.d_content)->d_st.serial;
328 }
329 }
330
331 cout<<"This delta ("<<ourSerial<<" - "<<newserial<<") has "<<remove.size()<<" removals, "<<add.size()<<" additions"<<endl;
332 ofstream report(directory +"/delta."+std::to_string(ourSerial)+"-"+std::to_string(newserial));
333 if(remove.empty()) {
334 cout<<"This delta is a whole new zone"<<endl;
335 report<<"- everything, whole new zone update follow"<<endl;
336 records.clear();
337 }
338
339 bool stop=false;
340
341 for(const auto& rr : remove) {
342 report<<'-'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.d_content->getZoneRepresentation()<<endl;
343 auto range = records.equal_range(tie(rr.d_name, rr.d_type, rr.d_class, rr.d_content));
344 if(range.first == range.second) {
345 cout<<endl<<" !! Could not find record "<<rr.d_name<<" to remove!!"<<endl;
346 // stop=true;
347 report.flush();
348 }
349 records.erase(range.first, range.second);
350 }
351
352 for(const auto& rr : add) {
353 report<<'+'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.d_content->getZoneRepresentation()<<endl;
354 records.insert(rr);
355 }
356 if(stop) {
357 cerr<<"Had error condition, stopping.."<<endl;
358 report.flush();
359 exit(1);
360 }
361 }
362 cout<<"Writing zone to disk.. "; cout.flush();
363 writeZoneToDisk(records, zone, directory);
364 cout<<"Done"<<endl;
365 }
366 }
367 catch(PDNSException &e2) {
368 cerr<<"Fatal: "<<e2.reason<<endl;
369 }
370 catch(std::exception &e)
371 {
372 cerr<<"Fatal: "<<e.what()<<endl;
373 }
374 catch(...)
375 {
376 cerr<<"Any other exception"<<endl;
377 }