From: Bert Hubert Date: Wed, 27 Nov 2002 15:23:16 +0000 (+0000) Subject: pgmysqlbackend added X-Git-Tag: pdns-2.9.1~58 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2ffc06cb20fb65169733124ae8a4655c7c7cc4d2;p=thirdparty%2Fpdns.git pgmysqlbackend added git-svn-id: svn://svn.powerdns.com/pdns/trunk/pdns@6 d19b8d6e-7fed-0310-83ef-9ca221ded41b --- diff --git a/modules/gmysqlbackend/INSTALL b/modules/gmysqlbackend/INSTALL new file mode 100644 index 0000000000..427fff1b23 --- /dev/null +++ b/modules/gmysqlbackend/INSTALL @@ -0,0 +1,5 @@ +Edit the Makefile so it can find the main PowerDNS sources. This is the +first line of the Makefile. + +You may want to change other things so it can find your PostgreSQL and MySQL +sources. diff --git a/modules/gmysqlbackend/Makefile b/modules/gmysqlbackend/Makefile new file mode 100644 index 0000000000..e6792ca5e5 --- /dev/null +++ b/modules/gmysqlbackend/Makefile @@ -0,0 +1,42 @@ +PDNS_HOME = ../../pdns +PDNS_LIBDIR = /usr/lib/powerdns + +MYSQL_INCLUDES = /usr/include/mysql +MYSQL_LIBDIR = ./ +MYSQL_LIBS = -lmysqlclient + +POSTGRES_INCLUDES = /opt/postgresql/include +POSTGRES_LIBDIR = /opt/postgresql-with-3.2/lib +POSTGRES_LIBS = -Wl,-Bstatic -lpq++ -lpq -Wl,-Bdynamic -lssl -lcrypt -lcrypto +VERSION = 2.9 + +all: libgmysqlbackend.so smysql.o + +CPPFLAGS=-I$(PDNS_HOME) -I$(MYSQL_INCLUDES) -I$(POSTGRES_INCLUDES) + +LIBS= -L$(POSTGRES_LIBDIR) -L$(MYSQL_LIBDIR) $(MYSQL_LIBS) $(POSTGRES_LIBS) +DIRNAME=pdns-gmypgsqlbackend-$(VERSION) + +dist: + mkdir $(DIRNAME) + cp COPYING smysql.cc spgsql.cc gmysqlbackend.cc gmysqlbackend.hh smysql.hh ssql.hh spgsql.hh Makefile INSTALL $(DIRNAME) + tar cvzf $(DIRNAME).tar.gz $(DIRNAME) + rm -rf $(DIRNAME) + +deps: + g++ -M -g -c $(CXXFLAGS) $(CPPFLAGS) *.cc > deps + +-include deps + + +libgmysqlbackend.so: gmysqlbackend.o smysql.o spgsql.o + g++ gmysqlbackend.o -Wl,-rpath -Wl,$(POSTGRES_LIBDIR) smysql.o spgsql.o $(LIBS) -Wl,-soname -Wl,libgmysqlbackend.so.0 -shared -o libgmysqlbackend.so + +.cc.o: + g++ -MD -g -c $(CXXFLAGS) $(CPPFLAGS) $< + +clean: + rm -f *.o *.so *~ *.d deps + +install: libgmysqlbackend.so + mv $< $(PDNS_LIBDIR) \ No newline at end of file diff --git a/modules/gmysqlbackend/gmysqlbackend.cc b/modules/gmysqlbackend/gmysqlbackend.cc new file mode 100644 index 0000000000..a1a75b155d --- /dev/null +++ b/modules/gmysqlbackend/gmysqlbackend.cc @@ -0,0 +1,470 @@ +// $Id: gmysqlbackend.cc,v 1.1 2002/11/27 15:23:16 ahu Exp $ +#include +#include + +using namespace std; + +#include "dns.hh" +#include "dnsbackend.hh" +#include "gmysqlbackend.hh" +#include "dnspacket.hh" +#include "ueberbackend.hh" +#include "ahuexception.hh" +#include "logger.hh" +#include "arguments.hh" +#include "smysql.hh" +#include "spgsql.hh" +#include + + +void gMySQLBackend::setNotified(u_int32_t domain_id, u_int32_t serial) +{ + try { + d_db->doQuery("update domains set notified_serial="+itoa(serial)+" where id="+itoa(domain_id)); + } + catch(SSqlException &e) { + throw AhuException("gMySQLBackend unable to refresh domain_id "+itoa(domain_id)+": "+e.txtReason()); + } +} + +void gMySQLBackend::setFresh(u_int32_t domain_id) +{ + try { + d_db->doQuery("update domains set last_check="+itoa(time(0))+" where id="+itoa(domain_id)); + } + catch (SSqlException &e) { + throw AhuException("gMySQLBackend unable to refresh domain_id "+itoa(domain_id)+": "+e.txtReason()); + } +} + +bool gMySQLBackend::isMaster(const string &domain, const string &ip) +{ + try { + d_db->doQuery("select master from domains where name='"+sqlEscape(domain)+"' and type='SLAVE'", d_result); + } + catch (SSqlException &e) { + throw AhuException("gMySQLBackend unable to retrieve list of slave domains: "+e.txtReason()); + } + + if(d_result.empty()) + return 0; + + return !strcmp(ip.c_str(),d_result[0][0].c_str()); +} + +bool gMySQLBackend::getDomainInfo(const string &domain, DomainInfo &di) +{ + /* list all domains that need refreshing for which we are slave, and insert into SlaveDomain: + id,name,master IP,serial */ + + try { + d_db->doQuery("select id,name,master,last_check,notified_serial,type from domains where name='"+sqlEscape(domain)+"'",d_result); + } + catch(SSqlException &e) { + throw AhuException("gMySQLBackend unable to retrieve information about a domain: "+e.txtReason()); + } + + int numanswers=d_result.size(); + if(!numanswers) + return false; + + di.id=atol(d_result[0][0].c_str()); + di.zone=d_result[0][1]; + di.master=d_result[0][2]; + di.last_check=atol(d_result[0][3].c_str()); + di.backend=this; + + string type=d_result[0][4]; + if(type=="SLAVE") + di.kind=DomainInfo::Slave; + else if(type=="MASTER") + di.kind=DomainInfo::Slave; + else + di.kind=DomainInfo::Native; + + return true; +} + +void gMySQLBackend::getUnfreshSlaveInfos(vector *unfreshDomains) +{ + /* list all domains that need refreshing for which we are slave, and insert into SlaveDomain: + id,name,master IP,serial */ + + try { + d_db->doQuery("select id,name,master,last_check,type from domains where type='SLAVE'",d_result); + } + catch (SSqlException &e) { + throw AhuException("gMySQLBackend unable to retrieve list of slave domains: "+e.txtReason()); + } + + vectorallSlaves; + int numanswers=d_result.size(); + for(int n=0;n::iterator i=allSlaves.begin();i!=allSlaves.end();++i) { + SOAData sdata; + sdata.serial=0; + sdata.refresh=0; + getSOA(i->zone,sdata); + if(i->last_check+sdata.refreshserial=sdata.serial; + unfreshDomains->push_back(*i); + } + } +} + +void gMySQLBackend::getUpdatedMasters(vector *updatedDomains) +{ + /* list all domains that need notifications for which we are master, and insert into updatedDomains + id,name,master IP,serial */ + + try { + d_db->doQuery("select id,name,master,last_check,notified_serial,type from domains where type='MASTER'",d_result); + } + catch(SSqlException &e) { + throw AhuException("gMySQLBackend unable to retrieve list of master domains: "+e.txtReason()); + } + + vectorallMasters; + int numanswers=d_result.size(); + for(int n=0;n::iterator i=allMasters.begin();i!=allMasters.end();++i) { + SOAData sdata; + sdata.serial=0; + sdata.refresh=0; + getSOA(i->zone,sdata); + if(i->notified_serial!=sdata.serial) { + i->serial=sdata.serial; + updatedDomains->push_back(*i); + } + } +} + + +string gMySQLBackend::sqlEscape(const string &name) +{ + string a; + + for(string::const_iterator i=name.begin();i!=name.end();++i) + if(*i=='\'' || *i=='\\'){ + a+='\\'; + a+=*i; + } + else + a+=*i; + return a; +} + + +gMySQLBackend::gMySQLBackend(const string &mode, const string &suffix) +{ + setArgPrefix(mode+suffix); + + d_logprefix="["+mode+"Backend"+suffix+"] "; + d_db=0; + try { + if(mode=="gmysql") + d_db=new SMySQL(getArg("dbname"), + getArg("host"), + getArg("socket"), + getArg("user"), + getArg("password")); + else if(mode=="gpgsql2") + d_db=new SPgSQL(getArg("dbname"), + getArg("host"), + getArg("socket"), + getArg("user"), + getArg("password")); + else { + L<setLog(arg().mustDo("query-logging")); + + string lcqname=toLower(qname); + + if(qtype.getCode()!=QType::ANY) { + // qtype qname domain_id + if(domain_id<0) { + if(qname[0]=='%') + format=d_wildCardNoIDQuery; + else + format=d_noWildCardNoIDQuery; + + snprintf(output,1023, format.c_str(),sqlEscape(qtype.getName()).c_str(), sqlEscape(lcqname).c_str()); + } + else { + if(qname[0]!='%') + format=d_noWildCardIDQuery; + else + format=d_wildCardIDQuery; + snprintf(output,1023, format.c_str(),sqlEscape(qtype.getName()).c_str(),sqlEscape(lcqname).c_str(),domain_id); + } + } + else { + // qtype==ANY + // qname domain_id + if(domain_id<0) { + if(qname[0]=='%') + format=d_wildCardANYNoIDQuery; + else + format=d_noWildCardANYNoIDQuery; + + snprintf(output,1023, format.c_str(),sqlEscape(lcqname).c_str()); + } + else { + if(qname[0]!='%') + format=d_noWildCardANYIDQuery; + else + format=d_wildCardANYIDQuery; + snprintf(output,1023, format.c_str(),sqlEscape(lcqname).c_str(),domain_id); + } + } + DLOG(L<< "Query: '" << output << "'"<doQuery(output); + } + catch(SSqlException &e) { + throw AhuException(e.txtReason()); + } + + d_qname=qname; + + d_qtype=qtype; + d_count=0; +} +bool gMySQLBackend::list(int domain_id ) +{ + DLOG(L<<"gMySQLBackend constructing handle for list of domain id'"<doQuery(output); + } + catch(SSqlException &e) { + throw AhuException("gMySQLBackend list query: "+e.txtReason()); + } + + d_qname=""; + d_count=0; + return true; +} + +bool gMySQLBackend::superMasterBackend(const string &ip, const string &domain, const vector&nsset, string *account, DNSBackend **ddb) +{ + // check if we know the ip/ns couple in the database + for(vector::const_iterator i=nsset.begin();i!=nsset.end();++i) { + try { + d_db->doQuery(("select account from supermasters where ip='"+sqlEscape(ip)+"' and nameserver='"+sqlEscape(i->content)+"'"), + d_result); + } + catch (SSqlException &e) { + throw AhuException("gMySQLBackend unable to search for a domain: "+e.txtReason()); + } + + if(!d_result.empty()) { + *account=d_result[0][0]; + *ddb=this; + return true; + } + } + return false; +} + +bool gMySQLBackend::createSlaveDomain(const string &ip, const string &domain, const string &account) +{ + try { + d_db->doQuery(("insert into domains (type,name,master,account) values('SLAVE','"+ + sqlEscape(domain)+"','"+ + sqlEscape(ip)+"','"+sqlEscape(account)+"')")); + } + catch(SSqlException &e) { + throw AhuException("Database error trying to insert new slave '"+domain+"': "+ e.txtReason()); + } + return true; +} + + +bool gMySQLBackend::get(DNSResourceRecord &r) +{ + // L << "gMySQLBackend get() was called for "<getRow(row)) { + r.content=row[0]; + r.ttl=atol(row[1].c_str()); + r.priority=atol(row[2].c_str()); + if(!d_qname.empty()) + r.qname=d_qname; + else + r.qname=row[5]; + r.qtype=row[3]; + + r.domain_id=atoi(row[4].c_str()); + return true; + } + + return false; +} + +bool gMySQLBackend::feedRecord(const DNSResourceRecord &r) +{ + ostringstream os; + + os<<"insert into records (content,ttl,prio,type,domain_id,name) values ('"<< + sqlEscape(r.content)<<"', "<< + r.ttl<<", "<< + r.priority<<", '"<doQuery(os.str()); + } + catch (SSqlException &e) { + throw AhuException(e.txtReason()); + } + +} + +bool gMySQLBackend::startTransaction(const string &domain, int domain_id) +{ + try { + d_db->doQuery("begin"); + d_db->doQuery("delete from records where domain_id="+itoa(domain_id)); + } + catch (SSqlException &e) { + throw AhuException("Database failed to start transaction: "+e.txtReason()); + } + + return true; +} + +bool gMySQLBackend::commitTransaction() +{ + try { + d_db->doQuery("commit"); + } + catch (SSqlException &e) { + throw AhuException("Database failed to commit transaction: "+e.txtReason()); + } + return true; +} + +bool gMySQLBackend::abortTransaction() +{ + try { + d_db->doQuery("rollback"); + } + catch(SSqlException &e) { + throw AhuException("MySQL failed to abort transaction: "+string(e.txtReason())); + } + return true; +} + + +class gMySQLFactory : public BackendFactory +{ +public: + gMySQLFactory(const string &mode) : BackendFactory(mode),d_mode(mode) {} + + void declareArguments(const string &suffix="") + { + declare(suffix,"dbname","Pdns backend database name to connect to","powerdns"); + declare(suffix,"user","Pdns backend user to connect as","powerdns"); + declare(suffix,"host","Pdns backend host to connect to",""); + declare(suffix,"socket","Pdns backend socket to connect to",""); + declare(suffix,"password","Pdns backend password to connect with",""); + + declare(suffix,"basic-query","Basic query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s'"); + declare(suffix,"id-query","Basic with ID query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s' and domain_id=%d"); + declare(suffix,"wildcard-query","Wildcard query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name like '%s'"); + declare(suffix,"wildcard-id-query","Wildcard with ID query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name like '%s' and domain_id='%d'"); + + declare(suffix,"any-query","Any query","select content,ttl,prio,type,domain_id,name from records where name='%s'"); + declare(suffix,"any-id-query","Any with ID query","select content,ttl,prio,type,domain_id,name from records where name='%s' and domain_id=%d"); + declare(suffix,"wildcard-any-query","Wildcard ANY query","select content,ttl,prio,type,domain_id,name from records where name like '%s'"); + declare(suffix,"wildcard-any-id-query","Wildcard ANY with ID query","select content,ttl,prio,type,domain_id,name from records where like '%s' and domain_id='%d'"); + + declare(suffix,"list-query","AXFR query", "select content,ttl,prio,type,domain_id,name from records where domain_id='%d'"); + + } + + DNSBackend *make(const string &suffix="") + { + return new gMySQLBackend(d_mode,suffix); + } +private: + const string d_mode; +}; + + +//! Magic class that is activated when the dynamic library is loaded +class gMySQLLoader +{ +public: + //! This reports us to the main UeberBackend class + gMySQLLoader() + { + BackendMakers().report(new gMySQLFactory("gmysql")); + BackendMakers().report(new gMySQLFactory("gpgsql2")); + L< +#include +#include "smysql.hh" + +using namespace std; + +/** The gMySQLBackend is a DNSBackend that can answer DNS related questions. It looks up data + in PostgreSQL */ +class gMySQLBackend : public DNSBackend +{ +public: + gMySQLBackend(const string &mode, const string &suffix); //!< Makes our connection to the database. Throws an exception if it fails. + ~gMySQLBackend(); + + string sqlEscape(const string &name); + void lookup(const QType &, const string &qdomain, DNSPacket *p=0, int zoneId=-1); + bool list(int domain_id); + bool get(DNSResourceRecord &r); + bool isMaster(const string &domain, const string &ip); + + bool startTransaction(const string &domain, int domain_id=-1); + bool commitTransaction(); + bool abortTransaction(); + bool feedRecord(const DNSResourceRecord &r); + bool createSlaveDomain(const string &ip, const string &domain, const string &account); + bool superMasterBackend(const string &ip, const string &domain, const vector&nsset, string *account, DNSBackend **db); + void setFresh(u_int32_t domain_id); + void getUnfreshSlaveInfos(vector *domains); + void getUpdatedMasters(vector *updatedDomains); + bool getDomainInfo(const string &domain, DomainInfo &di); + void setNotified(u_int32_t domain_id, u_int32_t serial); +private: + string d_qname; + QType d_qtype; + int d_count; + SSql *d_db; + SSql::result_t d_result; + + string d_wildCardNoIDQuery; + string d_noWildCardNoIDQuery; + string d_noWildCardIDQuery; + string d_wildCardIDQuery; + string d_wildCardANYNoIDQuery; + string d_noWildCardANYNoIDQuery; + string d_noWildCardANYIDQuery; + string d_wildCardANYIDQuery; + string d_listQuery; + string d_logprefix; +}; diff --git a/modules/gmysqlbackend/smysql.cc b/modules/gmysqlbackend/smysql.cc new file mode 100644 index 0000000000..e5e9aa7a87 --- /dev/null +++ b/modules/gmysqlbackend/smysql.cc @@ -0,0 +1,126 @@ +/* Copyright 2001 Netherlabs BV, bert.hubert@netherlabs.nl. See LICENSE + for more information. + $Id: smysql.cc,v 1.1 2002/11/27 15:23:16 ahu Exp $ */ +#include "smysql.hh" +#include +#include +#include "logger.hh" +#include "dns.hh" +using namespace std; + +bool SMySQL::s_dolog; + +SMySQL::SMySQL(const string &database, const string &host, const string &msocket, const string &user, + const string &password) +{ + mysql_init(&d_db); + if (!mysql_real_connect(&d_db, host.empty() ? 0 : host.c_str(), + user.empty() ? 0 : user.c_str(), + password.empty() ? 0 : password.c_str(), + database.c_str(), 0, + msocket.empty() ? 0 : msocket.c_str(), + 0)) { + throw sPerrorException("Unable to connect to database"); + } + d_rres=0; +} + +void SMySQL::setLog(bool state) +{ + s_dolog=state; +} + +SMySQL::~SMySQL() +{ + mysql_close(&d_db); +} + +SSqlException SMySQL::sPerrorException(const string &reason) +{ + return SSqlException(reason+string(": ")+mysql_error(&d_db)); +} + +int SMySQL::doQuery(const string &query) +{ + if(d_rres) + throw SSqlException("Attempt to start new MySQL query while old one still in progress"); + + if(s_dolog) + L< +#include "ssql.hh" + +class SMySQL : public SSql +{ +public: + SMySQL(const string &database, const string &host="", + const string &msocket="",const string &user="", + const string &password=""); + + ~SMySQL(); + + SSqlException sPerrorException(const string &reason); + int doQuery(const string &query, result_t &result); + int doQuery(const string &query); + bool getRow(row_t &row); + string escape(const string &str); + void setLog(bool state); +private: + MYSQL d_db; + MYSQL_RES *d_rres; + static bool s_dolog; +}; + +#endif /* SSMYSQL_HH */ diff --git a/modules/gmysqlbackend/spgsql.cc b/modules/gmysqlbackend/spgsql.cc new file mode 100644 index 0000000000..8f39bafa27 --- /dev/null +++ b/modules/gmysqlbackend/spgsql.cc @@ -0,0 +1,105 @@ +/* Copyright 200w Netherlabs BV, bert.hubert@netherlabs.nl. See LICENSE + for more information. + $Id: spgsql.cc,v 1.1 2002/11/27 15:23:16 ahu Exp $ */ +#include "spgsql.hh" +#include +#include +#include "logger.hh" +#include "dns.hh" +using namespace std; + +bool SPgSQL::s_dolog; + +SPgSQL::SPgSQL(const string &database, const string &host, const string &msocket, const string &user, + const string &password) +{ + string connectstr; + + connectstr="dbname="; + connectstr+=database; + connectstr+=" user="; + connectstr+=user; + + if(!host.empty()) + connectstr+=" host="+host; + + if(!password.empty()) + connectstr+=" password="+password; + + d_db=new PgDatabase(connectstr.c_str()); + + // Check to see that the backend connection was successfully made + if (d_db->ConnectionBad() ) { + throw sPerrorException("Unable to connect to database"); + } + +} + +void SPgSQL::setLog(bool state) +{ + s_dolog=state; +} + +SPgSQL::~SPgSQL() +{ + delete d_db; +} + +SSqlException SPgSQL::sPerrorException(const string &reason) +{ + return SSqlException(reason+string(": ")+d_db->ErrorMessage()); +} + +int SPgSQL::doQuery(const string &query) +{ + if(s_dolog) + L<Exec(query.c_str())) { + throw sPerrorException("PostgreSQL failed to execute command"); + } + d_count=0; + return 0; +} + +int SPgSQL::doQuery(const string &query, result_t &result) +{ + result.clear(); + if(s_dolog) + L<ExecTuplesOk(query.c_str())) + throw sPerrorException("gPgSQLBackend failed to execute command that expected results"); + d_count=0; + + row_t row; + while(getRow(row)) + result.push_back(row); + + return result.size(); +} + +bool SPgSQL::getRow(row_t &row) +{ + row.clear(); + + if(d_count>=d_db->Tuples()) + return false; + + for(unsigned int i=0;iFields();i++) + row.push_back(d_db->GetValue(d_count,i) ?: ""); + d_count++; + return true; +} + +string SPgSQL::escape(const string &name) +{ + string a; + + for(string::const_iterator i=name.begin();i!=name.end();++i) { + if(*i=='\'' || *i=='\\') + a+='\\'; + a+=*i; + } + return a; +} diff --git a/modules/gmysqlbackend/spgsql.hh b/modules/gmysqlbackend/spgsql.hh new file mode 100644 index 0000000000..b9d8d17f6f --- /dev/null +++ b/modules/gmysqlbackend/spgsql.hh @@ -0,0 +1,30 @@ +/* Copyright 2001 Netherlabs BV, bert.hubert@netherlabs.nl. See LICENSE + for more information. + $Id: spgsql.hh,v 1.1 2002/11/27 15:23:16 ahu Exp $ */ +#ifndef SPGSQL_HH +#define SPGSQL_HH +#include +#include "ssql.hh" + +class SPgSQL : public SSql +{ +public: + SPgSQL(const string &database, const string &host="", + const string &msocket="",const string &user="", + const string &password=""); + + ~SPgSQL(); + + SSqlException sPerrorException(const string &reason); + int doQuery(const string &query, result_t &result); + int doQuery(const string &query); + bool getRow(row_t &row); + string escape(const string &str); + void setLog(bool state); +private: + PgDatabase *d_db; + int d_count; + static bool s_dolog; +}; + +#endif /* SPGSQL_HH */ diff --git a/modules/gmysqlbackend/ssql.hh b/modules/gmysqlbackend/ssql.hh new file mode 100644 index 0000000000..9837466883 --- /dev/null +++ b/modules/gmysqlbackend/ssql.hh @@ -0,0 +1,42 @@ +/* Copyright 2001 Netherlabs BV, bert.hubert@netherlabs.nl. See LICENSE + for more information. + $Id: ssql.hh,v 1.1 2002/11/27 15:23:16 ahu Exp $ */ +#ifndef SSQL_HH +#define SSQL_HH + +#include +#include +using namespace std; + + +class SSqlException +{ +public: + SSqlException(const string &reason) + { + d_reason=reason; + } + + string txtReason() + { + return d_reason; + } +private: + string d_reason; +}; + +class SSql +{ +public: + typedef vector row_t; + typedef vector result_t; + virtual SSqlException sPerrorException(const string &reason)=0; + virtual int doQuery(const string &query, result_t &result)=0; + virtual int doQuery(const string &query)=0; + virtual bool getRow(row_t &row)=0; + virtual string escape(const string &name)=0; + virtual void setLog(bool state){} + virtual ~SSql(){}; +}; + +#endif /* SSQL_HH */