diff --git modules/bindbackend/bindbackend2.cc modules/bindbackend/bindbackend2.cc index df7c5c3aa..39364cec2 100644 --- modules/bindbackend/bindbackend2.cc +++ modules/bindbackend/bindbackend2.cc @@ -329,27 +329,56 @@ static void stripDomainSuffix(string* qname, const ZoneName& zonename) auto prefix = qname->size() - domain.size(); qname->resize(prefix - 1); // also strip dot } } +// Perform adequate escaping of characters which have special meaning in +// Bind zone files. +// Note that the input is supposed to be a DNSName::toString() - or any of +// its variants - so we assume \ and . have been correctly escaped by +// DNSName::appendEscapedLabel already. +static const std::string bindEscape(const std::string& name) +{ + std::string ret; + std::array ebuf{}; + + for (char letter : name) { + switch (letter) { + case '$': + case '@': + case '"': + case ';': + case '(': + case ')': + snprintf(ebuf.data(), ebuf.size(), "\\%03u", static_cast(letter)); + ret += ebuf.data(); + break; + default: + ret += letter; + break; + } + } + return ret; +} + bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */) { if (d_transaction_id == UnknownDomainID) { throw DBException("Bind2Backend::feedRecord() called outside of transaction"); } string qname; if (d_transaction_qname.empty()) { - qname = rr.qname.toString(); + qname = bindEscape(rr.qname.toString()); } else if (rr.qname.isPartOf(d_transaction_qname)) { if (rr.qname == d_transaction_qname.operator const DNSName&()) { qname = "@"; } else { DNSName relName = rr.qname.makeRelative(d_transaction_qname); - qname = relName.toStringNoDot(); + qname = bindEscape(relName.toStringNoDot()); } } else { throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'"); }