]> git.ipfire.org Git - thirdparty/pdns.git/blame - modules/pipebackend/pipebackend.cc
Include config.h only in .cc files
[thirdparty/pdns.git] / modules / pipebackend / pipebackend.cc
CommitLineData
8bd743a8
BH
1// -*- sateh-c -*-
2// File : pdnsbackend.cc
35933370 3// Version : $Id$
8bd743a8
BH
4//
5
870a0fe4
AT
6#ifdef HAVE_CONFIG_H
7#include "config.h"
8#endif
8bd743a8
BH
9#include <string>
10#include <map>
11#include <unistd.h>
12#include <stdlib.h>
13#include <sstream>
14#include "coprocess.hh"
15
10f4eea8 16#include "pdns/namespaces.hh"
8bd743a8 17
21e99384
RK
18#include "pdns/dns.hh"
19#include "pdns/dnsbackend.hh"
20#include "pdns/dnspacket.hh"
21#include "pdns/ueberbackend.hh"
22#include "pdns/pdnsexception.hh"
23#include "pdns/logger.hh"
24#include "pdns/arguments.hh"
8bd743a8
BH
25#include <sys/socket.h>
26#include <netinet/in.h>
27#include <arpa/inet.h>
8348e9fd 28#include <boost/lexical_cast.hpp>
8bd743a8
BH
29#include "pipebackend.hh"
30
31static const char *kBackendId = "[PIPEBackend]";
32
33CoWrapper::CoWrapper(const string &command, int timeout)
34{
35 d_cp=0;
36 d_command=command;
37 d_timeout=timeout;
4b16334f 38 d_abiVersion = ::arg().asNum("pipebackend-abi-version");
8bd743a8
BH
39 launch(); // let exceptions fall through - if initial launch fails, we want to die
40 // I think
41}
42
f7a23c1c
BH
43CoWrapper::~CoWrapper()
44{
45 if(d_cp)
46 delete d_cp;
47}
48
49
8bd743a8
BH
50void CoWrapper::launch()
51{
52 if(d_cp)
53 return;
54
2f2b0145
BH
55 if(isUnixSocket(d_command))
56 d_cp = new UnixRemote(d_command, d_timeout);
57 else
58 d_cp = new CoProcess(d_command, d_timeout);
4b16334f 59 d_cp->send("HELO\t"+lexical_cast<string>(d_abiVersion));
8bd743a8
BH
60 string banner;
61 d_cp->receive(banner);
62 L<<Logger::Error<<"Backend launched with banner: "<<banner<<endl;
63}
64
65void CoWrapper::send(const string &line)
66{
67 launch();
68 try {
69 d_cp->send(line);
70 return;
71 }
3f81d239 72 catch(PDNSException &ae) {
8bd743a8
BH
73 delete d_cp;
74 d_cp=0;
75 throw;
76 }
77}
78void CoWrapper::receive(string &line)
79{
80 launch();
81 try {
82 d_cp->receive(line);
83 return;
84 }
3f81d239 85 catch(PDNSException &ae) {
c057bfaa 86 L<<Logger::Warning<<kBackendId<<" Unable to receive data from coprocess. "<<ae.reason<<endl;
8bd743a8
BH
87 delete d_cp;
88 d_cp=0;
89 throw;
90 }
91}
92
93PipeBackend::PipeBackend(const string &suffix)
94{
87da3242 95 signal(SIGCHLD, SIG_IGN);
35933370 96 setArgPrefix("pipe"+suffix);
8bd743a8 97 try {
f7a23c1c
BH
98 d_coproc=shared_ptr<CoWrapper>(new CoWrapper(getArg("command"), getArgAsNum("timeout")));
99 d_regex=getArg("regex").empty() ? 0 : new Regex(getArg("regex"));
100 d_regexstr=getArg("regex");
4b16334f 101 d_abiVersion = ::arg().asNum("pipebackend-abi-version");
8bd743a8
BH
102 }
103 catch(const ArgException &A) {
104 L<<Logger::Error<<kBackendId<<" Fatal argument error: "<<A.reason<<endl;
105 throw;
106 }
107 catch(...) {
108 throw;
109 }
110}
111
112void PipeBackend::lookup(const QType &qtype,const string &qname, DNSPacket *pkt_p, int zoneId)
113{
114 try {
115 d_disavow=false;
116 if(d_regex && !d_regex->match(qname+";"+qtype.getName())) {
0579f0db 117 if(::arg().mustDo("query-logging"))
8bd743a8
BH
118 L<<Logger::Error<<"Query for '"<<qname<<"' type '"<<qtype.getName()<<"' failed regex '"<<d_regexstr<<"'"<<endl;
119 d_disavow=true; // don't pass to backend
120 } else {
121 ostringstream query;
35933370
BH
122 string localIP="0.0.0.0";
123 string remoteIP="0.0.0.0";
19a39147 124 Netmask realRemote("0.0.0.0/0");
35933370
BH
125 if (pkt_p) {
126 localIP=pkt_p->getLocal();
f674c49e
BH
127 realRemote = pkt_p->getRealRemote();
128 remoteIP = pkt_p->getRemote();
35933370 129 }
35933370
BH
130 // pipebackend-abi-version = 1
131 // type qname qclass qtype id remote-ip-address
132 query<<"Q\t"<<qname<<"\tIN\t"<<qtype.getName()<<"\t"<<zoneId<<"\t"<<remoteIP;
133
134 // add the local-ip-address if pipebackend-abi-version is set to 2
4b16334f 135 if (d_abiVersion >= 2)
35933370 136 query<<"\t"<<localIP;
4b16334f 137 if(d_abiVersion >= 3)
f674c49e 138 query <<"\t"<<realRemote.toString();
35933370 139
0579f0db 140 if(::arg().mustDo("query-logging"))
8bd743a8
BH
141 L<<Logger::Error<<"Query: '"<<query.str()<<"'"<<endl;
142 d_coproc->send(query.str());
143 }
144 }
3f81d239 145 catch(PDNSException &ae) {
8bd743a8
BH
146 L<<Logger::Error<<kBackendId<<" Error from coprocess: "<<ae.reason<<endl;
147 throw; // hop
148 }
149 d_qtype=qtype;
150 d_qname=qname;
151}
152
cea26350 153bool PipeBackend::list(const string &target, int inZoneId, bool include_disabled)
8bd743a8
BH
154{
155 try {
156 d_disavow=false;
157 ostringstream query;
158// The question format:
159
160// type qname qclass qtype id ip-address
4b16334f 161 if (d_abiVersion >= 4)
4b25ef35
AT
162 query<<"AXFR\t"<<inZoneId<<"\t"<<target;
163 else
4b16334f 164 query<<"AXFR\t"<<inZoneId;
8bd743a8
BH
165
166 d_coproc->send(query.str());
167 }
3f81d239 168 catch(PDNSException &ae) {
8bd743a8
BH
169 L<<Logger::Error<<kBackendId<<" Error from coprocess: "<<ae.reason<<endl;
170 throw;
171 }
172 d_qname=itoa(inZoneId);
173 return true;
174}
175
176//! For the dynamic loader
177DNSBackend *PipeBackend::maker()
178{
179 try {
180 return new PipeBackend();
181 }
182 catch(...) {
183 L<<Logger::Error<<kBackendId<<" Unable to instantiate a pipebackend!"<<endl;
184 return 0;
185 }
186}
187
188PipeBackend::~PipeBackend()
189{
190 delete d_regex;
191}
192
193bool PipeBackend::get(DNSResourceRecord &r)
194{
195 if(d_disavow) // this query has been blocked
196 return false;
197
198 string line;
199
200 // The answer format:
201 // DATA qname qclass qtype ttl id content
42f1cf3e 202 unsigned int extraFields = 0;
d29e264e 203 if(d_abiVersion >= 3)
42f1cf3e 204 extraFields = 2;
f674c49e 205
8bd743a8
BH
206 for(;;) {
207 d_coproc->receive(line);
208 vector<string>parts;
209 stringtok(parts,line,"\t");
210 if(parts.empty()) {
c057bfaa 211 L<<Logger::Error<<kBackendId<<" Coprocess returned empty line in query for "<<d_qname<<endl;
3f81d239 212 throw PDNSException("Format error communicating with coprocess");
8bd743a8 213 }
668056a5
BH
214 else if(parts[0]=="FAIL") {
215 throw DBException("coprocess returned a FAIL");
216 }
8bd743a8
BH
217 else if(parts[0]=="END") {
218 return false;
219 }
220 else if(parts[0]=="LOG") {
221 L<<Logger::Error<<"Coprocess: "<<line.substr(4)<<endl;
222 continue;
223 }
224 else if(parts[0]=="DATA") { // yay
42f1cf3e 225 if(parts.size() < 7 + extraFields) {
c057bfaa 226 L<<Logger::Error<<kBackendId<<" Coprocess returned incomplete or empty line in data section for query for "<<d_qname<<endl;
3f81d239 227 throw PDNSException("Format error communicating with coprocess in data section");
8bd743a8
BH
228 // now what?
229 }
f674c49e 230
d29e264e 231 if(d_abiVersion >= 3) {
f674c49e 232 r.scopeMask = atoi(parts[1].c_str());
42f1cf3e
BH
233 r.auth = atoi(parts[2].c_str());
234 } else {
235 r.scopeMask = 0;
236 r.auth = 1;
237 }
238 r.qname=parts[1+extraFields];
239 r.qtype=parts[3+extraFields];
240 r.ttl=atoi(parts[4+extraFields].c_str());
241 r.domain_id=atoi(parts[5+extraFields].c_str());
242
f674c49e 243 if(r.qtype.getCode() != QType::MX && r.qtype.getCode() != QType::SRV) {
e0ec6e79 244 r.content.clear();
42f1cf3e 245 for(unsigned int n= 6 + extraFields; n < parts.size(); ++n) {
64ff52f3 246 if(n!=6+extraFields)
e0ec6e79
BH
247 r.content.append(1,' ');
248 r.content.append(parts[n]);
249 }
250 }
251 else {
42f1cf3e 252 if(parts.size()< 8 + extraFields) {
c057bfaa 253 L<<Logger::Error<<kBackendId<<" Coprocess returned incomplete MX/SRV line in data section for query for "<<d_qname<<endl;
3f81d239 254 throw PDNSException("Format error communicating with coprocess in data section of MX/SRV record");
e0ec6e79
BH
255 }
256
b9bafae0 257 r.content=parts[6+extraFields]+" "+parts[7+extraFields];
e0ec6e79 258 }
8bd743a8
BH
259 break;
260 }
261 else
3f81d239 262 throw PDNSException("Coprocess backend sent incorrect response '"+line+"'");
8bd743a8
BH
263 }
264 return true;
265}
266
267//
268// Magic class that is activated when the dynamic library is loaded
269//
270
271class PipeFactory : public BackendFactory
272{
273 public:
274 PipeFactory() : BackendFactory("pipe") {}
275
276 void declareArguments(const string &suffix="")
277 {
278 declare(suffix,"command","Command to execute for piping questions to","");
ecc96bb7 279 declare(suffix,"timeout","Number of milliseconds to wait for an answer","2000");
8bd743a8
BH
280 declare(suffix,"regex","Regular exception of queries to pass to coprocess","");
281 }
282
283 DNSBackend *make(const string &suffix="")
284 {
285 return new PipeBackend(suffix);
286 }
287};
288
1258abe0 289class PipeLoader
8bd743a8
BH
290{
291 public:
1258abe0 292 PipeLoader()
8bd743a8
BH
293 {
294 BackendMakers().report(new PipeFactory);
40428724 295 L << Logger::Info << kBackendId <<" This is the pipe backend version " VERSION " (" __DATE__ ", " __TIME__ ") reporting" << endl;
8bd743a8
BH
296 }
297};
298
1258abe0 299static PipeLoader pipeloader;
8bd743a8 300