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