]> granicus.if.org Git - pdns/commitdiff
pipebackend
authorBert Hubert <bert.hubert@netherlabs.nl>
Wed, 27 Nov 2002 15:31:59 +0000 (15:31 +0000)
committerBert Hubert <bert.hubert@netherlabs.nl>
Wed, 27 Nov 2002 15:31:59 +0000 (15:31 +0000)
git-svn-id: svn://svn.powerdns.com/pdns/trunk/pdns@8 d19b8d6e-7fed-0310-83ef-9ca221ded41b

modules/pipebackend/Makefile.am [new file with mode: 0644]
modules/pipebackend/bootstrap [new file with mode: 0755]
modules/pipebackend/configure.in [new file with mode: 0644]
modules/pipebackend/coprocess.cc [new file with mode: 0644]
modules/pipebackend/coprocess.hh [new file with mode: 0644]
modules/pipebackend/pipebackend.cc [new file with mode: 0644]
modules/pipebackend/pipebackend.hh [new file with mode: 0644]

diff --git a/modules/pipebackend/Makefile.am b/modules/pipebackend/Makefile.am
new file mode 100644 (file)
index 0000000..cece71b
--- /dev/null
@@ -0,0 +1,4 @@
+lib_LTLIBRARIES = libpipebackend.la 
+
+libpipebackend_la_SOURCES=pipebackend.cc pipebackend.hh coprocess.cc coprocess.hh
+
diff --git a/modules/pipebackend/bootstrap b/modules/pipebackend/bootstrap
new file mode 100755 (executable)
index 0000000..53600ac
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+aclocal
+autoconf
+autoheader
+automake --add-missing --foreign
+
diff --git a/modules/pipebackend/configure.in b/modules/pipebackend/configure.in
new file mode 100644 (file)
index 0000000..2d63731
--- /dev/null
@@ -0,0 +1,165 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(pipebackend.cc)
+AM_INIT_AUTOMAKE(pdns-pipebackend, 2.7)
+AC_CANONICAL_SYSTEM
+AC_PREFIX_DEFAULT(/opt/pdns)
+AC_PROG_CC
+AC_PROG_CXX
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+AC_DISABLE_STATIC
+AM_PROG_LIBTOOL
+AC_LANG_CPLUSPLUS
+
+dnl Checks for programs.
+
+dnl Checks for libraries.
+dnl Replace `main' with a function in -ldl:
+AC_CHECK_LIB(dl, main)
+AC_CHECK_LIB(z, main)
+dnl Replace `main' with a function in -lpthread:
+AC_CHECK_LIB(pthread, main)
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS(unistd.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+dnl Checks for ahudns functions
+dnl Check for ahudns
+
+search_ahudns=1
+
+AC_ARG_WITH(ahudns-includes, [  --with-ahudns-includes=PATH Specify location of ahudns headers],
+[ if test x"$withval" = x"no"; then
+    search_ahudns=0
+  else
+    #if test x"$withval" != x"yes"; then
+    if test -d "$withval"; then
+      AHUDNS_INCLUDES="-I$withval"
+      search_ahudns=0
+      has_ahudns=1
+    fi
+  fi
+])
+
+
+AC_DEFUN(AC_CHECK_AHUDNS,
+[ if test x"$search_ahudns" != x"0"; then
+    if test -f "$1/$2"; then
+      AC_MSG_RESULT($5)
+      AHUDNS_LIBS="$3"
+      AHUDNS_INCLUDES="$4"
+      search_ahudns=0
+      has_ahudns=1
+    fi
+  fi
+])
+
+AC_DEFUN(AC_SEARCH_AHUDNS,
+[ AC_MSG_CHECKING("location of ahudns logger.hh include")
+  AC_CHECK_AHUDNS($HOME/programming/ahudns, logger.hh,,-I$HOME/programming/ahudns, "found in $HOME/programming/ahudns")
+  AC_CHECK_AHUDNS(../ahudns, logger.hh,,-I../ahudns, "found in ../ahudns")
+  AC_CHECK_AHUDNS(../ahudns-1.2, logger.hh,,-I../ahudns-1.2, "found in ../ahudns-1.2")
+])
+
+if test x"$search_ahudns" != x"0"; then
+  AC_SEARCH_AHUDNS()
+fi
+
+if test x"$has_ahudns" = x"1"; then
+       echo Found ahudns include file, assuming working and compliant ahudns
+else
+       echo
+       echo Did not find ahudns include file
+       echo
+       exit 1 
+fi
+
+CPPFLAGS="$CPPFLAGS $AHUDNS_INCLUDES"
+
+
+dnl Checks for library functions.
+
+dnl Check for STL
+AC_CHECK_HEADER(sstream,dontneedstl=1)
+
+if test "$dontneedstl" != "1" 
+then
+search_stl=1
+
+AC_ARG_WITH(stl-includes, [  --with-stl-includes=PATH Specify location of STL headers],
+[ if test x"$withval" = x"no"; then
+    search_stl=0
+  else
+    #if test x"$withval" != x"yes"; then
+    if test -d "$withval"; then
+      STL_INCLUDES="-I$withval"
+      search_stl=0
+      has_stl=1
+    fi
+  fi
+])
+
+
+
+AC_ARG_WITH(stl-libs, [  --with-stl-libs=PATH    Specify location of STL libs],
+[ if test x"$withval" = x"no"; then
+    search_stl=0
+  else
+    #if test x"$withval" != x"yes"; then
+    if test -d "$withval"; then
+      STL_LIBS="$LIBS -L$withval -lstlport_gcc"
+      search_stl=0
+      has_stl=1
+    fi
+  fi
+])
+
+AC_DEFUN(AC_CHECK_STL,
+[ if test x"$search_stl" != x"0"; then
+    if test -f "$1/$2"; then
+      AC_MSG_RESULT($5)
+      STL_LIBS="$3"
+      STL_INCLUDES="$4"
+      search_stl=0
+      has_stl=1
+    fi
+  fi
+])
+
+AC_DEFUN(AC_SEARCH_STL,
+[ AC_MSG_CHECKING("location of sstream")
+  AC_CHECK_STL(/usr/include, sstream, -lstlport_gcc,, "found in /usr/include")
+  AC_CHECK_STL(/usr/include/stlport, sstream, -lstlport_gcc, -I/usr/include/stlport, "found in /usr/include/stlport")
+  AC_CHECK_STL(/usr/include, sstream, -lstlport_gcc,, "found in /usr/include")
+  AC_CHECK_STL(/usr/local/include, sstream, -L/usr/local/lib -lstlport_gcc, -I/usr/local/include, "found in /usr/local")
+  AC_CHECK_STL(/usr/local/include/stl, sstream, -L/usr/local/lib -L/usr/local/lib/stl -lstlport_gcc, -I/usr/local/include/stl, "found in /usr/local/include/stl")
+  AC_CHECK_STL(/home/ahu/download/STLport-4.0/stlport, sstream,-L/home/ahu/download/STLport-4.0/lib/ -lstlport_gcc, -I/home/ahu/download/STLport-4.0/stlport/, "found in /home/ahu/download/STLport-4.0/stlport")
+  AC_CHECK_STL($HOME/STLport-4.0/stlport, sstream,-L$HOME/STLport-4.0/lib/ -lstlport_gcc, -I$HOME/STLport-4.0/stlport/, "found in $HOME/STLport-4.0/stlport")
+])
+
+if test x"$search_stl" != x"0"; then
+  AC_SEARCH_STL()
+fi
+
+if test x"$has_stl" = x"1"; then
+       echo Found sstream include file, assuming working and compliant STL
+else
+       echo
+       echo Did not find sstream include file, this probably means that your STL is not
+       echo compliant - the default gcc libstdc++ isn\'t. Download STLport-4.0 from
+       echo http://www.stlport.org, or use --with-stl-includes and --with-stl-libs
+       echo to specify the location of your STL.
+       echo
+       exit 1 
+fi
+
+CPPFLAGS="$CPPFLAGS $STL_INCLUDES"
+LIBS="$LIBS $STL_LIBS"
+fi
+
+AM_CONFIG_HEADER(config.h)
+AC_OUTPUT(Makefile)
diff --git a/modules/pipebackend/coprocess.cc b/modules/pipebackend/coprocess.cc
new file mode 100644 (file)
index 0000000..63af41c
--- /dev/null
@@ -0,0 +1,164 @@
+#include "coprocess.hh"
+#include <stdlib.h>
+#include <unistd.h>
+#include <string>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <misc.hh>
+
+CoProcess::CoProcess(const string &command,int timeout, int infd, int outfd)
+{
+  const char *argv[2];
+  argv[0]=strdup(command.c_str());
+  argv[1]=0;
+
+  launch(argv,timeout,infd,outfd);
+}
+
+CoProcess::CoProcess(const char **argv, int timeout, int infd, int outfd)
+{
+  launch(argv,timeout,infd,outfd);
+}
+
+void CoProcess::launch(const char **argv, int timeout, int infd, int outfd)
+{
+  d_timeout=timeout;
+  d_infd=infd;
+  d_outfd=outfd;
+
+  signal(SIGPIPE, SIG_IGN);
+
+  if(access(argv[0],X_OK)) // check before fork so we can throw
+    throw AhuException("Command '"+string(argv[0])+"' cannot be executed: "+stringerror());
+
+  if(pipe(d_fd1)<0 || pipe(d_fd2)<0)
+    throw AhuException("Unable to open pipe for coprocess: "+string(strerror(errno)));
+
+  if((d_pid=fork())<0)
+    throw AhuException("Unable to fork for coprocess: "+stringerror());
+  else if(d_pid>0) { // parent speaking
+    close(d_fd1[0]);
+    close(d_fd2[1]);
+    if(!(d_fp=fdopen(d_fd2[0],"r")))
+      throw AhuException("Unable to associate a file pointer with pipe: "+stringerror());
+    setbuf(d_fp,0); // no buffering please, confuses select
+  }
+  else if(!d_pid) { // child
+    close(d_fd1[1]);
+    close(d_fd2[0]);
+
+    if(d_fd1[0]!= infd) {
+      dup2(d_fd1[0], infd);
+      close(d_fd1[0]);
+    }
+
+    if(d_fd2[1]!= outfd) {
+      dup2(d_fd2[1], outfd);
+      close(d_fd2[1]);
+    }
+
+    // stdin & stdout are now connected, fire up our coprocess!
+
+    if(execv(argv[0], const_cast<char * const *>(argv))<0) // now what
+      exit(123);
+
+    /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
+       no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
+  }
+
+}
+
+void CoProcess::checkStatus()
+{
+  int status;
+  int ret=waitpid(d_pid, &status, WNOHANG);
+  if(ret<0) 
+    throw AhuException("Unable to ascertain status of coprocess "+itoa(d_pid)+" from "+itoa(getpid())+": "+string(strerror(errno)));
+  else if(ret) {
+    if(WIFEXITED(status)) {
+      int ret=WEXITSTATUS(status);
+      throw AhuException("Coprocess exited with code "+itoa(ret));
+    }
+    if(WIFSIGNALED(status)) {
+      int sig=WTERMSIG(status);
+      string reason="CoProcess died on receiving signal "+itoa(sig);
+#ifdef WCOREDUMP
+      if(WCOREDUMP(status)) 
+       reason+=". Dumped core";
+#endif
+      
+      throw AhuException(reason);
+    }
+  }
+}
+
+void CoProcess::send(const string &snd)
+{
+  checkStatus();
+  string line(snd);
+  line.append(1,'\n');
+  
+  int sent=0;
+  int bytes;
+
+  // writen routine - socket may not accept al data in one go
+  while(sent<line.size()) {
+    bytes=write(d_fd1[1],line.c_str()+sent,line.length()-sent);
+    if(bytes<0)
+      throw AhuException("Writing to coprocess failed: "+string(strerror(errno)));
+
+    sent+=bytes;
+  }
+}
+
+void CoProcess::receive(string &receive)
+{
+  char line[1024];
+  memset(line,0,1024);
+  
+  if(d_timeout) {
+    struct timeval tv={tv_sec: d_timeout, tv_usec: 0,};
+    fd_set rds;
+    FD_ZERO(&rds);
+    FD_SET(fileno(d_fp),&rds);
+    int ret=select(fileno(d_fp)+1,&rds,0,0,&tv);
+    if(ret<0)
+      throw AhuException("Error waiting on data from coprocess: "+stringerror());
+    if(!ret)
+      throw AhuException("Timeout waiting for data from coprocess");
+  }
+
+  if(!fgets(line,1023,d_fp))
+    throw AhuException("Child closed pipe");
+  
+  char *p;
+  if((p=strrchr(line,'\n')))
+    *p=0;
+
+  receive=line;
+}
+void CoProcess::sendReceive(const string &snd, string &rcv)
+{
+  checkStatus();
+  send(snd);
+  receive(rcv);
+
+}
+#ifdef TESTDRIVER
+main()
+{
+  try {
+    CoProcess cp("./irc.pl");
+    string reply;
+    cp.sendReceive("www.trilab.com", reply);
+    cout<<"Answered: '"<<reply<<"'"<<endl;
+  }
+  catch(AhuException &ae) {
+    cerr<<ae.reason<<endl;
+  }
+  
+}
+#endif
diff --git a/modules/pipebackend/coprocess.hh b/modules/pipebackend/coprocess.hh
new file mode 100644 (file)
index 0000000..1a8a42c
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef PDNS_COPROCESS_HH
+#define PDNS_COPROCESS_HH
+
+
+#include <ahuexception.hh>
+#include <iostream>
+#include <stdio.h>
+
+using namespace std; 
+
+class CoProcess
+{
+public:
+  CoProcess(const string &command,int timeout=0, int infd=0, int outfd=1);
+  CoProcess(const char **argv, int timeout=0, int infd=0, int outfd=1);
+  void launch(const char **argv, int timeout=0, int infd=0, int outfd=1);
+  void sendReceive(const string &send, string &receive);
+  void receive(string &rcv);
+  void send(const string &send);
+private:
+  void CoProcess::checkStatus();
+  int d_fd1[2], d_fd2[2];
+  int d_pid;
+  int d_infd;
+  int d_outfd;
+  int d_timeout;
+  FILE *d_fp;
+};
+#endif
diff --git a/modules/pipebackend/pipebackend.cc b/modules/pipebackend/pipebackend.cc
new file mode 100644 (file)
index 0000000..781aed3
--- /dev/null
@@ -0,0 +1,238 @@
+// -*- sateh-c -*- 
+// File    : pdnsbackend.cc
+// Version : $Id: pipebackend.cc,v 1.1 2002/11/27 15:31:59 ahu Exp $ 
+//
+
+#include <string>
+#include <map>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sstream>
+#include "coprocess.hh"
+
+using namespace std;
+
+#include <dns.hh>
+#include <dnsbackend.hh>
+#include <dnspacket.hh>
+#include <ueberbackend.hh>
+#include <ahuexception.hh>
+#include <logger.hh>
+#include <arguments.hh>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#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
+}
+
+void CoWrapper::launch()
+{
+   if(d_cp)
+      return;
+
+   d_cp=new CoProcess(d_command, d_timeout); 
+   d_cp->send("HELO\t1");
+   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)
+{
+  setArgPrefix("pipe"+suffix);
+   try {
+      d_coproc=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;
+         // type    qname           qclass  qtype   id      ip-address
+         query<<"Q\t"<<qname<<"\tIN\t"<<qtype.getName()<<"\t"<<zoneId<<"\t"<<(pkt_p ? pkt_p->getRemote() : "0.0.0.0");
+         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(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 
+
+   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]=="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) {
+            L<<Logger::Error<<kBackendId<<" coprocess returned emtpy line in data section for query for "<<d_qname<<endl;
+            throw AhuException("Format error communicating with coprocess in data section");
+            // now what?
+         }
+         r.qname=parts[1];
+         r.qtype=parts[3];
+         r.ttl=atoi(parts[4].c_str());
+         r.domain_id=atoi(parts[5].c_str());
+         r.content=parts[6];
+         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 Loader
+{
+   public:
+      Loader()
+      {
+         BackendMakers().report(new PipeFactory);
+         
+         L<<Logger::Notice<<kBackendId<<" This is the pipebackend version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
+      }  
+};
+
+static Loader loader;
+
diff --git a/modules/pipebackend/pipebackend.hh b/modules/pipebackend/pipebackend.hh
new file mode 100644 (file)
index 0000000..eb4472c
--- /dev/null
@@ -0,0 +1,78 @@
+//
+// File    : pdnsbackend.hh
+// Version : $Id: pipebackend.hh,v 1.1 2002/11/27 15:31:59 ahu Exp $
+//
+
+#ifndef PIPEBACKEND_HH
+#define PIPEBACKEND_HH
+
+#include <string>
+#include <map>
+#include <sys/types.h>
+#include <regex.h>
+
+using namespace std;
+
+/** very small regex wrapper */
+class Regex
+{
+public:
+  /** constructor that accepts the expression to regex */
+  Regex(const string &expr)
+  {
+    if(regcomp(&d_preg, expr.c_str(), REG_ICASE|REG_NOSUB|REG_EXTENDED))
+      throw AhuException("Regular expression did not compile");
+  }
+  ~Regex()
+  {
+    regfree(&d_preg);
+  }
+  /** call this to find out if 'line' matches your expression */
+  bool match(const string &line)
+  {
+    return regexec(&d_preg,line.c_str(),0,0,0)==0;
+  }
+  
+private:
+  regex_t d_preg;
+};
+
+
+/** The CoWrapper class wraps around a coprocess and restarts it if needed.
+    It may also send out pings and expect banners */
+class CoWrapper
+{
+public:
+  CoWrapper(const string &command, int timeout=0);
+  void send(const string &line);
+  void receive(string &line);
+private:
+  CoProcess *d_cp;
+  string d_command;
+  void launch();
+  int d_timeout;
+};
+
+class PipeBackend : public DNSBackend
+{
+public:
+  PipeBackend(const string &suffix="");
+  ~PipeBackend();
+  void lookup(const QType &, const string &qdomain, DNSPacket *p=0, int zoneId=-1);
+  bool list(int domain_id);
+  bool get(DNSResourceRecord &r);
+  
+  static DNSBackend *maker();
+  
+private:
+  CoWrapper *d_coproc;
+  string d_qname;
+  QType d_qtype;
+  Regex* d_regex;
+  string d_regexstr;
+  bool d_disavow;
+};
+
+
+#endif
+