/***************************************************************************
secondary.cpp - secondary dns
-------------------
begin : wo mei 14 2003
copyright : (C) 2003 by Meilof
email : meilof@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <poslib/poslib.h>
#include <poslib/server/server.h>
#include <poslib/server/configuration.h>
#include "secondary.h"
#include "resolver.h"
#include "updates.h"
#include "masterfile.h"
#include "query.h"
void *secondary_transfer_thread(void *zn);
void start_secondaryzone_update(domainname znroot, SecondaryZone *zn) {
_addr a;
txt_to_addr(&a, "0.0.0.0");
zn->pending_updates.push_front(a);
start_secondaryzone_update_thread(znroot, zn);
}
class SecZoneData { public: domainname znroot; SecondaryZone *zn; };
void start_secondaryzone_update_thread(domainname znroot, SecondaryZone *zn) {
pthread_t tr;
SecZoneData *dat = new SecZoneData();
dat->znroot = znroot;
dat->zn = zn;
posthread_create(&tr, secondary_transfer_thread, dat);
}
SecondaryZone::SecondaryZone() {
update_busy = false;
type = Z_SECONDARY;
}
SecondaryZone::~SecondaryZone() {
}
void SecondaryZone::feed_setting(const char *name, const char *val) {
_addr a;
addrrange rng;
if (strcmpi(name, "reset") == 0) {
masters.clear();
allow_notify_from.clear();
auth_feed_setting("reset", "");
return;
} else if (strcmpi(name, "reload") == 0) {
if (!update_busy) {
update_list_unregister(this);
start_secondaryzone_update(getfqdn(), this);
}
}
if (auth_feed_setting(name, val)) return;
if (strcmpi(name, "masters") == 0) {
txt_to_addr(&a, val);
masters.push_front(a);
} else if (strcmpi(name, "allow_notify_from") == 0) {
txt_to_addrrange(rng.range, val);
allow_notify_from.push_front(rng);
} else {
throw PException(true, "Unknown options %s", name);
}
}
void SecondaryZone::end_setting() {
AuthMemZone::end_setting();
start_secondaryzone_update(getfqdn(), this);
}
/* load bin file */
void load_bin_file(SecondaryZone *zn, domainname &znroot, const char *fname) {
char len[2];
char *buff = NULL;
message_buff mbuf;
int x, y;
FILE *f = try_fopen(fname, "rb");
DnsRR rr;
ZoneDomain *zonedom;
RrSet *set;
if (!f) throw PException(true, "Cannot open %s", fname);
delete zn->rootdomain; zn->rootdomain = new ZoneDomain(NULL, "");
while (!feof(f)) {
try {
if (buff) free(buff); buff = NULL;
y = fread(len, 1, 2, f);
if (y == 0) break;
if (y != 2) throw PException(true, "Cannot read from data file: returned %d", y);
buff = (char *)malloc(uint16_value(len));
if (fread(buff, 1, uint16_value(len), f) < uint16_value(len)) throw PException("Cannot read from data file: unexpected EOF");
mbuf = message_buff(buff, uint16_value(len));
rr.NAME = domainname(mbuf, 0);
if (!(rr.NAME >= znroot)) {
pos_log(context_zonedata, log_error, "Ignoring domain %s outside of %s zone", rr.NAME.tocstr(), znroot.tocstr());
continue;
}
x = dom_comprlen(mbuf, 0);
if (x + 10 > mbuf.len) throw PException("Cannot read from data file: unexpected EOF");
rr.TYPE = uint16_value(mbuf.msg + x);
rr.CLASS = uint16_value(mbuf.msg + x + 2);
rr.TTL = uint32_value(mbuf.msg + x + 4);
rr.RDLENGTH = uint16_value(mbuf.msg + x + 8);
if (x + 10 + rr.RDLENGTH > mbuf.len) throw PException("Cannot read from data file: unexpected EOF");
if (rr.RDATA) free(rr.RDATA);
rr.RDATA = (char *)malloc(rr.RDLENGTH);
memcpy(rr.RDATA, mbuf.msg + x + 10, rr.RDLENGTH);
if (rr.NAME == znroot && rr.TYPE == DNS_TYPE_SOA)
/* set serial for zone */
zn->serial = rr_getlong(rr.RDATA, DNS_TYPE_SOA, 2);
/* put it into the tree */
zonedom = create_domainname(zn->rootdomain, znroot, rr.NAME);
if (zonedom) {
set = zonedom->create_rrset(rr.TYPE);
set->add_rr(rr.TTL, rr.RDLENGTH, rr.RDATA);
} else {
pos_log(context_zonedata, log_error, "%s: Could not create zone node %s: ignored", znroot.tocstr(), rr.NAME.tocstr());
}
} catch (PException p) {
if (buff) free(buff);
throw p;
}
}
zn->zone_is_loaded = true;
}
uint32_t getserial(pos_cliresolver &res, domainname &znroot, int sockid) {
DnsMessage *q = NULL, *a = NULL;
uint32_t serial;
try {
q = create_query(znroot, DNS_TYPE_SOA, false);
res.tcpquery(q, a, sockid);
if (a->RCODE != RCODE_NOERROR || a->answers.size() != 1 ||
a->answers.begin()->NAME != znroot || a->answers.begin()->TYPE != DNS_TYPE_SOA) {
throw PException("incorrect answer");
}
serial = rr_getlong(a->answers.begin()->RDATA, DNS_TYPE_SOA, 2);
} catch (PException p) {
if (q) delete q;
if (a) delete a;
throw PException("Could not retrieve SOA from master: ", p);
}
if (q) delete q;
if (a) delete a;
return serial;
}
bool zone_is_valid(domainname &znroot, Zone *zn) {
domainname znroot2;
Zone *z = lookup_authoritative_zone(znroot, &znroot2, false);
if (!z || znroot != znroot2 || zn != z || zn->type != z->type) {
pthread_mutex_unlock(&m_auth_zones);
return false;
}
return true;
}
void queue_new_update(domainname &znroot, SecondaryZone *zn, bool succeeded, bool is_general) {
RrSet *set;
_addr nil;
postime_t time = getcurtime();
txt_to_addr(&nil, "0.0.0.0");
if (!is_general && !succeeded) {
pthread_mutex_unlock(&m_auth_zones);
return;
}
if (!succeeded && zn->zone_is_loaded && zn->rootdomain &&
(set = zn->rootdomain->get_rrset(DNS_TYPE_SOA)) != NULL &&
set->rrs.count == 1) {
/* check whether we should discard the zone */
uint32_t expire = rr_getlong(set->rrs[0]->rdata, DNS_TYPE_SOA, 5) * 1000,
retry = rr_getlong(set->rrs[0]->rdata, DNS_TYPE_SOA, 4) * 1000;
if (zn->loadtime + expire - retry > getcurtime()) {
/* next update won't come in time */
pos_log(context_server, log_error, "The zone %s has just expired because no masters are reachable!",
znroot.tocstr());
delete zn->rootdomain; zn->rootdomain = NULL;
zn->zone_is_loaded = false;
}
}
if (zn->zone_is_loaded && zn->rootdomain &&
(set = zn->rootdomain->get_rrset(DNS_TYPE_SOA)) != NULL &&
set->rrs.count == 1) {
/* zone loaded */
if (succeeded)
time = time + (rr_getlong(set->rrs[0]->rdata, DNS_TYPE_SOA, 3) * 1000);
else
time = time + (rr_getlong(set->rrs[0]->rdata, DNS_TYPE_SOA, 4) * 1000);
} else {
time = time + 1800000; /* default: try again in half an hour */
}
zn->pending_updates.push_back(nil);
enqueue_update(time, znroot, zn, (update_function)start_secondaryzone_update_thread);
pthread_mutex_unlock(&m_auth_zones);
}
/* rfc1982 serial arithmetic, taken from posadis 0.50.x */
inline int soa_firstsmaller(unsigned int i1, unsigned int i2) {
return (i1 != i2 &&
((i1 < i2 && i2 - i1 < 2147483647) ||
(i1 > i2 && i1 - i2 > 2147483647)));
}
void *secondary_transfer_thread(void *_dat) {
SecZoneData *dat = (SecZoneData *)_dat;
SecondaryZone *zn = dat->zn;
domainname znroot = dat->znroot;
_addr cur;
char *ptr;
stl_slist(_addr) addrlist;
stl_slist(_addr)::iterator it;
bool zn_loaded = false;
uint32_t curserial, newserial;
pos_cliresolver res;
int sockid;
FILE *out;
int ilen, pos, n, t, l;
char *msg = NULL;
_domain retdom = NULL;
char len[2];
char *rdata = NULL;
uint16_t rdlen;
int soa = 0;
DnsMessage *q;
message_buff buff;
char *fname_ptr;
char fname[PATH_MAX];
bool has_generic = false, is_generic, result = true;
delete dat;
bool first = true;
start:
try {
pthread_mutex_lock(&m_auth_zones);
if (first && zn->update_busy) { pthread_mutex_unlock(&m_auth_zones); return NULL; }
if (!zone_is_valid(znroot, zn)) return NULL;
if (!datadir[0]) {
pos_log(context_server, log_error, "Since there is no data directory, secondary zone transfers will fail (%s being an example)!", znroot.tocstr());
pthread_mutex_unlock(&m_auth_zones);
return NULL;
}
first = false;
zn->update_busy = true;
if (zn->pending_updates.size() == 0) {
/* done! */
zn->update_busy = false;
/* schedule new update */
queue_new_update(znroot, zn, result, has_generic);
return NULL;
}
cur = *zn->pending_updates.begin();
zn->pending_updates.pop_front();
ptr = get_ipv4_ptr(&cur);
if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 0) {
/* no particular address */
addrlist = zn->masters;
has_generic = true;
is_generic = true;
} else {
addrlist.push_front(cur);
is_generic = false;
}
if (zn->zone_is_loaded) {
zn_loaded = true;
curserial = zn->serial;
}
pthread_mutex_unlock(&m_auth_zones);
/* allright, make tcp connect */
it = addrlist.begin();
while (true) {
next_server:
if (it == addrlist.end()) {
pos_log(context_server, log_error, "No servers could be reached for %s: delaying zone transfer", znroot.tocstr());
if (is_generic) result = false;
goto start;
break;
}
try {
sockid = res.tcpconnect(&*it);
} catch (PException p) {
pos_log(context_server, log_error, "Could not contact %s for %s zone transfer!",
addr_to_string(&*it).c_str(), znroot.tocstr());
sockid = -1;
it++;
continue;
}
if (!sockid) {
pos_log(context_server, log_error, "No servers could be reached for %s: delaying zone transfer", znroot.tocstr());
if (is_generic) result = false;
goto start;
}
try {
/* do zone transfer (code from getzone.cpp) */
if (zn_loaded) {
/* see for secondary */
newserial = getserial(res, znroot, sockid);
if (!soa_firstsmaller(curserial, newserial)) {
/* no update nessecary */
result = true;
goto start;
}
}
/* we're needing an update here */
out = NULL;
q = create_query(znroot, QTYPE_AXFR);
buff = q->compile(TCP_MSG_SIZE);
delete q;
len[0] = buff.len / 256; len[1] = buff.len;
msg = NULL;
retdom = NULL;
rdata = NULL;
soa = 0;
rdlen = 0;
tcpsendall(sockid, len, 2, conf_get_tcp_io_timeout() / 4);
tcpsendall(sockid, buff.msg, buff.len, conf_get_tcp_io_timeout() / 4);
try {
while (soa != 2) {
/* read message */
if (msg) { free(msg); msg = NULL; }
tcpreadall(sockid, len, 2, conf_get_tcp_io_timeout() / 4);
ilen = len[0] * 256 + len[1];
if (ilen < 12) throw PException("Incomplete answer message");
msg = (char *)malloc(ilen);
tcpreadall(sockid, msg, ilen, conf_get_tcp_io_timeout() / 4);
buff = message_buff(msg, ilen);
/* interpret it */
if ((msg[3] & 15) != RCODE_NOERROR) throw PException(true, "Erroneous answer: %s", str_rcode(msg[3]&15).c_str());
n = msg[6] * 256 + msg[7];
pos = 12;
if (msg[4] * 256 + msg[5] >= 1) {
/* some DNS server is telling is what we already know (yes I'm looking at you BIND9) */
pos += dom_comprlen(buff, pos);
pos += 4;
}
for (t = 0; t < n && pos < ilen; t++) {
/* read this RR */
if (retdom) { free(retdom); retdom = NULL; }
if (rdata) { free(rdata); rdata = NULL; }
retdom = dom_uncompress(buff, pos);
pos += dom_comprlen(buff, pos);
if (pos + 10 > ilen) throw PException(true, "Truncated answer message");
l = buff.msg[pos+8] * 256 + buff.msg[pos+9];
if (pos + 10 + l > ilen) throw PException(true, "Truncated answer message");
if (buff.msg[pos] * 256 + buff.msg[pos+1] == DNS_TYPE_SOA) {
rr_read(buff.msg[pos] * 256 + buff.msg[pos+1], rdata, rdlen, buff, pos + 10, l);
if (soa == 0) {
fname_ptr = build_filename(znroot, rr_getlong(rdata, DNS_TYPE_SOA, 2));
if (!fname_ptr)
throw PException(true, "Cannot determine data file name for secondary zone transfer for %s!", znroot.tocstr());
strcpy(fname, fname_ptr);
if ((out = try_fopen(fname, "wb")) == NULL)
throw PException(true, "Cannot open data file for secondary zone transfer for %s!", znroot.tocstr());
}
soa++;
if (soa == 2) {
/* done! */
break;
}
} else {
if (!out) throw PException("Zone transfer doesn't start with SOA record!");
rr_read(buff.msg[pos] * 256 + buff.msg[pos+1], rdata, rdlen, buff, pos + 10, l);
}
fwrite(uint16_buff(domlen(retdom) + rdlen + 10), 1, 2, out);
fwrite(retdom, 1, domlen(retdom), out);
fwrite(buff.msg + pos, 1, 8, out);
fwrite(uint16_buff(rdlen), 1, 2, out);
fwrite(rdata, 1, rdlen, out);
pos += 10 + l;
}
}
} catch (PException p) {
pos_log(context_server, log_error, "Error in zone transfer from %s for %s: %s!",
addr_to_string(&*it).c_str(), znroot.tocstr(), p.message);
if (msg) free(msg);
if (retdom) free(retdom);
if (rdata) free(rdata);
if (out) fclose(out);
if (is_generic) result = false;
throw p;
}
if (msg) free(msg);
if (retdom) free(retdom);
if (rdata) free(rdata);
fclose(out);
pthread_mutex_lock(&m_auth_zones);
if (!zone_is_valid(znroot, zn)) goto start;
try {
load_bin_file(zn, znroot, fname);
zn->loadtime = getcurtime();
pos_log(context_zonedata, log_info, "secondary %s (re)loaded, serial %d", znroot.tocstr(), zn->serial);
zn->start_notification();
pthread_mutex_unlock(&m_auth_zones);
} catch (PException p) {
delete zn->rootdomain; zn->rootdomain = new ZoneDomain(NULL, "");
zn->zone_is_loaded = false;
if (is_generic) result = false;
pthread_mutex_unlock(&m_auth_zones);
pos_log(context_server, log_error, "Error in retrieved zone %s: %s. Zone discarded.", znroot.tocstr(), p.message);
goto start;
}
result = true;
goto start;
} catch (PException p) { it++; goto next_server; }
}
} catch (PException p) { }
goto start;
}
DnsMessage *answer_notify(pending_query *q) {
bool update = true;
bool is_from_master;
if (q->message->questions.size() != 1 ||
q->message->questions.begin()->QTYPE != DNS_TYPE_SOA)
return new_srvfail(q->message, RCODE_QUERYERR);
domainname dom = q->message->questions.begin()->QNAME;
domainname dom2;
pthread_mutex_lock(&m_auth_zones);
SecondaryZone *z = (SecondaryZone*)lookup_authoritative_zone(dom, &dom2, false);
if (dom != dom2 || z->type != Z_SECONDARY) {
pthread_mutex_unlock(&m_auth_zones);
return new_srvfail(q->message, RCODE_NOTIMP);
}
if (!in_addr_list(z->masters, &q->querier)) {
if (!in_addrrange_list(z->allow_notify_from, &q->querier)) {
pthread_mutex_unlock(&m_auth_zones);
return new_srvfail(q->message, RCODE_REFUSED);
} else {
is_from_master = false;
}
} else {
is_from_master = true;
}
if (q->message->answers.size() &&
q->message->answers.begin()->TYPE == DNS_TYPE_SOA) {
uint32_t ser = rr_getlong(q->message->answers.begin()->RDATA, DNS_TYPE_SOA, 2);
if (z->zone_is_loaded && ser == z->serial) update = false;
}
if (update) {
_addr ad;
if (is_from_master) {
/* use master for zone transfer */
ad = q->querier;
addr_setport(&ad, DNS_PORT);
} else {
/* don't explicitly use this server */
txt_to_addr(&ad, "0.0.0.0");
}
z->pending_updates.push_back(ad);
if (!z->update_busy) start_secondaryzone_update_thread(dom, z);
}
pthread_mutex_unlock(&m_auth_zones);
DnsMessage *a = new DnsMessage();
a->ID = q->message->ID;
a->RCODE = RCODE_NOERROR;
a->OPCODE = OPCODE_NOTIFY;
a->AA = true;
a->questions.push_front(*q->message->questions.begin());
return a;
}
syntax highlighted by Code2HTML, v. 0.9.1