]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfrutils.cc
rec: mention rust compiler in compiling docs
[thirdparty/pdns.git] / pdns / ixfrutils.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 <cinttypes>
24 #include <dirent.h>
25 #include <cerrno>
26 #include <sys/stat.h>
27
28 #include "ixfrutils.hh"
29 #include "sstuff.hh"
30 #include "dnssecinfra.hh"
31 #include "zoneparser-tng.hh"
32 #include "dnsparser.hh"
33
34 uint32_t getSerialFromPrimary(const ComboAddress& primary, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt, const uint16_t timeout)
35 {
36 vector<uint8_t> packet;
37 DNSPacketWriter pw(packet, zone, QType::SOA);
38 if(!tt.algo.empty()) {
39 TSIGRecordContent trc;
40 trc.d_algoName = tt.algo;
41 trc.d_time = time(nullptr);
42 trc.d_fudge = 300;
43 trc.d_origID=ntohs(pw.getHeader()->id);
44 trc.d_eRcode=0;
45 addTSIG(pw, trc, tt.name, tt.secret, "", false);
46 }
47
48 Socket s(primary.sin4.sin_family, SOCK_DGRAM);
49 s.connect(primary);
50 string msg((const char*)&packet[0], packet.size());
51 s.writen(msg);
52
53 string reply;
54 reply.resize(4096);
55 // will throw a NetworkError on timeout
56 ssize_t got = s.readWithTimeout(&reply[0], reply.size(), timeout);
57 if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader)) {
58 throw std::runtime_error("Invalid response size " + std::to_string(got));
59 }
60
61 reply.resize(got);
62
63 MOADNSParser mdp(false, reply);
64 if(mdp.d_header.rcode) {
65 throw std::runtime_error("RCODE from response is not NoError but " + RCode::to_s(mdp.d_header.rcode));
66 }
67 for(const auto& r: mdp.d_answers) {
68 if(r.first.d_type == QType::SOA) {
69 sr = getRR<SOARecordContent>(r.first);
70 if(sr != nullptr) {
71 return sr->d_st.serial;
72 }
73 }
74 }
75 return 0;
76 }
77
78 uint32_t getSerialFromDir(const std::string& dir)
79 {
80 uint32_t ret = 0;
81 auto directoryError = pdns::visit_directory(dir, [&ret]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
82 try {
83 auto version = pdns::checked_stoi<uint32_t>(std::string(name));
84 if (std::to_string(version) == name) {
85 ret = std::max(version, ret);
86 }
87 }
88 catch (...) {
89 }
90 return true;
91 });
92
93 if (directoryError) {
94 throw runtime_error("Could not open IXFR directory '" + dir + "': " + *directoryError);
95 }
96
97 return ret;
98 }
99
100 uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret)
101 {
102 uint16_t t=QType::SOA;
103
104 auto found = records.equal_range(std::tie(g_rootdnsname, t));
105
106 for(auto iter = found.first; iter != found.second; ++iter) {
107 auto soa = getRR<SOARecordContent>(*iter);
108 if (soa) {
109 soaret = *iter;
110 return soa->d_st.serial;
111 }
112 }
113 return 0;
114 }
115
116 static void writeRecords(FILE* fp, const records_t& records)
117 {
118 for(const auto& r: records) {
119 if(fprintf(fp, "%s\t%" PRIu32 "\tIN\t%s\t%s\n",
120 r.d_name.isRoot() ? "@" : r.d_name.toStringNoDot().c_str(),
121 r.d_ttl,
122 DNSRecordContent::NumberToType(r.d_type).c_str(),
123 r.getContent()->getZoneRepresentation().c_str()) < 0) {
124 throw runtime_error(stringerror());
125 }
126 }
127 }
128
129 void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::string& directory)
130 {
131 DNSRecord soa;
132 auto serial = getSerialFromRecords(records, soa);
133 string fname = directory + "/" + std::to_string(serial);
134 /* ensure that the partial zone file will only be accessible by the current user, not even
135 by other users in the same group, and certainly not by other users. */
136 umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
137 auto filePtr = pdns::UniqueFilePtr(fopen((fname+".partial").c_str(), "w"));
138 if (!filePtr) {
139 throw runtime_error("Unable to open file '"+fname+".partial' for writing: "+stringerror());
140 }
141
142 records_t soarecord;
143 soarecord.insert(soa);
144 if (fprintf(filePtr.get(), "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
145 string error = "Error writing to zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
146 filePtr.reset();
147 unlink((fname+".partial").c_str());
148 throw std::runtime_error(error);
149 }
150
151 try {
152 writeRecords(filePtr.get(), soarecord);
153 writeRecords(filePtr.get(), records);
154 writeRecords(filePtr.get(), soarecord);
155 } catch (runtime_error &e) {
156 filePtr.reset();
157 unlink((fname+".partial").c_str());
158 throw runtime_error("Error closing zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + e.what());
159 }
160
161 if (fclose(filePtr.release()) != 0) {
162 string error = "Error closing zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
163 unlink((fname+".partial").c_str());
164 throw std::runtime_error(error);
165 }
166
167 if (rename( (fname+".partial").c_str(), fname.c_str()) != 0) {
168 throw std::runtime_error("Unable to move the zone file for " + zone.toLogString() + " from " + fname + ".partial to " + fname + ": " + stringerror());
169 }
170 }
171
172 void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zone)
173 {
174 ZoneParserTNG zpt(fname, zone);
175
176 zpt.disableGenerate();
177 DNSResourceRecord rr;
178 bool seenSOA=false;
179 while(zpt.get(rr)) {
180 if(rr.qtype.getCode() == QType::CNAME && rr.content.empty())
181 rr.content=".";
182 rr.qname = rr.qname.makeRelative(zone);
183
184 if(rr.qtype.getCode() != QType::SOA || seenSOA==false)
185 records.insert(DNSRecord(rr));
186 if(rr.qtype.getCode() == QType::SOA) {
187 seenSOA=true;
188 }
189 }
190 if(!(rr.qtype.getCode() == QType::SOA && seenSOA)) {
191 records.clear();
192 throw runtime_error("Zone not complete!");
193 }
194 }
195
196 /*
197 * Load the zone `zone` from `fname` and put the first found SOA into `soa`
198 * Does NOT check for nullptr
199 */
200 void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr<const SOARecordContent>& soa, uint32_t& soaTTL)
201 {
202 ZoneParserTNG zpt(fname, zone);
203 zpt.disableGenerate();
204 DNSResourceRecord rr;
205
206 while(zpt.get(rr)) {
207 if (rr.qtype == QType::SOA) {
208 soa = getRR<SOARecordContent>(DNSRecord(rr));
209 soaTTL = rr.ttl;
210 return;
211 }
212 }
213 }