]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/pipebackend/pipebackend.cc
Logging: have a global g_log
[thirdparty/pdns.git] / modules / pipebackend / pipebackend.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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <string>
26 #include <map>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <sstream>
30 #include "coprocess.hh"
31
32 #include "pdns/namespaces.hh"
33
34 #include "pdns/dns.hh"
35 #include "pdns/dnsbackend.hh"
36 #include "pdns/dnspacket.hh"
37 #include "pdns/pdnsexception.hh"
38 #include "pdns/logger.hh"
39 #include "pdns/arguments.hh"
40 #include <sys/socket.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
43 #include "pipebackend.hh"
44
45 static const char *kBackendId = "[PIPEBackend]";
46
47 CoWrapper::CoWrapper(const string &command, int timeout, int abiVersion)
48 {
49 d_cp=0;
50 d_command=command;
51 d_timeout=timeout;
52 d_abiVersion = abiVersion;
53 launch(); // let exceptions fall through - if initial launch fails, we want to die
54 // I think
55 }
56
57 CoWrapper::~CoWrapper()
58 {
59 if(d_cp)
60 delete d_cp;
61 }
62
63
64 void CoWrapper::launch()
65 {
66 if(d_cp)
67 return;
68
69 if(d_command.empty())
70 throw ArgException("pipe-command is not specified");
71
72 if(isUnixSocket(d_command))
73 d_cp = new UnixRemote(d_command, d_timeout);
74 else
75 d_cp = new CoProcess(d_command, d_timeout);
76
77 d_cp->send("HELO\t"+std::to_string(d_abiVersion));
78 string banner;
79 d_cp->receive(banner);
80 g_log<<Logger::Error<<"Backend launched with banner: "<<banner<<endl;
81 }
82
83 void CoWrapper::send(const string &line)
84 {
85 launch();
86 try {
87 d_cp->send(line);
88 return;
89 }
90 catch(PDNSException &ae) {
91 delete d_cp;
92 d_cp=0;
93 throw;
94 }
95 }
96 void CoWrapper::receive(string &line)
97 {
98 launch();
99 try {
100 d_cp->receive(line);
101 return;
102 }
103 catch(PDNSException &ae) {
104 g_log<<Logger::Warning<<kBackendId<<" Unable to receive data from coprocess. "<<ae.reason<<endl;
105 delete d_cp;
106 d_cp=0;
107 throw;
108 }
109 }
110
111 PipeBackend::PipeBackend(const string &suffix)
112 {
113 d_disavow=false;
114 d_regex=nullptr;
115 signal(SIGCHLD, SIG_IGN);
116 setArgPrefix("pipe"+suffix);
117 try {
118 launch();
119 }
120 catch(const ArgException &A) {
121 g_log<<Logger::Error<<kBackendId<<" Unable to launch, fatal argument error: "<<A.reason<<endl;
122 throw;
123 }
124 catch(...) {
125 throw;
126 }
127 }
128
129 void PipeBackend::launch()
130 {
131 if(d_coproc)
132 return;
133
134 try {
135 d_regex=getArg("regex").empty() ? 0 : new Regex(getArg("regex"));
136 d_regexstr=getArg("regex");
137 d_abiVersion = getArgAsNum("abi-version");
138 d_coproc=unique_ptr<CoWrapper> (new CoWrapper(getArg("command"), getArgAsNum("timeout"), getArgAsNum("abi-version")));
139 }
140
141 catch(const ArgException &A) {
142 cleanup();
143 throw;
144 }
145 }
146
147 /*
148 * Cleans up the co-process wrapper
149 */
150 void PipeBackend::cleanup()
151 {
152 d_coproc.reset(0);
153 delete d_regex;
154 d_regexstr = string();
155 d_abiVersion = 0;
156 }
157
158 void PipeBackend::lookup(const QType& qtype,const DNSName& qname, DNSPacket *pkt_p, int zoneId)
159 {
160 try {
161 launch();
162 d_disavow=false;
163 if(d_regex && !d_regex->match(qname.toStringRootDot())) {
164 if(::arg().mustDo("query-logging"))
165 g_log<<Logger::Error<<"Query for '"<<qname<<"' failed regex '"<<d_regexstr<<"'"<<endl;
166 d_disavow=true; // don't pass to backend
167 } else {
168 ostringstream query;
169 string localIP="0.0.0.0";
170 string remoteIP="0.0.0.0";
171 Netmask realRemote("0.0.0.0/0");
172 if (pkt_p) {
173 localIP=pkt_p->getLocal().toString();
174 realRemote = pkt_p->getRealRemote();
175 remoteIP = pkt_p->getRemote().toString();
176 }
177 // abi-version = 1
178 // type qname qclass qtype id remote-ip-address
179 query<<"Q\t"<<qname.toStringRootDot()<<"\tIN\t"<<qtype.getName()<<"\t"<<zoneId<<"\t"<<remoteIP;
180
181 // add the local-ip-address if abi-version is set to 2
182 if (d_abiVersion >= 2)
183 query<<"\t"<<localIP;
184 if(d_abiVersion >= 3)
185 query <<"\t"<<realRemote.toString();
186
187 if(::arg().mustDo("query-logging"))
188 g_log<<Logger::Error<<"Query: '"<<query.str()<<"'"<<endl;
189 d_coproc->send(query.str());
190 }
191 }
192 catch(PDNSException &pe) {
193 g_log<<Logger::Error<<kBackendId<<" Error from coprocess: "<<pe.reason<<endl;
194 d_disavow = true;
195 }
196 d_qtype=qtype;
197 d_qname=qname;
198 }
199
200 bool PipeBackend::list(const DNSName& target, int inZoneId, bool include_disabled)
201 {
202 try {
203 launch();
204 d_disavow=false;
205 ostringstream query;
206 // The question format:
207
208 // type qname qclass qtype id ip-address
209 if (d_abiVersion >= 4)
210 query<<"AXFR\t"<<inZoneId<<"\t"<<target.toStringRootDot();
211 else
212 query<<"AXFR\t"<<inZoneId;
213
214 d_coproc->send(query.str());
215 }
216 catch(PDNSException &ae) {
217 g_log<<Logger::Error<<kBackendId<<" Error from coprocess: "<<ae.reason<<endl;
218 }
219 d_qname=DNSName(itoa(inZoneId)); // why do we store a number here??
220 return true;
221 }
222
223 string PipeBackend::directBackendCmd(const string &query) {
224 if (d_abiVersion < 5)
225 return "not supported on ABI version " + std::to_string(d_abiVersion) + " (use ABI version 5 or later)\n";
226
227 try {
228 launch();
229 ostringstream oss;
230 oss<<"CMD\t"<<query;
231 d_coproc->send(oss.str());
232 }
233 catch(PDNSException &ae) {
234 g_log<<Logger::Error<<kBackendId<<" Error from coprocess: "<<ae.reason<<endl;
235 cleanup();
236 }
237
238 ostringstream oss;
239 while(true) {
240 string line;
241 d_coproc->receive(line);
242 if (line == "END") break;
243 oss << line << std::endl;
244 };
245
246 return oss.str();
247 }
248
249 //! For the dynamic loader
250 DNSBackend *PipeBackend::maker()
251 {
252 try {
253 return new PipeBackend();
254 }
255 catch(...) {
256 g_log<<Logger::Error<<kBackendId<<" Unable to instantiate a pipebackend!"<<endl;
257 return 0;
258 }
259 }
260
261 PipeBackend::~PipeBackend()
262 {
263 cleanup();
264 }
265
266 bool PipeBackend::get(DNSResourceRecord &r)
267 {
268 if(d_disavow) // this query has been blocked
269 return false;
270
271 string line;
272
273 // The answer format:
274 // DATA qname qclass qtype ttl id content
275 unsigned int extraFields = 0;
276 if(d_abiVersion >= 3)
277 extraFields = 2;
278
279 try{
280 launch();
281 for(;;) {
282 d_coproc->receive(line);
283 vector<string>parts;
284 stringtok(parts,line,"\t");
285 if(parts.empty()) {
286 g_log<<Logger::Error<<kBackendId<<" Coprocess returned empty line in query for "<<d_qname<<endl;
287 throw PDNSException("Format error communicating with coprocess");
288 }
289 else if(parts[0]=="FAIL") {
290 throw DBException("coprocess returned a FAIL");
291 }
292 else if(parts[0]=="END") {
293 return false;
294 }
295 else if(parts[0]=="LOG") {
296 g_log<<Logger::Error<<"Coprocess: "<<line.substr(4)<<endl;
297 continue;
298 }
299 else if(parts[0]=="DATA") { // yay
300 if(parts.size() < 7 + extraFields) {
301 g_log<<Logger::Error<<kBackendId<<" Coprocess returned incomplete or empty line in data section for query for "<<d_qname<<endl;
302 throw PDNSException("Format error communicating with coprocess in data section");
303 // now what?
304 }
305
306 if(d_abiVersion >= 3) {
307 r.scopeMask = std::stoi(parts[1]);
308 r.auth = (parts[2] == "1");
309 } else {
310 r.scopeMask = 0;
311 r.auth = 1;
312 }
313 r.qname=DNSName(parts[1+extraFields]);
314 r.qtype=parts[3+extraFields];
315 r.ttl=pdns_stou(parts[4+extraFields]);
316 r.domain_id=std::stoi(parts[5+extraFields]);
317
318 if(r.qtype.getCode() != QType::MX && r.qtype.getCode() != QType::SRV) {
319 r.content.clear();
320 for(unsigned int n= 6 + extraFields; n < parts.size(); ++n) {
321 if(n!=6+extraFields)
322 r.content.append(1,' ');
323 r.content.append(parts[n]);
324 }
325 }
326 else {
327 if(parts.size()< 8 + extraFields) {
328 g_log<<Logger::Error<<kBackendId<<" Coprocess returned incomplete MX/SRV line in data section for query for "<<d_qname<<endl;
329 throw PDNSException("Format error communicating with coprocess in data section of MX/SRV record");
330 }
331
332 r.content=parts[6+extraFields]+" "+parts[7+extraFields];
333 }
334 break;
335 }
336 else
337 throw PDNSException("Coprocess backend sent incorrect response '"+line+"'");
338 }
339 }
340 catch (DBException &dbe) {
341 g_log<<Logger::Error<<kBackendId<<" "<<dbe.reason<<endl;
342 throw;
343 }
344 catch (PDNSException &pe) {
345 g_log<<Logger::Error<<kBackendId<<" "<<pe.reason<<endl;
346 cleanup();
347 throw;
348 }
349 return true;
350 }
351
352 //
353 // Magic class that is activated when the dynamic library is loaded
354 //
355
356 class PipeFactory : public BackendFactory
357 {
358 public:
359 PipeFactory() : BackendFactory("pipe") {}
360
361 void declareArguments(const string &suffix="")
362 {
363 declare(suffix,"command","Command to execute for piping questions to","");
364 declare(suffix,"timeout","Number of milliseconds to wait for an answer","2000");
365 declare(suffix,"regex","Regular expression of queries to pass to coprocess","");
366 declare(suffix,"abi-version","Version of the pipe backend ABI","1");
367 }
368
369 DNSBackend *make(const string &suffix="")
370 {
371 return new PipeBackend(suffix);
372 }
373 };
374
375 class PipeLoader
376 {
377 public:
378 PipeLoader()
379 {
380 BackendMakers().report(new PipeFactory);
381 g_log << Logger::Info << kBackendId <<" This is the pipe backend version " VERSION
382 #ifndef REPRODUCIBLE
383 << " (" __DATE__ " " __TIME__ ")"
384 #endif
385 << " reporting" << endl;
386 }
387 };
388
389 static PipeLoader pipeloader;
390